pax_global_header00006660000000000000000000000064147375662230014531gustar00rootroot0000000000000052 comment=952fb93373a6f9f9e187bf9bc35c41a9fc25efa6 nfs-ganesha-6.5/000077500000000000000000000000001473756622300135755ustar00rootroot00000000000000nfs-ganesha-6.5/.git-blame-ignore-revs000066400000000000000000000001731473756622300176760ustar00rootroot00000000000000c0bde563d863c1865228ce185abfc2cd65fb7de5 90db2dbb660711b33bb061299f371464488a1777 5c7b586f816cc71ea33a6450eddb45eacdea9907 nfs-ganesha-6.5/.github/000077500000000000000000000000001473756622300151355ustar00rootroot00000000000000nfs-ganesha-6.5/.github/pull_request_template.md000066400000000000000000000014411473756622300220760ustar00rootroot00000000000000 nfs-ganesha-6.5/.gitignore000066400000000000000000000030201473756622300155600ustar00rootroot00000000000000# dirs oldtars/ linuxbox-ceph/ src/Docs/html/ src/Docs/latex/ src/autom4te.cache/ src/build-aux/ src/rpm/ src/pkg-deb/ src/docker/ src/.settings/ src/.cache/ .deps/ .libs/ CMakeFiles/ build/ .cache/ .vscode/ # files *.kdevelop* *.kdevses TAGS tags Makefile.in Makefile libtool configure depcomp missing ltmain.sh install-sh config.* aclocal.m4 *.o *.a libganeshaNFS.pc nfs-ganesha.spec cppcheck.* cscope.* ylwrap nfs-ganesha*.tar.gz nfs-ganesha*.tar.bz2 patch-for-HPSS-nfs-ganesha-* *.diff *.patch !debian/patches/*.patch core.* *cmake_install.cmake libfsal*.so* libganesha_nfsd.so* libgmonitoring.so* libganesha_trace.so* Doxyfile *~ *.swp .project .checkpatch.conf nohup.out src/scripts/systemd/nfs-ganesha-config.service src/FSAL/FSAL_VFS/vfs/lustre_main.c src/FSAL/FSAL_VFS/vfs/main.c src/FSAL/FSAL_VFS/vfs/dummy_lustre_main.c src/Protocols/NLM/sm_notify.ganesha src/sm_notify.ganesha src/ganesha.nfsd src/CMakeCache.txt src/CPackConfig.cmake src/CPackSourceConfig.cmake src/.cproject src/tools/multilock/ml_cephfs_client src/CMakeDoxyfile.in src/CMakeDoxygenDefaults.cmake src/SAL/libganesha_rados_recov.so src/config_parsing/libganesha_rados_urls.so src/ganesha-rados-grace src/install_manifest.txt src/sm_notify.ganesha src/tools/multilock/ml_cephfs_client src/tools/multilock/ml_console src/tools/multilock/ml_glusterfs_client src/tools/multilock/ml_posix_client .DS_Store src/compile_commands.json .cproject .settings/ .pydevproject **/.vscode gsh_lttng_generation_rules.cmake gsh_lttng_generation_file_properties.cmake gsh_generated_include nfs-ganesha-6.5/.gitmodules000066400000000000000000000003671473756622300157600ustar00rootroot00000000000000[submodule "src/libntirpc"] path = src/libntirpc url = https://github.com/nfs-ganesha/ntirpc.git [submodule "src/monitoring/prometheus-cpp-lite"] path = src/monitoring/prometheus-cpp-lite url = https://github.com/biaks/prometheus-cpp-lite.git nfs-ganesha-6.5/.gitreview000066400000000000000000000001321473756622300155770ustar00rootroot00000000000000[gerrit] host=review.gerrithub.io port=29418 project=ffilz/nfs-ganesha defaultbranch=next nfs-ganesha-6.5/.mailmap000066400000000000000000000053721473756622300152250ustar00rootroot00000000000000# .mailmap, see 'git shortlog --help' for details # # Listing of contributors that filed patches with different email addresses. # Format: # Allison Henderson Daniel Gryniewicz Daniel Gryniewicz Daniel Gryniewicz Dominique Martinet Frank S. Filz Frank S. Filz Frank S. Filz Jeremy Bongio Jeremy Bongio Jeremy Bongio Jim Lieb Manfred Haubrich Marc Eshel Matt Benjamin Matt Benjamin Matt Benjamin matt Meghana Madhusudhan Philippe DENIEL Philippe DENIEL Philippe DENIEL Philippe DENIEL Philippe DENIEL Philippe DENIEL Philippe DENIEL Philippe DENIEL Philippe DENIEL Philippe DENIEL Rong Zeng Rong Zeng Rong Zeng Rong Zeng Suhrud Patankar Thomas LEIBOVICI Thomas LEIBOVICI Thomas LEIBOVICI Thomas LEIBOVICI Thomas LEIBOVICI Thomas LEIBOVICI Venkateswararao Jujjuri (JV) William Allen Simpson William Allen Simpson nfs-ganesha-6.5/.travis.yml000066400000000000000000000055511473756622300157140ustar00rootroot00000000000000language: c compiler: - gcc - clang env: - BUILD_TYPE=Maintainer - BUILD_TYPE=Debug - BUILD_TYPE=Release branches: only: - next matrix: allow_failures: - compiler: clang env: BUILD_TYPE=Maintainer - compiler: clang env: BUILD_TYPE=Debug before_install: - git submodule update --init --recursive - sudo add-apt-repository -y ppa:gluster/glusterfs-3.6 - sudo apt-add-repository -y ppa:lttng/ppa - sudo apt-get update -q - sudo apt-get install -y libnfsidmap2 - sudo apt-get install -y libnfsidmap-dev - sudo apt-get install -y libkrb5-3 - sudo apt-get install -y libkrb5-dev - sudo apt-get install -y libk5crypto3 - sudo apt-get install -y libgssapi-krb5-2 - sudo apt-get install -y libgssglue1 - sudo apt-get install -y libdbus-1-3 - sudo apt-get install -y libattr1-dev - sudo apt-get install -y libacl1-dev - sudo apt-get install -y dbus - sudo apt-get install -y libdbus-1-dev - sudo apt-get install -y libcap-dev - sudo apt-get install -y libjemalloc-dev - sudo apt-get install -y glusterfs-common - sudo apt-get install -y uuid-dev - sudo apt-get install -y libblkid-dev - sudo apt-get install -y xfslibs-dev # - sudo apt-get install -y libcephfs-dev - sudo apt-get install -y libwbclient-dev - sudo apt-get install -y lttng-tools - sudo apt-get install -y liblttng-ust-dev - sudo apt-get install -y lttng-modules-dkms - sudo apt-get install -y pyqt4-dev-tools - sudo apt-get install -y rpm2cpio - sudo apt-get install -y libaio-dev - sudo apt-get install -y libibverbs-dev - sudo apt-get install -y librdmacm-dev install: - wget https://downloads.hpdd.intel.com/public/lustre/latest-maintenance-release/el6/server/RPMS/x86_64/lustre-2.5.3-2.6.32_431.23.3.el6_lustre.x86_64.x86_64.rpm -O /tmp/lustre.rpm - mkdir /tmp/lustre && pushd /tmp/lustre ; rpm2cpio /tmp/lustre.rpm | cpio -id ./usr/include/\* ./usr/lib64/liblustreapi.a ; popd - wget https://github.com/tfb-bull/mooshika/archive/0.7.1.tar.gz -O /tmp/mooshika.tar.gz - mkdir /tmp/mooshika && tar xf /tmp/mooshika.tar.gz -C /tmp && cd /tmp/mooshika-0.7.1 && sh autogen.sh && ./configure --prefix=/tmp/mooshika && make && make install && cd $TRAVIS_BUILD_DIR - if [[ ${CC} == 'gcc' ]]; then cd contrib/libzfswrap && aclocal -I m4 && libtoolize --force --copy && autoconf && autoheader && automake -a --add-missing -Wall && ./configure --prefix=/tmp/libzfswrap && make && make install && cd $TRAVIS_BUILD_DIR ; fi script: - mkdir ../build && cd ../build && cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_CONFIG=everything -DUSE_FSAL_PT=ON -DUSE_FSAL_CEPH=OFF -DUSE_ADMIN_TOOLS=ON -DUSE_LTTNG=ON -DUSE_TIRPC_IPV6=ON -DUSE_9P_RDMA=ON -D_USE_9P_RDMA=ON -DLUSTRE_PREFIX=/tmp/lustre/usr -DZFS_PREFIX=/tmp/libzfswrap -DMOOSHIKA_PREFIX=/tmp/mooshika ../nfs-ganesha/src/ && make #notifications: # email: # recipients: # - thomas.favre-bulle@bull.net # # on_success: always # on_failure: always nfs-ganesha-6.5/README.md000066400000000000000000000011431473756622300150530ustar00rootroot00000000000000[![Coverity Scan Build Status](https://scan.coverity.com/projects/2187/badge.svg)](https://scan.coverity.com/projects/2187) nfs-ganesha =========== NFS-Ganesha is an NFSv3,v4,v4.1 fileserver that runs in user mode on most UNIX/Linux systems. It also supports the 9p.2000L protocol. For more information, consult the [project wiki](https://github.com/nfs-ganesha/nfs-ganesha/wiki). # CONTRIBUTING Code contributions to Ganesha are managed by submission to gerrithub for review. We do not merge from github pull requests. See src/CONTRIBUTING_HOWTO.txt for details. # BUILDING See src/COMPILING_HOWTO.txt nfs-ganesha-6.5/coverity/000077500000000000000000000000001473756622300154415ustar00rootroot00000000000000nfs-ganesha-6.5/coverity/ganesha_model.c000066400000000000000000000001211473756622300203650ustar00rootroot00000000000000/* coverity [+free] */ int dlclose(void *handle) { __coverity_free__(handle); } nfs-ganesha-6.5/jenkins/000077500000000000000000000000001473756622300152365ustar00rootroot00000000000000nfs-ganesha-6.5/jenkins/ganesha.test.conf000066400000000000000000000042521473756622300204740ustar00rootroot00000000000000################################################### # Export entries ################################################### EXPORT { # Export Id (mandatory) Export_Id = 77 ; # Exported path (mandatory) Path = "/tmp" ; Root_Access = "*" ; Access = "*"; # Pseudo path for NFSv4 export (mandatory) Pseudo = "/tmp"; SecType = "sys"; # The uid for root when its host doesn't have a root_access. (default: -2) Anonymous_root_uid = -2 ; NFS_Protocols = "3,4" ; SecType = "sys"; # Maximum size for a read operation. MaxRead = 32768; # Maximum size for a write operation. MaxWrite = 32768; # Preferred size for a read operation. PrefRead = 32768; # Preferred size for a write operation. PrefWrite = 32768; # Preferred size for a readdir operation. PrefReaddir = 32768; # Filesystem ID (default 666.666) # This sets the filesystem id for the entries of this export. Filesystem_id = 192.168 ; # Should the client to this export entry come from a privileged port ? PrivilegedPort = FALSE ; # Export entry "tag" name # Can be used as an alternative way of addressing the # export entry at mount time ( alternate to the 'Path') Tag = "vfs" ; FSAL = "VFS" ; } FSAL { VFS { FSAL_Shared_Library = /tmp/libfsalvfs.so.4.2.0 ; } } FileSystem { Umask = 0000 ; Link_support = TRUE; # hardlink support Symlink_support = TRUE; # symlinks support CanSetTime = TRUE; # Is it possible to change file times } NFS_Core_Param { # Number of worker threads to be used Nb_Worker = 60 ; # NFS Port to be used # Default value is 2049 NFS_Port = 2049 ; # Mount port to be used # Default is 0 (let the system use an available ephemeral port) #MNT_Port = 0 ; # NFS RPC Program number # Default value is 100003 #NFS_Program = 100003 ; # Mount protocol RPC Program Number # Default value is 100005 #MNT_Program = 100005 ; NFS_Protocols = "3,4" ; # Size to be used for the core dump file (if the daemon crashes) ##Core_Dump_Size = 0 ; } _9P { _9P_TCP_Port = 564 ; _9P_RDMA_Port = 5640 ; DebugLevel = NIV_DEBUG ; # } nfs-ganesha-6.5/jenkins/sigmund_as_root.rc000066400000000000000000000013541473756622300207630ustar00rootroot00000000000000# Configuration file for the run_test.sh framework # # Use this template to build your own test_variables.rc # ##### Repo to get to build test GIT_PYNFS_URL=git://git.linux-nfs.org/projects/bfields/pynfs.git ##### Root of test ##### TEST_DIR=/mnt/sigmund BUILD_TEST_DIR=/tmp/sigmund ##### Variables to be used by module allfs ##### # Path to cthon04 test's suite CTHON04_DIR=/opt/cthon04 GIT_CLONE_URL=/opt/GANESHA/.git # Non-root user that will run part of the test TEST_USER=root # Primary group for the TEST_USER GROUP1=adm # One of the alternate group for TEST_USER GROUP2=sys ##### Variables to be used by module nfsv41 ##### # path to pynfs repo PYNFS_DIR=/opt/pynfs # Remote URL to be used by PYNFS PYNFS_URL="aury62:/vfs/pynfs40" nfs-ganesha-6.5/src/000077500000000000000000000000001473756622300143645ustar00rootroot00000000000000nfs-ganesha-6.5/src/.clang-format000066400000000000000000000532531473756622300167470ustar00rootroot00000000000000# 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 # Taken from: # git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ tools/ \ # | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \ # | LC_ALL=C sort -u ForEachMacros: - '__ata_qc_for_each' - '__bio_for_each_bvec' - '__bio_for_each_segment' - '__evlist__for_each_entry' - '__evlist__for_each_entry_continue' - '__evlist__for_each_entry_from' - '__evlist__for_each_entry_reverse' - '__evlist__for_each_entry_safe' - '__for_each_mem_range' - '__for_each_mem_range_rev' - '__for_each_thread' - '__hlist_for_each_rcu' - '__map__for_each_symbol_by_name' - '__pci_bus_for_each_res0' - '__pci_bus_for_each_res1' - '__pci_dev_for_each_res0' - '__pci_dev_for_each_res1' - '__perf_evlist__for_each_entry' - '__perf_evlist__for_each_entry_reverse' - '__perf_evlist__for_each_entry_safe' - '__rq_for_each_bio' - '__shost_for_each_device' - '__sym_for_each' - 'apei_estatus_for_each_section' - 'ata_for_each_dev' - 'ata_for_each_link' - 'ata_qc_for_each' - 'ata_qc_for_each_raw' - 'ata_qc_for_each_with_internal' - 'ax25_for_each' - 'ax25_uid_for_each' - 'bio_for_each_bvec' - 'bio_for_each_bvec_all' - 'bio_for_each_folio_all' - 'bio_for_each_integrity_vec' - 'bio_for_each_segment' - 'bio_for_each_segment_all' - 'bio_list_for_each' - 'bip_for_each_vec' - 'bond_for_each_slave' - 'bond_for_each_slave_rcu' - 'bpf_for_each' - 'bpf_for_each_reg_in_vstate' - 'bpf_for_each_reg_in_vstate_mask' - 'bpf_for_each_spilled_reg' - 'bpf_object__for_each_map' - 'bpf_object__for_each_program' - 'btree_for_each_safe128' - 'btree_for_each_safe32' - 'btree_for_each_safe64' - 'btree_for_each_safel' - 'card_for_each_dev' - 'cgroup_taskset_for_each' - 'cgroup_taskset_for_each_leader' - 'cpu_aggr_map__for_each_idx' - 'cpufreq_for_each_efficient_entry_idx' - 'cpufreq_for_each_entry' - 'cpufreq_for_each_entry_idx' - 'cpufreq_for_each_valid_entry' - 'cpufreq_for_each_valid_entry_idx' - 'css_for_each_child' - 'css_for_each_descendant_post' - 'css_for_each_descendant_pre' - 'damon_for_each_region' - 'damon_for_each_region_from' - 'damon_for_each_region_safe' - 'damon_for_each_scheme' - 'damon_for_each_scheme_safe' - 'damon_for_each_target' - 'damon_for_each_target_safe' - 'damos_for_each_filter' - 'damos_for_each_filter_safe' - 'data__for_each_file' - 'data__for_each_file_new' - 'data__for_each_file_start' - 'device_for_each_child_node' - 'displayid_iter_for_each' - 'dma_fence_array_for_each' - 'dma_fence_chain_for_each' - 'dma_fence_unwrap_for_each' - 'dma_resv_for_each_fence' - 'dma_resv_for_each_fence_unlocked' - 'do_for_each_ftrace_op' - 'drm_atomic_crtc_for_each_plane' - 'drm_atomic_crtc_state_for_each_plane' - 'drm_atomic_crtc_state_for_each_plane_state' - 'drm_atomic_for_each_plane_damage' - 'drm_client_for_each_connector_iter' - 'drm_client_for_each_modeset' - 'drm_connector_for_each_possible_encoder' - 'drm_exec_for_each_locked_object' - 'drm_exec_for_each_locked_object_reverse' - 'drm_for_each_bridge_in_chain' - 'drm_for_each_connector_iter' - 'drm_for_each_crtc' - 'drm_for_each_crtc_reverse' - 'drm_for_each_encoder' - 'drm_for_each_encoder_mask' - 'drm_for_each_fb' - 'drm_for_each_legacy_plane' - 'drm_for_each_plane' - 'drm_for_each_plane_mask' - 'drm_for_each_privobj' - 'drm_gem_for_each_gpuva' - 'drm_gem_for_each_gpuva_safe' - 'drm_gpuva_for_each_op' - 'drm_gpuva_for_each_op_from_reverse' - 'drm_gpuva_for_each_op_safe' - 'drm_gpuvm_for_each_va' - 'drm_gpuvm_for_each_va_range' - 'drm_gpuvm_for_each_va_range_safe' - 'drm_gpuvm_for_each_va_safe' - 'drm_mm_for_each_hole' - 'drm_mm_for_each_node' - 'drm_mm_for_each_node_in_range' - 'drm_mm_for_each_node_safe' - 'dsa_switch_for_each_available_port' - 'dsa_switch_for_each_cpu_port' - 'dsa_switch_for_each_cpu_port_continue_reverse' - 'dsa_switch_for_each_port' - 'dsa_switch_for_each_port_continue_reverse' - 'dsa_switch_for_each_port_safe' - 'dsa_switch_for_each_user_port' - 'dsa_tree_for_each_cpu_port' - 'dsa_tree_for_each_user_port' - 'dsa_tree_for_each_user_port_continue_reverse' - 'dso__for_each_symbol' - 'dsos__for_each_with_build_id' - 'elf_hash_for_each_possible' - 'elf_symtab__for_each_symbol' - 'evlist__for_each_cpu' - 'evlist__for_each_entry' - 'evlist__for_each_entry_continue' - 'evlist__for_each_entry_from' - 'evlist__for_each_entry_reverse' - 'evlist__for_each_entry_safe' - 'flow_action_for_each' - 'for_each_acpi_consumer_dev' - 'for_each_acpi_dev_match' - 'for_each_active_dev_scope' - 'for_each_active_drhd_unit' - 'for_each_active_iommu' - 'for_each_active_route' - 'for_each_aggr_pgid' - 'for_each_and_bit' - 'for_each_andnot_bit' - 'for_each_available_child_of_node' - 'for_each_bench' - 'for_each_bio' - 'for_each_board_func_rsrc' - 'for_each_btf_ext_rec' - 'for_each_btf_ext_sec' - 'for_each_bvec' - 'for_each_card_auxs' - 'for_each_card_auxs_safe' - 'for_each_card_components' - 'for_each_card_dapms' - 'for_each_card_pre_auxs' - 'for_each_card_prelinks' - 'for_each_card_rtds' - 'for_each_card_rtds_safe' - 'for_each_card_widgets' - 'for_each_card_widgets_safe' - 'for_each_cgroup_storage_type' - 'for_each_child_of_node' - 'for_each_clear_bit' - 'for_each_clear_bit_from' - 'for_each_clear_bitrange' - 'for_each_clear_bitrange_from' - 'for_each_cmd' - 'for_each_cmsghdr' - 'for_each_collection' - 'for_each_comp_order' - 'for_each_compatible_node' - 'for_each_component_dais' - 'for_each_component_dais_safe' - 'for_each_conduit' - 'for_each_console' - 'for_each_console_srcu' - 'for_each_cpu' - 'for_each_cpu_and' - 'for_each_cpu_andnot' - 'for_each_cpu_or' - 'for_each_cpu_wrap' - 'for_each_dapm_widgets' - 'for_each_dedup_cand' - 'for_each_dev_addr' - 'for_each_dev_scope' - 'for_each_dma_cap_mask' - 'for_each_dpcm_be' - 'for_each_dpcm_be_rollback' - 'for_each_dpcm_be_safe' - 'for_each_dpcm_fe' - 'for_each_drhd_unit' - 'for_each_dss_dev' - 'for_each_efi_memory_desc' - 'for_each_efi_memory_desc_in_map' - 'for_each_element' - 'for_each_element_extid' - 'for_each_element_id' - 'for_each_endpoint_of_node' - 'for_each_event' - 'for_each_event_tps' - 'for_each_evictable_lru' - 'for_each_fib6_node_rt_rcu' - 'for_each_fib6_walker_rt' - 'for_each_free_mem_pfn_range_in_zone' - 'for_each_free_mem_pfn_range_in_zone_from' - 'for_each_free_mem_range' - 'for_each_free_mem_range_reverse' - 'for_each_func_rsrc' - 'for_each_gpiochip_node' - 'for_each_group_evsel' - 'for_each_group_evsel_head' - 'for_each_group_member' - 'for_each_group_member_head' - 'for_each_hstate' - 'for_each_if' - 'for_each_inject_fn' - 'for_each_insn' - 'for_each_insn_prefix' - 'for_each_intid' - 'for_each_iommu' - 'for_each_ip_tunnel_rcu' - 'for_each_irq_nr' - 'for_each_lang' - 'for_each_link_codecs' - 'for_each_link_cpus' - 'for_each_link_platforms' - 'for_each_lru' - 'for_each_matching_node' - 'for_each_matching_node_and_match' - 'for_each_media_entity_data_link' - 'for_each_mem_pfn_range' - 'for_each_mem_range' - 'for_each_mem_range_rev' - 'for_each_mem_region' - 'for_each_member' - 'for_each_memory' - 'for_each_migratetype_order' - 'for_each_missing_reg' - 'for_each_mle_subelement' - 'for_each_mod_mem_type' - 'for_each_net' - 'for_each_net_continue_reverse' - 'for_each_net_rcu' - 'for_each_netdev' - 'for_each_netdev_continue' - 'for_each_netdev_continue_rcu' - 'for_each_netdev_continue_reverse' - 'for_each_netdev_dump' - 'for_each_netdev_feature' - 'for_each_netdev_in_bond_rcu' - 'for_each_netdev_rcu' - 'for_each_netdev_reverse' - 'for_each_netdev_safe' - 'for_each_new_connector_in_state' - 'for_each_new_crtc_in_state' - 'for_each_new_mst_mgr_in_state' - 'for_each_new_plane_in_state' - 'for_each_new_plane_in_state_reverse' - 'for_each_new_private_obj_in_state' - 'for_each_new_reg' - 'for_each_node' - 'for_each_node_by_name' - 'for_each_node_by_type' - 'for_each_node_mask' - 'for_each_node_state' - 'for_each_node_with_cpus' - 'for_each_node_with_property' - 'for_each_nonreserved_multicast_dest_pgid' - 'for_each_numa_hop_mask' - 'for_each_of_allnodes' - 'for_each_of_allnodes_from' - 'for_each_of_cpu_node' - 'for_each_of_pci_range' - 'for_each_old_connector_in_state' - 'for_each_old_crtc_in_state' - 'for_each_old_mst_mgr_in_state' - 'for_each_old_plane_in_state' - 'for_each_old_private_obj_in_state' - 'for_each_oldnew_connector_in_state' - 'for_each_oldnew_crtc_in_state' - 'for_each_oldnew_mst_mgr_in_state' - 'for_each_oldnew_plane_in_state' - 'for_each_oldnew_plane_in_state_reverse' - 'for_each_oldnew_private_obj_in_state' - 'for_each_online_cpu' - 'for_each_online_node' - 'for_each_online_pgdat' - 'for_each_or_bit' - 'for_each_path' - 'for_each_pci_bridge' - 'for_each_pci_dev' - 'for_each_pcm_streams' - 'for_each_physmem_range' - 'for_each_populated_zone' - 'for_each_possible_cpu' - 'for_each_present_blessed_reg' - 'for_each_present_cpu' - 'for_each_prime_number' - 'for_each_prime_number_from' - 'for_each_probe_cache_entry' - 'for_each_process' - 'for_each_process_thread' - 'for_each_prop_codec_conf' - 'for_each_prop_dai_codec' - 'for_each_prop_dai_cpu' - 'for_each_prop_dlc_codecs' - 'for_each_prop_dlc_cpus' - 'for_each_prop_dlc_platforms' - 'for_each_property_of_node' - 'for_each_reg' - 'for_each_reg_filtered' - 'for_each_reloc' - 'for_each_reloc_from' - 'for_each_requested_gpio' - 'for_each_requested_gpio_in_range' - 'for_each_reserved_mem_range' - 'for_each_reserved_mem_region' - 'for_each_rtd_codec_dais' - 'for_each_rtd_components' - 'for_each_rtd_cpu_dais' - 'for_each_rtd_dais' - 'for_each_sband_iftype_data' - 'for_each_script' - 'for_each_sec' - 'for_each_set_bit' - 'for_each_set_bit_from' - 'for_each_set_bit_wrap' - 'for_each_set_bitrange' - 'for_each_set_bitrange_from' - 'for_each_set_clump8' - 'for_each_sg' - 'for_each_sg_dma_page' - 'for_each_sg_page' - 'for_each_sgtable_dma_page' - 'for_each_sgtable_dma_sg' - 'for_each_sgtable_page' - 'for_each_sgtable_sg' - 'for_each_sibling_event' - 'for_each_sta_active_link' - 'for_each_subelement' - 'for_each_subelement_extid' - 'for_each_subelement_id' - 'for_each_sublist' - 'for_each_subsystem' - 'for_each_supported_activate_fn' - 'for_each_supported_inject_fn' - 'for_each_sym' - 'for_each_test' - 'for_each_thread' - 'for_each_token' - 'for_each_unicast_dest_pgid' - 'for_each_valid_link' - 'for_each_vif_active_link' - 'for_each_vma' - 'for_each_vma_range' - 'for_each_vsi' - 'for_each_wakeup_source' - 'for_each_zone' - 'for_each_zone_zonelist' - 'for_each_zone_zonelist_nodemask' - 'func_for_each_insn' - 'fwnode_for_each_available_child_node' - 'fwnode_for_each_child_node' - 'fwnode_for_each_parent_node' - 'fwnode_graph_for_each_endpoint' - 'gadget_for_each_ep' - 'genradix_for_each' - 'genradix_for_each_from' - 'genradix_for_each_reverse' - 'hash_for_each' - 'hash_for_each_possible' - 'hash_for_each_possible_rcu' - 'hash_for_each_possible_rcu_notrace' - 'hash_for_each_possible_safe' - 'hash_for_each_rcu' - 'hash_for_each_safe' - 'hashmap__for_each_entry' - 'hashmap__for_each_entry_safe' - 'hashmap__for_each_key_entry' - 'hashmap__for_each_key_entry_safe' - 'hctx_for_each_ctx' - 'hists__for_each_format' - 'hists__for_each_sort_list' - 'hlist_bl_for_each_entry' - 'hlist_bl_for_each_entry_rcu' - 'hlist_bl_for_each_entry_safe' - 'hlist_for_each' - 'hlist_for_each_entry' - 'hlist_for_each_entry_continue' - 'hlist_for_each_entry_continue_rcu' - 'hlist_for_each_entry_continue_rcu_bh' - 'hlist_for_each_entry_from' - 'hlist_for_each_entry_from_rcu' - 'hlist_for_each_entry_rcu' - 'hlist_for_each_entry_rcu_bh' - 'hlist_for_each_entry_rcu_notrace' - 'hlist_for_each_entry_safe' - 'hlist_for_each_entry_srcu' - 'hlist_for_each_safe' - 'hlist_nulls_for_each_entry' - 'hlist_nulls_for_each_entry_from' - 'hlist_nulls_for_each_entry_rcu' - 'hlist_nulls_for_each_entry_safe' - 'i3c_bus_for_each_i2cdev' - 'i3c_bus_for_each_i3cdev' - 'idr_for_each_entry' - 'idr_for_each_entry_continue' - 'idr_for_each_entry_continue_ul' - 'idr_for_each_entry_ul' - 'in_dev_for_each_ifa_rcu' - 'in_dev_for_each_ifa_rtnl' - 'inet_bind_bucket_for_each' - 'interval_tree_for_each_span' - 'intlist__for_each_entry' - 'intlist__for_each_entry_safe' - 'kcore_copy__for_each_phdr' - 'key_for_each' - 'key_for_each_safe' - 'klp_for_each_func' - 'klp_for_each_func_safe' - 'klp_for_each_func_static' - 'klp_for_each_object' - 'klp_for_each_object_safe' - 'klp_for_each_object_static' - 'kunit_suite_for_each_test_case' - 'kvm_for_each_memslot' - 'kvm_for_each_memslot_in_gfn_range' - 'kvm_for_each_vcpu' - 'libbpf_nla_for_each_attr' - 'list_for_each' - 'list_for_each_codec' - 'list_for_each_codec_safe' - 'list_for_each_continue' - 'list_for_each_entry' - 'list_for_each_entry_continue' - 'list_for_each_entry_continue_rcu' - 'list_for_each_entry_continue_reverse' - 'list_for_each_entry_from' - 'list_for_each_entry_from_rcu' - 'list_for_each_entry_from_reverse' - 'list_for_each_entry_lockless' - 'list_for_each_entry_rcu' - 'list_for_each_entry_reverse' - 'list_for_each_entry_safe' - 'list_for_each_entry_safe_continue' - 'list_for_each_entry_safe_from' - 'list_for_each_entry_safe_reverse' - 'list_for_each_entry_srcu' - 'list_for_each_from' - 'list_for_each_prev' - 'list_for_each_prev_safe' - 'list_for_each_rcu' - 'list_for_each_reverse' - 'list_for_each_safe' - 'llist_for_each' - 'llist_for_each_entry' - 'llist_for_each_entry_safe' - 'llist_for_each_safe' - 'lwq_for_each_safe' - 'map__for_each_symbol' - 'map__for_each_symbol_by_name' - 'maps__for_each_entry' - 'maps__for_each_entry_safe' - 'mas_for_each' - 'mci_for_each_dimm' - 'media_device_for_each_entity' - 'media_device_for_each_intf' - 'media_device_for_each_link' - 'media_device_for_each_pad' - 'media_entity_for_each_pad' - 'media_pipeline_for_each_entity' - 'media_pipeline_for_each_pad' - 'mlx5_lag_for_each_peer_mdev' - 'msi_domain_for_each_desc' - 'msi_for_each_desc' - 'mt_for_each' - 'nanddev_io_for_each_page' - 'netdev_for_each_lower_dev' - 'netdev_for_each_lower_private' - 'netdev_for_each_lower_private_rcu' - 'netdev_for_each_mc_addr' - 'netdev_for_each_synced_mc_addr' - 'netdev_for_each_synced_uc_addr' - 'netdev_for_each_uc_addr' - 'netdev_for_each_upper_dev_rcu' - 'netdev_hw_addr_list_for_each' - 'nft_rule_for_each_expr' - 'nla_for_each_attr' - 'nla_for_each_nested' - 'nlmsg_for_each_attr' - 'nlmsg_for_each_msg' - 'nr_neigh_for_each' - 'nr_neigh_for_each_safe' - 'nr_node_for_each' - 'nr_node_for_each_safe' - 'of_for_each_phandle' - 'of_property_for_each_string' - 'of_property_for_each_u32' - 'pci_bus_for_each_resource' - 'pci_dev_for_each_resource' - 'pcl_for_each_chunk' - 'pcl_for_each_segment' - 'pcm_for_each_format' - 'perf_config_items__for_each_entry' - 'perf_config_sections__for_each_entry' - 'perf_config_set__for_each_entry' - 'perf_cpu_map__for_each_cpu' - 'perf_cpu_map__for_each_idx' - 'perf_evlist__for_each_entry' - 'perf_evlist__for_each_entry_reverse' - 'perf_evlist__for_each_entry_safe' - 'perf_evlist__for_each_evsel' - 'perf_evlist__for_each_mmap' - 'perf_hpp_list__for_each_format' - 'perf_hpp_list__for_each_format_safe' - 'perf_hpp_list__for_each_sort_list' - 'perf_hpp_list__for_each_sort_list_safe' - 'perf_tool_event__for_each_event' - 'plist_for_each' - 'plist_for_each_continue' - 'plist_for_each_entry' - 'plist_for_each_entry_continue' - 'plist_for_each_entry_safe' - 'plist_for_each_safe' - 'pnp_for_each_card' - 'pnp_for_each_dev' - 'protocol_for_each_card' - 'protocol_for_each_dev' - 'queue_for_each_hw_ctx' - 'radix_tree_for_each_slot' - 'radix_tree_for_each_tagged' - 'rb_for_each' - 'rbtree_postorder_for_each_entry_safe' - 'rdma_for_each_block' - 'rdma_for_each_port' - 'rdma_umem_for_each_dma_block' - 'resort_rb__for_each_entry' - 'resource_list_for_each_entry' - 'resource_list_for_each_entry_safe' - 'rhl_for_each_entry_rcu' - 'rhl_for_each_rcu' - 'rht_for_each' - 'rht_for_each_entry' - 'rht_for_each_entry_from' - 'rht_for_each_entry_rcu' - 'rht_for_each_entry_rcu_from' - 'rht_for_each_entry_safe' - 'rht_for_each_from' - 'rht_for_each_rcu' - 'rht_for_each_rcu_from' - 'rq_for_each_bvec' - 'rq_for_each_segment' - 'rq_list_for_each' - 'rq_list_for_each_safe' - 'sample_read_group__for_each' - 'scsi_for_each_prot_sg' - 'scsi_for_each_sg' - 'sctp_for_each_hentry' - 'sctp_skb_for_each' - 'sec_for_each_insn' - 'sec_for_each_insn_continue' - 'sec_for_each_insn_from' - 'sec_for_each_sym' - 'shdma_for_each_chan' - 'shost_for_each_device' - 'sk_for_each' - 'sk_for_each_bound' - 'sk_for_each_bound_bhash2' - 'sk_for_each_entry_offset_rcu' - 'sk_for_each_from' - 'sk_for_each_rcu' - 'sk_for_each_safe' - 'sk_nulls_for_each' - 'sk_nulls_for_each_from' - 'sk_nulls_for_each_rcu' - 'snd_array_for_each' - 'snd_pcm_group_for_each_entry' - 'snd_soc_dapm_widget_for_each_path' - 'snd_soc_dapm_widget_for_each_path_safe' - 'snd_soc_dapm_widget_for_each_sink_path' - 'snd_soc_dapm_widget_for_each_source_path' - 'strlist__for_each_entry' - 'strlist__for_each_entry_safe' - 'sym_for_each_insn' - 'sym_for_each_insn_continue_reverse' - 'symbols__for_each_entry' - 'tb_property_for_each' - 'tcf_act_for_each_action' - 'tcf_exts_for_each_action' - 'ttm_resource_manager_for_each_res' - 'twsk_for_each_bound_bhash2' - 'udp_portaddr_for_each_entry' - 'udp_portaddr_for_each_entry_rcu' - 'usb_hub_for_each_child' - 'v4l2_device_for_each_subdev' - 'v4l2_m2m_for_each_dst_buf' - 'v4l2_m2m_for_each_dst_buf_safe' - 'v4l2_m2m_for_each_src_buf' - 'v4l2_m2m_for_each_src_buf_safe' - 'virtio_device_for_each_vq' - 'while_for_each_ftrace_op' - 'xa_for_each' - 'xa_for_each_marked' - 'xa_for_each_range' - 'xa_for_each_start' - 'xas_for_each' - 'xas_for_each_conflict' - 'xas_for_each_marked' - 'xbc_array_for_each_value' - 'xbc_for_each_key_value' - 'xbc_node_for_each_array_value' - 'xbc_node_for_each_child' - 'xbc_node_for_each_key_value' - 'xbc_node_for_each_subkey' - 'zorro_for_each_dev' 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 ... nfs-ganesha-6.5/src/.clang-format-ignore000066400000000000000000000000361473756622300202170ustar00rootroot00000000000000FSAL/FSAL_GPFS/include/gpfs.h nfs-ganesha-6.5/src/CMakeLists.txt000066400000000000000000001734101473756622300171320ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # NFS Ganesha Cmake # Current version as of Fedora 16. Not tested with earlier. cmake_minimum_required(VERSION 2.6.3) message( STATUS "cmake version ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" ) if( "${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" VERSION_GREATER "2.6" ) if(COMMAND cmake_policy) cmake_policy(SET CMP0017 NEW) endif(COMMAND cmake_policy) endif( "${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" VERSION_GREATER "2.6" ) # Find packages and libs we need for building include(CheckIncludeFiles) include(CheckLibraryExists) include(CheckCSourceCompiles) include(TestBigEndian) # Set GANESHA_TOP_CMAKE_DIR to this CMakeLists.txt path set(GANESHA_TOP_CMAKE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${GANESHA_TOP_CMAKE_DIR}/cmake/modules/") project(nfs-ganesha C CXX) # Project versioning set(GANESHA_MAJOR_VERSION 6) # Minor version is blank for development. On release, it becomes ".0". On a # stable maintenance branch, this becomes ".N" where N is monotonically # increasing starting at 1. Remember to include the "." !! set(GANESHA_MINOR_VERSION .5) IF(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") set(GANESHA_BUILD_RELEASE 1) ELSE() set(GANESHA_BUILD_RELEASE 0) ENDIF(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") # needs to come after project() IF(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT OR CMAKE_INSTALL_PREFIX STREQUAL "/usr") SET(CMAKE_INSTALL_PREFIX "/usr" CACHE PATH "Install prefix for common files" FORCE) message(STATUS "override default CMAKE_INSTALL_PREFIX = ${CMAKE_INSTALL_PREFIX}") SET(SYSCONFDIR "/etc" CACHE PATH "Install prefix for common files") SET(SYSSTATEDIR "/var" CACHE PATH "Install prefix for common files") ELSE() message(STATUS "was set CMAKE_INSTALL_PREFIX = ${CMAKE_INSTALL_PREFIX}") SET(SYSCONFDIR "${CMAKE_INSTALL_PREFIX}/etc" CACHE PATH "Install prefix for common files") SET(SYSSTATEDIR "${CMAKE_INSTALL_PREFIX}/var" CACHE PATH "Install prefix for common files") ENDIF(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT OR CMAKE_INSTALL_PREFIX STREQUAL "/usr") SET(RUNTIMEDIR "${SYSSTATEDIR}/run/ganesha" CACHE PATH "Runtime directory (for things like pid file, not touched by install)") # Extra version is for naming development/RC. It is blank in stable branches # so it can be available to end-users to name local variants/versions # If used, it is always of the form "-whateveryouwant" set(GANESHA_EXTRA_VERSION ) set(GANESHA_VERSION ${GANESHA_MAJOR_VERSION}${GANESHA_MINOR_VERSION}${GANESHA_EXTRA_VERSION}) set(GANESHA_BASE_VERSION ${GANESHA_MAJOR_VERSION}${GANESHA_MINOR_VERSION}) set(VERSION_COMMENT "GANESHA file server is 64 bits compliant and supports NFS v3,4.0,4.1 (pNFS) and 9P" ) # find out which platform we are building on if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") set(LINUX ON) set(UNIX ON) # Now detects the Linux's distro set(DISTRO "UNKNOWN") set(LIBEXECDIR "/${CMAKE_INSTALL_PREFIX}/libexec") EXECUTE_PROCESS( COMMAND awk -F= "/^NAME=/ { print $2 }" /etc/os-release OUTPUT_VARIABLE SYS_RELEASE ERROR_QUIET ) EXECUTE_PROCESS( COMMAND awk -F= "/^ID_LIKE=/ { print $2 }" /etc/os-release OUTPUT_VARIABLE ID_LIKE ) # Red Hat Enterprise Linux versions before 7.0 will be detected as UNKNOWN if( ${SYS_RELEASE} MATCHES "Red Hat" ) message( STATUS "Detected a Linux Red Hat machine" ) set(DISTRO "RED_HAT") elseif( ${SYS_RELEASE} MATCHES "Fedora" ) message( STATUS "Detected a Linux Fedora machine" ) set(DISTRO "FEDORA") elseif( ${SYS_RELEASE} MATCHES "SLES" ) message( STATUS "Detected a Linux SLES machine" ) set(DISTRO "SLES") set(LIBEXECDIR "/${CMAKE_INSTALL_PREFIX}/lib") elseif( ${SYS_RELEASE} MATCHES "openSUSE Leap" ) message( STATUS "Detected a Linux openSUSE Leap machine" ) set(DISTRO "SLES") set(LIBEXECDIR "/${CMAKE_INSTALL_PREFIX}/lib") elseif( ${SYS_RELEASE} MATCHES "openSUSE Tumbleweed" ) message( STATUS "Detected a Linux openSUSE Tumbleweed machine" ) set(DISTRO "SLES") set(LIBEXECDIR "/${CMAKE_INSTALL_PREFIX}/lib") elseif( (${SYS_RELEASE} MATCHES "Debian GNU/Linux") OR (${SYS_RELEASE} MATCHES "Ubuntu") ) message( STATUS "Detected a Linux Debian base machine" ) set(DISTRO "DEBIAN") set(LIBEXECDIR "/${CMAKE_INSTALL_PREFIX}/lib") else( ${SYS_RELEASE} MATCHES "Red Hat" ) if( ${ID_LIKE} MATCHES "suse" ) message( STATUS "Detected a SUSE Build server machine" ) set(DISTRO "SLES") set(LIBEXECDIR "/usr/lib") else( ${ID_LIKE} MATCHES "suse" ) message( STATUS "Detected an UNKNOWN Linux machine" ) set(DISTRO "UNKNOWN") endif( ${ID_LIKE} MATCHES "suse" ) endif( ${SYS_RELEASE} MATCHES "Red Hat" ) endif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") set(BSDBASED ON) set(FREEBSD ON) set(UNIX ON) # On FreeBSD libc doesn't directly provide libexecinfo, so we have to find it set(USE_EXECINFO ON) set(LIBEXECDIR "/${CMAKE_INSTALL_PREFIX}/libexec") endif(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") set(BSDBASED ON) set(DARWIN ON) set(UNIX ON) set(LIBEXECDIR "/${CMAKE_INSTALL_PREFIX}/libexec") endif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") set(WINDOWS ON) endif(${CMAKE_SYSTEM_NAME} MATCHES "Windows") if(DARWIN) # Apple's /usr/bin/ld doesn't use the same flags for specifying treatment of # undefined symbols, and its defaults are opposite of GNU's and LLVM's ld. set(LDFLAG_ALLOW_UNDEF "-Wl,-undefined,suppress") set(LDFLAG_DISALLOW_UNDEF "-Wl,-undefined,error") elseif(FREEBSD OR LINUX) # llvm-ld seems to be missing a flag to explicitly allow undefined, but this # is the default anyway for both llvm-ld and GNU ld. set(LDFLAG_ALLOW_UNDEF "") set(LDFLAG_DISALLOW_UNDEF "-Wl,--no-undefined") endif(DARWIN) # Identify the host we are building on EXECUTE_PROCESS( COMMAND hostname OUTPUT_VARIABLE BUILD_HOST_NAME OUTPUT_STRIP_TRAILING_WHITESPACE ) find_package(Toolchain REQUIRED) find_package(Sanitizers) # Add maintainer mode for (mainly) strict builds include(${GANESHA_TOP_CMAKE_DIR}/cmake/maintainer_mode.cmake) # For libraries that provide pkg-config files include(FindPkgConfig) # For our option system include(${GANESHA_TOP_CMAKE_DIR}/cmake/goption.cmake) goption(USE_RELATIVE_FILE_PATHS_IN_LOGS "Use relative file paths in log files" ON) gopt_test(USE_RELATIVE_FILE_PATHS_IN_LOGS) include(CheckCCompilerFlag) CHECK_C_COMPILER_FLAG("-fmacro-prefix-map=old=new" HAS_MACRO_PREFIX_MAP_FLAG) if(${HAS_MACRO_PREFIX_MAP_FLAG} AND USE_RELATIVE_FILE_PATHS_IN_LOGS) add_compile_options(-fmacro-prefix-map=${GANESHA_TOP_CMAKE_DIR}/=./) endif() # If we are in a git tree, then this CMakeLists.txt is in "src/" and go .git is in "src/.." IF( EXISTS ${GANESHA_TOP_CMAKE_DIR}/../.git/HEAD ) message( STATUS "Compilation from within a git repository. Using git rev-parse HEAD") EXECUTE_PROCESS( COMMAND git rev-parse HEAD WORKING_DIRECTORY ${GANESHA_TOP_CMAKE_DIR} OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET OUTPUT_VARIABLE _GIT_HEAD_COMMIT) EXECUTE_PROCESS( COMMAND git describe --long WORKING_DIRECTORY ${GANESHA_TOP_CMAKE_DIR} OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET OUTPUT_VARIABLE _GIT_DESCRIBE) ELSE( EXISTS ${GANESHA_TOP_CMAKE_DIR}/../.git/HEAD ) message( STATUS "Outside a git repository, use saved data" ) EXEC_PROGRAM(${GANESHA_TOP_CMAKE_DIR}/cmake/githead_from_path.sh ARGS ${GANESHA_TOP_CMAKE_DIR} OUTPUT_VARIABLE _GIT_HEAD_COMMIT) EXEC_PROGRAM(${GANESHA_TOP_CMAKE_DIR}/cmake/gitdesc_from_path.sh ARGS ${GANESHA_TOP_CMAKE_DIR} OUTPUT_VARIABLE _GIT_DESCRIBE) ENDIF( EXISTS ${GANESHA_TOP_CMAKE_DIR}/../.git/HEAD ) STRING(SUBSTRING ${_GIT_HEAD_COMMIT} 0 7 _GIT_HEAD_COMMIT_ABBREV ) if (BSDBASED) #default gcc doesn't like using -Wuninitialized without -O on FreeBSD set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2 -ggdb") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-optimize-sibling-calls") endif(BSDBASED) if (FREEBSD) set(PLATFORM "FREEBSD") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--export-dynamic") set(OS_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/include/os/freebsd") endif(FREEBSD) if (DARWIN) set(PLATFORM "DARWIN") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-export_dynamic") set(OS_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/include/os/darwin") endif(DARWIN) if (LINUX) set(PLATFORM "LINUX") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-strict-aliasing") set(OS_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/include/os/linux") endif(LINUX) if (MSVC) add_definitions(-D_CRT_SECURE_NO_WARNINGS) endif(MSVC) # Library path name get_property(USE_LIB64 GLOBAL PROPERTY FIND_LIBRARY_USE_LIB64_PATHS) if (USE_LIB64) set(LIB_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/lib64 CACHE PATH "Specify name of libdir inside install path") else (USE_LIB64) set(LIB_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/lib CACHE PATH "Specify name of libdir inside install path") endif (USE_LIB64) IF(FSAL_DESTINATION) set( FSAL_DESTINATION ${FSAL_DESTINATION} ) ELSE() set( FSAL_DESTINATION "${LIB_INSTALL_DIR}/ganesha") ENDIF() if (CMAKE_SYSTEM_PROCESSOR MATCHES "unknown") # uname -p is broken on this system. Try uname -m EXECUTE_PROCESS( COMMAND uname -m OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET OUTPUT_VARIABLE ARCH) else (CMAKE_SYSTEM_PROCESSOR MATCHES "unknown") set(ARCH ${CMAKE_SYSTEM_PROCESSOR}) endif (CMAKE_SYSTEM_PROCESSOR MATCHES "unknown") if (ARCH MATCHES "x86_64") # Nothing special to do for x86_64 elseif (ARCH MATCHES "i386") # Nothing special to do for i386 elseif (ARCH MATCHES "mips") set(SYSTEM_LIBRARIES "-latomic" ${SYSTEM_LIBRARIES}) else() message(WARNING "Unhandled architecture ${ARCH}") endif () # FSAL selection # FSALs which are enabled by default but could be disabled # during the build goption(USE_FSAL_PROXY_V4 "build PROXY_V4 FSAL shared library" ON) goption(USE_FSAL_PROXY_V3 "build PROXY_V3 FSAL shared library" ON) goption(USE_FSAL_VFS "build VFS FSAL shared library" ON) goption(USE_FSAL_LUSTRE "build LUSTRE FSAL shared library" ON) goption(USE_FSAL_LIZARDFS "build LIZARDFS FSAL shared library" ON) goption(USE_FSAL_KVSFS "build KVSFS FSAL shared library" ON) goption(USE_FSAL_CEPH "build CEPH FSAL shared library" ON) goption(USE_FSAL_GPFS "build GPFS FSAL" ON) goption(USE_FSAL_XFS "build XFS support in VFS FSAL" ON) goption(USE_FSAL_GLUSTER "build GLUSTER FSAL shared library" ON) goption(USE_FSAL_NULL "build NULL FSAL shared library" ON) goption(USE_FSAL_RGW "build RGW FSAL shared library" ON) goption(USE_FSAL_MEM "build Memory FSAL shared library" ON) goption(USE_FSAL_SAUNAFS "build SAUNAFS FSAL shared library" ON) # Internal capability required by some FSALs option(GSH_CAN_HOST_LOCAL_FS "Ganesha supports hosting local filesystems" ON) # Monitoring stack. goption(USE_MONITORING "Build monitoring stack" OFF) gopt_test(USE_MONITORING) if(USE_MONITORING) set(MONITORING_LIBRARIES gmonitoring ) endif(USE_MONITORING) # nTIRPC option(USE_SYSTEM_NTIRPC "Use the system nTIRPC, rather than the submodule" OFF) option (USE_GSS "enable RPCSEC_GSS support" ON) option(TIRPC_EPOLL "platform supports EPOLL or emulation" ON) # Build configure options goption(USE_DBUS "enable DBUS protocol support" ON) # Various DBUS enabled features option(USE_CB_SIMULATOR "enable callback simulator thread" OFF) goption(USE_NFSIDMAP "Use of libnfsidmap for name resolution" ON) option(ENABLE_ERROR_INJECTION "enable error injection" OFF) goption(ENABLE_VFS_POSIX_ACL "Enable NFSv4 to POSIX ACL mapping for VFS" OFF) option(ENABLE_VFS_DEBUG_ACL "Enable debug ACL store for VFS" OFF) option(ENABLE_RFC_ACL "Use all RFC ACL checks" OFF) option(USE_TOOL_MULTILOCK "build multilock tool" OFF) # Electric Fence (-lefence) link flag goption(USE_EFENCE "link with efence memory debug library" OFF) # These are -D_FOO options, why ??? should be flags?? option(_NO_TCP_REGISTER "disable registration of tcp services on portmapper" OFF) option(RPCBIND "enable registration with rpcbind" ON) option(DEBUG_SAL "enable debugging of SAL by keeping list of all locks, stateids, and state owners" OFF) option(_VALGRIND_MEMCHECK "Initialize buffers passed to GPFS ioctl that valgrind doesn't understand" OFF) option(ENABLE_LOCKTRACE "Enable lock trace" OFF) goption(PROXYV4_HANDLE_MAPPING "enable NFSv3 handle mapping for PROXY_V4 FSAL" OFF) option(DEBUG_MDCACHE "Add various asserts to mdcache" OFF) # Debug symbols (-g) build flag option(DEBUG_SYMS "include debug symbols to binaries (-g option)" OFF) # Add coverage information to build tree option(COVERAGE "add flag to generate coverage data at runtime" OFF) option(ENFORCE_GCC "enforce gcc as a the C compiler used for the project" OFF) # Define CPACK component (to deal with sub packages) set(CPACK_COMPONENTS_ALL daemon fsal headers tools) set(CPACK_COMPONENT_DAEMON_DISPLAY_NAME "NFS-Ganesha daemon") if (USE_SYSTEM_NTIRPC) # Don't include libntirpc in the tarball set(CPACK_SOURCE_IGNORE_FILES "libntirpc") else(USE_SYSTEM_NTIRPC) # Don't include libntirpc's spec file; this can confuse rpmbuild set(CPACK_SOURCE_IGNORE_FILES "libntirpc.spec$") endif(USE_SYSTEM_NTIRPC) # Include custom config and cpack module include(${GANESHA_TOP_CMAKE_DIR}/cmake/cpack_config.cmake) include(CPack) # MSPAC support -lwbclient link flag goption(_MSPAC_SUPPORT "enable mspac Winbind support" ON) # CUnit goption(USE_CUNIT "Use Cunit test framework" OFF) # GTest goption(USE_GTEST "Use Google Test test framework" OFF) gopt_test(USE_GTEST) if(USE_GTEST) find_package(LTTng ${USE_GTEST_REQUIRED}) find_package(Gperftools ${USE_GTEST_REQUIRED}) find_package(GTest ${USE_GTEST_REQUIRED}) find_package(Boost 1.34.0 ${USE_GTEST_REQUIRED} COMPONENTS filesystem program_options) if((NOT LTTNG_FOUND) OR (NOT GPERFTOOLS_FOUND) OR (NOT GTEST_FOUND)) message(WARNING "Couldn't find GTest dependencies. Disabling USE_GTEST") set(USE_GTEST OFF) endif((NOT LTTNG_FOUND) OR (NOT GPERFTOOLS_FOUND) OR (NOT GTEST_FOUND)) endif(USE_GTEST) # Enable NFS Over RDMA Support option(USE_NFS_RDMA "enable NFS/RDMA support" ON) # Enable 9P Support option(USE_9P "enable 9P support" ON) option(USE_9P_RDMA "enable 9P_RDMA support" OFF) # Enable NFSv3 Support option(USE_NFS3 "enable NFSv3 support" ON) if(USE_NFS3) # Enable NLM Support option(USE_NLM "enable NLM support" ON) # Enable NFSACL3 protocol extension support option(USE_NFSACL3 "enable NFSACLv3 support" ON) else(USE_NFS3) # Disable NLM Support set(USE_NLM OFF) # Disable NFSACL3 protocol extension support set(USE_NFSACL3 OFF) endif(USE_NFS3) # Enable RQUOTA support option(USE_RQUOTA "enable RQUOTA support" ON) # AF_VSOCK host support (NFS) option(USE_VSOCK "enable AF_VSOCK listener" OFF) if(USE_VSOCK) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DRPC_VSOCK") endif(USE_VSOCK) # This option will trigger "long distro name" aka name that contains git information option(DISTNAME_HAS_GIT_DATA "Distribution package's name carries git data" OFF ) # Build and package Python admin scripts for managing via DBus goption(USE_ADMIN_TOOLS "Package Admin scripts" OFF) # Build and package Python gui admin scripts for managing via DBus goption(USE_GUI_ADMIN_TOOLS "Package GUI Admin scripts" ON) # Enable LTTng tracing goption(USE_LTTNG "Enable LTTng tracing" OFF) # Build man page. goption(USE_MAN_PAGE "Build MAN page" OFF) # Enable Rados KV store for recovery goption(USE_RADOS_RECOV "Enable Rados KV Recovery" ON) # Enable RADOS URL config file sections goption(RADOS_URLS "Enable config file inclusion from RADOS objects" ON) # Enable CephFS POSIX ACL goption(CEPHFS_POSIX_ACL "Enable CephFS POSIX ACL" ON) # Enable NFSv4 and POSIX acls mapping option(USE_ACL_MAPPING "Build NFSv4 to POSIX ACL mapping" OFF) # # End build options # # Choose a shortcut build config IF(BUILD_CONFIG) INCLUDE( ${GANESHA_TOP_CMAKE_DIR}/cmake/build_configurations/${BUILD_CONFIG}.cmake) ENDIF() IF(DEBUG_SYMS) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g") ENDIF(DEBUG_SYMS) IF(COVERAGE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage") ENDIF(COVERAGE) # Set what's needed is GCC is enforced IF(ENFORCE_GCC) set(CMAKE_COMPILER_IS_GNUCXX TRUE) set(CMAKE_C_COMPILER gcc) ENDIF(ENFORCE_GCC) include(CheckSymbolExists) check_symbol_exists(__GLIBC__ features.h HAVE_GLIBC) if(HAVE_GLIBC) add_definitions(-D_GNU_SOURCE=1) endif(HAVE_GLIBC) IF(USE_FSAL_GLUSTER) IF(GLUSTER_PREFIX) set(GLUSTER_PREFIX ${GLUSTER_PREFIX} CACHE PATH "Path to Gluster installation") LIST(APPEND CMAKE_PREFIX_PATH "${GLUSTER_PREFIX}") LIST(APPEND CMAKE_LIBRARY_PATH "${GLUSTER_PREFIX}/lib") LIST(APPEND CMAKE_LIBRARY_PATH "${GLUSTER_PREFIX}/local/lib") LIST(APPEND CMAKE_LIBRARY_PATH "${GLUSTER_PREFIX}/local/lib64") LIST(APPEND CMAKE_REQUIRED_INCLUDES "${GLUSTER_PREFIX}/include") ELSE() set(GLUSTER_PREFIX "/usr" CACHE PATH "Path to Gluster installation") ENDIF() ENDIF() IF(KRB5_PREFIX) set(KRB5_PREFIX ${KRB5_PREFIX} CACHE PATH "Path to Krb5 installation") LIST(APPEND CMAKE_PREFIX_PATH "${KRB5_PREFIX}") LIST(APPEND CMAKE_LIBRARY_PATH "${KRB5_PREFIX}/lib") ENDIF() if(SAMBA4_PREFIX) set(SAMBA4_PREFIX ${SAMBA4_PREFIX} CACHE PATH "Path to Samba4 installation") LIST(APPEND CMAKE_PREFIX_PATH "${SAMBA4_PREFIX}") LIST(APPEND CMAKE_LIBRARY_PATH "${SAMBA4_PREFIX}/lib") endif() IF(MOOSHIKA_PREFIX) set(MOOSHIKA_PREFIX ${MOOSHIKA_PREFIX} CACHE PATH "Path to Mooshika installation") set(ENV{PKG_CONFIG_PATH} "${PKG_CONFIG_PATH}:${MOOSHIKA_PREFIX}/lib/pkgconfig") ENDIF() if(USE_NFS_RDMA OR USE_9P_RDMA) check_library_exists(rdmacm rdma_create_qp "" RDMACM_FOUND) check_library_exists(ibverbs ibv_alloc_pd "" IBVERBS_FOUND) if(RDMACM_FOUND AND IBVERBS_FOUND) find_package(RDMA REQUIRED) include_directories(${RDMA_INCLUDE_DIR}) set(SYSTEM_LIBRARIES ${SYSTEM_LIBRARIES} ${RDMA_LIBRARY}) else(RDMACM_FOUND AND IBVERBS_FOUND) set(USE_NFS_RDMA OFF) set(USE_9P_RDMA OFF) message(WARNING "Could not find RDMA dependencies(RDMACM=${RDMACM_FOUND} IBVERBS=${IBVERBS_FOUND}), disabling RDMA Support") endif(RDMACM_FOUND AND IBVERBS_FOUND) endif(USE_NFS_RDMA OR USE_9P_RDMA) if(USE_9P_RDMA AND NOT USE_9P) message(WARNING "The support of 9P/RDMA needs 9P protocol support. Enabling 9P") set(USE_9P ON) endif(USE_9P_RDMA AND NOT USE_9P) IF(ALLOCATOR) set(ALLOCATOR ${ALLOCATOR} CACHE STRING "memory allocator: jemalloc|tcmalloc|libc") ELSE() if( "${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" VERSION_GREATER "2.6" ) set(ALLOCATOR "jemalloc" CACHE STRING "specify the memory allocator to use: jemalloc|tcmalloc|libc") else("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" VERSION_GREATER "2.6" ) set(ALLOCATOR "libc" CACHE STRING "specify the memory allocator to use: jemalloc|tcmalloc|libc") endif( "${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" VERSION_GREATER "2.6" ) ENDIF() check_include_files(strings.h HAVE_STRINGS_H) check_include_files(string.h HAVE_STRING_H) if(HAVE_STRING_H AND HAVE_STRINGS_H) # we have all the libraries and include files to use string.h set(HAVE_STRNLEN ON) endif(HAVE_STRING_H AND HAVE_STRINGS_H) IF(_VALGRIND_MEMCHECK) check_include_files(valgrind/memcheck.h HAVE_MEMCHECK_H) if(NOT HAVE_MEMCHECK_H) message(FATAL_ERROR "Cannot find valgrind/memcheck.h, install valgrind-devel package to enable _VALGRIND_MEMCHECK") ENDIF(NOT HAVE_MEMCHECK_H) ENDIF(_VALGRIND_MEMCHECK) TEST_BIG_ENDIAN(BIGENDIAN) if(NOT ${BIGENDIAN}) set(LITTLEEND ON) endif(NOT ${BIGENDIAN}) if( "${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" VERSION_GREATER "2.6" ) find_package(Threads REQUIRED) endif( "${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" VERSION_GREATER "2.6" ) if (USE_GSS) find_package(Krb5 REQUIRED gssapi) check_include_files(gssapi.h HAVE_GSSAPI_H) if (NOT HAVE_GSSAPI_H) # Debian/Ubuntu 12 magic set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I/usr/include/mit-krb5/") check_include_files(gssapi.h HAVE_GSSAPI_H) endif(NOT HAVE_GSSAPI_H) if(KRB5_FOUND AND HAVE_GSSAPI_H) set(HAVE_KRB5 ON) set(_HAVE_GSSAPI ON) else(KRB5_FOUND AND HAVE_GSSAPI_H) if (NOT KRB5_FOUND) message(FATAL_ERROR "Cannot find kerberos libraries") endif(NOT KRB5_FOUND) if (NOT HAVE_GSSAPI_H) message(FATAL_ERROR "Cannot find GSS libraries") endif (NOT HAVE_GSSAPI_H) endif(KRB5_FOUND AND HAVE_GSSAPI_H) endif(USE_GSS) set(WITH_PYTHON3 "3" CACHE STRING "build with specified python3 version") gopt_test(USE_ADMIN_TOOLS) if (USE_ADMIN_TOOLS) find_package (Python3 ${WITH_PYTHON3} EXACT REQUIRED COMPONENTS Interpreter) if (NOT Python3_Interpreter_FOUND) message(WARNING "Cannot find python. Disablin admin tools") set(USE_ADMIN_TOOLS OFF) endif (NOT Python3_Interpreter_FOUND) endif (USE_ADMIN_TOOLS) gopt_test(USE_GUI_ADMIN_TOOLS) if (USE_ADMIN_TOOLS) if (USE_GUI_ADMIN_TOOLS) set(PYQTX PyQt5) find_program(PYUIC NAMES pyuic5 DOC "PyQT UI-compiler executable") if (NOT PYUIC) set(PYQTX PyQt4) find_program(PYUIC NAMES pyuic4 DOC "PyQT UI-compiler executable") endif (NOT PYUIC) if (NOT PYUIC) if (USE_GUI_ADMIN_TOOLS_REQUIRED) message(FATAL_ERROR "Cannot find PyQt but GUI admin tools requested on command line") else (USE_GUI_ADMIN_TOOLS_REQUIRED) message(WARNING "Cannot find PyQt4 or PyQt5. Disabling GUI admin tools") set(USE_GUI_ADMIN_TOOLS OFF) endif (USE_GUI_ADMIN_TOOLS_REQUIRED) endif (NOT PYUIC) endif (USE_GUI_ADMIN_TOOLS) endif (USE_ADMIN_TOOLS) gopt_test(USE_MAN_PAGE) if (USE_MAN_PAGE) find_program(SPHINX_BUILD sphinx-build) if(NOT SPHINX_BUILD) find_program(SPHINX_BUILD sphinx-build-3) endif(NOT SPHINX_BUILD) if(NOT SPHINX_BUILD) if (USE_MAN_PAGE_REQUIRED) message(FATAL_ERROR "Can't find sphinx-build but man pages requested on command line") else (USE_MAN_PAGE_REQUIRED) message(WARNING "Can't find sphinx-build. Disabling man pages") set(USE_MAN_PAGE OFF) endif (USE_MAN_PAGE_REQUIRED) endif(NOT SPHINX_BUILD) endif(USE_MAN_PAGE) # POSIX ACLs - these may be used in multiple places find_package(LibACL) # Validate fsal dependencies gopt_test(USE_FSAL_PROXY_V4) if (USE_FSAL_PROXY_V4) # PROXY_V4 has no deps of it's own, but it has a dependent option # PROXY_V4 handle mapping needs sqlite3 gopt_test(PROXYV4_HANDLE_MAPPING) if(PROXYV4_HANDLE_MAPPING) check_include_files(sqlite3.h HAVE_SQLITE3_H) check_library_exists( sqlite3 sqlite3_open "" HAVE_SQLITE3 ) if(NOT HAVE_SQLITE3 OR NOT HAVE_SQLITE3_H) if(PROXYV4_HANDLE_MAPPING_REQUIRED) message(FATAL_ERROR "Cannot find sqlite3.h or the library but proxyv4 handle mapping requested on command line") else(PROXYV4_HANDLE_MAPPING_REQUIRED) message(WARNING "Cannot find sqlite3.h or the library. Disabling proxyv4 handle mapping") set(PROXYV4_HANDLE_MAPPING OFF) endif(PROXYV4_HANDLE_MAPPING_REQUIRED) endif(NOT HAVE_SQLITE3 OR NOT HAVE_SQLITE3_H) endif(PROXYV4_HANDLE_MAPPING) endif (USE_FSAL_PROXY_V4) gopt_test(USE_FSAL_VFS) if(USE_FSAL_VFS) # VFS has an optional dependency for libbtrfsutil find_library(BTRFSUTIL_LIB btrfsutil) if (BTRFSUTIL_LIB) message(STATUS "Found BTRFS Util library: ${BTRFSUTIL_LIB}") set(USE_BTRFSUTIL ON) set(SYSTEM_LIBRARIES ${BTRFSUTIL_LIB} ${SYSTEM_LIBRARIES}) endif (BTRFSUTIL_LIB) if(ENABLE_VFS_DEBUG_ACL) set(ENABLE_VFS_ACL ON) if(ENABLE_VFS_POSIX_ACL) set(ENABLE_VFS_POSIX_ACL OFF) message(WARNING "ENABLE_VFS_DEBUG_ACL is on, so disabling ENABLE_VFS_POSIX_ACL") endif(ENABLE_VFS_POSIX_ACL) else(ENABLE_VFS_DEBUG_ACL) # POSIX ACL support? gopt_test(ENABLE_VFS_POSIX_ACL) if(ENABLE_VFS_POSIX_ACL) if(NOT HAVE_LIBACL) set(ENABLE_VFS_POSIX_ACL OFF) message(WARNING "Could not find libacl") endif(NOT HAVE_LIBACL) if(NOT HAVE_SYS_ACL_H) set(ENABLE_VFS_POSIX_ACL OFF) message(WARNING "Could not find sys/acl.h") endif(NOT HAVE_SYS_ACL_H) if(NOT (HAVE_ACL_GET_FD_NP OR LINUX)) set(ENABLE_VFS_POSIX_ACL OFF) message(WARNING "Could not find acl_get_fd_np() and not Linux") endif(NOT (HAVE_ACL_GET_FD_NP OR LINUX)) if(NOT (HAVE_ACL_SET_FD_NP OR LINUX)) set(ENABLE_VFS_POSIX_ACL OFF) message(WARNING "Could not find acl_set_fd_np() and not Linux") endif(NOT (HAVE_ACL_SET_FD_NP OR LINUX)) if(ENABLE_VFS_POSIX_ACL) set(ENABLE_VFS_ACL ON) set(USE_ACL_MAPPING ON) # libacl only required on Linux set(LibACL_FIND_REQUIRED HAVE_LIBACL) else(ENABLE_VFS_POSIX_ACL) if(ENABLE_VFS_POSIX_ACL_REQUIRED) message(FATAL_ERROR "Missing VFS FSAL prerequisites, but requested on command line") else(ENABLE_VFS_POSIX_ACL_REQUIRED) message(WARNING "Missing VFS FSAL prerequisites, disabling ENABLE_VFS_POSIX_ACL") endif(ENABLE_VFS_POSIX_ACL_REQUIRED) endif(ENABLE_VFS_POSIX_ACL) endif(ENABLE_VFS_POSIX_ACL) endif(ENABLE_VFS_DEBUG_ACL) endif(USE_FSAL_VFS) gopt_test(USE_FSAL_LUSTRE) if(USE_FSAL_LUSTRE) ########### lustre hsm version test ########## # Lustre/HSM feature needs Lustre 2.5.0. # As some hsm calls were already landed as empty nutshells in 2.4 # we rely on this new call of 2.5.0: llapi_hsm_state_get_fd(). ############################################## CHECK_LIBRARY_EXISTS(lustreapi llapi_hsm_state_get_fd "" USE_LLAPI) if(NOT USE_LLAPI) if(USE_FSAL_LUSTRE_REQUIRED) message(FATAL_ERROR "Cannot find lustreapi, but requested on command line") else(USE_FSAL_LUSTRE_REQUIRED) message(WARNING "Cannot find lustreapi. We will only build a dummy lustre fsal.") endif(USE_FSAL_LUSTRE_REQUIRED) endif(NOT USE_LLAPI) endif(USE_FSAL_LUSTRE) gopt_test(USE_FSAL_LIZARDFS) if(USE_FSAL_LIZARDFS) find_library(LIZARDFS_CLIENT_LIB lizardfs-client) if (LIZARDFS_CLIENT_LIB) message(STATUS "Found LizardFS client library: ${LIZARDFS_CLIENT_LIB}") else(LIZARDFS_CLIENT_LIB) message(WARNING "Cannot find LizardFS client lib. Disabling lizardfs fsal") set(USE_FSAL_LIZARDFS OFF) endif(LIZARDFS_CLIENT_LIB) endif(USE_FSAL_LIZARDFS) gopt_test(USE_FSAL_KVSFS) if(USE_FSAL_KVSFS) find_library(KVSNS_LIB kvsns) if (KVSNS_LIB) message(STATUS "Found KVSNS library: ${KVSNS_LIB}") else (KVSNS_LIB) message(WARNING "Cannot find libkvsns. Disabling KVSNS fsal") set(USE_FSAL_KVSFS OFF) endif (KVSNS_LIB) endif(USE_FSAL_KVSFS) gopt_test(USE_FSAL_CEPH) if(USE_FSAL_CEPH) message(STATUS ${USE_FSAL_CEPH_REQUIRED}) find_package(CEPHFS ${USE_FSAL_CEPH_REQUIRED}) if(NOT CEPHFS_FOUND) message(WARNING "Cannot find CEPH runtime. Disabling CEPH fsal build") set(USE_FSAL_CEPH OFF) endif(NOT CEPHFS_FOUND) gopt_test(CEPHFS_POSIX_ACL) if(CEPHFS_POSIX_ACL) if(HAVE_LIBACL) set(USE_ACL_MAPPING ON) else(HAVE_LIBACL) set(CEPHFS_POSIX_ACL OFF) message(STATUS "Could not find libacl, disabling CephFS POSIX ACL") endif(HAVE_LIBACL) endif(CEPHFS_POSIX_ACL) endif(USE_FSAL_CEPH) gopt_test(USE_NFSACL3) if(USE_NFSACL3) if(HAVE_LIBACL) set(USE_ACL_MAPPING ON) else(HAVE_LIBACL) set(USE_NFSACL3 OFF) message(STATUS "Could not find libacl, disabling USE_NFSACL3") endif(HAVE_LIBACL) endif(USE_NFSACL3) gopt_test(USE_FSAL_GPFS) gopt_test(USE_FSAL_XFS) if(USE_FSAL_XFS) if(EXISTS /lib/libhandle.so) check_library_exists(handle "open_by_handle" "/./lib" HAVE_XFS_LIB) if(HAVE_XFS_LIB) set(PATH_LIBHANDLE "/lib/libhandle.so" CACHE INTERNAL "debian stretch and ubuntu xenial hack") endif(HAVE_XFS_LIB) else(EXISTS /lib/libhandle.so) check_library_exists(handle "open_by_handle" "" HAVE_XFS_LIB) endif(EXISTS /lib/libhandle.so) check_include_files("xfs/xfs.h" HAVE_XFS_H) if((NOT HAVE_XFS_LIB) OR (NOT HAVE_XFS_H)) if(USE_FSAL_XFS_REQUIRED) message(FATAL_ERROR "Cannot find XFS runtime, but requested on command line.") else(USE_FSAL_XFS_REQUIRED) message(WARNING "Cannot find XFS runtime. Disabling XFS build") set(USE_FSAL_XFS OFF) endif(USE_FSAL_XFS_REQUIRED) endif((NOT HAVE_XFS_LIB) OR (NOT HAVE_XFS_H)) endif(USE_FSAL_XFS) # VFS, GPFS, LUSTRE, and XFS require GSH_CAN_HOST_LOCAL_FS if(NOT GSH_CAN_HOST_LOCAL_FS) if((USE_FSAL_VFS AND USE_FSAL_VFS_REQUIRED) OR (USE_FSAL_GPFS AND USE_FSAL_GPFS_REQUIRED) OR (USE_FSAL_LUSTRE AND USE_FSAL_LUSTRE_REQUIRED) OR (USE_FSAL_XFS AND USE_FSAL_XFS_REQUIRED)) message(FATAL_ERROR "USE_FSAL_VFS, _GPFS, _LUSTRE, or _XFS requested but local FS hosting is not enabled") endif((USE_FSAL_VFS AND USE_FSAL_VFS_REQUIRED) OR (USE_FSAL_GPFS AND USE_FSAL_GPFS_REQUIRED) OR (USE_FSAL_LUSTRE AND USE_FSAL_LUSTRE_REQUIRED) OR (USE_FSAL_XFS AND USE_FSAL_XFS_REQUIRED)) set(USE_FSAL_VFS OFF) set(USE_FSAL_GPFS OFF) set(USE_FSAL_LUSTRE OFF) set(USE_FSAL_XFS OFF) endif(NOT GSH_CAN_HOST_LOCAL_FS) gopt_test(USE_FSAL_GLUSTER) if(USE_FSAL_GLUSTER) find_package(PkgConfig) IF(GLUSTER_PREFIX) set(ENV{PKG_CONFIG_PATH} "${PKG_CONFIG_PATH}:${GLUSTER_PREFIX}/lib/pkgconfig") ENDIF(GLUSTER_PREFIX) # pkg_check_modules doesn't fatal error on REQUIRED, so handle it ourselves pkg_check_modules(GFAPI glusterfs-api>=7.6.6) if(NOT GFAPI_FOUND) if(USE_FSAL_GLUSTER_REQUIRED) message(FATAL_ERROR "Cannot find GLUSTER GFAPI runtime but requested on command line") else(USE_FSAL_GLUSTER_REQUIRED) message(WARNING "Cannot find GLUSTER GFAPI runtime. Disabling GLUSTER fsal build") set(USE_FSAL_GLUSTER OFF) endif(USE_FSAL_GLUSTER_REQUIRED) else(NOT GFAPI_FOUND) message(STATUS "GFAPI_INCLUDE_DIRS=${GFAPI_INCLUDE_DIRS}") message(STATUS "GFAPI_LIBRARY_DIRS=${GFAPI_LIBRARY_DIRS}") message(STATUS "GFAPI_LIBDIR=${GFAPI_LIBDIR}") include_directories(${GFAPI_INCLUDE_DIRS}) # missing directory not provided by current version of GlusterFS include_directories(${GFAPI_PREFIX}/include) link_directories (${GFAPI_LIBRARY_DIRS}) pkg_check_modules(STAT_FETCH_GFAPI glusterfs-api>=7.6) if(STAT_FETCH_GFAPI_FOUND) set(USE_GLUSTER_STAT_FETCH_API ON) else() set(USE_GLUSTER_STAT_FETCH_API OFF) message(STATUS "turning off stat api's") endif(STAT_FETCH_GFAPI_FOUND) endif(NOT GFAPI_FOUND) if(USE_FSAL_GLUSTER) check_include_files("unistd.h;sys/xattr.h" HAVE_SYS_XATTR_H) if(NOT HAVE_SYS_XATTR_H) if(USE_FSAL_GLUSTER_REQUIRED) message(FATAL_ERROR "Can not find sys/xattr.h, but GLUSTER requested on command line") else(USE_FSAL_GLUSTER_REQUIRED) message(WARNING "Can not find sys/xattr.h, disabling GLUSTER fsal build") set(USE_FSAL_GLUSTER OFF) endif(USE_FSAL_GLUSTER_REQUIRED) endif(NOT HAVE_SYS_XATTR_H) if(HAVE_LIBACL) set(USE_POSIX_ACLS ON) set(USE_ACL_MAPPING ON) else(HAVE_LIBACL) set(USE_POSIX_ACLS OFF) set(USE_FSAL_GLUSTER OFF) message(STATUS "Could not find libacl, disabling GLUSTER fsal build") endif(HAVE_LIBACL) check_library_exists(gfapi glfs_xreaddirplus_r ${GFAPI_LIBDIR} HAVE_XREADDIRPLUS) if(HAVE_XREADDIRPLUS) set(USE_GLUSTER_XREADDIRPLUS ON) else() set(USE_GLUSTER_XREADDIRPLUS OFF) message(STATUS "Could not find glfs_xreaddirplus, switching to glfs_readdir_r") endif(HAVE_XREADDIRPLUS) check_library_exists(gfapi glfs_fd_set_lkowner ${GFAPI_LIBDIR} HAVE_LKOWNER) if(HAVE_LKOWNER) set(USE_LKOWNER ON) else() set(USE_LKOWNER OFF) set(USE_FSAL_GLUSTER OFF) message(STATUS "lkowner support is needed to enable GLUSTER build") endif(HAVE_LKOWNER) check_library_exists(gfapi glfs_upcall_register ${GFAPI_LIBDIR} HAVE_REGISTER_UPCALL) if(HAVE_REGISTER_UPCALL) set(USE_GLUSTER_UPCALL_REGISTER ON) else() set(USE_GLUSTER_UPCALL_REGISTER OFF) message(STATUS "Could not find glfs_upcall_register, switching to glfs_h_poll_upcall") endif(HAVE_REGISTER_UPCALL) check_library_exists(gfapi glfs_upcall_lease_get_object ${GFAPI_LIBDIR} HAVE_DELEG) if(HAVE_DELEG) set(USE_GLUSTER_DELEGATION ON) else() set(USE_GLUSTER_DELEGATION OFF) message(STATUS "Could not find glfs_upcall_lease_get_object, switching off delegations") endif(HAVE_DELEG) endif(USE_FSAL_GLUSTER) endif(USE_FSAL_GLUSTER) gopt_test(USE_FSAL_NULL) # NULL has no dependencies gopt_test(USE_FSAL_RGW) if(USE_FSAL_RGW) # require RGW w/API version 1.2.1 find_package(RGW 1.2.1 ${USE_FSAL_RGW_REQUIRED}) if(NOT RGW_FOUND) message(WARNING "Cannot find supported RGW runtime. Disabling RGW fsal build") set(USE_FSAL_RGW OFF) endif(NOT RGW_FOUND) endif(USE_FSAL_RGW) gopt_test(USE_FSAL_SAUNAFS) gopt_test(USE_FSAL_MEM) # MEM has no dependencies # sort out which allocator to use if(${ALLOCATOR} STREQUAL "jemalloc") find_package(JeMalloc) if(JEMALLOC_FOUND) set(SYSTEM_LIBRARIES ${JEMALLOC_LIBRARIES} ${SYSTEM_LIBRARIES}) else(JEMALLOC_FOUND) message(WARNING "jemalloc not found, falling back to libc") set(ALLOCATOR "libc") endif(JEMALLOC_FOUND) elseif(${ALLOCATOR} STREQUAL "tcmalloc") find_package(TcMalloc) if(TCMALLOC_FOUND) set(SYSTEM_LIBRARIES ${TCMALLOC_LIBRARIES} ${SYSTEM_LIBRARIES}) else(TCMALLOC_FOUND) message(WARNING "tcmalloc not found, falling back to libc") set(ALLOCATOR "libc") endif(TCMALLOC_FOUND) else() if(NOT ${ALLOCATOR} STREQUAL "libc") message(SEND_ERROR "${ALLOCATOR} is not a valid option. Valid allocators are: jemalloc|tcmalloc|libc") endif() endif() # Find optional libraries/packages if (USE_ACL_MAPPING) if(HAVE_LIBACL) set(SYSTEM_LIBRARIES ${LIBACL_LIBRARY} ${SYSTEM_LIBRARIES}) else(HAVE_LIBACL) set(USE_ACL_MAPPING OFF) # HAVE_LIBACL must be set before setting USE_ACL_MAPPING message(FATAL_ERROR "Could not find libacl, disabling ACL mapping build") endif(HAVE_LIBACL) endif(USE_ACL_MAPPING) gopt_test(USE_EFENCE) if(USE_EFENCE) find_library(LIBEFENCE efence) if(LIBEFENCE_FOUND) set(SYSTEM_LIBRARIES ${LIBEFENCE} ${SYSTEM_LIBRARIES}) else(LIBEFENCE_FOUND) if(USE_EFENCE_REQUIRED) message(FATAL_ERROR "Cannot find efence libs but requested on command line") else(USE_EFENCE_REQUIRED) message(WARNING "Cannot find efence libs. Disabling efence support") endif(USE_EFENCE_REQUIRED) set(USE_EFENCE OFF) endif(LIBEFENCE_FOUND) endif(USE_EFENCE) gopt_test(USE_DBUS) if(USE_DBUS) find_package(PkgConfig) # pkg_check_modules doesn't fatal error on REQUIRED, so handle it ourselves pkg_check_modules(DBUS ${USE_DBUS_REQUIRED} dbus-1) if(NOT DBUS_FOUND) if(USE_DBUS_REQUIRED) message(FATAL_ERROR "Cannot find DBUS libs but requested on command line") else(USE_DBUS_REQUIRED) message(WARNING "Cannot find DBUS libs. Disabling DBUS support") set(USE_DBUS OFF) endif(USE_DBUS_REQUIRED) else(NOT DBUS_FOUND) set(SYSTEM_LIBRARIES ${DBUS_LIBRARIES} ${SYSTEM_LIBRARIES}) LIST(APPEND CMAKE_LIBRARY_PATH ${DBUS_LIBRARY_DIRS}) link_directories (${DBUS_LIBRARY_DIRS}) endif(NOT DBUS_FOUND) endif(USE_DBUS) if(USE_CB_SIMULATOR AND NOT USE_DBUS) message(WARNING "The callback simulator needs DBUS. Disabling callback simulator") set(USE_CB_SIMULATOR OFF) endif(USE_CB_SIMULATOR AND NOT USE_DBUS) gopt_test(USE_NFSIDMAP) if(USE_NFSIDMAP) find_package(NfsIdmap ${USE_NFSIDMAP_REQUIRED}) if(NFSIDMAP_FOUND) set(SYSTEM_LIBRARIES ${NFSIDMAP_LIBRARY} ${SYSTEM_LIBRARIES}) else(NFSIDMAP_FOUND) message(WARNING "libnfsidmap not found, disabling USE_NFSIDMAP") set(USE_NFSIDMAP OFF) endif(NFSIDMAP_FOUND) endif(USE_NFSIDMAP) if(USE_EXECINFO) find_package(ExecInfo REQUIRED) set(SYSTEM_LIBRARIES ${EXECINFO_LIBRARY} ${SYSTEM_LIBRARIES}) endif(USE_EXECINFO) gopt_test(USE_CUNIT) if(USE_CUNIT) find_package(CUnit ${USE_CUNIT_REQUIRED}) if (CUNIT_FOUND) set(SYSTEM_LIBRARIES ${CUNIT_LIBRARIES} ${SYSTEM_LIBRARIES}) else (CUNIT_FOUND) message(WARNING "CUnit not found. Disabling USE_CUNIT") set(USE_CUNIT OFF) endif (CUNIT_FOUND) endif(USE_CUNIT) gopt_test(_MSPAC_SUPPORT) if(_MSPAC_SUPPORT) find_package(WBclient ${_MSPAC_SUPPORT_REQUIRED}) if(WBCLIENT_FOUND) set(SYSTEM_LIBRARIES ${WBCLIENT_LIBRARIES} ${SYSTEM_LIBRARIES}) else(WBCLIENT_FOUND) message(WARNING "Samba 4 wbclient not found. Disabling MSPAC_SUPPORT") set(_MSPAC_SUPPORT OFF) endif(WBCLIENT_FOUND) endif(_MSPAC_SUPPORT) gopt_test(USE_LTTNG) if(USE_LTTNG) # Set LTTNG_PATH_HINT on the command line # if your LTTng is not in a standard place find_package(LTTng ${USE_LTTNG_REQUIRED}) if(LTTNG_FOUND) if(NOT CMAKE_EXPORT_COMPILE_COMMANDS) # We must create compile_commands.json for lttng trace generation set(CMAKE_EXPORT_COMPILE_COMMANDS 1) endif(NOT CMAKE_EXPORT_COMPILE_COMMANDS) set(CMAKE_C_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}) execute_process( COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/gsh_generated_include/gsh_lttng/generated_traces/" COMMAND_ERROR_IS_FATAL ANY ) include_directories( ${LTTNG_INCLUDE_DIR} "${CMAKE_CURRENT_SOURCE_DIR}/libntirpc/src/lttng/generator/include/" "${CMAKE_CURRENT_BINARY_DIR}/gsh_generated_include/" "${CMAKE_CURRENT_BINARY_DIR}/libntirpc/ntirpc_generated_include/" ) add_definitions(-DUSE_LTTNG) # Generate build rules for trace generator execute_process( COMMAND bash -c "${CMAKE_CURRENT_SOURCE_DIR}/libntirpc/src/lttng/generator/generate_cmake_rules.py \ --project_path ${CMAKE_CURRENT_SOURCE_DIR} \ --provider_include_base_path gsh_lttng/generated_traces/ \ --unique_prefix gsh \ --filter_out libntirpc \ --traces_output_dir ${CMAKE_CURRENT_BINARY_DIR}/gsh_generated_include/gsh_lttng/generated_traces/ \ --include_path ${CMAKE_CURRENT_SOURCE_DIR}/include:${CMAKE_CURRENT_SOURCE_DIR}/libntirpc/ntirpc:${CMAKE_CURRENT_SOURCE_DIR}/libntirpc/src/lttng/generator/include/:${CMAKE_CURRENT_BINARY_DIR}/gsh_generated_include/:${CMAKE_CURRENT_BINARY_DIR}/libntirpc/ntirpc_generated_include/ \ --targets_output_path ${CMAKE_CURRENT_BINARY_DIR}/gsh_lttng_generation_rules.cmake \ --file_properties_output_path ${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake" COMMAND_ERROR_IS_FATAL ANY ) include("${CMAKE_CURRENT_BINARY_DIR}/gsh_lttng_generation_rules.cmake") include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") else(LTTNG_FOUND) message(WARNING "LTTng libraries not found. Disabling USE_LTTNG") set(USE_LTTNG OFF) endif(LTTNG_FOUND) endif(USE_LTTNG) gopt_test(USE_RADOS_RECOV) if(USE_RADOS_RECOV) find_package(RADOS ${USE_RADOS_RECOV_REQUIRED}) if(NOT RADOS_FOUND) message(WARNING "Rados libraries not found. Disabling USE_RADOS_RECOV") set(USE_RADOS_RECOV OFF) endif(NOT RADOS_FOUND) endif(USE_RADOS_RECOV) gopt_test(RADOS_URLS) if(RADOS_URLS) find_package(RADOS ${RADOS_URLS_REQUIRED}) if(NOT RADOS_FOUND) message(WARNING "Rados libraries not found. Disabling RADOS_URLS") set(RADOS_URLS OFF) endif(NOT RADOS_FOUND) endif(RADOS_URLS) # Cmake 2.6 has issue in managing BISON and FLEX if( "${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" VERSION_LESS "2.8" ) message( status "CMake 2.6 detected, using portability hooks" ) set(CMAKE_CURRENT_LIST_DIR /usr/share/cmake/Modules ) set(CMAKE_MODULE_PATH ${GANESHA_TOP_CMAKE_DIR}/cmake/portability_cmake_2.8 /usr/share/cmake/Modules ${CMAKE_MODULE_PATH}) endif( "${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" VERSION_LESS "2.8" ) include_directories( "${PROJECT_BINARY_DIR}/include" "${PROJECT_SOURCE_DIR}/include" "${PROJECT_BINARY_DIR}/monitoring/include" "${PROJECT_SOURCE_DIR}/monitoring/include" "${OS_INCLUDE_DIR}" ) if (HAVE_KRB5) include_directories( "${KRB5_INCLUDE_DIRS}" ) endif (HAVE_KRB5) # check for d_off support, FreeBSD doesn't support it before 1200500 if(LINUX) add_definitions(-DHAS_DOFF) elseif(FREEBSD) EXECUTE_PROCESS( COMMAND uname -K OUTPUT_VARIABLE FREEBSD_VERSION ERROR_QUIET ) if (FREEBSD_VERSION GREATER 1200500) add_definitions(-DHAS_DOFF) endif() elseif(DARWIN) # d_off unavailable on macOS else() # d_off is on by default for other cases add_definitions(-DHAS_DOFF) endif() # Fixup loose bits of autotools legacy set(_USE_9P ${USE_9P}) set(_USE_9P_RDMA ${USE_9P_RDMA}) set(_USE_NFS3 ${USE_NFS3}) set(_USE_NLM ${USE_NLM}) set(_USE_RQUOTA ${USE_RQUOTA}) set(_USE_CB_SIMULATOR ${USE_CB_SIMULATOR}) ########### add a "make dist" and a "make rpm" ############### set( PKG_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}.tar.gz") add_custom_target(dist COMMAND ${CMAKE_MAKE_PROGRAM} package_source) # Find misc system libs goption(USE_CAPS "Enable capability management" ON) gopt_test(USE_CAPS) if(USE_CAPS) find_package(Caps ${USE_CAPS_REQUIRED}) if (CAPS_FOUND) set(SYSTEM_LIBRARIES ${SYSTEM_LIBRARIES} ${CAPS_LIBRARIES}) else (CAPS_FOUND) message(WARNING "Capability libraries not found. Disabling USE_CAPS") set(USE_CAPS OFF) endif (CAPS_FOUND) endif(USE_CAPS) # default to ON so that it does the right thing in the CentOS CI goption(USE_LEGACY_PYTHON_INSTALL "Use 'python setup.py install'" ON) # Check if we have libblkid and libuuid, will just be reported under one # flag USE_BLKID check_include_files("blkid/blkid.h" HAVE_LIBBLKID_H) find_library(LIBBLKID blkid) # Management of Capabilities check_library_exists( blkid blkid_devno_to_devname "" HAVE_LIBBLKID ) check_include_files("uuid/uuid.h" HAVE_LIBUUID_H) find_library(LIBUUID uuid) # Management of Capabilities check_library_exists( uuid uuid_parse "" HAVE_LIBUUID ) if(HAVE_LIBBLKID AND HAVE_LIBUUID AND HAVE_LIBBLKID_H AND HAVE_LIBUUID_H) # we have all the libraries and include files to use libblkid and libuuid set(SYSTEM_LIBRARIES ${SYSTEM_LIBRARIES} ${LIBBLKID} ${LIBUUID}) set(USE_BLKID ON) else(HAVE_LIBBLKID AND HAVE_LIBUUID AND HAVE_LIBBLKID_H AND HAVE_LIBUUID_H) # we are missing something and can't use libblkid and libuuid set(USE_BLKID OFF) if(NOT HAVE_LIBBLKID) message(STATUS "Could not find blkid library, disabling USE_BLKID") elseif(NOT HAVE_LIBUUID) message(STATUS "Could not find uuid library, disabling USE_BLKID") elseif(NOT HAVE_LIBBLKID_H) message(STATUS "Could not find blkid header files, disabling USE_BLKID") else(NOT HAVE_LIBBLKID) message(STATUS "Could not find uuid header files, disabling USE_BLKID") endif(NOT HAVE_LIBBLKID) endif(HAVE_LIBBLKID AND HAVE_LIBUUID AND HAVE_LIBBLKID_H AND HAVE_LIBUUID_H) # check is daemon exists # I use check_library_exists there to be portab;e check_library_exists( c daemon "" HAVE_DAEMON ) # Needs to be before ntirpc add_custom_target( rpm DEPENDS dist) add_custom_command(TARGET rpm COMMAND sh -c "rpmbuild -ta ${PKG_NAME}" VERBATIM DEPENDS dist) set(RPMDEST "--define '_srcrpmdir ${CMAKE_CURRENT_BINARY_DIR}'") add_custom_target( srpm DEPENDS dist) add_custom_command(TARGET srpm COMMAND sh -c "rpmbuild ${RPMDEST} -ts ${PKG_NAME}" VERBATIM DEPENDS dist) # Roll up required libraries # enable libunwind if available goption(USE_UNWIND "Enable libunwind" ON) gopt_test(USE_UNWIND) if (USE_UNWIND) find_package(Unwind) if (UNWIND_FOUND) set(SYSTEM_LIBRARIES ${UNWIND_LIBRARIES} ${SYSTEM_LIBRARIES}) include_directories(${UNWIND_INCLUDE_DIR}) message(STATUS "libunwind found: ${UNWIND_LIBRARIES}") else (UNWIND_FOUND) message(WARNING "libunwind not found. Disabling USE_UNWIND") set(USE_UNWIND OFF) endif (UNWIND_FOUND) endif(USE_UNWIND) if(USE_9P_RDMA) find_package(PkgConfig) pkg_check_modules(MOOSHIKA REQUIRED libmooshika>=0.6) include_directories(${MOOSHIKA_INCLUDE_DIRS}) link_directories (${MOOSHIKA_LIBRARY_DIRS}) endif(USE_9P_RDMA) set(NTIRPC_MIN_VERSION 5.0) if (USE_SYSTEM_NTIRPC) find_package(NTIRPC ${NTIRPC_MIN_VERSION} REQUIRED) if (USE_LTTNG) set(NTIRPC_LIBRARY ${NTIRPC_LIBRARY} ${NTIRPC_LTTNG}) set(USE_LTTNG_NTIRPC ON) endif (USE_LTTNG) else (USE_SYSTEM_NTIRPC) # Set options for submodule set(USE_RPC_RDMA ${USE_NFS_RDMA} CACHE BOOL "Use RDMA") set(TIRPC_EPOLL ${TIRPC_EPOLL} CACHE BOOL "Use EPOLL") set(USE_GSS ${USE_GSS} CACHE BOOL "Use GSS") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${GANESHA_TOP_CMAKE_DIR}/libntirpc/cmake/modules/") add_subdirectory(libntirpc) set(NTIRPC_LIBRARY ntirpc) if (USE_LTTNG) set(NTIRPC_LIBRARY ${NTIRPC_LIBRARY} ntirpc_lttng) endif (USE_LTTNG) set(NTIRPC_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/libntirpc/ntirpc/") message(STATUS "Using ntirpc submodule") endif (USE_SYSTEM_NTIRPC) message(${NTIRPC_INCLUDE_DIR}) include_directories(${NTIRPC_INCLUDE_DIR}) find_library(LIBURCU_LIB urcu-bp) if (NOT LIBURCU_LIB) message(FATAL_ERROR "userspace-rcu library not found!") endif(NOT LIBURCU_LIB) find_path(LIBURCU_INC urcu-bp.h) if (NOT LIBURCU_INC) message(FATAL_ERROR "userspace-rcu includes not found!") endif(NOT LIBURCU_INC) check_symbol_exists(urcu_ref_get_unless_zero urcu/ref.h HAVE_URCU_REF_GET_UNLESS_ZERO) # All the plumbing in the basement set(SYSTEM_LIBRARIES ${NTIRPC_LIBRARY} ${SYSTEM_LIBRARIES} ${CMAKE_DL_LIBS} ${KRB5_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} ${LIBURCU_LIB} ) include_directories(${LIBURCU_INC}) # Config file; make sure it doesn't clobber an existing one include(${GANESHA_TOP_CMAKE_DIR}/cmake/modules/InstallPackageConfigFile.cmake) InstallPackageConfigFile(${GANESHA_TOP_CMAKE_DIR}/config_samples/ganesha.conf.example ${SYSCONFDIR}/ganesha ganesha.conf) add_subdirectory(log) add_subdirectory(config_parsing) add_subdirectory(cidr) add_subdirectory(test) add_subdirectory(avl) add_subdirectory(hashtable) add_subdirectory(SAL) add_subdirectory(RPCAL) add_subdirectory(Protocols) add_subdirectory(support) add_subdirectory(os) if(USE_MONITORING) add_subdirectory(monitoring) set(BCOND_MONITORING "%bcond_without") else(USE_MONITORING) set(BCOND_MONITORING "%bcond_with") endif(USE_MONITORING) add_subdirectory(FSAL) add_subdirectory(idmapper) add_subdirectory(MainNFSD) add_subdirectory(tools) if(USE_GTEST) add_subdirectory(gtest) endif(USE_GTEST) if(USE_DBUS) add_subdirectory(dbus) endif(USE_DBUS) if(USE_LTTNG) add_subdirectory(tracing) endif(USE_LTTNG) add_subdirectory(scripts) add_subdirectory(doc) # display configuration vars message(STATUS) message(STATUS "-------------------------------------------------------") message(STATUS "PLATFORM = ${PLATFORM}") message(STATUS "ARCH = ${ARCH}") message(STATUS "VERSION = ${GANESHA_VERSION}") message(STATUS "BUILD HOST = ${BUILD_HOST_NAME}") message(STATUS "C COMPILER = ${CMAKE_CXX_COMPILER_ID}") message(STATUS "C COMPILER VERSION = ${CMAKE_C_COMPILER_VERSION}") message(STATUS "-------------------------------------------------------") message(STATUS "USE_FSAL_PROXY_V4 = ${USE_FSAL_PROXY_V4}") message(STATUS "USE_FSAL_PROXY_V3 = ${USE_FSAL_PROXY_V3}") message(STATUS "USE_FSAL_VFS = ${USE_FSAL_VFS}") message(STATUS "USE_FSAL_LUSTRE = ${USE_FSAL_LUSTRE}") message(STATUS "USE_FSAL_LIZARDFS = ${USE_FSAL_LIZARDFS}") message(STATUS "USE_FSAL_KVSFS = ${USE_FSAL_KVSFS}") message(STATUS "USE_FSAL_CEPH = ${USE_FSAL_CEPH}") message(STATUS "USE_FSAL_CEPH_MKNOD = ${USE_FSAL_CEPH_MKNOD}") message(STATUS "USE_FSAL_CEPH_SETLK = ${USE_FSAL_CEPH_SETLK}") message(STATUS "USE_FSAL_CEPH_LL_LOOKUP_ROOT = ${USE_FSAL_CEPH_LL_LOOKUP_ROOT}") message(STATUS "USE_FSAL_CEPH_STATX = ${USE_FSAL_CEPH_STATX}") message(STATUS "USE_FSAL_CEPH_LL_DELEGATION = ${USE_FSAL_CEPH_LL_DELEGATION}") message(STATUS "USE_FSAL_CEPH_LL_SYNC_INODE = ${USE_FSAL_CEPH_LL_SYNC_INODE}") message(STATUS "USE_FSAL_CEPH_ABORT_CONN = ${USE_FSAL_CEPH_ABORT_CONN}") message(STATUS "USE_FSAL_CEPH_RECLAIM_RESET = ${USE_FSAL_CEPH_RECLAIM_RESET}") message(STATUS "USE_FSAL_CEPH_GET_FS_CID = ${USE_FSAL_CEPH_GET_FS_CID}") message(STATUS "USE_FSAL_CEPH_STATX = ${USE_FSAL_CEPH_STATX}") message(STATUS "USE_FSAL_CEPH_FS_NONBLOCKING_IO = ${USE_FSAL_CEPH_FS_NONBLOCKING_IO}") message(STATUS "USE_FSAL_CEPH_FS_ZEROCOPY_IO = ${USE_FSAL_CEPH_FS_ZEROCOPY_IO}") message(STATUS "USE_FSAL_RGW = ${USE_FSAL_RGW}") message(STATUS "USE_FSAL_SAUNAFS = ${USE_FSAL_SAUNAFS}") message(STATUS "USE_FSAL_XFS = ${USE_FSAL_XFS}") message(STATUS "USE_FSAL_GPFS = ${USE_FSAL_GPFS}") message(STATUS "USE_FSAL_GLUSTER = ${USE_FSAL_GLUSTER}") message(STATUS "USE_FSAL_NULL = ${USE_FSAL_NULL}") message(STATUS "USE_FSAL_MEM = ${USE_FSAL_MEM}") message(STATUS "GSH_CAN_HOST_LOCAL_FS = ${GSH_CAN_HOST_LOCAL_FS}") message(STATUS "USE_SYSTEM_NTIRPC = ${USE_SYSTEM_NTIRPC}") message(STATUS "USE_DBUS = ${USE_DBUS}") message(STATUS "USE_CB_SIMULATOR = ${USE_CB_SIMULATOR}") message(STATUS "USE_BTRFSUTIL = ${USE_BTRFSUTIL}") message(STATUS "USE_NFSIDMAP = ${USE_NFSIDMAP}") message(STATUS "USE_RELATIVE_FILE_PATHS_IN_LOGS = ${USE_RELATIVE_FILE_PATHS_IN_LOGS}") message(STATUS "ENABLE_ERROR_INJECTION = ${ENABLE_ERROR_INJECTION}") message(STATUS "ENABLE_VFS_POSIX_ACL = ${ENABLE_VFS_POSIX_ACL}") message(STATUS "ENABLE_VFS_DEBUG_ACL = ${ENABLE_VFS_DEBUG_ACL}") message(STATUS "ENABLE_RFC_ACL = ${ENABLE_RFC_ACL}") message(STATUS "CEPHFS_POSIX_ACL = ${CEPHFS_POSIX_ACL}") message(STATUS "USE_MONITORING = ${USE_MONITORING}") message(STATUS "USE_CAPS = ${USE_CAPS}") message(STATUS "USE_BLKID = ${USE_BLKID}") message(STATUS "DISTNAME_HAS_GIT_DATA = ${DISTNAME_HAS_GIT_DATA}" ) message(STATUS "_MSPAC_SUPPORT = ${_MSPAC_SUPPORT}") message(STATUS "USE_EFENCE = ${USE_EFENCE}") message(STATUS "_NO_TCP_REGISTER = ${_NO_TCP_REGISTER}") message(STATUS "RPCBIND = ${RPCBIND}") message(STATUS "DEBUG_SAL = ${DEBUG_SAL}") message(STATUS "_VALGRIND_MEMCHECK = ${_VALGRIND_MEMCHECK}") message(STATUS "ENABLE_LOCKTRACE = ${ENABLE_LOCKTRACE}") message(STATUS "PROXYV4_HANDLE_MAPPING = ${PROXYV4_HANDLE_MAPPING}") message(STATUS "DEBUG_MDCACHE = ${DEBUG_MDCACHE}") message(STATUS "DEBUG_SYMS = ${DEBUG_SYMS}") message(STATUS "COVERAGE = ${COVERAGE}") message(STATUS "ENFORCE_GCC = ${ENFORCE_GCC}") message(STATUS "USE_GTEST = ${USE_GTEST}") message(STATUS "USE_GSS = ${USE_GSS}") message(STATUS "TIRPC_EPOLL = ${TIRPC_EPOLL}") message(STATUS "USE_9P = ${USE_9P}") message(STATUS "_USE_9P = ${_USE_9P}") message(STATUS "_USE_9P_RDMA = ${_USE_9P_RDMA}") message(STATUS "USE_NFS_RDMA = ${USE_NFS_RDMA}") message(STATUS "USE_RPC_RDMA = ${USE_RPC_RDMA}") message(STATUS "USE_NFS3 = ${USE_NFS3}") message(STATUS "USE_NLM = ${USE_NLM}") message(STATUS "USE_NFSACL3 = ${USE_NFSACL3}") message(STATUS "USE_ACL_MAPPING = ${USE_ACL_MAPPING}") message(STATUS "KRB5_PREFIX = ${KRB5_PREFIX}") message(STATUS "CEPH_PREFIX = ${CEPH_PREFIX}") message(STATUS "RGW_PREFIX = ${RGW_PREFIX}") message(STATUS "GLUSTER_PREFIX = ${GLUSTER_PREFIX}") message(STATUS "CMAKE_PREFIX_PATH = ${CMAKE_PREFIX_PATH}") message(STATUS "_GIT_HEAD_COMMIT = ${_GIT_HEAD_COMMIT}") message(STATUS "_GIT_HEAD_COMMIT_ABBREV = ${_GIT_HEAD_COMMIT_ABBREV}") message(STATUS "_GIT_DESCRIBE = ${_GIT_DESCRIBE}") message(STATUS "ALLOCATOR = ${ALLOCATOR}") message(STATUS "GOLD_LINKER = ${GOLD_LINKER}") message(STATUS "CMAKE_INSTALL_PREFIX = ${CMAKE_INSTALL_PREFIX}") message(STATUS "FSAL_DESTINATION = ${FSAL_DESTINATION}") message(STATUS "USE_ADMIN_TOOLS = ${USE_ADMIN_TOOLS}") message(STATUS "USE_GUI_ADMIN_TOOLS = ${USE_GUI_ADMIN_TOOLS}") message(STATUS "MODULES_PATH = ${MODULES_PATH}") message(STATUS "USE_LTTNG = ${USE_LTTNG}") message(STATUS "USE_VSOCK = ${USE_VSOCK}") message(STATUS "USE_TOOL_MULTILOCK = ${USE_TOOL_MULTILOCK}") message(STATUS "USE_MAN_PAGE = ${USE_MAN_PAGE}") message(STATUS "USE_RADOS_RECOV = ${USE_RADOS_RECOV}") message(STATUS "RADOS_URLS = ${RADOS_URLS}") message(STATUS "USE_CUNIT = ${USE_CUNIT}") message(STATUS "USE_UNWIND = ${USE_UNWIND}") message(STATUS "SANITIZE_ADDRESS = ${SANITIZE_ADDRESS}") set(USE_CB_SIMULATOR ${USE_CB_SIMULATOR} CACHE BOOL "enable callback simulator thread" FORCE) set(DEBUG_SAL ${DEBUG_SAL} CACHE BOOL "enable debug SAL" FORCE) set(_VALGRIND_MEMCHECK ${_VALGRIND_MEMCHECK} CACHE BOOL "Initialize buffers passed to GPFS ioctl" FORCE) set(ENABLE_ERROR_INJECTION ${ENABLE_ERROR_INJECTION} CACHE BOOL "enable error injection" FORCE) set(ENABLE_VFS_POSIX_ACL ${ENABLE_VFS_POSIX_ACL} CACHE BOOL "Enable NFSv4 to POSIX ACL mapping for VFS" FORCE) set(ENABLE_VFS_DEBUG_ACL ${ENABLE_VFS_DEBUG_ACL} CACHE BOOL "Enable debug ACL store for VFS" FORCE) set(ENABLE_RFC_ACL ${ENABLE_RFC_ACL} CACHE BOOL "Enable debug ACL store for VFS" FORCE) set( DISTNAME_HAS_GIT_DATA ${DISTNAME_HAS_GIT_DATA} CACHE BOOL "Distribution package's name carries git data" FORCE) set(USE_9P ${USE_9P} CACHE BOOL "enable 9P support" FORCE) set(_USE_9P ${_USE_9P} CACHE BOOL "enable 9P support in config" FORCE) set(_USE_9P_RDMA ${_USE_9P_RDMA} CACHE BOOL "enable 9P_RDMA support" FORCE) set(USE_NFS3 ${USE_NFS3} CACHE BOOL "enable NFSv3 support" FORCE) set(USE_NLM ${USE_NLM} CACHE BOOL "enable NLM support" FORCE) set(USE_NFS_RDMA ${USE_NFS_RDMA} CACHE BOOL "enable NFS Over RDMA" FORCE) set(_USE_NFS_RDMA ${USE_NFS_RDMA} CACHE BOOL "enable NFS Over RDMA in config" FORCE) set(USE_ACL_MAPPING ${USE_ACL_MAPPING} CACHE BOOL "Build NFSv4 to POSIX ACL mapping" FORCE) # Now create a usable config.h configure_file( "${PROJECT_SOURCE_DIR}/include/config-h.in.cmake" "${PROJECT_BINARY_DIR}/include/config.h" ) # Tweak the "%bcond_ in the specfile for every # optional feature. Take care on the logic of this syntax # %bcond_with means you add a "--with" option, default is "without this feature" # %bcond_without adds a"--without" so the feature is enabled by default # This has to be coherent with chosen FSALs if(USE_FSAL_XFS) set(BCOND_XFS "%bcond_without") else(USE_FSAL_XFS) set(BCOND_XFS "%bcond_with") endif(USE_FSAL_XFS) if(USE_FSAL_LUSTRE AND USE_LLAPI) set(BCOND_LUSTRE "%bcond_without") else(USE_FSAL_LUSTRE AND USE_LLAPI) set(BCOND_LUSTRE "%bcond_with") endif(USE_FSAL_LUSTRE AND USE_LLAPI) if(USE_FSAL_GPFS) set(BCOND_GPFS "%bcond_without") else(USE_FSAL_GPFS) set(BCOND_GPFS "%bcond_with") endif(USE_FSAL_GPFS) if(USE_FSAL_LIZARDFS) set(BCOND_LIZARDFS "%bcond_without") else(USE_FSAL_LIZARDFS) set(BCOND_LIZARDFS "%bcond_with") endif(USE_FSAL_LIZARDFS) if(USE_FSAL_KVSFS) set(BCOND_KVSFS "%bcond_without") else(USE_FSAL_KVSFS) set(BCOND_KVSFS "%bcond_with") endif(USE_FSAL_KVSFS) if(USE_FSAL_CEPH) set(BCOND_CEPH "%bcond_without") else(USE_FSAL_CEPH) set(BCOND_CEPH "%bcond_with") endif(USE_FSAL_CEPH) if(USE_FSAL_RGW) set(BCOND_RGW "%bcond_without") else(USE_FSAL_RGW) set(BCOND_RGW "%bcond_with") endif(USE_FSAL_RGW) if(USE_FSAL_SAUNAFS) set(BCOND_SAUNAFS "%bcond_without") else(USE_FSAL_SAUNAFS) set(BCOND_SAUNAFS "%bcond_with") endif(USE_FSAL_SAUNAFS) if(USE_FSAL_GLUSTER) set(BCOND_GLUSTER "%bcond_without") else(USE_FSAL_GLUSTER) set(BCOND_GLUSTER "%bcond_with") endif(USE_FSAL_GLUSTER) if(USE_FSAL_NULL) set(BCOND_NULLFS "%bcond_without") else(USE_FSAL_NULL) set(BCOND_NULLFS "%bcond_with") endif(USE_FSAL_NULL) if(USE_FSAL_MEM) set(BCOND_MEM "%bcond_without") else(USE_FSAL_MEM) set(BCOND_MEM "%bcond_with") endif(USE_FSAL_MEM) if(USE_9P) set(BCOND_9P "%bcond_without") if(USE_9P_RDMA) set(BCOND_RDMA "%bcond_without") else(USE_9P_RDMA) set(BCOND_RDMA "%bcond_with") endif(USE_9P_RDMA) else(USE_9P) set(BCOND_9P "%bcond_with") set(BCOND_RDMA "%bcond_with") endif(USE_9P) if(USE_LTTNG) set(BCOND_LTTNG "%bcond_without") else(USE_LTTNG) set(BCOND_LTTNG "%bcond_with") endif(USE_LTTNG) if(${ALLOCATOR} STREQUAL "jemalloc") set(BCOND_JEMALLOC "%bcond_without") else(${ALLOCATOR} STREQUAL "jemalloc") set(BCOND_JEMALLOC "%bcond_with") endif(${ALLOCATOR} STREQUAL "jemalloc") if(${ALLOCATOR} STREQUAL "tcmalloc") set(BCOND_TCMALLOC "%bcond_without") else(${ALLOCATOR} STREQUAL "tcmalloc") set(BCOND_TCMALLOC "%bcond_with") endif(${ALLOCATOR} STREQUAL "tcmalloc") if(USE_ADMIN_TOOLS) set(BCOND_UTILS "%bcond_without") else(USE_ADMIN_TOOLS) set(BCOND_UTILS "%bcond_with") endif(USE_ADMIN_TOOLS) if(USE_GUI_ADMIN_TOOLS) set(BCOND_GUI_UTILS "%bcond_without") else(USE_GUI_ADMIN_TOOLS) set(BCOND_GUI_UTILS "%bcond_with") endif(USE_GUI_ADMIN_TOOLS) if (USE_SYSTEM_NTIRPC) set(BCOND_NTIRPC "%bcond_without") else(USE_SYSTEM_NTIRPC) set(BCOND_NTIRPC "%bcond_with") endif(USE_SYSTEM_NTIRPC) if (USE_MAN_PAGE) set(BCOND_MAN_PAGE "%bcond_without") else(USE_MAN_PAGE) set(BCOND_MAN_PAGE "%bcond_with") endif(USE_MAN_PAGE) if(USE_RADOS_RECOV) set(BCOND_RADOS_RECOV "%bcond_without") else(USE_RADOS_RECOV) set(BCOND_RADOS_RECOV "%bcond_with") endif(USE_RADOS_RECOV) if(RADOS_URLS) set(BCOND_RADOS_URLS "%bcond_without") else(RADOS_URLS) set(BCOND_RADOS_URLS "%bcond_with") endif(RADOS_URLS) if(RPCBIND) set(BCOND_RPCBIND "%bcond_without") else(RPCBIND) set(BCOND_RPCBIND "%bcond_with") endif(RPCBIND) if(USE_UNWIND) set(BCOND_UNWIND "%bcond_without") else(USE_UNWIND) set(BCOND_UNWIND "%bcond_with") endif(USE_UNWIND) if(_MSPAC_SUPPORT) set(BCOND_MSPAC_SUPPORT "%bcond_without") else(_MSPAC_SUPPORT) set(BCOND_MSPAC_SUPPORT "%bcond_with") endif(_MSPAC_SUPPORT) if(SANITIZE_ADDRESS) set(BCOND_SANITIZE_ADDRESS "%bcond_without") else(SANITIZE_ADDRESS) set(BCOND_SANITIZE_ADDRESS "%bcond_with") endif(SANITIZE_ADDRESS) if(USE_LEGACY_PYTHON_INSTALL) set(BCOND_LEGACY_PYTHON_INSTALL "%bcond_without") else(USE_LEGACY_PYTHON_INSTALL) set(BCOND_LEGACY_PYTHON_INSTALL "%bcond_with") endif(USE_LEGACY_PYTHON_INSTALL) # Now create a usable specfile configure_file( "${PROJECT_SOURCE_DIR}/nfs-ganesha.spec-in.cmake" "${PROJECT_SOURCE_DIR}/nfs-ganesha.spec" ) configure_file( "${PROJECT_SOURCE_DIR}/scripts/systemd/nfs-ganesha-config.service-in.cmake" "${PROJECT_SOURCE_DIR}/scripts/systemd/nfs-ganesha-config.service" ) # Make a docker image set(DOCKER_TMP_INSTALL_PATH "${PROJECT_BINARY_DIR}/docker/root") find_package(LSB_release) if(LSB_RELEASE_EXECUTABLE) string(TOLOWER ${LSB_RELEASE_ID_SHORT} DOCKER_DISTRO) set(DOCKER_DISTRO_VERSION "${LSB_RELEASE_RELEASE_SHORT}") configure_file( "${PROJECT_SOURCE_DIR}/scripts/docker/entrypoint.sh-in.cmake" "${PROJECT_BINARY_DIR}/docker/entrypoint.sh" @ONLY ) configure_file( "${PROJECT_SOURCE_DIR}/scripts/docker/Dockerfile-in.cmake" "${PROJECT_BINARY_DIR}/docker/Dockerfile" @ONLY ) add_custom_target(docker COMMAND sh -c "make DESTDIR=${PROJECT_BINARY_DIR}/docker/root install" COMMAND sh -c "docker build -t ganesha/dev ${PROJECT_BINARY_DIR}/docker" VERBATIM ) add_dependencies(docker ganesha.nfsd ) endif(LSB_RELEASE_EXECUTABLE) if(COVERAGE) find_program(LCOV_EXEC lcov) find_program(GENHTML_EXEC genhtml) if(LCOV_EXEC AND GENHTML_EXEC) add_custom_target(lcov) add_custom_command(TARGET lcov COMMAND ${LCOV_EXEC} --capture --directory . --output-file coverage.info COMMAND ${GENHTML_EXEC} coverage.info --output-directory ./coverage_html/ VERBATIM WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) endif(LCOV_EXEC AND GENHTML_EXEC) endif(COVERAGE) ########### next target ############### add_executable(ganesha.nfsd ./MainNFSD/nfs_main.c) add_sanitizers(ganesha.nfsd) target_link_libraries(ganesha.nfsd ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${SYSTEM_LIBRARIES} ${LTTNG_LIBRARIES} ${LDFLAG_DISALLOW_UNDEF} ${MONITORING_LIBRARIES} ) set_target_properties(ganesha.nfsd PROPERTIES COMPILE_FLAGS "-fPIC") install(TARGETS ganesha.nfsd COMPONENT daemon DESTINATION bin) ########### next target ############### if (USE_RADOS_RECOV) add_executable(ganesha-rados-grace ./tools/ganesha-rados-grace.c ./support/rados_grace.c) add_sanitizers(ganesha-rados-grace) include_directories(${RADOS_INCLUDE_DIR}) target_link_libraries(ganesha-rados-grace ganesha_nfsd ${RADOS_LIBRARIES} ${LDFLAG_DISALLOW_UNDEF} ) install(TARGETS ganesha-rados-grace COMPONENT tools DESTINATION bin) endif(USE_RADOS_RECOV) ########### next target ############### if(LINUX) add_executable(sm_notify.ganesha ./Protocols/NLM/sm_notify.c) add_sanitizers(sm_notify.ganesha) target_link_libraries(sm_notify.ganesha ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${SYSTEM_LIBRARIES} ${LTTNG_LIBRARIES} ${LDFLAG_DISALLOW_UNDEF} ) if( USE_ADMIN_TOOLS ) install(TARGETS sm_notify.ganesha COMPONENT daemon DESTINATION bin) endif( USE_ADMIN_TOOLS ) endif(LINUX) ########### next target ############### add_executable(verif_syntax EXCLUDE_FROM_ALL ./config_parsing/verif_syntax.c) add_sanitizers(verif_syntax) target_link_libraries(verif_syntax ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${SYSTEM_LIBRARIES} ${LTTNG_LIBRARIES} ${LDFLAG_DISALLOW_UNDEF} ) ########### next target ############### add_executable(test_parsing EXCLUDE_FROM_ALL ./config_parsing/test_parse.c) add_sanitizers(test_parsing) target_link_libraries(test_parsing ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${SYSTEM_LIBRARIES} ${LTTNG_LIBRARIES} ${LDFLAG_DISALLOW_UNDEF} ) ########### add a "make doc" target to call Doxygen find_package(Doxygen) if(DOXYGEN_FOUND) configure_file(${GANESHA_TOP_CMAKE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) add_custom_target(doc ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) endif(DOXYGEN_FOUND) nfs-ganesha-6.5/src/COMPILING_HOWTO.txt000066400000000000000000000136141473756622300174330ustar00rootroot00000000000000 Building nfs-ganesha From Source ================================ This HOWTO covers what is necessary to download, configure, and build the nfs-ganesha NFS server software. There are two major components to the software, nfs-ganesha itself, and the libntirpc library which is used for the transport layer. The server and the libntirpc library are in separate git repositories. For the build, the libntirpc source is a 'git submodule' of nfs-ganesha. The first step in building is to download (clone) the nfs-ganesha repository from its official location on github.com into the place in my home directory where I keep my git trees. $ cd ~/git $ git clone --recursive https://github.com/nfs-ganesha/nfs-ganesha.git Note the '--recursive' option which clones and initializes the submodule for libntirpc. If you do not use this option, you will have to use the git submodule command below before building. If you forget, the 'cmake' step below will terminate with an error message reminding you that you forgot. The git submodule ----------------- If you are building from a release source archive rather than from git, skip to the next section. However, if you are building from git, you must initialize the submodule after clone and after pulling a new update by going to the root of your repository and typing git submodule update --init Also, please note that the current HEAD of the ntirpc directory is a piece of tracked state. Please do not commit a change to the state unintentionally. NOTE: The submodule can get out of step with the main project. This can occur if you make a change to the nfs-ganesha working directory such as doing a checkout of another branch or a rebase of your work from an upstream update. In these cases you must do a 'git update' to make sure you are building nfs-ganesha with the correct version of libntirpc. CMAKE Build Instructions ------------------------ Cmake can and does prefer to build out- of-source. In other words, your build tree is over here and your git source tree is over there. The Makefiles are created by Cmake in the build tree, the objects and targets are in the build tree but the source is referenced "over there". For example, in a Ganesha build, we would do: $ cd some-build-sandbox $ rm -rf build_dir; mkdir build_dir $ cd build_dir $ cmake ~/git/nfs-ganesha/src $ make $ make install This gets the build completely away from where the git repo is. Note that I have thoroughly scrubbed the area before doing the build. You can also build in-tree but this litters the git repo with extra files just like autotools. See the Cmake manual for the details and restrictions. Building is a two step process. You first run Cmake to configure the build and then you do a conventional Make. You can do iterative development by editing files, including Cmake files (CMakeLists.txt) in the source tree and go back to the build directory and do a "make". The makefile will do the right thing and re-run Cmake if you messed with configuration files. Your configuration and build parameters are preserved in the build tree so you only have to do the full configuration step once. Unlike autotools where the build and source are in the same tree, having a separate build area allows you to do a couple of thing safely. * You can delete the whole build tree at any time. Simply repeat the configuration step and you get it all back. Your source is safely somewhere else. Be aware of which window/terminal you are in before doing an "rm -rf" however. Yes, I did that once so now I have the windows on separate monitors... * You can easily build multiple configurations. Simply create one build directory, enter it, and run Cmake with one set of parameters. Repeat in another build directory with a different set of parameters. Nice. System-wide NTIRPC ------------------ libntirpc may be installed separately on the system, and ganesha build with the -DUSE_SYSTEM_NTIRPC option. This does not require the submodule, and will not build ntirpc. Configuration Tweaking ---------------------- Cmake allows the setting of configuration parameters from the command line. You would use this in a similar way to how autotools works. You can discover what you can tweak by doing the following: $ mkdir tweaktest; cd tweaktest $ cmake -i ~/git/nfs-ganesha/src This will enter you into a "wizard" configuration (no fancy GUI stuff). Simply step through the configuration and note what knobs and switches are available and what their defaults are. After this, you can explicitly change parameters from the command line. For example: $ mkdir mybuild; cd mybuild $ cmake -D_USE_9P=OFF -D_HANDLE_MAPPING=ON -DALLOCATOR=tcmalloc \ ~/git/nfs-ganesha/src This will disable a 9P build, use handle mapping in the PROXY fsal and pick the tcmalloc allocator. There are two other variables of interest: CMAKE_BUILD_TYPE This is a global setting for the type of build. See the Cmake documentation for what this means. I have added a "Maintainer" type which forces strict compiles. It is what I intend to use on builds. BUILD_CONFIG This setting triggers the loading of a file in src/cmake/build_configurations. This is useful for having a "canned" configuration. There is only one file currently in use which will turn on every option available. Put these together and you get the build I use for merge testing: $ cmake -DCMAKE_BUILD_TYPE=Maintainer -DBUILD_CONFIG=everything \ ~/git/nfs-ganesha/src Look at src/cmake/build_configuration/everything.cmake to see what this turns on. If you want a custom, for say just a 9P server or only some features, create a file on the model of everything.cmake in that directory and then reference it on the command line. This eliminates the various shell scripts we have laying around... I stole this from the Mysql build where they use this trick to have things like 'redhat.cmake' and 'debian.cmake'. nfs-ganesha-6.5/src/CONTRIBUTING_HOWTO.txt000066400000000000000000000271331473756622300200220ustar00rootroot00000000000000Contributing and Using Git Paul Sheer - 18 October 2012 Dominique Martinet - 2015-02-26 Frank Filz - 2015-08-12 If you would like to contribute to nfs-ganesha development: There is a process to dumping the source (using git), modifying the source, and pushing your changes back. This process is quite simple and requires only a few commands. These are as follows: Establish from the other developers which branch of whose repository is the best to work in. Create an account on github.com, login and view that developers repository. Click "Fork" on the top right to create a copy of that repository. Let's say your name is "Paul Sheer" (replace "paul", "p_sheer" and "paulsheer" with your own name below), and say the the developer who owns the repository is named Code Leader and his github.com login is "codeleader". Now let's say the code branch is "new-unstable-dev-project". In addition to a Github account, you also need a linked Gerrithub account to push and review changes. Sign up for an account on gerrithub.io by linking it to your Github account. To update the content of the Wiki pages on github.com, you also need to be a member of nfs-ganesha organisation on Github. If you'd like to help keeping the documentation up to date, ask the developers for an invite. It's not required for code contributions. FETCHING THE CODE: First you need to fetch the code you wish to work on: git clone https://github.com/codeleader/nfs-ganesha.git --branch new-branch cd nfs-ganesha The current (as of 2019-02-07) branch for development is: git clone https://github.com/nfs-ganesha/nfs-ganesha.git --branch next Now check what you have: cd nfs-ganesha git status Now visit COMPILING_HOWTO.txt for instructions on building and running. Now fetch the latest updates: git remote update You will also need to pull in the libntirpc submodule: git submodule update --init TESTING Patches should be tested before submission. Some tests that are easy to run are: (Use src/test/run_test_mode.sh to run Ganesha for testing) Connectathon (cthon04) https://wiki.linux-nfs.org/wiki/index.php/Connectathon_test_suite Ideally should be run for both NFSv3 and NFSv4.x even if you don't support both. pynfs https://wiki.linux-nfs.org/wiki/index.php/Pynfs The pynfs tests must be run for both 4.0 and 4.1, the 4.1 tests do not include a lot of the basic functionality that is tested by 4.0. There are also some Ganesha specific tests. I (Frank Filz - maintainer) personally run: ./testserver.py -v --outfile /home/ffilz/ganesha/pynfs-results/TESTOUT.nfs4.0.all-ganesha-RD12-LOCK8c.2024-08-23.log --maketree 127.0.0.1:/export/test1/pynfs --showomit --secure --rundeps all ganesha RD12 LOCK8c and ./testserver.py -v --outfile /home/ffilz/ganesha/pynfs-results/TESTOUT.nfs4.1.all-ganesha.2024-08-23.log --maketree 127.0.0.1:/export/test1/pynfs --showomit --rundeps all ganesha As of V6.0 I get the following non-success results when run against FSAL_VFS: For 4.0: ************************************************** GSS2 st_gss.testInconsistentGssSeqnum : OMIT Dependency function _using_gss failed GSS6 st_gss.testHighSeqNum : OMIT Dependency function _using_gss failed GSS5 st_gss.testBadVersion : OMIT Dependency function _using_gss failed GSS3 st_gss.testBadVerfChecksum : OMIT Dependency function _using_gss failed GSS8 st_gss.testBadService : OMIT Dependency function _using_gss failed GSS7 st_gss.testBadProcedure : OMIT Dependency function _using_gss failed GSS1 st_gss.testBadGssSeqnum : OMIT Dependency function _using_gss failed GSS4 st_gss.testBadDataChecksum : OMIT Dependency function _using_gss failed WRT18 st_write.testChangeGranularityWrite : FAILURE consecutive SETATTR(mode)'s don't all change change attribute SATT9 st_setattr.testNonUTF8 : UNSUPPORTED FATTR4_MIMETYPE not supported RD5a st_read.testVeryLargeOffset : FAILURE Reading file /b'export/test1/pynfs/tree/file' should return NFS4_OK, instead got NFS4ERR_FBIG LOCK8c st_lock.testNonzeroLockSeqid : WARNING LOCK with newlockowner's lockseqid=1 should return NFS4ERR_BAD_SEQID, instead got NFS4_OK LOCK20 st_lock.testBlockTimeout : FAILURE Grabbing lock after another owner let his 'turn' expire should return NFS4_OK, instead got NFS4ERR_DENIED ACL10 st_acl.testLargeACL : OMIT Dependency ACL0 st_acl.testACLsupport had status UNSUPPORTED. ACL0 st_acl.testACLsupport : UNSUPPORTED FATTR4_ACL not supported ACL5 st_acl.testACL : OMIT Dependency ACL0 st_acl.testACLsupport had status UNSUPPORTED. ************************************************** Command line asked for 614 of 679 tests Of those: 10 Skipped, 3 Failed, 3 Warned, 598 Passed For 4.1: ************************************************** SEQ6 st_sequence.testRequestTooBig : FAILURE OP_LOOKUP should return NFS4ERR_REQ_TOO_BIG, instead got NFS4ERR_NAMETOOLONG CSESS21 st_create_session.testCallbackVersion : FAILURE No CB_NULL sent CSESS20 st_create_session.testCallbackProgram : FAILURE No CB_NULL sent EID9 st_exchange_id.testLeasePeriod : FAILURE OP_CREATE_SESSION should return NFS4ERR_STALE_CLIENTID, instead got NFS4_OK ************************************************** Command line asked for 179 of 262 tests Of those: 0 Skipped, 4 Failed, 0 Warned, 175 Passed There are some other tests mentioned on the Linux NFS Wiki: https://wiki.linux-nfs.org/wiki/index.php/Main_Page I also have two versions of the pjdfstests that have been modified to run better against Ganesha: https://github.com/ffilz/pjdfstest https://github.com/ffilz/ntfs-3g-pjd-fstest RUNNING GANESHA FOR TESTING Use src/test/run_test_mode.sh to run Ganesha for basic testing. PREPARING FOR PATCH SUBMISSION: We also have several commit hooks, please install them: src/scripts/git_hooks/install_git_hooks.sh If you haven't already, generate ssh keys and install them on Github and Gerrithub: ssh-keygen cat ~/.ssh/id_rsa.pub Copy the output into: Your Github account in "Settings" -> "SSH and GPG keys". Your Gerrithub account in "Settings" -> "SSH Keys". Also update your "Full Name" in Gerrithub: "Settings" -> "Profile". Install the Gerrithub change id commit hook scp -p -P 29418 USERNAMEHERE@review.gerrithub.io:hooks/commit-msg .git/hooks/ You may also want to do the following so you don't have to always provide your identity: git config user.name "Your Real Name" git config user.email "you@some.place" You can use the --global option to set these for all your git repos. Now do your development work. When you are developing, testing etc.: git commit --signoff -a There may be updates from other developers. Update to their new branch "more-stable-dev-project" to include other work other people may have done: git rebase origin/more-stable-dev-project SUBMITTING PATCHES: To push to the project, we now use Gerrithub for code submission and review. First, when creating a Gerrithub account (first time sign-in), do NOT copy your ganesha repo unless you plan to receive changes from someone else. It makes NO sense to push changes to your own repo (except for the gatekeeper) Now you have an account, you want to push some patch for us to review to ffilz/nfs-ganesha - you need to start by adding a new remote. You have a list of targets (anonymous http, ssh, http) to add on the project page: https://review.gerrithub.io/#/admin/projects/ffilz/nfs-ganesha If your network allows it, ssh is easier. http needs you to setup a generated password (settings page, "HTTP Password" menu on the left) Also make sure you use https (not http) so taking an example: git remote add gerrit ssh://USERNAMEHERE@review.gerrithub.io:29418/ffilz/nfs-ganesha git fetch gerrit git log gerrit/next..HEAD This should ONLY list the commits you want to push! Also, all should have a Change-Id. Finally push your patches to Gerrithub: git push gerrit HEAD:refs/for/next That's it. If you edit a commit, just push it again, gerrit will notice it has the same Change-Id and update your change with a new patch set. Please make sure you don't change the Change-Id if you update the patch otherwise this creates disconnected review. To re-use a Change-Id, add the --amend option: git commit --amend If you want specific people to review your code, please go to Gerrithub and add them. Please note that you can only push changes to another repo that you wrote, Gerrithub will check the mail address you're using and ask you to add any different mail address to your profile (settings -> Contact information). Well, just read the text if you get an error, it's usually clear enough :) PUSHING MULTIPLE PATCHES: If you have multiple patches to submit, you can either submit them separately to gerrithub, or you can submit them as a set of patches. If you have all the patches in a single branch on your github, the git push command above will submit them all as a group of patches. This is handy for the maintainer when merging because the patches may be pulled together. On the other hand, if you need to update any of the patches, you SHOULD push all of them again otherwise the maintainer risks not getting the latest version of the patches. For example, if you submitted 3 patches originally, and then only pushed updates to the first two, the 3rd patch which appears to be the "HEAD" when pulled from gerrithub will also pull the OLD versions of the first two patches. Pushing separate patches may make sense when they are truly independent but it does make more effort for the maintainer especially if they happen to conflict with each other and don't trivially merge. The maintainer appreciates when submitters resolve merge conflicts. For this reason you should also make sure your patches are based on the latest upstream next branch. If you are coordinating development with someone else, make sure one person pushes all the patches and has resolved any merge conflicts. gerrithub makes it easy to cherrypick patches, so if multiple independent patches have been submitted together and not all patches are ready for merge, the maintainer can simply cherrypick the patches that are ready for merge. PATCH UPDATES AND CHANGE ID: If you submit a new version of a patch, even if it's a very significant rewrite, please maintain the Change-Id. This allows gerrithub to keep all the comments and patch versions associated to better understand the history of the change. The multiple patch versions can easily be compared within gerrithub. If you do submit a new patch with a new Change-Id, perhaps because a completely different method is used to resolve the issue and is in a different section of code, make sure to abandon the old patch. Finally, if you're a new contributor and found a problem in this document, please submit fixes. Thank you! nfs-ganesha-6.5/src/Doxyfile.in000066400000000000000000002254121473756622300165050ustar00rootroot00000000000000# Doxyfile 1.8.1.2 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a 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 (" "). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config 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 # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or sequence of words) that should # identify the project. Note that if you do not use Doxywizard you need # to put quotes around the project name if it contains spaces. PROJECT_NAME = "NFS-Ganesha" # 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 = pre-2.0 # 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 = "An NFS server in userspace" # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # 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/doxygen # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 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 cause performance problems for the file system. CREATE_SUBDIRS = 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. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, # Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English # messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, # Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, # Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) 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. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) 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. 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" "the" ABBREVIATE_BRIEF = # 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. 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. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then 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. FULL_PATH_NAMES = YES # If the FULL_PATH_NAMES tag is set to YES then 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. 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 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 if your file system # doesn't support long names like on DOS, Mac, or CD-ROM. 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 # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = 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 comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) 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 behaviour. # 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 behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. 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. 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. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" 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:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding # "class=itcl::class" will allow you to use the command class in the # itcl::class meaning. TCL_SUBST = # 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. OPTIMIZE_OUTPUT_FOR_C = YES # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. 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. OPTIMIZE_OUTPUT_VHDL = 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++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. 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 that for custom extensions # you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. EXTENSION_MAPPING = h=C # If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all # comments according to the Markdown format, which allows for more readable # documentation. See http://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. MARKDOWN_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); v.s. # func(std::string) {}). This also makes the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip 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. 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 (the default) # will make doxygen 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. 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. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) 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. 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). INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and # unions with only public data 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 (the default), # structs, classes, and unions are shown on a separate page (for HTML and Man # pages) or section (for LaTeX and RTF). INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT 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. TYPEDEF_HIDES_STRUCT = YES # The SYMBOL_CACHE_SIZE determines the size of the internal cache use to # determine which symbols to keep in memory and which to flush to disk. # When the cache is full, less often used symbols will be written to disk. # For small to medium size projects (<1000 input files) the default value is # probably good enough. For larger projects a too small cache size can cause # doxygen to be busy swapping symbols to and from disk most of the time # causing a significant performance penalty. # If the system has enough physical memory increasing the cache will improve the # performance by keeping more symbols in memory. Note that the value works on # a logarithmic scale so increasing the size by one will roughly double the # memory usage. The cache size is given by this formula: # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols. SYMBOL_CACHE_SIZE = 0 # Similar to the SYMBOL_CACHE_SIZE 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 appear 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. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # 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 and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. 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. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When 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 (the default) only methods in the interface are included. 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 namespaces are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) 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. HIDE_UNDOC_MEMBERS = NO # 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 (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. 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 (the default) these blocks will be appended to the # function's detailed documentation block. 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 (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # 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. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) 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. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. 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 default) # the constructors will appear in the respective orders defined by # SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. # This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO # and ignored for detailed docs if SORT_MEMBER_DOCS is set to 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 default) # the group names will appear in their defined order. 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 default), 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. 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. 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. 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. 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. 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. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or macro consists of 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 initializer of individual variables and macros in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. 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. 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 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 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 , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. 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. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files # containing the references data. This must be a list of .bib files. The # .bib extension is automatically appended if omitted. Using this command # requires the bibtex tool to be installed. See also # http://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. 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 # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED 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. WARN_IF_UNDOCUMENTED = NO # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # The 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 (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = YES # 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) WARN_FORMAT = "$file:$line: $text" # 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 stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be 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. INPUT = @CMAKE_CURRENT_SOURCE_DIR@ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_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 the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh # *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py # *.f90 *.f *.for *.vhd *.vhdl FILE_PATTERNS = # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. 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 = @CMAKE_CURRENT_SOURCE_DIR@/tools @CMAKE_CURRENT_SOURCE_DIR@/testres-xml @CMAKE_CURRENT_SOURCE_DIR@/test @CMAKE_CURRENT_SOURCE_DIR@/scripts @CMAKE_CURRENT_SOURCE_DIR@/rpm @CMAKE_CURRENT_SOURCE_DIR@/HudsonCI @CMAKE_CURRENT_SOURCE_DIR@/example-fuse @CMAKE_CURRENT_SOURCE_DIR@/debian @CMAKE_CURRENT_SOURCE_DIR@/config_samples @CMAKE_CURRENT_SOURCE_DIR@/FSAL/FSAL_GPFS @CMAKE_CURRENT_SOURCE_DIR@/FSAL/FSAL_GLUSTER @CMAKE_CURRENT_SOURCE_DIR@/libntirpc # 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. 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, # AClass::ANamespace, ANamespace::*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 = # 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 = # 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. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are 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 , where # is the value of the INPUT_FILTER tag, and 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. 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 # info on how filters are used. If FILTER_PATTERNS is empty or if # non of the patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). 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 option only has effect when # FILTER_SOURCE_FILES is enabled. FILTER_SOURCE_PATTERNS = #--------------------------------------------------------------------------- # 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 also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C, C++ and Fortran comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES # then for each documented function all documented # functions referencing it will be listed. 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. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # 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. REFERENCES_LINK_SOURCE = 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 http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) 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. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # 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. ALPHABETICAL_INDEX = NO # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. 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. If left blank `html' will be used as the default path. 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). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. Note that when using a custom header you are responsible # for the proper inclusion of any scripts and style sheets that doxygen # needs, which is dependent on the configuration options used. # It is advised to generate a default header using "doxygen -w html # header.html footer.html stylesheet.css YourConfigFile" and then modify # that header. Note that the header is subject to change so you typically # have to redo this when upgrading to a newer version of doxygen or when # changing the value of configuration settings such as GENERATE_TREEVIEW! HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. 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 the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # style sheet in the HTML output directory as well, or it will be erased! HTML_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. HTML_EXTRA_FILES = # 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 colorwheel, # see http://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. # The allowed range is 0 to 359. 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 # grayscales only. A value of 255 will produce the most vivid colors. 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. 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 NO can help when comparing the output of multiple runs. HTML_TIMESTAMP = 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. HTML_DYNAMIC_SECTIONS = NO # 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. 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, 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 http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # 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. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, 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. DOCSET_BUNDLE_ID = org.doxygen.Project # When GENERATE_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. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, 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. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, 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. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, 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. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. 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. 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. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#namespace 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 # http://doc.trolltech.com/qthelpproject.html#virtual-folders QHP_VIRTUAL_FOLDER = doc # If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to # add. For more information please see # http://doc.trolltech.com/qthelpproject.html#custom-filters QHP_CUST_FILTER_NAME = # The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see # # Qt Help Project / Custom Filters. 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. QHP_SECT_FILTER_ATTRS = # If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can # be used to specify the location of Qt's qhelpgenerator. # If non-empty doxygen will try to run qhelpgenerator on the generated # .qhp file. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files # will be generated, which together with the HTML files, 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. 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. ECLIPSE_DOC_ID = org.doxygen.Project # The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) # at top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. Since the tabs have the same information as the # navigation tree you can set this option to NO if you already set # GENERATE_TREEVIEW 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. # Since the tree basically has the same information as the tab index you # could consider to set DISABLE_INDEX to NO when enabling this option. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values # (range [0,1..20]) 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. 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. TREEVIEW_WIDTH = 250 # When 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. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # 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. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are # not supported properly for IE 6.0, but are supported on all modern browsers. # Note that when changing this option you need to delete any form_*.png files # in the HTML output before the changes have effect. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax # (see http://www.mathjax.org) which uses client side Javascript for the # rendering instead of using prerendered 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. USE_MATHJAX = NO # 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 http://www.mathjax.org before deployment. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension # names that should be enabled during MathJax rendering. MATHJAX_EXTENSIONS = # 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. SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be # implemented using a PHP enabled web server instead of at the web client # using Javascript. Doxygen will generate the search PHP script and index # file to put on the web server. The advantage of the server # based approach is that it scales better to large projects and allows # full text search. The disadvantages are that it is more difficult to setup # and does not have live searching capabilities. SERVER_BASED_SEARCH = NO #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = YES # 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. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. # Note that when enabling USE_PDFLATEX this option is only used for # generating bitmaps for formulas in the HTML output, but not in the # Makefile that is written to the output directory. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = 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. 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, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = letter # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal 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. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # The LATEX_FOOTER tag can be used to specify a personal 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. Notice: only use this tag if you know what you are doing! LATEX_FOOTER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). 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. PDF_HYPERLINKS = YES # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. 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. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO # If LATEX_SOURCE_CODE is set to YES then doxygen will include # source code with syntax highlighting in the LaTeX output. # Note that which sources are shown also depends on other settings # such as SOURCE_BROWSER. LATEX_SOURCE_CODE = NO # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See # http://en.wikipedia.org/wiki/BibTeX for more info. LATEX_BIB_STYLE = plain #--------------------------------------------------------------------------- # 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 very pretty with # other RTF readers or editors. 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. If left blank `rtf' will be used as the default path. 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. 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 other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load style sheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages 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. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # 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 is NO. 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. 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. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # 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. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. 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. 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. 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. 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. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. 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 (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF 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. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # pointed to by INCLUDE_PATH will be searched when a #include is found. 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. 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. 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 # 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. PREDEFINED = # 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. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) 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, because these will confuse the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. 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. Note that 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. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. 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. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option also works with HAVE_DOT disabled, but it is recommended to # install and use dot, since it yields more powerful graphs. CLASS_DIAGRAMS = NO # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_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. 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, 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) HAVE_DOT = NO # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is # allowed to run in parallel. When set to 0 (the default) 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. DOT_NUM_THREADS = 0 # By default doxygen will use the Helvetica font for all dot files that # doxygen generates. When you want a differently looking font you can specify # the font name using DOT_FONTNAME. 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. DOT_FONTNAME = Helvetica # The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. # The default size is 10pt. DOT_FONTSIZE = 10 # By default doxygen will tell dot to use the Helvetica font. # If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to # set the path where dot can find it. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are 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. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies 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. 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. UML_LIMIT_NUM_FIELDS = 10 # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # 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. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are 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. CALL_GRAPH = NO # If the CALLER_GRAPH and HAVE_DOT tags are 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. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will generate a graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH and HAVE_DOT tags are 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. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are svg, png, jpg, or gif. # If left blank png will be used. 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). 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. 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. INTERACTIVE_SVG = NO # The tag DOT_PATH 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. 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). 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 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. 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. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, because dot on Windows does not # seem to support this out of the box. Warning: Depending on the platform used, # enabling this option may lead to badly anti-aliased labels on the edges of # a graph (i.e. they become hard to read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES 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. DOT_MULTI_TARGETS = YES # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES nfs-ganesha-6.5/src/FSAL/000077500000000000000000000000001473756622300151115ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/CMakeLists.txt000066400000000000000000000040661473756622300176570ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # Add the directory for stackable FSALs add_subdirectory(Stackable_FSALs) # Add the directory for Pseudo FSAL add_subdirectory(FSAL_PSEUDO) if(USE_FSAL_VFS OR USE_FSAL_LUSTRE OR USE_FSAL_XFS) add_subdirectory(FSAL_VFS) endif(USE_FSAL_VFS OR USE_FSAL_LUSTRE OR USE_FSAL_XFS) # All we need to do here is control the # build of chosen fsals if(USE_FSAL_PROXY_V4) add_subdirectory(FSAL_PROXY_V4) endif(USE_FSAL_PROXY_V4) if(USE_FSAL_PROXY_V3) add_subdirectory(FSAL_PROXY_V3) endif(USE_FSAL_PROXY_V3) if(USE_FSAL_CEPH) add_subdirectory(FSAL_CEPH) endif(USE_FSAL_CEPH) if(USE_FSAL_RGW) add_subdirectory(FSAL_RGW) endif(USE_FSAL_RGW) if(USE_FSAL_SAUNAFS) add_subdirectory(FSAL_SAUNAFS) endif(USE_FSAL_SAUNAFS) if(USE_FSAL_GPFS) add_subdirectory(FSAL_GPFS) endif(USE_FSAL_GPFS) if(USE_FSAL_GLUSTER) add_subdirectory(FSAL_GLUSTER) endif(USE_FSAL_GLUSTER) if(USE_FSAL_LIZARDFS) add_subdirectory(FSAL_LIZARDFS) endif(USE_FSAL_LIZARDFS) if(USE_FSAL_KVSFS) add_subdirectory(FSAL_KVSFS) endif(USE_FSAL_KVSFS) if(USE_FSAL_MEM) add_subdirectory(FSAL_MEM) endif(USE_FSAL_MEM) nfs-ganesha-6.5/src/FSAL/FSAL_CEPH/000077500000000000000000000000001473756622300164355ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_CEPH/CMakeLists.txt000066400000000000000000000033521473756622300212000ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- add_definitions( -D_FILE_OFFSET_BITS=64 ) SET(fsalceph_LIB_SRCS main.c export.c handle.c mds.c ds.c internal.c internal.h statx_compat.h ) if (NOT CEPH_FS_CEPH_STATX) SET(fsalceph_LIB_SRCS ${fsalceph_LIB_SRCS} statx_compat.c ) endif(NOT CEPH_FS_CEPH_STATX) message(STATUS "CEPHFS_INCLUDE_DIR = ${CEPHFS_INCLUDE_DIR}") include_directories(${CEPHFS_INCLUDE_DIR}) add_library(fsalceph MODULE ${fsalceph_LIB_SRCS}) add_sanitizers(fsalceph) target_link_libraries(fsalceph ganesha_nfsd ${CEPHFS_LIBRARIES} ${SYSTEM_LIBRARIES} ${LTTNG_LIBRARIES} ${LDFLAG_DISALLOW_UNDEF} ) set_target_properties(fsalceph PROPERTIES VERSION 4.2.0 SOVERSION 4) install(TARGETS fsalceph COMPONENT fsal DESTINATION ${FSAL_DESTINATION} ) nfs-ganesha-6.5/src/FSAL/FSAL_CEPH/ds.c000066400000000000000000000302551473756622300172140ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright © 2012 CohortFS, LLC. * Author: Adam C. Emerson * * This program 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 3 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 * 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 * * ------------- */ /** * @file ds.c * @author Adam C. Emerson * @date Mon Jul 30 12:29:22 2012 * * @brief pNFS DS operations for Ceph * * This file implements the read, write, commit, and dispose * operations for Ceph data-server handles. * * Also, creating a data server handle -- now called via the DS itself. */ #ifdef CEPH_PNFS #include "config.h" #include #include #include "fsal_api.h" #include "FSAL/fsal_commonlib.h" #include "../fsal_private.h" #include "fsal_up.h" #include "internal.h" #include "pnfs_utils.h" #define min(a, b) \ ({ \ typeof(a) _a = (a); \ typeof(b) _b = (b); \ _a < _b ? _a : _b; \ }) /** * @brief Local invalidate * * A shortcut method for invalidating inode attributes. It is * not sufficient to invalidate locally, but is immediate and * correct when the MDS and DS are colocated. */ static inline void local_invalidate(struct ds *ds, struct fsal_export *export) { struct gsh_buffdesc key = { .addr = &ds->wire.wire.vi, .len = sizeof(ds->wire.wire.vi) }; up_async_invalidate(general_fridge, export->up_ops, export->fsal, &key, MDCACHE_INVALIDATE_ATTRS, NULL, NULL); } /** * @brief Release a DS handle * * @param[in] ds_pub The object to release */ static void ds_handle_release(struct fsal_ds_handle *const ds_pub) { /* The private 'full' DS handle */ struct ds *ds = container_of(ds_pub, struct ds, ds); gsh_free(ds); } /** * @brief Read from a data-server handle. * * NFSv4.1 data server handles are disjount from normal * filehandles (in Ganesha, there is a ds_flag in the filehandle_v4_t * structure) and do not get loaded into mdcache or processed the * normal way. * * @param[in] ds_pub FSAL DS handle * @param[in] stateid The stateid supplied with the READ operation, * for validation * @param[in] offset The offset at which to read * @param[in] requested_length Length of read requested (and size of buffer) * @param[out] buffer The buffer to which to store read data * @param[out] supplied_length Length of data read * @param[out] eof True on end of file * * @return An NFSv4.1 status code. */ static nfsstat4 ds_read(struct fsal_ds_handle *const ds_pub, const stateid4 *stateid, const offset4 offset, const count4 requested_length, void *const buffer, count4 *const supplied_length, bool *const end_of_file) { /* The private 'full' export */ struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); /* The private 'full' DS handle */ struct ds *ds = container_of(ds_pub, struct ds, ds); /* The OSD number for this machine */ int local_OSD = 0; /* Width of a stripe in the file */ uint32_t stripe_width = 0; /* Beginning of a block */ uint64_t block_start = 0; /* Number of the stripe being read */ uint32_t stripe = 0; /* Internal offset within the stripe */ uint32_t internal_offset = 0; /* The amount actually read */ int amount_read = 0; /* Find out what my OSD ID is, so we can avoid talking to other OSDs. */ local_OSD = ceph_get_local_osd(export->cmount); if (local_OSD < 0) return posix2nfs4_error(-local_OSD); /* Find out what stripe we're writing to and where within the stripe. */ stripe_width = ds->wire.layout.fl_stripe_unit; stripe = offset / stripe_width; block_start = stripe * stripe_width; internal_offset = offset - block_start; if (local_OSD != ceph_ll_get_stripe_osd(export->cmount, ds->wire.wire.vi, stripe, &(ds->wire.layout))) { return NFS4ERR_PNFS_IO_HOLE; } amount_read = ceph_ll_read_block(export->cmount, ds->wire.wire.vi, stripe, buffer, internal_offset, min((stripe_width - internal_offset), requested_length), &(ds->wire.layout)); if (amount_read < 0) return posix2nfs4_error(-amount_read); *supplied_length = amount_read; *end_of_file = false; return NFS4_OK; } /** * * @brief Write to a data-server handle. * * This performs a DS write not going through the data server unless * FILE_SYNC4 is specified, in which case it connects the filehandle * and performs an MDS write. * * @param[in] ds_pub FSAL DS handle * @param[in] stateid The stateid supplied with the READ operation, * for validation * @param[in] offset The offset at which to read * @param[in] write_length Length of write requested (and size of buffer) * @param[out] buffer The buffer to which to store read data * @param[in] stability wanted Stability of write * @param[out] written_length Length of data written * @param[out] writeverf Write verifier * @param[out] stability_got Stability used for write (must be as * or more stable than request) * * @return An NFSv4.1 status code. */ static nfsstat4 ds_write(struct fsal_ds_handle *const ds_pub, const stateid4 *stateid, const offset4 offset, const count4 write_length, const void *buffer, const stable_how4 stability_wanted, count4 *const written_length, verifier4 *const writeverf, stable_how4 *const stability_got) { /* The private 'full' export */ struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); /* The private 'full' DS handle */ struct ds *ds = container_of(ds_pub, struct ds, ds); /* The OSD number for this host */ int local_OSD = 0; /* Width of a stripe in the file */ uint32_t stripe_width = 0; /* Beginning of a block */ uint64_t block_start = 0; /* Number of the stripe being written */ uint32_t stripe = 0; /* Internal offset within the stripe */ uint32_t internal_offset = 0; /* The amount actually written */ int32_t amount_written = 0; /* The adjusted write length, confined to one object */ uint32_t adjusted_write = 0; /* Return code from ceph calls */ int ceph_status = 0; memset(*writeverf, 0, NFS4_VERIFIER_SIZE); /* Find out what my OSD ID is, so we can avoid talking to other OSDs. */ local_OSD = ceph_get_local_osd(export->cmount); /* Find out what stripe we're writing to and where within the stripe. */ stripe_width = ds->wire.layout.fl_stripe_unit; stripe = offset / stripe_width; block_start = stripe * stripe_width; internal_offset = offset - block_start; if (local_OSD != ceph_ll_get_stripe_osd(export->cmount, ds->wire.wire.vi, stripe, &(ds->wire.layout))) { return NFS4ERR_PNFS_IO_HOLE; } adjusted_write = min((stripe_width - internal_offset), write_length); /* If the client specifies FILE_SYNC4, then we have to connect the filehandle and use the MDS to update size and access time. */ if (stability_wanted == FILE_SYNC4) { Fh *descriptor = NULL; if (!ds->connected) { ceph_status = ceph_ll_connectable_m( export->cmount, &ds->wire.wire.vi, ds->wire.wire.parent_ino, ds->wire.wire.parent_hash); if (ceph_status != 0) { LogMajor( COMPONENT_PNFS, "Filehandle connection failed with: %d\n", ceph_status); return posix2nfs4_error(-ceph_status); } ds->connected = true; } ceph_status = fsal_ceph_ll_open(export->cmount, ds->wire.wire.vi, O_WRONLY, &descriptor, &op_ctx->creds); if (ceph_status != 0) { LogMajor(COMPONENT_FSAL, "Open failed with: %d", ceph_status); return posix2nfs4_error(-ceph_status); } amount_written = ceph_ll_write(export->cmount, descriptor, offset, adjusted_write, buffer); if (amount_written < 0) { LogMajor(COMPONENT_FSAL, "Write failed with: %d", amount_written); ceph_ll_close(export->cmount, descriptor); return posix2nfs4_error(-amount_written); } ceph_status = ceph_ll_fsync(export->cmount, descriptor, 0); if (ceph_status < 0) { LogMajor(COMPONENT_FSAL "fsync failed with: %d", ceph_status); ceph_ll_close(export->cmount, descriptor); return posix2nfs4_error(-ceph_status); } ceph_status = ceph_ll_close(export->cmount, descriptor); if (ceph_status < 0) { LogMajor(COMPONENT_FSAL, "close failed with: %d", ceph_status); return posix2nfs4_error(-ceph_status); } /* invalidate client caches */ local_invalidate(ds, &export->export); *written_length = amount_written; *stability_got = FILE_SYNC4; } else { /* FILE_SYNC4 wasn't specified, so we don't have to bother with the MDS. */ amount_written = ceph_ll_write_block( export->cmount, ds->wire.wire.vi, stripe, (char *)buffer, internal_offset, adjusted_write, &(ds->wire.layout), ds->wire.snapseq, (stability_wanted == DATA_SYNC4)); if (amount_written < 0) return posix2nfs4_error(-amount_written); *written_length = amount_written; *stability_got = stability_wanted; } return NFS4_OK; } /** * @brief Commit a byte range to a DS handle. * * NFSv4.1 data server filehandles are disjount from normal * filehandles (in Ganesha, there is a ds_flag in the filehandle_v4_t * structure) and do not get loaded into mdcache or processed the * normal way. * * @param[in] ds_pub FSAL DS handle * @param[in] offset Start of commit window * @param[in] count Length of commit window * @param[out] writeverf Write verifier * * @return An NFSv4.1 status code. */ static nfsstat4 ds_commit(struct fsal_ds_handle *const ds_pub, const offset4 offset, const count4 count, verifier4 *const writeverf) { #ifdef COMMIT_FIX /* The private 'full' export */ struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); /* The private 'full' DS handle */ struct ds *ds = container_of(ds_pub, struct ds, ds); /* Error return from Ceph */ int rc = 0; /* Find out what stripe we're writing to and where within the stripe. */ rc = ceph_ll_commit_blocks(export->cmount, ds->wire.wire.vi, offset, (count == 0) ? UINT64_MAX : count); if (rc < 0) return posix2nfs4_error(rc); #endif /* COMMIT_FIX */ memset(*writeverf, 0, NFS4_VERIFIER_SIZE); LogCrit(COMPONENT_PNFS, "Commits should go to MDS\n"); return NFS4_OK; } /** * @brief Try to create a FSAL data server handle from a wire handle * * This function creates a FSAL data server handle from a client * supplied "wire" handle. This is also where validation gets done, * since PUTFH is the only operation that can return * NFS4ERR_BADHANDLE. * * @param[in] pds FSAL pNFS DS * @param[in] desc Buffer from which to create the file * @param[out] handle FSAL DS handle * * @return NFSv4.1 error codes. */ static nfsstat4 make_ds_handle(struct fsal_pnfs_ds *const pds, const struct gsh_buffdesc *const desc, struct fsal_ds_handle **const handle, int flags) { struct ds_wire *dsw = (struct ds_wire *)desc->addr; struct ds *ds; /* Handle to be created */ *handle = NULL; if (desc->len != sizeof(struct ds_wire)) return NFS4ERR_BADHANDLE; if (dsw->layout.fl_stripe_unit == 0) return NFS4ERR_BADHANDLE; ds = gsh_calloc(1, sizeof(struct ds)); *handle = &ds->ds; /* Connect lazily when a FILE_SYNC4 write forces us to, not here. */ ds->connected = false; memcpy(&ds->wire, desc->addr, desc->len); return NFS4_OK; } void pnfs_ds_ops_init(struct fsal_pnfs_ds_ops *ops) { memcpy(ops, &def_pnfs_ds_ops, sizeof(struct fsal_pnfs_ds_ops)); ops->make_ds_handle = make_ds_handle; ops->dsh_release = ds_handle_release; ops->dsh_read = ds_read; ops->dsh_write = ds_write; ops->dsh_commit = ds_commit; } #endif /* CEPH_PNFS */ nfs-ganesha-6.5/src/FSAL/FSAL_CEPH/export.c000066400000000000000000000341311473756622300201240ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright © 2012-2014, CohortFS, LLC. * Author: Adam C. Emerson * * contributeur : William Allen Simpson * Marcus Watts * * This program 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 3 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 * 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 * * ------------- */ /** * @file FSAL_CEPH/export.c * @author Adam C. Emerson * @author William Allen Simpson * @date Wed Oct 22 13:24:33 2014 * * @brief Implementation of FSAL export functions for Ceph * * This file implements the Ceph specific functionality for the FSAL * export handle. */ #include #include #include #include #include "abstract_mem.h" #include "fsal.h" #include "fsal_types.h" #include "fsal_api.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_config.h" #include "internal.h" #include "statx_compat.h" #include "sal_functions.h" #include "nfs_core.h" #ifdef CEPHFS_POSIX_ACL #include "nfs_exports.h" #endif /* CEPHFS_POSIX_ACL */ #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/fsal_ceph.h" #endif /* LTTNG_PARSING */ /** * @brief Clean up an export * * This function cleans up an export after the last reference is * released. * * @param[in,out] export The export to be released */ static void release(struct fsal_export *export_pub) { /* The private, expanded export */ struct ceph_export *export = container_of(export_pub, struct ceph_export, export); /* Ceph mount */ struct ceph_mount *cm = export->cm; deconstruct_handle(export->root); export->root = 0; fsal_detach_export(export->export.fsal, &export->export.exports); free_export_ops(&export->export); PTHREAD_RWLOCK_wrlock(&cmount_lock); /* Detach this export from the ceph_mount */ glist_del(&export->cm_list); if (--cm->cm_refcnt == 0) { /* This was the final reference */ ceph_shutdown(cm->cmount); ceph_mount_remove(&cm->cm_avl_mount); gsh_free(cm->cm_fs_name); gsh_free(cm->cm_mount_path); gsh_free(cm->cm_user_id); gsh_free(cm->cm_secret_key); gsh_free(cm); } else if (cm->cm_export == export) { /* Need to attach a different export for upcalls */ cm->cm_export = glist_first_entry(&cm->cm_exports, struct ceph_export, cm_list); } export->cmount = NULL; PTHREAD_RWLOCK_unlock(&cmount_lock); gsh_free(export->sec_label_xattr); gsh_free(export->fs_name); gsh_free(export->cmount_path); gsh_free(export->user_id); gsh_free(export->secret_key); gsh_free(export); export = NULL; } /** * @brief Return a handle corresponding to a path * * This function looks up the given path and supplies an FSAL object * handle. Because the root path specified for the export is a Ceph * style root as supplied to mount -t ceph of ceph-fuse (of the form * host:/path), we check to see if the path begins with / and, if not, * skip until we find one. * * @param[in] export_pub The export in which to look up the file * @param[in] path The path to look up * @param[out] pub_handle The created public FSAL handle * * @return FSAL status. */ static fsal_status_t lookup_path(struct fsal_export *export_pub, const char *path, struct fsal_obj_handle **pub_handle, struct fsal_attrlist *attrs_out) { /* The 'private' full export handle */ struct ceph_export *export = container_of(export_pub, struct ceph_export, export); /* The 'private' full object handle */ struct ceph_handle *handle = NULL; /* Inode pointer */ struct Inode *i = NULL; /* FSAL status structure */ fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; /* The buffer in which to store stat info */ struct ceph_statx stx; /* Return code from Ceph */ int rc, lmp; /* Find the actual path in the supplied path */ const char *realpath; if (*path != '/') { realpath = strchr(path, ':'); if (realpath == NULL) { status.major = ERR_FSAL_INVAL; return status; } if (*(++realpath) != '/') { status.major = ERR_FSAL_INVAL; return status; } } else { realpath = path; } *pub_handle = NULL; /* * sanity check: ensure that this is the right export. realpath * must be a superset of the export fullpath, or the string * handling will be broken. */ if (strstr(realpath, CTX_FULLPATH(op_ctx)) != realpath) { status.major = ERR_FSAL_SERVERFAULT; return status; } PTHREAD_RWLOCK_rdlock(&cmount_lock); lmp = strlen(export->cm->cm_mount_path); if (lmp == 1) { /* If cmount_path is "/" we need the leading '/'. */ lmp = 0; } /* Advance past the export's fullpath */ realpath += lmp; PTHREAD_RWLOCK_unlock(&cmount_lock); /* special case the root */ if (realpath[1] == '\0' || realpath[0] == '\0') { assert(export->root); *pub_handle = &export->root->handle; return status; } rc = fsal_ceph_ll_walk(export->cmount, realpath, &i, &stx, !!attrs_out, &op_ctx->creds); if (rc < 0) return ceph2fsal_error(rc); construct_handle(&stx, i, export, &handle); if (attrs_out != NULL) ceph2fsal_attributes(&stx, attrs_out); *pub_handle = &handle->handle; GSH_UNIQUE_AUTO_TRACEPOINT(fsal_ceph, ceph_create_handle, TRACE_DEBUG, "Created handle: {}", &handle->handle); return status; } /** * @brief Decode a digested handle * * This function decodes a previously digested handle. * * @param[in] exp_handle Handle of the relevant fs export * @param[in] in_type The type of digest being decoded * @param[out] fh_desc Address and length of key */ static fsal_status_t wire_to_host(struct fsal_export *exp_hdl, fsal_digesttype_t in_type, struct gsh_buffdesc *fh_desc, int flags) { struct ceph_host_handle *hhdl = fh_desc->addr; switch (in_type) { /* Digested Handles */ case FSAL_DIGEST_NFSV3: case FSAL_DIGEST_NFSV4: /* * Ganesha automatically mixes the export_id in with the * filehandle and strips that out before calling this * function. * * Note that we use a LE values in the filehandle. Earlier * versions treated those values as opaque, so this allows us to * maintain compatibility with legacy deployments (most of which * were on LE arch). */ hhdl->chk_ino = le64toh(hhdl->chk_ino); hhdl->chk_snap = le64toh(hhdl->chk_snap); hhdl->chk_fscid = le64toh(hhdl->chk_fscid); fh_desc->len = sizeof(*hhdl); break; default: return fsalstat(ERR_FSAL_SERVERFAULT, 0); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief extract "key" from a host handle * * This function extracts a "key" from a host handle. That is, when * given a handle that is extracted from wire_to_host() above, this * method will extract the unique bits used to index the inode cache. * * @param[in] exp_hdl Export handle * @param[in,out] fh_desc Buffer descriptor. The address of the * buffer is given in @c fh_desc->buf and must * not be changed. @c fh_desc->len is the length * of the data contained in the buffer, @c * fh_desc->len must be updated to the correct * size. In other words, the key has to be placed * at the beginning of the buffer! */ fsal_status_t host_to_key(struct fsal_export *exp_hdl, struct gsh_buffdesc *fh_desc) { struct ceph_handle_key *key = fh_desc->addr; /* * Ganesha automatically mixes the export_id in with the actual wire * filehandle and strips that out before transforming it to a host * handle. This method is called on a host handle which doesn't have * the export_id. * * Most FSALs don't factor in the export_id with the handle_key, * but we want to do that for FSAL_CEPH, primarily because we * want to do accesses via different exports via different cephx * creds. Mix the export_id back in here. */ key->export_id = op_ctx->ctx_export->export_id; fh_desc->len = sizeof(*key); return fsalstat(ERR_FSAL_NO_ERROR, 0); } #ifndef USE_FSAL_CEPH_LOOKUP_VINO static int ceph_ll_lookup_vino(struct ceph_mount_info *cmount, vinodeno_t vino, Inode **inode) { /* Check the cache first */ *inode = ceph_ll_get_inode(cmount, vino); if (*inode) return 0; /* * We can't look up snap inodes w/o ceph_ll_lookup_vino. Just * return -ESTALE if we get one that's not already in cache. */ if (vino.snapid.val != CEPH_NOSNAP) return -ESTALE; return ceph_ll_lookup_inode(cmount, vino.ino, inode); } #endif /* USE_FSAL_CEPH_LOOKUP_VINO */ /** * @brief Create a handle object from a wire handle * * The wire handle is given in a buffer outlined by desc, which it * looks like we shouldn't modify. * * @param[in] export_pub Public export * @param[in] desc Handle buffer descriptor (host handle) * @param[out] pub_handle The created handle * * @return FSAL status. */ static fsal_status_t create_handle(struct fsal_export *export_pub, struct gsh_buffdesc *desc, struct fsal_obj_handle **pub_handle, struct fsal_attrlist *attrs_out) { /* Full 'private' export structure */ struct ceph_export *export = container_of(export_pub, struct ceph_export, export); /* FSAL status to return */ fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; /* The FSAL specific portion of the handle received by the client */ struct ceph_host_handle *hhdl = desc->addr; /* Ceph return code */ int rc = 0; /* Stat buffer */ struct ceph_statx stx; /* Handle to be created */ struct ceph_handle *handle = NULL; /* Inode pointer */ struct Inode *i; vinodeno_t vi; *pub_handle = NULL; if (desc->len != sizeof(*hhdl)) { status.major = ERR_FSAL_INVAL; return status; } vi.ino.val = hhdl->chk_ino; vi.snapid.val = hhdl->chk_snap; rc = ceph_ll_lookup_vino(export->cmount, vi, &i); if (rc) return ceph2fsal_error(rc); rc = fsal_ceph_ll_getattr(export->cmount, i, &stx, attrs_out ? CEPH_STATX_ATTR_MASK : CEPH_STATX_HANDLE_MASK, &op_ctx->creds); if (rc < 0) return ceph2fsal_error(rc); construct_handle(&stx, i, export, &handle); if (attrs_out != NULL) ceph2fsal_attributes(&stx, attrs_out); *pub_handle = &handle->handle; GSH_UNIQUE_AUTO_TRACEPOINT(fsal_ceph, ceph_create_handle, TRACE_DEBUG, "Created handle: {}", &handle->handle); return status; } /** * @brief Get dynamic filesystem info * * This function returns dynamic filesystem information for the given * export. * * @param[in] export_pub The public export handle * @param[out] info The dynamic FS information * * @return FSAL status. */ static fsal_status_t get_fs_dynamic_info(struct fsal_export *export_pub, struct fsal_obj_handle *obj_hdl, fsal_dynamicfsinfo_t *info) { /* Full 'private' export */ struct ceph_export *export = container_of(export_pub, struct ceph_export, export); /* Return value from Ceph calls */ int rc = 0; /* Filesystem stat */ struct statvfs vfs_st; rc = ceph_ll_statfs(export->cmount, export->root->i, &vfs_st); if (rc < 0) return ceph2fsal_error(rc); memset(info, 0, sizeof(fsal_dynamicfsinfo_t)); info->total_bytes = vfs_st.f_frsize * vfs_st.f_blocks; info->free_bytes = vfs_st.f_frsize * vfs_st.f_bfree; info->avail_bytes = vfs_st.f_frsize * vfs_st.f_bavail; info->total_files = vfs_st.f_files; info->free_files = vfs_st.f_ffree; info->avail_files = vfs_st.f_favail; info->time_delta.tv_sec = 0; info->time_delta.tv_nsec = FSAL_DEFAULT_TIME_DELTA_NSEC; return fsalstat(ERR_FSAL_NO_ERROR, 0); } #ifdef CEPHFS_POSIX_ACL static fsal_aclsupp_t fs_acl_support(struct fsal_export *exp_hdl) { if (!op_ctx_export_has_option(EXPORT_OPTION_DISABLE_ACL)) return fsal_acl_support(&exp_hdl->fsal->fs_info); else return 0; } #endif /* CEPHFS_POSIX_ACL */ void ceph_prepare_unexport(struct fsal_export *export_pub) { struct ceph_export *export = container_of(export_pub, struct ceph_export, export); /* Flush all buffers */ ceph_sync_fs(export->cmount); #if USE_FSAL_CEPH_ABORT_CONN /* * If we're shutting down and are still a member of the cluster, do a * hard abort on the connection to ensure that state is left intact on * the MDS when we return. If we're not shutting down or aren't a * member any longer then cleanly tear down the export. */ if (admin_shutdown && nfs_grace_is_member()) ceph_abort_conn(export->cmount); #endif } /** * @brief Function to get the fasl_obj_handle that has fsal_fd as its global fd. * * @param[in] exp_hdl The export in which the handle exists * @param[in] fd File descriptor in question * @param[out] handle FSAL object handle * * @return the fsal_obj_handle. */ void get_fsal_obj_hdl(struct fsal_export *exp_hdl, struct fsal_fd *fd, struct fsal_obj_handle **handle) { struct ceph_fd *my_fd = NULL; struct ceph_handle *myself = NULL; my_fd = container_of(fd, struct ceph_fd, fsal_fd); myself = container_of(my_fd, struct ceph_handle, fd); *handle = &myself->handle; } /** * @brief Set operations for exports * * This function overrides operations that we've implemented, leaving * the rest for the default. * * @param[in,out] ops Operations vector */ void export_ops_init(struct export_ops *ops) { ops->prepare_unexport = ceph_prepare_unexport; ops->release = release; ops->lookup_path = lookup_path; ops->host_to_key = host_to_key; ops->wire_to_host = wire_to_host; ops->create_handle = create_handle; ops->get_fs_dynamic_info = get_fs_dynamic_info; ops->alloc_state = ceph_alloc_state; ops->get_fsal_obj_hdl = get_fsal_obj_hdl; #ifdef CEPHFS_POSIX_ACL ops->fs_acl_support = fs_acl_support; #endif /* CEPHFS_POSIX_ACL */ #ifdef CEPH_PNFS export_ops_pnfs(ops); #endif /* CEPH_PNFS */ } nfs-ganesha-6.5/src/FSAL/FSAL_CEPH/handle.c000066400000000000000000003046121473756622300200420ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright © 2012, CohortFS, LLC. * Author: Adam C. Emerson * * This program 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 3 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 * 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 * * ------------- */ /** * @file FSAL_CEPH/handle.c * @author Adam C. Emerson * @date Mon Jul 9 15:18:47 2012 * * @brief Interface to handle functionality * * This function implements the interfaces on the struct * fsal_obj_handle type. */ #include "config.h" #ifdef LINUX #include /* for makedev(3) */ #endif #include #include #include #include "fsal.h" #include "fsal_types.h" #include "fsal_convert.h" #include "fsal_api.h" #include "internal.h" #include "nfs_exports.h" #include "sal_data.h" #include "statx_compat.h" #include "nfs_core.h" #include "linux/falloc.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/fsal_ceph.h" #endif /* LTTNG_PARSING */ /* * If the inode has a mode that doesn't allow writes, then the follow-on * setxattr for setting the security context can fail. We set this flag * in op_ctx->fsal_private after a create to indicate that the setxattr * should be done as root. */ #define CEPH_SETXATTR_AS_ROOT ((void *)(-1UL)) /** * @brief Release an object * * This function destroys the object referred to by the given handle * * @param[in] obj_hdl The object to release * * @return FSAL status codes. */ static void ceph_fsal_release(struct fsal_obj_handle *obj_hdl) { /* The private 'full' handle */ struct ceph_handle *obj = container_of(obj_hdl, struct ceph_handle, handle); struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); if (obj_hdl->type == REGULAR_FILE) { fsal_status_t st; st = close_fsal_fd(obj_hdl, &obj->fd.fsal_fd, false); if (FSAL_IS_ERROR(st)) { LogCrit(COMPONENT_FSAL, "Could not close hdl 0x%p, status %s error %s(%d)", obj_hdl, fsal_err_txt(st), strerror(st.minor), st.minor); } } GSH_AUTO_TRACEPOINT(fsal_ceph, ceph_release, TRACE_DEBUG, "CEPH release handle. fileid: {}", obj_hdl->fileid); if (obj != export->root) deconstruct_handle(obj); } /** * @brief Look up an object by name * * This function looks up an object by name in a directory. * * @param[in] dir_pub The directory in which to look up the object. * @param[in] path The name to look up. * @param[out] obj_pub The looked up object. * * @return FSAL status codes. */ static fsal_status_t ceph_fsal_lookup(struct fsal_obj_handle *dir_pub, const char *path, struct fsal_obj_handle **obj_pub, struct fsal_attrlist *attrs_out) { /* Generic status return */ int rc = 0; /* Stat output */ struct ceph_statx stx; /* The private 'full' export */ struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); struct ceph_handle *dir = container_of(dir_pub, struct ceph_handle, handle); struct ceph_handle *obj = NULL; struct Inode *i = NULL; LogFullDebug(COMPONENT_FSAL, "Lookup %s", path); GSH_UNIQUE_AUTO_TRACEPOINT(fsal_ceph, ceph_lookup, TRACE_DEBUG, "Lookup. path: {}", TP_STR(path)); rc = fsal_ceph_ll_lookup(export->cmount, dir->i, path, &i, &stx, !!attrs_out, &op_ctx->creds); if (rc < 0) return ceph2fsal_error(rc); construct_handle(&stx, i, export, &obj); if (attrs_out != NULL) ceph2fsal_attributes(&stx, attrs_out); *obj_pub = &obj->handle; GSH_UNIQUE_AUTO_TRACEPOINT(fsal_ceph, ceph_lookup, TRACE_DEBUG, "Lookup. path: {}, handle: {}, ino: {}", TP_STR(path), &obj->handle, stx.stx_ino); return fsalstat(0, 0); } static int ceph_fsal_get_sec_label(struct ceph_handle *handle, struct fsal_attrlist *attrs) { int rc = 0; struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); if (FSAL_TEST_MASK(attrs->request_mask, ATTR4_SEC_LABEL) && op_ctx_export_has_option(EXPORT_OPTION_SECLABEL_SET)) { char label[NFS4_OPAQUE_LIMIT]; struct user_cred root_creds = {}; /* * It's possible that the user won't have permission to fetch * the xattrs, so use root creds to get them since it's * supposed to be part of the inode metadata. */ rc = fsal_ceph_ll_getxattr(export->cmount, handle->i, export->sec_label_xattr, label, NFS4_OPAQUE_LIMIT, &root_creds); if (rc < 0) { /* If there's no label then just do zero-length one */ if (rc != -ENODATA) goto out_err; rc = 0; } attrs->sec_label.slai_data.slai_data_len = rc; gsh_free(attrs->sec_label.slai_data.slai_data_val); if (rc > 0) { attrs->sec_label.slai_data.slai_data_val = gsh_memdup(label, rc); FSAL_SET_MASK(attrs->valid_mask, ATTR4_SEC_LABEL); } else { attrs->sec_label.slai_data.slai_data_val = NULL; FSAL_UNSET_MASK(attrs->valid_mask, ATTR4_SEC_LABEL); } } out_err: return rc; } /** * @brief Read a directory * * This function reads the contents of a directory (excluding . and * .., which is ironic since the Ceph readdir call synthesizes them * out of nothing) and passes dirent information to the supplied * callback. * * @param[in] dir_pub The directory to read * @param[in] whence The cookie indicating resumption, NULL to start * @param[in] dir_state Opaque, passed to cb * @param[in] cb Callback that receives directory entries * @param[out] eof True if there are no more entries * * @return FSAL status. */ static fsal_status_t ceph_fsal_readdir(struct fsal_obj_handle *dir_pub, fsal_cookie_t *whence, void *dir_state, fsal_readdir_cb cb, attrmask_t attrmask, bool *eof) { /* Generic status return */ int rc = 0; /* The private 'full' export */ struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); /* The private 'full' directory handle */ struct ceph_handle *dir = container_of(dir_pub, struct ceph_handle, handle); /* The director descriptor */ struct ceph_dir_result *dir_desc = NULL; /* Cookie marking the start of the readdir */ uint64_t start = 0; /* ceph_statx want mask */ unsigned int want = attrmask2ceph_want(attrmask); /* Return status */ fsal_status_t fsal_status = { ERR_FSAL_NO_ERROR, 0 }; /* local rfiles in target dir */ uint64_t rfiles = 0; rc = fsal_ceph_ll_opendir(export->cmount, dir->i, &dir_desc, &op_ctx->creds); if (rc < 0) return ceph2fsal_error(rc); if (whence != NULL) start = *whence; ceph_seekdir(export->cmount, dir_desc, start); while (!(*eof)) { struct ceph_statx stx; struct dirent de; struct Inode *i = NULL; rc = fsal_ceph_readdirplus(export->cmount, dir_desc, dir->i, &de, &stx, want, 0, &i, &op_ctx->creds); if (rc < 0) { fsal_status = ceph2fsal_error(rc); goto closedir; } else if (rc == 1) { struct ceph_handle *obj; struct fsal_attrlist attrs; enum fsal_dir_result cb_rc; /* skip . and .. */ if ((strcmp(de.d_name, ".") == 0) || (strcmp(de.d_name, "..") == 0)) { /* Deref inode here as we reference inode in * libcephfs readdir_r_cb. The other inodes * gets deref in deconstruct_handle. */ if (i != NULL) ceph_ll_put(export->cmount, i); continue; } construct_handle(&stx, i, export, &obj); fsal_prepare_attrs(&attrs, attrmask); ceph2fsal_attributes(&stx, &attrs); rc = ceph_fsal_get_sec_label(obj, &attrs); if (rc < 0) { fsal_status = ceph2fsal_error(rc); if (i != NULL) ceph_ll_put(export->cmount, i); goto closedir; } cb_rc = cb(de.d_name, &obj->handle, &attrs, dir_state, de.d_off); fsal_release_attrs(&attrs); /* Read ahead not supported by this FSAL. */ if (cb_rc >= DIR_READAHEAD) goto closedir; rfiles += 1; } else if (rc == 0) { *eof = true; } else { /* Can't happen */ abort(); } } GSH_AUTO_TRACEPOINT(fsal_ceph, ceph_readdir, TRACE_DEBUG, "Readdir. fileid: {}, rfiles: {}", dir_pub->fileid, rfiles); closedir: rc = ceph_ll_releasedir(export->cmount, dir_desc); if (rc < 0) fsal_status = ceph2fsal_error(rc); return fsal_status; } /** * @brief Create a directory * * This function creates a new directory. * * For support_ex, this method will handle attribute setting. The caller * MUST include the mode attribute and SHOULD NOT include the owner or * group attributes if they are the same as the op_ctx->cred. * * @param[in] dir_hdl Directory in which to create the * directory * @param[in] name Name of directory to create * @param[in] attrib Attributes to set on newly created * object * @param[out] new_obj Newly created object * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @note On success, @a new_obj has been ref'd * * @return FSAL status. */ static fsal_status_t ceph_fsal_mkdir(struct fsal_obj_handle *dir_hdl, const char *name, struct fsal_attrlist *attrib, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { /* Generic status return */ int rc = 0; /* The private 'full' export */ struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); /* The private 'full' directory handle */ struct ceph_handle *dir = container_of(dir_hdl, struct ceph_handle, handle); /* Stat result */ struct ceph_statx stx; mode_t unix_mode; /* Newly created object */ struct ceph_handle *obj = NULL; struct Inode *i = NULL; fsal_status_t status; LogFullDebug(COMPONENT_FSAL, "mode = %o uid=%d gid=%d", attrib->mode, (int)op_ctx->creds.caller_uid, (int)op_ctx->creds.caller_gid); unix_mode = fsal2unix_mode(attrib->mode) & ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export); rc = fsal_ceph_ll_mkdir(export->cmount, dir->i, name, unix_mode, &i, &stx, !!attrs_out, &op_ctx->creds); if (rc < 0) return ceph2fsal_error(rc); construct_handle(&stx, i, export, &obj); *new_obj = &obj->handle; /* We handled the mode above. */ FSAL_UNSET_MASK(attrib->valid_mask, ATTR_MODE); if (attrib->valid_mask) { /* Now per support_ex API, if there are any other attributes * set, go ahead and get them set now. * * Must use root creds to override some permissions checks * when the mode is not writeable (e.g. when setxattr'ing * security labels). */ op_ctx->fsal_private = CEPH_SETXATTR_AS_ROOT; status = (*new_obj)->obj_ops->setattr2(*new_obj, false, NULL, attrib); op_ctx->fsal_private = NULL; if (FSAL_IS_ERROR(status)) { /* Release the handle we just allocated. */ LogFullDebug(COMPONENT_FSAL, "setattr2 status=%s", fsal_err_txt(status)); (*new_obj)->obj_ops->release(*new_obj); *new_obj = NULL; } else if (attrs_out != NULL) { /* * We ignore errors here. The mkdir and setattr * succeeded, so we don't want to return error if the * getattrs fails. We'll just return no attributes * in that case. */ (*new_obj)->obj_ops->getattrs(*new_obj, attrs_out); } } else { status = fsalstat(ERR_FSAL_NO_ERROR, 0); if (attrs_out != NULL) { /* Since we haven't set any attributes other than what * was set on create, just use the stat results we used * to create the fsal_obj_handle. */ ceph2fsal_attributes(&stx, attrs_out); } } FSAL_SET_MASK(attrib->valid_mask, ATTR_MODE); GSH_AUTO_TRACEPOINT(fsal_ceph, ceph_mkdir, TRACE_DEBUG, "MKdir. name: {}, handle: {}, ino: {}", TP_STR(name), &obj->handle, stx.stx_ino); return status; } /** * @brief Create a special file * * This function creates a new special file. * * For support_ex, this method will handle attribute setting. The caller * MUST include the mode attribute and SHOULD NOT include the owner or * group attributes if they are the same as the op_ctx->cred. * * @param[in] dir_hdl Directory in which to create the object * @param[in] name Name of object to create * @param[in] nodetype Type of special file to create * @param[in] dev Major and minor device numbers for block or * character special * @param[in] attrib Attributes to set on newly created object * @param[out] new_obj Newly created object * * @note On success, @a new_obj has been ref'd * * @return FSAL status. */ static fsal_status_t ceph_fsal_mknode(struct fsal_obj_handle *dir_hdl, const char *name, object_file_type_t nodetype, struct fsal_attrlist *attrib, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { #ifdef USE_FSAL_CEPH_MKNOD /* Generic status return */ int rc = 0; /* The private 'full' export */ struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); /* The private 'full' directory handle */ struct ceph_handle *dir = container_of(dir_hdl, struct ceph_handle, handle); /* Newly opened file descriptor */ struct Inode *i = NULL; /* Status after create */ struct ceph_statx stx; mode_t unix_mode; dev_t unix_dev = 0; /* Newly created object */ struct ceph_handle *obj; fsal_status_t status; unix_mode = fsal2unix_mode(attrib->mode) & ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export); switch (nodetype) { case BLOCK_FILE: unix_mode |= S_IFBLK; unix_dev = makedev(attrib->rawdev.major, attrib->rawdev.minor); break; case CHARACTER_FILE: unix_mode |= S_IFCHR; unix_dev = makedev(attrib->rawdev.major, attrib->rawdev.minor); break; case FIFO_FILE: unix_mode |= S_IFIFO; break; case SOCKET_FILE: unix_mode |= S_IFSOCK; break; default: LogMajor(COMPONENT_FSAL, "Invalid node type in FSAL_mknode: %d", nodetype); return fsalstat(ERR_FSAL_INVAL, EINVAL); } rc = fsal_ceph_ll_mknod(export->cmount, dir->i, name, unix_mode, unix_dev, &i, &stx, !!attrs_out, &op_ctx->creds); if (rc < 0) return ceph2fsal_error(rc); construct_handle(&stx, i, export, &obj); *new_obj = &obj->handle; /* We handled the mode and rawdev above. */ FSAL_UNSET_MASK(attrib->valid_mask, ATTR_MODE | ATTR_RAWDEV); if (attrib->valid_mask) { /* Now per support_ex API, if there are any other attributes * set, go ahead and get them set now. */ op_ctx->fsal_private = CEPH_SETXATTR_AS_ROOT; status = (*new_obj)->obj_ops->setattr2(*new_obj, false, NULL, attrib); op_ctx->fsal_private = NULL; if (FSAL_IS_ERROR(status)) { /* Release the handle we just allocated. */ LogFullDebug(COMPONENT_FSAL, "setattr2 status=%s", fsal_err_txt(status)); (*new_obj)->obj_ops->release(*new_obj); *new_obj = NULL; } } else { status = fsalstat(ERR_FSAL_NO_ERROR, 0); if (attrs_out != NULL) { /* Since we haven't set any attributes other than what * was set on create, just use the stat results we used * to create the fsal_obj_handle. */ ceph2fsal_attributes(&stx, attrs_out); } } FSAL_SET_MASK(attrib->valid_mask, ATTR_MODE); GSH_AUTO_TRACEPOINT( fsal_ceph, ceph_mknod, TRACE_DEBUG, "Mknode. name: {}, node type: {}, handle: {}, ino: {}", TP_STR(name), nodetype, &obj->handle, stx.stx_ino); return status; #else return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); #endif } /** * @brief Create a symbolic link * * This function creates a new symbolic link. * * For support_ex, this method will handle attribute setting. The caller * MUST include the mode attribute and SHOULD NOT include the owner or * group attributes if they are the same as the op_ctx->cred. * * @param[in] dir_hdl Directory in which to create the object * @param[in] name Name of object to create * @param[in] link_path Content of symbolic link * @param[in] attrib Attributes to set on newly created * object * @param[out] new_obj Newly created object * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @note On success, @a new_obj has been ref'd * * @return FSAL status. */ static fsal_status_t ceph_fsal_symlink(struct fsal_obj_handle *dir_hdl, const char *name, const char *link_path, struct fsal_attrlist *attrib, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { /* Generic status return */ int rc = 0; /* The private 'full' export */ struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); /* The private 'full' directory handle */ struct ceph_handle *dir = container_of(dir_hdl, struct ceph_handle, handle); /* Stat result */ struct ceph_statx stx; struct Inode *i = NULL; /* Newly created object */ struct ceph_handle *obj = NULL; fsal_status_t status; rc = fsal_ceph_ll_symlink(export->cmount, dir->i, name, link_path, &i, &stx, !!attrs_out, &op_ctx->creds); if (rc < 0) return ceph2fsal_error(rc); construct_handle(&stx, i, export, &obj); *new_obj = &obj->handle; /* We handled the mode above. */ FSAL_UNSET_MASK(attrib->valid_mask, ATTR_MODE); if (attrib->valid_mask) { /* Now per support_ex API, if there are any other attributes * set, go ahead and get them set now. */ op_ctx->fsal_private = CEPH_SETXATTR_AS_ROOT; status = (*new_obj)->obj_ops->setattr2(*new_obj, false, NULL, attrib); op_ctx->fsal_private = NULL; if (FSAL_IS_ERROR(status)) { /* Release the handle we just allocated. */ LogFullDebug(COMPONENT_FSAL, "setattr2 status=%s", fsal_err_txt(status)); (*new_obj)->obj_ops->release(*new_obj); *new_obj = NULL; } } else { status = fsalstat(ERR_FSAL_NO_ERROR, 0); if (attrs_out != NULL) { /* Since we haven't set any attributes other than what * was set on create, just use the stat results we used * to create the fsal_obj_handle. */ ceph2fsal_attributes(&stx, attrs_out); } } FSAL_SET_MASK(attrib->valid_mask, ATTR_MODE); return status; } /** * @brief Retrieve the content of a symlink * * This function allocates a buffer, copying the symlink content into * it. * * @param[in] link_pub The handle for the link * @param[out] content_buf Buffdesc for symbolic link * @param[in] refresh true if the underlying content should be * refreshed. * * @return FSAL status. */ static fsal_status_t ceph_fsal_readlink(struct fsal_obj_handle *link_pub, struct gsh_buffdesc *content_buf, bool refresh) { /* Generic status return */ int rc = 0; /* The private 'full' export */ struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); /* The private 'full' directory handle */ struct ceph_handle *link = container_of(link_pub, struct ceph_handle, handle); /* Pointer to the Ceph link content */ char content[PATH_MAX]; rc = fsal_ceph_ll_readlink(export->cmount, link->i, content, PATH_MAX, &op_ctx->creds); if (rc < 0) return ceph2fsal_error(rc); /* XXX in Ceph through 1/2016, ceph_ll_readlink returns the * length of the path copied (truncated to 32 bits) in rc, * and it cannot exceed the passed buffer size */ content_buf->addr = gsh_strldup(content, MIN(rc, (PATH_MAX - 1)), &content_buf->len); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Freshen and return attributes * * This function freshens and returns the attributes of the given * file. * * @param[in] handle_pub Object to interrogate * * @return FSAL status. */ static fsal_status_t ceph_fsal_getattrs(struct fsal_obj_handle *handle_pub, struct fsal_attrlist *attrs) { /* Generic status return */ int rc = 0; /* The private 'full' export */ struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); /* The private 'full' directory handle */ struct ceph_handle *handle = container_of(handle_pub, struct ceph_handle, handle); /* Stat buffer */ struct ceph_statx stx; #ifdef CEPHFS_POSIX_ACL /* Object file type */ bool is_dir; #endif /* CEPHFS_POSIX_ACL */ rc = fsal_ceph_ll_getattr(export->cmount, handle->i, &stx, CEPH_STATX_ATTR_MASK, &op_ctx->creds); if (rc < 0) goto out_err; rc = ceph_fsal_get_sec_label(handle, attrs); if (rc < 0) goto out_err; #ifdef CEPHFS_POSIX_ACL if (attrs->request_mask & ATTR_ACL) { is_dir = (bool)(handle_pub->type == DIRECTORY); rc = ceph_get_acl(export, handle, is_dir, attrs); if (rc < 0) { LogDebug(COMPONENT_FSAL, "failed to get acl: %d", rc); goto out_err; } } #endif /* CEPHFS_POSIX_ACL */ GSH_AUTO_TRACEPOINT(fsal_ceph, ceph_getattrs, TRACE_DEBUG, "Getattrs. ino: {}, size: {}, mode: {}", stx.stx_ino, stx.stx_size, stx.stx_mode); ceph2fsal_attributes(&stx, attrs); return fsalstat(ERR_FSAL_NO_ERROR, 0); out_err: if (attrs->request_mask & ATTR_RDATTR_ERR) { /* Caller asked for error to be visible. */ attrs->valid_mask = ATTR_RDATTR_ERR; } return ceph2fsal_error(rc); } /** * @brief Create a hard link * * This function creates a link from the supplied file to a new name * in a new directory. * * @param[in] handle_pub File to link * @param[in] destdir_pub Directory in which to create link * @param[in] name Name of link * * @return FSAL status. */ static fsal_status_t ceph_fsal_link(struct fsal_obj_handle *handle_pub, struct fsal_obj_handle *destdir_pub, const char *name, struct fsal_attrlist *destdir_pre_attrs_out, struct fsal_attrlist *destdir_post_attrs_out) { /* Generic status return */ int rc = 0; /* The private 'full' export */ struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); /* The private 'full' object handle */ struct ceph_handle *handle = container_of(handle_pub, struct ceph_handle, handle); /* The private 'full' destination directory handle */ struct ceph_handle *destdir = container_of(destdir_pub, struct ceph_handle, handle); rc = fsal_ceph_ll_link(export->cmount, handle->i, destdir->i, name, &op_ctx->creds); if (rc < 0) return ceph2fsal_error(rc); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Rename a file * * This function renames a file, possibly moving it into another * directory. We assume most checks are done by the caller. * * @param[in] olddir_pub Source directory * @param[in] old_name Original name * @param[in] newdir_pub Destination directory * @param[in] new_name New name * @param[in,out] olddir_pre_attrs_out Optional attributes for olddir dir * before the operation. Should be atomic. * @param[in,out] olddir_post_attrs_out Optional attributes for olddir dir * after the operation. Should be atomic. * @param[in,out] newdir_pre_attrs_out Optional attributes for newdir dir * before the operation. Should be atomic. * @param[in,out] newdir_post_attrs_out Optional attributes for newdir dir * after the operation. Should be atomic. * * @return FSAL status. */ static fsal_status_t ceph_fsal_rename(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *olddir_pub, const char *old_name, struct fsal_obj_handle *newdir_pub, const char *new_name, struct fsal_attrlist *olddir_pre_attrs_out, struct fsal_attrlist *olddir_post_attrs_out, struct fsal_attrlist *newdir_pre_attrs_out, struct fsal_attrlist *newdir_post_attrs_out) { /* Generic status return */ int rc = 0; /* The private 'full' export */ struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); /* The private 'full' object handle */ struct ceph_handle *olddir = container_of(olddir_pub, struct ceph_handle, handle); /* The private 'full' destination directory handle */ struct ceph_handle *newdir = container_of(newdir_pub, struct ceph_handle, handle); rc = fsal_ceph_ll_rename(export->cmount, olddir->i, old_name, newdir->i, new_name, &op_ctx->creds); if (rc < 0) { /* * RFC5661, section 18.26.3 - renaming on top of a non-empty * directory should return NFS4ERR_EXIST. */ if (rc == -ENOTEMPTY) rc = -EEXIST; return ceph2fsal_error(rc); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Remove a name * * This function removes a name from the filesystem and possibly * deletes the associated file. Directories must be empty to be * removed. * * @param[in] dir_pub Parent directory * @param[in] name Name to remove * @param[in] obj_hdl The object being removed * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @return FSAL status. */ static fsal_status_t ceph_fsal_unlink(struct fsal_obj_handle *dir_pub, struct fsal_obj_handle *obj_pub, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { /* Generic status return */ int rc = 0; /* The private 'full' export */ struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); /* The private 'full' object handle */ struct ceph_handle *dir = container_of(dir_pub, struct ceph_handle, handle); LogFullDebug(COMPONENT_FSAL, "Unlink %s, I think it's a %s", name, object_file_type_to_str(obj_pub->type)); if (obj_pub->type != DIRECTORY) { rc = fsal_ceph_ll_unlink(export->cmount, dir->i, name, &op_ctx->creds); } else { rc = fsal_ceph_ll_rmdir(export->cmount, dir->i, name, &op_ctx->creds); } if (rc < 0) { LogDebug(COMPONENT_FSAL, "Unlink %s returned %s (%d)", name, strerror(-rc), -rc); return ceph2fsal_error(rc); } GSH_AUTO_TRACEPOINT(fsal_ceph, ceph_unlink, TRACE_DEBUG, "Unlink. name: {}, type: {}", TP_STR(name), obj_pub->type); return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t ceph_close_my_fd(struct ceph_fd *my_fd) { fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0); struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); if (my_fd->fd != NULL && my_fd->fsal_fd.openflags != FSAL_O_CLOSED) { int rc = ceph_ll_close(export->cmount, my_fd->fd); if (rc < 0) { /* * We expect -ENOTCONN errors on shutdown. Ignore * them so we don't spam the logs. */ if (rc == -ENOTCONN && admin_shutdown) rc = 0; status = ceph2fsal_error(rc); } my_fd->fd = NULL; my_fd->fsal_fd.openflags = FSAL_O_CLOSED; } else { status = fsalstat(ERR_FSAL_NOT_OPENED, 0); } return status; } /** * @brief Function to open an fsal_obj_handle's global file descriptor. * * @param[in] obj_hdl File on which to operate * @param[in] openflags New mode for open * @param[out] fd File descriptor that is to be used * * @return FSAL status. */ static fsal_status_t ceph_reopen_func(struct fsal_obj_handle *obj_hdl, fsal_openflags_t openflags, struct fsal_fd *fsal_fd) { struct ceph_handle *myself; struct ceph_fd *my_fd; int posix_flags = 0; Fh *fd; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; int rc; struct ceph_export *export; export = container_of(op_ctx->fsal_export, struct ceph_export, export); myself = container_of(obj_hdl, struct ceph_handle, handle); my_fd = container_of(fsal_fd, struct ceph_fd, fsal_fd); fsal2posix_openflags(openflags, &posix_flags); LogFullDebug(COMPONENT_FSAL, "my_fd->fd = %p openflags = %x, posix_flags = %x", my_fd->fd, openflags, posix_flags); rc = fsal_ceph_ll_open(export->cmount, myself->i, posix_flags, &fd, &op_ctx->creds); if (rc < 0) { LogFullDebug(COMPONENT_FSAL, "open failed with %s", strerror(-rc)); status = ceph2fsal_error(rc); } else { if (my_fd->fd != NULL) { /* File was previously open, close old fd */ LogFullDebug(COMPONENT_FSAL, "close failed with %s", strerror(-rc)); rc = ceph_ll_close(export->cmount, my_fd->fd); if (rc < 0) { LogFullDebug(COMPONENT_FSAL, "close failed with %s", strerror(-rc)); status = ceph2fsal_error(rc); /** @todo - what to do about error here... */ } } /* Save the file descriptor, make sure we only save the * open modes that actually represent the open file. */ LogFullDebug(COMPONENT_FSAL, "fd = %p, new openflags = %x", fd, openflags); my_fd->fd = fd; my_fd->fsal_fd.openflags = FSAL_O_NFS_FLAGS(openflags); } return status; } /** * @brief Function to close an fsal_obj_handle's global file descriptor. * * @param[in] obj_hdl File on which to operate * @param[in] fd File handle to close * * @return FSAL status. */ static fsal_status_t ceph_close_func(struct fsal_obj_handle *obj_hdl, struct fsal_fd *fd) { return ceph_close_my_fd(container_of(fd, struct ceph_fd, fsal_fd)); } /** * @brief Close a file * * This function closes a file, freeing resources used for read/write * access and releasing capabilities. * * @param[in] obj_hdl File to close * * @return FSAL status. */ static fsal_status_t ceph_fsal_close(struct fsal_obj_handle *obj_hdl) { fsal_status_t status; /* The private 'full' object handle */ struct ceph_handle *handle = container_of(obj_hdl, struct ceph_handle, handle); status = close_fsal_fd(obj_hdl, &handle->fd.fsal_fd, false); GSH_AUTO_TRACEPOINT(fsal_ceph, ceph_close, TRACE_DEBUG, "Unlink. fileid: {}", obj_hdl->fileid); return status; } void ceph_free_state(struct state_t *state) { struct ceph_fd *my_fd; my_fd = &container_of(state, struct ceph_state_fd, state)->ceph_fd; destroy_fsal_fd(&my_fd->fsal_fd); gsh_free(state); } /** * @brief Allocate a state_t structure * * Note that this is not expected to fail since memory allocation is * expected to abort on failure. * * @param[in] exp_hdl Export state_t will be associated with * @param[in] state_type Type of state to allocate * @param[in] related_state Related state if appropriate * * @returns a state structure. */ struct state_t *ceph_alloc_state(struct fsal_export *exp_hdl, enum state_type state_type, struct state_t *related_state) { struct state_t *state; struct ceph_fd *my_fd; state = init_state(gsh_calloc(1, sizeof(struct ceph_state_fd)), ceph_free_state, state_type, related_state); my_fd = &container_of(state, struct ceph_state_fd, state)->ceph_fd; init_fsal_fd(&my_fd->fsal_fd, FSAL_FD_STATE, op_ctx->fsal_export); my_fd->fd = NULL; return state; } /** * @brief Merge a duplicate handle with an original handle * * This function is used if an upper layer detects that a duplicate * object handle has been created. It allows the FSAL to merge anything * from the duplicate back into the original. * * The caller must release the object (the caller may have to close * files if the merge is unsuccessful). * * @param[in] orig_hdl Original handle * @param[in] dupe_hdl Handle to merge into original * * @return FSAL status. * */ static fsal_status_t ceph_fsal_merge(struct fsal_obj_handle *orig_hdl, struct fsal_obj_handle *dupe_hdl) { fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; if (orig_hdl->type == REGULAR_FILE && dupe_hdl->type == REGULAR_FILE) { /* We need to merge the share reservations on this file. * This could result in ERR_FSAL_SHARE_DENIED. */ struct ceph_handle *orig, *dupe; orig = container_of(orig_hdl, struct ceph_handle, handle); dupe = container_of(dupe_hdl, struct ceph_handle, handle); /* This can block over an I/O operation. */ status = merge_share(orig_hdl, &orig->share, &dupe->share); } return status; } static bool ceph_check_verifier_stat(struct ceph_statx *stx, fsal_verifier_t verifier) { uint32_t verf_hi, verf_lo; memcpy(&verf_hi, verifier, sizeof(uint32_t)); memcpy(&verf_lo, verifier + sizeof(uint32_t), sizeof(uint32_t)); LogFullDebug(COMPONENT_FSAL, "Passed verifier %" PRIx32 " %" PRIx32 " file verifier %" PRIx32 " %" PRIx32, verf_hi, verf_lo, (uint32_t)stx->stx_atime.tv_sec, (uint32_t)stx->stx_mtime.tv_sec); return stx->stx_atime.tv_sec == verf_hi && stx->stx_mtime.tv_sec == verf_lo; } static fsal_status_t ceph_open2_by_handle(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, fsal_verifier_t verifier, struct fsal_attrlist *attrs_out) { struct ceph_fd *my_fd = NULL; struct fsal_fd *fsal_fd; struct ceph_handle *myself; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; fsal_openflags_t old_openflags; bool truncated = openflags & FSAL_O_TRUNC; struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); myself = container_of(obj_hdl, struct ceph_handle, handle); if (state != NULL) my_fd = &container_of(state, struct ceph_state_fd, state) ->ceph_fd; else my_fd = &myself->fd; fsal_fd = &my_fd->fsal_fd; /* Indicate we want to do fd work (can't fail since not reclaiming) */ fsal_start_fd_work_no_reclaim(fsal_fd); old_openflags = my_fd->fsal_fd.openflags; if (state != NULL) { /* Prepare to take the share reservation, but only if we are * called with a valid state (if state is NULL the caller is a * stateless create such as NFS v3 CREATE and we're just going * to ignore share reservation stuff). */ /* Now that we have the mutex, and no I/O is in progress so we * have exclusive access to the share's fsal_fd, we can look at * its openflags. We also need to work the share reservation so * take the obj_lock. NOTE: This is the ONLY sequence where both * a work_mutex and the obj_lock are taken, so there is no * opportunity for ABBA deadlock. * * Note that we do hold the obj_lock over an open and a close * which is longer than normal, but the previous iteration of * the code held the obj lock (read granted) over whole I/O * operations... We don't block over I/O because we've assured * that no I/O is in progress or can start before proceeding * past the above while loop. */ PTHREAD_RWLOCK_wrlock(&obj_hdl->obj_lock); /* Now check the new share. */ status = check_share_conflict(&myself->share, openflags, false); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "check_share_conflict returned %s", fsal_err_txt(status)); goto exit; } } /* Check for a genuine no-op open. That means we aren't trying to * create, the file is already open in the same mode with the same * deny flags, and we aren't trying to truncate. In this case we want * to avoid bouncing the fd. In the case of JUST changing the deny mode * or an replayed exclusive create, we might bounce the fd when we could * have avoided that, but those scenarios are much less common. */ if (FSAL_O_NFS_FLAGS(openflags) == FSAL_O_NFS_FLAGS(old_openflags) && truncated == false && createmode == FSAL_NO_CREATE) { LogFullDebug(COMPONENT_FSAL, "no-op reopen2 my_fd->fd = %p openflags = %x", my_fd->fd, openflags); goto exit; } /* No share conflict, re-open the share fd */ status = ceph_reopen_func(obj_hdl, openflags, fsal_fd); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "ceph_reopen_func returned %s", fsal_err_txt(status)); goto exit; } /* Inserts to fd_lru only if open succeeds */ if (old_openflags == FSAL_O_CLOSED) { /* This is actually an open, need to increment * appropriate counter and insert into LRU. */ insert_fd_lru(fsal_fd); } else { /* Bump up the FD in fd_lru as it was already in fd lru. */ bump_fd_lru(fsal_fd); } if (createmode >= FSAL_EXCLUSIVE || (truncated && attrs_out)) { /* NOTE: won't come in here when called from ceph_reopen2... * truncated might be set, but attrs_out will be NULL. */ /* Refresh the attributes */ struct ceph_statx stx; int retval = fsal_ceph_ll_getattr(export->cmount, myself->i, &stx, !!attrs_out, &op_ctx->creds); if (retval == 0) { LogFullDebug(COMPONENT_FSAL, "New size = %" PRIx64, stx.stx_size); } else { /* Because we have an inode ref, we never * get EBADF like other FSALs might see. */ status = ceph2fsal_error(retval); } /* Now check verifier for exclusive, but not for * FSAL_EXCLUSIVE_9P. */ if (!FSAL_IS_ERROR(status) && createmode >= FSAL_EXCLUSIVE && createmode != FSAL_EXCLUSIVE_9P && !ceph_check_verifier_stat(&stx, verifier)) { /* Verifier didn't match, return EEXIST */ status = posix2fsal_status(EEXIST); } if (attrs_out) { /* Save out new attributes */ ceph2fsal_attributes(&stx, attrs_out); } } else if (attrs_out && attrs_out->request_mask & ATTR_RDATTR_ERR) { attrs_out->valid_mask = ATTR_RDATTR_ERR; } if (FSAL_IS_ERROR(status)) { if (old_openflags == FSAL_O_CLOSED) { /* Now that we have decided to close this FD, * let's clean it off from fd_lru and * ensure counters are decremented. */ remove_fd_lru(fsal_fd); } /* Close fd */ (void)ceph_close_my_fd(my_fd); } exit: if (state != NULL) { if (!FSAL_IS_ERROR(status)) { /* Success, establish the new share. */ update_share_counters(&myself->share, old_openflags, openflags); } /* Release obj_lock. */ PTHREAD_RWLOCK_unlock(&obj_hdl->obj_lock); } /* Indicate we are done with fd work and signal any waiters. */ fsal_complete_fd_work(fsal_fd); return status; } /** * @brief Open a file descriptor for read or write and possibly create * * This function opens a file for read or write, possibly creating it. * If the caller is passing a state, it must hold the state_lock * exclusive. * * state can be NULL which indicates a stateless open (such as via the * NFS v3 CREATE operation), in which case the FSAL must assure protection * of any resources. If the file is being created, such protection is * simple since no one else will have access to the object yet, however, * in the case of an exclusive create, the common resources may still need * protection. * * If Name is NULL, obj_hdl is the file itself, otherwise obj_hdl is the * parent directory. * * On an exclusive create, the upper layer may know the object handle * already, so it MAY call with name == NULL. In this case, the caller * expects just to check the verifier. * * On a call with an existing object handle for an UNCHECKED create, * we can set the size to 0. * * If attributes are not set on create, the FSAL will set some minimal * attributes (for example, mode might be set to 0600). * * If an open by name succeeds and did not result in Ganesha creating a file, * the caller will need to do a subsequent permission check to confirm the * open. This is because the permission attributes were not available * beforehand. * * @param[in] obj_hdl File to open or parent directory * @param[in,out] state state_t to use for this operation * @param[in] openflags Mode for open * @param[in] createmode Mode for create * @param[in] name Name for file if being created or opened * @param[in] attrib_set Attributes to set on created file * @param[in] verifier Verifier to use for exclusive create * @param[in,out] new_obj Newly created object * @param[in,out] caller_perm_check The caller must do a permission check * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @return FSAL status. */ static fsal_status_t ceph_fsal_open2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attrib_set, fsal_verifier_t verifier, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { int posix_flags = 0; int retval = 0; mode_t unix_mode = 0; fsal_status_t status = { 0, 0 }; struct ceph_fd *my_fd = NULL; struct ceph_handle *myself, *hdl = NULL; struct ceph_statx stx; bool created = false; struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); struct Inode *i = NULL; Fh *fd; LogAttrlist(COMPONENT_FSAL, NIV_FULL_DEBUG, "attrs ", attrib_set, false); if (state != NULL) my_fd = &container_of(state, struct ceph_state_fd, state) ->ceph_fd; myself = container_of(obj_hdl, struct ceph_handle, handle); fsal2posix_openflags(openflags, &posix_flags); if (createmode >= FSAL_EXCLUSIVE) { /* Now fixup attrs for verifier if exclusive create */ set_common_verifier(attrib_set, verifier, false); } if (name == NULL) { status = ceph_open2_by_handle(obj_hdl, state, openflags, createmode, verifier, attrs_out); *caller_perm_check = FSAL_IS_SUCCESS(status); return status; } /* In this path where we are opening by name, we can't check share * reservation yet since we don't have an object_handle yet. If we * indeed create the object handle (there is no race with another * open by name), then there CAN NOT be a share conflict, otherwise * the share conflict will be resolved when the object handles are * merged. */ if (createmode == FSAL_NO_CREATE) { /* Non creation case, libcephfs doesn't have open by name so we * have to do a lookup and then handle as an open by handle. */ struct fsal_obj_handle *temp = NULL; /* We don't have open by name... */ status = obj_hdl->obj_ops->lookup(obj_hdl, name, &temp, NULL); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "lookup returned %s", fsal_err_txt(status)); return status; } if (temp->type != REGULAR_FILE) { if (temp->type == DIRECTORY) { /* Trying to open2 a directory */ status = fsalstat(ERR_FSAL_ISDIR, 0); } else { /* Trying to open2 any other non-regular file */ status = fsalstat(ERR_FSAL_SYMLINK, 0); } /* Release the object we found by lookup. */ temp->obj_ops->release(temp); LogFullDebug(COMPONENT_FSAL, "open2 returning %s", fsal_err_txt(status)); return status; } /* Now call ourselves without name and attributes to open. */ status = obj_hdl->obj_ops->open2( temp, state, openflags, FSAL_NO_CREATE, NULL, NULL, verifier, new_obj, attrs_out, caller_perm_check, parent_pre_attrs_out, parent_post_attrs_out); if (FSAL_IS_ERROR(status)) { /* Release the object we found by lookup. */ temp->obj_ops->release(temp); LogFullDebug(COMPONENT_FSAL, "open returned %s", fsal_err_txt(status)); } GSH_AUTO_TRACEPOINT(fsal_ceph, ceph_opened, TRACE_DEBUG, "Opened. name: {}, handle: {}, ino: {}", TP_STR(name), *new_obj, stx.stx_ino); return status; } /* Now add in O_CREAT and O_EXCL. * Even with FSAL_UNGUARDED we try exclusive create first so * we can safely set attributes. */ if (createmode != FSAL_NO_CREATE) { /* Now add in O_CREAT and O_EXCL. */ posix_flags |= O_CREAT; /* And if we are at least FSAL_GUARDED, do an O_EXCL create. */ if (createmode >= FSAL_GUARDED) posix_flags |= O_EXCL; /* Fetch the mode attribute to use in the openat system call. */ unix_mode = fsal2unix_mode(attrib_set->mode) & ~op_ctx->fsal_export->exp_ops.fs_umask( op_ctx->fsal_export); /* Don't set the mode if we later set the attributes */ FSAL_UNSET_MASK(attrib_set->valid_mask, ATTR_MODE); } if (createmode == FSAL_UNCHECKED && (attrib_set->valid_mask != 0)) { /* If we have FSAL_UNCHECKED and want to set more attributes * than the mode, we attempt an O_EXCL create first, if that * succeeds, then we will be allowed to set the additional * attributes, otherwise, we don't know we created the file * and this can NOT set the attributes. */ posix_flags |= O_EXCL; } retval = fsal_ceph_ll_create(export->cmount, myself->i, name, unix_mode, posix_flags, &i, &fd, &stx, !!attrs_out, &op_ctx->creds); if (retval < 0) { LogFullDebug(COMPONENT_FSAL, "Create %s failed with %s", name, strerror(-retval)); } if (retval == -EEXIST && createmode == FSAL_UNCHECKED) { /* We tried to create O_EXCL to set attributes and failed. * Remove O_EXCL and retry, also remember not to set attributes. * We still try O_CREAT again just in case file disappears out * from under us. * * Note that because we have dropped O_EXCL, later on we will * not assume we created the file, and thus will not set * additional attributes. We don't need to separately track * the condition of not wanting to set attributes. */ posix_flags &= ~O_EXCL; retval = fsal_ceph_ll_create(export->cmount, myself->i, name, unix_mode, posix_flags, &i, &fd, &stx, !!attrs_out, &op_ctx->creds); if (retval < 0) { LogFullDebug(COMPONENT_FSAL, "Non-exclusive Create %s failed with %s", name, strerror(-retval)); } } if (retval < 0) { return ceph2fsal_error(retval); } /* Check if the opened file is not a regular file. */ if (posix2fsal_type(stx.stx_mode) == DIRECTORY) { /* Trying to open2 a directory */ status = fsalstat(ERR_FSAL_ISDIR, 0); goto fileerr; } if (posix2fsal_type(stx.stx_mode) != REGULAR_FILE) { /* Trying to open2 any other non-regular file */ status = fsalstat(ERR_FSAL_SYMLINK, 0); goto fileerr; } /* Remember if we were responsible for creating the file. * Note that in an UNCHECKED retry we MIGHT have re-created the * file and won't remember that. Oh well, so in that rare case we * leak a partially created file if we have a subsequent error in here. */ created = (posix_flags & O_EXCL) != 0; /** @todo FSF: Note that the current implementation of ceph_ll_create * does not accept an alt groups list, so it is possible * a create (including an UNCHECKED create on an already * existing file) would fail because the directory or * file was owned by a group other than the primary group. * Conversely, it could also succeed when it should have * failed if other is granted more permission than * one of the alt groups). */ /* Since we did the ceph_ll_create using the caller's credentials, * we don't need to do an additional permission check. */ *caller_perm_check = false; construct_handle(&stx, i, export, &hdl); /* If we didn't have a state above, use the global fd. At this point, * since we just created the global fd, no one else can have a * reference to it, and thus we can mamnipulate unlocked which is * handy since we can then call setattr2 which WILL take the lock * without a double locking deadlock. */ if (my_fd == NULL) { LogFullDebug(COMPONENT_FSAL, "Using global fd"); my_fd = &hdl->fd; /* Need to LRU track global fd including incrementing * fsal_fd_global_counter. */ insert_fd_lru(&my_fd->fsal_fd); } my_fd->fd = fd; my_fd->fsal_fd.openflags = FSAL_O_NFS_FLAGS(openflags); *new_obj = &hdl->handle; GSH_AUTO_TRACEPOINT(fsal_ceph, ceph_created, TRACE_DEBUG, "Created. name: {}, handle: {}, ino: {}", TP_STR(name), *new_obj, stx.stx_ino); if (created && attrib_set->valid_mask != 0) { /* Set attributes using our newly opened file descriptor as the * share_fd if there are any left to set (mode and truncate * have already been handled). * * Note that we only set the attributes if we were responsible * for creating the file and we have attributes to set. */ op_ctx->fsal_private = CEPH_SETXATTR_AS_ROOT; status = (*new_obj)->obj_ops->setattr2(*new_obj, false, state, attrib_set); op_ctx->fsal_private = NULL; if (FSAL_IS_ERROR(status)) goto fileerr; if (attrs_out != NULL) { status = (*new_obj)->obj_ops->getattrs(*new_obj, attrs_out); if (FSAL_IS_ERROR(status) && (attrs_out->request_mask & ATTR_RDATTR_ERR) == 0) { /* Get attributes failed and caller expected * to get the attributes. Otherwise continue * with attrs_out indicating ATTR_RDATTR_ERR. */ goto fileerr; } } } else if (attrs_out != NULL) { /* Since we haven't set any attributes other than what was set * on create (if we even created), just use the stat results * we used to create the fsal_obj_handle. */ ceph2fsal_attributes(&stx, attrs_out); } if (state != NULL) { /* Prepare to take the share reservation, but only if we are * called with a valid state (if state is NULL the caller is * a stateless create such as NFS v3 CREATE). */ /* Take the share reservation now by updating the counters. */ update_share_counters_locked(&hdl->handle, &hdl->share, FSAL_O_CLOSED, openflags); } return fsalstat(ERR_FSAL_NO_ERROR, 0); fileerr: /* Close the file we just opened. */ if (my_fd) (void)ceph_close_my_fd(my_fd); /* Release the handle we just allocated. */ if (*new_obj) { (*new_obj)->obj_ops->release(*new_obj); *new_obj = NULL; } if (created) { /* Remove the file we just created */ fsal_ceph_ll_unlink(export->cmount, myself->i, name, &op_ctx->creds); } return status; } /** * @brief Return open status of a state. * * This function returns open flags representing the current open * status for a state. The st_lock must be held. * * @param[in] obj_hdl File on which to operate * @param[in] state File state to interrogate * * @retval Flags representing current open status */ static fsal_openflags_t ceph_fsal_status2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { struct ceph_fd *my_fd = &((struct ceph_state_fd *)state)->ceph_fd; return my_fd->fsal_fd.openflags; } /** * @brief Re-open a file that may be already opened * * This function supports changing the access mode of a share reservation and * thus should only be called with a share state. The st_lock must be held. * * This MAY be used to open a file the first time if there is no need for * open by name or create semantics. One example would be 9P lopen. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] openflags Mode for re-open * * @return FSAL status. */ static fsal_status_t ceph_fsal_reopen2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags) { fsal_status_t status; struct user_cred root_creds = {}; struct user_cred saved_creds = op_ctx->creds; /* Ultimately fsal_ceph_ll_open will have to be called using root * creds. See github issue #577. */ op_ctx->creds = root_creds; status = ceph_open2_by_handle(obj_hdl, state, openflags, FSAL_NO_CREATE, NULL, NULL); /* Restore the creds. */ op_ctx->creds = saved_creds; return status; } #if USE_FSAL_CEPH_FS_NONBLOCKING_IO struct ceph_fsal_cb_info { struct fsal_io_arg *arg; struct gsh_export *exp; struct fsal_export *fsal_export; struct ceph_ll_io_info io_info; struct ceph_fd *my_fd; struct fsal_obj_handle *obj_hdl; fsal_async_cb done_cb; void *caller_arg; struct ceph_fd temp_fd; bool async; bool zerocopy; }; void ceph_read2_cb(struct ceph_ll_io_info *cb_info) { struct ceph_fsal_cb_info *cbi = cb_info->priv; struct fsal_io_arg *read_arg = cbi->arg; fsal_status_t status = { 0, 0 }, status2; struct fsal_obj_handle *obj_hdl = cbi->obj_hdl; struct ceph_handle *myself = container_of(cbi->obj_hdl, struct ceph_handle, handle); struct req_op_context ctx; /* Take a reference to the export for the callback. Note that while * this looks unsafe, we know that the caller's request can not complete * without this callback occurring, and since it can not complete, its * op_context is still valid and that holds a reference to this export. */ get_gsh_export_ref(cbi->exp); /* Even if we might already have an op context, we are going to build * a simple one from information in the cbu. The export was already * refcounted and the release_op_context() at the end will release * that refcount. */ init_op_context_simple(&ctx, cbi->exp, cbi->fsal_export); if (read_arg->fsal_resume) { assert(read_arg->fsal_resume == FSAL_CLOSEFD); read_arg->fsal_resume = FSAL_NORESUME; goto resume; } /* Check result of operation */ if (cb_info->result < 0) { /* An error occurred. */ status = ceph2fsal_error(cb_info->result); LogFullDebug(COMPONENT_FSAL, "Read returned %s", msg_fsal_err(status.major)); } else { /* I/O completed. */ read_arg->io_amount = cb_info->result; #ifdef USE_FSAL_CEPH_FS_ZEROCOPY_IO if (cb_info->zerocopy) { read_arg->iov_count = cb_info->iovcnt; read_arg->iov = cb_info->iov; read_arg->iov_release = cb_info->release; read_arg->release_data = cb_info->release_data; LogFullDebug( COMPONENT_FSAL, "cb_info->release %p cb_info->release_data %p cb_info->iov %p", cb_info->release, cb_info->release_data, cb_info->iov); if (isFullDebug(COMPONENT_FSAL)) { int i; size_t totlen = 0; for (i = 0; i < cb_info->iovcnt; i++) { totlen += cb_info->iov[i].iov_len; LogFullDebug( COMPONENT_FSAL, "cb_info->iov %p [%d].iov_base %p iov_len %zu for %zu of %" PRIu64, cb_info->iov, i, cb_info->iov[i].iov_base, cb_info->iov[i].iov_len, totlen, cb_info->result); } } } #endif LogFullDebug(COMPONENT_FSAL, "Read returned %" PRIu64, cb_info->result); } if (cbi->async && cbi->my_fd->fsal_fd.close_on_complete) { /* We need to ask to resume so we can complete I/O not on the * call back thread since we have to call close. */ read_arg->fsal_resume = FSAL_CLOSEFD; cbi->done_cb(obj_hdl, status, read_arg, cbi->caller_arg); release_op_context(); return; } resume: status2 = fsal_complete_io(obj_hdl, &cbi->my_fd->fsal_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (read_arg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->share, FSAL_O_READ, FSAL_O_CLOSED); } GSH_UNIQUE_AUTO_TRACEPOINT(fsal_ceph, ceph_read, TRACE_DEBUG, "Read. fileid: {}, result: {}", obj_hdl->fileid, cb_info->result); cbi->done_cb(obj_hdl, status, read_arg, cbi->caller_arg); release_op_context(); gsh_free(cbi); } #endif /** * @brief Read data from a file * * This function reads data from the given file. The FSAL must be able to * perform the read whether a state is presented or not. This function also * is expected to handle properly bypassing or not share reservations. This is * an (optionally) asynchronous call. When the I/O is complete, the done * callback is called with the results. * * @param[in] obj_hdl File on which to operate * @param[in] bypass If state doesn't indicate a share reservation, * bypass any deny read * @param[in,out] done_cb Callback to call when I/O is done * @param[in,out] read_arg Info about read, passed back in callback * @param[in,out] caller_arg Opaque arg from the caller for callback * * @return Nothing; results are in callback */ static void ceph_fsal_read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg) { fsal_status_t status = { 0, 0 }, status2; struct ceph_fd *my_fd; struct fsal_fd *out_fd; struct ceph_handle *myself = container_of(obj_hdl, struct ceph_handle, handle); struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); uint64_t offset = read_arg->offset; #if USE_FSAL_CEPH_FS_NONBLOCKING_IO struct ceph_fsal_cb_info *cbi; int64_t result; #endif ssize_t nb_read; struct ceph_fd temp_fd = { FSAL_FD_INIT, NULL }; int i; #if USE_FSAL_CEPH_FS_NONBLOCKING_IO if (read_arg->fsal_resume) { ceph_read2_cb(read_arg->cbi); return; } #endif if (read_arg->info != NULL) { /* Currently we don't support READ_PLUS */ done_cb(obj_hdl, fsalstat(ERR_FSAL_NOTSUPP, 0), read_arg, caller_arg); return; } #if USE_FSAL_CEPH_FS_NONBLOCKING_IO /* Allocate ceph call back information */ cbi = gsh_calloc(1, sizeof(*cbi)); init_fsal_fd(&cbi->temp_fd.fsal_fd, FSAL_FD_TEMP, op_ctx->fsal_export); #endif /* Indicate a desire to start io and get a usable file descritor */ #if USE_FSAL_CEPH_FS_NONBLOCKING_IO if (CephFSM.async || CephFSM.zerocopy) { status = fsal_start_io(&out_fd, obj_hdl, &myself->fd.fsal_fd, &cbi->temp_fd.fsal_fd, read_arg->state, FSAL_O_READ, false, NULL, bypass, &myself->share); } else { #else { #endif status = fsal_start_io(&out_fd, obj_hdl, &myself->fd.fsal_fd, &temp_fd.fsal_fd, read_arg->state, FSAL_O_READ, false, NULL, bypass, &myself->share); } if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct ceph_fd, fsal_fd); read_arg->io_amount = 0; #if USE_FSAL_CEPH_FS_NONBLOCKING_IO #ifdef USE_FSAL_CEPH_FS_ZEROCOPY_IO if (!CephFSM.async && !CephFSM.zerocopy) goto old_style; #else if (!CephFSM.async) goto old_style; #endif cbi->io_info.callback = ceph_read2_cb; cbi->io_info.priv = cbi; cbi->io_info.fh = my_fd->fd; cbi->io_info.iov = read_arg->iov; cbi->io_info.iovcnt = read_arg->iov_count; cbi->io_info.off = offset; cbi->io_info.write = false; cbi->arg = read_arg; cbi->exp = op_ctx->ctx_export; cbi->fsal_export = op_ctx->fsal_export; cbi->my_fd = my_fd; cbi->obj_hdl = obj_hdl; cbi->done_cb = done_cb; cbi->caller_arg = caller_arg; cbi->async = CephFSM.async; cbi->zerocopy = false; read_arg->cbi = cbi; #ifdef USE_FSAL_CEPH_FS_ZEROCOPY_IO /* We are only going to do zero copy if configure AND caller didn't * supply a buffer, otherwise, we will let ceph copy into the * provided iovec. */ cbi->io_info.zerocopy = CephFSM.zerocopy && read_arg->iov[0].iov_base == NULL; cbi->zerocopy = cbi->io_info.zerocopy; if (!CephFSM.async) { /* Do zerocopy non-async I/O */ ceph_ll_readv_writev(export->cmount, &cbi->io_info); return; } #endif /* Note that while we are passing an export to the callback, the * protocol request that drove this I/O can not complete until the * callback completes, which also means that its op_context with its * export reference is still valid until the callback completes. */ LogFullDebug(COMPONENT_FSAL, "Calling ceph_ll_nonblocking_readv_writev for read"); result = ceph_ll_nonblocking_readv_writev(export->cmount, &cbi->io_info); if (result < 0) { /* An error occurred. */ status = ceph2fsal_error(result); } else if (result == 0) { /* I/O will complete async, return. */ return; } else { /* I/O actually completed... */ read_arg->io_amount = result; } GSH_UNIQUE_AUTO_TRACEPOINT(fsal_ceph, ceph_read, TRACE_DEBUG, "Read. fileid: {}, result: {}", obj_hdl->fileid, result); goto out; old_style: #endif for (i = 0; i < read_arg->iov_count; i++) { nb_read = ceph_ll_read(export->cmount, my_fd->fd, offset, read_arg->iov[i].iov_len, read_arg->iov[i].iov_base); if (nb_read == 0) { read_arg->end_of_file = true; break; } else if (nb_read < 0) { status = ceph2fsal_error(nb_read); goto out; } read_arg->io_amount += nb_read; offset += nb_read; } GSH_UNIQUE_AUTO_TRACEPOINT(fsal_ceph, ceph_read, TRACE_DEBUG, "Read. fileid: {}, nb_read: {}", obj_hdl->fileid, nb_read); #if 0 /** @todo * * Is this all we really need to do to support READ_PLUS? Will anyone * ever get upset that we don't return holes, even for blocks of all * zeroes? * */ if (info != NULL) { info->io_content.what = NFS4_CONTENT_DATA; info->io_content.data.d_offset = offset + nb_read; info->io_content.data.d_data.data_len = nb_read; info->io_content.data.d_data.data_val = buffer; } #endif out: status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (read_arg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->share, FSAL_O_READ, FSAL_O_CLOSED); } exit: done_cb(obj_hdl, status, read_arg, caller_arg); #if USE_FSAL_CEPH_FS_NONBLOCKING_IO destroy_fsal_fd(&cbi->temp_fd.fsal_fd); gsh_free(cbi); #endif } #if USE_FSAL_CEPH_FS_NONBLOCKING_IO void ceph_write2_cb(struct ceph_ll_io_info *cb_info) { struct ceph_fsal_cb_info *cbi = cb_info->priv; struct fsal_io_arg *write_arg = cbi->arg; fsal_status_t status = { 0, 0 }, status2; struct fsal_obj_handle *obj_hdl = cbi->obj_hdl; struct ceph_handle *myself = container_of(cbi->obj_hdl, struct ceph_handle, handle); struct req_op_context ctx; /* Take a reference to the export for the callback. Note that while * this looks unsafe, we know that the caller's request can not complete * without this callback occurring, and since it can not complete, its * op_context is still valid and that holds a reference to this export. */ get_gsh_export_ref(cbi->exp); /* Even if we might already have an op context, we are going to build * a simple one from information in the cbu. The export was already * refcounted and the release_op_context() at the end will release * that refcount. */ init_op_context_simple(&ctx, cbi->exp, cbi->fsal_export); if (write_arg->fsal_resume) { assert(write_arg->fsal_resume == FSAL_CLOSEFD); write_arg->fsal_resume = FSAL_NORESUME; goto resume; } /* Check result of operation */ if (cb_info->result < 0) { /* An error occurred. */ status = ceph2fsal_error(cb_info->result); LogFullDebug(COMPONENT_FSAL, "Write returned %s", msg_fsal_err(status.major)); } else { /* I/O completed. */ write_arg->io_amount = cb_info->result; LogFullDebug(COMPONENT_FSAL, "Write returned %" PRIu64, cb_info->result); } if (cbi->my_fd->fsal_fd.close_on_complete) { /* We need to ask to resume so we can complete I/O not on the * call back thread since we have to call close. */ write_arg->fsal_resume = FSAL_CLOSEFD; cbi->done_cb(obj_hdl, status, write_arg, cbi->caller_arg); release_op_context(); return; } resume: status2 = fsal_complete_io(obj_hdl, &cbi->my_fd->fsal_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (write_arg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->share, FSAL_O_WRITE, FSAL_O_CLOSED); } GSH_UNIQUE_AUTO_TRACEPOINT(fsal_ceph, ceph_write, TRACE_DEBUG, "Write. fileid: {}, result: {}", obj_hdl->fileid, cb_info->result); cbi->done_cb(obj_hdl, status, write_arg, cbi->caller_arg); release_op_context(); gsh_free(cbi); } #endif /** * @brief Write data to a file * * This function writes data to a file. The FSAL must be able to * perform the write whether a state is presented or not. This function also * is expected to handle properly bypassing or not share reservations. Even * with bypass == true, it will enforce a mandatory (NFSv4) deny_write if * an appropriate state is not passed). * * The FSAL is expected to enforce sync if necessary. * * @param[in] obj_hdl File on which to operate * @param[in] bypass If state doesn't indicate a share reservation, * bypass any non-mandatory deny write * @param[in,out] done_cb Callback to call when I/O is done * @param[in,out] write_arg Info about write, passed back in callback * @param[in,out] caller_arg Opaque arg from the caller for callback */ static void ceph_fsal_write2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *caller_arg) { fsal_status_t status = { 0, 0 }, status2; struct ceph_fd *my_fd; struct fsal_fd *out_fd; struct ceph_handle *myself = container_of(obj_hdl, struct ceph_handle, handle); struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); uint64_t offset = write_arg->offset; #if USE_FSAL_CEPH_FS_NONBLOCKING_IO struct ceph_fsal_cb_info *cbi; int64_t result; #else ssize_t nb_written; struct ceph_fd temp_fd = { FSAL_FD_INIT, NULL }; int i, retval = 0; #endif #if USE_FSAL_CEPH_FS_NONBLOCKING_IO if (write_arg->fsal_resume) { ceph_write2_cb(write_arg->cbi); return; } /* Allocate ceph call back information */ cbi = gsh_calloc(1, sizeof(*cbi)); init_fsal_fd(&cbi->temp_fd.fsal_fd, FSAL_FD_TEMP, op_ctx->fsal_export); #endif /* Indicate a desire to start io and get a usable file descritor */ #if USE_FSAL_CEPH_FS_NONBLOCKING_IO status = fsal_start_io(&out_fd, obj_hdl, &myself->fd.fsal_fd, &cbi->temp_fd.fsal_fd, write_arg->state, FSAL_O_WRITE, false, NULL, bypass, &myself->share); #else status = fsal_start_io(&out_fd, obj_hdl, &myself->fd.fsal_fd, &temp_fd.fsal_fd, write_arg->state, FSAL_O_WRITE, false, NULL, bypass, &myself->share); #endif if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct ceph_fd, fsal_fd); #if USE_FSAL_CEPH_FS_NONBLOCKING_IO cbi->io_info.callback = ceph_write2_cb; cbi->io_info.priv = cbi; cbi->io_info.fh = my_fd->fd; cbi->io_info.iov = write_arg->iov; cbi->io_info.iovcnt = write_arg->iov_count; cbi->io_info.off = offset; cbi->io_info.write = true; cbi->io_info.fsync = write_arg->fsal_stable; cbi->io_info.syncdataonly = false; cbi->arg = write_arg; cbi->exp = op_ctx->ctx_export; cbi->fsal_export = op_ctx->fsal_export; cbi->my_fd = my_fd; cbi->obj_hdl = obj_hdl; cbi->done_cb = done_cb; cbi->caller_arg = caller_arg; write_arg->cbi = cbi; /* Note that while we are passing an export to the callback, the * protocol request that drove this I/O can not complete until the * callback completes, which also means that its op_context with its * export reference is still valid until the callback completes. */ LogFullDebug(COMPONENT_FSAL, "Calling ceph_ll_nonblocking_readv_writev for write"); result = ceph_ll_nonblocking_readv_writev(export->cmount, &cbi->io_info); LogFullDebug( COMPONENT_FSAL, "ceph_ll_nonblocking_readv_writev for write returned %" PRIi64, result); if (result < 0) { /* An error occurred. */ status = ceph2fsal_error(result); } else if (result == 0) { /* I/O will complete async, return. */ return; } else { /* I/O actually completed... */ write_arg->io_amount = result; } #else for (i = 0; i < write_arg->iov_count; i++) { nb_written = ceph_ll_write(export->cmount, my_fd->fd, offset, write_arg->iov[i].iov_len, write_arg->iov[i].iov_base); if (nb_written == 0) { break; } else if (nb_written < 0) { status = ceph2fsal_error(nb_written); goto out; } write_arg->io_amount += nb_written; offset += nb_written; } if (write_arg->fsal_stable) { retval = ceph_ll_fsync(export->cmount, my_fd->fd, false); if (retval < 0) { status = ceph2fsal_error(retval); write_arg->fsal_stable = false; } } GSH_UNIQUE_AUTO_TRACEPOINT(fsal_ceph, ceph_write, TRACE_DEBUG, "Write. fileid: {}, nb_written: {}", obj_hdl->fileid, nb_written); #endif #if USE_FSAL_CEPH_FS_NONBLOCKING_IO #else out: #endif status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (write_arg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->share, FSAL_O_WRITE, FSAL_O_CLOSED); } exit: done_cb(obj_hdl, status, write_arg, caller_arg); #if USE_FSAL_CEPH_FS_NONBLOCKING_IO destroy_fsal_fd(&cbi->temp_fd.fsal_fd); gsh_free(cbi); #endif } /** * @brief Commit written data * * This function flushes possibly buffered data to a file. This method * differs from commit due to the need to interact with share reservations * and the fact that the FSAL manages the state of "file descriptors". The * FSAL must be able to perform this operation without being passed a specific * state. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] offset Start of range to commit * @param[in] len Length of range to commit * * @return FSAL status. */ #ifdef USE_FSAL_CEPH_LL_SYNC_INODE static fsal_status_t ceph_fsal_commit2(struct fsal_obj_handle *obj_hdl, off_t offset, size_t len) { int retval; struct ceph_handle *myself = container_of(obj_hdl, struct ceph_handle, handle); struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); /* * If we have the ceph_ll_sync_inode call, then we can avoid opening * altogether. Since we don't need to check share reservation, this * totally avoids dealing with the obj_lock or any fsal_fd. */ retval = ceph_ll_sync_inode(export->cmount, myself->i, 0); GSH_UNIQUE_AUTO_TRACEPOINT(fsal_ceph, ceph_commit, TRACE_DEBUG, "Write. fileid: {}", obj_hdl->fileid); return ceph2fsal_error(retval); } #else static fsal_status_t ceph_fsal_commit2(struct fsal_obj_handle *obj_hdl, off_t offset, size_t len) { struct ceph_handle *myself; fsal_status_t status, status2; int retval; struct ceph_fd temp_fd = { FSAL_FD_INIT, NULL }; struct fsal_fd *out_fd; struct ceph_fd *my_fd; struct user_cred saved_creds = op_ctx->creds; struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); myself = container_of(obj_hdl, struct ceph_handle, handle); /* It's possible that the file has changed permissions since it was * opened by the writer, so open the file with root creds here since * we're just doing a fsync. */ memset(&op_ctx->creds, 0, sizeof(op_ctx->creds)); /* Make sure file is open in appropriate mode. * Do not check share reservation. */ status = fsal_start_global_io(&out_fd, obj_hdl, &myself->fd.fsal_fd, &temp_fd.fsal_fd, FSAL_O_ANY, false, NULL); /* Restore creds */ op_ctx->creds = saved_creds; if (FSAL_IS_ERROR(status)) return status; my_fd = container_of(out_fd, struct ceph_fd, fsal_fd); retval = ceph_ll_fsync(export->cmount, my_fd->fd, false); if (retval < 0) status = ceph2fsal_error(retval); GSH_UNIQUE_AUTO_TRACEPOINT(fsal_ceph, ceph_commit, TRACE_DEBUG, "Write. fileid: {}", obj_hdl->fileid); status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); /* We did not do share reservation stuff... */ return status; } #endif #ifdef USE_FSAL_CEPH_SETLK /** * @brief Perform a lock operation * * This function performs a lock operation (lock, unlock, test) on a * file. This method assumes the FSAL is able to support lock owners, * though it need not support asynchronous blocking locks. Passing the * lock state allows the FSAL to associate information with a specific * lock owner for each file (which may include use of a "file descriptor". * * For FSAL_VFS etc. we ignore owner, implicitly we have a lock_fd per * lock owner (i.e. per state). * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] owner Lock owner * @param[in] lock_op Operation to perform * @param[in] request_lock Lock to take/release/test * @param[out] conflicting_lock Conflicting lock * * @return FSAL status. */ static fsal_status_t ceph_fsal_lock_op2(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *owner, fsal_lock_op_t lock_op, fsal_lock_param_t *request_lock, fsal_lock_param_t *conflicting_lock) { struct flock lock_args; fsal_status_t status = { 0, 0 }, status2; int retval = 0; struct ceph_fd *my_fd; struct ceph_fd temp_fd = { FSAL_FD_INIT, NULL }; struct fsal_fd *out_fd; struct ceph_handle *myself = container_of(obj_hdl, struct ceph_handle, handle); bool bypass = false; fsal_openflags_t openflags = FSAL_O_RDWR; struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); LogFullDebug(COMPONENT_FSAL, "Locking: op:%d type:%d start:%" PRIu64 " length:%" PRIu64 " ", lock_op, request_lock->lock_type, request_lock->lock_start, request_lock->lock_length); if (lock_op == FSAL_OP_LOCKT) { /* We may end up using global fd, don't fail on a deny mode */ bypass = true; openflags = FSAL_O_ANY; } else if (lock_op == FSAL_OP_LOCK) { if (request_lock->lock_type == FSAL_LOCK_R) openflags = FSAL_O_READ; else if (request_lock->lock_type == FSAL_LOCK_W) openflags = FSAL_O_WRITE; } else if (lock_op == FSAL_OP_UNLOCK) { openflags = FSAL_O_ANY; } else { LogDebug( COMPONENT_FSAL, "ERROR: Lock operation requested was not TEST, READ, or WRITE."); return fsalstat(ERR_FSAL_NOTSUPP, 0); } if (lock_op != FSAL_OP_LOCKT && state == NULL) { LogCrit(COMPONENT_FSAL, "Non TEST operation with NULL state"); return fsalstat(posix2fsal_error(EINVAL), EINVAL); } if (request_lock->lock_type == FSAL_LOCK_R) { lock_args.l_type = F_RDLCK; } else if (request_lock->lock_type == FSAL_LOCK_W) { lock_args.l_type = F_WRLCK; } else { LogDebug( COMPONENT_FSAL, "ERROR: The requested lock type was not read or write."); return fsalstat(ERR_FSAL_NOTSUPP, 0); } if (lock_op == FSAL_OP_UNLOCK) lock_args.l_type = F_UNLCK; lock_args.l_pid = 0; lock_args.l_len = request_lock->lock_length; lock_args.l_start = request_lock->lock_start; lock_args.l_whence = SEEK_SET; /* flock.l_len being signed long integer, larger lock ranges may * get mapped to negative values. As per 'man 3 fcntl', posix * locks can accept negative l_len values which may lead to * unlocking an unintended range. Better bail out to prevent that. */ if (lock_args.l_len < 0) { LogCrit(COMPONENT_FSAL, "The requested lock length is out of range- lock_args.l_len(%ld), request_lock_length(%" PRIu64 ")", lock_args.l_len, request_lock->lock_length); return fsalstat(ERR_FSAL_BAD_RANGE, 0); } /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->fd.fsal_fd, &temp_fd.fsal_fd, state, openflags, true, NULL, bypass, &myself->share); if (FSAL_IS_ERROR(status)) { LogCrit(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct ceph_fd, fsal_fd); if (lock_op == FSAL_OP_LOCKT) { retval = ceph_ll_getlk(export->cmount, my_fd->fd, &lock_args, (uint64_t)owner); } else { retval = ceph_ll_setlk(export->cmount, my_fd->fd, &lock_args, (uint64_t)owner, false); } if (retval < 0) { LogDebug(COMPONENT_FSAL, "%s returned %d %s", lock_op == FSAL_OP_LOCKT ? "ceph_ll_getlk" : "ceph_ll_setlk", -retval, strerror(-retval)); if (conflicting_lock != NULL) { int retval2; /* Get the conflicting lock */ retval2 = ceph_ll_getlk(export->cmount, my_fd->fd, &lock_args, (uint64_t)owner); if (retval2 < 0) { LogCrit(COMPONENT_FSAL, "After failing a lock request, I couldn't even get the details of who owns the lock, error %d %s", -retval2, strerror(-retval2)); goto err; } conflicting_lock->lock_length = lock_args.l_len; conflicting_lock->lock_start = lock_args.l_start; conflicting_lock->lock_type = lock_args.l_type; } goto err; } /* F_UNLCK is returned then the tested operation would be possible. */ if (conflicting_lock != NULL) { if (lock_op == FSAL_OP_LOCKT && lock_args.l_type != F_UNLCK) { conflicting_lock->lock_length = lock_args.l_len; conflicting_lock->lock_start = lock_args.l_start; conflicting_lock->lock_type = lock_args.l_type; } else { conflicting_lock->lock_length = 0; conflicting_lock->lock_start = 0; conflicting_lock->lock_type = FSAL_NO_LOCK; } } /* Fall through (retval == 0) */ GSH_AUTO_TRACEPOINT(fsal_ceph, ceph_lock, TRACE_DEBUG, "Lock. fileid: {}, lock_op: {}", obj_hdl->fileid, lock_op); err: status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->share, openflags, FSAL_O_CLOSED); } exit: return ceph2fsal_error(retval); } #endif #ifdef USE_FSAL_CEPH_LL_DELEGATION static void ceph_deleg_cb(Fh *fh, void *vhdl) { fsal_status_t fsal_status; struct fsal_obj_handle *obj_hdl = vhdl; struct ceph_handle *hdl = container_of(obj_hdl, struct ceph_handle, handle); struct gsh_buffdesc key = { .addr = &hdl->key.hhdl, .len = sizeof(hdl->key.hhdl) }; LogDebug(COMPONENT_FSAL, "Recalling delegations on %p", hdl); fsal_status = up_async_delegrecall(general_fridge, hdl->up_ops, &key, NULL, NULL); if (FSAL_IS_ERROR(fsal_status)) LogCrit(COMPONENT_FSAL, "Unable to queue delegrecall for 0x%p: %s", hdl, fsal_err_txt(fsal_status)); } static fsal_status_t ceph_fsal_lease_op2(struct fsal_obj_handle *obj_hdl, state_t *state, void *owner, fsal_deleg_t deleg) { fsal_status_t status = { 0, 0 }, status2; int retval = 0; unsigned int cmd; struct ceph_fd *my_fd; struct ceph_fd temp_fd = { FSAL_FD_INIT, NULL }; struct fsal_fd *out_fd; struct ceph_handle *myself = container_of(obj_hdl, struct ceph_handle, handle); fsal_openflags_t openflags = FSAL_O_READ; struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); switch (deleg) { case FSAL_DELEG_NONE: cmd = CEPH_DELEGATION_NONE; break; case FSAL_DELEG_RD: cmd = CEPH_DELEGATION_RD; break; case FSAL_DELEG_WR: /* No write delegations (yet!) */ return ceph2fsal_error(-ENOTSUP); default: LogCrit(COMPONENT_FSAL, "Unknown requested lease state"); return ceph2fsal_error(-EINVAL); }; /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->fd.fsal_fd, &temp_fd.fsal_fd, state, openflags, false, NULL, false, &myself->share); if (FSAL_IS_ERROR(status)) { LogCrit(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct ceph_fd, fsal_fd); retval = ceph_ll_delegation(export->cmount, my_fd->fd, cmd, ceph_deleg_cb, obj_hdl); GSH_AUTO_TRACEPOINT(fsal_ceph, ceph_lease, TRACE_DEBUG, "Lease. fileid: {}, cmd: {}", obj_hdl->fileid, cmd); status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->share, openflags, FSAL_O_CLOSED); } exit: return ceph2fsal_error(retval); } #endif /** * @brief Set attributes on an object * * This function sets attributes on an object. Which attributes are * set is determined by attrib_set->valid_mask. The FSAL must manage bypass * or not of share reservations, and a state may be passed. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] attrib_set Attributes to set * * @return FSAL status. */ static fsal_status_t ceph_fsal_setattr2(struct fsal_obj_handle *obj_hdl, bool bypass, struct state_t *state, struct fsal_attrlist *attrib_set) { struct ceph_handle *myself = container_of(obj_hdl, struct ceph_handle, handle); fsal_status_t status = { 0, 0 }; int rc = 0; struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); /* Stat buffer */ struct ceph_statx stx; /* Mask of attributes to set */ uint32_t mask = 0; bool need_share = false; if (attrib_set->valid_mask & ~CEPH_SETTABLE_ATTRIBUTES) { LogDebug(COMPONENT_FSAL, "bad mask %" PRIx64 " not settable %" PRIx64, attrib_set->valid_mask, attrib_set->valid_mask & ~CEPH_SETTABLE_ATTRIBUTES); return fsalstat(ERR_FSAL_INVAL, 0); } LogAttrlist(COMPONENT_FSAL, NIV_FULL_DEBUG, "attrs ", attrib_set, false); /* apply umask, if mode attribute is to be changed */ if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MODE)) attrib_set->mode &= ~op_ctx->fsal_export->exp_ops.fs_umask( op_ctx->fsal_export); #ifdef CEPHFS_POSIX_ACL if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_ACL)) { status = ceph_set_acl(export, myself, false, attrib_set); if (FSAL_IS_ERROR(status)) { LogMajor(COMPONENT_FSAL, "set access acl status = %s", fsal_err_txt(status)); goto out; } if (obj_hdl->type == DIRECTORY) { status = ceph_set_acl(export, myself, true, attrib_set); if (FSAL_IS_ERROR(status)) { LogWarn(COMPONENT_FSAL, "set default acl status = %s", fsal_err_txt(status)); } } } #endif /* CEPHFS_POSIX_ACL */ /* Test if size is being set, make sure file is regular and if so, * require a read/write file descriptor. */ if (state != NULL && FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_SIZE)) { if (obj_hdl->type != REGULAR_FILE) { LogFullDebug(COMPONENT_FSAL, "Setting size on non-regular file"); return fsalstat(ERR_FSAL_INVAL, EINVAL); } /* Now check the new share and establish if OK. */ status = check_share_conflict_and_update_locked( obj_hdl, &myself->share, FSAL_O_CLOSED, FSAL_O_RDWR, false); if (FSAL_IS_ERROR(status)) { LogFullDebug( COMPONENT_FSAL, "check_share_conflict_and_update_locked status=%s", fsal_err_txt(status)); goto out; } need_share = true; } memset(&stx, 0, sizeof(stx)); if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_SIZE)) { mask |= CEPH_SETATTR_SIZE; stx.stx_size = attrib_set->filesize; LogDebug(COMPONENT_FSAL, "setting size to %lu", stx.stx_size); } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MODE)) { mask |= CEPH_SETATTR_MODE; stx.stx_mode = fsal2unix_mode(attrib_set->mode); } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_OWNER)) { mask |= CEPH_SETATTR_UID; stx.stx_uid = attrib_set->owner; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_GROUP)) { mask |= CEPH_SETATTR_GID; stx.stx_gid = attrib_set->group; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_ATIME)) { mask |= CEPH_SETATTR_ATIME; stx.stx_atime = attrib_set->atime; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_ATIME_SERVER)) { struct timespec timestamp; mask |= CEPH_SETATTR_ATIME; #ifdef CEPH_SETATTR_ATIME_NOW mask |= CEPH_SETATTR_ATIME_NOW; #endif rc = clock_gettime(CLOCK_REALTIME, ×tamp); if (rc != 0) { LogDebug(COMPONENT_FSAL, "clock_gettime returned %s (%d)", strerror(errno), errno); status = fsalstat(posix2fsal_error(errno), errno); goto out; } stx.stx_atime = timestamp; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MTIME)) { mask |= CEPH_SETATTR_MTIME; stx.stx_mtime = attrib_set->mtime; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MTIME_SERVER)) { struct timespec timestamp; mask |= CEPH_SETATTR_MTIME; #ifdef CEPH_SETATTR_MTIME_NOW mask |= CEPH_SETATTR_MTIME_NOW; #endif rc = clock_gettime(CLOCK_REALTIME, ×tamp); if (rc != 0) { LogDebug(COMPONENT_FSAL, "clock_gettime returned %s (%d)", strerror(errno), errno); status = fsalstat(posix2fsal_error(errno), errno); goto out; } stx.stx_mtime = timestamp; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_CTIME)) { mask |= CEPH_SETATTR_CTIME; stx.stx_ctime = attrib_set->ctime; } #ifdef CEPH_SETATTR_BTIME if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_CREATION)) { mask |= CEPH_SETATTR_BTIME; stx.stx_btime = attrib_set->creation; } #endif rc = fsal_ceph_ll_setattr(export->cmount, myself->i, &stx, mask, &op_ctx->creds); GSH_AUTO_TRACEPOINT(fsal_ceph, ceph_setattrs, TRACE_DEBUG, "Setattrs. ino: {}, size: {}, mode: {}", stx.stx_ino, stx.stx_size, stx.stx_mode); if (rc < 0) { LogDebug(COMPONENT_FSAL, "setattrx returned %s (%d)", strerror(-rc), -rc); status = ceph2fsal_error(rc); goto out; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR4_SEC_LABEL)) { struct user_cred creds = op_ctx->creds; if (op_ctx->fsal_private == CEPH_SETXATTR_AS_ROOT) memset(&creds, 0, sizeof(creds)); rc = fsal_ceph_ll_setxattr( export->cmount, myself->i, export->sec_label_xattr, attrib_set->sec_label.slai_data.slai_data_val, attrib_set->sec_label.slai_data.slai_data_len, 0, &creds); if (rc < 0) { status = ceph2fsal_error(rc); goto out; } } /* Success */ status = fsalstat(ERR_FSAL_NO_ERROR, 0); out: if (need_share) { /* Release the temporary share. */ update_share_counters_locked(obj_hdl, &myself->share, FSAL_O_RDWR, FSAL_O_CLOSED); } return status; } /** * @brief Manage closing a file when a state is no longer needed. * * When the upper layers are ready to dispense with a state, this method is * called to allow the FSAL to close any file descriptors or release any other * resources associated with the state. A call to free_state should be assumed * to follow soon. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * * @return FSAL status. */ static fsal_status_t ceph_fsal_close2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { struct ceph_handle *myself = container_of(obj_hdl, struct ceph_handle, handle); struct ceph_fd *my_fd = &container_of(state, struct ceph_state_fd, state)->ceph_fd; if (state->state_type == STATE_TYPE_SHARE || state->state_type == STATE_TYPE_NLM_SHARE || state->state_type == STATE_TYPE_9P_FID) { /* This is a share state, we must update the share counters */ update_share_counters_locked(obj_hdl, &myself->share, my_fd->fsal_fd.openflags, FSAL_O_CLOSED); } return close_fsal_fd(obj_hdl, &my_fd->fsal_fd, false); } /** * @brief Write wire handle * * This function writes a 'wire' handle to be sent to clients and * received from the. * * @param[in] handle_pub Handle to digest * @param[in] output_type Type of digest requested * @param[in,out] fh_desc Location/size of buffer for * digest/Length modified to digest length * * @return FSAL status. */ static fsal_status_t ceph_fsal_handle_to_wire(const struct fsal_obj_handle *handle_pub, uint32_t output_type, struct gsh_buffdesc *fh_desc) { /* The private 'full' object handle */ const struct ceph_handle *handle = container_of(handle_pub, const struct ceph_handle, handle); switch (output_type) { /* Digested Handles */ case FSAL_DIGEST_NFSV3: case FSAL_DIGEST_NFSV4: if (fh_desc->len < sizeof(handle->key.hhdl)) { LogMajor( COMPONENT_FSAL, "digest_handle: space too small for handle. Need %zu, have %zu", sizeof(handle->key.hhdl), fh_desc->len); return fsalstat(ERR_FSAL_TOOSMALL, 0); } else { struct ceph_host_handle *hhdl = fh_desc->addr; /* See comments in wire_to_host */ hhdl->chk_ino = htole64(handle->key.hhdl.chk_ino); hhdl->chk_snap = htole64(handle->key.hhdl.chk_snap); hhdl->chk_fscid = htole64(handle->key.hhdl.chk_fscid); fh_desc->len = sizeof(*hhdl); } break; default: return fsalstat(ERR_FSAL_SERVERFAULT, 0); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Give a hash key for file handle * * This function locates a unique hash key for a given file. * * @param[in] handle_pub The file whose key is to be found * @param[out] fh_desc Address and length of key */ static void ceph_fsal_handle_to_key(struct fsal_obj_handle *handle_pub, struct gsh_buffdesc *fh_desc) { /* The private 'full' object handle */ struct ceph_handle *handle = container_of(handle_pub, struct ceph_handle, handle); fh_desc->addr = &handle->key; fh_desc->len = sizeof(handle->key); } #ifdef USE_CEPH_LL_FALLOCATE static fsal_status_t ceph_fsal_fallocate(struct fsal_obj_handle *obj_hdl, state_t *state, uint64_t offset, uint64_t length, bool allocate) { fsal_status_t status = { 0, 0 }, status2; int retval = 0; struct ceph_fd *my_fd; struct ceph_fd temp_fd = { FSAL_FD_INIT, NULL }; struct fsal_fd *out_fd; struct ceph_handle *myself = container_of(obj_hdl, struct ceph_handle, handle); int mode; struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->fd.fsal_fd, &temp_fd.fsal_fd, state, FSAL_O_WRITE, false, NULL, false, &myself->share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct ceph_fd, fsal_fd); mode = allocate ? 0 : FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE; retval = ceph_ll_fallocate(export->cmount, my_fd->fd, mode, offset, length); if (retval < 0) { status = ceph2fsal_error(retval); goto out; } retval = ceph_ll_fsync(export->cmount, my_fd->fd, false); if (retval < 0) status = ceph2fsal_error(retval); GSH_AUTO_TRACEPOINT( fsal_ceph, ceph_falloc, TRACE_DEBUG, "Falloc. fileid: {}, mode: {}, offset: {}, length: {}", obj_hdl->fileid, mode, offset, length); out: status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->share, FSAL_O_WRITE, FSAL_O_CLOSED); } exit: return status; } #endif static fsal_status_t ceph_fsal_getxattrs(struct fsal_obj_handle *handle_pub, xattrkey4 *xa_name, xattrvalue4 *xa_value) { int rc = 0; fsal_status_t status; struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); const struct ceph_handle *handle = container_of(handle_pub, const struct ceph_handle, handle); char name[sizeof("user.") + NAME_MAX]; /* * The nfs client only deals with user.* xattrs, but doesn't send * the namespace on the wire. We have to add it in here. */ rc = snprintf(name, sizeof(name), "user.%.*s", xa_name->utf8string_len, xa_name->utf8string_val); if (rc >= sizeof(name)) return ceph2fsal_error(-ENAMETOOLONG); rc = fsal_ceph_ll_getxattr(export->cmount, handle->i, name, xa_value->utf8string_val, xa_value->utf8string_len, &op_ctx->creds); if (rc < 0) { LogDebug(COMPONENT_FSAL, "GETXATTRS returned rc %d", rc); if (rc == -ERANGE) { status = fsalstat(ERR_FSAL_XATTR2BIG, 0); goto out; } if (rc == -ENODATA) { status = fsalstat(ERR_FSAL_NOXATTR, 0); goto out; } status = ceph2fsal_error(rc); goto out; } xa_value->utf8string_len = rc; LogDebug(COMPONENT_FSAL, "GETXATTRS %s is '%.*s'", name, xa_value->utf8string_len, xa_value->utf8string_val); status = fsalstat(ERR_FSAL_NO_ERROR, 0); out: return status; } static fsal_status_t ceph_fsal_setxattrs(struct fsal_obj_handle *handle_pub, setxattr_option4 option, xattrkey4 *xa_name, xattrvalue4 *xa_value) { int rc = 0; int flags; fsal_status_t status = { 0, 0 }; struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); const struct ceph_handle *handle = container_of(handle_pub, const struct ceph_handle, handle); char name[sizeof("user.") + NAME_MAX]; /* * The nfs client only deals with user.* xattrs, but doesn't send * the namespace on the wire. We have to add it in here. */ rc = snprintf(name, sizeof(name), "user.%.*s", xa_name->utf8string_len, xa_name->utf8string_val); if (rc >= sizeof(name)) return ceph2fsal_error(-ENAMETOOLONG); switch (option) { case SETXATTR4_EITHER: flags = 0; break; case SETXATTR4_CREATE: flags = XATTR_CREATE; break; case SETXATTR4_REPLACE: flags = XATTR_REPLACE; break; default: return ceph2fsal_error(-EINVAL); } LogDebug(COMPONENT_FSAL, "SETXATTR of %s to %*.s", name, xa_value->utf8string_len, xa_value->utf8string_val); rc = fsal_ceph_ll_setxattr(export->cmount, handle->i, name, xa_value->utf8string_val, xa_value->utf8string_len, flags, &op_ctx->creds); if (rc < 0) { LogDebug(COMPONENT_FSAL, "SETXATTRS returned rc %d", rc); if (rc == -ERANGE) { status = fsalstat(ERR_FSAL_XATTR2BIG, 0); goto out; } if (rc == -ENODATA) { status = fsalstat(ERR_FSAL_NOXATTR, 0); goto out; } status = ceph2fsal_error(rc); goto out; } status = fsalstat(ERR_FSAL_NO_ERROR, 0); out: return status; } static fsal_status_t ceph_fsal_removexattrs(struct fsal_obj_handle *handle_pub, xattrkey4 *xa_name) { int rc = 0; fsal_status_t status = { 0, 0 }; struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); const struct ceph_handle *handle = container_of(handle_pub, const struct ceph_handle, handle); char name[sizeof("user.") + NAME_MAX]; /* * The nfs client only deals with user.* xattrs, but doesn't send * the namespace on the wire. We have to add it in here. */ rc = snprintf(name, sizeof(name), "user.%.*s", xa_name->utf8string_len, xa_name->utf8string_val); if (rc >= sizeof(name)) return ceph2fsal_error(-ENAMETOOLONG); rc = fsal_ceph_ll_removexattr(export->cmount, handle->i, name, &op_ctx->creds); if (rc < 0) { if (rc == -ERANGE) { status = fsalstat(ERR_FSAL_XATTR2BIG, 0); goto out; } if (rc == -ENODATA) { status = fsalstat(ERR_FSAL_NOXATTR, 0); goto out; } LogDebug(COMPONENT_FSAL, "REMOVEXATTR returned rc %d", rc); status = ceph2fsal_error(rc); goto out; } status = fsalstat(ERR_FSAL_NO_ERROR, 0); out: return status; } static fsal_status_t ceph_fsal_listxattrs(struct fsal_obj_handle *handle_pub, uint32_t maxbytes, nfs_cookie4 *lxa_cookie, bool_t *lxr_eof, xattrlist4 *lxr_names) { char *buf = NULL; int rc, loop; size_t listlen = 0; struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); const struct ceph_handle *handle = container_of(handle_pub, const struct ceph_handle, handle); UserPerm *perms = user_cred2ceph(&op_ctx->creds); fsal_status_t status; if (!perms) return fsalstat(ERR_FSAL_NOMEM, ENOMEM); /* Log Message */ LogFullDebug(COMPONENT_FSAL, "in cookie %llu length %d", (unsigned long long)lxa_cookie, maxbytes); /* Get a listing, but give up if we keep getting ERANGE back. */ loop = 0; do { rc = ceph_ll_listxattr(export->cmount, handle->i, NULL, 0, &listlen, perms); if (rc < 0) { status = ceph2fsal_error(rc); goto out; } gsh_free(buf); buf = gsh_malloc(listlen); rc = ceph_ll_listxattr(export->cmount, handle->i, buf, listlen, &listlen, perms); } while (rc == -ERANGE && loop++ < 5); if (rc < 0) { LogDebug(COMPONENT_FSAL, "ceph_ll_listxattr returned rc %d", rc); if (rc == -ERANGE) { status = fsalstat(ERR_FSAL_SERVERFAULT, 0); goto out; } status = ceph2fsal_error(rc); goto out; } status = fsal_listxattr_helper(buf, listlen, maxbytes, lxa_cookie, lxr_eof, lxr_names); out: gsh_free(buf); ceph_userperm_destroy(perms); return status; } /** * @brief Override functions in ops vector * * This function overrides implemented functions in the ops vector * with versions for this FSAL. * * @param[in] ops Handle operations vector */ void handle_ops_init(struct fsal_obj_ops *ops) { fsal_default_obj_ops_init(ops); ops->release = ceph_fsal_release; ops->merge = ceph_fsal_merge; ops->lookup = ceph_fsal_lookup; ops->mkdir = ceph_fsal_mkdir; ops->mknode = ceph_fsal_mknode; ops->readdir = ceph_fsal_readdir; ops->symlink = ceph_fsal_symlink; ops->readlink = ceph_fsal_readlink; ops->getattrs = ceph_fsal_getattrs; ops->link = ceph_fsal_link; ops->rename = ceph_fsal_rename; ops->unlink = ceph_fsal_unlink; ops->close = ceph_fsal_close; ops->handle_to_wire = ceph_fsal_handle_to_wire; ops->handle_to_key = ceph_fsal_handle_to_key; ops->open2 = ceph_fsal_open2; ops->status2 = ceph_fsal_status2; ops->reopen2 = ceph_fsal_reopen2; ops->read2 = ceph_fsal_read2; ops->write2 = ceph_fsal_write2; ops->commit2 = ceph_fsal_commit2; #ifdef USE_FSAL_CEPH_SETLK ops->lock_op2 = ceph_fsal_lock_op2; #endif #ifdef USE_FSAL_CEPH_LL_DELEGATION ops->lease_op2 = ceph_fsal_lease_op2; #endif ops->setattr2 = ceph_fsal_setattr2; ops->close2 = ceph_fsal_close2; ops->close_func = ceph_close_func; ops->reopen_func = ceph_reopen_func; #ifdef CEPH_PNFS handle_ops_pnfs(ops); #endif /* CEPH_PNFS */ #ifdef USE_CEPH_LL_FALLOCATE ops->fallocate = ceph_fsal_fallocate; #endif ops->getxattrs = ceph_fsal_getxattrs; ops->setxattrs = ceph_fsal_setxattrs; ops->listxattrs = ceph_fsal_listxattrs; ops->removexattrs = ceph_fsal_removexattrs; } nfs-ganesha-6.5/src/FSAL/FSAL_CEPH/internal.c000066400000000000000000000320241473756622300204160ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright © 2012-2014, CohortFS, LLC. * Author: Adam C. Emerson * * contributeur : William Allen Simpson * Marcus Watts * * This program 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 3 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 * 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 * * ------------- */ /** * @file internal.c * @author Adam C. Emerson * @author William Allen Simpson * @date Wed Oct 22 13:24:33 2014 * * @brief Internal definitions for the Ceph FSAL * * This file includes internal function definitions, constants, and * variable declarations used to implement the Ceph FSAL, but not * exposed as part of the API. */ #include #ifdef CEPHFS_POSIX_ACL #include #include #endif /* CEPHFS_POSIX_ACL */ #include #include "fsal_types.h" #include "fsal.h" #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "statx_compat.h" #include "nfs_exports.h" #include "internal.h" #ifdef CEPHFS_POSIX_ACL #include "posix_acls.h" #endif /* CEPHFS_POSIX_ACL */ /** * @brief Construct a new filehandle * * This function constructs a new Ceph FSAL object handle and attaches * it to the export. After this call the attributes have been filled * in and the handdle is up-to-date and usable. * * @param[in] stx ceph_statx data for the file * @param[in] export The export on which the object lives * @param[out] obj Object created * * @return 0 on success, negative error codes on failure. */ void construct_handle(const struct ceph_statx *stx, struct Inode *i, struct ceph_export *export, struct ceph_handle **obj) { /* Pointer to the handle under construction */ struct ceph_handle *constructing = NULL; assert(i); constructing = gsh_calloc(1, sizeof(struct ceph_handle)); constructing->key.hhdl.chk_ino = stx->stx_ino; #ifdef CEPH_NOSNAP constructing->key.hhdl.chk_snap = stx->stx_dev; #endif /* CEPH_NOSNAP */ constructing->key.hhdl.chk_fscid = export->fscid; constructing->key.export_id = export->export.export_id; constructing->i = i; constructing->up_ops = export->export.up_ops; fsal_obj_handle_init(&constructing->handle, &export->export, posix2fsal_type(stx->stx_mode), true); constructing->handle.obj_ops = &CephFSM.handle_ops; constructing->handle.fsid = posix2fsal_fsid(stx->stx_dev); constructing->handle.fileid = stx->stx_ino; if (constructing->handle.type == REGULAR_FILE) { init_fsal_fd(&constructing->fd.fsal_fd, FSAL_FD_GLOBAL, op_ctx->fsal_export); } *obj = constructing; } /** * @brief Release all resources for a handle * * @param[in] obj Handle to release */ void deconstruct_handle(struct ceph_handle *obj) { struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); assert(op_ctx->fsal_export->export_id == obj->key.export_id); ceph_ll_put(export->cmount, obj->i); if (obj->handle.type == REGULAR_FILE) destroy_fsal_fd(&obj->fd.fsal_fd); fsal_obj_handle_fini(&obj->handle, true); gsh_free(obj); } unsigned int attrmask2ceph_want(attrmask_t mask) { unsigned int want = 0; if (mask & ATTR_MODE) want |= CEPH_STATX_MODE; if (mask & ATTR_OWNER) want |= CEPH_STATX_UID; if (mask & ATTR_GROUP) want |= CEPH_STATX_GID; if (mask & ATTR_SIZE) want |= CEPH_STATX_SIZE; if (mask & ATTR_NUMLINKS) want |= CEPH_STATX_NLINK; if (mask & ATTR_SPACEUSED) want |= CEPH_STATX_BLOCKS; if (mask & ATTR_ATIME) want |= CEPH_STATX_ATIME; if (mask & ATTR_CTIME) want |= CEPH_STATX_CTIME; if (mask & ATTR_MTIME) want |= CEPH_STATX_MTIME; if (mask & ATTR_CREATION) want |= CEPH_STATX_BTIME; if (mask & ATTR_CHANGE) want |= CEPH_STATX_VERSION; return want; } void ceph2fsal_attributes(const struct ceph_statx *stx, struct fsal_attrlist *fsalattr) { /* These are always considered to be available */ fsalattr->valid_mask |= ATTR_TYPE | ATTR_FSID | ATTR_RAWDEV | ATTR_FILEID; fsalattr->supported = CEPH_SUPPORTED_ATTRS; fsalattr->type = posix2fsal_type(stx->stx_mode); fsalattr->rawdev = posix2fsal_devt(stx->stx_rdev); fsalattr->fsid = posix2fsal_fsid(stx->stx_dev); fsalattr->fileid = stx->stx_ino; /* Disable seclabels if not enabled in config */ if (!op_ctx_export_has_option(EXPORT_OPTION_SECLABEL_SET)) fsalattr->supported &= ~ATTR4_SEC_LABEL; if (stx->stx_mask & CEPH_STATX_MODE) { fsalattr->valid_mask |= ATTR_MODE; fsalattr->mode = unix2fsal_mode(stx->stx_mode); } if (stx->stx_mask & CEPH_STATX_UID) { fsalattr->valid_mask |= ATTR_OWNER; fsalattr->owner = stx->stx_uid; } if (stx->stx_mask & CEPH_STATX_GID) { fsalattr->valid_mask |= ATTR_GROUP; fsalattr->group = stx->stx_gid; } if (stx->stx_mask & CEPH_STATX_SIZE) { fsalattr->valid_mask |= ATTR_SIZE; fsalattr->filesize = stx->stx_size; } if (stx->stx_mask & CEPH_STATX_NLINK) { fsalattr->valid_mask |= ATTR_NUMLINKS; fsalattr->numlinks = stx->stx_nlink; } if (stx->stx_mask & CEPH_STATX_BLOCKS) { fsalattr->valid_mask |= ATTR_SPACEUSED; fsalattr->spaceused = stx->stx_blocks * S_BLKSIZE; } /* Use full timer resolution */ if (stx->stx_mask & CEPH_STATX_ATIME) { fsalattr->valid_mask |= ATTR_ATIME; fsalattr->atime = stx->stx_atime; } if (stx->stx_mask & CEPH_STATX_CTIME) { fsalattr->valid_mask |= ATTR_CTIME; fsalattr->ctime = stx->stx_ctime; } if (stx->stx_mask & CEPH_STATX_MTIME) { fsalattr->valid_mask |= ATTR_MTIME; fsalattr->mtime = stx->stx_mtime; } if (stx->stx_mask & CEPH_STATX_BTIME) { fsalattr->valid_mask |= ATTR_CREATION; fsalattr->creation = stx->stx_btime; } if (stx->stx_mask & CEPH_STATX_VERSION) { fsalattr->valid_mask |= ATTR_CHANGE; fsalattr->change = stx->stx_version; } } #ifdef CEPHFS_POSIX_ACL /* * @brief Get posix acl from cephfs * @param[in] export Export on which the object lives * @param[in] objhandle Object * @param[in] name Name of the extended attribute * @param[out] p_acl Posix ACL * * @return 0 on success, negative error codes on failure. */ int ceph_get_posix_acl(struct ceph_export *export, struct ceph_handle *objhandle, const char *name, acl_t *p_acl) { char *value = NULL; int rc = 0, size; acl_t acl_tmp = NULL; LogFullDebug(COMPONENT_FSAL, "get POSIX ACL"); /* Get extended attribute size */ size = fsal_ceph_ll_getxattr(export->cmount, objhandle->i, name, NULL, 0, &op_ctx->creds); if (size <= 0) { LogFullDebug(COMPONENT_FSAL, "getxattr returned %d", size); return 0; } value = gsh_malloc(size); /* Read extended attribute's value */ rc = fsal_ceph_ll_getxattr(export->cmount, objhandle->i, name, value, size, &op_ctx->creds); if (rc < 0) { LogMajor(COMPONENT_FSAL, "getxattr returned %d", rc); if (rc == -ENODATA) { rc = 0; } goto out; } /* Convert extended attribute to posix acl */ acl_tmp = xattr_2_posix_acl((struct acl_ea_header *)value, size); if (!acl_tmp) { LogMajor(COMPONENT_FSAL, "failed to convert xattr to posix acl"); rc = -EFAULT; goto out; } *p_acl = acl_tmp; out: gsh_free(value); return rc; } /* * @brief Set Posix ACL * @param[in] export Export on which the object lives * @param[in] objhandle Object * @param[in] is_dir True when object type is directory * @param[in] attrs Attributes * * @return FSAL status. */ fsal_status_t ceph_set_acl(struct ceph_export *export, struct ceph_handle *objhandle, bool is_dir, struct fsal_attrlist *attrs) { int size = 0, count, rc; acl_t acl = NULL; acl_type_t type; char *name = NULL; void *value = NULL; fsal_status_t status = { 0, 0 }; if (!attrs->acl) { LogWarn(COMPONENT_FSAL, "acl is empty"); status = fsalstat(ERR_FSAL_FAULT, 0); goto out; } if (is_dir) { type = ACL_TYPE_DEFAULT; name = ACL_EA_DEFAULT; } else { type = ACL_TYPE_ACCESS; name = ACL_EA_ACCESS; } acl = fsal_acl_2_posix_acl(attrs->acl, type); if (acl_valid(acl) != 0) { LogWarn(COMPONENT_FSAL, "failed to convert fsal acl to posix acl"); status = fsalstat(ERR_FSAL_FAULT, 0); goto out; } count = acl_entries(acl); if (count > 0) { size = posix_acl_xattr_size(count); value = gsh_malloc(size); rc = posix_acl_2_xattr(acl, value, size); if (rc < 0) { LogMajor(COMPONENT_FSAL, "failed to convert posix acl to xattr"); status = fsalstat(ERR_FSAL_FAULT, 0); goto out; } } rc = fsal_ceph_ll_setxattr(export->cmount, objhandle->i, name, value, size, 0, &op_ctx->creds); if (rc < 0) { status = ceph2fsal_error(rc); } out: if (acl) { acl_free((void *)acl); } if (value) { gsh_free(value); } return status; } /* * @brief Get FSAL ACL * @param[in] export Export on which the object lives * @param[in] objhandle Object * @param[in] is_dir True when object type is directory * @param[out] attrs Attributes * * @return 0 on success, negative error codes on failure. */ int ceph_get_acl(struct ceph_export *export, struct ceph_handle *objhandle, bool is_dir, struct fsal_attrlist *attrs) { acl_t e_acl = NULL, i_acl = NULL; fsal_acl_data_t acldata; fsal_ace_t *pace = NULL; fsal_acl_status_t aclstatus; int e_count = 0, i_count = 0, new_count = 0, new_i_count = 0; int rc = 0; rc = ceph_get_posix_acl(export, objhandle, ACL_EA_ACCESS, &e_acl); if (rc < 0) { LogMajor(COMPONENT_FSAL, "failed to get posix acl: %s", ACL_EA_ACCESS); goto out; } e_count = ace_count(e_acl); if (is_dir) { rc = ceph_get_posix_acl(export, objhandle, ACL_EA_DEFAULT, &i_acl); if (rc < 0) { LogMajor(COMPONENT_FSAL, "failed to get posix acl: %s", ACL_EA_DEFAULT); } else { i_count = ace_count(i_acl); } } acldata.naces = 2 * (e_count + i_count); LogDebug(COMPONENT_FSAL, "No of aces present in fsal_acl_t = %d", acldata.naces); if (!acldata.naces) { rc = 0; goto out; } acldata.aces = (fsal_ace_t *)nfs4_ace_alloc(acldata.naces); pace = acldata.aces; if (e_count > 0) { new_count = posix_acl_2_fsal_acl(e_acl, is_dir, false, ACL_FOR_V4, &pace); } else { LogDebug(COMPONENT_FSAL, "effective acl is not set for this object"); } if (i_count > 0) { new_i_count = posix_acl_2_fsal_acl(i_acl, true, true, ACL_FOR_V4, &pace); new_count += new_i_count; } else { LogDebug(COMPONENT_FSAL, "Inherit acl is not set for this directory"); } /* Reallocating acldata into the required size */ acldata.aces = (fsal_ace_t *)gsh_realloc( acldata.aces, new_count * sizeof(fsal_ace_t)); acldata.naces = new_count; attrs->acl = nfs4_acl_new_entry(&acldata, &aclstatus); if (attrs->acl == NULL) { LogCrit(COMPONENT_FSAL, "failed to create a new acl entry"); rc = -EFAULT; goto out; } rc = 0; attrs->valid_mask |= ATTR_ACL; out: if (e_acl) { acl_free((void *)e_acl); } if (i_acl) { acl_free((void *)i_acl); } return rc; } #endif /* CEPHFS_POSIX_ACL */ struct avltree avl_cmount; pthread_rwlock_t cmount_lock = RWLOCK_INITIALIZER; static inline int key_strcmp(const char *left, const char *right) { if (left == NULL && right != NULL) return -1; if (left != NULL && right == NULL) return 1; if (left == right) return 0; return strcmp(left, right); } int ceph_mount_key_cmpf(const struct avltree_node *lhs, const struct avltree_node *rhs) { struct ceph_mount *lk, *rk; int rc; lk = avltree_container_of(lhs, struct ceph_mount, cm_avl_mount); rk = avltree_container_of(rhs, struct ceph_mount, cm_avl_mount); /* cm_mount_path will be non-NULL even if cmount_path was not configured */ assert(lk->cm_mount_path != NULL && rk->cm_mount_path != NULL); rc = key_strcmp(lk->cm_fs_name, rk->cm_fs_name); if (rc != 0) return rc; rc = key_strcmp(lk->cm_mount_path, rk->cm_mount_path); if (rc != 0) return rc; rc = key_strcmp(lk->cm_user_id, rk->cm_user_id); if (rc != 0) return rc; return key_strcmp(lk->cm_secret_key, rk->cm_secret_key); } void ceph_mount_init(void) { avltree_init(&avl_cmount, ceph_mount_key_cmpf, 0); } struct ceph_mount *ceph_mount_lookup(const struct avltree_node *key) { struct avltree_node *node = avltree_inline_lookup(key, &avl_cmount, ceph_mount_key_cmpf); if (node != NULL) return avltree_container_of(node, struct ceph_mount, cm_avl_mount); else return NULL; } void ceph_mount_insert(struct avltree_node *key) { struct avltree_node *node; node = avltree_inline_insert(key, &avl_cmount, ceph_mount_key_cmpf); assert(node == NULL); } void ceph_mount_remove(struct avltree_node *key) { avltree_remove(key, &avl_cmount); } nfs-ganesha-6.5/src/FSAL/FSAL_CEPH/internal.h000066400000000000000000000167461473756622300204400ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright © 2012-2014, CohortFS, LLC. * Author: Adam C. Emerson * * contributeur : William Allen Simpson * Marcus Watts * * This program 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 3 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 * 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 * * ------------- */ /** * @file internal.h * @author Adam C. Emerson * @author William Allen Simpson * @date Wed Oct 22 13:24:33 2014 * * @brief Internal declarations for the Ceph FSAL * * This file includes declarations of data types, functions, * variables, and constants for the Ceph FSAL. */ #ifndef FSAL_CEPH_INTERNAL_INTERNAL__ #define FSAL_CEPH_INTERNAL_INTERNAL__ #include #include "fsal.h" #include "fsal_types.h" #include "fsal_api.h" #include "fsal_convert.h" #include #include #include "statx_compat.h" #include "FSAL/fsal_commonlib.h" #include "avltree.h" /* Max length of a user_id string that we pass to ceph_mount */ #define MAXUIDLEN (64) /* Max length of a secret key for this user */ #define MAXSECRETLEN (88) /** * Ceph Main (global) module object */ struct ceph_fsal_module { struct fsal_module fsal; struct fsal_obj_ops handle_ops; char *conf_path; bool client_oc; bool async; bool zerocopy; }; extern struct ceph_fsal_module CephFSM; struct ceph_mount { /** Node to put this in an avl tree. * Indexed by cm_user_id and cm_secret_key. */ struct avltree_node cm_avl_mount; /** List of exports that use this ceph_mount */ struct glist_head cm_exports; /** Count of how many exports are using this ceph mount */ int32_t cm_refcnt; /** The mount object used to access all Ceph methods */ struct ceph_mount_info *cmount; /** cephfs filesystem */ char *cm_fs_name; /** The cephfs mount point (may be different than export path */ char *cm_mount_path; /** The RADOS user_id */ char *cm_user_id; /** The RADOS secret key */ char *cm_secret_key; /* Cluster fsid for named fs' */ int64_t cm_fscid; /* cmpount export_id for reclaim uuid */ uint16_t cm_export_id; /* an export for release upcalls */ struct ceph_export *cm_export; }; /** * Ceph private export object */ struct ceph_export { struct fsal_export export; /*< The public export object */ struct glist_head cm_list; /*< On list of exports for ceph_mount */ struct ceph_mount *cm; /*< The ceph_mount descriptor */ struct ceph_mount_info *cmount; /*< The mount object used to access all Ceph methods on this export. */ struct ceph_handle *root; /*< The root handle */ char *user_id; /* cephx user_id for this mount */ char *secret_key; char *sec_label_xattr; /* name of xattr for security label */ char *fs_name; /* filesystem name */ char *cmount_path; /* path to cmount at */ int64_t fscid; /* Cluster fsid for named fs' */ }; struct ceph_fd { /** open and share mode plus fd management */ struct fsal_fd fsal_fd; /** The cephfs file descriptor. */ Fh *fd; }; struct ceph_state_fd { /** state MUST be first to use default free_state */ struct state_t state; struct ceph_fd ceph_fd; }; /** * The 'private' Ceph FSAL handle */ struct ceph_host_handle { uint64_t chk_ino; uint64_t chk_snap; int64_t chk_fscid; } __attribute__((__packed__)); struct ceph_handle_key { /* NOTE: The ceph_host_handle MUST be first in this structure */ struct ceph_host_handle hhdl; uint16_t export_id; }; struct ceph_handle { struct fsal_obj_handle handle; /*< The public handle */ struct ceph_fd fd; struct Inode *i; /*< The Ceph inode */ const struct fsal_up_vector *up_ops; /*< Upcall operations */ struct ceph_handle_key key; /*< The handle-key that includes the ceph_host_handle. */ struct fsal_share share; #ifdef CEPH_PNFS uint64_t rd_issued; uint64_t rd_serial; uint64_t rw_issued; uint64_t rw_serial; uint64_t rw_max_len; #endif /* CEPH_PNFS */ }; #ifdef CEPH_PNFS /** * The wire content of a DS (data server) handle */ struct ds_wire { struct wire_handle wire; /*< All the information of a regular handle */ struct ceph_file_layout layout; /*< Layout information */ uint64_t snapseq; /*< And a single entry giving a degenerate snaprealm. */ }; /** * The full, 'private' DS (data server) handle */ struct ds { struct fsal_ds_handle ds; /*< Public DS handle */ struct ds_wire wire; /*< Wire data */ bool connected; /*< True if the handle has been connected (in Ceph) */ }; #endif /* CEPH_PNFS */ #ifdef CEPHFS_POSIX_ACL #define POSIX_ACL_ATTR ATTR_ACL #else /* CEPHFS_POSIX_ACL */ #define POSIX_ACL_ATTR 0 #endif /* CEPHFS_POSIX_ACL */ #define CEPH_SUPPORTED_ATTRS \ ((const attrmask_t)(ATTRS_POSIX | ATTR4_SEC_LABEL | ATTR4_XATTR | \ POSIX_ACL_ATTR)) #define CEPH_SETTABLE_ATTRIBUTES \ ((const attrmask_t)(ATTR_MODE | ATTR_OWNER | ATTR_GROUP | ATTR_ATIME | \ ATTR_CTIME | ATTR_MTIME | ATTR_SIZE | \ ATTR_MTIME_SERVER | ATTR_ATIME_SERVER | \ ATTR4_SEC_LABEL | POSIX_ACL_ATTR)) /* Prototypes */ void construct_handle(const struct ceph_statx *stx, struct Inode *i, struct ceph_export *export, struct ceph_handle **obj); void deconstruct_handle(struct ceph_handle *obj); /** * @brief FSAL status from Ceph error * * This function returns a fsal_status_t with the FSAL error as the * major, and the posix error as minor. (Ceph's error codes are just * negative signed versions of POSIX error codes.) * * @param[in] ceph_errorcode Ceph error (negative Posix) * * @return FSAL status. */ static inline fsal_status_t ceph2fsal_error(const int ceph_errorcode) { return fsalstat(posix2fsal_error(-ceph_errorcode), -ceph_errorcode); } unsigned int attrmask2ceph_want(attrmask_t mask); void ceph2fsal_attributes(const struct ceph_statx *stx, struct fsal_attrlist *fsalattr); void export_ops_init(struct export_ops *ops); void handle_ops_init(struct fsal_obj_ops *ops); #ifdef CEPH_PNFS void pnfs_ds_ops_init(struct fsal_pnfs_ds_ops *ops); void export_ops_pnfs(struct export_ops *ops); void handle_ops_pnfs(struct fsal_obj_ops *ops); #endif /* CEPH_PNFS */ struct state_t *ceph_alloc_state(struct fsal_export *exp_hdl, enum state_type state_type, struct state_t *related_state); #ifdef CEPHFS_POSIX_ACL fsal_status_t ceph_set_acl(struct ceph_export *export, struct ceph_handle *objhandle, bool is_dir, struct fsal_attrlist *attrs); int ceph_get_acl(struct ceph_export *export, struct ceph_handle *objhandle, bool is_dir, struct fsal_attrlist *attrs); #endif /* CEPHFS_POSIX_ACL */ extern pthread_rwlock_t cmount_lock; void ceph_mount_init(void); struct ceph_mount *ceph_mount_lookup(const struct avltree_node *key); void ceph_mount_insert(struct avltree_node *key); void ceph_mount_remove(struct avltree_node *key); #endif /* !FSAL_CEPH_INTERNAL_INTERNAL__ */ nfs-ganesha-6.5/src/FSAL/FSAL_CEPH/main.c000066400000000000000000000575561473756622300175470ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright © 2012-2014, CohortFS, LLC. * Author: Adam C. Emerson * * contributeur : William Allen Simpson * Marcus Watts * * This program 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 3 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 * 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 * * ------------- */ /** * @file FSAL_CEPH/main.c * @author Adam C. Emerson * @author William Allen Simpson * @date Wed Oct 22 13:24:33 2014 * * @brief Implementation of FSAL module founctions for Ceph * * This file implements the module functions for the Ceph FSAL, for * initialization, teardown, configuration, and creation of exports. */ #include #include #include "fsal.h" #include "fsal_types.h" #include "FSAL/fsal_init.h" #include "FSAL/fsal_commonlib.h" #include "fsal_api.h" #include "internal.h" #include "abstract_mem.h" #include "nfs_exports.h" #include "export_mgr.h" #include "statx_compat.h" #include "nfs_core.h" #include "sal_functions.h" /** * The name of this module. */ static const char *module_name = "Ceph"; /** * Ceph global module object. */ struct ceph_fsal_module CephFSM = { .fsal = { .fs_info = { #if 0 .umask = 0, #endif /* fixed */ .symlink_support = true, .link_support = true, .cansettime = true, .no_trunc = true, .chown_restricted = true, .case_preserving = true, .maxfilesize = INT64_MAX, .maxread = FSAL_MAXIOSIZE, .maxwrite = FSAL_MAXIOSIZE, .maxlink = 1024, .maxnamelen = NAME_MAX, .maxpathlen = PATH_MAX, #ifdef CEPHFS_POSIX_ACL .acl_support = FSAL_ACLSUPPORT_ALLOW, #else /* CEPHFS_POSIX_ACL */ .acl_support = 0, #endif /* CEPHFS_POSIX_ACL */ .supported_attrs = CEPH_SUPPORTED_ATTRS, #ifdef USE_FSAL_CEPH_SETLK .lock_support = true, .lock_support_async_block = false, #endif .unique_handles = true, .homogenous = true, #ifdef USE_FSAL_CEPH_LL_DELEGATION .delegations = FSAL_OPTION_FILE_READ_DELEG, #endif .readdir_plus = true, .xattr_support = true, #ifdef USE_FSAL_CEPH_FS_ZEROCOPY_IO .allocate_own_read_buffer = true, #else .allocate_own_read_buffer = false, #endif .expire_time_parent = -1, } } }; static int ceph_conf_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { struct ceph_fsal_module *CephFSM = self_struct; if (CephFSM->client_oc && CephFSM->zerocopy) { LogWarn(COMPONENT_FSAL, "client_oc and zerocopy are incompatible"); err_type->invalid = true; return 1; } return 0; } static struct config_item ceph_items[] = { CONF_ITEM_PATH("ceph_conf", 1, MAXPATHLEN, NULL, ceph_fsal_module, conf_path), CONF_ITEM_MODE("umask", 0, ceph_fsal_module, fsal.fs_info.umask), CONF_ITEM_BOOL("client_oc", false, ceph_fsal_module, client_oc), CONF_ITEM_BOOL("async", false, ceph_fsal_module, async), CONF_ITEM_BOOL("zerocopy", false, ceph_fsal_module, zerocopy), CONFIG_EOL }; static struct config_block ceph_block = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.ceph", .blk_desc.name = "Ceph", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = ceph_items, .blk_desc.u.blk.commit = ceph_conf_commit }; /* Module methods */ /* init_config * must be called with a reference taken (via lookup_fsal) */ static fsal_status_t init_config(struct fsal_module *module_in, config_file_t config_struct, struct config_error_type *err_type) { struct ceph_fsal_module *myself = container_of(module_in, struct ceph_fsal_module, fsal); LogDebug(COMPONENT_FSAL, "Ceph module setup."); (void)load_config_from_parse(config_struct, &ceph_block, myself, true, err_type); if (!config_error_is_harmless(err_type)) return fsalstat(ERR_FSAL_INVAL, 0); display_fsinfo(&myself->fsal); return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t find_cephfs_root(struct ceph_export *export, Inode **pi, struct ceph_statx *stx, bool *stxr) { int lmp, rc; struct user_cred root_creds = {}; char *walk_path; #ifdef USE_FSAL_CEPH_LL_LOOKUP_ROOT /* If no cmount_path or cmount_path is the same as CTX_FULLPATH(op_ctx) * then we just want to lookup the root of the cmount. */ if (export->cmount_path == NULL || strcmp(export->cmount_path, CTX_FULLPATH(op_ctx)) == 0) { rc = ceph_ll_lookup_root(export->cmount, pi); if (rc) { LogWarn(COMPONENT_FSAL, "Root lookup failed for %s : %s", CTX_FULLPATH(op_ctx), strerror(-rc)); } *stxr = false; goto out; } #endif if (export->cmount_path != NULL) { /* Find the portion of CTX_FULLPATH(op_ctx) that is deeper than * cmount_path. For example, if: * cmount_path = "/export" * and * CTX_FULLPATH(op_ctx) = "/export/exp1" * then we want to walk from the root of the cmount (at /export) * to /exp1. * * If cmount_path is just "/", we will want the whole * CTX_FULLPATH(op_ctx) */ lmp = strlen(export->cmount_path); if (lmp == 1) { /* If cmount_path is "/" we need the leading '/'. */ lmp = 0; } walk_path = CTX_FULLPATH(op_ctx) + lmp; } else { /* No cmount_path, so we did a cmount at CTX_FULLPATH(op_ctx) * and now we just need to walk to the root of the cmount. */ walk_path = "/"; } LogDebug(COMPONENT_FSAL, "Cmount path %s, walk_path %s", export->cmount_path, walk_path); /* Now walk the path */ rc = fsal_ceph_ll_walk(export->cmount, walk_path, pi, stx, false, &root_creds); if (rc) { LogWarn(COMPONENT_FSAL, "ceph_ll_walk failed for %s : %s", walk_path, strerror(-rc)); } *stxr = true; out: return ceph2fsal_error(rc); } static int ceph_export_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { struct ceph_export *export = self_struct; int lmp, lpath; /* If cmount_path is not configured, no further checks */ if (export->cmount_path == NULL) return 0; if (export->cmount_path[0] != '/') { LogWarn(COMPONENT_FSAL, "cmount path not starting with / : %s", export->cmount_path); err_type->invalid = true; return 1; } /* Get length of cmount_path and remove trailing slash, adjusting * length. */ lmp = strlen(export->cmount_path); while ((export->cmount_path[lmp - 1] == '/') && (lmp > 1)) { /* Trim a trailing '/' */ lmp--; } export->cmount_path[lmp] = '\0'; /* Get the length of the full path from the export */ lpath = strlen(op_ctx->ctx_export->cfg_fullpath); LogDebug(COMPONENT_FSAL, "Commit %s mount path %s", op_ctx->ctx_export->cfg_fullpath, export->cmount_path); if (lpath < lmp) { LogWarn(COMPONENT_FSAL, "cmount path is bigger than export path"); err_type->invalid = true; return 1; } if (lmp > 1 && strncmp(export->cmount_path, CTX_FULLPATH(op_ctx), lmp) != 0) { /* path is not a sub-directory of mount_path - error */ LogWarn(COMPONENT_FSAL, "Export path is not sub-directory of cmount path, cmount_path : %s, export : %s", export->cmount_path, op_ctx->ctx_export->cfg_fullpath); err_type->invalid = true; return 1; } return 0; } static struct config_item export_params[] = { CONF_ITEM_NOOP("name"), CONF_ITEM_STR("user_id", 0, MAXUIDLEN, NULL, ceph_export, user_id), CONF_ITEM_STR("filesystem", 0, NAME_MAX, NULL, ceph_export, fs_name), CONF_ITEM_PATH("cmount_path", 1, MAXPATHLEN, NULL, ceph_export, cmount_path), CONF_ITEM_STR("secret_access_key", 0, MAXSECRETLEN, NULL, ceph_export, secret_key), CONF_ITEM_STR("sec_label_xattr", 0, 256, "security.selinux", ceph_export, sec_label_xattr), CONFIG_EOL }; static struct config_block export_param_block = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.ceph-export%d", .blk_desc.name = "FSAL", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = export_params, .blk_desc.u.blk.commit = ceph_export_commit }; #ifdef USE_FSAL_CEPH_LL_DELEGATION static void enable_delegations(struct ceph_mount *cm) { struct export_perms *export_perms = &op_ctx->ctx_export->export_perms; if (export_perms->options & EXPORT_OPTION_DELEGATIONS) { /* * Ganesha will time out delegations when the recall fails * for two lease periods. We add just a little bit above that * as a scheduling fudge-factor. * * The idea here is to make this long enough to give ganesha * a chance to kick out a misbehaving client, but shorter * than ceph cluster-wide MDS session timeout. * * Exceeding the MDS session timeout may result in the client * (ganesha) being blacklisted in the cluster. Fixing that can * require a long wait and/or administrative intervention. */ unsigned int dt = nfs_param.nfsv4_param.lease_lifetime * 2 + 5; int ceph_status; LogDebug(COMPONENT_FSAL, "Setting deleg timeout to %u", dt); ceph_status = ceph_set_deleg_timeout(cm->cmount, dt); if (ceph_status != 0) { export_perms->options &= ~EXPORT_OPTION_DELEGATIONS; LogWarn(COMPONENT_FSAL, "Unable to set delegation timeout for %s. Disabling delegation support: %s", CTX_FULLPATH(op_ctx), strerror(-ceph_status)); } } } #else /* !USE_FSAL_CEPH_LL_DELEGATION */ static inline void enable_delegations(struct ceph_mount *cm) { } #endif /* USE_FSAL_CEPH_LL_DELEGATION */ #ifdef USE_FSAL_CEPH_RECLAIM_RESET #define RECLAIM_UUID_PREFIX "ganesha-" static int reclaim_reset(struct ceph_mount *cm) { int ceph_status; char *nodeid, *uuid; size_t len; /* * Set long timeout for the session to ensure that MDS doesn't lose * state before server can come back and do recovery. */ ceph_set_session_timeout(cm->cmount, 300); /* * For the uuid here, we just use whatever ganesha- + whatever * nodeid the recovery backend reports. */ ceph_status = nfs_recovery_get_nodeid(&nodeid); if (ceph_status != 0) { LogEvent(COMPONENT_FSAL, "couldn't get nodeid: %s", strerror(errno)); return ceph_status; } len = strlen(RECLAIM_UUID_PREFIX) + strlen(nodeid) + 1 + 4 + 1; uuid = gsh_malloc(len); (void)snprintf(uuid, len, RECLAIM_UUID_PREFIX "%s-%4.4hx", nodeid, cm->cm_export_id); /* If this fails, log a message but soldier on */ LogDebug(COMPONENT_FSAL, "Issuing reclaim reset for %s", uuid); ceph_status = ceph_start_reclaim(cm->cmount, uuid, CEPH_RECLAIM_RESET); if (ceph_status) LogEvent(COMPONENT_FSAL, "start_reclaim failed: %s", strerror(-ceph_status)); ceph_finish_reclaim(cm->cmount); ceph_set_uuid(cm->cmount, uuid); gsh_free(nodeid); gsh_free(uuid); return 0; } #undef RECLAIM_UUID_PREFIX #else static inline int reclaim_reset(struct ceph_mount *cm) { return 0; } #endif #ifdef USE_FSAL_CEPH_GET_FS_CID static int select_filesystem(struct ceph_mount *cm) { int ceph_status; if (cm->cm_fs_name) { ceph_status = ceph_select_filesystem(cm->cmount, cm->cm_fs_name); if (ceph_status != 0) { LogCrit(COMPONENT_FSAL, "Unable to set filesystem to %s.", cm->cm_fs_name); return ceph_status; } } return 0; } #else /* USE_FSAL_CEPH_GET_FS_CID */ static int select_filesystem(struct ceph_mount *cm) { if (cm->fs_name) { LogCrit(COMPONENT_FSAL, "This libcephfs version doesn't support named filesystems."); return -EINVAL; } return 0; } #endif /* USE_FSAL_CEPH_GET_FS_CID */ #ifdef USE_FSAL_CEPH_REGISTER_CALLBACKS static void ino_release_cb(void *handle, vinodeno_t vino) { struct ceph_mount *cm = handle; struct ceph_handle_key key; struct gsh_buffdesc fh_desc; LogDebug(COMPONENT_FSAL, "libcephfs asking to release 0x%lx:0x%lx:0x%lx", cm->cm_fscid, vino.snapid.val, vino.ino.val); key.hhdl.chk_ino = vino.ino.val; key.hhdl.chk_snap = vino.snapid.val; key.hhdl.chk_fscid = cm->cm_fscid; key.export_id = cm->cm_export_id; fh_desc.addr = &key; fh_desc.len = sizeof(key); PTHREAD_RWLOCK_rdlock(&cmount_lock); cm->cm_export->export.up_ops->try_release(cm->cm_export->export.up_ops, &fh_desc, 0); PTHREAD_RWLOCK_unlock(&cmount_lock); } /* Callback for inode invalidation. This callback is triggered when ceph client * cache is invalidated due to file attribute change */ static void ino_invalidate_cb(void *handle, vinodeno_t vino, int64_t offset, int64_t len) { struct ceph_mount *cm = handle; struct ceph_handle_key key; struct gsh_buffdesc fh_desc; LogDebug(COMPONENT_FSAL, "libcephfs asking to invalidate 0x%lx:0x%lx:0x%lx", cm->cm_fscid, vino.snapid.val, vino.ino.val); key.hhdl.chk_ino = vino.ino.val; key.hhdl.chk_snap = vino.snapid.val; key.hhdl.chk_fscid = cm->cm_fscid; key.export_id = cm->cm_export_id; fh_desc.addr = &key; fh_desc.len = sizeof(key); cm->cm_export->export.up_ops->invalidate( cm->cm_export->export.up_ops, &fh_desc, FSAL_UP_INVALIDATE_CACHE); } static mode_t umask_cb(void *handle) { mode_t umask = CephFSM.fsal.fs_info.umask; LogDebug(COMPONENT_FSAL, "libcephfs set umask = %04o by umask callback", umask); return umask; } static void register_callbacks(struct ceph_mount *cm) { struct ceph_client_callback_args args = { .handle = cm, .ino_cb = ino_invalidate_cb, .ino_release_cb = ino_release_cb, .umask_cb = umask_cb }; ceph_ll_register_callbacks(cm->cmount, &args); } #else /* USE_FSAL_CEPH_REGISTER_CALLBACKS */ static void register_callbacks(struct ceph_mount *cm) { LogWarnOnce( COMPONENT_FSAL, "This libcephfs does not support registering callbacks. Ganesha will be unable to respond to MDS cache pressure."); } #endif /* USE_FSAL_CEPH_REGISTER_CALLBACKS */ /** * @brief Create a new export under this FSAL * * This function creates a new export object for the Ceph FSAL. * * @todo ACE: We do not handle re-exports of the same cluster in a * sane way. Currently we create multiple handles and cache objects * pointing to the same one. This is not necessarily wrong, but it is * inefficient. It may also not be something we expect to use enough * to care about. * * @param[in] module_in The supplied module handle * @param[in] path The path to export * @param[in] options Export specific options for the FSAL * @param[in,out] list_entry Our entry in the export list * @param[in] next_fsal Next stacked FSAL * @param[out] pub_export Newly created FSAL export object * * @return FSAL status. */ static fsal_status_t create_export(struct fsal_module *module_in, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops) { /* The ceph module */ struct ceph_fsal_module *my_module = container_of(module_in, struct ceph_fsal_module, fsal); /* The status code to return */ fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; /* The internal export object */ struct ceph_export *export = gsh_calloc(1, sizeof(struct ceph_export)); /* The 'private' root handle */ struct ceph_handle *handle = NULL; /* Root inode */ struct Inode *i = NULL; /* Stat for root */ struct ceph_statx stx; /* Return code */ int rc; /* Return code from Ceph calls */ int ceph_status; /* Ceph mount key */ struct ceph_mount cm_key; /* Ceph mount */ struct ceph_mount *cm; /* stx is filled in */ bool stxr = false; fsal_export_init(&export->export); export_ops_init(&export->export.exp_ops); /* get params for this export, if any */ if (parse_node) { rc = load_config_from_node(parse_node, &export_param_block, export, true, err_type); if (rc != 0) { gsh_free(export); LogWarn(COMPONENT_FSAL, "Unable to load config for export : %s", CTX_FULLPATH(op_ctx)); return fsalstat(ERR_FSAL_INVAL, 0); } } memset(&cm_key, 0, sizeof(cm_key)); cm_key.cm_fs_name = export->fs_name; cm_key.cm_user_id = export->user_id; cm_key.cm_secret_key = export->secret_key; /* If cmount_path is configured, use that, otherwise use * CTX_FULLPATH(op_ctx). This allows an export where cmount_path * was going to be the same as CTX_FULLPATH(op_ctx) to share the * cmount with other exports that use the same cmount_path (but then * MUST be exporting a sub-directory) and cmount_path need not be * specified for the export where CTX_FULLPATH(op_ctx) is the same as * that later cmount_path. */ if (export->cmount_path != NULL) cm_key.cm_mount_path = export->cmount_path; else cm_key.cm_mount_path = CTX_FULLPATH(op_ctx); PTHREAD_RWLOCK_wrlock(&cmount_lock); cm = ceph_mount_lookup(&cm_key.cm_avl_mount); if (cm != NULL) { cm->cm_refcnt++; LogDebug(COMPONENT_FSAL, "Re-using cmount %s for %s", cm->cm_mount_path, CTX_FULLPATH(op_ctx)); goto has_cmount; } cm = gsh_calloc(1, sizeof(*cm)); cm->cm_refcnt = 1; if (export->fs_name) cm->cm_fs_name = gsh_strdup(export->fs_name); if (export->cmount_path) cm->cm_mount_path = gsh_strdup(export->cmount_path); else cm->cm_mount_path = gsh_strdup(CTX_FULLPATH(op_ctx)); if (export->user_id) cm->cm_user_id = gsh_strdup(export->user_id); if (export->secret_key) cm->cm_secret_key = gsh_strdup(export->secret_key); LogDebug(COMPONENT_FSAL, "New cmount %s for %s", cm->cm_mount_path, CTX_FULLPATH(op_ctx)); cm->cm_export_id = export->export.export_id; cm->cm_export = export; glist_init(&cm->cm_exports); ceph_mount_insert(&cm->cm_avl_mount); /* allocates ceph_mount_info */ ceph_status = ceph_create(&cm->cmount, cm->cm_user_id); if (ceph_status != 0) { status.major = ERR_FSAL_SERVERFAULT; LogCrit(COMPONENT_FSAL, "Unable to create Ceph handle for %s : %s", CTX_FULLPATH(op_ctx), strerror(-ceph_status)); goto error; } ceph_status = ceph_conf_read_file(cm->cmount, CephFSM.conf_path); if (ceph_status != 0) { status.major = ERR_FSAL_SERVERFAULT; LogCrit(COMPONENT_FSAL, "Unable to read Ceph configuration for %s : %s", CTX_FULLPATH(op_ctx), strerror(-ceph_status)); goto error; } if (cm->cm_secret_key) { ceph_status = ceph_conf_set(cm->cmount, "key", cm->cm_secret_key); if (ceph_status) { status.major = ERR_FSAL_INVAL; LogCrit(COMPONENT_FSAL, "Unable to set Ceph secret key for %s: %s", CTX_FULLPATH(op_ctx), strerror(-ceph_status)); goto error; } } /* * Workaround for broken libcephfs that doesn't handle the path * given in ceph_mount properly. Should be harmless for fixed * libcephfs as well (see http://tracker.ceph.com/issues/18254). */ ceph_status = ceph_conf_set(cm->cmount, "client_mountpoint", "/"); if (ceph_status) { status.major = ERR_FSAL_INVAL; LogCrit(COMPONENT_FSAL, "Unable to set Ceph client_mountpoint: %s", strerror(-ceph_status)); goto error; } ceph_status = ceph_conf_set(cm->cmount, "client_acl_type", "posix_acl"); if (ceph_status < 0) { status.major = ERR_FSAL_SERVERFAULT; LogCrit(COMPONENT_FSAL, "Unable to set Ceph client_acl_type: %s", strerror(-ceph_status)); goto error; } ceph_status = ceph_conf_set(cm->cmount, "client_oc", my_module->client_oc ? "true" : "false"); if (ceph_status) { status.major = ERR_FSAL_INVAL; LogCrit(COMPONENT_FSAL, "Unable to set Ceph client_oc: %d", ceph_status); goto error; } ceph_status = ceph_init(cm->cmount); if (ceph_status != 0) { status.major = ERR_FSAL_SERVERFAULT; LogCrit(COMPONENT_FSAL, "Unable to init Ceph handle : %s", strerror(-ceph_status)); goto error; } register_callbacks(cm); ceph_status = select_filesystem(cm); if (ceph_status != 0) { status.major = ERR_FSAL_SERVERFAULT; LogCrit(COMPONENT_FSAL, "Unable to select/use file system for %s : %s", CTX_FULLPATH(op_ctx), strerror(-ceph_status)); goto error; } ceph_status = reclaim_reset(cm); if (ceph_status != 0) { status.major = ERR_FSAL_SERVERFAULT; LogCrit(COMPONENT_FSAL, "Unable to do reclaim_reset for %s : %s", CTX_FULLPATH(op_ctx), strerror(-ceph_status)); goto error; } ceph_status = ceph_mount(cm->cmount, cm->cm_mount_path); if (ceph_status != 0) { status.major = ERR_FSAL_SERVERFAULT; LogCrit(COMPONENT_FSAL, "Unable to mount Ceph cluster for %s : %s", CTX_FULLPATH(op_ctx), strerror(-ceph_status)); goto error; } #ifdef USE_FSAL_CEPH_GET_FS_CID /* Fetch fscid for use in filehandles */ cm->cm_fscid = ceph_get_fs_cid(cm->cmount); if (cm->cm_fscid < 0) { status.major = ERR_FSAL_SERVERFAULT; LogCrit(COMPONENT_FSAL, "Error getting fscid for %s.", cm->cm_fs_name); goto error; } #endif /* USE_FSAL_CEPH_GET_FS_CID */ enable_delegations(cm); has_cmount: export->cm = cm; export->cmount = cm->cmount; export->fscid = cm->cm_fscid; export->export.fsal = module_in; export->export.up_ops = up_ops; glist_add_tail(&cm->cm_exports, &export->cm_list); LogDebug(COMPONENT_FSAL, "Ceph module export %s.", CTX_FULLPATH(op_ctx)); status = find_cephfs_root(export, &i, &stx, &stxr); if (FSAL_IS_ERROR(status)) { LogCrit(COMPONENT_FSAL, "Error finding root for %s.", CTX_FULLPATH(op_ctx)); goto error; } if (!stxr) { rc = fsal_ceph_ll_getattr(export->cmount, i, &stx, CEPH_STATX_HANDLE_MASK, &op_ctx->creds); if (rc < 0) { LogCrit(COMPONENT_FSAL, "Ceph getattr failed %s : %s", CTX_FULLPATH(op_ctx), strerror(-rc)); status = ceph2fsal_error(rc); goto error; } } LogDebug(COMPONENT_FSAL, "Ceph module export %s root %" PRIx64, CTX_FULLPATH(op_ctx), stx.stx_ino); construct_handle(&stx, i, export, &handle); export->root = handle; op_ctx->fsal_export = &export->export; if (fsal_attach_export(module_in, &export->export.exports) != 0) { status.major = ERR_FSAL_SERVERFAULT; LogCrit(COMPONENT_FSAL, "Unable to attach export for %s.", CTX_FULLPATH(op_ctx)); goto error; } PTHREAD_RWLOCK_unlock(&cmount_lock); return status; error: if (i) ceph_ll_put(export->cmount, i); /* Detach this export from the ceph_mount */ glist_del(&export->cm_list); if (--cm->cm_refcnt == 0) { /* This was the initial reference */ if (cm->cmount) ceph_shutdown(cm->cmount); ceph_mount_remove(&cm->cm_avl_mount); gsh_free(cm->cm_fs_name); gsh_free(cm->cm_mount_path); gsh_free(cm->cm_user_id); gsh_free(cm->cm_secret_key); gsh_free(cm); } gsh_free(export); PTHREAD_RWLOCK_unlock(&cmount_lock); return status; } /** * @brief Initialize and register the FSAL * * This function initializes the FSAL module handle, being called * before any configuration or even mounting of a Ceph cluster. It * exists solely to produce a properly constructed FSAL module * handle. */ MODULE_INIT void init(void) { struct fsal_module *myself = &CephFSM.fsal; LogDebug(COMPONENT_FSAL, "Ceph module registering."); if (register_fsal(myself, module_name, FSAL_MAJOR_VERSION, FSAL_MINOR_VERSION, FSAL_ID_CEPH) != 0) { /* The register_fsal function prints its own log message if it fails */ LogCrit(COMPONENT_FSAL, "Ceph module failed to register."); } ceph_mount_init(); /* Set up module operations */ #ifdef CEPH_PNFS myself->m_ops.fsal_pnfs_ds_ops = pnfs_ds_ops_init; #endif /* CEPH_PNFS */ myself->m_ops.create_export = create_export; myself->m_ops.init_config = init_config; /* Initialize the fsal_obj_handle ops for FSAL CEPH */ handle_ops_init(&CephFSM.handle_ops); } /** * @brief Release FSAL resources * * This function unregisters the FSAL and frees its module handle. * The Ceph FSAL has no other resources to release on the per-FSAL * level. */ MODULE_FINI void finish(void) { LogDebug(COMPONENT_FSAL, "Ceph module finishing."); if (unregister_fsal(&CephFSM.fsal) != 0) { LogCrit(COMPONENT_FSAL, "Unable to unload Ceph FSAL. Dying with extreme prejudice."); abort(); } } nfs-ganesha-6.5/src/FSAL/FSAL_CEPH/mds.c000066400000000000000000000503341473756622300173710ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright © 2012-2014, CohortFS, LLC. * Author: Adam C. Emerson * * This program 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 3 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 * 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 * * ------------- */ #include "gsh_rpc.h" #include #include "fsal.h" #include "fsal_types.h" #include "fsal_api.h" #include "fsal_up.h" #include "pnfs_utils.h" #include "internal.h" #include "nfs_exports.h" #include "FSAL/fsal_commonlib.h" #include "statx_compat.h" #ifdef CEPH_PNFS /** * Linux supports a stripe pattern with no more than 4096 stripes, but * for now we stick to 1024 to keep them da_addrs from being too * gigantic. */ static const size_t BIGGEST_PATTERN = 1024; /** * @file FSAL_CEPH/mds.c * @author Adam C. Emerson * @date Wed Oct 22 13:24:33 2014 * * @brief pNFS Metadata Server Operations for the Ceph FSAL * * This file implements the layoutget, layoutreturn, layoutcommit, * getdeviceinfo, and getdevicelist operations and export query * support for the Ceph FSAL. */ static bool initiate_recall(vinodeno_t vi, bool write, void *opaque) { /* The private 'full' object handle */ struct ceph_handle *handle = (struct ceph_handle *)opaque; /* Return code from upcall operation */ state_status_t status = STATE_SUCCESS; struct gsh_buffdesc key = { .addr = &handle->vi, .len = sizeof(vinodeno_t) }; struct pnfs_segment segment = { .offset = 0, .length = UINT64_MAX, .io_mode = (write ? LAYOUTIOMODE4_RW : LAYOUTIOMODE4_READ) }; status = handle->up_ops->layoutrecall(&key, LAYOUT4_NFSV4_1_FILES, false, &segment, NULL, NULL); if (status != STATE_SUCCESS) return false; return true; } /** * @brief Describe a Ceph striping pattern * * At present, we support a files based layout only. The CRUSH * striping pattern is a-periodic * * @param[in] export_pub Public export handle * @param[out] da_addr_body Stream we write the result to * @param[in] type Type of layout that gave the device * @param[in] deviceid The device to look up * * @return Valid error codes in RFC 5661, p. 365. */ static nfsstat4 getdeviceinfo(struct fsal_export *export_pub, XDR *da_addr_body, const layouttype4 type, const struct pnfs_deviceid *deviceid) { /* Full 'private' export */ struct ceph_export *export = container_of(export_pub, struct ceph_export, export); /* The number of Ceph OSDs in the cluster */ unsigned int num_osds = ceph_ll_num_osds(export->cmount); /* Minimal information needed to get layout info */ vinodeno_t vinode; /* Structure containing the storage parameters of the file within the Ceph cluster. */ struct ceph_file_layout file_layout; /* Currently, all layouts have the same number of stripes */ uint32_t stripes = BIGGEST_PATTERN; /* Index for iterating over stripes */ size_t stripe = 0; /* Index for iterating over OSDs */ size_t osd = 0; /* NFSv4 status code */ nfsstat4 nfs_status = 0; vinode.ino.val = deviceid->devid; vinode.snapid.val = CEPH_NOSNAP; /* Sanity check on type */ if (type != LAYOUT4_NFSV4_1_FILES) { LogCrit(COMPONENT_PNFS, "Unsupported layout type: %x", type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } /* Retrieve and calculate storage parameters of layout */ memset(&file_layout, 0, sizeof(struct ceph_file_layout)); if (ceph_ll_file_layout(export->cmount, vinode, &file_layout) != 0) { LogCrit(COMPONENT_PNFS, "Failed to get Ceph layout for inode"); return NFS4ERR_SERVERFAULT; } /* As this is large, we encode as we go rather than building a structure and encoding it all at once. */ /* The first entry in the nfsv4_1_file_ds_addr4 is the array of stripe indices. First we encode the count of stripes. Since our pattern doesn't repeat, we have as many indices as we do stripes. */ if (!inline_xdr_u_int32_t(da_addr_body, &stripes)) { LogCrit(COMPONENT_PNFS, "Failed to encode length of stripe_indices array: %" PRIu32 ".", stripes); return NFS4ERR_SERVERFAULT; } for (stripe = 0; stripe < stripes; stripe++) { uint32_t stripe_osd = stripe_osd = ceph_ll_get_stripe_osd( export->cmount, vinode, stripe, &file_layout); if (stripe_osd < 0) { LogCrit(COMPONENT_PNFS, "Failed to retrieve OSD for stripe %lu of file %" PRIu64 ". Error: %u", stripe, deviceid->devid, -stripe_osd); return NFS4ERR_SERVERFAULT; } if (!inline_xdr_u_int32_t(da_addr_body, &stripe_osd)) { LogCrit(COMPONENT_PNFS, "Failed to encode OSD for stripe %lu.", stripe); return NFS4ERR_SERVERFAULT; } } /* The number of OSDs in our cluster is the length of our array of multipath_lists */ if (!inline_xdr_u_int32_t(da_addr_body, &num_osds)) { LogCrit(COMPONENT_PNFS, "Failed to encode length of multipath_ds_list array: %u", num_osds); return NFS4ERR_SERVERFAULT; } /* Since our index is the OSD number itself, we have only one host per multipath_list. */ for (osd = 0; osd < num_osds; osd++) { fsal_multipath_member_t host; memset(&host, 0, sizeof(fsal_multipath_member_t)); host.proto = 6; if (ceph_ll_osdaddr(export->cmount, osd, &host.addr) < 0) { LogCrit(COMPONENT_PNFS, "Unable to get IP address for OSD %lu.", osd); return NFS4ERR_SERVERFAULT; } host.port = 2049; nfs_status = FSAL_encode_v4_multipath(da_addr_body, 1, &host); if (nfs_status != NFS4_OK) return nfs_status; } return NFS4_OK; } /** * @brief Get list of available devices * * We do not support listing devices and just set EOF without doing * anything. * * @param[in] export_pub Export handle * @param[in] type Type of layout to get devices for * @param[in] cb Function taking device ID halves * @param[in,out] res In/out and output arguments of the function * * @return Valid error codes in RFC 5661, pp. 365-6. */ static nfsstat4 getdevicelist(struct fsal_export *export_pub, layouttype4 type, void *opaque, bool (*cb)(void *opaque, const uint64_t id), struct fsal_getdevicelist_res *res) { res->eof = true; return NFS4_OK; } /** * @brief Get layout types supported by export * * We just return a pointer to the single type and set the count to 1. * * @param[in] export_pub Public export handle * @param[out] count Number of layout types in array * @param[out] types Static array of layout types that must not be * freed or modified and must not be dereferenced * after export reference is relinquished */ static void fs_layouttypes(struct fsal_export *export_pub, int32_t *count, const layouttype4 **types) { static const layouttype4 supported_layout_type = LAYOUT4_NFSV4_1_FILES; *types = &supported_layout_type; *count = 1; } /** * @brief Get layout block size for export * * This function just returns the Ceph default. * * @param[in] export_pub Public export handle * * @return 4 MB. */ static uint32_t fs_layout_blocksize(struct fsal_export *export_pub) { return 0x400000; } /** * @brief Maximum number of segments we will use * * Since current clients only support 1, that's what we'll use. * * @param[in] export_pub Public export handle * * @return 1 */ static uint32_t fs_maximum_segments(struct fsal_export *export_pub) { return 1; } /** * @brief Size of the buffer needed for a loc_body * * Just a handle plus a bit. * * @param[in] export_pub Public export handle * * @return Size of the buffer needed for a loc_body */ static size_t fs_loc_body_size(struct fsal_export *export_pub) { return 0x100; } /** * @brief Size of the buffer needed for a ds_addr * * This one is huge, due to the striping pattern. * * @param[in] export_pub Public export handle * * @return Size of the buffer needed for a ds_addr */ static size_t fs_da_addr_size(struct fsal_export *export_pub) { return 0x1400; } void export_ops_pnfs(struct export_ops *ops) { ops->getdeviceinfo = getdeviceinfo; ops->getdevicelist = getdevicelist; ops->fs_layouttypes = fs_layouttypes; ops->fs_layout_blocksize = fs_layout_blocksize; ops->fs_maximum_segments = fs_maximum_segments; ops->fs_loc_body_size = fs_loc_body_size; ops->fs_da_addr_size = fs_da_addr_size; } /** * @brief Grant a layout segment. * * Grant a layout on a subset of a file requested. As a special case, * lie and grant a whole-file layout if requested, because Linux will * ignore it otherwise. * * @param[in] obj_pub Public object handle * @param[out] loc_body An XDR stream to which the FSAL must encode * the layout specific portion of the granted * layout segment. * @param[in] arg Input arguments of the function * @param[in,out] res In/out and output arguments of the function * * @return Valid error codes in RFC 5661, pp. 366-7. */ static nfsstat4 layoutget(struct fsal_obj_handle *obj_pub, XDR *loc_body, const struct fsal_layoutget_arg *arg, struct fsal_layoutget_res *res) { /* The private 'full' export */ struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); /* The private 'full' object handle */ struct ceph_handle *handle = container_of(obj_pub, struct ceph_handle, handle); /* Structure containing the storage parameters of the file within the Ceph cluster. */ struct ceph_file_layout file_layout; /* Width of each stripe on the file */ uint32_t stripe_width = 0; /* Utility parameter */ nfl_util4 util = 0; /* The last byte that can be accessed through pNFS */ uint64_t last_possible_byte = 0; /* The deviceid for this layout */ struct pnfs_deviceid deviceid = DEVICE_ID_INIT_ZERO(FSAL_ID_CEPH); /* NFS Status */ nfsstat4 nfs_status = 0; /* DS wire handle */ struct ds_wire ds_wire; /* Descriptor for DS handle */ struct gsh_buffdesc ds_desc = { .addr = &ds_wire, .len = sizeof(struct ds_wire) }; /* The smallest layout the client will accept */ struct pnfs_segment smallest_acceptable = { .io_mode = res->segment.io_mode, .offset = res->segment.offset, .length = arg->minlength }; struct pnfs_segment forbidden_area = { .io_mode = res->segment.io_mode, .length = NFS4_UINT64_MAX }; /* We support only LAYOUT4_NFSV4_1_FILES layouts */ if (arg->type != LAYOUT4_NFSV4_1_FILES) { LogCrit(COMPONENT_PNFS, "Unsupported layout type: %x", arg->type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } /* Get basic information on the file and calculate the dimensions of the layout we can support. */ memset(&file_layout, 0, sizeof(struct ceph_file_layout)); ceph_ll_file_layout(export->cmount, handle->wire.vi, &file_layout); stripe_width = file_layout.fl_stripe_unit; last_possible_byte = (BIGGEST_PATTERN * stripe_width) - 1; forbidden_area.offset = last_possible_byte + 1; /* Since the Linux kernel refuses to work with any layout that doesn't cover the whole file, if a whole file layout is requested, lie. Otherwise, make sure the required layout doesn't go beyond what can be accessed through pNFS. This is a preliminary check before even talking to Ceph. */ if (!((res->segment.offset == 0) && (res->segment.length == NFS4_UINT64_MAX))) { if (pnfs_segments_overlap(&smallest_acceptable, &forbidden_area)) { LogCrit(COMPONENT_PNFS, "Required layout extends beyond allowed region. offset: %" PRIu64 ", minlength: %" PRIu64 ".", res->segment.offset, arg->minlength); return NFS4ERR_BADLAYOUT; } res->segment.offset = 0; res->segment.length = stripe_width * BIGGEST_PATTERN; } LogFullDebug(COMPONENT_PNFS, "will issue layout offset: %" PRIu64 " length: %" PRIu64, res->segment.offset, res->segment.length); /* We are using sparse layouts with commit-through-DS, so our utility word contains only the stripe width, our first stripe is always at the beginning of the layout, and there is no pattern offset. */ if ((stripe_width & ~NFL4_UFLG_STRIPE_UNIT_SIZE_MASK) != 0) { LogCrit(COMPONENT_PNFS, "Ceph returned stripe width that is disallowed by NFS: %" PRIu32 ".", stripe_width); return NFS4ERR_SERVERFAULT; } util = stripe_width; /* If we have a cached capbility, use that. Otherwise, call in to Ceph. */ PTHREAD_RWLOCK_wrlock(&handle->handle.obj_lock); if (res->segment.io_mode == LAYOUTIOMODE4_READ) { int32_t r = 0; if (handle->rd_issued == 0) { #if 0 /* (ceph part might be here: thunderbeast mbarrier1) */ r = ceph_ll_hold_rw(export->cmount, handle->wire.vi, false, initiate_recall, handle, &handle->rd_serial, NULL); #endif if (r < 0) { PTHREAD_RWLOCK_unlock(&handle->handle.obj_lock); return posix2nfs4_error(-r); } } ++handle->rd_issued; } else { int32_t r = 0; if (handle->rw_issued == 0) { #if 0 r = ceph_ll_hold_rw(export->cmount, handle->wire.vi, true, initiate_recall, handle, &handle->rw_serial, &handle->rw_max_len); #endif if (r < 0) { PTHREAD_RWLOCK_unlock(&handle->handle.obj_lock); return posix2nfs4_error(-r); } } forbidden_area.offset = handle->rw_max_len; if (pnfs_segments_overlap(&smallest_acceptable, &forbidden_area)) { PTHREAD_RWLOCK_unlock(&handle->handle.obj_lock); return NFS4ERR_BADLAYOUT; } #if CLIENTS_WILL_ACCEPT_SEGMENTED_LAYOUTS /* sigh */ res->segment.length = (handle->rw_max_len - res->segment.offset); #endif ++handle->rw_issued; } PTHREAD_RWLOCK_unlock(&handle->handle.obj_lock); /* For now, just make the low quad of the deviceid be the inode number. With the span of the layouts constrained above, this lets us generate the device address on the fly from the deviceid rather than storing it. */ deviceid.devid = handle->wire.vi.ino.val; /* We return exactly one filehandle, filling in the necessary information for the DS server to speak to the Ceph OSD directly. */ ds_wire.wire = handle->wire; ds_wire.layout = file_layout; ds_wire.snapseq = ceph_ll_snap_seq(export->cmount, handle->wire.vi); nfs_status = FSAL_encode_file_layout(loc_body, &deviceid, util, 0, 0, &op_ctx->export->export_id, 1, &ds_desc, false); if (nfs_status != NFS4_OK) { LogCrit(COMPONENT_PNFS, "Failed to encode nfsv4_1_file_layout."); goto relinquish; } /* We grant only one segment, and we want it back when the file is closed. */ res->return_on_close = true; res->last_segment = true; return NFS4_OK; relinquish: /* If we failed in encoding the lo_content, relinquish what we reserved for it. */ PTHREAD_RWLOCK_wrlock(&handle->handle.obj_lock); if (res->segment.io_mode == LAYOUTIOMODE4_READ) { if (--handle->rd_issued != 0) { PTHREAD_RWLOCK_unlock(&handle->handle.obj_lock); return nfs_status; } } else { if (--handle->rd_issued != 0) { PTHREAD_RWLOCK_unlock(&handle->handle.obj_lock); return nfs_status; } } #if 0 ceph_ll_return_rw(export->cmount, handle->wire.vi, res->segment.io_mode == LAYOUTIOMODE4_READ ? handle->rd_serial : handle->rw_serial); #endif PTHREAD_RWLOCK_unlock(&handle->handle.obj_lock); return nfs_status; } /** * @brief Potentially return one layout segment * * Since we don't make any reservations, in this version, or get any * pins to release, always succeed * * @param[in] obj_pub Public object handle * @param[in] lrf_body Nothing for us * @param[in] arg Input arguments of the function * * @return Valid error codes in RFC 5661, p. 367. */ static nfsstat4 layoutreturn(struct fsal_obj_handle *obj_pub, XDR *lrf_body, const struct fsal_layoutreturn_arg *arg) { /* The private 'full' export */ struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); /* The private 'full' object handle */ struct ceph_handle *handle = container_of(obj_pub, struct ceph_handle, handle); /* Sanity check on type */ if (arg->lo_type != LAYOUT4_NFSV4_1_FILES) { LogCrit(COMPONENT_PNFS, "Unsupported layout type: %x", arg->lo_type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } if (arg->dispose) { PTHREAD_RWLOCK_wrlock(&handle->handle.obj_lock); if (arg->cur_segment.io_mode == LAYOUTIOMODE4_READ) { if (--handle->rd_issued != 0) { PTHREAD_RWLOCK_unlock(&handle->handle.obj_lock); return NFS4_OK; } } else { if (--handle->rd_issued != 0) { PTHREAD_RWLOCK_unlock(&handle->handle.obj_lock); return NFS4_OK; } } #if 0 ceph_ll_return_rw(export->cmount, handle->wire.vi, arg->cur_segment.io_mode == LAYOUTIOMODE4_READ ? handle->rd_serial : handle->rw_serial); #endif PTHREAD_RWLOCK_unlock(&handle->handle.obj_lock); } return NFS4_OK; } /** * @brief Commit a segment of a layout * * Update the size and time for a file accessed through a layout. * * @param[in] obj_pub Public object handle * @param[in] lou_body An XDR stream containing the layout * type-specific portion of the LAYOUTCOMMIT * arguments. * @param[in] arg Input arguments of the function * @param[in,out] res In/out and output arguments of the function * * @return Valid error codes in RFC 5661, p. 366. */ static nfsstat4 layoutcommit(struct fsal_obj_handle *obj_pub, XDR *lou_body, const struct fsal_layoutcommit_arg *arg, struct fsal_layoutcommit_res *res) { /* The private 'full' export */ struct ceph_export *export = container_of(op_ctx->fsal_export, struct ceph_export, export); /* The private 'full' object handle */ struct ceph_handle *handle = container_of(obj_pub, struct ceph_handle, handle); /* Old stat, so we don't truncate file or reverse time */ struct ceph_statx stxold; /* new stat to set time and size */ struct ceph_statx stxnew; /* Mask to determine exactly what gets set */ int attrmask = 0; /* Error returns from Ceph */ int ceph_status = 0; /* Sanity check on type */ if (arg->type != LAYOUT4_NFSV4_1_FILES) { LogCrit(COMPONENT_PNFS, "Unsupported layout type: %x", arg->type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } /* A more proper and robust implementation of this would use Ceph caps, but we need to hack at the client to expose those before it can work. */ ceph_status = fsal_ceph_ll_getattr(export->cmount, handle->wire.vi, &stxold, CEPH_STATX_SIZE | CEPH_STATX_MTIME, op_ctx->creds); if (ceph_status < 0) { LogCrit(COMPONENT_PNFS, "Error %d in attempt to get attributes of file %" PRIu64 ".", -ceph_status, handle->wire.vi.ino.val); return posix2nfs4_error(-ceph_status); } stxnew->stx_mask = 0; if (arg->new_offset) { if (stxold.stx_size < arg->last_write + 1) { attrmask |= CEPH_SETATTR_SIZE; stxnew.stx_size = arg->last_write + 1; res->size_supplied = true; res->new_size = arg->last_write + 1; } } if (arg->time_changed && (arg->new_time.seconds > stxold.stx_mtime || (arg->new_time.seconds == stxold.stx_mtime && arg->new_time.nseconds > stxold.stx_mtime_ns))) { stxnew.stx_mtime = arg->new_time.seconds; stxnew.stx_mtime_ns = arg->new_time.nseconds; } else { struct timespec now; ceph_status = clock_gettime(CLOCK_REALTIME, &now); if (ceph_status != 0) return posix2nfs4_error(errno); stxnew.stx_mtime = now.tv_sec; stxnew.stx_mtime_ns = now.tv_nsec; } attrmask |= CEPH_SETATTR_MTIME; ceph_status = fsal_ceph_ll_setattr(export->cmount, handle->wire.vi, &stxnew, attrmask, op_ctx->creds); if (ceph_status < 0) { LogCrit(COMPONENT_PNFS, "Error %d in attempt to get attributes of file %" PRIu64 ".", -ceph_status, handle->wire.vi.ino.val); return posix2nfs4_error(-ceph_status); } /* This is likely universal for files. */ res->commit_done = true; return NFS4_OK; } void handle_ops_pnfs(struct fsal_obj_ops *ops) { ops->layoutget = layoutget; ops->layoutreturn = layoutreturn; ops->layoutcommit = layoutcommit; } #endif /* CEPH_PNFS */ nfs-ganesha-6.5/src/FSAL/FSAL_CEPH/statx_compat.c000066400000000000000000000131051473756622300213070ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2016 Red Hat, Inc. and/or its affiliates. * Author: Jeff Layton * * This program 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 3 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 * 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 * */ #include #include #include #include #include #include "common_utils.h" #include "fsal_types.h" #include "statx_compat.h" static void posix2ceph_statx(struct stat *st, struct ceph_statx *stx) { memset(stx, 0, sizeof(*stx)); stx->stx_mask = CEPH_STATX_BASIC_STATS | CEPH_STATX_VERSION; stx->stx_blksize = st->st_blksize; stx->stx_nlink = st->st_nlink; stx->stx_uid = st->st_uid; stx->stx_gid = st->st_gid; stx->stx_mode = st->st_mode; stx->stx_ino = st->st_ino; stx->stx_size = st->st_size; stx->stx_blocks = st->st_blocks; stx->stx_dev = st->st_dev; stx->stx_rdev = st->st_rdev; stx->stx_atime = st->st_atim; stx->stx_ctime = st->st_ctim; stx->stx_mtime = st->st_mtim; stx->stx_version = timespec_to_nsecs(&st->st_ctim); } int fsal_ceph_ll_walk(struct ceph_mount_info *cmount, const char *name, Inode **i, struct ceph_statx *stx, bool full, const struct user_cred *cred) { int rc; struct stat st; rc = ceph_ll_walk(cmount, name, i, &st); if (rc == 0) posix2ceph_statx(&st, stx); return rc; } int fsal_ceph_ll_getattr(struct ceph_mount_info *cmount, struct Inode *in, struct ceph_statx *stx, unsigned int want, const struct user_cred *cred) { int rc; struct stat st; rc = ceph_ll_getattr(cmount, in, &st, cred->caller_uid, cred->caller_gid); if (rc == 0) posix2ceph_statx(&st, stx); return rc; } int fsal_ceph_ll_lookup(struct ceph_mount_info *cmount, Inode *parent, const char *name, Inode **out, struct ceph_statx *stx, bool full, const struct user_cred *cred) { int rc; struct stat st; rc = ceph_ll_lookup(cmount, parent, name, &st, out, cred->caller_uid, cred->caller_gid); if (rc == 0) posix2ceph_statx(&st, stx); return rc; } int fsal_ceph_ll_mkdir(struct ceph_mount_info *cmount, Inode *parent, const char *name, mode_t mode, Inode **out, struct ceph_statx *stx, bool full, const struct user_cred *cred) { int rc; struct stat st; rc = ceph_ll_mkdir(cmount, parent, name, mode, &st, out, cred->caller_uid, cred->caller_gid); if (rc == 0) posix2ceph_statx(&st, stx); return rc; } #ifdef USE_FSAL_CEPH_MKNOD int fsal_ceph_ll_mknod(struct ceph_mount_info *cmount, Inode *parent, const char *name, mode_t mode, dev_t rdev, Inode **out, struct ceph_statx *stx, bool full, const struct user_cred *cred) { int rc; struct stat st; rc = ceph_ll_mknod(cmount, parent, name, mode, rdev, &st, out, cred->caller_uid, cred->caller_gid); if (rc == 0) posix2ceph_statx(&st, stx); return rc; } #endif /* USE_FSAL_CEPH_MKNOD */ int fsal_ceph_ll_symlink(struct ceph_mount_info *cmount, Inode *parent, const char *name, const char *link_path, Inode **out, struct ceph_statx *stx, bool full, const struct user_cred *cred) { int rc; struct stat st; rc = ceph_ll_symlink(cmount, parent, name, link_path, &st, out, cred->caller_uid, cred->caller_gid); if (rc == 0) posix2ceph_statx(&st, stx); return rc; } int fsal_ceph_ll_create(struct ceph_mount_info *cmount, Inode *parent, const char *name, mode_t mode, int oflags, Inode **outp, Fh **fhp, struct ceph_statx *stx, bool full, const struct user_cred *cred) { int rc; struct stat st; rc = ceph_ll_create(cmount, parent, name, mode, oflags, &st, outp, fhp, cred->caller_uid, cred->caller_gid); if (rc == 0) posix2ceph_statx(&st, stx); return rc; } int fsal_ceph_ll_setattr(struct ceph_mount_info *cmount, Inode *i, struct ceph_statx *stx, unsigned int mask, const struct user_cred *cred) { struct stat st; memset(&st, 0, sizeof(st)); if (mask & CEPH_SETATTR_MODE) st.st_mode = stx->stx_mode; if (mask & CEPH_SETATTR_UID) st.st_uid = stx->stx_uid; if (mask & CEPH_SETATTR_GID) st.st_gid = stx->stx_gid; if (mask & CEPH_SETATTR_ATIME) st.st_atim = stx->stx_atime; if (mask & CEPH_SETATTR_MTIME) st.st_mtim = stx->stx_mtime; if (mask & CEPH_SETATTR_CTIME) st.st_ctim = stx->stx_ctime; if (mask & CEPH_SETATTR_SIZE) st.st_size = stx->stx_size; return ceph_ll_setattr(cmount, i, &st, mask, cred->caller_uid, cred->caller_gid); } int fsal_ceph_readdirplus(struct ceph_mount_info *cmount, struct ceph_dir_result *dirp, Inode *dir, struct dirent *de, struct ceph_statx *stx, unsigned int want, unsigned int flags, Inode **out, struct user_cred *cred) { int stmask, rc; struct stat st; rc = ceph_readdirplus_r(cmount, dirp, de, &st, &stmask); if (rc <= 0) return rc; if (flags & AT_NO_ATTR_SYNC) { posix2ceph_statx(&st, stx); } else { rc = fsal_ceph_ll_lookup(cmount, dir, de->d_name, out, stx, true, cred); if (rc >= 0) rc = 1; } return rc; } nfs-ganesha-6.5/src/FSAL/FSAL_CEPH/statx_compat.h000066400000000000000000000362701473756622300213240ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2016 Red Hat, Inc. and/or its affiliates. * Author: Jeff Layton * * This program 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 3 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 * 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 * */ #ifndef _FSAL_CEPH_STATX_COMPAT_H #define _FSAL_CEPH_STATX_COMPAT_H #include "fsal_types.h" /* * Depending on what we'll be doing with the resulting statx structure, we * either set the mask for the minimum that construct_handle requires, or a * full set of attributes. * * Note that even though construct_handle accesses the stx_mode field, we * don't need to request CEPH_STATX_MODE here, as the type bits are always * accessible. */ #define CEPH_STATX_HANDLE_MASK (CEPH_STATX_INO) #define CEPH_STATX_ATTR_MASK \ (CEPH_STATX_BASIC_STATS | CEPH_STATX_BTIME | CEPH_STATX_VERSION) #ifdef USE_FSAL_CEPH_STATX static inline UserPerm *user_cred2ceph(const struct user_cred *cred) { return ceph_userperm_new(cred->caller_uid, cred->caller_gid, cred->caller_glen, cred->caller_garray); } static inline int fsal_ceph_ll_walk(struct ceph_mount_info *cmount, const char *name, Inode **i, struct ceph_statx *stx, bool full, const struct user_cred *creds) { int ret; UserPerm *perms = user_cred2ceph(creds); if (!perms) return -ENOMEM; ret = ceph_ll_walk(cmount, name, i, stx, full ? CEPH_STATX_ATTR_MASK : CEPH_STATX_HANDLE_MASK, 0, perms); ceph_userperm_destroy(perms); return ret; } static inline int fsal_ceph_ll_getattr(struct ceph_mount_info *cmount, struct Inode *in, struct ceph_statx *stx, unsigned int want, const struct user_cred *creds) { int ret; UserPerm *perms = user_cred2ceph(creds); if (!perms) return -ENOMEM; ret = ceph_ll_getattr(cmount, in, stx, want, 0, perms); ceph_userperm_destroy(perms); return ret; } static inline int fsal_ceph_ll_lookup(struct ceph_mount_info *cmount, Inode *parent, const char *name, Inode **out, struct ceph_statx *stx, bool full, const struct user_cred *creds) { int ret; UserPerm *perms = user_cred2ceph(creds); if (!perms) return -ENOMEM; ret = ceph_ll_lookup( cmount, parent, name, out, stx, full ? CEPH_STATX_ATTR_MASK : CEPH_STATX_HANDLE_MASK, 0, perms); ceph_userperm_destroy(perms); return ret; } static inline int fsal_ceph_ll_mkdir(struct ceph_mount_info *cmount, Inode *parent, const char *name, mode_t mode, Inode **out, struct ceph_statx *stx, bool full, const struct user_cred *creds) { int ret; UserPerm *perms = user_cred2ceph(creds); if (!perms) return -ENOMEM; ret = ceph_ll_mkdir( cmount, parent, name, mode, out, stx, full ? CEPH_STATX_ATTR_MASK : CEPH_STATX_HANDLE_MASK, 0, perms); ceph_userperm_destroy(perms); return ret; } static inline int fsal_ceph_ll_mknod(struct ceph_mount_info *cmount, Inode *parent, const char *name, mode_t mode, dev_t rdev, Inode **out, struct ceph_statx *stx, bool full, const struct user_cred *creds) { int ret; UserPerm *perms = user_cred2ceph(creds); if (!perms) return -ENOMEM; ret = ceph_ll_mknod( cmount, parent, name, mode, rdev, out, stx, full ? CEPH_STATX_ATTR_MASK : CEPH_STATX_HANDLE_MASK, 0, perms); ceph_userperm_destroy(perms); return ret; } static inline int fsal_ceph_ll_symlink(struct ceph_mount_info *cmount, Inode *parent, const char *name, const char *link_path, Inode **out, struct ceph_statx *stx, bool full, const struct user_cred *creds) { int ret; UserPerm *perms = user_cred2ceph(creds); if (!perms) return -ENOMEM; ret = ceph_ll_symlink( cmount, parent, name, link_path, out, stx, full ? CEPH_STATX_ATTR_MASK : CEPH_STATX_HANDLE_MASK, 0, perms); ceph_userperm_destroy(perms); return ret; } static inline int fsal_ceph_ll_readlink(struct ceph_mount_info *cmount, Inode *in, char *buf, size_t bufsize, const struct user_cred *creds) { int ret; UserPerm *perms = user_cred2ceph(creds); if (!perms) return -ENOMEM; ret = ceph_ll_readlink(cmount, in, buf, bufsize, perms); ceph_userperm_destroy(perms); return ret; } static inline int fsal_ceph_ll_create(struct ceph_mount_info *cmount, Inode *parent, const char *name, mode_t mode, int oflags, Inode **outp, Fh **fhp, struct ceph_statx *stx, bool full, const struct user_cred *creds) { int ret; UserPerm *perms = user_cred2ceph(creds); if (!perms) return -ENOMEM; ret = ceph_ll_create( cmount, parent, name, mode, oflags, outp, fhp, stx, full ? CEPH_STATX_ATTR_MASK : CEPH_STATX_HANDLE_MASK, 0, perms); ceph_userperm_destroy(perms); return ret; } static inline int fsal_ceph_ll_setattr(struct ceph_mount_info *cmount, Inode *i, struct ceph_statx *stx, unsigned int mask, const struct user_cred *creds) { int ret; UserPerm *perms = user_cred2ceph(creds); if (!perms) return -ENOMEM; ret = ceph_ll_setattr(cmount, i, stx, mask, perms); #ifdef USE_FSAL_CEPH_LL_SYNC_INODE if (!ret) ret = ceph_ll_sync_inode(cmount, i, 0); #endif /* USE_FSAL_CEPH_LL_SYNC_INODE */ ceph_userperm_destroy(perms); return ret; } static inline int fsal_ceph_ll_open(struct ceph_mount_info *cmount, Inode *i, int flags, Fh **fh, const struct user_cred *creds) { int ret; UserPerm *perms = user_cred2ceph(creds); if (!perms) return -ENOMEM; ret = ceph_ll_open(cmount, i, flags, fh, perms); ceph_userperm_destroy(perms); return ret; } static inline int fsal_ceph_ll_opendir(struct ceph_mount_info *cmount, struct Inode *in, struct ceph_dir_result **dirpp, const struct user_cred *creds) { int ret; UserPerm *perms = user_cred2ceph(creds); if (!perms) return -ENOMEM; ret = ceph_ll_opendir(cmount, in, dirpp, perms); ceph_userperm_destroy(perms); return ret; } static inline int fsal_ceph_ll_link(struct ceph_mount_info *cmount, Inode *i, Inode *newparent, const char *name, const struct user_cred *creds) { int ret; UserPerm *perms = user_cred2ceph(creds); if (!perms) return -ENOMEM; ret = ceph_ll_link(cmount, i, newparent, name, perms); ceph_userperm_destroy(perms); return ret; } static inline int fsal_ceph_ll_unlink(struct ceph_mount_info *cmount, struct Inode *in, const char *name, const struct user_cred *creds) { int ret; UserPerm *perms = user_cred2ceph(creds); if (!perms) return -ENOMEM; ret = ceph_ll_unlink(cmount, in, name, perms); ceph_userperm_destroy(perms); return ret; } static inline int fsal_ceph_ll_rename(struct ceph_mount_info *cmount, struct Inode *parent, const char *name, struct Inode *newparent, const char *newname, const struct user_cred *creds) { int ret; UserPerm *perms = user_cred2ceph(creds); if (!perms) return -ENOMEM; ret = ceph_ll_rename(cmount, parent, name, newparent, newname, perms); ceph_userperm_destroy(perms); return ret; } static inline int fsal_ceph_ll_rmdir(struct ceph_mount_info *cmount, struct Inode *in, const char *name, const struct user_cred *creds) { int ret; UserPerm *perms = user_cred2ceph(creds); if (!perms) return -ENOMEM; ret = ceph_ll_rmdir(cmount, in, name, perms); ceph_userperm_destroy(perms); return ret; } static inline int fsal_ceph_readdirplus(struct ceph_mount_info *cmount, struct ceph_dir_result *dirp, Inode *dir, struct dirent *de, struct ceph_statx *stx, unsigned int want, unsigned int flags, Inode **out, struct user_cred *cred) { return ceph_readdirplus_r(cmount, dirp, de, stx, want, flags, out); } static inline int fsal_ceph_ll_getxattr(struct ceph_mount_info *cmount, struct Inode *in, const char *name, char *val, size_t size, const struct user_cred *creds) { int ret; UserPerm *perms = user_cred2ceph(creds); if (!perms) return -ENOMEM; ret = ceph_ll_getxattr(cmount, in, name, val, size, perms); ceph_userperm_destroy(perms); return ret; } static inline int fsal_ceph_ll_setxattr(struct ceph_mount_info *cmount, struct Inode *in, const char *name, char *val, size_t size, int flags, const struct user_cred *creds) { int ret; UserPerm *perms = user_cred2ceph(creds); if (!perms) return -ENOMEM; ret = ceph_ll_setxattr(cmount, in, name, val, size, flags, perms); ceph_userperm_destroy(perms); return ret; } static inline int fsal_ceph_ll_removexattr(struct ceph_mount_info *cmount, struct Inode *in, const char *name, const struct user_cred *creds) { int ret; UserPerm *perms = user_cred2ceph(creds); if (!perms) return -ENOMEM; ret = ceph_ll_removexattr(cmount, in, name, perms); ceph_userperm_destroy(perms); return ret; } #else /* USE_FSAL_CEPH_STATX */ #ifndef AT_NO_ATTR_SYNC #define AT_NO_ATTR_SYNC 0x4000 #endif /* AT_NO_ATTR_SYNC */ struct ceph_statx { uint32_t stx_mask; uint32_t stx_blksize; uint32_t stx_nlink; uint32_t stx_uid; uint32_t stx_gid; uint16_t stx_mode; uint64_t stx_ino; uint64_t stx_size; uint64_t stx_blocks; dev_t stx_dev; dev_t stx_rdev; struct timespec stx_atime; struct timespec stx_ctime; struct timespec stx_mtime; struct timespec stx_btime; uint64_t stx_version; }; #define CEPH_STATX_MODE 0x00000001U /* Want/got stx_mode */ #define CEPH_STATX_NLINK 0x00000002U /* Want/got stx_nlink */ #define CEPH_STATX_UID 0x00000004U /* Want/got stx_uid */ #define CEPH_STATX_GID 0x00000008U /* Want/got stx_gid */ #define CEPH_STATX_RDEV 0x00000010U /* Want/got stx_rdev */ #define CEPH_STATX_ATIME 0x00000020U /* Want/got stx_atime */ #define CEPH_STATX_MTIME 0x00000040U /* Want/got stx_mtime */ #define CEPH_STATX_CTIME 0x00000080U /* Want/got stx_ctime */ #define CEPH_STATX_INO 0x00000100U /* Want/got stx_ino */ #define CEPH_STATX_SIZE 0x00000200U /* Want/got stx_size */ #define CEPH_STATX_BLOCKS 0x00000400U /* Want/got stx_blocks */ #define CEPH_STATX_BASIC_STATS 0x000007ffU /* posix stat struct fields */ #define CEPH_STATX_BTIME 0x00000800U /* Want/got stx_btime */ #define CEPH_STATX_VERSION 0x00001000U /* Want/got stx_version */ #define CEPH_STATX_ALL_STATS 0x00001fffU /* All supported stats */ int fsal_ceph_ll_walk(struct ceph_mount_info *cmount, const char *name, Inode **i, struct ceph_statx *stx, bool full, const struct user_cred *cred); int fsal_ceph_ll_getattr(struct ceph_mount_info *cmount, struct Inode *in, struct ceph_statx *stx, unsigned int want, const struct user_cred *cred); int fsal_ceph_ll_lookup(struct ceph_mount_info *cmount, Inode *parent, const char *name, Inode **out, struct ceph_statx *stx, bool full, const struct user_cred *cred); int fsal_ceph_ll_mkdir(struct ceph_mount_info *cmount, Inode *parent, const char *name, mode_t mode, Inode **out, struct ceph_statx *stx, bool full, const struct user_cred *cred); #ifdef USE_FSAL_CEPH_MKNOD int fsal_ceph_ll_mknod(struct ceph_mount_info *cmount, Inode *parent, const char *name, mode_t mode, dev_t rdev, Inode **out, struct ceph_statx *stx, bool full, const struct user_cred *cred); #endif /* USE_FSAL_CEPH_MKNOD */ int fsal_ceph_ll_symlink(struct ceph_mount_info *cmount, Inode *parent, const char *name, const char *link_path, Inode **out, struct ceph_statx *stx, bool full, const struct user_cred *cred); int fsal_ceph_ll_create(struct ceph_mount_info *cmount, Inode *parent, const char *name, mode_t mode, int oflags, Inode **outp, Fh **fhp, struct ceph_statx *stx, bool full, const struct user_cred *cred); int fsal_ceph_ll_setattr(struct ceph_mount_info *cmount, Inode *i, struct ceph_statx *stx, unsigned int mask, const struct user_cred *cred); int fsal_ceph_readdirplus(struct ceph_mount_info *cmount, struct ceph_dir_result *dirp, Inode *dir, struct dirent *de, struct ceph_statx *stx, unsigned int want, unsigned int flags, Inode **out, struct user_cred *cred); static inline int fsal_ceph_ll_readlink(struct ceph_mount_info *cmount, Inode *in, char *buf, size_t bufsize, const struct user_cred *creds) { return ceph_ll_readlink(cmount, in, buf, bufsize, creds->caller_uid, creds->caller_gid); } static inline int fsal_ceph_ll_open(struct ceph_mount_info *cmount, Inode *i, int flags, Fh **fh, const struct user_cred *cred) { return ceph_ll_open(cmount, i, flags, fh, cred->caller_uid, cred->caller_gid); } static inline int fsal_ceph_ll_opendir(struct ceph_mount_info *cmount, struct Inode *in, struct ceph_dir_result **dirpp, const struct user_cred *cred) { return ceph_ll_opendir(cmount, in, dirpp, cred->caller_uid, cred->caller_gid); } static inline int fsal_ceph_ll_link(struct ceph_mount_info *cmount, Inode *i, Inode *newparent, const char *name, const struct user_cred *cred) { struct stat st; return ceph_ll_link(cmount, i, newparent, name, &st, cred->caller_uid, cred->caller_gid); } static inline int fsal_ceph_ll_unlink(struct ceph_mount_info *cmount, struct Inode *in, const char *name, const struct user_cred *cred) { return ceph_ll_unlink(cmount, in, name, cred->caller_uid, cred->caller_gid); } static inline int fsal_ceph_ll_rename(struct ceph_mount_info *cmount, struct Inode *parent, const char *name, struct Inode *newparent, const char *newname, const struct user_cred *cred) { return ceph_ll_rename(cmount, parent, name, newparent, newname, cred->caller_uid, cred->caller_gid); } static inline int fsal_ceph_ll_rmdir(struct ceph_mount_info *cmount, struct Inode *in, const char *name, const struct user_cred *cred) { return ceph_ll_rmdir(cmount, in, name, cred->caller_uid, cred->caller_gid); } static inline int fsal_ceph_ll_getxattr(struct ceph_mount_info *cmount, struct Inode *in, const char *name, char *val, size_t size, const struct user_cred *cred) { return ceph_ll_getxattr(cmount, in, name, val, size, cred->caller_uid, cred->caller_gid); } static inline int fsal_ceph_ll_setxattr(struct ceph_mount_info *cmount, struct Inode *in, const char *name, char *val, size_t size, int flags, const struct user_cred *cred) { return ceph_ll_setxattr(cmount, in, name, val, size, flags, cred->caller_uid, cred->caller_gid); } static inline int fsal_ceph_ll_removexattr(struct ceph_mount_info *cmount, struct Inode *in, const char *name, const struct user_cred *cred) { return ceph_ll_removexattr(cmount, in, name, cred->caller_uid, cred->caller_gid); } #endif /* USE_FSAL_CEPH_STATX */ #endif /* _FSAL_CEPH_STATX_COMPAT_H */ nfs-ganesha-6.5/src/FSAL/FSAL_GLUSTER/000077500000000000000000000000001473756622300170435ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_GLUSTER/CMakeLists.txt000066400000000000000000000033221473756622300216030ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- add_definitions( -D__USE_GNU ${GFAPI_CFLAGS} ) set( LIB_PREFIX 64) #if(USE_FSAL_XFS) # add_subdirectory(xfs) #endif(USE_FSAL_XFS) ########### next target ############### SET(fsalgluster_LIB_SRCS main.c export.c handle.c fsal_up.c gluster_internal.h gluster_internal.c mds.c ds.c ) add_library(fsalgluster MODULE ${fsalgluster_LIB_SRCS}) add_sanitizers(fsalgluster) target_link_libraries(fsalgluster ganesha_nfsd ${SYSTEM_LIBRARIES} ${GFAPI_LIBRARIES} ${LTTNG_LIBRARIES} ${LDFLAG_DISALLOW_UNDEF} ) set_target_properties(fsalgluster PROPERTIES VERSION 4.2.0 SOVERSION 4) install(TARGETS fsalgluster COMPONENT fsal DESTINATION ${FSAL_DESTINATION} ) ########### install files ############### nfs-ganesha-6.5/src/FSAL/FSAL_GLUSTER/README000066400000000000000000000017541473756622300177320ustar00rootroot00000000000000################################################### # Compiling nfs-ganesha with FSAL_GLUSTER support: #----------------------------------------------- # #1. You need the libgfapi.so installed. #2. You also need glfs.h, glfs-handles.h files # which are exported and available for use on # installing a standard glusterfs src/bin rpms. # #----------------------------------------------- # Exporting GLUSTERFS volume: #----------------------------------------------- # # To export a GLUSTERFS volume, make sure # * the volume is online and healthy. # * gluster-nfs and kernel-nfs are disabled. # * Create a sample .conf file with the Gluster # volume related info. Sample config file is # located at 'src/config_samples/gluster.conf' # # For extensive list of NFS options available, # please refer to 'src/config_samples/config.txt' # * Execute the command - # 'ganesha.nfsd -f /path/to/config_file -L /path/to/log/file -N debug_level -d' ################################################### nfs-ganesha-6.5/src/FSAL/FSAL_GLUSTER/ds.c000066400000000000000000000217131473756622300176210ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Red Hat Inc., 2014 * Author: Jiffin Tony Thottan jthottan@redhat.com * : Anand Subramanian anands@redhat.com * * This program 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 3 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 * 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 * * ------------- */ #include #include "fsal.h" #include "fsal_types.h" #include "fsal_api.h" #include "fsal_up.h" #include "gluster_internal.h" #include "FSAL/fsal_commonlib.h" #include "fsal_convert.h" #include "pnfs_utils.h" #include "nfs_exports.h" #include #include "../fsal_private.h" /** * @brief Release a DS object * * @param[in] obj_pub The object to release * * @return NFS Status codes. */ static void dsh_release(struct fsal_ds_handle *const ds_pub) { int rc = 0; struct glfs_ds_handle *ds = container_of(ds_pub, struct glfs_ds_handle, ds); if (ds->glhandle) { rc = glfs_h_close(ds->glhandle); if (rc) { LogMajor(COMPONENT_PNFS, "glfs_h_close returned error %s(%d)", strerror(errno), errno); } } gsh_free(ds); } /** * @brief Read from a data-server handle. * * NFSv4.1 data server handles are disjount from normal * filehandles (in Ganesha, there is a ds_flag in the filehandle_v4_t * structure) and do not get loaded into mdcache or processed the * normal way. * * @param[in] ds_pub FSAL DS handle * @param[in] stateid The stateid supplied with the READ operation, * for validation * @param[in] offset The offset at which to read * @param[in] requested_length Length of read requested (and size of buffer) * @param[out] buffer The buffer to which to store read data * @param[out] supplied_length Length of data read * @param[out] eof True on end of file * * @return An NFSv4.1 status code. */ static nfsstat4 ds_read(struct fsal_ds_handle *const ds_pub, const stateid4 *stateid, const offset4 offset, const count4 requested_length, void *const buffer, count4 *const supplied_length, bool *const end_of_file) { /* The private DS handle */ struct glfs_ds_handle *ds = container_of(ds_pub, struct glfs_ds_handle, ds); int rc = 0; struct glusterfs_export *glfs_export = container_of(op_ctx->ctx_pnfs_ds->mds_fsal_export, struct glusterfs_export, export); if (ds->glhandle == NULL) LogDebug(COMPONENT_PNFS, "glhandle NULL"); rc = glfs_h_anonymous_read(glfs_export->gl_fs->fs, ds->glhandle, buffer, requested_length, offset); if (rc < 0) { rc = errno; LogMajor(COMPONENT_PNFS, "Read failed on DS"); return posix2nfs4_error(rc); } *supplied_length = rc; if (rc == 0 || rc < requested_length) *end_of_file = true; return NFS4_OK; } /** * * @brief Write to a data-server handle. * * @param[in] ds_pub FSAL DS handle * @param[in] stateid The stateid supplied with the READ operation, * for validation * @param[in] offset The offset at which to read * @param[in] write_length Length of write requested (and size of buffer) * @param[out] buffer The buffer to which to store read data * @param[in] stability wanted Stability of write * @param[out] written_length Length of data written * @param[out] writeverf Write verifier * @param[out] stability_got Stability used for write (must be as * or more stable than request) * * @return An NFSv4.1 status code. */ static nfsstat4 ds_write(struct fsal_ds_handle *const ds_pub, const stateid4 *stateid, const offset4 offset, const count4 write_length, const void *buffer, const stable_how4 stability_wanted, count4 *const written_length, verifier4 *const writeverf, stable_how4 *const stability_got) { struct glfs_ds_handle *ds = container_of(ds_pub, struct glfs_ds_handle, ds); struct glusterfs_export *glfs_export = container_of(op_ctx->ctx_pnfs_ds->mds_fsal_export, struct glusterfs_export, export); int rc = 0; memset(writeverf, 0, NFS4_VERIFIER_SIZE); if (ds->glhandle == NULL) LogDebug(COMPONENT_PNFS, "glhandle NULL"); rc = glfs_h_anonymous_write(glfs_export->gl_fs->fs, ds->glhandle, buffer, write_length, offset); if (rc < 0) { rc = errno; LogMajor(COMPONENT_PNFS, "status after write %d", rc); return posix2nfs4_error(rc); } /** @todo:Here DS is performing the write operation, so the MDS is not * aware of the change.We should inform MDS through upcalls about * change in file attributes such as size and time. */ *written_length = rc; *stability_got = stability_wanted; ds->stability_got = stability_wanted; /* In case of MDS being DS, there shall not be upcalls sent from * backend. Hence invalidate the entry here */ (void)up_process_event_object(glfs_export->gl_fs, ds->glhandle, GLFS_EVENT_INODE_INVALIDATE); return NFS4_OK; } /** * @brief Commit a byte range to a DS handle. * * NFSv4.1 data server filehandles are disjount from normal * filehandles (in Ganesha, there is a ds_flag in the filehandle_v4_t * structure) and do not get loaded into mdcache or processed the * normal way. * * @param[in] ds_pub FSAL DS handle * @param[in] offset Start of commit window * @param[in] count Length of commit window * @param[out] writeverf Write verifier * * @return An NFSv4.1 status code. */ static nfsstat4 ds_commit(struct fsal_ds_handle *const ds_pub, const offset4 offset, const count4 count, verifier4 *const writeverf) { memset(writeverf, 0, NFS4_VERIFIER_SIZE); struct glfs_ds_handle *ds = container_of(ds_pub, struct glfs_ds_handle, ds); int rc = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; if (ds->stability_got == FILE_SYNC4) { struct glusterfs_export *glfs_export = container_of(op_ctx->ctx_pnfs_ds->mds_fsal_export, struct glusterfs_export, export); struct glfs_fd *glfd = NULL; SET_GLUSTER_CREDS_OP_CTX(glfs_export); glfd = glfs_h_open(glfs_export->gl_fs->fs, ds->glhandle, O_RDWR); if (glfd == NULL) { LogDebug(COMPONENT_PNFS, "glfd in ds_handle is NULL"); RESET_GLUSTER_CREDS(glfs_export); return NFS4ERR_SERVERFAULT; } #ifdef USE_GLUSTER_STAT_FETCH_API rc = glfs_fsync(glfd, NULL, NULL); #else rc = glfs_fsync(glfd); #endif if (rc != 0) LogMajor(COMPONENT_PNFS, "glfs_fsync failed %d", errno); rc = glfs_close(glfd); if (rc != 0) LogDebug(COMPONENT_PNFS, "status after close %d", errno); RESET_GLUSTER_CREDS(glfs_export); } if ((rc != 0) || (status.major != ERR_FSAL_NO_ERROR)) return NFS4ERR_INVAL; return NFS4_OK; } /** * @brief Create a FSAL data server handle from a wire handle * * This function creates a FSAL data server handle from a client * supplied "wire" handle. This is also where validation gets done, * since PUTFH is the only operation that can return * NFS4ERR_BADHANDLE. * * @param[in] export_pub The export in which to create the handle * @param[in] desc Buffer from which to create the file * @param[out] ds_pub FSAL data server handle * * @return NFSv4.1 error codes. */ static nfsstat4 make_ds_handle(struct fsal_pnfs_ds *const pds, const struct gsh_buffdesc *const hdl_desc, struct fsal_ds_handle **const handle, int flags) { /* Handle to be created for DS */ struct glfs_ds_handle *ds = NULL; unsigned char globjhdl[GFAPI_HANDLE_LENGTH] = { '\0' }; struct stat sb; struct glusterfs_export *glfs_export = container_of( pds->mds_fsal_export, struct glusterfs_export, export); *handle = NULL; if (hdl_desc->len != sizeof(struct glfs_ds_wire)) return NFS4ERR_BADHANDLE; ds = gsh_calloc(1, sizeof(struct glfs_ds_handle)); *handle = &ds->ds; memcpy(globjhdl, hdl_desc->addr, GFAPI_HANDLE_LENGTH); /* Create glfs_object for the DS handle */ ds->glhandle = glfs_h_create_from_handle( glfs_export->gl_fs->fs, globjhdl, GFAPI_HANDLE_LENGTH, &sb); if (ds->glhandle == NULL) { LogDebug(COMPONENT_PNFS, "glhandle in ds_handle is NULL"); return NFS4ERR_SERVERFAULT; } /* Connect lazily when a FILE_SYNC4 write forces us to, not here. */ ds->connected = false; return NFS4_OK; } void pnfs_ds_ops_init(struct fsal_pnfs_ds_ops *ops) { memcpy(ops, &def_pnfs_ds_ops, sizeof(struct fsal_pnfs_ds_ops)); ops->make_ds_handle = make_ds_handle; ops->dsh_release = dsh_release; ops->dsh_read = ds_read; ops->dsh_write = ds_write; ops->dsh_commit = ds_commit; } nfs-ganesha-6.5/src/FSAL/FSAL_GLUSTER/export.c000066400000000000000000000523741473756622300205430ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Red Hat Inc., 2013 * Author: Anand Subramanian anands@redhat.com * * This program 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 3 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 * 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 * * ------------- */ /** * @file export.c * @author Shyamsundar R * @author Anand Subramanian * * @brief GLUSTERFS FSAL export object */ #include #include #include #include #include "fsal.h" #include "FSAL/fsal_config.h" #include "fsal_convert.h" #include "config_parsing.h" #include "gluster_internal.h" #include "nfs_exports.h" #include "export_mgr.h" #include "pnfs_utils.h" #include "sal_data.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/fsal_gl.h" #endif /* LTTNG_PARSING */ /* The default location of gfapi log * if glfs_log param is not defined in * the export file */ #define GFAPI_LOG_LOCATION "/var/log/ganesha/ganesha-gfapi.log" /** * @brief Implements GLUSTER FSAL exportoperation release */ static void export_release(struct fsal_export *exp_hdl) { struct glusterfs_export *glfs_export = container_of(exp_hdl, struct glusterfs_export, export); /* check activity on the export */ /* detach the export */ fsal_detach_export(glfs_export->export.fsal, &glfs_export->export.exports); free_export_ops(&glfs_export->export); glusterfs_free_fs(glfs_export->gl_fs); gsh_free(glfs_export->mount_path); gsh_free(glfs_export->export_path); gsh_free(glfs_export->sec_label_xattr); gsh_free(glfs_export); } /** * @brief Implements GLUSTER FSAL exportoperation lookup_path */ static fsal_status_t lookup_path(struct fsal_export *export_pub, const char *path, struct fsal_obj_handle **pub_handle, struct fsal_attrlist *attrs_out) { int rc = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; char *realpath = NULL; struct stat sb; struct glfs_object *glhandle = NULL; unsigned char globjhdl[GFAPI_HANDLE_LENGTH] = { '\0' }; struct glusterfs_handle *objhandle = NULL; struct glusterfs_export *glfs_export = container_of(export_pub, struct glusterfs_export, export); char vol_uuid[GLAPI_UUID_LENGTH] = { '\0' }; LogFullDebug(COMPONENT_FSAL, "In args: path = %s", path); *pub_handle = NULL; if (strcmp(path, glfs_export->mount_path) == 0) { realpath = gsh_strdup(glfs_export->export_path); } else { int len_export_path = strlen(glfs_export->export_path); int len_path = strlen(path); int len_mount_path = strlen(glfs_export->mount_path); int len_dest_path = len_path - len_mount_path; char *dest_path; /* * mount path is not same as the exported one. Should be subdir * then. */ /** @todo: How do we handle symlinks if present in the path. */ realpath = gsh_malloc(len_export_path + len_dest_path + 1); /* * Handle the case wherein glfs_export->export_path * is root i.e, '/' separately. */ if (len_export_path != 1) { memcpy(realpath, glfs_export->export_path, len_export_path); dest_path = realpath + len_export_path; } else { dest_path = realpath; } memcpy(dest_path, path + len_mount_path, len_path - len_mount_path + 1); } glhandle = glfs_h_lookupat(glfs_export->gl_fs->fs, NULL, realpath, &sb, 1); if (glhandle == NULL) { status = gluster2fsal_error(errno); goto out; } rc = glfs_h_extract_handle(glhandle, globjhdl, GFAPI_HANDLE_LENGTH); if (rc < 0) { status = gluster2fsal_error(errno); goto out; } rc = glfs_get_volumeid(glfs_export->gl_fs->fs, vol_uuid, GLAPI_UUID_LENGTH); if (rc < 0) { status = gluster2fsal_error(errno); goto out; } construct_handle(glfs_export, &sb, glhandle, globjhdl, &objhandle, vol_uuid); if (attrs_out != NULL) { posix2fsal_attributes_all(&sb, attrs_out); } *pub_handle = &objhandle->handle; gsh_free(realpath); return status; out: gluster_cleanup_vars(glhandle); gsh_free(realpath); return status; } /** * @brief Implements GLUSTER FSAL exportoperation wire_to_host */ static fsal_status_t wire_to_host(struct fsal_export *exp_hdl, fsal_digesttype_t in_type, struct gsh_buffdesc *fh_desc, int flags) { size_t fh_size; #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif /* sanity checks */ if (!fh_desc || !fh_desc->addr) return fsalstat(ERR_FSAL_FAULT, 0); fh_size = GLAPI_HANDLE_LENGTH; if (fh_desc->len != fh_size) { LogMajor(COMPONENT_FSAL, "Size mismatch for handle. should be %zu, got %zu", fh_size, fh_desc->len); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } fh_desc->len = fh_size; /* pass back the actual size */ #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_wire_to_host); #endif return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Implements GLUSTER FSAL exportoperation create_handle */ static fsal_status_t create_handle(struct fsal_export *export_pub, struct gsh_buffdesc *fh_desc, struct fsal_obj_handle **pub_handle, struct fsal_attrlist *attrs_out) { int rc = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct stat sb; struct glfs_object *glhandle = NULL; unsigned char globjhdl[GFAPI_HANDLE_LENGTH] = { '\0' }; struct glusterfs_handle *objhandle = NULL; struct glusterfs_export *glfs_export = container_of(export_pub, struct glusterfs_export, export); char vol_uuid[GLAPI_UUID_LENGTH] = { '\0' }; #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif *pub_handle = NULL; if (fh_desc->len != GLAPI_HANDLE_LENGTH) { status.major = ERR_FSAL_INVAL; goto out; } /* First 16bytes contain volume UUID. globjhdl is in the second */ /* half(16bytes) of the fs_desc->addr. */ memcpy(globjhdl, fh_desc->addr + GLAPI_UUID_LENGTH, GFAPI_HANDLE_LENGTH); glhandle = glfs_h_create_from_handle(glfs_export->gl_fs->fs, globjhdl, GFAPI_HANDLE_LENGTH, &sb); if (glhandle == NULL) { status = gluster2fsal_error(errno); goto out; } rc = glfs_get_volumeid(glfs_export->gl_fs->fs, vol_uuid, GLAPI_UUID_LENGTH); if (rc < 0) { status = gluster2fsal_error(errno); goto out; } construct_handle(glfs_export, &sb, glhandle, globjhdl, &objhandle, vol_uuid); if (attrs_out != NULL) { posix2fsal_attributes_all(&sb, attrs_out); } *pub_handle = &objhandle->handle; GSH_UNIQUE_AUTO_TRACEPOINT(fsal_gl, gl_handle, TRACE_DEBUG, "Handle: {}", &objhandle->handle); out: if (status.major != ERR_FSAL_NO_ERROR) gluster_cleanup_vars(glhandle); #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_create_handle); #endif return status; } /** * @brief Given a glfs_object handle, construct handle for * FSAL to use. */ fsal_status_t glfs2fsal_handle(struct glusterfs_export *glfs_export, struct glfs_object *glhandle, struct fsal_obj_handle **pub_handle, struct stat *sb, struct fsal_attrlist *attrs_out) { int rc = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; unsigned char globjhdl[GFAPI_HANDLE_LENGTH] = { '\0' }; struct glusterfs_handle *objhandle = NULL; char vol_uuid[GLAPI_UUID_LENGTH] = { '\0' }; #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif *pub_handle = NULL; if (!glfs_export || !glhandle) { status.major = ERR_FSAL_INVAL; goto out; } rc = glfs_h_extract_handle(glhandle, globjhdl, GFAPI_HANDLE_LENGTH); if (rc < 0) { status = gluster2fsal_error(errno); goto out; } rc = glfs_get_volumeid(glfs_export->gl_fs->fs, vol_uuid, GLAPI_UUID_LENGTH); if (rc < 0) { status = gluster2fsal_error(errno); goto out; } construct_handle(glfs_export, sb, glhandle, globjhdl, &objhandle, vol_uuid); if (attrs_out != NULL) { posix2fsal_attributes_all(sb, attrs_out); } *pub_handle = &objhandle->handle; GSH_UNIQUE_AUTO_TRACEPOINT(fsal_gl, gl_handle, TRACE_DEBUG, "Handle: {}", &objhandle->handle); out: #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_create_handle); #endif return status; } /** * @brief Implements GLUSTER FSAL exportoperation get_fs_dynamic_info */ static fsal_status_t get_dynamic_info(struct fsal_export *exp_hdl, struct fsal_obj_handle *obj_hdl, fsal_dynamicfsinfo_t *infop) { int rc = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct statvfs vfssb; struct glusterfs_export *glfs_export = container_of(exp_hdl, struct glusterfs_export, export); rc = glfs_statvfs(glfs_export->gl_fs->fs, glfs_export->export_path, &vfssb); if (rc != 0) return gluster2fsal_error(errno); memset(infop, 0, sizeof(fsal_dynamicfsinfo_t)); infop->total_bytes = vfssb.f_frsize * vfssb.f_blocks; infop->free_bytes = vfssb.f_frsize * vfssb.f_bfree; infop->avail_bytes = vfssb.f_frsize * vfssb.f_bavail; infop->total_files = vfssb.f_files; infop->free_files = vfssb.f_ffree; infop->avail_files = vfssb.f_favail; infop->time_delta.tv_sec = 0; infop->time_delta.tv_nsec = FSAL_DEFAULT_TIME_DELTA_NSEC; return status; } void gluster_free_state(struct state_t *state) { struct glusterfs_fd *my_fd; my_fd = &container_of(state, struct glusterfs_state_fd, state) ->glusterfs_fd; destroy_fsal_fd(&my_fd->fsal_fd); gsh_free(state); } /** * @brief Allocate a state_t structure * * Note that this is not expected to fail since memory allocation is * expected to abort on failure. * * @param[in] exp_hdl Export state_t will be associated with * @param[in] state_type Type of state to allocate * @param[in] related_state Related state if appropriate * * @returns a state structure. */ struct state_t *glusterfs_alloc_state(struct fsal_export *exp_hdl, enum state_type state_type, struct state_t *related_state) { struct state_t *state; struct glusterfs_fd *my_fd; state = init_state(gsh_calloc(1, sizeof(struct glusterfs_state_fd)), gluster_free_state, state_type, related_state); my_fd = &container_of(state, struct glusterfs_state_fd, state) ->glusterfs_fd; init_fsal_fd(&my_fd->fsal_fd, FSAL_FD_STATE, op_ctx->fsal_export); my_fd->glfd = NULL; return state; } /** @todo: We have gone POSIX way for the APIs below, can consider the CEPH way * in case all are constants across all volumes etc. */ /** * @brief Implements GLUSTER FSAL exportoperation fs_supported_attrs */ static attrmask_t fs_supported_attrs(struct fsal_export *exp_hdl) { attrmask_t supported_mask; supported_mask = fsal_supported_attrs(&exp_hdl->fsal->fs_info); if (!NFSv4_ACL_SUPPORT) supported_mask &= ~ATTR_ACL; return supported_mask; } /** * @brief Function to get the fasl_obj_handle that has fsal_fd as its global fd. * * @param[in] exp_hdl The export in which the handle exists * @param[in] fd File descriptor in question * @param[out] handle FSAL object handle * * @return the fsal_obj_handle. */ void get_fsal_obj_hdl(struct fsal_export *exp_hdl, struct fsal_fd *fd, struct fsal_obj_handle **handle) { struct glusterfs_fd *my_fd = NULL; struct glusterfs_handle *myself = NULL; my_fd = container_of(fd, struct glusterfs_fd, fsal_fd); myself = container_of(my_fd, struct glusterfs_handle, globalfd); *handle = &myself->handle; } /** * @brief Registers GLUSTER FSAL exportoperation vector * * This function overrides operations that we've implemented, leaving * the rest for the default. * * @param[in,out] ops Operations vector */ void export_ops_init(struct export_ops *ops) { ops->release = export_release; ops->lookup_path = lookup_path; ops->wire_to_host = wire_to_host; ops->create_handle = create_handle; ops->get_fs_dynamic_info = get_dynamic_info; ops->fs_supported_attrs = fs_supported_attrs; ops->alloc_state = glusterfs_alloc_state; ops->get_fsal_obj_hdl = get_fsal_obj_hdl; } enum transport { GLUSTER_TCP_VOL, GLUSTER_RDMA_VOL }; struct glexport_params { char *glvolname; char *glhostname; char *glvolpath; char *glfs_log; uint64_t up_poll_usec; bool enable_upcall; enum transport gltransport; char *sec_label_xattr; }; static struct config_item_list transportformats[] = { CONFIG_LIST_TOK("tcp", GLUSTER_TCP_VOL), CONFIG_LIST_TOK("rdma", GLUSTER_RDMA_VOL), CONFIG_LIST_EOL }; static struct config_item export_params[] = { CONF_ITEM_NOOP("name"), CONF_MAND_STR("volume", 1, MAXPATHLEN, NULL, glexport_params, glvolname), CONF_MAND_STR("hostname", 1, MAXPATHLEN, NULL, glexport_params, glhostname), CONF_ITEM_PATH("volpath", 1, MAXPATHLEN, "/", glexport_params, glvolpath), CONF_ITEM_PATH("glfs_log", 1, MAXPATHLEN, GFAPI_LOG_LOCATION, glexport_params, glfs_log), CONF_ITEM_UI64("up_poll_usec", 1, 60 * 1000 * 1000, 10, glexport_params, up_poll_usec), CONF_ITEM_BOOL("enable_upcall", true, glexport_params, enable_upcall), CONF_ITEM_TOKEN("transport", GLUSTER_TCP_VOL, transportformats, glexport_params, gltransport), CONF_ITEM_STR("sec_label_xattr", 0, 256, "security.selinux", glexport_params, sec_label_xattr), CONFIG_EOL }; static struct config_block export_param = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.gluster-export%d", .blk_desc.name = "FSAL", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = export_params, .blk_desc.u.blk.commit = noop_conf_commit }; /* * Given glusterfs_fs object, decrement the refcount. In case if it * becomes zero, free the resources. */ void glusterfs_free_fs(struct glusterfs_fs *gl_fs) { int64_t refcnt; int err = 0; PTHREAD_MUTEX_lock(&GlusterFS.glfs_lock); refcnt = --(gl_fs->refcnt); assert(refcnt >= 0); if (refcnt) { LogDebug(COMPONENT_FSAL, "There are still (%" PRIi64 ")active shares for volume(%s)", gl_fs->refcnt, gl_fs->volname); PTHREAD_MUTEX_unlock(&GlusterFS.glfs_lock); return; } glist_del(&gl_fs->fs_obj); PTHREAD_MUTEX_unlock(&GlusterFS.glfs_lock); atomic_inc_int8_t(&gl_fs->destroy_mode); if (!gl_fs->enable_upcall) goto skip_upcall; /* Cancel upcall readiness if not yet done */ up_ready_cancel((struct fsal_up_vector *)gl_fs->up_ops); #ifndef USE_GLUSTER_UPCALL_REGISTER int *retval = NULL; /* Wait for up_thread to exit */ err = pthread_join(gl_fs->up_thread, (void **)&retval); if (retval && *retval) { LogDebug(COMPONENT_FSAL, "Up_thread join returned value %d", *retval); } if (err) { LogWarn(COMPONENT_FSAL, "Up_thread join failed (%s)", strerror(err)); } #else err = glfs_upcall_unregister(gl_fs->fs, GLFS_EVENT_ANY); if ((err < 0) || (!(err & GLFS_EVENT_INODE_INVALIDATE))) { /* Or can we ignore the error like in case of single * node ganesha server. */ LogWarn(COMPONENT_FSAL, "Unable to unregister for upcalls. Volume: %s", gl_fs->volname); } #endif skip_upcall: /* Gluster and memory cleanup */ glfs_fini(gl_fs->fs); gsh_free(gl_fs->volname); gsh_free(gl_fs); } /** * @brief Given Gluster export params, find and return if there is * already existing export entry. If not create one. */ struct glusterfs_fs *glusterfs_get_fs(struct glexport_params params, const struct fsal_up_vector *up_ops) { int rc = 0; struct glusterfs_fs *gl_fs = NULL; glfs_t *fs = NULL; struct glist_head *glist, *glistn; PTHREAD_MUTEX_lock(&GlusterFS.glfs_lock); glist_for_each_safe(glist, glistn, &GlusterFS.fs_obj) { gl_fs = glist_entry(glist, struct glusterfs_fs, fs_obj); if (!strcmp(params.glvolname, gl_fs->volname)) { goto found; } } gl_fs = gsh_calloc(1, sizeof(struct glusterfs_fs)); glist_init(&gl_fs->fs_obj); fs = glfs_new(params.glvolname); if (!fs) { LogCrit(COMPONENT_FSAL, "Unable to create new glfs. Volume: %s", params.glvolname); goto out; } switch (params.gltransport) { case GLUSTER_RDMA_VOL: rc = glfs_set_volfile_server(fs, "rdma", params.glhostname, 24007); break; default: rc = glfs_set_volfile_server(fs, "tcp", params.glhostname, 24007); break; } if (rc != 0) { LogCrit(COMPONENT_FSAL, "Unable to set volume file. Volume: %s", params.glvolname); goto out; } rc = glfs_set_logging(fs, params.glfs_log, 7); if (rc != 0) { LogCrit(COMPONENT_FSAL, "Unable to set logging. Volume: %s", params.glvolname); goto out; } rc = glfs_init(fs); if (rc != 0) { LogCrit(COMPONENT_FSAL, "Unable to initialize volume. Volume: %s", params.glvolname); goto out; } gl_fs->fs = fs; gl_fs->volname = strdup(params.glvolname); gl_fs->destroy_mode = 0; gl_fs->up_poll_usec = params.up_poll_usec; gl_fs->up_ops = up_ops; gl_fs->enable_upcall = params.enable_upcall; if (!gl_fs->enable_upcall) goto skip_upcall; #ifndef USE_GLUSTER_UPCALL_REGISTER if (initiate_up_thread(gl_fs) != 0) goto out; #else /* We are mainly interested in INODE_INVALIDATE for now. Still * register for all the events */ rc = glfs_upcall_register(fs, GLFS_EVENT_ANY, gluster_process_upcall, gl_fs); if ((rc < 0) || (!(rc & GLFS_EVENT_INODE_INVALIDATE))) { /* Or can we ignore the error like in case of single * node ganesha server. */ LogCrit(COMPONENT_FSAL, "Unable to register for upcalls. Volume: %s", params.glvolname); goto out; } #endif skip_upcall: glist_add(&GlusterFS.fs_obj, &gl_fs->fs_obj); found: ++(gl_fs->refcnt); PTHREAD_MUTEX_unlock(&GlusterFS.glfs_lock); return gl_fs; out: PTHREAD_MUTEX_unlock(&GlusterFS.glfs_lock); if (fs) glfs_fini(fs); glist_del(&gl_fs->fs_obj); /* not needed atm */ gsh_free(gl_fs); return NULL; } /** * @brief Implements GLUSTER FSAL moduleoperation create_export */ fsal_status_t glusterfs_create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops) { int rc = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct glusterfs_export *glfsexport = NULL; bool fsal_attached = false; struct glexport_params params = { .glvolname = NULL, .glhostname = NULL, .glvolpath = NULL, .glfs_log = NULL }; LogDebug(COMPONENT_FSAL, "In args: export path = %s", CTX_FULLPATH(op_ctx)); glfsexport = gsh_calloc(1, sizeof(struct glusterfs_export)); rc = load_config_from_node(parse_node, &export_param, ¶ms, true, err_type); if (rc != 0) { LogCrit(COMPONENT_FSAL, "Incorrect or missing parameters for export %s", CTX_FULLPATH(op_ctx)); status.major = ERR_FSAL_INVAL; goto out; } LogEvent(COMPONENT_FSAL, "Volume %s exported at : '%s'", params.glvolname, params.glvolpath); fsal_export_init(&glfsexport->export); export_ops_init(&glfsexport->export.exp_ops); glfsexport->gl_fs = glusterfs_get_fs(params, up_ops); if (!glfsexport->gl_fs) { status.major = ERR_FSAL_SERVERFAULT; goto out; } rc = fsal_attach_export(fsal_hdl, &glfsexport->export.exports); if (rc != 0) { status.major = ERR_FSAL_SERVERFAULT; LogCrit(COMPONENT_FSAL, "Unable to attach export. Export: %s", CTX_FULLPATH(op_ctx)); goto out; } fsal_attached = true; glfsexport->mount_path = gsh_strdup(CTX_FULLPATH(op_ctx)); glfsexport->export_path = params.glvolpath; glfsexport->saveduid = geteuid(); glfsexport->savedgid = getegid(); glfsexport->export.fsal = fsal_hdl; glfsexport->sec_label_xattr = params.sec_label_xattr; op_ctx->fsal_export = &glfsexport->export; glfsexport->pnfs_ds_enabled = glfsexport->export.exp_ops.fs_supports( &glfsexport->export, fso_pnfs_ds_supported); if (glfsexport->pnfs_ds_enabled) { struct fsal_pnfs_ds *pds = NULL; status = fsal_hdl->m_ops.create_fsal_pnfs_ds(fsal_hdl, parse_node, &pds); if (status.major != ERR_FSAL_NO_ERROR) goto out; /* special case: server_id matches export_id */ pds->id_servers = op_ctx->ctx_export->export_id; pds->mds_export = op_ctx->ctx_export; pds->mds_fsal_export = op_ctx->fsal_export; if (!pnfs_ds_insert(pds)) { LogCrit(COMPONENT_CONFIG, "Server id %d already in use.", pds->id_servers); status.major = ERR_FSAL_EXIST; /* Return the ref taken by create_fsal_pnfs_ds */ pnfs_ds_put(pds); goto out; } LogDebug(COMPONENT_PNFS, "glusterfs_fsal_create: pnfs ds was enabled for [%s]", CTX_FULLPATH(op_ctx)); } glfsexport->pnfs_mds_enabled = glfsexport->export.exp_ops.fs_supports( &glfsexport->export, fso_pnfs_mds_supported); if (glfsexport->pnfs_mds_enabled) { LogDebug(COMPONENT_PNFS, "glusterfs_fsal_create: pnfs mds was enabled for [%s]", CTX_FULLPATH(op_ctx)); export_ops_pnfs(&glfsexport->export.exp_ops); fsal_ops_pnfs(&glfsexport->export.fsal->m_ops); } glfsexport->export.up_ops = up_ops; out: gsh_free(params.glvolname); gsh_free(params.glhostname); gsh_free(params.glfs_log); if (status.major != ERR_FSAL_NO_ERROR) { gsh_free(params.glvolpath); if (fsal_attached) fsal_detach_export(fsal_hdl, &glfsexport->export.exports); if (glfsexport->gl_fs) glusterfs_free_fs(glfsexport->gl_fs); gsh_free(glfsexport); } return status; } nfs-ganesha-6.5/src/FSAL/FSAL_GLUSTER/fsal_up.c000066400000000000000000000216601473756622300206450ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright (C) Red Hat Inc., 2015 * * This program 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 3 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. * * --------------------------------------- */ /** * @file fsal_up.c * * @author Soumya Koduri * * @brief Upcall Interface for FSAL_GLUSTER * */ #include "config.h" #include "fsal.h" #include "fsal_up.h" #include "gluster_internal.h" #include "fsal_convert.h" #include #include #include #include #include #include "sal_functions.h" int up_process_event_object(struct glusterfs_fs *gl_fs, struct glfs_object *object, enum glfs_upcall_reason reason) { int rc = -1; glfs_t *fs = NULL; char vol_uuid[GLAPI_UUID_LENGTH] = { '\0' }; unsigned char globjhdl[GLAPI_HANDLE_LENGTH]; struct gsh_buffdesc key; const struct fsal_up_vector *event_func; fsal_status_t fsal_status = { 0, 0 }; #ifdef USE_GLUSTER_DELEGATION state_status_t rc_s; #endif fs = gl_fs->fs; if (!fs) { LogCrit(COMPONENT_FSAL_UP, "Invalid fs object of the glusterfs_fs(%p)", gl_fs); goto out; } rc = glfs_h_extract_handle(object, globjhdl + GLAPI_UUID_LENGTH, GFAPI_HANDLE_LENGTH); if (rc < 0) { LogDebug(COMPONENT_FSAL_UP, "glfs_h_extract_handle failed %p", fs); goto out; } rc = glfs_get_volumeid(fs, vol_uuid, GLAPI_UUID_LENGTH); if (rc < 0) { LogDebug(COMPONENT_FSAL_UP, "glfs_get_volumeid failed %p", fs); goto out; } memcpy(globjhdl, vol_uuid, GLAPI_UUID_LENGTH); key.addr = &globjhdl; key.len = GLAPI_HANDLE_LENGTH; LogDebug(COMPONENT_FSAL_UP, "Received event to process for %p", fs); event_func = gl_fs->up_ops; switch (reason) { case GLFS_EVENT_INODE_INVALIDATE: fsal_status = event_func->invalidate_close( event_func, &key, FSAL_UP_INVALIDATE_CACHE); rc = fsal_status.major; if (FSAL_IS_ERROR(fsal_status) && fsal_status.major != ERR_FSAL_NOENT) { LogWarn(COMPONENT_FSAL_UP, "UP event:GLFS_EVENT_INODE_INVALIDATE could not be processed for fs (%p), rc(%d)", gl_fs->fs, rc); } break; #ifdef USE_GLUSTER_DELEGATION case GLFS_EVENT_RECALL_LEASE: rc_s = event_func->delegrecall(event_func, &key); if (rc_s != STATE_SUCCESS) { LogWarn(COMPONENT_FSAL_UP, "UP event:GLFS_EVENT_RECALL_LEASE could not be processed for fs(%p), reason(%s)", gl_fs->fs, state_err_str(rc_s)); rc = -1; } break; #endif default: /* invalid reason */ rc = EINVAL; LogWarn(COMPONENT_FSAL_UP, "UP event: Invalid value provided for fs(%p), event(%d)", gl_fs->fs, reason); } out: return rc; } void *GLUSTERFSAL_UP_Thread(void *Arg) { struct glusterfs_fs *gl_fs = Arg; struct fsal_up_vector *event_func; char thr_name[16]; int rc = 0; struct glfs_upcall *cbk = NULL; enum glfs_upcall_reason reason = 0; int retry = 0; int errsv = 0; struct glfs_object *object = NULL; struct glfs_object *p_object = NULL; struct glfs_object *oldp_object = NULL; struct glfs_upcall_inode *in_arg = NULL; #ifdef USE_GLUSTER_DELEGATION struct glfs_upcall_lease *lease_arg = NULL; #endif rcu_register_thread(); (void)snprintf(thr_name, sizeof(thr_name), "fsal_up_%p", gl_fs->fs); SetNameFunction(thr_name); /* Set the FSAL UP functions that will be used to process events. */ event_func = (struct fsal_up_vector *)gl_fs->up_ops; if (event_func == NULL) { LogFatal(COMPONENT_FSAL_UP, "FSAL up vector does not exist. Can not continue."); gsh_free(Arg); goto out; } LogFullDebug(COMPONENT_FSAL_UP, "Initializing FSAL Callback context for %p.", gl_fs->fs); if (!gl_fs->fs) { LogCrit(COMPONENT_FSAL_UP, "FSAL Callback interface - Null glfs context."); goto out; } /* wait for upcall readiness */ up_ready_wait(event_func); /* Start querying for events and processing. */ /** @todo : Do batch processing instead */ while (!atomic_fetch_int8_t(&gl_fs->destroy_mode)) { LogFullDebug( COMPONENT_FSAL_UP, "Requesting event from FSAL Callback interface for %p.", gl_fs->fs); reason = 0; rc = glfs_h_poll_upcall(gl_fs->fs, &cbk); errsv = errno; if (rc != 0) { /* if ENOMEM retry for couple of times * and then exit */ if ((errsv == ENOMEM) && (retry < 10)) { sleep(1); retry++; continue; } else { switch (errsv) { case ENOMEM: LogMajor( COMPONENT_FSAL_UP, "Memory allocation failed during poll_upcall for (%p).", gl_fs->fs); abort(); case ENOTSUP: LogEvent( COMPONENT_FSAL_UP, "Upcall feature is not supported for (%p).", gl_fs->fs); break; default: LogCrit(COMPONENT_FSAL_UP, "Poll upcall failed for %p. rc %d errno %d (%s) reason %d", gl_fs->fs, rc, errsv, strerror(errsv), reason); } goto out; } } retry = 0; LogFullDebug(COMPONENT_FSAL_UP, "Received upcall event: reason(%d)", reason); if (!cbk) { usleep(gl_fs->up_poll_usec); continue; } reason = glfs_upcall_get_reason(cbk); /* Decide what type of event this is * inode update / invalidate? */ switch (reason) { case GLFS_UPCALL_EVENT_NULL: usleep(gl_fs->up_poll_usec); continue; case GLFS_UPCALL_INODE_INVALIDATE: in_arg = glfs_upcall_get_event(cbk); if (!in_arg) { /* Could be ENOMEM issues. continue */ LogWarn(COMPONENT_FSAL_UP, "Received NULL upcall event arg"); break; } object = glfs_upcall_inode_get_object(in_arg); if (object) up_process_event_object(gl_fs, object, reason); p_object = glfs_upcall_inode_get_pobject(in_arg); if (p_object) up_process_event_object(gl_fs, p_object, reason); oldp_object = glfs_upcall_inode_get_oldpobject(in_arg); if (oldp_object) up_process_event_object(gl_fs, oldp_object, reason); break; #ifdef USE_GLUSTER_DELEGATION case GLFS_UPCALL_RECALL_LEASE: lease_arg = glfs_upcall_get_event(cbk); if (!lease_arg) { /* Could be ENOMEM issues. continue */ LogWarn(COMPONENT_FSAL_UP, "Received NULL upcall event arg"); break; } object = glfs_upcall_lease_get_object(lease_arg); if (object) up_process_event_object(gl_fs, object, reason); break; #endif default: LogWarn(COMPONENT_FSAL_UP, "Unknown event: %d", reason); continue; } if (cbk) { glfs_free(cbk); cbk = NULL; } } out: rcu_unregister_thread(); return NULL; } /* GLUSTERFSFSAL_UP_Thread */ void gluster_process_upcall(struct glfs_upcall *cbk, void *data) { struct glusterfs_fs *gl_fs = data; struct fsal_up_vector *event_func; enum glfs_upcall_reason reason = 0; struct glfs_object *object = NULL; struct glfs_object *p_object = NULL; struct glfs_object *oldp_object = NULL; struct glfs_upcall_inode *in_arg = NULL; #ifdef USE_GLUSTER_DELEGATION struct glfs_upcall_lease *lease_arg = NULL; #endif if (!cbk) { LogFatal(COMPONENT_FSAL_UP, "Upcall received with no data"); return; } /* Set the FSAL UP functions that will be used to process events. */ event_func = (struct fsal_up_vector *)gl_fs->up_ops; if (!event_func) { LogFatal(COMPONENT_FSAL_UP, "FSAL up vector does not exist. Can not continue."); goto out; } if (!gl_fs->fs) { LogCrit(COMPONENT_FSAL_UP, "FSAL Callback interface - Null glfs context."); goto out; } /* wait for upcall readiness */ up_ready_wait(event_func); reason = glfs_upcall_get_reason(cbk); /* Decide what type of event this is * inode update / invalidate? */ switch (reason) { case GLFS_UPCALL_INODE_INVALIDATE: in_arg = glfs_upcall_get_event(cbk); if (!in_arg) { /* Could be ENOMEM issues. continue */ LogWarn(COMPONENT_FSAL_UP, "Received NULL upcall event arg"); break; } object = glfs_upcall_inode_get_object(in_arg); if (object) up_process_event_object(gl_fs, object, reason); p_object = glfs_upcall_inode_get_pobject(in_arg); if (p_object) up_process_event_object(gl_fs, p_object, reason); oldp_object = glfs_upcall_inode_get_oldpobject(in_arg); if (oldp_object) up_process_event_object(gl_fs, oldp_object, reason); break; #ifdef USE_GLUSTER_DELEGATION case GLFS_UPCALL_RECALL_LEASE: lease_arg = glfs_upcall_get_event(cbk); if (!lease_arg) { /* Could be ENOMEM issues. continue */ LogWarn(COMPONENT_FSAL_UP, "Received NULL upcall event arg"); break; } object = glfs_upcall_lease_get_object(lease_arg); if (object) up_process_event_object(gl_fs, object, reason); break; #endif default: LogWarn(COMPONENT_FSAL_UP, "Unknown event: %d", reason); } out: glfs_free(cbk); } nfs-ganesha-6.5/src/FSAL/FSAL_GLUSTER/gluster_internal.c000066400000000000000000000275651473756622300226070ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Red Hat Inc., 2011 * Author: Anand Subramanian anands@redhat.com * * This program 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 3 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 * 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 * * ------------- */ /* main.c * Module core functions */ #include #include "os/xattr.h" #include "gluster_internal.h" #include "fsal_api.h" #include "fsal_convert.h" #include "nfs4_acls.h" #include "FSAL/fsal_commonlib.h" #include "posix_acls.h" #include "nfs_exports.h" /** * @brief FSAL status mapping from GlusterFS errors * * This function returns a fsal_status_t with the FSAL error as the * major, and the posix error as minor. Please note that this routine * needs to be used only in case of failures. * * @param[in] gluster_errorcode Gluster error * * @return FSAL status. */ fsal_status_t gluster2fsal_error(const int err) { fsal_status_t status; int g_err = err; if (!g_err) { LogWarn(COMPONENT_FSAL, "appropriate errno not set"); g_err = EINVAL; } status.minor = g_err; status.major = posix2fsal_error(g_err); return status; } /** * @brief Convert a struct stat from Gluster to a struct fsal_attrlist * * This function writes the content of the supplied struct stat to the * struct fsalsattr. * * @param[in] buffstat Stat structure * @param[out] fsalattr FSAL attributes */ void stat2fsal_attributes(const struct stat *buffstat, struct fsal_attrlist *fsalattr) { /* Indicate which attributes we have set without affecting the * other bits in the mask. */ fsalattr->valid_mask |= ATTRS_POSIX; fsalattr->supported = op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export); /* Fills the output struct */ fsalattr->type = posix2fsal_type(buffstat->st_mode); fsalattr->filesize = buffstat->st_size; fsalattr->fsid = posix2fsal_fsid(buffstat->st_dev); fsalattr->fileid = buffstat->st_ino; fsalattr->mode = unix2fsal_mode(buffstat->st_mode); fsalattr->numlinks = buffstat->st_nlink; fsalattr->owner = buffstat->st_uid; fsalattr->group = buffstat->st_gid; /** @todo: gfapi currently only fills in the legacy time_t fields * when it supports the timespec fields calls to this * function should be replaced with calls to * posix2fsal_attributes rather than changing this code. */ fsalattr->atime = posix2fsal_time(buffstat->st_atime, 0); fsalattr->ctime = posix2fsal_time(buffstat->st_ctime, 0); fsalattr->mtime = posix2fsal_time(buffstat->st_mtime, 0); fsalattr->change = MAX(buffstat->st_mtime, buffstat->st_ctime); fsalattr->spaceused = buffstat->st_blocks * S_BLKSIZE; fsalattr->rawdev = posix2fsal_devt(buffstat->st_rdev); /* Disable seclabels if not enabled in config */ if (!op_ctx_export_has_option(EXPORT_OPTION_SECLABEL_SET)) fsalattr->supported &= ~ATTR4_SEC_LABEL; } /** * @brief Construct a new filehandle * * This function constructs a new Gluster FSAL object handle and attaches * it to the export. After this call the attributes have been filled * in and the handdle is up-to-date and usable. * * @param[in] st Stat data for the file * @param[in] export The export on which the object lives * @param[out] obj Object created * * @return 0 on success, negative error codes on failure. */ void construct_handle(struct glusterfs_export *glexport, const struct stat *st, struct glfs_object *glhandle, unsigned char *globjhdl, struct glusterfs_handle **obj, const char *vol_uuid) { struct glusterfs_handle *constructing = NULL; constructing = gsh_calloc(1, sizeof(struct glusterfs_handle)); constructing->glhandle = glhandle; memcpy(constructing->globjhdl, vol_uuid, GLAPI_UUID_LENGTH); memcpy(constructing->globjhdl + GLAPI_UUID_LENGTH, globjhdl, GFAPI_HANDLE_LENGTH); constructing->globalfd.glfd = NULL; fsal_obj_handle_init(&constructing->handle, &glexport->export, posix2fsal_type(st->st_mode), true); constructing->handle.fsid = posix2fsal_fsid(st->st_dev); constructing->handle.fileid = st->st_ino; constructing->handle.obj_ops = &GlusterFS.handle_ops; if (constructing->handle.type == REGULAR_FILE) { init_fsal_fd(&constructing->globalfd.fsal_fd, FSAL_FD_GLOBAL, op_ctx->fsal_export); } *obj = constructing; } void gluster_cleanup_vars(struct glfs_object *glhandle) { if (glhandle) { /* Error ignored, this is a cleanup operation, can't do much. */ /** @todo: Useful point for logging? */ glfs_h_close(glhandle); } } void setglustercreds(struct glusterfs_export *glfs_export, uid_t *uid, gid_t *gid, unsigned int ngrps, gid_t *groups, char *client_addr, unsigned int client_addr_len, char *file, int line, char *function) { int rc = 0; #ifdef USE_GLUSTER_DELEGATION char lease_id[GLAPI_LEASE_ID_SIZE]; #endif if (uid) { if (*uid != glfs_export->saveduid) rc = glfs_setfsuid(*uid); } else { rc = glfs_setfsuid(glfs_export->saveduid); } if (rc) goto out; if (gid) { if (*gid != glfs_export->savedgid) rc = glfs_setfsgid(*gid); } else { rc = glfs_setfsgid(glfs_export->savedgid); } if (rc) goto out; if (ngrps != 0 && groups) rc = glfs_setfsgroups(ngrps, groups); else rc = glfs_setfsgroups(0, NULL); #ifdef USE_GLUSTER_DELEGATION if ((client_addr_len <= GLAPI_LEASE_ID_SIZE) && client_addr) { memset(lease_id, 0, GLFS_LEASE_ID_SIZE); memcpy(lease_id, client_addr, client_addr_len); rc = glfs_setfsleaseid(lease_id); } else rc = glfs_setfsleaseid(NULL); #endif out: if (rc != 0) { DisplayLogComponentLevel( COMPONENT_FSAL, file, line, function, NIV_FATAL, "Could not set Gluster credentials - uid(%d), gid(%d)", uid ? *uid : glfs_export->saveduid, gid ? *gid : glfs_export->savedgid); } } /* * Read the ACL in GlusterFS format and convert it into fsal ACL before * storing it in fsalattr */ fsal_status_t glusterfs_get_acl(struct glusterfs_export *glfs_export, struct glfs_object *glhandle, glusterfs_fsal_xstat_t *buffxstat, struct fsal_attrlist *fsalattr) { fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; fsal_acl_data_t acldata; fsal_acl_status_t aclstatus; fsal_ace_t *pace = NULL; int e_count = 0, i_count = 0, new_count = 0, new_i_count = 0; if (fsalattr->acl != NULL) { /* We should never be passed attributes that have an * ACL attached, but just in case some future code * path changes that assumption, let's release the * old ACL properly. */ nfs4_acl_release_entry(fsalattr->acl); fsalattr->acl = NULL; } if (NFSv4_ACL_SUPPORT) { buffxstat->e_acl = glfs_h_acl_get(glfs_export->gl_fs->fs, glhandle, ACL_TYPE_ACCESS); if (!buffxstat->e_acl) { status = gluster2fsal_error(errno); return status; } e_count = ace_count(buffxstat->e_acl); if (buffxstat->is_dir) { buffxstat->i_acl = glfs_h_acl_get(glfs_export->gl_fs->fs, glhandle, ACL_TYPE_DEFAULT); i_count = ace_count(buffxstat->i_acl); } /* Allocating memory for both ALLOW and DENY entries */ acldata.naces = 2 * (e_count + i_count); LogDebug(COMPONENT_FSAL, "No of aces present in fsal_acl_t = %d", acldata.naces); if (!acldata.naces) return status; FSAL_SET_MASK(buffxstat->attr_valid, XATTR_ACL); acldata.aces = (fsal_ace_t *)nfs4_ace_alloc(acldata.naces); pace = acldata.aces; new_count = posix_acl_2_fsal_acl(buffxstat->e_acl, buffxstat->is_dir, false, ACL_FOR_V4, &pace); if (new_count < 0) return fsalstat(ERR_FSAL_NO_ACE, -1); if (i_count > 0) { new_i_count = posix_acl_2_fsal_acl(buffxstat->i_acl, true, true, ACL_FOR_V4, &pace); if (new_i_count > 0) new_count += new_i_count; else LogDebug( COMPONENT_FSAL, "Inherit acl is not set for this directory"); } /* Reallocating acldata into the required size */ acldata.aces = (fsal_ace_t *)gsh_realloc( acldata.aces, new_count * sizeof(fsal_ace_t)); acldata.naces = new_count; fsalattr->acl = nfs4_acl_new_entry(&acldata, &aclstatus); LogDebug(COMPONENT_FSAL, "fsal acl = %p, fsal_acl_status = %u", fsalattr->acl, aclstatus); if (fsalattr->acl == NULL) { LogCrit(COMPONENT_FSAL, "failed to create a new acl entry"); return fsalstat(ERR_FSAL_NOMEM, -1); } fsalattr->valid_mask |= ATTR_ACL; } else { /* We were asked for ACL but do not support. */ status = fsalstat(ERR_FSAL_NOTSUPP, 0); } return status; } /* * Store the Glusterfs ACL using setxattr call. */ fsal_status_t glusterfs_set_acl(struct glusterfs_export *glfs_export, struct glusterfs_handle *objhandle, glusterfs_fsal_xstat_t *buffxstat) { int rc = 0; rc = glfs_h_acl_set(glfs_export->gl_fs->fs, objhandle->glhandle, ACL_TYPE_ACCESS, buffxstat->e_acl); if (rc < 0) { /** @todo: check if error is appropriate.*/ LogMajor(COMPONENT_FSAL, "failed to set access type posix acl"); return fsalstat(ERR_FSAL_INVAL, 0); } /* For directories consider inherited acl too */ if (buffxstat->is_dir && buffxstat->i_acl) { rc = glfs_h_acl_set(glfs_export->gl_fs->fs, objhandle->glhandle, ACL_TYPE_DEFAULT, buffxstat->i_acl); if (rc < 0) { LogMajor(COMPONENT_FSAL, "failed to set default type posix acl"); return fsalstat(ERR_FSAL_INVAL, 0); } } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* * Process NFSv4 ACLs passed in setattr call */ fsal_status_t glusterfs_process_acl(struct glfs *fs, struct glfs_object *object, struct fsal_attrlist *attrs, glusterfs_fsal_xstat_t *buffxstat) { LogDebug(COMPONENT_FSAL, "setattr acl = %p", attrs->acl); /* Convert FSAL ACL to POSIX ACL */ buffxstat->e_acl = fsal_acl_2_posix_acl(attrs->acl, ACL_TYPE_ACCESS); if (!buffxstat->e_acl) { LogMajor(COMPONENT_FSAL, "failed to set access type posix acl"); return fsalstat(ERR_FSAL_FAULT, 0); } /* For directories consider inherited acl too */ if (buffxstat->is_dir) { buffxstat->i_acl = fsal_acl_2_posix_acl(attrs->acl, ACL_TYPE_DEFAULT); if (!buffxstat->i_acl) LogDebug(COMPONENT_FSAL, "inherited acl is not defined for directory"); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } int initiate_up_thread(struct glusterfs_fs *gl_fs) { pthread_attr_t up_thr_attr; int err = 0; int retries = 10; memset(&up_thr_attr, 0, sizeof(up_thr_attr)); /* Initialization of thread attributes from nfs_init.c */ PTHREAD_ATTR_init(&up_thr_attr); PTHREAD_ATTR_setscope(&up_thr_attr, PTHREAD_SCOPE_SYSTEM); PTHREAD_ATTR_setdetachstate(&up_thr_attr, PTHREAD_CREATE_JOINABLE); PTHREAD_ATTR_setstacksize(&up_thr_attr, 2116488); do { err = pthread_create(&gl_fs->up_thread, &up_thr_attr, GLUSTERFSAL_UP_Thread, gl_fs); sleep(1); } while (err && (err == EAGAIN) && (retries-- > 0)); PTHREAD_ATTR_destroy(&up_thr_attr); if (err) { LogCrit(COMPONENT_THREAD, "can't create GLUSTERFSAL_UP_Thread for volume %s error - %s (%d)", gl_fs->volname, strerror(err), err); return -1; } return 0; } #ifdef GLTIMING void latency_update(struct timespec *s_time, struct timespec *e_time, int opnum) { atomic_add_uint64_t(&glfsal_latencies[opnum].overall_time, timespec_diff(s_time, e_time)); atomic_add_uint64_t(&glfsal_latencies[opnum].count, 1); } void latency_dump(void) { int i = 0; for (; i < LATENCY_SLOTS; i++) { LogCrit(COMPONENT_FSAL, "Op:%d:Count:%" PRIu64 ":nsecs:%" PRIu64, i, glfsal_latencies[i].count, glfsal_latencies[i].overall_time); } } #endif nfs-ganesha-6.5/src/FSAL/FSAL_GLUSTER/gluster_internal.h000066400000000000000000000351241473756622300226020ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Red Hat Inc., 2011 * Author: Anand Subramanian anands@redhat.com * * This program 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 3 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 * 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 * * ------------- */ #ifndef GLUSTER_INTERNAL #define GLUSTER_INTERNAL #include "fsal.h" #include "fsal_types.h" #include "fsal_api.h" #include "posix_acls.h" #include "FSAL/fsal_commonlib.h" #include #include #include "nfs_exports.h" #define GLUSTER_VOLNAME_KEY "volume" #define GLUSTER_HOSTNAME_KEY "hostname" #define GLUSTER_VOLPATH_KEY "volpath" /* defined the set of attributes supported with POSIX */ #define GLUSTERFS_SUPPORTED_ATTRIBUTES \ (ATTRS_POSIX | ATTR_ACL | ATTR4_SEC_LABEL) /** * The attributes this FSAL can set. */ #define GLUSTERFS_SETTABLE_ATTRIBUTES \ (ATTR_MODE | ATTR_OWNER | ATTR_GROUP | ATTR_ATIME | ATTR_CTIME | \ ATTR_MTIME | ATTR_SIZE | ATTR_MTIME_SERVER | ATTR_ATIME_SERVER | \ ATTR_ACL | ATTR4_SEC_LABEL | ATTR4_XATTR) /** * Override internal Gluster defines for the time being. */ /* Values for valid flags to be used when using XXXsetattr, to set multiple * attribute values passed via the related stat structure. */ #define GLAPI_SET_ATTR_MODE GFAPI_SET_ATTR_MODE #define GLAPI_SET_ATTR_UID GFAPI_SET_ATTR_UID #define GLAPI_SET_ATTR_GID GFAPI_SET_ATTR_GID #define GLAPI_SET_ATTR_SIZE GFAPI_SET_ATTR_SIZE #define GLAPI_SET_ATTR_ATIME GFAPI_SET_ATTR_ATIME #define GLAPI_SET_ATTR_MTIME GFAPI_SET_ATTR_MTIME /* UUID length for the object returned from glfs_get_volume_id */ #define GLAPI_UUID_LENGTH 16 /* * GFAPI_HANDLE_LENGTH is the handle length for object handles * returned from glfs_h_extract_handle or glfs_h_create_from_handle * * GLAPI_HANDLE_LENGTH is the total length of the handle descriptor * to be used in the wire handle. */ #define GLAPI_HANDLE_LENGTH (GFAPI_HANDLE_LENGTH + GLAPI_UUID_LENGTH) /* Flags to determine if ACLs are supported */ #define NFSv4_ACL_SUPPORT (!op_ctx_export_has_option(EXPORT_OPTION_DISABLE_ACL)) /* define flags for attr_valid */ #define XATTR_STAT (1 << 0) /* 01 */ #define XATTR_ACL (1 << 1) /* 02 */ /* END Override */ #ifdef GLTIMING typedef enum { lat_handle_release = 0, lat_lookup, lat_create, lat_getattrs, lat_handle_to_wire, lat_handle_to_key, lat_extract_handle, lat_create_handle, lat_read_dirents, lat_makedir, lat_makenode, lat_setattrs, lat_file_unlink, lat_file_open, lat_file_read, lat_file_seek, lat_file_write, lat_commit, lat_file_close, lat_makesymlink, lat_readsymlink, lat_linkfile, lat_renamefile, lat_lock_op, lat_end_slots } latency_slots_t; #define LATENCY_SLOTS 24 struct latency_data { uint64_t count; nsecs_elapsed_t overall_time; }; #endif struct glusterfs_fsal_module { struct fsal_module fsal; struct fsal_obj_ops handle_ops; struct glist_head fs_obj; /* list of glusterfs_fs filesystem objects */ pthread_mutex_t glfs_lock; /* lock to protect above list */ }; extern struct glusterfs_fsal_module GlusterFS; struct glusterfs_fs { struct glist_head fs_obj; /* link to glusterfs_fs filesystem objects */ char *volname; glfs_t *fs; const struct fsal_up_vector *up_ops; /*< Upcall operations */ int64_t refcnt; pthread_t up_thread; /* upcall thread */ int8_t destroy_mode; uint64_t up_poll_usec; bool enable_upcall; }; struct glusterfs_export { struct glusterfs_fs *gl_fs; char *mount_path; char *export_path; uid_t saveduid; gid_t savedgid; struct fsal_export export; bool pnfs_ds_enabled; bool pnfs_mds_enabled; char *sec_label_xattr; }; #ifdef USE_GLUSTER_DELEGATION #define GLAPI_LEASE_ID_SIZE GLFS_LEASE_ID_SIZE #endif struct glusterfs_fd { /** open and share mode plus fd management */ struct fsal_fd fsal_fd; /** Gluster file descriptor. */ struct glfs_fd *glfd; struct user_cred creds; /* user creds opening fd*/ #ifdef USE_GLUSTER_DELEGATION char lease_id[GLAPI_LEASE_ID_SIZE]; #endif }; #define GLUSTERFS_FD_INIT \ { \ FSAL_FD_INIT, NULL, \ { \ 0, 0, 0, NULL \ } \ } struct glusterfs_handle { struct glfs_object *glhandle; unsigned char globjhdl[GLAPI_HANDLE_LENGTH]; /* handle descriptor, for wire handle */ struct glusterfs_fd globalfd; struct fsal_obj_handle handle; /* public FSAL handle */ struct fsal_share share; /* share_reservations */ /* following added for pNFS support */ uint64_t rd_issued; uint64_t rd_serial; uint64_t rw_issued; uint64_t rw_serial; uint64_t rw_max_len; #ifdef USE_GLUSTER_DELEGATION glfs_lease_types_t lease_type; /* Store lease_type granted to this file: NONE,RD or RW */ #endif }; /* Structures defined for PNFS */ struct glfs_ds_handle { struct fsal_ds_handle ds; struct glfs_object *glhandle; stable_how4 stability_got; bool connected; }; struct glfs_file_layout { uint32_t stripe_length; uint64_t stripe_type; uint32_t devid; }; struct glfs_ds_wire { unsigned char gfid[16]; struct glfs_file_layout layout; /*< Layout information */ }; /* Define the buffer size for GLUSTERFS NFS4 ACL. */ #define GLFS_ACL_BUF_SIZE 0x1000 /* A set of buffers to retrieve multiple attributes at the same time. */ typedef struct fsal_xstat__ { int attr_valid; struct stat buffstat; acl_t e_acl; /* stores effective acl */ acl_t i_acl; /* stores inherited acl */ bool is_dir; } glusterfs_fsal_xstat_t; static inline void glusterfs_fsal_clean_xstat(glusterfs_fsal_xstat_t *buffxstat) { if (buffxstat->e_acl) { acl_free(buffxstat->e_acl); buffxstat->e_acl = NULL; } if (buffxstat->i_acl) { acl_free(buffxstat->i_acl); buffxstat->i_acl = NULL; } } struct glusterfs_state_fd { /** state MUST be first to use default free_state */ struct state_t state; struct glusterfs_fd glusterfs_fd; }; void setglustercreds(struct glusterfs_export *glfs_export, uid_t *uid, gid_t *gid, unsigned int ngrps, gid_t *groups, char *client_addr, unsigned int client_addr_len, char *file, int line, char *function); #ifdef USE_GLUSTER_DELEGATION #define SET_GLUSTER_CREDS_OP_CTX(glfs_export) \ do { \ int old_errno = errno; \ void *sa = NULL; \ size_t sa_len = 0; \ \ if (op_ctx->caller_addr != NULL) { \ sa = socket_addr(op_ctx->caller_addr); \ sa_len = socket_addr_len(op_ctx->caller_addr); \ } else if (op_ctx->client != NULL) { \ sa = socket_addr(&op_ctx->client->cl_addrbuf); \ sa_len = socket_addr_len(&op_ctx->client->cl_addrbuf); \ } \ \ ((void)setglustercreds( \ glfs_export, &op_ctx->creds.caller_uid, \ &op_ctx->creds.caller_gid, op_ctx->creds.caller_glen, \ op_ctx->creds.caller_garray, sa, sa_len, \ (char *)__FILE__, __LINE__, (char *)__func__)); \ errno = old_errno; \ } while (0) #define SET_GLUSTER_CREDS_MY_FD(glfs_export, my_fd) \ do { \ int old_errno = errno; \ \ ((void)setglustercreds( \ glfs_export, &(my_fd)->creds.caller_uid, \ &(my_fd)->creds.caller_gid, \ (my_fd)->creds.caller_glen, \ (my_fd)->creds.caller_garray, (my_fd)->lease_id, \ GLAPI_LEASE_ID_SIZE, (char *)__FILE__, __LINE__, \ (char *)__func__)); \ errno = old_errno; \ } while (0) /** @todo: This doesn't quite work right if op_ctx has no client, hopefully * in that case there won't actually ever be any leases... */ #define SET_GLUSTER_LEASE_ID(my_fd) \ do { \ void *sa = NULL; \ size_t sa_len = 0; \ \ if (op_ctx->caller_addr != NULL) { \ sa = socket_addr(op_ctx->caller_addr); \ sa_len = socket_addr_len(op_ctx->caller_addr); \ } else if (op_ctx->client != NULL) { \ sa = socket_addr(&op_ctx->client->cl_addrbuf); \ sa_len = socket_addr_len(&op_ctx->client->cl_addrbuf); \ } \ \ memset((my_fd)->lease_id, 0, GLAPI_LEASE_ID_SIZE); \ \ if (sa_len != 0 && sa_len <= GLAPI_LEASE_ID_SIZE) { \ memcpy((my_fd)->lease_id, sa, sa_len); \ } \ } while (0) #else #define SET_GLUSTER_CREDS_OP_CTX(glfs_export) \ do { \ int old_errno = errno; \ \ ((void)setglustercreds( \ glfs_export, &op_ctx->creds.caller_uid, \ &op_ctx->creds.caller_gid, op_ctx->creds.caller_glen, \ op_ctx->creds.caller_garray, NULL, 0, \ (char *)__FILE__, __LINE__, (char *)__func__)); \ errno = old_errno; \ } while (0) #define SET_GLUSTER_CREDS_MY_FD(glfs_export, my_fd) \ do { \ int old_errno = errno; \ \ ((void)setglustercreds( \ glfs_export, &(my_fd)->creds.caller_uid, \ &(my_fd)->creds.caller_gid, \ (my_fd)->creds.caller_glen, \ (my_fd)->creds.caller_garray, NULL, 0, \ (char *)__FILE__, __LINE__, (char *)__func__)); \ errno = old_errno; \ } while (0) #define SET_GLUSTER_LEASE_ID(my_fd) \ do { \ /* NOTHING TO DO */ \ } while (0) #endif #define RESET_GLUSTER_CREDS(glfs_export) \ do { \ int old_errno = errno; \ ((void)setglustercreds(glfs_export, NULL, NULL, 0, NULL, NULL, \ 0, (char *)__FILE__, __LINE__, \ (char *)__func__)); \ errno = old_errno; \ } while (0) #ifdef GLTIMING struct latency_data glfsal_latencies[LATENCY_SLOTS]; void latency_update(struct timespec *s_time, struct timespec *e_time, int opnum); void latency_dump(void); #endif void handle_ops_init(struct fsal_obj_ops *ops); fsal_status_t gluster2fsal_error(const int gluster_errorcode); void stat2fsal_attributes(const struct stat *buffstat, struct fsal_attrlist *fsalattr); void construct_handle(struct glusterfs_export *glexport, const struct stat *st, struct glfs_object *glhandle, unsigned char *globjhdl, struct glusterfs_handle **obj, const char *vol_uuid); fsal_status_t glfs2fsal_handle(struct glusterfs_export *glfs_export, struct glfs_object *glhandle, struct fsal_obj_handle **pub_handle, struct stat *sb, struct fsal_attrlist *attrs_out); fsal_status_t glusterfs_create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops); void gluster_cleanup_vars(struct glfs_object *glhandle); fsal_status_t glusterfs_get_acl(struct glusterfs_export *glfs_export, struct glfs_object *objhandle, glusterfs_fsal_xstat_t *buffxstat, struct fsal_attrlist *fsalattr); fsal_status_t glusterfs_set_acl(struct glusterfs_export *glfs_export, struct glusterfs_handle *objhandle, glusterfs_fsal_xstat_t *buffxstat); fsal_status_t glusterfs_process_acl(struct glfs *fs, struct glfs_object *object, struct fsal_attrlist *attrs, glusterfs_fsal_xstat_t *buffxstat); void glusterfs_free_fs(struct glusterfs_fs *gl_fs); /* * Following have been introduced for pNFS support */ /* Need to call this to initialize export_ops for pnfs */ void export_ops_pnfs(struct export_ops *ops); /* Need to call this to initialize obj_ops for pnfs */ void handle_ops_pnfs(struct fsal_obj_ops *ops); /* Need to call this to initialize ops for pnfs */ void fsal_ops_pnfs(struct fsal_ops *ops); void dsh_ops_init(struct fsal_dsh_ops *ops); void pnfs_ds_ops_init(struct fsal_pnfs_ds_ops *ops); nfsstat4 getdeviceinfo(struct fsal_module *fsal_hdl, XDR *da_addr_body, const layouttype4 type, const struct pnfs_deviceid *deviceid); /* UP thread routines */ void *GLUSTERFSAL_UP_Thread(void *Arg); int initiate_up_thread(struct glusterfs_fs *gl_fs); int up_process_event_object(struct glusterfs_fs *gl_fs, struct glfs_object *object, enum glfs_upcall_reason reason); void gluster_process_upcall(struct glfs_upcall *cbk, void *data); fsal_status_t glusterfs_close_my_fd(struct glusterfs_fd *my_fd); #endif /* GLUSTER_INTERNAL */ nfs-ganesha-6.5/src/FSAL/FSAL_GLUSTER/handle.c000066400000000000000000002677711473756622300204660ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Red Hat Inc., 2013 * Author: Anand Subramanian anands@redhat.com * * This program 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 3 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 * 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 * * ------------- */ #include "config.h" #ifdef LINUX #include /* for makedev(3) */ #endif #include #include "fsal.h" #include "gluster_internal.h" #include "FSAL/fsal_commonlib.h" #include "fsal_convert.h" #include "pnfs_utils.h" #include "nfs_exports.h" #include "sal_data.h" #include "sal_functions.h" #include "fsal_types.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/fsal_gl.h" #endif /* LTTNG_PARSING */ /* fsal_obj_handle common methods */ /** * @brief Implements GLUSTER FSAL objectoperation handle_release * * Free up the GLUSTER handle and associated data if any * Typically free up any members of the struct glusterfs_handle */ static void handle_release(struct fsal_obj_handle *obj_hdl) { int rc = 0; struct glusterfs_handle *objhandle = container_of(obj_hdl, struct glusterfs_handle, handle); struct glusterfs_fd *my_fd = &objhandle->globalfd; #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif fsal_obj_handle_fini(&objhandle->handle, true); if (obj_hdl->type == REGULAR_FILE) { fsal_status_t st; st = close_fsal_fd(obj_hdl, &my_fd->fsal_fd, false); if (FSAL_IS_ERROR(st)) { LogCrit(COMPONENT_FSAL, "Could not close hdl 0x%p, status %s error %s(%d)", obj_hdl, fsal_err_txt(st), strerror(st.minor), st.minor); } } if (my_fd->creds.caller_garray) { gsh_free(my_fd->creds.caller_garray); my_fd->creds.caller_garray = NULL; } if (objhandle->glhandle) { rc = glfs_h_close(objhandle->glhandle); if (rc) { LogCrit(COMPONENT_FSAL, "glfs_h_close returned error %s(%d)", strerror(errno), errno); } objhandle->glhandle = NULL; } if (objhandle->handle.type == REGULAR_FILE) destroy_fsal_fd(&my_fd->fsal_fd); gsh_free(objhandle); #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_handle_release); #endif } /** * @brief Implements GLUSTER FSAL objectoperation lookup */ static fsal_status_t lookup(struct fsal_obj_handle *parent, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { int rc = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct stat sb; struct glfs_object *glhandle = NULL; unsigned char globjhdl[GFAPI_HANDLE_LENGTH] = { '\0' }; char vol_uuid[GLAPI_UUID_LENGTH] = { '\0' }; struct glusterfs_handle *objhandle = NULL; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *parenthandle = container_of(parent, struct glusterfs_handle, handle); glusterfs_fsal_xstat_t buffxstat = { .e_acl = NULL, .i_acl = NULL }; #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif /* set proper credentials */ SET_GLUSTER_CREDS_OP_CTX(glfs_export); glhandle = glfs_h_lookupat(glfs_export->gl_fs->fs, parenthandle->glhandle, path, &sb, 0); RESET_GLUSTER_CREDS(glfs_export); if (glhandle == NULL) { status = gluster2fsal_error(errno); LogFullDebug(COMPONENT_FSAL, "glfs_h_lookupat %s returned %s", path, msg_fsal_err(status.major)); goto out; } rc = glfs_h_extract_handle(glhandle, globjhdl, GFAPI_HANDLE_LENGTH); if (rc < 0) { status = gluster2fsal_error(errno); LogFullDebug(COMPONENT_FSAL, "glfs_h_extract_handle %s returned %s", path, msg_fsal_err(status.major)); goto out; } rc = glfs_get_volumeid(glfs_export->gl_fs->fs, vol_uuid, GLAPI_UUID_LENGTH); if (rc < 0) { status = gluster2fsal_error(errno); LogFullDebug(COMPONENT_FSAL, "glfs_get_volumeid %s returned %s", path, msg_fsal_err(status.major)); goto out; } construct_handle(glfs_export, &sb, glhandle, globjhdl, &objhandle, vol_uuid); if (attrs_out != NULL) { posix2fsal_attributes_all(&sb, attrs_out); if (attrs_out->request_mask & ATTR_ACL) { /* Fetch the ACL */ status = glusterfs_get_acl(glfs_export, glhandle, &buffxstat, attrs_out); if (status.major == ERR_FSAL_NOENT) { if (attrs_out->type == SYMBOLIC_LINK) status = fsalstat(ERR_FSAL_NO_ERROR, 0); else status = gluster2fsal_error(ESTALE); } if (!FSAL_IS_ERROR(status)) { /* Success, so mark ACL as valid. */ attrs_out->valid_mask |= ATTR_ACL; } else { if (attrs_out->request_mask & ATTR_RDATTR_ERR) attrs_out->valid_mask = ATTR_RDATTR_ERR; fsal_release_attrs(attrs_out); LogFullDebug(COMPONENT_FSAL, "glusterfs_get_acl %s returned %s", path, msg_fsal_err(status.major)); goto out; } } } *handle = &objhandle->handle; out: if (status.major != ERR_FSAL_NO_ERROR) gluster_cleanup_vars(glhandle); #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_lookup); #endif glusterfs_fsal_clean_xstat(&buffxstat); return status; } static int glusterfs_fsal_get_sec_label(struct glusterfs_handle *glhandle, struct fsal_attrlist *attrs) { int rc = 0; struct glusterfs_export *export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); if (FSAL_TEST_MASK(attrs->request_mask, ATTR4_SEC_LABEL) && op_ctx_export_has_option(EXPORT_OPTION_SECLABEL_SET)) { char label[NFS4_OPAQUE_LIMIT]; rc = glfs_h_getxattrs(export->gl_fs->fs, glhandle->glhandle, export->sec_label_xattr, label, NFS4_OPAQUE_LIMIT); if (rc < 0) { /* If there's no label then just do zero-length one */ if (errno != ENODATA) { rc = -errno; goto out_err; } rc = 0; } attrs->sec_label.slai_data.slai_data_len = rc; gsh_free(attrs->sec_label.slai_data.slai_data_val); if (rc > 0) { attrs->sec_label.slai_data.slai_data_val = gsh_memdup(label, rc); FSAL_SET_MASK(attrs->valid_mask, ATTR4_SEC_LABEL); } else { attrs->sec_label.slai_data.slai_data_val = NULL; FSAL_UNSET_MASK(attrs->valid_mask, ATTR4_SEC_LABEL); } } out_err: return rc; } /** * @brief Implements GLUSTER FSAL objectoperation readdir */ static fsal_status_t read_dirents(struct fsal_obj_handle *dir_hdl, fsal_cookie_t *whence, void *dir_state, fsal_readdir_cb cb, attrmask_t attrmask, bool *eof) { int rc = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct glfs_fd *glfd = NULL; long offset = 0; struct dirent *pde = NULL; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *objhandle = container_of(dir_hdl, struct glusterfs_handle, handle); #ifdef USE_GLUSTER_XREADDIRPLUS struct glfs_object *glhandle = NULL; struct glfs_xreaddirp_stat *xstat = NULL; uint32_t flags = (GFAPI_XREADDIRP_STAT | GFAPI_XREADDIRP_HANDLE); struct glfs_object *tmp = NULL; struct stat *sb; glusterfs_fsal_xstat_t buffxstat = { .e_acl = NULL, .i_acl = NULL }; #endif #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif SET_GLUSTER_CREDS_OP_CTX(glfs_export); /** @todo : Can we use globalfd instead */ glfd = glfs_h_opendir(glfs_export->gl_fs->fs, objhandle->glhandle); RESET_GLUSTER_CREDS(glfs_export); if (glfd == NULL) return gluster2fsal_error(errno); if (whence != NULL) offset = *whence; glfs_seekdir(glfd, offset); while (!(*eof)) { struct dirent de; struct fsal_obj_handle *obj; SET_GLUSTER_CREDS_OP_CTX(glfs_export); #ifndef USE_GLUSTER_XREADDIRPLUS rc = glfs_readdir_r(glfd, &de, &pde); #else rc = glfs_xreaddirplus_r(glfd, flags, &xstat, &de, &pde); #endif RESET_GLUSTER_CREDS(glfs_export); if (rc < 0) { status = gluster2fsal_error(errno); goto out; } if (rc == 0 && pde == NULL) { *eof = true; goto out; } struct fsal_attrlist attrs; enum fsal_dir_result cb_rc; /* skip . and .. */ if ((strcmp(de.d_name, ".") == 0) || (strcmp(de.d_name, "..") == 0)) { #ifdef USE_GLUSTER_XREADDIRPLUS if (xstat) { glfs_free(xstat); xstat = NULL; } #endif continue; } fsal_prepare_attrs(&attrs, attrmask); #ifndef USE_GLUSTER_XREADDIRPLUS status = lookup(dir_hdl, de.d_name, &obj, &attrs); if (FSAL_IS_ERROR(status)) goto out; #else if (!xstat || !(rc & GFAPI_XREADDIRP_HANDLE)) { status = gluster2fsal_error(errno); goto out; } sb = glfs_xreaddirplus_get_stat(xstat); tmp = glfs_xreaddirplus_get_object(xstat); if (!sb || !tmp) { status = gluster2fsal_error(errno); goto out; } glhandle = glfs_object_copy(tmp); if (!glhandle) { status = gluster2fsal_error(errno); goto out; } status = glfs2fsal_handle(glfs_export, glhandle, &obj, sb, &attrs); glfs_free(xstat); xstat = NULL; if (FSAL_IS_ERROR(status)) { gluster_cleanup_vars(glhandle); goto out; } if (attrs.request_mask & ATTR_ACL) { /* Fetch the ACL */ status = glusterfs_get_acl(glfs_export, glhandle, &buffxstat, &attrs); if (status.major == ERR_FSAL_NOENT) { if (attrs.type == SYMBOLIC_LINK) status = fsalstat(ERR_FSAL_NO_ERROR, 0); else status = gluster2fsal_error(ESTALE); } if (!FSAL_IS_ERROR(status)) { /* Success, so mark ACL as valid. */ attrs.valid_mask |= ATTR_ACL; } else { if (attrs.request_mask & ATTR_RDATTR_ERR) attrs.valid_mask = ATTR_RDATTR_ERR; fsal_release_attrs(&attrs); glusterfs_fsal_clean_xstat(&buffxstat); goto out; } } #endif rc = glusterfs_fsal_get_sec_label(objhandle, &attrs); if (rc < 0) { status = gluster2fsal_error(errno); goto out; } cb_rc = cb(de.d_name, obj, &attrs, dir_state, glfs_telldir(glfd)); fsal_release_attrs(&attrs); glusterfs_fsal_clean_xstat(&buffxstat); /* Read ahead not supported by this FSAL. */ if (cb_rc >= DIR_READAHEAD) goto out; } out: #ifdef USE_GLUSTER_XREADDIRPLUS if (xstat) glfs_free(xstat); #endif SET_GLUSTER_CREDS_OP_CTX(glfs_export); rc = glfs_closedir(glfd); RESET_GLUSTER_CREDS(glfs_export); if (rc < 0) status = gluster2fsal_error(errno); #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_read_dirents); #endif return status; } /** * @brief Implements GLUSTER FSAL objectoperation mkdir */ static fsal_status_t makedir(struct fsal_obj_handle *dir_hdl, const char *name, struct fsal_attrlist *attrib, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { int rc = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct stat sb; struct glfs_object *glhandle = NULL; unsigned char globjhdl[GFAPI_HANDLE_LENGTH] = { '\0' }; char vol_uuid[GLAPI_UUID_LENGTH] = { '\0' }; struct glusterfs_handle *objhandle = NULL; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *parenthandle = container_of(dir_hdl, struct glusterfs_handle, handle); #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif SET_GLUSTER_CREDS_OP_CTX(glfs_export); glhandle = glfs_h_mkdir(glfs_export->gl_fs->fs, parenthandle->glhandle, name, fsal2unix_mode(attrib->mode), &sb); RESET_GLUSTER_CREDS(glfs_export); if (glhandle == NULL) { status = gluster2fsal_error(errno); goto out; } rc = glfs_h_extract_handle(glhandle, globjhdl, GFAPI_HANDLE_LENGTH); if (rc < 0) { status = gluster2fsal_error(errno); goto out; } rc = glfs_get_volumeid(glfs_export->gl_fs->fs, vol_uuid, GLAPI_UUID_LENGTH); if (rc < 0) { status = gluster2fsal_error(errno); goto out; } construct_handle(glfs_export, &sb, glhandle, globjhdl, &objhandle, vol_uuid); if (attrs_out != NULL) { posix2fsal_attributes_all(&sb, attrs_out); } *handle = &objhandle->handle; /* We handled the mode above. */ FSAL_UNSET_MASK(attrib->valid_mask, ATTR_MODE); if (attrib->valid_mask) { /* Now per support_ex API, if there are any other attributes * set, go ahead and get them set now. */ status = (*handle)->obj_ops->setattr2(*handle, false, NULL, attrib); if (FSAL_IS_ERROR(status)) { /* Release the handle we just allocated. */ LogFullDebug(COMPONENT_FSAL, "setattr2 status=%s", fsal_err_txt(status)); (*handle)->obj_ops->release(*handle); /* We released handle at this point */ glhandle = NULL; *handle = NULL; } } else { status.major = ERR_FSAL_NO_ERROR; status.minor = 0; } FSAL_SET_MASK(attrib->valid_mask, ATTR_MODE); out: if (status.major != ERR_FSAL_NO_ERROR) gluster_cleanup_vars(glhandle); #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_makedir); #endif return status; } /** * @brief Implements GLUSTER FSAL objectoperation mknode */ static fsal_status_t makenode(struct fsal_obj_handle *dir_hdl, const char *name, object_file_type_t nodetype, struct fsal_attrlist *attrib, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { int rc = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct stat sb; struct glfs_object *glhandle = NULL; unsigned char globjhdl[GFAPI_HANDLE_LENGTH] = { '\0' }; char vol_uuid[GLAPI_UUID_LENGTH] = { '\0' }; struct glusterfs_handle *objhandle = NULL; dev_t ndev = { 0, }; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *parenthandle = container_of(dir_hdl, struct glusterfs_handle, handle); mode_t create_mode; #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif switch (nodetype) { case BLOCK_FILE: /* FIXME: This needs a feature flag test? */ ndev = makedev(attrib->rawdev.major, attrib->rawdev.minor); create_mode = S_IFBLK; break; case CHARACTER_FILE: ndev = makedev(attrib->rawdev.major, attrib->rawdev.minor); create_mode = S_IFCHR; break; case FIFO_FILE: create_mode = S_IFIFO; break; case SOCKET_FILE: create_mode = S_IFSOCK; break; default: LogMajor(COMPONENT_FSAL, "Invalid node type in FSAL_mknode: %d", nodetype); return fsalstat(ERR_FSAL_INVAL, 0); } SET_GLUSTER_CREDS_OP_CTX(glfs_export); glhandle = glfs_h_mknod(glfs_export->gl_fs->fs, parenthandle->glhandle, name, create_mode | fsal2unix_mode(attrib->mode), ndev, &sb); RESET_GLUSTER_CREDS(glfs_export); if (glhandle == NULL) { status = gluster2fsal_error(errno); goto out; } rc = glfs_h_extract_handle(glhandle, globjhdl, GFAPI_HANDLE_LENGTH); if (rc < 0) { status = gluster2fsal_error(errno); goto out; } rc = glfs_get_volumeid(glfs_export->gl_fs->fs, vol_uuid, GLAPI_UUID_LENGTH); if (rc < 0) { status = gluster2fsal_error(errno); goto out; } construct_handle(glfs_export, &sb, glhandle, globjhdl, &objhandle, vol_uuid); if (attrs_out != NULL) { posix2fsal_attributes_all(&sb, attrs_out); } *handle = &objhandle->handle; /* We handled the mode above. */ FSAL_UNSET_MASK(attrib->valid_mask, ATTR_MODE); if (attrib->valid_mask) { /* Now per support_ex API, if there are any other attributes * set, go ahead and get them set now. */ status = (*handle)->obj_ops->setattr2(*handle, false, NULL, attrib); if (FSAL_IS_ERROR(status)) { /* Release the handle we just allocated. */ LogFullDebug(COMPONENT_FSAL, "setattr2 status=%s", fsal_err_txt(status)); (*handle)->obj_ops->release(*handle); /* We released handle at this point */ glhandle = NULL; *handle = NULL; } } else { status.major = ERR_FSAL_NO_ERROR; status.minor = 0; } FSAL_SET_MASK(attrib->valid_mask, ATTR_MODE); out: if (status.major != ERR_FSAL_NO_ERROR) gluster_cleanup_vars(glhandle); #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_makenode); #endif return status; } /** * @brief Implements GLUSTER FSAL objectoperation symlink */ static fsal_status_t makesymlink(struct fsal_obj_handle *dir_hdl, const char *name, const char *link_path, struct fsal_attrlist *attrib, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { int rc = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct stat sb; struct glfs_object *glhandle = NULL; unsigned char globjhdl[GFAPI_HANDLE_LENGTH] = { '\0' }; char vol_uuid[GLAPI_UUID_LENGTH] = { '\0' }; struct glusterfs_handle *objhandle = NULL; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *parenthandle = container_of(dir_hdl, struct glusterfs_handle, handle); #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif SET_GLUSTER_CREDS_OP_CTX(glfs_export); glhandle = glfs_h_symlink(glfs_export->gl_fs->fs, parenthandle->glhandle, name, link_path, &sb); RESET_GLUSTER_CREDS(glfs_export); if (glhandle == NULL) { status = gluster2fsal_error(errno); goto out; } rc = glfs_h_extract_handle(glhandle, globjhdl, GFAPI_HANDLE_LENGTH); if (rc < 0) { status = gluster2fsal_error(errno); goto out; } rc = glfs_get_volumeid(glfs_export->gl_fs->fs, vol_uuid, GLAPI_UUID_LENGTH); if (rc < 0) { status = gluster2fsal_error(errno); goto out; } construct_handle(glfs_export, &sb, glhandle, globjhdl, &objhandle, vol_uuid); if (attrs_out != NULL) { posix2fsal_attributes_all(&sb, attrs_out); } *handle = &objhandle->handle; if (attrib->valid_mask) { /* Now per support_ex API, if there are any other attributes * set, go ahead and get them set now. */ status = (*handle)->obj_ops->setattr2(*handle, false, NULL, attrib); if (FSAL_IS_ERROR(status)) { /* Release the handle we just allocated. */ LogFullDebug(COMPONENT_FSAL, "setattr2 status=%s", fsal_err_txt(status)); (*handle)->obj_ops->release(*handle); /* We released handle at this point */ glhandle = NULL; *handle = NULL; } } else { status.major = ERR_FSAL_NO_ERROR; status.minor = 0; } out: if (status.major != ERR_FSAL_NO_ERROR) gluster_cleanup_vars(glhandle); #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_makesymlink); #endif return status; } /** * @brief Implements GLUSTER FSAL objectoperation readlink */ static fsal_status_t readsymlink(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *link_content, bool refresh) { int rc = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *objhandle = container_of(obj_hdl, struct glusterfs_handle, handle); #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif link_content->len = MAXPATHLEN; /* Max link path */ link_content->addr = gsh_malloc(link_content->len); SET_GLUSTER_CREDS_OP_CTX(glfs_export); rc = glfs_h_readlink(glfs_export->gl_fs->fs, objhandle->glhandle, link_content->addr, link_content->len); RESET_GLUSTER_CREDS(glfs_export); if (rc < 0) { status = gluster2fsal_error(errno); goto out; } if (rc >= MAXPATHLEN) { status = gluster2fsal_error(EINVAL); goto out; } /* rc is the number of bytes copied into link_content->addr * without including '\0' character. */ *(char *)(link_content->addr + rc) = '\0'; link_content->len = rc + 1; out: if (status.major != ERR_FSAL_NO_ERROR) { gsh_free(link_content->addr); link_content->addr = NULL; link_content->len = 0; } #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_readsymlink); #endif return status; } /** * @brief Implements GLUSTER FSAL objectoperation getattrs */ static fsal_status_t getattrs(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs) { int rc = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; glusterfs_fsal_xstat_t buffxstat = { 0 }; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *objhandle = container_of(obj_hdl, struct glusterfs_handle, handle); #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif /* * There is a kind of race here when the glfd part of the * FSAL GLUSTER object handle is destroyed during a close * coming in from another NFSv3 WRITE thread which does * fsal_open(). Since the context/fd is destroyed * we cannot depend on glfs_fstat assuming glfd is valid. * Fixing the issue by removing the glfs_fstat call here. * So default to glfs_h_stat and re-optimize if a better * way is found - that may involve introducing locks in * the gfapi's for close and getattrs etc. */ /** @todo: With support_ex() above may no longer be valid. * This needs to be revisited */ /* @todo: with POSIX ACLs every user shall have permissions to * read stat & ACLs. But that may not be the case with RichACLs. * If the ganesha service is started by non-root user, that user * may get restricted from reading ACL. */ rc = glfs_h_stat(glfs_export->gl_fs->fs, objhandle->glhandle, &buffxstat.buffstat); if (rc != 0) { if (errno == ENOENT) status = gluster2fsal_error(ESTALE); else status = gluster2fsal_error(errno); if (attrs->request_mask & ATTR_RDATTR_ERR) { /* Caller asked for error to be visible. */ attrs->valid_mask = ATTR_RDATTR_ERR; } goto out; } stat2fsal_attributes(&buffxstat.buffstat, attrs); if (obj_hdl->type == DIRECTORY) buffxstat.is_dir = true; else buffxstat.is_dir = false; if (attrs->request_mask & ATTR_ACL) { /* Fetch the ACL */ status = glusterfs_get_acl(glfs_export, objhandle->glhandle, &buffxstat, attrs); if (!FSAL_IS_ERROR(status)) { /* Success, so mark ACL as valid. */ attrs->valid_mask |= ATTR_ACL; } } rc = glusterfs_fsal_get_sec_label(objhandle, attrs); if (rc < 0) { if (errno == ENOENT) status = gluster2fsal_error(ESTALE); else status = gluster2fsal_error(errno); if (attrs->request_mask & ATTR_RDATTR_ERR) { /* Caller asked for error to be visible. */ attrs->valid_mask = ATTR_RDATTR_ERR; } goto out; } /* * * The error ENOENT is not an expected error for GETATTRS * Due to this, operations such as RENAME will fail when * it calls GETATTRS on removed file. But for dead links * we should not return error * */ if (status.major == ERR_FSAL_NOENT) { if (obj_hdl->type == SYMBOLIC_LINK) status = fsalstat(ERR_FSAL_NO_ERROR, 0); else status = gluster2fsal_error(ESTALE); } if (FSAL_IS_ERROR(status)) { if (attrs->request_mask & ATTR_RDATTR_ERR) { /* Caller asked for error to be visible. */ attrs->valid_mask = ATTR_RDATTR_ERR; } } out: #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_getattrs); #endif glusterfs_fsal_clean_xstat(&buffxstat); return status; } /** * @brief Implements GLUSTER FSAL objectoperation link */ static fsal_status_t linkfile(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *destdir_hdl, const char *name, struct fsal_attrlist *destdir_pre_attrs_out, struct fsal_attrlist *destdir_post_attrs_out) { int rc = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *objhandle = container_of(obj_hdl, struct glusterfs_handle, handle); struct glusterfs_handle *dstparenthandle = container_of(destdir_hdl, struct glusterfs_handle, handle); #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif SET_GLUSTER_CREDS_OP_CTX(glfs_export); rc = glfs_h_link(glfs_export->gl_fs->fs, objhandle->glhandle, dstparenthandle->glhandle, name); RESET_GLUSTER_CREDS(glfs_export); if (rc != 0) { status = gluster2fsal_error(errno); goto out; } out: #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_linkfile); #endif return status; } /** * @brief Implements GLUSTER FSAL objectoperation rename */ static fsal_status_t renamefile(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *olddir_hdl, const char *old_name, struct fsal_obj_handle *newdir_hdl, const char *new_name, struct fsal_attrlist *olddir_pre_attrs_out, struct fsal_attrlist *olddir_post_attrs_out, struct fsal_attrlist *newdir_pre_attrs_out, struct fsal_attrlist *newdir_post_attrs_out) { int rc = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *srcparenthandle = container_of(olddir_hdl, struct glusterfs_handle, handle); struct glusterfs_handle *dstparenthandle = container_of(newdir_hdl, struct glusterfs_handle, handle); #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif SET_GLUSTER_CREDS_OP_CTX(glfs_export); rc = glfs_h_rename(glfs_export->gl_fs->fs, srcparenthandle->glhandle, old_name, dstparenthandle->glhandle, new_name); RESET_GLUSTER_CREDS(glfs_export); if (rc != 0) { status = gluster2fsal_error(errno); goto out; } out: #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_renamefile); #endif return status; } /** * @brief Implements GLUSTER FSAL objectoperation unlink */ static fsal_status_t file_unlink(struct fsal_obj_handle *dir_hdl, struct fsal_obj_handle *obj_hdl, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { int rc = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *parenthandle = container_of(dir_hdl, struct glusterfs_handle, handle); #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif SET_GLUSTER_CREDS_OP_CTX(glfs_export); rc = glfs_h_unlink(glfs_export->gl_fs->fs, parenthandle->glhandle, name); RESET_GLUSTER_CREDS(glfs_export); if (rc != 0) status = gluster2fsal_error(errno); #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_file_unlink); #endif return status; } /** * @brief Implements GLUSTER FSAL objectoperation share_op */ /* static fsal_status_t share_op(struct fsal_obj_handle *obj_hdl, void *p_owner, fsal_share_param_t request_share) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } */ /* * @brief: Copy glusterfs_fd structure * * Note: If is_dup is set to true, a new glusterfs_fd is created * with extra ref and hence need to be closed separately. * Whereas if false, both src_fd and dst_fd contain single reference * to glfd. Hence closing one of the them shall destroy other fd too. */ void glusterfs_copy_my_fd(struct glusterfs_fd *src_fd, struct glusterfs_fd *dst_fd, bool is_dup) { assert(src_fd != NULL && dst_fd != NULL); if (is_dup) { dst_fd->glfd = glfs_dup(src_fd->glfd); if (src_fd->creds.caller_glen) dst_fd->creds.caller_garray = gsh_memdup( src_fd->creds.caller_garray, src_fd->creds.caller_glen * sizeof(gid_t)); /* Need to LRU track global fd including incrementing * fsal_fd_global_counter. */ insert_fd_lru(&dst_fd->fsal_fd); } else { dst_fd->glfd = src_fd->glfd; dst_fd->creds.caller_garray = src_fd->creds.caller_garray; } dst_fd->fsal_fd.openflags = src_fd->fsal_fd.openflags; dst_fd->creds.caller_uid = src_fd->creds.caller_uid; dst_fd->creds.caller_gid = src_fd->creds.caller_gid; dst_fd->creds.caller_glen = src_fd->creds.caller_glen; #ifdef USE_GLUSTER_DELEGATION memcpy(dst_fd->lease_id, src_fd->lease_id, GLAPI_LEASE_ID_SIZE); #endif } struct glfs_object * glusterfs_create_my_fd(struct glusterfs_handle *parenthandle, const char *name, fsal_openflags_t openflags, int posix_flags, mode_t unix_mode, struct stat *sb, struct glusterfs_fd *my_fd) { struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); gid_t **garray_copy = NULL; struct glfs_object *glhandle = NULL; #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif if (!parenthandle || !name || !sb || !my_fd) { errno = EINVAL; return NULL; } LogFullDebug(COMPONENT_FSAL, "my_fd->fd = %p openflags = %x, posix_flags = %x", my_fd->glfd, openflags, posix_flags); assert(my_fd->glfd == NULL && my_fd->fsal_fd.openflags == FSAL_O_CLOSED && openflags != 0); LogFullDebug(COMPONENT_FSAL, "openflags = %x, posix_flags = %x", openflags, posix_flags); SET_GLUSTER_CREDS_OP_CTX(glfs_export); glhandle = glfs_h_creat_open(glfs_export->gl_fs->fs, parenthandle->glhandle, name, posix_flags, unix_mode, sb, &my_fd->glfd); /* restore credentials */ RESET_GLUSTER_CREDS(glfs_export); if (!glhandle || my_fd->glfd == NULL) { goto out; } my_fd->fsal_fd.openflags = FSAL_O_NFS_FLAGS(openflags); my_fd->creds.caller_uid = op_ctx->creds.caller_uid; my_fd->creds.caller_gid = op_ctx->creds.caller_gid; my_fd->creds.caller_glen = op_ctx->creds.caller_glen; garray_copy = &my_fd->creds.caller_garray; if ((*garray_copy) != NULL) { /* Replace old creds */ gsh_free(*garray_copy); *garray_copy = NULL; } if (op_ctx->creds.caller_glen) { (*garray_copy) = gsh_malloc(op_ctx->creds.caller_glen * sizeof(gid_t)); memcpy((*garray_copy), op_ctx->creds.caller_garray, op_ctx->creds.caller_glen * sizeof(gid_t)); } SET_GLUSTER_LEASE_ID(my_fd); out: #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_file_open); #endif return glhandle; } fsal_status_t glusterfs_close_my_fd(struct glusterfs_fd *my_fd) { int rc = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif if (my_fd->glfd && my_fd->fsal_fd.openflags != FSAL_O_CLOSED) { /* During shutdown, the op_ctx is NULL, * Since handle gets released as part of internal * operation, we may not need to set credentials */ if (op_ctx && op_ctx->fsal_export) { /* Use the same credentials which opened up the fd */ SET_GLUSTER_CREDS_MY_FD(glfs_export, my_fd); } GSH_UNIQUE_AUTO_TRACEPOINT(fsal_gl, close_fd, TRACE_DEBUG, "Close fd: {}", my_fd->glfd); rc = glfs_close(my_fd->glfd); /* During shutdown, the op_ctx is NULL */ if (op_ctx && op_ctx->fsal_export) { /* restore credentials */ RESET_GLUSTER_CREDS(glfs_export); } if (rc != 0) { status = gluster2fsal_error(errno); LogCrit(COMPONENT_FSAL, "glfs_close returned %s (%d)", strerror(errno), errno); } my_fd->glfd = NULL; my_fd->fsal_fd.openflags = FSAL_O_CLOSED; my_fd->creds.caller_uid = 0; my_fd->creds.caller_gid = 0; my_fd->creds.caller_glen = 0; if (my_fd->creds.caller_garray) { gsh_free(my_fd->creds.caller_garray); my_fd->creds.caller_garray = NULL; } } else { status = fsalstat(ERR_FSAL_NOT_OPENED, 0); } #ifdef USE_GLUSTER_DELEGATION memset(my_fd->lease_id, 0, GLAPI_LEASE_ID_SIZE); #endif #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_file_close); #endif return status; } /** * @brief Implements GLUSTER FSAL objectoperation close @todo: close2() could be used to close globalfd as well. */ static fsal_status_t file_close(struct fsal_obj_handle *obj_hdl) { fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct glusterfs_handle *objhandle = container_of(obj_hdl, struct glusterfs_handle, handle); #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif assert(obj_hdl->type == REGULAR_FILE); status = close_fsal_fd(obj_hdl, &objhandle->globalfd.fsal_fd, false); #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_file_close); #endif return status; } /** * @brief Gluster function to reopen a fsal_fd. * * @param[in] obj_hdl File on which to operate * @param[in] openflags Mode for open * @param[out] fd File descriptor that is to be used * * @return FSAL status. */ fsal_status_t glusterfs_reopen_func(struct fsal_obj_handle *obj_hdl, fsal_openflags_t openflags, struct fsal_fd *fsal_fd) { struct glusterfs_handle *myself; struct glusterfs_fd *my_fd; struct glfs_fd *glfd; int posix_flags = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif myself = container_of(obj_hdl, struct glusterfs_handle, handle); my_fd = container_of(fsal_fd, struct glusterfs_fd, fsal_fd); fsal2posix_openflags(openflags, &posix_flags); LogFullDebug(COMPONENT_FSAL, "my_fd->fd = %p openflags = %x, posix_flags = %x", my_fd->glfd, openflags, posix_flags); SET_GLUSTER_CREDS_OP_CTX(glfs_export); /* Open a new fd without closing the old one yet. */ glfd = glfs_h_open(glfs_export->gl_fs->fs, myself->glhandle, posix_flags); /* restore credentials */ RESET_GLUSTER_CREDS(glfs_export); if (glfd == NULL) { status = gluster2fsal_error(errno); goto out; } if (my_fd->glfd != NULL && fsal_fd->openflags != FSAL_O_CLOSED) { /* We succeded in opening new fd, close the old one. */ int rc; /* Use the same credentials which opened up the fd */ SET_GLUSTER_CREDS_MY_FD(glfs_export, my_fd); rc = glfs_close(my_fd->glfd); /* restore credentials */ RESET_GLUSTER_CREDS(glfs_export); if (rc != 0) { status = gluster2fsal_error(errno); LogCrit(COMPONENT_FSAL, "Error : close returns with %s", strerror(errno)); /** @todo - what to do about error here... */ } /* Free the old creds */ gsh_free(my_fd->creds.caller_garray); my_fd->creds.caller_garray = NULL; } assert(my_fd->creds.caller_garray == NULL); /* Copy the new fd into the glusterfs_fd */ my_fd->glfd = glfd; fsal_fd->openflags = FSAL_O_NFS_FLAGS(openflags); my_fd->creds.caller_uid = op_ctx->creds.caller_uid; my_fd->creds.caller_gid = op_ctx->creds.caller_gid; my_fd->creds.caller_glen = op_ctx->creds.caller_glen; if (op_ctx->creds.caller_glen) { my_fd->creds.caller_garray = gsh_malloc(op_ctx->creds.caller_glen * sizeof(gid_t)); memcpy(my_fd->creds.caller_garray, op_ctx->creds.caller_garray, op_ctx->creds.caller_glen * sizeof(gid_t)); } SET_GLUSTER_LEASE_ID(my_fd); GSH_UNIQUE_AUTO_TRACEPOINT(fsal_gl, close_fd, TRACE_DEBUG, "Open fd: {}, posix_flags: {}", myself->globalfd.glfd, posix_flags); out: #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_file_open); #endif return status; } /** * @brief Function to close an fsal_obj_handle's global file descriptor. * * @param[in] obj_hdl File on which to operate * @param[in] fd File handle to close * * @return FSAL status. */ fsal_status_t glusterfs_close_func(struct fsal_obj_handle *obj_hdl, struct fsal_fd *fsal_fd) { return glusterfs_close_my_fd( container_of(fsal_fd, struct glusterfs_fd, fsal_fd)); } /** * @brief Merge a duplicate handle with an original handle * * This function is used if an upper layer detects that a duplicate * object handle has been created. It allows the FSAL to merge anything * from the duplicate back into the original. * * The caller must release the object (the caller may have to close * files if the merge is unsuccessful). * * @param[in] orig_hdl Original handle * @param[in] dupe_hdl Handle to merge into original * * @return FSAL status. * */ fsal_status_t glusterfs_merge(struct fsal_obj_handle *orig_hdl, struct fsal_obj_handle *dupe_hdl) { fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; if (orig_hdl->type == REGULAR_FILE && dupe_hdl->type == REGULAR_FILE) { /* We need to merge the share reservations on this file. * This could result in ERR_FSAL_SHARE_DENIED. */ struct glusterfs_handle *orig, *dupe; orig = container_of(orig_hdl, struct glusterfs_handle, handle); dupe = container_of(dupe_hdl, struct glusterfs_handle, handle); /* This can block over an I/O operation. */ status = merge_share(orig_hdl, &orig->share, &dupe->share); } return status; } static fsal_status_t glusterfs_open2_by_handle(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, fsal_verifier_t verifier, struct fsal_attrlist *attrs_out) { struct glusterfs_fd *my_fd = NULL; struct fsal_fd *fsal_fd; struct glusterfs_handle *myself; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; fsal_openflags_t old_openflags; bool truncated = openflags & FSAL_O_TRUNC; myself = container_of(obj_hdl, struct glusterfs_handle, handle); if (state != NULL) my_fd = &container_of(state, struct glusterfs_state_fd, state) ->glusterfs_fd; else my_fd = &myself->globalfd; fsal_fd = &my_fd->fsal_fd; #if 0 /** @todo: fsid work */ if (obj_hdl->fsal != obj_hdl->fs->fsal) { LogDebug(COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", obj_hdl->fsal->name, obj_hdl->fs->fsal->name); return fsalstat(posix2fsal_error(EXDEV), EXDEV); } #endif /* Indicate we want to do fd work (can't fail since not reclaiming) */ fsal_start_fd_work_no_reclaim(fsal_fd); old_openflags = my_fd->fsal_fd.openflags; if (state != NULL) { /* Prepare to take the share reservation, but only if we are * called with a valid state (if state is NULL the caller is a * stateless create such as NFS v3 CREATE and we're just going * to ignore share reservation stuff). */ /* Now that we have the mutex, and no I/O is in progress so we * have exclusive access to the share's fsal_fd, we can look at * its openflags. We also need to work the share reservation so * take the obj_lock. NOTE: This is the ONLY sequence where both * a work_mutex and the obj_lock are taken, so there is no * opportunity for ABBA deadlock. * * Note that we do hold the obj_lock over an open and a close * which is longer than normal, but the previous iteration of * the code held the obj lock (read granted) over whole I/O * operations... We don't block over I/O because we've assured * that no I/O is in progress or can start before proceeding * past the above while loop. */ PTHREAD_RWLOCK_wrlock(&obj_hdl->obj_lock); /* Now check the new share. */ status = check_share_conflict(&myself->share, openflags, false); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "check_share_conflict returned %s", fsal_err_txt(status)); goto exit; } } /* Check for a genuine no-op open. That means we aren't trying to * create, the file is already open in the same mode with the same * deny flags, and we aren't trying to truncate. In this case we want * to avoid bouncing the fd. In the case of JUST changing the deny mode * or an replayed exclusive create, we might bounce the fd when we could * have avoided that, but those scenarios are much less common. */ if (FSAL_O_NFS_FLAGS(openflags) == FSAL_O_NFS_FLAGS(old_openflags) && truncated == false && createmode == FSAL_NO_CREATE) { LogFullDebug(COMPONENT_FSAL, "no-op reopen2 my_fd->glfd = %p openflags = %x", my_fd->glfd, openflags); goto exit; } /* No share conflict, re-open the share fd */ status = glusterfs_reopen_func(obj_hdl, openflags, fsal_fd); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "glusterfs_reopen_func returned %s", fsal_err_txt(status)); goto exit; } /* Inserts to fd_lru only if open succeeds */ if (old_openflags == FSAL_O_CLOSED) { /* This is actually an open, need to increment * appropriate counter and insert into LRU. */ insert_fd_lru(fsal_fd); } else { /* Bump up the FD in fd_lru as it was already in fd lru. */ bump_fd_lru(fsal_fd); } if (createmode >= FSAL_EXCLUSIVE || (truncated && attrs_out)) { /* NOTE: won't come in here when called from vfs_reopen2... * truncated might be set, but attrs_out will be NULL. */ /* Fetch the attributes to check against the * verifier in case of exclusive open/create. */ struct stat stat; int retval; /* set proper credentials */ /* @todo: with POSIX ACLs every user shall have * permissions to read stat & ACLs. But that may not be * the case with RichACLs. If the ganesha service is * started by non-root user, that user may get * restricted from reading ACL. */ retval = glfs_fstat(my_fd->glfd, &stat); if (retval == 0) { LogFullDebug(COMPONENT_FSAL, "New size = %" PRIx64, stat.st_size); if (createmode >= FSAL_EXCLUSIVE && createmode != FSAL_EXCLUSIVE_9P && !check_verifier_stat(&stat, verifier, false)) { /* Verifier didn't match, return EEXIST */ status = posix2fsal_status(EEXIST); } else if (attrs_out) { posix2fsal_attributes_all(&stat, attrs_out); } } else { if (errno == EBADF) errno = ESTALE; status = posix2fsal_status(errno); } } else if (attrs_out && attrs_out->request_mask & ATTR_RDATTR_ERR) { attrs_out->valid_mask = ATTR_RDATTR_ERR; } if (FSAL_IS_ERROR(status)) { if (old_openflags == FSAL_O_CLOSED) { /* Now that we have decided to close this FD, * let's clean it off from fd_lru and * ensure counters are decremented. */ remove_fd_lru(fsal_fd); } /* Close fd */ (void)glusterfs_close_my_fd(my_fd); } exit: if (state != NULL) { if (!FSAL_IS_ERROR(status)) { /* Success, establish the new share. */ update_share_counters(&myself->share, old_openflags, openflags); } /* Release obj_lock. */ PTHREAD_RWLOCK_unlock(&obj_hdl->obj_lock); } /* Indicate we are done with fd work and signal any waiters. */ fsal_complete_fd_work(fsal_fd); return status; } /* open2 */ static fsal_status_t glusterfs_open2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attrib_set, fsal_verifier_t verifier, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; int p_flags = 0; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *myself, *parenthandle = NULL; struct glusterfs_fd *my_fd = NULL; struct glusterfs_fd tmp_fd = {}; struct stat sb = { 0 }; struct glfs_object *glhandle = NULL; unsigned char globjhdl[GFAPI_HANDLE_LENGTH] = { '\0' }; char vol_uuid[GLAPI_UUID_LENGTH] = { '\0' }; bool created = false; int retval = 0; mode_t unix_mode; #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif if (state != NULL) my_fd = &container_of(state, struct glusterfs_state_fd, state) ->glusterfs_fd; fsal2posix_openflags(openflags, &p_flags); if (createmode >= FSAL_EXCLUSIVE) { /* Now fixup attrs for verifier if exclusive create */ set_common_verifier(attrib_set, verifier, false); } if (name == NULL) { status = glusterfs_open2_by_handle(obj_hdl, state, openflags, createmode, verifier, attrs_out); *caller_perm_check = FSAL_IS_SUCCESS(status); return status; } LogFullDebug(COMPONENT_FSAL, "open2 processing %s", name); /* case name_not_null */ /* In this path where we are opening by name, we can't check share * reservation yet since we don't have an object_handle yet. If we * indeed create the object handle (there is no race with another * open by name), then there CAN NOT be a share conflict, otherwise * the share conflict will be resolved when the object handles are * merged. */ if (createmode != FSAL_NO_CREATE) { /* Now add in O_CREAT and O_EXCL. */ p_flags |= O_CREAT; /* And if we are at least FSAL_GUARDED, do an O_EXCL create. */ if (createmode >= FSAL_GUARDED) p_flags |= O_EXCL; /* Fetch the mode attribute to use. */ unix_mode = fsal2unix_mode(attrib_set->mode) & ~op_ctx->fsal_export->exp_ops.fs_umask( op_ctx->fsal_export); /* Don't set the mode if we later set the attributes */ FSAL_UNSET_MASK(attrib_set->valid_mask, ATTR_MODE); } if (createmode == FSAL_UNCHECKED && (attrib_set->valid_mask != 0)) { /* If we have FSAL_UNCHECKED and want to set more attributes * than the mode, we attempt an O_EXCL create first, if that * succeeds, then we will be allowed to set the additional * attributes, otherwise, we don't know we created the file * and this can NOT set the attributes. */ p_flags |= O_EXCL; } /** @todo: we do not have openat implemented yet..meanwhile * use 'glfs_h_creat' */ /* obtain parent directory handle */ parenthandle = container_of(obj_hdl, struct glusterfs_handle, handle); if (createmode == FSAL_NO_CREATE) { /* lookup if the object exists */ status = (obj_hdl)->obj_ops->lookup(obj_hdl, name, new_obj, attrs_out); LogFullDebug(COMPONENT_FSAL, "lookup %s returned %s", name, msg_fsal_err(status.major)); if (FSAL_IS_ERROR(status)) { *new_obj = NULL; goto direrr; } if ((*new_obj)->type != REGULAR_FILE) { if ((*new_obj)->type == DIRECTORY) { /* Trying to open2 a directory */ status = fsalstat(ERR_FSAL_ISDIR, 0); } else { /* Trying to open2 any other non-regular file */ status = fsalstat(ERR_FSAL_SYMLINK, 0); } /* Release the object we found by lookup. */ LogFullDebug(COMPONENT_FSAL, "open2 returning %s", fsal_err_txt(status)); goto direrr; } myself = container_of(*new_obj, struct glusterfs_handle, handle); /* Now it's basically an open by handle... */ status = glusterfs_open2_by_handle(*new_obj, state, openflags, createmode, verifier, attrs_out); *caller_perm_check = FSAL_IS_SUCCESS(status); LogFullDebug(COMPONENT_FSAL, "glusterfs_open2_by_handle for %s returned %s", name, fsal_err_txt(status)); if (FSAL_IS_ERROR(status)) goto direrr; else goto open; } if (!my_fd) { /* case: state == NULL * This only lasts long enough to get the file open and a new * fsal_obj_handle created with a globalfd that we can transfer * the tmp_fd to. Should we not get that far, my_fd will remain * pointing to tmp_fd and will result in proper cleanup. */ my_fd = &tmp_fd; } /* Become the user because we are creating an object in this dir. */ /* set proper credentials */ SET_GLUSTER_CREDS_OP_CTX(glfs_export); /** @todo: glfs_h_creat doesn't honour NO_CREATE mode. Instead use * glfs_h_open to verify if the file already exists. */ glhandle = glusterfs_create_my_fd(parenthandle, name, openflags, p_flags, unix_mode, &sb, my_fd); retval = errno; LogFullDebug(COMPONENT_FSAL, "glusterfs_create_my_fd %s returned %s (%d)", name, strerror(retval), retval); if (glhandle == NULL && retval == EEXIST && createmode == FSAL_UNCHECKED) { /* We tried to create O_EXCL to set attributes and failed. * Remove O_EXCL and retry, also remember not to set attributes. * We still try O_CREAT again just in case file disappears out * from under us. * * Note that because we have dropped O_EXCL, later on we will * not assume we created the file, and thus will not set * additional attributes. We don't need to separately track * the condition of not wanting to set attributes. */ p_flags &= ~O_EXCL; glhandle = glusterfs_create_my_fd(parenthandle, name, openflags, p_flags, unix_mode, &sb, my_fd); retval = errno; LogFullDebug(COMPONENT_FSAL, "glusterfs_create_my_fd %s returned %s (%d)", name, strerror(retval), retval); } else if (!retval) { created = true; } /* restore credentials */ RESET_GLUSTER_CREDS(glfs_export); if (glhandle == NULL || my_fd->glfd == NULL) { status = gluster2fsal_error(retval); goto out; } /* Check if the opened file is not a regular file. */ if (posix2fsal_type(sb.st_mode) == DIRECTORY) { /* Trying to open2 a directory */ status = fsalstat(ERR_FSAL_ISDIR, 0); goto direrr; } if (posix2fsal_type(sb.st_mode) != REGULAR_FILE) { /* Trying to open2 any other non-regular file */ status = fsalstat(ERR_FSAL_SYMLINK, 0); goto direrr; } /* Remember if we were responsible for creating the file. * Note that in an UNCHECKED retry we MIGHT have re-created the * file and won't remember that. Oh well, so in that rare case we * leak a partially created file if we have a subsequent error in here. * Also notify caller to do permission check if we DID NOT create the * file. Note it IS possible in the case of a race between an UNCHECKED * open and an external unlink, we did create the file, but we will * still force a permission check. That permission check might fail * if the file created after the unlink has a mode that doesn't allow * the caller/creator to open the file (on the other hand, one hopes * a non-exclusive open doesn't set a mode that doesn't allow read/write * since the application clearly expects that another process may have * created the file). This failure case really isn't too awful since * it would just look to the caller like someone else had created the * file with a mode that prevented the open this caller was attempting. */ /* Do a permission check if we were not attempting to create. If we * were attempting any sort of create, then the openat call was made * with the caller's credentials active and as such was permission * checked. */ *caller_perm_check = !created; /* Since the file is created, remove O_CREAT/O_EXCL flags */ p_flags &= ~(O_EXCL | O_CREAT); retval = glfs_h_extract_handle(glhandle, globjhdl, GFAPI_HANDLE_LENGTH); if (retval < 0) { retval = errno; LogFullDebug(COMPONENT_FSAL, "glfs_h_extract_handle %s returned %s (%d)", name, strerror(retval), retval); status = gluster2fsal_error(retval); goto direrr; } retval = glfs_get_volumeid(glfs_export->gl_fs->fs, vol_uuid, GLAPI_UUID_LENGTH); if (retval < 0) { retval = errno; LogFullDebug(COMPONENT_FSAL, "glfs_get_volumeid %s returned %s (%d)", name, strerror(retval), retval); status = gluster2fsal_error(retval); goto direrr; } construct_handle(glfs_export, &sb, glhandle, globjhdl, &myself, vol_uuid); *new_obj = &myself->handle; /* If we didn't have a state above, use the global fd. At this point, * since we just created the global fd, no one else can have a * reference to it, and thus we can mamnipulate unlocked which is * handy since we can then call setattr2 which WILL take the lock * without a double locking deadlock. * * if state != NULL, my_fd contains a valid glfd and hence need to be * dup'ed to be copied to globalfd. Else my_fd is referring to tmp_fd * which can safely be copied as is to globalfd. * * In case of any further errors, this globalfd gets destroyed as part * of new_obj->release. * */ glusterfs_copy_my_fd(my_fd, &myself->globalfd, (state != NULL)); if (state == NULL) { /* In this case my_fd == tmp_fd, which we shallow copied * caller_garray into globalfd. Since the two are now * equivalent, we no longer want any reference to tmp_fd. This * will prevent a double free of caller_garray if we wind up at * fileerr. This also properly manages the glfd. */ my_fd = &myself->globalfd; } LogFullDebug(COMPONENT_FSAL, "glusterfs_copy_my_fd %s returned %s", name, msg_fsal_err(status.major)); GSH_UNIQUE_AUTO_TRACEPOINT(fsal_gl, close_fd, TRACE_DEBUG, "Open fd: {}, posix_flags: {}", my_fd->glfd, p_flags); open: if (created && attrib_set->valid_mask != 0) { /* Set attributes using our newly opened file descriptor as the * share_fd if there are any left to set (mode and truncate * have already been handled). * * Note that we only set the attributes if we were responsible * for creating the file and we have attributes to set. */ status = (*new_obj)->obj_ops->setattr2(*new_obj, false, state, attrib_set); LogFullDebug(COMPONENT_FSAL, "setattr2 %s returned %s", name, msg_fsal_err(status.major)); if (FSAL_IS_ERROR(status)) goto fileerr; if (attrs_out != NULL) { status = (*new_obj)->obj_ops->getattrs(*new_obj, attrs_out); LogFullDebug(COMPONENT_FSAL, "getattrs %s returned %s", name, msg_fsal_err(status.major)); if (FSAL_IS_ERROR(status) && (attrs_out->request_mask & ATTR_RDATTR_ERR) == 0) { /* Get attributes failed and caller expected * to get the attributes. Otherwise continue * with attrs_out indicating ATTR_RDATTR_ERR. */ goto fileerr; } } } else if (attrs_out != NULL) { /* Since we haven't set any attributes other than what was set * on create (if we even created), just use the stat results * we used to create the fsal_obj_handle. */ posix2fsal_attributes_all(&sb, attrs_out); } if (state != NULL) { /* Prepare to take the share reservation, but only if we are * called with a valid state (if state is NULL the caller is * a stateless create such as NFS v3 CREATE). */ /* Take the share reservation now by updating the counters. */ update_share_counters_locked(*new_obj, &myself->share, FSAL_O_CLOSED, openflags); } return fsalstat(ERR_FSAL_NO_ERROR, 0); fileerr: /* Avoiding use after freed, make sure close my_fd before * obj_ops->release(), glfs_close is called depends on * FSAL_O_CLOSED flags, it's harmless of closing my_fd twice * in the floowing obj_ops->release(). */ GSH_UNIQUE_AUTO_TRACEPOINT(fsal_gl, close_fd, TRACE_DEBUG, "Close fd: {}", my_fd->glfd); glusterfs_close_my_fd(my_fd); direrr: /* Release the handle we just allocated. */ if (*new_obj) { (*new_obj)->obj_ops->release(*new_obj); /* We released handle at this point */ glhandle = NULL; *new_obj = NULL; } /* Delete the file if we actually created it. */ if (created) { SET_GLUSTER_CREDS_OP_CTX(glfs_export); glfs_h_unlink(glfs_export->gl_fs->fs, parenthandle->glhandle, name); RESET_GLUSTER_CREDS(glfs_export); } if (status.major != ERR_FSAL_NO_ERROR) gluster_cleanup_vars(glhandle); out: #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_file_open); #endif return status; } /** * @brief Return open status of a state. * * This function returns open flags representing the current open * status for a state. The st_lock must be held. * * @param[in] obj_hdl File on which to operate * @param[in] state File state to interrogate * * @retval Flags representing current open status */ static fsal_openflags_t glusterfs_status2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { struct glusterfs_fd *my_fd = &((struct glusterfs_state_fd *)state)->glusterfs_fd; return my_fd->fsal_fd.openflags; } /* reopen2 */ static fsal_status_t glusterfs_reopen2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags) { return glusterfs_open2_by_handle(obj_hdl, state, openflags, FSAL_NO_CREATE, NULL, NULL); } /* read2 */ static void glusterfs_read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg) { struct glusterfs_fd *my_fd; struct glusterfs_fd temp_fd = GLUSTERFS_FD_INIT; struct fsal_fd *out_fd; ssize_t nb_read; fsal_status_t status, status2; int retval = 0; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); size_t total_size = 0; uint64_t seek_descriptor = read_arg->offset; int i; struct glusterfs_handle *myself; myself = container_of(obj_hdl, struct glusterfs_handle, handle); if (read_arg->info != NULL) { /* Currently we don't support READ_PLUS */ done_cb(obj_hdl, fsalstat(ERR_FSAL_NOTSUPP, 0), read_arg, caller_arg); return; } #if 0 /** @todo: fsid work */ if (obj_hdl->fsal != obj_hdl->fs->fsal) { LogDebug(COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", obj_hdl->fsal->name, obj_hdl->fs->fsal->name); return fsalstat(posix2fsal_error(EXDEV), EXDEV); } #endif /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->globalfd.fsal_fd, &temp_fd.fsal_fd, read_arg->state, FSAL_O_READ, false, NULL, bypass, &myself->share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct glusterfs_fd, fsal_fd); SET_GLUSTER_CREDS_OP_CTX(glfs_export); /* XXX dang switch to preadv_async once async supported */ nb_read = glfs_preadv(my_fd->glfd, read_arg->iov, read_arg->iov_count, seek_descriptor, 0); /* restore credentials */ RESET_GLUSTER_CREDS(glfs_export); if (seek_descriptor == -1 || nb_read == -1) { retval = errno; status = fsalstat(posix2fsal_error(retval), retval); goto out; } read_arg->io_amount = nb_read; for (i = 0; i < read_arg->iov_count; i++) { total_size += read_arg->iov[i].iov_len; } if (nb_read < total_size) read_arg->end_of_file = true; #if 0 /** @todo * * Is this all we really need to do to support READ_PLUS? Will anyone * ever get upset that we don't return holes, even for blocks of all * zeroes? * */ if (info != NULL) { info->io_content.what = NFS4_CONTENT_DATA; info->io_content.data.d_offset = offset + nb_read; info->io_content.data.d_data.data_len = nb_read; info->io_content.data.d_data.data_val = buffer; } #endif out: status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (read_arg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->share, FSAL_O_READ, FSAL_O_CLOSED); } exit: done_cb(obj_hdl, status, read_arg, caller_arg); } /* write2 */ static void glusterfs_write2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *caller_arg) { ssize_t nb_written; fsal_status_t status, status2; int retval = 0; struct glusterfs_fd *my_fd; struct glusterfs_fd temp_fd = GLUSTERFS_FD_INIT; struct fsal_fd *out_fd; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *myself; myself = container_of(obj_hdl, struct glusterfs_handle, handle); #if 0 /** @todo: fsid work */ if (obj_hdl->fsal != obj_hdl->fs->fsal) { LogDebug(COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", obj_hdl->fsal->name, obj_hdl->fs->fsal->name); return fsalstat(posix2fsal_error(EXDEV), EXDEV); } #endif /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->globalfd.fsal_fd, &temp_fd.fsal_fd, write_arg->state, FSAL_O_WRITE, false, NULL, bypass, &myself->share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct glusterfs_fd, fsal_fd); SET_GLUSTER_CREDS_OP_CTX(glfs_export); /* XXX dang switch to pwritev_async once async supported */ nb_written = glfs_pwritev(my_fd->glfd, write_arg->iov, write_arg->iov_count, write_arg->offset, (write_arg->fsal_stable ? O_SYNC : 0)); /* restore credentials */ RESET_GLUSTER_CREDS(glfs_export); if (nb_written == -1) { retval = errno; status = fsalstat(posix2fsal_error(retval), retval); goto out; } write_arg->io_amount = nb_written; out: status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (write_arg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->share, FSAL_O_WRITE, FSAL_O_CLOSED); } exit: done_cb(obj_hdl, status, write_arg, caller_arg); } /** * @brief Implements GLUSTER FSAL objectoperation seek (DATA/HOLE) * seek2 */ static fsal_status_t seek2(struct fsal_obj_handle *obj_hdl, struct state_t *state, struct io_info *info) { off_t ret = 0, offset = info->io_content.hole.di_offset; int what = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }, status2; struct glusterfs_fd *my_fd; struct glusterfs_fd temp_fd = GLUSTERFS_FD_INIT; struct fsal_fd *out_fd; struct stat sbuf = { 0 }; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *myself; #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif myself = container_of(obj_hdl, struct glusterfs_handle, handle); /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->globalfd.fsal_fd, &temp_fd.fsal_fd, state, FSAL_O_ANY, false, NULL, true, &myself->share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct glusterfs_fd, fsal_fd); ret = glfs_fstat(my_fd->glfd, &sbuf); if (ret != 0) { if (errno == EBADF) errno = ESTALE; status = gluster2fsal_error(errno); goto out; } /* RFC7862 15.11.3, * If the sa_offset is beyond the end of the file, * then SEEK MUST return NFS4ERR_NXIO. */ if (offset >= sbuf.st_size) { status = gluster2fsal_error(ENXIO); goto out; } SET_GLUSTER_CREDS_OP_CTX(glfs_export); if (info->io_content.what == NFS4_CONTENT_DATA) { what = SEEK_DATA; } else if (info->io_content.what == NFS4_CONTENT_HOLE) { what = SEEK_HOLE; } else { status = fsalstat(ERR_FSAL_UNION_NOTSUPP, 0); goto out; } ret = glfs_lseek(my_fd->glfd, offset, what); /* restore credentials */ RESET_GLUSTER_CREDS(glfs_export); if (ret < 0) { if (errno == ENXIO) { info->io_eof = TRUE; } else { status = gluster2fsal_error(errno); } goto out; } else { info->io_eof = (ret >= sbuf.st_size) ? TRUE : FALSE; info->io_content.hole.di_offset = ret; } out: status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); /* We did FSAL_O_ANY so no share reservation was acquired */ exit: return status; } /* commit2 */ static fsal_status_t glusterfs_commit2(struct fsal_obj_handle *obj_hdl, off_t offset, size_t len) { fsal_status_t status, status2; int retval; struct glusterfs_fd *my_fd; struct glusterfs_fd temp_fd = GLUSTERFS_FD_INIT; struct fsal_fd *out_fd; struct glusterfs_handle *myself = NULL; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); myself = container_of(obj_hdl, struct glusterfs_handle, handle); /* Make sure file is open in appropriate mode. * Do not check share reservation. */ status = fsal_start_global_io(&out_fd, obj_hdl, &myself->globalfd.fsal_fd, &temp_fd.fsal_fd, FSAL_O_ANY, false, NULL); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); return status; } my_fd = container_of(out_fd, struct glusterfs_fd, fsal_fd); SET_GLUSTER_CREDS_OP_CTX(glfs_export); #ifdef USE_GLUSTER_STAT_FETCH_API retval = glfs_fsync(my_fd->glfd, NULL, NULL); #else retval = glfs_fsync(my_fd->glfd); #endif if (retval == -1) { retval = errno; status = fsalstat(posix2fsal_error(retval), retval); } /* restore credentials */ RESET_GLUSTER_CREDS(glfs_export); status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); /* We did not do share reservation stuff... */ return status; } /* lock_op2 */ static fsal_status_t glusterfs_lock_op2(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *p_owner, fsal_lock_op_t lock_op, fsal_lock_param_t *request_lock, fsal_lock_param_t *conflicting_lock) { struct flock lock_args; int fcntl_comm; fsal_status_t status = { 0, 0 }, status2; int retval = 0; struct glusterfs_fd *my_fd; struct glusterfs_fd temp_fd = GLUSTERFS_FD_INIT; struct fsal_fd *out_fd; bool bypass = false; fsal_openflags_t openflags = FSAL_O_RDWR; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *myself; bool open_for_locks; myself = container_of(obj_hdl, struct glusterfs_handle, handle); #if 0 /** @todo: fsid work */ if (obj_hdl->fsal != obj_hdl->fs->fsal) { LogDebug(COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", obj_hdl->fsal->name, obj_hdl->fs->fsal->name); return fsalstat(posix2fsal_error(EXDEV), EXDEV); } #endif LogFullDebug(COMPONENT_FSAL, "Locking: op(%d) type(%d) start(%" PRIu64 ") length(%" PRIu64 ")", lock_op, request_lock->lock_type, request_lock->lock_start, request_lock->lock_length); if (lock_op == FSAL_OP_LOCKT) { /* We may end up using global fd, don't fail on a deny mode */ bypass = true; fcntl_comm = F_GETLK; openflags = FSAL_O_ANY; } else if (lock_op == FSAL_OP_LOCK) { fcntl_comm = F_SETLK; if (request_lock->lock_type == FSAL_LOCK_R) openflags = FSAL_O_READ; else if (request_lock->lock_type == FSAL_LOCK_W) openflags = FSAL_O_WRITE; } else if (lock_op == FSAL_OP_UNLOCK) { fcntl_comm = F_SETLK; openflags = FSAL_O_ANY; } else { LogDebug( COMPONENT_FSAL, "ERROR: Lock operation requested was not TEST, READ, or WRITE."); return fsalstat(ERR_FSAL_NOTSUPP, 0); } if (lock_op != FSAL_OP_LOCKT && state == NULL) { LogCrit(COMPONENT_FSAL, "Non TEST operation with NULL state"); return fsalstat(posix2fsal_error(EINVAL), EINVAL); } if (request_lock->lock_type == FSAL_LOCK_R) { lock_args.l_type = F_RDLCK; } else if (request_lock->lock_type == FSAL_LOCK_W) { lock_args.l_type = F_WRLCK; } else { LogDebug( COMPONENT_FSAL, "ERROR: The requested lock type was not read or write."); return fsalstat(ERR_FSAL_NOTSUPP, 0); } if (lock_op == FSAL_OP_UNLOCK) lock_args.l_type = F_UNLCK; lock_args.l_pid = 0; lock_args.l_len = request_lock->lock_length; lock_args.l_start = request_lock->lock_start; lock_args.l_whence = SEEK_SET; /* flock.l_len being signed long integer, larger lock ranges may * get mapped to negative values. As per 'man 3 fcntl', posix * locks can accept negative l_len values which may lead to * unlocking an unintended range. Better bail out to prevent that. */ if (lock_args.l_len < 0) { LogCrit(COMPONENT_FSAL, "The requested lock length is out of range: lock_args.l_len(%" PRId64 "), request_lock_length(%" PRIu64 ")", lock_args.l_len, request_lock->lock_length); return fsalstat(ERR_FSAL_BAD_RANGE, 0); } if (state != NULL && (state->state_type == STATE_TYPE_NLM_LOCK || state->state_type == STATE_TYPE_9P_FID)) { /* For Gluster, we will only open for locks if the state_t is * from NLM or 9P, otherwise we will either use the global fd * for a LOCKT without state, or use the associated open state * for an NFSv4 LOCK or LOCKU. */ open_for_locks = true; } /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->globalfd.fsal_fd, &temp_fd.fsal_fd, state, openflags, open_for_locks, NULL, bypass, &myself->share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct glusterfs_fd, fsal_fd); errno = 0; SET_GLUSTER_CREDS_MY_FD(glfs_export, my_fd); /* Convert lkowner ptr address to opaque string */ retval = glfs_fd_set_lkowner(my_fd->glfd, p_owner, sizeof(p_owner)); if (retval) { LogCrit(COMPONENT_FSAL, "Setting lkowner failed"); goto err; } retval = glfs_posix_lock(my_fd->glfd, fcntl_comm, &lock_args); if (retval /* && lock_op == FSAL_OP_LOCK */) { retval = errno; int rc = 0; LogDebug(COMPONENT_FSAL, "fcntl returned %d %s", retval, strerror(retval)); if (conflicting_lock != NULL) { /* Get the conflicting lock */ rc = glfs_fd_set_lkowner(my_fd->glfd, p_owner, sizeof(p_owner)); if (rc) { retval = errno; /* we losethe initial error */ LogCrit(COMPONENT_FSAL, "Setting lkowner while trying to get conflicting lock failed"); goto err; } rc = glfs_posix_lock(my_fd->glfd, F_GETLK, &lock_args); if (rc) { retval = errno; /* we lose the initial error */ LogCrit(COMPONENT_FSAL, "After failing a lock request, I couldn't even get the details of who owns the lock."); goto err; } conflicting_lock->lock_length = lock_args.l_len; conflicting_lock->lock_start = lock_args.l_start; conflicting_lock->lock_type = lock_args.l_type; } goto err; } /* F_UNLCK is returned then the tested operation would be possible. */ if (conflicting_lock != NULL) { if (lock_op == FSAL_OP_LOCKT && lock_args.l_type != F_UNLCK) { conflicting_lock->lock_length = lock_args.l_len; conflicting_lock->lock_start = lock_args.l_start; conflicting_lock->lock_type = lock_args.l_type; } else { conflicting_lock->lock_length = 0; conflicting_lock->lock_start = 0; conflicting_lock->lock_type = FSAL_NO_LOCK; } } /* Fall through (retval == 0) */ err: RESET_GLUSTER_CREDS(glfs_export); status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); exit: return status; } #ifdef USE_GLUSTER_DELEGATION static fsal_status_t glusterfs_lease_op2(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *owner, fsal_deleg_t deleg) { int retval = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }, status2; struct glusterfs_fd *my_fd; struct glusterfs_fd temp_fd = GLUSTERFS_FD_INIT; struct fsal_fd *out_fd; fsal_openflags_t openflags = FSAL_O_RDWR; struct glusterfs_handle *myself = NULL; struct glfs_lease lease = { 0, }; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); assert(state != NULL); myself = container_of(obj_hdl, struct glusterfs_handle, handle); switch (deleg) { case FSAL_DELEG_NONE: lease.cmd = GLFS_UNLK_LEASE; openflags = FSAL_O_ANY; /* 'myself' should contain the lease_type obtained. * If not, we had already unlocked the lease and this is * duplicate request. Return as noop. */ if (myself->lease_type == 0) { LogDebug(COMPONENT_FSAL, "No lease found to unlock"); return status; } lease.lease_type = myself->lease_type; break; case FSAL_DELEG_RD: lease.cmd = GLFS_SET_LEASE; openflags = FSAL_O_READ; lease.lease_type = GLFS_RD_LEASE; break; case FSAL_DELEG_WR: lease.cmd = GLFS_SET_LEASE; openflags = FSAL_O_WRITE; lease.lease_type = GLFS_RW_LEASE; break; default: LogCrit(COMPONENT_FSAL, "Unknown requested lease state"); return gluster2fsal_error(EINVAL); } /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->globalfd.fsal_fd, &temp_fd.fsal_fd, state, openflags, false, NULL, false, NULL); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct glusterfs_fd, fsal_fd); /* Since we open unique fd for each NFSv4.x OPEN * operation, we should have had lease_id set */ memcpy(lease.lease_id, my_fd->lease_id, GLAPI_LEASE_ID_SIZE); errno = 0; SET_GLUSTER_CREDS_MY_FD(glfs_export, my_fd); retval = glfs_lease(my_fd->glfd, &lease, NULL, NULL); if (retval) { retval = errno; LogWarn(COMPONENT_FSAL, "Unable to %s lease", (deleg == FSAL_DELEG_NONE) ? "release" : "acquire"); } else { if (deleg == FSAL_DELEG_NONE) { /* reset lease_type */ myself->lease_type = 0; } else { myself->lease_type = lease.lease_type; } } RESET_GLUSTER_CREDS(glfs_export); status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); /* We always have a state so no share reservation was acquired */ exit: return status; } #endif /** * @brief Set attributes on an object * * This function sets attributes on an object. Which attributes are * set is determined by attrib_set->valid_mask. The FSAL must manage bypass * or not of share reservations, and a state may be passed. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] attrib_set Attributes to set * * @return FSAL status. */ static fsal_status_t glusterfs_setattr2(struct fsal_obj_handle *obj_hdl, bool bypass, struct state_t *state, struct fsal_attrlist *attrib_set) { struct glusterfs_handle *myself; fsal_status_t status = { 0, 0 }, status2; int retval = 0; fsal_openflags_t openflags = FSAL_O_ANY; struct glusterfs_fd *my_fd; struct glusterfs_fd temp_fd = GLUSTERFS_FD_INIT; struct fsal_fd *out_fd; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); glusterfs_fsal_xstat_t buffxstat = { 0 }; int attr_valid = 0; int mask = 0; /** @todo: Handle special file symbolic links etc */ /* apply umask, if mode attribute is to be changed */ if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MODE)) attrib_set->mode &= ~op_ctx->fsal_export->exp_ops.fs_umask( op_ctx->fsal_export); myself = container_of(obj_hdl, struct glusterfs_handle, handle); #if 0 /** @todo: fsid work */ if (obj_hdl->fsal != obj_hdl->fs->fsal) { LogDebug(COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", obj_hdl->fsal->name, obj_hdl->fs->fsal->name); return fsalstat(posix2fsal_error(EXDEV), EXDEV); } #endif /* Test if size is being set, make sure file is regular and if so, * require a read/write file descriptor. */ if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_SIZE)) { if (obj_hdl->type != REGULAR_FILE) return fsalstat(ERR_FSAL_INVAL, EINVAL); openflags = FSAL_O_WRITE; } /** TRUNCATE **/ if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_SIZE) && (obj_hdl->type == REGULAR_FILE)) { /* Indicate a desire to start io and get a usable file * descritor. Share conflict is only possible if size is being * set. For special files, handle via handle. */ status = fsal_start_io(&out_fd, obj_hdl, &myself->globalfd.fsal_fd, &temp_fd.fsal_fd, state, openflags, false, NULL, false, &myself->share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); return status; } my_fd = container_of(out_fd, struct glusterfs_fd, fsal_fd); SET_GLUSTER_CREDS_OP_CTX(glfs_export); #ifdef USE_GLUSTER_STAT_FETCH_API retval = glfs_ftruncate(my_fd->glfd, attrib_set->filesize, NULL, NULL); #else retval = glfs_ftruncate(my_fd->glfd, attrib_set->filesize); #endif RESET_GLUSTER_CREDS(glfs_export); status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (state == NULL) { /* We did I/O without a state so we need to release the * temp share reservation acquired. */ /* Release the share reservation now by updating the * counters. */ update_share_counters_locked(obj_hdl, &myself->share, openflags, FSAL_O_CLOSED); } if (retval != 0) { status = gluster2fsal_error(errno); goto out; } } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MODE)) { FSAL_SET_MASK(mask, GLAPI_SET_ATTR_MODE); buffxstat.buffstat.st_mode = fsal2unix_mode(attrib_set->mode); } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_OWNER)) { FSAL_SET_MASK(mask, GLAPI_SET_ATTR_UID); buffxstat.buffstat.st_uid = attrib_set->owner; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_GROUP)) { FSAL_SET_MASK(mask, GLAPI_SET_ATTR_GID); buffxstat.buffstat.st_gid = attrib_set->group; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_ATIME)) { FSAL_SET_MASK(mask, GLAPI_SET_ATTR_ATIME); buffxstat.buffstat.st_atim = attrib_set->atime; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_ATIME_SERVER)) { FSAL_SET_MASK(mask, GLAPI_SET_ATTR_ATIME); struct timespec timestamp; retval = clock_gettime(CLOCK_REALTIME, ×tamp); if (retval != 0) { status = gluster2fsal_error(errno); goto out; } buffxstat.buffstat.st_atim = timestamp; } /* try to look at glfs_futimens() instead as done in vfs */ if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MTIME)) { FSAL_SET_MASK(mask, GLAPI_SET_ATTR_MTIME); buffxstat.buffstat.st_mtim = attrib_set->mtime; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MTIME_SERVER)) { FSAL_SET_MASK(mask, GLAPI_SET_ATTR_MTIME); struct timespec timestamp; retval = clock_gettime(CLOCK_REALTIME, ×tamp); if (retval != 0) { status = gluster2fsal_error(errno); goto out; } buffxstat.buffstat.st_mtim = timestamp; } /** @todo: Check for attributes not supported and return */ /* EATTRNOTSUPP error. */ if (NFSv4_ACL_SUPPORT) { if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_ACL) && attrib_set->acl && attrib_set->acl->naces) { if (obj_hdl->type == DIRECTORY) buffxstat.is_dir = true; else buffxstat.is_dir = false; FSAL_SET_MASK(attr_valid, XATTR_ACL); status = glusterfs_process_acl(glfs_export->gl_fs->fs, myself->glhandle, attrib_set, &buffxstat); if (FSAL_IS_ERROR(status)) goto out; /* setting the ACL will set the */ /* mode-bits too if not already passed */ FSAL_SET_MASK(mask, GLAPI_SET_ATTR_MODE); } } else if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_ACL)) { status = fsalstat(ERR_FSAL_ATTRNOTSUPP, 0); goto out; } SET_GLUSTER_CREDS_OP_CTX(glfs_export); /* If any stat changed, indicate that */ if (mask != 0) FSAL_SET_MASK(attr_valid, XATTR_STAT); if (FSAL_TEST_MASK(attr_valid, XATTR_STAT)) { /* Only if there is any change in attrs send them down to fs */ /** @todo: instead use glfs_fsetattr().... looks like there is * fix needed in there..it doesn't convert the mask flags * to corresponding gluster flags. */ retval = glfs_h_setattrs(glfs_export->gl_fs->fs, myself->glhandle, &buffxstat.buffstat, mask); if (retval != 0) { status = gluster2fsal_error(errno); goto creds; } } if (FSAL_TEST_MASK(attr_valid, XATTR_ACL)) status = glusterfs_set_acl(glfs_export, myself, &buffxstat); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "setting ACL failed"); goto creds; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR4_SEC_LABEL) && op_ctx_export_has_option(EXPORT_OPTION_SECLABEL_SET)) { retval = glfs_h_setxattrs( glfs_export->gl_fs->fs, myself->glhandle, glfs_export->sec_label_xattr, attrib_set->sec_label.slai_data.slai_data_val, attrib_set->sec_label.slai_data.slai_data_len, 0); if (retval < 0) { status = gluster2fsal_error(errno); LogCrit(COMPONENT_FSAL, "Error : seclabel failed with error %s", strerror(errno)); } } creds: RESET_GLUSTER_CREDS(glfs_export); out: if (FSAL_IS_ERROR(status)) { LogCrit(COMPONENT_FSAL, "setattrs failed with error %s", strerror(status.minor)); } glusterfs_fsal_clean_xstat(&buffxstat); return status; } /* close2 */ static fsal_status_t glusterfs_close2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { struct glusterfs_handle *myself = NULL; struct glusterfs_fd *my_fd = &container_of(state, struct glusterfs_state_fd, state) ->glusterfs_fd; myself = container_of(obj_hdl, struct glusterfs_handle, handle); if (state->state_type == STATE_TYPE_SHARE || state->state_type == STATE_TYPE_NLM_SHARE || state->state_type == STATE_TYPE_9P_FID) { /* This is a share state, we must update the share counters */ update_share_counters_locked(obj_hdl, &myself->share, my_fd->fsal_fd.openflags, FSAL_O_CLOSED); } GSH_UNIQUE_AUTO_TRACEPOINT(fsal_gl, close_fd, TRACE_DEBUG, "Close fd: {}", my_fd->glfd); return close_fsal_fd(obj_hdl, &my_fd->fsal_fd, false); } /* * getxattrs */ static fsal_status_t getxattrs(struct fsal_obj_handle *obj_hdl, xattrkey4 *xa_name, xattrvalue4 *xa_value) { int rc = 0; int errsv = 0; fsal_status_t status; struct glusterfs_export *export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *glhandle = container_of(obj_hdl, struct glusterfs_handle, handle); rc = glfs_h_getxattrs(export->gl_fs->fs, glhandle->glhandle, xa_name->utf8string_val, xa_value->utf8string_val, xa_value->utf8string_len); if (rc < 0) { errsv = errno; LogDebug(COMPONENT_FSAL, "GETXATTRS returned rc %d errsv %d", rc, errsv); if (errsv == ERANGE) { status = fsalstat(ERR_FSAL_TOOSMALL, 0); goto out; } if (errsv == ENODATA) { status = fsalstat(ERR_FSAL_NOENT, 0); goto out; } status = fsalstat(posix2fsal_error(errsv), errsv); goto out; } /* Make sure utf8string is null terminated */ xa_value->utf8string_val[xa_value->utf8string_len] = '\0'; LogDebug(COMPONENT_FSAL, "GETXATTRS returned value %s length %d rc %d", xa_value->utf8string_val, xa_value->utf8string_len, rc); status = fsalstat(ERR_FSAL_NO_ERROR, 0); out: return status; } /* * setxattrs */ static fsal_status_t setxattrs(struct fsal_obj_handle *obj_hdl, setxattr_option4 option, xattrkey4 *xa_name, xattrvalue4 *xa_value) { int rc = 0; int errsv = 0; fsal_status_t status = { 0, 0 }; struct glusterfs_export *export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *glhandle = container_of(obj_hdl, struct glusterfs_handle, handle); /* @todo: ensure that the options/type is correct */ rc = glfs_h_setxattrs(export->gl_fs->fs, glhandle->glhandle, xa_name->utf8string_val, xa_value->utf8string_val, xa_value->utf8string_len, option - 1); if (rc < 0) { errsv = errno; LogDebug(COMPONENT_FSAL, "SETXATTRS returned rc %d errsv %d", rc, errsv); status = fsalstat(posix2fsal_error(errsv), errsv); goto out; } status = fsalstat(ERR_FSAL_NO_ERROR, 0); out: return status; } /* * removexattrs */ static fsal_status_t removexattrs(struct fsal_obj_handle *obj_hdl, xattrkey4 *xa_name) { int rc = 0; int errsv = 0; fsal_status_t status = { 0, 0 }; struct glusterfs_export *export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *glhandle = container_of(obj_hdl, struct glusterfs_handle, handle); rc = glfs_h_removexattrs(export->gl_fs->fs, glhandle->glhandle, xa_name->utf8string_val); if (rc < 0) { errsv = errno; LogDebug(COMPONENT_FSAL, "REMOVEXATTRS returned rc %d errsv %d", rc, errsv); status = fsalstat(posix2fsal_error(errsv), errsv); goto out; } status = fsalstat(ERR_FSAL_NO_ERROR, 0); out: return status; } /* * listxattrs */ static fsal_status_t listxattrs(struct fsal_obj_handle *obj_hdl, count4 la_maxcount, nfs_cookie4 *la_cookie, bool_t *lr_eof, xattrlist4 *lr_names) { int rc = 0; int errsv = 0; int entryCount = 0; char *name, *next, *end, *val, *valstart; char *buf = NULL; component4 *entry = lr_names->xl4_entries; fsal_status_t status = { 0, 0 }; struct glusterfs_export *export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *glhandle = container_of(obj_hdl, struct glusterfs_handle, handle); val = (char *)entry + la_maxcount; valstart = val; #define MAXCOUNT (1024 * 64) buf = gsh_malloc(MAXCOUNT); /* Log Message */ LogFullDebug(COMPONENT_FSAL, "in cookie %llu length %d", (unsigned long long)la_cookie, la_maxcount); rc = glfs_h_getxattrs(export->gl_fs->fs, glhandle->glhandle, NULL, &buf, MAXCOUNT); if (rc < 0) { errsv = errno; LogDebug(COMPONENT_FSAL, "LISTXATTRS returned rc %d errsv %d", rc, errsv); if (errsv == ERANGE) { status = fsalstat(ERR_FSAL_TOOSMALL, 0); goto out; } status = fsalstat(posix2fsal_error(errsv), errsv); goto out; } name = buf; end = buf + rc; entry->utf8string_len = 0; entry->utf8string_val = NULL; while (name < end) { next = strchr(name, '\0'); next += 1; LogDebug(COMPONENT_FSAL, "name %s at offset %td", name, (next - name)); if (entryCount >= *la_cookie) { if ((((char *)entry - (char *)lr_names->xl4_entries) + sizeof(component4) > la_maxcount) || ((val - valstart) + (next - name) > la_maxcount)) { gsh_free(buf); *lr_eof = false; lr_names->xl4_count = entryCount - *la_cookie; *la_cookie += entryCount; LogFullDebug(COMPONENT_FSAL, "out1 cookie %llu off %td eof %d", (unsigned long long)*la_cookie, (next - name), *lr_eof); if (lr_names->xl4_count == 0) { status = fsalstat(ERR_FSAL_TOOSMALL, 0); goto out; } status = fsalstat(ERR_FSAL_NO_ERROR, 0); goto out; } entry->utf8string_len = next - name; entry->utf8string_val = val; memcpy(entry->utf8string_val, name, entry->utf8string_len); entry->utf8string_val[entry->utf8string_len] = '\0'; LogFullDebug( COMPONENT_FSAL, "entry %d val %p at %p len %d at %p name %s", entryCount, val, entry, entry->utf8string_len, entry->utf8string_val, entry->utf8string_val); val += entry->utf8string_len; entry += 1; } name = next; entryCount += 1; } lr_names->xl4_count = entryCount - *la_cookie; *la_cookie = 0; *lr_eof = true; gsh_free(buf); LogFullDebug(COMPONENT_FSAL, "out2 cookie %llu eof %d", (unsigned long long)*la_cookie, *lr_eof); status = fsalstat(ERR_FSAL_NO_ERROR, 0); out: return status; } /** * @brief Implements GLUSTER FSAL objectoperation list_ext_attrs */ /* static fsal_status_t list_ext_attrs(struct fsal_obj_handle *obj_hdl, const struct req_op_context *opctx, unsigned int cookie, fsal_xattrent_t * xattrs_tab, unsigned int xattrs_tabsize, unsigned int *p_nb_returned, int *end_of_list) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } */ /** * @brief Implements GLUSTER FSAL objectoperation getextattr_id_by_name */ /* static fsal_status_t getextattr_id_by_name(struct fsal_obj_handle *obj_hdl, const struct req_op_context *opctx, const char *xattr_name, unsigned int *pxattr_id) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } */ /** * @brief Implements GLUSTER FSAL objectoperation getextattr_value_by_name */ /* static fsal_status_t getextattr_value_by_name(struct fsal_obj_handle *obj_hdl, const struct req_op_context *opctx, const char *xattr_name, void *buffer_addr, size_t buffer_size, size_t * p_output_size) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } */ /** * @brief Implements GLUSTER FSAL objectoperation getextattr_value_by_id */ /* static fsal_status_t getextattr_value_by_id(struct fsal_obj_handle *obj_hdl, const struct req_op_context *opctx, unsigned int xattr_id, void *buffer_addr, size_t buffer_size, size_t *p_output_size) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } */ /** * @brief Implements GLUSTER FSAL objectoperation setextattr_value */ /* static fsal_status_t setextattr_value(struct fsal_obj_handle *obj_hdl, const struct req_op_context *opctx, const char *xattr_name, void *buffer_addr, size_t buffer_size, int create) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } */ /** * @brief Implements GLUSTER FSAL objectoperation setextattr_value_by_id */ /* static fsal_status_t setextattr_value_by_id(struct fsal_obj_handle *obj_hdl, const struct req_op_context *opctx, unsigned int xattr_id, void *buffer_addr, size_t buffer_size) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } */ /** * @brief Implements GLUSTER FSAL objectoperation getextattr_attrs */ /* static fsal_status_t getextattr_attrs(struct fsal_obj_handle *obj_hdl, const struct req_op_context *opctx, unsigned int xattr_id, struct fsal_attrlist *p_attrs) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } */ /** * @brief Implements GLUSTER FSAL objectoperation remove_extattr_by_id */ /* static fsal_status_t remove_extattr_by_id(struct fsal_obj_handle *obj_hdl, const struct req_op_context *opctx, unsigned int xattr_id) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } */ /** * @brief Implements GLUSTER FSAL objectoperation remove_extattr_by_name */ /* static fsal_status_t remove_extattr_by_name(struct fsal_obj_handle *obj_hdl, const struct req_op_context *opctx, const char *xattr_name) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } */ /** * @brief Implements GLUSTER FSAL objectoperation handle_to_wire */ static fsal_status_t handle_to_wire(const struct fsal_obj_handle *obj_hdl, fsal_digesttype_t output_type, struct gsh_buffdesc *fh_desc) { fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; size_t fh_size; struct glusterfs_handle *objhandle; #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif if (!fh_desc) return fsalstat(ERR_FSAL_FAULT, 0); objhandle = container_of(obj_hdl, struct glusterfs_handle, handle); switch (output_type) { case FSAL_DIGEST_NFSV3: case FSAL_DIGEST_NFSV4: fh_size = GLAPI_HANDLE_LENGTH; if (fh_desc->len < fh_size) { LogMajor( COMPONENT_FSAL, "Space too small for handle. need %zu, have %zu", fh_size, fh_desc->len); status.major = ERR_FSAL_TOOSMALL; goto out; } memcpy(fh_desc->addr, objhandle->globjhdl, fh_size); break; default: status.major = ERR_FSAL_SERVERFAULT; goto out; } fh_desc->len = fh_size; out: #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_handle_to_wire); #endif return status; } /** * @brief Implements GLUSTER FSAL objectoperation handle_to_key */ static void handle_to_key(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *fh_desc) { struct glusterfs_handle *objhandle; #ifdef GLTIMING struct timespec s_time, e_time; now(&s_time); #endif objhandle = container_of(obj_hdl, struct glusterfs_handle, handle); fh_desc->addr = objhandle->globjhdl; fh_desc->len = GLAPI_HANDLE_LENGTH; #ifdef GLTIMING now(&e_time); latency_update(&s_time, &e_time, lat_handle_to_key); #endif } /** * @brief Registers GLUSTER FSAL objectoperation vector */ void handle_ops_init(struct fsal_obj_ops *ops) { fsal_default_obj_ops_init(ops); ops->release = handle_release; ops->merge = glusterfs_merge; ops->lookup = lookup; ops->mkdir = makedir; ops->mknode = makenode; ops->readdir = read_dirents; ops->symlink = makesymlink; ops->readlink = readsymlink; ops->getattrs = getattrs; ops->getxattrs = getxattrs; ops->setxattrs = setxattrs; ops->removexattrs = removexattrs; ops->listxattrs = listxattrs; ops->link = linkfile; ops->rename = renamefile; ops->unlink = file_unlink; ops->handle_to_wire = handle_to_wire; ops->handle_to_key = handle_to_key; ops->close = file_close; /* fops with OpenTracking (multi-fd) enabled */ ops->open2 = glusterfs_open2; ops->status2 = glusterfs_status2; ops->reopen2 = glusterfs_reopen2; ops->read2 = glusterfs_read2; ops->write2 = glusterfs_write2; ops->commit2 = glusterfs_commit2; ops->lock_op2 = glusterfs_lock_op2; ops->setattr2 = glusterfs_setattr2; ops->close2 = glusterfs_close2; ops->close_func = glusterfs_close_func; ops->reopen_func = glusterfs_reopen_func; ops->seek2 = seek2; #ifdef USE_GLUSTER_DELEGATION ops->lease_op2 = glusterfs_lease_op2; #endif /* pNFS related ops */ handle_ops_pnfs(ops); } nfs-ganesha-6.5/src/FSAL/FSAL_GLUSTER/main.c000066400000000000000000000115411473756622300201350ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Red Hat Inc., 2013 * Author: Anand Subramanian anands@redhat.com * * This program 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 3 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 * 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 * * ------------- */ /** * @file main.c * @author Anand Subramanian * * @author Shyamsundar R * * @brief Module core functions for FSAL_GLUSTER functionality, init etc. * */ #include "fsal.h" #include "FSAL/fsal_init.h" #include "gluster_internal.h" #include "FSAL/fsal_commonlib.h" /* GLUSTERFS FSAL module private storage */ static const char glfsal_name[] = "GLUSTER"; /** * Gluster global module object */ struct glusterfs_fsal_module GlusterFS = { .fsal = { .fs_info = { .maxfilesize = INT64_MAX, .maxlink = _POSIX_LINK_MAX, .maxnamelen = 1024, .maxpathlen = 1024, .no_trunc = true, .chown_restricted = true, .case_insensitive = false, .case_preserving = true, .link_support = true, .symlink_support = true, .lock_support = true, .lock_support_async_block = false, .named_attr = true, .unique_handles = true, .acl_support = FSAL_ACLSUPPORT_ALLOW | FSAL_ACLSUPPORT_DENY, .cansettime = true, .homogenous = true, .supported_attrs = GLUSTERFS_SUPPORTED_ATTRIBUTES, .maxread = 0, .maxwrite = 0, .umask = 0, .auth_exportpath_xdev = false, .pnfs_mds = false, .pnfs_ds = true, .link_supports_permission_checks = true, .delegations = FSAL_OPTION_FILE_DELEGATIONS, .readdir_plus = true, .expire_time_parent = -1, } } }; static struct config_item glfs_params[] = { CONF_ITEM_BOOL("pnfs_mds", false, fsal_staticfsinfo_t, pnfs_mds), CONF_ITEM_BOOL("pnfs_ds", true, fsal_staticfsinfo_t, pnfs_ds), CONFIG_EOL }; struct config_block glfs_param = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.gluster", .blk_desc.name = "GLUSTER", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = glfs_params, .blk_desc.u.blk.commit = noop_conf_commit }; static fsal_status_t init_config(struct fsal_module *fsal_hdl, config_file_t config_struct, struct config_error_type *err_type) { struct glusterfs_fsal_module *glfsal_module = container_of(fsal_hdl, struct glusterfs_fsal_module, fsal); (void)load_config_from_parse(config_struct, &glfs_param, &glfsal_module->fsal.fs_info, true, err_type); /* * Global block is not mandatory, so evenif * it is not parsed correctly, don't consider * that as an error */ if (!config_error_is_harmless(err_type)) LogDebug(COMPONENT_FSAL, "Parsing Export Block failed"); display_fsinfo(&glfsal_module->fsal); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* Module methods */ MODULE_INIT void glusterfs_init(void) { struct fsal_module *myself = &GlusterFS.fsal; if (register_fsal(myself, glfsal_name, FSAL_MAJOR_VERSION, FSAL_MINOR_VERSION, FSAL_ID_GLUSTER) != 0) { LogCrit(COMPONENT_FSAL, "Gluster FSAL module failed to register."); return; } /* set up module operations */ myself->m_ops.create_export = glusterfs_create_export; /* setup global handle internals */ myself->m_ops.init_config = init_config; /* * Following inits needed for pNFS support * get device info will used by pnfs meta data server */ myself->m_ops.getdeviceinfo = getdeviceinfo; myself->m_ops.fsal_pnfs_ds_ops = pnfs_ds_ops_init; /* Initialize the fsal_obj_handle ops for FSAL GLUSTER */ handle_ops_init(&GlusterFS.handle_ops); PTHREAD_MUTEX_init(&GlusterFS.glfs_lock, NULL); glist_init(&GlusterFS.fs_obj); LogDebug(COMPONENT_FSAL, "FSAL Gluster initialized"); } MODULE_FINI void glusterfs_unload(void) { if (unregister_fsal(&GlusterFS.fsal) != 0) { LogCrit(COMPONENT_FSAL, "FSAL Gluster unable to unload. Dying ..."); return; } /* All the shares should have been unexported */ if (!glist_empty(&GlusterFS.fs_obj)) { LogWarn(COMPONENT_FSAL, "FSAL Gluster still contains active shares."); } PTHREAD_MUTEX_destroy(&GlusterFS.glfs_lock); LogDebug(COMPONENT_FSAL, "FSAL Gluster unloaded"); } nfs-ganesha-6.5/src/FSAL/FSAL_GLUSTER/mds.c000066400000000000000000000431751473756622300200040ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Red Hat Inc., 2014 * Author: Jiffin Tony Thottan jthottan@redhat.com * Anand Subramanian anands@redhat.com * * This program 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 3 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 * 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 * * ------------- */ #include #include "fsal.h" #include "fsal_types.h" #include "fsal_api.h" #include "fsal_up.h" #include "gsh_rpc.h" #include "gluster_internal.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_config.h" #include "fsal_convert.h" #include "pnfs_utils.h" #include "nfs_exports.h" #include #include #include #define get16bits(d) (*((const uint16_t *)(d))) #define MAX_DS_COUNT 100 /** * @brief Get layout types supported by export * * We just return a pointer to the single type and set the count to 1. * * @param[in] export_pub Public export handle * @param[out] count Number of layout types in array * @param[out] types Static array of layout types that must not be * freed or modified and must not be dereferenced * after export reference is relinquished */ static void fs_layouttypes(struct fsal_export *export_pub, int32_t *count, const layouttype4 **types) { /* Only supported layout type is file */ static const layouttype4 supported_layout_type = LAYOUT4_NFSV4_1_FILES; *types = &supported_layout_type; *count = 1; } /** * @brief Get layout block size for export * * This function just returns the Gluster default. * * @param[in] export_pub Public export handle * * @return 4 MB. */ static uint32_t fs_layout_blocksize(struct fsal_export *export_pub) { return 0x400000; } /** * @brief Maximum number of segments we will use * * Since current clients only support 1, that's what we'll use. * * @param[in] export_pub Public export handle * * @return 1 */ static uint32_t fs_maximum_segments(struct fsal_export *export_pub) { return 1; } /** * @brief Size of the buffer needed for a loc_body * * Just a handle plus a bit. * * @param[in] export_pub Public export handle * * @return Size of the buffer needed for a loc_body */ static size_t fs_loc_body_size(struct fsal_export *export_pub) { return 0x100; } /** * @brief Size of the buffer needed for a ds_addr * * This one is huge, due to the striping pattern. * * @param[in] export_pub Public export handle * * @return Size of the buffer needed for a ds_addr */ size_t fs_da_addr_size(struct fsal_module *fsal_hdl) { return 0x1400; } int glfs_get_ds_addr(struct glfs *fs, struct glfs_object *object, uint32_t *ds_addr); /** * @brief Grant a layout segment. * * Grants whole layout of the file requested. * * @param[in] obj_pub Public object handle * @param[out] loc_body An XDR stream to which the FSAL must encode * the layout specific portion of the granted * layout segment. * @param[in] arg Input arguments of the function * @param[in,out] res In/out and output arguments of the function * * @return Valid error codes in RFC 5661, pp. 366-7. */ static nfsstat4 pnfs_layout_get(struct fsal_obj_handle *obj_pub, XDR *loc_body, const struct fsal_layoutget_arg *arg, struct fsal_layoutget_res *res) { struct glusterfs_export *export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *handle = container_of(obj_pub, struct glusterfs_handle, handle); int rc = 0; /* Structure containing the storage parameters of the file within glusterfs. */ struct glfs_file_layout file_layout; /* Utility parameter */ nfl_util4 util = 0; /* Stores Data server address */ struct pnfs_deviceid deviceid = DEVICE_ID_INIT_ZERO(FSAL_ID_GLUSTER); nfsstat4 nfs_status = NFS4_OK; /* Descriptor for DS handle */ struct gsh_buffdesc ds_desc; /* DS wire handle send to client */ struct glfs_ds_wire ds_wire; /* Supports only LAYOUT4_NFSV4_1_FILES layouts */ if (arg->type != LAYOUT4_NFSV4_1_FILES) { LogMajor(COMPONENT_PNFS, "Unsupported layout type: %x", arg->type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } memset(&file_layout, 0, sizeof(struct glfs_file_layout)); /** * Currently whole file is given as file layout, * * Stripe type is dense which is supported right now. * Stripe length is max possible length of file that * can be accessed by the client to perform a read or * write. */ file_layout.stripe_type = NFL4_UFLG_DENSE; file_layout.stripe_length = 0x100000; util |= file_layout.stripe_type | file_layout.stripe_length; /* @todo need to handle IPv6 here */ rc = glfs_get_ds_addr(export->gl_fs->fs, handle->glhandle, &deviceid.device_id4); if (rc) { LogMajor(COMPONENT_PNFS, "Invalid hostname for DS"); return NFS4ERR_INVAL; } /** @todo: When more than one client tries access the same layout * for the write operation, then last write will overwrite * for the write operation, then last write will overwrite * the previous ones, the MDS should intelligently deal * those scenarios */ /* We return exactly one wirehandle, filling in the necessary * information for the DS server to speak to the gluster bricks * For this, wire handle stores gfid and file layout */ rc = glfs_h_extract_handle(handle->glhandle, ds_wire.gfid, GFAPI_HANDLE_LENGTH); if (rc < 0) { rc = errno; LogMajor(COMPONENT_PNFS, "Invalid glfs_object"); return posix2nfs4_error(rc); } ds_wire.layout = file_layout; ds_desc.addr = &ds_wire; ds_desc.len = sizeof(struct glfs_ds_wire); nfs_status = FSAL_encode_file_layout(loc_body, &deviceid, util, 0, 0, &op_ctx->ctx_export->export_id, 1, &ds_desc, false); if (nfs_status) { LogMajor(COMPONENT_PNFS, "Failed to encode nfsv4_1_file_layout."); goto out; } /* We grant only one segment, and we want it back * when the file is closed. */ res->return_on_close = true; res->last_segment = true; out: return nfs_status; } /** * @brief Potentially return one layout segment * * Since we don't make any reservations, in this version, or get any * pins to release, always succeed * * @param[in] obj_pub Public object handle * @param[in] lrf_body Nothing for us * @param[in] arg Input arguments of the function * * @return Valid error codes in RFC 5661, p. 367. */ static nfsstat4 pnfs_layout_return(struct fsal_obj_handle *obj_pub, XDR *lrf_body, const struct fsal_layoutreturn_arg *arg) { if (arg->lo_type != LAYOUT4_NFSV4_1_FILES) { LogDebug(COMPONENT_PNFS, "Unsupported layout type: %x", arg->lo_type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } return NFS4_OK; } /** * @brief Commit a segment of a layout * * Update the size and time for a file accessed through a layout. * * @param[in] obj_pub Public object handle * @param[in] lou_body An XDR stream containing the layout * type-specific portion of the LAYOUTCOMMIT * arguments. * @param[in] arg Input arguments of the function * @param[in,out] res In/out and output arguments of the function * * @return Valid error codes in RFC 5661, p. 366. */ static nfsstat4 pnfs_layout_commit(struct fsal_obj_handle *obj_pub, XDR *lou_body, const struct fsal_layoutcommit_arg *arg, struct fsal_layoutcommit_res *res) { /* Old stat, so we don't truncate file or reverse time */ struct stat old_stat; /* new stat to set time and size */ struct stat new_stat; struct glusterfs_export *glfs_export = container_of( op_ctx->fsal_export, struct glusterfs_export, export); struct glusterfs_handle *objhandle = container_of(obj_pub, struct glusterfs_handle, handle); /* Mask to determine exactly what gets set */ int mask = 0; int rc = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; if (arg->type != LAYOUT4_NFSV4_1_FILES) { LogMajor(COMPONENT_PNFS, "Unsupported layout type: %x", arg->type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } /* Gets previous status of file in the MDS */ rc = glfs_h_stat(glfs_export->gl_fs->fs, objhandle->glhandle, &old_stat); if (rc != 0) { LogMajor(COMPONENT_PNFS, "Commit layout, stat unsuccessfully completed"); return NFS4ERR_INVAL; } memset(&new_stat, 0, sizeof(struct stat)); /* Set the new attributes for the file if it is changed */ if (arg->new_offset) { if (old_stat.st_size < arg->last_write + 1) { new_stat.st_size = arg->last_write + 1; res->size_supplied = true; res->new_size = arg->last_write + 1; rc = glfs_h_truncate(glfs_export->gl_fs->fs, objhandle->glhandle, res->new_size); if (rc != 0) { LogMajor( COMPONENT_PNFS, "Commit layout, size change unsuccessfully completed"); return NFS4ERR_INVAL; } } } if ((arg->time_changed) && (arg->new_time.seconds > old_stat.st_mtime)) new_stat.st_mtime = arg->new_time.seconds; else new_stat.st_mtime = time(NULL); mask |= GLAPI_SET_ATTR_MTIME; SET_GLUSTER_CREDS_OP_CTX(glfs_export); rc = glfs_h_setattrs(glfs_export->gl_fs->fs, objhandle->glhandle, &new_stat, mask); RESET_GLUSTER_CREDS(glfs_export); if ((rc != 0) || (status.major != ERR_FSAL_NO_ERROR)) { LogMajor(COMPONENT_PNFS, "commit layout, setattr unsuccessfully completed"); return NFS4ERR_INVAL; } res->commit_done = true; return NFS4_OK; } /** * @brief Describes the DS information for the client * * @param[in] export_pub Public export handle * @param[out] da_addr_body Stream we write the result to * @param[in] type Type of layout that gave the device * @param[in] deviceid The device to look up * * @return Valid error codes in RFC 5661, p. 365. */ nfsstat4 getdeviceinfo(struct fsal_module *fsal_hdl, XDR *da_addr_body, const layouttype4 type, const struct pnfs_deviceid *deviceid) { nfsstat4 nfs_status = 0; /* Stores IP address of DS */ fsal_multipath_member_t host; /* Entire file layout will be situated inside ONE DS * And whole file is provided to the DS, so the starting * index for that file is zero */ unsigned int num_ds = 1; uint32_t stripes = 1; uint32_t stripe_ind = 0; if (type != LAYOUT4_NFSV4_1_FILES) { LogMajor(COMPONENT_PNFS, "Unsupported layout type: %x", type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } if (!inline_xdr_u_int32_t(da_addr_body, &stripes)) { LogMajor( COMPONENT_PNFS, "Failed to encode length of stripe_indices array: %" PRIu32 ".", stripes); return NFS4ERR_SERVERFAULT; } if (!inline_xdr_u_int32_t(da_addr_body, &stripe_ind)) { LogMajor(COMPONENT_PNFS, "Failed to encode ds for the stripe: %" PRIu32 ".", stripe_ind); return NFS4ERR_SERVERFAULT; } if (!inline_xdr_u_int32_t(da_addr_body, &num_ds)) { LogMajor( COMPONENT_PNFS, "Failed to encode length of multipath_ds_list array: %u", num_ds); return NFS4ERR_SERVERFAULT; } memset(&host, 0, sizeof(fsal_multipath_member_t)); host.addr = ntohl(deviceid->device_id4); host.port = 2049; host.proto = 6; nfs_status = FSAL_encode_v4_multipath(da_addr_body, 1, &host); if (nfs_status != NFS4_OK) { LogMajor(COMPONENT_PNFS, "Failed to encode data server address"); return nfs_status; } /** @todo: Here information about Data-Server where file resides * is only send from MDS.If that Data-Server is down then * read or write will performed through MDS. * Instead should we send the information about all * the available data-servers, so that these fops will * always performed through Data-Servers. * (Like in replicated volume contains more than ONE DS) */ return NFS4_OK; } /** * @brief Get list of available devices * * We do not support listing devices and just set EOF without doing * anything. * * @param[in] export_pub Export handle * @param[in] type Type of layout to get devices for * @param[in] cb Function taking device ID halves * @param[in,out] res In/out and output arguments of the function * * @return Valid error codes in RFC 5661, pp. 365-6. */ static nfsstat4 getdevicelist(struct fsal_export *export_pub, layouttype4 type, void *opaque, bool (*cb)(void *opaque, const uint64_t id), struct fsal_getdevicelist_res *res) { res->eof = true; return NFS4_OK; } void handle_ops_pnfs(struct fsal_obj_ops *ops) { ops->layoutget = pnfs_layout_get; ops->layoutreturn = pnfs_layout_return; ops->layoutcommit = pnfs_layout_commit; } void fsal_ops_pnfs(struct fsal_ops *ops) { ops->getdeviceinfo = getdeviceinfo; ops->fs_da_addr_size = fs_da_addr_size; } void export_ops_pnfs(struct export_ops *ops) { ops->getdevicelist = getdevicelist; ops->fs_layouttypes = fs_layouttypes; ops->fs_layout_blocksize = fs_layout_blocksize; ops->fs_maximum_segments = fs_maximum_segments; ops->fs_loc_body_size = fs_loc_body_size; } /* * * Calculates a hash value for a given string buffer */ uint32_t superfasthash(const unsigned char *data, uint32_t len) { uint32_t hash = len, tmp; int32_t rem; rem = len & 3; len >>= 2; /* Main loop */ for (; len > 0; len--) { hash += get16bits(data); tmp = (get16bits(data + 2) << 11) ^ hash; hash = (hash << 16) ^ tmp; data += 2 * sizeof(uint16_t); hash += hash >> 11; } /* Handle end cases */ switch (rem) { case 3: hash += get16bits(data); hash ^= hash << 16; hash ^= data[sizeof(uint16_t)] << 18; hash += hash >> 11; break; case 2: hash += get16bits(data); hash ^= hash << 11; hash += hash >> 17; break; case 1: hash += *data; hash ^= hash << 10; hash += hash >> 1; } /* Force "avalanching" of final 127 bits */ hash ^= hash << 3; hash += hash >> 5; hash ^= hash << 4; hash += hash >> 17; hash ^= hash << 25; hash += hash >> 6; return hash; } /** * It will extract hostname from pathinfo.PATH_INFO_KEYS gives * details about all the servers and path in that server where * file resides. * First it selects the DS based on distributed hashing, then * with the help of some basic string manipulations, the hostname * can be fetched from the pathinfo * * Returns zero and valid hostname on success */ int select_ds(struct glfs_object *object, char *pathinfo, char *hostname, size_t size) { /* Represents starting of each server in the list*/ static const char posix[10] = "POSIX"; /* Array of pathinfo of available dses */ char *ds_path_info[MAX_DS_COUNT]; /* Key for hashing */ unsigned char key[16]; /* Starting of first brick path in the pathinfo */ char *tmp = NULL; /* Stores starting of hostname */ char *start = NULL; /* Stores ending of hostname */ char *end = NULL; int ret = -1; int i = 0; /* counts no of available ds */ int no_of_ds = 0; if (!pathinfo || !size) goto out; tmp = pathinfo; while ((tmp = strstr(tmp, posix))) { ds_path_info[no_of_ds] = tmp; tmp++; no_of_ds++; /* * * If no of dses reaches maximum count, then * perform load balance on current list */ if (no_of_ds == MAX_DS_COUNT) break; } if (no_of_ds == 0) { LogCrit(COMPONENT_PNFS, "Invalid pathinfo(%s) attribute found while selecting DS.", pathinfo); goto out; } ret = glfs_h_extract_handle(object, key, GFAPI_HANDLE_LENGTH); if (ret < 0) goto out; /* Pick DS from the list */ if (no_of_ds == 1) ret = 0; else ret = superfasthash(key, 16) % no_of_ds; start = strchr(ds_path_info[ret], ':'); if (!start) goto out; end = start + 1; end = strchr(end, ':'); if (start == end) goto out; memset(hostname, 0, size); while (++start != end) hostname[i++] = *start; ret = 0; LogDebug(COMPONENT_PNFS, "hostname %s", hostname); out: return ret; } /* * The data server address will be send from here * * The information about the first server present * in the PATH_INFO_KEY will be returned, since * entire file is consistent over the servers * (Striped volumes are not considered right now) * * On success, returns zero with ip address of * the server will be send */ int glfs_get_ds_addr(struct glfs *fs, struct glfs_object *object, uint32_t *ds_addr) { int ret = 0; char pathinfo[1024] = { 0, }; char hostname[256] = { 0, }; struct addrinfo hints = { 0, }; struct addrinfo *res = NULL; const char *pathinfokey = "trusted.glusterfs.pathinfo"; ret = glfs_h_getxattrs(fs, object, pathinfokey, pathinfo, sizeof(pathinfo)); LogDebug(COMPONENT_PNFS, "pathinfo %s", pathinfo); ret = select_ds(object, pathinfo, hostname, sizeof(hostname)); if (ret) { LogMajor(COMPONENT_PNFS, "No DS found"); goto out; } hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_INET; ret = getaddrinfo(hostname, NULL, &hints, &res); /* we trust getaddrinfo() never returns EAI_AGAIN! */ if (ret != 0) { *ds_addr = 0; LogMajor(COMPONENT_PNFS, "error %s\n", gai_strerror(ret)); goto out; } if (isDebug(COMPONENT_PNFS)) { char scratch[SOCK_NAME_MAX]; struct display_buffer dspbuf = { sizeof(scratch), scratch, scratch }; display_sockip(&dspbuf, (sockaddr_t *)res->ai_addr); LogDebug(COMPONENT_PNFS, "ip address : %s", scratch); } *ds_addr = ((struct sockaddr_in *)(res->ai_addr))->sin_addr.s_addr; out: freeaddrinfo(res); return ret; } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/000077500000000000000000000000001473756622300164555ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/CMakeLists.txt000066400000000000000000000034741473756622300212250ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- if(USE_DBUS) include_directories( ${DBUS_INCLUDE_DIRS} ) endif(USE_DBUS) include_directories("${PROJECT_SOURCE_DIR}/FSAL/FSAL_GPFS") ########### next target ############### SET(fsalgpfs_LIB_SRCS main.c export.c handle.c file.c fsal_up.c fsal_ds.c fsal_mds.c fsal_unlink.c fsal_symlinks.c fsal_rename.c fsal_create.c fsal_fileop.c fsal_attrs.c fsal_lock.c fsal_lookup.c fsal_convert.c fsal_internal.c gpfsext.c fsal_stats_gpfs.c ) add_library(fsalgpfs MODULE ${fsalgpfs_LIB_SRCS}) add_sanitizers(fsalgpfs) target_link_libraries(fsalgpfs ganesha_nfsd ${SYSTEM_LIBRARIES} ${LDFLAG_DISALLOW_UNDEF} ) set_target_properties(fsalgpfs PROPERTIES VERSION 4.2.0 SOVERSION 4) ########### install files ############### install(TARGETS fsalgpfs COMPONENT fsal DESTINATION ${FSAL_DESTINATION} ) nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/README000066400000000000000000000013101473756622300173300ustar00rootroot00000000000000= FSAL_GPFS = The following is an experimental FSAL for use with IBM's GPFS filesystem. Eventually we hope to use the open-by-handle mechanism that's being developed for upstream Linux. There is currently nothing GPFS specific in the code base, though we expect that as more advanced nfsv4 permissions are supported there will be some calls directly into GPFS due to the handling of RichACLs in the Linux kernel. In the short term we're using a kernel module that provides a character driver to the same effect to prove out this approach. This kernel is in the "kernel" subdirectory, though has only been tested on a 2.6.18 kernel. Use with other versions is not recommended, and will probably not work. nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/export.c000066400000000000000000000524771473756622300201610ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /** @file export.c * @brief GPFS FSAL module export functions. * * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #include "config.h" #include #include /* used for 'dirname' */ #include #include #include #include #include #include #include "fsal.h" #include "fsal_internal.h" #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_config.h" #include "FSAL/fsal_localfs.h" #include "gpfs_methods.h" #include "nfs_exports.h" #include "export_mgr.h" #include "pnfs_utils.h" #include "include/gpfs.h" /* export object methods */ static void release(struct fsal_export *exp_hdl) { struct gpfs_fsal_export *myself = container_of(exp_hdl, struct gpfs_fsal_export, export); unclaim_all_export_maps(exp_hdl); fsal_detach_export(exp_hdl->fsal, &exp_hdl->exports); free_export_ops(exp_hdl); close(myself->export_fd); gsh_free(myself); /* elvis has left the building */ } static fsal_status_t get_dynamic_info(struct fsal_export *exp_hdl, struct fsal_obj_handle *obj_hdl, fsal_dynamicfsinfo_t *infop) { fsal_status_t status; struct statfs buffstatgpfs; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; if (!infop) { fsal_error = ERR_FSAL_FAULT; goto out; } status = GPFSFSAL_statfs(export_fd, obj_hdl, &buffstatgpfs); if (FSAL_IS_ERROR(status)) return status; infop->total_bytes = buffstatgpfs.f_frsize * buffstatgpfs.f_blocks; infop->free_bytes = buffstatgpfs.f_frsize * buffstatgpfs.f_bfree; infop->avail_bytes = buffstatgpfs.f_frsize * buffstatgpfs.f_bavail; infop->total_files = buffstatgpfs.f_files; infop->free_files = buffstatgpfs.f_ffree; infop->avail_files = buffstatgpfs.f_ffree; infop->maxread = buffstatgpfs.f_bsize; infop->maxwrite = buffstatgpfs.f_bsize; infop->time_delta.tv_sec = 0; infop->time_delta.tv_nsec = FSAL_DEFAULT_TIME_DELTA_NSEC; out: return fsalstat(fsal_error, 0); } static attrmask_t fs_supported_attrs(struct fsal_export *exp_hdl) { attrmask_t supported_mask; struct gpfs_fsal_export *gpfs_export; gpfs_export = container_of(exp_hdl, struct gpfs_fsal_export, export); supported_mask = fsal_supported_attrs(&exp_hdl->fsal->fs_info); /* Fixup supported_mask to indicate if ACL is actually supported for * this export. */ if (gpfs_export->use_acl) supported_mask |= ATTR_ACL; else supported_mask &= ~ATTR_ACL; return supported_mask; } /* get_quota * return quotas for this export. * path could cross a lower mount boundary which could * mask lower mount values with those of the export root * if this is a real issue, we can scan each time with setmntent() * better yet, compare st_dev of the file with st_dev of root_fd. * on linux, can map st_dev -> /proc/partitions name -> /dev/ */ static fsal_status_t get_quota(struct fsal_export *exp_hdl, const char *filepath, int quota_type, int quota_id, fsal_quota_t *fsal_quota) { gpfs_quotaInfo_t gpfs_quota = { 0 }; struct stat path_stat; int retval = 0; struct quotactl_arg args; struct fsal_filesystem *fs = container_of(exp_hdl, struct gpfs_fsal_export, export)->root_fs; if (stat(filepath, &path_stat) < 0) { retval = errno; LogMajor(COMPONENT_FSAL, "GPFS get quota, stat: root_path: %s, errno=(%d) %s", fs->path, retval, strerror(retval)); return fsalstat(posix2fsal_error(retval), retval); } if ((major(path_stat.st_dev) != fs->dev.major) || (minor(path_stat.st_dev) != fs->dev.minor)) { LogMajor( COMPONENT_FSAL, "GPFS get quota: crossed mount boundary! root_path: %s, quota path: %s", fs->path, filepath); return fsalstat(ERR_FSAL_FAULT, 0); /* maybe a better error? */ } args.pathname = filepath; args.cmd = GPFS_QCMD(Q_GETQUOTA, quota_type); args.qid = quota_id; args.bufferP = &gpfs_quota; args.cli_ip = NULL; if (op_ctx && op_ctx->client) args.cli_ip = op_ctx->client->hostaddr_str; fsal_set_credentials(&op_ctx->creds); if (gpfs_ganesha(OPENHANDLE_QUOTA, &args) < 0) retval = errno; fsal_restore_ganesha_credentials(); if (retval) return fsalstat(posix2fsal_error(retval), retval); fsal_quota->bhardlimit = gpfs_quota.blockHardLimit; fsal_quota->bsoftlimit = gpfs_quota.blockSoftLimit; fsal_quota->curblocks = gpfs_quota.blockUsage; fsal_quota->fhardlimit = gpfs_quota.inodeHardLimit; fsal_quota->fsoftlimit = gpfs_quota.inodeSoftLimit; fsal_quota->curfiles = gpfs_quota.inodeUsage; fsal_quota->btimeleft = gpfs_quota.blockGraceTime; fsal_quota->ftimeleft = gpfs_quota.inodeGraceTime; fsal_quota->bsize = 1024; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* set_quota * same lower mount restriction applies */ static fsal_status_t set_quota(struct fsal_export *exp_hdl, const char *filepath, int quota_type, int quota_id, fsal_quota_t *fsal_quota, fsal_quota_t *res_quota) { gpfs_quotaInfo_t gpfs_quota = { 0 }; struct stat path_stat; int retval = 0; struct quotactl_arg args; struct fsal_filesystem *fs = container_of(exp_hdl, struct gpfs_fsal_export, export)->root_fs; if (stat(filepath, &path_stat) < 0) { retval = errno; LogMajor(COMPONENT_FSAL, "GPFS set quota, stat: root_path: %s, errno=(%d) %s", fs->path, retval, strerror(retval)); return fsalstat(posix2fsal_error(retval), retval); } if ((major(path_stat.st_dev) != fs->dev.major) || (minor(path_stat.st_dev) != fs->dev.minor)) { LogMajor( COMPONENT_FSAL, "GPFS set quota: crossed mount boundary! root_path: %s, quota path: %s", fs->path, filepath); return fsalstat(ERR_FSAL_FAULT, 0); /* maybe a better error? */ } gpfs_quota.blockHardLimit = fsal_quota->bhardlimit; gpfs_quota.blockSoftLimit = fsal_quota->bsoftlimit; gpfs_quota.inodeHardLimit = fsal_quota->fhardlimit; gpfs_quota.inodeSoftLimit = fsal_quota->fsoftlimit; gpfs_quota.blockGraceTime = fsal_quota->btimeleft; gpfs_quota.inodeGraceTime = fsal_quota->ftimeleft; args.pathname = filepath; args.cmd = GPFS_QCMD(Q_SETQUOTA, quota_type); args.qid = quota_id; args.bufferP = &gpfs_quota; args.cli_ip = NULL; if (op_ctx && op_ctx->client) args.cli_ip = op_ctx->client->hostaddr_str; fsal_set_credentials(&op_ctx->creds); if (gpfs_ganesha(OPENHANDLE_QUOTA, &args) < 0) retval = errno; fsal_restore_ganesha_credentials(); if (retval) return fsalstat(posix2fsal_error(retval), retval); if (res_quota != NULL) return get_quota(exp_hdl, filepath, quota_type, quota_id, res_quota); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* extract a file handle from a buffer. * do verification checks and flag any and all suspicious bits. * Return an updated fh_desc into whatever was passed. The most * common behavior, done here is to just reset the length. */ static fsal_status_t gpfs_wire_to_host(struct fsal_export *exp_hdl, fsal_digesttype_t in_type, struct gsh_buffdesc *fh_desc, int flags) { struct gpfs_file_handle *hdl; size_t fh_size; /* sanity checks */ if (!fh_desc || !fh_desc->addr) return fsalstat(ERR_FSAL_FAULT, 0); hdl = (struct gpfs_file_handle *)fh_desc->addr; if (flags & FH_FSAL_BIG_ENDIAN) { #if (BYTE_ORDER != BIG_ENDIAN) hdl->handle_size = bswap_16(hdl->handle_size); hdl->handle_type = bswap_16(hdl->handle_type); hdl->handle_version = bswap_16(hdl->handle_version); hdl->handle_key_size = bswap_16(hdl->handle_key_size); #endif } else { #if (BYTE_ORDER == BIG_ENDIAN) hdl->handle_size = bswap_16(hdl->handle_size); hdl->handle_type = bswap_16(hdl->handle_type); hdl->handle_version = bswap_16(hdl->handle_version); hdl->handle_key_size = bswap_16(hdl->handle_key_size); #endif } fh_size = gpfs_sizeof_handle(hdl); LogFullDebug( COMPONENT_FSAL, "flags 0x%X size %d type %d ver %d key_size %d FSID 0x%X:%X fh_size %zu", flags, hdl->handle_size, hdl->handle_type, hdl->handle_version, hdl->handle_key_size, hdl->handle_fsid[0], hdl->handle_fsid[1], fh_size); /* Some older file handles include additional 16 bytes in fh_desc->len. * Honor those as well. */ if (fh_desc->len != fh_size && fh_desc->len != fh_size + 16) { LogMajor(COMPONENT_FSAL, "Size mismatch for handle. should be %zu, got %zu", fh_size, fh_desc->len); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } fh_desc->len = hdl->handle_size; /* pass back the size */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* Produce handle-key from a host-handle */ static fsal_status_t gpfs_host_to_key(struct fsal_export *exp_hdl, struct gsh_buffdesc *fh_desc) { struct gpfs_file_handle *hdl; if (fh_desc->len < offsetof(struct gpfs_file_handle, f_handle)) return fsalstat(ERR_FSAL_INVAL, 0); hdl = (struct gpfs_file_handle *)fh_desc->addr; fh_desc->len = hdl->handle_key_size; /* pass back the key size */ LogFullDebug(COMPONENT_FSAL, "size %d type %d ver %d key_size %d FSID 0x%X:%X", hdl->handle_size, hdl->handle_type, hdl->handle_version, hdl->handle_key_size, hdl->handle_fsid[0], hdl->handle_fsid[1]); return fsalstat(ERR_FSAL_NO_ERROR, 0); } void gpfs_free_state(struct state_t *state) { struct gpfs_fd *my_fd; my_fd = &container_of(state, struct gpfs_state_fd, state)->gpfs_fd; destroy_fsal_fd(&my_fd->fsal_fd); gsh_free(state); } /** * @brief Allocate a state_t structure * * Note that this is not expected to fail since memory allocation is * expected to abort on failure. * * @param[in] exp_hdl Export state_t will be associated with * @param[in] state_type Type of state to allocate * @param[in] related_state Related state if appropriate * * @returns a state structure. */ struct state_t *gpfs_alloc_state(struct fsal_export *exp_hdl, enum state_type state_type, struct state_t *related_state) { struct state_t *state; struct gpfs_fd *my_fd; state = init_state(gsh_calloc(1, sizeof(struct gpfs_state_fd)), gpfs_free_state, state_type, related_state); my_fd = &container_of(state, struct gpfs_state_fd, state)->gpfs_fd; init_fsal_fd(&my_fd->fsal_fd, FSAL_FD_STATE, op_ctx->fsal_export); my_fd->fd = -1; return state; } /** * @brief Function to get the fasl_obj_handle that has fsal_fd as its global fd. * * @param[in] exp_hdl The export in which the handle exists * @param[in] fd File descriptor in question * @param[out] handle FSAL object handle * * @return the fsal_obj_handle. */ void get_fsal_obj_hdl(struct fsal_export *exp_hdl, struct fsal_fd *fd, struct fsal_obj_handle **handle) { struct gpfs_fd *my_fd = NULL; struct gpfs_fsal_obj_handle *myself = NULL; my_fd = container_of(fd, struct gpfs_fd, fsal_fd); myself = container_of(my_fd, struct gpfs_fsal_obj_handle, u.file.fd); *handle = &myself->obj_handle; } /** * @brief overwrite vector entries with the methods that we support * @param ops type of struct export_ops */ void gpfs_export_ops_init(struct export_ops *ops) { ops->release = release; ops->lookup_path = gpfs_lookup_path; ops->wire_to_host = gpfs_wire_to_host; ops->host_to_key = gpfs_host_to_key; ops->create_handle = gpfs_create_handle; ops->get_fs_dynamic_info = get_dynamic_info; ops->fs_supported_attrs = fs_supported_attrs; ops->get_quota = get_quota; ops->set_quota = set_quota; ops->alloc_state = gpfs_alloc_state; ops->get_fsal_obj_hdl = get_fsal_obj_hdl; } static void free_gpfs_filesystem(struct gpfs_filesystem *gpfs_fs) { if (gpfs_fs->root_fd >= 0) close(gpfs_fs->root_fd); gsh_free(gpfs_fs); } /** * @brief Extract major from fsid * @param fh GPFS file handle * @param fsid FSAL ID */ void gpfs_extract_fsid(struct gpfs_file_handle *fh, struct fsal_fsid__ *fsid) { memcpy(&fsid->major, fh->handle_fsid, sizeof(fsid->major)); fsid->minor = 0; } /** * @brief Open root fd * @param gpfs_fs GPFS filesystem * @return 0(zero) on success, otherwise error. */ int open_root_fd(struct gpfs_filesystem *gpfs_fs) { struct fsal_fsid__ fsid; int retval; fsal_status_t status; struct gpfs_file_handle fh = { 0 }; gpfs_fs->root_fd = open(gpfs_fs->fs->path, O_RDONLY | O_DIRECTORY); if (gpfs_fs->root_fd < 0) { retval = errno; LogMajor(COMPONENT_FSAL, "Could not open GPFS mount point %s: rc = %s (%d)", gpfs_fs->fs->path, strerror(retval), retval); return retval; } LogFullDebug(COMPONENT_FSAL, "root export_fd %d path %s", gpfs_fs->root_fd, gpfs_fs->fs->path); status = fsal_internal_get_handle_at( gpfs_fs->root_fd, gpfs_fs->fs->path, &fh, gpfs_fs->root_fd); if (FSAL_IS_ERROR(status)) { retval = status.minor; LogMajor(COMPONENT_FSAL, "Get root handle for %s failed with %s (%d)", gpfs_fs->fs->path, strerror(retval), retval); goto errout; } gpfs_extract_fsid(&fh, &fsid); retval = re_index_fs_fsid(gpfs_fs->fs, GPFS_FSID_TYPE, &fsid); if (retval == 0) return 0; LogCrit(COMPONENT_FSAL, "Could not re-index GPFS file system fsid for %s, error:%d", gpfs_fs->fs->path, retval); assert(retval < 0); retval = -retval; errout: close(gpfs_fs->root_fd); gpfs_fs->root_fd = -1; return retval; } /** * @brief Claim GPFS filesystem * @param fs FSAL filesystem * @param exp FSAL export * @return 0(zero) on success, otherwise error. */ int gpfs_claim_filesystem(struct fsal_filesystem *fs, struct fsal_export *exp, void **private_data) { struct gpfs_filesystem *gpfs_fs = NULL; int retval; pthread_attr_t attr_thr; int rc; struct gpfs_fsal_export *myself; LogFilesystem("GPFS CLAIM FS", "", fs); if (strcmp(fs->type, "gpfs") != 0) { LogEvent(COMPONENT_FSAL, "Attempt to claim non-GPFS filesystem %s", fs->path); return ENXIO; } myself = container_of(exp, struct gpfs_fsal_export, export); myself->export_fd = open(CTX_FULLPATH(op_ctx), O_RDONLY | O_DIRECTORY); if (myself->export_fd < 0) { retval = errno; LogMajor(COMPONENT_FSAL, "Could not open GPFS export point %s: rc = %s (%d)", CTX_FULLPATH(op_ctx), strerror(retval), retval); goto errout; } if (*private_data != NULL) { /* Already claimed, and private_data is already set, nothing to * do here. */ LogDebug( COMPONENT_FSAL, "file system %s is already claimed with fd %d private_data %p", fs->path, (int)(long)*private_data, *private_data); return 0; } /* first export by GPFS */ gpfs_fs = gsh_calloc(1, sizeof(*gpfs_fs)); gpfs_fs->root_fd = -1; gpfs_fs->fs = fs; LogFullDebug(COMPONENT_FSAL, "export_fd %d path %s", myself->export_fd, CTX_FULLPATH(op_ctx)); /* Get an fd for the root and create an upcall thread */ retval = open_root_fd(gpfs_fs); if (retval != 0) { if (retval == ENOTTY) { LogInfo(COMPONENT_FSAL, "file system %s is not exportable with %s", fs->path, exp->fsal->name); retval = ENXIO; } goto errout; } gpfs_fs->stop_thread = false; PTHREAD_ATTR_init(&attr_thr); PTHREAD_ATTR_setscope(&attr_thr, PTHREAD_SCOPE_SYSTEM); PTHREAD_ATTR_setdetachstate(&attr_thr, PTHREAD_CREATE_JOINABLE); PTHREAD_ATTR_setstacksize(&attr_thr, 2116488); rc = pthread_create(&gpfs_fs->up_thread, &attr_thr, GPFSFSAL_UP_Thread, gpfs_fs); PTHREAD_ATTR_destroy(&attr_thr); if (rc != 0) { retval = errno; LogCrit(COMPONENT_THREAD, "Could not create GPFSFSAL_UP_Thread, error = %d (%s)", retval, strerror(retval)); goto errout; } *private_data = gpfs_fs; return 0; errout: if (myself->export_fd >= 0) { close(myself->export_fd); myself->export_fd = -1; } if (gpfs_fs != NULL) free_gpfs_filesystem(gpfs_fs); return retval; } /** * @brief Unclaim filesystem * @param fs FSAL filesystem */ void gpfs_unclaim_filesystem(struct fsal_filesystem *fs) { struct gpfs_filesystem *gpfs_fs = fs->private_data; struct callback_arg callback = { 0 }; int reason = THREAD_STOP; if (gpfs_fs == NULL) goto out; /* Terminate GPFS upcall thread */ callback.mountdirfd = gpfs_fs->root_fd; callback.reason = &reason; if (gpfs_ganesha(OPENHANDLE_THREAD_UPDATE, &callback)) LogCrit(COMPONENT_FSAL, "Unable to stop upcall thread for %s, fd=%d, errno=%d", fs->path, gpfs_fs->root_fd, errno); else LogFullDebug(COMPONENT_FSAL, "Thread STOP successful"); /* Prior to calling pthread_join(), we should set stop_thread=true. * Its not being set atomically because the synchronization requirement * is not critical enough. */ gpfs_fs->stop_thread = true; pthread_join(gpfs_fs->up_thread, NULL); free_gpfs_filesystem(gpfs_fs); fs->private_data = NULL; out: LogInfo(COMPONENT_FSAL, "GPFS Unclaiming %s", fs->path); } /* GPFS FSAL export config */ static struct config_item export_params[] = { CONF_ITEM_NOOP("name"), CONF_ITEM_BOOL("ignore_mode_change", false, gpfs_fsal_export, ignore_mode_change), CONFIG_EOL }; static struct config_block export_param = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.gpfs-export%d", .blk_desc.name = "FSAL", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = export_params, .blk_desc.u.blk.commit = noop_conf_commit }; /** * @brief create_export * * Create an export point and return a handle to it to be kept * in the export list. * First lookup the fsal, then create the export and then put the fsal back. * returns the export with one reference taken. * * @return FSAL status */ fsal_status_t gpfs_create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops) { /* The status code to return */ fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct gpfs_fsal_export *gpfs_exp; struct fsal_export *exp; int rc; gpfs_exp = gsh_calloc(1, sizeof(struct gpfs_fsal_export)); exp = &gpfs_exp->export; glist_init(&gpfs_exp->filesystems); status.minor = fsal_internal_version(); LogInfo(COMPONENT_FSAL, "GPFS get version is %d options 0x%X id %d", status.minor, op_ctx->export_perms.options, op_ctx->ctx_export->export_id); fsal_export_init(exp); gpfs_export_ops_init(&exp->exp_ops); /* Load GPFS FSAL specific export config */ rc = load_config_from_node(parse_node, &export_param, gpfs_exp, true, err_type); if (rc != 0) { LogCrit(COMPONENT_FSAL, "Incorrect or missing parameters for export %s", CTX_FULLPATH(op_ctx)); status.major = ERR_FSAL_INVAL; goto free; } status.minor = fsal_attach_export(fsal_hdl, &exp->exports); if (status.minor != 0) { status.major = posix2fsal_error(status.minor); goto free; /* seriously bad */ } exp->fsal = fsal_hdl; exp->up_ops = up_ops; op_ctx->fsal_export = exp; status.minor = resolve_posix_filesystem(CTX_FULLPATH(op_ctx), fsal_hdl, exp, gpfs_claim_filesystem, gpfs_unclaim_filesystem, &gpfs_exp->root_fs); if (status.minor != 0) { LogCrit(COMPONENT_FSAL, "resolve_posix_filesystem(%s) returned %s (%d)", CTX_FULLPATH(op_ctx), strerror(status.minor), status.minor); status.major = posix2fsal_error(status.minor); goto detach; } /* if the nodeid has not been obtained, get it now */ if (!g_nodeid) { struct gpfs_filesystem *gpfs_fs = gpfs_exp->root_fs->private_data; struct grace_period_arg gpa; int nodeid; gpa.mountdirfd = gpfs_fs->root_fd; nodeid = gpfs_ganesha(OPENHANDLE_GET_NODEID, &gpa); if (nodeid > 0) { g_nodeid = nodeid; LogFullDebug(COMPONENT_FSAL, "nodeid %d", g_nodeid); } else LogCrit(COMPONENT_FSAL, "OPENHANDLE_GET_NODEID failed rc %d", nodeid); } gpfs_exp->pnfs_ds_enabled = exp->exp_ops.fs_supports(exp, fso_pnfs_ds_supported); gpfs_exp->pnfs_mds_enabled = exp->exp_ops.fs_supports(exp, fso_pnfs_mds_supported); if (gpfs_exp->pnfs_ds_enabled) { struct fsal_pnfs_ds *pds = NULL; status = fsal_hdl->m_ops.create_fsal_pnfs_ds(fsal_hdl, parse_node, &pds); if (status.major != ERR_FSAL_NO_ERROR) goto unexport; /* special case: server_id matches export_id */ pds->id_servers = op_ctx->ctx_export->export_id; pds->mds_export = op_ctx->ctx_export; pds->mds_fsal_export = op_ctx->fsal_export; if (!pnfs_ds_insert(pds)) { LogCrit(COMPONENT_CONFIG, "Server id %d already in use.", pds->id_servers); status.major = ERR_FSAL_EXIST; /* Return the ref taken by create_fsal_pnfs_ds */ pnfs_ds_put(pds); goto unexport; } LogInfo(COMPONENT_FSAL, "gpfs_fsal_create: pnfs ds was enabled for [%s]", CTX_FULLPATH(op_ctx)); export_ops_pnfs(&exp->exp_ops); } gpfs_exp->use_acl = !op_ctx_export_has_option(EXPORT_OPTION_DISABLE_ACL); return status; unexport: unclaim_all_export_maps(exp); detach: fsal_detach_export(fsal_hdl, &exp->exports); free: free_export_ops(exp); gsh_free(gpfs_exp); return status; } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/file.c000066400000000000000000001340541473756622300175470ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /** @file file.c * @brief GPFS FSAL module file I/O functions * * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /* _FILE_OFFSET_BITS macro causes F_GETLK/SETLK/SETLKW to be defined to * F_GETLK64/SETLK64/SETLKW64. Currently GPFS kernel module doesn't work * with these 64 bit macro values through ganesha interface. Undefine it * here to use plain F_GETLK/SETLK/SETLKW values. */ #undef _FILE_OFFSET_BITS #include #include "fsal.h" #include "FSAL/access_check.h" #include "FSAL/fsal_localfs.h" #include "fsal_convert.h" #include "fsal_internal.h" #include #include #include "gpfs_methods.h" #define STATE2FD(s) (&container_of(s, struct gpfs_state_fd, state)->gpfs_fd) extern uint64_t get_handle2inode(struct gpfs_file_handle *gfh); /** * @brief GPFS Function to open or reopen a fsal_fd. * * @param[in] obj_hdl File on which to operate * @param[in] openflags New mode for open * @param[out] fsal_fd File descriptor that is to be used * * @return FSAL status. */ fsal_status_t gpfs_reopen_func(struct fsal_obj_handle *obj_hdl, fsal_openflags_t openflags, struct fsal_fd *fsal_fd) { fsal_status_t status, status2; struct gpfs_fd *my_fd; int posix_flags = 0; int fd; my_fd = container_of(fsal_fd, struct gpfs_fd, fsal_fd); fsal2posix_openflags(openflags, &posix_flags); LogFullDebug(COMPONENT_FSAL, "my_fd->fd = %d openflags = %x, posix_flags = %x", my_fd->fd, openflags, posix_flags); status = GPFSFSAL_open(obj_hdl, posix_flags, &fd); if (FSAL_IS_ERROR(status)) return status; if (my_fd->fd != -1) { /* File was previously open, close old fd */ status2 = fsal_internal_close(my_fd->fd, NULL, 0); if (FSAL_IS_ERROR(status2)) { LogFullDebug(COMPONENT_FSAL, "close failed with %s", fsal_err_txt(status)); /** @todo - what to do about error here... */ } } /* Save the file descriptor, make sure we only save the * open modes that actually represent the open file. */ LogFullDebug(COMPONENT_FSAL, "fd = %d, new openflags = %x", fd, openflags); if (fd == 0) LogCrit(COMPONENT_FSAL, "fd = %d, new openflags = %x", fd, openflags); my_fd->fd = fd; my_fd->fsal_fd.openflags = FSAL_O_NFS_FLAGS(openflags); return status; } /** * @brief GPFS Function to close a fsal_fd. * * @param[in] obj_hdl File on which to operate * @param[in] fsal_fd File handle to close * * @return FSAL status. */ fsal_status_t gpfs_close_func(struct fsal_obj_handle *obj_hdl, struct fsal_fd *fsal_fd) { fsal_status_t status; struct gpfs_fd *my_fd; my_fd = container_of(fsal_fd, struct gpfs_fd, fsal_fd); status = fsal_internal_close(my_fd->fd, NULL, 0); my_fd->fd = -1; my_fd->fsal_fd.openflags = FSAL_O_CLOSED; return status; } /** * @brief Merge a duplicate handle with an original handle * * This function is used if an upper layer detects that a duplicate * object handle has been created. It allows the FSAL to merge anything * from the duplicate back into the original. * * The caller must release the object (the caller may have to close * files if the merge is unsuccessful). * * @param[in] orig_hdl Original handle * @param[in] dupe_hdl Handle to merge into original * * @return FSAL status. * */ fsal_status_t gpfs_merge(struct fsal_obj_handle *orig_hdl, struct fsal_obj_handle *dupe_hdl) { fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; if (orig_hdl->type == REGULAR_FILE && dupe_hdl->type == REGULAR_FILE) { /* We need to merge the share reservations on this file. * This could result in ERR_FSAL_SHARE_DENIED. */ struct gpfs_fsal_obj_handle *orig, *dupe; orig = container_of(orig_hdl, struct gpfs_fsal_obj_handle, obj_handle); dupe = container_of(dupe_hdl, struct gpfs_fsal_obj_handle, obj_handle); /* This can block over an I/O operation. */ status = merge_share(orig_hdl, &orig->u.file.share, &dupe->u.file.share); } return status; } static fsal_status_t open_by_handle(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, fsal_verifier_t verifier, struct fsal_attrlist *attrs_out) { struct fsal_export *export = op_ctx->fsal_export; struct gpfs_fsal_obj_handle *gpfs_hdl; struct gpfs_filesystem *gpfs_fs = obj_hdl->fs->private_data; fsal_status_t status; bool truncated = openflags & FSAL_O_TRUNC; struct gpfs_fd *my_fd; struct fsal_fd *fsal_fd; fsal_openflags_t old_openflags; bool is_fresh_open = false; gpfs_hdl = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); if (state != NULL) { my_fd = &container_of(state, struct gpfs_state_fd, state) ->gpfs_fd; } else { /* We need to use the global fd to continue. */ my_fd = &gpfs_hdl->u.file.fd; } fsal_fd = &my_fd->fsal_fd; /* Indicate we want to do fd work (can't fail since not reclaiming) */ fsal_start_fd_work_no_reclaim(fsal_fd); old_openflags = my_fd->fsal_fd.openflags; if (state != NULL) { /* Prepare to take the share reservation, but only if we are * called with a valid state (if state is NULL the caller is a * stateless create such as NFS v3 CREATE and we're just going * to ignore share reservation stuff). */ /* Now that we have the mutex, and no I/O is in progress so we * have exclusive access to the share's fsal_fd, we can look at * its openflags. We also need to work the share reservation so * take the obj_lock. NOTE: This is the ONLY sequence where both * a work_mutex and the obj_lock are taken, so there is no * opportunity for ABBA deadlock. * * Note that we do hold the obj_lock over an open and a close * which is longer than normal, but the previous iteration of * the code held the obj lock (read granted) over whole I/O * operations... We don't block over I/O because we've assured * that no I/O is in progress or can start before proceeding * past the above while loop. */ PTHREAD_RWLOCK_wrlock(&obj_hdl->obj_lock); /* Now check the new share. */ status = check_share_conflict(&gpfs_hdl->u.file.share, openflags, false); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "check_share_conflict returned %s", fsal_err_txt(status)); goto exit; } } /* Check for a genuine no-op open. That means we aren't trying to * create, the file is already open in the same mode with the same * deny flags, and we aren't trying to truncate. In this case we want * to avoid bouncing the fd. In the case of JUST changing the deny mode * or an replayed exclusive create, we might bounce the fd when we could * have avoided that, but those scenarios are much less common. */ if (FSAL_O_NFS_FLAGS(openflags) == FSAL_O_NFS_FLAGS(old_openflags) && truncated == false && createmode == FSAL_NO_CREATE) { LogFullDebug(COMPONENT_FSAL, "no-op reopen2 my_fd->fd = %d openflags = %x", my_fd->fd, openflags); goto exit; } /* Tracking if we were going to reopen a fd that was * closed by another thread before we got here. */ is_fresh_open = ((old_openflags == FSAL_O_CLOSED) && (my_fd->fd < 0)); /* No share conflict, re-open the share fd */ status = gpfs_reopen_func(obj_hdl, openflags, fsal_fd); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "gpfs_reopen_func returned %s", fsal_err_txt(status)); goto exit; } /* Inserts to fd_lru only if open succeeds */ if (is_fresh_open) { /* This is actually an open, need to increment * appropriate counter and insert into LRU. */ insert_fd_lru(fsal_fd); } else { /* Bump up the FD in fd_lru as it was already in fd lru. */ bump_fd_lru(fsal_fd); } if (createmode >= FSAL_EXCLUSIVE || (truncated && attrs_out)) { /* NOTE: won't come in here when called from gpfs_reopen2... * truncated might be set, but attrs_out will be NULL. */ /* Refresh the attributes */ status = GPFSFSAL_getattrs(export, gpfs_fs, gpfs_hdl->handle, attrs_out); if (!FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "New size = %" PRIx64, attrs_out->filesize); /* Now check verifier for exclusive */ if (createmode >= FSAL_EXCLUSIVE && !check_verifier_attrlist(attrs_out, verifier, false)) { /* Verifier didn't match, return EEXIST */ status = fsalstat(posix2fsal_error(EEXIST), EEXIST); } } } else if (attrs_out && attrs_out->request_mask & ATTR_RDATTR_ERR) { attrs_out->valid_mask = ATTR_RDATTR_ERR; } if (FSAL_IS_ERROR(status)) { if (is_fresh_open) { /* Now that we have decided to close this FD, * let's clean it off from fd_lru and * ensure counters are decremented. */ remove_fd_lru(fsal_fd); } /* Close fd */ (void)gpfs_close_func(obj_hdl, fsal_fd); } exit: if (state != NULL) { if (!FSAL_IS_ERROR(status)) { /* Success, establish the new share. */ update_share_counters(&gpfs_hdl->u.file.share, old_openflags, openflags); } /* Release obj_lock. */ PTHREAD_RWLOCK_unlock(&obj_hdl->obj_lock); } /* Indicate we are done with fd work and signal any waiters. */ fsal_complete_fd_work(fsal_fd); return status; } static fsal_status_t open_by_name(struct fsal_obj_handle *obj_hdl, struct state_t *state, const char *name, fsal_openflags_t openflags, fsal_verifier_t verifier, struct fsal_attrlist *attrs_out) { struct fsal_obj_handle *temp = NULL; fsal_status_t status; /* We don't have open by name... */ status = obj_hdl->obj_ops->lookup(obj_hdl, name, &temp, NULL); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "lookup returned %s", fsal_err_txt(status)); return status; } if (temp->type != REGULAR_FILE) { if (temp->type == DIRECTORY) { /* Trying to open2 a directory */ status = fsalstat(ERR_FSAL_ISDIR, 0); } else { /* Trying to open2 any other non-regular file */ status = fsalstat(ERR_FSAL_SYMLINK, 0); } /* Release the object we found by lookup. */ temp->obj_ops->release(temp); LogFullDebug(COMPONENT_FSAL, "open returned %s", fsal_err_txt(status)); return status; } status = open_by_handle(temp, state, openflags, FSAL_NO_CREATE, verifier, attrs_out); if (FSAL_IS_ERROR(status)) { /* Release the object we found by lookup. */ temp->obj_ops->release(temp); LogFullDebug(COMPONENT_FSAL, "open returned %s", fsal_err_txt(status)); } return status; } /** * @brief Open a file descriptor for read or write and possibly create * * This function opens a file for read or write, possibly creating it. * If the caller is passing a state, it must hold the state_lock * exclusive. * * state can be NULL which indicates a stateless open (such as via the * NFS v3 CREATE operation), in which case the FSAL must assure protection * of any resources. If the file is being created, such protection is * simple since no one else will have access to the object yet, however, * in the case of an exclusive create, the common resources may still need * protection. * * If Name is NULL, obj_hdl is the file itself, otherwise obj_hdl is the * parent directory. * * On an exclusive create, the upper layer may know the object handle * already, so it MAY call with name == NULL. In this case, the caller * expects just to check the verifier. * * On a call with an existing object handle for an UNCHECKED create, * we can set the size to 0. * * At least the mode attribute must be set if createmode is not FSAL_NO_CREATE. * Some FSALs may still have to pass a mode on a create call for exclusive, * and even with FSAL_NO_CREATE, and empty set of attributes MUST be passed. * * If an open by name succeeds and did not result in Ganesha creating a file, * the caller will need to do a subsequent permission check to confirm the * open. This is because the permission attributes were not available * beforehand. * * @param[in] obj_hdl File to open or parent directory * @param[in,out] state state_t to use for this operation * @param[in] openflags Mode for open * @param[in] createmode Mode for create * @param[in] name Name for file if being created or opened * @param[in] attr_set Attributes to set on created file * @param[in] verifier Verifier to use for exclusive create * @param[in,out] new_obj Newly created object * @param[in,out] attrs_out Newly created object attributes * @param[in,out] caller_perm_check The caller must do a permission check * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @return FSAL status. */ fsal_status_t gpfs_open2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attr_set, fsal_verifier_t verifier, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct gpfs_fsal_obj_handle *hdl = NULL; struct fsal_export *export = op_ctx->fsal_export; struct gpfs_file_handle fh; int posix_flags = 0; bool created = false; fsal_status_t status; mode_t unix_mode; LogAttrlist(COMPONENT_FSAL, NIV_FULL_DEBUG, "attrs ", attr_set, false); fsal2posix_openflags(openflags, &posix_flags); if (createmode >= FSAL_EXCLUSIVE) /* Now fixup attrs for verifier if exclusive create */ set_common_verifier(attr_set, verifier, false); if (name == NULL) { status = open_by_handle(obj_hdl, state, openflags, createmode, verifier, attrs_out); *caller_perm_check = FSAL_IS_SUCCESS(status); return status; } /* In this path where we are opening by name, we can't check share * reservation yet since we don't have an object_handle yet. If we * indeed create the object handle (there is no race with another * open by name), then there CAN NOT be a share conflict, otherwise * the share conflict will be resolved when the object handles are * merged. */ /* Non creation case, libgpfs doesn't have open by name so we * have to do a lookup and then handle as an open by handle. */ if (createmode == FSAL_NO_CREATE) { /** @todo FSF - this is very suspicious that we don't return * a new fsal_obj_handle for the newly opened * file... */ status = open_by_name(obj_hdl, state, name, openflags, verifier, attrs_out); *caller_perm_check = FSAL_IS_SUCCESS(status); return status; } /** @todo: to proceed past here, we need a struct fsal_attrlist in order * to create the fsal_obj_handle, so if it actually is NULL (it * will actually never be since mdcache will always ask for * attributes) we really should create a temporary * fsal_attrlist... */ posix_flags |= O_CREAT; /* And if we are at least FSAL_GUARDED, do an O_EXCL create. */ if (createmode >= FSAL_GUARDED) posix_flags |= O_EXCL; /* Fetch the mode attribute to use in the openat system call. */ unix_mode = fsal2unix_mode(attr_set->mode) & ~export->exp_ops.fs_umask(export); /* Don't set the mode if we later set the attributes */ FSAL_UNSET_MASK(attr_set->valid_mask, ATTR_MODE); if (createmode == FSAL_UNCHECKED && (attr_set->valid_mask != 0)) { /* If we have FSAL_UNCHECKED and want to set more attributes * than the mode, we attempt an O_EXCL create first, if that * succeeds, then we will be allowed to set the additional * attributes, otherwise, we don't know we created the file * and this can NOT set the attributes. */ posix_flags |= O_EXCL; } status = GPFSFSAL_create2(obj_hdl, name, unix_mode, &fh, posix_flags, attrs_out); if (status.major == ERR_FSAL_EXIST && createmode == FSAL_UNCHECKED && (posix_flags & O_EXCL) != 0) { /* If we tried to create O_EXCL to set attributes and * failed. Remove O_EXCL and retry, also remember not * to set attributes. We still try O_CREAT again just * in case file disappears out from under us. * * Note that because we have dropped O_EXCL, later on we * will not assume we created the file, and thus will * not set additional attributes. We don't need to * separately track the condition of not wanting to set * attributes. */ posix_flags &= ~O_EXCL; status = GPFSFSAL_create2(obj_hdl, name, unix_mode, &fh, posix_flags, attrs_out); } if (FSAL_IS_ERROR(status)) return status; /* Remember if we were responsible for creating the file. * Note that in an UNCHECKED retry we MIGHT have re-created the * file and won't remember that. Oh well, so in that rare case we * leak a partially created file if we have a subsequent error in here. * Since we were able to do the permission check even if we were not * creating the file, let the caller know the permission check has * already been done. Note it IS possible in the case of a race between * an UNCHECKED open and an external unlink, we did create the file. */ created = (posix_flags & O_EXCL) != 0; *caller_perm_check = false; /* Check if the object type is SYMBOLIC_LINK for a state object. * If yes, then give error ERR_FSAL_SYMLINK. */ if (state != NULL && attrs_out != NULL && attrs_out->type != REGULAR_FILE) { LogDebug(COMPONENT_FSAL, "Trying to open a non-regular file"); if (attrs_out->type == DIRECTORY) { /* Trying to open2 a directory */ status = fsalstat(ERR_FSAL_ISDIR, 0); } else { /* Trying to open2 any other non-regular file */ status = fsalstat(ERR_FSAL_SYMLINK, 0); } goto fileerr; } /* allocate an obj_handle and fill it up */ hdl = alloc_handle(&fh, obj_hdl->fs, attrs_out, NULL, export); if (hdl == NULL) { status = fsalstat(posix2fsal_error(ENOMEM), ENOMEM); goto fileerr; } *new_obj = &hdl->obj_handle; if (created && attr_set->valid_mask != 0) { /* Set attributes using our newly opened file descriptor as the * share_fd if there are any left to set (mode and truncate * have already been handled). * * Note that we only set the attributes if we were responsible * for creating the file. */ status = (*new_obj)->obj_ops->setattr2(*new_obj, false, state, attr_set); if (FSAL_IS_ERROR(status)) goto fileerr; if (attrs_out != NULL) { status = (*new_obj)->obj_ops->getattrs(*new_obj, attrs_out); if (FSAL_IS_ERROR(status) && (attrs_out->request_mask & ATTR_RDATTR_ERR) == 0) /* Get attributes failed and caller expected * to get the attributes. Otherwise continue * with attrs_out indicating ATTR_RDATTR_ERR. */ goto fileerr; } } /* We created a file with the caller's credentials active, so as such * permission check was done. So we don't need the caller to do * permission check again (for that we have already set * *caller_perm_check=false). */ return open_by_handle(&hdl->obj_handle, state, openflags, createmode, verifier, attrs_out); fileerr: if (hdl != NULL) { /* Release the handle we just allocated. */ (*new_obj)->obj_ops->release(*new_obj); *new_obj = NULL; } if (created) { fsal_status_t status2; /* Remove the file we just created */ status2 = GPFSFSAL_unlink(obj_hdl, name); if (FSAL_IS_ERROR(status2)) { LogEvent(COMPONENT_FSAL, "GPFSFSAL_unlink failed, error: %s", msg_fsal_err(status2.major)); } } if (FSAL_IS_ERROR(status)) { struct gpfs_file_handle *gfh = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle) ->handle; LogDebug(COMPONENT_FSAL, "Inode involved: %" PRIu64 ", error: %s", get_handle2inode(gfh), msg_fsal_err(status.major)); } return status; } /** * @brief GPFS read plus * * @param obj_hdl FSAL object handle / or fd * @param offset The offset to read from * @param buffer_size Size of buffer * @param buffer void reference to buffer * @param read_amount size_t reference to amount of data read * @param end_of_file boolean indiocating the end of file * @param info I/O information * @return FSAL status */ fsal_status_t gpfs_read_plus_fd(int my_fd, uint64_t offset, size_t buffer_size, void *buffer, size_t *read_amount, bool *end_of_file, struct io_info *info, int expfd) { const fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct read_arg rarg = { 0 }; ssize_t nb_read; int errsv; if (!buffer || !read_amount || !end_of_file || !info) return fsalstat(ERR_FSAL_FAULT, 0); assert(my_fd >= 3); rarg.mountdirfd = expfd; rarg.fd = my_fd; rarg.bufP = buffer; rarg.offset = offset; rarg.length = buffer_size; rarg.options = IO_SKIP_HOLE; rarg.cli_ip = NULL; if (op_ctx && op_ctx->client) rarg.cli_ip = op_ctx->client->hostaddr_str; nb_read = gpfs_ganesha(OPENHANDLE_READ_BY_FD, &rarg); errsv = errno; if (nb_read < 0) { if (errsv == EUNATCH) LogFatal(COMPONENT_FSAL, "GPFS Returned EUNATCH"); if (errsv != ENODATA) return fsalstat(posix2fsal_error(errsv), errsv); /* errsv == ENODATA */ #if 0 /** @todo FSF: figure out how to fix this... */ if ((buffer_size + offset) > myself->attributes.filesize) { if (offset >= myself->attributes.filesize) *read_amount = 0; else *read_amount = myself->attributes.filesize - offset; info->io_content.hole.di_length = *read_amount; } else { *read_amount = buffer_size; info->io_content.hole.di_length = buffer_size; } #endif info->io_content.what = NFS4_CONTENT_HOLE; info->io_content.hole.di_offset = offset; } else { info->io_content.what = NFS4_CONTENT_DATA; info->io_content.data.d_offset = offset + nb_read; info->io_content.data.d_data.data_len = nb_read; info->io_content.data.d_data.data_val = buffer; *read_amount = nb_read; } if (nb_read != -1 && (nb_read == 0 || nb_read < buffer_size)) *end_of_file = true; else *end_of_file = false; return status; } /** * @brief Return open status of a state. * * This function returns open flags representing the current open * status for a state. The st_lock must be held. * * @param[in] obj_hdl File on which to operate * @param[in] state File state to interrogate * * @retval Flags representing current open status */ fsal_openflags_t gpfs_status2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { struct gpfs_fd *my_fd = &((struct gpfs_state_fd *)state)->gpfs_fd; return my_fd->fsal_fd.openflags; } /** * @brief Re-open a file that may be already opened * * This function supports changing the access mode of a share reservation and * thus should only be called with a share state. The st_lock must be held. * * This MAY be used to open a file the first time if there is no need for * open by name or create semantics. One example would be 9P lopen. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] openflags Mode for re-open * * @return FSAL status. */ fsal_status_t gpfs_reopen2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags) { return open_by_handle(obj_hdl, state, openflags, FSAL_NO_CREATE, NULL, NULL); } /** * @brief Read data from a file * * This function reads data from the given file. The FSAL must be able to * perform the read whether a state is presented or not. This function also * is expected to handle properly bypassing or not share reservations. This is * an (optionally) asynchronous call. When the I/O is complete, the done * callback is called with the results. * * @note This does not handle iovecs larger than 1 * * @param[in] obj_hdl File on which to operate * @param[in] bypass If state doesn't indicate a share reservation, * bypass any deny read * @param[in,out] done_cb Callback to call when I/O is done * @param[in,out] read_arg Info about read, passed back in callback * @param[in,out] caller_arg Opaque arg from the caller for callback * * @return Nothing; results are in callback */ void gpfs_read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg) { fsal_status_t status, status2; struct gpfs_fd *my_fd; struct gpfs_fd temp_fd = { FSAL_FD_INIT, -1 }; struct fsal_fd *out_fd; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; struct gpfs_fsal_obj_handle *myself; int i; uint64_t offset = read_arg->offset; myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); if (obj_hdl->fsal != obj_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", obj_hdl->fsal->name, obj_hdl->fs->fsal->name); done_cb(obj_hdl, fsalstat(posix2fsal_error(EXDEV), EXDEV), read_arg, caller_arg); return; } /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->u.file.fd.fsal_fd, &temp_fd.fsal_fd, read_arg->state, FSAL_O_READ, false, NULL, bypass, &myself->u.file.share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct gpfs_fd, fsal_fd); read_arg->io_amount = 0; for (i = 0; i < read_arg->iov_count; i++) { size_t io_amount; if (read_arg->info) status = gpfs_read_plus_fd(my_fd->fd, offset, read_arg->iov[i].iov_len, read_arg->iov[i].iov_base, &io_amount, &read_arg->end_of_file, read_arg->info, export_fd); else status = GPFSFSAL_read( my_fd->fd, offset, read_arg->iov[i].iov_len, read_arg->iov[i].iov_base, &io_amount, &read_arg->end_of_file, export_fd); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "Inode involved: %" PRIu64 ", error: %s", get_handle2inode(myself->handle), fsal_err_txt(status)); break; } offset += read_arg->iov[i].iov_len; read_arg->io_amount += io_amount; } status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (read_arg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->u.file.share, FSAL_O_READ, FSAL_O_CLOSED); } exit: done_cb(obj_hdl, status, read_arg, caller_arg); } /** * @brief Write data to a file * * This function writes data to a file. The FSAL must be able to * perform the write whether a state is presented or not. This function also * is expected to handle properly bypassing or not share reservations. Even * with bypass == true, it will enforce a mandatory (NFSv4) deny_write if * an appropriate state is not passed). * * The FSAL is expected to enforce sync if necessary. * * @param[in] obj_hdl File on which to operate * @param[in] bypass If state doesn't indicate a share reservation, * bypass any non-mandatory deny write * @param[in,out] done_cb Callback to call when I/O is done * @param[in,out] write_arg Info about write, passed back in callback * @param[in,out] caller_arg Opaque arg from the caller for callback */ void gpfs_write2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *caller_arg) { fsal_status_t status, status2; struct gpfs_fd *my_fd; struct gpfs_fd temp_fd = { FSAL_FD_INIT, -1 }; struct fsal_fd *out_fd; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; struct gpfs_fsal_obj_handle *myself; int i; uint64_t offset = write_arg->offset; myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); if (obj_hdl->fsal != obj_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", obj_hdl->fsal->name, obj_hdl->fs->fsal->name); done_cb(obj_hdl, fsalstat(posix2fsal_error(EXDEV), EXDEV), write_arg, caller_arg); return; } /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->u.file.fd.fsal_fd, &temp_fd.fsal_fd, write_arg->state, FSAL_O_WRITE, false, NULL, bypass, &myself->u.file.share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct gpfs_fd, fsal_fd); write_arg->io_amount = 0; for (i = 0; i < write_arg->iov_count; i++) { size_t io_amount; status = GPFSFSAL_write(my_fd->fd, offset, write_arg->iov[i].iov_len, write_arg->iov[i].iov_base, &io_amount, &write_arg->fsal_stable, export_fd); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "Inode involved: %" PRIu64 ", error: %s", get_handle2inode(myself->handle), fsal_err_txt(status)); break; } offset += write_arg->iov[i].iov_len; write_arg->io_amount += io_amount; } status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (write_arg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->u.file.share, FSAL_O_WRITE, FSAL_O_CLOSED); } exit: done_cb(obj_hdl, status, write_arg, caller_arg); } fsal_status_t gpfs_fallocate(struct fsal_obj_handle *obj_hdl, state_t *state, uint64_t offset, uint64_t length, bool allocate) { fsal_status_t status, status2; struct gpfs_fd *my_fd; struct gpfs_fd temp_fd = { FSAL_FD_INIT, -1 }; struct fsal_fd *out_fd; struct gpfs_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); if (obj_hdl->fsal != obj_hdl->fs->fsal) { LogMajor( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", obj_hdl->fsal->name, obj_hdl->fs->fsal->name); return fsalstat(posix2fsal_error(EXDEV), EXDEV); } /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->u.file.fd.fsal_fd, &temp_fd.fsal_fd, state, FSAL_O_WRITE, false, NULL, false, &myself->u.file.share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct gpfs_fd, fsal_fd); status = GPFSFSAL_alloc(my_fd->fd, offset, length, allocate); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "Inode involved: %" PRIu64 ", error: %s", get_handle2inode(myself->handle), fsal_err_txt(status)); } status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->u.file.share, FSAL_O_WRITE, FSAL_O_CLOSED); } exit: return status; } static fsal_status_t gpfs_commit_fd(int my_fd, struct gpfs_fsal_obj_handle *myself, off_t offset, size_t len) { struct fsync_arg arg = { 0 }; verifier4 writeverf = { 0 }; int retval; assert(my_fd >= 3); arg.mountdirfd = my_fd; arg.handle = myself->handle; arg.offset = offset; arg.length = len; arg.verifier4 = (int32_t *)&writeverf; if (gpfs_ganesha(OPENHANDLE_FSYNC, &arg) == -1) { retval = errno; if (retval == EUNATCH) LogFatal(COMPONENT_FSAL, "GPFS Returned EUNATCH"); return fsalstat(posix2fsal_error(retval), retval); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Commit written data * * This function flushes possibly buffered data to a file. This method * differs from commit due to the need to interact with share reservations * and the fact that the FSAL manages the state of "file descriptors". The * FSAL must be able to perform this operation without being passed a specific * state. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] offset Start of range to commit * @param[in] len Length of range to commit * * @return FSAL status. */ fsal_status_t gpfs_commit2(struct fsal_obj_handle *obj_hdl, off_t offset, size_t len) { fsal_status_t status, status2; struct gpfs_fd *my_fd; struct gpfs_fd temp_fd = { FSAL_FD_INIT, -1 }; struct fsal_fd *out_fd; struct gpfs_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); /* Make sure file is open in appropriate mode. * Do not check share reservation. */ status = fsal_start_global_io(&out_fd, obj_hdl, &myself->u.file.fd.fsal_fd, &temp_fd.fsal_fd, FSAL_O_ANY, false, NULL); if (FSAL_IS_ERROR(status)) return status; my_fd = container_of(out_fd, struct gpfs_fd, fsal_fd); fsal_set_credentials(&op_ctx->creds); status = gpfs_commit_fd(my_fd->fd, myself, offset, len); fsal_restore_ganesha_credentials(); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "Inode involved: %" PRIu64 ", error: %s", get_handle2inode(myself->handle), fsal_err_txt(status)); } status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); /* We did not do share reservation stuff... */ return status; } /** * @brief Perform a lock operation * * This function performs a lock operation (lock, unlock, test) on a * file. This method assumes the FSAL is able to support lock owners, * though it need not support asynchronous blocking locks. Passing the * lock state allows the FSAL to associate information with a specific * lock owner for each file (which may include use of a "file descriptor". * * For FSAL_VFS etc. we ignore owner, implicitly we have a lock_fd per * lock owner (i.e. per state). * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] owner Lock owner * @param[in] lock_op Operation to perform * @param[in] req_lock Lock to take/release/test * @param[out] conflicting_lock Conflicting lock * * @return FSAL status. */ fsal_status_t gpfs_lock_op2(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *owner, fsal_lock_op_t lock_op, fsal_lock_param_t *req_lock, fsal_lock_param_t *conflicting_lock) { struct fsal_export *export = op_ctx->fsal_export; struct glock glock_args; struct set_get_lock_arg gpfs_sg_arg; fsal_openflags_t openflags; fsal_status_t status, status2; struct gpfs_fd *my_fd; struct gpfs_fd temp_fd = { FSAL_FD_INIT, -1 }; struct fsal_fd *out_fd; bool bypass = false; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; struct gpfs_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); LogFullDebug( COMPONENT_FSAL, "Locking: op:%d sle_type:%d type:%d start:%llu length:%llu owner:%p", lock_op, req_lock->lock_sle_type, req_lock->lock_type, (unsigned long long)req_lock->lock_start, (unsigned long long)req_lock->lock_length, owner); if (obj_hdl == NULL) { LogCrit(COMPONENT_FSAL, "obj_hdl arg is NULL."); return fsalstat(ERR_FSAL_FAULT, 0); } if (owner == NULL) { LogCrit(COMPONENT_FSAL, "owner arg is NULL."); return fsalstat(ERR_FSAL_FAULT, 0); } if (conflicting_lock == NULL && lock_op == FSAL_OP_LOCKT) { LogDebug( COMPONENT_FSAL, "Conflicting_lock argument can't be NULL with lock_op = LOCKT"); return fsalstat(ERR_FSAL_FAULT, 0); } if (lock_op != FSAL_OP_LOCKT && state == NULL) { LogCrit(COMPONENT_FSAL, "Non TEST operation with NULL state"); return fsalstat(posix2fsal_error(EINVAL), EINVAL); } /* flock.l_len being signed long integer, larger lock ranges may * get mapped to negative values. As per 'man 3 fcntl', posix * locks can accept negative l_len values which may lead to * unlocking an unintended range. Better bail out to prevent that. */ if (req_lock->lock_length > LONG_MAX) { LogCrit(COMPONENT_FSAL, "Requested lock length is out of range- MAX(%lu), req_lock_length(%" PRIu64 ")", LONG_MAX, req_lock->lock_length); return fsalstat(ERR_FSAL_BAD_RANGE, 0); } switch (req_lock->lock_type) { case FSAL_LOCK_R: glock_args.flock.l_type = F_RDLCK; openflags = FSAL_O_READ; break; case FSAL_LOCK_W: glock_args.flock.l_type = F_WRLCK; openflags = FSAL_O_WRITE; break; default: LogDebug( COMPONENT_FSAL, "ERROR: The requested lock type was not read or write."); return fsalstat(ERR_FSAL_NOTSUPP, 0); } switch (lock_op) { case FSAL_OP_LOCKT: /* We may end up using global fd, don't fail on a deny mode */ bypass = true; glock_args.cmd = F_GETLK; openflags = FSAL_O_ANY; break; case FSAL_OP_UNLOCK: glock_args.flock.l_type = F_UNLCK; glock_args.cmd = F_SETLK; openflags = FSAL_O_ANY; break; case FSAL_OP_LOCK: glock_args.cmd = F_SETLK; break; case FSAL_OP_LOCKB: glock_args.cmd = F_SETLKW; break; case FSAL_OP_CANCEL: glock_args.cmd = GPFS_F_CANCELLK; openflags = FSAL_O_ANY; break; default: LogDebug( COMPONENT_FSAL, "ERROR: Lock operation requested was not TEST, GET, or SET."); return fsalstat(ERR_FSAL_NOTSUPP, 0); } /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->u.file.fd.fsal_fd, &temp_fd.fsal_fd, state, openflags, true, NULL, bypass, &myself->u.file.share); if (FSAL_IS_ERROR(status)) { LogCrit(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct gpfs_fd, fsal_fd); glock_args.lfd = my_fd->fd; glock_args.flock.l_len = req_lock->lock_length; glock_args.flock.l_start = req_lock->lock_start; glock_args.flock.l_whence = SEEK_SET; glock_args.lock_owner = owner; gpfs_sg_arg.lock = &glock_args; gpfs_sg_arg.reclaim = req_lock->lock_reclaim; gpfs_sg_arg.mountdirfd = export_fd; gpfs_sg_arg.cli_ip = NULL; if (op_ctx && op_ctx->client) gpfs_sg_arg.cli_ip = op_ctx->client->hostaddr_str; status = GPFSFSAL_lock_op(export, lock_op, req_lock, conflicting_lock, &gpfs_sg_arg); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "Inode involved: %" PRIu64 ", error: %s", get_handle2inode(myself->handle), fsal_err_txt(status)); } status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->u.file.share, openflags, FSAL_O_CLOSED); } exit: return status; } /** * @brief Seek to data or hole * * This function seek to data or hole in a file. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in,out] info Information about the data * * @return FSAL status. */ fsal_status_t gpfs_seek2(struct fsal_obj_handle *obj_hdl, struct state_t *state, struct io_info *info) { off_t offset = info->io_content.hole.di_offset; fsal_status_t status, status2; struct fsal_attrlist attrs; struct gpfs_fd *my_fd; struct gpfs_fd temp_fd = { FSAL_FD_INIT, -1 }; struct fsal_fd *out_fd; struct gpfs_io_info io_info = { 0 }; struct fseek_arg arg = { 0 }; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; struct gpfs_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); io_info.io_offset = info->io_content.hole.di_offset; switch (info->io_content.what) { case NFS4_CONTENT_DATA: io_info.io_what = SEEK_DATA; break; case NFS4_CONTENT_HOLE: io_info.io_what = SEEK_HOLE; break; default: return fsalstat(ERR_FSAL_UNION_NOTSUPP, 0); } /** @todo: WARNING there's a size check that isn't atomic, Fixing this * would probably mean taking the object lock, but that would * not be entirely safe since we no longer use the object lock * to proect I/O on the object... */ /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->u.file.fd.fsal_fd, &temp_fd.fsal_fd, state, FSAL_O_ANY, false, NULL, true, NULL); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct gpfs_fd, fsal_fd); arg.mountdirfd = export_fd; arg.openfd = my_fd->fd; arg.info = &io_info; fsal_prepare_attrs(&attrs, (op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) & ~(ATTR_ACL | ATTR4_FS_LOCATIONS))); status = GPFSFSAL_getattrs(op_ctx->fsal_export, obj_hdl->fs->private_data, myself->handle, &attrs); fsal_release_attrs(&attrs); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "GPFSFSAL_getattrs failed returning %s", fsal_err_txt(status)); goto out; } /* RFC7862 15.11.3, * If the sa_offset is beyond the end of the file, * then SEEK MUST return NFS4ERR_NXIO. */ if (offset >= attrs.filesize) { status = posix2fsal_status(ENXIO); LogFullDebug(COMPONENT_FSAL, "offset >= file size, returning %s", fsal_err_txt(status)); goto out; } if (gpfs_ganesha(OPENHANDLE_SEEK_BY_FD, &arg) == -1) { if (errno == EUNATCH) LogFatal(COMPONENT_FSAL, "GPFS Returned EUNATCH"); status = posix2fsal_status(errno); goto out; } info->io_eof = io_info.io_eof; info->io_content.hole.di_offset = io_info.io_offset; info->io_content.hole.di_length = io_info.io_len; out: status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); /* We did FSAL_O_ANY so no share reservation was acquired */ exit: return status; } /** * @brief GPFS IO advise * * @param obj_hdl FSAL object handle * @param io_hints I/O information * @return FSAL status * */ fsal_status_t gpfs_io_advise(struct fsal_obj_handle *obj_hdl, struct io_hints *hints) { struct gpfs_fsal_obj_handle *myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); struct fadvise_arg arg = { 0 }; assert(myself->u.file.fd.fd >= 3 && myself->u.file.fd.fsal_fd.openflags != FSAL_O_CLOSED); arg.mountdirfd = myself->u.file.fd.fd; arg.openfd = myself->u.file.fd.fd; arg.offset = hints->offset; arg.length = hints->count; arg.hints = &hints->hints; if (gpfs_ganesha(OPENHANDLE_FADVISE_BY_FD, &arg) == -1) { if (errno == EUNATCH) LogFatal(COMPONENT_FSAL, "GPFS Returned EUNATCH"); hints->hints = 0; return fsalstat(posix2fsal_error(errno), 0); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Close the file if it is still open. * * @param obj_hdl FSAL object handle * @return FSAL status */ fsal_status_t gpfs_close(struct fsal_obj_handle *obj_hdl) { struct gpfs_fsal_obj_handle *myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); assert(obj_hdl->type == REGULAR_FILE); return close_fsal_fd(obj_hdl, &myself->u.file.fd.fsal_fd, false); } /** * @brief Manage closing a file when a state is no longer needed. * * When the upper layers are ready to dispense with a state, this method is * called to allow the FSAL to close any file descriptors or release any other * resources associated with the state. A call to free_state should be assumed * to follow soon. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * * @return FSAL status. */ fsal_status_t gpfs_close2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { struct gpfs_fsal_obj_handle *myself; fsal_status_t status; struct gpfs_fd *my_fd = &container_of(state, struct gpfs_state_fd, state)->gpfs_fd; LogFullDebug(COMPONENT_FSAL, "state %p", state); myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); if (state->state_type == STATE_TYPE_SHARE || state->state_type == STATE_TYPE_NLM_SHARE || state->state_type == STATE_TYPE_9P_FID) { /* This is a share state, we must update the share counters */ update_share_counters_locked(obj_hdl, &myself->u.file.share, my_fd->fsal_fd.openflags, FSAL_O_CLOSED); } status = close_fsal_fd(obj_hdl, &my_fd->fsal_fd, false); if (FSAL_IS_ERROR(status)) { struct gpfs_file_handle *gfh = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle) ->handle; LogDebug(COMPONENT_FSAL, "Inode involved: %" PRIu64 ", error: %s", get_handle2inode(gfh), msg_fsal_err(status.major)); } return status; } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/fsal_attrs.c000066400000000000000000000316441473756622300207730ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /** @file fsal_attrs.c * @brief GPFS FSAL attribute functions * * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ #include "config.h" #include "fsal.h" #include "fsal_internal.h" #include "fsal_convert.h" #include "gpfs_methods.h" #include #include #include #include #include "export_mgr.h" #include "FSAL/fsal_localfs.h" extern fsal_status_t fsal_acl_2_gpfs_acl(struct fsal_obj_handle *, fsal_acl_t *, gpfsfsal_xstat_t *, gpfs_acl_t *acl_buf, unsigned int acl_buflen); /** * @brief Get fs_locations attribute for the object specified by its * filehandle. * * @param export FSAL export * @param gpfs_fs GPFS filesystem * @param gpfs_fh GPFS file handle * @param attrs Object attributes (fs_locations is initialized * on a successful return) * @return FSAL status * */ fsal_status_t GPFSFSAL_fs_loc(struct fsal_export *export, struct gpfs_filesystem *gpfs_fs, struct gpfs_file_handle *gpfs_fh, struct fsal_attrlist *attrs) { char root[MAXPATHLEN]; char path[MAXPATHLEN]; char server[MAXHOSTNAMELEN]; int errsv, rc; struct fs_loc_arg loc_arg; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; loc_arg.fs_root = root; loc_arg.fs_root_len = sizeof(root); loc_arg.fs_path = path; loc_arg.fs_path_len = sizeof(path); loc_arg.fs_server = server; loc_arg.fs_server_len = sizeof(server); loc_arg.mountdirfd = export_fd; loc_arg.handle = gpfs_fh; rc = gpfs_ganesha(OPENHANDLE_FS_LOCATIONS, &loc_arg); errsv = errno; LogDebug(COMPONENT_FSAL, "gpfs_ganesha: FS_LOCATIONS returned, rc %d errsv %d", rc, errsv); if (rc) return fsalstat(ERR_FSAL_ATTRNOTSUPP, 0); // Release old fs locations if any nfs4_fs_locations_release(attrs->fs_locations); attrs->fs_locations = nfs4_fs_locations_new(root, path, 1); attrs->fs_locations->nservers = 1; attrs->fs_locations->server[0].utf8string_len = strlen(server); attrs->fs_locations->server[0].utf8string_val = gsh_memdup(server, strlen(server)); LogDebug(COMPONENT_FSAL, "gpfs_ganesha: FS_LOCATIONS root=%s path=%s server=%s", root, path, server); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Get attributes for the object specified by its filehandle. * * @param export FSAL export * @param gpfs_fs GPFS filesystem * @param gpfs_fh GPFS file handle * @param obj_attr Object attributes * @return FSAL status */ fsal_status_t GPFSFSAL_getattrs(struct fsal_export *export, struct gpfs_filesystem *gpfs_fs, struct gpfs_file_handle *gpfs_fh, struct fsal_attrlist *obj_attr) { fsal_status_t st; gpfsfsal_xstat_t buffxstat; bool expire; uint32_t expire_time_attr = 0; /*< Expiration time for attributes. */ struct gpfs_fsal_export *gpfs_export; gpfs_acl_t *acl_buf; unsigned int acl_buflen; bool use_acl; int retry; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; /* Initialize fsal_fsid to 0.0 in case older GPFS */ buffxstat.fsal_fsid.major = 0; buffxstat.fsal_fsid.minor = 0; expire = op_ctx->export_perms.expire_time_attr > 0; gpfs_export = container_of(export, struct gpfs_fsal_export, export); /* Let us make the first request using the acl buffer that * is part of buffxstat itself. If that is not sufficient, * we allocate from heap and retry. */ use_acl = obj_attr->request_mask & ATTR_ACL; for (retry = 0; retry < GPFS_ACL_MAX_RETRY; retry++) { switch (retry) { case 0: /* first attempt */ acl_buf = (gpfs_acl_t *)buffxstat.buffacl; acl_buflen = GPFS_ACL_BUF_SIZE; break; case 1: /* first retry, don't free the old stack buffer */ acl_buflen = acl_buf->acl_len; acl_buf = gsh_malloc(acl_buflen); break; default: /* second or later retry, free the old heap buffer */ acl_buflen = acl_buf->acl_len; gsh_free(acl_buf); acl_buf = gsh_malloc(acl_buflen); break; } st = fsal_get_xstat_by_handle(export_fd, gpfs_fh, &buffxstat, acl_buf, acl_buflen, &expire_time_attr, expire, use_acl); if (FSAL_IS_ERROR(st) || !use_acl || acl_buflen >= acl_buf->acl_len) break; } if (retry == GPFS_ACL_MAX_RETRY) { /* make up an error */ LogCrit(COMPONENT_FSAL, "unable to get ACLs"); st = fsalstat(ERR_FSAL_SERVERFAULT, 0); } if (FSAL_IS_ERROR(st)) goto error; /* convert attributes */ if (expire_time_attr != 0) obj_attr->expire_time_attr = expire_time_attr; /* Assume if fsid = 0.0, then old GPFS didn't fill it in, in that * case, fill in from the object's filesystem. */ if (buffxstat.fsal_fsid.major == 0 && buffxstat.fsal_fsid.minor == 0) buffxstat.fsal_fsid = gpfs_fs->fs->fsid; st = gpfsfsal_xstat_2_fsal_attributes(&buffxstat, obj_attr, acl_buf, gpfs_export->use_acl); error: if (FSAL_IS_ERROR(st)) { if (obj_attr->request_mask & ATTR_RDATTR_ERR) { /* Caller asked for error to be visible. */ obj_attr->valid_mask = ATTR_RDATTR_ERR; } } /* Free acl buffer if we allocated on heap */ if (acl_buflen != GPFS_ACL_BUF_SIZE) { assert(acl_buf != (gpfs_acl_t *)buffxstat.buffacl); gsh_free(acl_buf); } return st; } /** * @brief Get fs attributes for the object specified by its filehandle. * * @param mountdirfd Mounted filesystem * @param obj_hdl Object handle * @param buf reference to statfs structure * @return FSAL status * */ fsal_status_t GPFSFSAL_statfs(int mountdirfd, struct fsal_obj_handle *obj_hdl, struct statfs *buf) { int rc; struct statfs_arg sarg; struct gpfs_fsal_obj_handle *myself; int errsv; myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); sarg.handle = myself->handle; sarg.mountdirfd = mountdirfd; sarg.buf = buf; rc = gpfs_ganesha(OPENHANDLE_STATFS_BY_FH, &sarg); errsv = errno; LogFullDebug(COMPONENT_FSAL, "OPENHANDLE_STATFS_BY_FH returned: rc %d", rc); if (rc < 0) { if (errsv == EUNATCH) LogFatal(COMPONENT_FSAL, "GPFS Returned EUNATCH"); return fsalstat(posix2fsal_error(errsv), errsv); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Set attributes for the object specified by its filehandle. * * @param dir_hdl The handle of the object to get parameters. * @param obj_attr The post operation attributes for the object. * As input, it defines the attributes that the caller * wants to retrieve (by positioning flags into this structure) * and the output is built considering this input * (it fills the structure according to the flags it contains). * May be NULL. * @return FSAL status */ fsal_status_t GPFSFSAL_setattrs(struct fsal_obj_handle *dir_hdl, struct fsal_attrlist *obj_attr) { fsal_status_t status; struct gpfs_fsal_obj_handle *myself; gpfsfsal_xstat_t buffxstat; struct gpfs_fsal_export *gpfs_export; gpfs_acl_t *acl_buf = NULL; unsigned int acl_buflen = 0; bool use_acl; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; /* Indicate if stat or acl or both should be changed. */ int attr_valid = 0; /* Indicate which attribute in stat should be changed. */ int attr_changed = 0; myself = container_of(dir_hdl, struct gpfs_fsal_obj_handle, obj_handle); gpfs_export = container_of(op_ctx->fsal_export, struct gpfs_fsal_export, export); use_acl = gpfs_export->use_acl; /* First, check that FSAL attributes changes are allowed. */ /* Is it allowed to change times ? */ if (!op_ctx->fsal_export->exp_ops.fs_supports(op_ctx->fsal_export, fso_cansettime)) { if (obj_attr->valid_mask & (ATTR_ATIME | ATTR_CREATION | ATTR_CTIME | ATTR_MTIME | ATTR_MTIME_SERVER | ATTR_ATIME_SERVER)) { /* handled as an unsettable attribute. */ return fsalstat(ERR_FSAL_INVAL, 0); } } /* apply umask, if mode attribute is to be changed */ if (FSAL_TEST_MASK(obj_attr->valid_mask, ATTR_MODE)) { obj_attr->mode &= ~op_ctx->fsal_export->exp_ops.fs_umask( op_ctx->fsal_export); } /************** * TRUNCATE * **************/ if (FSAL_TEST_MASK(obj_attr->valid_mask, ATTR_SIZE)) { attr_changed |= XATTR_SIZE; /* Fill wanted size. */ buffxstat.buffstat.st_size = obj_attr->filesize; LogDebug(COMPONENT_FSAL, "new size = %llu", (unsigned long long)buffxstat.buffstat.st_size); } /******************* * SPACE RESERVED * ************)******/ if (FSAL_TEST_MASK(obj_attr->valid_mask, ATTR4_SPACE_RESERVED)) { attr_changed |= XATTR_SPACE_RESERVED; /* Fill wanted space. */ buffxstat.buffstat.st_size = obj_attr->filesize; LogDebug(COMPONENT_FSAL, "new size = %llu", (unsigned long long)buffxstat.buffstat.st_size); } /*********** * CHMOD * ***********/ if (FSAL_TEST_MASK(obj_attr->valid_mask, ATTR_MODE) && !exp->ignore_mode_change) { /* The POSIX chmod call don't affect the symlink object, but * the entry it points to. So we must ignore it. */ if (dir_hdl->type != SYMBOLIC_LINK) { attr_changed |= XATTR_MODE; /* Fill wanted mode. */ buffxstat.buffstat.st_mode = fsal2unix_mode(obj_attr->mode); LogDebug(COMPONENT_FSAL, "new mode = %o", buffxstat.buffstat.st_mode); } } /*********** * CHOWN * ***********/ /* Fill wanted owner. */ if (FSAL_TEST_MASK(obj_attr->valid_mask, ATTR_OWNER)) { attr_changed |= XATTR_UID; buffxstat.buffstat.st_uid = (int)obj_attr->owner; LogDebug(COMPONENT_FSAL, "new uid = %d", buffxstat.buffstat.st_uid); } /* Fill wanted group. */ if (FSAL_TEST_MASK(obj_attr->valid_mask, ATTR_GROUP)) { attr_changed |= XATTR_GID; buffxstat.buffstat.st_gid = (int)obj_attr->group; LogDebug(COMPONENT_FSAL, "new gid = %d", buffxstat.buffstat.st_gid); } /*********** * UTIME * ***********/ /* Fill wanted atime. */ if (FSAL_TEST_MASK(obj_attr->valid_mask, ATTR_ATIME)) { attr_changed |= XATTR_ATIME; buffxstat.buffstat.st_atime = (time_t)obj_attr->atime.tv_sec; buffxstat.buffstat.st_atim.tv_nsec = obj_attr->atime.tv_nsec; LogDebug(COMPONENT_FSAL, "new atime = %lu", (unsigned long)buffxstat.buffstat.st_atime); } /* Fill wanted mtime. */ if (FSAL_TEST_MASK(obj_attr->valid_mask, ATTR_MTIME)) { attr_changed |= XATTR_MTIME; buffxstat.buffstat.st_mtime = (time_t)obj_attr->mtime.tv_sec; buffxstat.buffstat.st_mtim.tv_nsec = obj_attr->mtime.tv_nsec; LogDebug(COMPONENT_FSAL, "new mtime = %lu", (unsigned long)buffxstat.buffstat.st_mtime); } /* Asking to set atime to NOW */ if (FSAL_TEST_MASK(obj_attr->valid_mask, ATTR_ATIME_SERVER)) { attr_changed |= XATTR_ATIME | XATTR_ATIME_NOW; LogDebug(COMPONENT_FSAL, "new atime = NOW"); } /* Asking to set atime to NOW */ if (FSAL_TEST_MASK(obj_attr->valid_mask, ATTR_MTIME_SERVER)) { attr_changed |= XATTR_MTIME | XATTR_MTIME_NOW; LogDebug(COMPONENT_FSAL, "new mtime = NOW"); } /* If any stat changed, indicate that */ if (attr_changed != 0) attr_valid |= XATTR_STAT; if (use_acl && FSAL_TEST_MASK(obj_attr->valid_mask, ATTR_ACL) && obj_attr->acl && obj_attr->acl->naces) { attr_valid |= XATTR_ACL; LogDebug(COMPONENT_FSAL, "setattr acl = %p", obj_attr->acl); /* Convert FSAL ACL to GPFS NFS4 ACL and fill buffer. */ acl_buflen = offsetof(gpfs_acl_t, ace_v1) + obj_attr->acl->naces * sizeof(gpfs_ace_v4_t); if (acl_buflen > GPFS_ACL_BUF_SIZE) acl_buf = gsh_malloc(acl_buflen); else acl_buf = (gpfs_acl_t *)buffxstat.buffacl; status = fsal_acl_2_gpfs_acl(dir_hdl, obj_attr->acl, &buffxstat, acl_buf, acl_buflen); if (FSAL_IS_ERROR(status)) goto acl_free; } /* If there is any change in stat or acl or both, send it down to fs. */ if (attr_valid != 0) { status = fsal_set_xstat_by_handle(export_fd, myself->handle, attr_valid, attr_changed, &buffxstat, acl_buf); if (FSAL_IS_ERROR(status)) goto acl_free; } status = fsalstat(ERR_FSAL_NO_ERROR, 0); acl_free: if (acl_buflen > GPFS_ACL_BUF_SIZE) gsh_free(acl_buf); return status; } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/fsal_convert.c000066400000000000000000000327141473756622300213150ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /** @file fsal_convert.c * @brief GPFS FSAL module convert functions * * This program 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 3 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 * 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 * * --------------------------------------- * * vim:noexpandtab:shiftwidth=8:tabstop=8: * * GPFS-FSAL type translation functions. */ #include "config.h" #include "fsal_convert.h" #include "fsal_internal.h" #include "nfs4_acls.h" #include "include/gpfs.h" #include #include #include #include #include static fsal_status_t gpfs_acl_2_fsal_acl(struct fsal_attrlist *p_object_attributes, gpfs_acl_t *p_gpfsacl); /** * @brief convert GPFS xstat to FSAl attributes * * @param gpfs_buf Reference to GPFS stat buffer * @param fsal_attr Reference to attribute list * @param use_acl Bool whether ACL are used * @return FSAL status * * Same function as posixstat64_2_fsal_attributes. When NFS4 ACL support * is enabled, this will replace posixstat64_2_fsal_attributes. */ fsal_status_t gpfsfsal_xstat_2_fsal_attributes(gpfsfsal_xstat_t *gpfs_buf, struct fsal_attrlist *fsal_attr, gpfs_acl_t *acl_buf, bool use_acl) { struct stat *p_buffstat; /* sanity checks */ if (!gpfs_buf || !fsal_attr) return fsalstat(ERR_FSAL_FAULT, 0); fsal_attr->supported = GPFS_SUPPORTED_ATTRIBUTES; p_buffstat = &gpfs_buf->buffstat; LogDebug(COMPONENT_FSAL, "inode %" PRId64, p_buffstat->st_ino); /* Fills the output struct */ if (FSAL_TEST_MASK(fsal_attr->request_mask, ATTR_TYPE)) { fsal_attr->type = posix2fsal_type(p_buffstat->st_mode); fsal_attr->valid_mask |= ATTR_TYPE; LogFullDebug(COMPONENT_FSAL, "type = 0x%x", fsal_attr->type); } if (FSAL_TEST_MASK(fsal_attr->request_mask, ATTR_SIZE)) { fsal_attr->filesize = p_buffstat->st_size; fsal_attr->valid_mask |= ATTR_SIZE; LogFullDebug(COMPONENT_FSAL, "filesize = %llu", (unsigned long long)fsal_attr->filesize); } if (FSAL_TEST_MASK(fsal_attr->request_mask, ATTR_FSID)) { fsal_attr->fsid = gpfs_buf->fsal_fsid; fsal_attr->valid_mask |= ATTR_FSID; LogFullDebug(COMPONENT_FSAL, "fsid=0x%016" PRIx64 ".0x%016" PRIx64, fsal_attr->fsid.major, fsal_attr->fsid.minor); } if (FSAL_TEST_MASK(fsal_attr->request_mask, ATTR_ACL)) { if (fsal_attr->acl != NULL) { /* We should never be passed attributes that have an * ACL attached, but just in case some future code * path changes that assumption, let's release the * old ACL properly. */ LogCrit(COMPONENT_FSAL, "attrs passed in with acl, shouldn't happen"); nfs4_acl_release_entry(fsal_attr->acl); fsal_attr->acl = NULL; } if (use_acl && gpfs_buf->attr_valid & XATTR_ACL) { /* ACL is valid, so try to convert fsal acl. */ fsal_status_t status = gpfs_acl_2_fsal_acl(fsal_attr, acl_buf); if (!FSAL_IS_ERROR(status)) { /* Only mark ACL valid if we actually provide * one in fsal_attr. */ fsal_attr->valid_mask |= ATTR_ACL; } else { /* Otherwise, we were asked for ACL and could * not provide one, so we must fail. */ return status; } } LogFullDebug(COMPONENT_FSAL, "acl = %p", fsal_attr->acl); } if (FSAL_TEST_MASK(fsal_attr->request_mask, ATTR_FILEID)) { fsal_attr->fileid = (uint64_t)(p_buffstat->st_ino); fsal_attr->valid_mask |= ATTR_FILEID; LogFullDebug(COMPONENT_FSAL, "fileid = %" PRIu64, fsal_attr->fileid); } if (FSAL_TEST_MASK(fsal_attr->request_mask, ATTR_MODE)) { fsal_attr->mode = unix2fsal_mode(p_buffstat->st_mode); fsal_attr->valid_mask |= ATTR_MODE; LogFullDebug(COMPONENT_FSAL, "mode = %" PRIu32, fsal_attr->mode); } if (FSAL_TEST_MASK(fsal_attr->request_mask, ATTR_NUMLINKS)) { fsal_attr->numlinks = p_buffstat->st_nlink; fsal_attr->valid_mask |= ATTR_NUMLINKS; LogFullDebug(COMPONENT_FSAL, "numlinks = %u", fsal_attr->numlinks); } if (FSAL_TEST_MASK(fsal_attr->request_mask, ATTR_OWNER)) { fsal_attr->owner = p_buffstat->st_uid; fsal_attr->valid_mask |= ATTR_OWNER; LogFullDebug(COMPONENT_FSAL, "owner = %" PRIu64, fsal_attr->owner); } if (FSAL_TEST_MASK(fsal_attr->request_mask, ATTR_GROUP)) { fsal_attr->group = p_buffstat->st_gid; fsal_attr->valid_mask |= ATTR_GROUP; LogFullDebug(COMPONENT_FSAL, "group = %" PRIu64, fsal_attr->group); } if (FSAL_TEST_MASK(fsal_attr->request_mask, ATTR_ATIME)) { fsal_attr->atime = posix2fsal_time(p_buffstat->st_atime, p_buffstat->st_atim.tv_nsec); fsal_attr->valid_mask |= ATTR_ATIME; LogFullDebug(COMPONENT_FSAL, "atime = %lu", fsal_attr->atime.tv_sec); } if (FSAL_TEST_MASK(fsal_attr->request_mask, ATTR_CTIME)) { fsal_attr->ctime = posix2fsal_time(p_buffstat->st_ctime, p_buffstat->st_ctim.tv_nsec); fsal_attr->valid_mask |= ATTR_CTIME; LogFullDebug(COMPONENT_FSAL, "ctime = %lu", fsal_attr->ctime.tv_sec); } if (FSAL_TEST_MASK(fsal_attr->request_mask, ATTR_MTIME)) { fsal_attr->mtime = posix2fsal_time(p_buffstat->st_mtime, p_buffstat->st_mtim.tv_nsec); fsal_attr->valid_mask |= ATTR_MTIME; LogFullDebug(COMPONENT_FSAL, "mtime = %lu", fsal_attr->mtime.tv_sec); } if (FSAL_TEST_MASK(fsal_attr->request_mask, ATTR_CHANGE)) { if (p_buffstat->st_mtime == p_buffstat->st_ctime) { if (p_buffstat->st_mtim.tv_nsec > p_buffstat->st_ctim.tv_nsec) { fsal_attr->change = (uint64_t)p_buffstat->st_mtim.tv_sec + (uint64_t)p_buffstat->st_mtim.tv_nsec; } else { fsal_attr->change = (uint64_t)p_buffstat->st_ctim.tv_sec + (uint64_t)p_buffstat->st_ctim.tv_nsec; } } else if (p_buffstat->st_mtime > p_buffstat->st_ctime) { fsal_attr->change = (uint64_t)p_buffstat->st_mtim.tv_sec + (uint64_t)p_buffstat->st_mtim.tv_nsec; } else { fsal_attr->change = (uint64_t)p_buffstat->st_ctim.tv_sec + (uint64_t)p_buffstat->st_ctim.tv_nsec; } fsal_attr->valid_mask |= ATTR_CHANGE; LogFullDebug(COMPONENT_FSAL, "change = %" PRIu64, fsal_attr->change); } if (FSAL_TEST_MASK(fsal_attr->request_mask, ATTR_SPACEUSED)) { fsal_attr->spaceused = p_buffstat->st_blocks * S_BLKSIZE; fsal_attr->valid_mask |= ATTR_SPACEUSED; LogFullDebug(COMPONENT_FSAL, "spaceused = %llu", (unsigned long long)fsal_attr->spaceused); } if (FSAL_TEST_MASK(fsal_attr->request_mask, ATTR_RAWDEV)) { fsal_attr->rawdev = posix2fsal_devt(p_buffstat->st_rdev); fsal_attr->valid_mask |= ATTR_RAWDEV; LogFullDebug(COMPONENT_FSAL, "rawdev major = %u, minor = %u", (unsigned int)fsal_attr->rawdev.major, (unsigned int)fsal_attr->rawdev.minor); } /* everything has been copied ! */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* Convert GPFS NFS4 ACLs to FSAL ACLs, and set the ACL * pointer of attribute. */ static fsal_status_t gpfs_acl_2_fsal_acl(struct fsal_attrlist *p_object_attributes, gpfs_acl_t *p_gpfsacl) { fsal_acl_status_t status; fsal_acl_data_t acldata; fsal_ace_t *pace; fsal_acl_t *pacl; gpfs_ace_v4_t *pace_gpfs; /* sanity checks */ if (!p_object_attributes || !p_gpfsacl) return fsalstat(ERR_FSAL_FAULT, 0); /* Create fsal acl data. */ acldata.naces = p_gpfsacl->acl_nace; acldata.aces = (fsal_ace_t *)nfs4_ace_alloc(acldata.naces); /* Fill fsal acl data from gpfs acl. */ for (pace = acldata.aces, pace_gpfs = p_gpfsacl->ace_v4; pace < acldata.aces + acldata.naces; pace++, pace_gpfs++) { pace->type = pace_gpfs->aceType; pace->flag = pace_gpfs->aceFlags; pace->iflag = pace_gpfs->aceIFlags; pace->perm = pace_gpfs->aceMask; if (IS_FSAL_ACE_SPECIAL_ID(*pace)) { /* Record special user. */ pace->who.uid = pace_gpfs->aceWho; } else { if (IS_FSAL_ACE_GROUP_ID(*pace)) pace->who.gid = pace_gpfs->aceWho; else /* Record user. */ pace->who.uid = pace_gpfs->aceWho; } LogMidDebug( COMPONENT_FSAL, "fsal ace: type = 0x%x, flag = 0x%x, perm = 0x%x, special = %d, %s = 0x%x", pace->type, pace->flag, pace->perm, IS_FSAL_ACE_SPECIAL_ID(*pace), GET_FSAL_ACE_WHO_TYPE(*pace), GET_FSAL_ACE_WHO(*pace)); } /* Create a new hash table entry for fsal acl. */ pacl = nfs4_acl_new_entry(&acldata, &status); LogMidDebug(COMPONENT_FSAL, "fsal acl = %p, fsal_acl_status = %u", pacl, status); if (pacl == NULL) { LogCrit(COMPONENT_FSAL, "failed to create a new acl entry"); return fsalstat(ERR_FSAL_FAULT, 0); } /* Add fsal acl to attribute. */ p_object_attributes->acl = pacl; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** @fn fsal_status_t * fsal_acl_2_gpfs_acl(struct fsal_obj_handle *dir_hdl, * fsal_acl_t *fsal_acl, * gpfsfsal_xstat_t *gpfs_buf) * @param dir_hdl Object handle * @param fsal_acl GPFS access control list * @param gpfs_buf Reference to GPFS stat buffer * @return FSAL status * * @brief Convert FSAL ACLs to GPFS NFS4 ACLs. */ fsal_status_t fsal_acl_2_gpfs_acl(struct fsal_obj_handle *dir_hdl, fsal_acl_t *fsal_acl, gpfsfsal_xstat_t *gpfs_buf, gpfs_acl_t *acl_buf, unsigned int acl_buflen) { int i; fsal_ace_t *pace; acl_buf->acl_level = 0; acl_buf->acl_version = GPFS_ACL_VERSION_NFS4; acl_buf->acl_type = GPFS_ACL_TYPE_NFS4; acl_buf->acl_nace = fsal_acl->naces; acl_buf->acl_len = acl_buflen; /* GPFS can support max 638 entries */ if (fsal_acl->naces > GPFS_ACL_MAX_NACES) { LogInfo(COMPONENT_FSAL, "No. of ACE's:%d higher than supported by GPFS", fsal_acl->naces); return fsalstat(ERR_FSAL_INVAL, 0); } for (pace = fsal_acl->aces, i = 0; pace < fsal_acl->aces + fsal_acl->naces; pace++, i++) { acl_buf->ace_v4[i].aceType = pace->type; acl_buf->ace_v4[i].aceFlags = pace->flag; acl_buf->ace_v4[i].aceIFlags = pace->iflag; acl_buf->ace_v4[i].aceMask = pace->perm; if (IS_FSAL_ACE_SPECIAL_ID(*pace)) acl_buf->ace_v4[i].aceWho = pace->who.uid; else { if (IS_FSAL_ACE_GROUP_ID(*pace)) acl_buf->ace_v4[i].aceWho = pace->who.gid; else acl_buf->ace_v4[i].aceWho = pace->who.uid; } LogMidDebug( COMPONENT_FSAL, "gpfs ace: type = 0x%x, flag = 0x%x, perm = 0x%x, special = %d, %s = 0x%x", acl_buf->ace_v4[i].aceType, acl_buf->ace_v4[i].aceFlags, acl_buf->ace_v4[i].aceMask, (acl_buf->ace_v4[i].aceIFlags & FSAL_ACE_IFLAG_SPECIAL_ID) ? 1 : 0, (acl_buf->ace_v4[i].aceFlags & FSAL_ACE_FLAG_GROUP_ID) ? "gid" : "uid", acl_buf->ace_v4[i].aceWho); /* It is invalid to set inherit flags on non dir objects */ if (dir_hdl->type != DIRECTORY && (acl_buf->ace_v4[i].aceFlags & FSAL_ACE_FLAG_INHERIT) != 0) { LogMidDebug( COMPONENT_FSAL, "attempt to set inherit flag to non dir object"); return fsalstat(ERR_FSAL_INVAL, 0); } /* It is invalid to set inherit only with * out an actual inherit flag */ if ((acl_buf->ace_v4[i].aceFlags & FSAL_ACE_FLAG_INHERIT) == FSAL_ACE_FLAG_INHERIT_ONLY) { LogMidDebug( COMPONENT_FSAL, "attempt to set inherit only without an inherit flag"); return fsalstat(ERR_FSAL_INVAL, 0); } } return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t fsal_cred_2_gpfs_cred(struct user_cred *p_fsalcred, struct xstat_cred_t *p_gpfscred) { int i; if (!p_fsalcred || !p_gpfscred) return fsalstat(ERR_FSAL_FAULT, 0); p_gpfscred->principal = p_fsalcred->caller_uid; p_gpfscred->group = p_fsalcred->caller_gid; p_gpfscred->num_groups = p_fsalcred->caller_glen; for (i = 0; i < p_fsalcred->caller_glen; i++) p_gpfscred->eGroups[i] = p_fsalcred->caller_garray[i]; return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t fsal_mode_2_gpfs_mode(mode_t fsal_mode, fsal_accessflags_t v4mask, unsigned int *p_gpfsmode, bool is_dir) { int gpfs_mode = 0; if (!p_gpfsmode) return fsalstat(ERR_FSAL_FAULT, 0); /* If mode is zero, translate v4mask to posix mode. */ if (fsal_mode == 0) { if (!is_dir) { if (v4mask & FSAL_ACE_PERM_READ_DATA) gpfs_mode |= FSAL_R_OK; } else { if (v4mask & FSAL_ACE_PERM_LIST_DIR) { gpfs_mode |= FSAL_R_OK; gpfs_mode |= FSAL_X_OK; } } if (!is_dir) { if (v4mask & FSAL_ACE_PERM_WRITE_DATA) gpfs_mode |= FSAL_W_OK; } else { if (v4mask & FSAL_ACE_PERM_ADD_FILE) { gpfs_mode |= FSAL_W_OK; gpfs_mode |= FSAL_X_OK; } } if (!is_dir) { if (v4mask & FSAL_ACE_PERM_APPEND_DATA) gpfs_mode |= FSAL_W_OK; } else { if (v4mask & FSAL_ACE_PERM_ADD_SUBDIRECTORY) { gpfs_mode |= FSAL_W_OK; gpfs_mode |= FSAL_X_OK; } } if (!is_dir) { if (v4mask & FSAL_ACE_PERM_EXECUTE) gpfs_mode |= FSAL_X_OK; } else { if (v4mask & FSAL_ACE_PERM_DELETE_CHILD) { gpfs_mode |= FSAL_W_OK; gpfs_mode |= FSAL_X_OK; } } if (v4mask & FSAL_ACE_PERM_DELETE) gpfs_mode |= FSAL_W_OK; gpfs_mode = gpfs_mode >> 24; } else { gpfs_mode = fsal_mode >> 24; } LogMidDebug( COMPONENT_FSAL, "fsal_mode 0x%x, v4mask 0x%x, is_dir %d converted to gpfs_mode 0x%x", fsal_mode, v4mask, is_dir, gpfs_mode); *p_gpfsmode = gpfs_mode; return fsalstat(ERR_FSAL_NO_ERROR, 0); } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/fsal_create.c000066400000000000000000000176651473756622300211100ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /** @file fsal_create * @brief GPFS FSAL Filesystem objects creation functions * * This program 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 3 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 * 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 * * --------------------------------------- * * vim:noexpandtab:shiftwidth=8:tabstop=8: * */ #include "config.h" #include "fsal.h" #include "fsal_internal.h" #include "fsal_convert.h" #include "gpfs_methods.h" #include #include #include #include "FSAL/access_check.h" #include "FSAL/fsal_localfs.h" /** * @brief Create a regular file. * * @param dir_hdl Handle of parent directory where the file is to be created. * @param filename Pointer to the name of the file to be created. * @param accessmode Mode for the file to be created. * @param gpfs_fh Pointer to the handle of the created file. * @param fsal_attr Attributes of the created file. * @return ERR_FSAL_NO_ERROR on success, otherwise error * */ fsal_status_t GPFSFSAL_create(struct fsal_obj_handle *dir_hdl, const char *filename, uint32_t accessmode, struct gpfs_file_handle *gpfs_fh, struct fsal_attrlist *fsal_attr) { fsal_status_t status; mode_t unix_mode; /* note : fsal_attr is optional. */ if (!dir_hdl || !op_ctx || !gpfs_fh || !filename) return fsalstat(ERR_FSAL_FAULT, 0); /* convert fsal mode to unix mode. */ unix_mode = fsal2unix_mode(accessmode); /* Apply umask */ unix_mode = unix_mode & ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export); LogFullDebug(COMPONENT_FSAL, "Creation mode: 0%o", accessmode); /* call to filesystem */ fsal_set_credentials(&op_ctx->creds); status = fsal_internal_create(dir_hdl, filename, unix_mode | S_IFREG, 0, gpfs_fh, NULL); fsal_restore_ganesha_credentials(); if (FSAL_IS_ERROR(status)) return status; /* retrieve file attributes */ return GPFSFSAL_getattrs(op_ctx->fsal_export, dir_hdl->fs->private_data, gpfs_fh, fsal_attr); } fsal_status_t GPFSFSAL_create2(struct fsal_obj_handle *dir_hdl, const char *filename, mode_t unix_mode, struct gpfs_file_handle *gpfs_fh, int posix_flags, struct fsal_attrlist *fsal_attr) { fsal_status_t status; /* note : fsal_attr is optional. */ if (!dir_hdl || !op_ctx || !gpfs_fh || !filename) return fsalstat(ERR_FSAL_FAULT, 0); LogFullDebug(COMPONENT_FSAL, "Creation mode: 0%o", unix_mode); /* call to filesystem */ fsal_set_credentials(&op_ctx->creds); status = fsal_internal_create(dir_hdl, filename, unix_mode | S_IFREG, posix_flags, gpfs_fh, NULL); fsal_restore_ganesha_credentials(); if (!FSAL_IS_ERROR(status) && fsal_attr != NULL) { /* retrieve file attributes */ status = GPFSFSAL_getattrs(op_ctx->fsal_export, dir_hdl->fs->private_data, gpfs_fh, fsal_attr); } return status; } /** * @brief Create a directory. * * @param dir_hdl Handle of the parent directory * @param dir_name Pointer to the name of the directory to be created. * @param accessmode Mode for the directory to be created. * @param gpfs_fh Pointer to the handle of the created directory. * @param fsal_attr Attributes of the created directory. * @return ERR_FSAL_NO_ERROR on success, error otherwise * */ fsal_status_t GPFSFSAL_mkdir(struct fsal_obj_handle *dir_hdl, const char *dir_name, uint32_t accessmode, struct gpfs_file_handle *gpfs_fh, struct fsal_attrlist *obj_attr) { mode_t unix_mode; fsal_status_t status; /* note : obj_attr is optional. */ if (!dir_hdl || !op_ctx || !gpfs_fh || !dir_name) return fsalstat(ERR_FSAL_FAULT, 0); /* convert FSAL mode to unix mode. */ unix_mode = fsal2unix_mode(accessmode); /* Apply umask */ unix_mode = unix_mode & ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export); /* build new entry path */ /* creates the directory and get its handle */ fsal_set_credentials(&op_ctx->creds); status = fsal_internal_create(dir_hdl, dir_name, unix_mode | S_IFDIR, 0, gpfs_fh, NULL); fsal_restore_ganesha_credentials(); if (FSAL_IS_ERROR(status)) return status; /* retrieve file attributes */ return GPFSFSAL_getattrs(op_ctx->fsal_export, dir_hdl->fs->private_data, gpfs_fh, obj_attr); } /** * @brief Create a hardlink. * * @param dir_hdl Handle of the target object. * @param gpfs_fh Pointer to the dire handle where hardlink is to be created. * @param linkname Pointer to the name of the hardlink to be created. * @return ERR_FSAL_NO_ERROR on success, error otherwise * */ fsal_status_t GPFSFSAL_link(struct fsal_obj_handle *dir_hdl, struct gpfs_file_handle *gpfs_fh, const char *linkname) { fsal_status_t status; struct gpfs_fsal_obj_handle *dest_dir; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; dest_dir = container_of(dir_hdl, struct gpfs_fsal_obj_handle, obj_handle); /* Tests if hardlinking is allowed by configuration. */ if (!op_ctx->fsal_export->exp_ops.fs_supports(op_ctx->fsal_export, fso_link_support)) return fsalstat(ERR_FSAL_NOTSUPP, 0); /* Create the link on the filesystem */ fsal_set_credentials(&op_ctx->creds); status = fsal_internal_link_fh(export_fd, gpfs_fh, dest_dir->handle, linkname); fsal_restore_ganesha_credentials(); return status; } /** * @brief Create a special object in the filesystem. * * @param dir_hdl Handle of the parent dir where the file is to be created. * @param node_name Pointer to the name of the file to be created. * @param accessmode Mode for the file to be created. * @param node_type Type of file to create. * @param dev Device id of file to create. * @param gpfs_fh Pointer to the handle of the created file. * @param fsal_attr Attributes of the created file. * @return ERR_FSAL_NO_ERROR on success, error otherwise * */ fsal_status_t GPFSFSAL_mknode(struct fsal_obj_handle *dir_hdl, const char *node_name, uint32_t accessmode, mode_t nodetype, fsal_dev_t *dev, struct gpfs_file_handle *gpfs_fh, struct fsal_attrlist *fsal_attr) { fsal_status_t status; mode_t unix_mode = 0; dev_t unix_dev = 0; /* note : fsal_attr is optional. */ if (!dir_hdl || !op_ctx || !node_name) return fsalstat(ERR_FSAL_FAULT, 0); unix_mode = fsal2unix_mode(accessmode); /* Apply umask */ unix_mode = unix_mode & ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export); switch (nodetype) { case BLOCK_FILE: if (!dev) return fsalstat(ERR_FSAL_FAULT, 0); unix_mode |= S_IFBLK; unix_dev = (dev->major << 20) | (dev->minor & 0xFFFFF); break; case CHARACTER_FILE: if (!dev) return fsalstat(ERR_FSAL_FAULT, 0); unix_mode |= S_IFCHR; unix_dev = (dev->major << 20) | (dev->minor & 0xFFFFF); break; case SOCKET_FILE: unix_mode |= S_IFSOCK; break; case FIFO_FILE: unix_mode |= S_IFIFO; break; default: LogMajor(COMPONENT_FSAL, "Invalid node type in FSAL_mknode: %d", nodetype); return fsalstat(ERR_FSAL_INVAL, 0); } fsal_set_credentials(&op_ctx->creds); status = fsal_internal_mknode(dir_hdl, node_name, unix_mode, unix_dev, gpfs_fh, NULL); fsal_restore_ganesha_credentials(); if (FSAL_IS_ERROR(status)) return status; /* Fills the attributes */ return GPFSFSAL_getattrs(op_ctx->fsal_export, dir_hdl->fs->private_data, gpfs_fh, fsal_attr); } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/fsal_ds.c000066400000000000000000000352461473756622300202460ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright © 2012 CohortFS, LLC. * Author: Adam C. Emerson * * This program 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 3 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 * 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 * * ------------- */ /** * @file ds.c * @author Adam C. Emerson * @date Mon Jul 30 12:29:22 2012 * * @brief pNFS DS operations for GPFS * * This file implements the read, write, commit, and dispose * operations for GPFS data-server handles. * * Also, creating a data server handle -- now called via the DS itself. */ #include "config.h" #include #include "fsal_api.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_localfs.h" #include "../fsal_private.h" #include "fsal_convert.h" #include "fsal_internal.h" #include "gpfs_methods.h" #include "pnfs_utils.h" #include "nfs_creds.h" #define min(a, b) \ ({ \ typeof(a) _a = (a); \ typeof(b) _b = (b); \ _a < _b ? _a : _b; \ }) /** * @brief Release a DS handle * * @param[in] ds_pub The object to release */ static void ds_handle_release(struct fsal_ds_handle *const ds_pub) { /* The private 'full' DS handle */ struct gpfs_ds *ds = container_of(ds_pub, struct gpfs_ds, ds); gsh_free(ds); } /** * @brief Read from a data-server handle. * * NFSv4.1 data server handles are disjount from normal * filehandles (in Ganesha, there is a ds_flag in the filehandle_v4_t * structure) and do not get loaded into mdcache or processed the * normal way. * * @param[in] ds_pub FSAL DS handle * @param[in] stateid The stateid supplied with the READ operation, * for validation * @param[in] offset The offset at which to read * @param[in] requested_length Length of read requested (and size of buffer) * @param[out] buffer The buffer to which to store read data * @param[out] supplied_length Length of data read * @param[out] eof True on end of file * * @return An NFSv4.1 status code. */ static nfsstat4 ds_read(struct fsal_ds_handle *const ds_pub, const stateid4 *stateid, const offset4 offset, const count4 requested_length, void *const buffer, count4 *const supplied_length, bool *const end_of_file) { /* The private 'full' DS handle */ struct gpfs_ds *ds = container_of(ds_pub, struct gpfs_ds, ds); struct gpfs_file_handle *gpfs_handle = &ds->wire; /* The amount actually read */ int amount_read = 0; struct dsread_arg rarg; unsigned int *fh; int errsv = 0; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; fh = (int *)&(gpfs_handle->f_handle); rarg.mountdirfd = export_fd; rarg.handle = gpfs_handle; rarg.bufP = buffer; rarg.offset = offset; rarg.length = requested_length; rarg.options = 0; rarg.cli_ip = NULL; if (op_ctx && op_ctx->client) rarg.cli_ip = op_ctx->client->hostaddr_str; LogDebug( COMPONENT_PNFS, "fh len %d type %d key %d: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", gpfs_handle->handle_size, gpfs_handle->handle_type, gpfs_handle->handle_key_size, fh[0], fh[1], fh[2], fh[3], fh[4], fh[5], fh[6], fh[7], fh[8], fh[9]); amount_read = gpfs_ganesha(OPENHANDLE_DS_READ, &rarg); errsv = errno; if (amount_read < 0) { if (errsv == EUNATCH) LogFatal(COMPONENT_PNFS, "GPFS Returned EUNATCH"); return posix2nfs4_error(errsv); } *supplied_length = amount_read; if (amount_read == 0 || amount_read < requested_length) *end_of_file = true; return NFS4_OK; } /** * @brief Read plus from a data-server handle. * * NFSv4.2 data server handles are disjount from normal * filehandles (in Ganesha, there is a ds_flag in the filehandle_v4_t * structure) and do not get loaded into mdcache or processed the * normal way. * * @param[in] ds_pub FSAL DS handle * @param[in] stateid The stateid supplied with the READ operation, * for validation * @param[in] offset The offset at which to read * @param[in] requested_length Length of read requested (and size of buffer) * @param[out] buffer The buffer to which to store read data * @param[out] supplied_length Length of data read * @param[out] eof True on end of file * @param[out] info IO info * * @return An NFSv4.2 status code. */ static nfsstat4 ds_read_plus(struct fsal_ds_handle *const ds_pub, const stateid4 *stateid, const offset4 offset, const count4 requested_length, void *const buffer, const count4 supplied_length, bool *const end_of_file, struct io_info *info) { /* The private 'full' DS handle */ struct gpfs_ds *ds = container_of(ds_pub, struct gpfs_ds, ds); struct gpfs_file_handle *gpfs_handle = &ds->wire; /* The amount actually read */ int amount_read = 0; struct dsread_arg rarg; unsigned int *fh; uint64_t filesize; int errsv = 0; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; fh = (int *)&(gpfs_handle->f_handle); rarg.mountdirfd = export_fd; rarg.handle = gpfs_handle; rarg.bufP = buffer; rarg.offset = offset; rarg.length = requested_length; rarg.filesize = &filesize; rarg.options = IO_SKIP_HOLE; rarg.cli_ip = NULL; if (op_ctx && op_ctx->client) rarg.cli_ip = op_ctx->client->hostaddr_str; LogDebug( COMPONENT_PNFS, "fh len %d type %d key %d: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", gpfs_handle->handle_size, gpfs_handle->handle_type, gpfs_handle->handle_key_size, fh[0], fh[1], fh[2], fh[3], fh[4], fh[5], fh[6], fh[7], fh[8], fh[9]); amount_read = gpfs_ganesha(OPENHANDLE_DS_READ, &rarg); errsv = errno; if (amount_read < 0) { if (errsv == EUNATCH) LogFatal(COMPONENT_PNFS, "GPFS Returned EUNATCH"); if (errsv != ENODATA) return posix2nfs4_error(errsv); /* errsv == ENODATA */ info->io_content.what = NFS4_CONTENT_HOLE; info->io_content.hole.di_offset = offset; /*offset of hole*/ info->io_content.hole.di_length = requested_length; /*hole len*/ if ((requested_length + offset) > filesize) { amount_read = filesize - offset; if (amount_read < 0) { amount_read = 0; *end_of_file = true; } else if (amount_read < requested_length) *end_of_file = true; info->io_content.hole.di_length = amount_read; } } else { info->io_content.what = NFS4_CONTENT_DATA; info->io_content.data.d_offset = offset + amount_read; info->io_content.data.d_data.data_len = amount_read; info->io_content.data.d_data.data_val = buffer; if (amount_read == 0 || amount_read < requested_length) *end_of_file = true; } return NFS4_OK; } /** * * @brief Write to a data-server handle. * * This performs a DS write not going through the data server unless * FILE_SYNC4 is specified, in which case it connects the filehandle * and performs an MDS write. * * @param[in] ds_pub FSAL DS handle * @param[in] stateid The stateid supplied with the READ operation, * for validation * @param[in] offset The offset at which to read * @param[in] write_length Length of write requested (and size of buffer) * @param[out] buffer The buffer to which to store read data * @param[in] stability wanted Stability of write * @param[out] written_length Length of data written * @param[out] writeverf Write verifier * @param[out] stability_got Stability used for write (must be as * or more stable than request) * * @return An NFSv4.1 status code. */ static nfsstat4 ds_write(struct fsal_ds_handle *const ds_pub, const stateid4 *stateid, const offset4 offset, const count4 write_length, const void *buffer, const stable_how4 stability_wanted, count4 *const written_length, verifier4 *const writeverf, stable_how4 *const stability_got) { /* The private 'full' DS handle */ struct gpfs_ds *ds = container_of(ds_pub, struct gpfs_ds, ds); struct gpfs_file_handle *gpfs_handle = &ds->wire; /* The amount actually read */ int32_t amount_written = 0; struct dswrite_arg warg; unsigned int *fh; struct gsh_buffdesc key; int errsv = 0; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; fh = (int *)&(gpfs_handle->f_handle); memset(writeverf, 0, NFS4_VERIFIER_SIZE); warg.mountdirfd = export_fd; warg.handle = gpfs_handle; warg.bufP = (char *)buffer; warg.offset = offset; warg.length = write_length; warg.stability_wanted = stability_wanted; warg.stability_got = stability_got; warg.verifier4 = (int32_t *)writeverf; warg.options = 0; warg.cli_ip = NULL; if (op_ctx && op_ctx->client) warg.cli_ip = op_ctx->client->hostaddr_str; LogDebug( COMPONENT_PNFS, "fh len %d type %d key %d: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", gpfs_handle->handle_size, gpfs_handle->handle_type, gpfs_handle->handle_key_size, fh[0], fh[1], fh[2], fh[3], fh[4], fh[5], fh[6], fh[7], fh[8], fh[9]); amount_written = gpfs_ganesha(OPENHANDLE_DS_WRITE, &warg); errsv = errno; if (amount_written < 0) { if (errsv == EUNATCH) LogFatal(COMPONENT_PNFS, "GPFS Returned EUNATCH"); return posix2nfs4_error(errsv); } LogDebug(COMPONENT_PNFS, "write verifier %d-%d\n", warg.verifier4[0], warg.verifier4[1]); key.addr = gpfs_handle; key.len = gpfs_handle->handle_key_size; op_ctx->fsal_export->up_ops->invalidate(op_ctx->fsal_export->up_ops, &key, FSAL_UP_INVALIDATE_CACHE); *written_length = amount_written; return NFS4_OK; } /** * @brief Commit a byte range to a DS handle. * * NFSv4.1 data server filehandles are disjount from normal * filehandles (in Ganesha, there is a ds_flag in the filehandle_v4_t * structure) and do not get loaded into mdcache or processed the * normal way. * * @param[in] ds_pub FSAL DS handle * @param[in] offset Start of commit window * @param[in] count Length of commit window * @param[out] writeverf Write verifier * * @return An NFSv4.1 status code. */ static nfsstat4 ds_commit(struct fsal_ds_handle *const ds_pub, const offset4 offset, const count4 count, verifier4 *const writeverf) { /* The private 'full' DS handle */ struct gpfs_ds *ds = container_of(ds_pub, struct gpfs_ds, ds); struct gpfs_file_handle *gpfs_handle = &ds->wire; struct fsync_arg arg = { 0 }; unsigned int *fh; int retval; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; fh = (int *)&(gpfs_handle->f_handle); memset(writeverf, 0, NFS4_VERIFIER_SIZE); arg.mountdirfd = export_fd; arg.handle = gpfs_handle; arg.offset = offset; arg.length = count; arg.verifier4 = (int32_t *)writeverf; LogDebug( COMPONENT_PNFS, "fh len %d type %d key %d: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", gpfs_handle->handle_size, gpfs_handle->handle_type, gpfs_handle->handle_key_size, fh[0], fh[1], fh[2], fh[3], fh[4], fh[5], fh[6], fh[7], fh[8], fh[9]); if (gpfs_ganesha(OPENHANDLE_FSYNC, &arg) == -1) retval = errno; LogDebug(COMPONENT_PNFS, "write retval %d verifier %d-%d\n", retval, arg.verifier4[0], arg.verifier4[1]); return NFS4_OK; } /** * @brief Try to create a FSAL data server handle from a wire handle * * This function creates a FSAL data server handle from a client * supplied "wire" handle. This is also where validation gets done, * since PUTFH is the only operation that can return * NFS4ERR_BADHANDLE. * * @param[in] pds FSAL pNFS DS * @param[in] desc Buffer from which to create the file * @param[out] handle FSAL DS handle * * @return NFSv4.1 error codes. */ static nfsstat4 make_ds_handle(struct fsal_pnfs_ds *const pds, const struct gsh_buffdesc *const desc, struct fsal_ds_handle **const handle, int flags) { struct gpfs_file_handle *fh = (struct gpfs_file_handle *)desc->addr; struct gpfs_ds *ds; /* Handle to be created */ struct fsal_filesystem *fs; struct fsal_fsid__ fsid; *handle = NULL; if (desc->len != sizeof(struct gpfs_file_handle)) return NFS4ERR_BADHANDLE; if (flags & FH_FSAL_BIG_ENDIAN) { #if (BYTE_ORDER != BIG_ENDIAN) fh->handle_size = bswap_16(fh->handle_size); fh->handle_type = bswap_16(fh->handle_type); fh->handle_version = bswap_16(fh->handle_version); fh->handle_key_size = bswap_16(fh->handle_key_size); #endif } else { #if (BYTE_ORDER == BIG_ENDIAN) fh->handle_size = bswap_16(fh->handle_size); fh->handle_type = bswap_16(fh->handle_type); fh->handle_version = bswap_16(fh->handle_version); fh->handle_key_size = bswap_16(fh->handle_key_size); #endif } LogFullDebug( COMPONENT_FSAL, "flags 0x%X size %d type %d ver %d key_size %d FSID 0x%X:%X", flags, fh->handle_size, fh->handle_type, fh->handle_version, fh->handle_key_size, fh->handle_fsid[0], fh->handle_fsid[1]); gpfs_extract_fsid(fh, &fsid); fs = lookup_fsid(&fsid, GPFS_FSID_TYPE); if (fs == NULL) { LogInfo(COMPONENT_FSAL, "Could not find filesystem for fsid=0x%016" PRIx64 ".0x%016" PRIx64 " from handle", fsid.major, fsid.minor); return NFS4ERR_STALE; } if (fs->fsal != pds->fsal) { LogInfo(COMPONENT_FSAL, "Non GPFS filesystem fsid=0x%016" PRIx64 ".0x%016" PRIx64 " from handle", fsid.major, fsid.minor); return NFS4ERR_STALE; } ds = gsh_calloc(1, sizeof(struct gpfs_ds)); *handle = &ds->ds; /* Connect lazily when a FILE_SYNC4 write forces us to, not here. */ ds->connected = false; ds->gpfs_fs = fs->private_data; memcpy(&ds->wire, desc->addr, desc->len); return NFS4_OK; } static nfsstat4 pds_permissions(struct fsal_pnfs_ds *const pds, struct svc_req *req) { /* special case: related export has been set */ return nfs4_export_check_access(req); } /** * @param ops FSAL pNFS ds ops */ void pnfs_ds_ops_init(struct fsal_pnfs_ds_ops *ops) { memcpy(ops, &def_pnfs_ds_ops, sizeof(struct fsal_pnfs_ds_ops)); ops->ds_permissions = pds_permissions; ops->make_ds_handle = make_ds_handle; ops->dsh_release = ds_handle_release; ops->dsh_read = ds_read; ops->dsh_read_plus = ds_read_plus; ops->dsh_write = ds_write; ops->dsh_commit = ds_commit; } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/fsal_fileop.c000066400000000000000000000173751473756622300211210ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * @file fsal_fileop.c * @date $Date: 2006/01/17 14:20:07 $ * @brief Files operations. * * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #include "fsal.h" #include "fsal_internal.h" #include "fsal_convert.h" #include "gpfs_methods.h" #include "FSAL/access_check.h" /** @fn fsal_status_t * GPFSFSAL_open(struct fsal_obj_handle *obj_hdl, * fsal_openflags_t openflags, int *file_desc, * struct fsal_attrlist *fsal_attr) * * @brief Open a regular file for reading/writing its data content. * * @param obj_hdl Handle of the file to be read/modified. * @param op_ctx Authentication context for the operation (user,...). * @param openflags Flags that indicates behavior for file opening and access. * This is an inclusive OR of the following values * ( such of them are not compatible) : * - FSAL_O_RDONLY: opening file for reading only. * - FSAL_O_RDWR: opening file for reading and writing. * - FSAL_O_WRONLY: opening file for writing only. * - FSAL_O_APPEND: always write at the end of the file. * - FSAL_O_TRUNC: truncate the file to 0 on opening. * @param file_desc The file descriptor to be used for FSAL_read/write ops. * * @return ERR_FSAL_NO_ERROR on success, error otherwise */ fsal_status_t GPFSFSAL_open(struct fsal_obj_handle *obj_hdl, int posix_flags, int *file_desc) { struct gpfs_fsal_obj_handle *myself; fsal_status_t status; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; /* sanity checks. */ if (!obj_hdl || !file_desc) return fsalstat(ERR_FSAL_FAULT, 0); myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); LogFullDebug(COMPONENT_FSAL, "posix_flags 0x%X export_fd %d", posix_flags, export_fd); fsal_set_credentials(&op_ctx->creds); status = fsal_internal_handle2fd(export_fd, myself->handle, file_desc, posix_flags); fsal_restore_ganesha_credentials(); if (FSAL_IS_ERROR(status)) { /** Try open as root access if the above call fails, * permission will be checked somewhere else in the code. */ status = fsal_internal_handle2fd(export_fd, myself->handle, file_desc, posix_flags); } return status; } /** @fn fsal_status_t * GPFSFSAL_read(int fd, uint64_t offset, size_t buffer_size, * void * buffer, size_t *p_read_amount, * bool *p_end_of_file) * @brief Perform a read operation on an opened file. * @param fd The file descriptor returned by FSAL_open. * @offset Offset * @param buf_size Amount (in bytes) of data to be read. * @param buf Address where the read data is to be stored in memory. * @param read_amount Pointer to the amount of data (in bytes) that have been * read during this call. * @param end_of_file Pointer to a boolean that indicates whether the end of * file has been reached during this call. * * @return ERR_FSAL_NO_ERROR on success, error otherwise. */ fsal_status_t GPFSFSAL_read(int fd, uint64_t offset, size_t buf_size, void *buf, size_t *read_amount, bool *end_of_file, int expfd) { struct read_arg rarg = { 0 }; ssize_t nb_read; int errsv; /* sanity checks. */ if (!buf || !read_amount || !end_of_file) return fsalstat(ERR_FSAL_FAULT, 0); rarg.mountdirfd = expfd; rarg.fd = fd; rarg.bufP = buf; rarg.offset = offset; rarg.length = buf_size; rarg.options = 0; rarg.cli_ip = NULL; if (op_ctx && op_ctx->client) rarg.cli_ip = op_ctx->client->hostaddr_str; fsal_set_credentials(&op_ctx->creds); nb_read = gpfs_ganesha(OPENHANDLE_READ_BY_FD, &rarg); errsv = errno; fsal_restore_ganesha_credentials(); /* negative values mean error */ if (nb_read < 0) { /* if nb_read is not -1, the split rc/errno didn't work */ if (nb_read != -1) { errsv = labs(nb_read); LogWarn(COMPONENT_FSAL, "Received negative value (%d) from ioctl().", (int)nb_read); } if (errsv == EUNATCH) LogFatal(COMPONENT_FSAL, "GPFS Returned EUNATCH"); return fsalstat(posix2fsal_error(errsv), errsv); } if (nb_read == 0 || nb_read < buf_size) *end_of_file = true; *read_amount = nb_read; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** @fn fsal_status_t * GPFSFSAL_write(int fd, uint64_t offset, size_t buf_size, void * buf, * size_t *write_amount, bool *fsal_stable) * @brief Perform a write operation on an opened file. * * @param fd The file descriptor returned by FSAL_open. * @param buf_size Amount (in bytes) of data to be written. * @param buf Address where the data is in memory. * @param write_amount Pointer to the amount of data (in bytes) that have been * written during this call. * * @return ERR_FSAL_NO_ERROR on success, error otherwise */ fsal_status_t GPFSFSAL_write(int fd, uint64_t offset, size_t buf_size, void *buf, size_t *write_amount, bool *fsal_stable, int expfd) { struct write_arg warg = { 0 }; uint32_t stability_got = 0; ssize_t nb_write; int errsv; /* sanity checks. */ if (!buf || !write_amount) return fsalstat(ERR_FSAL_FAULT, 0); warg.mountdirfd = expfd; warg.fd = fd; warg.bufP = buf; warg.offset = offset; warg.length = buf_size; warg.stability_wanted = *fsal_stable; warg.stability_got = &stability_got; warg.options = 0; warg.cli_ip = NULL; if (op_ctx && op_ctx->client) warg.cli_ip = op_ctx->client->hostaddr_str; fsal_set_credentials(&op_ctx->creds); nb_write = gpfs_ganesha(OPENHANDLE_WRITE_BY_FD, &warg); errsv = errno; fsal_restore_ganesha_credentials(); if (nb_write == -1) { if (errsv == EUNATCH) LogFatal(COMPONENT_FSAL, "GPFS Returned EUNATCH"); return fsalstat(posix2fsal_error(errsv), errsv); } *write_amount = nb_write; *fsal_stable = (stability_got) ? true : false; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** @fn fsal_status_t * GPFSFSAL_alloc(int fd, uint64_t offset, uint64_t length, bool allocate) * @brief Perform a de/allocc operation on an opened file. * @param fd The file descriptor returned by FSAL_open. * @param offset The offset to allocate at * @param length The length of the allocation * @param allocate Flag to indicate allocate or deallocate * * @return ERR_FSAL_NO_ERROR on success, error otherwise */ fsal_status_t GPFSFSAL_alloc(int fd, uint64_t offset, uint64_t length, bool allocate) { struct alloc_arg aarg = { 0 }; int errsv; int rc; aarg.fd = fd; aarg.offset = offset; aarg.length = length; aarg.options = (allocate) ? IO_ALLOCATE : IO_DEALLOCATE; fsal_set_credentials(&op_ctx->creds); rc = gpfs_ganesha(OPENHANDLE_ALLOCATE_BY_FD, &aarg); errsv = errno; fsal_restore_ganesha_credentials(); if (rc == -1) { if (errsv == EUNATCH) LogFatal(COMPONENT_FSAL, "GPFS Returned EUNATCH"); return fsalstat(posix2fsal_error(errsv), errsv); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/fsal_internal.c000066400000000000000000000526171473756622300214550ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * @file fsal_internal.c * @date $Date: 2006/01/17 14:20:07 $ * @brief Defines the data that are to be accessed as extern by the fsal * modules * * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #include "config.h" #include #include "fsal.h" #include "fsal_internal.h" #include "gpfs_methods.h" #include "fsal_convert.h" #include /* used for 'dirname' */ #include "abstract_mem.h" #include #include #include #include "include/gpfs.h" #define FSAL_INTERNAL_ERROR(__error, __msg) \ ({ \ if ((__error) == EUNATCH) \ LogFatal(COMPONENT_FSAL, "GPFS Returned EUNATCH"); \ \ LogFullDebug(COMPONENT_FSAL, "%s returned errno=%d", __msg, \ __error); \ \ fsalstat(posix2fsal_error(__error), __error); \ }) /********************************************************************* * * GPFS FSAL char device driver interfaces * ********************************************************************/ /** * @brief Close by fd * * @param fd Open file descriptor * * @return status of operation */ fsal_status_t fsal_internal_close(int fd, void *owner, int cflags) { struct close_file_arg carg; carg.mountdirfd = fd; carg.close_fd = fd; carg.close_flags = cflags; carg.close_owner = owner; carg.cli_ip = NULL; if (op_ctx && op_ctx->client) carg.cli_ip = op_ctx->client->hostaddr_str; if (unlikely(gpfs_ganesha(OPENHANDLE_CLOSE_FILE, &carg) < 0)) return FSAL_INTERNAL_ERROR(errno, "OPENHANDLE_CLOSE_FILE"); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Open a file by handle from in an open directory * * @param dirfd Open file descriptor of parent directory * @param gpfs_fh Opaque filehandle * @param fd File descriptor opened by the function * @param oflags Flags to open the file with * * @return status of operation */ fsal_status_t fsal_internal_handle2fd(int dirfd, struct gpfs_file_handle *gpfs_fh, int *fd, int oflags) { struct open_arg oarg = { 0 }; int rc; oarg.mountdirfd = dirfd; oarg.handle = gpfs_fh; oarg.flags = oflags; oarg.cli_ip = NULL; if (op_ctx && op_ctx->client) oarg.cli_ip = op_ctx->client->hostaddr_str; rc = gpfs_ganesha(OPENHANDLE_OPEN_BY_HANDLE, &oarg); if (unlikely(rc < 0)) return FSAL_INTERNAL_ERROR(errno, "OPENHANDLE_OPEN_BY_HANDLE"); *fd = rc; /* Make sure the fd is not less than 3 */ assert(*fd >= 3); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Create a handle from a directory pointer and filename * * @param dfd Open directory handle * @param fs_name Name of the file * @param gpfs_fh The handle that is found and returned * * @return status of operation */ fsal_status_t fsal_internal_get_handle_at(int dfd, const char *fs_name, struct gpfs_file_handle *gpfs_fh, int expfd) { struct name_handle_arg harg; if (!gpfs_fh) return fsalstat(ERR_FSAL_FAULT, 0); harg.handle = gpfs_fh; harg.handle->handle_size = GPFS_MAX_FH_SIZE; harg.handle->handle_version = OPENHANDLE_VERSION; harg.handle->handle_key_size = OPENHANDLE_KEY_LEN; harg.name = fs_name; harg.dfd = dfd; harg.expfd = expfd; harg.flag = 0; LogFullDebug(COMPONENT_FSAL, "Lookup handle at %d for %s", dfd, fs_name); if (unlikely(gpfs_ganesha(OPENHANDLE_NAME_TO_HANDLE, &harg) < 0)) return FSAL_INTERNAL_ERROR(errno, "OPENHANDLE_NAME_TO_HANDLE"); /* Workaround: Currently for lookup of name (specifically ..), GPFS may * return a handle with handle_size=40, which is not correct. Expected * value of handle_size for a GPFS handle is 48 bytes. * Till we don't have a fix from GPFS for this issue we will set the * handle_size to 48 in case GPFS returned handle with handle_size=40. */ if (harg.handle->handle_size == 40) harg.handle->handle_size = 48; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Create a handle from a directory handle and filename * * @param dirfd Open file descriptor of parent directory * @param gpfs_fh The handle for the parent directory * @param fs_name Name of the file * @param gpfs_fh_out The handle that is found and returned * * @return status of operation */ fsal_status_t fsal_internal_get_fh(int dirfd, struct gpfs_file_handle *gpfs_fh, const char *fs_name, struct gpfs_file_handle *gpfs_fh_out) { struct get_handle_arg harg; if (!gpfs_fh_out || !gpfs_fh || !fs_name) return fsalstat(ERR_FSAL_FAULT, 0); harg.mountdirfd = dirfd; harg.dir_fh = gpfs_fh; harg.out_fh = gpfs_fh_out; harg.out_fh->handle_size = GPFS_MAX_FH_SIZE; harg.out_fh->handle_version = OPENHANDLE_VERSION; harg.out_fh->handle_key_size = OPENHANDLE_KEY_LEN; harg.len = strlen(fs_name); harg.name = fs_name; LogFullDebug(COMPONENT_FSAL, "Lookup handle for %s", fs_name); if (unlikely(gpfs_ganesha(OPENHANDLE_GET_HANDLE, &harg) < 0)) return FSAL_INTERNAL_ERROR(errno, "OPENHANDLE_GET_HANDLE"); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief convert an fd to a handle * * @param fd Open file descriptor for target file * @param gpfs_fh The handle that is found and returned * * @return status of operation */ fsal_status_t fsal_internal_fd2handle(int fd, struct gpfs_file_handle *gpfs_fh) { struct name_handle_arg harg = { 0 }; if (!gpfs_fh) return fsalstat(ERR_FSAL_FAULT, 0); harg.handle = gpfs_fh; harg.handle->handle_size = GPFS_MAX_FH_SIZE; harg.handle->handle_key_size = OPENHANDLE_KEY_LEN; harg.handle->handle_version = OPENHANDLE_VERSION; harg.dfd = fd; LogFullDebug(COMPONENT_FSAL, "Lookup handle by fd for %d", fd); if (unlikely(gpfs_ganesha(OPENHANDLE_NAME_TO_HANDLE, &harg) < 0)) return FSAL_INTERNAL_ERROR(errno, "OPENHANDLE_NAME_TO_HANDLE"); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Create a link based on a file fh, dir fh, and new name * * @param dir_fd Open file descriptor of parent directory * @param gpfs_fh_tgt file handle of target file * @param gpfs_fh file handle of source directory * @param link_name name for the new file * * @return status of operation */ fsal_status_t fsal_internal_link_fh(int dirfd, struct gpfs_file_handle *gpfs_fh_tgt, struct gpfs_file_handle *gpfs_fh, const char *link_name) { struct link_fh_arg linkarg; if (!link_name) return fsalstat(ERR_FSAL_FAULT, 0); linkarg.mountdirfd = dirfd; linkarg.len = strlen(link_name); linkarg.name = link_name; linkarg.dir_fh = gpfs_fh; linkarg.dst_fh = gpfs_fh_tgt; linkarg.cli_ip = NULL; if (op_ctx && op_ctx->client) linkarg.cli_ip = op_ctx->client->hostaddr_str; if (unlikely(gpfs_ganesha(OPENHANDLE_LINK_BY_FH, &linkarg) < 0)) return FSAL_INTERNAL_ERROR(errno, "OPENHANDLE_LINK_BY_FH"); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Stat a file by name * * @param dirfd Open file descriptor of parent directory * @param gpfs_fh file handle of directory * @param stat_name name to stat * @param buf buffer reference to buffer * * @return status of operation */ fsal_status_t fsal_internal_stat_name(int dirfd, struct gpfs_file_handle *gpfs_fh, const char *stat_name, struct stat *buf) { struct stat_name_arg statarg; if (!stat_name) return fsalstat(ERR_FSAL_FAULT, 0); statarg.mountdirfd = dirfd; statarg.len = strlen(stat_name); statarg.name = stat_name; statarg.handle = gpfs_fh; statarg.buf = buf; statarg.cli_ip = NULL; if (op_ctx && op_ctx->client) statarg.cli_ip = op_ctx->client->hostaddr_str; if (unlikely(gpfs_ganesha(OPENHANDLE_STAT_BY_NAME, &statarg) < 0)) return FSAL_INTERNAL_ERROR(errno, "OPENHANDLE_STAT_BY_NAME"); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Unlink a file/directory by name * * @param dirfd Open file descriptor of parent directory * @param gpfs_fh file handle of directory * @param stat_name name to unlink * @param buf reference to buffer * * @return status of operation */ fsal_status_t fsal_internal_unlink(int dirfd, struct gpfs_file_handle *gpfs_fh, const char *stat_name, struct stat *buf) { struct stat_name_arg statarg; int rc, errsv; if (!stat_name) return fsalstat(ERR_FSAL_FAULT, 0); statarg.mountdirfd = dirfd; statarg.len = strlen(stat_name); statarg.name = stat_name; statarg.handle = gpfs_fh; statarg.buf = buf; statarg.cli_ip = NULL; if (op_ctx && op_ctx->client) statarg.cli_ip = op_ctx->client->hostaddr_str; fsal_set_credentials(&op_ctx->creds); rc = gpfs_ganesha(OPENHANDLE_UNLINK_BY_NAME, &statarg); errsv = errno; fsal_restore_ganesha_credentials(); if (unlikely(rc < 0)) return FSAL_INTERNAL_ERROR(errsv, "OPENHANDLE_UNLINK_BY_NAME"); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Create a file/directory by name * * @param dir_hdl file handle of directory * @param stat_name name to create * @param mode file type for mknode * @param dev file dev for mknode * @param fh file handle of new file * @param buf file attributes of new file * * @return status of operation */ fsal_status_t fsal_internal_create(struct fsal_obj_handle *dir_hdl, const char *stat_name, mode_t mode, int posix_flags, struct gpfs_file_handle *fh, struct stat *buf) { struct create_name_arg crarg = { 0 }; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; if (!stat_name) return fsalstat(ERR_FSAL_FAULT, 0); crarg.mountdirfd = export_fd; crarg.mode = mode; crarg.dev = posix_flags; crarg.len = strlen(stat_name); crarg.name = stat_name; crarg.new_fh = fh; crarg.new_fh->handle_size = GPFS_MAX_FH_SIZE; crarg.new_fh->handle_key_size = OPENHANDLE_KEY_LEN; crarg.new_fh->handle_version = OPENHANDLE_VERSION; crarg.buf = buf; crarg.dir_fh = container_of(dir_hdl, struct gpfs_fsal_obj_handle, obj_handle) ->handle; crarg.cli_ip = NULL; if (op_ctx && op_ctx->client) crarg.cli_ip = op_ctx->client->hostaddr_str; if (unlikely(gpfs_ganesha(OPENHANDLE_CREATE_BY_NAME, &crarg) < 0)) return FSAL_INTERNAL_ERROR(errno, "OPENHANDLE_CREATE_BY_NAME"); return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t fsal_internal_mknode(struct fsal_obj_handle *dir_hdl, const char *stat_name, mode_t mode, dev_t dev, struct gpfs_file_handle *fh, struct stat *buf) { struct create_name_arg crarg = { 0 }; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; if (!stat_name) return fsalstat(ERR_FSAL_FAULT, 0); crarg.mountdirfd = export_fd; crarg.mode = mode; crarg.dev = dev; crarg.len = strlen(stat_name); crarg.name = stat_name; crarg.new_fh = fh; crarg.new_fh->handle_size = GPFS_MAX_FH_SIZE; crarg.new_fh->handle_key_size = OPENHANDLE_KEY_LEN; crarg.new_fh->handle_version = OPENHANDLE_VERSION; crarg.buf = buf; crarg.dir_fh = container_of(dir_hdl, struct gpfs_fsal_obj_handle, obj_handle) ->handle; crarg.cli_ip = NULL; if (op_ctx && op_ctx->client) crarg.cli_ip = op_ctx->client->hostaddr_str; if (unlikely(gpfs_ganesha(OPENHANDLE_MKNODE_BY_NAME, &crarg) < 0)) return FSAL_INTERNAL_ERROR(errno, "OPENHANDLE_MKNODE_BY_NAME"); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Rename old file name to new name * * @param dirfd Open file descriptor of parent directory * @param gpfs_fh_old file handle of old file * @param gpfs_fh_new file handle of new directory * @param old_name name for the old file * @param new_name name for the new file * * @return status of operation */ fsal_status_t fsal_internal_rename_fh(int dirfd, struct gpfs_file_handle *gpfs_fh_old, struct gpfs_file_handle *gpfs_fh_new, const char *old_name, const char *new_name) { struct rename_fh_arg renamearg; int rc, errsv; if (!old_name || !new_name) return fsalstat(ERR_FSAL_FAULT, 0); renamearg.mountdirfd = dirfd; renamearg.old_len = strlen(old_name); renamearg.old_name = old_name; renamearg.new_len = strlen(new_name); renamearg.new_name = new_name; renamearg.old_fh = gpfs_fh_old; renamearg.new_fh = gpfs_fh_new; renamearg.cli_ip = NULL; if (op_ctx && op_ctx->client) renamearg.cli_ip = op_ctx->client->hostaddr_str; fsal_set_credentials(&op_ctx->creds); rc = gpfs_ganesha(OPENHANDLE_RENAME_BY_FH, &renamearg); errsv = errno; fsal_restore_ganesha_credentials(); if (unlikely(rc < 0)) return FSAL_INTERNAL_ERROR(errsv, "OPENHANDLE_RENAME_BY_FH"); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Reads the contents of the link * * @param dirfd Open file descriptor of parent directory * @param gpfs_fh file handle of file * @param buf Buffer * @param maxlen Max length of buffer * * @return status of operation */ fsal_status_t fsal_readlink_by_handle(int dirfd, struct gpfs_file_handle *gpfs_fh, char *buf, size_t maxlen) { struct readlink_fh_arg readlinkarg; int rc; readlinkarg.mountdirfd = dirfd; readlinkarg.handle = gpfs_fh; readlinkarg.buffer = buf; readlinkarg.size = maxlen - 1; /* reserve 1 for terminating 0-byte */ rc = gpfs_ganesha(OPENHANDLE_READLINK_BY_FH, &readlinkarg); if (unlikely(rc < 0)) return FSAL_INTERNAL_ERROR(errno, "OPENHANDLE_READLINK_BY_FH"); buf[rc] = '\0'; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief fsal_internal_version; * * @return GPFS version */ int fsal_internal_version(void) { int rc; /* Try VERSION4 first, followed by VERSION3,2 */ rc = gpfs_ganesha(OPENHANDLE_GET_VERSION4, NULL); if (rc != -1) return 0; rc = gpfs_ganesha(OPENHANDLE_GET_VERSION3, NULL); if (rc != -1) return 0; rc = gpfs_ganesha(OPENHANDLE_GET_VERSION2, NULL); if (rc != -1) return 0; LogMajor(COMPONENT_FSAL, "OPENHANDLE_GET_VERSION failed: %d", errno); return errno; } /** * @brief Get NFS4 ACL as well as stat. * * @param dirfd Open file descriptor of parent directory * @param gpfs_fh file handle of file * @param buffxstat Buffer * @param expire_time_attr Expire time attributes * @param expire Bool expire * @param use_acl Bool whether to ACL is to be used * @return status of operation */ fsal_status_t fsal_get_xstat_by_handle(int dirfd, struct gpfs_file_handle *gpfs_fh, gpfsfsal_xstat_t *buffxstat, gpfs_acl_t *acl_buf, unsigned int acl_buflen, uint32_t *expire_time_attr, bool expire, bool use_acl) { struct xstat_arg xstatarg = { 0 }; int errsv; int rc; if (!gpfs_fh || !buffxstat) return fsalstat(ERR_FSAL_FAULT, 0); /* Initialize acl header so that GPFS knows what we want. */ if (use_acl) { acl_buf->acl_level = 0; acl_buf->acl_version = GPFS_ACL_VERSION_NFS4; acl_buf->acl_type = GPFS_ACL_TYPE_NFS4; acl_buf->acl_len = acl_buflen; acl_buf->acl_nace = 0; xstatarg.acl = acl_buf; xstatarg.attr_valid = XATTR_STAT | XATTR_ACL; } else { xstatarg.acl = NULL; xstatarg.attr_valid = XATTR_STAT; } if (expire) xstatarg.attr_valid |= XATTR_EXPIRE; xstatarg.mountdirfd = dirfd; xstatarg.handle = gpfs_fh; xstatarg.attr_changed = 0; xstatarg.buf = &buffxstat->buffstat; xstatarg.fsid = (struct fsal_fsid *)&buffxstat->fsal_fsid; xstatarg.attr_valid |= XATTR_FSID; xstatarg.expire_attr = expire_time_attr; xstatarg.cli_ip = NULL; if (op_ctx && op_ctx->client) xstatarg.cli_ip = op_ctx->client->hostaddr_str; rc = gpfs_ganesha(OPENHANDLE_GET_XSTAT, &xstatarg); errsv = errno; LogDebug(COMPONENT_FSAL, "GET_XSTAT returned, fd %d rc %d fh_size %d", dirfd, rc, gpfs_fh->handle_size); if (rc < 0) { switch (errsv) { case ENODATA: /* For the special file that do not have ACL, GPFS returns ENODATA. In this case, return okay with stat. */ buffxstat->attr_valid = XATTR_STAT; LogFullDebug(COMPONENT_FSAL, "GET_XSTAT retrieved only stat, not acl"); return fsalstat(ERR_FSAL_NO_ERROR, 0); case ENOSPC: /* If the supplied acl buffer is too small, we * get this errno! acl_len will be updated to * the required length. * * Return success and let the caller check the length */ if (use_acl && acl_buf->acl_len > acl_buflen) { LogFullDebug( COMPONENT_FSAL, "GET_XSTAT returned buffer too small, passed len: %u, required len: %u, ", acl_buflen, acl_buf->acl_len); errno = 0; return fsalstat(ERR_FSAL_NO_ERROR, 0); } LogWarn(COMPONENT_FSAL, "GET_XSTAT returned bogus ENOSPC, passed len: %u, required len: %u", acl_buflen, acl_buf->acl_len); return fsalstat(ERR_FSAL_SERVERFAULT, errsv); default: /* Handle other errors. */ LogFullDebug(COMPONENT_FSAL, "GET_XSTAT returned errno:%d -- %s", errsv, strerror(errsv)); if (errsv == EUNATCH) LogFatal(COMPONENT_FSAL, "GPFS Returned EUNATCH"); return fsalstat(posix2fsal_error(errsv), errsv); } } if (use_acl) { if (acl_buf->acl_nace > GPFS_ACL_MAX_NACES) { LogEvent( COMPONENT_FSAL, "No. of ACE's:%d higher than supported by GPFS", acl_buf->acl_nace); /* Fail the request if ACL is invalid*/ return fsalstat(ERR_FSAL_SERVERFAULT, 0); } buffxstat->attr_valid = XATTR_FSID | XATTR_STAT | XATTR_ACL; } else { buffxstat->attr_valid = XATTR_FSID | XATTR_STAT; } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Set NFS4 ACL as well as stat. * * @param dirfd Open file descriptor of parent directory * @param gpfs_fh file handle of file * @param attr_valid Attributes valid * @param attr_changed Attributes changed * @param buffxstat Buffer * * @return status of operation */ fsal_status_t fsal_set_xstat_by_handle(int dirfd, struct gpfs_file_handle *gpfs_fh, int attr_valid, int attr_changed, gpfsfsal_xstat_t *buffxstat, gpfs_acl_t *acl_buf) { struct xstat_arg xstatarg = { 0 }; int errsv; int rc; if (!gpfs_fh || !buffxstat) return fsalstat(ERR_FSAL_FAULT, 0); xstatarg.attr_valid = attr_valid; xstatarg.mountdirfd = dirfd; xstatarg.handle = gpfs_fh; xstatarg.acl = acl_buf; xstatarg.attr_changed = attr_changed; xstatarg.buf = &buffxstat->buffstat; xstatarg.cli_ip = NULL; if (op_ctx && op_ctx->client) xstatarg.cli_ip = op_ctx->client->hostaddr_str; /* We explicitly do NOT do setfsuid/setfsgid here because truncate, even to enlarge a file, doesn't actually allocate blocks. GPFS implements sparse files, so blocks of all 0 will not actually be allocated. */ fsal_set_credentials(&op_ctx->creds); rc = gpfs_ganesha(OPENHANDLE_SET_XSTAT, &xstatarg); errsv = errno; fsal_restore_ganesha_credentials(); LogDebug(COMPONENT_FSAL, "gpfs_ganesha: SET_XSTAT returned, rc = %d", rc); if (rc < 0) { if (errsv == EUNATCH) LogFatal(COMPONENT_FSAL, "GPFS Returned EUNATCH"); return fsalstat(posix2fsal_error(errsv), errsv); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @param dirfd Open file descriptor of parent directory * @param gpfs_fh file handle of file * @param size The size to truncate the file to * * @return status of operation */ fsal_status_t fsal_trucate_by_handle(int dirfd, struct gpfs_file_handle *gpfs_fh, u_int64_t size) { gpfsfsal_xstat_t buffxstat = { 0 }; if (!gpfs_fh || !op_ctx) return fsalstat(ERR_FSAL_FAULT, 0); buffxstat.buffstat.st_size = size; return fsal_set_xstat_by_handle(dirfd, gpfs_fh, XATTR_STAT, XATTR_SIZE, &buffxstat, NULL); } /** * @brief Indicates if an FSAL error should be posted as an event * * @param status(input): The fsal status whom event is to be tested. * @return - true if the error event is to be posted. * - false if the error event is NOT to be posted. */ bool fsal_error_is_event(fsal_status_t status) { switch (status.major) { case ERR_FSAL_IO: case ERR_FSAL_STALE: return true; default: return false; } } /** * @brief Indicates if an FSAL error should be posted as an INFO level debug * message. * * @param status(input): The fsal status whom event is to be tested. * @return - true if the error event is to be posted. * - false if the error event is NOT to be posted. */ bool fsal_error_is_info(fsal_status_t status) { switch (status.major) { case ERR_FSAL_NOTDIR: case ERR_FSAL_NOMEM: case ERR_FSAL_FAULT: case ERR_FSAL_EXIST: case ERR_FSAL_XDEV: case ERR_FSAL_ISDIR: case ERR_FSAL_INVAL: case ERR_FSAL_FBIG: case ERR_FSAL_NOSPC: case ERR_FSAL_MLINK: case ERR_FSAL_NAMETOOLONG: case ERR_FSAL_STALE: case ERR_FSAL_NOTSUPP: case ERR_FSAL_OVERFLOW: case ERR_FSAL_DEADLOCK: case ERR_FSAL_INTERRUPT: case ERR_FSAL_SERVERFAULT: return true; default: return false; } } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/fsal_internal.h000066400000000000000000000241171473756622300214540ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * * @file fsal_internal.h * @brief Extern definitions for variables that are * defined in fsal_internal.c. */ #ifndef FSAL_INTERNAL_H #define FSAL_INTERNAL_H #include #include "fsal.h" #include "gsh_list.h" #include "fsal_types.h" #include "fcntl.h" #include "include/gpfs_nfs.h" #include "include/gpfs.h" #include "fsal_up.h" #include "gsh_config.h" #include "FSAL/fsal_commonlib.h" struct gpfs_filesystem; /* * GPFS internal module */ struct gpfs_fsal_module { struct fsal_module module; struct fsal_obj_ops handle_ops; struct fsal_obj_ops handle_ops_with_pnfs; }; extern struct gpfs_fsal_module GPFS; void gpfs_handle_ops_init(struct fsal_obj_ops *ops); bool fsal_error_is_event(fsal_status_t status); /* * Tests whether an error code should be raised as an info debug. */ bool fsal_error_is_info(fsal_status_t status); /** * The full, 'private' DS (data server) handle */ struct gpfs_ds { struct fsal_ds_handle ds; /*< Public DS handle */ struct gpfs_file_handle wire; /*< Wire data */ struct gpfs_filesystem *gpfs_fs; /*< filesystem handle belongs to */ bool connected; /*< True if the handle has been connected */ }; struct gpfs_fd { /** open and share mode plus fd management */ struct fsal_fd fsal_fd; /** The gpfsfs file descriptor. */ int fd; }; struct gpfs_state_fd { /** state MUST be first to use default free_state */ struct state_t state; struct gpfs_fd gpfs_fd; }; /* defined the set of attributes supported with POSIX */ #define GPFS_SUPPORTED_ATTRIBUTES \ ((const attrmask_t)(ATTRS_POSIX | ATTR_ACL | ATTR4_SPACE_RESERVED | \ ATTR4_FS_LOCATIONS)) #define GPFS_MAX_FH_SIZE OPENHANDLE_HANDLE_LEN /* Define the buffer size for GPFS NFS4 ACL. */ #define GPFS_ACL_BUF_SIZE 0x1000 #define GPFS_ACL_MAX_RETRY 10 /* Max no. of ACEs supported for GPFS NFS4 ACL */ #define GPFS_ACL_MAX_NACES 638 /* Define the standard fsid_type for GPFS*/ #define GPFS_FSID_TYPE FSID_MAJOR_64 /* A set of buffers to retrieve multiple attributes at the same time. */ typedef struct fsal_xstat__ { int attr_valid; struct stat buffstat; fsal_fsid_t fsal_fsid; char buffacl[GPFS_ACL_BUF_SIZE]; } gpfsfsal_xstat_t; static inline size_t gpfs_sizeof_handle(const struct gpfs_file_handle *hdl) { return hdl->handle_size; } void export_ops_init(struct export_ops *ops); void handle_ops_init(struct fsal_obj_ops *ops); void pnfs_ds_ops_init(struct fsal_pnfs_ds_ops *ops); void export_ops_pnfs(struct export_ops *ops); void handle_ops_pnfs(struct fsal_obj_ops *ops); fsal_status_t fsal_internal_close(int fd, void *owner, int cflags); int fsal_internal_version(void); fsal_status_t fsal_internal_get_handle_at(int dfd, const char *p_fsalname, struct gpfs_file_handle *p_handle, int expfd); fsal_status_t gpfsfsal_xstat_2_fsal_attributes(gpfsfsal_xstat_t *gpfs_buf, struct fsal_attrlist *fsal_attr, gpfs_acl_t *acl_buf, bool use_acl); fsal_status_t fsal_internal_handle2fd(int dirfd, struct gpfs_file_handle *phandle, int *pfd, int oflags); /** * Gets a file handle from a parent handle and name */ fsal_status_t fsal_internal_get_fh(int dirfd, struct gpfs_file_handle *p_dir_handle, const char *p_fsalname, struct gpfs_file_handle *p_handle); /** * Access a link by a file handle. */ fsal_status_t fsal_readlink_by_handle(int dirfd, struct gpfs_file_handle *p_handle, char *__buf, size_t maxlen); /** * Get the handle for a path (posix or fid path) */ fsal_status_t fsal_internal_fd2handle(int fd, struct gpfs_file_handle *p_handle); fsal_status_t fsal_internal_link_at(int srcfd, int dfd, char *name); fsal_status_t fsal_internal_link_fh(int dirfd, struct gpfs_file_handle *p_target_handle, struct gpfs_file_handle *p_dir_handle, const char *p_link_name); fsal_status_t fsal_internal_stat_name(int dirfd, struct gpfs_file_handle *p_dir_handle, const char *p_stat_name, struct stat *buf); fsal_status_t fsal_internal_unlink(int dirfd, struct gpfs_file_handle *p_dir_handle, const char *p_stat_name, struct stat *buf); fsal_status_t fsal_internal_create(struct fsal_obj_handle *dir_hdl, const char *p_stat_name, mode_t mode, int posix_flags, struct gpfs_file_handle *p_new_handle, struct stat *buf); fsal_status_t fsal_internal_mknode(struct fsal_obj_handle *dir_hdl, const char *p_stat_name, mode_t mode, dev_t dev, struct gpfs_file_handle *p_new_handle, struct stat *buf); fsal_status_t fsal_internal_rename_fh(int dirfd, struct gpfs_file_handle *p_old_handle, struct gpfs_file_handle *p_new_handle, const char *p_old_name, const char *p_new_name); fsal_status_t fsal_get_xstat_by_handle(int dirfd, struct gpfs_file_handle *p_handle, gpfsfsal_xstat_t *p_buffxstat, gpfs_acl_t *acl_buf, unsigned int acl_buflen, uint32_t *expire_time_attr, bool expire, bool use_acl); fsal_status_t fsal_set_xstat_by_handle(int dirfd, struct gpfs_file_handle *p_handle, int attr_valid, int attr_changed, gpfsfsal_xstat_t *p_buffxstat, gpfs_acl_t *acl_buf); fsal_status_t fsal_trucate_by_handle(int dirfd, struct gpfs_file_handle *p_handle, u_int64_t size); /* All the call to FSAL to be wrapped */ fsal_status_t GPFSFSAL_getattrs(struct fsal_export *export, struct gpfs_filesystem *gpfs_fs, struct gpfs_file_handle *p_filehandle, struct fsal_attrlist *p_object_attributes); fsal_status_t GPFSFSAL_fs_loc(struct fsal_export *export, struct gpfs_filesystem *gpfs_fs, struct gpfs_file_handle *p_filehandle, struct fsal_attrlist *attrs); fsal_status_t GPFSFSAL_statfs(int fd, struct fsal_obj_handle *obj_hdl, struct statfs *buf); fsal_status_t GPFSFSAL_setattrs(struct fsal_obj_handle *dir_hdl, struct fsal_attrlist *p_object_attributes); fsal_status_t GPFSFSAL_create(struct fsal_obj_handle *dir_hdl, const char *p_filename, uint32_t accessmode, struct gpfs_file_handle *p_object_handle, struct fsal_attrlist *p_object_attributes); fsal_status_t GPFSFSAL_create2(struct fsal_obj_handle *dir_hdl, const char *p_filename, mode_t unix_mode, struct gpfs_file_handle *p_object_handle, int posix_flags, struct fsal_attrlist *p_object_attributes); fsal_status_t GPFSFSAL_mkdir(struct fsal_obj_handle *dir_hdl, const char *p_dirname, uint32_t accessmode, struct gpfs_file_handle *p_object_handle, struct fsal_attrlist *p_object_attributes); fsal_status_t GPFSFSAL_link(struct fsal_obj_handle *dir_hdl, struct gpfs_file_handle *p_target_handle, const char *p_link_name); fsal_status_t GPFSFSAL_mknode(struct fsal_obj_handle *dir_hdl, const char *p_node_name, uint32_t accessmode, mode_t nodetype, fsal_dev_t *dev, struct gpfs_file_handle *p_object_handle, struct fsal_attrlist *node_attributes); fsal_status_t GPFSFSAL_open(struct fsal_obj_handle *obj_hdl, int posix_flags, int *p_file_descriptor); fsal_status_t GPFSFSAL_read(int fd, uint64_t offset, size_t buffer_size, void *buffer, size_t *p_read_amount, bool *p_end_of_file, int expfs); fsal_status_t GPFSFSAL_write(int fd, uint64_t offset, size_t buffer_size, void *buffer, size_t *p_write_amount, bool *fsal_stable, int expfs); fsal_status_t GPFSFSAL_alloc(int fd, uint64_t offset, uint64_t length, bool options); fsal_status_t GPFSFSAL_lookup(struct fsal_obj_handle *parent, const char *p_filename, struct fsal_attrlist *p_object_attr, struct gpfs_file_handle *fh, struct fsal_filesystem **new_fs); fsal_status_t GPFSFSAL_lock_op(struct fsal_export *export, fsal_lock_op_t lock_op, fsal_lock_param_t *req_lock, fsal_lock_param_t *confl_lock, struct set_get_lock_arg *sg_lock_arg); fsal_status_t GPFSFSAL_rename(struct fsal_obj_handle *old_hdl, const char *p_old_name, struct fsal_obj_handle *new_hdl, const char *p_new_name); fsal_status_t GPFSFSAL_readlink(struct fsal_obj_handle *dir_hdl, char *p_link_content, size_t link_len); fsal_status_t GPFSFSAL_symlink(struct fsal_obj_handle *dir_hdl, const char *p_linkname, const char *p_linkcontent, uint32_t accessmode, /* IN (ignored) */ struct gpfs_file_handle *p_link_handle, struct fsal_attrlist *p_link_attributes); fsal_status_t GPFSFSAL_unlink(struct fsal_obj_handle *dir_hdl, const char *p_object_name); void *GPFSFSAL_UP_Thread(void *Arg); size_t fs_da_addr_size(struct fsal_module *fsal_hdl); nfsstat4 getdeviceinfo(struct fsal_module *fsal_hdl, XDR *da_addr_body, const layouttype4 type, const struct pnfs_deviceid *deviceid); void fsal_gpfs_extract_stats(struct fsal_module *fsal_hdl, void *iter); void fsal_gpfs_reset_stats(struct fsal_module *fsal_hdl); void prepare_for_stats(struct fsal_module *fsal_hdl); int gpfs_op2index(int op); #endif nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/fsal_lock.c000066400000000000000000000067731473756622300205730ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /** @file fsal_lock.s * * Copyright IBM Corporation, 2010 * Contributor: Aneesh Kumar K.v * * * This software is a server that implements the NFS protocol. * * * This program 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 3 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 * 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 * */ /* _FILE_OFFSET_BITS macro causes F_GETLK/SETLK/SETLKW to be defined to * F_GETLK64/SETLK64/SETLKW64. Currently GPFS kernel module doesn't work * with these 64 bit macro values through ganesha interface. Undefine it * here to use plain F_GETLK/SETLK/SETLKW values. */ #undef _FILE_OFFSET_BITS #include "config.h" #include "fsal.h" #include "fsal_internal.h" #include "fsal_convert.h" #include "gpfs_methods.h" fsal_status_t GPFSFSAL_lock_op(struct fsal_export *export, fsal_lock_op_t lock_op, fsal_lock_param_t *req_lock, fsal_lock_param_t *confl_lock, struct set_get_lock_arg *sg_lock_arg) { struct glock *glock = sg_lock_arg->lock; int retval; int errsv; if (req_lock->lock_sle_type == FSAL_LEASE_LOCK) retval = gpfs_ganesha(OPENHANDLE_SET_DELEGATION, sg_lock_arg); else if (lock_op == FSAL_OP_LOCKT) retval = gpfs_ganesha(OPENHANDLE_GET_LOCK, sg_lock_arg); else retval = gpfs_ganesha(OPENHANDLE_SET_LOCK, sg_lock_arg); if (retval) { errsv = errno; goto err_out; } /* F_UNLCK is returned then the tested operation would be possible. */ if (confl_lock != NULL) { if (lock_op == FSAL_OP_LOCKT && glock->flock.l_type != F_UNLCK) { confl_lock->lock_length = glock->flock.l_len; confl_lock->lock_start = glock->flock.l_start; confl_lock->lock_type = glock->flock.l_type; } else { confl_lock->lock_length = 0; confl_lock->lock_start = 0; confl_lock->lock_type = FSAL_NO_LOCK; } } return fsalstat(ERR_FSAL_NO_ERROR, 0); err_out: /* error only section from here on */ if ((confl_lock != NULL) && (lock_op == FSAL_OP_LOCK || lock_op == FSAL_OP_LOCKB)) { glock->cmd = F_GETLK; if (gpfs_ganesha(OPENHANDLE_GET_LOCK, sg_lock_arg)) { int errsv2 = errno; LogCrit(COMPONENT_FSAL, "After failing a set lock request, an attempt to get the current owner details also failed."); if (errsv2 == EUNATCH) LogFatal(COMPONENT_FSAL, "GPFS Returned EUNATCH"); } else { confl_lock->lock_length = glock->flock.l_len; confl_lock->lock_start = glock->flock.l_start; confl_lock->lock_type = glock->flock.l_type; } } if (retval == 1) { LogFullDebug(COMPONENT_FSAL, "GPFS queued blocked lock"); return fsalstat(ERR_FSAL_BLOCKED, 0); } LogFullDebug(COMPONENT_FSAL, "GPFS lock operation failed error %d %d (%s)", retval, errsv, strerror(errsv)); if (errsv == EUNATCH) LogFatal(COMPONENT_FSAL, "GPFS Returned EUNATCH"); if (errsv == EGRACE) return fsalstat(ERR_FSAL_IN_GRACE, 0); return fsalstat(posix2fsal_error(errsv), errsv); } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/fsal_lookup.c000066400000000000000000000142061473756622300211420ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /** * @file fsal_lookup.c * @date $Date: 2006/01/24 13:45:37 $ * @brief Lookup operations. * * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #include "config.h" #include #include "fsal.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_localfs.h" #include "fsal_internal.h" #include "fsal_convert.h" #include "gpfs_methods.h" uint64_t get_handle2inode(struct gpfs_file_handle *gfh) { struct f_handle { char unused1[8]; uint64_t inode; /* inode for file */ char unused2[8]; uint64_t pinode; /* inode for parent */ } *f_handle = (struct f_handle *)gfh->f_handle; return f_handle->inode; } /** * @brief Looks up for an object into a directory. * * if parent handle and filename are NULL, * this retrieves root's handle. * * @param parent Handle of the parent directory to search the object in. * @param filename The name of the object to find. * @param fsal_attr Pointer to the attributes of the object we found. * @param fh The handle of the object corresponding to filename. * @param new_fs New FS * * @return - ERR_FSAL_NO_ERROR, if no error. * - Another error code else. */ #define GPFS_ROOT_INODE 3 fsal_status_t GPFSFSAL_lookup(struct fsal_obj_handle *parent, const char *filename, struct fsal_attrlist *fsal_attr, struct gpfs_file_handle *fh, struct fsal_filesystem **new_fs) { fsal_status_t status; int parent_fd; struct gpfs_fsal_obj_handle *parent_hdl; struct gpfs_filesystem *gpfs_fs; struct fsal_fsid__ fsid; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; if (!parent || !filename) return fsalstat(ERR_FSAL_FAULT, 0); assert(*new_fs == parent->fs); parent_hdl = container_of(parent, struct gpfs_fsal_obj_handle, obj_handle); gpfs_fs = parent->fs->private_data; status = fsal_internal_handle2fd(export_fd, parent_hdl->handle, &parent_fd, O_RDONLY); if (FSAL_IS_ERROR(status)) return status; /* Be careful about junction crossing, symlinks, hardlinks,... */ switch (parent->type) { case DIRECTORY: /* OK */ break; case REGULAR_FILE: case SYMBOLIC_LINK: /* not a directory */ fsal_internal_close(parent_fd, NULL, 0); return fsalstat(ERR_FSAL_NOTDIR, 0); default: fsal_internal_close(parent_fd, NULL, 0); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } status = fsal_internal_get_handle_at(parent_fd, filename, fh, export_fd); /* After getting file handle 'fh' we won't be using parent_fd, * hence we can close parent_fd here. */ fsal_internal_close(parent_fd, NULL, 0); if (status.major == ERR_FSAL_NOENT && strcmp(filename, "..") == 0) { unsigned long long pinode; pinode = get_handle2inode(parent_hdl->handle); if (pinode == GPFS_ROOT_INODE) { LogEvent(COMPONENT_FSAL, "Lookup of DOTDOT failed in ROOT dir"); *fh = *parent_hdl->handle; status = fsalstat(ERR_FSAL_NO_ERROR, 0); } else { LogEvent(COMPONENT_FSAL, "Lookup of DOTDOT failed in dirinode: %llu", pinode); } } if (FSAL_IS_ERROR(status)) return status; /* Sometimes GPFS sends us the same object as its parent with * lookup of DOTDOT. This is incorrect and also results in ABBA * deadlock with content_lock and attr_lock (readdirplus holds * content_lock on the directory and then attr_lock on the * direntry (which happens to be the same object for DOTDOT * direntry with this bug). Other requests hold attr_lock * followed by content_lock. * * If we detect this error, send DELAY error and hope it goes * away on the retry! */ if (strcmp(filename, "..") == 0) { struct gpfs_file_handle *gfh; unsigned long long inode; gfh = parent_hdl->handle; inode = get_handle2inode(gfh); if (inode != GPFS_ROOT_INODE && gfh->handle_size == fh->handle_size && memcmp(gfh, fh, gfh->handle_size) == 0) { LogCrit(COMPONENT_FSAL, "DOTDOT error, inode: %llu", inode); return fsalstat(ERR_FSAL_DELAY, 0); } } /* In order to check XDEV, we need to get the fsid from the handle. * We need to do this before getting attributes in order to have the * correct gpfs_fs to pass to GPFSFSAL_getattrs. We also return * the correct fs to the caller. */ gpfs_extract_fsid(fh, &fsid); if (fsid.major != parent_hdl->obj_handle.fsid.major) { /* XDEV */ *new_fs = lookup_fsid(&fsid, GPFS_FSID_TYPE); if (*new_fs == NULL) { LogDebug( COMPONENT_FSAL, "Lookup of %s crosses filesystem boundary to unknown file system fsid=0x%016" PRIx64 ".0x%016" PRIx64, filename, fsid.major, fsid.minor); return fsalstat(ERR_FSAL_XDEV, EXDEV); } if ((*new_fs)->fsal != parent->fsal) { LogDebug( COMPONENT_FSAL, "Lookup of %s crosses filesystem boundary to file system %s into FSAL %s", filename, (*new_fs)->path, (*new_fs)->fsal != NULL ? (*new_fs)->fsal->name : "(none)"); return fsalstat(ERR_FSAL_XDEV, EXDEV); } else { LogDebug( COMPONENT_FSAL, "Lookup of %s crosses filesystem boundary to file system %s", filename, (*new_fs)->path); } gpfs_fs = (*new_fs)->private_data; } /* get object attributes */ status = GPFSFSAL_getattrs(op_ctx->fsal_export, gpfs_fs, fh, fsal_attr); /* lookup complete ! */ return status; } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/fsal_mds.c000066400000000000000000000411061473756622300204130ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright © 2012 CohortFS, LLC. * Author: Adam C. Emerson * * This program 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 3 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 * 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 * * ------------- */ #include "fsal.h" #include "fsal_types.h" #include "fsal_api.h" #include "fsal_up.h" #include "pnfs_utils.h" #include "fsal_internal.h" #include "gpfs_methods.h" #include "nfs_exports.h" #include "FSAL/fsal_commonlib.h" #include "export_mgr.h" /** * @brief Get layout types supported by export * * We just return a pointer to the single type and set the count to 1. * * @param[in] export_pub Public export handle * @param[out] count Number of layout types in array * @param[out] types Static array of layout types that must not be * freed or modified and must not be dereferenced * after export reference is relinquished */ static void fs_layouttypes(struct fsal_export *export_hdl, int32_t *count, const layouttype4 **types) { int rc; struct open_arg arg; static const layouttype4 supported_layout_type = LAYOUT4_NFSV4_1_FILES; int errsv = 0; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; /** @todo FSF: needs real getdeviceinfo that gets to the correct * filesystem, this will not work for sub-mounted filesystems. */ arg.mountdirfd = export_fd; rc = gpfs_ganesha(OPENHANDLE_LAYOUT_TYPE, &arg); errsv = errno; if (rc < 0 || (rc != LAYOUT4_NFSV4_1_FILES)) { LogDebug(COMPONENT_PNFS, "rc %d", rc); if (errsv == EUNATCH) LogFatal(COMPONENT_PNFS, "GPFS Returned EUNATCH"); *count = 0; return; } *types = &supported_layout_type; *count = 1; } /** * @brief Get layout block size for export * * This function just returns the GPFS default. * * @param[in] export_pub Public export handle * * @return 4 MB. */ static uint32_t fs_layout_blocksize(struct fsal_export *export_pub) { return 0x400000; } /** * @brief Maximum number of segments we will use * * Since current clients only support 1, that's what we'll use. * * @param[in] export_pub Public export handle * * @return 1 */ static uint32_t fs_maximum_segments(struct fsal_export *export_pub) { return 1; } /** * @brief Size of the buffer needed for a loc_body * * Just a handle plus a bit. * * @param[in] export_pub Public export handle * * @return Size of the buffer needed for a loc_body */ static size_t fs_loc_body_size(struct fsal_export *export_pub) { return 0x100; } /** * @brief Size of the buffer needed for a ds_addr * * This one is huge, due to the striping pattern. * * @param[in] export_pub Public export handle * * @return Size of the buffer needed for a ds_addr */ size_t fs_da_addr_size(struct fsal_module *fsal_hdl) { return 0x20000; } /** * @brief Describe a GPFS striping pattern * * At present, we support a files based layout only. The CRUSH * striping pattern is a-periodic * * @param[in] export_pub Public export handle * @param[out] da_addr_body Stream we write the result to * @param[in] type Type of layout that gave the device * @param[in] deviceid The device to look up * * @return Valid error codes in RFC 5661, p. 365. */ nfsstat4 getdeviceinfo(struct fsal_module *fsal_hdl, XDR *da_addr_body, const layouttype4 type, const struct pnfs_deviceid *deviceid) { struct deviceinfo_arg darg; /* The position before any bytes are sent to the stream */ size_t da_beginning; size_t ds_buffer; /* The total length of the XDR-encoded da_addr_body */ size_t da_length; int rc; int errsv; darg.mountdirfd = deviceid->device_id4; darg.type = LAYOUT4_NFSV4_1_FILES; darg.devid.devid = deviceid->devid; darg.devid.device_id1 = deviceid->device_id1; darg.devid.device_id2 = deviceid->device_id2; darg.devid.device_id4 = deviceid->device_id4; da_beginning = xdr_getpos(da_addr_body); darg.xdr.p = xdr_inline_encode(da_addr_body, 0); ds_buffer = xdr_size_inline(da_addr_body); darg.xdr.end = (int *)(darg.xdr.p + ((ds_buffer - da_beginning) / BYTES_PER_XDR_UNIT)); LogDebug( COMPONENT_PNFS, "p %p end %p da_length %zu ds_buffer %zu seq %d fd %d fsid 0x%" PRIx64, darg.xdr.p, darg.xdr.end, da_beginning, ds_buffer, deviceid->device_id2, deviceid->device_id4, deviceid->devid); rc = gpfs_ganesha(OPENHANDLE_GET_DEVICEINFO, &darg); errsv = errno; if (rc < 0) { LogDebug(COMPONENT_PNFS, "rc %d", rc); if (errsv == EUNATCH) LogFatal(COMPONENT_PNFS, "GPFS Returned EUNATCH"); return NFS4ERR_RESOURCE; } (void)xdr_inline_encode(da_addr_body, rc); da_length = xdr_getpos(da_addr_body) - da_beginning; LogDebug(COMPONENT_PNFS, "rc %d da_length %zd", rc, da_length); return NFS4_OK; } /** * @brief Get list of available devices * * We do not support listing devices and just set EOF without doing * anything. * * @param[in] export_pub Export handle * @param[in] type Type of layout to get devices for * @param[in] cb Function taking device ID halves * @param[in,out] res In/out and output arguments of the function * * @return Valid error codes in RFC 5661, pp. 365-6. */ static nfsstat4 getdevicelist(struct fsal_export *export_pub, layouttype4 type, void *opaque, bool (*cb)(void *opaque, const uint64_t id), struct fsal_getdevicelist_res *res) { res->eof = true; return NFS4_OK; } static void fs_verifier(struct fsal_export *exp_hdl, struct gsh_buffdesc *verf_desc) { struct readlink_arg varg; unsigned int rc = 0; int errsv = 0; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; varg.fd = export_fd; varg.buffer = (char *)verf_desc; varg.size = sizeof(struct gsh_buffdesc); rc = gpfs_ganesha(OPENHANDLE_GET_VERIFIER, &varg); errsv = errno; if (rc != 0) LogDebug(COMPONENT_PNFS, "rc %d err %d", rc, errsv); } /** * @brief set export options * @param ops reference to struct export_ops */ void export_ops_pnfs(struct export_ops *ops) { ops->getdevicelist = getdevicelist; ops->fs_layouttypes = fs_layouttypes; ops->fs_layout_blocksize = fs_layout_blocksize; ops->fs_maximum_segments = fs_maximum_segments; ops->fs_loc_body_size = fs_loc_body_size; ops->get_write_verifier = fs_verifier; } /** * @brief Grant a layout segment. * * Grant a layout on a subset of a file requested. As a special case, * lie and grant a whole-file layout if requested, because Linux will * ignore it otherwise. * * @param[in] obj_pub Public object handle * @param[out] loc_body An XDR stream to which the FSAL must encode * the layout specific portion of the granted * layout segment. * @param[in] arg Input arguments of the function * @param[in,out] res In/out and output arguments of the function * * @return Valid error codes in RFC 5661, pp. 366-7. */ static nfsstat4 layoutget(struct fsal_obj_handle *obj_hdl, XDR *loc_body, const struct fsal_layoutget_arg *arg, struct fsal_layoutget_res *res) { struct gpfs_fsal_obj_handle *myself; struct gpfs_file_handle gpfs_ds_handle; struct layoutget_arg larg; struct layoutreturn_arg lrarg; unsigned int rc, *fh; /* Structure containing the storage parameters of the file within the GPFS cluster. */ struct pnfs_filelayout_layout file_layout; /* Width of each stripe on the file */ uint32_t stripe_width = 0; uint32_t num_fh = 0; /* Utility parameter */ nfl_util4 util = 0; /* The last byte that can be accessed through pNFS */ /* uint64_t last_possible_byte = 0; strict. set but unused */ /* The deviceid for this layout */ struct pnfs_deviceid deviceid = DEVICE_ID_INIT_ZERO(FSAL_ID_GPFS); /* NFS Status */ nfsstat4 nfs_status = 0; /* Descriptor for DS handle */ struct gsh_buffdesc ds_desc; int errsv = 0; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); /* We support only LAYOUT4_NFSV4_1_FILES layouts */ if (arg->type != LAYOUT4_NFSV4_1_FILES) { LogCrit(COMPONENT_PNFS, "Unsupported layout type: %x", arg->type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } /* Get basic information on the file and calculate the dimensions of the layout we can support. */ memset(&file_layout, 0, sizeof(struct pnfs_filelayout_layout)); memcpy(&gpfs_ds_handle, myself->handle, sizeof(struct gpfs_file_handle)); larg.fd = export_fd; larg.args.lg_minlength = arg->minlength; larg.args.lg_sbid = arg->export_id; larg.args.lg_fh = &gpfs_ds_handle; larg.args.lg_iomode = res->segment.io_mode; larg.handle = &gpfs_ds_handle; larg.file_layout = &file_layout; larg.xdr = NULL; fh = (int *)&(gpfs_ds_handle.f_handle); LogDebug( COMPONENT_PNFS, "fh in len %d type %d key %d: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x", gpfs_ds_handle.handle_size, gpfs_ds_handle.handle_type, gpfs_ds_handle.handle_key_size, fh[0], fh[1], fh[2], fh[3], fh[4], fh[5], fh[6], fh[7], fh[8], fh[9]); rc = gpfs_ganesha(OPENHANDLE_LAYOUT_GET, &larg); errsv = errno; if (rc != 0) { LogDebug(COMPONENT_PNFS, "GPFSFSAL_layoutget rc %d", rc); if (errsv == EUNATCH) LogFatal(COMPONENT_PNFS, "GPFS Returned EUNATCH"); return NFS4ERR_LAYOUTUNAVAILABLE; } fh = (int *)&(gpfs_ds_handle.f_handle); LogDebug( COMPONENT_PNFS, "fh out len %d type %d key %d: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x", gpfs_ds_handle.handle_size, gpfs_ds_handle.handle_type, gpfs_ds_handle.handle_key_size, fh[0], fh[1], fh[2], fh[3], fh[4], fh[5], fh[6], fh[7], fh[8], fh[9]); /* We grant only one segment, and we want it back when file is closed.*/ res->return_on_close = true; res->last_segment = true; res->segment.offset = 0; res->segment.length = NFS4_UINT64_MAX; stripe_width = file_layout.lg_stripe_unit; num_fh = file_layout.lg_fh_length; util |= stripe_width; deviceid.fsal_id = file_layout.device_id.fsal_id; deviceid.device_id2 = file_layout.device_id.device_id2; deviceid.device_id4 = file_layout.device_id.device_id4; deviceid.devid = file_layout.device_id.devid; /* last_possible_byte = NFS4_UINT64_MAX; strict. set but unused */ LogDebug(COMPONENT_PNFS, "fsal_id %d seq %d fd %d fsid 0x%" PRIx64 " index %d", deviceid.fsal_id, deviceid.device_id2, deviceid.device_id4, deviceid.devid, file_layout.lg_first_stripe_index); ds_desc.addr = &gpfs_ds_handle; ds_desc.len = sizeof(struct gpfs_file_handle); nfs_status = FSAL_encode_file_layout(loc_body, &deviceid, util, file_layout.lg_first_stripe_index, 0, &op_ctx->ctx_export->export_id, num_fh, &ds_desc, true); if (nfs_status) { if (arg->maxcount <= op_ctx->fsal_export->exp_ops.fs_loc_body_size( op_ctx->fsal_export)) { nfs_status = NFS4ERR_TOOSMALL; LogDebug(COMPONENT_PNFS, "Failed to encode nfsv4_1_file_layout."); } else LogCrit(COMPONENT_PNFS, "Failed to encode nfsv4_1_file_layout."); goto relinquish; } return NFS4_OK; relinquish: /* If we failed in encoding the lo_content, relinquish what we reserved for it. */ lrarg.mountdirfd = export_fd; lrarg.handle = &gpfs_ds_handle; lrarg.args.lr_return_type = arg->type; lrarg.args.lr_reclaim = false; lrarg.args.lr_seg.clientid = 0; lrarg.args.lr_seg.layout_type = arg->type; lrarg.args.lr_seg.iomode = res->segment.io_mode; lrarg.args.lr_seg.offset = 0; lrarg.args.lr_seg.length = NFS4_UINT64_MAX; rc = gpfs_ganesha(OPENHANDLE_LAYOUT_RETURN, &lrarg); errsv = errno; LogDebug(COMPONENT_PNFS, "GPFSFSAL_layoutreturn rc %d", rc); if (rc != 0) { LogDebug(COMPONENT_PNFS, "GPFSFSAL_layoutget rc %d", rc); if (errsv == EUNATCH) LogFatal(COMPONENT_PNFS, "GPFS Returned EUNATCH"); } return nfs_status; } /** * @brief Potentially return one layout segment * * Since we don't make any reservations, in this version, or get any * pins to release, always succeed * * @param[in] obj_pub Public object handle * @param[in] lrf_body Nothing for us * @param[in] arg Input arguments of the function * * @return Valid error codes in RFC 5661, p. 367. */ static nfsstat4 layoutreturn(struct fsal_obj_handle *obj_hdl, XDR *lrf_body, const struct fsal_layoutreturn_arg *arg) { struct layoutreturn_arg larg; struct gpfs_fsal_obj_handle *myself; /* The private 'full' object handle */ struct gpfs_file_handle *gpfs_handle; int errsv = 0; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; int rc = 0; /* Sanity check on type */ if (arg->lo_type != LAYOUT4_NFSV4_1_FILES) { LogCrit(COMPONENT_PNFS, "Unsupported layout type: %x", arg->lo_type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); gpfs_handle = myself->handle; if (arg->dispose) { larg.mountdirfd = export_fd; larg.handle = gpfs_handle; larg.args.lr_return_type = arg->lo_type; larg.args.lr_reclaim = (arg->circumstance == circumstance_reclaim); larg.args.lr_seg.clientid = 0; larg.args.lr_seg.layout_type = arg->lo_type; larg.args.lr_seg.iomode = arg->spec_segment.io_mode; larg.args.lr_seg.offset = arg->spec_segment.offset; larg.args.lr_seg.length = arg->spec_segment.length; rc = gpfs_ganesha(OPENHANDLE_LAYOUT_RETURN, &larg); errsv = errno; if (rc != 0) { LogDebug(COMPONENT_PNFS, "GPFSFSAL_layoutreturn rc %d", rc); if (errsv == EUNATCH) LogFatal(COMPONENT_PNFS, "GPFS Returned EUNATCH"); return NFS4ERR_NOMATCHING_LAYOUT; } } return NFS4_OK; } /** * @brief Commit a segment of a layout * * Update the size and time for a file accessed through a layout. * * @param[in] obj_pub Public object handle * @param[in] lou_body An XDR stream containing the layout * type-specific portion of the LAYOUTCOMMIT * arguments. * @param[in] arg Input arguments of the function * @param[in,out] res In/out and output arguments of the function * * @return Valid error codes in RFC 5661, p. 366. */ static nfsstat4 layoutcommit(struct fsal_obj_handle *obj_hdl, XDR *lou_body, const struct fsal_layoutcommit_arg *arg, struct fsal_layoutcommit_res *res) { struct gpfs_fsal_obj_handle *myself; /* The private 'full' object handle */ struct gpfs_file_handle *gpfs_handle; int rc = 0; struct layoutcommit_arg targ; int errsv = 0; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; /* Sanity check on type */ if (arg->type != LAYOUT4_NFSV4_1_FILES) { LogCrit(COMPONENT_PNFS, "Unsupported layout type: %x", arg->type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); gpfs_handle = myself->handle; targ.mountdirfd = export_fd; targ.handle = gpfs_handle; targ.xdr = NULL; targ.offset = arg->segment.offset; targ.length = arg->segment.length; targ.reclaim = arg->reclaim; /* True if this is a reclaim commit */ targ.new_offset = arg->new_offset; /* True if the client has suggested a new offset */ if (arg->new_offset) targ.last_write = arg->last_write; /* The offset of the last byte written */ targ.time_changed = arg->time_changed; /*True if provided a new mtime*/ if (arg->time_changed) { targ.new_time.t_sec = arg->new_time.seconds; targ.new_time.t_nsec = arg->new_time.nseconds; } rc = gpfs_ganesha(OPENHANDLE_LAYOUT_COMMIT, &targ); errsv = errno; if (rc != 0) { LogDebug(COMPONENT_PNFS, "GPFSFSAL_layoutcommit rc %d", rc); if (errsv == EUNATCH) LogFatal(COMPONENT_PNFS, "GPFS Returned EUNATCH"); return posix2nfs4_error(-rc); } res->size_supplied = false; res->commit_done = true; return NFS4_OK; } /** * @brief set ops layout * * @param ops reference to object */ void handle_ops_pnfs(struct fsal_obj_ops *ops) { ops->layoutget = layoutget; ops->layoutreturn = layoutreturn; ops->layoutcommit = layoutcommit; } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/fsal_rename.c000066400000000000000000000053431473756622300211020ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /** * @file fsal_rename.c * @date $Date: 2006/01/24 13:45:37 $ * @brief object renaming/moving function. * * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #include "config.h" #include "fsal.h" #include "fsal_internal.h" #include "fsal_convert.h" #include "gpfs_methods.h" /** * @brief Change name and/or parent dir of a filesystem object. * * @param old_hdl Source parent directory of the object is to be moved/renamed. * @param old_name Current name of the object to be moved/renamed. * @param new_hdl Target parent directory for the object. * @param new_name New name for the object. * * @return Major error codes : * - ERR_FSAL_NO_ERROR (no error) * - Another error code if an error occurred. */ fsal_status_t GPFSFSAL_rename(struct fsal_obj_handle *old_hdl, const char *old_name, struct fsal_obj_handle *new_hdl, const char *new_name) { fsal_status_t status; struct stat buffstat; struct gpfs_fsal_obj_handle *old_gpfs_hdl, *new_gpfs_hdl; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; old_gpfs_hdl = container_of(old_hdl, struct gpfs_fsal_obj_handle, obj_handle); new_gpfs_hdl = container_of(new_hdl, struct gpfs_fsal_obj_handle, obj_handle); /* build file paths */ status = fsal_internal_stat_name(export_fd, old_gpfs_hdl->handle, old_name, &buffstat); if (FSAL_IS_ERROR(status)) return status; /************************************* * Rename the file on the filesystem * *************************************/ status = fsal_internal_rename_fh(export_fd, old_gpfs_hdl->handle, new_gpfs_hdl->handle, old_name, new_name); if (FSAL_IS_ERROR(status)) return status; return fsalstat(ERR_FSAL_NO_ERROR, 0); } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/fsal_stats_gpfs.c000066400000000000000000000216371473756622300220140ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* */ /* Copyright (C) 2017 International Business Machines */ /* 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. */ /* 3. The name of the author may not be used to endorse or promote products */ /* derived from this software without specific prior written */ /* permission. */ /* */ /* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. */ /* */ /* %Z%%M% %I% %W% %G% %U% */ #include "config.h" #include "gsh_list.h" #ifdef USE_DBUS #include "gsh_dbus.h" #endif #include #include "fsal.h" #include "fsal_internal.h" #include "nfs_proto_functions.h" #include "include/gpfs_nfs.h" struct fsal_op_stats gpfs_op_stats[GPFS_STAT_MAX_OPS]; struct fsal_stats gpfs_stats; #ifdef USE_DBUS /** * * @brief Return string for gpfs opcode * * * * @param[in] gpfs opcode * */ static char *gpfs_opcode_to_name(int opcode) { switch (opcode) { case OPENHANDLE_GET_VERSION: return "GET_VERSION"; case OPENHANDLE_NAME_TO_HANDLE: return "NAME_TO_HANDLE"; case OPENHANDLE_OPEN_BY_HANDLE: return "OPEN_BY_HANDLE"; case OPENHANDLE_LAYOUT_TYPE: return "LAYOUT_TYPE"; case OPENHANDLE_GET_DEVICEINFO: return "GET_DEVICEINFO"; case OPENHANDLE_GET_DEVICELIST: return "GET_DEVICELIST"; case OPENHANDLE_LAYOUT_GET: return "LAYOUT_GET"; case OPENHANDLE_LAYOUT_RETURN: return "LAYOUT_RETURN"; case OPENHANDLE_INODE_UPDATE: return "INODE_UPDATE"; case OPENHANDLE_GET_XSTAT: return "GET_XSTAT"; case OPENHANDLE_SET_XSTAT: return "SET_XSTAT"; case OPENHANDLE_CHECK_ACCESS: return "CHECK_ACCESS"; case OPENHANDLE_OPEN_SHARE_BY_HANDLE: return "OPEN_SHARE_BY_HANDLE"; case OPENHANDLE_GET_LOCK: return "GET_LOCK"; case OPENHANDLE_SET_LOCK: return "SET_LOCK"; case OPENHANDLE_THREAD_UPDATE: return "THREAD_UPDATE"; case OPENHANDLE_LAYOUT_COMMIT: return "LAYOUT_COMMIT"; case OPENHANDLE_DS_READ: return "DS_READ"; case OPENHANDLE_DS_WRITE: return "DS_WRITE"; case OPENHANDLE_GET_VERIFIER: return "GET_VERIFIER"; case OPENHANDLE_FSYNC: return "FSYNC"; case OPENHANDLE_SHARE_RESERVE: return "SHARE_RESERVE"; case OPENHANDLE_GET_NODEID: return "GET_NODEID"; case OPENHANDLE_SET_DELEGATION: return "SET_DELEGATION"; case OPENHANDLE_CLOSE_FILE: return "CLOSE_FILE"; case OPENHANDLE_LINK_BY_FH: return "LINK_BY_FH"; case OPENHANDLE_RENAME_BY_FH: return "RENAME_BY_FH"; case OPENHANDLE_STAT_BY_NAME: return "STAT_BY_NAME"; case OPENHANDLE_GET_HANDLE: return "GET_HANDLE"; case OPENHANDLE_READLINK_BY_FH: return "READLINK_BY_FH"; case OPENHANDLE_UNLINK_BY_NAME: return "UNLINK_BY_NAME"; case OPENHANDLE_CREATE_BY_NAME: return "CREATE_BY_NAME"; case OPENHANDLE_READ_BY_FD: return "READ_BY_FD"; case OPENHANDLE_WRITE_BY_FD: return "WRITE_BY_FD"; case OPENHANDLE_CREATE_BY_NAME_ATTR: return "CREATE_BY_NAME_ATTR"; case OPENHANDLE_GRACE_PERIOD: return "GRACE_PERIOD"; case OPENHANDLE_ALLOCATE_BY_FD: return "ALLOCATE_BY_FD"; case OPENHANDLE_REOPEN_BY_FD: return "REOPEN_BY_FD"; case OPENHANDLE_FADVISE_BY_FD: return "FADVISE_BY_FD"; case OPENHANDLE_SEEK_BY_FD: return "SEEK_BY_FD"; case OPENHANDLE_STATFS_BY_FH: return "STATFS_BY_FH"; case OPENHANDLE_GETXATTRS: return "GETXATTRS"; case OPENHANDLE_SETXATTRS: return "SETXATTRS"; case OPENHANDLE_REMOVEXATTRS: return "REMOVEXATTRS"; case OPENHANDLE_LISTXATTRS: return "LISTXATTRS"; case OPENHANDLE_MKNODE_BY_NAME: return "MKNODE_BY_NAME"; case OPENHANDLE_reserved: return "reserved"; case OPENHANDLE_TRACE_ME: return "TRACE_ME"; case OPENHANDLE_QUOTA: return "QUOTA"; case OPENHANDLE_FS_LOCATIONS: return "FS_LOCATIONS"; default: return "UNMONITORED"; } } #endif /* USE_DBUS */ /** @fn prepare_for_stats(struct fsal_module *fsal_hdl) * * @brief prepare the structure which will hold the stats * */ void prepare_for_stats(struct fsal_module *fsal_hdl) { int idx, op; gpfs_stats.total_ops = GPFS_TOTAL_OPS; gpfs_stats.op_stats = gpfs_op_stats; fsal_hdl->stats = &gpfs_stats; for (op = GPFS_MIN_OP; op <= GPFS_MAX_OP; op++) { idx = gpfs_op2index(op); fsal_hdl->stats->op_stats[idx].op_code = op; } } #ifdef USE_DBUS /** @fn fsal_gpfs_extract_stats(struct fsal_module *fsal_hdl, void *iter) * * @brief Extract the FSAL specific performance counters * */ void fsal_gpfs_extract_stats(struct fsal_module *fsal_hdl, void *iter) { DBusMessageIter struct_iter; DBusMessageIter *iter1 = (DBusMessageIter *)iter; char *message; uint64_t total_ops, total_resp, min_resp, max_resp, op_counter = 0; double res = 0.0; int i; struct fsal_stats *gpfs_stats; gpfs_stats = fsal_hdl->stats; message = "GPFS"; dbus_message_iter_append_basic(iter1, DBUS_TYPE_STRING, &message); dbus_message_iter_open_container(iter1, DBUS_TYPE_STRUCT, NULL, &struct_iter); for (i = 0; i < GPFS_STAT_PH_INDEX; i++) { if (i == GPFS_STAT_NO_OP_1 || i == GPFS_STAT_NO_OP_2 || i == GPFS_STAT_NO_OP_3) continue; total_ops = atomic_fetch_uint64_t(&gpfs_stats->op_stats[i].num_ops); if (total_ops == 0) continue; total_resp = atomic_fetch_uint64_t( &gpfs_stats->op_stats[i].resp_time); min_resp = atomic_fetch_uint64_t( &gpfs_stats->op_stats[i].resp_time_min); max_resp = atomic_fetch_uint64_t( &gpfs_stats->op_stats[i].resp_time_max); /* We have valid stats, send it across */ message = gpfs_opcode_to_name(gpfs_stats->op_stats[i].op_code); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &message); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &total_ops); res = (double)total_resp * 0.000001 / total_ops; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_DOUBLE, &res); res = (double)min_resp * 0.000001; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_DOUBLE, &res); res = (double)max_resp * 0.000001; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_DOUBLE, &res); op_counter += total_ops; } if (op_counter == 0) { message = "None"; /* insert dummy stats to avoid dbus crash */ dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &message); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &total_ops); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_DOUBLE, &res); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_DOUBLE, &res); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_DOUBLE, &res); } else { message = "OK"; } dbus_message_iter_close_container(iter1, &struct_iter); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &message); } #endif /* USE_DBUS */ void fsal_gpfs_reset_stats(struct fsal_module *fsal_hdl) { int i; struct fsal_stats *gpfs_stats; gpfs_stats = fsal_hdl->stats; /* reset all the counters */ for (i = 0; i < GPFS_STAT_PH_INDEX; i++) { atomic_store_uint64_t(&gpfs_stats->op_stats[i].num_ops, 0); atomic_store_uint64_t(&gpfs_stats->op_stats[i].resp_time, 0); atomic_store_uint64_t(&gpfs_stats->op_stats[i].resp_time_min, 0); atomic_store_uint64_t(&gpfs_stats->op_stats[i].resp_time_max, 0); } } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/fsal_symlinks.c000066400000000000000000000120751473756622300215040ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /** * @file fsal_symlinks.c * @date $Date: 2005/07/29 09:39:04 $ * @brief symlinks operations. * * * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #include "config.h" #include "fsal.h" #include "fsal_internal.h" #include "fsal_convert.h" #include "gpfs_methods.h" #include #include #include "FSAL/fsal_localfs.h" /** * @brief Read the content of a symbolic link. * * @param dir_hdl Handle of the link to be read. * @param link_content Fsal path struct where the link content is to be stored * @param link_len Len of content buff. Out actual len of content. * * @return Major error codes : * - ERR_FSAL_NO_ERROR (no error) * - Another error code if an error occurred. */ fsal_status_t GPFSFSAL_readlink(struct fsal_obj_handle *dir_hdl, char *link_content, size_t link_len) { struct gpfs_fsal_obj_handle *gpfs_hdl; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; gpfs_hdl = container_of(dir_hdl, struct gpfs_fsal_obj_handle, obj_handle); /* Read the link on the filesystem */ return fsal_readlink_by_handle(export_fd, gpfs_hdl->handle, link_content, link_len); } /** * @brief Create a symbolic link. * * @param dir_hdl Handle of the parent dir where the link is to be created. * @param linkname Name of the link to be created. * @param linkcontent Content of the link to be created. * @param accessmode Mode of the link to be created. * It has no sense in POSIX filesystems. * @param gpfs_fh Pointer to the handle of the created symlink. * @param link_attr Attributes of the newly created symlink. * As input, it defines the attributes that the caller * wants to retrieve (by positioning flags into this structure) * and the output is built considering this input * (it fills the structure according to the flags it contains). * May be NULL. * * @return Major error codes : * - ERR_FSAL_NO_ERROR (no error) * - Another error code if an error occurred. */ fsal_status_t GPFSFSAL_symlink(struct fsal_obj_handle *dir_hdl, const char *linkname, const char *linkcontent, uint32_t accessmode, struct gpfs_file_handle *gpfs_fh, struct fsal_attrlist *link_attr) { int rc, errsv; fsal_status_t status; int fd; struct gpfs_fsal_obj_handle *gpfs_hdl; struct gpfs_filesystem *gpfs_fs; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; gpfs_hdl = container_of(dir_hdl, struct gpfs_fsal_obj_handle, obj_handle); gpfs_fs = dir_hdl->fs->private_data; /* Tests if symlinking is allowed by configuration. */ if (!op_ctx->fsal_export->exp_ops.fs_supports(op_ctx->fsal_export, fso_symlink_support)) return fsalstat(ERR_FSAL_NOTSUPP, 0); status = fsal_internal_handle2fd(export_fd, gpfs_hdl->handle, &fd, O_RDONLY | O_DIRECTORY); if (FSAL_IS_ERROR(status)) return status; /* build symlink path */ /* create the symlink on the filesystem using the credentials * for proper ownership assignment. */ fsal_set_credentials(&op_ctx->creds); rc = symlinkat(linkcontent, fd, linkname); errsv = errno; fsal_restore_ganesha_credentials(); if (rc) { fsal_internal_close(fd, NULL, 0); return fsalstat(posix2fsal_error(errsv), errsv); } /* now get the associated handle, while there is a race, there is also a race lower down */ status = fsal_internal_get_handle_at(fd, linkname, gpfs_fh, export_fd); if (FSAL_IS_ERROR(status)) { fsal_internal_close(fd, NULL, 0); return status; } /* get attributes */ status = GPFSFSAL_getattrs(op_ctx->fsal_export, gpfs_fs, gpfs_fh, link_attr); if (!FSAL_IS_ERROR(status) && link_attr->type != SYMBOLIC_LINK) { /* We could wind up not failing the creation of the symlink * and the only way we know is that the object type isn't * a symlink. */ fsal_release_attrs(link_attr); status = fsalstat(ERR_FSAL_EXIST, 0); } fsal_internal_close(fd, NULL, 0); return status; } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/fsal_unlink.c000066400000000000000000000046361473756622300211370ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /** * @file fsal_unlink.c * @date $Date: 2006/01/24 13:45:37 $ * @brief object removing function. * * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #include "config.h" #include "fsal.h" #include "fsal_internal.h" #include "fsal_convert.h" #include "gpfs_methods.h" #include /** * @brief Remove a filesystem object . * * @param dir_hdl Handle of the parent directory of the object to be deleted. * @param object_name Name of the object to be removed. * * @return Major error codes : * - ERR_FSAL_NO_ERROR (no error) * - Another error code if an error occurred. */ fsal_status_t GPFSFSAL_unlink(struct fsal_obj_handle *dir_hdl, const char *object_name) { fsal_status_t status; gpfsfsal_xstat_t buffxstat; struct gpfs_fsal_obj_handle *gpfs_hdl; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; gpfs_hdl = container_of(dir_hdl, struct gpfs_fsal_obj_handle, obj_handle); /* get file metadata */ status = fsal_internal_stat_name(export_fd, gpfs_hdl->handle, object_name, &buffxstat.buffstat); if (FSAL_IS_ERROR(status)) return status; /****************************** * DELETE FROM THE FILESYSTEM * ******************************/ status = fsal_internal_unlink(export_fd, gpfs_hdl->handle, object_name, &buffxstat.buffstat); if (FSAL_IS_ERROR(status)) return status; return fsalstat(ERR_FSAL_NO_ERROR, 0); } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/fsal_up.c000066400000000000000000000313371473756622300202610ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /** * @file fsal_up.c * @brief FSAL Upcall Interface * * This program 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 3 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 * 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 * * --------------------------------------- */ #include "config.h" #include "fsal.h" #include "fsal_up.h" #include "fsal_internal.h" #include "fsal_convert.h" #include "gpfs_methods.h" #include "nfs_init.h" #include #include #include #include #include #include "FSAL/fsal_localfs.h" /** * @brief Up Thread * * @param Arg reference to void * */ void *GPFSFSAL_UP_Thread(void *Arg) { struct gpfs_filesystem *gpfs_fs = Arg; const struct fsal_up_vector *event_func; char thr_name[16]; int rc = 0; struct pnfs_deviceid devid; struct stat buf; struct glock fl; struct callback_arg callback; struct gpfs_file_handle handle; int reason = 0; int flags = 0; unsigned int *fhP; int retry = 0; struct gsh_buffdesc key; uint32_t expire_time_attr = 0; uint32_t upflags; int errsv = 0; fsal_status_t fsal_status = { 0, }; struct req_op_context op_context; struct gsh_export *gsh_export; struct fsal_export *fsal_export; rcu_register_thread(); #ifdef _VALGRIND_MEMCHECK memset(&handle, 0, sizeof(handle)); memset(&buf, 0, sizeof(buf)); memset(&fl, 0, sizeof(fl)); memset(&devid, 0, sizeof(devid)); #endif (void)snprintf(thr_name, sizeof(thr_name), "fsal_up_%" PRIu64 ".%" PRIu64, gpfs_fs->fs->dev.major, gpfs_fs->fs->dev.minor); SetNameFunction(thr_name); LogFullDebug(COMPONENT_FSAL_UP, "Initializing FSAL Callback context for %d.", gpfs_fs->root_fd); /* wait for nfs init completion to get general_fridge * initialized which is needed for processing some upcall events */ while (1) { rc = nfs_init_wait_timeout(1); /* First check if the thread needs to be stopped */ if (gpfs_fs->stop_thread) return NULL; if (rc == 0) break; else if (rc == ETIMEDOUT) continue; else { LogEvent(COMPONENT_FSAL_UP, "nfs_init_wait_timeout() completed with rc %d", rc); return NULL; } } /* Start querying for events and processing. */ while (1) { LogFullDebug( COMPONENT_FSAL_UP, "Requesting event from FSAL Callback interface for %d.", gpfs_fs->root_fd); handle.handle_size = GPFS_MAX_FH_SIZE; handle.handle_key_size = OPENHANDLE_KEY_LEN; handle.handle_version = OPENHANDLE_VERSION; callback.interface_version = GPFS_INTERFACE_VERSION + GPFS_INTERFACE_SUB_VER; callback.mountdirfd = gpfs_fs->root_fd; callback.handle = &handle; callback.reason = &reason; callback.flags = &flags; callback.buf = &buf; callback.fl = &fl; callback.dev_id = &devid; callback.expire_attr = &expire_time_attr; rc = gpfs_ganesha(OPENHANDLE_INODE_UPDATE, &callback); errsv = errno; if (rc != 0) { rc = -(rc); if (rc > GPFS_INTERFACE_VERSION) { LogFatal( COMPONENT_FSAL_UP, "Ganesha version %d mismatch GPFS version %d.", callback.interface_version, rc); goto out; } if (errsv == EINTR) continue; LogCrit(COMPONENT_FSAL_UP, "OPENHANDLE_INODE_UPDATE failed for %d. rc %d, errno %d (%s) reason %d", gpfs_fs->root_fd, rc, errsv, strerror(errsv), reason); /* @todo 1000 retry logic will go away once the * OPENHANDLE_INODE_UPDATE ioctl separates EINTR * and EUNATCH. */ if (errsv == EUNATCH && ++retry > 1000) LogFatal(COMPONENT_FSAL_UP, "GPFS file system %d has gone away.", gpfs_fs->root_fd); continue; } retry = 0; /* flags is int, but only the least significant 2 bytes * are valid. We are getting random bits into the upper * 2 bytes! Workaround this until the kernel module * gets fixed. */ flags = flags & 0xffff; LogDebug(COMPONENT_FSAL_UP, "inode update: rc %d reason %d update ino %" PRId64 " flags:%x", rc, reason, callback.buf->st_ino, flags); LogFullDebug( COMPONENT_FSAL_UP, "inode update: flags:%x callback.handle:%p handle size = %u handle_type:%d handle_version:%d key_size = %u handle_fsid=%X.%X f_handle:%p expire: %d", *callback.flags, callback.handle, callback.handle->handle_size, callback.handle->handle_type, callback.handle->handle_version, callback.handle->handle_key_size, callback.handle->handle_fsid[0], callback.handle->handle_fsid[1], callback.handle->f_handle, expire_time_attr); callback.handle->handle_version = OPENHANDLE_VERSION; fhP = (int *)&(callback.handle->f_handle[0]); LogFullDebug( COMPONENT_FSAL_UP, " inode update: handle %08x %08x %08x %08x %08x %08x %08x", fhP[0], fhP[1], fhP[2], fhP[3], fhP[4], fhP[5], fhP[6]); /* Here is where we decide what type of event this is * ... open,close,read,...,invalidate? */ key.addr = &handle; key.len = handle.handle_key_size; LogDebug(COMPONENT_FSAL_UP, "Received event to process for %d", gpfs_fs->root_fd); /* Get a ref to the first export from the fsal_fs and * initialize op_context for the thread. */ get_fs_first_export_ref(gpfs_fs->fs, &gsh_export, &fsal_export); if (gsh_export == NULL || fsal_export == NULL) { /* @todo: not quite sure what to do here... * Oops, something went wrong. */ LogCrit(COMPONENT_FSAL_UP, "No export for filesystem %s", gpfs_fs->fs->path); continue; } init_op_context_simple(&op_context, gsh_export, fsal_export); /* Fetch the up vector */ event_func = fsal_export->up_ops; switch (reason) { case INODE_LOCK_GRANTED: /* Lock Event */ case INODE_LOCK_AGAIN: /* Lock Event */ { LogMidDebug( COMPONENT_FSAL_UP, "%s: owner %p pid %d type %d start %lld len %lld", reason == INODE_LOCK_GRANTED ? "inode lock granted" : "inode lock again", fl.lock_owner, fl.flock.l_pid, fl.flock.l_type, (long long)fl.flock.l_start, (long long)fl.flock.l_len); fsal_lock_param_t lockdesc = { .lock_sle_type = FSAL_POSIX_LOCK, .lock_type = fl.flock.l_type, .lock_start = fl.flock.l_start, .lock_length = fl.flock.l_len }; if (reason == INODE_LOCK_AGAIN) fsal_status = up_async_lock_avail( general_fridge, event_func, &key, fl.lock_owner, &lockdesc, NULL, NULL); else fsal_status = up_async_lock_grant( general_fridge, event_func, &key, fl.lock_owner, &lockdesc, NULL, NULL); } break; case BREAK_DELEGATION: /* Delegation Event */ LogDebug(COMPONENT_FSAL_UP, "delegation recall: flags:%x ino %" PRId64, flags, callback.buf->st_ino); fsal_status = up_async_delegrecall( general_fridge, event_func, &key, NULL, NULL); break; case LAYOUT_FILE_RECALL: /* Layout file recall Event */ { struct pnfs_segment segment = { .offset = 0, .length = UINT64_MAX, .io_mode = LAYOUTIOMODE4_ANY }; LogDebug(COMPONENT_FSAL_UP, "layout file recall: flags:%x ino %" PRId64, flags, callback.buf->st_ino); fsal_status = up_async_layoutrecall( general_fridge, event_func, &key, LAYOUT4_NFSV4_1_FILES, false, &segment, NULL, NULL, NULL, NULL); } break; case LAYOUT_RECALL_ANY: /* Recall all layouts Event */ LogDebug(COMPONENT_FSAL_UP, "layout recall any: flags:%x ino %" PRId64, flags, callback.buf->st_ino); /** * @todo This functionality needs to be implemented as a * bulk FSID CB_LAYOUTRECALL. RECALL_ANY isn't suitable * since it can't be restricted to just one FSAL. Also * an FSID LAYOUTRECALL lets you have multiplke * filesystems exported from one FSAL and not yank layouts * on all of them when you only need to recall them for one. */ break; case LAYOUT_NOTIFY_DEVICEID: /* Device update Event */ LogDebug(COMPONENT_FSAL_UP, "layout dev update: flags:%x ino %" PRId64 " seq %d fd %d fsid 0x%" PRIx64, flags, callback.buf->st_ino, devid.device_id2, devid.device_id4, devid.devid); memset(&devid, 0, sizeof(devid)); devid.fsal_id = FSAL_ID_GPFS; fsal_status = up_async_notify_device( general_fridge, event_func, NOTIFY_DEVICEID4_DELETE_MASK, LAYOUT4_NFSV4_1_FILES, &devid, true, NULL, NULL); break; case INODE_UPDATE: /* Update Event */ { struct fsal_attrlist attr; LogMidDebug(COMPONENT_FSAL_UP, "inode update: flags:%x update ino %" PRId64 " n_link:%d", flags, callback.buf->st_ino, (int)callback.buf->st_nlink); /** @todo: This notification is completely * asynchronous. If we happen to change some * of the attributes later, we end up over * writing those with these possibly stale * values as we don't know when we get to * update with these up call values. We should * probably use time stamp or let the up call * always provide UP_TIMES flag in which case * we can compare the current ctime vs up call * provided ctime before updating the * attributes. * * For now, we think size attribute is more * important than others, so invalidate the * attributes and let ganesha fetch attributes * as needed if this update includes a size * change. We are careless for other attribute * changes, and we may end up with stale values * until this gets fixed! */ if (flags & (UP_SIZE | UP_SIZE_BIG)) { fsal_status = event_func->invalidate( event_func, &key, FSAL_UP_INVALIDATE_CACHE); break; } /* Check for accepted flags, any other changes just invalidate. */ if (flags & ~(UP_SIZE | UP_NLINK | UP_MODE | UP_OWN | UP_TIMES | UP_ATIME | UP_SIZE_BIG)) { fsal_status = event_func->invalidate( event_func, &key, FSAL_UP_INVALIDATE_CACHE); } else { /* buf may not have all attributes set. * Set the mask to what is changed */ attr.valid_mask = 0; attr.acl = NULL; upflags = 0; if (flags & UP_SIZE) attr.valid_mask |= ATTR_CHANGE | ATTR_SIZE | ATTR_SPACEUSED; if (flags & UP_SIZE_BIG) { attr.valid_mask |= ATTR_CHANGE | ATTR_SIZE | ATTR_SPACEUSED; upflags |= fsal_up_update_filesize_inc | fsal_up_update_spaceused_inc; } if (flags & UP_MODE) attr.valid_mask |= ATTR_CHANGE | ATTR_MODE; if (flags & UP_OWN) attr.valid_mask |= ATTR_CHANGE | ATTR_OWNER | ATTR_GROUP | ATTR_MODE; if (flags & UP_TIMES) attr.valid_mask |= ATTR_CHANGE | ATTR_ATIME | ATTR_CTIME | ATTR_MTIME; if (flags & UP_ATIME) attr.valid_mask |= ATTR_CHANGE | ATTR_ATIME; if (flags & UP_NLINK) attr.valid_mask |= ATTR_NUMLINKS; attr.request_mask = attr.valid_mask; attr.expire_time_attr = expire_time_attr; posix2fsal_attributes(&buf, &attr); fsal_status = event_func->update( event_func, &key, &attr, upflags); if ((flags & UP_NLINK) && (attr.numlinks == 0)) { upflags = fsal_up_nlink; attr.valid_mask = 0; attr.request_mask = 0; fsal_status = up_async_update( general_fridge, event_func, &key, &attr, upflags, NULL, NULL); } } } break; case THREAD_STOP: /* We wanted to terminate this thread */ LogDebug(COMPONENT_FSAL_UP, "Terminating the GPFS up call thread for %d", gpfs_fs->root_fd); release_op_context(); goto out; case INODE_INVALIDATE: LogMidDebug( COMPONENT_FSAL_UP, "inode invalidate: flags:%x update ino %" PRId64, flags, callback.buf->st_ino); upflags = FSAL_UP_INVALIDATE_CACHE; fsal_status = event_func->invalidate_close( event_func, &key, upflags); break; case THREAD_PAUSE: /* File system image is probably going away, but * we don't need to do anything here as we * eventually get other errors that stop this * thread. */ release_op_context(); continue; /* get next event */ default: release_op_context(); LogWarn(COMPONENT_FSAL_UP, "Unknown event: %d", reason); continue; } release_op_context(); if (FSAL_IS_ERROR(fsal_status) && fsal_status.major != ERR_FSAL_NOENT) { LogWarn(COMPONENT_FSAL_UP, "Event %d could not be processed for fd %d rc %s", reason, gpfs_fs->root_fd, fsal_err_txt(fsal_status)); } } out: rcu_unregister_thread(); return NULL; } /* GPFSFSAL_UP_Thread */ nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/gpfs_methods.h000066400000000000000000000133111473756622300213070ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /** * * This program 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 3 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 * 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 * * --------------------------------------- */ /* GPFS methods for handles */ #ifndef GPFS_METHODS_H #define GPFS_METHODS_H #include #include "include/gpfs_nfs.h" /* private helpers from export */ /* method proto linkage to handle.c for export */ fsal_status_t gpfs_lookup_path(struct fsal_export *exp_hdl, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); fsal_status_t gpfs_create_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *hdl_desc, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); /* * GPFS internal export */ struct gpfs_fsal_export { struct fsal_export export; struct fsal_filesystem *root_fs; struct glist_head filesystems; int export_fd; bool pnfs_ds_enabled; bool pnfs_mds_enabled; bool use_acl; bool ignore_mode_change; }; /* * GPFS internal filesystem */ struct gpfs_filesystem { struct fsal_filesystem *fs; int root_fd; bool stop_thread; pthread_t up_thread; /* upcall thread */ }; void gpfs_extract_fsid(struct gpfs_file_handle *fh, struct fsal_fsid__ *fsid); fsal_status_t gpfs_merge(struct fsal_obj_handle *orig_hdl, struct fsal_obj_handle *dupe_hdl); /** * @brief GPFS internal object handle * * The handle is a pointer because * a) the last element of file_handle is a char[] meaning variable len... * b) we cannot depend on it *always* being last or being the only * variable sized struct here... a pointer is safer. * wrt locks, should this be a lock counter?? * AF_UNIX sockets are strange ducks. I personally cannot see why they * are here except for the ability of a client to see such an animal with * an 'ls' or get rid of one with an 'rm'. You can't open them in the * usual file way so open_by_handle_at leads to a deadend. To work around * this, we save the args that were used to mknod or lookup the socket. */ struct gpfs_fsal_obj_handle { struct fsal_obj_handle obj_handle; struct gpfs_file_handle *handle; union { struct { struct fsal_share share; struct gpfs_fd fd; } file; struct { unsigned char *link_content; int link_size; } symlink; } u; }; /* I/O management */ struct gpfs_fsal_obj_handle *alloc_handle(struct gpfs_file_handle *fh, struct fsal_filesystem *fs, struct fsal_attrlist *attributes, const char *link_content, struct fsal_export *exp_hdl); fsal_status_t gpfs_open2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attrib_set, fsal_verifier_t verifier, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out); fsal_openflags_t gpfs_status2(struct fsal_obj_handle *obj_hdl, struct state_t *state); fsal_status_t gpfs_reopen2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags); void gpfs_read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg); void gpfs_write2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *caller_arg); fsal_status_t gpfs_commit2(struct fsal_obj_handle *obj_hdl, off_t offset, size_t len); fsal_status_t gpfs_lock_op2(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *owner, fsal_lock_op_t lock_op, fsal_lock_param_t *request_lock, fsal_lock_param_t *conflicting_lock); fsal_status_t gpfs_close2(struct fsal_obj_handle *obj_hdl, struct state_t *state); fsal_status_t gpfs_reopen_func(struct fsal_obj_handle *obj_hdl, fsal_openflags_t openflags, struct fsal_fd *fsal_fd); fsal_status_t gpfs_close_func(struct fsal_obj_handle *obj_hdl, struct fsal_fd *fd); fsal_status_t gpfs_setattr2(struct fsal_obj_handle *obj_hdl, bool bypass, struct state_t *state, struct fsal_attrlist *attrib_set); fsal_status_t gpfs_read_plus_fd(int my_fs, uint64_t offset, size_t buffer_size, void *buffer, size_t *read_amount, bool *end_of_file, struct io_info *info, int expfd); fsal_status_t gpfs_seek2(struct fsal_obj_handle *obj_hdl, struct state_t *state, struct io_info *info); fsal_status_t gpfs_io_advise(struct fsal_obj_handle *obj_hdl, struct io_hints *hints); fsal_status_t gpfs_share_op(struct fsal_obj_handle *obj_hdl, void *p_owner, fsal_share_param_t request_share); fsal_status_t gpfs_close(struct fsal_obj_handle *obj_hdl); fsal_status_t gpfs_fallocate(struct fsal_obj_handle *obj_hdl, state_t *state, uint64_t offset, uint64_t length, bool allocate); /* Internal GPFS method linkage to export object */ fsal_status_t gpfs_create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops); #endif nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/gpfsext.c000066400000000000000000000164241473756622300203100ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /**------------------------------------------------------------------------ * @file gpfsext.c * @brief Use ioctl to call into the GPFS kernel module. * * This program 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 3 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 * 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 * * --------------------------------------- * * NAME: gpfs_ganesha() * * FUNCTION: Use ioctl to call into the GPFS kernel module. * If GPFS isn't loaded they receive ENOSYS. * * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS No quality of service function available * ENOENT File not found * EINVAL Not a GPFS file * ESTALE cached fs information was invalid *-------------------------------------------------------------------------*/ #include "config.h" #include #include #include #include #include #ifdef _VALGRIND_MEMCHECK #include #include #include #include "include/gpfs.h" #endif #include "fsal.h" #define NFS_CONTAINERIZATION 1 #include "include/gpfs_nfs.h" #include "gsh_config.h" struct kxArgs { signed long arg1; signed long arg2; }; #ifdef _VALGRIND_MEMCHECK static void valgrind_kganesha(struct kxArgs *args) { int op = (int)args->arg1; switch (op) { case OPENHANDLE_STATFS_BY_FH: { struct statfs_arg *arg = (void *)args->arg2; VALGRIND_MAKE_MEM_DEFINED(arg->buf, sizeof(*arg->buf)); break; } case OPENHANDLE_READ_BY_FD: { struct read_arg *arg = (void *)args->arg2; VALGRIND_MAKE_MEM_DEFINED(arg->bufP, arg->length); break; } case OPENHANDLE_NAME_TO_HANDLE: { struct name_handle_arg *arg = (void *)args->arg2; VALGRIND_MAKE_MEM_DEFINED(arg->handle, sizeof(struct gpfs_file_handle)); break; } case OPENHANDLE_GET_HANDLE: { struct get_handle_arg *arg = (void *)args->arg2; VALGRIND_MAKE_MEM_DEFINED(arg->out_fh, sizeof(struct gpfs_file_handle)); break; } case OPENHANDLE_STAT_BY_NAME: { struct stat_name_arg *arg = (void *)args->arg2; VALGRIND_MAKE_MEM_DEFINED(arg->buf, sizeof(*arg->buf)); break; } case OPENHANDLE_CREATE_BY_NAME: { struct create_name_arg *arg = (void *)args->arg2; VALGRIND_MAKE_MEM_DEFINED(arg->new_fh, sizeof(struct gpfs_file_handle)); break; } case OPENHANDLE_READLINK_BY_FH: { struct readlink_fh_arg *arg = (void *)args->arg2; VALGRIND_MAKE_MEM_DEFINED(arg->buffer, arg->size); break; } case OPENHANDLE_GET_XSTAT: { struct xstat_arg *arg = (void *)args->arg2; VALGRIND_MAKE_MEM_DEFINED(arg->buf, sizeof(*arg->buf)); VALGRIND_MAKE_MEM_DEFINED(arg->fsid, sizeof(*arg->fsid)); if (arg->acl) { struct gpfs_acl *gacl; size_t outlen; /* * arg->acl points to an IN/OUT buffer. First * few fields are initialized by the caller and * the rest are filled in by the ioctl call. */ gacl = arg->acl; outlen = gacl->acl_len - offsetof(struct gpfs_acl, acl_nace); VALGRIND_MAKE_MEM_DEFINED(&gacl->acl_nace, outlen); } break; } case OPENHANDLE_WRITE_BY_FD: { struct write_arg *arg = (void *)args->arg2; VALGRIND_MAKE_MEM_DEFINED(arg->stability_got, sizeof(*arg->stability_got)); break; } default: break; } } #endif int gpfs_op2index(int op) { if ((op < GPFS_MIN_OP) || (op > GPFS_MAX_OP) || (op == 103 || op == 104 || op == 105)) return GPFS_STAT_PH_INDEX; else return (op - GPFS_MIN_OP); } /** * @param op Operation * @param *oarg Arguments * * @return Result */ int gpfs_ganesha(int op, void *oarg) { int rc, idx; static int gpfs_fd = -2; struct kxArgs args; struct timespec start_time; struct timespec stop_time; nsecs_elapsed_t resp_time; #ifdef NFS_CONTAINERIZATION FILE *file = NULL; bool readDone = false; bool gotPath = false; char *dirPath = NULL; #endif if (gpfs_fd < 0) { /* If we enable fsal_trace in the config, the following * LogFatal would call us here again for fsal tracing! * Since we can't log as we are unable to open the device, * just exit. * * Also, exit handler _dl_fini() will call gpfs_unload * which will call release_log_facility that tries to * acquire log_rwlock a second time! So do an immediate * exit. */ if (gpfs_fd == -1) /* failed in a prior invocation */ _exit(1); assert(gpfs_fd == -2); gpfs_fd = open(GPFS_DEVNAMEX, O_RDONLY); #ifdef NFS_CONTAINERIZATION if (gpfs_fd < 0) { /* In containerization, /dev/ss0 may not be present. * So look for gpfs mount. */ file = fopen("/proc/mounts", "r"); if (file == NULL) goto exit; while (!readDone) { char lineBuf[2048]; char *savePtr; char *type; char *ptr; /* Read the next line from the file */ if (fgets(lineBuf, sizeof(lineBuf), file) == NULL) { readDone = true; break; /* Something is broken with the * command pipe, * maybe done or reached to EOF */ } ptr = strtok_r(lineBuf, " ", &savePtr); if (ptr == NULL) continue; /* 2nd is the mount point */ ptr = strtok_r(NULL, " ", &savePtr); if (ptr == NULL) continue; else dirPath = ptr; /* 3rd is the type */ type = strtok_r(NULL, " ", &savePtr); if (strcmp(type, "gpfs") == 0) { readDone = true; gotPath = true; } } fclose(file); if (gotPath) { gpfs_fd = open(dirPath, O_RDONLY); if (gpfs_fd >= 0) LogEvent( COMPONENT_FSAL, "%s GPFS file system found, fd %d,dirPath=%s\n", __func__, gpfs_fd, dirPath); } exit: if (!gotPath) LogEvent( COMPONENT_FSAL, "%s no mounted GPFS file system found, fd %d\n", __func__, gpfs_fd); } #endif if (gpfs_fd == -1) LogFatal(COMPONENT_FSAL, "open of %s failed with errno %d", GPFS_DEVNAMEX, errno); (void)fcntl(gpfs_fd, F_SETFD, FD_CLOEXEC); } args.arg1 = op; args.arg2 = (long)oarg; #ifdef _VALGRIND_MEMCHECK valgrind_kganesha(&args); #endif if (nfs_param.core_param.enable_FSALSTATS) { /* Collect FSAL stats */ now(&start_time); rc = ioctl(gpfs_fd, kGanesha, &args); now(&stop_time); resp_time = timespec_diff(&start_time, &stop_time); /* record FSAL stats */ idx = gpfs_op2index(op); (void)atomic_inc_uint64_t(&gpfs_stats.op_stats[idx].num_ops); (void)atomic_add_uint64_t(&gpfs_stats.op_stats[idx].resp_time, resp_time); if (gpfs_stats.op_stats[idx].resp_time_max < resp_time) gpfs_stats.op_stats[idx].resp_time_max = resp_time; if (gpfs_stats.op_stats[idx].resp_time_min == 0 || gpfs_stats.op_stats[idx].resp_time_min > resp_time) gpfs_stats.op_stats[idx].resp_time_min = resp_time; } else { rc = ioctl(gpfs_fd, kGanesha, &args); } return rc; } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/handle.c000066400000000000000000001066421473756622300200650ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /** * @file handle.c * @brief GPFS object (file|dir) handle object * * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #include "config.h" #include /* used for 'dirname' */ #include #include #include #include #include #include #include "fsal.h" #include "fsal_internal.h" #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_localfs.h" #include "gpfs_methods.h" #include "nfs_proto_tools.h" /* alloc_handle * allocate and fill in a handle * this uses malloc/free for the time being. */ #define ATTR_GPFS_ALLOC_HANDLE (ATTR_TYPE | ATTR_FILEID | ATTR_FSID) struct gpfs_fsal_obj_handle *alloc_handle(struct gpfs_file_handle *fh, struct fsal_filesystem *fs, struct fsal_attrlist *attributes, const char *link_content, struct fsal_export *exp_hdl) { struct gpfs_fsal_export *myself = container_of(exp_hdl, struct gpfs_fsal_export, export); struct gpfs_fsal_obj_handle *hdl = gsh_calloc(1, sizeof(struct gpfs_fsal_obj_handle) + sizeof(struct gpfs_file_handle)); hdl->handle = (struct gpfs_file_handle *)&hdl[1]; hdl->obj_handle.fs = fs; memcpy(hdl->handle, fh, fh->handle_size); /* See fsal_internal_get_handle_at() for details. Some versions * of GPFS may return 40 byte sized handles and 48 byte sized * handles for the same object. Having different sized file * handles for the same object causes multiple entries in the * cache leading to junction check failures. This code makes * sure that any client sending us the old 40 byte sized handle * will be modified here to match its 48 byte sized handle! */ if (hdl->handle->handle_size == 40) { hdl->handle->handle_size = 48; } hdl->obj_handle.type = attributes->type; if (hdl->obj_handle.type == REGULAR_FILE) { hdl->u.file.fd.fd = -1; /* no open on this yet */ hdl->u.file.fd.fsal_fd.openflags = FSAL_O_CLOSED; } else if (hdl->obj_handle.type == SYMBOLIC_LINK && link_content != NULL) { size_t len = strlen(link_content) + 1; hdl->u.symlink.link_content = gsh_malloc(len); memcpy(hdl->u.symlink.link_content, link_content, len); hdl->u.symlink.link_size = len; } fsal_obj_handle_init(&hdl->obj_handle, exp_hdl, attributes->type, true); hdl->obj_handle.fsid = attributes->fsid; hdl->obj_handle.fileid = attributes->fileid; if (hdl->obj_handle.type == REGULAR_FILE) { init_fsal_fd(&hdl->u.file.fd.fsal_fd, FSAL_FD_GLOBAL, op_ctx->fsal_export); } if (myself->pnfs_mds_enabled) hdl->obj_handle.obj_ops = &GPFS.handle_ops_with_pnfs; else hdl->obj_handle.obj_ops = &GPFS.handle_ops; return hdl; } /* lookup * deprecated NULL parent && NULL path implies root handle */ static fsal_status_t lookup(struct fsal_obj_handle *parent, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int retval = 0; fsal_status_t status; struct gpfs_fsal_obj_handle *hdl; struct fsal_attrlist attrib; struct gpfs_file_handle *fh = alloca(sizeof(struct gpfs_file_handle)); struct fsal_filesystem *fs; *handle = NULL; /* poison it first */ fs = parent->fs; if (!path) return fsalstat(ERR_FSAL_FAULT, 0); memset(fh, 0, sizeof(struct gpfs_file_handle)); fh->handle_size = GPFS_MAX_FH_SIZE; if (!fsal_obj_handle_is(parent, DIRECTORY)) { LogCrit(COMPONENT_FSAL, "Parent handle is not a directory. hdl = 0x%p", parent); return fsalstat(ERR_FSAL_NOTDIR, 0); } if (parent->fsal != parent->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", parent->fsal->name, parent->fs->fsal->name); retval = EXDEV; goto hdlerr; } fsal_prepare_attrs(&attrib, ATTR_GPFS_ALLOC_HANDLE); if (attrs_out != NULL) attrib.request_mask |= attrs_out->request_mask; status = GPFSFSAL_lookup(parent, path, &attrib, fh, &fs); if (FSAL_IS_ERROR(status)) return status; /* allocate an obj_handle and fill it up */ hdl = alloc_handle(fh, fs, &attrib, NULL, op_ctx->fsal_export); if (attrs_out != NULL) { /* Copy the attributes to caller, passing ACL ref. */ fsal_copy_attrs(attrs_out, &attrib, true); } else { /* Done with the attrs */ fsal_release_attrs(&attrib); } *handle = &hdl->obj_handle; return fsalstat(ERR_FSAL_NO_ERROR, 0); hdlerr: fsal_error = posix2fsal_error(retval); return fsalstat(fsal_error, retval); } static fsal_status_t makedir(struct fsal_obj_handle *dir_hdl, const char *name, struct fsal_attrlist *attr_in, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct gpfs_fsal_obj_handle *hdl; fsal_status_t status; struct gpfs_file_handle *fh = alloca(sizeof(struct gpfs_file_handle)); /* Use a separate fsal_attrlist to getch the actual attributes into */ struct fsal_attrlist attrib; *handle = NULL; /* poison it */ if (!fsal_obj_handle_is(dir_hdl, DIRECTORY)) { LogCrit(COMPONENT_FSAL, "Parent handle is not a directory. hdl = 0x%p", dir_hdl); return fsalstat(ERR_FSAL_NOTDIR, 0); } memset(fh, 0, sizeof(struct gpfs_file_handle)); fh->handle_size = GPFS_MAX_FH_SIZE; fsal_prepare_attrs(&attrib, ATTR_GPFS_ALLOC_HANDLE); if (attrs_out != NULL) attrib.request_mask |= attrs_out->request_mask; status = GPFSFSAL_mkdir(dir_hdl, name, attr_in->mode, fh, &attrib); if (FSAL_IS_ERROR(status)) return status; /* allocate an obj_handle and fill it up */ hdl = alloc_handle(fh, dir_hdl->fs, &attrib, NULL, op_ctx->fsal_export); if (attrs_out != NULL) { /* Copy the attributes to caller, passing ACL ref. */ fsal_copy_attrs(attrs_out, &attrib, true); } else { /* Done with the attrs */ fsal_release_attrs(&attrib); } *handle = &hdl->obj_handle; /* We handled the mode above. */ FSAL_UNSET_MASK(attr_in->valid_mask, ATTR_MODE); if (attr_in->valid_mask) { /* Now per support_ex API, if there are any other attributes * set, go ahead and get them set now. */ status = (*handle)->obj_ops->setattr2(*handle, false, NULL, attr_in); if (FSAL_IS_ERROR(status)) { /* Release the handle we just allocated. */ LogFullDebug(COMPONENT_FSAL, "setattr2 status=%s", fsal_err_txt(status)); (*handle)->obj_ops->release(*handle); *handle = NULL; } } else { status = fsalstat(ERR_FSAL_NO_ERROR, 0); } FSAL_SET_MASK(attr_in->valid_mask, ATTR_MODE); return status; } static fsal_status_t makenode(struct fsal_obj_handle *dir_hdl, const char *name, object_file_type_t nodetype, struct fsal_attrlist *attr_in, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { fsal_status_t status; struct gpfs_fsal_obj_handle *hdl; struct gpfs_file_handle *fh = alloca(sizeof(struct gpfs_file_handle)); /* Use a separate fsal_attrlist to getch the actual attributes into */ struct fsal_attrlist attrib; *handle = NULL; /* poison it */ if (!fsal_obj_handle_is(dir_hdl, DIRECTORY)) { LogCrit(COMPONENT_FSAL, "Parent handle is not a directory. hdl = 0x%p", dir_hdl); return fsalstat(ERR_FSAL_NOTDIR, 0); } memset(fh, 0, sizeof(struct gpfs_file_handle)); fh->handle_size = GPFS_MAX_FH_SIZE; fsal_prepare_attrs(&attrib, ATTR_GPFS_ALLOC_HANDLE); if (attrs_out != NULL) attrib.request_mask |= attrs_out->request_mask; status = GPFSFSAL_mknode(dir_hdl, name, attr_in->mode, nodetype, &attr_in->rawdev, fh, &attrib); if (FSAL_IS_ERROR(status)) return status; /* allocate an obj_handle and fill it up */ hdl = alloc_handle(fh, dir_hdl->fs, &attrib, NULL, op_ctx->fsal_export); if (attrs_out != NULL) { /* Copy the attributes to caller, passing ACL ref. */ fsal_copy_attrs(attrs_out, &attrib, true); } else { /* Done with the attrs */ fsal_release_attrs(&attrib); } *handle = &hdl->obj_handle; /* We handled the mode above. */ FSAL_UNSET_MASK(attr_in->valid_mask, ATTR_MODE); if (attr_in->valid_mask) { /* Now per support_ex API, if there are any other attributes * set, go ahead and get them set now. */ status = (*handle)->obj_ops->setattr2(*handle, false, NULL, attr_in); if (FSAL_IS_ERROR(status)) { /* Release the handle we just allocated. */ LogFullDebug(COMPONENT_FSAL, "setattr2 status=%s", fsal_err_txt(status)); (*handle)->obj_ops->release(*handle); *handle = NULL; } } else { status = fsalstat(ERR_FSAL_NO_ERROR, 0); } FSAL_SET_MASK(attr_in->valid_mask, ATTR_MODE); return status; } /** makesymlink * Note that we do not set mode bits on symlinks for Linux/POSIX * They are not really settable in the kernel and are not checked * anyway (default is 0777) because open uses that target's mode */ static fsal_status_t makesymlink(struct fsal_obj_handle *dir_hdl, const char *name, const char *link_path, struct fsal_attrlist *attr_in, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { fsal_status_t status; struct gpfs_fsal_obj_handle *hdl; struct gpfs_file_handle *fh = alloca(sizeof(struct gpfs_file_handle)); /* Use a separate fsal_attrlist to getch the actual attributes into */ struct fsal_attrlist attrib; *handle = NULL; /* poison it first */ if (!fsal_obj_handle_is(dir_hdl, DIRECTORY)) { LogCrit(COMPONENT_FSAL, "Parent handle is not a directory. hdl = 0x%p", dir_hdl); return fsalstat(ERR_FSAL_NOTDIR, 0); } memset(fh, 0, sizeof(struct gpfs_file_handle)); fh->handle_size = GPFS_MAX_FH_SIZE; fsal_prepare_attrs(&attrib, ATTR_GPFS_ALLOC_HANDLE); if (attrs_out != NULL) attrib.request_mask |= attrs_out->request_mask; status = GPFSFSAL_symlink(dir_hdl, name, link_path, attr_in->mode, fh, &attrib); if (FSAL_IS_ERROR(status)) return status; /* allocate an obj_handle and fill it up */ hdl = alloc_handle(fh, dir_hdl->fs, &attrib, link_path, op_ctx->fsal_export); if (attrs_out != NULL) { /* Copy the attributes to caller, passing ACL ref. */ fsal_copy_attrs(attrs_out, &attrib, true); } else { /* Done with the attrs */ fsal_release_attrs(&attrib); } *handle = &hdl->obj_handle; /* We handled the mode above. */ FSAL_UNSET_MASK(attr_in->valid_mask, ATTR_MODE); if (attr_in->valid_mask) { /* Now per support_ex API, if there are any other attributes * set, go ahead and get them set now. */ status = (*handle)->obj_ops->setattr2(*handle, false, NULL, attr_in); if (FSAL_IS_ERROR(status)) { /* Release the handle we just allocated. */ LogFullDebug(COMPONENT_FSAL, "setattr2 status=%s", fsal_err_txt(status)); (*handle)->obj_ops->release(*handle); *handle = NULL; } } else { status = fsalstat(ERR_FSAL_NO_ERROR, 0); } FSAL_SET_MASK(attr_in->valid_mask, ATTR_MODE); return status; } static fsal_status_t readsymlink(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *link_content, bool refresh) { struct gpfs_fsal_obj_handle *myself = NULL; fsal_status_t status; if (obj_hdl->type != SYMBOLIC_LINK) return fsalstat(ERR_FSAL_FAULT, 0); myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); if (refresh) { /* lazy load or LRU'd storage */ char link_buff[PATH_MAX]; if (myself->u.symlink.link_content != NULL) { gsh_free(myself->u.symlink.link_content); myself->u.symlink.link_content = NULL; myself->u.symlink.link_size = 0; } status = GPFSFSAL_readlink(obj_hdl, link_buff, sizeof(link_buff)); if (FSAL_IS_ERROR(status)) return status; myself->u.symlink.link_content = gsh_strdup(link_buff); myself->u.symlink.link_size = strlen(link_buff) + 1; } if (myself->u.symlink.link_content == NULL) return fsalstat(ERR_FSAL_FAULT, 0); link_content->len = myself->u.symlink.link_size; link_content->addr = gsh_strdup(myself->u.symlink.link_content); return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t linkfile(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *destdir_hdl, const char *name, struct fsal_attrlist *destdir_pre_attrs_out, struct fsal_attrlist *destdir_post_attrs_out) { fsal_status_t status; struct gpfs_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); status = GPFSFSAL_link(destdir_hdl, myself->handle, name); return status; } #define BUF_SIZE 1024 /** * read_dirents * read the directory and call through the callback function for * each entry. * @param dir_hdl [IN] the directory to read * @param whence [IN] where to start (next) * @param dir_state [IN] pass thru of state to callback * @param cb [IN] callback function * @param eof [OUT] eof marker true == end of dir */ static fsal_status_t read_dirents(struct fsal_obj_handle *dir_hdl, fsal_cookie_t *whence, void *dir_state, fsal_readdir_cb cb, attrmask_t attrmask, bool *eof) { fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int retval = 0; struct gpfs_fsal_obj_handle *myself; int dirfd; fsal_status_t status; off_t seekloc = 0; int bpos, nread; struct dirent64 *dentry; char buf[BUF_SIZE]; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; if (whence != NULL) seekloc = (off_t)*whence; myself = container_of(dir_hdl, struct gpfs_fsal_obj_handle, obj_handle); status = fsal_internal_handle2fd(export_fd, myself->handle, &dirfd, O_RDONLY | O_DIRECTORY); if (FSAL_IS_ERROR(status)) return status; seekloc = lseek(dirfd, seekloc, SEEK_SET); if (seekloc < 0) { retval = errno; fsal_error = posix2fsal_error(retval); goto done; } do { nread = syscall(SYS_getdents64, dirfd, buf, BUF_SIZE); if (nread < 0) { retval = errno; fsal_error = posix2fsal_error(retval); goto done; } if (nread == 0) break; for (bpos = 0; bpos < nread;) { struct fsal_obj_handle *hdl; struct fsal_attrlist attrs; enum fsal_dir_result cb_rc; dentry = (struct dirent64 *)(buf + bpos); if (strcmp(dentry->d_name, ".") == 0 || strcmp(dentry->d_name, "..") == 0) goto skip; /* must skip '.' and '..' */ fsal_prepare_attrs(&attrs, attrmask); status = lookup(dir_hdl, dentry->d_name, &hdl, &attrs); /* The entries we get in getdents64 syscall may * not be there by the time we do lookup, so * handle some errors here. * * Since we do lookup by name, we mostly get * ERR_FSAL_NOENT, but handle other similar * errors! */ if (FSAL_IS_ERROR(status)) { if (status.major == ERR_FSAL_NOENT || status.major == ERR_FSAL_STALE || status.major == ERR_FSAL_XDEV) { goto skip; } else { fsal_error = status.major; goto done; } } /* callback to MDCACHE */ cb_rc = cb(dentry->d_name, hdl, &attrs, dir_state, (fsal_cookie_t)dentry->d_off); fsal_release_attrs(&attrs); /* Read ahead not supported by this FSAL. */ if (cb_rc >= DIR_READAHEAD) goto done; skip: bpos += dentry->d_reclen; } } while (nread > 0); *eof = true; done: fsal_internal_close(dirfd, NULL, 0); return fsalstat(fsal_error, retval); } static fsal_status_t renamefile(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *olddir_hdl, const char *old_name, struct fsal_obj_handle *newdir_hdl, const char *new_name, struct fsal_attrlist *olddir_pre_attrs_out, struct fsal_attrlist *olddir_post_attrs_out, struct fsal_attrlist *newdir_pre_attrs_out, struct fsal_attrlist *newdir_post_attrs_out) { fsal_status_t status; status = GPFSFSAL_rename(olddir_hdl, old_name, newdir_hdl, new_name); return status; } /* FIXME: attributes are now merged into fsal_obj_handle. This * spreads everywhere these methods are used. eventually deprecate * everywhere except where we explicitly want to refresh them. * NOTE: this is done under protection of the attributes rwlock in the * cache entry. */ static fsal_status_t getattrs(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs) { struct gpfs_fsal_obj_handle *myself; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); status = GPFSFSAL_getattrs(op_ctx->fsal_export, obj_hdl->fs->private_data, myself->handle, attrs); if (FSAL_IS_ERROR(status)) { goto out; } if (!FSAL_TEST_MASK(attrs->request_mask, ATTR4_FS_LOCATIONS)) { goto out; } if (obj_hdl->type != DIRECTORY) goto out; fsal_status_t fs_loc_status = GPFSFSAL_fs_loc(op_ctx->fsal_export, obj_hdl->fs->private_data, myself->handle, attrs); if (FSAL_IS_SUCCESS(fs_loc_status)) { FSAL_SET_MASK(attrs->valid_mask, ATTR4_FS_LOCATIONS); } else { LogDebug( COMPONENT_FSAL, "Request for attribute fs_locations failed with error: %s", msg_fsal_err(fs_loc_status.major)); } out: return status; } static fsal_status_t getxattrs(struct fsal_obj_handle *obj_hdl, xattrkey4 *xa_name, xattrvalue4 *xa_value) { int rc; int errsv; struct getxattr_arg gxarg; struct gpfs_fsal_obj_handle *myself; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); gxarg.mountdirfd = export_fd; gxarg.handle = myself->handle; gxarg.name_len = xa_name->utf8string_len; gxarg.name = xa_name->utf8string_val; gxarg.value_len = xa_value->utf8string_len; gxarg.value = xa_value->utf8string_val; rc = gpfs_ganesha(OPENHANDLE_GETXATTRS, &gxarg); if (rc < 0) { errsv = errno; LogDebug(COMPONENT_FSAL, "GETXATTRS returned rc %d errsv %d", rc, errsv); if (errsv == ERANGE) return fsalstat(ERR_FSAL_TOOSMALL, 0); if (errsv == ENODATA) return fsalstat(ERR_FSAL_NOENT, 0); return fsalstat(posix2fsal_error(errsv), errsv); } /* Make sure utf8string is NUL terminated */ xa_value->utf8string_val[gxarg.value_len] = '\0'; LogDebug(COMPONENT_FSAL, "GETXATTRS returned value %s len %d rc %d", (char *)gxarg.value, gxarg.value_len, rc); xa_value->utf8string_len = gxarg.value_len; return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t setxattrs(struct fsal_obj_handle *obj_hdl, setxattr_option4 option, xattrkey4 *xa_name, xattrvalue4 *xa_value) { int rc; int errsv; struct setxattr_arg sxarg; struct gpfs_fsal_obj_handle *myself; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); sxarg.mountdirfd = export_fd; sxarg.handle = myself->handle; sxarg.name_len = xa_name->utf8string_len; sxarg.name = xa_name->utf8string_val; sxarg.value_len = xa_value->utf8string_len; sxarg.value = xa_value->utf8string_val; sxarg.cli_ip = NULL; if (op_ctx && op_ctx->client) sxarg.cli_ip = op_ctx->client->hostaddr_str; rc = gpfs_ganesha(OPENHANDLE_SETXATTRS, &sxarg); if (rc < 0) { errsv = errno; LogDebug(COMPONENT_FSAL, "SETXATTRS returned rc %d errsv %d", rc, errsv); return fsalstat(posix2fsal_error(errsv), errsv); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t removexattrs(struct fsal_obj_handle *obj_hdl, xattrkey4 *xa_name) { int rc; int errsv; struct removexattr_arg rxarg; struct gpfs_fsal_obj_handle *myself; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); rxarg.mountdirfd = export_fd; rxarg.handle = myself->handle; rxarg.name_len = xa_name->utf8string_len; rxarg.name = xa_name->utf8string_val; rxarg.cli_ip = NULL; if (op_ctx && op_ctx->client) rxarg.cli_ip = op_ctx->client->hostaddr_str; rc = gpfs_ganesha(OPENHANDLE_REMOVEXATTRS, &rxarg); if (rc < 0) { errsv = errno; LogDebug(COMPONENT_FSAL, "REMOVEXATTRS returned rc %d errsv %d", rc, errsv); return fsalstat(posix2fsal_error(errsv), errsv); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t listxattrs(struct fsal_obj_handle *obj_hdl, count4 la_maxcount, nfs_cookie4 *la_cookie, bool_t *lr_eof, xattrlist4 *lr_names) { int rc; int errsv; char *name, *next, *end, *val, *valstart; int entryCount = 0; char *buf = NULL; struct listxattr_arg lxarg; struct gpfs_fsal_obj_handle *myself; component4 *entry = lr_names->xl4_entries; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; val = (char *)entry + la_maxcount; valstart = val; myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); #define MAXCOUNT (1024 * 64) buf = gsh_malloc(MAXCOUNT); lxarg.mountdirfd = export_fd; lxarg.handle = myself->handle; lxarg.cookie = 0; /* For now gpfs doesn't support cookie */ lxarg.verifier = 0; /* @todo: protocol has no verifier now */ lxarg.eof = false; lxarg.name_len = MAXCOUNT; lxarg.names = buf; lxarg.cli_ip = NULL; if (op_ctx && op_ctx->client) lxarg.cli_ip = op_ctx->client->hostaddr_str; LogFullDebug(COMPONENT_FSAL, "in cookie %llu len %d", (unsigned long long)lxarg.cookie, la_maxcount); rc = gpfs_ganesha(OPENHANDLE_LISTXATTRS, &lxarg); if (rc < 0) { errsv = errno; LogDebug(COMPONENT_FSAL, "LISTXATTRS returned rc %d errsv %d", rc, errsv); gsh_free(buf); if (errsv == ERANGE) return fsalstat(ERR_FSAL_TOOSMALL, 0); return fsalstat(posix2fsal_error(errsv), errsv); } if (!lxarg.eof) { errsv = ERR_FSAL_SERVERFAULT; LogCrit(COMPONENT_FSAL, "Unable to get xattr."); return fsalstat(posix2fsal_error(errsv), errsv); } /* Only return names that the caller can read via getxattr */ name = buf; end = buf + rc; entry->utf8string_len = 0; entry->utf8string_val = NULL; while (name < end) { next = strchr(name, '\0'); next += 1; LogDebug(COMPONENT_FSAL, "nameP %s at offset %td", name, (next - name)); if (entryCount >= *la_cookie) { if ((((char *)entry - (char *)lr_names->xl4_entries) + sizeof(component4) > la_maxcount) || ((val - valstart) + (next - name) > la_maxcount)) { gsh_free(buf); *lr_eof = false; lr_names->xl4_count = entryCount - *la_cookie; *la_cookie += entryCount; LogFullDebug(COMPONENT_FSAL, "out1 cookie %llu off %td eof %d", (unsigned long long)*la_cookie, (next - name), *lr_eof); if (lr_names->xl4_count == 0) return fsalstat(ERR_FSAL_TOOSMALL, 0); return fsalstat(ERR_FSAL_NO_ERROR, 0); } entry->utf8string_len = next - name; entry->utf8string_val = val; memcpy(entry->utf8string_val, name, entry->utf8string_len); entry->utf8string_val[entry->utf8string_len] = '\0'; LogFullDebug( COMPONENT_FSAL, "entry %d val %p at %p len %d at %p name %s", entryCount, val, entry, entry->utf8string_len, entry->utf8string_val, entry->utf8string_val); val += entry->utf8string_len; entry += 1; } /* Advance to next name in original buffer */ name = next; entryCount += 1; } lr_names->xl4_count = entryCount - *la_cookie; *la_cookie = 0; *lr_eof = true; gsh_free(buf); LogFullDebug(COMPONENT_FSAL, "out2 cookie %llu eof %d", (unsigned long long)*la_cookie, *lr_eof); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* * NOTE: this is done under protection of the attributes rwlock in cache entry. */ fsal_status_t gpfs_setattr2(struct fsal_obj_handle *obj_hdl, bool bypass, struct state_t *state, struct fsal_attrlist *attrs) { fsal_status_t status; status = GPFSFSAL_setattrs(obj_hdl, attrs); return status; } /* file_unlink * unlink the named file in the directory */ static fsal_status_t file_unlink(struct fsal_obj_handle *dir_hdl, struct fsal_obj_handle *obj_hdl, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { fsal_status_t status; status = GPFSFSAL_unlink(dir_hdl, name); return status; } /* handle_to_wire * fill in the opaque f/s file handle part. * we zero the buffer to length first. This MAY already be done above * at which point, remove memset here because the caller is zeroing * the whole struct. */ static fsal_status_t handle_to_wire(const struct fsal_obj_handle *obj_hdl, fsal_digesttype_t output_type, struct gsh_buffdesc *fh_desc) { const struct gpfs_fsal_obj_handle *myself; struct gpfs_file_handle *fh; size_t fh_size; /* sanity checks */ if (!fh_desc) return fsalstat(ERR_FSAL_FAULT, 0); myself = container_of(obj_hdl, const struct gpfs_fsal_obj_handle, obj_handle); fh = myself->handle; switch (output_type) { case FSAL_DIGEST_NFSV3: case FSAL_DIGEST_NFSV4: fh_size = gpfs_sizeof_handle(fh); if (fh_desc->len < fh_size) goto errout; memcpy(fh_desc->addr, fh, fh_size); break; default: return fsalstat(ERR_FSAL_SERVERFAULT, 0); } fh_desc->len = fh_size; LogFullDebug(COMPONENT_FSAL, "FSAL fh_size %zu type %d", fh_size, output_type); return fsalstat(ERR_FSAL_NO_ERROR, 0); errout: LogMajor(COMPONENT_FSAL, "Space too small for handle. need %zu, have %zu", fh_size, fh_desc->len); return fsalstat(ERR_FSAL_TOOSMALL, 0); } /** * handle_to_key * return a handle descriptor into the handle in this object handle * @TODO reminder. make sure things like hash keys don't point here * after the handle is released. */ static void handle_to_key(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *fh_desc) { struct gpfs_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); fh_desc->addr = myself->handle; fh_desc->len = myself->handle->handle_key_size; } /** * @brief release object handle * * release our export first so they know we are gone */ static void release(struct fsal_obj_handle *obj_hdl) { struct gpfs_fsal_obj_handle *myself; const object_file_type_t type = obj_hdl->type; myself = container_of(obj_hdl, struct gpfs_fsal_obj_handle, obj_handle); LogFullDebug(COMPONENT_FSAL, "type %d", type); if (type == REGULAR_FILE) { fsal_status_t st; st = close_fsal_fd(obj_hdl, &myself->u.file.fd.fsal_fd, false); if (FSAL_IS_ERROR(st)) { LogCrit(COMPONENT_FSAL, "Could not close hdl 0x%p, status %s error %s(%d)", obj_hdl, fsal_err_txt(st), strerror(st.minor), st.minor); } } if (myself->obj_handle.type == REGULAR_FILE) destroy_fsal_fd(&myself->u.file.fd.fsal_fd); fsal_obj_handle_fini(obj_hdl, true); if (type == SYMBOLIC_LINK) { gsh_free(myself->u.symlink.link_content); } gsh_free(myself); } /** * * @param ops Object operations */ void gpfs_handle_ops_init(struct fsal_obj_ops *ops) { fsal_default_obj_ops_init(ops); ops->release = release; ops->lookup = lookup; ops->readdir = read_dirents; ops->mkdir = makedir; ops->mknode = makenode; ops->symlink = makesymlink; ops->readlink = readsymlink; ops->getattrs = getattrs; ops->link = linkfile; ops->rename = renamefile; ops->unlink = file_unlink; ops->seek2 = gpfs_seek2; ops->io_advise = gpfs_io_advise; ops->close = gpfs_close; ops->handle_to_wire = handle_to_wire; ops->handle_to_key = handle_to_key; handle_ops_pnfs(ops); ops->getxattrs = getxattrs; ops->setxattrs = setxattrs; ops->removexattrs = removexattrs; ops->listxattrs = listxattrs; ops->open2 = gpfs_open2; ops->status2 = gpfs_status2; ops->reopen2 = gpfs_reopen2; ops->read2 = gpfs_read2; ops->write2 = gpfs_write2; ops->commit2 = gpfs_commit2; ops->setattr2 = gpfs_setattr2; ops->close2 = gpfs_close2; ops->close_func = gpfs_close_func; ops->reopen_func = gpfs_reopen_func; ops->lock_op2 = gpfs_lock_op2; ops->merge = gpfs_merge; ops->is_referral = fsal_common_is_referral; ops->fallocate = gpfs_fallocate; } /** * @param exp_hdl Handle * @param path The path to lookup * @param handle Reference to handle * * modelled on old api except we don't stuff attributes. * @return Status of operation */ fsal_status_t gpfs_lookup_path(struct fsal_export *exp_hdl, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { fsal_status_t fsal_status; int retval = 0; int dir_fd; struct fsal_filesystem *fs; struct gpfs_fsal_obj_handle *hdl; struct fsal_attrlist attributes; gpfsfsal_xstat_t buffxstat; struct gpfs_file_handle *fh = alloca(sizeof(struct gpfs_file_handle)); struct fsal_fsid__ fsid; struct gpfs_fsal_export *gpfs_export; gpfs_acl_t *acl_buf; unsigned int acl_buflen; bool use_acl; int retry; memset(fh, 0, sizeof(struct gpfs_file_handle)); fh->handle_size = GPFS_MAX_FH_SIZE; *handle = NULL; /* poison it */ dir_fd = open_dir_by_path_walk(-1, path, &buffxstat.buffstat); fsal_prepare_attrs(&attributes, ATTR_GPFS_ALLOC_HANDLE); if (attrs_out != NULL) attributes.request_mask |= attrs_out->request_mask; if (dir_fd < 0) { LogDebug(COMPONENT_FSAL, "Could not open directory for path %s", path); fsal_status = fsalstat(posix2fsal_error(-dir_fd), retval); goto errout; } fsal_status = fsal_internal_fd2handle(dir_fd, fh); if (FSAL_IS_ERROR(fsal_status)) goto fileerr; gpfs_export = container_of(exp_hdl, struct gpfs_fsal_export, export); /* Let us make the first request using the acl buffer that is * part of buffxstat itself. If that is not sufficient, we * allocate from heap and retry. */ use_acl = attributes.request_mask & ATTR_ACL; for (retry = 0; retry < GPFS_ACL_MAX_RETRY; retry++) { switch (retry) { case 0: /* first attempt */ acl_buf = (gpfs_acl_t *)buffxstat.buffacl; acl_buflen = GPFS_ACL_BUF_SIZE; break; case 1: /* first retry, don't free the old stack buffer */ acl_buflen = acl_buf->acl_len; acl_buf = gsh_malloc(acl_buflen); break; default: /* second or later retry, free the old heap buffer */ acl_buflen = acl_buf->acl_len; gsh_free(acl_buf); acl_buf = gsh_malloc(acl_buflen); break; } fsal_status = fsal_get_xstat_by_handle(dir_fd, fh, &buffxstat, acl_buf, acl_buflen, NULL, false, use_acl); if (FSAL_IS_ERROR(fsal_status) || !use_acl || acl_buflen >= acl_buf->acl_len) break; } if (retry == GPFS_ACL_MAX_RETRY) { /* make up an error */ LogCrit(COMPONENT_FSAL, "unable to get ACLs"); fsal_status = fsalstat(ERR_FSAL_SERVERFAULT, 0); } if (FSAL_IS_ERROR(fsal_status)) goto xstat_err; fsal_status = gpfsfsal_xstat_2_fsal_attributes( &buffxstat, &attributes, acl_buf, gpfs_export->use_acl); LogFullDebug(COMPONENT_FSAL, "fsid=0x%016" PRIx64 ".0x%016" PRIx64, attributes.fsid.major, attributes.fsid.minor); if (FSAL_IS_ERROR(fsal_status)) goto xstat_err; if (acl_buflen != GPFS_ACL_BUF_SIZE) { assert(acl_buf != (gpfs_acl_t *)buffxstat.buffacl); gsh_free(acl_buf); } close(dir_fd); gpfs_extract_fsid(fh, &fsid); fs = lookup_fsid(&fsid, GPFS_FSID_TYPE); if (fs == NULL) { LogInfo(COMPONENT_FSAL, "Could not find file system for path %s", path); fsal_status = fsalstat(posix2fsal_error(ENOENT), ENOENT); goto errout; } if (fs->fsal != exp_hdl->fsal) { LogInfo(COMPONENT_FSAL, "File system for path %s did not belong to FSAL %s", path, exp_hdl->fsal->name); fsal_status = fsalstat(posix2fsal_error(EACCES), EACCES); goto errout; } LogDebug(COMPONENT_FSAL, "filesystem %s for path %s", fs->path, path); /* allocate an obj_handle and fill it up */ hdl = alloc_handle(fh, fs, &attributes, NULL, exp_hdl); if (attrs_out != NULL) { /* Copy the attributes to caller, passing ACL ref. */ fsal_copy_attrs(attrs_out, &attributes, true); } else { /* Done with the attrs */ fsal_release_attrs(&attributes); } *handle = &hdl->obj_handle; return fsalstat(ERR_FSAL_NO_ERROR, 0); xstat_err: /* free if the acl buffer is from the heap */ if (acl_buflen != GPFS_ACL_BUF_SIZE) { assert(acl_buf != (gpfs_acl_t *)buffxstat.buffacl); gsh_free(acl_buf); } fileerr: close(dir_fd); errout: /* Done with attributes */ fsal_release_attrs(&attributes); return fsal_status; } /** * @brief create GPFS handle * * @param exp_hdl export handle * @param hdl_desc handle description * @param handle object handle * @return status * * Does what original FSAL_ExpandHandle did (sort of) * returns a ref counted handle to be later used in mdcache etc. * NOTE! you must release this thing when done with it! * BEWARE! Thanks to some holes in the *AT syscalls implementation, * we cannot get an fd on an AF_UNIX socket, nor reliably on block or * character special devices. Sorry, it just doesn't... * we could if we had the handle of the dir it is in, but this method * is for getting handles off the wire for cache entries that have LRU'd. * Ideas and/or clever hacks are welcome... */ fsal_status_t gpfs_create_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *hdl_desc, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { fsal_status_t status; struct gpfs_fsal_obj_handle *hdl; struct gpfs_file_handle *fh; struct fsal_attrlist attrib; char link_buff[PATH_MAX]; struct fsal_fsid__ fsid; struct fsal_filesystem *fs; struct gpfs_filesystem *gpfs_fs; struct gpfs_fsal_export *exp = container_of( op_ctx->fsal_export, struct gpfs_fsal_export, export); int export_fd = exp->export_fd; *handle = NULL; /* poison it first */ if ((hdl_desc->len > (sizeof(struct gpfs_file_handle)))) return fsalstat(ERR_FSAL_FAULT, 0); fh = alloca(hdl_desc->len); memcpy(fh, hdl_desc->addr, hdl_desc->len); gpfs_extract_fsid(fh, &fsid); fs = lookup_fsid(&fsid, GPFS_FSID_TYPE); if (fs == NULL) { LogInfo(COMPONENT_FSAL, "Could not find filesystem for fsid=0x%016" PRIx64 ".0x%016" PRIx64 " from handle", fsid.major, fsid.minor); return fsalstat(ERR_FSAL_STALE, ESTALE); } if (fs->fsal != exp_hdl->fsal) { LogInfo(COMPONENT_FSAL, "Non GPFS filesystem fsid=0x%016" PRIx64 ".0x%016" PRIx64 " from handle", fsid.major, fsid.minor); return fsalstat(ERR_FSAL_STALE, ESTALE); } gpfs_fs = fs->private_data; fsal_prepare_attrs(&attrib, ATTR_GPFS_ALLOC_HANDLE); if (attrs_out != NULL) attrib.request_mask |= attrs_out->request_mask; status = GPFSFSAL_getattrs(exp_hdl, gpfs_fs, fh, &attrib); if (FSAL_IS_ERROR(status)) return status; if (attrib.type == SYMBOLIC_LINK) { /* I could lazy eval this... */ status = fsal_readlink_by_handle(export_fd, fh, link_buff, sizeof(link_buff)); if (FSAL_IS_ERROR(status)) return status; } hdl = alloc_handle(fh, fs, &attrib, link_buff, exp_hdl); if (attrs_out != NULL) { /* Copy the attributes to caller, passing ACL ref. */ fsal_copy_attrs(attrs_out, &attrib, true); } else { /* Done with the attrs */ fsal_release_attrs(&attrib); } *handle = &hdl->obj_handle; return status; } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/include/000077500000000000000000000000001473756622300201005ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/include/gpfs.h000066400000000000000000005065341473756622300212250ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ /* NOTE: This file is directly taken from the GPFS source code and should not */ /* be modified beyond the inclusion of this notice and the SPDX header */ /* above. */ /* */ /* Copyright (C) 2001 International Business Machines */ /* All rights reserved. */ /* */ /* This file is part of the GPFS user library. */ /* */ /* 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. */ /* 3. The name of the author may not be used to endorse or promote products */ /* derived from this software without specific prior written */ /* permission. */ /* */ /* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. */ /* */ /* %Z%%M% %I% %W% %G% %U% */ /* * Library calls for GPFS interfaces */ #ifndef H_GPFS #define H_GPFS #include /* Define GPFS_64BIT_INODES to map the default interface definitions to 64-bit interfaces. Without this define, the 32-bit interface is the default. Both interfaces are always present, but the define sets the default. The actual mapping can be found near the end of this header. */ /* #define GPFS_64BIT_INODES 1 */ #define NFS_IP_SIZE 32 #ifdef __cplusplus extern "C" { #endif #if defined(WIN32) && defined(GPFSDLL) /* The following errno values either are missing from Windows errno.h or have a conflicting value. Other errno values (e.g. EPERM) are okay. */ #define GPFS_EALREADY 37 /* Operation already in progress */ #define GPFS_EOPNOTSUPP 45 /* Operation not supported */ #define GPFS_EDQUOT 69 /* Disk quota exceeded */ #define GPFS_ESTALE 9 /* No file system (mapped to EBADF) */ #define GPFS_EFORMAT 19 /* Unformatted media (mapped to ENODEV) */ /* specify the library calling convention */ #define GPFS_API __stdcall /* On Windows, this is a HANDLE as returned by CreateFile() */ typedef void* gpfs_file_t; #else /* not gpfs.dll on Windows */ #define GPFS_API /* On UNIX systems, this is a file descriptor as returned by open() */ typedef int gpfs_file_t; #endif typedef unsigned int gpfs_uid_t; typedef long long gpfs_off64_t; typedef unsigned long long gpfs_uid64_t; typedef struct gpfs_timestruc { unsigned int tv_sec; unsigned int tv_nsec; } gpfs_timestruc_t; typedef struct gpfs_timestruc64 { long long tv_sec; unsigned int tv_nsec; } gpfs_timestruc64_t; #define GPFS_SLITE_SIZE_BIT 0x00000001 #define GPFS_SLITE_BLKSIZE_BIT 0x00000002 #define GPFS_SLITE_BLOCKS_BIT 0x00000004 #define GPFS_SLITE_ATIME_BIT 0x00000010 #define GPFS_SLITE_MTIME_BIT 0x00000020 #define GPFS_SLITE_CTIME_BIT 0x00000040 #define GPFS_SLITE_EXACT_BITS 0x00000077 /* Returns "1" if the attribute is requested to be accurate. (On output, indicates the value returned in statbufP is accurate). */ #define GPFS_SLITE(m) (0 == (m)) #define GPFS_SLITE_SIZET(m) (0 != ((m) & GPFS_SLITE_SIZE_BIT)) #define GPFS_SLITE_BLKSIZE(m) (0 != ((m) & GPFS_SLITE_BLKSIZE_BIT)) #define GPFS_SLITE_BLOCKS(m) (0 != ((m) & GPFS_SLITE_BLOCKS_BIT)) #define GPFS_SLITE_ATIME(m) (0 != ((m) & GPFS_SLITE_ATIME_BIT)) #define GPFS_SLITE_MTIME(m) (0 != ((m) & GPFS_SLITE_MTIME_BIT)) #define GPFS_SLITE_CTIME(m) (0 != ((m) & GPFS_SLITE_CTIME_BIT)) #define GPFS_SLITE_EXACT(m) (GPFS_SLITE_EXACT_BITS == (m)) /* Sets the litemask bit indicating that the attribute should be accurate */ #define GPFS_S_SLITE(m) (m) = 0 #define GPFS_S_SLITE_SIZET(m) (m) |= GPFS_SLITE_SIZE_BIT #define GPFS_S_SLITE_BLKSIZE(m) (m) |= GPFS_SLITE_BLKSIZE_BIT #define GPFS_S_SLITE_BLOCKS(m) (m) |= GPFS_SLITE_BLOCKS_BIT #define GPFS_S_SLITE_ATIME(m) (m) |= GPFS_SLITE_ATIME_BIT #define GPFS_S_SLITE_MTIME(m) (m) |= GPFS_SLITE_MTIME_BIT #define GPFS_S_SLITE_CTIME(m) (m) |= GPFS_SLITE_CTIME_BIT #define GPFS_S_SLITE_EXACT(m) (m) |= GPFS_SLITE_EXACT_BITS #define GPFS_STATLITE 0 #define GPFS_NOFOLLOW 1 /* Mapping of buffer for gpfs_getacl, gpfs_putacl. */ typedef struct gpfs_opaque_acl { int acl_buffer_len; /* INPUT: Total size of buffer (including this field). OUTPUT: Actual size of the ACL information. */ unsigned short acl_version; /* INPUT: Set to zero. OUTPUT: Current version of the returned ACL. */ unsigned char acl_type; /* INPUT: Type of ACL: access (1) or default (2). */ char acl_var_data[1]; /* OUTPUT: Remainder of the ACL information. */ } gpfs_opaque_acl_t; /* ACL types (acl_type field in gpfs_opaque_acl_t or gpfs_acl_t) */ #define GPFS_ACL_TYPE_ACCESS 1 #define GPFS_ACL_TYPE_DEFAULT 2 #define GPFS_ACL_TYPE_NFS4 3 /* gpfs_getacl, gpfs_putacl flag indicating structures instead of the opaque style data normally used. */ #define GPFS_GETACL_STRUCT 0x00000020 #define GPFS_PUTACL_STRUCT 0x00000020 /* gpfs_getacl, gpfs_putacl flag indicating smbd is the caller */ #define GPFS_ACL_SAMBA 0x00000040 /* Defined values for gpfs_aclVersion_t */ #define GPFS_ACL_VERSION_POSIX 1 #define GPFS_ACL_VERSION_NFS4F 3 /* GPFS_ACL_VERSION_NFS4 plus V4FLAGS */ #define GPFS_ACL_VERSION_NFS4 4 /* Values for gpfs_aclLevel_t */ #define GPFS_ACL_LEVEL_BASE 0 /* compatible with all acl_version values */ #define GPFS_ACL_LEVEL_V4FLAGS 1 /* requires GPFS_ACL_VERSION_NFS4 */ /* Values for gpfs_aceType_t (ACL_VERSION_POSIX) */ #define GPFS_ACL_USER_OBJ 1 #define GPFS_ACL_GROUP_OBJ 2 #define GPFS_ACL_OTHER 3 #define GPFS_ACL_MASK 4 #define GPFS_ACL_USER 5 #define GPFS_ACL_GROUP 6 /* Values for gpfs_acePerm_t (ACL_VERSION_POSIX) */ #define ACL_PERM_EXECUTE 001 #define ACL_PERM_WRITE 002 #define ACL_PERM_READ 004 #define ACL_PERM_CONTROL 010 /* Values for gpfs_aceType_t (ACL_VERSION_NFS4) */ #define ACE4_TYPE_ALLOW 0 #define ACE4_TYPE_DENY 1 #define ACE4_TYPE_AUDIT 2 #define ACE4_TYPE_ALARM 3 /* Values for gpfs_aceFlags_t (ACL_VERSION_NFS4) */ #define ACE4_FLAG_FILE_INHERIT 0x00000001 #define ACE4_FLAG_DIR_INHERIT 0x00000002 #define ACE4_FLAG_NO_PROPAGATE 0x00000004 #define ACE4_FLAG_INHERIT_ONLY 0x00000008 #define ACE4_FLAG_SUCCESSFUL 0x00000010 #define ACE4_FLAG_FAILED 0x00000020 #define ACE4_FLAG_GROUP_ID 0x00000040 #define ACE4_FLAG_INHERITED 0x00000080 /* GPFS-defined flags. Placed in a separate ACL field to avoid ever running into newly defined NFSv4 flags. */ #define ACE4_IFLAG_SPECIAL_ID 0x80000000 /* Values for gpfs_aceMask_t (ACL_VERSION_NFS4) */ #define ACE4_MASK_READ 0x00000001 #define ACE4_MASK_LIST_DIR 0x00000001 #define ACE4_MASK_WRITE 0x00000002 #define ACE4_MASK_ADD_FILE 0x00000002 #define ACE4_MASK_APPEND 0x00000004 #define ACE4_MASK_ADD_SUBDIR 0x00000004 #define ACE4_MASK_READ_NAMED 0x00000008 #define ACE4_MASK_WRITE_NAMED 0x00000010 #define ACE4_MASK_EXECUTE 0x00000020 /* The rfc doesn't provide a mask equivalent to "search" ("x" on a * directory in posix), but it also doesn't say that its EXECUTE * is to have this dual use (even though it does so for other dual * use permissions such as read/list. Going to make the assumption * here that the EXECUTE bit has this dual meaning... otherwise * we're left with no control over search. */ #define ACE4_MASK_SEARCH 0x00000020 #define ACE4_MASK_DELETE_CHILD 0x00000040 #define ACE4_MASK_READ_ATTR 0x00000080 #define ACE4_MASK_WRITE_ATTR 0x00000100 #define ACE4_MASK_DELETE 0x00010000 #define ACE4_MASK_READ_ACL 0x00020000 #define ACE4_MASK_WRITE_ACL 0x00040000 #define ACE4_MASK_WRITE_OWNER 0x00080000 #define ACE4_MASK_SYNCHRONIZE 0x00100000 #define ACE4_MASK_ALL 0x001f01ff /* Values for gpfs_uid_t (ACL_VERSION_NFS4) */ #define ACE4_SPECIAL_OWNER 1 #define ACE4_SPECIAL_GROUP 2 #define ACE4_SPECIAL_EVERYONE 3 /* per-ACL flags imported from a Windows security descriptor object */ #define ACL4_FLAG_OWNER_DEFAULTED 0x00000100 #define ACL4_FLAG_GROUP_DEFAULTED 0x00000200 #define ACL4_FLAG_DACL_PRESENT 0x00000400 #define ACL4_FLAG_DACL_DEFAULTED 0x00000800 #define ACL4_FLAG_SACL_PRESENT 0x00001000 #define ACL4_FLAG_SACL_DEFAULTED 0x00002000 #define ACL4_FLAG_DACL_UNTRUSTED 0x00004000 #define ACL4_FLAG_SERVER_SECURITY 0x00008000 #define ACL4_FLAG_DACL_AUTO_INHERIT_REQ 0x00010000 #define ACL4_FLAG_SACL_AUTO_INHERIT_REQ 0x00020000 #define ACL4_FLAG_DACL_AUTO_INHERITED 0x00040000 #define ACL4_FLAG_SACL_AUTO_INHERITED 0x00080000 #define ACL4_FLAG_DACL_PROTECTED 0x00100000 #define ACL4_FLAG_SACL_PROTECTED 0x00200000 #define ACL4_FLAG_RM_CONTROL_VALID 0x00400000 #define ACL4_FLAG_NULL_DACL 0x00800000 #define ACL4_FLAG_NULL_SACL 0x01000000 #define ACL4_FLAG_VALID_FLAGS 0x01ffff00 /* Externalized ACL defintions */ typedef unsigned int gpfs_aclType_t; typedef unsigned int gpfs_aclLen_t; typedef unsigned int gpfs_aclLevel_t; typedef unsigned int gpfs_aclVersion_t; typedef unsigned int gpfs_aclCount_t; typedef unsigned int gpfs_aclFlag_t; typedef unsigned int gpfs_aceType_t; typedef unsigned int gpfs_aceFlags_t; typedef unsigned int gpfs_acePerm_t; typedef unsigned int gpfs_aceMask_t; /* A POSIX ACL Entry */ typedef struct gpfs_ace_v1 { gpfs_aceType_t ace_type; /* POSIX ACE type */ gpfs_uid_t ace_who; /* uid/gid */ gpfs_acePerm_t ace_perm; /* POSIX permissions */ } gpfs_ace_v1_t; /* An NFSv4 ACL Entry */ typedef struct gpfs_ace_v4 { gpfs_aceType_t aceType; /* Allow or Deny */ gpfs_aceFlags_t aceFlags; /* Inherit specifications, etc. */ gpfs_aceFlags_t aceIFlags; /* GPFS Internal flags */ gpfs_aceMask_t aceMask; /* NFSv4 mask specification */ gpfs_uid_t aceWho; /* User/Group identification */ } gpfs_ace_v4_t; /* when GPFS_ACL_VERSION_NFS4, and GPFS_ACL_LEVEL_V4FLAGS */ typedef struct v4Level1_ext /* ACL extension */ { gpfs_aclFlag_t acl_flags; /* per-ACL flags */ gpfs_ace_v4_t ace_v4[1]; } v4Level1_t; /* The GPFS ACL */ typedef struct gpfs_acl { gpfs_aclLen_t acl_len; /* Total length of this ACL in bytes */ gpfs_aclLevel_t acl_level; /* Reserved (must be zero) */ gpfs_aclVersion_t acl_version; /* POSIX or NFS4 ACL */ gpfs_aclType_t acl_type; /* Access, Default, or NFS4 */ gpfs_aclCount_t acl_nace; /* Number of Entries that follow */ union { gpfs_ace_v1_t ace_v1[1]; /* when GPFS_ACL_VERSION_POSIX */ gpfs_ace_v4_t ace_v4[1]; /* when GPFS_ACL_VERSION_NFS4 */ v4Level1_t v4Level1; /* when GPFS_ACL_LEVEL_V4FLAGS */ }; } gpfs_acl_t; /* NAME: gpfs_getacl() * * FUNCTION: Retrieves the ACL information for a file. * * The aclP parameter must point to a buffer mapped by either: * - gpfs_opaque_acl_t (when flags are zero). In this case, * the opaque data that is intended to be used by a backup * program (restoreed by passing this data back on a subsequent * call to gpfs_putacl). * - gpfs_acl_t (when GPFS_GETACL_STRUCT is specified). In this * case, the data can be interpreted by the calling application * (and may be modified and applied to the file by passing it * to gpfs_putacl...along with the GPFS_PUTACL_STRUCT flag). * * On input, the first four bytes of the buffer must contain its * total size. * * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS function not available * ENOSPC buffer too small to return the entire ACL. * Needed size is returned in the first four * bytes of the buffer pointed to by aclP. * EINVAL Invalid arguments * ENOTDIR Not on directory * ENOMEM Out of memory */ int GPFS_API gpfs_getacl(const char *pathname, int flags, void *acl); /* NAME: gpfs_putacl() * * FUNCTION: Sets the ACL information for a file. * The buffer passed in should contain the ACL data * that was obtained by a previous call to gpfs_getacl. * * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS function not available * EINVAL Invalid arguments * ENOTDIR Not on directory * ENOMEM Out of memory * EPERM Caller does not hold appropriate privilege */ int GPFS_API gpfs_putacl(const char *pathname, int flags, void *acl); /* NAME: gpfs_prealloc() * * FUNCTION: Preallocate disk storage for a file or directory, starting * at the specified startOffset and covering at least the number * of bytes requested by bytesToPrealloc. Allocations are rounded * to block boundaries (block size can be found in st_blksize * returned by fstat()), or possibly larger sizes. For files, the * file descriptor must be open for write, but any existing data * already present will not be modified. Reading the preallocated * blocks will return zeros. For directories, the file descriptor * may be open for read but the caller must have write permission, * and existing entries are unaffected; startOffset must be zero. * * This function implements the behavior of mmchattr when invoked * with --compact[=minimumEntries]. The minimumEntries value * specifies both the lower bound on automatic compaction and the * desired size for pre-allocation. It defaults to zero, meaning * no pre-allocation and compact the directory as much as * possible. The mapping between minimumEntries and the * bytesToPrealloc is given by GPFS_PREALLOC_DIR_SLOT_SIZE, see * below. * * Directory compaction (zero bytesToPrealloc) requires a file * system supporting V2 directories (format version 1400, v4.1). * Directories created before upgrading the file system to version * 4.1, are upgraded from V1 to V2 by this operation even if no * other change is made. Since v4.2.2, bytesToPrealloc may be * nonzero effecting pre-allocation by setting a minimum * compaction size. Prior to v4.2.2 the minimum size of any * directory is zero. * * Returns: 0 Success * -1 Failure * * Errno: ENOSYS No prealloc service available * EBADF Bad file descriptor * EINVAL Not a GPFS file * EINVAL Not a regular file or directory * EINVAL Directory pre-allocation not supported * EINVAL startOffset or bytesToPrealloc < 0 * EACCES File not opened for writing * EACCES Caller does not have write access to directory. * EDQUOT Quota exceeded * ENOSPC Not enough space on disk * EPERM File is in a snapshot */ int GPFS_API gpfs_prealloc(gpfs_file_t fileDesc, gpfs_off64_t startOffset, gpfs_off64_t bytesToPrealloc); /* Directory entries are nominally (assuming compact names of 19 bytes or less) 32 bytes in size. This conversion factor is used in mapping between a number of entries (for mmchattr) and a size when calling gpfs_prealloc. */ #define GPFS_PREALLOC_DIR_SLOT_SIZE 32 /* for size => bytes per entry */ typedef struct gpfs_winattr { gpfs_timestruc_t creationTime; unsigned int winAttrs; /* values as defined below */ } gpfs_winattr_t; /* winAttrs values */ #define GPFS_WINATTR_ARCHIVE 0x0001 #define GPFS_WINATTR_COMPRESSED 0x0002 #define GPFS_WINATTR_DEVICE 0x0004 #define GPFS_WINATTR_DIRECTORY 0x0008 #define GPFS_WINATTR_ENCRYPTED 0x0010 #define GPFS_WINATTR_HIDDEN 0x0020 #define GPFS_WINATTR_NORMAL 0x0040 #define GPFS_WINATTR_NOT_CONTENT_INDEXED 0x0080 #define GPFS_WINATTR_OFFLINE 0x0100 #define GPFS_WINATTR_READONLY 0x0200 #define GPFS_WINATTR_REPARSE_POINT 0x0400 #define GPFS_WINATTR_SPARSE_FILE 0x0800 #define GPFS_WINATTR_SYSTEM 0x1000 #define GPFS_WINATTR_TEMPORARY 0x2000 #define GPFS_WINATTR_HAS_STREAMS 0x4000 /* NAME: gpfs_get_winattrs() * gpfs_get_winattrs_path() * * FUNCTION: Returns gpfs_winattr_t attributes * * Returns: 0 Success * -1 Failure * * Errno: ENOENT file not found * EBADF Bad file handle, not a GPFS file * ENOMEM Memory allocation failed * EACCESS Permission denied * EFAULT Bad address provided * EINVAL Not a regular file * ENOSYS function not available */ int GPFS_API gpfs_get_winattrs(gpfs_file_t fileDesc, gpfs_winattr_t *attrP); int GPFS_API gpfs_get_winattrs_path(const char *pathname, gpfs_winattr_t *attrP); /* NAME: gpfs_set_winattrs() * gpfs_set_winattrs_path() * * FUNCTION: Sets gpfs_winattr_t attributes (as specified by * the flags). * * Returns: 0 Success * -1 Failure * * Errno: ENOENT file not found * EBADF Bad file handle, not a GPFS file * ENOMEM Memory allocation failed * EACCESS Permission denied * EFAULT Bad address provided * EINVAL Not a regular file * ENOSYS function not available */ int GPFS_API gpfs_set_winattrs(gpfs_file_t fileDesc, int flags, gpfs_winattr_t *attrP); int GPFS_API gpfs_set_winattrs_path(const char *pathname, int flags, gpfs_winattr_t *attrP); /* gpfs_set_winattr flag values */ #define GPFS_WINATTR_SET_CREATION_TIME 0x08 #define GPFS_WINATTR_SET_ATTRS 0x10 /* * NAME: gpfs_set_times(), gpfs_set_times_path() * * FUNCTION: Sets file access time, modified time, change time, * and/or creation time (as specified by the flags). * * Input: flagsfileDesc : file descriptor of the object to set * pathname : path to a file or directory * flag : define time value to set * GPFS_SET_ATIME - set access time * GPFS_SET_MTIME - set mod. time * GPFS_SET_CTIME - set change time * GPFS_SET_CREATION_TIME - set creation time * GPFS_SET_TIME_NO_FOLLOW - don't follow links * times : array to times * * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS function not available * EBADF Not a GPFS File * EINVAL invalid argument * EACCES Permission denied * EROFS Filesystem is read only * ENOENT No such file or directory */ typedef gpfs_timestruc_t gpfs_times_vector_t[4]; int GPFS_API gpfs_set_times(gpfs_file_t fileDesc, int flags, gpfs_times_vector_t times); int GPFS_API gpfs_set_times_path(char *pathname, int flags, gpfs_times_vector_t times); /* gpfs_set_times flag values */ #define GPFS_SET_ATIME 0x01 #define GPFS_SET_MTIME 0x02 #define GPFS_SET_CTIME 0x04 #define GPFS_SET_CREATION_TIME 0x08 #define GPFS_SET_TIME_NO_FOLLOW 0x10 /* NAME: gpfs_set_share() * * FUNCTION: Acquire shares * * Input: fileDesc : file descriptor * allow : share type being requested * GPFS_SHARE_NONE, GPFS_SHARE_READ, * GPFS_SHARE_WRITE, GPFS_SHARE_BOTH * deny : share type to deny to others * GPFS_DENY_NONE, GPFS_DENY_READ, * GPFS_DENY_WRITE, GPFS_DENY_BOTH * * Returns: 0 Success * -1 Failure * * Errno: EBADF Bad file handle * EINVAL Bad argument given * EFAULT Bad address provided * ENOMEM Memory allocation failed * EACCES share mode not available * ENOSYS function not available */ /* allow/deny specifications */ #define GPFS_SHARE_NONE 0 #define GPFS_SHARE_READ 1 #define GPFS_SHARE_WRITE 2 #define GPFS_SHARE_BOTH 3 #define GPFS_SHARE_ALL 3 #define GPFS_DENY_NONE 0 #define GPFS_DENY_READ 1 #define GPFS_DENY_WRITE 2 #define GPFS_DENY_BOTH 3 #define GPFS_DENY_DELETE 4 #define GPFS_DENY_ALL 7 int GPFS_API gpfs_set_share(gpfs_file_t fileDesc, unsigned int share, unsigned int deny); /* NAME: gpfs_set_lease() * * FUNCTION: Acquire leases for Samba * * Input: fileDesc : file descriptor * leaseType : lease type being requested * GPFS_LEASE_NONE GPFS_LEASE_READ, * GPFS_LEASE_WRITE * * Returns: 0 Success * -1 Failure * * Errno: EBADF Bad file handle * EINVAL Bad argument given * EFAULT Bad address provided * ENOMEM Memory allocation failed * EAGAIN lease not available * EACCES permission denied * EOPNOTSUPP unsupported leaseType * ESTALE unmounted file system * ENOSYS function not available */ /* leaseType specifications */ #define GPFS_LEASE_NONE 0 #define GPFS_LEASE_READ 1 #define GPFS_LEASE_WRITE 2 int GPFS_API gpfs_set_lease(gpfs_file_t fileDesc, unsigned int leaseType); /* NAME: gpfs_get_lease() * * FUNCTION: Returns the type of lease currently held * * Returns: GPFS_LEASE_READ * GPFS_LEASE_WRITE * GPFS_LEASE_NONE * * Returns: >= 0 Success * -1 Failure * * Errno: EINVAL */ int GPFS_API gpfs_get_lease(gpfs_file_t fileDesc); /* NAME: gpfs_get_realfilename(), gpfs_get_realfilename_path() * * FUNCTION: Interface to get real name of a file. * * INPUT: File descriptor, pathname, buffer, bufferlength * OUTPUT: Real file name stored in file system * * Returns: 0 Success * -1 Failure * * Errno: EBADF Bad file handle * EINVAL Not a regular file * EFAULT Bad address provided * ENOSPC buffer too small to return the real file name. * Needed size is returned in buflen parameter. * ENOENT File does not exist * ENOMEM Memory allocation failed * EACCESS Permission denied * ENOSYS function not available */ int GPFS_API gpfs_get_realfilename(gpfs_file_t fileDesc, char *fileNameP, int *buflen); int GPFS_API gpfs_get_realfilename_path(const char *pathname, char *fileNameP, int *buflen); /* NAME: gpfs_ftruncate() * * FUNCTION: Interface to truncate a file. * * INPUT: File descriptor * length * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS function not available * EBADF Bad file handle * EBADF Not a GPFS file * EINVAL Not a regular file * ENOENT File does not exist * ENOMEM Memory allocation failed * EINVAL length < 0 * EACCESS Permission denied */ int GPFS_API gpfs_ftruncate(gpfs_file_t fileDesc, gpfs_off64_t length); #define GPFS_WIN_CIFS_REGISTERED 0x02000000 typedef struct cifsThreadData_t { unsigned int dataLength; /* Total buffer length */ unsigned int share; /* gpfs_set_share declaration */ unsigned int deny; /* gpfs_set_share specification */ unsigned int lease; /* gpfs_set_lease lease type */ unsigned int secInfoFlags; /* Future use. Must be zero */ gpfs_uid_t sdUID; /* Owning user */ gpfs_uid_t sdGID; /* Owning group */ int shareLocked_fd; /* file descriptor with share locks */ unsigned int aclLength ; /* Length of the following ACL */ gpfs_acl_t acl; /* The initial ACL for create/mkdir */ } cifsThreadData_t; /* NAME: gpfs_register_cifs_export() * * FUNCTION: Register a CIFS export process. * * INPUT: implicit use of the process ids * * Returns: 0 Successful * ENOSYS function not available * EACCES cannot establish credentials * ENOMEM temporary shortage of memory * EINVAL prior process/thread registrations exist * EBADF unable to allocate a file descriptor */ int GPFS_API gpfs_register_cifs_export(void); /* NAME: gpfs_unregister_cifs_export() * * FUNCTION: remove a registration for a CIFS export * * INPUT: implicit use of the process ids * * Returns: 0 Successful * ENOSYS function not available * EACCES cannot establish credentials * ENOMEM temporary shortage of memory */ int GPFS_API gpfs_unregister_cifs_export(void); /* NAME: gpfs_register_cifs_buffer() * * FUNCTION: Register a CIFS thread/buffer combination * * INPUT: implicit use of the process and thread ids * Address of a cifsThreadData_t structure that will include * a GPFS ACL (GPFS_ACL_VERSION_NFS4/GPFS_ACL_LEVEL_V4FLAGS) * that can be applied at file/dir creation. * * Returns: 0 Successful * ENOSYS function not available * EACCES cannot establish credentials * ENOMEM unable to allocate required memory * EINVAL no associated process registrion exists * bad dataLength in buffer. */ int GPFS_API gpfs_register_cifs_buffer(cifsThreadData_t *bufP); /* NAME: gpfs_unregister_cifs_buffer() * * FUNCTION: remove a CIFS thread/buffer registration * * INPUT: implicit use of the process and thread ids * * Returns: 0 Successful * ENOSYS function not available * EACCES cannot establish credentials * ENOMEM unable to allocate required memory * EINVAL no associated process registrion exists */ int GPFS_API gpfs_unregister_cifs_buffer(void); /* NAME: gpfs_lib_init() * * FUNCTION: Open GPFS main module device file * * INPUT: Flags * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS Function not available */ int GPFS_API gpfs_lib_init(int flags); /* NAME: gpfs_lib_term() * * FUNCTION: Close GPFS main module device file * * INPUT: Flags * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS Function not available */ int GPFS_API gpfs_lib_term(int flags); /* Define maximum length of the name for a GPFS named object, such as a snapshot, storage pool or fileset. The name is a null-terminated character string, which is not include in the max length */ #define GPFS_MAXNAMLEN 255 /* Define maximum length of the path to a GPFS named object such as a snapshot or fileset. If the absolute path name exceeds this limit, then use a relative path name. The path is a null-terminated character string, which is not included in the max length */ #define GPFS_MAXPATHLEN 1023 /* ASCII code for "GPFS" in the struct statfs f_type field */ #define GPFS_SUPER_MAGIC 0x47504653 /* GPFS inode attributes gpfs_uid_t - defined above gpfs_uid64_t - defined above gpfs_off64_t - defined above gpfs_mode_t may include gpfs specific values including 0x02000000 To have a gpfs_mode_t be equivalent to a mode_t mask that value out. */ typedef unsigned int gpfs_mode_t; typedef unsigned int gpfs_gid_t; typedef unsigned long long gpfs_gid64_t; typedef unsigned int gpfs_ino_t; typedef unsigned long long gpfs_ino64_t; typedef unsigned int gpfs_gen_t; typedef unsigned long long gpfs_gen64_t; typedef unsigned int gpfs_dev_t; typedef unsigned int gpfs_mask_t; typedef unsigned int gpfs_pool_t; typedef unsigned int gpfs_snapid_t; typedef unsigned long long gpfs_snapid64_t; typedef unsigned long long gpfs_fsid64_t[2]; typedef short gpfs_nlink_t; typedef long long gpfs_nlink64_t; #if defined(WIN32) || defined(_MS_SUA_) typedef struct gpfs_stat64 { gpfs_dev_t st_dev; /* id of device containing file */ gpfs_ino64_t st_ino; /* file inode number */ gpfs_mode_t st_mode; /* access mode */ gpfs_nlink64_t st_nlink; /* number of links */ unsigned int st_flags; /* flag word */ gpfs_uid64_t st_uid; /* owner uid */ gpfs_gid64_t st_gid; /* owner gid */ gpfs_dev_t st_rdev; /* device id (if special file) */ gpfs_off64_t st_size; /* file size in bytes */ gpfs_timestruc64_t st_atime; /* time of last access */ gpfs_timestruc64_t st_mtime; /* time of last data modification */ gpfs_timestruc64_t st_ctime; /* time of last status change */ int st_blksize; /* preferred block size for io */ gpfs_off64_t st_blocks; /* 512 byte blocks of disk held by file */ long long st_fsid; /* file system id */ unsigned int st_type; /* file type */ gpfs_gen64_t st_gen; /* inode generation number */ gpfs_timestruc64_t st_createtime; /* time of creation */ unsigned int st_attrs; /* Windows flags */ } gpfs_stat64_t; #else typedef struct stat64 gpfs_stat64_t; #endif #if defined(WIN32) || defined(_MS_SUA_) typedef struct gpfs_statfs64 { gpfs_off64_t f_blocks; /* total data blocks in file system */ gpfs_off64_t f_bfree; /* free block in fs */ gpfs_off64_t f_bavail; /* free blocks avail to non-superuser */ int f_bsize; /* optimal file system block size */ gpfs_ino64_t f_files; /* total file nodes in file system */ gpfs_ino64_t f_ffree; /* free file nodes in fs */ gpfs_fsid64_t f_fsid; /* file system id */ int f_fsize; /* fundamental file system block size */ int f_sector_size; /* logical disk sector size */ char f_fname[32]; /* file system name (usually mount pt.) */ char f_fpack[32]; /* file system pack name */ int f_name_max; /* maximum component name length for posix */ } gpfs_statfs64_t; #else typedef struct statfs64 gpfs_statfs64_t; #endif /* Declarations for backwards compatibility. */ typedef gpfs_stat64_t stat64_t; typedef gpfs_statfs64_t statfs64_t; /* Define a version number for the directory entry data to allow future changes in this structure. Careful callers should also use the d_reclen field for the size of the structure rather than sizeof, to allow some degree of forward compatibility */ #define GPFS_D_VERSION 1 typedef struct gpfs_direntx { int d_version; /* this struct's version */ unsigned short d_reclen; /* actual size of this struct including null terminated variable length d_name */ unsigned short d_type; /* Types are defined below */ gpfs_ino_t d_ino; /* File inode number */ gpfs_gen_t d_gen; /* Generation number for the inode */ char d_name[256]; /* null terminated variable length name */ } gpfs_direntx_t; #define GPFS_D64_VERSION 2 typedef struct gpfs_direntx64 { int d_version; /* this struct's version */ unsigned short d_reclen; /* actual size of this struct including null terminated variable length d_name */ unsigned short d_type; /* Types are defined below */ gpfs_ino64_t d_ino; /* File inode number */ gpfs_gen64_t d_gen; /* Generation number for the inode */ unsigned int d_flags; /* Flags are defined below */ char d_name[1028]; /* null terminated variable length name */ /* (1020+null+7 byte pad to double word) */ /* to handle up to 255 UTF-8 chars */ } gpfs_direntx64_t; /* File types for d_type field in gpfs_direntx_t */ #define GPFS_DE_OTHER 0 #define GPFS_DE_FIFO 1 #define GPFS_DE_CHR 2 #define GPFS_DE_DIR 4 #define GPFS_DE_BLK 6 #define GPFS_DE_REG 8 #define GPFS_DE_LNK 10 #define GPFS_DE_SOCK 12 #define GPFS_DE_DEL 16 /* Define flags for gpfs_direntx64_t */ #define GPFS_DEFLAG_NONE 0x0000 /* Default value, no flags set */ #define GPFS_DEFLAG_JUNCTION 0x0001 /* DirEnt is a fileset junction */ #define GPFS_DEFLAG_IJUNCTION 0x0002 /* DirEnt is a inode space junction */ #define GPFS_DEFLAG_ORPHAN 0x0004 /* DirEnt is an orphan (pcache) */ #define GPFS_DEFLAG_CLONE 0x0008 /* DirEnt is a clone child */ /* Define a version number for the iattr data to allow future changes in this structure. Careful callers should also use the ia_reclen field for the size of the structure rather than sizeof, to allow some degree of forward compatibility */ #define GPFS_IA_VERSION 1 #define GPFS_IA64_VERSION 3 /* ver 3 adds ia_repl_xxxx bytes instead of ia_pad2 */ #define GPFS_IA64_RESERVED 4 #define GPFS_IA64_UNUSED 8 typedef struct gpfs_iattr { int ia_version; /* this struct version */ int ia_reclen; /* sizeof this structure */ int ia_checksum; /* validity check on iattr struct */ gpfs_mode_t ia_mode; /* access mode; see gpfs_mode_t comment */ gpfs_uid_t ia_uid; /* owner uid */ gpfs_gid_t ia_gid; /* owner gid */ gpfs_ino_t ia_inode; /* file inode number */ gpfs_gen_t ia_gen; /* inode generation number */ gpfs_nlink_t ia_nlink; /* number of links */ short ia_flags; /* Flags (defined below) */ int ia_blocksize; /* preferred block size for io */ gpfs_mask_t ia_mask; /* Initial attribute mask (not used) */ unsigned int ia_pad1; /* reserved space */ gpfs_off64_t ia_size; /* file size in bytes */ gpfs_off64_t ia_blocks; /* 512 byte blocks of disk held by file */ gpfs_timestruc_t ia_atime; /* time of last access */ gpfs_timestruc_t ia_mtime; /* time of last data modification */ gpfs_timestruc_t ia_ctime; /* time of last status change */ gpfs_dev_t ia_rdev; /* id of device */ unsigned int ia_xperm; /* extended attributes (defined below) */ unsigned int ia_modsnapid; /* snapshot id of last modification */ unsigned int ia_filesetid; /* fileset ID */ unsigned int ia_datapoolid; /* storage pool ID for data */ unsigned int ia_pad2; /* reserved space */ } gpfs_iattr_t; typedef struct gpfs_iattr64 { int ia_version; /* this struct version */ int ia_reclen; /* sizeof this structure */ int ia_checksum; /* validity check on iattr struct */ gpfs_mode_t ia_mode; /* access mode; see gpfs_mode_t comment */ gpfs_uid64_t ia_uid; /* owner uid */ gpfs_gid64_t ia_gid; /* owner gid */ gpfs_ino64_t ia_inode; /* file inode number */ gpfs_gen64_t ia_gen; /* inode generation number */ gpfs_nlink64_t ia_nlink; /* number of links */ gpfs_off64_t ia_size; /* file size in bytes */ gpfs_off64_t ia_blocks; /* 512 byte blocks of disk held by file */ gpfs_timestruc64_t ia_atime; /* time of last access */ unsigned int ia_winflags; /* windows flags (defined below) */ unsigned int ia_pad1; /* reserved space */ gpfs_timestruc64_t ia_mtime; /* time of last data modification */ unsigned int ia_flags; /* flags (defined below) */ /* next four bytes were ia_pad2 */ unsigned char ia_repl_data; /* data replication factor */ unsigned char ia_repl_data_max; /* data replication max factor */ unsigned char ia_repl_meta; /* meta data replication factor */ unsigned char ia_repl_meta_max; /* meta data replication max factor */ gpfs_timestruc64_t ia_ctime; /* time of last status change */ int ia_blocksize; /* preferred block size for io */ unsigned int ia_pad3; /* reserved space */ gpfs_timestruc64_t ia_createtime; /* creation time */ gpfs_mask_t ia_mask; /* initial attribute mask (not used) */ int ia_pad4; /* reserved space */ unsigned int ia_reserved[GPFS_IA64_RESERVED]; /* reserved space */ unsigned int ia_xperm; /* extended attributes (defined below) */ gpfs_dev_t ia_dev; /* id of device containing file */ gpfs_dev_t ia_rdev; /* device id (if special file) */ unsigned int ia_pcacheflags; /* pcache inode bits */ gpfs_snapid64_t ia_modsnapid; /* snapshot id of last modification */ unsigned int ia_filesetid; /* fileset ID */ unsigned int ia_datapoolid; /* storage pool ID for data */ gpfs_ino64_t ia_inode_space_mask; /* inode space mask of this file system */ /* This value is saved in the iattr structure during backup and used during restore */ gpfs_off64_t ia_dirminsize; /* dir pre-allocation size in bytes */ unsigned int ia_unused[GPFS_IA64_UNUSED]; /* reserved space */ } gpfs_iattr64_t; /* Define flags for inode attributes */ #define GPFS_IAFLAG_SNAPDIR 0x0001 /* (obsolete) */ #define GPFS_IAFLAG_USRQUOTA 0x0002 /* inode is a user quota file */ #define GPFS_IAFLAG_GRPQUOTA 0x0004 /* inode is a group quota file */ #define GPFS_IAFLAG_ERROR 0x0008 /* error reading inode */ /* Define flags for inode replication attributes */ #define GPFS_IAFLAG_FILESET_ROOT 0x0010 /* root dir of a fileset */ #define GPFS_IAFLAG_NO_SNAP_RESTORE 0x0020 /* don't restore from snapshots */ #define GPFS_IAFLAG_FILESETQUOTA 0x0040 /* inode is a fileset quota file */ #define GPFS_IAFLAG_COMANAGED 0x0080 /* file data is co-managed */ #define GPFS_IAFLAG_ILLPLACED 0x0100 /* may not be properly placed */ #define GPFS_IAFLAG_REPLMETA 0x0200 /* metadata replication set */ #define GPFS_IAFLAG_REPLDATA 0x0400 /* data replication set */ #define GPFS_IAFLAG_EXPOSED 0x0800 /* may have data on suspended disks */ #define GPFS_IAFLAG_ILLREPLICATED 0x1000 /* may not be properly replicated */ #define GPFS_IAFLAG_UNBALANCED 0x2000 /* may not be properly balanced */ #define GPFS_IAFLAG_DATAUPDATEMISS 0x4000 /* has stale data blocks on unavailable disk */ #define GPFS_IAFLAG_METAUPDATEMISS 0x8000 /* has stale metadata on unavailable disk */ #define GPFS_IAFLAG_IMMUTABLE 0x00010000 /* Immutability */ #define GPFS_IAFLAG_INDEFRETENT 0x00020000 /* Indefinite retention */ #define GPFS_IAFLAG_SECUREDELETE 0x00040000 /* Secure deletion */ #define GPFS_IAFLAG_TRUNCMANAGED 0x00080000 /* dmapi truncate event enabled */ #define GPFS_IAFLAG_READMANAGED 0x00100000 /* dmapi read event enabled */ #define GPFS_IAFLAG_WRITEMANAGED 0x00200000 /* dmapi write event enabled */ #define GPFS_IAFLAG_APPENDONLY 0x00400000 /* AppendOnly only */ #define GPFS_IAFLAG_DELETED 0x00800000 /* inode has been deleted */ #ifdef ZIP #define GPFS_IAFLAG_ILLCOMPRESSED 0x01000000 /* may not be properly compressed */ #endif #define GPFS_IAFLAG_FPOILLPLACED 0x02000000 /* may not be properly placed per FPO attributes (bgf, wad, wadfg) */ /* Define flags for window's attributes */ #define GPFS_IWINFLAG_ARCHIVE 0x0001 /* Archive */ #define GPFS_IWINFLAG_HIDDEN 0x0002 /* Hidden */ #define GPFS_IWINFLAG_NOTINDEXED 0x0004 /* Not content indexed */ #define GPFS_IWINFLAG_OFFLINE 0x0008 /* Off-line */ #define GPFS_IWINFLAG_READONLY 0x0010 /* Read-only */ #define GPFS_IWINFLAG_REPARSE 0x0020 /* Reparse point */ #define GPFS_IWINFLAG_SYSTEM 0x0040 /* System */ #define GPFS_IWINFLAG_TEMPORARY 0x0080 /* Temporary */ #define GPFS_IWINFLAG_COMPRESSED 0x0100 /* Compressed */ #define GPFS_IWINFLAG_ENCRYPTED 0x0200 /* Encrypted */ #define GPFS_IWINFLAG_SPARSE 0x0400 /* Sparse file */ #define GPFS_IWINFLAG_HASSTREAMS 0x0800 /* Has streams */ /* Define flags for extended attributes */ #define GPFS_IAXPERM_ACL 0x0001 /* file has acls */ #define GPFS_IAXPERM_XATTR 0x0002 /* file has extended attributes */ #define GPFS_IAXPERM_DMATTR 0x0004 /* file has dm attributes */ #define GPFS_IAXPERM_DOSATTR 0x0008 /* file has non-default dos attrs */ #define GPFS_IAXPERM_RPATTR 0x0010 /* file has restore policy attrs */ /* Define flags for pcache bits defined in the inode */ #define GPFS_ICAFLAG_CACHED 0x0001 /* "cached complete" */ #define GPFS_ICAFLAG_CREATE 0x0002 /* "created" */ #define GPFS_ICAFLAG_DIRTY 0x0004 /* "data dirty" */ #define GPFS_ICAFLAG_LINK 0x0008 /* "hard linked" */ #define GPFS_ICAFLAG_SETATTR 0x0010 /* "attr changed" */ #define GPFS_ICAFLAG_LOCAL 0x0020 /* "local" */ #define GPFS_ICAFLAG_APPEND 0x0040 /* "append" */ #define GPFS_ICAFLAG_STATE 0x0080 /* "has remote state" */ /* Define pointers to interface types */ typedef struct gpfs_fssnap_handle gpfs_fssnap_handle_t; typedef struct gpfs_iscan gpfs_iscan_t; typedef struct gpfs_ifile gpfs_ifile_t; typedef struct gpfs_restore gpfs_restore_t; typedef struct gpfs_fssnap_id { char opaque[48]; } gpfs_fssnap_id_t; /* Define extended return codes for gpfs backup & restore calls without an explicit return code will return the value in errno */ #define GPFS_NEW_ERRNO_BASE 185 #define GPFS_E_INVAL_INUM (GPFS_NEW_ERRNO_BASE+0) /* invalid inode number */ #define GPFS_ERRNO_BASE 190 #define GPFS_E_INVAL_FSSNAPID (GPFS_ERRNO_BASE+0) /* invalid fssnap id */ #define GPFS_E_INVAL_ISCAN (GPFS_ERRNO_BASE+1) /* invalid iscan pointer */ #define GPFS_E_INVAL_IFILE (GPFS_ERRNO_BASE+2) /* invalid ifile pointer */ #define GPFS_E_INVAL_IATTR (GPFS_ERRNO_BASE+3) /* invalid iattr structure */ #define GPFS_E_INVAL_RESTORE (GPFS_ERRNO_BASE+4) /* invalid restore pointer */ #define GPFS_E_INVAL_FSSNAPHANDLE (GPFS_ERRNO_BASE+5) /* invalid fssnap handle */ #define GPFS_E_INVAL_SNAPNAME (GPFS_ERRNO_BASE+6) /* invalid snapshot name */ #define GPFS_E_FS_NOT_RESTORABLE (GPFS_ERRNO_BASE+7) /* FS is not clean */ #define GPFS_E_RESTORE_NOT_ENABLED (GPFS_ERRNO_BASE+8) /* Restore was not enabled */ #define GPFS_E_RESTORE_STARTED (GPFS_ERRNO_BASE+9) /* Restore is running */ #define GPFS_E_INVAL_XATTR (GPFS_ERRNO_BASE+10) /* invalid extended attribute pointer */ /* Define flags parameter for get/put file attributes. Used by gpfs_fgetattr, gpfs_fputattr, gpfs_fputattrwithpath gpfs_igetattrsx, gpfs_iputattrsx and gpfs_lwe_getattrs, gpfs_lwe_putattrs */ #define GPFS_ATTRFLAG_DEFAULT 0x0000 /* default behavior */ #define GPFS_ATTRFLAG_NO_PLACEMENT 0x0001 /* exclude file placement attributes */ #define GPFS_ATTRFLAG_IGNORE_POOL 0x0002 /* saved poolid is not valid */ #define GPFS_ATTRFLAG_USE_POLICY 0x0004 /* use restore policy rules to determine poolid */ #define GPFS_ATTRFLAG_INCL_DMAPI 0x0008 /* Include dmapi attributes */ #define GPFS_ATTRFLAG_FINALIZE_ATTRS 0x0010 /* Finalize immutability attributes */ #define GPFS_ATTRFLAG_SKIP_IMMUTABLE 0x0020 /* Skip immutable attributes */ #define GPFS_ATTRFLAG_INCL_ENCR 0x0040 /* Include encryption attributes */ #define GPFS_ATTRFLAG_SKIP_CLONE 0x0080 /* Skip clone attributes */ #define GPFS_ATTRFLAG_MODIFY_CLONEPARENT 0x0100 /* Allow modification on clone parent */ #ifdef ZIP #define GPFS_ATTRFLAG_NO_COMPRESSED 0x0200 /* exclude "compressed" attribute */ #endif /* Define structure used by gpfs_statfspool */ typedef struct gpfs_statfspool_s { gpfs_off64_t f_blocks; /* total data blocks in pool */ gpfs_off64_t f_bfree; /* free blocks in pool */ gpfs_off64_t f_bavail; /* free blocks avail to non-superuser */ gpfs_off64_t f_mblocks; /* total metadata blocks in pool */ gpfs_off64_t f_mfree; /* free blocks avail for system metadata */ int f_bsize; /* optimal storage pool block size */ int f_files; /* total file nodes assigned to pool */ gpfs_pool_t f_poolid; /* storage pool id */ int f_fsize; /* fundamental file system block size */ unsigned int f_usage; /* data and/or metadata stored in pool */ int f_replica; /* replica */ int f_bgf; /* block group factor */ int f_wad; /* write affinity depth */ int f_allowWriteAffinity; /* allow write affinity depth. 1 means yes */ int f_reserved[3];/* Current unused and set to zero */ } gpfs_statfspool_t; #define STATFSPOOL_USAGE_DATA 0x0001 /* Pool stores user data */ #define STATFSPOOL_USAGE_METADATA 0x0002 /* Pool stores system metadata */ /* NAME: gpfs_fstat(), gpfs_stat() * * FUNCTION: Get exact stat information for a file descriptor (or filename). * Forces all other nodes to flush dirty data and metadata to disk. * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS function not available * EBADF Bad file desc * EINVAL Not a GPFS file * ESTALE cached fs information was invalid */ int GPFS_API gpfs_fstat(gpfs_file_t fileDesc, gpfs_stat64_t *buffer); int GPFS_API gpfs_stat(const char *pathname, /* File pathname */ gpfs_stat64_t *buffer); /* NAME: gpfs_fstat_x(), gpfs_stat_x() * * FUNCTION: Returns extended stat() information with specified accuracy * for a file descriptor (or filename) * * Input: fileDesc : file descriptor or handle * pathname : path to a file or directory * iattrBufLen : length of iattr buffer * * In/Out: st_litemaskP: bitmask specification of required accuracy * iattr : buffer for returned stat information * * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS function not available * ENOENT invalid pathname * EBADF Bad file desc * EINVAL Not a GPFS file * ESTALE cached fs information was invalid */ int GPFS_API gpfs_fstat_x(gpfs_file_t fileDesc, unsigned int *st_litemaskP, gpfs_iattr64_t *iattr, size_t iattrBufLen); int GPFS_API gpfs_stat_x(const char *pathname, /* File pathname */ unsigned int *st_litemaskP, gpfs_iattr64_t *iattr, size_t iattrBufLen); /* NAME: gpfs_statfs64() * * FUNCTION: Get information about the file system. * * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS function not available * EBADF Bad file desc * EINVAL Not a GPFS file * ESTALE cached fs information was invalid */ int GPFS_API gpfs_statfs64(const char *pathname, /* File pathname */ gpfs_statfs64_t *buffer); /* NAME: gpfs_statlite() * gpfs_lstatlite() - do not follow a symlink at the end of the path * * FUNCTION: Returns stat() information with specified accuracy * * Input: pathname : path to a file or directory * * In/Out: st_litemaskP: bitmask specification of required accuracy * statbufP : buffer for returned stat information * * Returns: 0 Successful * -1 Failure * * Errno: Specific error indication * EINVAL * */ int GPFS_API gpfs_statlite(const char *pathname, unsigned int *st_litemaskP, gpfs_stat64_t *statbufP); int GPFS_API gpfs_lstatlite(const char *pathname, unsigned int *st_litemaskP, gpfs_stat64_t *statbufP); /* NAME: gpfs_fgetattrs() * * FUNCTION: Retrieves all extended file attributes in opaque format. * This function together with gpfs_fputattrs is intended for * use by a backup program to save (gpfs_fgetattrs) and * restore (gpfs_fputattrs) all extended file attributes * (ACLs, user attributes, ...) in one call. * * NOTE: This call does not return extended attributes used for * the Data Storage Management (XDSM) API (aka DMAPI). * * Input: flags Define behavior of get attributes * GPFS_ATTRFLAG_NO_PLACEMENT - file attributes for placement * are not saved, neither is the current storage pool. * GPFS_ATTRFLAG_IGNORE_POOL - file attributes for placement * are saved, but the current storage pool is not. * * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS function not available * EINVAL Not a GPFS file * EINVAL invalid flags provided * ENOSPC buffer too small to return all attributes * *attrSizeP will be set to the size necessary */ int GPFS_API gpfs_fgetattrs(gpfs_file_t fileDesc, int flags, void *bufferP, int bufferSize, int *attrSizeP); /* NAME: gpfs_fputattrs() * * FUNCTION: Sets all extended file attributes of a file * and sets the file's storage pool and data replication * to the values saved in the extended attributes. * * If the saved storage pool is not valid or if the IGNORE_POOL * flag is set, then it will select the storage pool by matching * a PLACEMENT rule using the saved file attributes. * If it fails to match a placement rule or if there are * no placement rules installed it will assign the file * to the "system" storage pool. * * The buffer passed in should contain extended attribute data * that was obtained by a previous call to gpfs_fgetattrs. * * Input: flags Define behavior of put attributes * GPFS_ATTRFLAG_NO_PLACEMENT - file attributes are restored * but the storage pool and data replication are unchanged * GPFS_ATTRFLAG_IGNORE_POOL - file attributes are restored * but the storage pool and data replication are selected * by matching the saved attributes to a placement rule * instead of restoring the saved storage pool. * * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS function not available * EINVAL Not a GPFS file * EINVAL the buffer does not contain valid attribute data * EINVAL invalid flags provided */ int GPFS_API gpfs_fputattrs(gpfs_file_t fileDesc, int flags, void *bufferP); /* NAME: gpfs_fputattrswithpathname() * * FUNCTION: Sets all extended file attributes of a file and invokes * the policy engine to match a RESTORE rule using the file's * attributes saved in the extended attributes to set the * file's storage pool and data replication. The caller should * include the full path to the file, including the file name, * to allow rule selection based on file name or path. * * If the file fails to match a RESTORE rule, or if there are * no RESTORE rules installed, then the storage pool and data * replication are selected as when calling gpfs_fputattrs(). * * The buffer passed in should contain extended attribute data * that was obtained by a previous call to gpfs_fgetattrs. * * pathName is a UTF-8 encoded string. On Windows, applications * can convert UTF-16 ("Unicode") to UTF-8 using the platforms * WideCharToMultiByte function. * * * Input: flags Define behavior of put attributes * GPFS_ATTRFLAG_NO_PLACEMENT - file attributes are restored * but the storage pool and data replication are unchanged * GPFS_ATTRFLAG_IGNORE_POOL - file attributes are restored * but if the file fails to match a RESTORE rule, it * ignore the saved storage pool and select a pool * by matching the saved attributes to a PLACEMENT rule. * GPFS_ATTRFLAG_SKIP_IMMUTABLE - Skip immutable/appendOnly flags * before restoring file data. Then use GPFS_ATTRFLAG_FINALIZE_ATTRS * to restore immutable/appendOnly flags after data is restored. * GPFS_ATTRFLAG_FINALIZE_ATTRS - file attributes that are restored * after data is retored. If file is immutable/appendOnly * call without this flag before restoring data * then call with this flag after restoring data * * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS function not available * EINVAL Not a GPFS file * EINVAL the buffer does not contain valid attribute data * ENOENT invalid pathname * EINVAL invalid flags provided */ int GPFS_API gpfs_fputattrswithpathname(gpfs_file_t fileDesc, int flags, void *bufferP, const char *pathName); /* NAME: gpfs_get_fssnaphandle_by_path() * * FUNCTION: Get a volatile handle to uniquely identify a file system * and snapshot by the path to the file system and snapshot * * Input: pathName: path to a file or directory in a gpfs file system * or to one of its snapshots * * Returns: pointer to gpfs_fssnap_handle_t (Successful) * NULL and errno is set (Failure) * * Errno: ENOSYS function not available * EINVAL Not a GPFS file * ENOENT invalid pathname * see system calls open(), fstatfs(), and malloc() ERRORS */ gpfs_fssnap_handle_t * GPFS_API gpfs_get_fssnaphandle_by_path(const char *pathName); /* NAME: gpfs_get_fssnaphandle_by_name() * * FUNCTION: Get a volatile handle to uniquely identify a file system * and snapshot by the file system name and snapshot name. * * Input: fsName: unique name for gpfs file system (may be specified * as fsName or /dev/fsName) * snapName: name for snapshot within that file system * or NULL to access the active file system rather * than a snapshot within the file system. * * Returns: pointer to gpfs_fssnap_handle_t (Successful) * NULL and errno is set (Failure) * * Errno: ENOSYS function not available * ENOENT invalid file system name * GPFS_E_INVAL_SNAPNAME invalid snapshot name * see system calls open(), fstatfs(), and malloc() ERRORS */ gpfs_fssnap_handle_t * GPFS_API gpfs_get_fssnaphandle_by_name(const char *fsName, const char *snapName); /* NAME: gpfs_get_fssnaphandle_by_fssnapid() * * FUNCTION: Get a volatile handle to uniquely identify a file system * and snapshot by a fssnapId created from a previous handle. * * Input: fssnapId: unique id for a file system and snapshot * * Returns: pointer to gpfs_fssnap_handle_t (Successful) * NULL and errno is set (Failure) * * Errno: ENOSYS function not available * GPFS_E_INVAL_FSSNAPID invalid snapshot id * see system calls open(), fstatfs(), and malloc() ERRORS */ gpfs_fssnap_handle_t * GPFS_API gpfs_get_fssnaphandle_by_fssnapid(const gpfs_fssnap_id_t *fssnapId); /* NAME: gpfs_get_fset_snaphandle_by_path() * * FUNCTION: Get a volatile handle to uniquely identify an inode space within a * filesyetsm and snapshot by the path to the file system and snapshot. * * Input: pathName: path to a file or directory in a gpfs file system * or to one of its snapshots * * Returns: pointer to gpfs_fssnap_handle_t (Successful) * NULL and errno is set (Failure) * * Errno: ENOSYS function not available * EINVAL Not a GPFS file * ENOENT invalid pathname * see system calls open(), fstatfs(), and malloc() ERRORS */ gpfs_fssnap_handle_t * GPFS_API gpfs_get_fset_snaphandle_by_path(const char *pathName); /* NAME: gpfs_get_fset_snaphandle_by_name() * * FUNCTION: Get a volatile handle to uniquely identify an inode space within a * file system and snapshot by the independent fileset name, file system * name and snapshot name. * * Input: fsName: unique name for gpfs file system (may be specified * as fsName or /dev/fsName) * fsetName name of the independent fileset that owns the inode space * snapName: name for snapshot within that file system * or NULL to access the active file system rather * than a snapshot within the file system. * * Returns: pointer to gpfs_fssnap_handle_t (Successful) * NULL and errno is set (Failure) * * Errno: ENOSYS function not available * ENOENT invalid file system name * GPFS_E_INVAL_FSETNAME invalid fset nsmae * GPFS_E_INVAL_SNAPNAME invalid snapshot name * see system calls open(), fstatfs(), and malloc() ERRORS */ gpfs_fssnap_handle_t * GPFS_API gpfs_get_fset_snaphandle_by_name(const char *fsName, const char *fsetName, const char *snapName); /* NAME: gpfs_get_fset_snaphandle_by_fset_snapid() * * FUNCTION: Get a volatile handle to uniquely identify a file system * and snapshot by a fssnapId created from a previous handle. * * Input: fssnapId: unique id for a file system and snapshot * * Returns: pointer to gpfs_fssnap_handle_t (Successful) * NULL and errno is set (Failure) * * Errno: ENOSYS function not available * GPFS_E_INVAL_FSSNAPID invalid snapshot id * see system calls open(), fstatfs(), and malloc() ERRORS */ gpfs_fssnap_handle_t * GPFS_API gpfs_get_fset_snaphandle_by_fset_snapid(const gpfs_fssnap_id_t *fsetsnapId); /* NAME: gpfs_get_pathname_from_fssnaphandle() * * FUNCTION: Get the mountpoint and path to a file system * and snapshot identified by a fssnapHandle * * Input: fssnapHandle: ptr to file system & snapshot handle * * Returns: ptr to path name to the file system (Successful) * NULL and errno is set (Failure) * * Errno: ENOSYS function not available * GPFS_E_INVAL_FSSNAPHANDLE invalid fssnapHandle */ const char * GPFS_API gpfs_get_pathname_from_fssnaphandle(gpfs_fssnap_handle_t *fssnapHandle); /* NAME: gpfs_get_fsname_from_fssnaphandle() * * FUNCTION: Get the unique name for the file system * identified by a fssnapHandle * * Input: fssnapHandle: ptr to file system & snapshot handle * * Returns: ptr to name of the file system (Successful) * NULL and errno is set (Failure) * * Errno: ENOSYS function not available * GPFS_E_INVAL_FSSNAPHANDLE invalid fssnapHandle */ const char * GPFS_API gpfs_get_fsname_from_fssnaphandle(gpfs_fssnap_handle_t *fssnapHandle); /* NAME: gpfs_get_snapname_from_fssnaphandle() * * FUNCTION: Get the name for the snapshot * uniquely identified by a fssnapHandle * * Input: fssnapHandle: ptr to file system & snapshot handle * * Returns: ptr to name assigned to the snapshot (Successful) * NULL and errno is set (Failure) * * Errno: ENOSYS function not available * GPFS_E_INVAL_FSSNAPHANDLE invalid fssnaphandle * GPFS_E_INVAL_SNAPNAME snapshot has been deleted * * Notes: If the snapshot has been deleted from the file system * the snapId may still be valid, but the call will fail * with errno set to GPFS_E_INVAL_SNAPNAME. */ const char * GPFS_API gpfs_get_snapname_from_fssnaphandle(gpfs_fssnap_handle_t *fssnapHandle); /* NAME: gpfs_get_snapid_from_fssnaphandle() * * FUNCTION: Get the numeric id for the snapshot identified * by a fssnapHandle. The snapshots define an ordered * sequence of changes to each file. The file's iattr * structure defines the snapshot id in which the file * was last modified (ia_modsnapid). This numeric value * can be compared to the numeric snapid from a fssnaphandle * to determine if the file changed before or after the * snapshot identified by the fssnaphandle. * * Input: fssnapHandle: ptr to file system & snapshot handle * * Returns: Numeric id for the snapshot referred to by the fssnaphandle * 0 if the fssnaphandle does not refer to a snapshot * -1 and errno is set (Failure) * * Errno: ENOSYS function not available * GPFS_E_INVAL_FSSNAPHANDLE invalid fssnaphandle * * Notes: The snapshot need not be on-line to determine the * snapshot's numeric id. */ gpfs_snapid_t GPFS_API gpfs_get_snapid_from_fssnaphandle(gpfs_fssnap_handle_t *fssnapHandle); gpfs_snapid64_t GPFS_API gpfs_get_snapid_from_fssnaphandle64(gpfs_fssnap_handle_t *fssnapHandle); /* NAME: gpfs_get_fssnapid_from_fssnaphandle() * * FUNCTION: Get a unique, non-volatile file system and snapshot id * for the file system and snapshot identified by a * volatile fssnap handle. * * Input: fssnapHandle: ptr to file system & snapshot handle * fssnapId: returned fssnapId uniquely identifying the * file system and snapshot being scanned * * Returns: 0 and fssnapId is set with id (Successful) * -1 and errno is set (Failure) * * Errno: GPFS_E_INVAL_FSSNAPHANDLE invalid fssnaphandle * EINVAL null ptr given for returned fssnapId * EFAULT size mismatch for fssnapId */ int GPFS_API gpfs_get_fssnapid_from_fssnaphandle(gpfs_fssnap_handle_t *fssnapHandle, gpfs_fssnap_id_t *fssnapId); /* NAME: gpfs_get_restore_fssnapid_from_fssnaphandle() * * FUNCTION: Get the unique, non-volatile file system and snapshot id * used for the last complete restore of a mirrored file * system. The file system must been a previous restore * target and ready for additional incremental restore. * * Input: fssnapHandle: ptr to file system & snapshot handle * fssnapId: returned fssnapId uniquely identifying the * last complete restored file system. * * Returns: 0 and fssnapId is set with id (Successful) * -1 and errno is set (Failure) * * Errno: GPFS_E_INVAL_FSSNAPHANDLE invalid fssnaphandle * EINVAL null ptr given for returned fssnapId * EFAULT size mismatch for fssnapId * EPERM caller must have superuser privilege * ENOMEM unable to allocate memory for request * GPFS_E_FS_NOT_RESTORABLE fs is not clean for restore */ int GPFS_API gpfs_get_restore_fssnapid_from_fssnaphandle(gpfs_fssnap_handle_t *fssnapHandle, gpfs_fssnap_id_t *fssnapId); /* NAME: gpfs_free_fssnaphandle() * * FUNCTION: Free a fssnapHandle * * Input: fssnapHandle: ptr to file system & snapshot handle * * Returns: void * * Errno: None */ void GPFS_API gpfs_free_fssnaphandle(gpfs_fssnap_handle_t *fssnapHandle); /* NAME: gpfs_get_snapdirname() * * FUNCTION: Get the name of the directory containing snapshots. * * Input: fssnapHandle: handle for the file system * snapdirName: buffer into which the name of the snapshot * directory will be copied * bufLen: the size of the provided buffer * * Returns: 0 (Successful) * -1 and errno is set (Failure) * * Errno: ENOSYS function not available * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * ENOMEM unable to allocate memory for request * GPFS_E_INVAL_FSSNAPHANDLE fssnapHandle is invalid * E2BIG buffer too small to return the snapshot directory name */ int GPFS_API gpfs_get_snapdirname(gpfs_fssnap_handle_t *fssnapHandle, char *snapdirName, int bufLen); /* NAME: gpfs_open_inodescan() * * FUNCTION: Open inode file for inode scan. * * Input: fssnapHandle: handle for file system and snapshot * to be scanned * prev_fssnapId: * if NULL, all inodes of existing file will be returned; * if non-null, only returns inodes of files that have changed * since the specified previous snapshot; * if specifies the same snapshot as the one referred by * fssnapHandle, only the snapshot inodes that have been * copied into this snap inode file are returned; * maxIno: if non-null, returns the maximum inode number * available in the inode file being scanned. * * Returns: pointer to gpfs_iscan_t (Successful) * NULL and errno is set (Failure) * * Errno: ENOSYS function not available * EINVAL bad parameters * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * ENOMEM unable to allocate memory for request * GPFS_E_INVAL_FSSNAPHANDLE fssnapHandle is invalid * GPFS_E_INVAL_FSSNAPID prev_fssnapId is invalid * EDOM prev_fssnapId is from a different fs * ERANGE prev_fssnapId is more recent than snapId * being scanned * see system calls dup() and malloc() ERRORS */ gpfs_iscan_t * GPFS_API gpfs_open_inodescan(gpfs_fssnap_handle_t *fssnapHandle, const gpfs_fssnap_id_t *prev_fssnapId, gpfs_ino_t *maxIno); gpfs_iscan_t * GPFS_API gpfs_open_inodescan64(gpfs_fssnap_handle_t *fssnapHandle, const gpfs_fssnap_id_t *prev_fssnapId, gpfs_ino64_t *maxIno); /* NAME: gpfs_open_inodescan_with_xattrs() * * FUNCTION: Open inode file and extended attributes for an inode scan * * Input: fssnapHandle: handle for file system and snapshot * to be scanned * prev_fssnapId: if NULL, all inodes of existing file will * be returned; if non-null, only returns inodes of files * that have changed since the specified previous snapshot; * if specifies the same snapshot as the one referred by * fssnapHandle, only the snapshot inodes that have been * copied into this snap inode file are returned; * nxAttrs: count of extended attributes to be returned. * if nxAttrs is set to 0, call returns no extended * attributes, like gpfs_open_inodescan. * if nxAttrs is set to -1, call returns all extended attributes * xAttrList: pointer to array of pointers to names of extended * attribute to be returned. nxAttrList may be null if nxAttrs * is set to 0 or -1. * maxIno: if non-null, returns the maximum inode number * available in the inode file being scanned. * * Returns: pointer to gpfs_iscan_t (Successful) * NULL and errno is set (Failure) * * Errno: ENOSYS function not available * EINVAL bad parameters * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * ENOMEM unable to allocate memory for request * GPFS_E_INVAL_FSSNAPHANDLE fssnapHandle is invalid * GPFS_E_INVAL_FSSNAPID prev_fssnapId is invalid * EDOM prev_fssnapId is from a different fs * ERANGE prev_fssnapId is more recent than snapId * being scanned * see system calls dup() and malloc() ERRORS */ gpfs_iscan_t * GPFS_API gpfs_open_inodescan_with_xattrs(gpfs_fssnap_handle_t *fssnapHandle, const gpfs_fssnap_id_t *prev_fssnapId, int nxAttrs, const char *xattrsList[], gpfs_ino_t *maxIno); gpfs_iscan_t * GPFS_API gpfs_open_inodescan_with_xattrs64(gpfs_fssnap_handle_t *fssnapHandle, const gpfs_fssnap_id_t *prev_fssnapId, int nxAttrs, const char *xattrList[], gpfs_ino64_t *maxIno); /* NAME: gpfs_next_inode() * * FUNCTION: Get next inode from inode scan. Scan terminates before * the last inode specified or the last inode in the * inode file being scanned. * * If the inode scan was opened to expressly look for inodes * in a snapshot, and not dittos, gets the next inode skipping * holes, if any. * * Input: iscan: ptr to inode scan descriptor * termIno: scan terminates before this inode number * caller may specify maxIno from gpfs_open_inodescan() * or 0 to scan the entire inode file. * iattr: pointer to returned pointer to file's iattr. * * Returns: 0 and *iattr set to point to gpfs_iattr_t (Successful) * 0 and *iattr set to NULL for no more inodes before termIno * -1 and errno is set (Failure) * * Errno: ENOSYS function not available * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * ENOMEM buffer too small * GPFS_E_INVAL_ISCAN bad parameters * GPFS_E_INVAL_FSSNAPID the snapshot id provided in the * gpfs iscan is not valid * * Notes: The data returned by gpfs_next_inode() is overwritten by * subsequent calls to gpfs_next_inode() or gpfs_seek_inode(). * * The termIno parameter provides a means to partition an * inode scan such that it may be executed on more than one node. */ int GPFS_API gpfs_next_inode(gpfs_iscan_t *iscan, gpfs_ino_t termIno, const gpfs_iattr_t **iattr); int GPFS_API gpfs_next_inode64(gpfs_iscan_t *iscan, gpfs_ino64_t termIno, const gpfs_iattr64_t **iattr); /* NAME: gpfs_next_inode_with_xattrs() * * FUNCTION: Get next inode and its extended attributes from the inode scan. * The set of extended attributes returned were defined when * the inode scan was opened. The scan terminates before the last * inode specified or the last inode in the inode file being * scanned. * * If the inode scan was opened to expressly look for inodes * in a snapshot, and not dittos, gets the next inode skipping * holes, if any. * * Input: iscan: ptr to inode scan descriptor * termIno: scan terminates before this inode number * caller may specify maxIno from gpfs_open_inodescan() * or 0 to scan the entire inode file. * iattr: pointer to returned pointer to file's iattr. * xattrBuf: pointer to returned pointer to xattr buffer * xattrBufLen: returned length of xattr buffer * * * Returns: 0 and *iattr set to point to gpfs_iattr_t (Successful) * 0 and *iattr set to NULL for no more inodes before termIno * -1 and errno is set (Failure) * * Errno: ENOSYS function not available * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * EFAULT buffer data was overwritten * ENOMEM buffer too small * GPFS_E_INVAL_ISCAN bad parameters * GPFS_E_INVAL_XATTR bad parameters * * Notes: The data returned by gpfs_next_inode() is overwritten by * subsequent calls to gpfs_next_inode(), gpfs_seek_inode() * or gpfs_stat_inode(). * * The termIno parameter provides a means to partition an * inode scan such that it may be executed on more than one node. * * The returned values for xattrBuf and xattrBufLen must be * provided to gpfs_next_xattr() to obtain the extended attribute * names and values. The buffer used for the extended attributes * is overwritten by subsequent calls to gpfs_next_inode(), * gpfs_seek_inode() or gpfs_stat_inode(); * * The returned pointers to the extended attribute name and value * will be aligned to a double-word boundary. */ int GPFS_API gpfs_next_inode_with_xattrs(gpfs_iscan_t *iscan, gpfs_ino_t termIno, const gpfs_iattr_t **iattr, const char **xattrBuf, unsigned int *xattrBufLen); int GPFS_API gpfs_next_inode_with_xattrs64(gpfs_iscan_t *iscan, gpfs_ino64_t termIno, const gpfs_iattr64_t **iattr, const char **xattrBuf, unsigned int *xattrBufLen); /* NAME: gpfs_next_xattr() * * FUNCTION: Iterate over the extended attributes buffer returned * by get_next_inode_with_xattrs to return the individual * attributes and their values. Note that the attribute names * are null-terminated strings, whereas the atttribute value * contains binary data. * * Input: iscan: ptr to inode scan descriptor * xattrBufLen: ptr to attribute buffer length * xattrBuf: ptr to the ptr to the attribute buffer * * Returns: 0 and *name set to point attribue name (Successful) * also sets: *valueLen to length of attribute value * *value to point to attribute value * *xattrBufLen to remaining length of buffer * **xattrBuf to index next attribute in buffer * 0 and *name set to NULL for no more attributes in buffer * also sets: *valueLen to 0 * *value to NULL * *xattrBufLen to 0 * **xattrBuf to NULL * -1 and errno is set (Failure) * * Errno: ENOSYS function not available * GPFS_E_INVAL_ISCAN invalid iscan parameter * GPFS_E_INVAL_XATTR invalid xattr parameters * * Notes: The caller is not allowed to modify the returned attribute * names or values. The data returned by gpfs_next_attribute() * may be overwritten by subsequent calls to gpfs_next_attribute() * or other gpfs library calls. */ int GPFS_API gpfs_next_xattr(gpfs_iscan_t *iscan, const char **xattrBuf, unsigned int *xattrBufLen, const char **name, unsigned int *valueLen, const char **value); /* NAME: gpfs_seek_inode() * * FUNCTION: Seek to a given inode number. * * Input: iscan: ptr to inode scan descriptor * ino: next inode number to be scanned * * Returns: 0 Successful * -1 Failure and errno is set * * Errno: ENOSYS function not available * GPFS_E_INVAL_ISCAN bad parameters */ int GPFS_API gpfs_seek_inode(gpfs_iscan_t *iscan, gpfs_ino_t ino); int GPFS_API gpfs_seek_inode64(gpfs_iscan_t *iscan, gpfs_ino64_t ino); #ifdef SNAPSHOT_ILM /* define GPFS generated errno */ #define GPFS_E_HOLE_IN_IFILE 238 /* hole in inode file */ #endif /* NAME: gpfs_stat_inode() * NAME: gpfs_stat_inode_with_xattrs() * * FUNCTION: Seek to the specified inode and get that inode and * its extended attributes from the inode scan. This is * simply a combination of gpfs_seek_inode and get_next_inode * but will only return the specified inode. * * Input: iscan: ptr to inode scan descriptor * ino: inode number to be returned * termIno: prefetch inodes up to this inode * caller may specify maxIno from gpfs_open_inodescan() * or 0 to allow prefetching over the entire inode file. * iattr: pointer to returned pointer to file's iattr. * xattrBuf: pointer to returned pointer to xattr buffer * xattrBufLen: returned length of xattr buffer * * Returns: 0 and *iattr set to point to gpfs_iattr_t (Successful) * 0 and *iattr set to NULL for no more inodes before termIno * or if requested inode does not exist. * -1 and errno is set (Failure) * * Errno: ENOSYS function not available * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * ENOMEM buffer too small * GPFS_E_INVAL_ISCAN bad parameters * GPFS_E_HOLE_IN_IFILE if we are expressly looking for inodes in * the snapshot file and this one has yet not * been copied into snapshot. * * Notes: The data returned by gpfs_next_inode() is overwritten by * subsequent calls to gpfs_next_inode(), gpfs_seek_inode() * or gpfs_stat_inode(). * * The termIno parameter provides a means to partition an * inode scan such that it may be executed on more than one node. * It is only used by this call to control prefetching. * * The returned values for xattrBuf and xattrBufLen must be * provided to gpfs_next_xattr() to obtain the extended attribute * names and values. The buffer used for the extended attributes * is overwritten by subsequent calls to gpfs_next_inode(), * gpfs_seek_inode() or gpfs_stat_inode(); */ int GPFS_API gpfs_stat_inode(gpfs_iscan_t *iscan, gpfs_ino_t ino, gpfs_ino_t termIno, const gpfs_iattr_t **iattr); int GPFS_API gpfs_stat_inode64(gpfs_iscan_t *iscan, gpfs_ino64_t ino, gpfs_ino64_t termIno, const gpfs_iattr64_t **iattr); int GPFS_API gpfs_stat_inode_with_xattrs(gpfs_iscan_t *iscan, gpfs_ino_t ino, gpfs_ino_t termIno, const gpfs_iattr_t **iattr, const char **xattrBuf, unsigned int *xattrBufLen); int GPFS_API gpfs_stat_inode_with_xattrs64(gpfs_iscan_t *iscan, gpfs_ino64_t ino, gpfs_ino64_t termIno, const gpfs_iattr64_t **iattr, const char **xattrBuf, unsigned int *xattrBufLen); /* NAME: gpfs_close_inodescan() * * FUNCTION: Close inode file. * * Input: iscan: ptr to inode scan descriptor * * Returns: void * * Errno: None */ void GPFS_API gpfs_close_inodescan(gpfs_iscan_t *iscan); /* NAME: gpfs_cmp_fssnapid() * * FUNCTION: Compare two fssnapIds for the same file system to * determine the order in which the two snapshots were taken. * The 'result' variable will be set as follows: * *result < 0: snapshot 1 was taken before snapshot 2 * *result == 0: snapshot 1 and 2 are the same * *result > 0: snapshot 1 was taken after snapshot 2 * * Input: fssnapId1: ptr to fssnapId 1 * fssnapId2: ptr to fssnapId id 2 * result: ptr to returned results * * Returns: 0 and *result is set as described above (Successful) * -1 and errno is set (Failure) * * Errno: ENOSYS function not available * GPFS_E_INVAL_FSSNAPID fssnapid1 or fssnapid2 is not a * valid snapshot id * EDOM the two snapshots cannot be compared because * they were taken from two different file systems. */ int GPFS_API gpfs_cmp_fssnapid(const gpfs_fssnap_id_t *fssnapId1, const gpfs_fssnap_id_t *fssnapId2, int *result); /* NAME: gpfs_iopen() * * FUNCTION: Open a file or directory by inode number. * * Input: fssnapHandle: handle for file system and snapshot * being scanned * ino: inode number * open_flags: O_RDONLY for gpfs_iread() * O_WRONLY for gpfs_iwrite() * O_CREAT create the file if it doesn't exist * O_TRUNC if the inode already exists delete it * caller may use GPFS_O_BACKUP to read files for backup * and GPFS_O_RESTORE to write files for restore * statxbuf: used only with O_CREAT/GPFS_O_BACKUP * all other cases set to NULL * symLink: used only with O_CREAT/GPFS_O_BACKUP for a symbolic link * all other cases set to NULL * * Returns: pointer to gpfs_ifile_t (Successful) * NULL and errno is set (Failure) * * Errno: ENOSYS function not available * ENOENT file not existed * EINVAL missing or bad parameter * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * ENOMEM unable to allocate memory for request * EFORMAT invalid fs version number * EIO error reading original inode * ERANGE error ino is out of range, should use gpfs_iopen64 * GPFS_E_INVAL_INUM reserved inode is not allowed to open * GPFS_E_INVAL_IATTR iattr structure was corrupted * see dup() and malloc() ERRORS */ gpfs_ifile_t * GPFS_API gpfs_iopen(gpfs_fssnap_handle_t *fssnapHandle, gpfs_ino_t ino, int open_flags, const gpfs_iattr_t *statxbuf, const char *symLink); gpfs_ifile_t * GPFS_API gpfs_iopen64(gpfs_fssnap_handle_t *fssnapHandle, gpfs_ino64_t ino, int open_flags, const gpfs_iattr64_t *statxbuf, const char *symLink); /* Define gpfs_iopen flags as used by the backup & restore by inode. The backup code will only read the source files. The restore code writes the target files & creates them if they don't already exist. The file length is set by the inode attributes. Consequently, to restore a user file it is unnecessary to include the O_TRUNC flag. */ #define GPFS_O_BACKUP (O_RDONLY) #define GPFS_O_RESTORE (O_WRONLY | O_CREAT) /* NAME: gpfs_iread() * * FUNCTION: Read file opened by gpfs_iopen. * * Input: ifile: pointer to gpfs_ifile_t from gpfs_iopen * buffer: buffer for data to be read * bufferSize: size of buffer (ie amount of data to be read) * In/Out offset: offset of where within the file to read * if successful, offset will be updated to the * next byte after the last one that was read * * Returns: number of bytes read (Successful) * -1 and errno is set (Failure) * * Errno: ENOSYS function not available * EISDIR file is a directory * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * GPFS_E_INVAL_IFILE bad ifile parameters * see system call read() ERRORS */ int GPFS_API gpfs_iread(gpfs_ifile_t *ifile, void *buffer, int bufferSize, gpfs_off64_t *offset); /* NAME: gpfs_iwrite() * * FUNCTION: Write file opened by gpfs_iopen. * * Input: ifile: pointer to gpfs_ifile_t from gpfs_iopen * buffer: the data to be written * writeLen: how much to write * In/Out offset: offset of where within the file to write * if successful, offset will be updated to the * next byte after the last one that was written * * Returns: number of bytes written (Successful) * -1 and errno is set (Failure) * * Errno: ENOSYS function not available * EISDIR file is a directory * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * GPFS_E_INVAL_IFILE bad ifile parameters * see system call write() ERRORS */ int GPFS_API gpfs_iwrite(gpfs_ifile_t *ifile, void *buffer, int writeLen, gpfs_off64_t *offset); /* NAME: gpfs_ireaddir() * * FUNCTION: Get next directory entry. * * Input: idir: pointer to gpfs_ifile_t from gpfs_iopen * dirent: pointer to returned pointer to directory entry * * Returns: 0 and pointer to gpfs_direntx set (Successful) * 0 and pointer to gpfs_direntx set to NULL (End of directory) * -1 and errno is set (Failure) * * Errno: ENOSYS function not available * ENOTDIR file is not a directory * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * GPFS_E_INVAL_IFILE bad ifile parameter * ENOMEM unable to allocate memory for request * * Notes: The data returned by gpfs_ireaddir() is overwritten by * subsequent calls to gpfs_ireaddir(). */ int GPFS_API gpfs_ireaddir(gpfs_ifile_t *idir, const gpfs_direntx_t **dirent); int GPFS_API gpfs_ireaddir64(gpfs_ifile_t *idir, const gpfs_direntx64_t **dirent); int GPFS_API gpfs_ireaddirx(gpfs_ifile_t *idir, gpfs_iscan_t *iscan, /* in only */ const gpfs_direntx_t **dirent); int GPFS_API gpfs_ireaddirx64(gpfs_ifile_t *idir, gpfs_iscan_t *iscan, /* in only */ const gpfs_direntx64_t **dirent); /* NAME: gpfs_iwritedir() * * FUNCTION: Create a directory entry in a directory opened by gpfs_iopen. * * Input: idir: pointer to gpfs_ifile_t from gpfs_iopen * dirent: directory entry to be written * * Returns: 0 (Successful) * -1 and errno is set (Failure) * * Errno: ENOSYS function not available * GPFS_E_INVAL_IFILE bad file pointer * ENOTDIR file is not a directory * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * ENOMEM unable to allocate memory for request * EFORMAT invalid dirent version number * see system call write() ERRORS */ int GPFS_API gpfs_iwritedir(gpfs_ifile_t *idir, const gpfs_direntx_t *dirent); int GPFS_API gpfs_iwritedir64(gpfs_ifile_t *idir, const gpfs_direntx64_t *dirent); /* NAME: gpfs_igetattrs() * * FUNCTION: Retrieves all extended file attributes in opaque format. * This function together with gpfs_iputattrs is intended for * use by a backup program to save (gpfs_igetattrs) and * restore (gpfs_iputattrs) all extended file attributes * (ACLs, user attributes, ...) in one call. * * NOTE: This call does not return extended attributes used for * the Data Storage Management (XDSM) API (aka DMAPI). * * Input: ifile: pointer to gpfs_ifile_t from gpfs_iopen * buffer: pointer to buffer for returned attributes * bufferSize: size of buffer * attrSize: ptr to returned size of attributes * * Returns: 0 Successful * -1 Failure and errno is set * * Errno: ENOSYS function not available * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * ENOSPC buffer too small to return all attributes * *attrSizeP will be set to the size necessary * GPFS_E_INVAL_IFILE bad ifile parameters */ int GPFS_API gpfs_igetattrs(gpfs_ifile_t *ifile, void *buffer, int bufferSize, int *attrSize); /* NAME: gpfs_igetattrsx() * * FUNCTION: Retrieves all extended file attributes in opaque format. * This function together with gpfs_iputattrsx is intended for * use by a backup program to save (gpfs_igetattrsx) and * restore (gpfs_iputattrsx) all extended file attributes * (ACLs, user attributes, ...) in one call. * * NOTE: This call can optionally return extended attributes * used for the Data Storage Management (XDSM) API * (aka DMAPI). * * Input: ifile: pointer to gpfs_ifile_t from gpfs_iopen * flags Define behavior of get attributes * GPFS_ATTRFLAG_NO_PLACEMENT - file attributes for placement * are not saved, neither is the current storage pool. * GPFS_ATTRFLAG_IGNORE_POOL - file attributes for placement * are saved, but the current storage pool is not. * GPFS_ATTRFLAG_INCL_DMAPI - file attributes for dmapi are * included in the returned buffer * GPFS_ATTRFLAG_INCL_ENCR - file attributes for encryption * are included in the returned buffer * * buffer: pointer to buffer for returned attributes * bufferSize: size of buffer * attrSize: ptr to returned size of attributes * * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS function not available * EINVAL Not a GPFS file * EINVAL invalid flags provided * ENOSPC buffer too small to return all attributes * *attrSizeP will be set to the size necessary */ int GPFS_API gpfs_igetattrsx(gpfs_ifile_t *ifile, int flags, void *buffer, int bufferSize, int *attrSize); /* NAME: gpfs_igetxattr() * * FUNCTION: Retrieves an extended file attributes from ifile which has been open * by gpfs_iopen(). * * NOTE: This call does not return extended attributes used for * the Data Storage Management (XDSM) API (aka DMAPI). * * Input: ifile: pointer to gpfs_ifile_t from gpfs_iopen * buffer: pointer to buffer for key and returned extended * attribute value * bufferSize: size of buffer, should be enough to save attribute value * attrSize: ptr to key length as input and ptr to the returned * size of attributes as putput. * * Returns: 0 Successful * -1 Failure and errno is set * * Errno: ENOSYS function not available * EPERM caller must have superuser priviledges * ESTALE cached fs information was invalid * ENOSPC buffer too small to return all attributes * *attrSize will be set to the size necessary * GPFS_E_INVAL_IFILE bad ifile parameters */ int GPFS_API gpfs_igetxattr(gpfs_ifile_t *ifile, void *buffer, int bufferSize, int *attrSize); /* NAME: gpfs_iputattrs() * * FUNCTION: Sets all extended file attributes of a file. * The buffer passed in should contain extended attribute data * that was obtained by a previous call to gpfs_igetattrs. * * NOTE: This call will not restore extended attributes * used for the Data Storage Management (XDSM) API * (aka DMAPI). They will be silently ignored. * * Input: ifile: pointer to gpfs_ifile_t from gpfs_iopen * buffer: pointer to buffer for returned attributes * * Returns: 0 Successful * -1 Failure and errno is set * * Errno: ENOSYS function not available * EINVAL the buffer does not contain valid attribute data * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * GPFS_E_INVAL_IFILE bad ifile parameters */ int GPFS_API gpfs_iputattrs(gpfs_ifile_t *ifile, void *buffer); /* NAME: gpfs_iputattrsx() * * FUNCTION: Sets all extended file attributes of a file. * * This routine can optionally invoke the policy engine * to match a RESTORE rule using the file's attributes saved * in the extended attributes to set the file's storage pool and * data replication as when calling gpfs_fputattrswithpathname. * When used with the policy the caller should include the * full path to the file, including the file name, to allow * rule selection based on file name or path. * * By default, the routine will not use RESTORE policy rules * for data placement. The pathName parameter will be ignored * and may be set to NULL. * * If the call does not use RESTORE policy rules, or if the * file fails to match a RESTORE rule, or if there are no * RESTORE rules installed, then the storage pool and data * replication are selected as when calling gpfs_fputattrs(). * * The buffer passed in should contain extended attribute data * that was obtained by a previous call to gpfs_fgetattrs. * * pathName is a UTF-8 encoded string. On Windows, applications * can convert UTF-16 ("Unicode") to UTF-8 using the platforms * WideCharToMultiByte function. * * NOTE: This call will restore extended attributes * used for the Data Storage Management (XDSM) API * (aka DMAPI) if they are present in the buffer. * * Input: ifile: pointer to gpfs_ifile_t from gpfs_iopen * flags Define behavior of put attributes * GPFS_ATTRFLAG_NO_PLACEMENT - file attributes are restored * but the storage pool and data replication are unchanged * GPFS_ATTRFLAG_IGNORE_POOL - file attributes are restored * but the storage pool and data replication are selected * by matching the saved attributes to a placement rule * instead of restoring the saved storage pool. * GPFS_ATTRFLAG_USE_POLICY - file attributes are restored * but the storage pool and data replication are selected * by matching the saved attributes to a RESTORE rule * instead of restoring the saved storage pool. * GPFS_ATTRFLAG_FINALIZE_ATTRS - file attributes that are restored * after data is retored. If file is immutable/appendOnly * call without this flag before restoring data * then call with this flag after restoring data * GPFS_ATTRFLAG_INCL_ENCR - file attributes for encryption * are restored. Note that this may result in the file's * File Encryption Key (FEK) being changed, and in this * case any prior content in the file is effectively lost. * This option should only be used when the entire file * content is restored after the attributes are restored. * * buffer: pointer to buffer for returned attributes * pathName: pointer to file path and file name for file * May be set to NULL. * * Returns: 0 Successful * -1 Failure and errno is set * * Errno: ENOSYS function not available * EINVAL the buffer does not contain valid attribute data * EINVAL invalid flags provided * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * GPFS_E_INVAL_IFILE bad ifile parameters */ int GPFS_API gpfs_iputattrsx(gpfs_ifile_t *ifile, int flags, void *buffer, const char *pathName); /* NAME: gpfs_igetfilesetname() * * FUNCTION: Retrieves the name of the fileset which contains this file. * The fileset name is a null-terminated string, with a * a maximum length of GPFS_MAXNAMLEN. * * Input: iscan: ptr to gpfs_iscan_t from gpfs_open_inodescan() * filesetId: ia_filesetId returned in an iattr from the iscan * buffer: pointer to buffer for returned fileset name * bufferSize: size of buffer * * Returns: 0 Successful * -1 Failure and errno is set * * Errno: ENOSYS function not available * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * ENOSPC buffer too small to return fileset name * GPFS_E_INVAL_ISCAN bad iscan parameter */ int GPFS_API gpfs_igetfilesetname(gpfs_iscan_t *iscan, unsigned int filesetId, void *buffer, int bufferSize); /* NAME: gpfs_igetstoragepool() * * FUNCTION: Retrieves the name of the storage pool assigned for * this file's data. The storage pool name is a null-terminated * string, with a maximum length of GPFS_MAXNAMLEN. * * Input: iscan: ptr to gpfs_iscan_t from gpfs_open_inodescan() * dataPoolId: ia_dataPoolId returned in an iattr from the iscan * buffer: pointer to buffer for returned attributes * bufferSize: size of buffer * * Returns: 0 Successful * -1 Failure and errno is set * * Errno: ENOSYS function not available * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * ENOSPC buffer too small to return all storage pool name * GPFS_E_INVAL_ISCAN bad iscan parameters */ int GPFS_API gpfs_igetstoragepool(gpfs_iscan_t *iscan, unsigned int dataPoolId, void *buffer, int bufferSize); /* NAME: gpfs_iclose() * * FUNCTION: Close file opened by inode and update dates. * * Input: ifile: pointer to gpfs_ifile_t from gpfs_iopen * * Returns: void */ void GPFS_API gpfs_iclose(gpfs_ifile_t *ifile); /* NAME: gpfs_ireadlink() * * FUNCTION: Read symbolic link by inode number. * * Input: fssnapHandle: handle for file system & snapshot being scanned * ino: inode number of link file to read * buffer: pointer to buffer for returned link data * bufferSize: size of the buffer * * Returns: number of bytes read (Successful) * -1 and errno is set (Failure) * * Errno: ENOSYS function not available * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * GPFS_E_INVAL_FSSNAPHANDLE invalid fssnap handle * see system call readlink() ERRORS */ int GPFS_API gpfs_ireadlink(gpfs_fssnap_handle_t *fssnapHandle, gpfs_ino_t ino, char *buffer, int bufferSize); int GPFS_API gpfs_ireadlink64(gpfs_fssnap_handle_t *fssnapHandle, gpfs_ino64_t ino, char *buffer, int bufferSize); /* NAME: gpfs_sync_fs() * * FUNCTION: sync file system. * * Input: fssnapHandle: handle for file system being restored * * Returns: 0 all data flushed to disk (Successful) * -1 and errno is set (Failure) * * Errno: ENOSYS function not available * ENOMEM unable to allocate memory for request * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * GPFS_E_INVAL_FSSNAPHANDLE invalid fssnapHandle */ int GPFS_API gpfs_sync_fs(gpfs_fssnap_handle_t *fssnapHandle); /* NAME: gpfs_enable_restore() * * FUNCTION: Mark file system as enabled for restore on/off * * Input: fssnapHandle: handle for file system to be enabled * or disabled for restore * on_off: flag set to 1 to enable restore * 0 to disable restore * * Returns: 0 (Successful) * -1 and errno is set (Failure) * * Errno: ENOSYS function not available * EINVAL bad parameters * GPFS_E_INVAL_FSSNAPHANDLE invalid fssnapHandle * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * ENOMEM unable to allocate memory for request * E_FS_NOT_RESTORABLE fs is not clean * EALREADY fs already marked as requested * E_RESTORE_STARTED restore in progress * * Notes: EALREADY indicates enable/disable restore was already called * for this fs. The caller must decide if EALREADY represents an * error condition. */ int GPFS_API gpfs_enable_restore(gpfs_fssnap_handle_t *fssnapHandle, int on_off); /* NAME: gpfs_start_restore() * * FUNCTION: Start a restore session. * * Input: fssnapHandle: handle for file system to be restored * restore_flags: Flag to indicate the restore should be started * even if a prior restore has not completed. * old_fssnapId: fssnapId of last restored snapshot * new_fssnapId: fssnapId of snapshot being restored * * Returns: pointer to gpfs_restore_t (Successful) * NULL and errno is set (Failure) * * Errno: ENOSYS function not available * ENOMEM unable to allocate memory for request * EINVAL missing parameter * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * EDOM restore fs does not match existing fs * ERANGE restore is missing updates * EFORMAT invalid fs version number * GPFS_E_INVAL_FSSNAPHANDLE invalid fssnaphandle * GPFS_E_INVAL_FSSNAPID bad fssnapId parameter * E_FS_NOT_RESTORABLE fs is not clean for restore * E_RESTORE_NOT_ENABLED fs is not enabled for restore * EALREADY Restore already in progress * * Note: EALREADY indicates start restore was already called for * this fs. This could be due to a prior restore process that failed * or it could be due to a concurrent restore process still running. * The caller must decide if EALREADY represents an error condition. */ gpfs_restore_t * GPFS_API gpfs_start_restore(gpfs_fssnap_handle_t *fssnapHandle, int restore_flags, const gpfs_fssnap_id_t *old_fssnapId, const gpfs_fssnap_id_t *new_fssnapId); #define GPFS_RESTORE_NORMAL 0 /* Restore not started if prior restore has not completed. */ #define GPFS_RESTORE_FORCED 1 /* Restore starts even if prior restore has not completed. */ /* NAME: gpfs_end_restore() * * FUNCTION: End a restore session. * * Input: restoreId: ptr to gpfs_restore_t * * Returns: 0 (Successful) * -1 and errno is set (Failure) * * Errno: ENOSYS function not available * EINVAL bad parameters * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * GPFS_E_INVAL_RESTORE bad restoreId parameter * GPFS_E_FS_NOT_RESTORABLE fs is not clean for restore * GPFS_E_RESTORE_NOT_ENABLED fs is not enabled for restore * EALREADY Restore already ended * * Note: EALREADY indicates end restore was already called for * this fs. This could be due to a concurrent restore process that * already completed. The caller must decide if EALREADY represents * an error condition. */ int GPFS_API gpfs_end_restore(gpfs_restore_t *restoreId); /* NAME: gpfs_ireadx() * * FUNCTION: Block level incremental read on a file opened by gpfs_iopen * with a given incremental scan opened via gpfs_open_inodescan. * * Input: ifile: ptr to gpfs_ifile_t returned from gpfs_iopen() * iscan: ptr to gpfs_iscan_t from gpfs_open_inodescan() * buffer: ptr to buffer for returned data * bufferSize: size of buffer for returned data * offset: ptr to offset value * termOffset: read terminates before reading this offset * caller may specify ia_size for the file's * gpfs_iattr_t or 0 to scan the entire file. * hole: ptr to returned flag to indicate a hole in the file * * Returns: number of bytes read and returned in buffer * or size of hole encountered in the file. (Success) * -1 and errno is set (Failure) * * On input, *offset contains the offset in the file * at which to begin reading to find a difference same file * in a previous snapshot specified when the inodescan was opened. * On return, *offset contains the offset of the first * difference. * * On return, *hole indicates if the change in the file * was data (*hole == 0) and the data is returned in the * buffer provided. The function's value is the amount of data * returned. If the change is a hole in the file, * *hole != 0 and the size of the changed hole is returned * as the function value. * * A call with a NULL buffer pointer will query the next increment * to be read from the current offset. The *offset, *hole and * returned length will be set for the next increment to be read, * but no data will be returned. The bufferSize parameter is * ignored, but the termOffset parameter will limit the * increment returned. * * Errno: ENOSYS function not available * EINVAL missing or bad parameter * EISDIR file is a directory * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * ENOMEM unable to allocate memory for request * EDOM fs snapId does match local fs * ERANGE previous snapId is more recent than scanned snapId * GPFS_E_INVAL_IFILE bad ifile parameter * GPFS_E_INVAL_ISCAN bad iscan parameter * see system call read() ERRORS * * Notes: The termOffset parameter provides a means to partition a * file's data such that it may be read on more than one node. */ gpfs_off64_t GPFS_API gpfs_ireadx(gpfs_ifile_t *ifile, /* in only */ gpfs_iscan_t *iscan, /* in only */ void *buffer, /* in only */ int bufferSize, /* in only */ gpfs_off64_t *offset, /* in/out */ gpfs_off64_t termOffset, /* in only */ int *hole); /* out only */ /* NAME: gpfs_ireadx_ext * * FUNCTION: gpfs_ireadx_ext is used to find different blocks between clone * child and parent files. Input and output are the same as * gpfs_ireadx. * * Returns: See gpfs_ireadx() */ gpfs_off64_t GPFS_API gpfs_ireadx_ext(gpfs_ifile_t *ifile, /* in only */ gpfs_iscan_t *iscan, /* in only */ void *buffer, /* in only */ int bufferSize, /* in only */ gpfs_off64_t *offset, /* in/out */ gpfs_off64_t termOffset, /* in only */ int *hole); /* NAME: gpfs_iwritex() * * FUNCTION: Write file opened by gpfs_iopen. * If parameter hole == 0, then write data * addressed by buffer to the given offset for the * given length. If hole != 0, then write * a hole at the given offset for the given length. * * Input: ifile : ptr to gpfs_ifile_t returned from gpfs_iopen() * buffer: ptr to data buffer * writeLen: length of data to write * offset: offset in file to write data * hole: flag =1 to write a "hole" * =0 to write data * * Returns: number of bytes/size of hole written (Success) * -1 and errno is set (Failure) * * Errno: ENOSYS function not available * EINVAL missing or bad parameter * EISDIR file is a directory * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * GPFS_E_INVAL_IFILE bad ifile parameter * see system call write() ERRORS */ gpfs_off64_t GPFS_API gpfs_iwritex(gpfs_ifile_t *ifile, /* in only */ void *buffer, /* in only */ gpfs_off64_t writeLen, /* in only */ gpfs_off64_t offset, /* in only */ int hole); /* in only */ /* NAME: gpfs_statfspool() * * FUNCTION: Obtain status information about the storage pools * * Input: pathname : path to any file in the file system * poolId : id of first pool to return * on return set to next poolId or -1 * to indicate there are no more pools. * options : option flags (currently not used) * nPools : number of stat structs requested or 0 * on return number of stat structs in buffer * or if nPools was 0 its value is the max number * of storage pools currently defined * buffer : ptr to return stat structures * bufferSize : sizeof stat buffer * * The user is expected to issue two or more calls. On the first * call the user should pass nPools set to 0 and gpfs will * return in nPools the total number of storage pools currently * defined for the file system indicated by the pathname * and it returns in poolId the id of the first storage pool. * The buffer parameter may be set to NULL for this call. * * The user may then allocate a buffer large enough to contain * a gpfs_statfspool_t structure for each of the pools and issue * a second call to obtain stat information about each pool. * Parameter nPools should be set the number of pools requested. * On return, nPools will be set to the number of stat structs * contained in the buffer, and poolId will be set to the id * of the next storage pool or -1 to indicate there are no * additional storage pools defined. * * Alternatively, if the user has a valid poolId from a previous * call, the user may provide that poolId and a buffer large * enough for a single gpfs_statfspool_t structure, and the call * will return the status for a single storage pool. * * * Returns: 0 Successful * -1 Failure * * Errno: Specific error indication * EINVAL */ int GPFS_API gpfs_statfspool(const char *pathname, /* in only: path to file system*/ gpfs_pool_t *poolId, /* in out: id of first pool to return on return set to next poolId or -1 when there are no more pools */ unsigned int options, /* in only: option flags */ int *nPools, /* in out: number of pool stats requested on return number of stat structs returned in buffer or if nPools was set to 0, the return value is the number of pools currently defined */ void *buffer, /* ptr to return stat structures */ int bufferSize); /* sizeof stat buffer or 0 */ /* NAME: gpfs_getpoolname() * * FUNCTION: Retrieves the name of the storage pool assigned for * this file's data. The storage pool name is a null-terminated * string, with a maximum length of GPFS_MAXNAMLEN. * * Input: pathname: path to any file in the file system * poolId: f_poolid returned in gpfs_statfspool_t * buffer: pointer to buffer for returned name * bufferSize: size of buffer * * Returns: 0 Successful * -1 Failure and errno is set * * Errno: ENOSYS function not available * ESTALE file system was unmounted * E_FORMAT_INCOMPAT file system does not support pools * E2BIG buffer too small to return storage pool name */ int GPFS_API gpfs_getpoolname(const char *pathname, gpfs_pool_t poolId, void *buffer, int bufferSize); /* /usr/src/linux/include/linux/fs.h includes /usr/src/linux/include/linux/quota.h which has conflicting definitions. */ #ifdef _LINUX_QUOTA_ #undef Q_SYNC #undef Q_GETQUOTA #undef Q_SETQUOTA #undef Q_QUOTAON #undef Q_QUOTAOFF #endif /* GPFS QUOTACTL */ /* * Command definitions for the 'gpfs_quotactl' system call. * The commands are broken into a main command defined below * and a subcommand that is used to convey the type of * quota that is being manipulated (see above). */ #define SUBCMDMASK 0x00ff #define SUBCMDSHIFT 8 #define GPFS_QCMD(cmd, type) (((cmd) << SUBCMDSHIFT) | ((type) & SUBCMDMASK)) #define Q_QUOTAON 0x0100 /* enable quotas */ #define Q_QUOTAOFF 0x0200 /* disable quotas */ #define Q_GETQUOTA 0x0300 /* get limits and usage */ #ifndef _LINUX_SOURCE_COMPAT /* Standard AIX definitions of quota commands */ #define Q_SETQUOTA 0x0400 /* set limits */ #define Q_SETQLIM Q_SETQUOTA #else /* Alternate definitions, for Linux Affinity */ #define Q_SETQLIM 0x0400 /* set limits */ #define Q_SETQUOTA 0x0700 /* set limits and usage */ #endif #define Q_SETUSE 0x0500 /* set usage */ #define Q_SYNC 0x0600 /* sync disk copy of a file systems quotas */ #define Q_SETGRACETIME 0x0900 /* set grace time */ #define Q_SETGRACETIME_ENHANCE 0x0800 /* set grace time and update all * quota entries */ #define Q_GETDQPFSET 0x0A00 /* get default quota per fileset */ #define Q_SETDQPFSET 0x0B00 /* set default quota per fileset */ #define Q_SETQUOTA_UPDATE_ET 0x0C00 /* this SETQUOTA needs to update entryType */ #define Q_GETDQPFSYS 0x0D00 /* get default quota per file system */ #define Q_SETDQPFSYS 0x0E00 /* set default quota per file system */ /* gpfs quota types */ #define GPFS_USRQUOTA 0 #define GPFS_GRPQUOTA 1 #define GPFS_FILESETQUOTA 2 /* define GPFS generated errno */ #define GPFS_E_NO_QUOTA_INST 237 /* file system does not support quotas */ typedef struct gpfs_quotaInfo { gpfs_off64_t blockUsage; /* current block count in 1 KB units*/ gpfs_off64_t blockHardLimit; /* absolute limit on disk blks alloc */ gpfs_off64_t blockSoftLimit; /* preferred limit on disk blks */ gpfs_off64_t blockInDoubt; /* distributed shares + "lost" usage for blks */ int inodeUsage; /* current # allocated inodes */ int inodeHardLimit; /* absolute limit on allocated inodes */ int inodeSoftLimit; /* preferred inode limit */ int inodeInDoubt; /* distributed shares + "lost" usage for inodes */ gpfs_uid_t quoId; /* uid, gid or fileset id */ int entryType; /* entry type, not used */ unsigned int blockGraceTime; /* time limit for excessive disk use */ unsigned int inodeGraceTime; /* time limit for excessive inode use */ } gpfs_quotaInfo_t; /* NAME: gpfs_quotactl() * * FUNCTION: Manipulate disk quotas * INPUT: pathname: specifies the pathname of any file within the * mounted file system to which the command is to * be applied * cmd: specifies a quota control command to be applied * to UID/GID/FILESETID id. The cmd parameter can be * constructed using GPFS_QCMD(cmd, type) macro defined * in gpfs.h * id: UID or GID or FILESETID that command applied to. * bufferP: points to the address of an optional, command * specific, data structure that is copied in or out of * the system. * * OUTPUT: bufferP, if applicable. * * Returns: 0 success * -1 failure * * Errno: EACCESS * EFAULT An invalid bufferP parameter is supplied; * the associated structure could not be copied * in or out of the kernel * EINVAL * ENOENT No such file or directory * EPERM The quota control command is privileged and * the caller did not have root user authority * EOPNOTSUPP * GPFS_E_NO_QUOTA_INST The file system does not support quotas */ int GPFS_API gpfs_quotactl(const char *pathname, int cmd, int id, void *bufferP); /* NAME: gpfs_getfilesetid() * * FUNCTION: Translate FilesetName to FilesetID * * INPUT: pathname: specifies the pathname of any file within the * mounted file system to which the command is to * be applied * name: name of the fileset * * OUTPUT: idP: points to the address of an integer that receives the ID * * Returns: 0 success * -1 failure * * Errno: EACCESS * EFAULT An invalid pointer is supplied; the associated * data could not be copied in or out of the kernel * EINVAL * ENOENT No such file, directory or fileset */ int GPFS_API gpfs_getfilesetid(const char *pathname, const char *name, int *idP); /* NAME: gpfs_clone_snap() * * FUNCTION: Create an immutable clone parent from a source file * * Input: sourcePathP: path to source file, which will be cloned * destPathP: path to destination file, to be created * * If destPathP is NULL, then the source file will be changed * in place into an immutable clone parent. * * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS Function not available * ENOENT File does not exist * EACCESS Write access to target or source search permission denied * EINVAL Not a regular file or not a GPFS file system * EFAULT Input argument points outside accessible address space * ENAMETOOLONG Source or destination path name too long * ENOSPC Not enough space on disk * EISDIR Destination is a directory * EXDEV Source and destination aren't in the same file system * EROFS Destination is read-only * EPERM Invalid source file * EEXIST Destination file already exists * EBUSY Source file is open * EFORMAT File system does not support clones * EMEDIUMTYPE File system does not support clones */ int GPFS_API gpfs_clone_snap(const char *sourcePathP, const char *destPathP); /* NAME: gpfs_clone_copy() * * FUNCTION: Create a clone copy of an immutable clone parent file * * Input: sourcePathP: path to immutable source file, to be cloned * destPathP: path to destination file, to be created * * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS Function not available * ENOENT File does not exist * EACCESS Write access to target or source search permission denied * EINVAL Not a regular file or not a GPFS file system * EFAULT Input argument points outside accessible address space * ENAMETOOLONG Source or destination path name too long * ENOSPC Not enough space on disk * EISDIR Destination is a directory * EXDEV Source and destination aren't in the same file system * EROFS Destination is read-only * EPERM Invalid source or destination file * EEXIST Destination file already exists * EFORMAT File system does not support clones * EMEDIUMTYPE File system does not support clones */ int GPFS_API gpfs_clone_copy(const char *sourcePathP, const char *destPathP); /* NAME: gpfs_declone() * * FUNCTION: Copy blocks from clone parent(s) to child so that the * parent blocks are no longer referenced by the child. * * Input: fileDesc: File descriptor for file to be de-cloned * ancLimit: Ancestor limit (immediate parent only, or all) * nBlocks: Maximum number of GPFS blocks to copy * In/Out: offsetP: Pointer to starting offset within file (will be * updated to offset of next block to process or * -1 if no more blocks) * * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS Function not available * EINVAL Invalid argument to function * EBADF Bad file descriptor or not a GPFS file * EPERM Not a regular file * EACCESS Write access to target file not permitted * EFAULT Input argument points outside accessible address space * ENOSPC Not enough space on disk */ /* Values for ancLimit */ #define GPFS_CLONE_ALL 0 #define GPFS_CLONE_PARENT_ONLY 1 int GPFS_API gpfs_declone(gpfs_file_t fileDesc, int ancLimit, gpfs_off64_t nBlocks, gpfs_off64_t *offsetP); /* NAME: gpfs_clone_split() * * FUNCTION: Split a clone child file from its parent. Must call * gpfs_declone first, to remove all references. * * Input: fileDesc: File descriptor for file to be split * ancLimit: Ancestor limit (immediate parent only, or all) * * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS Function not available * EINVAL Invalid argument to function * EBADF Bad file descriptor or not a GPFS file * EPERM Not a regular file or not a clone child * EACCESS Write access to target file not permitted */ int GPFS_API gpfs_clone_split(gpfs_file_t fileDesc, int ancLimit); /* NAME: gpfs_clone_unsnap() * * FUNCTION: Change a clone parent with no children back into a * normal file. * * Input: fileDesc: File descriptor for file to be un-snapped * * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS Function not available * EINVAL Invalid argument to function * EBADF Bad file descriptor or not a GPFS file * EPERM Not a regular file or not a clone parent * EACCESS Write access to target file not permitted */ int GPFS_API gpfs_clone_unsnap(gpfs_file_t fileDesc); /* NAME: gpfs_get_fset_masks() * * FUNCTION: return bit masks governing "external" inode and inode-space numbering * * Input: fset_snaphandle: ptr to an fset snaphandle * Output: the bit masks and inodes per block factor. * * Returns: 0 Success * -1 Failure * * Errno: ENOSYS function not available * GPFS_E_INVAL_FSSNAPHANDLE invalid fssnapHandle */ int GPFS_API gpfs_get_fset_masks(gpfs_fssnap_handle_t* fset_snapHandle, gpfs_ino64_t* inodeSpaceMask, gpfs_ino64_t* inodeBlockMask, int* inodesPerInodeBlock); /* * API functions for Light Weight Event */ /* * Define light weight event types */ typedef enum { GPFS_LWE_EVENT_UNKNOWN = 0, /* "Uknown event" */ GPFS_LWE_EVENT_FILEOPEN = 1, /* 'OPEN' - look at getInfo('OPEN_FLAGS') if you care */ GPFS_LWE_EVENT_FILECLOSE = 2, /* "File Close Event" 'CLOSE' */ GPFS_LWE_EVENT_FILEREAD = 3, /* "File Read Event" 'READ' */ GPFS_LWE_EVENT_FILEWRITE = 4, /* "File Write Event" 'WRITE' */ GPFS_LWE_EVENT_FILEDESTROY = 5, /* File is being destroyed 'DESTROY' */ GPFS_LWE_EVENT_FILEEVICT = 6, /* OpenFile object is being evicted from memory 'FILE_EVICT' */ GPFS_LWE_EVENT_BUFFERFLUSH = 7, /* Data buffer is being written to disk 'BUFFER_FLUSH' */ GPFS_LWE_EVENT_POOLTHRESHOLD = 8, /* Storage pool exceeded defined utilization 'POOL_THRESHOLD' */ GPFS_LWE_EVENT_FILEDATA = 9, /* "Read/Write/Trunc" event on open file */ GPFS_LWE_EVENT_FILERENAME = 10, /* Rename event on open file */ GPFS_LWE_EVENT_FILEUNLINK = 11, /* Unlink file event */ GPFS_LWE_EVENT_FILERMDIR = 12, /* Remove directory event */ GPFS_LWE_EVENT_EVALUATE = 13, /* Evaluate And Set Events */ GPFS_LWE_EVENT_FILEOPEN_READ = 14, /* Open for Read Only - EVENT 'OPEN_READ' - deprecated, use 'OPEN' */ GPFS_LWE_EVENT_FILEOPEN_WRITE = 15, /* Open with Writing privileges - EVENT 'OPEN_WRITE' - deprecated, use 'OPEN' */ GPFS_LWE_EVENT_FILEPOOL_CHANGE = 16, /* Open with Writing privileges - EVENT 'OPEN_WRITE' - deprecated, use 'OPEN' */ GPFS_LWE_EVENT_MAX = 17, /* 1 greater than any of the above */ } gpfs_lwe_eventtype_t; /* Define light weight event response types */ typedef enum { GPFS_LWE_RESP_INVALID = 0, /* "Response Invalid/Unknown" */ GPFS_LWE_RESP_CONTINUE = 1, /* "Response Continue" */ GPFS_LWE_RESP_ABORT = 2, /* "Response Abort" */ GPFS_LWE_RESP_DONTCARE = 3 /* "Response DontCare" */ } gpfs_lwe_resp_t; /* * Define light weight event inofrmation */ #define LWE_DATA_FS_NAME 0x00000001 /* "fsName" */ #define LWE_DATA_PATH_NAME 0x00000002 /* "pathName" */ #define LWE_DATA_PATH_NEW_NAME 0x00000004 /* "pathNewName" for reanem */ #define LWE_DATA_URL 0x00000008 /* "URL" */ #define LWE_DATA_INODE 0x00000010 /* "inode" */ #define LWE_DATA_OPEN_FLAGS 0x00000020 /* "openFlags" */ #define LWE_DATA_POOL_NAME 0x00000040 /* "poolName" */ #define LWE_DATA_FILE_SIZE 0x00000080 /* "fileSize" */ #define LWE_DATA_OWNER_UID 0x00000100 /* "ownerUserId" */ #define LWE_DATA_OWNER_GID 0x00000200 /* "ownerGroupId" */ #define LWE_DATA_ATIME 0x00000400 /* "atime" */ #define LWE_DATA_MTIME 0x00000800 /* "mtime" */ #define LWE_DATA_NOW_TIME 0x00001000 /* "nowTime" */ #define LWE_DATA_ELAPSED_TIME 0x00002000 /* "elapsedTime" */ #define LWE_DATA_CLIENT_UID 0x00004000 /* "clientUserId" */ #define LWE_DATA_CLIENT_GID 0x00008000 /* "clientGroupId" */ #define LWE_DATA_NFS_IP 0x00010000 /* "clientIp" */ #define LWE_DATA_PROCESS_ID 0x00020000 /* "processId" */ #define LWE_DATA_TARGET_POOL_NAME 0x00040000 /* "targetPoolName" */ #define LWE_DATA_BYTES_READ 0x00080000 /* "bytesRead" */ #define LWE_DATA_BYTES_WRITTEN 0x00100000 /* "bytesWritten" */ #define LWE_DATA_CLUSTER_NAME 0x00200000 /* "clusterName" */ #define LWE_DATA_NODE_NAME 0x00400000 /* "nodeName" */ /* * Define light weight events */ #define LWE_EVENT_EVALUATED 0x00000001 /* policy was evaluated */ #define LWE_EVENT_FILEOPEN 0x00000002 /* "op_open" */ #define LWE_EVENT_FILECLOSE 0x00000004 /* "op_close" */ #define LWE_EVENT_FILEREAD 0x00000008 /* "op_read" */ #define LWE_EVENT_FILEWRITE 0x00000010 /* "op_write" */ #define LWE_EVENT_FILEDESTROY 0x00000020 /* "op_destroy" */ #define LWE_EVENT_FILEEVICT 0x00000040 /* "op_evict" OpenFile object is being evicted from memory 'FILE_EVICT' */ #define LWE_EVENT_BUFFERFLUSH 0x00000080 /* "op_buffer_flush" Data buffer is being written to disk 'BUFFER_FLUSH' */ #define LWE_EVENT_POOLTHRESHOLD 0x00000100 /* "op_pool_threshhold" Storage pool exceeded defined utilization 'POOL_THRESHOLD' */ #define LWE_EVENT_FILEDATA 0x00000200 /* "op_data" "Read/Write/Trunc" event on open file */ #define LWE_EVENT_FILERENAME 0x00000400 /* "op_rename" Rename event on open file */ #define LWE_EVENT_FILEUNLINK 0x00000800 /* "op_unlink" Unlink file event */ #define LWE_EVENT_FILERMDIR 0x00001000 /* "op_rmdir" Remove directory event */ #define LWE_EVENT_FILEOPEN_READ 0x00002000 /* "op_open_read" Open for Read Only - EVENT 'OPEN_READ' - deprecated, use 'OPEN' */ #define LWE_EVENT_FILEOPEN_WRITE 0x00004000 /* "op_open_write" Open with Writing privileges - EVENT 'OPEN_WRITE' - deprecated, use 'OPEN' */ #define LWE_EVENT_FILEPOOL_CHANGE 0x00008000 /* "op_pool_change" Open with Writing privileges - EVENT 'OPEN_WRITE' - deprecated, use 'OPEN' */ /* * Defines for light weight sessions */ typedef unsigned long long gpfs_lwe_sessid_t; #define GPFS_LWE_NO_SESSION ((gpfs_lwe_sessid_t) 0) #define GPFS_LWE_SESSION_INFO_LEN 256 /* * Define light weight token to identify access right */ typedef struct gpfs_lwe_token { unsigned long long high; unsigned long long low; #ifdef __cplusplus bool operator == (const struct gpfs_lwe_token& rhs) const { return high == rhs.high && low == rhs.low; }; bool operator != (const struct gpfs_lwe_token& rhs) const { return high != rhs.high || low != rhs.low; }; #endif /* __cplusplus */ } gpfs_lwe_token_t; /* Define special tokens */ static const gpfs_lwe_token_t _gpfs_lwe_no_token = { 0, 0 }; #define GPFS_LWE_NO_TOKEN _gpfs_lwe_no_token static const gpfs_lwe_token_t _gpfs_lwe_invalid_token = { 0, 1 }; #define GPFS_LWE_INVALID_TOKEN _gpfs_lwe_invalid_token /* * Note: LWE data managers can set a file's off-line bit * or any of the managed bits visible to the policy language * by calling dm_set_region or dm_set_region_nosync * with a LWE session and LWE exclusive token. To set the bits * there must be * exactly one managed region with offset = -1 * and size = 0. Any other values will return EINVAL. */ /* LWE also provides light weight regions * that are set via policy rules. */ #define GPFS_LWE_MAX_REGIONS 2 /* LWE data events are generated from user access * to a LWE managed region. */ #define GPFS_LWE_DATAEVENT_NONE (0x0) #define GPFS_LWE_DATAEVENT_READ (0x1) #define GPFS_LWE_DATAEVENT_WRITE (0x2) #define GPFS_LWE_DATAEVENT_TRUNCATE (0x4) /* * Define light weight event structure */ typedef struct gpfs_lwe_event { int eventLen; /* offset 0 */ gpfs_lwe_eventtype_t eventType; /* offset 4 */ gpfs_lwe_token_t eventToken; /* offset 8 <--- Must on DWORD */ int isSync; /* offset 16 */ int parmLen; /* offset 20 */ char* parmP; /* offset 24 <-- Must on DWORD */ } gpfs_lwe_event_t; /* * Define light weight access rights */ #define GPFS_LWE_RIGHT_NULL 0 #define GPFS_LWE_RIGHT_SHARED 1 #define GPFS_LWE_RIGHT_EXCL 2 /* Flag indicating whether to wait * when requesting a right or an event */ #define GPFS_LWE_FLAG_NONE 0 #define GPFS_LWE_FLAG_WAIT 1 /* NAME: gpfs_lwe_create_session() * * FUNCTION: create a light weight event session * * Input: oldsid: existing session id, * Set to GPFS_LWE_NO_SESSION to start new session * - If a session with the same name and id already exists * it is not terminated, nor will outstanding events * be redelivered. This is typically used if a session * is shared between multiple processes. * Set to an existing session's id to resume that session * - If a session with the same name exists, that session * will be terminated. All pending/outstanding events * for the old session will be redelivered on the new one. * This is typically used to take over a session from a * failed/hung process. * sessinfop: session string, unique for each session * * Output: newsidp: session id for new session * * Returns: 0 Success * -1 Failure * * Errno: ENOSYS Function not available * EINVAL invalid parameters * ENFILE maximum number of sessions have already been created * ENOMEM insufficient memory to create new session * ENOENT session to resume does not exist * EEXIST session to resume exists with different id * EPERM Caller does not hold appropriate privilege */ int GPFS_API gpfs_lwe_create_session(gpfs_lwe_sessid_t oldsid, /* IN */ char *sessinfop, /* IN */ gpfs_lwe_sessid_t *newsidp); /* OUT */ #define GPFS_MAX_LWE_SESSION_INFO_LEN 100 /* NAME: gpfs_lwe_destroy_session() * * FUNCTION: destroy a light weight event session * * Input: sid: id of the session to be destroyed * * Returns: 0 Success * -1 Failure * * Errno: ENOSYS Function not available * EINVAL sid invalid * EBUSY session is busy * EPERM Caller does not hold appropriate privilege */ int GPFS_API gpfs_lwe_destroy_session(gpfs_lwe_sessid_t sid); /* IN */ /* NAME: gpfs_lwe_getall_sessions() * * FUNCTION: fetch all lwe sessions * * Input: nelem: max number of elements * sidbufp: array of session id * nelemp: number of session returned in sidbufp * * Returns: 0 Success * -1 Failure * * Errno: ENOSYS Function not available * EINVAL pass in args invalid * E2BIG information is too large * EPERM Caller does not hold appropriate privilege */ int GPFS_API gpfs_lwe_getall_sessions(unsigned int nelem, /* IN */ gpfs_lwe_sessid_t *sidbufp, /* OUT */ unsigned int *nelemp); /* OUT */ /* NAME: gpfs_lw_query_session() * * FUNCTION: query session string by id * * Input: sid: id of session to be queryed * buflen: length of buffer * bufp: buffer to store sessions string * rlenp: returned length of bufp * * Returns: 0 Success * -1 Failure * * Errno: ENOSYS Function not available * EINVAL pass in args invalid * E2BIG information is too large * EPERM Caller does not hold appropriate privilege */ int GPFS_API gpfs_lwe_query_session(gpfs_lwe_sessid_t sid, /* IN */ size_t buflen, /* IN */ void *bufp, /* OUT */ size_t *rlenP); /* OUT */ /* NAME: gpfs_lwe_get_events() * * FUNCTION: get events from a light weight session * * Input: sid: id of the session * maxmsgs: max number of event to fetch, * 0 to fetch all possible * flags: GPFS_LWE_EV_WAIT: waiting for new events if event * queue is empty * buflen: length of the buffer * bufp: buffer to hold events * rlenp: returned length of bufp * * Returns: 0 Success * E2BIG information is too large * EINVAL pass in args invalid */ int GPFS_API gpfs_lwe_get_events(gpfs_lwe_sessid_t sid, /* IN */ unsigned int maxmsgs, /* IN */ unsigned int flags, /* IN */ size_t buflen, /* IN */ void *bufp, /* OUT */ size_t *rlenp); /* OUT */ /* NAME: gpfs_lwe_respond_event() * * FUNCTION: response to a light weight event * * Input: sid: id of the session * token: token of the event * response: response to the event * reterror: return error to event callers * * Returns: 0 Success * EINVAL pass in args invalid * */ int GPFS_API gpfs_lwe_respond_event(gpfs_lwe_sessid_t sid, /* IN */ gpfs_lwe_token_t token, /* IN */ gpfs_lwe_resp_t response, /* IN */ int reterror); /* IN */ /* NAME: gpfs_lwe_request_right * * FUNCTION: Request an access right to a file using a dmapi handle * * Input: sid Id of lw session * hanp Pointer to dmapi handle * hlen Length of dmapi handle * right Shared or exclusive access requested * flags Caller will wait to acquire access if necessary * * Output: token Unique identifier for access right * * Returns: 0 Success * -1 Failure * * Errno: ENOSYS Function not available * ESTALE GPFS not available * EINVAL Invalid arguments * EFAULT Invalid pointer provided * EBADF Bad file * ENOMEM Uable to allocate memory for request * EPERM Caller does not hold appropriate privilege * EAGAIN flags parameter did not include WAIT * and process would be blocked * */ int GPFS_API gpfs_lwe_request_right(gpfs_lwe_sessid_t sid, /* IN */ void *hanp, /* IN */ size_t hlen, /* IN */ unsigned int right, /* IN */ unsigned int flags, /* IN */ gpfs_lwe_token_t *token); /* OUT */ /* NAME: gpfs_lwe_upgrade_right * * FUNCTION: Upgrade an access right from shared to exclusive * * This is a non-blocking call to upgrade an access right * from shared to exclusive. If the token already conveys * exclusive access this call returns imediately with sucess. * If another process also holds a shared access right * this call fails with EBUSY to avoid deadlocks. * * Input: sid Id of lw session * hanp Pointer to dmapi handle * hlen Length of dmapi handle * token Unique identifier for access right * * Output: None * * Returns: 0 Success * -1 Failure * * Errno: ENOSYS Function not available * ESTALE GPFS not available * EINVAL Invalid arguments * EINVAL The token is invalid * EFAULT Invalid pointer provided * EPERM Caller does not hold appropriate privilege * EPERM Token's right is not shared or exclusive * EBUSY Process would be blocked * */ int GPFS_API gpfs_lwe_upgrade_right(gpfs_lwe_sessid_t sid, /* IN */ void *hanp, /* IN */ size_t hlen, /* IN */ gpfs_lwe_token_t token); /* IN */ /* NAME: gpfs_lwe_downgrade_right * * FUNCTION: Downgrade an access right from exclusive to shared * * This reduces an access right from exclusive to shared * without dropping the exclusive right to acquire the shared. * The token must convey exclusive right before the call. * * Input: sid Id of lw session * hanp Pointer to dmapi handle * hlen Length of dmapi handle * token Unique identifier for access right * * Output: None * * Returns: 0 Success * -1 Failure * * Errno: ENOSYS Function not available * ESTALE GPFS not available * EINVAL Invalid arguments * EINVAL The token is invalid * EFAULT Invalid pointer provided * EPERM Caller does not hold appropriate privilege * EPERM Token's right is not exclusive * */ int GPFS_API gpfs_lwe_downgrade_right(gpfs_lwe_sessid_t sid, /* IN */ void *hanp, /* IN */ size_t hlen, /* IN */ gpfs_lwe_token_t token); /* IN */ /* NAME: gpfs_lwe_release_right * * FUNCTION: Release an access right conveyed by a token * * This releases the access right held by a token * and invalidates the token. Once the access right * is released the token cannot be reused. * * Input: sid Id of lw session * hanp Pointer to dmapi handle * hlen Length of dmapi handle * token Unique identifier for access right * * Output: None * * Returns: 0 Success * -1 Failure * * Errno: ENOSYS Function not available * ESTALE GPFS not available * EINVAL Invalid arguments * EINVAL The token is invalid * EFAULT Invalid pointer provided * EPERM Caller does not hold appropriate privilege */ int GPFS_API gpfs_lwe_release_right(gpfs_lwe_sessid_t sid, /* IN */ void *hanp, /* IN */ size_t hlen, /* IN */ gpfs_lwe_token_t token); /* IN */ /* NAME: gpfs_lwe_getattrs() * * FUNCTION: Retrieves all extended file attributes in opaque format. * This function together with gpfs_lwe_putattrs is intended for * use by a backup program to save (gpfs_lwe_getattrs) and * restore (gpfs_lwe_putattrs) all extended file attributes * (ACLs, user attributes, ...) in one call. * * NOTE: This call is the lwe equivalent of gpfs_igetattrsx * but uses a file handle to identify the file * and an existing LWE token for locking it. * * * Input: sid Id of lw session * hanp Pointer to dmapi handle * hlen Length of dmapi handle * token Unique identifier for access right * flags Define behavior of get attributes * GPFS_ATTRFLAG_NO_PLACEMENT - file attributes for placement * are not saved, neither is the current storage pool. * GPFS_ATTRFLAG_IGNORE_POOL - file attributes for placement * are saved, but the current storage pool is not. * GPFS_ATTRFLAG_INCL_DMAPI - file attributes for dmapi are * included in the returned buffer * GPFS_ATTRFLAG_INCL_ENCR - file attributes for encryption * are included in the returned buffer * * buffer: pointer to buffer for returned attributes * bufferSize: size of buffer * attrSize: ptr to returned size of attributes * * Returns: 0 Successful * -1 Failure * * Errno: ENOSYS function not available * EINVAL Not a GPFS file * EINVAL invalid flags provided * ENOSPC buffer too small to return all attributes * *attrSizeP will be set to the size necessary */ int GPFS_API gpfs_lwe_getattrs(gpfs_lwe_sessid_t sid, void *hanp, size_t hlen, gpfs_lwe_token_t token, int flags, void *buffer, int bufferSize, int *attrSize); /* NAME: gpfs_lwe_putattrs() * * FUNCTION: Sets all extended file attributes of a file. * * This routine can optionally invoke the policy engine * to match a RESTORE rule using the file's attributes saved * in the extended attributes to set the file's storage pool and * data replication as when calling gpfs_fputattrswithpathname. * When used with the policy the caller should include the * full path to the file, including the file name, to allow * rule selection based on file name or path. * * By default, the routine will not use RESTORE policy rules * for data placement. The pathName parameter will be ignored * and may be set to NULL. * * If the call does not use RESTORE policy rules, or if the * file fails to match a RESTORE rule, or if there are no * RESTORE rules installed, then the storage pool and data * replication are selected as when calling gpfs_fputattrs(). * * The buffer passed in should contain extended attribute data * that was obtained by a previous call to gpfs_fgetattrs. * * pathName is a UTF-8 encoded string. On Windows, applications * can convert UTF-16 ("Unicode") to UTF-8 using the platforms * WideCharToMultiByte function. * * NOTE: This call is the lwe equivalent of gpfs_iputaattrsx * but uses a file handle to identify the file * and an existing LWE token for locking it. * * * Input: sid Id of lw session * hanp Pointer to dmapi handle * hlen Length of dmapi handle * token Unique identifier for access right * flags Define behavior of put attributes * GPFS_ATTRFLAG_NO_PLACEMENT - file attributes are restored * but the storage pool and data replication are unchanged * GPFS_ATTRFLAG_IGNORE_POOL - file attributes are restored * but the storage pool and data replication are selected * by matching the saved attributes to a placement rule * instead of restoring the saved storage pool. * GPFS_ATTRFLAG_USE_POLICY - file attributes are restored * but the storage pool and data replication are selected * by matching the saved attributes to a RESTORE rule * instead of restoring the saved storage pool. * GPFS_ATTRFLAG_FINALIZE_ATTRS - file attributes that are restored * after data is retored. If file is immutable/appendOnly * call without this flag before restoring data * then call with this flag after restoring data * GPFS_ATTRFLAG_INCL_ENCR - file attributes for encryption * are restored. Note that this may result in the file's * File Encryption Key (FEK) being changed, and in this * case any prior content in the file is effectively lost. * This option should only be used when the entire file * content is restored after the attributes are restored. * * buffer: pointer to buffer for returned attributes * pathName: pointer to file path and file name for file * May be set to NULL. * * Returns: 0 Successful * -1 Failure and errno is set * * Errno: ENOSYS function not available * EINVAL the buffer does not contain valid attribute data * EINVAL invalid flags provided * EPERM caller must have superuser privilege * ESTALE cached fs information was invalid * GPFS_E_INVAL_IFILE bad ifile parameters */ int GPFS_API gpfs_lwe_putattrs(gpfs_lwe_sessid_t sid, void *hanp, size_t hlen, gpfs_lwe_token_t token, int flags, void *buffer, const char *pathName); const char* GPFS_API gpfs_get_fspathname_from_fsname(const char* fsname_or_path); /* Check that fsname_or_path refers to a GPFS file system and find the path to its root Return a strdup()ed copy of the path -OR- NULL w/errno */ int GPFS_API /* experimental */ gpfs_qos_getstats( const char *fspathname, /* in only: path to file system*/ unsigned int options, /* in only: option flags: 0=begin at specified qip, 1=begin after qip */ unsigned int qosid, /* in only: 0 or a specific qosid at which to start or continue */ gpfs_pool_t poolid, /* in only: -1 or a specific poolid at which to start or continue */ unsigned int mqips, /* in only: max number of qip=(qosid,poolid) histories to retrieve */ unsigned int nslots, /* in only: max number of time slots of history to retrieve */ void *bufferP, /* ptr to return stat structures */ unsigned int bufferSize); /* sizeof stat buffer or 0 */ int GPFS_API /* experimental */ gpfs_qos_control( const char *fspathname, /* in only: path to file system*/ void *bufferP, /* in/out control/get/set structs */ unsigned int bufferSize); int GPFS_API gpfs_qos_set( const char *fspathname, const char *classname, /* "gold", "silver", or .. "1" or "2" .. */ int id, /* process id or pgrp or userid */ int which, /* process, pgrp or user */ double* qshareP); /* return the share, percentage or when negative IOP limit */ /* if id==0 then getpid() or getpgrp() or getuid() if which==0 or 1 then process, if 2 process then group, if 3 then userid Return -1 on error, with errno= ENOSYS if QOS is not available in the currently installed GPFS software. ENOENT if classname is not recognized. ENXIO if QOS throttling is not active (but classname is recognized and *qshareP has configured value) */ /* For the given process get QOS info */ int GPFS_API gpfs_qos_get( const char *fspathname, int *classnumP, char classname[18], /* "gold", "silver", or .. "1" or "2" .. */ int id, /* process id or pgrp or userid */ int which, /* process, pgrp or user */ double* qshareP); /* return the share, percentage or when negative IOP limit */ /* given classname, set *classnumP and set *qshareP Return -1 on error, with errno= ENOSYS if QOS is not available in the currently installed GPFS software. ENOENT if classname is not recognized. ENXIO if QOS throttling is not active (but classname is recognized, *classnumP and *qshareP have configured values) */ int GPFS_API gpfs_qos_lkupName( const char *fspathname, int *classnumP, const char *classname, double* qshareP); /* given classnumber, find name and share (similar to above), but start with number instead of name */ int GPFS_API gpfs_qos_lkupVal( const char *fspathname, int val, char classname[18], double* qshareP); int GPFS_API gpfs_ioprio_set(int,int,int); /* do not call directly */ int GPFS_API gpfs_ioprio_get(int,int); /* do not call directly */ /* NAME: gpfs_enc_file_rewrap_key() * * FUNCTION: Re-wrap the File Encryption Key (FEK) for the file, * replacing the usage of the original (second parameter) * Master Encryption Key (MEK) with the new key provided as * the third parameter. The content of the file remains intact. * * If the FEK is not currently being wrapped with the MEK * identified by the second parameter then no action is taken. * * This function is normally invoked before the original MEK is * removed. * * The file may be opened in read-only mode for this function * to perform the key rewrap. * * Superuser privilege is required to invoke this API. * * INPUT: fileDesc: File descriptor for file whose key is to be rewrapped * orig_key_p: Key ID for the key (MEK) to be replaced * new_key_p: Key ID for the new key (MEK) to be used * * OUTPUT: N/A * * Returns: 0 success * -1 failure * * Errno: * EACCESS Existing or new key cannot be retrieved * The new key is already being used to wrap the * file's FEK * EBADF Bad file descriptor * EINVAL Arguments are invalid: key format is incorrect * EFAULT An invalid pointer is supplied; the associated * data could not be copied in or out of the kernel * E2BIG Key IDs provided are too long * ENOSYS Function not available (cluster or file system not * enabled for encryption) * EPERM File is in a snapshot * Caller must have superuser privilege */ /* The Key ID is a string comprised of the key ID and the remote key server RKM ID, separated by ':' */ typedef const char *gpfs_enc_key_id_t; /* " : " */ int GPFS_API gpfs_enc_file_rewrap_key(gpfs_file_t fileDesc, gpfs_enc_key_id_t orig_key_p, gpfs_enc_key_id_t new_key_p); /* NAME: gpfs_enc_get_algo() * * FUNCTION: Retrieve a string describing the encryption algorithm, key * length, Master Encryption Key(s) ID, and wrapping and combining * mechanisms used for the file. * * INPUT: fileDesc: File descriptor for file whose encryption * algorithm is being retrieved * encryption_xattrP: content of the gpfs.Encryption * extended attribute, retrieved by a call to * gpfs_fcntl (with structure type GPFS_FCNTL_GET_XATTR) * xattr_len: length of the data in encryption_xattrP * algo_txt_size: space reserved by the caller for algo_txtP * * OUTPUT: algo_txtP: NULL-terminated string describing the * encryption for the file * * Returns: 0 success * -1 failure * * Errno: * ENOENT File not found * EBADF Bad file handle, not a GPFS file * EACCESS Permission denied * EFAULT Bad address provided * EINVAL Not a regular file * EINVAL Invalid values for xattr_len or algo_txt_size * EINVAL Invalid content of encryption extended attribute * ENOSYS Function not available * E2BIG Output string does not fit in algo_txtP */ int GPFS_API gpfs_enc_get_algo(gpfs_file_t fileDesc, const char *encryption_xattrP, int xattr_len, char *algo_txtP, int algo_txt_size); /* NAME: gpfs_init_trace() * * FUNCTION: Initialize the GPFS trace facility and start to use it. * Must be called before calling gpfs_add_trace(). * * Returns: 0 Success * -1 Failure * * Errno: ENOENT file not found * ENOMEM Memory allocation failed * EACCESS Permission denied * ENFILE Too many open files * ENOSYS Function not available */ int GPFS_API gpfs_init_trace(void); /* NAME: gpfs_query_trace() * * FUNCTION: Query and cache the latest settings of GPFS trace facility. * Generally this should be called by the notification handler * for the "traceConfigChanged" event, which is invoked when * something changes in the configuration of the trace facility. * * Returns: 0 Success * -1 Failure * * Errno: ENOENT file not found * ENOMEM Memory allocation failed * EACCESS Permission denied * ENFILE Too many open files * ENOSYS Function not available */ int GPFS_API gpfs_query_trace(void); /* NAME: gpfs_add_trace() * * FUNCTION: write the logs into GPFS trace driver. When the user specified * parameter "level" is less than or equal to the GPFS trace level, * the log message pointed to by parameter "msg" would be written to * GPFS trace buffer, and user can use mmtracectl command to cut * the GPFS trace buffer into a file to observe. Must be called after * the call to gpfs_init_trace(). Also ensure the gpfs_query_trace() * is called properly to update the gpfs trace level cached in * application, otherwise, the trace may miss to write down to * GPFS trace driver. * * Input: level: the level for this trace generation. When the level * is less than or equal to the GPFS trace level, this * trace record would be written to GPFS trace buffer. * msg: the message string that would be put into GPFS trace buffer. * * Returns: None. */ void GPFS_API gpfs_add_trace(int level, const char *msg); /* NAME: gpfs_fini_trace() * * FUNCTION: Stop using GPFS trace facility. This should be paired with * gpfs_init_trace(), and must be called after the last * gpfs_add_trace(). * * Returns: None. */ void gpfs_fini_trace(void); /* * When GPFS_64BIT_INODES is defined, use the 64-bit interface definitions as * the default. */ #ifdef GPFS_64BIT_INODES #undef GPFS_D_VERSION #define GPFS_D_VERSION GPFS_D64_VERSION #undef GPFS_IA_VERSION #define GPFS_IA_VERSION GPFS_IA64_VERSION #define gpfs_ino_t gpfs_ino64_t #define gpfs_gen_t gpfs_gen64_t #define gpfs_uid_t gpfs_uid64_t #define gpfs_gid_t gpfs_gid64_t #define gpfs_snapid_t gpfs_snapid64_t #define gpfs_nlink_t gpfs_nlink64_t #define gpfs_timestruc_t gpfs_timestruc64_t #define gpfs_direntx_t gpfs_direntx64_t #define gpfs_direntx gpfs_direntx64 #define gpfs_iattr_t gpfs_iattr64_t #define gpfs_get_snapid_from_fssnaphandle gpfs_get_snapid_from_fssnaphandle64 #define gpfs_open_inodescan gpfs_open_inodescan64 #define gpfs_open_inodescan_with_xattrs gpfs_open_inodescan_with_xattrs64 #define gpfs_next_inode gpfs_next_inode64 #define gpfs_next_inode_with_xattrs gpfs_next_inode_with_xattrs64 #define gpfs_seek_inode gpfs_seek_inode64 #define gpfs_stat_inode gpfs_stat_inode64 #define gpfs_stat_inode_with_xattrs gpfs_stat_inode_with_xattrs64 #define gpfs_iopen gpfs_iopen64 #define gpfs_ireaddir gpfs_ireaddir64 #define gpfs_ireaddirx gpfs_ireaddirx64 #define gpfs_iwritedir gpfs_iwritedir64 #define gpfs_ireadlink gpfs_ireadlink64 #endif #define gpfs_icreate gpfs_icreate64 #ifdef __cplusplus } #endif #endif /* H_GPFS */ nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/include/gpfs_nfs.h000066400000000000000000000441101473756622300220560ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ /* */ /* Copyright (C) 2001 International Business Machines */ /* All rights reserved. */ /* */ /* This file is part of the GPFS user library. */ /* */ /* 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. */ /* 3. The name of the author may not be used to endorse or promote products */ /* derived from this software without specific prior written */ /* permission. */ /* */ /* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. */ /* */ /* %Z%%M% %I% %W% %G% %U% */ /* * Library calls for GPFS interfaces */ #ifndef H_GPFS_NFS #define H_GPFS_NFS #ifdef __cplusplus extern "C" { #endif #ifdef WIN32 struct flock {}; #endif /* GANESHA common information */ #define GPFS_DEVNAMEX "/dev/ss0" /* Must be the same as GPFS_DEVNAME */ #define kGanesha 140 /* Must be the same as Ganesha in enum kxOps */ #define OPENHANDLE_GET_VERSION 100 #define OPENHANDLE_GET_VERSION2 1002 #define OPENHANDLE_GET_VERSION3 1003 #define OPENHANDLE_GET_VERSION4 1004 #define OPENHANDLE_NAME_TO_HANDLE 101 #define OPENHANDLE_OPEN_BY_HANDLE 102 #define OPENHANDLE_LAYOUT_TYPE 106 #define OPENHANDLE_GET_DEVICEINFO 107 #define OPENHANDLE_GET_DEVICELIST 108 #define OPENHANDLE_LAYOUT_GET 109 #define OPENHANDLE_LAYOUT_RETURN 110 #define OPENHANDLE_INODE_UPDATE 111 #define OPENHANDLE_GET_XSTAT 112 #define OPENHANDLE_SET_XSTAT 113 #define OPENHANDLE_CHECK_ACCESS 114 #define OPENHANDLE_OPEN_SHARE_BY_HANDLE 115 #define OPENHANDLE_GET_LOCK 116 #define OPENHANDLE_SET_LOCK 117 #define OPENHANDLE_THREAD_UPDATE 118 #define OPENHANDLE_LAYOUT_COMMIT 119 #define OPENHANDLE_DS_READ 120 #define OPENHANDLE_DS_WRITE 121 #define OPENHANDLE_GET_VERIFIER 122 #define OPENHANDLE_FSYNC 123 #define OPENHANDLE_SHARE_RESERVE 124 #define OPENHANDLE_GET_NODEID 125 #define OPENHANDLE_SET_DELEGATION 126 #define OPENHANDLE_CLOSE_FILE 127 #define OPENHANDLE_LINK_BY_FH 128 #define OPENHANDLE_RENAME_BY_FH 129 #define OPENHANDLE_STAT_BY_NAME 130 #define OPENHANDLE_GET_HANDLE 131 #define OPENHANDLE_READLINK_BY_FH 132 #define OPENHANDLE_UNLINK_BY_NAME 133 #define OPENHANDLE_CREATE_BY_NAME 134 #define OPENHANDLE_READ_BY_FD 135 #define OPENHANDLE_WRITE_BY_FD 136 #define OPENHANDLE_CREATE_BY_NAME_ATTR 137 #define OPENHANDLE_GRACE_PERIOD 138 #define OPENHANDLE_ALLOCATE_BY_FD 139 #define OPENHANDLE_REOPEN_BY_FD 140 #define OPENHANDLE_FADVISE_BY_FD 141 #define OPENHANDLE_SEEK_BY_FD 142 #define OPENHANDLE_STATFS_BY_FH 143 #define OPENHANDLE_GETXATTRS 144 #define OPENHANDLE_SETXATTRS 145 #define OPENHANDLE_REMOVEXATTRS 146 #define OPENHANDLE_LISTXATTRS 147 #define OPENHANDLE_MKNODE_BY_NAME 148 #define OPENHANDLE_reserved 149 #define OPENHANDLE_TRACE_ME 150 #define OPENHANDLE_QUOTA 151 #define OPENHANDLE_FS_LOCATIONS 152 #define OPENHANDLE_TRACE_UTIL 155 #define OPENHANDLE_TRACE_COS 156 /* If there is any change in above constants, then update below values. * Currently ignoring opcode 1002 */ #define GPFS_MIN_OP OPENHANDLE_GET_VERSION #define GPFS_MAX_OP OPENHANDLE_FS_LOCATIONS #define GPFS_STAT_NO_OP_1 3 #define GPFS_STAT_NO_OP_2 4 #define GPFS_STAT_NO_OP_3 5 /* max stat ops including placeholder for phantom ops */ #define GPFS_STAT_MAX_OPS (GPFS_MAX_OP - GPFS_MIN_OP + 2) /* placeholder index is the last index in the array */ #define GPFS_STAT_PH_INDEX (GPFS_STAT_MAX_OPS - 1) /* total ops excluding the missing ops 103, 104 and 105 and the placeholder * for phantom ops */ #define GPFS_TOTAL_OPS (GPFS_STAT_MAX_OPS - 4) struct trace_arg { uint32_t level; uint32_t len; char *str; }; #define ganesha_v1 1 #define ganesha_v2 2 #define ganesha_v3 3 #define ganesha_v4 4 int gpfs_ganesha(int op, void *oarg); #define OPENHANDLE_HANDLE_LEN 40 #define OPENHANDLE_KEY_LEN 28 #define OPENHANDLE_VERSION 2 struct xstat_cred_t { uint32_t principal; /* user id */ uint32_t group; /* primary group id */ uint16_t num_groups; /* number secondary groups for this user */ #define XSTAT_CRED_NGROUPS 32 uint32_t eGroups[XSTAT_CRED_NGROUPS]; /* array of secondary groups */ }; struct gpfs_time_t { uint32_t t_sec; uint32_t t_nsec; }; struct gpfs_file_handle { uint16_t handle_size; uint16_t handle_type; uint16_t handle_version; uint16_t handle_key_size; uint32_t handle_fsid[2]; /* file identifier */ unsigned char f_handle[OPENHANDLE_HANDLE_LEN]; }; struct name_handle_arg { int dfd; int flag; const char *name; struct gpfs_file_handle *handle; int expfd; }; struct get_handle_arg { int mountdirfd; int len; const char *name; struct gpfs_file_handle *dir_fh; struct gpfs_file_handle *out_fh; }; struct open_arg { int mountdirfd; int flags; int openfd; struct gpfs_file_handle *handle; const char *cli_ip; }; struct link_fh_arg { int mountdirfd; int len; const char *name; struct gpfs_file_handle *dir_fh; struct gpfs_file_handle *dst_fh; const char *cli_ip; }; struct rename_fh_arg { int mountdirfd; int old_len; const char *old_name; int new_len; const char *new_name; struct gpfs_file_handle *old_fh; struct gpfs_file_handle *new_fh; const char *cli_ip; }; struct glock { int cmd; int lfd; void *lock_owner; struct flock flock; }; #define GPFS_F_CANCELLK (1024 + 5) /* Maps to Linux F_CANCELLK */ #define FL_RECLAIM 4 #define EGRACE 140 struct set_get_lock_arg { int mountdirfd; struct glock *lock; int reclaim; const char *cli_ip; }; struct open_share_arg { int mountdirfd; int flags; int openfd; struct gpfs_file_handle *handle; int share_access; int share_deny; int reclaim; const char *cli_ip; }; struct share_reserve_arg { int mountdirfd; int openfd; int share_access; int share_deny; const char *cli_ip; }; struct fadvise_arg { int mountdirfd; int openfd; uint64_t offset; uint64_t length; uint32_t *hints; }; struct gpfs_io_info { uint32_t io_what; uint64_t io_offset; uint64_t io_len; uint32_t io_eof; uint32_t io_alloc; }; struct fseek_arg { int mountdirfd; int openfd; struct gpfs_io_info *info; }; struct close_file_arg { int mountdirfd; int close_fd; int close_flags; void *close_owner; const char *cli_ip; }; struct link_arg { int file_fd; int dir_fd; const char *name; const char *cli_ip; }; struct readlink_arg { int fd; char *buffer; int size; }; struct readlink_fh_arg { int mountdirfd; struct gpfs_file_handle *handle; char *buffer; int size; }; struct nfsd4_pnfs_deviceid { /** FSAL_ID - to dispatch getdeviceinfo based on */ uint8_t fsal_id; /** Break up the remainder into useful chunks */ uint8_t device_id1; uint16_t device_id2; uint32_t device_id4; uint64_t devid; }; struct gpfs_exp_xdr_stream { int *p; int *end; }; enum x_nfsd_fsid { x_FSID_DEV = 0, x_FSID_NUM, x_FSID_MAJOR_MINOR, x_FSID_ENCODE_DEV, x_FSID_UUID4_INUM, x_FSID_UUID8, x_FSID_UUID16, x_FSID_UUID16_INUM, x_FSID_MAX }; enum x_pnfs_layouttype { x_LAYOUT_NFSV4_1_FILES = 1, x_LAYOUT_OSD2_OBJECTS = 2, x_LAYOUT_BLOCK_VOLUME = 3, x_NFS4_PNFS_PRIVATE_LAYOUT = 0x80000000 }; /* used for both layout return and recall */ enum x_pnfs_layoutreturn_type { x_RETURN_FILE = 1, x_RETURN_FSID = 2, x_RETURN_ALL = 3 }; enum x_pnfs_iomode { x_IOMODE_READ = 1, x_IOMODE_RW = 2, x_IOMODE_ANY = 3, }; enum stable_nfs { x_UNSTABLE4 = 0, x_DATA_SYNC4 = 1, x_FILE_SYNC4 = 2 }; struct pnfstime4 { uint64_t seconds; uint32_t nseconds; }; struct nfsd4_pnfs_dev_iter_res { uint64_t gd_cookie; /* request/response */ uint64_t gd_verf; /* request/response */ uint64_t gd_devid; /* response */ uint32_t gd_eof; /* response */ }; /* Arguments for set_device_notify */ struct pnfs_devnotify_arg { struct nfsd4_pnfs_deviceid dn_devid; /* request */ uint32_t dn_layout_type; /* request */ uint32_t dn_notify_types; /* request/response */ }; struct nfsd4_layout_seg { uint64_t clientid; uint32_t layout_type; uint32_t iomode; uint64_t offset; uint64_t length; }; struct nfsd4_pnfs_layoutget_arg { uint64_t lg_minlength; uint64_t lg_sbid; struct gpfs_file_handle *lg_fh; uint32_t lg_iomode; }; struct nfsd4_pnfs_layoutget_res { struct nfsd4_layout_seg lg_seg; /* request/response */ uint32_t lg_return_on_close; }; struct nfsd4_pnfs_layoutcommit_arg { struct nfsd4_layout_seg lc_seg; /* request */ uint32_t lc_reclaim; /* request */ uint32_t lc_newoffset; /* request */ uint64_t lc_last_wr; /* request */ struct pnfstime4 lc_mtime; /* request */ uint32_t lc_up_len; /* layout length */ void *lc_up_layout; /* decoded by callback */ }; struct nfsd4_pnfs_layoutcommit_res { uint32_t lc_size_chg; /* boolean for response */ uint64_t lc_newsize; /* response */ }; struct nfsd4_pnfs_layoutreturn_arg { uint32_t lr_return_type; /* request */ struct nfsd4_layout_seg lr_seg; /* request */ uint32_t lr_reclaim; /* request */ uint32_t lrf_body_len; /* request */ void *lrf_body; /* request */ void *lr_cookie; /* fs private */ }; struct x_xdr_netobj { unsigned int len; unsigned char *data; }; struct pnfs_filelayout_devaddr { struct x_xdr_netobj r_netid; struct x_xdr_netobj r_addr; }; /* list of multipath servers */ struct pnfs_filelayout_multipath { uint32_t fl_multipath_length; struct pnfs_filelayout_devaddr *fl_multipath_list; }; struct pnfs_filelayout_device { uint32_t fl_stripeindices_length; uint32_t *fl_stripeindices_list; uint32_t fl_device_length; struct pnfs_filelayout_multipath *fl_device_list; }; struct pnfs_filelayout_layout { uint32_t lg_layout_type; /* response */ uint32_t lg_stripe_type; /* response */ uint32_t lg_commit_through_mds; /* response */ uint64_t lg_stripe_unit; /* response */ uint64_t lg_pattern_offset; /* response */ uint32_t lg_first_stripe_index; /* response */ struct nfsd4_pnfs_deviceid device_id; /* response */ uint32_t lg_fh_length; /* response */ struct gpfs_file_handle *lg_fh_list; /* response */ }; enum stripetype4 { STRIPE_SPARSE = 1, STRIPE_DENSE = 2 }; struct deviceinfo_arg { int mountdirfd; int type; struct nfsd4_pnfs_deviceid devid; struct gpfs_exp_xdr_stream xdr; }; struct layoutget_arg { int fd; struct gpfs_file_handle *handle; struct nfsd4_pnfs_layoutget_arg args; struct pnfs_filelayout_layout *file_layout; struct gpfs_exp_xdr_stream *xdr; }; struct layoutreturn_arg { int mountdirfd; struct gpfs_file_handle *handle; struct nfsd4_pnfs_layoutreturn_arg args; }; struct dsread_arg { int mountdirfd; struct gpfs_file_handle *handle; char *bufP; uint64_t offset; uint64_t length; uint64_t *filesize; int options; const char *cli_ip; }; /* define flags for options */ #define IO_SKIP_HOLE (1 << 0) /* 01 */ #define IO_SKIP_DATA (1 << 1) /* 02 */ #define IO_ALLOCATE (1 << 2) /* 04 */ #define IO_DEALLOCATE (1 << 3) /* 08 */ struct dswrite_arg { int mountdirfd; struct gpfs_file_handle *handle; char *bufP; uint64_t offset; uint64_t length; uint32_t stability_wanted; uint32_t *stability_got; uint32_t *verifier4; int options; const char *cli_ip; }; struct read_arg { int mountdirfd; int fd; char *bufP; uint64_t offset; uint64_t length; uint32_t stability_wanted; uint32_t *stability_got; uint32_t *verifier4; int options; const char *cli_ip; }; struct write_arg { int mountdirfd; int fd; char *bufP; uint64_t offset; uint64_t length; uint32_t stability_wanted; uint32_t *stability_got; uint32_t *verifier4; int options; const char *cli_ip; }; struct alloc_arg { int fd; uint64_t offset; uint64_t length; int options; }; struct layoutcommit_arg { int mountdirfd; struct gpfs_file_handle *handle; uint64_t offset; uint64_t length; uint32_t reclaim; /* True if this is a reclaim commit */ uint32_t new_offset; /* True if the client has suggested a new offset */ uint64_t last_write; /* The offset of the last byte written, if new_offset if set, otherwise undefined. */ uint32_t time_changed; /* True if the client provided a new value for mtime */ struct gpfs_time_t new_time; /* If time_changed is true, the client-supplied modification time for the file. otherwise, undefined. */ struct gpfs_exp_xdr_stream *xdr; }; struct fsync_arg { int mountdirfd; struct gpfs_file_handle *handle; uint64_t offset; uint64_t length; uint32_t *verifier4; }; struct statfs_arg { int mountdirfd; struct gpfs_file_handle *handle; struct statfs *buf; }; struct stat_arg { int mountdirfd; struct gpfs_file_handle *handle; struct stat *buf; }; struct grace_period_arg { int mountdirfd; int grace_sec; }; struct create_name_arg { int mountdirfd; /* in */ struct gpfs_file_handle *dir_fh; /* in */ uint32_t dev; /* in dev or posix flags */ int mode; /* in */ int len; /* in */ const char *name; /* in */ struct gpfs_file_handle *new_fh; /* out */ struct stat *buf; /* in/out */ int attr_valid; /* in */ int attr_changed; /* in */ struct gpfs_acl *acl; /* in/out */ const char *cli_ip; }; struct stat_name_arg { int mountdirfd; int len; const char *name; struct gpfs_file_handle *handle; struct stat *buf; const char *cli_ip; }; struct callback_arg { int interface_version; int mountdirfd; int *reason; struct gpfs_file_handle *handle; struct glock *fl; int *flags; struct stat *buf; struct pnfs_deviceid *dev_id; uint32_t *expire_attr; }; #define GPFS_INTERFACE_VERSION 10000 #define GPFS_INTERFACE_SUB_VER 1 /* Defines for the flags in callback_arg, keep up to date with CXIUP_xxx */ #define UP_NLINK 0x00000001 /* update nlink */ #define UP_MODE 0x00000002 /* update mode and ctime */ #define UP_OWN 0x00000004 /* update mode,uid,gid and ctime */ #define UP_SIZE 0x00000008 /* update fsize */ #define UP_SIZE_BIG 0x00000010 /* update fsize if bigger */ #define UP_TIMES 0x00000020 /* update all times */ #define UP_ATIME 0x00000040 /* update atime only */ #define UP_PERM 0x00000080 /* update fields needed for permission checking*/ #define UP_RENAME 0x00000100 /* this is a rename op */ #define UP_DESTROY_FLAG 0x00000200 /* clear destroyIfDelInode flag */ #define UP_GANESHA 0x00000400 /* this is a ganesha op */ /* reason list for reason in callback_arg */ #define INODE_INVALIDATE 1 #define INODE_UPDATE 2 #define INODE_LOCK_GRANTED 3 #define INODE_LOCK_AGAIN 4 #define THREAD_STOP 5 #define THREAD_PAUSE 6 #define BREAK_DELEGATION 7 #define LAYOUT_FILE_RECALL 8 #define LAYOUT_RECALL_ANY 9 #define LAYOUT_NOTIFY_DEVICEID 10 /* define flags for attr_valid */ #define XATTR_STAT (1 << 0) #define XATTR_ACL (1 << 1) #define XATTR_NO_CACHE (1 << 2) #define XATTR_EXPIRE (1 << 3) #define XATTR_FSID (1 << 4) /* define flags for attr_chaged */ #define XATTR_MODE (1 << 0) // 01 #define XATTR_UID (1 << 1) // 02 #define XATTR_GID (1 << 2) // 04 #define XATTR_SIZE (1 << 3) // 08 #define XATTR_ATIME (1 << 4) // 10 #define XATTR_MTIME (1 << 5) // 20 #define XATTR_CTIME (1 << 6) // 40 #define XATTR_ATIME_SET (1 << 7) // 80 #define XATTR_MTIME_SET (1 << 8) // 100 #define XATTR_ATIME_NOW (1 << 9) // 200 #define XATTR_MTIME_NOW (1 << 10) // 400 #define XATTR_SPACE_RESERVED (1 << 11) // 800 struct fsal_fsid { uint64_t major; uint64_t minor; }; struct xstat_arg { int attr_valid; int mountdirfd; struct gpfs_file_handle *handle; struct gpfs_acl *acl; int attr_changed; struct stat *buf; struct fsal_fsid *fsid; uint32_t *expire_attr; const char *cli_ip; }; struct getxattr_arg { int mountdirfd; struct gpfs_file_handle *handle; uint32_t name_len; char *name; uint32_t value_len; void *value; }; struct setxattr_arg { int mountdirfd; struct gpfs_file_handle *handle; int type; uint32_t name_len; char *name; uint32_t value_len; void *value; const char *cli_ip; }; struct removexattr_arg { int mountdirfd; struct gpfs_file_handle *handle; uint32_t name_len; char *name; const char *cli_ip; }; struct listxattr_arg { int mountdirfd; struct gpfs_file_handle *handle; uint64_t cookie; uint64_t verifier; uint32_t eof; uint32_t name_len; void *names; const char *cli_ip; }; struct fs_loc_arg { int mountdirfd; struct gpfs_file_handle *handle; int fs_root_len; char *fs_root; int fs_path_len; char *fs_path; int fs_server_len; char *fs_server; }; struct xstat_access_arg { int mountdirfd; struct gpfs_file_handle *handle; struct gpfs_acl *acl; struct xstat_cred_t *cred; unsigned int posix_mode; unsigned int access; /* v4maske */ unsigned int *supported; }; struct quotactl_arg { const char *pathname; int cmd; int qid; void *bufferP; const char *cli_ip; }; #ifdef __cplusplus } #endif extern struct fsal_stats gpfs_stats; #endif /* H_GPFS_NFS */ nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/main.c000066400000000000000000000163621473756622300175550ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /** @file main.c * @brief GPFS FSAL module core functions * * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #include #include "fsal.h" #include "fsal_internal.h" #include "FSAL/fsal_init.h" #include "gpfs_methods.h" #include "gsh_config.h" static const char myname[] = "GPFS"; /** @struct GPFS * @brief my module private storage */ struct gpfs_fsal_module GPFS = { .module = { .fs_info = { .maxfilesize = INT64_MAX, .maxlink = _POSIX_LINK_MAX, .maxnamelen = 1024, .maxpathlen = 1024, .no_trunc = true, .chown_restricted = false, .case_insensitive = false, .case_preserving = true, .link_support = true, .symlink_support = true, .lock_support = true, .lock_support_async_block = false, .named_attr = true, .unique_handles = true, .acl_support = FSAL_ACLSUPPORT_ALLOW | FSAL_ACLSUPPORT_DENY, .cansettime = true, .homogenous = true, .supported_attrs = GPFS_SUPPORTED_ATTRIBUTES, .maxread = FSAL_MAXIOSIZE, .maxwrite = FSAL_MAXIOSIZE, .umask = 0, .auth_exportpath_xdev = true, /* @todo Update lease handling to use new interfaces */ #if 0 /** not working with pNFS */ .delegations = FSAL_OPTION_FILE_READ_DELEG, #endif .pnfs_mds = true, .pnfs_ds = true, .fsal_trace = true, .fsal_grace = false, .link_supports_permission_checks = true, .expire_time_parent = 60, } } }; /** @struct gpfs_params * @brief Configuration items */ static struct config_item gpfs_params[] = { CONF_ITEM_BOOL("link_support", true, fsal_staticfsinfo_t, link_support), CONF_ITEM_BOOL("symlink_support", true, fsal_staticfsinfo_t, symlink_support), CONF_ITEM_BOOL("cansettime", true, fsal_staticfsinfo_t, cansettime), CONF_ITEM_MODE("umask", 0, fsal_staticfsinfo_t, umask), CONF_ITEM_BOOL("auth_xdev_export", false, fsal_staticfsinfo_t, auth_exportpath_xdev), /** At the moment GPFS doesn't support WRITE delegations */ CONF_ITEM_ENUM_BITS("Delegations", FSAL_OPTION_FILE_READ_DELEG, FSAL_OPTION_FILE_DELEGATIONS, deleg_types, fsal_staticfsinfo_t, delegations), CONF_ITEM_BOOL("PNFS_MDS", true, fsal_staticfsinfo_t, pnfs_mds), CONF_ITEM_BOOL("PNFS_DS", true, fsal_staticfsinfo_t, pnfs_ds), CONF_ITEM_BOOL("fsal_trace", true, fsal_staticfsinfo_t, fsal_trace), CONF_ITEM_BOOL("fsal_grace", false, fsal_staticfsinfo_t, fsal_grace), CONFIG_EOL }; /** @struct gpfs_param * @brief Configuration block */ static struct config_block gpfs_param = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.gpfs", .blk_desc.name = "GPFS", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = gpfs_params, .blk_desc.u.blk.commit = noop_conf_commit }; /** @fn static int * log_to_gpfs(log_header_t headers, void *private, log_levels_t level, * struct display_buffer *buffer, char *compstr, char *message) * @brief Log to gpfs */ static int log_to_gpfs(log_header_t headers, void *private, log_levels_t level, struct display_buffer *buffer, char *compstr, char *message) { struct trace_arg targ = { 0 }; if (level <= 0) return 0; targ.level = level; targ.len = strlen(compstr); targ.str = compstr; return gpfs_ganesha(OPENHANDLE_TRACE_ME, &targ); } /** @fn static fsal_status_t init_config(struct fsal_module *fsal_hdl, * config_file_t config_struct, struct config_error_type *err_type) * @brief must be called with a reference taken (via lookup_fsal) */ static fsal_status_t init_config(struct fsal_module *gpfs_fsal_module, config_file_t config_struct, struct config_error_type *err_type) { int rc; (void)prepare_for_stats(gpfs_fsal_module); LogFullDebug(COMPONENT_FSAL, "Supported attributes default = 0x%" PRIx64, gpfs_fsal_module->fs_info.supported_attrs); (void)load_config_from_parse(config_struct, &gpfs_param, &gpfs_fsal_module->fs_info, true, err_type); if (!config_error_is_harmless(err_type)) return fsalstat(ERR_FSAL_INVAL, 0); display_fsinfo(gpfs_fsal_module); LogFullDebug(COMPONENT_FSAL, "Supported attributes constant = 0x%" PRIx64, GPFS_SUPPORTED_ATTRIBUTES); LogDebug(COMPONENT_FSAL, "FSAL INIT: Supported attributes mask = 0x%" PRIx64, gpfs_fsal_module->fs_info.supported_attrs); rc = create_log_facility(myname, log_to_gpfs, NIV_FULL_DEBUG, LH_COMPONENT, NULL); if (rc != 0) { LogCrit(COMPONENT_FSAL, "Could not create GPFS logger (%s)", strerror(-rc)); return fsalstat(ERR_FSAL_INVAL, 0); } if (gpfs_fsal_module->fs_info.fsal_trace) { rc = enable_log_facility(myname); if (rc == 0) return fsalstat(ERR_FSAL_NO_ERROR, 0); LogCrit(COMPONENT_FSAL, "Could not enable GPFS logger (%s)", strerror(-rc)); return fsalstat(ERR_FSAL_INVAL, 0); } rc = disable_log_facility(myname); if (rc == 0) return fsalstat(ERR_FSAL_NO_ERROR, 0); LogCrit(COMPONENT_FSAL, "Could not disable GPFS logger (%s)", strerror(-rc)); return fsalstat(ERR_FSAL_INVAL, 0); } /** @fn MODULE_INIT void gpfs_init(void) * @brief Module initialization. * * Called by dlopen() to register the module * keep a private pointer to me in myself */ MODULE_INIT void gpfs_init(void) { struct fsal_module *myself = &GPFS.module; if (register_fsal(myself, myname, FSAL_MAJOR_VERSION, FSAL_MINOR_VERSION, FSAL_ID_GPFS) != 0) { fprintf(stderr, "GPFS module failed to register"); return; } /** Set up module operations */ myself->m_ops.fsal_pnfs_ds_ops = pnfs_ds_ops_init; myself->m_ops.create_export = gpfs_create_export; myself->m_ops.init_config = init_config; myself->m_ops.getdeviceinfo = getdeviceinfo; myself->m_ops.fs_da_addr_size = fs_da_addr_size; #ifdef USE_DBUS myself->m_ops.fsal_extract_stats = fsal_gpfs_extract_stats; #endif myself->m_ops.fsal_reset_stats = fsal_gpfs_reset_stats; /* Initialize the fsal_obj_handle ops for FSAL GPFS */ gpfs_handle_ops_init(&GPFS.handle_ops); gpfs_handle_ops_init(&GPFS.handle_ops_with_pnfs); handle_ops_pnfs(&GPFS.handle_ops_with_pnfs); } /** @fn MODULE_FINI void gpfs_unload(void) * @brief unload module */ MODULE_FINI void gpfs_unload(void) { release_log_facility(myname); if (unregister_fsal(&GPFS.module) != 0) fprintf(stderr, "GPFS module failed to unregister"); } nfs-ganesha-6.5/src/FSAL/FSAL_GPFS/maketest.conf000066400000000000000000000011611473756622300211400ustar00rootroot00000000000000 Test Find { Product = FSAL POSIX. Command = ../scripts/non_reg_fsal_posix/compare_find_posix_posixdb.bash /etc Comment = FSAL posixdb find & unix find comparison. # test if we executed all the commands (10) # and the program ended well. Success TestOk { STDOUT =~ /OK/m AND STATUS == 0 } # error Failure TestBAD { STDOUT =~ /BAD/m AND STATUS != 0 } # fsal_posix_tool failure Failure TestFailure { STDERR =~ /Error/m } } nfs-ganesha-6.5/src/FSAL/FSAL_KVSFS/000077500000000000000000000000001473756622300166125ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_KVSFS/CMakeLists.txt000066400000000000000000000032601473756622300213530ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- ########### next target ############### IF(KVSFS_PREFIX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${KVSNS_PREFIX}/include") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -L${KVSNS_PREFIX}/lib") ENDIF() SET(fsalkvsfs_LIB_SRCS kvsfs_fsal_internal.c kvsfs_main.c kvsfs_export.c kvsfs_handle.c kvsfs_file.c kvsfs_xattrs.c kvsfs_mds.c kvsfs_ds.c ) add_library(fsalkvsfs MODULE ${fsalkvsfs_LIB_SRCS}) add_sanitizers(fsalceph) target_link_libraries(fsalkvsfs kvsns hiredis ${SYSTEM_LIBRARIES} ) set_target_properties(fsalkvsfs PROPERTIES VERSION 4.2.0 SOVERSION 4) ########### install files ############### install(TARGETS fsalkvsfs COMPONENT fsal DESTINATION ${FSAL_DESTINATION} ) nfs-ganesha-6.5/src/FSAL/FSAL_KVSFS/kvsfs_ds.c000066400000000000000000000176501473756622300206110ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright © 2020 CEA * Author: Philippe DENIEL * * This program 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 3 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 * 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 * */ /** * @file ds.c * * @brief pNFS DS operations for KVSFS * * This file implements the read, write, commit, and dispose * operations for KVSFS data-server handles. * * Also, creating a data server handle -- now called via the DS itself. */ #include "config.h" #include #include /* used for 'dirname' */ #include #include #include #include #include #include #include #include #include "gsh_list.h" #include "fsal.h" #include "kvsfs_fsal_internal.h" #include "fsal_convert.h" #include "../fsal_private.h" #include "FSAL/access_check.h" #include "FSAL/fsal_config.h" #include "FSAL/fsal_commonlib.h" #include "kvsfs_methods.h" #include "nfs_exports.h" #include "nfs_creds.h" #include "pnfs_utils.h" #include /** * @brief Release a DS handle * * @param[in] ds_pub The object to release */ static void kvsfs_release(struct fsal_ds_handle *const ds_pub) { /* The private 'full' DS handle */ struct kvsfs_ds *ds = container_of(ds_pub, struct kvsfs_ds, ds); gsh_free(ds); } /** * @brief Read from a data-server handle. * * NFSv4.1 data server handles are disjount from normal * filehandles (in Ganesha, there is a ds_flag in the filehandle_v4_t * structure) and do not get loaded into mdcache or processed the * normal way. * * @param[in] ds_pub FSAL DS handle * @param[in] stateid The stateid supplied with the READ operation, * for validation * @param[in] offset The offset at which to read * @param[in] requested_length Length of read requested (and size of buffer) * @param[out] buffer The buffer to which to store read data * @param[out] supplied_length Length of data read * @param[out] eof True on end of file * * @return An NFSv4.1 status code. */ static nfsstat4 kvsfs_ds_read(struct fsal_ds_handle *const ds_pub, const stateid4 *stateid, const offset4 offset, const count4 requested_length, void *const buffer, count4 *const supplied_length, bool *const end_of_file) { /* The private 'full' DS handle */ struct kvsfs_ds *ds = container_of(ds_pub, struct kvsfs_ds, ds); struct kvsfs_file_handle *kvsfs_fh = &ds->wire; /* The amount actually read */ int amount_read = 0; kvsns_cred_t cred; kvsns_file_open_t fd; int rc; cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; rc = kvsns_open(&cred, &kvsfs_fh->kvsfs_handle, O_RDONLY, 0777, &fd); if (rc < 0) return posix2nfs4_error(-rc); /* write the data */ amount_read = kvsns_read(&cred, &fd, (char *)buffer, requested_length, offset); if (amount_read < 0) { /* ignore any potential error on close if read failed? */ kvsns_close(&fd); return posix2nfs4_error(-amount_read); } rc = kvsns_close(&fd); if (rc < 0) return posix2nfs4_error(-rc); *supplied_length = amount_read; *end_of_file = amount_read == 0 ? true : false; return NFS4_OK; } /** * * @brief Write to a data-server handle. * * This performs a DS write not going through the data server unless * FILE_SYNC4 is specified, in which case it connects the filehandle * and performs an MDS write. * * @param[in] ds_pub FSAL DS handle * @param[in] stateid The stateid supplied with the READ operation, * for validation * @param[in] offset The offset at which to read * @param[in] write_length Length of write requested (and size of buffer) * @param[out] buffer The buffer to which to store read data * @param[in] stability wanted Stability of write * @param[out] written_length Length of data written * @param[out] writeverf Write verifier * @param[out] stability_got Stability used for write (must be as * or more stable than request) * * @return An NFSv4.1 status code. */ static nfsstat4 kvsfs_ds_write(struct fsal_ds_handle *const ds_pub, const stateid4 *stateid, const offset4 offset, const count4 write_length, const void *buffer, const stable_how4 stability_wanted, count4 *written_length, verifier4 *writeverf, stable_how4 *stability_got) { /* The private 'full' DS handle */ struct kvsfs_ds *ds = container_of(ds_pub, struct kvsfs_ds, ds); struct kvsfs_file_handle *kvsfs_fh = &ds->wire; /* The amount actually read */ int32_t amount_written = 0; kvsns_cred_t cred; kvsns_file_open_t fd; int rc; memset(writeverf, 0, NFS4_VERIFIER_SIZE); cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; /** @todo Add some debug code here about the fh to be used */ /* @todo: we could take care of parameter stability_wanted here */ rc = kvsns_open(&cred, &kvsfs_fh->kvsfs_handle, O_WRONLY, 0777, &fd); if (rc < 0) return posix2nfs4_error(-rc); /* write the data */ amount_written = kvsns_write(&cred, &fd, (char *)buffer, write_length, offset); if (amount_written < 0) { kvsns_close(&fd); return posix2nfs4_error(-amount_written); } rc = kvsns_close(&fd); if (rc < 0) return posix2nfs4_error(-rc); *written_length = amount_written; *stability_got = stability_wanted; return NFS4_OK; } /** * @brief Commit a byte range to a DS handle. * * NFSv4.1 data server filehandles are disjount from normal * filehandles (in Ganesha, there is a ds_flag in the filehandle_v4_t * structure) and do not get loaded into mdcache or processed the * normal way. * * @param[in] ds_pub FSAL DS handle * @param[in] offset Start of commit window * @param[in] count Length of commit window * @param[out] writeverf Write verifier * * @return An NFSv4.1 status code. */ static nfsstat4 kvsfs_ds_commit(struct fsal_ds_handle *const ds_pub, const offset4 offset, const count4 count, verifier4 *const writeverf) { memset(writeverf, 0, NFS4_VERIFIER_SIZE); return NFS4_OK; } /** * @brief Try to create a FSAL data server handle * * @param[in] pds FSAL pNFS DS * @param[in] desc Buffer from which to create the file * @param[out] handle FSAL DS handle * * @return NFSv4.1 error codes. */ static nfsstat4 make_ds_handle(struct fsal_pnfs_ds *const pds, const struct gsh_buffdesc *const desc, struct fsal_ds_handle **const handle, int flags) { struct kvsfs_ds *ds; /* Handle to be created */ *handle = NULL; if (desc->len != sizeof(struct kvsfs_file_handle)) return NFS4ERR_BADHANDLE; ds = gsh_calloc(1, sizeof(struct kvsfs_ds)); *handle = &ds->ds; /* Connect lazily when a FILE_SYNC4 write forces us to, not here. */ ds->connected = false; memcpy(&ds->wire, desc->addr, desc->len); return NFS4_OK; } static nfsstat4 pds_permissions(struct fsal_pnfs_ds *const pds, struct svc_req *req) { /* special case: related export has been set */ return nfs4_export_check_access(req); } void kvsfs_pnfs_ds_ops_init(struct fsal_pnfs_ds_ops *ops) { memcpy(ops, &def_pnfs_ds_ops, sizeof(struct fsal_pnfs_ds_ops)); ops->ds_permissions = pds_permissions; ops->make_ds_handle = make_ds_handle; ops->dsh_release = kvsfs_release; ops->dsh_read = kvsfs_ds_read; ops->dsh_write = kvsfs_ds_write; ops->dsh_commit = kvsfs_ds_commit; } nfs-ganesha-6.5/src/FSAL/FSAL_KVSFS/kvsfs_export.c000066400000000000000000000236361473756622300215250ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /* export.c * KVSFS FSAL export object */ #include "config.h" #include /* used for 'dirname' */ #include #include #include #include "fsal.h" #include "fsal_api.h" #include "fsal_types.h" #include "fsal_pnfs.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_init.h" #include "FSAL/fsal_config.h" #include "pnfs_utils.h" #include "kvsfs_fsal_internal.h" #include "kvsfs_methods.h" /* export object methods */ static void kvsfs_export_release(struct fsal_export *exp_hdl) { struct kvsfs_fsal_export *myself; myself = container_of(exp_hdl, struct kvsfs_fsal_export, export); fsal_detach_export(exp_hdl->fsal, &exp_hdl->exports); free_export_ops(exp_hdl); gsh_free(myself); /* elvis has left the building */ } static fsal_status_t get_dynamic_info(struct fsal_export *exp_hdl, struct fsal_obj_handle *obj_hdl, fsal_dynamicfsinfo_t *infop) { /** @todo I'm not sure how this gets away without filling anything in. */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* extract a file handle from a buffer. * do verification checks and flag any and all suspicious bits. * Return an updated fh_desc into whatever was passed. The most * common behavior, done here is to just reset the length. There * is the option to also adjust the start pointer. */ static fsal_status_t kvsfs_wire_to_host(struct fsal_export *exp_hdl, fsal_digesttype_t in_type, struct gsh_buffdesc *fh_desc, int flags) { struct kvsfs_file_handle *hdl; size_t fh_size; /* sanity checks */ if (!fh_desc || !fh_desc->addr) return fsalstat(ERR_FSAL_FAULT, 0); hdl = (struct kvsfs_file_handle *)fh_desc->addr; fh_size = kvsfs_sizeof_handle(hdl); if (fh_desc->len != fh_size) { LogMajor(COMPONENT_FSAL, "Size mismatch for handle. should be %lu, got %u", (unsigned long)fh_size, (unsigned int)fh_desc->len); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } fh_desc->len = fh_size; /* pass back the actual size */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } static attrmask_t kvsfs_supported_attrs(struct fsal_export *exp_hdl) { attrmask_t supported_mask; supported_mask = fsal_supported_attrs(&exp_hdl->fsal->fs_info); supported_mask &= ~ATTR_ACL; return supported_mask; } void kvsfs_free_state(struct state_t *state) { struct kvsfs_fd *my_fd; my_fd = &container_of(state, struct kvsfs_state_fd, state)->kvsfs_fd; destroy_fsal_fd(&my_fd->fsal_fd); gsh_free(state); } /** * @brief Allocate a state_t structure * * Note that this is not expected to fail since memory allocation is * expected to abort on failure. * * @param[in] exp_hdl Export state_t will be associated with * @param[in] state_type Type of state to allocate * @param[in] related_state Related state if appropriate * * @returns a state structure. */ static struct state_t *kvsfs_alloc_state(struct fsal_export *exp_hdl, enum state_type state_type, struct state_t *related_state) { struct state_t *state; struct kvsfs_fd *my_fd; state = init_state(gsh_calloc(1, sizeof(struct kvsfs_state_fd)), kvsfs_free_state, state_type, related_state); my_fd = &container_of(state, struct kvsfs_state_fd, state)->kvsfs_fd; init_fsal_fd(&my_fd->fsal_fd, FSAL_FD_STATE, op_ctx->fsal_export); return state; } /** * @brief Function to get the fasl_obj_handle that has fsal_fd as its global fd. * * @param[in] exp_hdl The export in which the handle exists * @param[in] fd File descriptor in question * @param[out] handle FSAL object handle * * @return the fsal_obj_handle. */ void get_fsal_obj_hdl(struct fsal_export *exp_hdl, struct fsal_fd *fd, struct fsal_obj_handle **handle) { struct kvsfs_fd *my_fd = NULL; struct kvsfs_fsal_obj_handle *myself = NULL; my_fd = container_of(fd, struct kvsfs_fd, fsal_fd); myself = container_of(my_fd, struct kvsfs_fsal_obj_handle, u.file.fd); *handle = &myself->obj_handle; } /* kvsfs_export_ops_init * overwrite vector entries with the methods that we support */ void kvsfs_export_ops_init(struct export_ops *ops) { ops->release = kvsfs_export_release; ops->lookup_path = kvsfs_lookup_path; ops->wire_to_host = kvsfs_wire_to_host; ops->create_handle = kvsfs_create_handle; ops->get_fs_dynamic_info = get_dynamic_info; ops->fs_supported_attrs = kvsfs_supported_attrs; ops->alloc_state = kvsfs_alloc_state; ops->get_fsal_obj_hdl = get_fsal_obj_hdl; } static int kvsfs_conf_pnfs_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { /* struct lustre_pnfs_param *lpp = self_struct; */ /* Verifications/parameter checking to be added here */ return 0; } static struct config_item ds_array_params[] = { CONF_MAND_IP_ADDR("DS_Addr", "127.0.0.1", kvsfs_pnfs_ds_parameter, ipaddr), CONF_ITEM_UI16("DS_Port", 1024, UINT16_MAX, 2049, kvsfs_pnfs_ds_parameter, ipport), /* default is nfs */ CONFIG_EOL }; static struct config_item pnfs_params[] = { CONF_MAND_UI32("Stripe_Unit", 8192, 1024 * 1024, 1024, kvsfs_exp_pnfs_parameter, stripe_unit), CONF_ITEM_BOOL("pnfs_enabled", false, kvsfs_exp_pnfs_parameter, pnfs_enabled), CONF_MAND_UI32("Nb_Dataserver", 1, 4, 1, kvsfs_exp_pnfs_parameter, nb_ds), CONF_ITEM_BLOCK("DS1", ds_array_params, noop_conf_init, noop_conf_commit, kvsfs_exp_pnfs_parameter, ds_array[0]), CONF_ITEM_BLOCK("DS2", ds_array_params, noop_conf_init, noop_conf_commit, kvsfs_exp_pnfs_parameter, ds_array[1]), CONF_ITEM_BLOCK("DS3", ds_array_params, noop_conf_init, noop_conf_commit, kvsfs_exp_pnfs_parameter, ds_array[2]), CONF_ITEM_BLOCK("DS4", ds_array_params, noop_conf_init, noop_conf_commit, kvsfs_exp_pnfs_parameter, ds_array[3]), CONFIG_EOL }; static struct config_item export_params[] = { CONF_ITEM_NOOP("name"), CONF_ITEM_STR("kvsns_config", 0, MAXPATHLEN, KVSNS_DEFAULT_CONFIG, kvsfs_fsal_export, kvsns_config), CONF_ITEM_BLOCK("PNFS", pnfs_params, noop_conf_init, kvsfs_conf_pnfs_commit, kvsfs_fsal_export, pnfs_param), CONFIG_EOL }; static struct config_block export_param = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.kvsfs-export", .blk_desc.name = "FSAL", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = export_params, .blk_desc.u.blk.commit = noop_conf_commit }; /* create_export * Create an export point and return a handle to it to be kept * in the export list. * First lookup the fsal, then create the export and then put the fsal back. * returns the export with one reference taken. */ fsal_status_t kvsfs_create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops) { struct kvsfs_fsal_export *myself = NULL; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; int retval = 0; fsal_errors_t fsal_error = ERR_FSAL_INVAL; myself = gsh_calloc(1, sizeof(struct kvsfs_fsal_export)); fsal_export_init(&myself->export); kvsfs_export_ops_init(&myself->export.exp_ops); myself->export.up_ops = up_ops; LogDebug(COMPONENT_FSAL, "kvsfs_create_export"); retval = load_config_from_node(parse_node, &export_param, myself, true, err_type); if (retval != 0) goto errout; retval = kvsns_start(myself->kvsns_config); if (retval != 0) { LogMajor(COMPONENT_FSAL, "Can't start KVSNS API: %d (%s)", retval, strerror(-retval)); goto errout; } else LogEvent(COMPONENT_FSAL, "KVSNS API is running, config = %s", myself->kvsns_config); retval = fsal_attach_export(fsal_hdl, &myself->export.exports); if (retval != 0) goto err_locked; /* seriously bad */ myself->export.fsal = fsal_hdl; op_ctx->fsal_export = &myself->export; myself->pnfs_ds_enabled = myself->export.exp_ops.fs_supports(&myself->export, fso_pnfs_ds_supported) && myself->pnfs_param.pnfs_enabled; myself->pnfs_mds_enabled = myself->export.exp_ops.fs_supports(&myself->export, fso_pnfs_mds_supported) && myself->pnfs_param.pnfs_enabled; if (myself->pnfs_ds_enabled) { struct fsal_pnfs_ds *pds = NULL; status = fsal_hdl->m_ops.create_fsal_pnfs_ds(fsal_hdl, parse_node, &pds); if (status.major != ERR_FSAL_NO_ERROR) goto err_locked; /* special case: server_id matches export_id */ pds->id_servers = op_ctx->ctx_export->export_id; pds->mds_export = op_ctx->ctx_export; if (!pnfs_ds_insert(pds)) { LogCrit(COMPONENT_CONFIG, "Server id %d already in use.", pds->id_servers); status.major = ERR_FSAL_EXIST; fsal_pnfs_ds_fini(pds); gsh_free(pds); goto err_locked; } LogInfo(COMPONENT_FSAL, "kvsfs_fsal_create: pnfs DS was enabled"); } if (myself->pnfs_mds_enabled) { LogInfo(COMPONENT_FSAL, "kvsfs_fsal_create: pnfs MDS was enabled"); export_ops_pnfs(&myself->export.exp_ops); } return fsalstat(ERR_FSAL_NO_ERROR, 0); err_locked: if (myself->export.fsal != NULL) fsal_detach_export(fsal_hdl, &myself->export.exports); errout: /* elvis has left the building */ gsh_free(myself); return fsalstat(fsal_error, retval); } nfs-ganesha-6.5/src/FSAL/FSAL_KVSFS/kvsfs_file.c000066400000000000000000000642511473756622300211210ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributor : Philippe DENIEL philippe.deniel@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * \file file.c * \brief File I/O methods for KVSFS module */ #include "config.h" #include #include "fsal.h" #include "kvsfs_fsal_internal.h" #include "FSAL/access_check.h" #include "fsal_convert.h" #include #include #include "FSAL/fsal_commonlib.h" #include "kvsfs_methods.h" #include static fsal_status_t kvsfs_open_by_handle(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, fsal_verifier_t verifier, struct fsal_attrlist *attrs_out) { struct kvsfs_fsal_obj_handle *myself; fsal_status_t status; struct kvsfs_fd *my_fd = NULL; struct fsal_fd *fsal_fd; int retval = 0; kvsns_cred_t cred; fsal_openflags_t old_openflags; bool truncated = openflags & FSAL_O_TRUNC; myself = container_of(obj_hdl, struct kvsfs_fsal_obj_handle, obj_handle); if (state != NULL) my_fd = &container_of(state, struct kvsfs_state_fd, state) ->kvsfs_fd; else my_fd = &myself->u.file.fd; fsal_fd = &my_fd->fsal_fd; /* Indicate we want to do fd work (can't fail since not reclaiming) */ fsal_start_fd_work_no_reclaim(fsal_fd); if (state != NULL) { /* Prepare to take the share reservation, but only if we are * called with a valid state (if state is NULL the caller is a * stateless create such as NFS v3 CREATE and we're just going * to ignore share reservation stuff). */ /* Now that we have the mutex, and no I/O is in progress so we * have exclusive access to the share's fsal_fd, we can look at * its openflags. We also need to work the share reservation so * take the obj_lock. NOTE: This is the ONLY sequence where both * a work_mutex and the obj_lock are taken, so there is no * opportunity for ABBA deadlock. * * Note that we do hold the obj_lcok over an open and a close * which is longer than normal, but the previous iteration of * the code held the obj lock (read granted) over whole I/O * operations... We don't block over I/O because we've assured * that no I/O is in progress or can start before proceeding * past the above while loop. */ PTHREAD_RWLOCK_wrlock(&obj_hdl->obj_lock); old_openflags = my_fd->fsal_fd.openflags; /* Now check the new share. */ status = check_share_conflict(&myself->u.file.share, openflags, false); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "check_share_conflict returned %s", fsal_err_txt(status)); goto exit; } } /* Check for a genuine no-op open. That means we aren't trying to * create, the file is already open in the same mode with the same * deny flags, and we aren't trying to truncate. In this case we want * to avoid bouncing the fd. In the case of JUST changing the deny mode * or an replayed exclusive create, we might bounce the fd when we could * have avoided that, but those scenarios are much less common. */ if (FSAL_O_NFS_FLAGS(openflags) == FSAL_O_NFS_FLAGS(old_openflags) && truncated == false && createmode == FSAL_NO_CREATE) { LogFullDebug(COMPONENT_FSAL, "no-op reopen2 my_fd->fd = %p openflags = %x", &my_fd->fd, openflags); goto exit; } /* No share conflict, re-open the share fd */ status = kvsfs_reopen_func(obj_hdl, openflags, fsal_fd); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "kvsfs_reopen_func returned %s", fsal_err_txt(status)); goto exit; } /* Inserts to fd_lru only if open succeeds */ if (old_openflags == FSAL_O_CLOSED) { /* This is actually an open, need to increment * appropriate counter and insert into LRU. */ insert_fd_lru(fsal_fd); } else { /* Bump up the FD in fd_lru as it was already in fd lru. */ bump_fd_lru(fsal_fd); } if (attrs_out && (createmode >= FSAL_EXCLUSIVE || truncated)) { /* NOTE: won't come in here when called from vfs_reopen2... */ /* Refresh the attributes */ struct stat stat; cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; retval = kvsns_getattr(&cred, &myself->handle->kvsfs_handle, &stat); status = posix2fsal_status(-retval); if (FSAL_IS_SUCCESS(status)) { if (attrs_out != NULL) posix2fsal_attributes_all(&stat, attrs_out); } } else if (attrs_out && attrs_out->request_mask & ATTR_RDATTR_ERR) { attrs_out->valid_mask = ATTR_RDATTR_ERR; } /* TODO - This was missing exclusive create, need verify */ if (FSAL_IS_ERROR(status)) { if (old_openflags == FSAL_O_CLOSED) { /* Now that we have decided to close this FD, * let's clean it off from fd_lru and * ensure counters are decremented. */ remove_fd_lru(fsal_fd); } /* Close fd */ (void)kvsfs_close_func(obj_hdl, fsal_fd); } exit: if (state != NULL) { if (!FSAL_IS_ERROR(status)) { /* Success, establish the new share. */ update_share_counters(&myself->u.file.share, old_openflags, openflags); } /* Release obj_lock. */ PTHREAD_RWLOCK_unlock(&obj_hdl->obj_lock); } /* Indicate we are done with fd work and signal any waiters. */ fsal_complete_fd_work(fsal_fd); return status; } static fsal_status_t kvsfs_open_by_name(struct fsal_obj_handle *obj_hdl, struct state_t *state, const char *name, fsal_openflags_t openflags, int posix_flags, fsal_verifier_t verifier, struct fsal_attrlist *attrs_out, bool *cpm_check) { struct fsal_obj_handle *temp = NULL; fsal_status_t status; /* We don't have open by name... */ status = obj_hdl->obj_ops->lookup(obj_hdl, name, &temp, NULL); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "lookup returned %s", fsal_err_txt(status)); return status; } if (temp->type != REGULAR_FILE) { if (temp->type == DIRECTORY) { /* Trying to open2 a directory */ status = fsalstat(ERR_FSAL_ISDIR, 0); } else { /* Trying to open2 any other non-regular file */ status = fsalstat(ERR_FSAL_SYMLINK, 0); } /* Release the object we found by lookup. */ temp->obj_ops->release(temp); LogFullDebug(COMPONENT_FSAL, "open returned %s", fsal_err_txt(status)); return status; } status = kvsfs_open_by_handle(temp, state, openflags, FSAL_NO_CREATE, verifier, attrs_out); *cpm_check = FSAL_IS_SUCCESS(status); if (FSAL_IS_ERROR(status)) { /* Release the object we found by lookup. */ temp->obj_ops->release(temp); LogFullDebug(COMPONENT_FSAL, "open returned %s", fsal_err_txt(status)); } return status; } /** * @brief KVSFS Function to open or reopen a fsal_fd. * * @param[in] obj_hdl File on which to operate * @param[in] openflags New mode for open * @param[out] fsal_fd File descriptor that is to be used * * @return FSAL status. */ fsal_status_t kvsfs_reopen_func(struct fsal_obj_handle *obj_hdl, fsal_openflags_t openflags, struct fsal_fd *fsal_fd) { fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct kvsfs_fd *my_fd; int posix_flags = 0; kvsns_cred_t cred; int retval = 0; struct kvsfs_fsal_obj_handle *myself; kvsns_file_open_t fd; myself = container_of(obj_hdl, struct kvsfs_fsal_obj_handle, obj_handle); my_fd = container_of(fsal_fd, struct kvsfs_fd, fsal_fd); cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; fsal2posix_openflags(openflags, &posix_flags); LogFullDebug(COMPONENT_FSAL, "my_fd->fd.ino = %llu openflags = %x, posix_flags = %x", my_fd->fd.ino, openflags, posix_flags); retval = kvsns_open(&cred, &myself->handle->kvsfs_handle, posix_flags, 0777, &fd); if (retval < 0) { LogFullDebug(COMPONENT_FSAL, "kvsns_open failed with %s", strerror(-retval)); status = posix2fsal_status(-retval); } else { if (my_fd->fsal_fd.openflags != FSAL_O_CLOSED) { /* File was previously open, close old fd */ retval = kvsns_close(&my_fd->fd); if (retval < 0) { LogFullDebug(COMPONENT_FSAL, "kvsns_close failed with %s", strerror(-retval)); /** @todo - what to do about error here... */ } } /* Save the file descriptor, make sure we only save the * open modes that actually represent the open file. */ LogFullDebug(COMPONENT_FSAL, "fd.ino = %llu, new openflags = %x", fd.ino, openflags); my_fd->fd = fd; my_fd->fsal_fd.openflags = FSAL_O_NFS_FLAGS(openflags); } if (FSAL_IS_ERROR(status)) return status; my_fd->fsal_fd.openflags = FSAL_O_NFS_FLAGS(openflags); return status; } fsal_status_t kvsfs_close_func(struct fsal_obj_handle *obj_hdl, struct fsal_fd *fd) { struct kvsfs_fd *my_fd = (struct kvsfs_fd *)fd; int retval; retval = kvsns_close(&my_fd->fd); memset(&my_fd->fd, 0, sizeof(kvsns_file_open_t)); my_fd->fsal_fd.openflags = FSAL_O_CLOSED; return fsalstat(posix2fsal_error(-retval), -retval); } /** * @brief Open a file descriptor for read or write and possibly create * * This function opens a file for read or write, possibly creating it. * If the caller is passing a state, it must hold the state_lock * exclusive. * * state can be NULL which indicates a stateless open (such as via the * NFS v3 CREATE operation), in which case the FSAL must assure protection * of any resources. If the file is being created, such protection is * simple since no one else will have access to the object yet, however, * in the case of an exclusive create, the common resources may still need * protection. * * If Name is NULL, obj_hdl is the file itself, otherwise obj_hdl is the * parent directory. * * On an exclusive create, the upper layer may know the object handle * already, so it MAY call with name == NULL. In this case, the caller * expects just to check the verifier. * * On a call with an existing object handle for an UNCHECKED create, * we can set the size to 0. * * At least the mode attribute must be set if createmode is not FSAL_NO_CREATE. * Some FSALs may still have to pass a mode on a create call for exclusive, * and even with FSAL_NO_CREATE, and empty set of attributes MUST be passed. * * If an open by name succeeds and did not result in Ganesha creating a file, * the caller will need to do a subsequent permission check to confirm the * open. This is because the permission attributes were not available * beforehand. * * @param[in] obj_hdl File to open or parent directory * @param[in,out] state state_t to use for this operation * @param[in] openflags Mode for open * @param[in] createmode Mode for create * @param[in] name Name for file if being created or opened * @param[in] attr_set Attributes to set on created file * @param[in] verifier Verifier to use for exclusive create * @param[in,out] new_obj Newly created object * @param[in,out] attrs_out Newly created object attributes * @param[in,out] caller_perm_check The caller must do a permission check * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @return FSAL status. */ fsal_status_t kvsfs_open2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attr_set, fsal_verifier_t verifier, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct fsal_export *export = op_ctx->fsal_export; struct kvsfs_fsal_obj_handle *hdl = NULL; struct kvsfs_file_handle fh; int posix_flags = 0; bool created = false; fsal_status_t status; mode_t unix_mode; LogAttrlist(COMPONENT_FSAL, NIV_FULL_DEBUG, "attrs ", attr_set, false); fsal2posix_openflags(openflags, &posix_flags); if (createmode >= FSAL_EXCLUSIVE) /* Now fixup attrs for verifier if exclusive create */ set_common_verifier(attr_set, verifier, false); if (name == NULL) { status = kvsfs_open_by_handle(obj_hdl, state, openflags, createmode, verifier, attrs_out); *caller_perm_check = FSAL_IS_SUCCESS(status); return status; } /* In this path where we are opening by name, we can't check share * reservation yet since we don't have an object_handle yet. If we *indeed create the object handle (there is no race with another * open by name), then there CAN NOT be a share conflict, otherwise * the share conflict will be resolved when the object handles are * merged. */ /* Non creation case, libgpfs doesn't have open by name so we * have to do a lookup and then handle as an open by handle. */ if (createmode == FSAL_NO_CREATE) return kvsfs_open_by_name(obj_hdl, state, name, openflags, posix_flags, verifier, attrs_out, caller_perm_check); /** @todo: to proceed past here, we need a struct fsal_attrlist in order * to create the fsal_obj_handle, so if it actually is NULL (it * will actually never be since mdcache will always ask for * attributes) we really should create a temporary fsal_attrlist * ... */ posix_flags |= O_CREAT; /* And if we are at least FSAL_GUARDED, do an O_EXCL create. */ if (createmode >= FSAL_GUARDED) posix_flags |= O_EXCL; /* Fetch the mode attribute to use in the openat system call. */ unix_mode = fsal2unix_mode(attr_set->mode) & ~export->exp_ops.fs_umask(export); /* Don't set the mode if we later set the attributes */ FSAL_UNSET_MASK(attr_set->valid_mask, ATTR_MODE); if (createmode == FSAL_UNCHECKED && (attr_set->valid_mask != 0)) { /* If we have FSAL_UNCHECKED and want to set more attributes * than the mode, we attempt an O_EXCL create first, if that * succeeds, then we will be allowed to set the additional * attributes, otherwise, we don't know we created the file * and this can NOT set the attributes. */ posix_flags |= O_EXCL; } status = kvsfs_create2(obj_hdl, name, op_ctx, unix_mode, &fh, posix_flags, attrs_out); if (status.major == ERR_FSAL_EXIST && createmode == FSAL_UNCHECKED && (posix_flags & O_EXCL) != 0) { /* If we tried to create O_EXCL to set attributes and * failed. Remove O_EXCL and retry, also remember not * to set attributes. We still try O_CREAT again just * in case file disappears out from under us. * * Note that because we have dropped O_EXCL, later on we * will not assume we created the file, and thus will * not set additional attributes. We don't need to * separately track the condition of not wanting to set * attributes. */ posix_flags &= ~O_EXCL; status = kvsfs_create2(obj_hdl, name, op_ctx, unix_mode, &fh, posix_flags, attrs_out); } if (FSAL_IS_ERROR(status)) return status; /* Remember if we were responsible for creating the file. * Note that in an UNCHECKED retry we MIGHT have re-created the * file and won't remember that. Oh well, so in that rare case we * leak a partially created file if we have a subsequent error in here. * Since we were able to do the permission check even if we were not * creating the file, let the caller know the permission check has * already been done. Note it IS possible in the case of a race between * an UNCHECKED open and an external unlink, we did create the file. */ created = (posix_flags & O_EXCL) != 0; *caller_perm_check = false; /* Check if the object type is SYMBOLIC_LINK for a state object. * If yes, then give error ERR_FSAL_SYMLINK. */ if (state != NULL && attrs_out != NULL && attrs_out->type != REGULAR_FILE) { LogDebug(COMPONENT_FSAL, "Trying to open a non-regular file"); if (attrs_out->type == DIRECTORY) { /* Trying to open2 a directory */ status = fsalstat(ERR_FSAL_ISDIR, 0); } else { /* Trying to open2 any other non-regular file */ status = fsalstat(ERR_FSAL_SYMLINK, 0); } goto fileerr; } /* allocate an obj_handle and fill it up */ hdl = kvsfs_alloc_handle(&fh, attrs_out, NULL, export); if (hdl == NULL) { status = fsalstat(posix2fsal_error(ENOMEM), ENOMEM); goto fileerr; } *new_obj = &hdl->obj_handle; if (created && attr_set->valid_mask != 0) { /* Set attributes using our newly opened file descriptor as the * share_fd if there are any left to set (mode and truncate * have already been handled). * * Note that we only set the attributes if we were responsible * for creating the file. */ status = (*new_obj)->obj_ops->setattr2(*new_obj, false, state, attr_set); if (FSAL_IS_ERROR(status)) goto fileerr; if (attrs_out != NULL) { status = (*new_obj)->obj_ops->getattrs(*new_obj, attrs_out); if (FSAL_IS_ERROR(status) && (attrs_out->request_mask & ATTR_RDATTR_ERR) == 0) /* Get attributes failed and caller expected * to get the attributes. Otherwise continue * with attrs_out indicating ATTR_RDATTR_ERR. */ goto fileerr; } } /* Restore posix_flags as it was modified for create above */ fsal2posix_openflags(openflags, &posix_flags); /* We created a file with the caller's credentials active, so as such * permission check was done. So we don't need the caller to do * permission check again (for that we have already set * *caller_perm_check=false). Don't modify the value at * caller_perm_check. */ status = kvsfs_open_by_handle(&hdl->obj_handle, state, openflags, createmode, verifier, attrs_out); fileerr: if (hdl != NULL) { /* Release the handle we just allocated. */ (*new_obj)->obj_ops->release(*new_obj); *new_obj = NULL; } if (created) { fsal_status_t status2; kvsns_cred_t cred; struct kvsfs_fsal_obj_handle *myself; int retval = 0; myself = container_of(obj_hdl, struct kvsfs_fsal_obj_handle, obj_handle); cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; /* Remove the file we just created */ retval = kvsns_unlink(&cred, &myself->handle->kvsfs_handle, (char *)name); status2 = fsalstat(posix2fsal_error(-retval), -retval); if (FSAL_IS_ERROR(status2)) { LogEvent(COMPONENT_FSAL, "kvsns_unlink failed, error: %s", msg_fsal_err(status2.major)); } } return status; } /** * @brief Return open status of a state. * * This function returns open flags representing the current open * status for a state. The st_lock must be held. * * @param[in] obj_hdl File on which to operate * @param[in] state File state to interrogate * * @retval Flags representing current open status */ fsal_openflags_t kvsfs_status2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { struct kvsfs_fd *my_fd = &((struct kvsfs_state_fd *)state)->kvsfs_fd; return my_fd->fsal_fd.openflags; } /** * @brief Re-open a file that may be already opened * * This function supports changing the access mode of a share reservation and * thus should only be called with a share state. The state_lock must be held. * * This MAY be used to open a file the first time if there is no need for * open by name or create semantics. One example would be 9P lopen. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] openflags Mode for re-open * * @return FSAL status. */ fsal_status_t kvsfs_reopen2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags) { return kvsfs_open_by_handle(obj_hdl, state, openflags, FSAL_NO_CREATE, NULL, NULL); } /** * @brief Commit written data * * This function flushes possibly buffered data to a file. This method * differs from commit due to the need to interact with share reservations * and the fact that the FSAL manages the state of "file descriptors". The * FSAL must be able to perform this operation without being passed a specific * state. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] offset Start of range to commit * @param[in] len Length of range to commit * * @return FSAL status. */ fsal_status_t kvsfs_commit2(struct fsal_obj_handle *obj_hdl, /* sync */ off_t offset, size_t len) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Manage closing a file when a state is no longer needed. * * When the upper layers are ready to dispense with a state, this method is * called to allow the FSAL to close any file descriptors or release any other * resources associated with the state. A call to free_state should be assumed * to follow soon. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * * @return FSAL status. */ fsal_status_t kvsfs_close2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { struct kvsfs_fsal_obj_handle *myself; struct kvsfs_fd *my_fd = NULL; assert(obj_hdl->type == REGULAR_FILE); assert(state != NULL); myself = container_of(obj_hdl, struct kvsfs_fsal_obj_handle, obj_handle); my_fd = &container_of(state, struct kvsfs_state_fd, state)->kvsfs_fd; if (state->state_type == STATE_TYPE_SHARE || state->state_type == STATE_TYPE_NLM_SHARE || state->state_type == STATE_TYPE_9P_FID) { /* This is a share state, we must update the share counters */ update_share_counters_locked(obj_hdl, &myself->u.file.share, my_fd->fsal_fd.openflags, FSAL_O_CLOSED); } return close_fsal_fd(obj_hdl, &my_fd->fsal_fd, false); } void kvsfs_read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg) { fsal_status_t status, status2; ssize_t nb_read; uint64_t offset = read_arg->offset; kvsns_cred_t cred; int i; struct kvsfs_fd *my_fd; struct kvsfs_fd temp_fd = { FSAL_FD_INIT, KVSNS_FILE_OPEN_INIT }; struct fsal_fd *out_fd; struct kvsfs_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct kvsfs_fsal_obj_handle, obj_handle); cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; if (read_arg->info != NULL) { done_cb(obj_hdl, fsalstat(ERR_FSAL_NOTSUPP, 0), read_arg, caller_arg); return; } /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->u.file.fd.fsal_fd, &temp_fd.fsal_fd, read_arg->state, FSAL_O_READ, false, NULL, bypass, &myself->u.file.share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct kvsfs_fd, fsal_fd); for (i = 0; i < read_arg->iov_count; i++) { nb_read = kvsns_read(&cred, &my_fd->fd, read_arg->iov[i].iov_base, read_arg->iov[i].iov_len, offset); if (nb_read < 0) { status = posix2fsal_status(-nb_read); break; } read_arg->io_amount += nb_read; offset += nb_read; } read_arg->end_of_file = (read_arg->io_amount == 0); status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (read_arg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->u.file.share, FSAL_O_READ, FSAL_O_CLOSED); } exit: done_cb(obj_hdl, status, read_arg, caller_arg); } void kvsfs_write2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *caller_arg) { fsal_status_t status, status2; ssize_t nb_written; uint64_t offset = write_arg->offset; kvsns_cred_t cred; int i; struct kvsfs_fd *my_fd; struct kvsfs_fd temp_fd = { FSAL_FD_INIT, KVSNS_FILE_OPEN_INIT }; struct fsal_fd *out_fd; struct kvsfs_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct kvsfs_fsal_obj_handle, obj_handle); cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; if (write_arg->info) return done_cb(obj_hdl, fsalstat(ERR_FSAL_NOTSUPP, 0), write_arg, caller_arg); /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->u.file.fd.fsal_fd, &temp_fd.fsal_fd, write_arg->state, FSAL_O_WRITE, false, NULL, bypass, &myself->u.file.share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct kvsfs_fd, fsal_fd); for (i = 0; i < write_arg->iov_count; i++) { nb_written = kvsns_write(&cred, &my_fd->fd, write_arg->iov[i].iov_base, write_arg->iov[i].iov_len, offset); if (nb_written < 0) { status = posix2fsal_status(-nb_written); break; } write_arg->io_amount += nb_written; offset += nb_written; } status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (write_arg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->u.file.share, FSAL_O_WRITE, FSAL_O_CLOSED); } exit: done_cb(obj_hdl, status, write_arg, caller_arg); } nfs-ganesha-6.5/src/FSAL/FSAL_KVSFS/kvsfs_fsal_internal.c000066400000000000000000000027111473756622300230140ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: */ /** * * \file fsal_internal.c * \date $Date: 2006/02/08 12:46:59 $ * \brief Defines the data that are to be * accessed as extern by the fsal modules * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributor : Philippe DENIEL philippe.deniel@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #define FSAL_INTERNAL_C #include "config.h" #include "fsal.h" #include "kvsfs_fsal_internal.h" #include "abstract_mem.h" #include /* static filesystem info. * The access is thread-safe because * it is read-only, except during initialization. */ struct fsal_staticfsinfo_t global_fs_info; nfs-ganesha-6.5/src/FSAL/FSAL_KVSFS/kvsfs_fsal_internal.h000066400000000000000000000055441473756622300230300ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /** * * \file fsal_internal.h * \date $Date: 2006/01/24 13:45:37 $ * \brief Extern definitions for variables that are * defined in fsal_internal.c. * */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributor : Philippe DENIEL philippe.deniel@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #ifndef _FSAL_INTERNAL_H #define _FSAL_INTERNAL_H #include "fsal.h" #include /* linkage to the exports and handle ops initializers */ void kvsfs_export_ops_init(struct export_ops *ops); void kvsfs_handle_ops_init(struct fsal_obj_ops *ops); typedef struct kvsfs_file_handle { kvsns_ino_t kvsfs_handle; } kvsfs_file_handle_t; struct kvsfs_ds { struct fsal_ds_handle ds; /*< Public DS handle */ struct kvsfs_file_handle wire; /*< Wire data */ struct kvsfs_filesystem *kvsfs_fs; /*< Related kvsfs filesystem */ bool connected; /*< True if the handle has been connected */ }; #define KVSNS_DEFAULT_CONFIG "/etc/kvsns.d/kvsns.ini" /* defined the set of attributes supported with POSIX */ #define KVSFS_SUPPORTED_ATTRIBUTES \ (ATTR_TYPE | ATTR_SIZE | ATTR_FSID | ATTR_FILEID | ATTR_MODE | \ ATTR_NUMLINKS | ATTR_OWNER | ATTR_GROUP | ATTR_ATIME | ATTR_RAWDEV | \ ATTR_CTIME | ATTR_MTIME | ATTR_SPACEUSED | ATTR_CHANGE) static inline size_t kvsfs_sizeof_handle(struct kvsfs_file_handle *hdl) { return (size_t)sizeof(struct kvsfs_file_handle); } /* the following variables must not be defined in fsal_internal.c */ #ifndef FSAL_INTERNAL_C /* static filesystem info. * read access only. */ extern struct fsal_staticfsinfo_t global_fs_info; /* KVSFS methods for pnfs */ nfsstat4 kvsfs_getdeviceinfo(struct fsal_module *fsal_hdl, XDR *da_addr_body, const layouttype4 type, const struct pnfs_deviceid *deviceid); size_t kvsfs_fs_da_addr_size(struct fsal_module *fsal_hdl); void export_ops_pnfs(struct export_ops *ops); void handle_ops_pnfs(struct fsal_obj_ops *ops); void kvsfs_pnfs_ds_ops_init(struct fsal_pnfs_ds_ops *ops); #endif #endif nfs-ganesha-6.5/src/FSAL/FSAL_KVSFS/kvsfs_handle.c000066400000000000000000000710401473756622300214270ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /* handle.c * KVSFS (via KVSNS) object (file|dir) handle object */ #include "config.h" #include /* used for 'dirname' */ #include #include #include #include #include #include #include "gsh_list.h" #include "fsal.h" #include "kvsfs_fsal_internal.h" #include "fsal_convert.h" #include "FSAL/fsal_config.h" #include "FSAL/fsal_commonlib.h" #include "kvsfs_methods.h" #include /* helpers */ /* alloc_handle * allocate and fill in a handle * this uses malloc/free for the time being. */ struct kvsfs_fsal_obj_handle *kvsfs_alloc_handle(struct kvsfs_file_handle *fh, struct fsal_attrlist *attr, const char *link_content, struct fsal_export *exp_hdl) { struct kvsfs_fsal_export *myself = container_of(exp_hdl, struct kvsfs_fsal_export, export); struct kvsfs_fsal_obj_handle *hdl; struct kvsfs_fsal_module *my_module; my_module = container_of(exp_hdl->fsal, struct kvsfs_fsal_module, fsal); hdl = gsh_malloc(sizeof(struct kvsfs_fsal_obj_handle) + sizeof(struct kvsfs_file_handle)); memset(hdl, 0, (sizeof(struct kvsfs_fsal_obj_handle) + sizeof(struct kvsfs_file_handle))); hdl->handle = (struct kvsfs_file_handle *)&hdl[1]; memcpy(hdl->handle, fh, sizeof(struct kvsfs_file_handle)); hdl->obj_handle.type = attr->type; hdl->obj_handle.fsid = attr->fsid; hdl->obj_handle.fileid = attr->fileid; if ((hdl->obj_handle.type == SYMBOLIC_LINK) && (link_content != NULL)) { size_t len = strlen(link_content) + 1; hdl->u.symlink.link_content = gsh_malloc(len); memcpy(hdl->u.symlink.link_content, link_content, len); hdl->u.symlink.link_size = len; } fsal_obj_handle_init(&hdl->obj_handle, exp_hdl, attr->type, true); if (hdl->obj_handle.type == REGULAR_FILE) { init_fsal_fd(&hdl->u.file.fd.fsal_fd, FSAL_FD_GLOBAL, op_ctx->fsal_export); } hdl->obj_handle.obj_ops = &my_module->handle_ops; if (myself->pnfs_mds_enabled) handle_ops_pnfs(hdl->obj_handle.obj_ops); return hdl; } static struct kvsfs_fsal_obj_handle *alloc_handle(struct kvsfs_file_handle *fh, struct stat *stat, const char *link_content, struct fsal_export *exp_hdl) { struct fsal_attrlist attr; posix2fsal_attributes_all(stat, &attr); return kvsfs_alloc_handle(fh, &attr, link_content, exp_hdl); } /* handle methods */ /* lookup * deprecated NULL parent && NULL path implies root handle */ static fsal_status_t kvsfs_lookup(struct fsal_obj_handle *parent, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { struct kvsfs_fsal_obj_handle *parent_hdl, *hdl; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int retval; struct stat stat; struct kvsfs_file_handle fh; kvsns_cred_t cred; kvsns_ino_t object; if (!path) return fsalstat(ERR_FSAL_FAULT, 0); memset(&fh, 0, sizeof(struct kvsfs_file_handle)); parent_hdl = container_of(parent, struct kvsfs_fsal_obj_handle, obj_handle); LogFullDebug(COMPONENT_FSAL, "lookup: %d/%s", (unsigned int)parent_hdl->handle->kvsfs_handle, path); if (!fsal_obj_handle_is(parent, DIRECTORY)) { LogCrit(COMPONENT_FSAL, "Parent handle is not a directory. hdl = 0x%p", parent); return fsalstat(ERR_FSAL_NOTDIR, 0); } cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; /* Do we lookup for parent or current directory ? */ if (!strcmp(path, ".")) { retval = 0; object = parent_hdl->handle->kvsfs_handle; } else if (!strcmp(path, "..")) retval = kvsns_lookupp(&cred, &parent_hdl->handle->kvsfs_handle, &object); else retval = kvsns_lookup(&cred, &parent_hdl->handle->kvsfs_handle, (char *)path, &object); if (retval) { fsal_error = posix2fsal_error(-retval); goto errout; } retval = kvsns_getattr(&cred, &object, &stat); if (retval) { fsal_error = posix2fsal_error(-retval); goto errout; } /* allocate an obj_handle and fill it up */ hdl = alloc_handle(&fh, &stat, NULL, op_ctx->fsal_export); *handle = &hdl->obj_handle; hdl->handle->kvsfs_handle = object; if (attrs_out != NULL) posix2fsal_attributes_all(&stat, attrs_out); return fsalstat(ERR_FSAL_NO_ERROR, 0); errout: return fsalstat(fsal_error, -retval); } /* lookup_path * should not be used for "/" only is exported */ fsal_status_t kvsfs_lookup_path(struct fsal_export *exp_hdl, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { kvsns_ino_t object; int rc = 0; struct kvsfs_file_handle fh; struct stat stat; kvsns_cred_t cred; struct kvsfs_fsal_obj_handle *hdl; if (strcmp(path, "/")) { LogMajor(COMPONENT_FSAL, "KVSFS can only mount /, no subdirectory"); return fsalstat(ERR_FSAL_NOTSUPP, 0); } LogFullDebug(COMPONENT_FSAL, "lookup_path: %s", path); rc = kvsns_get_root(&object); if (rc != 0) return fsalstat(posix2fsal_error(-rc), -rc); cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; rc = kvsns_getattr(&cred, &object, &stat); if (rc != 0) return fsalstat(posix2fsal_error(-rc), -rc); fh.kvsfs_handle = object; hdl = alloc_handle(&fh, &stat, NULL, exp_hdl); *handle = &hdl->obj_handle; if (attrs_out != NULL) posix2fsal_attributes_all(&stat, attrs_out); return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t kvsfs_create2(struct fsal_obj_handle *dir_hdl, const char *filename, const struct req_op_context *op_ctx, mode_t unix_mode, struct kvsfs_file_handle *kvsfs_fh, int posix_flags, struct fsal_attrlist *fsal_attr) { struct kvsfs_fsal_obj_handle *myself, *hdl; int retval = 0; kvsns_cred_t cred; kvsns_ino_t object; struct stat stat; /* note : fsal_attr is optional. */ if (!dir_hdl || !op_ctx || !kvsfs_fh || !filename) return fsalstat(ERR_FSAL_FAULT, 0); LogFullDebug(COMPONENT_FSAL, "Creation mode: 0%o", unix_mode); if (!fsal_obj_handle_is(dir_hdl, DIRECTORY)) { LogCrit(COMPONENT_FSAL, "Parent handle is not a directory. hdl = 0x%p", dir_hdl); return fsalstat(ERR_FSAL_NOTDIR, 0); } memset(kvsfs_fh, 0, sizeof(struct kvsfs_file_handle)); myself = container_of(dir_hdl, struct kvsfs_fsal_obj_handle, obj_handle); LogFullDebug(COMPONENT_FSAL, "create2: %d/%s mode=0%o", (unsigned int)myself->handle->kvsfs_handle, filename, unix_mode); cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; retval = kvsns_creat(&cred, &myself->handle->kvsfs_handle, (char *)filename, unix_mode, &object); if (retval) goto fileerr; retval = kvsns_getattr(&cred, &object, &stat); if (retval) goto fileerr; /* allocate an obj_handle and fill it up */ hdl = alloc_handle(kvsfs_fh, &stat, NULL, op_ctx->fsal_export); /* >> set output handle << */ hdl->handle->kvsfs_handle = object; kvsfs_fh->kvsfs_handle = object; /* Useful ? */ if (fsal_attr != NULL) posix2fsal_attributes_all(&stat, fsal_attr); return fsalstat(ERR_FSAL_NO_ERROR, 0); fileerr: return fsalstat(posix2fsal_error(-retval), -retval); } static fsal_status_t kvsfs_mkdir(struct fsal_obj_handle *dir_hdl, const char *name, struct fsal_attrlist *attrib, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct kvsfs_fsal_obj_handle *myself, *hdl; int retval = 0; struct kvsfs_file_handle fh; kvsns_cred_t cred; struct stat stat; *handle = NULL; /* poison it */ if (!fsal_obj_handle_is(dir_hdl, DIRECTORY)) { LogCrit(COMPONENT_FSAL, "Parent handle is not a directory. hdl = 0x%p", dir_hdl); return fsalstat(ERR_FSAL_NOTDIR, 0); } memset(&fh, 0, sizeof(struct kvsfs_file_handle)); myself = container_of(dir_hdl, struct kvsfs_fsal_obj_handle, obj_handle); LogFullDebug(COMPONENT_FSAL, "mkdir: %d/%s", (unsigned int)myself->handle->kvsfs_handle, name); cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; retval = kvsns_mkdir(&cred, &myself->handle->kvsfs_handle, (char *)name, fsal2unix_mode(attrib->mode), &fh.kvsfs_handle); if (retval) goto fileerr; retval = kvsns_getattr(&cred, &fh.kvsfs_handle, &stat); if (retval) goto fileerr; /* allocate an obj_handle and fill it up */ hdl = alloc_handle(&fh, &stat, NULL, op_ctx->fsal_export); /* >> set output handle << */ *handle = &hdl->obj_handle; if (attrs_out != NULL) posix2fsal_attributes_all(&stat, attrs_out); return fsalstat(ERR_FSAL_NO_ERROR, 0); fileerr: return fsalstat(posix2fsal_error(-retval), -retval); } static fsal_status_t kvsfs_makenode(struct fsal_obj_handle *dir_hdl, const char *name, object_file_type_t nodetype, /* IN */ struct fsal_attrlist *attrib, struct fsal_obj_handle **handle, struct fsal_attrlist *attrsout, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } /*! \brief Merge a duplicate handle with an original handle * * * * \see fsal_api.h for more information * */ static fsal_status_t kvsfs_merge(struct fsal_obj_handle *orig_hdl, struct fsal_obj_handle *dupe_hdl) { fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0); if (orig_hdl->type == REGULAR_FILE && dupe_hdl->type == REGULAR_FILE) { struct kvsfs_fsal_obj_handle *orig; struct kvsfs_fsal_obj_handle *dupe; orig = container_of(orig_hdl, struct kvsfs_fsal_obj_handle, obj_handle); dupe = container_of(dupe_hdl, struct kvsfs_fsal_obj_handle, obj_handle); /* This can block over an I/O operation. */ status = merge_share(orig_hdl, &orig->u.file.share, &dupe->u.file.share); } return status; } /** makesymlink * Note that we do not set mode bits on symlinks for Linux/POSIX * They are not really settable in the kernel and are not checked * anyway (default is 0777) because open uses that target's mode */ static fsal_status_t kvsfs_makesymlink(struct fsal_obj_handle *dir_hdl, const char *name, const char *link_path, struct fsal_attrlist *attrib, struct fsal_obj_handle **handle, struct fsal_attrlist *attrsout, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct kvsfs_fsal_obj_handle *myself, *hdl; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int retval = 0; kvsns_cred_t cred; kvsns_ino_t object; struct stat stat; struct kvsfs_file_handle fh; *handle = NULL; /* poison it first */ if (!fsal_obj_handle_is(dir_hdl, DIRECTORY)) { LogCrit(COMPONENT_FSAL, "Parent handle is not a directory. hdl = 0x%p", dir_hdl); return fsalstat(ERR_FSAL_NOTDIR, 0); } memset(&fh, 0, sizeof(struct kvsfs_file_handle)); myself = container_of(dir_hdl, struct kvsfs_fsal_obj_handle, obj_handle); LogFullDebug(COMPONENT_FSAL, "makesymlink: %d/%s -> %s", (unsigned int)myself->handle->kvsfs_handle, name, link_path); cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; retval = kvsns_symlink(&cred, &myself->handle->kvsfs_handle, (char *)name, (char *)link_path, &object); if (retval) goto err; retval = kvsns_getattr(&cred, &object, &stat); if (retval) goto err; /* allocate an obj_handle and fill it up */ hdl = alloc_handle(&fh, &stat, link_path, op_ctx->fsal_export); *handle = &hdl->obj_handle; hdl->handle->kvsfs_handle = object; if (attrsout != NULL) posix2fsal_attributes_all(&stat, attrsout); return fsalstat(ERR_FSAL_NO_ERROR, 0); err: if (retval == -ENOENT) fsal_error = ERR_FSAL_STALE; else fsal_error = posix2fsal_error(-retval); return fsalstat(fsal_error, -retval); } static fsal_status_t kvsfs_readsymlink(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *link_content, bool refresh) { struct kvsfs_fsal_obj_handle *myself = NULL; int retval = 0; int retlink = 0; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; kvsns_cred_t cred; if (obj_hdl->type != SYMBOLIC_LINK) { fsal_error = ERR_FSAL_FAULT; goto out; } myself = container_of(obj_hdl, struct kvsfs_fsal_obj_handle, obj_handle); LogFullDebug(COMPONENT_FSAL, "readsymlink: %d", (unsigned int)myself->handle->kvsfs_handle); cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; /* The link length should be cached in the file handle */ link_content->len = fsal_default_linksize; link_content->addr = gsh_malloc(link_content->len); retlink = kvsns_readlink(&cred, &myself->handle->kvsfs_handle, link_content->addr, &link_content->len); if (retlink) { fsal_error = posix2fsal_error(-retlink); gsh_free(link_content->addr); link_content->addr = NULL; link_content->len = 0; goto out; } link_content->len = strlen(link_content->addr) + 1; out: return fsalstat(fsal_error, -retval); } static fsal_status_t kvsfs_linkfile(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *destdir_hdl, const char *name, struct fsal_attrlist *destdir_pre_attrs_out, struct fsal_attrlist *destdir_post_attrs_out) { struct kvsfs_fsal_obj_handle *myself, *destdir; int retval = 0; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; kvsns_cred_t cred; myself = container_of(obj_hdl, struct kvsfs_fsal_obj_handle, obj_handle); destdir = container_of(destdir_hdl, struct kvsfs_fsal_obj_handle, obj_handle); LogFullDebug(COMPONENT_FSAL, "linkfile: %d -> %d/%s", (unsigned int)myself->handle->kvsfs_handle, (unsigned int)destdir->handle->kvsfs_handle, name); cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; retval = kvsns_link(&cred, &myself->handle->kvsfs_handle, &destdir->handle->kvsfs_handle, (char *)name); if (retval) fsal_error = posix2fsal_error(-retval); return fsalstat(fsal_error, -retval); } #define MAX_ENTRIES 256 /** * read_dirents * read the directory and call through the callback function for * each entry. * @param dir_hdl [IN] the directory to read * @param entry_cnt [IN] limit of entries. 0 implies no limit * @param whence [IN] where to start (next) * @param dir_state [IN] pass thru of state to callback * @param cb [IN] callback function * @param eof [OUT] eof marker true == end of dir */ static fsal_status_t kvsfs_readdir(struct fsal_obj_handle *dir_hdl, fsal_cookie_t *whence, void *dir_state, fsal_readdir_cb cb, attrmask_t attrmask, bool *eof) { struct kvsfs_fsal_obj_handle *myself; int retval = 0; off_t seekloc = 0; kvsns_cred_t cred; kvsns_dentry_t dirents[MAX_ENTRIES]; unsigned int index = 0; unsigned int nb_rddir_done = 0; int size = 0; kvsns_dir_t ddir; struct fsal_attrlist attrs; fsal_status_t status; struct fsal_obj_handle *hdl; int cb_rc; fsal_cookie_t cookie; if (whence != NULL) seekloc = (off_t)*whence; /* Think about '.' and '..' */ #define DOTS_OFFSET 2 if (seekloc > 0) seekloc = seekloc - DOTS_OFFSET; myself = container_of(dir_hdl, struct kvsfs_fsal_obj_handle, obj_handle); cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; retval = kvsns_opendir(&cred, &myself->handle->kvsfs_handle, &ddir); if (retval < 0) goto out; /* Open the directory */ nb_rddir_done = 0; *eof = false; do { size = MAX_ENTRIES; memset(dirents, 0, sizeof(kvsns_dentry_t) * MAX_ENTRIES); retval = kvsns_readdir(&cred, &ddir, seekloc, dirents, &size); if (retval) goto out; if (size < MAX_ENTRIES) *eof = true; for (index = 0; index < size; index++) { fsal_prepare_attrs(&attrs, attrmask); status = kvsfs_lookup(dir_hdl, dirents[index].name, &hdl, &attrs); if (FSAL_IS_ERROR(status)) { kvsns_closedir(&ddir); return status; } /* callback to mdcache */ cookie = seekloc + index + (nb_rddir_done * MAX_ENTRIES) + DOTS_OFFSET + 1; cb_rc = cb(dirents[index].name, hdl, &attrs, dir_state, cookie); LogFullDebug(COMPONENT_FSAL, "readdir: %s cookie=%llu cb_rc=%d", dirents[index].name, (unsigned long long)cookie, cb_rc); fsal_release_attrs(&attrs); if (cb_rc >= DIR_READAHEAD) { /* if we did not reach the last entry in * dirents array, then EOF is not reached */ if (*eof == true && index < size) *eof = false; goto done; } } seekloc += MAX_ENTRIES; nb_rddir_done += 1; } while (size != 0 && *eof == false); done: retval = kvsns_closedir(&ddir); if (retval < 0) goto out; return fsalstat(ERR_FSAL_NO_ERROR, 0); out: return fsalstat(posix2fsal_error(-retval), -retval); } static fsal_status_t kvsfs_rename(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *olddir_hdl, const char *old_name, struct fsal_obj_handle *newdir_hdl, const char *new_name, struct fsal_attrlist *olddir_pre_attrs_out, struct fsal_attrlist *olddir_post_attrs_out, struct fsal_attrlist *newdir_pre_attrs_out, struct fsal_attrlist *newdir_post_attrs_out) { struct kvsfs_fsal_obj_handle *olddir, *newdir; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int retval = 0; kvsns_cred_t cred; olddir = container_of(olddir_hdl, struct kvsfs_fsal_obj_handle, obj_handle); newdir = container_of(newdir_hdl, struct kvsfs_fsal_obj_handle, obj_handle); cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; retval = kvsns_rename(&cred, &olddir->handle->kvsfs_handle, (char *)old_name, &newdir->handle->kvsfs_handle, (char *)new_name); if (retval) fsal_error = posix2fsal_error(-retval); return fsalstat(fsal_error, -retval); } /* FIXME: attributes are now merged into fsal_obj_handle. This * spreads everywhere these methods are used. eventually deprecate * everywhere except where we explicitly want to refresh them. * NOTE: this is done under protection of the attributes rwlock in the * cache entry. */ static fsal_status_t kvsfs_getattrs(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs) { struct kvsfs_fsal_obj_handle *myself; struct stat stat; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int retval = 0; kvsns_cred_t cred; myself = container_of(obj_hdl, struct kvsfs_fsal_obj_handle, obj_handle); cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; retval = kvsns_getattr(&cred, &myself->handle->kvsfs_handle, &stat); if (retval) goto errout; /* convert attributes */ if (attrs != NULL) posix2fsal_attributes_all(&stat, attrs); goto out; errout: if (retval == ENOENT) fsal_error = ERR_FSAL_STALE; else fsal_error = posix2fsal_error(-retval); out: return fsalstat(fsal_error, -retval); } /* * NOTE: this is done under protection of the attributes rwlock * in the cache entry. */ static fsal_status_t kvsfs_setattr2(struct fsal_obj_handle *obj_hdl, bool bypass, struct state_t *state, struct fsal_attrlist *attrs) { struct kvsfs_fsal_obj_handle *myself; struct stat stats = { 0 }; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int retval = 0; int flags = 0; kvsns_cred_t cred; /* apply umask, if mode attribute is to be changed */ if (FSAL_TEST_MASK(attrs->valid_mask, ATTR_MODE)) attrs->mode &= ~op_ctx->fsal_export->exp_ops.fs_umask( op_ctx->fsal_export); myself = container_of(obj_hdl, struct kvsfs_fsal_obj_handle, obj_handle); /* First, check that FSAL attributes */ if (FSAL_TEST_MASK(attrs->valid_mask, ATTR_SIZE)) { if (obj_hdl->type != REGULAR_FILE) { fsal_error = ERR_FSAL_INVAL; return fsalstat(fsal_error, retval); } flags |= STAT_SIZE_SET; stats.st_size = attrs->filesize; } if (FSAL_TEST_MASK(attrs->valid_mask, ATTR_MODE)) { flags |= STAT_MODE_SET; stats.st_mode = fsal2unix_mode(attrs->mode); } if (FSAL_TEST_MASK(attrs->valid_mask, ATTR_OWNER)) { flags |= STAT_UID_SET; stats.st_uid = attrs->owner; } if (FSAL_TEST_MASK(attrs->valid_mask, ATTR_GROUP)) { flags |= STAT_GID_SET; stats.st_gid = attrs->group; } if (FSAL_TEST_MASK(attrs->valid_mask, ATTR_ATIME)) { flags |= STAT_ATIME_SET; stats.st_atime = attrs->atime.tv_sec; } if (FSAL_TEST_MASK(attrs->valid_mask, ATTR_ATIME_SERVER)) { flags |= STAT_ATIME_SET; struct timespec timestamp; retval = clock_gettime(CLOCK_REALTIME, ×tamp); if (retval != 0) goto out; stats.st_atim = timestamp; } if (FSAL_TEST_MASK(attrs->valid_mask, ATTR_MTIME)) { flags |= STAT_MTIME_SET; stats.st_mtime = attrs->mtime.tv_sec; } if (FSAL_TEST_MASK(attrs->valid_mask, ATTR_MTIME_SERVER)) { flags |= STAT_MTIME_SET; struct timespec timestamp; retval = clock_gettime(CLOCK_REALTIME, ×tamp); if (retval != 0) goto out; stats.st_mtim = timestamp; } cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; retval = kvsns_setattr(&cred, &myself->handle->kvsfs_handle, &stats, flags); if (retval) goto out; out: if (retval == 0) return fsalstat(ERR_FSAL_NO_ERROR, 0); /* Exit with an error */ fsal_error = posix2fsal_error(-retval); return fsalstat(fsal_error, -retval); } static fsal_status_t kvsfs_close(struct fsal_obj_handle *obj_hdl) { struct kvsfs_fsal_obj_handle *myself; assert(obj_hdl->type == REGULAR_FILE); myself = container_of(obj_hdl, struct kvsfs_fsal_obj_handle, obj_handle); return close_fsal_fd(obj_hdl, &myself->u.file.fd.fsal_fd, false); } /* file_unlink * unlink the named file in the directory */ static fsal_status_t kvsfs_unlink(struct fsal_obj_handle *dir_hdl, struct fsal_obj_handle *obj_hdl, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct kvsfs_fsal_obj_handle *myself; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int retval = 0; kvsns_cred_t cred; cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; myself = container_of(dir_hdl, struct kvsfs_fsal_obj_handle, obj_handle); if (obj_hdl->type == DIRECTORY) retval = kvsns_rmdir(&cred, &myself->handle->kvsfs_handle, (char *)name); else retval = kvsns_unlink(&cred, &myself->handle->kvsfs_handle, (char *)name); if (retval) fsal_error = posix2fsal_error(-retval); return fsalstat(fsal_error, -retval); } /* handle_digest * fill in the opaque f/s file handle part. * we zero the buffer to length first. This MAY already be done above * at which point, remove memset here because the caller is zeroing * the whole struct. */ static fsal_status_t kvsfs_handle_to_wire(const struct fsal_obj_handle *obj_hdl, fsal_digesttype_t output_type, struct gsh_buffdesc *fh_desc) { const struct kvsfs_fsal_obj_handle *myself; struct kvsfs_file_handle *fh; size_t fh_size; /* sanity checks */ if (!fh_desc) return fsalstat(ERR_FSAL_FAULT, 0); myself = container_of(obj_hdl, const struct kvsfs_fsal_obj_handle, obj_handle); fh = myself->handle; switch (output_type) { case FSAL_DIGEST_NFSV3: case FSAL_DIGEST_NFSV4: fh_size = kvsfs_sizeof_handle(fh); if (fh_desc->len < fh_size) goto errout; memcpy(fh_desc->addr, fh, fh_size); break; default: return fsalstat(ERR_FSAL_SERVERFAULT, 0); } fh_desc->len = fh_size; return fsalstat(ERR_FSAL_NO_ERROR, 0); errout: LogMajor(COMPONENT_FSAL, "Space too small for handle. need %lu, have %lu", fh_size, fh_desc->len); return fsalstat(ERR_FSAL_TOOSMALL, 0); } /** * @brief release object handle * * release our export first so they know we are gone */ static void kvsfs_release(struct fsal_obj_handle *obj_hdl) { struct kvsfs_fsal_obj_handle *myself; object_file_type_t type = obj_hdl->type; myself = container_of(obj_hdl, struct kvsfs_fsal_obj_handle, obj_handle); if (type == SYMBOLIC_LINK) { if (myself->u.symlink.link_content != NULL) gsh_free(myself->u.symlink.link_content); } else if (type == REGULAR_FILE) { fsal_status_t st; st = close_fsal_fd(obj_hdl, &myself->u.file.fd.fsal_fd, false); if (FSAL_IS_ERROR(st)) { LogCrit(COMPONENT_FSAL, "Could not close hdl 0x%p, status %s error %s(%d)", obj_hdl, fsal_err_txt(st), strerror(st.minor), st.minor); } destroy_fsal_fd(&myself->u.file.fd.fsal_fd); } fsal_obj_handle_fini(obj_hdl, true); gsh_free(myself); } /* export methods that create object handles */ /** * handle_to_key * return a handle descriptor into the handle in this object handle * @TODO reminder. make sure things like hash keys don't point here * after the handle is released. */ static void kvsfs_handle_to_key(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *fh_desc) { struct kvsfs_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct kvsfs_fsal_obj_handle, obj_handle); fh_desc->addr = myself->handle; fh_desc->len = kvsfs_sizeof_handle(myself->handle); } /* create_handle * Does what original FSAL_ExpandHandle did (sort of) * returns a ref counted handle to be later used in mdcache etc. * NOTE! you must release this thing when done with it! * BEWARE! Thanks to some holes in the *AT syscalls implementation, * we cannot get an fd on an AF_UNIX socket. Sorry, it just doesn't... * we could if we had the handle of the dir it is in, but this method * is for getting handles off the wire for cache entries that have LRU'd. * Ideas and/or clever hacks are welcome... */ fsal_status_t kvsfs_create_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *hdl_desc, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { struct kvsfs_fsal_obj_handle *hdl; struct kvsfs_file_handle fh; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; char link_buff[PATH_MAX]; char *link_content = NULL; struct stat stat; kvsns_cred_t cred; int retval; size_t size; *handle = NULL; /* poison it first */ if (hdl_desc->len > sizeof(struct kvsfs_file_handle)) return fsalstat(ERR_FSAL_FAULT, 0); memcpy(&fh, hdl_desc->addr, hdl_desc->len); /* struct aligned copy */ LogFullDebug(COMPONENT_FSAL, "create_handle: %d", (unsigned int)fh.kvsfs_handle); cred.uid = op_ctx->creds.caller_uid; cred.gid = op_ctx->creds.caller_gid; retval = kvsns_getattr(&cred, &fh.kvsfs_handle, &stat); if (retval) return fsalstat(posix2fsal_error(-retval), -retval); link_content = NULL; if (S_ISLNK(stat.st_mode)) { size = PATH_MAX; retval = kvsns_readlink(&cred, &fh.kvsfs_handle, link_buff, &size); if (retval) return fsalstat(posix2fsal_error(-retval), -retval); link_content = link_buff; } hdl = alloc_handle(&fh, &stat, link_content, exp_hdl); *handle = &hdl->obj_handle; if (attrs_out != NULL) posix2fsal_attributes_all(&stat, attrs_out); return fsalstat(fsal_error, 0); } void kvsfs_handle_ops_init(struct fsal_obj_ops *ops) { fsal_default_obj_ops_init(ops); ops->release = kvsfs_release; ops->merge = kvsfs_merge; ops->lookup = kvsfs_lookup; ops->mkdir = kvsfs_mkdir; ops->mknode = kvsfs_makenode; ops->readdir = kvsfs_readdir; ops->symlink = kvsfs_makesymlink; ops->readlink = kvsfs_readsymlink; ops->getattrs = kvsfs_getattrs; ops->link = kvsfs_linkfile; ops->rename = kvsfs_rename; ops->unlink = kvsfs_unlink; ops->close = kvsfs_close; ops->handle_to_wire = kvsfs_handle_to_wire; ops->handle_to_key = kvsfs_handle_to_key; ops->open2 = kvsfs_open2; ops->status2 = kvsfs_status2; ops->reopen2 = kvsfs_reopen2; ops->read2 = kvsfs_read2; ops->write2 = kvsfs_write2; ops->commit2 = kvsfs_commit2; ops->setattr2 = kvsfs_setattr2; ops->close2 = kvsfs_close2; ops->reopen_func = kvsfs_reopen_func; ops->close_func = kvsfs_close_func; // ops->create = kvsfs_create; // ops->test_access = fsal_test_access; /* xattr related functions */ ops->list_ext_attrs = kvsfs_list_ext_attrs; ops->getextattr_id_by_name = kvsfs_getextattr_id_by_name; ops->getextattr_value_by_name = kvsfs_getextattr_value_by_name; ops->getextattr_value_by_id = kvsfs_getextattr_value_by_id; ops->setextattr_value = kvsfs_setextattr_value; ops->setextattr_value_by_id = kvsfs_setextattr_value_by_id; ops->remove_extattr_by_id = kvsfs_remove_extattr_by_id; ops->remove_extattr_by_name = kvsfs_remove_extattr_by_name; } nfs-ganesha-6.5/src/FSAL/FSAL_KVSFS/kvsfs_main.c000066400000000000000000000100421473756622300211130ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /* main.c * Module core functions */ #include "config.h" #include /* used for 'dirname' */ #include #include #include #include "fsal.h" #include "fsal_api.h" #include "fsal_types.h" #include "fsal_pnfs.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_init.h" #include "pnfs_utils.h" #include "kvsfs_fsal_internal.h" #include "kvsfs_methods.h" static const char myname[] = "KVSFS"; /* filesystem info for your filesystem */ struct kvsfs_fsal_module KVSFS = { .fsal = { .fs_info = { .maxfilesize = INT64_MAX, .maxlink = _POSIX_LINK_MAX, .maxnamelen = MAXNAMLEN, .maxpathlen = MAXPATHLEN, .no_trunc = true, .chown_restricted = false, .case_insensitive = false, .case_preserving = true, .link_support = true, .symlink_support = false, .lock_support = false, .lock_support_async_block = false, .named_attr = true, /* XXX */ .unique_handles = true, .acl_support = 0, .cansettime = true, .homogenous = true, .supported_attrs = KVSFS_SUPPORTED_ATTRIBUTES, .maxread = FSAL_MAXIOSIZE, .maxwrite = FSAL_MAXIOSIZE, .umask = 0, } } }; /* Module methods */ /* kvsfs_init_config * must be called with a reference taken (via lookup_fsal) */ static fsal_status_t kvsfs_init_config(struct fsal_module *fsal_hdl, config_file_t config_struct, struct config_error_type *err_type) { struct kvsfs_fsal_module *kvsfs_me = container_of(fsal_hdl, struct kvsfs_fsal_module, fsal); LogDebug(COMPONENT_FSAL, "KVSFS module setup."); display_fsinfo(fsal_hdl); LogFullDebug(COMPONENT_FSAL, "Supported attributes constant = 0x%" PRIx64, (uint64_t)KVSFS_SUPPORTED_ATTRIBUTES); LogDebug(COMPONENT_FSAL, "FSAL INIT: Supported attributes mask = 0x%" PRIx64, kvsfs_me->fsal.fs_info.supported_attrs); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* Internal KVSFS method linkage to export object */ fsal_status_t kvsfs_create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops); /* Module initialization. * Called by dlopen() to register the module * keep a private pointer to me in myself */ /* my module private storage */ MODULE_INIT void kvsfs_load(void) { int retval; struct fsal_module *myself = &KVSFS.fsal; retval = register_fsal(myself, myname, FSAL_MAJOR_VERSION, FSAL_MINOR_VERSION, FSAL_ID_KVSFS); if (retval != 0) { fprintf(stderr, "KVSFS module failed to register\n"); return; } myself->m_ops.create_export = kvsfs_create_export; myself->m_ops.init_config = kvsfs_init_config; myself->m_ops.fsal_pnfs_ds_ops = kvsfs_pnfs_ds_ops_init; myself->m_ops.getdeviceinfo = kvsfs_getdeviceinfo; myself->m_ops.fs_da_addr_size = kvsfs_fs_da_addr_size; kvsfs_handle_ops_init(&KVSFS.handle_ops); } MODULE_FINI void kvsfs_unload(void) { int retval; retval = unregister_fsal(&KVSFS.fsal); if (retval != 0) { fprintf(stderr, "KVSFS module failed to unregister"); return; } } nfs-ganesha-6.5/src/FSAL/FSAL_KVSFS/kvsfs_mds.c000066400000000000000000000306461473756622300207660ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright © 2020 CEA * Author: Philippe DENIEL * * This program 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 3 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 * 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 * */ #include "config.h" #include #include /* used for 'dirname' */ #include #include #include #include #include #include #include "gsh_list.h" #include "fsal.h" #include "kvsfs_fsal_internal.h" #include "fsal_convert.h" #include "../fsal_private.h" #include "FSAL/fsal_config.h" #include "FSAL/fsal_commonlib.h" #include "kvsfs_methods.h" #include "nfs_exports.h" #include "nfs_creds.h" #include "pnfs_utils.h" #include #include /** * @brief Get layout types supported by export * * We just return a pointer to the single type and set the count to 1. * * @param[in] export_pub Public export handle * @param[out] count Number of layout types in array * @param[out] types Static array of layout types that must not be * freed or modified and must not be dereferenced * after export reference is relinquished */ static void kvsfs_fs_layouttypes(struct fsal_export *export_hdl, int32_t *count, const layouttype4 **types) { static const layouttype4 supported_layout_type = LAYOUT4_NFSV4_1_FILES; /* FSAL_KVSFS currently supports only LAYOUT4_NFSV4_1_FILES */ /** @todo: do a switch that checks which layout is OK */ *types = &supported_layout_type; *count = 1; } /** * @brief Get layout block size for export * * This function just returns the KVSFS default. * * @param[in] export_pub Public export handle * * @return 4 MB. */ static uint32_t kvsfs_fs_layout_blocksize(struct fsal_export *export_pub) { return 0x400000; } /** * @brief Maximum number of segments we will use * * Since current clients only support 1, that's what we'll use. * * @param[in] export_pub Public export handle * * @return 1 */ static uint32_t kvsfs_fs_maximum_segments(struct fsal_export *export_pub) { return 1; } /** * @brief Size of the buffer needed for a loc_body * * Just a handle plus a bit. * * @param[in] export_pub Public export handle * * @return Size of the buffer needed for a loc_body */ static size_t kvsfs_fs_loc_body_size(struct fsal_export *export_pub) { return 0x100; } /** * @brief Size of the buffer needed for a ds_addr * * This one is huge, due to the striping pattern. * * @param[in] export_pub Public export handle * * @return Size of the buffer needed for a ds_addr */ size_t kvsfs_fs_da_addr_size(struct fsal_module *fsal_hdl) { return 0x1400; } /** * @param[out] da_addr_body Stream we write the result to * @param[in] type Type of layout that gave the device * @param[in] deviceid The device to look up * * @return Valid error codes in RFC 5661, p. 365. */ nfsstat4 kvsfs_getdeviceinfo(struct fsal_module *fsal_hdl, XDR *da_addr_body, const layouttype4 type, const struct pnfs_deviceid *deviceid) { /* The number of DSs */ unsigned int num_ds = 0; /** @todo To be set via a call to llapi */ /* Currently, all layouts have the same number of stripes */ uint32_t stripe_count = 0; uint32_t stripe = 0; /* NFSv4 status code */ nfsstat4 nfs_status = 0; /* ds list iterator */ struct kvsfs_pnfs_ds_parameter *ds; struct fsal_export *exp_hdl; struct kvsfs_fsal_export *export = NULL; struct kvsfs_exp_pnfs_parameter *pnfs_exp_param; unsigned int i; exp_hdl = glist_first_entry(&fsal_hdl->exports, struct fsal_export, exports); export = container_of(exp_hdl, struct kvsfs_fsal_export, export); pnfs_exp_param = &export->pnfs_param; /* Sanity check on type */ if (type != LAYOUT4_NFSV4_1_FILES) { LogCrit(COMPONENT_PNFS, "Unsupported layout type: %x", type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } /* Retrieve and calculate storage parameters of layout */ stripe_count = pnfs_exp_param->nb_ds; LogDebug(COMPONENT_PNFS, "device_id %u/%u/%u %lu", deviceid->device_id1, deviceid->device_id2, deviceid->device_id4, deviceid->devid); if (!inline_xdr_u_int32_t(da_addr_body, &stripe_count)) { LogCrit(COMPONENT_PNFS, "Failed to encode length of stripe_indices array: %" PRIu32 ".", stripe_count); return NFS4ERR_SERVERFAULT; } for (stripe = 0; stripe < stripe_count; stripe++) { if (!inline_xdr_u_int32_t(da_addr_body, &stripe)) { LogCrit(COMPONENT_PNFS, "Failed to encode OSD for stripe %u.", stripe); return NFS4ERR_SERVERFAULT; } } num_ds = stripe_count; /* aka glist_length(&pnfs_param->ds_list) */ if (!inline_xdr_u_int32_t(da_addr_body, &num_ds)) { LogCrit(COMPONENT_PNFS, "Failed to encode length of multipath_ds_list array: %u", num_ds); return NFS4ERR_SERVERFAULT; } /* lookup for the right DS in the ds_list */ for (i = 0; i < pnfs_exp_param->nb_ds; i++) { fsal_multipath_member_t host; ds = &pnfs_exp_param->ds_array[i]; LogDebug(COMPONENT_PNFS, "advertises DS addr=%u.%u.%u.%u port=%u", (ntohl(ds->ipaddr.sin_addr.s_addr) & 0xFF000000) >> 24, (ntohl(ds->ipaddr.sin_addr.s_addr) & 0x00FF0000) >> 16, (ntohl(ds->ipaddr.sin_addr.s_addr) & 0x0000FF00) >> 8, (unsigned int)ntohl(ds->ipaddr.sin_addr.s_addr) & 0x000000FF, (unsigned short)ntohs(ds->ipport)); host.proto = IPPROTO_TCP; host.addr = ntohl(ds->ipaddr.sin_addr.s_addr); host.port = ntohs(ds->ipport); nfs_status = FSAL_encode_v4_multipath(da_addr_body, 1, &host); if (nfs_status != NFS4_OK) return nfs_status; /** @todo TO BE REMOVED ONCE CONFIG IS CLEAN */ } return NFS4_OK; } /** * @brief Get list of available devices * * We do not support listing devices and just set EOF without doing * anything. * * @param[in] export_pub Export handle * @param[in] type Type of layout to get devices for * @param[in] cb Function taking device ID halves * @param[in,out] res In/out and output arguments of the function * * @return Valid error codes in RFC 5661, pp. 365-6. */ static nfsstat4 kvsfs_getdevicelist(struct fsal_export *export_pub, layouttype4 type, void *opaque, bool (*cb)(void *opaque, const uint64_t id), struct fsal_getdevicelist_res *res) { res->eof = true; return NFS4_OK; } void export_ops_pnfs(struct export_ops *ops) { ops->getdevicelist = kvsfs_getdevicelist; ops->fs_layouttypes = kvsfs_fs_layouttypes; ops->fs_layout_blocksize = kvsfs_fs_layout_blocksize; ops->fs_maximum_segments = kvsfs_fs_maximum_segments; ops->fs_loc_body_size = kvsfs_fs_loc_body_size; } /** * @brief Grant a layout segment. * * Grant a layout on a subset of a file requested. As a special case, * lie and grant a whole-file layout if requested, because Linux will * ignore it otherwise. * * @param[in] obj_pub Public object handle * @param[out] loc_body An XDR stream to which the FSAL must encode * the layout specific portion of the granted * layout segment. * @param[in] arg Input arguments of the function * @param[in,out] res In/out and output arguments of the function * * @return Valid error codes in RFC 5661, pp. 366-7. */ static nfsstat4 kvsfs_layoutget(struct fsal_obj_handle *obj_hdl, XDR *loc_body, const struct fsal_layoutget_arg *arg, struct fsal_layoutget_res *res) { struct kvsfs_fsal_obj_handle *myself; struct kvsfs_exp_pnfs_parameter *pnfs_exp_param; struct kvsfs_fsal_export *myexport; struct kvsfs_file_handle kvsfs_ds_handle; uint32_t stripe_unit = 0; nfl_util4 util = 0; struct pnfs_deviceid deviceid = DEVICE_ID_INIT_ZERO(FSAL_ID_KVSFS); nfsstat4 nfs_status = 0; struct gsh_buffdesc ds_desc; myexport = container_of(op_ctx->fsal_export, struct kvsfs_fsal_export, export); pnfs_exp_param = &myexport->pnfs_param; /* We support only LAYOUT4_NFSV4_1_FILES layouts */ if (arg->type != LAYOUT4_NFSV4_1_FILES) { LogCrit(COMPONENT_PNFS, "Unsupported layout type: %x", arg->type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } /* Get basic information on the file and calculate the dimensions *of the layout we can support. */ myself = container_of(obj_hdl, struct kvsfs_fsal_obj_handle, obj_handle); memcpy(&kvsfs_ds_handle, myself->handle, sizeof(struct kvsfs_file_handle)); /** @todo: here, put some code to check if such * a layout is available. If not, * return NFS4ERR_UNKNOWN_LAYOUTTYPE */ /* We grant only one segment, and we want * it back when the file is closed. */ res->return_on_close = true; res->last_segment = true; res->segment.offset = 0; res->segment.length = NFS4_UINT64_MAX; stripe_unit = pnfs_exp_param->stripe_unit; /* util |= stripe_unit | NFL4_UFLG_COMMIT_THRU_MDS; */ util |= stripe_unit & ~NFL4_UFLG_MASK; if (util != stripe_unit) LogEvent(COMPONENT_PNFS, "Invalid stripe_unit %u, truncated to %u", stripe_unit, util); /** @todo: several DSs not handled yet */ deviceid.devid = 1; /* last_possible_byte = NFS4_UINT64_MAX; strict. set but unused */ LogDebug(COMPONENT_PNFS, "devid nodeAddr %016" PRIx64, deviceid.devid); ds_desc.addr = &kvsfs_ds_handle; ds_desc.len = sizeof(struct kvsfs_file_handle); nfs_status = FSAL_encode_file_layout(loc_body, &deviceid, util, 0, 0, &op_ctx->ctx_export->export_id, 1, &ds_desc, false); if (nfs_status) { LogCrit(COMPONENT_PNFS, "Failed to encode nfsv4_1_file_layout."); goto relinquish; } return NFS4_OK; relinquish: return nfs_status; } /** * @brief Potentially return one layout segment * * Since we don't make any reservations, in this version, or get any * pins to release, always succeed * * @param[in] obj_pub Public object handle * @param[in] lrf_body Nothing for us * @param[in] arg Input arguments of the function * * @return Valid error codes in RFC 5661, p. 367. */ static nfsstat4 kvsfs_layoutreturn(struct fsal_obj_handle *obj_hdl, XDR *lrf_body, const struct fsal_layoutreturn_arg *arg) { struct kvsfs_fsal_obj_handle *myself; /* The private 'full' object handle */ struct kvsfs_file_handle *kvsfs_handle __attribute__((unused)); /* Sanity check on type */ if (arg->lo_type != LAYOUT4_NFSV4_1_FILES) { LogCrit(COMPONENT_PNFS, "Unsupported layout type: %x", arg->lo_type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } myself = container_of(obj_hdl, struct kvsfs_fsal_obj_handle, obj_handle); kvsfs_handle = myself->handle; return NFS4_OK; } /** * @brief Commit a segment of a layout * * Update the size and time for a file accessed through a layout. * * @param[in] obj_pub Public object handle * @param[in] lou_body An XDR stream containing the layout * type-specific portion of the LAYOUTCOMMIT * arguments. * @param[in] arg Input arguments of the function * @param[in,out] res In/out and output arguments of the function * * @return Valid error codes in RFC 5661, p. 366. */ static nfsstat4 kvsfs_layoutcommit(struct fsal_obj_handle *obj_hdl, XDR *lou_body, const struct fsal_layoutcommit_arg *arg, struct fsal_layoutcommit_res *res) { struct kvsfs_fsal_obj_handle *myself; /* The private 'full' object handle */ struct kvsfs_file_handle *kvsfs_handle __attribute__((unused)); /* Sanity check on type */ if (arg->type != LAYOUT4_NFSV4_1_FILES) { LogCrit(COMPONENT_PNFS, "Unsupported layout type: %x", arg->type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } myself = container_of(obj_hdl, struct kvsfs_fsal_obj_handle, obj_handle); kvsfs_handle = myself->handle; /** @todo: here, add code to actually commit the layout */ res->size_supplied = false; res->commit_done = true; return NFS4_OK; } void handle_ops_pnfs(struct fsal_obj_ops *ops) { ops->layoutget = kvsfs_layoutget; ops->layoutreturn = kvsfs_layoutreturn; ops->layoutcommit = kvsfs_layoutcommit; } nfs-ganesha-6.5/src/FSAL/FSAL_KVSFS/kvsfs_methods.h000066400000000000000000000153211473756622300216440ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* KVSFS methods for handles */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributor : Philippe DENIEL philippe.deniel@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ void kvsfs_handle_ops_init(struct fsal_obj_ops *ops); /* method proto linkage to handle.c for export */ fsal_status_t kvsfs_lookup_path(struct fsal_export *exp_hdl, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); fsal_status_t kvsfs_create_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *hdl_desc, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); /* this needs to be refactored to put ipport inside sockaddr_in */ struct kvsfs_pnfs_ds_parameter { struct glist_head ds_list; struct sockaddr_in ipaddr; unsigned short ipport; unsigned int id; }; /* KVSFS FSAL module private storage */ struct kvsfs_fsal_module { struct fsal_module fsal; struct fsal_obj_ops handle_ops; }; /* * KVSFS internal export */ #define KVSFS_NB_DS 4 struct kvsfs_exp_pnfs_parameter { unsigned int stripe_unit; bool pnfs_enabled; unsigned int nb_ds; struct kvsfs_pnfs_ds_parameter ds_array[KVSFS_NB_DS]; }; struct kvsfs_fsal_export { struct fsal_export export; kvsns_ino_t root_inode; char *kvsns_config; bool pnfs_ds_enabled; bool pnfs_mds_enabled; struct kvsfs_exp_pnfs_parameter pnfs_param; }; struct kvsfs_fd { /** open and share mode plus fd management */ struct fsal_fd fsal_fd; /** kvsns file descriptor */ kvsns_file_open_t fd; }; #define KVSNS_FILE_OPEN_INIT \ { \ 0, { 0, 0 }, 0 \ } struct kvsfs_state_fd { /** state MUST be first to use default free_state */ struct state_t state; struct kvsfs_fd kvsfs_fd; }; /** * @brief KVSFS internal object handle * * The handle is a pointer because * a) the last element of file_handle is a char[] meaning variable len... * b) we cannot depend on it *always* being last or being the only * variable sized struct here... a pointer is safer. * wrt locks, should this be a lock counter?? */ struct kvsfs_fsal_obj_handle { struct fsal_obj_handle obj_handle; struct kvsfs_file_handle *handle; union { struct { struct fsal_share share; kvsns_ino_t inode; struct kvsfs_fd fd; kvsns_cred_t cred; } file; struct { unsigned char *link_content; int link_size; } symlink; } u; }; struct kvsfs_fsal_obj_handle *kvsfs_alloc_handle(struct kvsfs_file_handle *fh, struct fsal_attrlist *attr, const char *link_content, struct fsal_export *exp_hdl); /* I/O management */ /* OK */ fsal_status_t kvsfs_open2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attr_set, fsal_verifier_t verifier, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out); fsal_openflags_t kvsfs_status2(struct fsal_obj_handle *obj_hdl, struct state_t *state); fsal_status_t kvsfs_reopen2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags); fsal_status_t kvsfs_commit2(struct fsal_obj_handle *obj_hdl, /* sync */ off_t offset, size_t len); /* OK */ fsal_status_t kvsfs_reopen_func(struct fsal_obj_handle *obj_hdl, fsal_openflags_t openflags, struct fsal_fd *fsal_fd); fsal_status_t kvsfs_close_func(struct fsal_obj_handle *obj_hdl, struct fsal_fd *fd); void kvsfs_read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg); void kvsfs_write2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *caller_arg); /* OK */ fsal_status_t kvsfs_close2(struct fsal_obj_handle *obj_hdl, struct state_t *state); /* OK */ fsal_status_t kvsfs_create2(struct fsal_obj_handle *dir_hdl, const char *filename, const struct req_op_context *op_ctx, mode_t unix_mode, struct kvsfs_file_handle *kvsfs_fh, int posix_flags, struct fsal_attrlist *fsal_attr); fsal_status_t kvsfs_share_op(struct fsal_obj_handle *obj_hdl, void *p_owner, fsal_share_param_t request_share); /* extended attributes management */ fsal_status_t kvsfs_list_ext_attrs(struct fsal_obj_handle *obj_hdl, unsigned int cookie, fsal_xattrent_t *xattrs_tab, unsigned int xattrs_tabsize, unsigned int *p_nb_returned, int *end_of_list); fsal_status_t kvsfs_getextattr_id_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, unsigned int *pxattr_id); fsal_status_t kvsfs_getextattr_value_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, size_t *p_output_size); fsal_status_t kvsfs_getextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buffer_addr, size_t buffer_size, size_t *p_output_size); fsal_status_t kvsfs_setextattr_value(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, int create); fsal_status_t kvsfs_setextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buffer_addr, size_t buffer_size); fsal_status_t kvsfs_getextattr_attrs(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, struct fsal_attrlist *p_attrs); fsal_status_t kvsfs_remove_extattr_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id); fsal_status_t kvsfs_remove_extattr_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name); fsal_status_t kvsfs_lock_op(struct fsal_obj_handle *obj_hdl, void *p_owner, fsal_lock_op_t lock_op, fsal_lock_param_t *request_lock, fsal_lock_param_t *conflicting_lock); nfs-ganesha-6.5/src/FSAL/FSAL_KVSFS/kvsfs_xattrs.c000066400000000000000000000100431473756622300215150ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) CEA, 2020 * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /* xattrs.c * KVSFS object (file|dir) handle object extended attributes */ #include "config.h" #include #include #include #include #include #include #include #include "gsh_list.h" #include "kvsfs_fsal_internal.h" #include "fsal_convert.h" #include "FSAL/fsal_config.h" #include "FSAL/fsal_commonlib.h" #include typedef int (*xattr_getfunc_t)(struct fsal_obj_handle *, /* object handle */ caddr_t, /* output buff */ size_t, /* output buff size */ size_t *, /* output size */ void *arg); /* optional argument */ typedef int (*xattr_setfunc_t)(struct fsal_obj_handle *, /* object handle */ caddr_t, /* input buff */ size_t, /* input size */ int, /* creation flag */ void *arg); /* optional argument */ typedef struct fsal_xattr_def__ { char xattr_name[MAXNAMLEN + 1]; xattr_getfunc_t get_func; xattr_setfunc_t set_func; int flags; void *arg; } fsal_xattr_def_t; /* * DEFINE GET/SET FUNCTIONS */ int print_vfshandle(struct fsal_obj_handle *obj_hdl, caddr_t buffer_addr, size_t buffer_size, size_t *p_output_size, void *arg) { *p_output_size = snprintf(buffer_addr, buffer_size, "(not yet implemented)"); return 0; } /* print_fid */ fsal_status_t kvsfs_list_ext_attrs(struct fsal_obj_handle *obj_hdl, unsigned int argcookie, fsal_xattrent_t *xattrs_tab, unsigned int xattrs_tabsize, unsigned int *p_nb_returned, int *end_of_list) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t kvsfs_getextattr_id_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, unsigned int *pxattr_id) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t kvsfs_getextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, caddr_t buffer_addr, size_t buffer_size, size_t *p_output_size) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t kvsfs_getextattr_value_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, caddr_t buffer_addr, size_t buffer_size, size_t *p_output_size) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t kvsfs_setextattr_value(struct fsal_obj_handle *obj_hdl, const char *xattr_name, caddr_t buffer_addr, size_t buffer_size, int create) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t kvsfs_setextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, caddr_t buffer_addr, size_t buffer_size) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t kvsfs_getextattr_attrs(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, struct fsal_attrlist *p_attrs) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t kvsfs_remove_extattr_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t kvsfs_remove_extattr_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } nfs-ganesha-6.5/src/FSAL/FSAL_LIZARDFS/000077500000000000000000000000001473756622300171345ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_LIZARDFS/CMakeLists.txt000066400000000000000000000031521473756622300216750ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- add_definitions( -D__USE_GNU ${LIZARDFS_CFLAGS} ) set( LIB_PREFIX 64) ########### next target ############### SET(fsallizardfs_LIB_SRCS context_wrap.c context_wrap.h ds.c export.c handle.c lzfs_acl.c lzfs_internal.c lzfs_internal.h main.c mds_export.c mds_handle.c ) add_library(fsallizardfs MODULE ${fsallizardfs_LIB_SRCS}) add_sanitizers(fsallizardfs) target_link_libraries(fsallizardfs ${SYSTEM_LIBRARIES} ${LIZARDFS_CLIENT_LIB} ) set_target_properties(fsallizardfs PROPERTIES VERSION 3.12.0 SOVERSION 3) install(TARGETS fsallizardfs COMPONENT fsal DESTINATION ${FSAL_DESTINATION} ) nfs-ganesha-6.5/src/FSAL/FSAL_LIZARDFS/context_wrap.c000066400000000000000000000210101473756622300220070ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright (C) 2019 Skytechnology sp. z o.o. * * This program 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 3 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #include "context_wrap.h" #include "lzfs_internal.h" int liz_cred_lookup(liz_t *instance, struct user_cred *cred, liz_inode_t parent, const char *path, struct liz_entry *entry) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_lookup(instance, ctx, parent, path, entry); liz_destroy_context(ctx); return rc; } int liz_cred_mknod(liz_t *instance, struct user_cred *cred, liz_inode_t parent, const char *path, mode_t mode, dev_t rdev, struct liz_entry *entry) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_mknod(instance, ctx, parent, path, mode, rdev, entry); liz_destroy_context(ctx); return rc; } liz_fileinfo_t *liz_cred_open(liz_t *instance, struct user_cred *cred, liz_inode_t inode, int flags) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return NULL; } liz_fileinfo_t *ret = liz_open(instance, ctx, inode, flags); liz_destroy_context(ctx); return ret; } ssize_t liz_cred_read(liz_t *instance, struct user_cred *cred, liz_fileinfo_t *fileinfo, off_t offset, size_t size, char *buffer) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } ssize_t ret = liz_read(instance, ctx, fileinfo, offset, size, buffer); liz_destroy_context(ctx); return ret; } ssize_t liz_cred_write(liz_t *instance, struct user_cred *cred, liz_fileinfo_t *fileinfo, off_t offset, size_t size, const char *buffer) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } ssize_t ret = liz_write(instance, ctx, fileinfo, offset, size, buffer); liz_destroy_context(ctx); return ret; } int liz_cred_flush(liz_t *instance, struct user_cred *cred, liz_fileinfo_t *fileinfo) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_flush(instance, ctx, fileinfo); liz_destroy_context(ctx); return rc; } int liz_cred_getattr(liz_t *instance, struct user_cred *cred, liz_inode_t inode, struct liz_attr_reply *reply) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_getattr(instance, ctx, inode, reply); liz_destroy_context(ctx); return rc; } liz_fileinfo_t *liz_cred_opendir(liz_t *instance, struct user_cred *cred, liz_inode_t inode) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return NULL; } liz_fileinfo_t *ret = liz_opendir(instance, ctx, inode); liz_destroy_context(ctx); return ret; } int liz_cred_readdir(liz_t *instance, struct user_cred *cred, struct liz_fileinfo *fileinfo, off_t offset, size_t max_entries, struct liz_direntry *buf, size_t *num_entries) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_readdir(instance, ctx, fileinfo, offset, max_entries, buf, num_entries); liz_destroy_context(ctx); return rc; } int liz_cred_mkdir(liz_t *instance, struct user_cred *cred, liz_inode_t parent, const char *name, mode_t mode, struct liz_entry *out_entry) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_mkdir(instance, ctx, parent, name, mode, out_entry); liz_destroy_context(ctx); return rc; } int liz_cred_rmdir(liz_t *instance, struct user_cred *cred, liz_inode_t parent, const char *name) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_rmdir(instance, ctx, parent, name); liz_destroy_context(ctx); return rc; } int liz_cred_unlink(liz_t *instance, struct user_cred *cred, liz_inode_t parent, const char *name) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_unlink(instance, ctx, parent, name); liz_destroy_context(ctx); return rc; } int liz_cred_setattr(liz_t *instance, struct user_cred *cred, liz_inode_t inode, struct stat *stbuf, int to_set, struct liz_attr_reply *reply) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_setattr(instance, ctx, inode, stbuf, to_set, reply); liz_destroy_context(ctx); return rc; } int liz_cred_fsync(liz_t *instance, struct user_cred *cred, struct liz_fileinfo *fileinfo) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_fsync(instance, ctx, fileinfo); liz_destroy_context(ctx); return rc; } int liz_cred_rename(liz_t *instance, struct user_cred *cred, liz_inode_t parent, const char *name, liz_inode_t new_parent, const char *new_name) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_rename(instance, ctx, parent, name, new_parent, new_name); liz_destroy_context(ctx); return rc; } int liz_cred_symlink(liz_t *instance, struct user_cred *cred, const char *link, liz_inode_t parent, const char *name, struct liz_entry *entry) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_symlink(instance, ctx, link, parent, name, entry); liz_destroy_context(ctx); return rc; } int liz_cred_readlink(liz_t *instance, struct user_cred *cred, liz_inode_t inode, char *buf, size_t size) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_readlink(instance, ctx, inode, buf, size); liz_destroy_context(ctx); return rc; } int liz_cred_link(liz_t *instance, struct user_cred *cred, liz_inode_t inode, liz_inode_t parent, const char *name, struct liz_entry *entry) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_link(instance, ctx, inode, parent, name, entry); liz_destroy_context(ctx); return rc; } int liz_cred_get_chunks_info(liz_t *instance, struct user_cred *cred, liz_inode_t inode, uint32_t chunk_index, liz_chunk_info_t *buffer, uint32_t buffer_size, uint32_t *reply_size) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_get_chunks_info(instance, ctx, inode, chunk_index, buffer, buffer_size, reply_size); liz_destroy_context(ctx); return rc; } int liz_cred_setacl(liz_t *instance, struct user_cred *cred, liz_inode_t inode, const liz_acl_t *acl) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_setacl(instance, ctx, inode, acl); liz_destroy_context(ctx); return rc; } int liz_cred_getacl(liz_t *instance, struct user_cred *cred, liz_inode_t inode, liz_acl_t **acl) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_getacl(instance, ctx, inode, acl); liz_destroy_context(ctx); return rc; } int liz_cred_setlk(liz_t *instance, struct user_cred *cred, liz_fileinfo_t *fileinfo, const liz_lock_info_t *lock) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_setlk(instance, ctx, fileinfo, lock, NULL, NULL); liz_destroy_context(ctx); return rc; } int liz_cred_getlk(liz_t *instance, struct user_cred *cred, liz_fileinfo_t *fileinfo, liz_lock_info_t *lock) { liz_context_t *ctx = lzfs_fsal_create_context(instance, cred); if (ctx == NULL) { return -1; } int rc = liz_getlk(instance, ctx, fileinfo, lock); return rc; } nfs-ganesha-6.5/src/FSAL/FSAL_LIZARDFS/context_wrap.h000066400000000000000000000076251473756622300220340ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright (C) 2019 Skytechnology sp. z o.o. * * This program 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 3 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #include #include "lizardfs/lizardfs_c_api.h" int liz_cred_lookup(liz_t *instance, struct user_cred *cred, liz_inode_t parent, const char *path, struct liz_entry *entry); int liz_cred_mknod(liz_t *instance, struct user_cred *cred, liz_inode_t parent, const char *path, mode_t mode, dev_t rdev, struct liz_entry *entry); liz_fileinfo_t *liz_cred_open(liz_t *instance, struct user_cred *cred, liz_inode_t inode, int flags); ssize_t liz_cred_read(liz_t *instance, struct user_cred *cred, liz_fileinfo_t *fileinfo, off_t offset, size_t size, char *buffer); ssize_t liz_cred_write(liz_t *instance, struct user_cred *cred, liz_fileinfo_t *fileinfo, off_t offset, size_t size, const char *buffer); int liz_cred_flush(liz_t *instance, struct user_cred *cred, liz_fileinfo_t *fileinfo); int liz_cred_getattr(liz_t *instance, struct user_cred *cred, liz_inode_t inode, struct liz_attr_reply *reply); liz_fileinfo_t *liz_cred_opendir(liz_t *instance, struct user_cred *cred, liz_inode_t inode); int liz_cred_readdir(liz_t *instance, struct user_cred *cred, struct liz_fileinfo *fileinfo, off_t offset, size_t max_entries, struct liz_direntry *buf, size_t *num_entries); int liz_cred_mkdir(liz_t *instance, struct user_cred *cred, liz_inode_t parent, const char *name, mode_t mode, struct liz_entry *out_entry); int liz_cred_rmdir(liz_t *instance, struct user_cred *cred, liz_inode_t parent, const char *name); int liz_cred_unlink(liz_t *instance, struct user_cred *cred, liz_inode_t parent, const char *name); int liz_cred_setattr(liz_t *instance, struct user_cred *cred, liz_inode_t inode, struct stat *stbuf, int to_set, struct liz_attr_reply *reply); int liz_cred_fsync(liz_t *instance, struct user_cred *cred, struct liz_fileinfo *fileinfo); int liz_cred_rename(liz_t *instance, struct user_cred *cred, liz_inode_t parent, const char *name, liz_inode_t new_parent, const char *new_name); int liz_cred_symlink(liz_t *instance, struct user_cred *cred, const char *link, liz_inode_t parent, const char *name, struct liz_entry *entry); int liz_cred_readlink(liz_t *instance, struct user_cred *cred, liz_inode_t inode, char *buf, size_t size); int liz_cred_link(liz_t *instance, struct user_cred *cred, liz_inode_t inode, liz_inode_t parent, const char *name, struct liz_entry *entry); int liz_cred_get_chunks_info(liz_t *instance, struct user_cred *cred, liz_inode_t inode, uint32_t chunk_index, liz_chunk_info_t *buffer, uint32_t buffer_size, uint32_t *reply_size); int liz_cred_setacl(liz_t *instance, struct user_cred *cred, liz_inode_t inode, const liz_acl_t *acl); int liz_cred_getacl(liz_t *instance, struct user_cred *cred, liz_inode_t inode, liz_acl_t **acl); int liz_cred_setlk(liz_t *instance, struct user_cred *cred, liz_fileinfo_t *fileinfo, const liz_lock_info_t *lock); int liz_cred_getlk(liz_t *instance, struct user_cred *cred, liz_fileinfo_t *fileinfo, liz_lock_info_t *lock); nfs-ganesha-6.5/src/FSAL/FSAL_LIZARDFS/ds.c000066400000000000000000000217101473756622300177070ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright (C) 2019 Skytechnology sp. z o.o. * * This program 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 3 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #include "fsal.h" #include "fsal_api.h" #include "fsal_convert.h" #include "fsal_types.h" #include "fsal_up.h" #include "FSAL/fsal_commonlib.h" #include "../fsal_private.h" #include "nfs_exports.h" #include "pnfs_utils.h" #include "context_wrap.h" #include "lzfs_internal.h" static void lzfs_int_clear_fileinfo_cache(struct lzfs_fsal_export *lzfs_export, int count) { assert(lzfs_export->fileinfo_cache); for (int i = 0; i < count; ++i) { liz_fileinfo_entry_t *cache_handle; liz_fileinfo_t *file_handle; cache_handle = liz_fileinfo_cache_pop_expired( lzfs_export->fileinfo_cache); if (cache_handle == NULL) { break; } file_handle = liz_extract_fileinfo(cache_handle); liz_release(lzfs_export->lzfs_instance, file_handle); liz_fileinfo_entry_free(cache_handle); } } /*! \brief Clean up a DS handle * * \see fsal_api.h for more information */ static void lzfs_fsal_ds_handle_release(struct fsal_ds_handle *const ds_pub) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_ds_handle *lzfs_ds; lzfs_export = container_of(op_ctx->ctx_pnfs_ds->mds_fsal_export, struct lzfs_fsal_export, export); lzfs_ds = container_of(ds_pub, struct lzfs_fsal_ds_handle, ds); assert(lzfs_export->fileinfo_cache); if (lzfs_ds->cache_handle != NULL) { liz_fileinfo_cache_release(lzfs_export->fileinfo_cache, lzfs_ds->cache_handle); } gsh_free(lzfs_ds); lzfs_int_clear_fileinfo_cache(lzfs_export, 5); } static nfsstat4 lzfs_int_openfile(struct lzfs_fsal_export *lzfs_export, struct lzfs_fsal_ds_handle *lzfs_ds) { assert(lzfs_export->fileinfo_cache); if (lzfs_ds->cache_handle != NULL) { return NFS4_OK; } lzfs_int_clear_fileinfo_cache(lzfs_export, 2); lzfs_ds->cache_handle = liz_fileinfo_cache_acquire( lzfs_export->fileinfo_cache, lzfs_ds->inode); if (lzfs_ds->cache_handle == NULL) { return NFS4ERR_IO; } liz_fileinfo_t *file_handle = liz_extract_fileinfo(lzfs_ds->cache_handle); if (file_handle != NULL) { return NFS4_OK; } file_handle = liz_cred_open(lzfs_export->lzfs_instance, NULL, lzfs_ds->inode, O_RDWR); if (file_handle == NULL) { liz_fileinfo_cache_erase(lzfs_export->fileinfo_cache, lzfs_ds->cache_handle); lzfs_ds->cache_handle = NULL; return NFS4ERR_IO; } liz_attach_fileinfo(lzfs_ds->cache_handle, file_handle); return NFS4_OK; } /*! \brief Read from a data-server handle. * * \see fsal_api.h for more information */ static nfsstat4 lzfs_fsal_ds_handle_read(struct fsal_ds_handle *const ds_hdl, const stateid4 *stateid, const offset4 offset, const count4 requested_length, void *const buffer, count4 *const supplied_length, bool *const end_of_file) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_ds_handle *lzfs_ds; liz_fileinfo_t *file_handle; ssize_t nb_read; nfsstat4 nfs_status; lzfs_export = container_of(op_ctx->ctx_pnfs_ds->mds_fsal_export, struct lzfs_fsal_export, export); lzfs_ds = container_of(ds_hdl, struct lzfs_fsal_ds_handle, ds); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIu32 " offset=%" PRIu64 " size=%" PRIu32, lzfs_export->export.export_id, lzfs_ds->inode, offset, requested_length); nfs_status = lzfs_int_openfile(lzfs_export, lzfs_ds); if (nfs_status != NFS4_OK) { return nfs_status; } file_handle = liz_extract_fileinfo(lzfs_ds->cache_handle); nb_read = liz_cred_read(lzfs_export->lzfs_instance, NULL, file_handle, offset, requested_length, buffer); if (nb_read < 0) { return lzfs_nfs4_last_err(); } *supplied_length = nb_read; *end_of_file = (nb_read == 0); return NFS4_OK; } /*! \brief Write to a data-server handle. * * \see fsal_api.h for more information */ static nfsstat4 lzfs_fsal_ds_handle_write( struct fsal_ds_handle *const ds_hdl, const stateid4 *stateid, const offset4 offset, const count4 write_length, const void *buffer, const stable_how4 stability_wanted, count4 *const written_length, verifier4 *const writeverf, stable_how4 *const stability_got) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_ds_handle *lzfs_ds; liz_fileinfo_t *file_handle; ssize_t nb_write; nfsstat4 nfs_status; lzfs_export = container_of(op_ctx->ctx_pnfs_ds->mds_fsal_export, struct lzfs_fsal_export, export); lzfs_ds = container_of(ds_hdl, struct lzfs_fsal_ds_handle, ds); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIu32 " offset=%" PRIu64 " size=%" PRIu32, lzfs_export->export.export_id, lzfs_ds->inode, offset, write_length); nfs_status = lzfs_int_openfile(lzfs_export, lzfs_ds); if (nfs_status != NFS4_OK) { return nfs_status; } file_handle = liz_extract_fileinfo(lzfs_ds->cache_handle); nb_write = liz_cred_write(lzfs_export->lzfs_instance, NULL, file_handle, offset, write_length, buffer); if (nb_write < 0) { return lzfs_nfs4_last_err(); } int rc = 0; if (stability_wanted != UNSTABLE4) { rc = liz_cred_flush(lzfs_export->lzfs_instance, NULL, file_handle); } *written_length = nb_write; *stability_got = (rc < 0) ? UNSTABLE4 : stability_wanted; return NFS4_OK; } /*! \brief Commit a byte range to a DS handle. * * \see fsal_api.h for more information */ static nfsstat4 lzfs_fsal_ds_handle_commit(struct fsal_ds_handle *const ds_hdl, const offset4 offset, const count4 count, verifier4 *const writeverf) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_ds_handle *lzfs_ds; liz_fileinfo_t *file_handle; nfsstat4 nfs_status; int rc; memset(writeverf, 0, NFS4_VERIFIER_SIZE); lzfs_export = container_of(op_ctx->ctx_pnfs_ds->mds_fsal_export, struct lzfs_fsal_export, export); lzfs_ds = container_of(ds_hdl, struct lzfs_fsal_ds_handle, ds); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIu32 " offset=%" PRIu64 " size=%" PRIu32, lzfs_export->export.export_id, lzfs_ds->inode, offset, count); nfs_status = lzfs_int_openfile(lzfs_export, lzfs_ds); if (nfs_status != NFS4_OK) { // If we failed here then there is no opened // LizardFS file descriptor, which implies that we don't need // to flush anything. return NFS4_OK; } file_handle = liz_extract_fileinfo(lzfs_ds->cache_handle); rc = liz_cred_flush(lzfs_export->lzfs_instance, NULL, file_handle); if (rc < 0) { LogMajor(COMPONENT_PNFS, "ds_commit() failed '%s'", liz_error_string(liz_last_err())); return NFS4ERR_INVAL; } return NFS4_OK; } /*! \brief Read plus from a data-server handle. * * \see fsal_api.h for more information */ static nfsstat4 lzfs_fsal_ds_read_plus(struct fsal_ds_handle *const ds_hdl, const stateid4 *stateid, const offset4 offset, const count4 requested_length, void *const buffer, const count4 supplied_length, bool *const end_of_file, struct io_info *info) { LogCrit(COMPONENT_PNFS, "Unimplemented DS read_plus!"); return NFS4ERR_NOTSUPP; } /*! \brief Create a FSAL data server handle from a wire handle * * \see fsal_api.h for more information */ static nfsstat4 lzfs_fsal_make_ds_handle(struct fsal_pnfs_ds *const pds, const struct gsh_buffdesc *const desc, struct fsal_ds_handle **const handle, int flags) { struct lzfs_fsal_ds_wire *dsw = (struct lzfs_fsal_ds_wire *)desc->addr; struct lzfs_fsal_ds_handle *lzfs_ds; *handle = NULL; if (desc->len != sizeof(struct lzfs_fsal_ds_wire)) return NFS4ERR_BADHANDLE; if (dsw->inode == 0) return NFS4ERR_BADHANDLE; lzfs_ds = gsh_calloc(1, sizeof(struct lzfs_fsal_ds_handle)); *handle = &lzfs_ds->ds; if (flags & FH_FSAL_BIG_ENDIAN) { #if (BYTE_ORDER != BIG_ENDIAN) lzfs_ds->inode = bswap_32(dsw->inode); #else lzfs_ds->inode = dsw->inode; #endif } else { #if (BYTE_ORDER == BIG_ENDIAN) lzfs_ds->inode = bswap_32(dsw->inode); #else lzfs_ds->inode = dsw->inode; #endif } return NFS4_OK; } void lzfs_fsal_ds_handle_ops_init(struct fsal_pnfs_ds_ops *ops) { memcpy(ops, &def_pnfs_ds_ops, sizeof(struct fsal_pnfs_ds_ops)); ops->make_ds_handle = lzfs_fsal_make_ds_handle; ops->dsh_release = lzfs_fsal_ds_handle_release; ops->dsh_read = lzfs_fsal_ds_handle_read; ops->dsh_write = lzfs_fsal_ds_handle_write; ops->dsh_commit = lzfs_fsal_ds_handle_commit; ops->dsh_read_plus = lzfs_fsal_ds_read_plus; } nfs-ganesha-6.5/src/FSAL/FSAL_LIZARDFS/export.c000066400000000000000000000275421473756622300206330ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright (C) 2019 Skytechnology sp. z o.o. * * This program 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 3 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #include #include #include "fsal_convert.h" #include "fsal_types.h" #include "FSAL/fsal_config.h" #include "context_wrap.h" #include "lzfs_internal.h" /*! \brief Finalize an export * * \see fsal_api.h for more information */ static void lzfs_fsal_release(struct fsal_export *export_hdl) { struct lzfs_fsal_export *lzfs_export; lzfs_export = container_of(export_hdl, struct lzfs_fsal_export, export); lzfs_fsal_delete_handle(lzfs_export->root); lzfs_export->root = NULL; fsal_detach_export(lzfs_export->export.fsal, &lzfs_export->export.exports); free_export_ops(&lzfs_export->export); if (lzfs_export->fileinfo_cache) { liz_reset_fileinfo_cache_params(lzfs_export->fileinfo_cache, 0, 0); while (1) { liz_fileinfo_entry_t *cache_handle; liz_fileinfo_t *file_handle; cache_handle = liz_fileinfo_cache_pop_expired( lzfs_export->fileinfo_cache); if (!cache_handle) { break; } file_handle = liz_extract_fileinfo(cache_handle); liz_release(lzfs_export->lzfs_instance, file_handle); liz_fileinfo_entry_free(cache_handle); } liz_destroy_fileinfo_cache(lzfs_export->fileinfo_cache); lzfs_export->fileinfo_cache = NULL; } liz_destroy(lzfs_export->lzfs_instance); lzfs_export->lzfs_instance = NULL; gsh_free((char *)lzfs_export->lzfs_params.subfolder); gsh_free(lzfs_export); } /*! \brief Look up a path * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_lookup_path(struct fsal_export *export_hdl, const char *path, struct fsal_obj_handle **pub_handle, struct fsal_attrlist *attrs_out) { static const char *root_dir_path = "/"; struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *lzfs_handle = NULL; const char *real_path; int rc; LogFullDebug(COMPONENT_FSAL, "export_id=%" PRIu16 " path=%s", export_hdl->export_id, path); lzfs_export = container_of(export_hdl, struct lzfs_fsal_export, export); *pub_handle = NULL; // set the real_path to the path without the prefix from // ctx_export->fullpath if (*path != '/') { real_path = strchr(path, ':'); if (real_path == NULL) { return fsalstat(ERR_FSAL_INVAL, 0); } ++real_path; if (*real_path != '/') { return fsalstat(ERR_FSAL_INVAL, 0); } } else { real_path = path; } if (strstr(real_path, CTX_FULLPATH(op_ctx)) != real_path) { LogFullDebug(COMPONENT_FSAL, "no fullpath match"); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } real_path += strlen(CTX_FULLPATH(op_ctx)); if (*real_path == '\0') { real_path = root_dir_path; } LogFullDebug(COMPONENT_FSAL, "real_path=%s", real_path); // special case the root if (strcmp(real_path, "/") == 0) { assert(lzfs_export->root); *pub_handle = &lzfs_export->root->handle; if (attrs_out == NULL) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } } liz_entry_t result; rc = liz_cred_lookup(lzfs_export->lzfs_instance, &op_ctx->creds, SPECIAL_INODE_ROOT, real_path, &result); if (rc < 0) { return lzfs_fsal_last_err(); } if (attrs_out != NULL) { posix2fsal_attributes_all(&result.attr, attrs_out); } if (*pub_handle == NULL) { lzfs_handle = lzfs_fsal_new_handle(&result.attr, lzfs_export); *pub_handle = &lzfs_handle->handle; } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /*! \brief Convert a wire handle to a host handle * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_wire_to_host(struct fsal_export *exp_hdl, fsal_digesttype_t in_type, struct gsh_buffdesc *fh_desc, int flags) { liz_inode_t *inode; if (!fh_desc || !fh_desc->addr) return fsalstat(ERR_FSAL_FAULT, 0); inode = (liz_inode_t *)fh_desc->addr; if (flags & FH_FSAL_BIG_ENDIAN) { #if (BYTE_ORDER != BIG_ENDIAN) assert(sizeof(liz_inode_t) == 4); *inode = bswap_32(*inode); #endif } else { #if (BYTE_ORDER == BIG_ENDIAN) assert(sizeof(liz_inode_t) == 4); *inode = bswap_32(*inode); #endif } if (fh_desc->len != sizeof(liz_inode_t)) { LogMajor(COMPONENT_FSAL, "Size mismatch for handle. Should be %zu, got %zu", sizeof(liz_inode_t), fh_desc->len); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * \brief Create a FSAL object handle from a host handle * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_create_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *desc, struct fsal_obj_handle **pub_handle, struct fsal_attrlist *attrs_out) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *handle = NULL; liz_inode_t *inode; int rc; lzfs_export = container_of(exp_hdl, struct lzfs_fsal_export, export); inode = (liz_inode_t *)desc->addr; *pub_handle = NULL; if (desc->len != sizeof(liz_inode_t)) { return fsalstat(ERR_FSAL_INVAL, 0); } liz_attr_reply_t result; rc = liz_cred_getattr(lzfs_export->lzfs_instance, &op_ctx->creds, *inode, &result); if (rc < 0) { return lzfs_fsal_last_err(); } handle = lzfs_fsal_new_handle(&result.attr, lzfs_export); if (attrs_out != NULL) { posix2fsal_attributes_all(&result.attr, attrs_out); } *pub_handle = &handle->handle; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /*! \brief Get filesystem statistics * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_get_fs_dynamic_info(struct fsal_export *exp_hdl, struct fsal_obj_handle *obj_hdl, fsal_dynamicfsinfo_t *info) { struct lzfs_fsal_export *lzfs_export; int rc; lzfs_export = container_of(exp_hdl, struct lzfs_fsal_export, export); liz_stat_t st; rc = liz_statfs(lzfs_export->lzfs_instance, &st); if (rc < 0) { return lzfs_fsal_last_err(); } memset(info, 0, sizeof(fsal_dynamicfsinfo_t)); info->total_bytes = st.total_space; info->free_bytes = st.avail_space; info->avail_bytes = st.avail_space; info->total_files = MAX_REGULAR_INODE; info->free_files = MAX_REGULAR_INODE - st.inodes; info->avail_files = MAX_REGULAR_INODE - st.inodes; info->time_delta.tv_sec = 0; info->time_delta.tv_nsec = FSAL_DEFAULT_TIME_DELTA_NSEC; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /*! \brief Export feature test * * \see fsal_api.h for more information */ static bool lzfs_fsal_fs_supports(struct fsal_export *exp_hdl, fsal_fsinfo_options_t option) { struct fsal_staticfsinfo_t *info; info = lzfs_fsal_staticinfo(exp_hdl->fsal); return fsal_supports(info, option); } /*! \brief Get the greatest file size supported * * \see fsal_api.h for more information */ static uint64_t lzfs_fsal_fs_maxfilesize(struct fsal_export *exp_hdl) { struct fsal_staticfsinfo_t *info; info = lzfs_fsal_staticinfo(exp_hdl->fsal); return fsal_maxfilesize(info); } /*! \brief Get the greatest read size supported * * \see fsal_api.h for more information */ static uint32_t lzfs_fsal_fs_maxread(struct fsal_export *exp_hdl) { struct fsal_staticfsinfo_t *info; info = lzfs_fsal_staticinfo(exp_hdl->fsal); return fsal_maxread(info); } /*! \brief Get the greatest write size supported * * \see fsal_api.h for more information */ static uint32_t lzfs_fsal_fs_maxwrite(struct fsal_export *exp_hdl) { struct fsal_staticfsinfo_t *info; info = lzfs_fsal_staticinfo(exp_hdl->fsal); return fsal_maxwrite(info); } /*! \brief Get the greatest link count supported * * \see fsal_api.h for more information */ static uint32_t lzfs_fsal_fs_maxlink(struct fsal_export *exp_hdl) { struct fsal_staticfsinfo_t *info; info = lzfs_fsal_staticinfo(exp_hdl->fsal); return fsal_maxlink(info); } /*! \brief Get the greatest name length supported * * \see fsal_api.h for more information */ static uint32_t lzfs_fsal_fs_maxnamelen(struct fsal_export *exp_hdl) { struct fsal_staticfsinfo_t *info; info = lzfs_fsal_staticinfo(exp_hdl->fsal); return fsal_maxnamelen(info); } /*! \brief Get the greatest path length supported * * \see fsal_api.h for more information */ static uint32_t lzfs_fsal_fs_maxpathlen(struct fsal_export *exp_hdl) { struct fsal_staticfsinfo_t *info; info = lzfs_fsal_staticinfo(exp_hdl->fsal); return fsal_maxpathlen(info); } /*! \brief Get supported ACL types * * \see fsal_api.h for more information */ static fsal_aclsupp_t lzfs_fsal_fs_acl_support(struct fsal_export *exp_hdl) { struct fsal_staticfsinfo_t *info; info = lzfs_fsal_staticinfo(exp_hdl->fsal); return fsal_acl_support(info); } /*! \brief Get supported attributes * * \see fsal_api.h for more information */ static attrmask_t lzfs_fsal_fs_supported_attrs(struct fsal_export *exp_hdl) { struct fsal_staticfsinfo_t *info; attrmask_t supported_mask; info = lzfs_fsal_staticinfo(exp_hdl->fsal); supported_mask = fsal_supported_attrs(info); return supported_mask; } /*! \brief Get umask applied to created files * * \see fsal_api.h for more information */ static uint32_t lzfs_fsal_fs_umask(struct fsal_export *exp_hdl) { struct fsal_staticfsinfo_t *info; info = lzfs_fsal_staticinfo(exp_hdl->fsal); return fsal_umask(info); } void lzfs_free_state(struct state_t *state) { struct lzfs_fsal_fd *my_fd; my_fd = &container_of(state, struct lzfs_fsal_state_fd, state)->lzfs_fd; destroy_fsal_fd(&my_fd->fsal_fd); gsh_free(state); } /*! \brief Allocate a state_t structure * * \see fsal_api.h for more information */ struct state_t *lzfs_fsal_alloc_state(struct fsal_export *exp_hdl, enum state_type state_type, struct state_t *related_state) { struct state_t *state; struct lzfs_fsal_fd *my_fd; state = init_state(gsh_calloc(1, sizeof(struct lzfs_fsal_state_fd)), lzfs_free_state, state_type, related_state); my_fd = &container_of(state, struct lzfs_fsal_state_fd, state)->lzfs_fd; init_fsal_fd(&my_fd->fsal_fd, FSAL_FD_STATE, op_ctx->fsal_export); return state; } /** * @brief Function to get the fasl_obj_handle that has fsal_fd as its global fd. * * @param[in] exp_hdl The export in which the handle exists * @param[in] fd File descriptor in question * @param[out] handle FSAL object handle * * @return the fsal_obj_handle. */ void get_fsal_obj_hdl(struct fsal_export *exp_hdl, struct fsal_fd *fd, struct fsal_obj_handle **handle) { struct lzfs_fsal_fd *my_fd = NULL; struct lzfs_fsal_handle *myself = NULL; my_fd = container_of(fd, struct lzfs_fsal_fd, fsal_fd); myself = container_of(my_fd, struct lzfs_fsal_handle, fd); *handle = &myself->handle; } void lzfs_fsal_export_ops_init(struct export_ops *ops) { ops->release = lzfs_fsal_release; ops->lookup_path = lzfs_fsal_lookup_path; ops->wire_to_host = lzfs_fsal_wire_to_host; ops->create_handle = lzfs_fsal_create_handle; ops->get_fs_dynamic_info = lzfs_fsal_get_fs_dynamic_info; ops->fs_supports = lzfs_fsal_fs_supports; ops->fs_maxfilesize = lzfs_fsal_fs_maxfilesize; ops->fs_maxread = lzfs_fsal_fs_maxread; ops->fs_maxwrite = lzfs_fsal_fs_maxwrite; ops->fs_maxlink = lzfs_fsal_fs_maxlink; ops->fs_maxnamelen = lzfs_fsal_fs_maxnamelen; ops->fs_maxpathlen = lzfs_fsal_fs_maxpathlen; ops->fs_acl_support = lzfs_fsal_fs_acl_support; ops->fs_supported_attrs = lzfs_fsal_fs_supported_attrs; ops->fs_umask = lzfs_fsal_fs_umask; ops->alloc_state = lzfs_fsal_alloc_state; ops->get_fsal_obj_hdl = get_fsal_obj_hdl; lzfs_fsal_export_ops_pnfs(ops); } nfs-ganesha-6.5/src/FSAL/FSAL_LIZARDFS/fileinfo_cache.h000066400000000000000000000073461473756622300222350ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-3.0 */ /* Copyright 2017 Skytechnology sp. z o.o. This file is part of LizardFS. LizardFS 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, version 3. LizardFS 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 LizardFS. If not, see . */ #include "lizardfs/lizardfs_c_api.h" #ifdef __cplusplus extern "C" { #endif typedef struct liz_fileinfo_cache liz_fileinfo_cache_t; typedef struct liz_fileinfo_entry liz_fileinfo_entry_t; /*! * \brief Create fileinfo cache * \param max_entries max number of entries to be stored in cache * \param min_timeout_ms entries will not be removed until at least * min_timeout_ms ms has passed * \return pointer to fileinfo cache structure * \post Destroy with liz_destroy_fileinfo_cache function */ liz_fileinfo_cache_t *liz_create_fileinfo_cache(unsigned int max_entries, int min_timeout_ms); /*! * \brief Reset cache parameters * \param cache The cache to be modified * \param max_entries max number of entries to be stored in cache * \param min_timeout_ms entries will not be removed until at least * min_timeout_ms ms has passed */ void liz_reset_fileinfo_cache_params(liz_fileinfo_cache_t *cache, unsigned int max_entries, int min_timeout_ms); /*! * \brief Destroy fileinfo cache * \param cache pointer returned from liz_create_fileinfo_cache */ void liz_destroy_fileinfo_cache(liz_fileinfo_cache_t *cache); /*! * \brief Acquire fileinfo from cache * \param cache The cache to be modified * \param inode The inode of a file * \return Cache entry if succeeded, NULL if cache is full * \attention entry->fileinfo will be NULL if file still needs to be open first * \post Set fileinfo to a valid pointer after opening a file with * liz_attach_fileinfo */ liz_fileinfo_entry_t *liz_fileinfo_cache_acquire(liz_fileinfo_cache_t *cache, liz_inode_t inode); /*! * \brief Release fileinfo from cache * \param cache The cache to be modified * \param entry pointer returned from previous acquire() call */ void liz_fileinfo_cache_release(liz_fileinfo_cache_t *cache, liz_fileinfo_entry_t *entry); /*! * \brief Erase acquired entry * \attention This function should be used if entry should not reside in cache * (i.e. opening a file * failed) * \param cache The cache to be modified * \param entry pointer returned from previous acquire() call */ void liz_fileinfo_cache_erase(liz_fileinfo_cache_t *cache, liz_fileinfo_entry_t *entry); /*! * \brief Get expired fileinfo from cache * \param cache The cache to be modified * \return entry removed from cache * \post use this entry to call release() on entry->fileinfo and free entry * afterwards with * liz_fileinfo_entry_free */ liz_fileinfo_entry_t * liz_fileinfo_cache_pop_expired(liz_fileinfo_cache_t *cache); /*! * \brief Free unused fileinfo cache entry * \param entry The entry to be freed */ void liz_fileinfo_entry_free(liz_fileinfo_entry_t *entry); /*! * \brief Get fileinfo from cache entry * \param cache The cache to be modified * \return fileinfo extracted from entry */ liz_fileinfo_t *liz_extract_fileinfo(liz_fileinfo_entry_t *entry); /*! * \brief Attach fileinfo to an existing cache entry * \param entry The entry to be modified * \param fileinfoThe fileinfo to be attached to entry */ void liz_attach_fileinfo(liz_fileinfo_entry_t *entry, liz_fileinfo_t *fileinfo); #ifdef __cplusplus } #endif nfs-ganesha-6.5/src/FSAL/FSAL_LIZARDFS/handle.c000066400000000000000000001350641473756622300205440ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright (C) 2019 Skytechnology sp. z o.o. * * This program 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 3 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #include "config.h" #include #ifdef LINUX #include /* for makedev(3) */ #endif #include "fsal.h" #include "fsal_api.h" #include "fsal_convert.h" #include "fsal_types.h" #include "lizardfs/lizardfs_error_codes.h" #include "context_wrap.h" #include "lzfs_internal.h" /****************************************************************************** * @todo - FSF - this has been converted to the new fsal_fd handling but I am * very unsure of the conversion. It should not be trusted. ******************************************************************************/ /*! \brief Clean up a filehandle * * \see fsal_api.h for more information */ static void lzfs_fsal_release(struct fsal_obj_handle *obj_hdl) { struct lzfs_fsal_handle *lzfs_obj = container_of(obj_hdl, struct lzfs_fsal_handle, handle); if (lzfs_obj->handle.type == REGULAR_FILE) { fsal_status_t st; st = close_fsal_fd(obj_hdl, &lzfs_obj->fd.fsal_fd, false); if (FSAL_IS_ERROR(st)) { LogCrit(COMPONENT_FSAL, "Could not close hdl 0x%p, status %s error %s(%d)", obj_hdl, fsal_err_txt(st), strerror(st.minor), st.minor); } destroy_fsal_fd(&lzfs_obj->fd.fsal_fd); } if (lzfs_obj != lzfs_obj->export->root) { lzfs_fsal_delete_handle(lzfs_obj); } } /*! \brief Look up a filename * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_lookup(struct fsal_obj_handle *dir_hdl, const char *path, struct fsal_obj_handle **obj_hdl, struct fsal_attrlist *attrs_out) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *lzfs_obj, *lzfs_dir; struct liz_entry node; int rc; lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); lzfs_dir = container_of(dir_hdl, struct lzfs_fsal_handle, handle); LogFullDebug(COMPONENT_FSAL, "path=%s", path); rc = liz_cred_lookup(lzfs_export->lzfs_instance, &op_ctx->creds, lzfs_dir->inode, path, &node); if (rc < 0) { return lzfs_fsal_last_err(); } lzfs_obj = lzfs_fsal_new_handle(&node.attr, lzfs_export); if (attrs_out != NULL) { posix2fsal_attributes_all(&node.attr, attrs_out); } *obj_hdl = &lzfs_obj->handle; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /*! \brief Read a directory * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_readdir(struct fsal_obj_handle *dir_hdl, fsal_cookie_t *whence, void *dir_state, fsal_readdir_cb cb, attrmask_t attrmask, bool *eof) { static const int kBatchSize = 100; struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *lzfs_dir, *lzfs_obj; struct liz_direntry buffer[kBatchSize]; struct liz_fileinfo *dir_desc; struct fsal_attrlist attrs; off_t direntry_offset = 2; enum fsal_dir_result cb_rc; int rc; lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); lzfs_dir = container_of(dir_hdl, struct lzfs_fsal_handle, handle); liz_context_t *ctx = lzfs_fsal_create_context( lzfs_export->lzfs_instance, &op_ctx->creds); dir_desc = liz_opendir(lzfs_export->lzfs_instance, ctx, lzfs_dir->inode); if (!dir_desc) { liz_destroy_context(ctx); return lzfs_fsal_last_err(); } if (whence != NULL) { direntry_offset = MAX(3, *whence) - 1; } LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIu32 " offset=%lli", lzfs_export->export.export_id, lzfs_dir->inode, (long long)direntry_offset); while (1) { size_t i, entries_count = 0; rc = liz_readdir(lzfs_export->lzfs_instance, ctx, dir_desc, direntry_offset, kBatchSize, buffer, &entries_count); if (rc < 0) { liz_destroy_context(ctx); return lzfs_fsal_last_err(); } cb_rc = DIR_CONTINUE; for (i = 0; i < entries_count && cb_rc != DIR_TERMINATE; ++i) { lzfs_obj = lzfs_fsal_new_handle(&buffer[i].attr, lzfs_export); fsal_prepare_attrs(&attrs, attrmask); posix2fsal_attributes_all(&buffer[i].attr, &attrs); direntry_offset = buffer[i].next_entry_offset; cb_rc = cb(buffer[i].name, &lzfs_obj->handle, &attrs, dir_state, direntry_offset + 1); fsal_release_attrs(&attrs); } liz_destroy_direntry(buffer, entries_count); *eof = entries_count < kBatchSize && i == entries_count; if (cb_rc != DIR_CONTINUE || entries_count < kBatchSize) { break; } } rc = liz_releasedir(lzfs_export->lzfs_instance, dir_desc); liz_destroy_context(ctx); if (rc < 0) { return lzfs_fsal_last_err(); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /*! \brief Create a directory * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_mkdir(struct fsal_obj_handle *dir_hdl, const char *name, struct fsal_attrlist *attrib, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *lzfs_dir, *lzfs_obj; struct liz_entry dir_entry; mode_t unix_mode; fsal_status_t status; int rc; lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); lzfs_dir = container_of(dir_hdl, struct lzfs_fsal_handle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " parent_inode=%" PRIu32 " mode=%" PRIo32 " name=%s", lzfs_export->export.export_id, lzfs_dir->inode, attrib->mode, name); unix_mode = fsal2unix_mode(attrib->mode) & ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export); rc = liz_cred_mkdir(lzfs_export->lzfs_instance, &op_ctx->creds, lzfs_dir->inode, name, unix_mode, &dir_entry); if (rc < 0) { return lzfs_fsal_last_err(); } lzfs_obj = lzfs_fsal_new_handle(&dir_entry.attr, lzfs_export); *new_obj = &lzfs_obj->handle; FSAL_UNSET_MASK(attrib->valid_mask, ATTR_MODE); if (attrib->valid_mask) { status = (*new_obj)->obj_ops->setattr2(*new_obj, false, NULL, attrib); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "setattr2 status=%s", fsal_err_txt(status)); (*new_obj)->obj_ops->release(*new_obj); *new_obj = NULL; } } else { if (attrs_out != NULL) { posix2fsal_attributes_all(&dir_entry.attr, attrs_out); } } FSAL_SET_MASK(attrib->valid_mask, ATTR_MODE); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /*! \brief Create a special file * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_mknode(struct fsal_obj_handle *dir_hdl, const char *name, object_file_type_t nodetype, struct fsal_attrlist *attrib, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *lzfs_dir, *lzfs_obj; struct liz_entry node_entry; mode_t unix_mode; dev_t unix_dev = 0; int rc; lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); lzfs_dir = container_of(dir_hdl, struct lzfs_fsal_handle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " parent_inode=%" PRIu32 " mode=%" PRIo32 " name=%s", lzfs_export->export.export_id, lzfs_dir->inode, attrib->mode, name); unix_mode = fsal2unix_mode(attrib->mode) & ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export); switch (nodetype) { case BLOCK_FILE: unix_mode |= S_IFBLK; unix_dev = makedev(attrib->rawdev.major, attrib->rawdev.minor); break; case CHARACTER_FILE: unix_mode |= S_IFCHR; unix_dev = makedev(attrib->rawdev.major, attrib->rawdev.minor); break; case FIFO_FILE: unix_mode |= S_IFIFO; break; case SOCKET_FILE: unix_mode |= S_IFSOCK; break; default: LogMajor(COMPONENT_FSAL, "Invalid node type in FSAL_mknode: %d", nodetype); return fsalstat(ERR_FSAL_INVAL, EINVAL); } rc = liz_cred_mknod(lzfs_export->lzfs_instance, &op_ctx->creds, lzfs_dir->inode, name, unix_mode, unix_dev, &node_entry); if (rc < 0) { return lzfs_fsal_last_err(); } lzfs_obj = lzfs_fsal_new_handle(&node_entry.attr, lzfs_export); *new_obj = &lzfs_obj->handle; // We handled the mode above. FSAL_UNSET_MASK(attrib->valid_mask, ATTR_MODE); if (attrib->valid_mask) { fsal_status_t status = (*new_obj)->obj_ops->setattr2( *new_obj, false, NULL, attrib); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "setattr2 status=%s", fsal_err_txt(status)); (*new_obj)->obj_ops->release(*new_obj); *new_obj = NULL; } } else { if (attrs_out != NULL) { posix2fsal_attributes_all(&node_entry.attr, attrs_out); } } FSAL_SET_MASK(attrib->valid_mask, ATTR_MODE); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /*! \brief Create a symbolic link * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_symlink(struct fsal_obj_handle *dir_hdl, const char *name, const char *link_path, struct fsal_attrlist *attrib, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *lzfs_dir, *lzfs_obj; struct liz_entry node_entry; int rc; lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); lzfs_dir = container_of(dir_hdl, struct lzfs_fsal_handle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " parent_inode=%" PRIu32 " name=%s", lzfs_export->export.export_id, lzfs_dir->inode, name); rc = liz_cred_symlink(lzfs_export->lzfs_instance, &op_ctx->creds, link_path, lzfs_dir->inode, name, &node_entry); if (rc < 0) { return lzfs_fsal_last_err(); } lzfs_obj = lzfs_fsal_new_handle(&node_entry.attr, lzfs_export); *new_obj = &lzfs_obj->handle; FSAL_UNSET_MASK(attrib->valid_mask, ATTR_MODE); if (attrib->valid_mask) { fsal_status_t status = (*new_obj)->obj_ops->setattr2( *new_obj, false, NULL, attrib); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "setattr2 status=%s", fsal_err_txt(status)); (*new_obj)->obj_ops->release(*new_obj); *new_obj = NULL; } } else { if (attrs_out != NULL) { posix2fsal_attributes_all(&node_entry.attr, attrs_out); } } FSAL_SET_MASK(attrib->valid_mask, ATTR_MODE); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /*! \brief Read the content of a link * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_readlink(struct fsal_obj_handle *link_hdl, struct gsh_buffdesc *content_buf, bool refresh) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *lzfs_link; char result[LIZARDFS_MAX_READLINK_LENGTH]; int rc; lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); lzfs_link = container_of(link_hdl, struct lzfs_fsal_handle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIu32, lzfs_export->export.export_id, lzfs_link->inode); rc = liz_cred_readlink(lzfs_export->lzfs_instance, &op_ctx->creds, lzfs_link->inode, result, LIZARDFS_MAX_READLINK_LENGTH); if (rc < 0) { return lzfs_fsal_last_err(); } rc = MIN(rc, LIZARDFS_MAX_READLINK_LENGTH); content_buf->addr = gsh_strldup(result, rc, &content_buf->len); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /*! \brief Get attributes * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_getattrs(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *lzfs_obj; struct liz_attr_reply lzfs_attrs; int rc; lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); lzfs_obj = container_of(obj_hdl, struct lzfs_fsal_handle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIu32, lzfs_export->export.export_id, lzfs_obj->inode); rc = liz_cred_getattr(lzfs_export->lzfs_instance, &op_ctx->creds, lzfs_obj->inode, &lzfs_attrs); if (rc < 0) { if (attrs->request_mask & ATTR_RDATTR_ERR) { attrs->valid_mask = ATTR_RDATTR_ERR; } LogFullDebug(COMPONENT_FSAL, "getattrs status=%s", liz_error_string(liz_last_err())); return lzfs_fsal_last_err(); } posix2fsal_attributes_all(&lzfs_attrs.attr, attrs); if (attrs->request_mask & ATTR_ACL) { fsal_status_t status = lzfs_int_getacl(lzfs_export, lzfs_obj->inode, lzfs_attrs.attr.st_uid, &attrs->acl); if (!FSAL_IS_ERROR(status)) { attrs->valid_mask |= ATTR_ACL; } } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /*! \brief Rename a file * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_rename(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *olddir_hdl, const char *old_name, struct fsal_obj_handle *newdir_hdl, const char *new_name, struct fsal_attrlist *olddir_pre_attrs_out, struct fsal_attrlist *olddir_post_attrs_out, struct fsal_attrlist *newdir_pre_attrs_out, struct fsal_attrlist *newdir_post_attrs_out) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *lzfs_olddir, *lzfs_newdir; int rc; lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); lzfs_olddir = container_of(olddir_hdl, struct lzfs_fsal_handle, handle); lzfs_newdir = container_of(newdir_hdl, struct lzfs_fsal_handle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " old_inode=%" PRIu32 " new_inode=%" PRIu32 " old_name=%s new_name=%s", lzfs_export->export.export_id, lzfs_olddir->inode, lzfs_newdir->inode, old_name, new_name); rc = liz_cred_rename(lzfs_export->lzfs_instance, &op_ctx->creds, lzfs_olddir->inode, old_name, lzfs_newdir->inode, new_name); if (rc < 0) { return lzfs_fsal_last_err(); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /*! \brief Remove a name from a directory * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_unlink(struct fsal_obj_handle *dir_hdl, struct fsal_obj_handle *obj_hdl, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *lzfs_dir; int rc; lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); lzfs_dir = container_of(dir_hdl, struct lzfs_fsal_handle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " parent_inode=%" PRIu32 " name=%s type=%s", lzfs_export->export.export_id, lzfs_dir->inode, name, object_file_type_to_str(obj_hdl->type)); if (obj_hdl->type != DIRECTORY) { rc = liz_cred_unlink(lzfs_export->lzfs_instance, &op_ctx->creds, lzfs_dir->inode, name); } else { rc = liz_cred_rmdir(lzfs_export->lzfs_instance, &op_ctx->creds, lzfs_dir->inode, name); } if (rc < 0) { return lzfs_fsal_last_err(); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /*! \brief Write wire handle * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_handle_to_wire(const struct fsal_obj_handle *obj_hdl, uint32_t output_type, struct gsh_buffdesc *fh_desc) { struct lzfs_fsal_handle *lzfs_obj; lzfs_obj = container_of(obj_hdl, struct lzfs_fsal_handle, handle); liz_inode_t inode = lzfs_obj->inode; if (fh_desc->len < sizeof(liz_inode_t)) { LogMajor(COMPONENT_FSAL, "Space too small for handle. Need %zu, have %zu", sizeof(liz_inode_t), fh_desc->len); return fsalstat(ERR_FSAL_TOOSMALL, 0); } memcpy(fh_desc->addr, &inode, sizeof(liz_inode_t)); fh_desc->len = sizeof(inode); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /*! \brief Get key for handle * * \see fsal_api.h for more information */ static void lzfs_fsal_handle_to_key(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *fh_desc) { struct lzfs_fsal_handle *lzfs_obj; lzfs_obj = container_of(obj_hdl, struct lzfs_fsal_handle, handle); fh_desc->addr = &lzfs_obj->unique_key; fh_desc->len = sizeof(struct lzfs_fsal_key); } static fsal_status_t lzfs_int_close_fd(struct lzfs_fsal_handle *lzfs_obj, struct lzfs_fsal_fd *fd) { if (fd->fd != NULL && fd->fsal_fd.openflags != FSAL_O_CLOSED) { int rc = liz_release(lzfs_obj->export->lzfs_instance, fd->fd); fd->fd = NULL; fd->fsal_fd.openflags = FSAL_O_CLOSED; if (rc < 0) return lzfs_fsal_last_err(); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief LZFS Function to open or reopen a fsal_fd. * * @param[in] obj_hdl File on which to operate * @param[in] openflags New mode for open * @param[out] fsal_fd File descriptor that is to be used * * @return FSAL status. */ fsal_status_t lzfs_reopen_func(struct fsal_obj_handle *obj_hdl, fsal_openflags_t openflags, struct fsal_fd *fsal_fd) { struct lzfs_fsal_handle *myself; struct lzfs_fsal_fd *lzfs_fd; struct liz_fileinfo *fd; struct lzfs_fsal_export *lzfs_export; int posix_flags; myself = container_of(obj_hdl, struct lzfs_fsal_handle, handle); lzfs_fd = container_of(fsal_fd, struct lzfs_fsal_fd, fsal_fd); fsal2posix_openflags(openflags, &posix_flags); if (openflags & 0x1000) { /** @todo FSF - I don't think this is correct.... */ posix_flags |= O_CREAT; } lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); LogFullDebug(COMPONENT_FSAL, "fd = %p fd->fd = %p openflags = %x, posix_flags = %x", lzfs_fd, lzfs_fd->fd, openflags, posix_flags); assert(lzfs_fd->fd == NULL && lzfs_fd->fsal_fd.openflags == FSAL_O_CLOSED && openflags != 0); fd = liz_cred_open(lzfs_export->lzfs_instance, &op_ctx->creds, myself->inode, posix_flags); if (fd == NULL) { LogFullDebug(COMPONENT_FSAL, "open failed with %s", liz_error_string(liz_last_err())); return lzfs_fsal_last_err(); } if (lzfs_fd->fd != NULL && lzfs_fd->fsal_fd.openflags != FSAL_O_CLOSED) { int rc; rc = liz_release(myself->export->lzfs_instance, lzfs_fd->fd); if (rc < 0) { LogFullDebug(COMPONENT_FSAL, "close failed with %s", liz_error_string(liz_last_err())); /** @todo - what to do about error here... */ } } lzfs_fd->fd = fd; LogFullDebug(COMPONENT_FSAL, "fd = %p, new openflags = %x", lzfs_fd->fd, openflags); lzfs_fd->fsal_fd.openflags = openflags; return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t lzfs_int_close_func(struct fsal_obj_handle *obj_hdl, struct fsal_fd *fd) { struct lzfs_fsal_handle *lzfs_hdl; lzfs_hdl = container_of(obj_hdl, struct lzfs_fsal_handle, handle); return lzfs_int_close_fd(lzfs_hdl, (struct lzfs_fsal_fd *)fd); } static fsal_status_t lzfs_int_open_by_handle(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, fsal_verifier_t verifier, struct fsal_attrlist *attrs_out, bool after_mknod) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *lzfs_hdl; struct lzfs_fsal_fd *lzfs_fd; struct fsal_fd *fsal_fd; fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0); int posix_flags; fsal_openflags_t old_openflags; bool truncated = openflags & FSAL_O_TRUNC; lzfs_hdl = container_of(obj_hdl, struct lzfs_fsal_handle, handle); lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); if (state != NULL) lzfs_fd = &container_of(state, struct lzfs_fsal_state_fd, state) ->lzfs_fd; else lzfs_fd = &lzfs_hdl->fd; fsal_fd = &lzfs_fd->fsal_fd; /* Indicate we want to do fd work (can't fail since not reclaiming) */ fsal_start_fd_work_no_reclaim(fsal_fd); old_openflags = lzfs_fd->fsal_fd.openflags; if (state != NULL) { /* Prepare to take the share reservation, but only if we are * called with a valid state (if state is NULL the caller is a * stateless create such as NFS v3 CREATE and we're just going * to ignore share reservation stuff). */ /* Now that we have the mutex, and no I/O is in progress so we * have exclusive access to the share's fsal_fd, we can look at * its openflags. We also need to work the share reservation so * take the obj_lock. NOTE: This is the ONLY sequence where both * a work_mutex and the obj_lock are taken, so there is no * opportunity for ABBA deadlock. * * Note that we do hold the obj_lcok over an open and a close * which is longer than normal, but the previous iteration of * the code held the obj lock (read granted) over whole I/O * operations... We don't block over I/O because we've assured * that no I/O is in progress or can start before proceeding * past the above while loop. */ PTHREAD_RWLOCK_wrlock(&obj_hdl->obj_lock); /* Now check the new share. */ status = check_share_conflict(&lzfs_hdl->share, openflags, false); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "check_share_conflict returned %s", fsal_err_txt(status)); goto exit; } } /* Check for a genuine no-op open. That means we aren't trying to * create, the file is already open in the same mode with the same * deny flags, and we aren't trying to truncate. In this case we want * to avoid bouncing the fd. In the case of JUST changing the deny mode * or an replayed exclusive create, we might bounce the fd when we could * have avoided that, but those scenarios are much less common. */ if (FSAL_O_NFS_FLAGS(openflags) == FSAL_O_NFS_FLAGS(old_openflags) && truncated == false && createmode == FSAL_NO_CREATE) { LogFullDebug(COMPONENT_FSAL, "no-op reopen2 lzfs_fd->fd = %p openflags = %x", lzfs_fd->fd, openflags); goto exit; } /* No share conflict, re-open the share fd */ status = lzfs_reopen_func(obj_hdl, openflags | after_mknod ? 0x1000 : 0, fsal_fd); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "lzfs_reopen_func returned %s", fsal_err_txt(status)); goto exit; } /* Inserts to fd_lru only if open succeeds */ if (old_openflags == FSAL_O_CLOSED) { /* This is actually an open, need to increment * appropriate counter and insert into LRU. */ insert_fd_lru(fsal_fd); } else { /* Bump up the FD in fd_lru as it was already in fd lru. */ bump_fd_lru(fsal_fd); } fsal2posix_openflags(openflags, &posix_flags); if (createmode >= FSAL_EXCLUSIVE || attrs_out) { /* NOTE: won't come in here when called from vfs_reopen2... * truncated might be set, but attrs_out will be NULL. * We don't need to look at truncated since other callers * are interested in attrs_out. */ /* Refresh the attributes */ struct liz_attr_reply lzfs_attrs; int rc; rc = liz_cred_getattr(lzfs_export->lzfs_instance, &op_ctx->creds, lzfs_hdl->inode, &lzfs_attrs); if (rc == 0) { LogFullDebug(COMPONENT_FSAL, "New size = %" PRIx64, (int64_t)lzfs_attrs.attr.st_size); } else { status = lzfs_fsal_last_err(); } if (!FSAL_IS_ERROR(status) && createmode >= FSAL_EXCLUSIVE && createmode != FSAL_EXCLUSIVE_9P && !check_verifier_stat(&lzfs_attrs.attr, verifier, false)) { /* Verifier didn't match, return EEXIST */ status = fsalstat(posix2fsal_error(EEXIST), EEXIST); } if (!FSAL_IS_ERROR(status) && attrs_out) { posix2fsal_attributes_all(&lzfs_attrs.attr, attrs_out); } } if (FSAL_IS_ERROR(status)) { if (old_openflags == FSAL_O_CLOSED) { /* Now that we have decided to close this FD, * let's clean it off from fd_lru and * ensure counters are decremented. */ remove_fd_lru(fsal_fd); } /* Close fd */ (void)lzfs_int_close_fd(lzfs_hdl, lzfs_fd); } exit: if (state != NULL) { if (!FSAL_IS_ERROR(status)) { /* Success, establish the new share. */ update_share_counters(&lzfs_hdl->share, old_openflags, openflags); } /* Release obj_lock. */ PTHREAD_RWLOCK_unlock(&obj_hdl->obj_lock); } /* Indicate we are done with fd work and signal any waiters. */ fsal_complete_fd_work(fsal_fd); return status; } static fsal_status_t lzfs_int_open_by_name(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, const char *name, fsal_verifier_t verifier, struct fsal_attrlist *attrs_out) { struct fsal_obj_handle *temp = NULL; fsal_status_t status; status = obj_hdl->obj_ops->lookup(obj_hdl, name, &temp, NULL); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "lookup returned %s", fsal_err_txt(status)); return status; } status = lzfs_int_open_by_handle(temp, state, openflags, FSAL_NO_CREATE, verifier, attrs_out, false); if (FSAL_IS_ERROR(status)) { temp->obj_ops->release(temp); LogFullDebug(COMPONENT_FSAL, "open returned %s", fsal_err_txt(status)); } return status; } /*! \brief Open a file descriptor for read or write and possibly create * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_open2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attr_set, fsal_verifier_t verifier, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *lzfs_obj; fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0); int rc; LogFullDebug(COMPONENT_FSAL, "name=%s", name); LogAttrlist(COMPONENT_FSAL, NIV_FULL_DEBUG, "attrs ", attr_set, false); if (createmode >= FSAL_EXCLUSIVE) { set_common_verifier(attr_set, verifier, false); } if (name == NULL) { status = lzfs_int_open_by_handle(obj_hdl, state, openflags, createmode, verifier, attrs_out, false); *caller_perm_check = FSAL_IS_SUCCESS(status); return status; } *caller_perm_check = createmode == FSAL_NO_CREATE; if (createmode == FSAL_NO_CREATE) { return lzfs_int_open_by_name(obj_hdl, state, openflags, name, verifier, attrs_out); } /* * Create file */ lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); lzfs_obj = container_of(obj_hdl, struct lzfs_fsal_handle, handle); mode_t unix_mode = fsal2unix_mode(attr_set->mode) & ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export); FSAL_UNSET_MASK(attr_set->valid_mask, ATTR_MODE); struct liz_entry lzfs_attrs; rc = liz_cred_mknod(lzfs_export->lzfs_instance, &op_ctx->creds, lzfs_obj->inode, name, unix_mode, 0, &lzfs_attrs); if (rc < 0 && liz_last_err() == LIZARDFS_ERROR_EEXIST && createmode == FSAL_UNCHECKED) { return lzfs_int_open_by_name(obj_hdl, state, openflags, name, verifier, attrs_out); } if (rc < 0) { return lzfs_fsal_last_err(); } /* File has been created by us. */ *caller_perm_check = false; struct lzfs_fsal_handle *lzfs_new_obj = lzfs_fsal_new_handle(&lzfs_attrs.attr, lzfs_export); *new_obj = &lzfs_new_obj->handle; if (attr_set->valid_mask != 0) { status = (*new_obj)->obj_ops->setattr2(*new_obj, false, state, attr_set); if (FSAL_IS_ERROR(status)) { goto fileerr; } if (attrs_out != NULL) { status = (*new_obj)->obj_ops->getattrs(*new_obj, attrs_out); if (FSAL_IS_ERROR(status) && (attrs_out->request_mask & ATTR_RDATTR_ERR) == 0) { goto fileerr; } attrs_out = NULL; } } if (attrs_out != NULL) { posix2fsal_attributes_all(&lzfs_attrs.attr, attrs_out); } return lzfs_int_open_by_handle(*new_obj, state, openflags, createmode, verifier, NULL, true); fileerr: (*new_obj)->obj_ops->release(*new_obj); *new_obj = NULL; rc = liz_cred_unlink(lzfs_export->lzfs_instance, &op_ctx->creds, lzfs_obj->inode, name); return status; } /*! \brief Return open status of a state. * * \see fsal_api.h for more information */ static fsal_openflags_t lzfs_fsal_status2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { struct lzfs_fsal_fd *lzfs_fd; lzfs_fd = &container_of(state, struct lzfs_fsal_state_fd, state)->lzfs_fd; return lzfs_fd->fsal_fd.openflags; } /*! \brief Re-open a file that may be already opened * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_reopen2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags) { return lzfs_int_open_by_handle(obj_hdl, state, openflags, FSAL_NO_CREATE, NULL, NULL, true); } /** * \brief Read data from a file * * \see fsal_api.h for more information */ static void lzfs_fsal_read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *lzfs_obj; struct lzfs_fsal_fd *my_fd; struct lzfs_fsal_fd temp_fd = { FSAL_FD_INIT, NULL }; struct fsal_fd *out_fd; fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0), status2; ssize_t nb_read; uint64_t offset = read_arg->offset; int i; lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); lzfs_obj = container_of(obj_hdl, struct lzfs_fsal_handle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIu32 " offset=%" PRIu64, lzfs_export->export.export_id, lzfs_obj->inode, offset); if (read_arg->info != NULL) { done_cb(obj_hdl, fsalstat(ERR_FSAL_NOTSUPP, 0), read_arg, caller_arg); return; } /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &lzfs_obj->fd.fsal_fd, &temp_fd.fsal_fd, read_arg->state, FSAL_O_READ, false, NULL, bypass, &lzfs_obj->share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct lzfs_fsal_fd, fsal_fd); for (i = 0; i < read_arg->iov_count; i++) { nb_read = liz_cred_read(lzfs_export->lzfs_instance, &op_ctx->creds, my_fd->fd, offset, read_arg->iov[i].iov_len, read_arg->iov[i].iov_base); if (offset == -1 || nb_read < 0) { status = lzfs_fsal_last_err(); goto out; } else if (offset == 0) { break; } read_arg->io_amount += nb_read; offset += nb_read; } read_arg->end_of_file = (read_arg->io_amount == 0); out: status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (read_arg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &lzfs_obj->share, FSAL_O_READ, FSAL_O_CLOSED); } exit: done_cb(obj_hdl, status, read_arg, caller_arg); } /*! \brief Write data to a file * * \see fsal_api.h for more information */ static void lzfs_fsal_write2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *caller_arg) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *lzfs_obj; struct lzfs_fsal_fd *my_fd; struct lzfs_fsal_fd temp_fd = { FSAL_FD_INIT, NULL }; struct fsal_fd *out_fd; fsal_status_t status, status2; ssize_t nb_written; uint64_t offset = write_arg->offset; int i; lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); lzfs_obj = container_of(obj_hdl, struct lzfs_fsal_handle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIu32 " offset=%" PRIu64, lzfs_export->export.export_id, lzfs_obj->inode, offset); if (write_arg->info) { return done_cb(obj_hdl, fsalstat(ERR_FSAL_NOTSUPP, 0), write_arg, caller_arg); } /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &lzfs_obj->fd.fsal_fd, &temp_fd.fsal_fd, write_arg->state, FSAL_O_WRITE, false, NULL, bypass, &lzfs_obj->share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct lzfs_fsal_fd, fsal_fd); for (i = 0; i < write_arg->iov_count; i++) { nb_written = liz_cred_write(lzfs_export->lzfs_instance, &op_ctx->creds, my_fd->fd, offset, write_arg->iov[i].iov_len, write_arg->iov[i].iov_base); if (nb_written < 0) { status = lzfs_fsal_last_err(); goto out; } else { write_arg->io_amount = nb_written; if (write_arg->fsal_stable) { int rc = liz_cred_fsync( lzfs_export->lzfs_instance, &op_ctx->creds, my_fd->fd); if (rc < 0) { status = lzfs_fsal_last_err(); } } } write_arg->io_amount += nb_written; offset += nb_written; } out: status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (write_arg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &lzfs_obj->share, FSAL_O_WRITE, FSAL_O_CLOSED); } exit: done_cb(obj_hdl, status, write_arg, caller_arg); } /*! \brief Commit written data * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_commit2(struct fsal_obj_handle *obj_hdl, off_t offset, size_t len) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *lzfs_obj; fsal_status_t status, status2; struct lzfs_fsal_fd temp_fd = { FSAL_FD_INIT, NULL }; struct fsal_fd *out_fd; struct lzfs_fsal_fd *my_fd; int rc; lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); lzfs_obj = container_of(obj_hdl, struct lzfs_fsal_handle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIu32 " offset=%lli len=%zu", lzfs_export->export.export_id, lzfs_obj->inode, (long long)offset, len); /* Make sure file is open in appropriate mode. * Do not check share reservation. */ status = fsal_start_global_io(&out_fd, obj_hdl, &lzfs_obj->fd.fsal_fd, &temp_fd.fsal_fd, FSAL_O_ANY, false, NULL); if (FSAL_IS_ERROR(status)) return status; my_fd = container_of(out_fd, struct lzfs_fsal_fd, fsal_fd); rc = liz_cred_fsync(lzfs_export->lzfs_instance, &op_ctx->creds, my_fd->fd); if (rc < 0) status = lzfs_fsal_last_err(); status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); /* We did not do share reservation stuff... */ return status; } /*! \brief Close a file * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_close(struct fsal_obj_handle *obj_hdl) { struct lzfs_fsal_handle *lzfs_obj; lzfs_obj = container_of(obj_hdl, struct lzfs_fsal_handle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIu32, lzfs_obj->unique_key.export_id, lzfs_obj->inode); return close_fsal_fd(obj_hdl, &lzfs_obj->fd.fsal_fd, false); } /*! \brief Merge a duplicate handle with an original handle * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_merge(struct fsal_obj_handle *orig_hdl, struct fsal_obj_handle *dupe_hdl) { fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0); if (orig_hdl->type == REGULAR_FILE && dupe_hdl->type == REGULAR_FILE) { struct lzfs_fsal_handle *lzfs_orig, *lzfs_dupe; lzfs_orig = container_of(orig_hdl, struct lzfs_fsal_handle, handle); lzfs_dupe = container_of(dupe_hdl, struct lzfs_fsal_handle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu32 " orig_inode=%" PRIu16 " dupe_inode=%" PRIu32, lzfs_orig->unique_key.export_id, lzfs_orig->inode, lzfs_dupe->inode); /* This can block over an I/O operation. */ status = merge_share(orig_hdl, &lzfs_orig->share, &lzfs_dupe->share); } return status; } /*! \brief Set attributes on an object * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_setattr2(struct fsal_obj_handle *obj_hdl, bool bypass, struct state_t *state, struct fsal_attrlist *attrib_set) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *lzfs_obj; fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0); bool has_share = false; lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); lzfs_obj = container_of(obj_hdl, struct lzfs_fsal_handle, handle); LogAttrlist(COMPONENT_FSAL, NIV_FULL_DEBUG, "attrs ", attrib_set, false); if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MODE)) { attrib_set->mode &= ~op_ctx->fsal_export->exp_ops.fs_umask( op_ctx->fsal_export); } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_SIZE)) { if (obj_hdl->type != REGULAR_FILE) { LogFullDebug(COMPONENT_FSAL, "Setting size on non-regular file"); return fsalstat(ERR_FSAL_INVAL, EINVAL); } if (state == NULL) { /* Check share reservation and if OK, update the * counters. */ status = check_share_conflict_and_update_locked( obj_hdl, &lzfs_obj->share, FSAL_O_CLOSED, FSAL_O_WRITE, bypass); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "check_share_conflict failed with %s", fsal_err_txt(status)); return status; } has_share = true; } } struct stat attr; int mask = 0; memset(&attr, 0, sizeof(attr)); if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_SIZE)) { mask |= LIZ_SET_ATTR_SIZE; attr.st_size = attrib_set->filesize; LogFullDebug(COMPONENT_FSAL, "setting size to %lld", (long long)attr.st_size); } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MODE)) { mask |= LIZ_SET_ATTR_MODE; attr.st_mode = fsal2unix_mode(attrib_set->mode); } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_OWNER)) { mask |= LIZ_SET_ATTR_UID; attr.st_uid = attrib_set->owner; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_GROUP)) { mask |= LIZ_SET_ATTR_GID; attr.st_gid = attrib_set->group; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_ATIME)) { mask |= LIZ_SET_ATTR_ATIME; attr.st_atim = attrib_set->atime; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_ATIME_SERVER)) { mask |= LIZ_SET_ATTR_ATIME_NOW; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MTIME)) { mask |= LIZ_SET_ATTR_MTIME; attr.st_mtim = attrib_set->mtime; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MTIME_SERVER)) { mask |= LIZ_SET_ATTR_MTIME_NOW; } liz_attr_reply_t reply; int rc = liz_cred_setattr(lzfs_export->lzfs_instance, &op_ctx->creds, lzfs_obj->inode, &attr, mask, &reply); if (rc < 0) { LogFullDebug(COMPONENT_FSAL, "liz_setattr returned %s (%d)", liz_error_string(liz_last_err()), liz_last_err()); status = lzfs_fsal_last_err(); goto out; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_ACL)) { status = lzfs_int_setacl(lzfs_export, lzfs_obj->inode, attrib_set->acl); } out: if (has_share) { /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &lzfs_obj->share, FSAL_O_WRITE, FSAL_O_CLOSED); } return status; } /*! \brief Manage closing a file when a state is no longer needed. * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_close2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { struct lzfs_fsal_handle *lzfs_obj; struct lzfs_fsal_fd *my_fd = &container_of(state, struct lzfs_fsal_state_fd, state)->lzfs_fd; lzfs_obj = container_of(obj_hdl, struct lzfs_fsal_handle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIu32, lzfs_obj->unique_key.export_id, lzfs_obj->inode); if (state->state_type == STATE_TYPE_SHARE || state->state_type == STATE_TYPE_NLM_SHARE || state->state_type == STATE_TYPE_9P_FID) { update_share_counters_locked(obj_hdl, &lzfs_obj->share, lzfs_obj->fd.fsal_fd.openflags, FSAL_O_CLOSED); } return close_fsal_fd(obj_hdl, &my_fd->fsal_fd, false); } fsal_status_t lzfs_fsal_lock_op2(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *owner, fsal_lock_op_t lock_op, fsal_lock_param_t *request_lock, fsal_lock_param_t *conflicting_lock) { struct lzfs_fsal_handle *lzfs_obj; struct lzfs_fsal_export *lzfs_export; liz_err_t last_err; liz_fileinfo_t *fileinfo; liz_lock_info_t lock_info; fsal_status_t status = { 0, 0 }, status2; int retval = 0; fsal_openflags_t openflags = FSAL_O_RDWR; struct lzfs_fsal_fd *my_fd; struct lzfs_fsal_fd temp_fd = { FSAL_FD_INIT, NULL }; struct fsal_fd *out_fd; bool bypass = false; lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); lzfs_obj = container_of(obj_hdl, struct lzfs_fsal_handle, handle); LogFullDebug(COMPONENT_FSAL, "op:%d type:%d start:%" PRIu64 " length:%" PRIu64 " ", lock_op, request_lock->lock_type, request_lock->lock_start, request_lock->lock_length); if (lock_op == FSAL_OP_LOCKT) { /* We may end up using global fd, don't fail on a deny mode */ bypass = true; openflags = FSAL_O_ANY; } else if (lock_op == FSAL_OP_LOCK) { if (request_lock->lock_type == FSAL_LOCK_R) { openflags = FSAL_O_READ; } else if (request_lock->lock_type == FSAL_LOCK_W) { openflags = FSAL_O_WRITE; } } else if (lock_op == FSAL_OP_UNLOCK) { openflags = FSAL_O_ANY; } else { LogFullDebug( COMPONENT_FSAL, "ERROR: Lock operation requested was not TEST, READ, or WRITE."); return fsalstat(ERR_FSAL_NOTSUPP, 0); } if (lock_op != FSAL_OP_LOCKT && state == NULL) { LogCrit(COMPONENT_FSAL, "Non TEST operation with NULL state"); return posix2fsal_status(EINVAL); } if (request_lock->lock_type == FSAL_LOCK_R) { lock_info.l_type = F_RDLCK; } else if (request_lock->lock_type == FSAL_LOCK_W) { lock_info.l_type = F_WRLCK; } else { LogFullDebug( COMPONENT_FSAL, "ERROR: The requested lock type was not read or write."); return fsalstat(ERR_FSAL_NOTSUPP, 0); } if (lock_op == FSAL_OP_UNLOCK) { lock_info.l_type = F_UNLCK; } lock_info.l_pid = 0; lock_info.l_len = request_lock->lock_length; lock_info.l_start = request_lock->lock_start; /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &lzfs_obj->fd.fsal_fd, &temp_fd.fsal_fd, state, openflags, true, NULL, bypass, &lzfs_obj->share); if (FSAL_IS_ERROR(status)) { LogCrit(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct lzfs_fsal_fd, fsal_fd); fileinfo = my_fd->fd; liz_set_lock_owner(fileinfo, (uint64_t)owner); if (lock_op == FSAL_OP_LOCKT) { retval = liz_cred_getlk(lzfs_export->lzfs_instance, &op_ctx->creds, fileinfo, &lock_info); } else { retval = liz_cred_setlk(lzfs_export->lzfs_instance, &op_ctx->creds, fileinfo, &lock_info); } if (retval < 0) { last_err = liz_last_err(); status = lizardfs2fsal_error(last_err); LogFullDebug(COMPONENT_FSAL, "Returning error %d", last_err); goto err; } /* F_UNLCK is returned then the tested operation would be possible. */ if (conflicting_lock != NULL) { if (lock_op == FSAL_OP_LOCKT && lock_info.l_type != F_UNLCK) { conflicting_lock->lock_length = lock_info.l_len; conflicting_lock->lock_start = lock_info.l_start; conflicting_lock->lock_type = lock_info.l_type; } else { conflicting_lock->lock_length = 0; conflicting_lock->lock_start = 0; conflicting_lock->lock_type = FSAL_NO_LOCK; } } err: last_err = liz_last_err(); status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &lzfs_obj->share, openflags, FSAL_O_CLOSED); } exit: return status; } /*! \brief Create a new link * * \see fsal_api.h for more information */ static fsal_status_t lzfs_fsal_link(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *destdir_hdl, const char *name, struct fsal_attrlist *destdir_pre_attrs_out, struct fsal_attrlist *destdir_post_attrs_out) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *lzfs_obj, *lzfs_destdir; lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); lzfs_obj = container_of(obj_hdl, struct lzfs_fsal_handle, handle); lzfs_destdir = container_of(destdir_hdl, struct lzfs_fsal_handle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIu32 " dest_inode=%" PRIu32 " name=%s", lzfs_export->export.export_id, lzfs_obj->inode, lzfs_destdir->inode, name); liz_entry_t result; int rc = liz_cred_link(lzfs_export->lzfs_instance, &op_ctx->creds, lzfs_obj->inode, lzfs_destdir->inode, name, &result); if (rc < 0) { return lzfs_fsal_last_err(); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } void lzfs_fsal_handle_ops_init(struct lzfs_fsal_export *lzfs_export, struct fsal_obj_ops *ops) { ops->release = lzfs_fsal_release; ops->merge = lzfs_fsal_merge; ops->lookup = lzfs_fsal_lookup; ops->mkdir = lzfs_fsal_mkdir; ops->mknode = lzfs_fsal_mknode; ops->readdir = lzfs_fsal_readdir; ops->symlink = lzfs_fsal_symlink; ops->readlink = lzfs_fsal_readlink; ops->getattrs = lzfs_fsal_getattrs; ops->link = lzfs_fsal_link; ops->rename = lzfs_fsal_rename; ops->unlink = lzfs_fsal_unlink; ops->close = lzfs_fsal_close; ops->handle_to_wire = lzfs_fsal_handle_to_wire; ops->handle_to_key = lzfs_fsal_handle_to_key; ops->open2 = lzfs_fsal_open2; ops->status2 = lzfs_fsal_status2; ops->reopen2 = lzfs_fsal_reopen2; ops->read2 = lzfs_fsal_read2; ops->write2 = lzfs_fsal_write2; ops->commit2 = lzfs_fsal_commit2; ops->setattr2 = lzfs_fsal_setattr2; ops->close2 = lzfs_fsal_close2; ops->lock_op2 = lzfs_fsal_lock_op2; ops->close_func = lzfs_int_close_func; ops->reopen_func = lzfs_reopen_func; if (lzfs_export->pnfs_mds_enabled) { lzfs_fsal_handle_ops_pnfs(ops); } } nfs-ganesha-6.5/src/FSAL/FSAL_LIZARDFS/lzfs_acl.c000066400000000000000000000131241473756622300210760ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright (C) 2019 Skytechnology sp. z o.o. * * This program 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 3 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #include "context_wrap.h" #include "lzfs_internal.h" void lzfs_int_apply_masks(liz_acl_t *lzfs_acl, uint32_t owner); liz_acl_t *lzfs_int_convert_fsal_acl(const fsal_acl_t *fsal_acl) { liz_acl_t *lzfs_acl = NULL; if (!fsal_acl || (!fsal_acl->aces && fsal_acl->naces > 0)) { return NULL; } int count = 0; for (unsigned int i = 0; i < fsal_acl->naces; ++i) { fsal_ace_t *fsal_ace = fsal_acl->aces + i; count += (IS_FSAL_ACE_ALLOW(*fsal_ace) || IS_FSAL_ACE_DENY(*fsal_ace)) ? 1 : 0; } lzfs_acl = liz_create_acl(); if (!lzfs_acl) { return NULL; } for (unsigned int i = 0; i < fsal_acl->naces; ++i) { fsal_ace_t *fsal_ace = fsal_acl->aces + i; if (!(IS_FSAL_ACE_ALLOW(*fsal_ace) || IS_FSAL_ACE_DENY(*fsal_ace))) { continue; } liz_acl_ace_t ace; ace.flags = fsal_ace->flag & 0xFF; ace.mask = fsal_ace->perm; ace.type = fsal_ace->type; if (IS_FSAL_ACE_GROUP_ID(*fsal_ace)) { ace.id = GET_FSAL_ACE_GROUP(*fsal_ace); } else { ace.id = GET_FSAL_ACE_USER(*fsal_ace); } if (IS_FSAL_ACE_SPECIAL_ID(*fsal_ace)) { ace.flags |= LIZ_ACL_SPECIAL_WHO; switch (GET_FSAL_ACE_USER(*fsal_ace)) { case FSAL_ACE_SPECIAL_OWNER: ace.id = LIZ_ACL_OWNER_SPECIAL_ID; break; case FSAL_ACE_SPECIAL_GROUP: ace.id = LIZ_ACL_GROUP_SPECIAL_ID; break; case FSAL_ACE_SPECIAL_EVERYONE: ace.id = LIZ_ACL_EVERYONE_SPECIAL_ID; break; default: LogFullDebug( COMPONENT_FSAL, "Invalid FSAL ACE special id type (%d)", (int)GET_FSAL_ACE_USER(*fsal_ace)); continue; } } liz_add_acl_entry(lzfs_acl, &ace); } return lzfs_acl; } fsal_acl_t *lzfs_int_convert_lzfs_acl(const liz_acl_t *lzfs_acl) { fsal_acl_data_t acl_data; fsal_acl_status_t acl_status; fsal_acl_t *fsal_acl = NULL; if (!lzfs_acl) { return NULL; } acl_data.naces = liz_get_acl_size(lzfs_acl); acl_data.aces = (fsal_ace_t *)nfs4_ace_alloc(acl_data.naces); if (!acl_data.aces) { return NULL; } for (unsigned int i = 0; i < acl_data.naces; ++i) { fsal_ace_t *fsal_ace = acl_data.aces + i; liz_acl_ace_t lzfs_ace; int rc = liz_get_acl_entry(lzfs_acl, i, &lzfs_ace); (void)rc; assert(rc == 0); fsal_ace->type = lzfs_ace.type; fsal_ace->flag = lzfs_ace.flags & 0xFF; fsal_ace->iflag = (lzfs_ace.flags & LIZ_ACL_SPECIAL_WHO) ? FSAL_ACE_IFLAG_SPECIAL_ID : 0; if (IS_FSAL_ACE_GROUP_ID(*fsal_ace)) { fsal_ace->who.gid = lzfs_ace.id; } else { fsal_ace->who.uid = lzfs_ace.id; } if (IS_FSAL_ACE_SPECIAL_ID(*fsal_ace)) { switch (lzfs_ace.id) { case LIZ_ACL_OWNER_SPECIAL_ID: fsal_ace->who.uid = FSAL_ACE_SPECIAL_OWNER; break; case LIZ_ACL_GROUP_SPECIAL_ID: fsal_ace->who.uid = FSAL_ACE_SPECIAL_GROUP; break; case LIZ_ACL_EVERYONE_SPECIAL_ID: fsal_ace->who.uid = FSAL_ACE_SPECIAL_EVERYONE; break; default: fsal_ace->who.uid = FSAL_ACE_NORMAL_WHO; LogWarn(COMPONENT_FSAL, "Invalid LizardFS ACE special id type (%u)", (unsigned int)lzfs_ace.id); } } } fsal_acl = nfs4_acl_new_entry(&acl_data, &acl_status); LogDebug(COMPONENT_FSAL, "fsal acl = %p, fsal_acl_status = %u", fsal_acl, acl_status); return fsal_acl; } fsal_status_t lzfs_int_getacl(struct lzfs_fsal_export *lzfs_export, uint32_t inode, uint32_t owner_id, fsal_acl_t **fsal_acl) { if (*fsal_acl) { nfs4_acl_release_entry(*fsal_acl); *fsal_acl = NULL; } liz_acl_t *acl = NULL; int rc = liz_cred_getacl(lzfs_export->lzfs_instance, &op_ctx->creds, inode, &acl); if (rc < 0) { LogFullDebug(COMPONENT_FSAL, "getacl status=%s export=%" PRIu16 " inode=%" PRIu32, liz_error_string(liz_last_err()), lzfs_export->export.export_id, inode); return lzfs_fsal_last_err(); } lzfs_int_apply_masks(acl, owner_id); *fsal_acl = lzfs_int_convert_lzfs_acl(acl); liz_destroy_acl(acl); if (*fsal_acl == NULL) { LogFullDebug( COMPONENT_FSAL, "Failed to convert lzfs acl to nfs4 acl, export=%" PRIu16 " inode=%" PRIu32, lzfs_export->export.export_id, inode); return fsalstat(ERR_FSAL_FAULT, 0); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t lzfs_int_setacl(struct lzfs_fsal_export *lzfs_export, uint32_t inode, const fsal_acl_t *fsal_acl) { if (!fsal_acl) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } liz_acl_t *lzfs_acl = lzfs_int_convert_fsal_acl(fsal_acl); if (!lzfs_acl) { LogFullDebug(COMPONENT_FSAL, "failed to convert acl"); return fsalstat(ERR_FSAL_FAULT, 0); } int rc = liz_cred_setacl(lzfs_export->lzfs_instance, &op_ctx->creds, inode, lzfs_acl); liz_destroy_acl(lzfs_acl); if (rc < 0) { LogFullDebug(COMPONENT_FSAL, "setacl returned %s (%d)", liz_error_string(liz_last_err()), (int)liz_last_err()); return lzfs_fsal_last_err(); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } nfs-ganesha-6.5/src/FSAL/FSAL_LIZARDFS/lzfs_internal.c000066400000000000000000000074731473756622300221650ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright (C) 2019 Skytechnology sp. z o.o. * * This program 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 3 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #include "fsal.h" #include "fsal_convert.h" #include "pnfs_utils.h" #include "lzfs_internal.h" fsal_status_t lizardfs2fsal_error(int ec) { fsal_status_t status; if (!ec) { LogWarn(COMPONENT_FSAL, "appropriate errno not set"); ec = EINVAL; } status.minor = ec; status.major = posix2fsal_error(liz_error_conv(ec)); return status; } nfsstat4 lizardfs2nfs4_error(int ec) { if (!ec) { LogWarn(COMPONENT_FSAL, "appropriate errno not set"); ec = EINVAL; } return posix2nfs4_error(liz_error_conv(ec)); } fsal_status_t lzfs_fsal_last_err(void) { return lizardfs2fsal_error(liz_last_err()); } nfsstat4 lzfs_nfs4_last_err(void) { return lizardfs2nfs4_error(liz_last_err()); } liz_context_t *lzfs_fsal_create_context(liz_t *instance, struct user_cred *cred) { static const int kLocalGArraySize = 64; if (cred == NULL) { liz_context_t *ctx = liz_create_user_context(0, 0, 0, 0); return ctx; } liz_context_t *ctx; uid_t uid = (cred->caller_uid == op_ctx->export_perms.anonymous_uid) ? 0 : cred->caller_uid; gid_t gid = (cred->caller_gid == op_ctx->export_perms.anonymous_gid) ? 0 : cred->caller_gid; ctx = liz_create_user_context(uid, gid, 0, 0); if (!ctx) { return NULL; } if (cred->caller_glen > 0) { if (cred->caller_glen > kLocalGArraySize) { gid_t *garray = gsh_malloc((cred->caller_glen + 1) * sizeof(gid_t)); garray[0] = gid; memcpy(garray + 1, cred->caller_garray, sizeof(gid_t) * cred->caller_glen); liz_update_groups(instance, ctx, garray, cred->caller_glen + 1); free(garray); return ctx; } gid_t garray[kLocalGArraySize + 1]; garray[0] = gid; int count = MIN(cred->caller_glen, kLocalGArraySize); memcpy(garray + 1, cred->caller_garray, sizeof(gid_t) * count); liz_update_groups(instance, ctx, garray, count + 1); } return ctx; } fsal_staticfsinfo_t *lzfs_fsal_staticinfo(struct fsal_module *module_hdl) { struct lzfs_fsal_module *lzfs_module = container_of(module_hdl, struct lzfs_fsal_module, fsal); return &lzfs_module->fs_info; } struct lzfs_fsal_handle * lzfs_fsal_new_handle(const struct stat *attr, struct lzfs_fsal_export *lzfs_export) { struct lzfs_fsal_handle *result = NULL; result = gsh_calloc(1, sizeof(struct lzfs_fsal_handle)); result->inode = attr->st_ino; result->unique_key.module_id = FSAL_ID_LIZARDFS; result->unique_key.export_id = lzfs_export->export.export_id; result->unique_key.inode = attr->st_ino; fsal_obj_handle_init(&result->handle, &lzfs_export->export, posix2fsal_type(attr->st_mode), true); lzfs_fsal_handle_ops_init(lzfs_export, result->handle.obj_ops); result->handle.fsid = posix2fsal_fsid(attr->st_dev); result->handle.fileid = attr->st_ino; result->export = lzfs_export; if (result->handle.type == REGULAR_FILE) { init_fsal_fd(&result->fd.fsal_fd, FSAL_FD_GLOBAL, op_ctx->fsal_export); } return result; } void lzfs_fsal_delete_handle(struct lzfs_fsal_handle *obj) { fsal_obj_handle_fini(&obj->handle, true); gsh_free(obj); } nfs-ganesha-6.5/src/FSAL/FSAL_LIZARDFS/lzfs_internal.h000066400000000000000000000120331473756622300221560ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright (C) 2019 Skytechnology sp. z o.o. * * This program 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 3 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #include "fsal_api.h" #include "FSAL/fsal_commonlib.h" #include "lizardfs/lizardfs_c_api.h" #include "fileinfo_cache.h" #define LIZARDFS_VERSION(major, minor, micro) \ (0x010000 * major + 0x0100 * minor + micro) #define kDisconnectedChunkserverVersion LIZARDFS_VERSION(256, 0, 0) #define MFS_NAME_MAX 255 #define MFSBLOCKSIZE 65536 #define MFSCHUNKSIZE (65536 * 1024) #define SPECIAL_INODE_BASE 0xFFFFFFF0U #define SPECIAL_INODE_ROOT 0x01U #define SPECIAL_INODE_MASTERINFO (SPECIAL_INODE_BASE + 0xFU) #define SPECIAL_INODE_STATS (SPECIAL_INODE_BASE + 0x0U) #define SPECIAL_INODE_OPLOG (SPECIAL_INODE_BASE + 0x1U) #define SPECIAL_INODE_OPHISTORY (SPECIAL_INODE_BASE + 0x2U) #define SPECIAL_INODE_TWEAKS (SPECIAL_INODE_BASE + 0x3U) #define SPECIAL_INODE_FILE_BY_INODE (SPECIAL_INODE_BASE + 0x4U) #define SPECIAL_INODE_META_TRASH (SPECIAL_INODE_BASE + 0x5U) #define SPECIAL_INODE_META_UNDEL (SPECIAL_INODE_BASE + 0x6U) #define SPECIAL_INODE_META_RESERVED (SPECIAL_INODE_BASE + 0x7U) #define SPECIAL_FILE_NAME_MASTERINFO ".masterinfo" #define SPECIAL_FILE_NAME_STATS ".stats" #define SPECIAL_FILE_NAME_OPLOG ".oplog" #define SPECIAL_FILE_NAME_OPHISTORY ".ophistory" #define SPECIAL_FILE_NAME_TWEAKS ".lizardfs_tweaks" #define SPECIAL_FILE_NAME_FILE_BY_INODE ".lizardfs_file_by_inode" #define SPECIAL_FILE_NAME_META_TRASH "trash" #define SPECIAL_FILE_NAME_META_UNDEL "undel" #define SPECIAL_FILE_NAME_META_RESERVED "reserved" #define MAX_REGULAR_INODE (SPECIAL_INODE_BASE - 0x01U) struct lzfs_fsal_module { struct fsal_module fsal; fsal_staticfsinfo_t fs_info; }; struct lzfs_fsal_handle; struct lzfs_fsal_export { struct fsal_export export; /*< The public export object */ liz_t *lzfs_instance; struct lzfs_fsal_handle *root; /*< The root handle */ liz_fileinfo_cache_t *fileinfo_cache; bool pnfs_mds_enabled; bool pnfs_ds_enabled; uint32_t fileinfo_cache_timeout; uint32_t fileinfo_cache_max_size; liz_init_params_t lzfs_params; }; struct lzfs_fsal_fd { /** open and share mode plus fd management */ struct fsal_fd fsal_fd; /** The lizardfs file descriptor. */ struct liz_fileinfo *fd; }; struct lzfs_fsal_state_fd { /** state MUST be first to use default free_state */ struct state_t state; struct lzfs_fsal_fd lzfs_fd; }; struct lzfs_fsal_key { uint16_t module_id; uint16_t export_id; liz_inode_t inode; }; struct lzfs_fsal_handle { struct fsal_obj_handle handle; /*< The public handle */ struct lzfs_fsal_fd fd; liz_inode_t inode; struct lzfs_fsal_key unique_key; struct lzfs_fsal_export *export; struct fsal_share share; }; struct lzfs_fsal_ds_wire { uint32_t inode; }; struct lzfs_fsal_ds_handle { struct fsal_ds_handle ds; uint32_t inode; liz_fileinfo_entry_t *cache_handle; }; #define LZFS_SUPPORTED_ATTRS \ (ATTR_TYPE | ATTR_SIZE | ATTR_FSID | ATTR_FILEID | ATTR_MODE | \ ATTR_NUMLINKS | ATTR_OWNER | ATTR_GROUP | ATTR_ATIME | ATTR_CTIME | \ ATTR_MTIME | ATTR_CHANGE | ATTR_SPACEUSED | ATTR_RAWDEV | ATTR_ACL) #define LZFS_BIGGEST_STRIPE_COUNT 4096 #define LZFS_STD_CHUNK_PART_TYPE 0 #define LZFS_EXPECTED_BACKUP_DS_COUNT 3 #define TCP_PROTO_NUMBER 6 fsal_status_t lizardfs2fsal_error(liz_err_t err); fsal_status_t lzfs_fsal_last_err(void); liz_context_t *lzfs_fsal_create_context(liz_t *instance, struct user_cred *cred); fsal_staticfsinfo_t *lzfs_fsal_staticinfo(struct fsal_module *module_hdl); void lzfs_fsal_export_ops_init(struct export_ops *ops); void lzfs_fsal_handle_ops_init(struct lzfs_fsal_export *lzfs_export, struct fsal_obj_ops *ops); void lzfs_fsal_handle_ops_pnfs(struct fsal_obj_ops *ops); void lzfs_fsal_export_ops_pnfs(struct export_ops *ops); void lzfs_fsal_ops_pnfs(struct fsal_ops *ops); struct lzfs_fsal_handle * lzfs_fsal_new_handle(const struct stat *attr, struct lzfs_fsal_export *lzfs_export); void lzfs_fsal_delete_handle(struct lzfs_fsal_handle *obj); void lzfs_fsal_ds_handle_ops_init(struct fsal_pnfs_ds_ops *ops); nfsstat4 lzfs_nfs4_last_err(void); fsal_status_t lzfs_int_getacl(struct lzfs_fsal_export *lzfs_export, uint32_t inode, uint32_t owner, fsal_acl_t **fsal_acl); fsal_status_t lzfs_int_setacl(struct lzfs_fsal_export *lzfs_export, uint32_t inode, const fsal_acl_t *fsal_acl); nfs-ganesha-6.5/src/FSAL/FSAL_LIZARDFS/main.c000066400000000000000000000267471473756622300202440ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright (C) 2019 Skytechnology sp. z o.o. * * This program 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 3 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #include #include "fsal.h" #include "fsal_api.h" #include "fsal_types.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_init.h" #include "pnfs_utils.h" #include "context_wrap.h" #include "lzfs_internal.h" static struct lzfs_fsal_module gLizardFSM; static const char *gModuleName = "LizardFS"; static fsal_staticfsinfo_t default_lizardfs_info = { .maxfilesize = UINT64_MAX, .maxlink = _POSIX_LINK_MAX, .maxnamelen = MFS_NAME_MAX, .maxpathlen = MAXPATHLEN, .no_trunc = true, .chown_restricted = false, .case_insensitive = false, .case_preserving = true, .link_support = true, .symlink_support = true, .lock_support = true, .lock_support_async_block = false, .named_attr = true, .unique_handles = true, .acl_support = FSAL_ACLSUPPORT_ALLOW | FSAL_ACLSUPPORT_DENY, .cansettime = true, .homogenous = true, .supported_attrs = LZFS_SUPPORTED_ATTRS, .maxread = FSAL_MAXIOSIZE, .maxwrite = FSAL_MAXIOSIZE, .umask = 0, .auth_exportpath_xdev = false, .pnfs_mds = false, .pnfs_ds = false, .fsal_trace = false, .fsal_grace = false, .link_supports_permission_checks = true, }; static struct config_item lzfs_fsal_items[] = { CONF_ITEM_MODE("umask", 0, fsal_staticfsinfo_t, umask), CONF_ITEM_BOOL("link_support", true, fsal_staticfsinfo_t, link_support), CONF_ITEM_BOOL("symlink_support", true, fsal_staticfsinfo_t, symlink_support), CONF_ITEM_BOOL("cansettime", true, fsal_staticfsinfo_t, cansettime), CONF_ITEM_BOOL("auth_xdev_export", false, fsal_staticfsinfo_t, auth_exportpath_xdev), CONF_ITEM_BOOL("PNFS_MDS", false, fsal_staticfsinfo_t, pnfs_mds), CONF_ITEM_BOOL("PNFS_DS", false, fsal_staticfsinfo_t, pnfs_ds), CONF_ITEM_BOOL("fsal_trace", true, fsal_staticfsinfo_t, fsal_trace), CONF_ITEM_BOOL("fsal_grace", false, fsal_staticfsinfo_t, fsal_grace), CONFIG_EOL }; static struct config_block lzfs_fsal_param_block = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.lizardfs", .blk_desc.name = "LizardFS", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = lzfs_fsal_items, .blk_desc.u.blk.commit = noop_conf_commit }; static struct config_item lzfs_fsal_export_params[] = { CONF_ITEM_NOOP("name"), CONF_MAND_STR("hostname", 1, MAXPATHLEN, NULL, lzfs_fsal_export, lzfs_params.host), CONF_ITEM_STR("port", 1, MAXPATHLEN, "9421", lzfs_fsal_export, lzfs_params.port), CONF_ITEM_STR("mountpoint", 1, MAXPATHLEN, "nfs-ganesha", lzfs_fsal_export, lzfs_params.mountpoint), CONF_ITEM_STR("subfolder", 1, MAXPATHLEN, "/", lzfs_fsal_export, lzfs_params.subfolder), CONF_ITEM_BOOL("delayed_init", false, lzfs_fsal_export, lzfs_params.delayed_init), CONF_ITEM_UI32("io_retries", 0, 1024, 30, lzfs_fsal_export, lzfs_params.io_retries), CONF_ITEM_UI32("chunkserver_round_time_ms", 0, 65536, 200, lzfs_fsal_export, lzfs_params.chunkserver_round_time_ms), CONF_ITEM_UI32("chunkserver_connect_timeout_ms", 0, 65536, 2000, lzfs_fsal_export, lzfs_params.chunkserver_connect_timeout_ms), CONF_ITEM_UI32("chunkserver_wave_read_timeout_ms", 0, 65536, 500, lzfs_fsal_export, lzfs_params.chunkserver_wave_read_timeout_ms), CONF_ITEM_UI32("total_read_timeout_ms", 0, 65536, 2000, lzfs_fsal_export, lzfs_params.total_read_timeout_ms), CONF_ITEM_UI32("cache_expiration_time_ms", 0, 65536, 1000, lzfs_fsal_export, lzfs_params.cache_expiration_time_ms), CONF_ITEM_UI32("readahead_max_window_size_kB", 0, 65536, 16384, lzfs_fsal_export, lzfs_params.readahead_max_window_size_kB), CONF_ITEM_UI32("write_cache_size", 0, 1024, 64, lzfs_fsal_export, lzfs_params.write_cache_size), CONF_ITEM_UI32("write_workers", 0, 32, 10, lzfs_fsal_export, lzfs_params.write_workers), CONF_ITEM_UI32("write_window_size", 0, 256, 32, lzfs_fsal_export, lzfs_params.write_window_size), CONF_ITEM_UI32("chunkserver_write_timeout_ms", 0, 60000, 5000, lzfs_fsal_export, lzfs_params.chunkserver_write_timeout_ms), CONF_ITEM_UI32("cache_per_inode_percentage", 0, 80, 25, lzfs_fsal_export, lzfs_params.cache_per_inode_percentage), CONF_ITEM_UI32("symlink_cache_timeout_s", 0, 60000, 3600, lzfs_fsal_export, lzfs_params.symlink_cache_timeout_s), CONF_ITEM_BOOL("debug_mode", false, lzfs_fsal_export, lzfs_params.debug_mode), CONF_ITEM_I32("keep_cache", 0, 2, 0, lzfs_fsal_export, lzfs_params.keep_cache), CONF_ITEM_BOOL("verbose", false, lzfs_fsal_export, lzfs_params.verbose), CONF_ITEM_UI32("fileinfo_cache_timeout", 1, 3600, 60, lzfs_fsal_export, fileinfo_cache_timeout), CONF_ITEM_UI32("fileinfo_cache_max_size", 100, 1000000, 1000, lzfs_fsal_export, fileinfo_cache_max_size), CONF_ITEM_STR("password", 1, 128, NULL, lzfs_fsal_export, lzfs_params.password), CONF_ITEM_STR("md5_pass", 32, 32, NULL, lzfs_fsal_export, lzfs_params.md5_pass), CONFIG_EOL }; static struct config_block lzfs_fsal_export_param_block = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.lizardfs-export%d", .blk_desc.name = "FSAL", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = lzfs_fsal_export_params, .blk_desc.u.blk.commit = noop_conf_commit }; static fsal_status_t lzfs_fsal_create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops) { struct lzfs_fsal_export *lzfs_export; fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0); int rc; struct fsal_pnfs_ds *pds = NULL; lzfs_export = gsh_calloc(1, sizeof(struct lzfs_fsal_export)); fsal_export_init(&lzfs_export->export); lzfs_fsal_export_ops_init(&lzfs_export->export.exp_ops); // parse params for this export liz_set_default_init_params(&lzfs_export->lzfs_params, "", "", ""); if (parse_node) { rc = load_config_from_node(parse_node, &lzfs_fsal_export_param_block, lzfs_export, true, err_type); if (rc != 0) { LogCrit(COMPONENT_FSAL, "Failed to parse export configuration for %s", CTX_FULLPATH(op_ctx)); status = fsalstat(ERR_FSAL_INVAL, 0); goto error; } } lzfs_export->lzfs_params.subfolder = gsh_strdup(CTX_FULLPATH(op_ctx)); lzfs_export->lzfs_instance = liz_init_with_params(&lzfs_export->lzfs_params); if (lzfs_export->lzfs_instance == NULL) { LogCrit(COMPONENT_FSAL, "Unable to mount LizardFS cluster for %s.", CTX_FULLPATH(op_ctx)); status = fsalstat(ERR_FSAL_SERVERFAULT, 0); goto error; } if (fsal_attach_export(fsal_hdl, &lzfs_export->export.exports) != 0) { LogCrit(COMPONENT_FSAL, "Unable to attach export for %s.", CTX_FULLPATH(op_ctx)); status = fsalstat(ERR_FSAL_SERVERFAULT, 0); goto error; } lzfs_export->export.fsal = fsal_hdl; lzfs_export->export.up_ops = up_ops; lzfs_export->pnfs_ds_enabled = lzfs_export->export.exp_ops.fs_supports( &lzfs_export->export, fso_pnfs_ds_supported); if (lzfs_export->pnfs_ds_enabled) { lzfs_export->fileinfo_cache = liz_create_fileinfo_cache( lzfs_export->fileinfo_cache_max_size, lzfs_export->fileinfo_cache_timeout * 1000); if (lzfs_export->fileinfo_cache == NULL) { LogCrit(COMPONENT_FSAL, "Unable to create fileinfo cache for %s.", CTX_FULLPATH(op_ctx)); status = fsalstat(ERR_FSAL_SERVERFAULT, 0); goto error; } status = fsal_hdl->m_ops.create_fsal_pnfs_ds(fsal_hdl, parse_node, &pds); if (status.major != ERR_FSAL_NO_ERROR) { goto error; } /* special case: server_id matches export_id */ pds->id_servers = op_ctx->ctx_export->export_id; pds->mds_export = op_ctx->ctx_export; pds->mds_fsal_export = &lzfs_export->export; if (!pnfs_ds_insert(pds)) { LogCrit(COMPONENT_CONFIG, "Server id %d already in use.", pds->id_servers); status.major = ERR_FSAL_EXIST; /* Return the ref taken by create_fsal_pnfs_ds */ pnfs_ds_put(pds); goto error; } LogDebug(COMPONENT_PNFS, "pnfs ds was enabled for [%s]", CTX_FULLPATH(op_ctx)); } lzfs_export->pnfs_mds_enabled = lzfs_export->export.exp_ops.fs_supports( &lzfs_export->export, fso_pnfs_mds_supported); if (lzfs_export->pnfs_mds_enabled) { LogDebug(COMPONENT_PNFS, "pnfs mds was enabled for [%s]", CTX_FULLPATH(op_ctx)); lzfs_fsal_export_ops_pnfs(&lzfs_export->export.exp_ops); } // get attributes for root inode liz_attr_reply_t ret; rc = liz_cred_getattr(lzfs_export->lzfs_instance, &op_ctx->creds, SPECIAL_INODE_ROOT, &ret); if (rc < 0) { status = lzfs_fsal_last_err(); if (pds != NULL) { /* Remove and destroy the fsal_pnfs_ds */ pnfs_ds_remove(pds->id_servers); } goto error_pds; } lzfs_export->root = lzfs_fsal_new_handle(&ret.attr, lzfs_export); op_ctx->fsal_export = &lzfs_export->export; LogDebug(COMPONENT_FSAL, "LizardFS module export %s.", CTX_FULLPATH(op_ctx)); return fsalstat(ERR_FSAL_NO_ERROR, 0); error_pds: if (pds != NULL) /* Return the ref taken by create_fsal_pnfs_ds */ pnfs_ds_put(pds); error: if (lzfs_export) { if (lzfs_export->lzfs_instance) { liz_destroy(lzfs_export->lzfs_instance); } if (lzfs_export->fileinfo_cache) { liz_destroy_fileinfo_cache(lzfs_export->fileinfo_cache); } gsh_free(lzfs_export); } return status; } static fsal_status_t lzfs_fsal_init_config(struct fsal_module *module_in, config_file_t config_struct, struct config_error_type *err_type) { struct lzfs_fsal_module *lzfs_module; lzfs_module = container_of(module_in, struct lzfs_fsal_module, fsal); LogDebug(COMPONENT_FSAL, "LizardFS module setup."); lzfs_module->fs_info = default_lizardfs_info; (void)load_config_from_parse(config_struct, &lzfs_fsal_param_block, &lzfs_module->fs_info, true, err_type); if (!config_error_is_harmless(err_type)) return fsalstat(ERR_FSAL_INVAL, 0); display_fsinfo(&lzfs_module->fsal); return fsalstat(ERR_FSAL_NO_ERROR, 0); } MODULE_INIT void init(void) { struct fsal_module *lzfs_module = &gLizardFSM.fsal; LogDebug(COMPONENT_FSAL, "LizardFS module registering."); memset(lzfs_module, 0, sizeof(*lzfs_module)); if (register_fsal(lzfs_module, gModuleName, FSAL_MAJOR_VERSION, FSAL_MINOR_VERSION, FSAL_ID_LIZARDFS)) { LogCrit(COMPONENT_FSAL, "LizardFS module failed to register."); } lzfs_module->m_ops.fsal_pnfs_ds_ops = lzfs_fsal_ds_handle_ops_init; lzfs_module->m_ops.create_export = lzfs_fsal_create_export; lzfs_module->m_ops.init_config = lzfs_fsal_init_config; lzfs_fsal_ops_pnfs(&lzfs_module->m_ops); } MODULE_FINI void finish(void) { LogDebug(COMPONENT_FSAL, "LizardFS module finishing."); if (unregister_fsal(&gLizardFSM.fsal) != 0) { LogCrit(COMPONENT_FSAL, "Unable to unload LizardFS FSAL. Dying with extreme prejudice."); abort(); } } nfs-ganesha-6.5/src/FSAL/FSAL_LIZARDFS/mds_export.c000066400000000000000000000323701473756622300214710ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright (C) 2019 Skytechnology sp. z o.o. * * This program 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 3 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #include "fsal.h" #include "fsal_api.h" #include "fsal_types.h" #include "fsal_up.h" #include "FSAL/fsal_commonlib.h" #include "gsh_config.h" #include "pnfs_utils.h" #include "context_wrap.h" #include "lzfs_internal.h" #include "lizardfs/lizardfs_c_api.h" static int cmp_func(const void *a, const void *b) { if (((const liz_chunkserver_info_t *)a)->ip < ((const liz_chunkserver_info_t *)b)->ip) { return -1; } if (((const liz_chunkserver_info_t *)a)->ip > ((const liz_chunkserver_info_t *)b)->ip) { return 1; } return 0; } static int is_disconnected(const void *a, void *unused) { return ((const liz_chunkserver_info_t *)a)->version == kDisconnectedChunkserverVersion; } static int is_same_ip(const void *a, void *base) { if (a == base) { return 0; } return ((const liz_chunkserver_info_t *)a)->ip == ((const liz_chunkserver_info_t *)a - 1)->ip; } static size_t remove_if(void *base, size_t num, size_t size, int (*predicate)(const void *a, void *work_data), void *work_data) { size_t i, j; j = 0; for (i = 0; i < num; ++i) { if (!predicate((uint8_t *)base + i * size, work_data)) { memcpy((uint8_t *)base + i * size, (uint8_t *)base + j * size, size); j++; } } return j; } static void shuffle(void *base, size_t num, size_t size) { uint8_t temp[size]; size_t i, j; if (num == 0) { return; } for (i = 0; i < num - 1; ++i) { j = i + rand() % (num - i); memcpy(temp, (uint8_t *)base + i * size, size); memcpy((uint8_t *)base + i * size, (uint8_t *)base + j * size, size); memcpy((uint8_t *)base + j * size, temp, size); } } static liz_chunkserver_info_t * lzfs_int_get_randomized_chunkserver_list(struct lzfs_fsal_export *lzfs_export, uint32_t *chunkserver_count) { liz_chunkserver_info_t *chunkserver_info = NULL; int rc; chunkserver_info = gsh_malloc(LZFS_BIGGEST_STRIPE_COUNT * sizeof(liz_chunkserver_info_t)); rc = liz_get_chunkservers_info(lzfs_export->lzfs_instance, chunkserver_info, LZFS_BIGGEST_STRIPE_COUNT, chunkserver_count); if (rc < 0) { *chunkserver_count = 0; gsh_free(chunkserver_info); return NULL; } // Free labels, we don't need them. liz_destroy_chunkservers_info(chunkserver_info); // remove disconnected *chunkserver_count = remove_if(chunkserver_info, *chunkserver_count, sizeof(liz_chunkserver_info_t), is_disconnected, NULL); // remove entries with the same ip qsort(chunkserver_info, *chunkserver_count, sizeof(liz_chunkserver_info_t), cmp_func); *chunkserver_count = remove_if(chunkserver_info, *chunkserver_count, sizeof(liz_chunkserver_info_t), is_same_ip, chunkserver_info); // randomize shuffle(chunkserver_info, *chunkserver_count, sizeof(liz_chunkserver_info_t)); return chunkserver_info; } /*! \brief Fill DS list with entries corresponding to chunks */ static int lzfs_int_fill_chunk_ds_list(XDR *da_addr_body, liz_chunk_info_t *chunk_info, liz_chunkserver_info_t *chunkserver_info, uint32_t chunk_count, uint32_t stripe_count, uint32_t chunkserver_count, uint32_t *chunkserver_index) { fsal_multipath_member_t host[LZFS_EXPECTED_BACKUP_DS_COUNT]; for (uint32_t chunk_index = 0; chunk_index < MIN(chunk_count, stripe_count); ++chunk_index) { liz_chunk_info_t *chunk = &chunk_info[chunk_index]; int server_count = 0; memset(host, 0, LZFS_EXPECTED_BACKUP_DS_COUNT * sizeof(fsal_multipath_member_t)); // prefer std chunk part type for (int i = 0; i < chunk->parts_size && server_count < LZFS_EXPECTED_BACKUP_DS_COUNT; ++i) { if (chunk->parts[i].part_type_id != LZFS_STD_CHUNK_PART_TYPE) { continue; } host[server_count].proto = TCP_PROTO_NUMBER; host[server_count].addr = chunk->parts[i].addr; host[server_count].port = NFS_PORT; ++server_count; } for (int i = 0; i < chunk->parts_size && server_count < LZFS_EXPECTED_BACKUP_DS_COUNT; ++i) { if (chunk->parts[i].part_type_id == LZFS_STD_CHUNK_PART_TYPE) { continue; } host[server_count].proto = TCP_PROTO_NUMBER; host[server_count].addr = chunk->parts[i].addr; host[server_count].port = NFS_PORT; ++server_count; } // fill unused entries with the servers from randomized // chunkserver list while (server_count < LZFS_EXPECTED_BACKUP_DS_COUNT) { host[server_count].proto = TCP_PROTO_NUMBER; host[server_count].addr = chunkserver_info[*chunkserver_index].ip; host[server_count].port = NFS_PORT; ++server_count; *chunkserver_index = (*chunkserver_index + 1) % chunkserver_count; } // encode ds entry nfsstat4 nfs_status = FSAL_encode_v4_multipath( da_addr_body, server_count, host); if (nfs_status != NFS4_OK) { return -1; } } return 0; } /*! \brief Fill unused part of DS list with servers from randomized chunkserver * list */ static int lzfs_int_fill_unused_ds_list( XDR *da_addr_body, liz_chunkserver_info_t *chunkserver_info, uint32_t chunk_count, uint32_t stripe_count, uint32_t chunkserver_count, uint32_t *chunkserver_index) { fsal_multipath_member_t host[LZFS_EXPECTED_BACKUP_DS_COUNT]; for (uint32_t chunk_index = MIN(chunk_count, stripe_count); chunk_index < stripe_count; ++chunk_index) { int server_count = 0, index; memset(host, 0, LZFS_EXPECTED_BACKUP_DS_COUNT * sizeof(fsal_multipath_member_t)); while (server_count < LZFS_EXPECTED_BACKUP_DS_COUNT) { index = (*chunkserver_index + server_count) % chunkserver_count; host[server_count].proto = TCP_PROTO_NUMBER; host[server_count].addr = chunkserver_info[index].ip; host[server_count].port = NFS_PORT; ++server_count; } *chunkserver_index = (*chunkserver_index + 1) % chunkserver_count; nfsstat4 nfs_status = FSAL_encode_v4_multipath( da_addr_body, server_count, host); if (nfs_status != NFS4_OK) { return -1; } } return 0; } /*! \brief Get information about a pNFS device * * The function converts LizardFS file's chunk information to pNFS device info. * * Linux pNFS client imposes limit on stripe size (LZFS_BIGGEST_STRIPE_COUNT = * 4096). If we would use straight forward approach of converting each chunk * to stripe entry, we would be limited to file size of 256 GB (4096 * 64MB). * * To avoid this problem each DS can read/write data from any chunk (Remember * that pNFS client takes DS address from DS list in round robin fashion). Of * course it's more efficient if DS is answering queries about chunks residing * locally. * * To achieve the best performance we fill the DS list in a following way: * * First we prepare randomized list of all chunkservers (RCSL). Then for each * chunk we fill multipath DS list entry with addresses of chunkservers storing * this chunk. If there is less chunkservers than LZFS_EXPECTED_BACKUP_DS_COUNT * then we use chunkservers from RCSL. * * If we didn't use all the possible space in DS list * (LZFS_BIGGEST_STRIPE_COUNT), then we fill rest of the stripe entries with * addresses from RCSL (again LZFS_EXPECTED_BACKUP_DS_COUNT addresses for each * stripe entry). * * \see fsal_api.h for more information */ static nfsstat4 lzfs_fsal_getdeviceinfo(struct fsal_module *fsal_hdl, XDR *da_addr_body, const layouttype4 type, const struct pnfs_deviceid *deviceid) { struct fsal_export *export_hdl; struct lzfs_fsal_export *lzfs_export = NULL; liz_chunk_info_t *chunk_info = NULL; liz_chunkserver_info_t *chunkserver_info = NULL; uint32_t chunk_count, chunkserver_count, stripe_count, chunkserver_index; struct glist_head *glist, *glistn; int rc; if (type != LAYOUT4_NFSV4_1_FILES) { LogCrit(COMPONENT_PNFS, "Unsupported layout type: %x", type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } uint16_t export_id = deviceid->device_id2; glist_for_each_safe(glist, glistn, &fsal_hdl->exports) { export_hdl = glist_entry(glist, struct fsal_export, exports); if (export_hdl->export_id == export_id) { lzfs_export = container_of( export_hdl, struct lzfs_fsal_export, export); break; } } if (!lzfs_export) { LogCrit(COMPONENT_PNFS, "Couldn't find export with id: %" PRIu16, export_id); return NFS4ERR_SERVERFAULT; } // get the chunk list for file chunk_info = gsh_malloc(LZFS_BIGGEST_STRIPE_COUNT * sizeof(liz_chunk_info_t)); rc = liz_cred_get_chunks_info(lzfs_export->lzfs_instance, &op_ctx->creds, deviceid->devid, 0, chunk_info, LZFS_BIGGEST_STRIPE_COUNT, &chunk_count); if (rc < 0) { LogCrit(COMPONENT_PNFS, "Failed to get LizardFS layout for export=%" PRIu16 " inode=%" PRIu64, export_id, deviceid->devid); goto generic_err; } chunkserver_info = lzfs_int_get_randomized_chunkserver_list( lzfs_export, &chunkserver_count); if (chunkserver_info == NULL || chunkserver_count == 0) { LogCrit(COMPONENT_PNFS, "Failed to get LizardFS layout for export=%" PRIu16 " inode=%" PRIu64, export_id, deviceid->devid); goto generic_err; } chunkserver_index = 0; stripe_count = MIN(chunk_count + chunkserver_count, LZFS_BIGGEST_STRIPE_COUNT); if (!inline_xdr_u_int32_t(da_addr_body, &stripe_count)) { goto encode_err; } for (uint32_t chunk_index = 0; chunk_index < stripe_count; ++chunk_index) { if (!inline_xdr_u_int32_t(da_addr_body, &chunk_index)) { goto encode_err; } } if (!inline_xdr_u_int32_t(da_addr_body, &stripe_count)) { goto encode_err; } rc = lzfs_int_fill_chunk_ds_list(da_addr_body, chunk_info, chunkserver_info, chunk_count, stripe_count, chunkserver_count, &chunkserver_index); if (rc < 0) { goto encode_err; } rc = lzfs_int_fill_unused_ds_list(da_addr_body, chunkserver_info, chunk_count, stripe_count, chunkserver_count, &chunkserver_index); if (rc < 0) { goto encode_err; } liz_destroy_chunks_info(chunk_info); gsh_free(chunk_info); gsh_free(chunkserver_info); return NFS4_OK; encode_err: LogCrit(COMPONENT_PNFS, "Failed to encode device information for export=%" PRIu16 " inode=%" PRIu64, export_id, deviceid->devid); generic_err: if (chunk_info) { liz_destroy_chunks_info(chunk_info); gsh_free(chunk_info); } if (chunkserver_info) { gsh_free(chunkserver_info); } return NFS4ERR_SERVERFAULT; } /*! \brief Get list of available devices * * \see fsal_api.h for more information */ static nfsstat4 lzfs_fsal_getdevicelist(struct fsal_export *export_hdl, layouttype4 type, void *opaque, bool (*cb)(void *opaque, const uint64_t id), struct fsal_getdevicelist_res *res) { res->eof = true; return NFS4_OK; } /*! \brief Get layout types supported by export * * \see fsal_api.h for more information */ static void lzfs_fsal_fs_layouttypes(struct fsal_export *export_hdl, int32_t *count, const layouttype4 **types) { static const layouttype4 supported_layout_type = LAYOUT4_NFSV4_1_FILES; *types = &supported_layout_type; *count = 1; } /* \brief Get layout block size for export * * \see fsal_api.h for more information */ static uint32_t lzfs_fsal_fs_layout_blocksize(struct fsal_export *export_hdl) { return MFSCHUNKSIZE; } /*! \brief Maximum number of segments we will use * * \see fsal_api.h for more information */ static uint32_t lzfs_fsal_fs_maximum_segments(struct fsal_export *export_hdl) { return 1; } /*! \brief Size of the buffer needed for loc_body at layoutget * * \see fsal_api.h for more information */ static size_t lzfs_fsal_fs_loc_body_size(struct fsal_export *export_hdl) { return 0x100; // typical value in NFS FSAL plugins } /*! \brief Max Size of the buffer needed for da_addr_body in getdeviceinfo * * \see fsal_api.h for more information */ static size_t lzfs_fsal_fs_da_addr_size(struct fsal_module *fsal_hdl) { // one stripe index + number of addresses + // LZFS_EXPECTED_BACKUP_DS_COUNT addresses per chunk each address takes // 37 bytes (we use 40 for safety) we add 32 bytes of overhead // (includes stripe count and DS count) return LZFS_BIGGEST_STRIPE_COUNT * (4 + (4 + LZFS_EXPECTED_BACKUP_DS_COUNT * 40)) + 32; } void lzfs_fsal_export_ops_pnfs(struct export_ops *ops) { ops->getdevicelist = lzfs_fsal_getdevicelist; ops->fs_layouttypes = lzfs_fsal_fs_layouttypes; ops->fs_layout_blocksize = lzfs_fsal_fs_layout_blocksize; ops->fs_maximum_segments = lzfs_fsal_fs_maximum_segments; ops->fs_loc_body_size = lzfs_fsal_fs_loc_body_size; } void lzfs_fsal_ops_pnfs(struct fsal_ops *ops) { ops->getdeviceinfo = lzfs_fsal_getdeviceinfo; ops->fs_da_addr_size = lzfs_fsal_fs_da_addr_size; } nfs-ganesha-6.5/src/FSAL/FSAL_LIZARDFS/mds_handle.c000066400000000000000000000122071473756622300214000ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright (C) 2019 Skytechnology sp. z o.o. * * This program 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 3 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. */ #include "fsal.h" #include "fsal_api.h" #include "fsal_types.h" #include "fsal_up.h" #include "FSAL/fsal_commonlib.h" #include "pnfs_utils.h" #include "context_wrap.h" #include "lzfs_internal.h" /*! \brief Grant a layout segment. * * \see fsal_api.h for more information */ static nfsstat4 lzfs_fsal_layoutget(struct fsal_obj_handle *obj_pub, XDR *loc_body, const struct fsal_layoutget_arg *arg, struct fsal_layoutget_res *res) { struct lzfs_fsal_handle *lzfs_hdl; struct lzfs_fsal_ds_wire ds_wire; struct gsh_buffdesc ds_desc = { .addr = &ds_wire, .len = sizeof(struct lzfs_fsal_ds_wire) }; struct pnfs_deviceid deviceid = DEVICE_ID_INIT_ZERO(FSAL_ID_LIZARDFS); nfl_util4 layout_util = 0; nfsstat4 nfs_status = NFS4_OK; lzfs_hdl = container_of(obj_pub, struct lzfs_fsal_handle, handle); if (arg->type != LAYOUT4_NFSV4_1_FILES) { LogMajor(COMPONENT_PNFS, "Unsupported layout type: %x", arg->type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } LogDebug(COMPONENT_PNFS, "will issue layout offset: %" PRIu64 " length: %" PRIu64, res->segment.offset, res->segment.length); deviceid.device_id2 = lzfs_hdl->export->export.export_id; deviceid.devid = lzfs_hdl->inode; ds_wire.inode = lzfs_hdl->inode; layout_util = MFSCHUNKSIZE; nfs_status = FSAL_encode_file_layout(loc_body, &deviceid, layout_util, 0, 0, &op_ctx->ctx_export->export_id, 1, &ds_desc, false); if (nfs_status) { LogMajor(COMPONENT_PNFS, "Failed to encode nfsv4_1_file_layout."); return nfs_status; } res->return_on_close = true; res->last_segment = true; return nfs_status; } /*! \brief Potentially return one layout segment * * \see fsal_api.h for more information */ static nfsstat4 lzfs_fsal_layoutreturn(struct fsal_obj_handle *obj_pub, XDR *lrf_body, const struct fsal_layoutreturn_arg *arg) { if (arg->lo_type != LAYOUT4_NFSV4_1_FILES) { LogDebug(COMPONENT_PNFS, "Unsupported layout type: %x", arg->lo_type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } return NFS4_OK; } /*! \brief Commit a segment of a layout * * \see fsal_api.h for more information */ static nfsstat4 lzfs_fsal_layoutcommit(struct fsal_obj_handle *obj_pub, XDR *lou_body, const struct fsal_layoutcommit_arg *arg, struct fsal_layoutcommit_res *res) { struct lzfs_fsal_export *lzfs_export; struct lzfs_fsal_handle *lzfs_hdl; struct liz_attr_reply lzfs_old; int rc; // FIXME(haze): Does this function make sense for our implementation ? /* Sanity check on type */ if (arg->type != LAYOUT4_NFSV4_1_FILES) { LogCrit(COMPONENT_PNFS, "Unsupported layout type: %x", arg->type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } lzfs_export = container_of(op_ctx->fsal_export, struct lzfs_fsal_export, export); lzfs_hdl = container_of(obj_pub, struct lzfs_fsal_handle, handle); rc = liz_cred_getattr(lzfs_export->lzfs_instance, &op_ctx->creds, lzfs_hdl->inode, &lzfs_old); if (rc < 0) { LogCrit(COMPONENT_PNFS, "Error '%s' in attempt to get attributes of file %lli.", liz_error_string(liz_last_err()), (long long)lzfs_hdl->inode); return lzfs_nfs4_last_err(); } struct stat attr; int mask = 0; memset(&attr, 0, sizeof(attr)); if (arg->new_offset && lzfs_old.attr.st_size < arg->last_write + 1) { mask |= LIZ_SET_ATTR_SIZE; attr.st_size = arg->last_write + 1; res->size_supplied = true; res->new_size = arg->last_write + 1; } if (arg->time_changed && (arg->new_time.seconds > lzfs_old.attr.st_mtim.tv_sec || (arg->new_time.seconds == lzfs_old.attr.st_mtim.tv_sec && arg->new_time.nseconds > lzfs_old.attr.st_mtim.tv_nsec))) { attr.st_mtim.tv_sec = arg->new_time.seconds; attr.st_mtim.tv_sec = arg->new_time.nseconds; mask |= LIZ_SET_ATTR_MTIME; } liz_attr_reply_t reply; rc = liz_cred_setattr(lzfs_export->lzfs_instance, &op_ctx->creds, lzfs_hdl->inode, &attr, mask, &reply); if (rc < 0) { LogCrit(COMPONENT_PNFS, "Error '%s' in attempt to set attributes of file %lli.", liz_error_string(liz_last_err()), (long long)lzfs_hdl->inode); return lzfs_nfs4_last_err(); } res->commit_done = true; return NFS4_OK; } void lzfs_fsal_handle_ops_pnfs(struct fsal_obj_ops *ops) { ops->layoutget = lzfs_fsal_layoutget; ops->layoutreturn = lzfs_fsal_layoutreturn; ops->layoutcommit = lzfs_fsal_layoutcommit; } nfs-ganesha-6.5/src/FSAL/FSAL_MEM/000077500000000000000000000000001473756622300163345ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_MEM/CMakeLists.txt000066400000000000000000000030471473756622300211000ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- add_definitions( -D__USE_GNU ) set( LIB_PREFIX 64) ########### next target ############### SET(fsalmem_LIB_SRCS mem_export.c mem_handle.c mem_int.h mem_main.c mem_up.c ) add_library(fsalmem MODULE ${fsalmem_LIB_SRCS}) add_sanitizers(fsalmem) target_link_libraries(fsalmem ganesha_nfsd ${SYSTEM_LIBRARIES} ${LTTNG_LIBRARIES} ${LDFLAG_DISALLOW_UNDEF} ) set_target_properties(fsalmem PROPERTIES VERSION 4.2.0 SOVERSION 4) install(TARGETS fsalmem COMPONENT fsal DESTINATION ${FSAL_DESTINATION} ) ########### install files ############### nfs-ganesha-6.5/src/FSAL/FSAL_MEM/mem_export.c000066400000000000000000000260671473756622300206720ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2017-2019 Red Hat, Inc. * Author: Daniel Gryniewicz dang@redhat.com * * * This program 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 3 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 * 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 * * ------------- */ /* export.c * MEM FSAL export object */ #include "config.h" #include "fsal.h" #include /* used for 'bswap*' */ #include /* used for 'dirname' */ #include #include #include #include #include #include #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_config.h" #include "mem_int.h" #include "nfs_exports.h" #include "nfs_core.h" #include "export_mgr.h" #include #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/fsalmem.h" #endif /* helpers to/from other MEM objects */ /* export object methods */ static void mem_release_export(struct fsal_export *exp_hdl) { struct mem_fsal_export *myself; myself = container_of(exp_hdl, struct mem_fsal_export, export); if (myself->root_handle != NULL) { mem_clean_export(myself->root_handle); fsal_obj_handle_fini(&myself->root_handle->obj_handle, true); LogDebug(COMPONENT_FSAL, "Releasing hdl=%p, name=%s", myself->root_handle, myself->root_handle->m_name); PTHREAD_RWLOCK_wrlock(&myself->mfe_exp_lock); mem_free_handle(myself->root_handle); PTHREAD_RWLOCK_unlock(&myself->mfe_exp_lock); myself->root_handle = NULL; } fsal_detach_export(exp_hdl->fsal, &exp_hdl->exports); free_export_ops(exp_hdl); glist_del(&myself->export_entry); PTHREAD_RWLOCK_destroy(&myself->mfe_exp_lock); gsh_free(myself->export_path); gsh_free(myself); } static fsal_status_t mem_get_dynamic_info(struct fsal_export *exp_hdl, struct fsal_obj_handle *obj_hdl, fsal_dynamicfsinfo_t *infop) { infop->total_bytes = 0; infop->free_bytes = 0; infop->avail_bytes = 0; infop->total_files = 0; infop->free_files = 0; infop->avail_files = 0; infop->time_delta.tv_sec = 0; infop->time_delta.tv_nsec = FSAL_DEFAULT_TIME_DELTA_NSEC; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* extract a file handle from a buffer. * do verification checks and flag any and all suspicious bits. * Return an updated fh_desc into whatever was passed. The most * common behavior, done here is to just reset the length. There * is the option to also adjust the start pointer. */ static fsal_status_t mem_wire_to_host(struct fsal_export *exp_hdl, fsal_digesttype_t in_type, struct gsh_buffdesc *fh_desc, int flags) { size_t fh_min; uint64_t *hashkey; ushort *len; fh_min = 1; if (fh_desc->len < fh_min) { LogMajor(COMPONENT_FSAL, "Size mismatch for handle. should be >= %zu, got %zu", fh_min, fh_desc->len); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } hashkey = (uint64_t *)fh_desc->addr; len = (ushort *)((char *)hashkey + sizeof(uint64_t)); if (flags & FH_FSAL_BIG_ENDIAN) { #if (BYTE_ORDER != BIG_ENDIAN) *len = bswap_16(*len); *hashkey = bswap_64(*hashkey); #endif } else { #if (BYTE_ORDER == BIG_ENDIAN) *len = bswap_16(*len); *hashkey = bswap_64(*hashkey); #endif } return fsalstat(ERR_FSAL_NO_ERROR, 0); } void mem_free_state(struct state_t *state) { struct fsal_fd *fsal_fd; fsal_fd = &container_of(state, struct mem_state_fd, state)->fsal_fd; destroy_fsal_fd(fsal_fd); gsh_free(state); } /** * @brief Allocate a state_t structure * * Note that this is not expected to fail since memory allocation is * expected to abort on failure. * * @param[in] exp_hdl Export state_t will be associated with * @param[in] state_type Type of state to allocate * @param[in] related_state Related state if appropriate * * @returns a state structure. */ static struct state_t *mem_alloc_state(struct fsal_export *exp_hdl, enum state_type state_type, struct state_t *related_state) { struct state_t *state; struct fsal_fd *fsal_fd; state = init_state(gsh_calloc(1, sizeof(struct mem_state_fd)), mem_free_state, state_type, related_state); fsal_fd = &container_of(state, struct mem_state_fd, state)->fsal_fd; init_fsal_fd(fsal_fd, FSAL_FD_STATE, op_ctx->fsal_export); GSH_AUTO_TRACEPOINT(fsalmem, mem_alloc_state, TRACE_DEBUG, "Allocated state: {}", state); return state; } /** * @brief Function to get the fasl_obj_handle that has fsal_fd as its global fd. * * @param[in] exp_hdl The export in which the handle exists * @param[in] fd File descriptor in question * @param[out] handle FSAL object handle * * @return the fsal_obj_handle. */ void get_fsal_obj_hdl(struct fsal_export *exp_hdl, struct fsal_fd *fd, struct fsal_obj_handle **handle) { struct mem_fsal_obj_handle *myself = NULL; myself = container_of(fd, struct mem_fsal_obj_handle, mh_file.fd); *handle = &myself->obj_handle; } /* mem_export_ops_init * overwrite vector entries with the methods that we support */ void mem_export_ops_init(struct export_ops *ops) { ops->release = mem_release_export; ops->lookup_path = mem_lookup_path; ops->wire_to_host = mem_wire_to_host; ops->create_handle = mem_create_handle; ops->get_fs_dynamic_info = mem_get_dynamic_info; ops->alloc_state = mem_alloc_state; ops->get_fsal_obj_hdl = get_fsal_obj_hdl; } const char *str_async_type(uint32_t async_type) { switch (async_type) { case MEM_INLINE: return "INLINE"; case MEM_RANDOM_OR_INLINE: return "RANDOM_OR_INLINE"; case MEM_RANDOM: return "RANDOM"; case MEM_FIXED: return "FIXED"; } return "UNKNOWN"; } static struct config_item_list async_types_conf[] = { CONFIG_LIST_TOK("inline", MEM_INLINE), CONFIG_LIST_TOK("fixed", MEM_FIXED), CONFIG_LIST_TOK("random", MEM_RANDOM), CONFIG_LIST_TOK("random_or_inline", MEM_RANDOM_OR_INLINE), CONFIG_LIST_EOL }; static struct config_item mem_export_params[] = { CONF_ITEM_NOOP("name"), CONF_ITEM_UI32("Async_Delay", 0, 1000, 0, mem_fsal_export, async_delay), CONF_ITEM_TOKEN("Async_Type", MEM_INLINE, async_types_conf, mem_fsal_export, async_type), CONF_ITEM_UI32("Async_Stall_Delay", 0, 1000, 0, mem_fsal_export, async_stall_delay), CONFIG_EOL }; static struct config_block mem_export_param_block = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.mem-export%d", .blk_desc.name = "FSAL", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = mem_export_params, .blk_desc.u.blk.commit = noop_conf_commit }; /* create_export * Create an export point and return a handle to it to be kept * in the export list. * First lookup the fsal, then create the export and then put the fsal back. * returns the export with one reference taken. */ fsal_status_t mem_create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops) { struct mem_fsal_export *myself; int retval = 0; fsal_status_t fsal_status = { 0, 0 }; myself = gsh_calloc(1, sizeof(struct mem_fsal_export)); glist_init(&myself->mfe_objs); PTHREAD_RWLOCK_init(&myself->mfe_exp_lock, NULL); fsal_export_init(&myself->export); mem_export_ops_init(&myself->export.exp_ops); retval = load_config_from_node(parse_node, &mem_export_param_block, myself, true, err_type); if (retval != 0) { fsal_status = posix2fsal_status(EINVAL); goto err_free; /* seriously bad */ } retval = fsal_attach_export(fsal_hdl, &myself->export.exports); if (retval != 0) { /* seriously bad */ LogMajor(COMPONENT_FSAL, "Could not attach export"); fsal_status = posix2fsal_status(retval); goto err_free; /* seriously bad */ } myself->export.fsal = fsal_hdl; myself->export.up_ops = up_ops; /* Save the export path. */ myself->export_path = gsh_strdup(CTX_FULLPATH(op_ctx)); op_ctx->fsal_export = &myself->export; /* Insert into exports list */ glist_add_tail(&MEM.mem_exports, &myself->export_entry); LogDebug(COMPONENT_FSAL, "Created exp %p - %s", myself, myself->export_path); return fsalstat(ERR_FSAL_NO_ERROR, 0); err_free: free_export_ops(&myself->export); gsh_free(myself); /* elvis has left the building */ return fsal_status; } /** * @brief Update an existing export * * This will result in a temporary fsal_export being created, and built into * a stacked export. * * On entry, op_ctx has the original gsh_export and no fsal_export. * * The caller passes the original fsal_export, as well as the new super_export's * FSAL when there is a stacked export. This will allow the underlying export to * validate that the stacking has not changed. * * This function does not actually create a new fsal_export, the only purpose is * to validate and update the config. * * @param[in] fsal_hdl FSAL module * @param[in] parse_node opaque pointer to parse tree node for * export options to be passed to * load_config_from_node * @param[out] err_type config processing error reporting * @param[in] original The original export that is being updated * @param[in] updated_super The updated super_export's FSAL * * @return FSAL status. */ fsal_status_t mem_update_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, struct fsal_export *original, struct fsal_module *updated_super) { struct mem_fsal_export myself; int retval = 0; struct mem_fsal_export *orig = container_of(original, struct mem_fsal_export, export); fsal_status_t status; /* Check for changes in stacking by calling default update_export. */ status = update_export(fsal_hdl, parse_node, err_type, original, updated_super); if (FSAL_IS_ERROR(status)) return status; memset(&myself, 0, sizeof(myself)); retval = load_config_from_node(parse_node, &mem_export_param_block, &myself, true, err_type); if (retval != 0) { return posix2fsal_status(EINVAL); } /* Update the async parameters */ atomic_store_uint32_t(&orig->async_delay, myself.async_delay); atomic_store_uint32_t(&orig->async_stall_delay, myself.async_stall_delay); atomic_store_uint32_t(&orig->async_type, myself.async_type); LogEvent(COMPONENT_FSAL, "Updated FSAL_MEM aync parameters type=%s, delay=%" PRIu32 ", stall_delay=%" PRIu32, str_async_type(myself.async_type), myself.async_delay, myself.async_stall_delay); return fsalstat(ERR_FSAL_NO_ERROR, 0); } nfs-ganesha-6.5/src/FSAL/FSAL_MEM/mem_handle.c000066400000000000000000002327321473756622300206020ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:shiftwidth=8:tabstop=8: * * Copyright 2017-2021 Red Hat, Inc. * Author: Daniel Gryniewicz dang@redhat.com * * * This program 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 3 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 * 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 * * ------------- */ /* handle.c */ #include "config.h" #include #include #include "fsal.h" #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "mem_int.h" #include "city.h" #include "nfs_file_handle.h" #include "display.h" #include "../Stackable_FSALs/FSAL_MDCACHE/mdcache_ext.h" #include "nfs_core.h" #include "common_utils.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/fsalmem.h" #endif static void mem_release(struct fsal_obj_handle *obj_hdl); /* Atomic uint64_t that is used to generate inode numbers in the mem FS */ uint64_t mem_inode_number = 1; /* helpers */ static inline int mem_n_cmpf(const struct avltree_node *lhs, const struct avltree_node *rhs) { struct mem_dirent *lk, *rk; lk = avltree_container_of(lhs, struct mem_dirent, avl_n); rk = avltree_container_of(rhs, struct mem_dirent, avl_n); return strcmp(lk->d_name, rk->d_name); } static inline int mem_i_cmpf(const struct avltree_node *lhs, const struct avltree_node *rhs) { struct mem_dirent *lk, *rk; lk = avltree_container_of(lhs, struct mem_dirent, avl_i); rk = avltree_container_of(rhs, struct mem_dirent, avl_i); if (lk->d_index < rk->d_index) return -1; if (lk->d_index == rk->d_index) return 0; return 1; } /** * @brief Clean up and free an object handle * * @param[in] obj_hdl Handle to release */ static void mem_cleanup(struct mem_fsal_obj_handle *myself) { struct mem_fsal_export *mfe; mfe = myself->mfo_exp; if (myself->is_export || !glist_empty(&myself->dirents)) { /* Entry is still live: it's either an export, or in a dir. * This is likely a bug. */ GSH_UNIQUE_AUTO_TRACEPOINT( fsalmem, mem_inuse, TRACE_DEBUG, "Mem cleanup. Handle: {}, num links: {}, is_export: {}", &myself->obj_handle, myself->attrs.numlinks, myself->is_export); LogDebug(COMPONENT_FSAL, "Releasing live hdl=%p, name=%s, don't deconstruct it", myself, myself->m_name); return; } fsal_obj_handle_fini(&myself->obj_handle, true); LogDebug(COMPONENT_FSAL, "Releasing obj_hdl=%p, myself=%p, name=%s", &myself->obj_handle, myself, myself->m_name); switch (myself->obj_handle.type) { case DIRECTORY: /* Empty directory */ mem_clean_all_dirents(myself); break; case REGULAR_FILE: /* destroy associated global FD */ destroy_fsal_fd(&myself->mh_file.fd); break; case SYMBOLIC_LINK: gsh_free(myself->mh_symlink.link_contents); break; case SOCKET_FILE: case CHARACTER_FILE: case BLOCK_FILE: case FIFO_FILE: break; default: break; } PTHREAD_RWLOCK_wrlock(&mfe->mfe_exp_lock); mem_free_handle(myself); PTHREAD_RWLOCK_unlock(&mfe->mfe_exp_lock); } #define mem_int_get_ref(myself) _mem_int_get_ref(myself, __func__, __LINE__) /** * @brief Get a ref for a handle * * @param[in] myself Handle to ref * @param[in] func Function getting ref * @param[in] line Line getting ref */ static void _mem_int_get_ref(struct mem_fsal_obj_handle *myself, const char *func, int line) { const int32_t refcount = atomic_inc_int32_t(&myself->refcount); GSH_AUTO_TRACEPOINT(fsalmem, mem_get_ref, TRACE_DEBUG, "Get ref. Handle: {}, name: {}, refcount: {}", &myself->obj_handle, TP_STR(myself->m_name), refcount); } #define mem_int_put_ref(myself) _mem_int_put_ref(myself, __func__, __LINE__) /** * @brief Put a ref for a handle * * If this is the last ref, clean up and free the handle * * @param[in] myself Handle to ref * @param[in] func Function getting ref * @param[in] line Line getting ref */ static void _mem_int_put_ref(struct mem_fsal_obj_handle *myself, const char *func, int line) { int32_t refcount = atomic_dec_int32_t(&myself->refcount); GSH_AUTO_TRACEPOINT(fsalmem, mem_put_ref, TRACE_DEBUG, "Put ref. Handle: {}, name: {}, refcount: {}", &myself->obj_handle, TP_STR(myself->m_name), refcount); assert(refcount >= 0); if (refcount == 0) { mem_cleanup(myself); } } /** * @brief Construct the fs opaque part of a mem nfsv4 handle * * Given the components of a mem nfsv4 handle, the nfsv4 handle is * created by concatenating the components. This is the fs opaque piece * of struct file_handle_v4 and what is sent over the wire. * * @param[in] myself Obj to create handle for * * @return The nfsv4 mem file handle as a char * */ static void package_mem_handle(struct mem_fsal_obj_handle *myself) { char buf[MAXPATHLEN]; uint16_t len_fileid, len_name; uint64_t hashkey; int opaque_bytes_used = 0, pathlen = 0; memset(buf, 0, sizeof(buf)); /* Make hashkey */ len_fileid = sizeof(myself->obj_handle.fileid); memcpy(buf, &myself->obj_handle.fileid, len_fileid); len_name = strlen(myself->m_name); memcpy(buf + len_fileid, myself->m_name, MIN(len_name, sizeof(buf) - len_name)); hashkey = CityHash64(buf, sizeof(buf)); memcpy(myself->handle, &hashkey, sizeof(hashkey)); opaque_bytes_used += sizeof(hashkey); /* include length of the name in the handle. * MAXPATHLEN=4096 ... max path length can be contained in a short int. */ memcpy(myself->handle + opaque_bytes_used, &len_name, sizeof(len_name)); opaque_bytes_used += sizeof(len_name); /* Either the nfsv4 fh opaque size or the length of the name. * Ideally we can include entire mem name for guaranteed * uniqueness of mem handles. */ pathlen = MIN(V4_FH_OPAQUE_SIZE - opaque_bytes_used, len_name); memcpy(myself->handle + opaque_bytes_used, myself->m_name, pathlen); opaque_bytes_used += pathlen; /* If there is more space in the opaque handle due to a short mem * path ... zero it. */ if (opaque_bytes_used < V4_FH_OPAQUE_SIZE) { memset(myself->handle + opaque_bytes_used, 0, V4_FH_OPAQUE_SIZE - opaque_bytes_used); } } /** * @brief Update the change attribute of the FSAL object * * @note Caller must hold the obj_lock on the obj * * @param[in] obj FSAL obj which was modified */ static void mem_update_change_locked(struct mem_fsal_obj_handle *obj) { now(&obj->attrs.mtime); obj->attrs.ctime = obj->attrs.mtime; obj->attrs.change = timespec_to_nsecs(&obj->attrs.mtime); } /** * @brief Get attributes for a file * * @param[in] obj_hdl File to get * @param[out] outattrs Attributes for file * @return FSAL status */ static fsal_status_t mem_getattrs(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *outattrs) { struct mem_fsal_obj_handle *myself = container_of(obj_hdl, struct mem_fsal_obj_handle, obj_handle); if (!myself->is_export && glist_empty(&myself->dirents)) { /* Removed entry - stale */ LogDebug(COMPONENT_FSAL, "Requesting attributes for removed entry %p, name=%s", myself, myself->m_name); return fsalstat(ERR_FSAL_STALE, ESTALE); } if (obj_hdl->type == DIRECTORY) { /* We need to update the numlinks */ myself->attrs.numlinks = atomic_fetch_uint32_t(&myself->mh_dir.numkids); } GSH_AUTO_TRACEPOINT( fsalmem, mem_getattrs, TRACE_DEBUG, "Get attrs. Handle: {}, name: {}, filesize: {}, numlinks: {}, change: {}", obj_hdl, TP_STR(myself->m_name), myself->attrs.filesize, myself->attrs.numlinks, myself->attrs.change); LogFullDebug(COMPONENT_FSAL, "hdl=%p, name=%s numlinks %" PRIu32, myself, myself->m_name, myself->attrs.numlinks); fsal_copy_attrs(outattrs, &myself->attrs, false); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Insert an obj into it's parent's tree * * @param[in] parent Parent directory * @param[in] child Child to insert. * @param[in] name Name to use for insertion * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. */ static void mem_insert_obj(struct mem_fsal_obj_handle *parent, struct mem_fsal_obj_handle *child, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct mem_dirent *dirent; uint32_t numkids; dirent = gsh_calloc(1, sizeof(*dirent)); dirent->hdl = child; mem_int_get_ref(child); dirent->dir = parent; dirent->d_name = gsh_strdup(name); /* Index is hash of the name */ dirent->d_index = CityHash64(name, strlen(name)); /* Link into child */ PTHREAD_RWLOCK_wrlock(&child->obj_handle.obj_lock); glist_add_tail(&child->dirents, &dirent->dlist); PTHREAD_RWLOCK_unlock(&child->obj_handle.obj_lock); /* Link into parent */ PTHREAD_RWLOCK_wrlock(&parent->obj_handle.obj_lock); if (parent_pre_attrs_out != NULL) mem_getattrs(&parent->obj_handle, parent_pre_attrs_out); /* Name tree */ avltree_insert(&dirent->avl_n, &parent->mh_dir.avl_name); /* Index tree */ avltree_insert(&dirent->avl_i, &parent->mh_dir.avl_index); /* Update numkids */ numkids = atomic_inc_uint32_t(&parent->mh_dir.numkids); LogFullDebug(COMPONENT_FSAL, "%s numkids %" PRIu32, parent->m_name, numkids); mem_update_change_locked(parent); if (parent_post_attrs_out != NULL) mem_getattrs(&parent->obj_handle, parent_post_attrs_out); PTHREAD_RWLOCK_unlock(&parent->obj_handle.obj_lock); } /** * @brief Find the dirent pointing to a name in a directory * * @param[in] dir Directory to search * @param[in] name Name to look up * @return Dirent on success, NULL on failure */ struct mem_dirent *mem_dirent_lookup(struct mem_fsal_obj_handle *dir, const char *name) { struct mem_dirent key; struct avltree_node *node; memset(&key, 0, sizeof(key)); key.d_name = name; node = avltree_lookup(&key.avl_n, &dir->mh_dir.avl_name); if (!node) { /* it's not there */ return NULL; } return avltree_container_of(node, struct mem_dirent, avl_n); } /** * @brief Get the next dirent in a directory * * @note Caller must hold the obj_lock on the obj * * @param[in] dirent Current dirent * @return Next dirent, or NULL if at EOD */ static struct mem_dirent *mem_dirent_next(struct mem_dirent *dirent) { struct avltree_node *node; node = avltree_next(&dirent->avl_i); if (!node) { return NULL; } return avltree_container_of(node, struct mem_dirent, avl_i); } /** * @brief Seek to a location in a directory * * Handle normal vs whence-is-name directories. * * @note Caller must hold the obj_lock on the obj * * @param[in] dir Directory to seek in * @param[in] seekloc Location to seek to * @return Dirent associated with seekloc */ static struct mem_dirent *mem_readdir_seekloc(struct mem_fsal_obj_handle *dir, fsal_cookie_t seekloc) { struct mem_dirent *dirent; struct avltree_node *node; struct mem_dirent key; if (!seekloc) { /* Start from the beginning. We walk the index tree, so always * grab from the index tree. */ node = avltree_first(&dir->mh_dir.avl_index); if (!node) { return NULL; } dirent = avltree_container_of(node, struct mem_dirent, avl_i); return dirent; } memset(&key, 0, sizeof(key)); key.d_index = seekloc; node = avltree_lookup(&key.avl_i, &dir->mh_dir.avl_index); if (!node) { /* Dirent was probably deleted. Find the next one */ node = avltree_sup(&key.avl_i, &dir->mh_dir.avl_index); } if (!node) { /* Done */ return NULL; } dirent = avltree_container_of(node, struct mem_dirent, avl_i); return dirent; } /** * @brief Remove an obj from it's parent's tree * * @note Caller must hold the obj_lock on the parent * * @param[in] parent Parent directory * @param[in] dirent Dirent to remove * @param[in] release If true and no more dirents, release child */ static void mem_remove_dirent_locked(struct mem_fsal_obj_handle *parent, struct mem_dirent *dirent) { struct mem_fsal_obj_handle *child; uint32_t numkids; avltree_remove(&dirent->avl_n, &parent->mh_dir.avl_name); avltree_remove(&dirent->avl_i, &parent->mh_dir.avl_index); /* Take the child lock, to remove from the child. This should not race * with @r mem_insert_obj since that takes the locks sequentially */ child = dirent->hdl; PTHREAD_RWLOCK_wrlock(&child->obj_handle.obj_lock); glist_del(&dirent->dlist); PTHREAD_RWLOCK_unlock(&child->obj_handle.obj_lock); numkids = atomic_dec_uint32_t(&parent->mh_dir.numkids); LogFullDebug(COMPONENT_FSAL, "%s numkids %" PRIu32, parent->m_name, numkids); /* Free dirent */ gsh_free((char *)dirent->d_name); gsh_free(dirent); mem_int_put_ref(child); mem_update_change_locked(parent); } /** * @brief Remove a dirent from it's parent's tree * * @param[in] parent Parent directory * @param[in] name Name to remove * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. */ static void mem_remove_dirent(struct mem_fsal_obj_handle *parent, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct mem_dirent *dirent; PTHREAD_RWLOCK_wrlock(&parent->obj_handle.obj_lock); if (parent_pre_attrs_out != NULL) mem_getattrs(&parent->obj_handle, parent_pre_attrs_out); dirent = mem_dirent_lookup(parent, name); if (dirent) mem_remove_dirent_locked(parent, dirent); if (parent_post_attrs_out != NULL) mem_getattrs(&parent->obj_handle, parent_post_attrs_out); PTHREAD_RWLOCK_unlock(&parent->obj_handle.obj_lock); } /** * @brief Recursively clean all objs/dirents on an export * * @note Caller MUST hold export lock for write * * @param[in] root Root to clean * @return Return description */ void mem_clean_export(struct mem_fsal_obj_handle *root) { struct mem_fsal_obj_handle *child; struct avltree_node *node; struct mem_dirent *dirent; GSH_UNIQUE_AUTO_TRACEPOINT( fsalmem, mem_inuse, TRACE_DEBUG, "Mem cleanup export. Handle: {}, num links: {}, is_export: {}", &root->obj_handle, root->attrs.numlinks, root->is_export); while ((node = avltree_first(&root->mh_dir.avl_name))) { dirent = avltree_container_of(node, struct mem_dirent, avl_n); child = dirent->hdl; if (child->obj_handle.type == DIRECTORY) { mem_clean_export(child); } PTHREAD_RWLOCK_wrlock(&root->obj_handle.obj_lock); mem_remove_dirent_locked(root, dirent); PTHREAD_RWLOCK_unlock(&root->obj_handle.obj_lock); } } /** * @brief Remove all children from a directory's tree * * @param[in] parent Directory to clean */ void mem_clean_all_dirents(struct mem_fsal_obj_handle *parent) { struct avltree_node *node; struct mem_dirent *dirent; while ((node = avltree_first(&parent->mh_dir.avl_name))) { dirent = avltree_container_of(node, struct mem_dirent, avl_n); mem_remove_dirent_locked(parent, dirent); } } static void mem_copy_attrs_mask(struct fsal_attrlist *attrs_in, struct fsal_attrlist *attrs_out) { /* Use full timer resolution */ now(&attrs_out->ctime); if (FSAL_TEST_MASK(attrs_in->valid_mask, ATTR_SIZE)) { attrs_out->filesize = attrs_in->filesize; } if (FSAL_TEST_MASK(attrs_in->valid_mask, ATTR_MODE)) { attrs_out->mode = attrs_in->mode & (~S_IFMT & 0xFFFF) & ~op_ctx->fsal_export->exp_ops.fs_umask( op_ctx->fsal_export); } if (FSAL_TEST_MASK(attrs_in->valid_mask, ATTR_OWNER)) { attrs_out->owner = attrs_in->owner; } if (FSAL_TEST_MASK(attrs_in->valid_mask, ATTR_GROUP)) { attrs_out->group = attrs_in->group; } if (FSAL_TEST_MASK(attrs_in->valid_mask, ATTRS_SET_TIME)) { if (FSAL_TEST_MASK(attrs_in->valid_mask, ATTR_ATIME_SERVER)) { attrs_out->atime.tv_sec = 0; attrs_out->atime.tv_nsec = UTIME_NOW; } else if (FSAL_TEST_MASK(attrs_in->valid_mask, ATTR_ATIME)) { attrs_out->atime = attrs_in->atime; } else { attrs_out->atime = attrs_out->ctime; } if (FSAL_TEST_MASK(attrs_in->valid_mask, ATTR_MTIME_SERVER)) { attrs_out->mtime.tv_sec = 0; attrs_out->mtime.tv_nsec = UTIME_NOW; } else if (FSAL_TEST_MASK(attrs_in->valid_mask, ATTR_MTIME)) { attrs_out->mtime = attrs_in->mtime; } else { attrs_out->mtime = attrs_out->ctime; } } if (FSAL_TEST_MASK(attrs_in->valid_mask, ATTR_CREATION)) { attrs_out->creation = attrs_in->creation; } if (FSAL_TEST_MASK(attrs_in->valid_mask, ATTR_SPACEUSED)) { attrs_out->spaceused = attrs_in->spaceused; } else { attrs_out->spaceused = attrs_out->filesize; } /* XXX TODO copy ACL */ /** @todo FSF - this calculation may be different than what particular * FSALs use. Is that a problem? */ attrs_out->change = timespec_to_nsecs(&attrs_out->ctime); } /** * @brief Open an FD * * @param[in] fd FD to open * @param[in] openflags New mode for open */ static inline void mem_open_my_fd(struct fsal_fd *fd, fsal_openflags_t openflags) { fd->openflags = FSAL_O_NFS_FLAGS(openflags); } /** * @brief Close an FD * * @param[in] fd FD to close * @return FSAL status */ static fsal_status_t mem_close_my_fd(struct fsal_fd *fd) { if (fd->openflags == FSAL_O_CLOSED) return fsalstat(ERR_FSAL_NOT_OPENED, 0); fd->openflags = FSAL_O_CLOSED; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief MEM Function to open or reopen a fsal_fd. * * @param[in] obj_hdl File on which to operate * @param[in] openflags New mode for open * @param[out] fd File descriptor that is to be used * * @return FSAL status. */ fsal_status_t mem_reopen_func(struct fsal_obj_handle *obj_hdl, fsal_openflags_t openflags, struct fsal_fd *fsal_fd) { mem_open_my_fd(fsal_fd, openflags); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief MEM Function to close a global FD * * @param[in] obj_hdl Object owning FD to close * @param[in] fd FD to close * @return FSAL status */ static fsal_status_t mem_close_func(struct fsal_obj_handle *obj_hdl, struct fsal_fd *fd) { return mem_close_my_fd(fd); } #define mem_alloc_handle(p, n, t, e, a, ppra, ppoa) \ _mem_alloc_handle(p, n, t, e, a, ppra, ppoa, __func__, __LINE__) /** * @brief Allocate a MEM handle * * @param[in] parent Parent directory handle * @param[in] name Name of handle to allocate * @param[in] type Type of handle to allocate * @param[in] mfe MEM Export owning new handle * @param[in] attrs Attributes of new handle * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @return Handle on success, NULL on failure */ static struct mem_fsal_obj_handle *_mem_alloc_handle( struct mem_fsal_obj_handle *parent, const char *name, object_file_type_t type, struct mem_fsal_export *mfe, struct fsal_attrlist *attrs, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out, const char *func, int line) { struct mem_fsal_obj_handle *hdl; size_t isize; isize = sizeof(struct mem_fsal_obj_handle); if (type == REGULAR_FILE) { /* Regular files need space to read/write */ isize += MEM.inode_size; } hdl = gsh_calloc(1, isize); /* Establish tree details for this directory */ hdl->m_name = gsh_strdup(name); hdl->obj_handle.fileid = atomic_postinc_uint64_t(&mem_inode_number); hdl->datasize = MEM.inode_size; glist_init(&hdl->dirents); PTHREAD_RWLOCK_wrlock(&mfe->mfe_exp_lock); glist_add_tail(&mfe->mfe_objs, &hdl->mfo_exp_entry); hdl->mfo_exp = mfe; PTHREAD_RWLOCK_unlock(&mfe->mfe_exp_lock); package_mem_handle(hdl); /* Fills the output struct */ hdl->obj_handle.type = type; hdl->attrs.type = hdl->obj_handle.type; /* Need an FSID */ hdl->obj_handle.fsid.major = op_ctx->ctx_export->export_id; hdl->obj_handle.fsid.minor = 0; hdl->attrs.fsid.major = hdl->obj_handle.fsid.major; hdl->attrs.fsid.minor = hdl->obj_handle.fsid.minor; hdl->attrs.fileid = hdl->obj_handle.fileid; if ((attrs && attrs->valid_mask & ATTR_MODE) != 0) hdl->attrs.mode = attrs->mode & (~S_IFMT & 0xFFFF) & ~op_ctx->fsal_export->exp_ops.fs_umask( op_ctx->fsal_export); else hdl->attrs.mode = 0600; if ((attrs && attrs->valid_mask & ATTR_OWNER) != 0) hdl->attrs.owner = attrs->owner; else hdl->attrs.owner = op_ctx->creds.caller_uid; if ((attrs && attrs->valid_mask & ATTR_GROUP) != 0) hdl->attrs.group = attrs->group; else hdl->attrs.group = op_ctx->creds.caller_gid; /* Use full timer resolution */ now(&hdl->attrs.ctime); if ((attrs && attrs->valid_mask & ATTR_ATIME) != 0) hdl->attrs.atime = attrs->atime; else hdl->attrs.atime = hdl->attrs.ctime; if ((attrs && attrs->valid_mask & ATTR_MTIME) != 0) hdl->attrs.mtime = attrs->mtime; else hdl->attrs.mtime = hdl->attrs.ctime; /** @todo FSF - this calculation may be different than what particular * FSALs use. Is that a problem? */ hdl->attrs.change = timespec_to_nsecs(&hdl->attrs.ctime); switch (type) { case REGULAR_FILE: if ((attrs && attrs->valid_mask & ATTR_SIZE) != 0) { hdl->attrs.filesize = attrs->filesize; hdl->attrs.spaceused = attrs->filesize; } else { hdl->attrs.filesize = 0; hdl->attrs.spaceused = 0; } hdl->attrs.numlinks = 1; break; case BLOCK_FILE: case CHARACTER_FILE: if ((attrs && attrs->valid_mask & ATTR_RAWDEV) != 0) { hdl->attrs.rawdev.major = attrs->rawdev.major; hdl->attrs.rawdev.minor = attrs->rawdev.minor; } else { hdl->attrs.rawdev.major = 0; hdl->attrs.rawdev.minor = 0; } hdl->attrs.numlinks = 1; break; case DIRECTORY: avltree_init(&hdl->mh_dir.avl_name, mem_n_cmpf, 0); avltree_init(&hdl->mh_dir.avl_index, mem_i_cmpf, 0); hdl->attrs.numlinks = 2; hdl->mh_dir.numkids = 2; hdl->mh_dir.parent = parent; break; default: hdl->attrs.numlinks = 1; break; } /* Set the mask at the end. */ hdl->attrs.valid_mask = ATTRS_POSIX; hdl->attrs.supported = ATTRS_POSIX; /* Initial ref */ hdl->refcount = 1; GSH_AUTO_TRACEPOINT(fsalmem, mem_alloc, TRACE_DEBUG, "Alloc. Handle: {}, name: {}, refcount: {}", &hdl->obj_handle, TP_STR(name), hdl->refcount); fsal_obj_handle_init(&hdl->obj_handle, &mfe->export, type, true); hdl->obj_handle.obj_ops = &MEM.handle_ops; if (parent != NULL) { /* Attach myself to my parent */ mem_insert_obj(parent, hdl, name, parent_pre_attrs_out, parent_post_attrs_out); } else { /* This is an export */ hdl->is_export = true; } if (hdl->obj_handle.type == REGULAR_FILE) { init_fsal_fd(&hdl->mh_file.fd, FSAL_FD_GLOBAL, op_ctx->fsal_export); } return hdl; } #define mem_int_lookup(d, p, e) _mem_int_lookup(d, p, e, __func__, __LINE__) static fsal_status_t _mem_int_lookup(struct mem_fsal_obj_handle *dir, const char *path, struct mem_fsal_obj_handle **entry, const char *func, int line) { struct mem_dirent *dirent; *entry = NULL; LogFullDebug(COMPONENT_FSAL, "Lookup %s in %p", path, dir); GSH_UNIQUE_AUTO_TRACEPOINT(fsalmem, mem_lookup, TRACE_DEBUG, "Lookup. Handle: {}, path: {}", &dir->obj_handle, TP_STR(path)); if (strcmp(path, "..") == 0) { /* lookup parent - lookupp */ if (dir->mh_dir.parent == NULL) { return fsalstat(ERR_FSAL_NOENT, 0); } *entry = dir->mh_dir.parent; LogFullDebug(COMPONENT_FSAL, "Found %s/%s hdl=%p", dir->m_name, path, *entry); return fsalstat(ERR_FSAL_NO_ERROR, 0); } else if (strcmp(path, ".") == 0) { *entry = dir; return fsalstat(ERR_FSAL_NO_ERROR, 0); } dirent = mem_dirent_lookup(dir, path); if (!dirent) { return fsalstat(ERR_FSAL_NOENT, 0); } *entry = dirent->hdl; GSH_UNIQUE_AUTO_TRACEPOINT(fsalmem, mem_lookup, TRACE_DEBUG, "Lookup. Handle: {}, path: {}", &(*entry)->obj_handle, TP_STR((*entry)->m_name)); return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t mem_create_obj(struct mem_fsal_obj_handle *parent, object_file_type_t type, const char *name, struct fsal_attrlist *attrs_in, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct mem_fsal_export *mfe = container_of( op_ctx->fsal_export, struct mem_fsal_export, export); struct mem_fsal_obj_handle *hdl; fsal_status_t status; *new_obj = NULL; /* poison it */ if (parent->obj_handle.type != DIRECTORY) { LogCrit(COMPONENT_FSAL, "Parent handle is not a directory. hdl = 0x%p", parent); return fsalstat(ERR_FSAL_NOTDIR, 0); } status = mem_int_lookup(parent, name, &hdl); if (!FSAL_IS_ERROR(status)) { /* It already exists */ return fsalstat(ERR_FSAL_EXIST, 0); } else if (status.major != ERR_FSAL_NOENT) { /* Some other error */ return status; } /* allocate an obj_handle and fill it up */ hdl = mem_alloc_handle(parent, name, type, mfe, attrs_in, parent_pre_attrs_out, parent_post_attrs_out); if (!hdl) return fsalstat(ERR_FSAL_NOMEM, 0); *new_obj = &hdl->obj_handle; if (attrs_out != NULL) fsal_copy_attrs(attrs_out, &hdl->attrs, false); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* handle methods */ /** * @brief Lookup a file * * @param[in] parent Parent directory * @param[in] path Path to lookup * @param[out] handle Found handle, on success * @param[out] attrs_out Attributes of found handle * @return FSAL status */ static fsal_status_t mem_lookup(struct fsal_obj_handle *parent, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { struct mem_fsal_obj_handle *myself, *hdl = NULL; fsal_status_t status; myself = container_of(parent, struct mem_fsal_obj_handle, obj_handle); /* Check if this context already holds the lock on * this directory. */ if (op_ctx->fsal_private != parent) PTHREAD_RWLOCK_rdlock(&parent->obj_lock); else LogFullDebug(COMPONENT_FSAL, "Skipping lock for %s", myself->m_name); status = mem_int_lookup(myself, path, &hdl); if (FSAL_IS_ERROR(status)) { goto out; } *handle = &hdl->obj_handle; mem_int_get_ref(hdl); out: if (op_ctx->fsal_private != parent) PTHREAD_RWLOCK_unlock(&parent->obj_lock); if (!FSAL_IS_ERROR(status) && attrs_out != NULL) { /* This is unlocked, however, for the most part, attributes * are read-only. Come back later and do some lock protection. */ fsal_copy_attrs(attrs_out, &hdl->attrs, false); } return status; } /** * @brief Read a directory * * @param[in] dir_hdl the directory to read * @param[in] whence where to start (next) * @param[in] dir_state pass thru of state to callback * @param[in] cb callback function * @param[out] eof eof marker true == end of dir */ static fsal_status_t mem_readdir(struct fsal_obj_handle *dir_hdl, fsal_cookie_t *whence, void *dir_state, fsal_readdir_cb cb, attrmask_t attrmask, bool *eof) { struct mem_fsal_obj_handle *myself; struct mem_dirent *dirent, *dirent_next; fsal_cookie_t cookie = 0; struct fsal_attrlist attrs; enum fsal_dir_result cb_rc; int count = 0; myself = container_of(dir_hdl, struct mem_fsal_obj_handle, obj_handle); if (whence != NULL) cookie = *whence; *eof = true; GSH_AUTO_TRACEPOINT(fsalmem, mem_readdir, TRACE_DEBUG, "Readdir. Handle: {}, name: {}, cookie: {}", dir_hdl, TP_STR(myself->m_name), cookie); LogFullDebug(COMPONENT_FSAL, "hdl=%p, name=%s", myself, myself->m_name); PTHREAD_RWLOCK_rdlock(&dir_hdl->obj_lock); /* Use fsal_private to signal to lookup that we hold * the lock. */ op_ctx->fsal_private = dir_hdl; dirent = mem_readdir_seekloc(myself, cookie); /* Always run in index order */ for (; dirent != NULL; dirent = dirent_next) { if (count >= 2 * mdcache_param.dir.avl_chunk) { LogFullDebug(COMPONENT_FSAL, "readahead done %d", count); /* Limit readahead to 1 chunk */ *eof = false; break; } dirent_next = mem_dirent_next(dirent); if (dirent_next) { cookie = dirent_next->d_index; } else { cookie = UINT64_MAX; } fsal_prepare_attrs(&attrs, attrmask); fsal_copy_attrs(&attrs, &dirent->hdl->attrs, false); mem_int_get_ref(dirent->hdl); cb_rc = cb(dirent->d_name, &dirent->hdl->obj_handle, &attrs, dir_state, cookie); fsal_release_attrs(&attrs); count++; if (cb_rc >= DIR_TERMINATE) { *eof = false; break; } } op_ctx->fsal_private = NULL; PTHREAD_RWLOCK_unlock(&dir_hdl->obj_lock); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Create a directory * * This function creates a new directory. * * While FSAL_MEM is a support_ex FSAL, it doesn't actually support * setting attributes, so only the mode attribute is relevant. Any other * attributes set on creation will be ignored. The owner and group will be * set from the active credentials. * * @param[in] dir_hdl Directory in which to create the * directory * @param[in] name Name of directory to create * @param[in] attrs_in Attributes to set on newly created * object * @param[out] new_ob Newly created object * @param[in,out] attrs_out Optional attributes for newly created * object * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @note On success, @a new_obj has been ref'd * * @return FSAL status. */ static fsal_status_t mem_mkdir(struct fsal_obj_handle *dir_hdl, const char *name, struct fsal_attrlist *attrs_in, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct mem_fsal_obj_handle *parent = container_of(dir_hdl, struct mem_fsal_obj_handle, obj_handle); LogDebug(COMPONENT_FSAL, "mkdir %s", name); GSH_AUTO_TRACEPOINT(fsalmem, mem_mkdir, TRACE_DEBUG, "Mkdir. Handle: {}, parent name: {}, name: {}", dir_hdl, TP_STR(parent->m_name), TP_STR(name)); return mem_create_obj(parent, DIRECTORY, name, attrs_in, new_obj, attrs_out, parent_pre_attrs_out, parent_post_attrs_out); } /** * @brief Make a device node * * @param[in] dir_hdl Parent directory handle * @param[in] name Name of new node * @param[in] nodetype Type of new node * @param[in] attrs_in Attributes for new node * @param[out] new_obj New object handle on success * @param[in,out] attrs_out Optional attributes for the create * object. Should be atomic. * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @note This returns an INITIAL ref'd entry on success * @return FSAL status */ static fsal_status_t mem_mknode(struct fsal_obj_handle *dir_hdl, const char *name, object_file_type_t nodetype, struct fsal_attrlist *attrs_in, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct mem_fsal_obj_handle *hdl, *parent = container_of(dir_hdl, struct mem_fsal_obj_handle, obj_handle); fsal_status_t status; LogDebug(COMPONENT_FSAL, "mknode %s", name); status = mem_create_obj(parent, nodetype, name, attrs_in, new_obj, attrs_out, parent_pre_attrs_out, parent_post_attrs_out); if (unlikely(FSAL_IS_ERROR(status))) return status; hdl = container_of(*new_obj, struct mem_fsal_obj_handle, obj_handle); hdl->mh_node.nodetype = nodetype; hdl->mh_node.dev = attrs_in->rawdev; /* struct copy */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Make a symlink * * @param[in] dir_hdl Parent directory handle * @param[in] name Name of new node * @param[in] link_path Contents of symlink * @param[in] attrs_in Attributes for new simlink * @param[out] new_obj New object handle on success * @param[in,out] attrs_out Optional attributes for the create * object. Should be atomic. * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @note This returns an INITIAL ref'd entry on success * @return FSAL status */ static fsal_status_t mem_symlink(struct fsal_obj_handle *dir_hdl, const char *name, const char *link_path, struct fsal_attrlist *attrs_in, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct mem_fsal_obj_handle *hdl, *parent = container_of(dir_hdl, struct mem_fsal_obj_handle, obj_handle); fsal_status_t status; LogDebug(COMPONENT_FSAL, "symlink %s", name); status = mem_create_obj(parent, SYMBOLIC_LINK, name, attrs_in, new_obj, attrs_out, parent_pre_attrs_out, parent_post_attrs_out); if (unlikely(FSAL_IS_ERROR(status))) return status; hdl = container_of(*new_obj, struct mem_fsal_obj_handle, obj_handle); hdl->mh_symlink.link_contents = gsh_strdup(link_path); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Read a symlink * * @param[in] obj_hdl Handle for symlink * @param[out] link_content Buffer to fill with link contents * @param[in] refresh If true, refresh attributes on symlink * @return FSAL status */ static fsal_status_t mem_readlink(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *link_content, bool refresh) { struct mem_fsal_obj_handle *myself = container_of(obj_hdl, struct mem_fsal_obj_handle, obj_handle); if (obj_hdl->type != SYMBOLIC_LINK) { LogCrit(COMPONENT_FSAL, "Handle is not a symlink. hdl = 0x%p", obj_hdl); return fsalstat(ERR_FSAL_INVAL, 0); } link_content->len = strlen(myself->mh_symlink.link_contents) + 1; link_content->addr = gsh_strdup(myself->mh_symlink.link_contents); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Set attributes on an object * * This function sets attributes on an object. Which attributes are * set is determined by attrs_set->valid_mask. The FSAL must manage bypass * or not of share reservations, and a state may be passed. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] attrs_set Attributes to set * * @return FSAL status. */ fsal_status_t mem_setattr2(struct fsal_obj_handle *obj_hdl, bool bypass, struct state_t *state, struct fsal_attrlist *attrs_set) { struct mem_fsal_obj_handle *myself = container_of(obj_hdl, struct mem_fsal_obj_handle, obj_handle); /* apply umask, if mode attribute is to be changed */ if (FSAL_TEST_MASK(attrs_set->valid_mask, ATTR_MODE)) attrs_set->mode &= ~op_ctx->fsal_export->exp_ops.fs_umask( op_ctx->fsal_export); /* Test if size is being set, make sure file is regular and if so, * require a read/write file descriptor. */ if (FSAL_TEST_MASK(attrs_set->valid_mask, ATTR_SIZE) && obj_hdl->type != REGULAR_FILE) { LogFullDebug(COMPONENT_FSAL, "Setting size on non-regular file"); return fsalstat(ERR_FSAL_INVAL, EINVAL); } mem_copy_attrs_mask(attrs_set, &myself->attrs); GSH_AUTO_TRACEPOINT( fsalmem, mem_setattrs, TRACE_DEBUG, "Set attrs. Handle: {}, name: {}, size: {}, numlinks: {}, change: {}", obj_hdl, TP_STR(myself->m_name), myself->attrs.filesize, myself->attrs.numlinks, myself->attrs.change); return fsalstat(ERR_FSAL_NO_ERROR, EINVAL); } /** * @brief Hard link an obj * * @param[in] obj_hdl File to link * @param[in] dir_hdl Directory to link into * @param[in] name Name to use for link * @param[in,out] destdir_pre_attrs_out Optional attributes for destdir dir * before the operation. Should be atomic. * @param[in,out] destdir_post_attrs_out Optional attributes for destdir dir * after the operation. Should be atomic. * * @return FSAL status. */ fsal_status_t mem_link(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *dir_hdl, const char *name, struct fsal_attrlist *destdir_pre_attrs_out, struct fsal_attrlist *destdir_post_attrs_out) { struct mem_fsal_obj_handle *myself = container_of(obj_hdl, struct mem_fsal_obj_handle, obj_handle); struct mem_fsal_obj_handle *dir = container_of(dir_hdl, struct mem_fsal_obj_handle, obj_handle); struct mem_fsal_obj_handle *hdl; fsal_status_t status = { 0, 0 }; status = mem_int_lookup(dir, name, &hdl); if (!FSAL_IS_ERROR(status)) { /* It already exists */ return fsalstat(ERR_FSAL_EXIST, 0); } else if (status.major != ERR_FSAL_NOENT) { /* Some other error */ return status; } mem_insert_obj(dir, myself, name, destdir_pre_attrs_out, destdir_post_attrs_out); myself->attrs.numlinks++; GSH_AUTO_TRACEPOINT( fsalmem, mem_link, TRACE_DEBUG, "Link. Dir handle: {}, dir name: {}, obj handle: {}, obj name: {}, new name {}, num links: {}", dir_hdl, TP_STR(dir->m_name), obj_hdl, TP_STR(myself->m_name), TP_STR(name), myself->attrs.numlinks); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Unlink a file * * @param[in] dir_hdl Parent directory handle * @param[in] obj_hdl Object being removed * @param[in] name Name of object to remove * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @return FSAL status */ static fsal_status_t mem_unlink(struct fsal_obj_handle *dir_hdl, struct fsal_obj_handle *obj_hdl, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct mem_fsal_obj_handle *parent, *myself; fsal_status_t status = { 0, 0 }; uint32_t numkids; struct mem_dirent *dirent; parent = container_of(dir_hdl, struct mem_fsal_obj_handle, obj_handle); myself = container_of(obj_hdl, struct mem_fsal_obj_handle, obj_handle); GSH_AUTO_TRACEPOINT( fsalmem, mem_unlink, TRACE_DEBUG, "Unlink. Dir handle: {}, dir name: {}, obj handle: {}, obj name: {}, num links: {}", dir_hdl, TP_STR(parent->m_name), obj_hdl, TP_STR(myself->m_name), myself->attrs.numlinks); PTHREAD_RWLOCK_wrlock(&dir_hdl->obj_lock); if (parent_pre_attrs_out != NULL) mem_getattrs(dir_hdl, parent_pre_attrs_out); switch (obj_hdl->type) { case DIRECTORY: /* Check if directory is empty */ numkids = atomic_fetch_uint32_t(&myself->mh_dir.numkids); if (numkids > 2) { LogFullDebug(COMPONENT_FSAL, "%s numkids %" PRIu32, myself->m_name, numkids); status = fsalstat(ERR_FSAL_NOTEMPTY, 0); goto unlock; } break; case REGULAR_FILE: /* Openable. Make sure it's closed */ if (myself->mh_file.fd.openflags != FSAL_O_CLOSED) { status = fsalstat(ERR_FSAL_FILE_OPEN, 0); goto unlock; } /* FALLTHROUGH */ case SYMBOLIC_LINK: case SOCKET_FILE: case CHARACTER_FILE: case BLOCK_FILE: case FIFO_FILE: /* Unopenable. Just clean up */ myself->attrs.numlinks--; break; default: break; } /* Remove the dirent from the parent*/ dirent = mem_dirent_lookup(parent, name); if (dirent) { mem_remove_dirent_locked(parent, dirent); } unlock: if (parent_post_attrs_out != NULL) mem_getattrs(dir_hdl, parent_post_attrs_out); PTHREAD_RWLOCK_unlock(&dir_hdl->obj_lock); return status; } /** * @brief Close a file's global descriptor * * @param[in] obj_hdl File on which to operate * * @return FSAL status. */ fsal_status_t mem_close(struct fsal_obj_handle *obj_hdl) { struct mem_fsal_obj_handle *myself = container_of(obj_hdl, struct mem_fsal_obj_handle, obj_handle); fsal_status_t status; assert(obj_hdl->type == REGULAR_FILE); status = close_fsal_fd(obj_hdl, &myself->mh_file.fd, false); return status; } /** * @brief Rename an object * * Rename the given object from @a old_name in @a olddir_hdl to @a new_name in * @a newdir_hdl. The old and new directories may be the same. * * @param[in] obj_hdl Object to rename * @param[in] olddir_hdl Directory containing @a obj_hdl * @param[in] old_name Current name of @a obj_hdl * @param[in] newdir_hdl Directory to move @a obj_hdl to * @param[in] new_name Name to rename @a obj_hdl to * @param[in,out] olddir_pre_attrs_out Optional attributes for olddir dir * before the operation. Should be atomic. * @param[in,out] olddir_post_attrs_out Optional attributes for olddir dir * after the operation. Should be atomic. * @param[in,out] newdir_pre_attrs_out Optional attributes for newdir dir * before the operation. Should be atomic. * @param[in,out] newdir_post_attrs_out Optional attributes for newdir dir * after the operation. Should be atomic. * * Note: This function is not atomic, and so, the pre/post attributes are also * not atomic. * * * @return FSAL status */ static fsal_status_t mem_rename(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *olddir_hdl, const char *old_name, struct fsal_obj_handle *newdir_hdl, const char *new_name, struct fsal_attrlist *olddir_pre_attrs_out, struct fsal_attrlist *olddir_post_attrs_out, struct fsal_attrlist *newdir_pre_attrs_out, struct fsal_attrlist *newdir_post_attrs_out) { struct mem_fsal_obj_handle *mem_olddir = container_of( olddir_hdl, struct mem_fsal_obj_handle, obj_handle); struct mem_fsal_obj_handle *mem_newdir = container_of( newdir_hdl, struct mem_fsal_obj_handle, obj_handle); struct mem_fsal_obj_handle *mem_obj = container_of(obj_hdl, struct mem_fsal_obj_handle, obj_handle); struct mem_fsal_obj_handle *mem_lookup_dst = NULL; fsal_status_t status; status = mem_int_lookup(mem_newdir, new_name, &mem_lookup_dst); if (!FSAL_IS_ERROR(status)) { uint32_t numkids; if (mem_obj == mem_lookup_dst) { /* Same source and destination */ return status; } if ((obj_hdl->type == DIRECTORY && mem_lookup_dst->obj_handle.type != DIRECTORY) || (obj_hdl->type != DIRECTORY && mem_lookup_dst->obj_handle.type == DIRECTORY)) { /* Types must be "compatible" */ return fsalstat(ERR_FSAL_EXIST, 0); } numkids = atomic_fetch_uint32_t(&mem_lookup_dst->mh_dir.numkids); if (mem_lookup_dst->obj_handle.type == DIRECTORY && numkids > 2) { /* Target dir must be empty */ return fsalstat(ERR_FSAL_EXIST, 0); } /* Unlink destination */ status = mem_unlink(newdir_hdl, &mem_lookup_dst->obj_handle, new_name, NULL, NULL); if (FSAL_IS_ERROR(status)) { return status; } } GSH_AUTO_TRACEPOINT( fsalmem, mem_rename, TRACE_DEBUG, "Rename. Handle: {}, old dir name: {}, old name: {}, new dir name: {}, new name: {}", obj_hdl, TP_STR(mem_olddir->m_name), TP_STR(old_name), TP_STR(mem_newdir->m_name), TP_STR(new_name)); /* Remove from old dir */ mem_remove_dirent(mem_olddir, old_name, olddir_pre_attrs_out, olddir_post_attrs_out); if (!strcmp(old_name, mem_obj->m_name)) { /* Change base name */ gsh_free(mem_obj->m_name); mem_obj->m_name = gsh_strdup(new_name); } /* Insert into new directory */ mem_insert_obj(mem_newdir, mem_obj, new_name, newdir_pre_attrs_out, newdir_post_attrs_out); return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t mem_open2_by_handle(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, fsal_verifier_t verifier, struct fsal_attrlist *attrs_out) { struct fsal_fd *my_fd; struct mem_fsal_obj_handle *myself; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; fsal_openflags_t old_openflags; bool truncated = openflags & FSAL_O_TRUNC; myself = container_of(obj_hdl, struct mem_fsal_obj_handle, obj_handle); if (state != NULL) my_fd = &container_of(state, struct mem_state_fd, state) ->fsal_fd; else my_fd = &myself->mh_file.fd; /* Indicate we want to do fd work (can't fail since not reclaiming) */ fsal_start_fd_work_no_reclaim(my_fd); old_openflags = my_fd->openflags; if (state != NULL) { /* Prepare to take the share reservation, but only if we are * called with a valid state (if state is NULL the caller is a * stateless create such as NFS v3 CREATE and we're just going * to ignore share reservation stuff). */ /* Now that we have the mutex, and no I/O is in progress so we * have exclusive access to the share's fsal_fd, we can look at * its openflags. We also need to work the share reservation so * take the obj_lock. NOTE: This is the ONLY sequence where both * a work_mutex and the obj_lock are taken, so there is no * opportunity for ABBA deadlock. * * Note that we do hold the obj_lock over an open and a close * which is longer than normal, but the previous iteration of * the code held the obj lock (read granted) over whole I/O * operations... We don't block over I/O because we've assured * that no I/O is in progress or can start before proceeding * past the above while loop. */ PTHREAD_RWLOCK_wrlock(&obj_hdl->obj_lock); /* Now check the new share. */ status = check_share_conflict(&myself->mh_file.share, openflags, false); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "check_share_conflict returned %s", fsal_err_txt(status)); goto exit; } } /* Check for a genuine no-op open. That means we aren't trying to * create, the file is already open in the same mode with the same * deny flags, and we aren't trying to truncate. In this case we want * to avoid bouncing the fd. In the case of JUST changing the deny mode * or an replayed exclusive create, we might bounce the fd when we could * have avoided that, but those scenarios are much less common. */ if (FSAL_O_NFS_FLAGS(openflags) == FSAL_O_NFS_FLAGS(old_openflags) && truncated == false && createmode == FSAL_NO_CREATE) { LogFullDebug(COMPONENT_FSAL, "no-op reopen2 my_fd = %p openflags = %x", my_fd, openflags); goto exit; } /* No share conflict, re-open the share fd (can't fail) */ (void)mem_reopen_func(obj_hdl, openflags, my_fd); /* Inserts to fd_lru only if open succeeds */ if (old_openflags == FSAL_O_CLOSED) { /* This is actually an open, need to increment * appropriate counter and insert into LRU. */ insert_fd_lru(my_fd); } else { /* Bump up the FD in fd_lru as it was already in fd lru. */ bump_fd_lru(my_fd); } if (truncated) myself->attrs.filesize = myself->attrs.spaceused = 0; /* Now check verifier for exclusive, but not for * FSAL_EXCLUSIVE_9P. */ if (createmode >= FSAL_EXCLUSIVE && createmode != FSAL_EXCLUSIVE_9P && !check_verifier_attrlist(&myself->attrs, verifier, false)) { /* Verifier didn't match, return EEXIST */ status = posix2fsal_status(EEXIST); } else if (attrs_out != NULL) { /* Note, myself->attrs is usually protected by a * the attr_lock in MDCACHE. It's not in this * case. Since MEM is not a production FSAL, * this is deemed to be okay for the moment. */ fsal_copy_attrs(attrs_out, &myself->attrs, false); } if (FSAL_IS_ERROR(status)) { if (old_openflags == FSAL_O_CLOSED) { /* Now that we have decided to close this FD, * let's clean it off from fd_lru and * ensure counters are decremented. */ remove_fd_lru(my_fd); } /* Close fd */ (void)mem_close_my_fd(my_fd); } exit: if (state != NULL) { if (!FSAL_IS_ERROR(status)) { /* Success, establish the new share. */ update_share_counters(&myself->mh_file.share, old_openflags, openflags); } /* Release obj_lock. */ PTHREAD_RWLOCK_unlock(&obj_hdl->obj_lock); } /* Indicate we are done with fd work and signal any waiters. */ fsal_complete_fd_work(my_fd); return status; } /** * @brief Open a file descriptor for read or write and possibly create * * @param[in] obj_hdl File to open or parent directory * @param[in,out] state state_t to use for this operation * @param[in] openflags Mode for open * @param[in] createmode Mode for create * @param[in] name Name for file if being created or opened * @param[in] attrs_in Attributes to set on created file * @param[in] verifier Verifier to use for exclusive create * @param[in,out] new_obj Newly created object * @param[in,out] attrs_out Optional attributes for newly created * object * @param[in,out] caller_perm_check The caller must do a permission check * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @return FSAL status. */ fsal_status_t mem_open2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attrs_set, fsal_verifier_t verifier, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { fsal_status_t status = { 0, 0 }; struct fsal_fd *my_fd = NULL; struct mem_fsal_obj_handle *myself, *hdl = NULL; bool truncated; bool setattrs = attrs_set != NULL; bool created = false; struct fsal_attrlist verifier_attr; if (state != NULL) my_fd = &container_of(state, struct mem_state_fd, state) ->fsal_fd; myself = container_of(obj_hdl, struct mem_fsal_obj_handle, obj_handle); if (setattrs) LogAttrlist(COMPONENT_FSAL, NIV_FULL_DEBUG, "attrs_set ", attrs_set, false); truncated = (openflags & FSAL_O_TRUNC) != 0; LogFullDebug(COMPONENT_FSAL, truncated ? "Truncate" : "No truncate"); /* Now fixup attrs for verifier if exclusive create */ if (createmode >= FSAL_EXCLUSIVE) { if (!setattrs) { /* We need to use verifier_attr */ attrs_set = &verifier_attr; memset(&verifier_attr, 0, sizeof(verifier_attr)); } set_common_verifier(attrs_set, verifier, false); } if (name == NULL) { /* This is an open by handle */ GSH_AUTO_TRACEPOINT( fsalmem, mem_open_by_handle, TRACE_DEBUG, "Open by handle. Handle: {}, name: {}, state: {}, truncated: {}, setattrs: {}", obj_hdl, TP_STR(myself->m_name), state, truncated, setattrs); status = mem_open2_by_handle(obj_hdl, state, openflags, createmode, verifier, attrs_out); *caller_perm_check = FSAL_IS_SUCCESS(status); return status; } /* In this path where we are opening by name, we can't check share * reservation yet since we don't have an object_handle yet. If we * indeed create the object handle (there is no race with another * open by name), then there CAN NOT be a share conflict, otherwise * the share conflict will be resolved when the object handles are * merged. */ status = mem_int_lookup(myself, name, &hdl); if (FSAL_IS_ERROR(status)) { struct fsal_obj_handle *create; if (status.major != ERR_FSAL_NOENT) { /* Actual error from lookup */ return status; } /* Doesn't exist, create it */ status = mem_create_obj(myself, REGULAR_FILE, name, attrs_set, &create, attrs_out, parent_pre_attrs_out, parent_post_attrs_out); if (FSAL_IS_ERROR(status)) { return status; } hdl = container_of(create, struct mem_fsal_obj_handle, obj_handle); created = true; } GSH_AUTO_TRACEPOINT( fsalmem, mem_open, TRACE_DEBUG, "Open . Handle: {}, name: {}, state: {}, truncated: {}, setattrs: {}", &hdl->obj_handle, TP_STR(hdl->m_name), state, truncated, setattrs); *caller_perm_check = !created; /* If we didn't have a state above, use the global fd. At this point, * since we just created the global fd, no one else can have a * reference to it, and thus we can mamnipulate unlocked which is * handy since we can then call setattr2 which WILL take the lock * without a double locking deadlock. */ if (my_fd == NULL) { LogFullDebug(COMPONENT_FSAL, "Using global fd"); my_fd = &hdl->mh_file.fd; /* Need to LRU track global fd including incrementing * fsal_fd_global_counter. */ insert_fd_lru(my_fd); } if (openflags & FSAL_O_WRITE) openflags |= FSAL_O_READ; mem_open_my_fd(my_fd, openflags); *new_obj = &hdl->obj_handle; if (!created) { /* Create sets and gets attributes, so only do this if not * creating */ if (setattrs && attrs_set->valid_mask != 0) { mem_copy_attrs_mask(attrs_set, &hdl->attrs); } if (attrs_out != NULL) { status = (*new_obj)->obj_ops->getattrs(*new_obj, attrs_out); if (FSAL_IS_ERROR(status) && (attrs_out->request_mask & ATTR_RDATTR_ERR) == 0) { /* Get attributes failed and caller expected * to get the attributes. Otherwise continue * with attrs_out indicating ATTR_RDATTR_ERR. */ return status; } } } if (state != NULL) { /* Prepare to take the share reservation, but only if we are * called with a valid state (if state is NULL the caller is * a stateless create such as NFS v3 CREATE). */ /* Take the share reservation now by updating the counters. */ update_share_counters_locked(*new_obj, &hdl->mh_file.share, FSAL_O_CLOSED, openflags); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Return open status of a state. * * This function returns open flags representing the current open * status for a state. The st_lock must be held. * * @param[in] obj_hdl File on which to operate * @param[in] state File state to interrogate * * @retval Flags representing current open status */ fsal_openflags_t mem_status2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { struct fsal_fd *my_fd = &((struct mem_state_fd *)state)->fsal_fd; return my_fd->openflags; } /** * @brief Re-open a file that may be already opened * * This function supports changing the access mode of a share reservation and * thus should only be called with a share state. The st_lock must be held. * * This MAY be used to open a file the first time if there is no need for * open by name or create semantics. One example would be 9P lopen. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] openflags Mode for re-open * * @return FSAL status. */ fsal_status_t mem_reopen2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags) { return mem_open2_by_handle(obj_hdl, state, openflags, FSAL_NO_CREATE, NULL, NULL); } struct mem_async_arg { struct fsal_obj_handle *obj_hdl; struct fsal_io_arg *io_arg; fsal_async_cb done_cb; void *caller_arg; struct gsh_export *exp; struct fsal_export *fsal_export; struct fsal_fd *out_fd; uint32_t share; struct fsal_fd temp_fd; }; static void mem_async_complete(struct fridgethr_context *ctx) { struct mem_async_arg *async_arg = ctx->arg; struct mem_fsal_export *mem_export = container_of( async_arg->fsal_export, struct mem_fsal_export, export); uint32_t async_delay = atomic_fetch_uint32_t(&mem_export->async_delay); fsal_status_t status; struct mem_fsal_obj_handle *myself = container_of( async_arg->obj_hdl, struct mem_fsal_obj_handle, obj_handle); struct req_op_context opctx; /* Now check if we need to delay the call back */ if (atomic_fetch_uint32_t(&mem_export->async_type) != MEM_FIXED) { /* Randomize delay */ async_delay = random() % async_delay; } if (async_delay != 0) { /* Now actually delay call back */ usleep(async_delay); } /* Even if we might already have an op context, we are going to build * a simple one from information in the mem_async_arg. */ get_gsh_export_ref(async_arg->exp); init_op_context_simple(&opctx, async_arg->exp, async_arg->fsal_export); status = fsal_complete_io(async_arg->obj_hdl, async_arg->out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status)); if (async_arg->io_arg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(async_arg->obj_hdl, &myself->mh_file.share, async_arg->share, FSAL_O_CLOSED); } async_arg->done_cb(async_arg->obj_hdl, fsalstat(ERR_FSAL_NO_ERROR, 0), async_arg->io_arg, async_arg->caller_arg); destroy_fsal_fd(&async_arg->temp_fd); release_op_context(); gsh_free(async_arg); } /** * @brief Read data from a file * * This function reads data from the given file. The FSAL must be able to * perform the read whether a state is presented or not. This function also * is expected to handle properly bypassing or not share reservations. This is * an (optionally) asynchronous call. When the I/O is complete, the done * callback is called with the results. * * @param[in] obj_hdl File on which to operate * @param[in] bypass If state doesn't indicate a share reservation, * bypass any deny read * @param[in,out] done_cb Callback to call when I/O is done * @param[in,out] read_arg Info about read, passed back in callback * @param[in,out] caller_arg Opaque arg from the caller for callback * * @return Nothing; results are in callback */ void mem_read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg) { struct mem_fsal_obj_handle *myself = container_of(obj_hdl, struct mem_fsal_obj_handle, obj_handle); struct fsal_fd *out_fd; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }, status2; uint64_t offset = read_arg->offset; int i; struct mem_fsal_export *mem_export = container_of( op_ctx->fsal_export, struct mem_fsal_export, export); uint32_t async_type = atomic_fetch_uint32_t(&mem_export->async_type); uint32_t async_stall_delay = atomic_fetch_uint32_t(&mem_export->async_stall_delay); struct mem_async_arg *async_arg; if (read_arg->info != NULL) { /* Currently we don't support READ_PLUS */ done_cb(obj_hdl, fsalstat(ERR_FSAL_NOTSUPP, 0), read_arg, caller_arg); return; } /* We always meed async_arg because that's where temp_fd is located * and we may need temp_fd before we know we actually are going async. */ async_arg = gsh_calloc(1, sizeof(*async_arg)); init_fsal_fd(&async_arg->temp_fd, FSAL_FD_TEMP, op_ctx->fsal_export); /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->mh_file.fd, &async_arg->temp_fd, read_arg->state, FSAL_O_READ, false, NULL, bypass, &myself->mh_file.share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } read_arg->io_amount = 0; for (i = 0; i < read_arg->iov_count; i++) { size_t bufsize; if (offset > myself->attrs.filesize) { /* Past end of file */ read_arg->end_of_file = true; break; } bufsize = read_arg->iov[i].iov_len; if (offset + bufsize > myself->attrs.filesize) { bufsize = myself->attrs.filesize - offset; } if (offset < myself->datasize) { size_t readsize; /* Data to read */ readsize = MIN(bufsize, myself->datasize - offset); memcpy(read_arg->iov[i].iov_base, myself->data + offset, readsize); if (readsize < bufsize) memset(read_arg->iov[i].iov_base + readsize, 'a', bufsize - readsize); } else { memset(read_arg->iov[i].iov_base, 'a', bufsize); } read_arg->io_amount += bufsize; offset += bufsize; } GSH_AUTO_TRACEPOINT( fsalmem, mem_read, TRACE_DEBUG, "Read. Handle: {}, name: {}, state: {}, size: {}, spaceused: {}", obj_hdl, TP_STR(myself->m_name), read_arg->state, myself->attrs.filesize, myself->attrs.spaceused); now(&myself->attrs.atime); if (MEM.async_threads > 0 && (async_type > MEM_RANDOM_OR_INLINE || ((async_type == MEM_RANDOM_OR_INLINE) && ((random() % 1) == 1)))) { /* Initialize the rest of async_arg */ async_arg->obj_hdl = obj_hdl; async_arg->io_arg = read_arg; async_arg->caller_arg = caller_arg; async_arg->done_cb = done_cb; async_arg->exp = op_ctx->ctx_export; async_arg->fsal_export = op_ctx->fsal_export; async_arg->out_fd = out_fd; async_arg->share = FSAL_O_READ; if (fridgethr_submit(mem_async_fridge, mem_async_complete, async_arg) == 0) { /* Async fired off... * I/O will complete async. */ goto bye; } } status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (read_arg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->mh_file.share, FSAL_O_READ, FSAL_O_CLOSED); } exit: done_cb(obj_hdl, fsalstat(ERR_FSAL_NO_ERROR, 0), read_arg, caller_arg); destroy_fsal_fd(&async_arg->temp_fd); /* Now free the async arg since we are done with it. */ gsh_free(async_arg); bye: if (async_stall_delay > 0) { /* We have been asked to stall the calling thread, whether we * issued an inline or async callback. */ usleep(async_stall_delay); } } /** * @brief Write data to a file * * This function writes data to a file. The FSAL must be able to * perform the write whether a state is presented or not. This function also * is expected to handle properly bypassing or not share reservations. Even * with bypass == true, it will enforce a mandatory (NFSv4) deny_write if * an appropriate state is not passed). * * The FSAL is expected to enforce sync if necessary. * * @param[in] obj_hdl File on which to operate * @param[in] bypass If state doesn't indicate a share reservation, * bypass any non-mandatory deny write * @param[in,out] done_cb Callback to call when I/O is done * @param[in,out] write_arg Info about write, passed back in callback * @param[in,out] caller_arg Opaque arg from the caller for callback */ void mem_write2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *caller_arg) { struct mem_fsal_obj_handle *myself = container_of(obj_hdl, struct mem_fsal_obj_handle, obj_handle); struct fsal_fd *out_fd; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }, status2; uint64_t offset = write_arg->offset; int i; struct mem_fsal_export *mem_export = container_of( op_ctx->fsal_export, struct mem_fsal_export, export); uint32_t async_type = atomic_fetch_uint32_t(&mem_export->async_type); uint32_t async_stall_delay = atomic_fetch_uint32_t(&mem_export->async_stall_delay); struct mem_async_arg *async_arg; if (obj_hdl->type != REGULAR_FILE) { /* Currently can only write to a file */ done_cb(obj_hdl, fsalstat(ERR_FSAL_INVAL, 0), write_arg, caller_arg); return; } /* We always meed async_arg because that's where temp_fd is located * and we may need temp_fd before we know we actually are going async. */ async_arg = gsh_calloc(1, sizeof(*async_arg)); init_fsal_fd(&async_arg->temp_fd, FSAL_FD_TEMP, op_ctx->fsal_export); /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->mh_file.fd, &async_arg->temp_fd, write_arg->state, FSAL_O_WRITE, false, NULL, bypass, &myself->mh_file.share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } for (i = 0; i < write_arg->iov_count; i++) { size_t bufsize; bufsize = write_arg->iov[i].iov_len; if (offset + bufsize > myself->attrs.filesize) { myself->attrs.filesize = myself->attrs.spaceused = offset + bufsize; } if (offset < myself->datasize) { size_t writesize; /* Data to write */ writesize = MIN(bufsize, myself->datasize - offset); memcpy(myself->data + offset, write_arg->iov[i].iov_base, writesize); } write_arg->io_amount += bufsize; offset += bufsize; } GSH_AUTO_TRACEPOINT( fsalmem, mem_write, TRACE_DEBUG, "Write. Handle: {}, name: {}, state: {}, size: {}, spaceused: {}", obj_hdl, TP_STR(myself->m_name), write_arg->state, myself->attrs.filesize, myself->attrs.spaceused); /* Update change stats */ now(&myself->attrs.mtime); /** @todo FSF - this calculation may be different than what particular * FSALs use. Is that a problem? Also, above, ctime is used * not mtime... */ myself->attrs.change = timespec_to_nsecs(&myself->attrs.mtime); if (MEM.async_threads > 0 && (async_type > MEM_RANDOM_OR_INLINE || ((async_type == MEM_RANDOM_OR_INLINE) && ((random() % 1) == 1)))) { struct mem_async_arg *async_arg; /* Was MEM_FIXED, MEM_RANDOM, or MEM_RANDOM_OR_INLINE and we * scored a non-inline. */ async_arg = gsh_malloc(sizeof(*async_arg)); async_arg->obj_hdl = obj_hdl; async_arg->io_arg = write_arg; async_arg->caller_arg = caller_arg; async_arg->done_cb = done_cb; async_arg->exp = op_ctx->ctx_export; async_arg->fsal_export = op_ctx->fsal_export; async_arg->out_fd = out_fd; async_arg->share = FSAL_O_WRITE; if (fridgethr_submit(mem_async_fridge, mem_async_complete, async_arg) == 0) { /* Async fired off... * I/O will complete async. */ goto bye; } } status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (write_arg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->mh_file.share, FSAL_O_WRITE, FSAL_O_CLOSED); } exit: done_cb(obj_hdl, fsalstat(ERR_FSAL_NO_ERROR, 0), write_arg, caller_arg); destroy_fsal_fd(&async_arg->temp_fd); /* Now free the async arg since we are done with it. */ gsh_free(async_arg); bye: if (async_stall_delay > 0) { /* We have been asked to stall the calling thread, whether we * issued an inline or async callback. */ usleep(async_stall_delay); } } /** * @brief Commit written data * * This function flushes possibly buffered data to a file. This method * differs from commit due to the need to interact with share reservations * and the fact that the FSAL manages the state of "file descriptors". The * FSAL must be able to perform this operation without being passed a specific * state. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] offset Start of range to commit * @param[in] len Length of range to commit * * @return FSAL status. */ fsal_status_t mem_commit2(struct fsal_obj_handle *obj_hdl, off_t offset, size_t len) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Manage closing a file when a state is no longer needed. * * When the upper layers are ready to dispense with a state, this method is * called to allow the FSAL to close any file descriptors or release any other * resources associated with the state. A call to free_state should be assumed * to follow soon. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * * @return FSAL status. */ fsal_status_t mem_close2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { struct fsal_fd *my_fd = &container_of(state, struct mem_state_fd, state)->fsal_fd; struct mem_fsal_obj_handle *myself = container_of(obj_hdl, struct mem_fsal_obj_handle, obj_handle); GSH_AUTO_TRACEPOINT(fsalmem, mem_close, TRACE_DEBUG, "Close. Handle: {}, name: {}, state: {}", obj_hdl, TP_STR(myself->m_name), state); if (state->state_type == STATE_TYPE_SHARE || state->state_type == STATE_TYPE_NLM_SHARE || state->state_type == STATE_TYPE_9P_FID) { /* This is a share state, we must update the share counters */ update_share_counters_locked(obj_hdl, &myself->mh_file.share, my_fd->openflags, FSAL_O_CLOSED); } return close_fsal_fd(obj_hdl, my_fd, false); } /** * @brief Get the wire version of a handle * * fill in the opaque f/s file handle part. * we zero the buffer to length first. This MAY already be done above * at which point, remove memset here because the caller is zeroing * the whole struct. * * @param[in] obj_hdl Handle to digest * @param[in] out_type Type of digest to get * @param[out] fh_desc Buffer to write digest into * @return FSAL status */ static fsal_status_t mem_handle_to_wire(const struct fsal_obj_handle *obj_hdl, fsal_digesttype_t output_type, struct gsh_buffdesc *fh_desc) { const struct mem_fsal_obj_handle *myself; myself = container_of(obj_hdl, const struct mem_fsal_obj_handle, obj_handle); switch (output_type) { case FSAL_DIGEST_NFSV3: case FSAL_DIGEST_NFSV4: if (fh_desc->len < V4_FH_OPAQUE_SIZE) { LogMajor( COMPONENT_FSAL, "Space too small for handle. need %lu, have %zu", ((unsigned long)V4_FH_OPAQUE_SIZE), fh_desc->len); return fsalstat(ERR_FSAL_TOOSMALL, 0); } memcpy(fh_desc->addr, myself->handle, V4_FH_OPAQUE_SIZE); fh_desc->len = V4_FH_OPAQUE_SIZE; break; default: return fsalstat(ERR_FSAL_SERVERFAULT, 0); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Get the unique key for a handle * * return a handle descriptor into the handle in this object handle * @TODO reminder. make sure things like hash keys don't point here * after the handle is released. * * @param[in] obj_hdl Handle to digest * @param[out] fh_desc Buffer to write key into */ static void mem_handle_to_key(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *fh_desc) { struct mem_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct mem_fsal_obj_handle, obj_handle); fh_desc->addr = myself->handle; fh_desc->len = V4_FH_OPAQUE_SIZE; } /** * @brief Get a ref on a MEM handle * * Stub, for bypass in unit tests * * @param[in] obj_hdl Handle to ref */ static void mem_get_ref(struct fsal_obj_handle *obj_hdl) { struct mem_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct mem_fsal_obj_handle, obj_handle); mem_int_get_ref(myself); } /** * @brief Put a ref on a MEM handle * * Stub, for bypass in unit tests * * @param[in] obj_hdl Handle to unref */ static void mem_put_ref(struct fsal_obj_handle *obj_hdl) { struct mem_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct mem_fsal_obj_handle, obj_handle); mem_int_put_ref(myself); } /** * @brief Release an object handle * * @param[in] obj_hdl Handle to release */ static void mem_release(struct fsal_obj_handle *obj_hdl) { struct mem_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct mem_fsal_obj_handle, obj_handle); if (myself->obj_handle.type == REGULAR_FILE) { fsal_status_t st; st = close_fsal_fd(obj_hdl, &myself->mh_file.fd, false); if (FSAL_IS_ERROR(st)) { LogCrit(COMPONENT_FSAL, "Could not close hdl 0x%p, status %s error %s(%d)", obj_hdl, fsal_err_txt(st), strerror(st.minor), st.minor); } } mem_int_put_ref(myself); } /** * @brief Merge two handles * * For a failed create, we need to merge the two handles. If the handles are * the same, we need to ref the handle, so that the following release doesn't * free it. * * @param[in] old_hdl Handle to merge * @param[in] new_hdl Handle to merge */ static fsal_status_t mem_merge(struct fsal_obj_handle *old_hdl, struct fsal_obj_handle *new_hdl) { fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; if (old_hdl == new_hdl) { /* Nothing to merge */ return status; } if (old_hdl->type == REGULAR_FILE && new_hdl->type == REGULAR_FILE) { /* We need to merge the share reservations on this file. * This could result in ERR_FSAL_SHARE_DENIED. */ struct mem_fsal_obj_handle *old, *new; old = container_of(old_hdl, struct mem_fsal_obj_handle, obj_handle); new = container_of(new_hdl, struct mem_fsal_obj_handle, obj_handle); /* This can block over an I/O operation. */ status = merge_share(old_hdl, &old->mh_file.share, &new->mh_file.share); } return status; } void mem_handle_ops_init(struct fsal_obj_ops *ops) { fsal_default_obj_ops_init(ops); ops->get_ref = mem_get_ref, ops->put_ref = mem_put_ref, ops->merge = mem_merge, ops->release = mem_release; ops->lookup = mem_lookup; ops->readdir = mem_readdir; ops->mkdir = mem_mkdir; ops->mknode = mem_mknode; ops->symlink = mem_symlink; ops->readlink = mem_readlink; ops->getattrs = mem_getattrs; ops->setattr2 = mem_setattr2; ops->link = mem_link; ops->rename = mem_rename; ops->unlink = mem_unlink; ops->close = mem_close; ops->open2 = mem_open2; ops->status2 = mem_status2; ops->reopen2 = mem_reopen2; ops->read2 = mem_read2; ops->write2 = mem_write2; ops->commit2 = mem_commit2; ops->close2 = mem_close2; ops->close_func = mem_close_func; ops->reopen_func = mem_reopen_func; ops->handle_to_wire = mem_handle_to_wire; ops->handle_to_key = mem_handle_to_key; } /* export methods that create object handles */ /* lookup_path * modelled on old api except we don't stuff attributes. * KISS */ fsal_status_t mem_lookup_path(struct fsal_export *exp_hdl, const char *path, struct fsal_obj_handle **obj_hdl, struct fsal_attrlist *attrs_out) { struct mem_fsal_export *mfe; struct fsal_attrlist attrs; mfe = container_of(exp_hdl, struct mem_fsal_export, export); if (strcmp(path, mfe->export_path) != 0) { /* Lookup of a path other than the export's root. */ LogCrit(COMPONENT_FSAL, "Attempt to lookup non-root path %s", path); return fsalstat(ERR_FSAL_NOENT, ENOENT); } attrs.valid_mask = ATTR_MODE; attrs.mode = 0777; if (mfe->root_handle == NULL) { mfe->root_handle = mem_alloc_handle(NULL, mfe->export_path, DIRECTORY, mfe, &attrs, NULL, NULL); } *obj_hdl = &mfe->root_handle->obj_handle; if (attrs_out != NULL) fsal_copy_attrs(attrs_out, &mfe->root_handle->attrs, false); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* create_handle * Does what original FSAL_ExpandHandle did (sort of) * returns a ref counted handle to be later used in mdcache etc. * NOTE! you must release this thing when done with it! * BEWARE! Thanks to some holes in the *AT syscalls implementation, * we cannot get an fd on an AF_UNIX socket, nor reliably on block or * character special devices. Sorry, it just doesn't... * we could if we had the handle of the dir it is in, but this method * is for getting handles off the wire for cache entries that have LRU'd. * Ideas and/or clever hacks are welcome... */ fsal_status_t mem_create_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *hdl_desc, struct fsal_obj_handle **obj_hdl, struct fsal_attrlist *attrs_out) { struct glist_head *glist; struct fsal_obj_handle *hdl; struct mem_fsal_obj_handle *my_hdl; *obj_hdl = NULL; if (hdl_desc->len != V4_FH_OPAQUE_SIZE) { LogCrit(COMPONENT_FSAL, "Invalid handle size %zu expected %lu", hdl_desc->len, ((unsigned long)V4_FH_OPAQUE_SIZE)); return fsalstat(ERR_FSAL_BADHANDLE, 0); } PTHREAD_RWLOCK_rdlock(&exp_hdl->fsal->fsm_lock); glist_for_each(glist, &exp_hdl->fsal->handles) { hdl = glist_entry(glist, struct fsal_obj_handle, handles); my_hdl = container_of(hdl, struct mem_fsal_obj_handle, obj_handle); if (memcmp(my_hdl->handle, hdl_desc->addr, V4_FH_OPAQUE_SIZE) == 0) { LogDebug(COMPONENT_FSAL, "Found hdl=%p name=%s", my_hdl, my_hdl->m_name); GSH_AUTO_TRACEPOINT( fsalmem, mem_create_handle, TRACE_DEBUG, "Create handle. Handle: {}, name: {}", hdl, TP_STR(my_hdl->m_name)); *obj_hdl = hdl; PTHREAD_RWLOCK_unlock(&exp_hdl->fsal->fsm_lock); if (attrs_out != NULL) { fsal_copy_attrs(attrs_out, &my_hdl->attrs, false); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } } LogDebug(COMPONENT_FSAL, "Could not find handle"); PTHREAD_RWLOCK_unlock(&exp_hdl->fsal->fsm_lock); return fsalstat(ERR_FSAL_STALE, ESTALE); } nfs-ganesha-6.5/src/FSAL/FSAL_MEM/mem_int.h000066400000000000000000000137621473756622300201460ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:shiftwidth=8:tabstop=8: * * Copyright 2017-2019 Red Hat, Inc. * Author: Daniel Gryniewicz dang@redhat.com * * * This program 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 3 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 * 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 * * ------------- */ /* MEM methods for handles */ #include "avltree.h" #include "gsh_list.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/fsalmem.h" #endif struct mem_fsal_obj_handle; enum async_types { MEM_INLINE, MEM_RANDOM_OR_INLINE, MEM_RANDOM, MEM_FIXED, }; /** * MEM internal export */ struct mem_fsal_export { /** Export this wraps */ struct fsal_export export; /** The path for this export */ char *export_path; /** Root object for this export */ struct mem_fsal_obj_handle *root_handle; /** Entry into list of exports */ struct glist_head export_entry; /** Lock protecting mfe_objs */ pthread_rwlock_t mfe_exp_lock; /** List of all the objects in this export */ struct glist_head mfe_objs; /** Async delay */ uint32_t async_delay; /** Async Stall delay */ uint32_t async_stall_delay; /** Type of async */ uint32_t async_type; }; fsal_status_t mem_lookup_path(struct fsal_export *exp_hdl, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); fsal_status_t mem_create_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *hdl_desc, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); struct mem_state_fd { /** state MUST be first to use default free_state */ struct state_t state; struct fsal_fd fsal_fd; }; /* * MEM internal object handle */ #define V4_FH_OPAQUE_SIZE 58 /* Size of state_obj digest */ struct mem_fsal_obj_handle { struct fsal_obj_handle obj_handle; struct fsal_attrlist attrs; uint64_t inode; char handle[V4_FH_OPAQUE_SIZE]; union { struct { struct mem_fsal_obj_handle *parent; struct avltree avl_name; struct avltree avl_index; uint32_t numkids; } mh_dir; struct { struct fsal_share share; struct fsal_fd fd; } mh_file; struct { object_file_type_t nodetype; fsal_dev_t dev; } mh_node; struct { char *link_contents; } mh_symlink; }; struct glist_head dirents; /**< List of dirents pointing to obj */ struct glist_head mfo_exp_entry; /**< Link into mfs_objs */ struct mem_fsal_export *mfo_exp; /**< Export owning object */ char *m_name; /**< Base name of obj, for debugging */ uint32_t datasize; bool is_export; uint32_t refcount; /**< We persist handles, so we need a refcount */ char data[0]; /* Allocated data */ }; /** * @brief Dirent for FSAL_MEM */ struct mem_dirent { struct mem_fsal_obj_handle *hdl; /**< Handle dirent points to */ struct mem_fsal_obj_handle *dir; /**< Dir containing dirent */ const char *d_name; /**< Name of dirent */ uint64_t d_index; /**< index in dir */ struct avltree_node avl_n; /**< Entry in dir's avl_name tree */ struct avltree_node avl_i; /**< Entry in dir's avl_index tree */ struct glist_head dlist; /**< Entry in hdl's dirents list */ }; static inline bool mem_unopenable_type(object_file_type_t type) { if ((type == SOCKET_FILE) || (type == CHARACTER_FILE) || (type == BLOCK_FILE)) { return true; } else { return false; } } void mem_handle_ops_init(struct fsal_obj_ops *ops); /* Internal MEM method linkage to export object */ fsal_status_t mem_create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops); fsal_status_t mem_update_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, struct fsal_export *original, struct fsal_module *updated_super); const char *str_async_type(uint32_t async_type); #define mem_free_handle(h) \ do { \ GSH_UNIQUE_AUTO_TRACEPOINT( \ fsalmem, mem_free, TRACE_DEBUG, \ "Freeing handle. hdl: {}, name: {}", h, \ TP_STR(h->m_name)); \ _mem_free_handle(h); \ } while (0) /** * @brief Free a MEM handle * * @note mfe_exp_lock MUST be held for write * @param[in] hdl Handle to free */ static inline void _mem_free_handle(struct mem_fsal_obj_handle *hdl) { glist_del(&hdl->mfo_exp_entry); hdl->mfo_exp = NULL; if (hdl->m_name != NULL) { gsh_free(hdl->m_name); hdl->m_name = NULL; } gsh_free(hdl); } void mem_clean_export(struct mem_fsal_obj_handle *root); void mem_clean_all_dirents(struct mem_fsal_obj_handle *parent); /** * @brief FSAL Module wrapper for MEM */ struct mem_fsal_module { /** Module we're wrapping */ struct fsal_module fsal; /** fsal_obj_handle ops vector */ struct fsal_obj_ops handle_ops; /** List of MEM exports. TODO Locking when we care */ struct glist_head mem_exports; /** Config - size of data in inode */ uint32_t inode_size; /** Config - Interval for UP call thread */ uint32_t up_interval; /** Next unused inode */ uint64_t next_inode; /** Config - number of async threads */ uint32_t async_threads; /** Config - whether so use whence-is-name */ bool whence_is_name; }; /* ASYNC testing */ extern struct fridgethr *mem_async_fridge; /* UP testing */ fsal_status_t mem_up_pkginit(void); fsal_status_t mem_up_pkgshutdown(void); extern struct mem_fsal_module MEM; nfs-ganesha-6.5/src/FSAL/FSAL_MEM/mem_main.c000066400000000000000000000167341473756622300202750ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:shiftwidth=8:tabstop=8: * * Copyright 2017-2019 Red Hat, Inc. * Author: Daniel Gryniewicz dang@redhat.com * * * This program 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 3 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 * 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 * * ------------- */ /* main.c * Module core functions */ #include "config.h" #include "fsal.h" #include /* used for 'dirname' */ #include #include #include #include #include "FSAL/fsal_init.h" #include "mem_int.h" #include "fsal_convert.h" #include "../fsal_private.h" /* MEM FSAL module private storage */ /* defined the set of attributes supported with POSIX */ #define MEM_SUPPORTED_ATTRIBUTES (ATTRS_POSIX) static const char memname[] = "MEM"; /* my module private storage */ struct mem_fsal_module MEM = { .fsal = { .fs_info = { .maxfilesize = INT64_MAX, .maxlink = 0, .maxnamelen = MAXNAMLEN, .maxpathlen = MAXPATHLEN, .no_trunc = true, .chown_restricted = true, .case_insensitive = false, .case_preserving = true, .link_support = true, .symlink_support = true, .lock_support = false, .lock_support_async_block = false, .named_attr = false, .unique_handles = true, .acl_support = 0, .cansettime = true, .homogenous = true, .supported_attrs = MEM_SUPPORTED_ATTRIBUTES, .maxread = FSAL_MAXIOSIZE, .maxwrite = FSAL_MAXIOSIZE, .umask = 0, .auth_exportpath_xdev = false, .link_supports_permission_checks = false, .readdir_plus = true, .expire_time_parent = -1, } } }; static struct config_item mem_items[] = { CONF_ITEM_UI32("Inode_Size", 0, 0x200000, 0, mem_fsal_module, inode_size), CONF_ITEM_UI32("Up_Test_Interval", 0, UINT32_MAX, 0, mem_fsal_module, up_interval), CONF_ITEM_UI32("Async_Threads", 0, 100, 0, mem_fsal_module, async_threads), CONF_ITEM_BOOL("Whence_is_name", false, mem_fsal_module, whence_is_name), CONFIG_EOL }; static struct config_block mem_block = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.mem", .blk_desc.name = "MEM", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = mem_items, .blk_desc.u.blk.commit = noop_conf_commit }; struct fridgethr *mem_async_fridge; /** * Initialize subsystem */ static fsal_status_t mem_async_pkginit(void) { /* Return code from system calls */ int code = 0; struct fridgethr_params frp; if (MEM.async_threads == 0) { /* Don't run async-threads */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } if (mem_async_fridge) { /* Already initialized */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } memset(&frp, 0, sizeof(struct fridgethr_params)); frp.thr_max = MEM.async_threads; frp.thr_min = 1; frp.flavor = fridgethr_flavor_worker; /* spawn MEM_ASYNC background thread */ code = fridgethr_init(&mem_async_fridge, "MEM_ASYNC_fridge", &frp); if (code != 0) { LogMajor( COMPONENT_FSAL, "Unable to initialize MEM_ASYNC fridge, error code %d.", code); } LogEvent(COMPONENT_FSAL, "Initialized FSAL_MEM async thread pool with %" PRIu32 " threads.", MEM.async_threads); return posix2fsal_status(code); } /** * Shutdown subsystem * * @return FSAL status */ static fsal_status_t mem_async_pkgshutdown(void) { if (!mem_async_fridge) { /* Async wasn't configured */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } int rc = fridgethr_sync_command(mem_async_fridge, fridgethr_comm_stop, 120); if (rc == ETIMEDOUT) { LogMajor(COMPONENT_FSAL, "Shutdown timed out, cancelling threads."); fridgethr_cancel(mem_async_fridge); } else if (rc != 0) { LogMajor(COMPONENT_FSAL, "Failed shutting down MEM_ASYNC threads: %d", rc); } fridgethr_destroy(mem_async_fridge); mem_async_fridge = NULL; return posix2fsal_status(rc); } /* private helper for export object */ /* Initialize mem fs info */ static fsal_status_t mem_init_config(struct fsal_module *fsal_hdl, config_file_t config_struct, struct config_error_type *err_type) { struct mem_fsal_module *mem_me = container_of(fsal_hdl, struct mem_fsal_module, fsal); fsal_status_t status = { 0, 0 }; LogDebug(COMPONENT_FSAL, "MEM module setup."); LogFullDebug(COMPONENT_FSAL, "Supported attributes default = 0x%" PRIx64, mem_me->fsal.fs_info.supported_attrs); /* if we have fsal specific params, do them here * fsal_hdl->name is used to find the block containing the * params. */ (void)load_config_from_parse(config_struct, &mem_block, mem_me, true, err_type); if (!config_error_is_harmless(err_type)) return fsalstat(ERR_FSAL_INVAL, 0); /* Initialize UP calls */ status = mem_up_pkginit(); if (FSAL_IS_ERROR(status)) { LogMajor(COMPONENT_FSAL, "Failed to initialize FSAL_MEM UP package %s", fsal_err_txt(status)); return status; } /* Initialize ASYNC call back threads */ status = mem_async_pkginit(); if (FSAL_IS_ERROR(status)) { LogMajor(COMPONENT_FSAL, "Failed to initialize FSAL_MEM ASYNC package %s", fsal_err_txt(status)); return status; } /* Set whence_is_name in fsinfo */ mem_me->fsal.fs_info.whence_is_name = mem_me->whence_is_name; display_fsinfo(&mem_me->fsal); LogFullDebug(COMPONENT_FSAL, "Supported attributes constant = 0x%" PRIx64, (uint64_t)MEM_SUPPORTED_ATTRIBUTES); LogDebug(COMPONENT_FSAL, "FSAL INIT: Supported attributes mask = 0x%" PRIx64, mem_me->fsal.fs_info.supported_attrs); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* Module initialization. * Called by dlopen() to register the module * keep a private pointer to me in myself */ /* linkage to the exports and handle ops initializers */ /** * @brief Initialize and register the FSAL * * This function initializes the FSAL module handle. It exists solely to * produce a properly constructed FSAL module handle. */ MODULE_INIT void init(void) { int retval; struct fsal_module *myself = &MEM.fsal; retval = register_fsal(myself, memname, FSAL_MAJOR_VERSION, FSAL_MINOR_VERSION, FSAL_ID_NO_PNFS); if (retval != 0) { LogCrit(COMPONENT_FSAL, "MEM module failed to register."); } myself->m_ops.create_export = mem_create_export; myself->m_ops.update_export = mem_update_export; myself->m_ops.init_config = mem_init_config; glist_init(&MEM.mem_exports); MEM.next_inode = 0xc0ffee; /* Initialize the fsal_obj_handle ops for FSAL MEM */ mem_handle_ops_init(&MEM.handle_ops); } MODULE_FINI void finish(void) { int retval; LogDebug(COMPONENT_FSAL, "MEM module finishing."); /* Shutdown UP calls */ mem_up_pkgshutdown(); /* Shutdown ASYNC threads */ mem_async_pkgshutdown(); retval = unregister_fsal(&MEM.fsal); if (retval != 0) { LogCrit(COMPONENT_FSAL, "Unable to unload MEM FSAL. Dying with extreme prejudice."); abort(); } } nfs-ganesha-6.5/src/FSAL/FSAL_MEM/mem_up.c000066400000000000000000000165321473756622300177710ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright © 2017, Red Hat, Inc. * Author: Daniel Gryniewicz * * This program 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 3 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 * 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 * * ------------- */ /** * @file FSAL_MEM/mem_up.c * @author Daniel Gryniewicz * * @brief Upcalls * * Implement upcalls for testing purposes */ #include "config.h" #include #include #include "fsal.h" #include "fsal_convert.h" #include "mem_int.h" static struct fridgethr *mem_up_fridge; /** * @brief Invalidate an object * * This function sends an invalidate for an object. The object itself is not * really deleted, since there's no way to get it back, but it should allow * testing of the invalidate UP call. * * @param[in] mfe MEM export owning handle * @param[in] hdl Handle to invalidate */ static void mem_invalidate(struct mem_fsal_export *mfe, struct mem_fsal_obj_handle *hdl) { const struct fsal_up_vector *up_ops = mfe->export.up_ops; fsal_status_t status; struct gsh_buffdesc fh_desc; LogFullDebug(COMPONENT_FSAL_UP, "invalidating %s", hdl->m_name); hdl->obj_handle.obj_ops->handle_to_key(&hdl->obj_handle, &fh_desc); status = up_ops->invalidate(up_ops, &fh_desc, FSAL_UP_INVALIDATE_CACHE); if (FSAL_IS_ERROR(status)) { LogMajor(COMPONENT_FSAL_UP, "error invalidating %s: %s", hdl->m_name, fsal_err_txt(status)); } } /** * @brief Invalidate and close an object * * This function sends an invalidate_close for an object. The object itself is * not really deleted, since there's no way to get it back, but it should allow * testing of the invalidate_close UP call. * * @param[in] mfe MEM export owning handle * @param[in] hdl Handle to invalidate */ static void mem_invalidate_close(struct mem_fsal_export *mfe, struct mem_fsal_obj_handle *hdl) { const struct fsal_up_vector *up_ops = mfe->export.up_ops; fsal_status_t status; struct gsh_buffdesc fh_desc; LogFullDebug(COMPONENT_FSAL_UP, "invalidate_closing %s", hdl->m_name); hdl->obj_handle.obj_ops->handle_to_key(&hdl->obj_handle, &fh_desc); status = up_ops->invalidate_close(up_ops, &fh_desc, FSAL_UP_INVALIDATE_CACHE); if (FSAL_IS_ERROR(status)) { LogMajor(COMPONENT_FSAL_UP, "error invalidate_closing %s: %s", hdl->m_name, fsal_err_txt(status)); } } /** * @brief Update an object * * This function sends an update for an object. In this case, we update some of * the times, just so something changed. * * @param[in] mfe MEM export owning handle * @param[in] hdl Handle to update */ static void mem_update(struct mem_fsal_export *mfe, struct mem_fsal_obj_handle *hdl) { const struct fsal_up_vector *up_ops = mfe->export.up_ops; fsal_status_t status; struct gsh_buffdesc fh_desc; struct fsal_attrlist attrs; LogFullDebug(COMPONENT_FSAL_UP, "updating %s", hdl->m_name); hdl->obj_handle.obj_ops->handle_to_key(&hdl->obj_handle, &fh_desc); fsal_prepare_attrs(&attrs, 0); /* Set CTIME */ now(&hdl->attrs.ctime); attrs.ctime = hdl->attrs.ctime; /* struct copy */ FSAL_SET_MASK(attrs.valid_mask, ATTR_CTIME); /* Set change */ hdl->attrs.change = timespec_to_nsecs(&hdl->attrs.ctime); attrs.change = hdl->attrs.change; FSAL_SET_MASK(attrs.valid_mask, ATTR_CHANGE); status = up_ops->update(up_ops, &fh_desc, &attrs, fsal_up_update_null); if (FSAL_IS_ERROR(status)) { LogMajor(COMPONENT_FSAL_UP, "error updating %s: %s", hdl->m_name, fsal_err_txt(status)); } } /** * @brief Select a random obj from an export * * @param[in] mfe Export to select from * @return Obj on success, NULL on failure */ struct mem_fsal_obj_handle *mem_rand_obj(struct mem_fsal_export *mfe) { struct mem_fsal_obj_handle *res = NULL; struct glist_head *glist, *glistn; uint32_t n = 2; if (glist_empty(&mfe->mfe_objs)) return NULL; PTHREAD_RWLOCK_rdlock(&mfe->mfe_exp_lock); glist_for_each_safe(glist, glistn, &mfe->mfe_objs) { if (res == NULL) { /* Grab first entry */ res = glist_entry(glist, struct mem_fsal_obj_handle, mfo_exp_entry); continue; } if (rand() % n == 0) { /* Replace with current */ res = glist_entry(glist, struct mem_fsal_obj_handle, mfo_exp_entry); break; } n++; } PTHREAD_RWLOCK_unlock(&mfe->mfe_exp_lock); return res; } /** * @brief Run an iteration of the UP call thread * * Each iteration exercises various UP calls. * * - Pick a random obj in each export, and invalidate it * * @param[in] ctx Thread fridge context * @return Return description */ static void mem_up_run(struct fridgethr_context *ctx) { struct glist_head *glist, *glistn; glist_for_each_safe(glist, glistn, &MEM.mem_exports) { struct mem_fsal_export *mfe; struct mem_fsal_obj_handle *hdl; mfe = glist_entry(glist, struct mem_fsal_export, export_entry); /* Update a handle */ hdl = mem_rand_obj(mfe); if (hdl) mem_update(mfe, hdl); /* Invalidate a handle */ hdl = mem_rand_obj(mfe); if (hdl) mem_invalidate(mfe, hdl); /* Invalidate and close a handle */ hdl = mem_rand_obj(mfe); if (hdl) mem_invalidate_close(mfe, hdl); } } /** * Initialize subsystem */ fsal_status_t mem_up_pkginit(void) { /* Return code from system calls */ int code = 0; struct fridgethr_params frp; if (MEM.up_interval == 0) { /* Don't run up-thread */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } if (mem_up_fridge) { /* Already initialized */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } memset(&frp, 0, sizeof(struct fridgethr_params)); frp.thr_max = 1; frp.thr_min = 1; frp.thread_delay = MEM.up_interval; frp.flavor = fridgethr_flavor_looper; /* spawn MEM_UP background thread */ code = fridgethr_init(&mem_up_fridge, "MEM_UP_fridge", &frp); if (code != 0) { LogMajor(COMPONENT_FSAL_UP, "Unable to initialize MEM_UP fridge, error code %d.", code); return posix2fsal_status(code); } code = fridgethr_submit(mem_up_fridge, mem_up_run, NULL); if (code != 0) { LogMajor(COMPONENT_FSAL_UP, "Unable to start MEM_UP thread, error code %d.", code); return fsalstat(posix2fsal_error(code), code); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * Shutdown subsystem * * @return FSAL status */ fsal_status_t mem_up_pkgshutdown(void) { if (!mem_up_fridge) { /* Interval wasn't configured */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } int rc = fridgethr_sync_command(mem_up_fridge, fridgethr_comm_stop, 120); if (rc == ETIMEDOUT) { LogMajor(COMPONENT_FSAL_UP, "Shutdown timed out, cancelling threads."); fridgethr_cancel(mem_up_fridge); } else if (rc != 0) { LogMajor(COMPONENT_FSAL_UP, "Failed shutting down MEM_UP thread: %d", rc); } fridgethr_destroy(mem_up_fridge); mem_up_fridge = NULL; return fsalstat(posix2fsal_error(rc), rc); } nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V3/000077500000000000000000000000001473756622300172075ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V3/CMakeLists.txt000066400000000000000000000033001473756622300217430ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- add_definitions( -D__USE_GNU ) ########### next target ############### SET(fsalproxy_v3_LIB_SRCS main.c nlm.c rpc.c utils.c ) add_library(fsalproxy_v3 MODULE ${fsalproxy_v3_LIB_SRCS} $) add_sanitizers(fsalproxy_v3) if (USE_LTTNG) add_dependencies(fsalproxy_v3 gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) target_link_libraries(fsalproxy_v3 ganesha_nfsd ${SYSTEM_LIBRARIES} ${LDFLAG_DISALLOW_UNDEF} ) set_target_properties(fsalproxy_v3 PROPERTIES VERSION 4.2.0 SOVERSION 4) install(TARGETS fsalproxy_v3 COMPONENT fsal DESTINATION ${FSAL_DESTINATION} ) ########### install files ############### nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V3/main.c000066400000000000000000002422521473756622300203060ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright 2020-2021 Google LLC * Author: Solomon Boulos * * This program 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 3 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 * 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 * * ------------- */ #include "config.h" #include "fsal.h" #include "fsal_types.h" #include "nfs_file_handle.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_config.h" #include "FSAL/fsal_init.h" #include "proxyv3_fsal_methods.h" /** * @struct PROXY_V3 * @brief Struct telling Ganesha what things we can handle or not. * * Some fields are overwritten later via an FSINFO call. */ struct proxyv3_fsal_module PROXY_V3 = { .module = { .fs_info = { .maxfilesize = INT64_MAX, .maxlink = _POSIX_LINK_MAX, .maxnamelen = 1024, .maxpathlen = 1024, .no_trunc = true, .chown_restricted = true, .cansettime = true, .case_insensitive = false, .case_preserving = true, .link_support = true, .symlink_support = true, .lock_support = true, .lock_support_async_block = false, .named_attr = false, .unique_handles = true, .acl_support = FSAL_ACLSUPPORT_ALLOW, .homogenous = true, .supported_attrs = ((const attrmask_t)ATTRS_POSIX), .link_supports_permission_checks = true, .readdir_plus = true, .expire_time_parent = -1, } } }; /** * @struct proxyv3_params * @brief Global/server-wide parameters for NFSv3 proxying. */ static struct config_item proxyv3_params[] = { /* Maximum read/write size in bytes */ CONF_ITEM_UI64("maxread", 1024, FSAL_MAXIOSIZE, 1048576, proxyv3_fsal_module, module.fs_info.maxread), CONF_ITEM_UI64("maxwrite", 1024, FSAL_MAXIOSIZE, 1048576, proxyv3_fsal_module, module.fs_info.maxwrite), /* How many sockets for our rpc layer */ CONF_ITEM_UI32("num_sockets", 1, 1000, 32, proxyv3_fsal_module, num_sockets), CONF_ITEM_BOOL("allow_lookup_optimization", true, proxyv3_fsal_module, allow_lookup_optimization), CONFIG_EOL }; /** * @struct proxyv3_export_params * @brief Per export config parameters (just srv_addr currently). */ static struct config_item proxyv3_export_params[] = { CONF_ITEM_NOOP("name"), CONF_MAND_IP_ADDR("Srv_Addr", "127.0.0.1", proxyv3_client_params, srv_addr), CONFIG_EOL }; /** * @struct proxyv3_param * @brief Config block for PROXY v3 parameters. */ struct config_block proxyv3_param = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.proxyv3", .blk_desc.name = "PROXY_V3", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = proxyv3_params, .blk_desc.u.blk.commit = noop_conf_commit }; /** * @struct proxyv3_export_param * @brief Config block for PROXY v3 per-export parameters. */ struct config_block proxyv3_export_param = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.proxyv3-export%d", .blk_desc.name = "FSAL", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = proxyv3_export_params, .blk_desc.u.blk.commit = noop_conf_commit }; /** * @brief Grab the sockaddr from our params via op_ctx->fsal_export. */ const struct sockaddr *proxyv3_sockaddr(void) { struct proxyv3_export *export = container_of( op_ctx->fsal_export, struct proxyv3_export, export); return export->params.sockaddr; } /** * @brief Grab the socklen from our params via op_ctx. */ const socklen_t proxyv3_socklen(void) { struct proxyv3_export *export = container_of( op_ctx->fsal_export, struct proxyv3_export, export); return export->params.socklen; } /** * @brief Grab the sockname from our params via op_ctx. */ static const char *proxyv3_sockname(void) { struct proxyv3_export *export = container_of( op_ctx->fsal_export, struct proxyv3_export, export); return export->params.sockname; } /** * @brief Grab the mountd port from our params via op_ctx. */ static const uint proxyv3_mountd_port(void) { struct proxyv3_export *export = container_of( op_ctx->fsal_export, struct proxyv3_export, export); return export->params.mountd_port; } /** * @brief Grab the nfsd port from our params via op_ctx. */ static const uint proxyv3_nfsd_port(void) { struct proxyv3_export *export = container_of( op_ctx->fsal_export, struct proxyv3_export, export); return export->params.nfsd_port; } /** * @brief Grab the NLM port from our params via op_ctx. */ const uint proxyv3_nlm_port(void) { struct proxyv3_export *export = container_of( op_ctx->fsal_export, struct proxyv3_export, export); return export->params.nlm_port; } /** * @brief Grab the user credentials from op_ctx. */ const struct user_cred *proxyv3_creds(void) { /* We want the resolved user credentials, so we reflect the client * after resolving managed groups and credentials squashing */ return &op_ctx->creds; } /** * @brief Grab the preferred bytes per READDIRPLUS from our params via op_ctx. */ const uint proxyv3_readdir_preferred(void) { struct proxyv3_export *export = container_of( op_ctx->fsal_export, struct proxyv3_export, export); fsal_staticfsinfo_t *fsinfo = &PROXY_V3.module.fs_info; uint preferred = export->params.readdir_preferred; uint maxread = fsinfo->maxread; /* If it's 0, just return maxread. */ if (preferred == 0) { return maxread; } /* If it's too big, clamp it. */ if (preferred > maxread) { return maxread; } return preferred; } /** * @brief Load configuration from the config file. * * @param fsal_handle A handle to the inner FSAL module. * @param config_file The config file to ask to parse. * @param error_type An output parameter for error reporting. * * @return - ERR_FSAL_NO_ERROR on success, ERR_FSAL_INVAL otherwise. */ static fsal_status_t proxyv3_init_config(struct fsal_module *fsal_handle, config_file_t config_file, struct config_error_type *error_type) { struct proxyv3_fsal_module *proxy_v3 = container_of(fsal_handle, struct proxyv3_fsal_module, module); LogDebug(COMPONENT_FSAL, "Loading the Proxy V3 Config"); (void)load_config_from_parse(config_file, &proxyv3_param, proxy_v3, true, error_type); if (!config_error_is_harmless(error_type)) { return fsalstat(ERR_FSAL_INVAL, 0); } display_fsinfo(&(proxy_v3->module)); /* Now that we have our config, try to setup our RPC layer. */ if (!proxyv3_rpc_init(proxy_v3->num_sockets)) { LogCrit(COMPONENT_FSAL, "ProxyV3 RPC failed to initialize"); return fsalstat(ERR_FSAL_INVAL, 0); } if (!proxyv3_nlm_init()) { LogCrit(COMPONENT_FSAL, "ProxyV3 NLM failed to initialize"); return fsalstat(ERR_FSAL_INVAL, 0); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Given a filehandle and attributes make a new object handle. * * @param export_handle A handle to our FSAL export. * @param fh3 The input NFSv3 file handle. * @param attrs The input fattr3 file attributes. * @param parent An optional pointer to this object's parent. * @param fsal_attrs_out An optional output for the FSAL version of attributes. * * @returns - A new proxyv3_obj_handle on success, NULL otherwise. */ static struct proxyv3_obj_handle * proxyv3_alloc_handle(struct fsal_export *export_handle, const nfs_fh3 *fh3, const fattr3 *attrs, const struct proxyv3_obj_handle *parent, struct fsal_attrlist *fsal_attrs_out) { /* Fill the attributes first to avoid an alloc on failure. */ struct fsal_attrlist local_attributes; struct fsal_attrlist *attrs_out; LogDebug(COMPONENT_FSAL, "Making handle from fh3 %p with parent %p", fh3, parent); LogFullDebugOpaque(COMPONENT_FSAL, " fh3 handle is %s", LEN_FH_STR, fh3->data.data_val, fh3->data.data_len); /* If we aren't given a destination, make up our own. */ if (fsal_attrs_out != NULL) { attrs_out = fsal_attrs_out; } else { /* Say we only want NFSv3 attributes. */ memset(&local_attributes, 0, sizeof(struct fsal_attrlist)); attrs_out = &local_attributes; FSAL_SET_MASK(attrs_out->request_mask, ATTRS_NFS3); } if (!fattr3_to_fsalattr(attrs, attrs_out)) { /* @note The callee already warned. No need to repeat. */ return NULL; } /* * Alright, ready to go. Instead of being fancy like the NFSv4 proxy, * we'll allocate the nested fh3 with an additional calloc call. */ struct proxyv3_obj_handle *result = gsh_calloc(1, sizeof(struct proxyv3_obj_handle)); /* Copy the fh3 struct. */ size_t len = fh3->data.data_len; result->fh3.data.data_len = len; result->fh3.data.data_val = gsh_calloc(1, len); memcpy(result->fh3.data.data_val, fh3->data.data_val, len); /* Copy the NFSv3 attrs. */ memcpy(&result->attrs, attrs, sizeof(fattr3)); fsal_obj_handle_init(&result->obj, export_handle, attrs_out->type, true); result->obj.fsid = attrs_out->fsid; result->obj.fileid = attrs_out->fileid; result->obj.obj_ops = &PROXY_V3.handle_ops; result->parent = parent; return result; } /** * @brief Clean up an object handle, freeing its memory. * * @param obj_hdl The object handle. */ static void proxyv3_handle_release(struct fsal_obj_handle *obj_hdl) { struct proxyv3_obj_handle *handle = container_of(obj_hdl, struct proxyv3_obj_handle, obj); LogDebug(COMPONENT_FSAL, "Cleaning up handle %p", handle); /* Free the underlying filehandle bytes. */ gsh_free(handle->fh3.data.data_val); /* Finish the outer object. */ fsal_obj_handle_fini(obj_hdl, true); /* Free our allocated handle. */ gsh_free(handle); } /** * @brief Given a path and parent object, do a *single* LOOKUP3. * * @param export_handle A pointer to our FSAL export. * @param path The file path (must not be NULL). * @param parent The parent directory of path. * @param handle The output argument for the new object handle. * @param attrs_out The output argument for the attributes. * * @returns - ERR_FSAL_NO_ERROR on success, an error otherwise. */ static fsal_status_t proxyv3_lookup_internal(struct fsal_export *export_handle, const char *path, struct fsal_obj_handle *parent, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { const struct fsal_module *fsal_handle = export_handle->fsal; const struct proxyv3_fsal_module *proxy_v3 = container_of(fsal_handle, struct proxyv3_fsal_module, module); LogDebug(COMPONENT_FSAL, "Doing a lookup of '%s'", path); if (parent == NULL) { LogCrit(COMPONENT_FSAL, "Error, expected a parent handle."); return fsalstat(ERR_FSAL_INVAL, 0); } if (parent->type != DIRECTORY) { LogCrit(COMPONENT_FSAL, "Error, expected parent to be a directory. Got %u", parent->type); return fsalstat(ERR_FSAL_NOTDIR, 0); } if (handle == NULL) { LogCrit(COMPONENT_FSAL, "Error, expected an output handle."); return fsalstat(ERR_FSAL_INVAL, 0); } /* Mark as NULL in case we fail along the way. */ *handle = NULL; if (path == NULL) { LogCrit(COMPONENT_FSAL, "Error, received garbage path"); return fsalstat(ERR_FSAL_INVAL, 0); } if (*path == '\0') { /* * @todo What does an empty path mean? We shouldn't have gotten * here... */ LogCrit(COMPONENT_FSAL, "Error. Path is NUL. Should have exited earlier."); return fsalstat(ERR_FSAL_INVAL, 0); } if (strchr(path, '/') != NULL) { LogCrit(COMPONENT_FSAL, "Path (%s) contains embedded forward slash.", path); return fsalstat(ERR_FSAL_INVAL, 0); } struct proxyv3_obj_handle *parent_obj = container_of(parent, struct proxyv3_obj_handle, obj); /* * Small optimization to avoid a network round-trip: if we already know * the answer, hand it back. */ if (proxy_v3->allow_lookup_optimization && (strcmp(path, ".") == 0 || /* * We may not have the parent pointer information (could be from a * create_handle from key thing, so let the backend respond) */ (strcmp(path, "..") == 0 && parent_obj->parent != NULL))) { /* We know the answer, just give it to them. */ LogDebug(COMPONENT_FSAL, "Got a lookup for '%s' returning the directory handle", path); struct proxyv3_obj_handle *which_dir; if (strcmp(path, ".") == 0) { which_dir = parent_obj; } else { /* * Sigh, cast away the const here. FSAL shouldn't be * asking to edit parent handles... */ const struct proxyv3_obj_handle *const_dir = parent_obj->parent; which_dir = (struct proxyv3_obj_handle *)const_dir; } /* Make a copy for the result. */ struct proxyv3_obj_handle *result_handle = proxyv3_alloc_handle( export_handle, &which_dir->fh3, &which_dir->attrs, which_dir->parent, attrs_out); if (result_handle == NULL) { return fsalstat(ERR_FSAL_FAULT, 0); } *handle = &result_handle->obj; return fsalstat(ERR_FSAL_NO_ERROR, 0); } LOOKUP3args args; LOOKUP3res result; LOOKUP3resok *resok = &result.LOOKUP3res_u.resok; /* The directory is the parent's fh3 handle. */ args.what.dir = parent_obj->fh3; /* @todo Is it actually safe to const cast this away? */ args.what.name = (char *)path; memset(&result, 0, sizeof(result)); if (!proxyv3_nfs_call(proxyv3_sockaddr(), proxyv3_socklen(), proxyv3_nfsd_port(), proxyv3_creds(), NFSPROC3_LOOKUP, (xdrproc_t)xdr_LOOKUP3args, &args, (xdrproc_t)xdr_LOOKUP3res, &result)) { LogWarn(COMPONENT_FSAL, "LOOKUP3 failed"); return fsalstat(ERR_FSAL_DELAY, 0); } if (result.status != NFS3_OK) { LogDebug(COMPONENT_FSAL, "LOOKUP3 failed, got %u", result.status); return nfsstat3_to_fsalstat(result.status); } /* We really need the attributes. Fail if we didn't get them. */ if (!resok->obj_attributes.attributes_follow) { /* Clean up, even though we're exiting early. */ xdr_free((xdrproc_t)xdr_LOOKUP3res, &result); LogDebug(COMPONENT_FSAL, "LOOKUP3 didn't return attributes"); return fsalstat(ERR_FSAL_INVAL, 0); } const nfs_fh3 *obj_fh = &resok->object; const fattr3 *obj_attrs = &resok->obj_attributes.post_op_attr_u.attributes; struct proxyv3_obj_handle *result_handle = proxyv3_alloc_handle( export_handle, obj_fh, obj_attrs, parent_obj, attrs_out); /* At this point, we've copied out the result. Clean up. */ xdr_free((xdrproc_t)xdr_LOOKUP3res, &result); if (result_handle == NULL) { return fsalstat(ERR_FSAL_FAULT, 0); } *handle = &result_handle->obj; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Do a GETATTR3 on an NFS fh3. * * @param fh3 The input fh3. * @param attrs_out The resulting attributes. * * @returns - ERR_FSAL_NO_ERROR on success, an error otherwise. */ static fsal_status_t proxyv3_getattr_from_fh3(struct nfs_fh3 *fh3, struct fsal_attrlist *attrs_out) { GETATTR3args args; GETATTR3res result; LogDebug(COMPONENT_FSAL, "Doing a getattr on fh3 (%p) with len %" PRIu32, fh3->data.data_val, fh3->data.data_len); LogFullDebugOpaque(COMPONENT_FSAL, " fh3 handle is %s", LEN_FH_STR, fh3->data.data_val, fh3->data.data_len); args.object.data.data_val = fh3->data.data_val; args.object.data.data_len = fh3->data.data_len; memset(&result, 0, sizeof(result)); /* If the call fails for any reason, exit. */ if (!proxyv3_nfs_call(proxyv3_sockaddr(), proxyv3_socklen(), proxyv3_nfsd_port(), proxyv3_creds(), NFSPROC3_GETATTR, (xdrproc_t)xdr_GETATTR3args, &args, (xdrproc_t)xdr_GETATTR3res, &result)) { LogWarn(COMPONENT_FSAL, "proxyv3_nfs_call failed (%u)", result.status); return fsalstat(ERR_FSAL_DELAY, 0); } /* If we didn't get back NFS3_OK, return the appropriate error. */ if (result.status != NFS3_OK) { LogDebug(COMPONENT_FSAL, "GETATTR failed. %u", result.status); /* If the request wants to know about errors, let them know. */ if (FSAL_TEST_MASK(attrs_out->request_mask, ATTR_RDATTR_ERR)) { FSAL_SET_MASK(attrs_out->valid_mask, ATTR_RDATTR_ERR); } return nfsstat3_to_fsalstat(result.status); } if (!fattr3_to_fsalattr(&result.GETATTR3res_u.resok.obj_attributes, attrs_out)) { /* The callee already complained, just exit. */ return fsalstat(ERR_FSAL_FAULT, 0); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Do a GETATTR3 for an object (see proxyv3_getattr_from_fh3). */ static fsal_status_t proxyv3_getattrs(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs_out) { struct proxyv3_obj_handle *handle = container_of(obj_hdl, struct proxyv3_obj_handle, obj); LogDebug(COMPONENT_FSAL, "Responding to GETATTR request for handle %p", handle); return proxyv3_getattr_from_fh3(&handle->fh3, attrs_out); } /** * @brief Do a SETATTR3 for an object. * * @param obj_hdl The object handle. * @param bypass Whether to bypass share reservations (ignored, we're v3). * @param state Object lock/share state (ignored, MDCACHE handles conflicts). * @param attrib_set The attributes to set on the object. * * @returns - ERR_FSAL_NO_ERROR on success, an error code otherwise. */ static fsal_status_t proxyv3_setattr2(struct fsal_obj_handle *obj_hdl, bool bypass /* ignored, since we'll happily "bypass" */, struct state_t *state, struct fsal_attrlist *attrib_set) { struct proxyv3_obj_handle *handle = container_of(obj_hdl, struct proxyv3_obj_handle, obj); SETATTR3args args; SETATTR3res result; memset(&result, 0, sizeof(result)); LogDebug(COMPONENT_FSAL, "Responding to SETATTR request for handle %p", handle); if (state != NULL && (state->state_type != STATE_TYPE_SHARE && state->state_type != STATE_TYPE_LOCK)) { LogDebug( COMPONENT_FSAL, "Asked for a stateful SETATTR2 of type %d. Probably a mistake", state->state_type); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } nfs_fh3 *fh3 = &handle->fh3; args.object.data.data_val = fh3->data.data_val; args.object.data.data_len = fh3->data.data_len; /* NOTE(boulos): Ganesha NFSD handles this above us in nfs3_setattr. */ args.guard.check = false; const bool allow_rawdev = false; if (!fsalattr_to_sattr3(attrib_set, allow_rawdev, &args.new_attributes)) { LogWarn(COMPONENT_FSAL, "SETATTR3() with invalid attributes"); return fsalstat(ERR_FSAL_INVAL, 0); } /* If the call fails for any reason, exit. */ if (!proxyv3_nfs_call(proxyv3_sockaddr(), proxyv3_socklen(), proxyv3_nfsd_port(), proxyv3_creds(), NFSPROC3_SETATTR, (xdrproc_t)xdr_SETATTR3args, &args, (xdrproc_t)xdr_SETATTR3res, &result)) { LogWarn(COMPONENT_FSAL, "proxyv3_nfs_call failed (%u)", result.status); return fsalstat(ERR_FSAL_DELAY, 0); } /* If we didn't get back NFS3_OK, return the appropriate error. */ if (result.status != NFS3_OK) { LogDebug(COMPONENT_FSAL, "SETATTR failed. %u", result.status); return nfsstat3_to_fsalstat(result.status); } /* Must have worked :). */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Do a specialized lookup for the root FH3 of an export via GETATTR3. */ fsal_status_t proxyv3_lookup_root(struct fsal_export *export_handle, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { struct proxyv3_export *export = container_of(export_handle, struct proxyv3_export, export); nfs_fh3 fh3; struct fsal_attrlist tmp_attrs; fh3.data.data_val = export->root_handle; fh3.data.data_len = export->root_handle_len; memset(&tmp_attrs, 0, sizeof(tmp_attrs)); if (attrs_out != NULL) { FSAL_SET_MASK(tmp_attrs.request_mask, attrs_out->request_mask); } fsal_status_t rc = proxyv3_getattr_from_fh3(&fh3, &tmp_attrs); if (FSAL_IS_ERROR(rc)) { return rc; } /* Bundle up the result into a new object handle. */ struct proxyv3_obj_handle *result_handle = proxyv3_alloc_handle(export_handle, &fh3, &tmp_attrs, NULL /* no parent */, attrs_out); /* If we couldn't allocate the handle, fail. */ if (result_handle == NULL) { return fsalstat(ERR_FSAL_FAULT, 0); } /* Shove this into our export for future use. */ export->root_handle_obj = result_handle; *handle = &(result_handle->obj); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Given an export and a path, try to perform a lookup. * * @param export_handle Our per-export handle * @param path The path on the export (including the root of the mount). * @param handle The output object handle for the result. * @param attrs_out The output attributes. * * @return - ERR_FSAL_NO_ERROR on success. An error code, otherwise. */ fsal_status_t proxyv3_lookup_path(struct fsal_export *export_handle, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { struct proxyv3_export *export = container_of(export_handle, struct proxyv3_export, export); LogDebug(COMPONENT_FSAL, "Looking up path '%s'", path); /* Check that the first part of the path matches our root. */ const char *root_path = CTX_FULLPATH(op_ctx); const size_t root_len = strlen(root_path); const char *p = path; /* Check that the path matches our root prefix. */ if (strncmp(path, root_path, root_len) != 0) { LogDebug(COMPONENT_FSAL, "path ('%s') doesn't match our root ('%s')", path, root_path); return fsalstat(ERR_FSAL_FAULT, 0); } /* The prefix matches our root path, move forward. */ p += root_len; if (*p == '\0') { /* Nothing left. Must have been just the root. */ LogDebug(COMPONENT_FSAL, "Root Lookup. Doing GETATTR instead"); return proxyv3_lookup_root(export_handle, handle, attrs_out); } /* * Okay, we've got a potential path with slashes. * * @todo Split up path, calling lookup internal on each part. */ return proxyv3_lookup_internal(export_handle, p, &export->root_handle_obj->obj, handle, attrs_out); } /** * @brief Perform a lookup by handle. See proxyv3_lookup_internal. */ static fsal_status_t proxyv3_lookup_handle(struct fsal_obj_handle *parent, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { LogDebug(COMPONENT_FSAL, "lookup_handle for path '%s'", path); return proxyv3_lookup_internal(op_ctx->fsal_export, path, parent, handle, attrs_out); } /** * @brief Issue a CREATE3/MKDIR3/SYMLINK style operation. * * This function handles all the "make sure we got back the attributes" * that is sadly optional in the NFS v3 spec. * * @param parent_obj The parent object. * @param nfsProc The NFS RPC to run (e.g., CREATE3). * @param procName The NFS RPC as a const char* (for error messages). * @param encFunc An XDR encoding function (e.g., xdr_CREATE3args) * @param encArgs The argument data (passed to encFunc). * @param decFunc The XDR decoding function (e.g., xdr_CREATE3res) * @param decArgs The output argument (passed to decFunc). * @param status The result nfsstat3 pointer 9inside of decArgs). * @param op_fh3 The result post_op_fh3 pointer (inside of decArgs) * @param op_attr The result post_op_attr (inside of decArgs). * @param new_obj The output argument for the new object. * @param attrs_out The output argument for the output attributes. * @param parent_wcc_data The result parent wcc data * @param parent_pre_attrs_out - The output fsal pre attributes * @param parent_post_attrs_out - The output fsal post attributes * * @returns - ERR_FSAL_NO_ERROR on success, an error code otherwise. */ static fsal_status_t proxyv3_issue_createlike( struct proxyv3_obj_handle *parent_obj, const rpcproc_t nfsProc, const char *procName, xdrproc_t encFunc, void *encArgs, xdrproc_t decFunc, void *decArgs, nfsstat3 *status, struct post_op_fh3 *op_fh3, struct post_op_attr *op_attr, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct wcc_data *parent_wcc_data, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { LogDebug(COMPONENT_FSAL, "Issuing a %s", procName); if (!proxyv3_nfs_call(proxyv3_sockaddr(), proxyv3_socklen(), proxyv3_nfsd_port(), proxyv3_creds(), nfsProc, encFunc, encArgs, decFunc, decArgs)) { LogWarn(COMPONENT_FSAL, "%s failed", procName); return fsalstat(ERR_FSAL_DELAY, 0); } /* Okay, let's see what we got. */ if (*status != NFS3_OK) { LogDebug(COMPONENT_FSAL, "%s failed, got %u", procName, *status); return nfsstat3_to_fsalstat(*status); } /* We need both the handle and attributes to fill in the results. */ if (!op_attr->attributes_follow || !op_fh3->handle_follows) { /* Since status was NFS3_OK, we may have allocated something. */ xdr_free(decFunc, decArgs); LogDebug(COMPONENT_FSAL, "%s didn't return obj attributes (%s) or handle (%s)", procName, op_attr->attributes_follow ? "T" : "F", op_fh3->handle_follows ? "T" : "F"); return fsalstat(ERR_FSAL_INVAL, 0); } const struct nfs_fh3 *obj_fh = &op_fh3->post_op_fh3_u.handle; const fattr3 *obj_attrs = &op_attr->post_op_attr_u.attributes; struct proxyv3_obj_handle *result_handle = proxyv3_alloc_handle( op_ctx->fsal_export, obj_fh, obj_attrs, parent_obj, attrs_out); pre_attrs_to_fsalattr(&parent_wcc_data->before, parent_pre_attrs_out); post_attrs_to_fsalattr(&parent_wcc_data->after, parent_post_attrs_out); /* At this point, we've copied out the result. Clean up. */ xdr_free(decFunc, decArgs); if (result_handle == NULL) { return fsalstat(ERR_FSAL_FAULT, 0); } *new_obj = &result_handle->obj; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Perform an "open" by handle. * * This comes from NFSv4 clients and we need to correctly allow it, and * replace the "opens" with either get/setattrs. * * @returns - ERR_FSAL_NOTSUPP if we're confused. * - Otherwise the result of proxyv3_getattrs. */ static fsal_status_t proxyv3_open_by_handle( struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, struct fsal_attrlist *attrib_set, fsal_verifier_t verifier, struct fsal_obj_handle **out_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check) { LogDebug(COMPONENT_FSAL, "open2 of obj_hdl %p flags %" PRIx16 " and mode %u", obj_hdl, openflags, createmode); if (createmode != FSAL_NO_CREATE) { /* They're not trying to open for read/write. */ LogCrit(COMPONENT_FSAL, "Don't know how to do create via handle"); return fsalstat(ERR_FSAL_NOTSUPP, 0); } /* Otherwise, this is actually a getattr. */ *out_obj = obj_hdl; return proxyv3_getattrs(obj_hdl, attrs_out); } /** * @brief Perform an "open" (really CREATE3). See proxyv3_issue_createlike. */ static fsal_status_t proxyv3_open2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attrib_set, fsal_verifier_t verifier, struct fsal_obj_handle **out_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { /* If name is NULL => open by handle. */ if (name == NULL) { return proxyv3_open_by_handle(obj_hdl, state, openflags, createmode, attrib_set, verifier, out_obj, attrs_out, caller_perm_check); } struct proxyv3_obj_handle *parent_obj = container_of(obj_hdl, struct proxyv3_obj_handle, obj); LogDebug(COMPONENT_FSAL, "open2 of obj_hdl %p, name %s with flags %" PRIx16 " and mode %u", obj_hdl, name, openflags, createmode); /* @todo Do we need to check the openflags, too? */ if (state != NULL && (state->state_type != STATE_TYPE_SHARE && state->state_type != STATE_TYPE_LOCK)) { LogCrit(COMPONENT_FSAL, "Asked for a stateful open2() of type %d. Probably a mistake", state->state_type); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } CREATE3args args; CREATE3res result; CREATE3resok *resok = &result.CREATE3res_u.resok; memset(&result, 0, sizeof(result)); args.where.dir.data.data_val = parent_obj->fh3.data.data_val; args.where.dir.data.data_len = parent_obj->fh3.data.data_len; /* We can safely const-cast away, this is an input. */ args.where.name = (char *)name; switch (createmode) { case FSAL_NO_CREATE: /* No create should have been handled via open_by_handle. */ case FSAL_EXCLUSIVE_41: case FSAL_EXCLUSIVE_9P: LogCrit(COMPONENT_FSAL, "Invalid createmode (%u) for NFSv3. Must be one of UNCHECKED, GUARDED, or EXCLUSIVE", createmode); return fsalstat(ERR_FSAL_SERVERFAULT, 0); case FSAL_UNCHECKED: args.how.mode = UNCHECKED; break; case FSAL_GUARDED: args.how.mode = GUARDED; break; case FSAL_EXCLUSIVE: args.how.mode = EXCLUSIVE; break; } if (createmode == FSAL_EXCLUSIVE) { /* Set the verifier */ memcpy(&args.how.createhow3_u.verf, verifier, sizeof(fsal_verifier_t)); } else { sattr3 *attrs; /* Otherwise, set the attributes for the file. */ if (attrib_set == NULL) { LogCrit(COMPONENT_FSAL, "Non-exclusive CREATE() without attributes."); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } attrs = &args.how.createhow3_u.obj_attributes; const bool allow_rawdev = false; if (!fsalattr_to_sattr3(attrib_set, allow_rawdev, attrs)) { LogCrit(COMPONENT_FSAL, "CREATE() with invalid attributes"); return fsalstat(ERR_FSAL_INVAL, 0); } } /* Issue the CREATE3 call. */ return proxyv3_issue_createlike( parent_obj, NFSPROC3_CREATE, "CREATE3", (xdrproc_t)xdr_CREATE3args, &args, (xdrproc_t)xdr_CREATE3res, &result, &result.status, &resok->obj, &resok->obj_attributes, out_obj, attrs_out, &resok->dir_wcc, parent_pre_attrs_out, parent_post_attrs_out); } static fsal_openflags_t proxyv3_status2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { /* first version of support_ex, no state, no saved openflags */ fsal_openflags_t null_flags = 0; /* closed and deny_none*/ return null_flags; } /** * @brief Re-open a file that may be already opened */ static fsal_status_t proxyv3_reopen2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags) { LogDebug(COMPONENT_FSAL, "reopen2 of obj_hdl %p flags %" PRIx16, obj_hdl, openflags); /* Nothing to do here as Ganesha already did state and access checks. * Ganesha will call update_stateid so no need to worry about the * state_seqid. */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Make a new symlink from dir/name => link_path. */ static fsal_status_t proxyv3_symlink(struct fsal_obj_handle *dir_hdl, const char *name, const char *link_path, struct fsal_attrlist *attrs_in, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { LogDebug(COMPONENT_FSAL, "symlink of parent %p, name %s to => %s", dir_hdl, name, link_path); SYMLINK3args args; SYMLINK3res result; SYMLINK3resok *resok = &result.SYMLINK3res_u.resok; memset(&result, 0, sizeof(result)); struct proxyv3_obj_handle *parent_obj = container_of(dir_hdl, struct proxyv3_obj_handle, obj); args.where.dir.data.data_val = parent_obj->fh3.data.data_val; args.where.dir.data.data_len = parent_obj->fh3.data.data_len; /* We can safely const-cast away, this is an input. */ args.where.name = (char *)name; if (attrs_in == NULL) { LogWarn(COMPONENT_FSAL, "symlink called without attributes. Unexpected"); return fsalstat(ERR_FSAL_FAULT, 0); } const bool allow_rawdev = false; if (!fsalattr_to_sattr3(attrs_in, allow_rawdev, &args.symlink.symlink_attributes)) { LogWarn(COMPONENT_FSAL, "SYMLINK3 with invalid attributes"); return fsalstat(ERR_FSAL_INVAL, 0); } /* Again, we can safely const-cast away, because this is an input. */ args.symlink.symlink_data = (char *)link_path; /* Issue the SYMLINK3 call. */ return proxyv3_issue_createlike( parent_obj, NFSPROC3_SYMLINK, "SYMLINK3", (xdrproc_t)xdr_SYMLINK3args, &args, (xdrproc_t)xdr_SYMLINK3res, &result, &result.status, &resok->obj, &resok->obj_attributes, new_obj, attrs_out, &resok->dir_wcc, parent_pre_attrs_out, parent_post_attrs_out); } /** * @brief Make a hardlink from obj => dir/name. */ static fsal_status_t proxyv3_hardlink(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *dir_hdl, const char *name, struct fsal_attrlist *destdir_pre_attrs_out, struct fsal_attrlist *destdir_post_attrs_out) { LogDebug(COMPONENT_FSAL, "(hard)link of object %p to %p/%s", obj_hdl, dir_hdl, name); LINK3args args; LINK3res result; memset(&result, 0, sizeof(result)); struct proxyv3_obj_handle *obj = container_of(obj_hdl, struct proxyv3_obj_handle, obj); struct proxyv3_obj_handle *dir = container_of(dir_hdl, struct proxyv3_obj_handle, obj); args.file.data.data_val = obj->fh3.data.data_val; args.file.data.data_len = obj->fh3.data.data_len; args.link.dir.data.data_val = dir->fh3.data.data_val; args.link.dir.data.data_len = dir->fh3.data.data_len; /* We can safely const-cast away, this is an input. */ args.link.name = (char *)name; /* If the call fails for any reason, exit. */ if (!proxyv3_nfs_call(proxyv3_sockaddr(), proxyv3_socklen(), proxyv3_nfsd_port(), proxyv3_creds(), NFSPROC3_LINK, (xdrproc_t)xdr_LINK3args, &args, (xdrproc_t)xdr_LINK3res, &result)) { LogWarn(COMPONENT_FSAL, "LINK3 failed"); return fsalstat(ERR_FSAL_DELAY, 0); } /* If we didn't get back NFS3_OK, leave a debugging note.*/ if (result.status != NFS3_OK) { LogDebug(COMPONENT_FSAL, "NFSPROC3_LINK failed. %u", result.status); } pre_attrs_to_fsalattr(&result.LINK3res_u.resok.linkdir_wcc.before, destdir_pre_attrs_out); post_attrs_to_fsalattr(&result.LINK3res_u.resok.linkdir_wcc.after, destdir_post_attrs_out); return nfsstat3_to_fsalstat(result.status); } /** * @brief Handle readlink requests. */ static fsal_status_t proxyv3_readlink(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *link_content, bool refresh) { LogDebug(COMPONENT_FSAL, "readlink of %p of type %d", obj_hdl, obj_hdl->type); READLINK3args args; READLINK3res result; memset(&result, 0, sizeof(result)); struct proxyv3_obj_handle *obj = container_of(obj_hdl, struct proxyv3_obj_handle, obj); if (obj_hdl->type != SYMBOLIC_LINK) { LogCrit(COMPONENT_FSAL, "Symlink called with obj %p type %d != symlink (%d)", obj_hdl, obj_hdl->type, SYMBOLIC_LINK); return fsalstat(ERR_FSAL_INVAL, 0); } args.symlink.data.data_val = obj->fh3.data.data_val; args.symlink.data.data_len = obj->fh3.data.data_len; if (!proxyv3_nfs_call(proxyv3_sockaddr(), proxyv3_socklen(), proxyv3_nfsd_port(), proxyv3_creds(), NFSPROC3_READLINK, (xdrproc_t)xdr_READLINK3args, &args, (xdrproc_t)xdr_READLINK3res, &result)) { LogWarn(COMPONENT_FSAL, "rpc for READLINK3 failed."); return fsalstat(ERR_FSAL_DELAY, 0); } if (result.status != NFS3_OK) { LogDebug(COMPONENT_FSAL, "READLINK3 failed (%u)", result.status); return nfsstat3_to_fsalstat(result.status); } /* The result is a char*. */ link_content->addr = gsh_strdup(result.READLINK3res_u.resok.data); link_content->len = strlen(link_content->addr) + 1; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Handle a "close" for a file. See proxyv3_close2. */ static fsal_status_t proxyv3_close(struct fsal_obj_handle *obj_hdl) { LogDebug( COMPONENT_FSAL, "Asking for stateless CLOSE of handle %p. Say its not 'opened'!", obj_hdl); return fsalstat(ERR_FSAL_NOT_OPENED, 0); } /** * @brief Perform a "close" on an object (with optional state). * * Since we're an NFSv3 proxy, we don't have anything "open". So we need * to return ERR_FSAL_NOT_OPENED to the layers above us (they try to keep * count of open FDs and such). * * @param obj_hdl The obj to close. * @param state Optional state (used for locking). * * @return - ERR_FSAL_NOT_OPENED on success. * - ERR_FSAL_NOTSUPP if we're confused. */ static fsal_status_t proxyv3_close2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { LogDebug(COMPONENT_FSAL, "Asking for CLOSE of handle %p (state is %p)", obj_hdl, state); if (state != NULL) { if (state->state_type == STATE_TYPE_NLM_LOCK || state->state_type == STATE_TYPE_LOCK) { /* * This is a cleanup of our lock. Callers don't seem to * care about the result. Stick with ERR_FSAL_NOT_OPENED * like close(). */ return fsalstat(ERR_FSAL_NOT_OPENED, 0); } if (state->state_type == STATE_TYPE_SHARE) { /* This is a close of a "regular" NFSv4 open. */ return fsalstat(ERR_FSAL_NOT_OPENED, 0); } LogWarn(COMPONENT_FSAL, "Received unexpected stateful CLOSE with state_type %d", state->state_type); return fsalstat(ERR_FSAL_NOTSUPP, 0); } /* * Stateless close through the other door, say it's not opened (avoid's * the decref in fsal_close). */ return fsalstat(ERR_FSAL_NOT_OPENED, 0); } /** * @brief Issue a MKDIR. See proxyv3_issue_createlike. */ static fsal_status_t proxyv3_mkdir(struct fsal_obj_handle *dir_hdl, const char *name, struct fsal_attrlist *attrs_in, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct proxyv3_obj_handle *parent_obj = container_of(dir_hdl, struct proxyv3_obj_handle, obj); LogDebug(COMPONENT_FSAL, "mkdir of %s in parent %p", name, dir_hdl); /* In case we fail along the way. */ *new_obj = NULL; MKDIR3args args; MKDIR3res result; MKDIR3resok *resok = &result.MKDIR3res_u.resok; memset(&result, 0, sizeof(result)); args.where.dir.data.data_val = parent_obj->fh3.data.data_val; args.where.dir.data.data_len = parent_obj->fh3.data.data_len; args.where.name = (char *)name; const bool allow_rawdev = false; if (!fsalattr_to_sattr3(attrs_in, allow_rawdev, &args.attributes)) { LogWarn(COMPONENT_FSAL, "MKDIR() with invalid attributes"); return fsalstat(ERR_FSAL_INVAL, 0); } /* Issue the MKDIR3 call. */ return proxyv3_issue_createlike( parent_obj, NFSPROC3_MKDIR, "MKDIR3", (xdrproc_t)xdr_MKDIR3args, &args, (xdrproc_t)xdr_MKDIR3res, &result, &result.status, &resok->obj, &resok->obj_attributes, new_obj, attrs_out, &resok->dir_wcc, parent_pre_attrs_out, parent_post_attrs_out); } /** * @brief Issue a MKNOD. See proxyv3_issue_createlike. */ static fsal_status_t proxyv3_mknode(struct fsal_obj_handle *dir_hdl, const char *name, object_file_type_t nodetype, struct fsal_attrlist *attrs_in, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct proxyv3_obj_handle *parent_obj = container_of(dir_hdl, struct proxyv3_obj_handle, obj); LogDebug(COMPONENT_FSAL, "mknod of %s in parent %p (type is %d)", name, dir_hdl, nodetype); /* In case we fail along the way, mark the output as NULL. */ *new_obj = NULL; MKNOD3args args; MKNOD3res result; sattr3 *attrs; MKNOD3resok *resok = &result.MKNOD3res_u.resok; const bool allow_rawdev = true; memset(&result, 0, sizeof(result)); args.where.dir.data.data_val = parent_obj->fh3.data.data_val; args.where.dir.data.data_len = parent_obj->fh3.data.data_len; /* Const-cast away is okay here, as it's an input. */ args.where.name = (char *)name; switch (nodetype) { case CHARACTER_FILE: args.what.type = NF3CHR; attrs = &args.what.mknoddata3_u.device.dev_attributes; break; case BLOCK_FILE: args.what.type = NF3BLK; attrs = &args.what.mknoddata3_u.device.dev_attributes; break; case SOCKET_FILE: args.what.type = NF3SOCK; attrs = &args.what.mknoddata3_u.pipe_attributes; break; case FIFO_FILE: args.what.type = NF3FIFO; attrs = &args.what.mknoddata3_u.pipe_attributes; break; default: LogWarn(COMPONENT_FSAL, "mknode got invalid MKNOD type %d", nodetype); return fsalstat(ERR_FSAL_INVAL, 0); } if (!fsalattr_to_sattr3(attrs_in, allow_rawdev, attrs)) { LogWarn(COMPONENT_FSAL, "MKNOD() with invalid attributes"); return fsalstat(ERR_FSAL_INVAL, 0); } /* Issue the MKNODE3 call. */ return proxyv3_issue_createlike( parent_obj, NFSPROC3_MKNOD, "MKNODE3", (xdrproc_t)xdr_MKNOD3args, &args, (xdrproc_t)xdr_MKNOD3res, &result, &result.status, &resok->obj, &resok->obj_attributes, new_obj, attrs_out, &resok->dir_wcc, parent_pre_attrs_out, parent_post_attrs_out); } /** * @brief Process the entries from a READDIR3 response. */ static fsal_status_t proxyv3_readdir_process_entries(entryplus3 *entry, cookie3 *cookie, struct proxyv3_obj_handle *parent_dir, fsal_readdir_cb cb, void *cbarg, attrmask_t attrmask) { int count = 0; bool readahead = false; /* * Loop over all the entries, making fsal objects from the * results and calling the given callback. */ for (; entry != NULL; entry = entry->nextentry, count++) { struct nfs_fh3 *fh3 = &entry->name_handle.post_op_fh3_u.handle; post_op_attr *post_op_attr = &entry->name_attributes; fattr3 *attrs = &post_op_attr->post_op_attr_u.attributes; struct fsal_attrlist cb_attrs; struct proxyv3_obj_handle *result_handle; enum fsal_dir_result cb_rc; /* * Don't forget to update the cookie, as long as we're * not just doing readahead. */ if (!readahead) { *cookie = entry->cookie; } if (strcmp(entry->name, ".") == 0 || strcmp(entry->name, "..") == 0) { LogFullDebug(COMPONENT_FSAL, "Skipping special value of '%s'", entry->name); continue; } if (!entry->name_handle.handle_follows) { /* * We didn't even get back a handle, so neither fh3 nor * attrs are going to be filled in. NFS clients seem to * issue a LOOKUP3 in response to that, so we'll do the * same (since Ganesha doesn't let us say "no fh3"). */ fsal_status_t rc; struct fsal_obj_handle *lookup_handle; struct proxyv3_obj_handle *lookup_obj; LogFullDebug( COMPONENT_FSAL, "READDIRPLUS didn't return a handle for '%s'. Trying LOOKUP", entry->name); rc = proxyv3_lookup_internal(op_ctx->fsal_export, entry->name, &parent_dir->obj, &lookup_handle, NULL /* drop attrs */); if (FSAL_IS_ERROR(rc)) { LogWarn(COMPONENT_FSAL, "Last chance LOOKUP failed for READDIRPLUS entry '%s'", entry->name); return rc; } /* Pull the fh3 out of the lookup_handle */ lookup_obj = container_of( lookup_handle, struct proxyv3_obj_handle, obj); memcpy(fh3, &lookup_obj->fh3, sizeof(struct nfs_fh3)); /* * We could use the attrs from the LOOKUP. But we're * also hoping that this code is temporary. So just fall * through and let the last-chance GETATTR below handle * it. */ } if (!entry->name_attributes.attributes_follow) { /* * We didn't get back attributes, so attrs is * currently not filled in / filled with * garbage. Let's do an explicit GETATTR as a * last chance. */ fsal_status_t rc; LogFullDebug( COMPONENT_FSAL, "READDIRPLUS didn't return attributes for '%s'. Trying GETATTR", entry->name); rc = proxyv3_getattr_from_fh3(fh3, attrs); if (FSAL_IS_ERROR(rc)) { LogWarn(COMPONENT_FSAL, "Last chance GETATTR failed for READDIRPLUS entry '%s'", entry->name); return rc; } } /* * Tell alloc_handle we just want the requested * attributes. */ memset(&cb_attrs, 0, sizeof(cb_attrs)); FSAL_SET_MASK(cb_attrs.request_mask, attrmask); result_handle = proxyv3_alloc_handle( op_ctx->fsal_export, fh3, attrs, parent_dir, &cb_attrs); if (result_handle == NULL) { LogCrit(COMPONENT_FSAL, "Failed to make a handle for READDIRPLUS result for entry '%s'", entry->name); return fsalstat(ERR_FSAL_FAULT, 0); } cb_rc = cb(entry->name, &result_handle->obj, &cb_attrs, cbarg, entry->cookie); /* * Other FSALs do this as >= DIR_READAHEAD, but I prefer * an explicit switch with no default. */ switch (cb_rc) { case DIR_CONTINUE: /* Next entry. */ continue; case DIR_READAHEAD: /* Keep processing the entries we've got. */ readahead = true; continue; case DIR_TERMINATE: /* Okay, all done. */ break; } } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Do a READDIR3 for a given directory. * * Do a READDIR3 for a given directory, calling a callback for each * resulting item. To support listing directories in chunks, the whence * object might be provided, which directs us where to pick up. * * @param dir_hdl The object handle for the directory. * @param whence An optional "start here". * @param cbarg Argument passed to the callback. * @param cb The readdir callback for each entry. * @param attrmask The requested attribute mask. * @param eof Output bool saying whether or not we reached the end. * * @returns - ERR_FSAL_NO_ERROR on success, an error code otherwise. */ static fsal_status_t proxyv3_readdir(struct fsal_obj_handle *dir_hdl, fsal_cookie_t *whence, void *cbarg, fsal_readdir_cb cb, attrmask_t attrmask, bool *eof) { struct proxyv3_obj_handle *dir = container_of(dir_hdl, struct proxyv3_obj_handle, obj); /* * The NFS V3 spec says: * "This should be set to 0 on the first request to read a directory." */ cookie3 cookie = (whence == NULL) ? 0 : *whence; /* * @todo Ganesha doesn't seem to have any way to pass this in alongside * whence... The comments in the Ganesha NFSD implementation for * READDIRPLUS suggest that most clients just ignore it / expect 0s. */ cookieverf3 cookie_verf; memset(&cookie_verf, 0, sizeof(cookie_verf)); LogDebug(COMPONENT_FSAL, "Doing READDIR for dir %p (cookie = %" PRIu64 ")", dir, cookie); /* Check that attrmask is at most NFSv3 */ if (!attrmask_is_posix(attrmask)) { LogWarn(COMPONENT_FSAL, "readdir asked for incompatible output attrs"); return fsalstat(ERR_FSAL_INVAL, 0); } *eof = false; while (!(*eof)) { /* @todo Move this entire block to a helper function. */ READDIRPLUS3args args; READDIRPLUS3res result; fsal_status_t rc; memset(&result, 0, sizeof(result)); args.dir.data.data_val = dir->fh3.data.data_val; args.dir.data.data_len = dir->fh3.data.data_len; args.cookie = cookie; memcpy(&args.cookieverf, &cookie_verf, sizeof(args.cookieverf)); /* * We need to let the server know how much data to return per * chunk. The V4 proxy uses 4KB and 16KB, but we should have * picked up the preferred amount from fsinfo. Use that for both * the dircount (we'll read all the data) and maxcount. */ args.dircount = args.maxcount = proxyv3_readdir_preferred(); LogFullDebug(COMPONENT_FSAL, "Calling READDIRPLUS with cookie %" PRIu64, cookie); xdrproc_t encFunc = (xdrproc_t)xdr_READDIRPLUS3args; xdrproc_t decFunc = (xdrproc_t)xdr_READDIRPLUS3res; if (!proxyv3_nfs_call(proxyv3_sockaddr(), proxyv3_socklen(), proxyv3_nfsd_port(), proxyv3_creds(), NFSPROC3_READDIRPLUS, encFunc, &args, decFunc, &result)) { LogWarn(COMPONENT_FSAL, "proxyv3_nfs_call for READDIRPLUS failed (%u)", result.status); return fsalstat(ERR_FSAL_DELAY, 0); } if (result.status != NFS3_OK) { LogDebug(COMPONENT_FSAL, "READDIRPLUS failed. %u", result.status); return nfsstat3_to_fsalstat(result.status); } LogFullDebug(COMPONENT_FSAL, "READDIRPLUS succeeded, looping over dirents"); READDIRPLUS3resok *resok = &result.READDIRPLUS3res_u.resok; /* Mark EOF now, if true. */ *eof = resok->reply.eof; /* Update the cookie verifier for the next iteration. */ memcpy(&cookie_verf, &resok->cookieverf, sizeof(cookie_verf)); /* Lookup over the entries, calling our callback for each. */ rc = proxyv3_readdir_process_entries(resok->reply.entries, &cookie, dir, cb, cbarg, attrmask); /* Cleanup any memory that READDIRPLUS3res allocated for us. */ xdr_free(decFunc, &result); if (FSAL_IS_ERROR(rc)) return rc; } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Handle a read from `obj_hdl` at offset read_arg->offset. * * Handle a read via READ3. When we're done, let done_cb know. * * @param obj_hdl The object for reading. * @param bypass Whether to bypass shares/delegations (ignored) * @param done_cb The callback for when we're done reading. * @param read_arg The offset and lengths to read. * @param cb_arg The additional arguments passed to done_cb. * */ static void proxyv3_read2(struct fsal_obj_handle *obj_hdl, bool bypass /* unused */, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *cb_arg) { struct proxyv3_obj_handle *obj = container_of(obj_hdl, struct proxyv3_obj_handle, obj); LogDebug(COMPONENT_FSAL, "Doing read2 at offset %" PRIu64 " in handle %p of len %zu", read_arg->offset, obj_hdl, read_arg->iov[0].iov_len); /* Signal that we've read 0 bytes. */ read_arg->io_amount = 0; /* Like Ceph, we don't handle READ_PLUS. */ if (read_arg->info != NULL) { LogCrit(COMPONENT_FSAL, "Got a READPLUS request. Not supported"); done_cb(obj_hdl, fsalstat(ERR_FSAL_NOTSUPP, 0), read_arg, cb_arg); return; } /* * Since we're just a V3 proxy, we are stateless. If we get an actually * stateful request, something bad must have happened. */ if (read_arg->state != NULL && (read_arg->state->state_type != STATE_TYPE_SHARE && read_arg->state->state_type != STATE_TYPE_LOCK)) { LogCrit(COMPONENT_FSAL, "Got a stateful READ w/ type %d. Not supported", read_arg->state->state_type); done_cb(obj_hdl, fsalstat(ERR_FSAL_NOTSUPP, 0), read_arg, cb_arg); return; } /* * NOTE(boulos): NFSv4 (and therefore Ganesha) doesn't actually have a * useful readv() equivalent, since it only allows a single offset * (read_arg->offset), so read2 implementations can only fill in * different amounts at an offset. NFSv3 doesn't have a readv() * equivalent, and Ganesha's NFSD won't generate it from clients anyway, * but warn here. */ if (read_arg->iov_count > 1) { LogCrit(COMPONENT_FSAL, "Got asked for multiple reads at once. Unsupported."); done_cb(obj_hdl, fsalstat(ERR_FSAL_NOTSUPP, 0), read_arg, cb_arg); return; } /* * @todo Maybe check / clamp read size against maxRead (but again, * Ganesha's NFSD layer will have already done so). */ READ3args args; READ3res result; READ3resok *resok = &result.READ3res_u.resok; memset(&result, 0, sizeof(result)); args.file.data.data_val = obj->fh3.data.data_val; args.file.data.data_len = obj->fh3.data.data_len; args.offset = read_arg->offset; args.count = read_arg->io_request; /* * Setup the resok struct with iovec using iov0. Because we are * decoding from an xdr_mem stream, there will only be one buffer. * We won't copy that buffer on decode. */ resok->data.data_len = read_arg->io_request; resok->data.iovcnt = 1; resok->data.iov = &resok->iov0; /* Issue the read. */ if (!proxyv3_nfs_call(proxyv3_sockaddr(), proxyv3_socklen(), proxyv3_nfsd_port(), proxyv3_creds(), NFSPROC3_READ, (xdrproc_t)xdr_READ3args, &args, (xdrproc_t)xdr_READ3res, &result)) { LogWarn(COMPONENT_FSAL, "proxyv3_nfs_call failed (%u)", result.status); done_cb(obj_hdl, fsalstat(ERR_FSAL_DELAY, 0), read_arg, cb_arg); return; } /* If the read failed, tell the callback about the error. */ if (result.status != NFS3_OK) { LogDebug(COMPONENT_FSAL, "READ failed: %u", result.status); done_cb(obj_hdl, nfsstat3_to_fsalstat(result.status), read_arg, cb_arg); return; } /* * NOTE(boulos): data_len is not part of the NFS spec, but Ganesha * should be getting the same number of bytes in the result. */ if (resok->count != resok->data.data_len) { LogCrit(COMPONENT_FSAL, "read of len %" PRIu32 " (resok.count) != %" PRIu32, resok->count, resok->data.data_len); done_cb(obj_hdl, fsalstat(ERR_FSAL_SERVERFAULT, 0), read_arg, cb_arg); return; } /* Copy the read buffer - unfortunately we can't avoid a data copy * here... */ assert(resok->data.iovcnt == 1); assert(read_arg->iov_count == 1); memcpy(read_arg->iov[0].iov_base, resok->data.iov[0].iov_base, resok->count); resok->data.iov[0].iov_len = resok->count; /* Fill in the rest of the return */ read_arg->end_of_file = resok->eof; read_arg->io_amount = resok->count; /* Let the caller know that we're done. */ done_cb(obj_hdl, fsalstat(ERR_FSAL_NO_ERROR, 0), read_arg, cb_arg); } /** * @brief Handle a write to a given object. See also proxyv3_read2. */ static void proxyv3_write2(struct fsal_obj_handle *obj_hdl, bool bypass /* unused */, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *cb_arg) { struct proxyv3_obj_handle *obj = container_of(obj_hdl, struct proxyv3_obj_handle, obj); LogDebug(COMPONENT_FSAL, "Doing write2 at offset %" PRIu64 " in handle %p of len %zu", write_arg->offset, obj_hdl, write_arg->iov[0].iov_len); /* Signal that we've written 0 bytes so far. */ write_arg->io_amount = 0; /* If info is only for READPLUS, it should definitely be NULL. */ if (write_arg->info != NULL) { LogCrit(COMPONENT_FSAL, "Write had 'readplus' info. Something went wrong"); done_cb(obj_hdl, fsalstat(ERR_FSAL_SERVERFAULT, 0), write_arg, cb_arg); return; } /* * Since we're just a V3 proxy, we are stateless. If we get an actually * stateful request, something bad must have happened. */ if (write_arg->state != NULL && (write_arg->state->state_type != STATE_TYPE_SHARE && write_arg->state->state_type != STATE_TYPE_LOCK)) { LogCrit(COMPONENT_FSAL, "Got a stateful WRITE of type %d. Not supported", write_arg->state->state_type); done_cb(obj_hdl, fsalstat(ERR_FSAL_NOTSUPP, 0), write_arg, cb_arg); return; } /* * NOTE(boulos): NFSv4 and therefore Ganesha doesn't actually have a * useful writev() equivalent, since it only allows a single offset * (write_arg->offset), so write2 implementations can just uselessly * fill in different amounts at an offset. NFSv3 doesn't have a writev() * equivalent, and Ganesha's NFSD won't generate it from clients anyway, * but warn here. */ if (write_arg->iov_count > 1) { LogCrit(COMPONENT_FSAL, "Got asked for multiple writes at once. Unsupported."); done_cb(obj_hdl, fsalstat(ERR_FSAL_NOTSUPP, 0), write_arg, cb_arg); return; } /* * @todo Check/clamp write size against maxWrite (but again, Ganesha's * NFSD layer will have already done so). */ WRITE3args args; WRITE3res result; WRITE3resok *resok = &result.WRITE3res_u.resok; memset(&result, 0, sizeof(result)); args.file.data.data_val = obj->fh3.data.data_val; args.file.data.data_len = obj->fh3.data.data_len; args.offset = write_arg->offset; args.count = write_arg->io_request; args.data.data_len = write_arg->io_request; args.data.iovcnt = write_arg->iov_count; args.data.iov = write_arg->iov; /* * If the request is for a stable write, ask for FILE_SYNC (rather than * just DATA_SYNC), like nfs3_write.c does. */ args.stable = (write_arg->fsal_stable) ? FILE_SYNC : UNSTABLE; /* Issue the write. */ if (!proxyv3_nfs_call(proxyv3_sockaddr(), proxyv3_socklen(), proxyv3_nfsd_port(), proxyv3_creds(), NFSPROC3_WRITE, (xdrproc_t)xdr_WRITE3args, &args, (xdrproc_t)xdr_WRITE3res, &result)) { LogWarn(COMPONENT_FSAL, "proxyv3_nfs_call failed (%u)", result.status); done_cb(obj_hdl, fsalstat(ERR_FSAL_DELAY, 0), write_arg, cb_arg); return; } /* If the write failed, tell the callback about the error. */ if (result.status != NFS3_OK) { LogDebug(COMPONENT_FSAL, "WRITE failed: %u", result.status); done_cb(obj_hdl, nfsstat3_to_fsalstat(result.status), write_arg, cb_arg); return; } /* Signal that we wrote resok->count bytes. */ write_arg->io_amount = resok->count; /* Let the caller know that we're done. */ done_cb(obj_hdl, fsalstat(ERR_FSAL_NO_ERROR, 0), write_arg, cb_arg); } /** * @brief Handle COMMIT requests. */ static fsal_status_t proxyv3_commit2(struct fsal_obj_handle *obj_hdl, off_t offset, size_t len) { struct proxyv3_obj_handle *obj = container_of(obj_hdl, struct proxyv3_obj_handle, obj); LogDebug(COMPONENT_FSAL, "Doing commit at offset %" PRIu64 " in handle %p of len %zu", offset, obj_hdl, len); COMMIT3args args; COMMIT3res result; memset(&result, 0, sizeof(result)); args.file.data.data_val = obj->fh3.data.data_val; args.file.data.data_len = obj->fh3.data.data_len; args.offset = offset; args.count = len; /* Issue the COMMIT. */ if (!proxyv3_nfs_call(proxyv3_sockaddr(), proxyv3_socklen(), proxyv3_nfsd_port(), proxyv3_creds(), NFSPROC3_COMMIT, (xdrproc_t)xdr_COMMIT3args, &args, (xdrproc_t)xdr_COMMIT3res, &result)) { LogWarn(COMPONENT_FSAL, "proxyv3_nfs_call failed (%u)", result.status); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } /* If the commit failed, report the error upwards. */ if (result.status != NFS3_OK) { LogDebug(COMPONENT_FSAL, "COMMIT failed: %u", result.status); return nfsstat3_to_fsalstat(result.status); } /* Commit happened, no problems to report. */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Handle REMOVE3/RMDIR3 requests. */ static fsal_status_t proxyv3_unlink(struct fsal_obj_handle *dir_hdl, struct fsal_obj_handle *obj_hdl, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct proxyv3_obj_handle *dir = container_of(dir_hdl, struct proxyv3_obj_handle, obj); LogDebug(COMPONENT_FSAL, "REMOVE request for dir %p of %s %s", dir_hdl, (obj_hdl->type == DIRECTORY) ? "directory" : "file", name); /* * NOTE(boulos): While the NFSv3 spec says: * * In general, REMOVE is intended to remove non-directory file objects * and RMDIR is to be used to remove directories. However, REMOVE can * be used to remove directories, subject to restrictions imposed by * either the client or server interfaces." * * It seems that in practice, Linux's kNFSd at least does not go in for * using REMOVE3 for directories and returns NFS3_ISDIR. */ bool is_rmdir = obj_hdl->type == DIRECTORY; REMOVE3args regular_args; REMOVE3res regular_result; RMDIR3args dir_args; RMDIR3res dir_result; diropargs3 *diropargs = (is_rmdir) ? &dir_args.object : ®ular_args.object; memset(®ular_result, 0, sizeof(regular_result)); memset(&dir_result, 0, sizeof(dir_result)); diropargs->dir.data.data_val = dir->fh3.data.data_val; diropargs->dir.data.data_len = dir->fh3.data.data_len; diropargs->name = (char *)name; rpcproc_t method = (is_rmdir) ? NFSPROC3_RMDIR : NFSPROC3_REMOVE; xdrproc_t enc = (is_rmdir) ? (xdrproc_t)xdr_RMDIR3args : (xdrproc_t)xdr_REMOVE3args; xdrproc_t dec = (is_rmdir) ? (xdrproc_t)xdr_RMDIR3res : (xdrproc_t)xdr_REMOVE3res; void *args = (is_rmdir) ? (void *)&dir_args : (void *)®ular_args; void *result = (is_rmdir) ? (void *)&dir_result : (void *)®ular_result; nfsstat3 *status = (is_rmdir) ? &dir_result.status : ®ular_result.status; /* Issue the REMOVE. */ if (!proxyv3_nfs_call(proxyv3_sockaddr(), proxyv3_socklen(), proxyv3_nfsd_port(), proxyv3_creds(), method, enc, args, dec, result)) { LogWarn(COMPONENT_FSAL, "proxyv3_nfs_call failed (%u)", *status); return fsalstat(ERR_FSAL_DELAY, 0); } /* If the REMOVE/RMDIR failed, report the error upwards. */ if (*status != NFS3_OK) { LogDebug(COMPONENT_FSAL, "%s failed: %u", (is_rmdir) ? "RMDIR" : "REMOVE", *status); return nfsstat3_to_fsalstat(*status); } if (is_rmdir) { pre_attrs_to_fsalattr( &dir_result.RMDIR3res_u.resok.dir_wcc.before, parent_pre_attrs_out); post_attrs_to_fsalattr( &dir_result.RMDIR3res_u.resok.dir_wcc.after, parent_post_attrs_out); } else { pre_attrs_to_fsalattr( ®ular_result.REMOVE3res_u.resok.dir_wcc.before, parent_pre_attrs_out); post_attrs_to_fsalattr( ®ular_result.REMOVE3res_u.resok.dir_wcc.after, parent_post_attrs_out); } /* Remove happened, no problems to report. */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Ask to rename obj_hdl from olddir/old_name to newdir/new_name. */ static fsal_status_t proxyv3_rename(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *olddir_hdl, const char *old_name, struct fsal_obj_handle *newdir_hdl, const char *new_name, struct fsal_attrlist *olddir_pre_attrs_out, struct fsal_attrlist *olddir_post_attrs_out, struct fsal_attrlist *newdir_pre_attrs_out, struct fsal_attrlist *newdir_post_attrs_out) { LogDebug(COMPONENT_FSAL, "Rename of obj %p which is at %p/%s => %p/%s", obj_hdl, olddir_hdl, old_name, newdir_hdl, new_name); RENAME3args args; RENAME3res result; memset(&result, 0, sizeof(result)); struct proxyv3_obj_handle *old_dir = container_of(olddir_hdl, struct proxyv3_obj_handle, obj); struct proxyv3_obj_handle *new_dir = container_of(newdir_hdl, struct proxyv3_obj_handle, obj); args.from.dir.data.data_val = old_dir->fh3.data.data_val; args.from.dir.data.data_len = old_dir->fh3.data.data_len; args.from.name = (char *)old_name; args.to.dir.data.data_val = new_dir->fh3.data.data_val; args.to.dir.data.data_len = new_dir->fh3.data.data_len; args.to.name = (char *)new_name; if (!proxyv3_nfs_call(proxyv3_sockaddr(), proxyv3_socklen(), proxyv3_nfsd_port(), proxyv3_creds(), NFSPROC3_RENAME, (xdrproc_t)xdr_RENAME3args, &args, (xdrproc_t)xdr_RENAME3res, &result)) { LogWarn(COMPONENT_FSAL, "proxyv3_nfs_call for RENAME failed"); return fsalstat(ERR_FSAL_DELAY, 0); } if (result.status != NFS3_OK) { LogDebug(COMPONENT_FSAL, "Rename failed! Got %d", result.status); } pre_attrs_to_fsalattr(&result.RENAME3res_u.resok.fromdir_wcc.before, olddir_pre_attrs_out); post_attrs_to_fsalattr(&result.RENAME3res_u.resok.fromdir_wcc.after, olddir_post_attrs_out); pre_attrs_to_fsalattr(&result.RENAME3res_u.resok.todir_wcc.before, newdir_pre_attrs_out); post_attrs_to_fsalattr(&result.RENAME3res_u.resok.todir_wcc.after, newdir_post_attrs_out); return nfsstat3_to_fsalstat(result.status); } /** * @brief Do an FSSTAT on an object in our export, and fill in infop. * * @param export_handle The fsal_export pointer (to us, a PROXY V3). Unused. * @param obj_hdl The fsal object handle enclosed by our proxyv3_obj_handle. * @param infop The output fsal_dynamicfsinfo_t struct. * * @return ERR_FSAL_NO_ERROR on success, an error code otherwise. */ static fsal_status_t proxyv3_get_dynamic_info(struct fsal_export *export_handle, struct fsal_obj_handle *obj_hdl, fsal_dynamicfsinfo_t *infop) { struct proxyv3_obj_handle *obj = container_of(obj_hdl, struct proxyv3_obj_handle, obj); FSSTAT3args args; FSSTAT3res result; args.fsroot.data.data_val = obj->fh3.data.data_val; args.fsroot.data.data_len = obj->fh3.data.data_len; memset(&result, 0, sizeof(result)); /* If the call fails for any reason, exit. */ if (!proxyv3_nfs_call(proxyv3_sockaddr(), proxyv3_socklen(), proxyv3_nfsd_port(), proxyv3_creds(), NFSPROC3_FSSTAT, (xdrproc_t)xdr_FSSTAT3args, &args, (xdrproc_t)xdr_FSSTAT3res, &result)) { LogWarn(COMPONENT_FSAL, "proxyv3_nfs_call for FSSTAT3 failed (%u)", result.status); return fsalstat(ERR_FSAL_DELAY, 0); } /* If we didn't get back NFS3_OK, return the appropriate error. */ if (result.status != NFS3_OK) { LogDebug(COMPONENT_FSAL, "FSSTAT3 failed. %u", result.status); return nfsstat3_to_fsalstat(result.status); } infop->total_bytes = result.FSSTAT3res_u.resok.tbytes; infop->free_bytes = result.FSSTAT3res_u.resok.fbytes; infop->avail_bytes = result.FSSTAT3res_u.resok.abytes; infop->total_files = result.FSSTAT3res_u.resok.tfiles; infop->free_files = result.FSSTAT3res_u.resok.ffiles; infop->avail_files = result.FSSTAT3res_u.resok.afiles; /* * maxread/maxwrite are *static* not dynamic info, we picked them up on * export init. */ /* time_delta should actually come from an FSINFO call which has * a timespec for time_delta, HOWEVER, the kernel NFS server * just reports 1 sec for time_delta which is proving to cause a * problem for some clients. So we are just going to hard code for now. */ infop->time_delta.tv_sec = 0; infop->time_delta.tv_nsec = FSAL_DEFAULT_TIME_DELTA_NSEC; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief "Convert" from our handle to an on-the-wire buffer. * * We use FH3s as our "handles", so this function just takes the fh3 from * the object handle and copies it into the fh_desc output. * * @param obj_hdl The input fsal object handle. * @param output_type The type of digest requested (NFSv4 or NFSv3). Ignored. * @param fh_desc The output file handle description (len and buf). * * @return ERR_FSAL_NO_ERROR on success, an error code otherwise. */ static fsal_status_t proxyv3_handle_to_wire(const struct fsal_obj_handle *obj_hdl, fsal_digesttype_t output_type, struct gsh_buffdesc *fh_desc) { struct proxyv3_obj_handle *handle = container_of(obj_hdl, struct proxyv3_obj_handle, obj); if (fh_desc == NULL) { LogCrit(COMPONENT_FSAL, "received null output buffer"); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } LogDebug(COMPONENT_FSAL, "handle_to_wire %p, with len %" PRIu32, handle->fh3.data.data_val, handle->fh3.data.data_len); LogFullDebugOpaque(COMPONENT_FSAL, " fh3 value is %s", LEN_FH_STR, handle->fh3.data.data_val, handle->fh3.data.data_len); size_t len = handle->fh3.data.data_len; const char *bytes = handle->fh3.data.data_val; /* Make sure the output buffer can handle our filehandle. */ if (fh_desc->len < len) { LogCrit(COMPONENT_FSAL, "not given enough buffer (%zu) for fh (%zu)", fh_desc->len, len); return fsalstat(ERR_FSAL_TOOSMALL, 0); } memcpy(fh_desc->addr, bytes, len); fh_desc->len = len; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief "Convert" from the on-the-wire format to FSAL. * * We use FH3s as our "handles", so this function just checks that the * requested handle is representable in NFSv3 (i.e., that fh_desc->len * fits within NFS3_FHSIZE). * * @param export_handle The fsal_export pointer (to us, a PROXY V3). Unused. * @param in_type The type of digest requested (NFSv4 or NFSv3). Ignored. * @param fh_desc The file handle description (len and buf). * @param flags Unused. * * @return ERR_FSAL_NO_ERROR on success, an error code otherwise. */ static fsal_status_t proxyv3_wire_to_host(struct fsal_export *export_handle, fsal_digesttype_t in_type, struct gsh_buffdesc *fh_desc, int flags) { if (fh_desc == NULL) { LogCrit(COMPONENT_FSAL, "Got NULL input pointers"); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } LogDebug(COMPONENT_FSAL, "wire_to_host of %p, with len %zu", fh_desc->addr, fh_desc->len); if (fh_desc->addr == NULL) { LogCrit(COMPONENT_FSAL, "wire_to_host received NULL address"); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } LogFullDebugOpaque(COMPONENT_FSAL, " fh3 handle is %s", LEN_FH_STR, fh_desc->addr, fh_desc->len); if (fh_desc->len > NFS3_FHSIZE) { LogCrit(COMPONENT_FSAL, "wire_to_host: handle that is too long for NFSv3"); return fsalstat(ERR_FSAL_INVAL, 0); } /* fh_desc->addr and fh_desc->len are already the nfs_fh3 we want. */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Create a new fsal_obj_handle from a given key (hdl_desc). * * @param export_handle The fsal_export pointer (to us, a PROXY V3). * @param hdl_desc A buffer and length from wire_to_host (an fh3). * @param handle Output param for new object handle. * @param attrs_out Optional file attributes. * * @return ERR_FSAL_NO_ERROR on success, an error code otherwise. */ static fsal_status_t proxyv3_create_handle(struct fsal_export *export_handle, struct gsh_buffdesc *hdl_desc, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { struct nfs_fh3 fh3; LogDebug(COMPONENT_FSAL, "Creating handle from %p with len %zu", hdl_desc->addr, hdl_desc->len); LogFullDebugOpaque(COMPONENT_FSAL, " fh3 handle is %s", LEN_FH_STR, hdl_desc->addr, hdl_desc->len); /* In case we die along the way. */ *handle = NULL; fh3.data.data_val = hdl_desc->addr; fh3.data.data_len = hdl_desc->len; struct fsal_attrlist tmp_attrs; memset(&tmp_attrs, 0, sizeof(tmp_attrs)); if (attrs_out != NULL) { FSAL_SET_MASK(tmp_attrs.request_mask, attrs_out->request_mask); } fsal_status_t rc = proxyv3_getattr_from_fh3(&fh3, &tmp_attrs); if (FSAL_IS_ERROR(rc)) { return rc; } /* Bundle up the result into a new object handle. */ struct proxyv3_obj_handle *result_handle = proxyv3_alloc_handle( export_handle, &fh3, &tmp_attrs, NULL /* don't have parent info */, attrs_out); /* If we couldn't allocate the handle, fail. */ if (result_handle == NULL) { return fsalstat(ERR_FSAL_FAULT, 0); } *handle = &(result_handle->obj); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Allocate a state_t structure * * Note that this is not expected to fail since memory allocation is * expected to abort on failure. * * @param[in] exp_hdl Export state_t will be associated with * @param[in] state_type Type of state to allocate * @param[in] related_state Related state if appropriate * * @returns a state structure. */ static struct state_t *proxyv3_alloc_state(struct fsal_export *exp_hdl, enum state_type state_type, struct state_t *related_state) { /* There is no need for a "file descriptor" or anything. */ return init_state(gsh_calloc(1, sizeof(struct state_t)), NULL, state_type, related_state); } /** * @brief "Convert" an fsal_obj_handle to an MDCACHE key. * * @param obj_hdl The input object handle. * @param fh_desc The output key description (in our case an fh3). */ static void proxyv3_handle_to_key(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *fh_desc) { struct proxyv3_obj_handle *handle = container_of(obj_hdl, struct proxyv3_obj_handle, obj); LogDebug(COMPONENT_FSAL, "handle to key for %p", handle); if (fh_desc == NULL) { LogCrit(COMPONENT_FSAL, "received null output buffer"); return; } LogFullDebugOpaque(COMPONENT_FSAL, " fh3 handle is %s", LEN_FH_STR, handle->fh3.data.data_val, handle->fh3.data.data_len); fh_desc->addr = handle->fh3.data.data_val; fh_desc->len = handle->fh3.data.data_len; } /** * @brief Fill in fs_info state for our export for a given file handle. * * @param fh3 The NFS v3 file handle. * * @return - fsal_status_t result of the FSINFO operation. */ static fsal_status_t proxyv3_fill_fsinfo(nfs_fh3 *fh3) { /* * Issue an FSINFO to ask the server about its max read/write sizes. */ FSINFO3args args; FSINFO3res result; FSINFO3resok *resok = &result.FSINFO3res_u.resok; fsal_staticfsinfo_t *fsinfo = &PROXY_V3.module.fs_info; struct proxyv3_export *export = container_of( op_ctx->fsal_export, struct proxyv3_export, export); memcpy(&args.fsroot, fh3, sizeof(*fh3)); memset(&result, 0, sizeof(result)); if (!proxyv3_nfs_call(proxyv3_sockaddr(), proxyv3_socklen(), proxyv3_nfsd_port(), proxyv3_creds(), NFSPROC3_FSINFO, (xdrproc_t)xdr_FSINFO3args, &args, (xdrproc_t)xdr_FSINFO3res, &result)) { LogWarn(COMPONENT_FSAL, "FSINFO failed"); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } if (result.status != NFS3_OK) { /* Okay, let's see what we got. */ LogDebug(COMPONENT_FSAL, "FSINFO failed, got %u", result.status); return nfsstat3_to_fsalstat(result.status); } LogDebug(COMPONENT_FSAL, "FSINFO3 returned maxread %" PRIu32 "maxwrite %" PRIu32 " maxfilesize %" PRIu64, resok->rtmax, resok->wtmax, resok->maxfilesize); /* * Lower any values we need to. NOTE(boulos): The export manager code * reads fsinfo->maxread/maxwrite/maxfilesize, but the *real* values are * the op_ctx->ctx_export->MaxRead/MaxWrite/PrefRead/PrefWrite fields * (which it feels gross to go writing into...). */ if (resok->rtmax != 0 && fsinfo->maxread > resok->rtmax) { LogWarn(COMPONENT_FSAL, "Changing maxread from %" PRIu64 " to %" PRIu32, fsinfo->maxread, resok->rtmax); fsinfo->maxread = resok->rtmax; } if (resok->wtmax != 0 && fsinfo->maxwrite > resok->wtmax) { LogWarn(COMPONENT_FSAL, "Reducing maxwrite from %" PRIu64 " to %" PRIu32, fsinfo->maxwrite, resok->wtmax); fsinfo->maxwrite = resok->wtmax; } if (resok->maxfilesize != 0 && fsinfo->maxfilesize > resok->maxfilesize) { LogWarn(COMPONENT_FSAL, "SKIPPING: Asked to change maxfilesize from %" PRIu64 " to %" PRIu64, fsinfo->maxfilesize, resok->maxfilesize); /* * NOTE(boulos): nlm_util tries to enforce the NFSv4 "offset + * length" > UINT_64_MAX => error but nothing else. This is best * described in the description of the LOCK op in NFSv4 in RFC * 5661, Section 18.10.3 * (https://tools.ietf.org/html/rfc5661#section-18.10.3). The * change to Ganesha's behavior was introduced in c811fe9323, * and means that if you set maxfilesize to what the backend * NFSD reports, we'll incorrectly fail various Lock requests as * NLM4_FBIG. */ /* * @todo Fix the ganesha handling of maxfilesize if * possible, by having a separate concept of "the maximum thing * I could ever support" (which isn't maxfilesize) and "the * maximum thing my export supports" (which might have * restrictions). dang@redhat.com worked around this for the * NFSv4 handlers in 3d069bf, but didn't do the same for * nlm_util. */ /* fsinfo->maxfilesize = resok->maxfilesize; */ } /* Pickup the preferred maxcount parameter for READDIR. */ if (resok->dtpref != 0) { LogDebug(COMPONENT_FSAL, "Setting dtpref to %" PRIu32 " based on fsinfo result", resok->dtpref); export->params.readdir_preferred = resok->dtpref; } /* Check that our assumptions about are true (or warn loudly). */ if ((resok->properties & FSF3_LINK) == 0) { LogWarn(COMPONENT_FSAL, "FSINFO says this backend doesn't support hard links"); } if ((resok->properties & FSF3_SYMLINK) == 0) { LogWarn(COMPONENT_FSAL, "FSINFO says this backend doesn't support symlinks"); } if ((resok->properties & FSF3_HOMOGENEOUS) == 0) { LogWarn(COMPONENT_FSAL, "FSINFO says this backend is not homogeneous"); } if ((resok->properties & FSF3_CANSETTIME) == 0) { LogWarn(COMPONENT_FSAL, "FSINFO says this backend cannot set time in setattr"); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Create a PROXY_V3 export. * * @param fsal_handle The fsal_module (currently unused). * @param parse_node The input config data from parsing. * @param error_type An output argument for load_config_from_node. * @param up_ops The input up_ops for upcalls. * * @return - ERR_FSAL_NO_ERROR and a mounted NFSv3 backend on success. * - An error status otherwise (e.g., ERR_FSAL_INVAL). */ static fsal_status_t proxyv3_create_export(struct fsal_module *fsal_handle, void *parse_node, struct config_error_type *error_type, const struct fsal_up_vector *up_ops) { struct proxyv3_export *export = gsh_calloc(1, sizeof(*export)); int ret; /* NOTE(boulos): fsal_export_init sets the export ops to defaults. */ fsal_export_init(&export->export); /* Set the export functions we know how to handle. */ export->export.exp_ops.lookup_path = proxyv3_lookup_path; export->export.exp_ops.get_fs_dynamic_info = proxyv3_get_dynamic_info; export->export.exp_ops.wire_to_host = proxyv3_wire_to_host; export->export.exp_ops.create_handle = proxyv3_create_handle; export->export.exp_ops.alloc_state = proxyv3_alloc_state; /* * Try to load the config. If it fails (say they didn't provide * Srv_Addr), exit early and free the allocated export. */ ret = load_config_from_node(parse_node, &proxyv3_export_param, &export->params, true, error_type); if (ret != 0) { LogCrit(COMPONENT_FSAL, "Bad params for export %s", CTX_FULLPATH(op_ctx)); gsh_free(export); return fsalstat(ERR_FSAL_INVAL, ret); } export->export.fsal = fsal_handle; export->export.up_ops = up_ops; op_ctx->fsal_export = &export->export; /* * Attempt to "attach" our FSAL to the export. (I think this just always * works...). */ ret = fsal_attach_export(fsal_handle, &export->export.exports); if (ret != 0) { LogCrit(COMPONENT_FSAL, "Failed to attach export %s", CTX_FULLPATH(op_ctx)); gsh_free(export); return fsalstat(ERR_FSAL_INVAL, ret); } /* Setup the pointer and socklen arguments. */ sockaddr_t *sockaddr = &export->params.srv_addr; export->params.sockaddr = (struct sockaddr *)sockaddr; if (sockaddr->ss_family == AF_INET) { export->params.socklen = sizeof(struct sockaddr_in); } else { export->params.socklen = sizeof(struct sockaddr_in6); } /* String-ify the "name" for debugging statements. */ struct display_buffer dspbuf = { sizeof(export->params.sockname), export->params.sockname, export->params.sockname }; display_sockaddr(&dspbuf, &export->params.srv_addr); LogDebug(COMPONENT_FSAL, "Got sockaddr %s", export->params.sockname); u_int mountd_port = 0; u_int nfsd_port = 0; u_int nlm_port = 0; if (!proxyv3_find_ports(proxyv3_sockaddr(), proxyv3_socklen(), &mountd_port, &nfsd_port, &nlm_port)) { LogDebug(COMPONENT_FSAL, "Failed to find mountd/nfsd/nlm, oh well"); } /* Copy into our param struct. */ export->params.mountd_port = mountd_port; export->params.nfsd_port = nfsd_port; export->params.nlm_port = nlm_port; mnt3_dirpath dirpath = CTX_FULLPATH(op_ctx); mountres3 result; memset(&result, 0, sizeof(result)); LogDebug(COMPONENT_FSAL, "Going to try to issue a NULL MOUNT at %s", proxyv3_sockname()); /* Be nice and try a MOUNT NULL first. */ if (!proxyv3_mount_call(proxyv3_sockaddr(), proxyv3_socklen(), proxyv3_mountd_port(), proxyv3_creds(), MOUNTPROC3_NULL, (xdrproc_t)xdr_void, NULL, (xdrproc_t)xdr_void, NULL)) { LogCrit(COMPONENT_FSAL, "proxyv3_mount_call for NULL failed"); gsh_free(export); return fsalstat(ERR_FSAL_INVAL, 0); } LogDebug(COMPONENT_FSAL, "Going to try to mount '%s' on %s", dirpath, proxyv3_sockname()); if (!proxyv3_mount_call(proxyv3_sockaddr(), proxyv3_socklen(), proxyv3_mountd_port(), proxyv3_creds(), MOUNTPROC3_MNT, (xdrproc_t)xdr_dirpath, &dirpath, (xdrproc_t)xdr_mountres3, &result)) { LogCrit(COMPONENT_FSAL, "proxyv3_mount_call for path '%s' failed", dirpath); gsh_free(export); return fsalstat(ERR_FSAL_INVAL, 0); } if (result.fhs_status != MNT3_OK) { LogCrit(COMPONENT_FSAL, "Mount failed. Got back %u for path '%s'", result.fhs_status, dirpath); gsh_free(export); return fsalstat(ERR_FSAL_INVAL, 0); } nfs_fh3 *fh3 = (nfs_fh3 *)&result.mountres3_u.mountinfo.fhandle; LogDebug(COMPONENT_FSAL, "Mount successful. Got back a %" PRIu32 " len fhandle", fh3->data.data_len); /* Copy the result for later use. */ export->root_handle_len = fh3->data.data_len; memcpy(export->root_handle, fh3->data.data_val, fh3->data.data_len); if (proxyv3_nlm_port() != 0) { /* Try to test NLM by sending a NULL command. */ if (!proxyv3_nlm_call(proxyv3_sockaddr(), proxyv3_socklen(), proxyv3_nlm_port(), proxyv3_creds(), NLMPROC4_NULL, (xdrproc_t)xdr_void, NULL, (xdrproc_t)xdr_void, NULL)) { /* nlm_call will already have said the RPC failed. */ gsh_free(export); return fsalstat(ERR_FSAL_INVAL, 0); } } /* Now fill in the fsinfo and we're done.*/ return proxyv3_fill_fsinfo(fh3); } /** * @brief Initialize the PROXY_V3 FSAL. */ MODULE_INIT void proxy_v3_init(void) { /* Try to register our FSAL. If it fails, exit. */ if (register_fsal(&PROXY_V3.module, "PROXY_V3", FSAL_MAJOR_VERSION, FSAL_MINOR_VERSION, FSAL_ID_NO_PNFS) != 0) { return; } /* * NOTE(boulos): We used to setup our RPC and NLM connections here * before exiting, but we need to wait for init_config in order to make * those configurable. The FSAL manager doesn't call anything else in * between anyway. */ PROXY_V3.module.m_ops.init_config = proxyv3_init_config; PROXY_V3.module.m_ops.create_export = proxyv3_create_export; /* * Fill in the objecting handling ops with the default "Hey! NOT * IMPLEMENTED!!" ones, and then override the ones we handle. */ fsal_default_obj_ops_init(&PROXY_V3.handle_ops); /* FSAL handle-related ops. */ PROXY_V3.handle_ops.handle_to_wire = proxyv3_handle_to_wire; PROXY_V3.handle_ops.handle_to_key = proxyv3_handle_to_key; PROXY_V3.handle_ops.release = proxyv3_handle_release; /* Attributes. */ PROXY_V3.handle_ops.lookup = proxyv3_lookup_handle; PROXY_V3.handle_ops.getattrs = proxyv3_getattrs; PROXY_V3.handle_ops.setattr2 = proxyv3_setattr2; /* Mkdir/Readir. (RMDIR is under unlink). */ PROXY_V3.handle_ops.mkdir = proxyv3_mkdir; PROXY_V3.handle_ops.readdir = proxyv3_readdir; /* Symlink and hardlink. */ PROXY_V3.handle_ops.link = proxyv3_hardlink; PROXY_V3.handle_ops.readlink = proxyv3_readlink; PROXY_V3.handle_ops.symlink = proxyv3_symlink; /* Block/Character/Fifo/Device files. */ PROXY_V3.handle_ops.mknode = proxyv3_mknode; /* Read/write/flush */ PROXY_V3.handle_ops.read2 = proxyv3_read2; PROXY_V3.handle_ops.write2 = proxyv3_write2; PROXY_V3.handle_ops.commit2 = proxyv3_commit2; /* Open/close. */ PROXY_V3.handle_ops.open2 = proxyv3_open2; PROXY_V3.handle_ops.status2 = proxyv3_status2; PROXY_V3.handle_ops.reopen2 = proxyv3_reopen2; PROXY_V3.handle_ops.close = proxyv3_close; PROXY_V3.handle_ops.close2 = proxyv3_close2; /* Remove (and RMDIR) and rename. */ PROXY_V3.handle_ops.unlink = proxyv3_unlink; PROXY_V3.handle_ops.rename = proxyv3_rename; /* Locking */ PROXY_V3.handle_ops.lock_op2 = proxyv3_lock_op2; } nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V3/nlm.c000066400000000000000000000361761473756622300201560ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright 2020 Google LLC * Author: Solomon Boulos * * This program 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 3 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 * 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 * * ------------- */ #include "proxyv3_fsal_methods.h" #include "nlm4.h" #include "nlm_util.h" /* Our hostname for the NLM "client" (this host, since we're a proxy). */ static char nlmMachineName[MAXHOSTNAMELEN + 1] = { 0 }; static pid_t nlmSvid; /** * @brief Setup our NLM "stack" for PROXY_V3. * * @return - True. */ bool proxyv3_nlm_init(void) { /* Initialise only once. */ static bool nlm_initialised; if (nlm_initialised) return true; /* Cache our hostname for auth later. */ if (gethostname(nlmMachineName, sizeof(nlmMachineName)) != 0) { const char *kClientName = "127.0.0.1"; LogCrit(COMPONENT_FSAL, "gethostname() failed. Errno %d (%s). Hardcoding a client IP instead.", errno, strerror(errno)); memcpy(nlmMachineName, kClientName, strlen(kClientName) + 1 /* For NULL */); } nlmSvid = (int32_t)getpid(); nlm_initialised = true; return true; } /** * @brief Determine if this is a lock op we can handle. * * @param obj_hdl The fsal_obj_handle (currently unused). * @param state The object lock state (currently unused). * @param owner The object owner (must be non-NULL for valid ops). * @param lock_op The lock op itself. We currently don't expect FSAL_OP_LOCKB. * @param request_lock The input lock info. * @param conflicting_lock Optional output lock. Required for FSAL_OP_LOCKT. * * @return - True, if we can handle this op. * - False, otherwise. */ static bool proxyv3_is_valid_lockop(struct fsal_obj_handle *obj_hdl, struct state_t *state, struct state_owner_t *owner, fsal_lock_op_t lock_op, fsal_lock_param_t *request_lock, fsal_lock_param_t *conflicting_lock) { if (lock_op == FSAL_OP_LOCKB) { LogCrit(COMPONENT_FSAL, "Asked to perform an async lock request. We told Ganesha we can't handle those..."); return false; } if (request_lock->lock_sle_type != FSAL_POSIX_LOCK) { LogCrit(COMPONENT_FSAL, "Asked to do an NFSv4 Delegation/Lease (%d)", request_lock->lock_sle_type); return false; } if (owner == NULL) { /* * We need the owner info to fill in the various alock fields in * the requests. */ LogCrit(COMPONENT_FSAL, "Didn't receive an owner. Unexpected."); return false; } if (lock_op == FSAL_OP_LOCKT && conflicting_lock == NULL) { LogCrit(COMPONENT_FSAL, "ERROR: Ganesha asked for NLM4_TEST, but output is NULL"); return false; } if (proxyv3_nlm_port() == 0) { LogCrit(COMPONENT_FSAL, "Got a lock op request, but we don't have a lockmanagerd port!"); return false; } return true; } /** * @brief Map from fsal_lock_op_t to a const char* string. * @param status Input lock op as a fsal_lock_op_t. * * @return - The status enum as a string (e.g., "LOCK_ASYNC"). * - "INVALID" otherwise. */ static const char *lock_op_to_cstr(fsal_lock_op_t op) { switch (op) { case FSAL_OP_LOCKT: return "TEST"; case FSAL_OP_LOCK: return "LOCK_IMMEDIATE"; case FSAL_OP_LOCKB: return "LOCK_ASYNC"; case FSAL_OP_UNLOCK: return "UNLOCK"; case FSAL_OP_CANCEL: return "CANCEL"; } return "INVALID"; } /** * @brief Map from nlm4_stats error codes a const char* string. * @param status Input status as an nlm4_stats. * * @return - The status enum as a string (e.g., "NLM4_GRANTED"). * - "INVALID" otherwise. */ static const char *nlm4stat_to_cstr(nlm4_stats status) { switch (status) { case NLM4_GRANTED: return "NLM4_GRANTED"; case NLM4_DENIED: return "NLM4_DENIED"; case NLM4_DENIED_NOLOCKS: return "NLM4_DENIED_NOLOCKS"; case NLM4_BLOCKED: return "NLM4_BLOCKED"; case NLM4_DENIED_GRACE_PERIOD: return "NLM4_DENIED_GRACE_PERIOD"; case NLM4_DEADLCK: return "NLM4_DEADLCK"; case NLM4_ROFS: return "NLM4_ROFS"; case NLM4_STALE_FH: return "NLM4_STALE_FH"; case NLM4_FBIG: return "NLM4_FBIG"; case NLM4_FAILED: return "NLM4_FAILED"; } return "INVALID"; } /** * @brief Fill in the common NLM arguments (cookie and lock). * * @param obj The object handle for the object (as proxyv3_obj_handle). * @param state The current object lock state. * @param state_owner The object owner info. * @param request_lock The input lock info. * @param cookie The output NLM cookie argument. * @param lock The output nlm4_lock argument. */ static void proxyv3_nlm_fill_common_args(struct proxyv3_obj_handle *obj, struct state_t *state, struct state_owner_t *state_owner, fsal_lock_param_t *request_lock, struct netobj *cookie, struct nlm4_lock *lock) { /* * Fill in the cookie. * * NFS Illustrated claims that the client (that's us!) get to fill this * in with whatever we want (I think it's an extra double check on top * of the XID in the RPC). My first plan was to use obj->fh3, but those * are often >32 bytes which Linux's NFSD doesn't like at the least: * * lockd: bad cookie size 36 (only cookies under 32 bytes are * supported.) * * So just trim the length to the first 32. */ cookie->n_bytes = obj->fh3.data.data_val; if (obj->fh3.data.data_len > 32) { cookie->n_len = 32; } else { cookie->n_len = obj->fh3.data.data_len; } /* * @todo: if we (the proxy) crash, the backend will try to reach out to * us, but Ganesha won't know what it's talking about (that might be * fine! lock recovery is cooperative). We will be in grace though, and * all *our* clients *should* reach out to us to reclaim their locks * with reclaim=true. */ /* We use *our* hostname to tell the backend that we are its client. */ lock->caller_name = nlmMachineName; lock->svid = nlmSvid; lock->fh.n_bytes = obj->fh3.data.data_val; lock->fh.n_len = obj->fh3.data.data_len; lock->oh.n_bytes = state_owner->so_owner_val; lock->oh.n_len = state_owner->so_owner_len; lock->l_offset = request_lock->lock_start; lock->l_len = request_lock->lock_length; } /** * @brief A little helper to perform an NLM RPC via proxyv3_nlm_call. */ static fsal_status_t proxyv3_nlm_commonrpc(rpcproc_t nlmProc, const char *procName, xdrproc_t encFunc, void *args, xdrproc_t decFunc, void *result, nlm4_stats *status, struct netobj *cookie, struct nlm4_lock *lock) { LogDebug(COMPONENT_FSAL, "Issuing an %s. Lock info: offset %" PRIu64 ", len %" PRIu64, procName, lock->l_offset, lock->l_len); if (!proxyv3_nlm_call(proxyv3_sockaddr(), proxyv3_socklen(), proxyv3_nlm_port(), &op_ctx->creds, nlmProc, encFunc, args, decFunc, result)) { LogCrit(COMPONENT_FSAL, "PROXY_V3: NLM op %s failed.", procName); return fsalstat(ERR_FSAL_DELAY, 0); } /* For now, always log the results. */ LogDebug(COMPONENT_FSAL, "PROXY_V3: NLM op %s returned %s", procName, nlm4stat_to_cstr(*status)); return nlm4stat_to_fsalstat(*status); } /** * @brief Handle NLM_TEST. */ static fsal_status_t proxyv3_nlm_test(struct proxyv3_obj_handle *obj, struct state_t *state, struct state_owner_t *state_owner, bool exclusive_lock, fsal_lock_param_t *request_lock, fsal_lock_param_t *conflicting_lock) { nlm4_testargs args; nlm4_testres result; nlm4_stats *status; nlm4_holder *holder; fsal_status_t rc; memset(&result, 0, sizeof(result)); args.exclusive = exclusive_lock; proxyv3_nlm_fill_common_args(obj, state, state_owner, request_lock, &args.cookie, &args.alock); status = &result.test_stat.stat; rc = proxyv3_nlm_commonrpc(NLMPROC4_TEST, "NLM_TEST", (xdrproc_t)xdr_nlm4_testargs, &args, (xdrproc_t)xdr_nlm4_testres, &result, status, &args.cookie, &args.alock); /* If we don't get an explicit DENIED response, return the result. */ if (*status != NLM4_DENIED) { return rc; } /* Otherwise, we need to fill in the conflict info. */ holder = &result.test_stat.nlm4_testrply_u.holder; /* * @todo The holder also has the other owner information, but it's not * clear if you're supposed to fill in state_owner with that info... */ conflicting_lock->lock_type = (holder->exclusive) ? FSAL_LOCK_W : FSAL_LOCK_R; conflicting_lock->lock_start = holder->l_offset; conflicting_lock->lock_length = holder->l_len; return rc; } /** * @brief Handle NLM_LOCK. */ static fsal_status_t proxyv3_nlm_lock(struct proxyv3_obj_handle *obj, struct state_t *state, struct state_owner_t *state_owner, bool exclusive_lock, fsal_lock_param_t *request_lock) { nlm4_lockargs args; nlm4_res result; memset(&result, 0, sizeof(result)); args.block = false; args.exclusive = exclusive_lock; args.reclaim = request_lock->lock_reclaim; /* * While sal_data.h says this is the NFSv4 Sequence ID, nlm4_Lock pushes * arg->state from v3 to eventually get_nlm_state as "nsm_state" which * goes into the state_seqid field. */ args.state = state->state_seqid; proxyv3_nlm_fill_common_args(obj, state, state_owner, request_lock, &args.cookie, &args.alock); return proxyv3_nlm_commonrpc(NLMPROC4_LOCK, "NLM_LOCK", (xdrproc_t)xdr_nlm4_lockargs, &args, (xdrproc_t)xdr_nlm4_res, &result, &result.stat.stat, &args.cookie, &args.alock); } /* * NOTE(boulos): We should never currently up end calling CANCEL, because we * tell Ganesha we aren't ready to deal with blocking locks (yet). */ /** * @brief Handle NLM_CANCEL. */ static fsal_status_t proxyv3_nlm_cancel(struct proxyv3_obj_handle *obj, struct state_t *state, struct state_owner_t *state_owner, bool exclusive_lock, fsal_lock_param_t *request_lock) { nlm4_cancargs args; nlm4_res result; memset(&result, 0, sizeof(result)); args.block = false; args.exclusive = exclusive_lock; proxyv3_nlm_fill_common_args(obj, state, state_owner, request_lock, &args.cookie, &args.alock); return proxyv3_nlm_commonrpc(NLMPROC4_CANCEL, "NLM_CANCEL", (xdrproc_t)xdr_nlm4_cancargs, &args, (xdrproc_t)xdr_nlm4_res, &result, &result.stat.stat, &args.cookie, &args.alock); } /** * @brief Handle NLM_UNLOCK. */ static fsal_status_t proxyv3_nlm_unlock(struct proxyv3_obj_handle *obj, struct state_t *state, struct state_owner_t *state_owner, bool exclusive_lock, fsal_lock_param_t *request_lock) { nlm4_unlockargs args; nlm4_res result; memset(&result, 0, sizeof(result)); proxyv3_nlm_fill_common_args(obj, state, state_owner, request_lock, &args.cookie, &args.alock); return proxyv3_nlm_commonrpc(NLMPROC4_UNLOCK, "NLM4_UNLOCK", (xdrproc_t)xdr_nlm4_unlockargs, &args, (xdrproc_t)xdr_nlm4_res, &result, &result.stat.stat, &args.cookie, &args.alock); } /** * @brief Clear the conflicting_lock parameter for lock operations. * * @param lock_op The type of lock op (should be FSAL_OP_LOCKT). * @param conflicting_lock The output fsal_lock_param_t to clear. * */ static void proxyv3_clear_conflicting_lock(fsal_lock_op_t lock_op, fsal_lock_param_t *conflicting_lock) { if (lock_op != FSAL_OP_LOCKT) { /* * @todo Alternatively, we can do a TEST afterwards to * fill in who the conflict was likely to be. But that can also * fail if the conflict gives up in between our LOCK. The CEPH * FSAL chooses to do this though, and it probably makes Ganesha * more able to handle immediate responses for lock requests * (i.e., if it knows that only a certain range is locked, it * might allow in a read lock to a non-overlapping range). But * the SAL do_lock_op always just fills in *holder with * &unknown_holder anyway... so it doesn't seem like we should * waste our time. */ LogDebug( COMPONENT_FSAL, "Lock op is %s, but Ganesha wants to know about the conflict. Report the whole file as locked like nlm_process_conflict.", lock_op_to_cstr(lock_op)); } conflicting_lock->lock_sle_type = FSAL_POSIX_LOCK; conflicting_lock->lock_type = FSAL_LOCK_W; /* Write lock / exclusive */ conflicting_lock->lock_start = 0; conflicting_lock->lock_length = 0; /* Whole file */ conflicting_lock->lock_reclaim = false; } /** * @brief Handle all basic NLM lock operations (LOCK, UNLOCK, TEST, CANCEL). * * @param obj_hdl The fsal_obj_handle for the object. * @param state The current object lock state. * @param owner The object owner info. * @param lock_op The lock op itself. * @param request_lock The input lock info. * @param conflicting_lock Optional output lock. Required for FSAL_OP_LOCKT. * * @return - fsal_status_t for the result of the operation. */ fsal_status_t proxyv3_lock_op2(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *void_owner, fsal_lock_op_t lock_op, fsal_lock_param_t *request_lock, fsal_lock_param_t *conflicting_lock) { LogDebug(COMPONENT_FSAL, "Got lock_op2 for obj %p. Op is %s", obj_hdl, lock_op_to_cstr(lock_op)); struct proxyv3_obj_handle *obj = container_of(obj_hdl, struct proxyv3_obj_handle, obj); /* * NOTE(boulos): I'm super confused as to whether state->state_owner is * supposed to be used here vs casting owner to state_owner_t... */ struct state_owner_t *owner = (struct state_owner_t *)void_owner; /* * A write lock is an exclusive request, while reads are not. See * nlm_process_parameters for reference. */ bool exclusive = request_lock->lock_type == FSAL_LOCK_W; /* * Before we fail or not, clear the output conflicting_lock if * appropriate. NOTE(boulos): Ganesha seems to (incorrectly?) fill in * the response for non-TEST calls with the conflict holder (e.g., in * nlm4_Lock) even though these RPCs are all supposed to return only a * nlm4_res which has no holder information. */ if (conflicting_lock != NULL) { proxyv3_clear_conflicting_lock(lock_op, conflicting_lock); } /* Make sure we can handle the request and that it's well formed. */ if (!proxyv3_is_valid_lockop(obj_hdl, state, owner, lock_op, request_lock, conflicting_lock)) { return fsalstat(ERR_FSAL_SERVERFAULT, 0); } switch (lock_op) { case FSAL_OP_LOCKT: return proxyv3_nlm_test(obj, state, owner, exclusive, request_lock, conflicting_lock); case FSAL_OP_LOCK: return proxyv3_nlm_lock(obj, state, owner, exclusive, request_lock); case FSAL_OP_UNLOCK: return proxyv3_nlm_unlock(obj, state, owner, exclusive, request_lock); case FSAL_OP_CANCEL: return proxyv3_nlm_cancel(obj, state, owner, exclusive, request_lock); default: /* UNREACHABLE. (Tested in is_valid_lockop). */ LogCrit(COMPONENT_FSAL, "Unexpected lock op %d", lock_op); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } } nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V3/proxyv3_fsal_methods.h000066400000000000000000000112611473756622300235430ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright 2020-2021 Google LLC * Author: Solomon Boulos * * This program 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 3 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 * 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 * * ------------- */ #ifndef _PROXY_V3_FSAL_METHODS_H_ #define _PROXY_V3_FSAL_METHODS_H_ #include "config.h" #include "fsal.h" #include "FSAL/fsal_init.h" #include struct proxyv3_fsal_module { struct fsal_module module; struct fsal_obj_ops handle_ops; /* The number of sockets in our connection pool. */ uint32_t num_sockets; /* Allow lookup optimization for CWD and CWD parent */ bool allow_lookup_optimization; }; /* Our global PROXY_V3 struct */ extern struct proxyv3_fsal_module PROXY_V3; /* Start with just needing the Srv_Addr parameter to an NFSv3 server. */ struct proxyv3_client_params { /* This is the actual server address. */ sockaddr_t srv_addr; /* These is *derived* from srv_addr and points to it. */ const struct sockaddr *sockaddr; socklen_t socklen; char sockname[SOCK_NAME_MAX]; /* Get the ports from portmapper and shove them here. */ uint mountd_port; uint nfsd_port; uint nlm_port; uint readdir_preferred; }; /* * Our private handle for fsal_obj_handle just keeps the fh3/fattr3 that we've * found, plus a parent pointer if we've got it. If the parent pointer is NULL, * that does *not* mean there isn't a parent (just that we don't know who the * parent is). */ struct proxyv3_obj_handle { struct fsal_obj_handle obj; nfs_fh3 fh3; fattr3 attrs; /* Optional pointer to the parent of this object, NULL for the root. */ const struct proxyv3_obj_handle *parent; }; /* Each export needs some params and a root handle. */ struct proxyv3_export { struct fsal_export export; struct proxyv3_client_params params; struct proxyv3_obj_handle *root_handle_obj; char root_handle[NFS3_FHSIZE]; size_t root_handle_len; }; bool proxyv3_rpc_init(const uint num_sockets); bool proxyv3_nlm_init(void); const struct sockaddr *proxyv3_sockaddr(void); const socklen_t proxyv3_socklen(void); const uint proxyv3_nlm_port(void); bool proxyv3_find_ports(const struct sockaddr *host, const socklen_t socklen, u_int *mountd_port, u_int *nfsd_port, u_int *nlm_port); bool proxyv3_nfs_call(const struct sockaddr *host, const socklen_t socklen, const uint nfsdPort, const struct user_cred *creds, const rpcproc_t nfsProc, const xdrproc_t encodeFunc, void *args, const xdrproc_t decodeFunc, void *output); bool proxyv3_mount_call(const struct sockaddr *host, const socklen_t socklen, const uint mountdPort, const struct user_cred *creds, const rpcproc_t mountProc, const xdrproc_t encodeFunc, void *args, const xdrproc_t decodeFunc, void *output); bool proxyv3_nlm_call(const struct sockaddr *host, const socklen_t socklen, const uint nlmPort, const struct user_cred *creds, const rpcproc_t nlmProc, const xdrproc_t encodeFunc, void *args, const xdrproc_t decodeFunc, void *output); /* * All the NLM operations funnel through lock_op2, and it's complicated enough * to need its own file. */ fsal_status_t proxyv3_lock_op2(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *owner, fsal_lock_op_t lock_op, fsal_lock_param_t *request_lock, fsal_lock_param_t *conflicting_lock); /* * Helpers for translating from nfsv3 structs to Ganesha data. These could go in * Protocols/NFS/nfs_proto_tools.c if someone wanted them. The doxygen comments * are in the implementation files. */ fsal_status_t nfsstat3_to_fsalstat(nfsstat3 status); fsal_status_t nlm4stat_to_fsalstat(nlm4_stats status); bool attrmask_is_posix(attrmask_t mask); bool fattr3_to_fsalattr(const fattr3 *attrs, struct fsal_attrlist *fsal_attrs_out); bool fsalattr_to_sattr3(const struct fsal_attrlist *fsal_attrs, const bool allow_rawdev, sattr3 *attrs_out); void pre_attrs_to_fsalattr(const pre_op_attr *pre_attrs, struct fsal_attrlist *out_attrs); void post_attrs_to_fsalattr(const post_op_attr *post_attrs, struct fsal_attrlist *out_attrs); #endif nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V3/rpc.c000066400000000000000000000723331473756622300201470ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright 2020-2021 Google LLC * Author: Solomon Boulos * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include "nfs23.h" #include "proxyv3_fsal_methods.h" static uint32_t global_xid; static char rpcMachineName[MAXHOSTNAMELEN + 1] = { 0 }; static pthread_mutex_t rpcLock; static unsigned int rpcNumSockets; /* Resizable buffer (capacity is allocated, len is used). */ struct rpc_buf { char *buf; size_t capacity; size_t len; }; /* An entry in our pool of sockets/buffers */ struct fd_entry { bool in_use; bool is_open; /* Reuse needs to match the socket/socklen/port. */ sockaddr_t socket; socklen_t socklen; uint16_t port; int fd; struct rpc_buf rpc_buf; }; /* @todo Replace with free list / hash table / whatever. */ struct fd_entry *fd_entries; /** * @brief Setup our RPC "stack" for PROXY_V3. * * @return - True, if no error. * - False, with emitted warnings, otherwise. */ bool proxyv3_rpc_init(const uint num_sockets) { /* Initialise only once. */ static bool rpc_initialised; if (rpc_initialised) return true; LogDebug(COMPONENT_FSAL, "Setting up connection pool with %u sockets", num_sockets); /* Cache our hostname for client auth later. */ if (gethostname(rpcMachineName, sizeof(rpcMachineName)) != 0) { const char *kClientName = "127.0.0.1"; LogCrit(COMPONENT_FSAL, "gethostname() failed. Errno %d (%s). Hardcoding a client IP instead.", errno, strerror(errno)); memcpy(rpcMachineName, kClientName, strlen(kClientName) + 1 /* For NUL */); } PTHREAD_MUTEX_init(&rpcLock, NULL); /* Initialize the fd_entries with not in_use sockets. */ rpcNumSockets = num_sockets; fd_entries = gsh_calloc(rpcNumSockets, sizeof(struct fd_entry)); /* Just in case the alloc failed, bail. */ rpc_initialised = (fd_entries != NULL); /* Could be constant but just to be on the safe side */ srand(time(NULL)); global_xid = (uint32_t)rand(); return rpc_initialised; } /** * @brief Given a host:port pair, try to open a socket. * * @param host Backend NFS host (as a sockaddr) * @param socklen Length of the host sockaddr (for IPv6 vs IPv4). * @param port The Port to connect to. * * @return - A valid fd on success, -1 otherwise. */ static int proxyv3_openfd(const struct sockaddr *host, const socklen_t socklen, uint16_t port) { int rc; LogDebug(COMPONENT_FSAL, "Opening a new socket"); if (host->sa_family != AF_INET && host->sa_family != AF_INET6) { LogCrit(COMPONENT_FSAL, "passed a host with sa_family %u", host->sa_family); return -1; } char addrForErrors[INET6_ADDRSTRLEN] = { 0 }; /* * Strangely, inet_ntop takes the length of the *buffer* not the length * of the socket (perhaps it just uses the sa_family for socklen) */ if (!inet_ntop(host->sa_family, host, addrForErrors, INET6_ADDRSTRLEN)) { LogCrit(COMPONENT_FSAL, "Couldn't decode host socket for debugging"); return -1; } bool ipv6 = host->sa_family == AF_INET6; size_t expected_len = (ipv6) ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in); if (socklen != expected_len) { LogCrit(COMPONENT_FSAL, "Given an ipv%s sockaddr (%s) with len %u != %zu", (ipv6) ? "6" : "4", addrForErrors, socklen, expected_len); return -1; } sockaddr_t hostAndPort; memset(&hostAndPort, 0, sizeof(hostAndPort)); /* Copy the input, and then override the port. */ memcpy(&hostAndPort, host, socklen); struct sockaddr_in *hostv4 = (struct sockaddr_in *)&hostAndPort; struct sockaddr_in6 *hostv6 = (struct sockaddr_in6 *)&hostAndPort; /* Check that the caller is letting us slip the port in. */ if ((ipv6 && hostv6->sin6_port != 0) || (!ipv6 && hostv4->sin_port != 0)) { unsigned int port = (ipv6) ? hostv6->sin6_port : hostv4->sin_port; LogCrit(COMPONENT_FSAL, "passed an address (%s) with non-zero port %u", addrForErrors, port); return -1; } int fd = socket((ipv6) ? PF_INET6 /* IPv6 */ : PF_INET /* IPv4 */, SOCK_STREAM /* TCP */, 0 /* Pick it up from TCP */); if (fd < 0) { LogCrit(COMPONENT_FSAL, "Failed to create a socket. %d %s", errno, strerror(errno)); return -1; } /* * NOTE(boulos): NFS daemons like nfsd in Linux require that the * clients come from a privileged port, so that they "must" be run * as root on the client. * * NOTE(boulos): Some bindresvport_sa implementations are *also* not * thread-safe (including libntirpc). So we need to hold a lock around * calling it. Our only caller (proxyv3_getfdentry) no longer holds the * rpcLock, so we can use that one. */ if (pthread_mutex_lock(&rpcLock) != 0) { LogCrit(COMPONENT_FSAL, "pthread_mutex_lock failed %d %s", errno, strerror(errno)); close(fd); return -1; } rc = bindresvport_sa(fd, NULL); /* Unlock the rpclock before we exit, even if bindresvport_sa failed */ if (pthread_mutex_unlock(&rpcLock) != 0) { LogCrit(COMPONENT_FSAL, "pthread_mutex_unlock failed %d %s", errno, strerror(errno)); close(fd); return -1; } if (rc < 0) { LogCrit(COMPONENT_FSAL, "Failed to reserve a privileged port. %d %s", errno, strerror(errno)); close(fd); return -1; } if (ipv6) { hostv6->sin6_port = htons(port); } else { hostv4->sin_port = htons(port); } if (connect(fd, (struct sockaddr *)&hostAndPort, socklen) < 0) { LogCrit(COMPONENT_FSAL, "Failed to connect to host '%s'. errno %d (%s)", addrForErrors, errno, strerror(errno)); close(fd); return -1; } LogDebug(COMPONENT_FSAL, "Got a new socket (%d) open to host %s", fd, addrForErrors); return fd; } /** * @brief Check that an fd (from a socket) is open and ready. * * @param fd The file descriptor for our socket. * * @return - True if the socket is open, false otherwise. */ static bool proxyv3_fd_is_open(int fd) { /* * If it's been a long time since we opened the socket, the * other end probably hung up. We peek at the recv buffer here, * to ensure that the socket is still open. If we happen to find * bytes, something horrible must have happened. */ char buf[1]; ssize_t bytes_read; /* * We need both DONTWAIT for non-blocking and PEEK, so we don't * actually pull any data off. */ bytes_read = recv(fd, buf, sizeof(buf), MSG_DONTWAIT | MSG_PEEK); if (bytes_read == -1 && ((errno == EAGAIN || errno == EWOULDBLOCK))) { /* We would block => the socket is open! */ LogFullDebug(COMPONENT_FSAL, "Socket %d was still open. Reusing.", fd); return true; } /* * Okay, we can't just re-use the existing socket. So we'll need * a new one, but first report why we did. */ if (bytes_read == 0) { /* The other end closed at some point. */ LogDebug(COMPONENT_FSAL, "Socket %d was closed by the backend.", fd); } else if (bytes_read > 0) { LogCrit(COMPONENT_FSAL, "Unexpected data left in socket %d.", fd); } else { /* Some other error. Log and exit. */ LogCrit(COMPONENT_FSAL, "Checking that socket %d was open had an error: %d '%s'.", fd, errno, strerror(errno)); } return false; } /** * @brief Create an rpc_buf with a given capacity. * * @param rpc_buf Input rpc_buf pointer. * @param capacity The desired capacity in bytes. */ static void proxyv3_rpcBuf_create(struct rpc_buf *rpc_buf, size_t capacity) { rpc_buf->buf = gsh_calloc(1, capacity); rpc_buf->capacity = capacity; rpc_buf->len = 0; } /** * @brief Resize an rpc_buf to be len bytes long (realloc'ing if needed). * * @param rpc_buf Input rpc_buf pointer. * @param len The desired length in bytes. * * @return - The underlying buffer pointer (potentially realloc'ed). */ static char *proxyv3_rpcBuf_resize(struct rpc_buf *rpc_buf, size_t len) { if (rpc_buf->capacity < len) { /* * Need to grow the buffer. NOTE(boulos): Unlike std::vector * this isn't going to be used in a loop growing byte by byte or * something, so while we could round up the requested length, * we're unlikely to get N^2 style re-allocs. */ rpc_buf->buf = gsh_realloc(rpc_buf->buf, len); rpc_buf->capacity = len; } rpc_buf->len = len; return rpc_buf->buf; } /** * @brief Given a host:port pair, try to get/open an fd_entry from our pool. * * @param host Backend NFS host (as a sockaddr) * @param socklen Length of the host sockaddr (for IPv6 vs IPv4). * @param port The port to connect to. * @param retry Whether or not the caller should retry later. * * @return - A valid fd_entry on success, NULL otherwise. */ static struct fd_entry *proxyv3_getfdentry(const struct sockaddr *host, const socklen_t socklen, uint16_t port, bool *retry) { /* In case we fail catastrophically, don't suggest a retry. */ *retry = false; if (pthread_mutex_lock(&rpcLock) != 0) { LogCrit(COMPONENT_FSAL, "pthread_mutex_lock failed %d %s", errno, strerror(errno)); return NULL; } LogFullDebug(COMPONENT_FSAL, "Looking for an open socket for port %" PRIu16, port); /* Find the first free, preferably open socket */ struct fd_entry *first_free = NULL; struct fd_entry *first_open = NULL; struct fd_entry *result = NULL; size_t i; for (i = 0; i < rpcNumSockets; i++) { struct fd_entry *entry = &fd_entries[i]; if (entry->in_use) { continue; } /* Remember that we saw a free slot. */ if (first_free == NULL) { first_free = entry; } /* NOTE(boulos): first_free is now definitely not NULL. */ if (!entry->is_open) { /* * This entry is a free and not even opened slot, prefer * that over our existing first_free if that one was * open, so we allow socket reuse by others. */ if (first_free->is_open == true) { first_free = entry; } } else { /* See if this open socket matches what we need. */ if (entry->socklen == socklen && entry->port == port && memcmp(&entry->socket, host, socklen) == 0) { LogFullDebug( COMPONENT_FSAL, "Found an already open socket, will reuse that"); first_open = entry; break; } } } /* The list is full! The caller needs to block. */ if (first_free == NULL) { LogFullDebug(COMPONENT_FSAL, "No available sockets. Tell the caller to wait"); if (pthread_mutex_unlock(&rpcLock) != 0) { LogCrit(COMPONENT_FSAL, "pthread_mutex_unlock failed %d %s", errno, strerror(errno)); return NULL; } *retry = true; return NULL; } /* Grab our result entry, and mark it as in use. */ result = (first_open != NULL) ? first_open : first_free; result->in_use = true; /* Release the lock now that we got our entry. */ if (pthread_mutex_unlock(&rpcLock) != 0) { LogCrit(COMPONENT_FSAL, "pthread_mutex_unlock failed %d %s", errno, strerror(errno)); /* * Return the entry to the list, since we aren't going to end up * using it. */ result->in_use = false; return NULL; } /* If we already got one, return it, if it's still open. */ if (first_open != NULL && proxyv3_fd_is_open(result->fd)) { return result; } if (result->is_open) { /* We should first close the existing socket. */ LogFullDebug(COMPONENT_FSAL, "Closing fd %d before we re-use the slot", result->fd); if (close(result->fd) != 0) { LogCrit(COMPONENT_FSAL, "close(%d) of re-used fd failed. Continuing. Errno %d (%s)", result->fd, errno, strerror(errno)); } /* Mark the entry as no longer open. */ result->is_open = false; } /* Allocate a buffer, if we've never done so. */ if (result->rpc_buf.buf == NULL) { /* * First time create. NOTE(boulos): We wait to allocate this * until its needed, because we want maxwrite to be filled in to * match the NFS FSINFO result (which it's not during rpc init). */ const uint kHeaderPadding = 512; const uint kBufSize = PROXY_V3.module.fs_info.maxwrite + kHeaderPadding; proxyv3_rpcBuf_create(&result->rpc_buf, kBufSize); } /* * No matter what, mark the buffer as having 0 bytes in use so far * (capacity will remain unchanged). */ proxyv3_rpcBuf_resize(&result->rpc_buf, 0); int fd = proxyv3_openfd(host, socklen, port); if (fd < 0) { /* * Failed for some reason. Mark this slot as empty, but leave * the memory buffer alone. NOTE(boulos): retry is still false. */ result->in_use = false; return NULL; } /* Fill in the socket info. */ result->fd = fd; result->is_open = true; memcpy(&result->socket, host, socklen); result->socklen = socklen; result->port = port; return result; } /** * @brief Given a host:port pair, try to open a socket (w/ exponential backoff). * * @param host Backend NFS host (as a sockaddr) * @param socklen Length of the host sockaddr (for IPv6 vs IPv4). * @param port The port to connect to. * * @return - A valid fd_entry pointer on success * - NULL, otherwise. */ static struct fd_entry *proxyv3_getfd_blocking(const struct sockaddr *host, const socklen_t socklen, uint16_t port) { const size_t kMaxIterations = 100; /* * So, within a datacenter, it's likely that we'll need to wait about 1 * millisecond for someone to finish. Let's start the backoff sooner * though at 256 microseconds, because while an end-to-end op is 1ms, * people should be finishing all the time. For folks across a WAN, * we'll back off quickly enough anyway. */ size_t numMicros = 256; /* Don't back off to more than 10 ms aka 10000 microsecond sleeps. */ const size_t maxMicros = 10000; size_t i; for (i = 0; i < kMaxIterations; i++) { bool retry = false; struct fd_entry *entry = proxyv3_getfdentry(host, socklen, port, &retry); /* If we got back a valid entry, return it. */ if (entry != NULL) { return entry; } /* If we not told to retry, exit. */ if (!retry) { return NULL; } /* We were told to retry, let's wait. */ struct timespec how_long = { /* 1M micros per second */ .tv_sec = numMicros / 1000000, /* Remainder => nanoseconds */ .tv_nsec = (numMicros % 1000000) * 1000 }; LogFullDebug(COMPONENT_FSAL, "Going to sleep for %zu microseconds", numMicros); if (nanosleep(&how_long, NULL) != 0) { /* * Let interrupts wake us up and not care. Anything else * should be fatal. */ if (errno != EINTR) { LogCrit(COMPONENT_FSAL, "nanosleep failed. Asked for %zu micros. Errno %d (%s)", numMicros, errno, strerror(errno)); return NULL; } } /* Next time around, double it. */ numMicros *= 2; if (numMicros > maxMicros) { numMicros = maxMicros; } } LogCrit(COMPONENT_FSAL, "Failed to ever acquire a new fd, dying"); return NULL; } /** * @brief Release an fd_entry to our pool (optionally closing the socket). * * @param entry An fd_entry pointer for an entry in our pool. * @param force_close Whether to always close the socket. * * @return - True, if we successfully cleaned up the entry. * - False, otherwise. */ static bool proxyv3_release_fdentry(struct fd_entry *entry, bool force_close) { LogFullDebug(COMPONENT_FSAL, "Releasing fd %d back into the pool (close = %s)", entry->fd, (force_close) ? "T" : "F"); if (pthread_mutex_lock(&rpcLock) != 0) { LogCrit(COMPONENT_FSAL, "pthread_mutex_lock failed %d %s", errno, strerror(errno)); return false; } if (entry->in_use != true) { LogCrit(COMPONENT_FSAL, "Tried to release entry (fd %d) that wasn't in_use!", entry->fd); } else { /* * Mark the entry as no longer in use. (But leave it open, * unless asked not to). */ entry->in_use = false; if (force_close) { /* Close the socket first. */ if (close(entry->fd) < 0) { LogCrit(COMPONENT_FSAL, "close(%d) failed. Errno %d (%s)", entry->fd, errno, strerror(errno)); } /* Clear the bytes that were *touched* not allocated. */ memset(entry->rpc_buf.buf, 0, entry->rpc_buf.len); entry->is_open = false; } } if (pthread_mutex_unlock(&rpcLock) != 0) { LogCrit(COMPONENT_FSAL, "pthread_mutex_unlock failed %d %s", errno, strerror(errno)); return false; } return true; } /* * NOTE(boulos): proxyv3_call is basically rpc_call redone by hand, because * ganesha's NFSD hijacks the RPC setup to the point where we can't issue our * own NFS-related rpcs as a simple client via clnt_ncreate (internally, * svc_exprt_lookup explodes saying "fd %d max_connections 0 exceeded"). */ /** * @brief Send an RPC to host and get a reply, handling XDR encode/decode. * * @param host Backend NFS host (as a sockaddr) * @param socklen Length of the host sockaddr (for IPv6 vs IPv4). * @param port The port to connect to. * @param creds Optional credentials for auth. * @param rpcProgram The RPC Program (e.g., MOUNTPROG). * @param rpcVersion The RPC Version (e.g., NFS_V3). * @param rpcProc The RPC Procedure (e.g., NFSPROC3_LOOKUP). * @param encodeFunc The XDR encoding function (e.g., xdr_LOOKUP3args). * @param args The arg data (passed to encodeFunc). * @param decodeFunc The XDR decoding function (e.g., xdr_LOOKUP3res) * @param output The output buffer (passed to decodeFunc). * * @return - True, if no error. * - False, with emitted warnings, otherwise. */ bool proxyv3_call(const struct sockaddr *host, const socklen_t socklen, uint16_t port, const struct user_cred *creds, const rpcprog_t rpcProgram, const rpcvers_t rpcVersion, const rpcproc_t rpcProc, const xdrproc_t encodeFunc, void *args, const xdrproc_t decodeFunc, void *output) { XDR x; struct rpc_msg rmsg; struct rpc_msg reply; struct fd_entry *fd_entry; /* Have a unique XID on each call */ uint32_t xid = atomic_inc_uint32_t(&global_xid); /* Log on entry, so we know what we were doing before we open the fd. */ LogFullDebug(COMPONENT_FSAL, "Sending an RPC: Program = %" PRIu32 ", Version = %" PRIu32 ", Procedure = %" PRIu32 ", XID = %" PRIx32, rpcProgram, rpcVersion, rpcProc, xid); fd_entry = proxyv3_getfd_blocking(host, socklen, port); if (fd_entry == NULL) { /* * Failed to get an fd even after blocking. Something probably * went wrong. */ return false; } int fd = fd_entry->fd; char *msgBuf = fd_entry->rpc_buf.buf; size_t bufSize = fd_entry->rpc_buf.capacity; AUTH *au; if (creds != NULL) { au = authunix_ncreate(rpcMachineName, creds->caller_uid, creds->caller_gid, creds->caller_glen, creds->caller_garray); } else { /* * Let ganesha do lots of syscalls to figure out our machiine * name, uid, gid and so on. */ LogFullDebug(COMPONENT_FSAL, "rpc, no creds => authunix_ncreate_default()"); au = authunix_ncreate_default(); } rmsg.rm_xid = xid; rmsg.rm_direction = CALL; rmsg.rm_call.cb_rpcvers = RPC_MSG_VERSION; /* *RPC* version not NFS */ rmsg.cb_prog = rpcProgram; rmsg.cb_vers = rpcVersion; rmsg.cb_proc = rpcProc; rmsg.cb_cred = au->ah_cred; rmsg.cb_verf = au->ah_verf; memset(&x, 0, sizeof(x)); /* * Setup x with our buffer for encoding. Keep space at the front for the * u_int recmark. */ xdrmem_create(&x, msgBuf + sizeof(u_int), bufSize - sizeof(u_int), XDR_ENCODE); if (!xdr_callmsg(&x, &rmsg)) { LogCrit(COMPONENT_FSAL, "Failed to Setup xdr_callmsg"); proxyv3_release_fdentry(fd_entry, true /* force close */); AUTH_DESTROY(au); return false; } if (!encodeFunc(&x, args)) { LogCrit(COMPONENT_FSAL, "Failed to xdr-encode the args"); proxyv3_release_fdentry(fd_entry, true /* force close */); AUTH_DESTROY(au); return false; } /* Extract out the position to encode the record marker. */ u_int pos = xdr_getpos(&x); u_int recmark = ntohl(pos | (1U << 31)); /* Write the recmark at the start of the buffer */ memcpy(msgBuf, &recmark, sizeof(recmark)); /* Send the message plus the recmark. */ size_t bytes_to_send = pos + sizeof(recmark); /* * xdrmem_create should have respected our length parameter. Make sure, * before we note via resize how many bytes we filled in. */ if (fd_entry->rpc_buf.capacity < bytes_to_send) { LogCrit(COMPONENT_FSAL, "xdrmem_create produced %zu bytes to send for our %zu-byte buffer", bytes_to_send, fd_entry->rpc_buf.capacity); proxyv3_release_fdentry(fd_entry, true /* force close */); AUTH_DESTROY(au); return false; } /* Do the actual "resize". */ (void)proxyv3_rpcBuf_resize(&fd_entry->rpc_buf, bytes_to_send); LogFullDebug(COMPONENT_FSAL, "Sending XID %" PRIu32 " with %zu bytes", rmsg.rm_xid, bytes_to_send); size_t total_bytes_written = 0; while (total_bytes_written < bytes_to_send) { size_t remaining = bytes_to_send - total_bytes_written; ssize_t bytes_written = write(fd, msgBuf + total_bytes_written, remaining); if (bytes_written < 0) { LogCrit(COMPONENT_FSAL, "Write at %zu failed (remaining was %zu). Errno %d (%s)", total_bytes_written, remaining, errno, strerror(errno)); proxyv3_release_fdentry(fd_entry, true /* force close */); AUTH_DESTROY(au); return false; } total_bytes_written += bytes_written; } /* Cleanup the auth struct. We're just reading from here on out. */ AUTH_DESTROY(au); /* Aww, short write. Exit. */ if (total_bytes_written != bytes_to_send) { LogCrit(COMPONENT_FSAL, "Only wrote %zu bytes out of %zu", total_bytes_written, bytes_to_send); proxyv3_release_fdentry(fd_entry, true /* force close */); return false; } /* Now flip it around and get the reply. */ struct { uint recmark; uint xid; } response_header; LogFullDebug(COMPONENT_FSAL, "Let's go ask for a response."); /* First try to read just the response "header". */ if (read(fd, &response_header, 8) != 8) { LogCrit(COMPONENT_FSAL, "Didn't get a response header. errno %d, errstring %s", errno, strerror(errno)); proxyv3_release_fdentry(fd_entry, true /* force close */); return false; } /* Flip endian-ness if required */ response_header.recmark = ntohl(response_header.recmark); response_header.xid = ntohl(response_header.xid); LogFullDebug(COMPONENT_FSAL, "Got recmark %" PRIx32 " (%" PRIu32 " bytes) xid %" PRIu32, response_header.recmark, response_header.recmark & ~(1U << 31), response_header.xid); if (response_header.xid != xid) { LogCrit(COMPONENT_FSAL, "Response xid %" PRIu32 " != request %" PRIu32, response_header.xid, xid); proxyv3_release_fdentry(fd_entry, true /* force close */); return false; } /* Clear the top bit of the recmark */ response_header.recmark &= ~(1U << 31); if (response_header.recmark < 8) { LogCrit(COMPONENT_FSAL, "Response claims to only have %" PRIu32 " bytes", response_header.recmark); proxyv3_release_fdentry(fd_entry, true /* force close */); return false; } /* We've already read the header (record mark) and xid. */ size_t bytes_to_read = response_header.recmark; size_t total_bytes_read = 4; size_t read_buffer_size = bytes_to_read + sizeof(xid); /* * We're going to need to read `read_buffer_size` bytes. Resize the * buffer if needed to let us slurp the whole response back. */ msgBuf = proxyv3_rpcBuf_resize(&fd_entry->rpc_buf, read_buffer_size); /* Write the xid into the buffer. */ memcpy(msgBuf, &xid, sizeof(xid)); LogFullDebug(COMPONENT_FSAL, "Going to read the remaining %zu bytes", bytes_to_read - total_bytes_read); while (total_bytes_read < bytes_to_read) { ssize_t bytes_read = read(fd, msgBuf + total_bytes_read, bytes_to_read - total_bytes_read); if (bytes_read < 0) { LogCrit(COMPONENT_FSAL, "Read at %zu failed. Errno %d (%s)", total_bytes_read, errno, strerror(errno)); proxyv3_release_fdentry(fd_entry, true /* force close */); return false; } total_bytes_read += bytes_read; } /* Aww, short read. Exit. */ if (total_bytes_read != bytes_to_read) { LogCrit(COMPONENT_FSAL, "Only read %zu bytes out of %zu", total_bytes_read, bytes_to_read); proxyv3_release_fdentry(fd_entry, true /* force close */); return false; } LogFullDebug(COMPONENT_FSAL, "Got all the bytes, time to decode"); /* Lets decode the reply. */ memset(&x, 0, sizeof(x)); xdrmem_create(&x, msgBuf, total_bytes_read, XDR_DECODE); memset(&reply, 0, sizeof(reply)); reply.RPCM_ack.ar_results.proc = decodeFunc; reply.RPCM_ack.ar_results.where = output; bool decoded = xdr_replymsg(&x, &reply); bool success = decoded && reply.rm_reply.rp_stat == MSG_ACCEPTED && reply.rm_reply.rp_acpt.ar_stat == SUCCESS; /* If we failed to decode, say so. */ if (!decoded) { LogCrit(COMPONENT_FSAL, "Failed to do xdr_replymsg"); } /* Check that it was accepted, if not, say why not. */ if (reply.rm_reply.rp_stat != MSG_ACCEPTED) { LogCrit(COMPONENT_FSAL, "Reply received but not accepted. REJ %d", reply.rm_reply.rp_rjct.rj_stat); } /* Check that it was accepted with success. */ if (reply.rm_reply.rp_acpt.ar_stat != SUCCESS) { LogCrit(COMPONENT_FSAL, "Reply accepted but unsuccessful. Reason %d", reply.rm_reply.rp_acpt.ar_stat); } /* * Clean up whatever xdr_replymsg may have allocated, but don't smash * the data in the output buffer. */ reply.RPCM_ack.ar_results.proc = (xdrproc_t)xdr_void; reply.RPCM_ack.ar_results.where = NULL; xdr_free((xdrproc_t)xdr_replymsg, &reply); /* Return our socket and buffer to the pool. */ proxyv3_release_fdentry(fd_entry, false /* let's reuse the socket */); LogFullDebug(COMPONENT_FSAL, "RPC Completed %s: Program = %" PRIu32 ", Version = %" PRIu32 ", Procedure = %" PRIu32, (success) ? "SUCCESSFULLY" : " but FAILED", rpcProgram, rpcVersion, rpcProc); return success; } /** * @brief Wrapper around proxyv3_call for NFS v3. */ bool proxyv3_nfs_call(const struct sockaddr *host, const socklen_t socklen, const uint nfsdPort, const struct user_cred *creds, const rpcproc_t nfsProc, const xdrproc_t encodeFunc, void *args, const xdrproc_t decodeFunc, void *output) { const int kProgramNFS = NFS_PROGRAM; const int kVersionNFSv3 = NFS_V3; return proxyv3_call(host, socklen, nfsdPort, creds, kProgramNFS, kVersionNFSv3, nfsProc, encodeFunc, args, decodeFunc, output); } /** * @brief Wrapper around proxyv3_call for MOUNT v3. */ bool proxyv3_mount_call(const struct sockaddr *host, const socklen_t socklen, const uint mountdPort, const struct user_cred *creds, const rpcproc_t mountProc, const xdrproc_t encodeFunc, void *args, const xdrproc_t decodeFunc, void *output) { const int kProgramMount = MOUNTPROG; const int kVersionMountv3 = MOUNT_V3; return proxyv3_call(host, socklen, mountdPort, creds, kProgramMount, kVersionMountv3, mountProc, encodeFunc, args, decodeFunc, output); } /** * @brief Wrapper around proxyv3_call for NLM v4. */ bool proxyv3_nlm_call(const struct sockaddr *host, const socklen_t socklen, const uint nlmPort, const struct user_cred *creds, const rpcproc_t nlmProc, const xdrproc_t encodeFunc, void *args, const xdrproc_t decodeFunc, void *output) { const int kProgramNLM = NLMPROG; const int kVersionNLMv4 = NLM4_VERS; return proxyv3_call(host, socklen, nlmPort, creds, kProgramNLM, kVersionNLMv4, nlmProc, encodeFunc, args, decodeFunc, output); } /** * @brief Ask portmapd for where MOUNTD and NFSD are running. * * @param host Backend NFS host (as a sockaddr) * @param socklen Length of the host sockaddr (for IPv6 vs IPv4). * @param mountd_port Port for MOUNTD. * @param nfsd_port Port for NFS (v3). * @param nlm_port Port for NLM. * * @return - True, if no error. * - False, with emitted warnings, otherwise. */ bool proxyv3_find_ports(const struct sockaddr *host, const socklen_t socklen, u_int *mountd_port, u_int *nfsd_port, u_int *nlm_port) { struct pmap mountd_query = { .pm_prog = MOUNTPROG, .pm_vers = MOUNT_V3, .pm_prot = IPPROTO_TCP, .pm_port = 0 /* ignored for getport */ }; struct pmap nfsd_query = { .pm_prog = NFS_PROGRAM, .pm_vers = NFS_V3, .pm_prot = IPPROTO_TCP, .pm_port = 0 /* ignored */ }; struct pmap nlm_query = { .pm_prog = NLMPROG, .pm_vers = NLM4_VERS, .pm_prot = IPPROTO_TCP, .pm_port = 0 /* ignored */ }; struct { struct pmap *input; u_int *port; const char *name; } queries[] = { { &mountd_query, mountd_port, "mountd" }, { &nfsd_query, nfsd_port, "nfsd" }, /* If we put NLM last, we can let it just warn in debug mode. */ { &nlm_query, nlm_port, "nlm" } }; size_t i; for (i = 0; i < sizeof(queries) / sizeof(queries[0]); i++) { LogDebug(COMPONENT_FSAL, "Asking portmap to tell us what the %s/tcp port is", queries[i].name); if (!proxyv3_call(host, socklen, PMAPPORT, NULL /* no auth for portmapd */, PMAPPROG, PMAPVERS, PMAPPROC_GETPORT, (xdrproc_t)xdr_pmap, queries[i].input, (xdrproc_t)xdr_u_int, queries[i].port)) { LogDebug(COMPONENT_FSAL, "Failed to find %s", queries[i].name); return false; } LogDebug(COMPONENT_FSAL, "Got back %s port %" PRIu32, queries[i].name, *queries[i].port); } return true; } nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V3/utils.c000066400000000000000000000262731473756622300205250ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright 2020 Google LLC * Author: Solomon Boulos * * This program 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 3 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 * 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 * * ------------- */ #include "nfs23.h" #include "fsal_convert.h" #include "proxyv3_fsal_methods.h" /** * @brief Map from nfsstat3 error codes to the FSAL error codes. * @param status Input status as an nfsstat3. * * @return - A corresponding fsal_errors_t, if one makes sense. * - ERR_FSAL_INVAL, otherwise. */ static fsal_errors_t nfsstat3_to_fsal(nfsstat3 status) { switch (status) { /* * Most of these have identical enum values, but do this * explicitly anyway. */ case NFS3_OK: return ERR_FSAL_NO_ERROR; case NFS3ERR_PERM: return ERR_FSAL_PERM; case NFS3ERR_NOENT: return ERR_FSAL_NOENT; case NFS3ERR_IO: return ERR_FSAL_IO; case NFS3ERR_NXIO: return ERR_FSAL_NXIO; case NFS3ERR_ACCES: return ERR_FSAL_ACCESS; case NFS3ERR_EXIST: return ERR_FSAL_EXIST; case NFS3ERR_XDEV: return ERR_FSAL_XDEV; /* * FSAL doesn't have NODEV, but NXIO is "No such device or * address" */ case NFS3ERR_NODEV: return ERR_FSAL_NXIO; case NFS3ERR_NOTDIR: return ERR_FSAL_NOTDIR; case NFS3ERR_ISDIR: return ERR_FSAL_ISDIR; case NFS3ERR_INVAL: return ERR_FSAL_INVAL; case NFS3ERR_FBIG: return ERR_FSAL_FBIG; case NFS3ERR_NOSPC: return ERR_FSAL_NOSPC; case NFS3ERR_ROFS: return ERR_FSAL_ROFS; case NFS3ERR_MLINK: return ERR_FSAL_MLINK; case NFS3ERR_NAMETOOLONG: return ERR_FSAL_NAMETOOLONG; case NFS3ERR_NOTEMPTY: return ERR_FSAL_NOTEMPTY; case NFS3ERR_DQUOT: return ERR_FSAL_DQUOT; case NFS3ERR_STALE: return ERR_FSAL_STALE; /* * FSAL doesn't have REMOTE (too many remotes), so just return * NAMETOOLONG. */ case NFS3ERR_REMOTE: return ERR_FSAL_NAMETOOLONG; case NFS3ERR_BADHANDLE: return ERR_FSAL_BADHANDLE; /* FSAL doesn't have NOT_SYNC, so... INVAL? */ case NFS3ERR_NOT_SYNC: return ERR_FSAL_INVAL; case NFS3ERR_BAD_COOKIE: return ERR_FSAL_BADCOOKIE; case NFS3ERR_NOTSUPP: return ERR_FSAL_NOTSUPP; case NFS3ERR_TOOSMALL: return ERR_FSAL_TOOSMALL; case NFS3ERR_SERVERFAULT: return ERR_FSAL_SERVERFAULT; case NFS3ERR_BADTYPE: return ERR_FSAL_BADTYPE; /* * FSAL doesn't have a single JUKEBOX error, so choose * ERR_FSAL_LOCKED. */ case NFS3ERR_JUKEBOX: return ERR_FSAL_LOCKED; } /* Shouldn't have gotten here with valid input. */ return ERR_FSAL_INVAL; } /** * @brief Map from nlm4_stats error codes to the FSAL error codes. * @param status Input status as an nlm4_stats. * * @return - A corresponding fsal_errors_t, if one makes sense. * - ERR_FSAL_INVAL, otherwise. */ static fsal_errors_t nlm4stat_to_fsal(nlm4_stats status) { switch (status) { case NLM4_GRANTED: return ERR_FSAL_NO_ERROR; /* * We want NLM4_DENIED to convert to STATE_LOCK_CONFLICT in * state_error_convert. */ case NLM4_DENIED: return ERR_FSAL_DELAY; /* No "space" to allocate. */ case NLM4_DENIED_NOLOCKS: return ERR_FSAL_NOSPC; case NLM4_BLOCKED: return ERR_FSAL_BLOCKED; case NLM4_DENIED_GRACE_PERIOD: return ERR_FSAL_IN_GRACE; case NLM4_DEADLCK: return ERR_FSAL_DEADLOCK; case NLM4_ROFS: return ERR_FSAL_ROFS; case NLM4_STALE_FH: return ERR_FSAL_STALE; case NLM4_FBIG: return ERR_FSAL_FBIG; /* Don't retry. */ case NLM4_FAILED: return ERR_FSAL_PERM; } /* Shouldn't get here. */ return ERR_FSAL_INVAL; } /** * @brief Map from nfsstat3 error codes to fsal_status_t. * @param status Input nfsstat3 status. * * @return - Corresponding fsal_status_t. */ fsal_status_t nfsstat3_to_fsalstat(nfsstat3 status) { fsal_errors_t rc = nfsstat3_to_fsal(status); return fsalstat(rc, (rc == ERR_FSAL_INVAL) ? (int)status : 0); } /** * @brief Map from nlm4_stats error codes to fsal_status_t. * @param status Input nlm4_stats status. * * @return - Corresponding fsal_status_t. */ fsal_status_t nlm4stat_to_fsalstat(nlm4_stats status) { fsal_errors_t rc = nlm4stat_to_fsal(status); return fsalstat(rc, (rc == ERR_FSAL_INVAL) ? (int)status : 0); } /** * @brief Determine if an attribute mask is POSIX which is NFS3 plus the change * attr) only. * @param mask Input attrmask_t of attributes. * * @return - True, if the attributes are representable in NFSv3. * - False, otherwise. */ bool attrmask_is_posix(attrmask_t mask) { /* * NOTE(boulos): Consider contributing this as FSAL_ONLY_MASK or * something. */ attrmask_t orig = mask; if (FSAL_UNSET_MASK(mask, ATTRS_POSIX | ATTR_RDATTR_ERR) != 0) { LogDebug(COMPONENT_FSAL, "requested = %0" PRIx64 "\tNFS3 = %0" PRIx64 "\tExtra = %0" PRIx64, orig, (attrmask_t)ATTRS_NFS3, mask); return false; } return true; } /** * @brief Determine if an attribute mask is valid for NFSv3. * @param mask Input attrmask_t of attributes. * @param allow_rawdev If rawdev is allowed in Input. * * @return - True, if the attributes are suitable for NFSv3. * - False, otherwise. */ static bool attrmask_valid(const attrmask_t mask, const bool allow_rawdev) { attrmask_t temp = mask; attrmask_t possible = /* mode, uid, gid, size, atime, mtime */ ATTRS_SET_TIME | ATTRS_CREDS | ATTR_SIZE | ATTR_MODE; if (allow_rawdev) { possible |= ATTR_RAWDEV; } if (FSAL_UNSET_MASK(temp, possible)) { LogDebug(COMPONENT_FSAL, "requested = %0" PRIx64 "\tNFS3 = %0" PRIx64 "\tExtra = %0" PRIx64, mask, possible, temp); return false; } /* Make sure that only one of ATIME | ATIME_SERVER is set. */ if (FSAL_TEST_MASK(mask, ATTR_ATIME) && FSAL_TEST_MASK(mask, ATTR_ATIME_SERVER)) { LogDebug(COMPONENT_FSAL, "Error: mask %0" PRIx64 " has both ATIME and ATIME_SERVER", mask); return false; } /* Make sure that only one of MTIME | MTIME_SERVER is set. */ if (FSAL_TEST_MASK(mask, ATTR_MTIME) && FSAL_TEST_MASK(mask, ATTR_MTIME_SERVER)) { LogDebug(COMPONENT_FSAL, "Error: mask %0" PRIx64 " has both MTIME and MTIME_SERVER", mask); return false; } return true; } static void update_attrs_change(struct fsal_attrlist *attrs_in_out) { /* Same logic like in posix2fsal_attributes */ attrs_in_out->change = gsh_time_cmp(&attrs_in_out->mtime, &attrs_in_out->ctime) > 0 ? timespec_to_nsecs(&attrs_in_out->mtime) : timespec_to_nsecs(&attrs_in_out->ctime); attrs_in_out->valid_mask |= ATTR_CHANGE; } /** * @brief Convert an fattr3 to fsal_attrlist. * @param attrs Input attributes as fattr3. * @param fsal_attrs_out Output attributes in FSAL form. * * @return - True, if the attributes are suitable for NFSv3. * - False, otherwise. */ bool fattr3_to_fsalattr(const fattr3 *attrs, struct fsal_attrlist *fsal_attrs_out) { if (!attrmask_is_posix(fsal_attrs_out->request_mask)) { return false; } /* * NOTE(boulos): Since nfs23.h typedefs fattr3 to fsal_attrlist (leaving * fattr3_wire for the real fattr3 from the protocol) this is just a * simple copy. */ *fsal_attrs_out = *attrs; update_attrs_change(fsal_attrs_out); /* Claim that only the POSIX attributes are valid. */ FSAL_SET_MASK(fsal_attrs_out->valid_mask, ATTRS_POSIX); /* @todo Do we have to even do this? The CEPH FSAL does... */ FSAL_SET_MASK(fsal_attrs_out->supported, ATTRS_POSIX); return true; } static void nfs_time_to_timespec(const nfstime3 *in, struct timespec *out) { out->tv_nsec = in->tv_nsec; out->tv_sec = in->tv_sec; } /** * @brief Convert an nfs pre attributes to fsal_attrlist. * @param pre_attrs Input pre attributes. * @param out_attrs Output attributes in FSAL form. * */ void pre_attrs_to_fsalattr(const pre_op_attr *pre_attrs, struct fsal_attrlist *out_attrs) { if (out_attrs == NULL) return; out_attrs->valid_mask = 0; if (!pre_attrs->attributes_follow) return; nfs_time_to_timespec(&pre_attrs->pre_op_attr_u.attributes.mtime, &out_attrs->mtime); nfs_time_to_timespec(&pre_attrs->pre_op_attr_u.attributes.ctime, &out_attrs->ctime); out_attrs->filesize = pre_attrs->pre_op_attr_u.attributes.size; out_attrs->valid_mask = ATTR_CTIME | ATTR_MTIME | ATTR_SIZE; update_attrs_change(out_attrs); } /** * @brief Convert an nfs post attributes to fsal_attrlist. * @param post_attrs Input post attributes. * @param out_attrs Output attributes in FSAL form. * */ void post_attrs_to_fsalattr(const post_op_attr *post_attrs, struct fsal_attrlist *out_attrs) { if (out_attrs == NULL) return; out_attrs->valid_mask = 0; if (!post_attrs->attributes_follow) return; (void)fattr3_to_fsalattr(&post_attrs->post_op_attr_u.attributes, out_attrs); } /** * @brief Convert an fsal_attrlist to sattr3. * @param fsal_attrs Input attributes as in fsal_attrlist form. * @param allow_rawdev Input rawdev is a possible attribute. * @param attrs_out Output attributes as sattr3. * * @return - True, if the attributes are suitable for NFSv3. * - False, otherwise. */ bool fsalattr_to_sattr3(const struct fsal_attrlist *fsal_attrs, const bool allow_rawdev, sattr3 *attrs_out) { /* * Zero the struct so that all the "set_it" optionals are false by * default. */ memset(attrs_out, 0, sizeof(*attrs_out)); /* Make sure there aren't any additional options we aren't expecting. */ if (!attrmask_valid(fsal_attrs->valid_mask, allow_rawdev)) return false; if (FSAL_TEST_MASK(fsal_attrs->valid_mask, ATTR_MODE)) { attrs_out->mode.set_it = true; attrs_out->mode.set_mode3_u.mode = fsal2unix_mode(fsal_attrs->mode); } if (FSAL_TEST_MASK(fsal_attrs->valid_mask, ATTR_OWNER)) { attrs_out->uid.set_it = true; attrs_out->uid.set_uid3_u.uid = fsal_attrs->owner; } if (FSAL_TEST_MASK(fsal_attrs->valid_mask, ATTR_GROUP)) { attrs_out->gid.set_it = true; attrs_out->gid.set_gid3_u.gid = fsal_attrs->group; } if (FSAL_TEST_MASK(fsal_attrs->valid_mask, ATTR_SIZE)) { attrs_out->size.set_it = true; attrs_out->size.set_size3_u.size = fsal_attrs->filesize; } if (FSAL_TEST_MASK(fsal_attrs->valid_mask, ATTR_ATIME)) { attrs_out->atime.set_it = SET_TO_CLIENT_TIME; attrs_out->atime.set_atime_u.atime.tv_sec = fsal_attrs->atime.tv_sec; attrs_out->atime.set_atime_u.atime.tv_nsec = fsal_attrs->atime.tv_nsec; } else if (FSAL_TEST_MASK(fsal_attrs->valid_mask, ATTR_ATIME_SERVER)) { attrs_out->atime.set_it = SET_TO_SERVER_TIME; } if (FSAL_TEST_MASK(fsal_attrs->valid_mask, ATTR_MTIME)) { attrs_out->mtime.set_it = SET_TO_CLIENT_TIME; attrs_out->mtime.set_mtime_u.mtime.tv_sec = fsal_attrs->mtime.tv_sec; attrs_out->mtime.set_mtime_u.mtime.tv_nsec = fsal_attrs->mtime.tv_nsec; } else if (FSAL_TEST_MASK(fsal_attrs->valid_mask, ATTR_MTIME_SERVER)) { attrs_out->mtime.set_it = SET_TO_SERVER_TIME; } return true; } nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V4/000077500000000000000000000000001473756622300172105ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V4/CMakeLists.txt000066400000000000000000000034621473756622300217550ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- add_definitions( -D__USE_GNU ) ########### next target ############### SET(fsalproxy_v4_LIB_SRCS handle.c main.c export.c xattrs.c ) if(PROXYV4_HANDLE_MAPPING) SET(fsalproxy_v4_LIB_SRCS ${fsalproxy_v4_LIB_SRCS} handle_mapping/handle_mapping.c handle_mapping/handle_mapping_db.c ) endif(PROXYV4_HANDLE_MAPPING) add_library(fsalproxy_v4 MODULE ${fsalproxy_v4_LIB_SRCS}) add_sanitizers(fsalproxy_v4) target_link_libraries(fsalproxy_v4 ganesha_nfsd ${SYSTEM_LIBRARIES} ${LDFLAG_DISALLOW_UNDEF} ) if(PROXYV4_HANDLE_MAPPING) target_link_libraries(fsalproxy_v4 sqlite3) endif(PROXYV4_HANDLE_MAPPING) set_target_properties(fsalproxy_v4 PROPERTIES VERSION 4.2.0 SOVERSION 4) install(TARGETS fsalproxy_v4 COMPONENT fsal DESTINATION ${FSAL_DESTINATION}) ########### install files ############### nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V4/export.c000066400000000000000000000177201473756622300207040ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Max Matveev, 2012 * * This program 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 3 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 * 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 */ /* Export-related methods */ #include "config.h" #include "fsal.h" #include "fsal_convert.h" #include #include #include "gsh_list.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_config.h" #include "proxyv4_fsal_methods.h" #include "nfs_exports.h" #include "export_mgr.h" #ifdef _USE_GSSRPC static struct config_item_list sec_types[] = { CONFIG_LIST_TOK("krb5", RPCSEC_GSS_SVC_NONE), CONFIG_LIST_TOK("krb5i", RPCSEC_GSS_SVC_INTEGRITY), CONFIG_LIST_TOK("krb5p", RPCSEC_GSS_SVC_PRIVACY), CONFIG_LIST_EOL }; #endif static struct config_item proxyv4_export_params[] = { CONF_ITEM_NOOP("name"), CONF_ITEM_UI32("Retry_SleepTime", 0, 60, 10, proxyv4_client_params, retry_sleeptime), CONF_MAND_IP_ADDR("Srv_Addr", "127.0.0.1", proxyv4_client_params, srv_addr), CONF_ITEM_UI32("NFS_Service", 0, UINT32_MAX, 100003, proxyv4_client_params, srv_prognum), CONF_ITEM_UI64("NFS_SendSize", 512 + SEND_RECV_HEADER_SPACE, FSAL_MAXIOSIZE, DEFAULT_MAX_WRITE_READ + SEND_RECV_HEADER_SPACE, proxyv4_client_params, srv_sendsize), CONF_ITEM_UI64("NFS_RecvSize", 512 + SEND_RECV_HEADER_SPACE, FSAL_MAXIOSIZE, DEFAULT_MAX_WRITE_READ + SEND_RECV_HEADER_SPACE, proxyv4_client_params, srv_recvsize), CONF_ITEM_UI16("NFS_Port", 0, UINT16_MAX, 2049, proxyv4_client_params, srv_port), CONF_ITEM_BOOL("Use_Privileged_Client_Port", true, proxyv4_client_params, use_privileged_client_port), CONF_ITEM_UI32("RPC_Client_Timeout", 1, 60 * 4, 60, proxyv4_client_params, srv_timeout), #ifdef _USE_GSSRPC CONF_ITEM_STR("Remote_PrincipalName", 0, MAXNAMLEN, NULL, proxyv4_client_params, remote_principal), CONF_ITEM_STR("KeytabPath", 0, MAXPATHLEN, "/etc/krb5.keytab" proxyv4_client_params, keytab), CONF_ITEM_UI32("Credential_LifeTime", 0, 86400 * 2, 86400, proxyv4_client_params, cred_lifetime), CONF_ITEM_TOKEN("Sec_Type", RPCSEC_GSS_SVC_NONE, sec_types, proxyv4_client_params, sec_type), CONF_ITEM_BOOL("Active_krb5", false, proxyv4_client_params, active_krb5), #endif #ifdef PROXYV4_HANDLE_MAPPING CONF_ITEM_BOOL("Enable_Handle_Mapping", false, proxyv4_client_params, enable_handle_mapping), CONF_ITEM_STR("HandleMap_DB_Dir", 0, MAXPATHLEN, "/var/ganesha/handlemap", proxyv4_client_params, hdlmap.databases_directory), CONF_ITEM_STR("HandleMap_Tmp_Dir", 0, MAXPATHLEN, "/var/ganesha/tmp", proxyv4_client_params, hdlmap.temp_directory), CONF_ITEM_UI32("HandleMap_DB_Count", 1, 16, 8, proxyv4_client_params, hdlmap.database_count), CONF_ITEM_UI32("HandleMap_HashTable_Size", 1, 127, 103, proxyv4_client_params, hdlmap.hashtable_size), #endif CONFIG_EOL }; static int remote_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { struct proxyv4_client_params *pcp = (struct proxyv4_client_params *)link_mem; struct proxyv4_fsal_module *proxyv4_module; proxyv4_module = container_of(op_ctx->fsal_module, struct proxyv4_fsal_module, module); if (proxyv4_module->module.fs_info.maxwrite + SEND_RECV_HEADER_SPACE > pcp->srv_sendsize || proxyv4_module->module.fs_info.maxread + SEND_RECV_HEADER_SPACE > pcp->srv_recvsize) { LogCrit(COMPONENT_CONFIG, "FSAL_PROXY_V4 CONF : maxwrite/maxread + header > Max_SendSize/Max_RecvSize"); err_type->invalid = true; return 1; } /* Other verifications/parameter checking to be added here */ return 0; } static struct config_block proxyv4_export_param = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.proxyv4-export%d", .blk_desc.name = "FSAL", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = proxyv4_export_params, .blk_desc.u.blk.commit = remote_commit }; static inline void proxyv4_export_init(struct proxyv4_export *proxyv4_exp) { proxyv4_exp->rpc.no_sessionid = true; PTHREAD_MUTEX_init(&proxyv4_exp->rpc.proxyv4_clientid_mutex, NULL); PTHREAD_COND_init(&proxyv4_exp->rpc.cond_sessionid, NULL); proxyv4_exp->rpc.rpc_sock = -1; PTHREAD_MUTEX_init(&proxyv4_exp->rpc.listlock, NULL); PTHREAD_COND_init(&proxyv4_exp->rpc.sockless, NULL); PTHREAD_COND_init(&proxyv4_exp->rpc.need_context, NULL); PTHREAD_MUTEX_init(&proxyv4_exp->rpc.context_lock, NULL); } static inline void proxyv4_export_destroy(struct proxyv4_export *proxyv4_exp) { PTHREAD_MUTEX_destroy(&proxyv4_exp->rpc.proxyv4_clientid_mutex); PTHREAD_COND_destroy(&proxyv4_exp->rpc.cond_sessionid); PTHREAD_MUTEX_destroy(&proxyv4_exp->rpc.listlock); PTHREAD_COND_destroy(&proxyv4_exp->rpc.sockless); PTHREAD_COND_destroy(&proxyv4_exp->rpc.need_context); PTHREAD_MUTEX_destroy(&proxyv4_exp->rpc.context_lock); } static void proxyv4_release(struct fsal_export *exp_hdl) { struct proxyv4_export *proxyv4_exp = container_of(exp_hdl, struct proxyv4_export, exp); fsal_detach_export(exp_hdl->fsal, &exp_hdl->exports); free_export_ops(exp_hdl); proxyv4_close_thread(proxyv4_exp); free_io_contexts(proxyv4_exp); proxyv4_export_destroy(proxyv4_exp); gsh_free(proxyv4_exp); } static attrmask_t proxyv4_get_supported_attrs(struct fsal_export *exp_hdl) { return fsal_supported_attrs(&exp_hdl->fsal->fs_info); } void proxyv4_export_ops_init(struct export_ops *ops) { ops->release = proxyv4_release; ops->lookup_path = proxyv4_lookup_path; ops->wire_to_host = proxyv4_wire_to_host; ops->create_handle = proxyv4_create_handle; ops->get_fs_dynamic_info = proxyv4_get_dynamic_info; ops->fs_supported_attrs = proxyv4_get_supported_attrs; ops->alloc_state = proxyv4_alloc_state; } fsal_status_t proxyv4_create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops) { fsal_status_t fsal_status = { 0, 0 }; struct proxyv4_export *exp = gsh_calloc(1, sizeof(*exp)); int rc; /* export initial values */ proxyv4_export_init(exp); /* general export init */ fsal_export_init(&exp->exp); /* proxyv4 export option parsing */ rc = load_config_from_node(parse_node, &proxyv4_export_param, &exp->info, true, err_type); if (rc != 0) { LogCrit(COMPONENT_FSAL, "Incorrect or missing parameters for export %s", CTX_FULLPATH(op_ctx)); fsal_status = fsalstat(ERR_FSAL_INVAL, rc); goto err_free; } /* proxyv4 export init */ proxyv4_export_ops_init(&exp->exp.exp_ops); exp->exp.fsal = fsal_hdl; exp->exp.up_ops = up_ops; op_ctx->fsal_export = &exp->exp; rc = fsal_attach_export(fsal_hdl, &exp->exp.exports); if (rc != 0) { fsal_status = posix2fsal_status(rc); goto err_free; } #ifdef PROXYV4_HANDLE_MAPPING rc = HandleMap_Init(&exp->info.hdlmap); if (rc < 0) { fsal_status = fsalstat(ERR_FSAL_INVAL, -rc); goto err_cleanup; } #endif /* create export client/server connection */ rc = proxyv4_init_rpc(exp); if (rc) { fsal_status = fsalstat(ERR_FSAL_FAULT, rc); goto err_cleanup; } return fsalstat(ERR_FSAL_NO_ERROR, 0); err_cleanup: proxyv4_close_thread(exp); free_io_contexts(exp); fsal_detach_export(fsal_hdl, &exp->exp.exports); err_free: free_export_ops(&exp->exp); proxyv4_export_destroy(exp); gsh_free(exp); return fsal_status; } nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V4/fsal_nfsv4_macros.h000066400000000000000000000714361473756622300230050ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: */ /* * Copyright CEA/DAM/DIF (2007) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: */ /** * @file fsal_nfsv4_macros.h * @author Author: deniel * @date 06/05/2007 * @brief Useful macros to manage NFSv4 call from FSAL_PROXY_V4 * * */ #ifndef _FSAL_NFSV4_MACROS_H #define _FSAL_NFSV4_MACROS_H #include "gsh_rpc.h" #include "nfs4.h" #include "fsal.h" #if 0 #include "fsal_convert.h" #endif #ifndef ARRAY_SIZE #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) #endif #define TIMEOUTRPC { 2, 0 } #define PRINT_HANDLE(tag, handle) \ do { \ if (isFullDebug(COMPONENT_FSAL)) { \ char outstr[1024]; \ snprintHandle(outstr, 1024, handle); \ LogFullDebug(COMPONENT_FSAL, \ "============> %s : handle=%s\n", tag, \ outstr); \ } \ } while (0) /* Free a compound */ #define COMPOUNDV4_ARG_FREE \ do { \ gsh_free(argcompound.argarray_val); \ } while (0) /* OP specific macros */ /** * Notice about NFS4_OP_SEQUENCE argop filling : * As rpc_context and slot are mutualized, sa_slotid and related sa_sequenceid * are place holder filled later on proxyv4_compoundv4_execute function, only * when the free proxyv4_rpc_io_context is chosen. */ #define COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argarray, sessionid, nb_slot) \ do { \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_SEQUENCE; \ memcpy(op->nfs_argop4_u.opsequence.sa_sessionid, sessionid, \ sizeof(sessionid4)); \ op->nfs_argop4_u.opsequence.sa_highest_slotid = nb_slot - 1; \ op->nfs_argop4_u.opsequence.sa_cachethis = false; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_GLOBAL_RECLAIM_COMPLETE(opcnt, argarray) \ do { \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_RECLAIM_COMPLETE; \ op->nfs_argop4_u.opreclaim_complete.rca_one_fs = false; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_CREATE_SESSION(opcnt, argarray, cid, seqid, \ info, sec_parms4) \ do { \ struct channel_attrs4 *fore_attrs; \ struct channel_attrs4 *back_attrs; \ CREATE_SESSION4args *opcreate_session; \ \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_CREATE_SESSION; \ opcreate_session = &op->nfs_argop4_u.opcreate_session; \ opcreate_session->csa_clientid = cid; \ opcreate_session->csa_sequence = seqid; \ opcreate_session->csa_flags = \ CREATE_SESSION4_FLAG_CONN_BACK_CHAN; \ fore_attrs = &opcreate_session->csa_fore_chan_attrs; \ fore_attrs->ca_headerpadsize = 0; \ fore_attrs->ca_maxrequestsize = info->srv_sendsize; \ fore_attrs->ca_maxresponsesize = info->srv_recvsize; \ fore_attrs->ca_maxresponsesize_cached = info->srv_recvsize; \ fore_attrs->ca_maxoperations = NB_MAX_OPERATIONS; \ fore_attrs->ca_maxrequests = NB_RPC_SLOT; \ fore_attrs->ca_rdma_ird.ca_rdma_ird_len = 0; \ fore_attrs->ca_rdma_ird.ca_rdma_ird_val = NULL; \ back_attrs = &opcreate_session->csa_back_chan_attrs; \ back_attrs->ca_headerpadsize = 0; \ back_attrs->ca_maxrequestsize = info->srv_recvsize; \ back_attrs->ca_maxresponsesize = info->srv_sendsize; \ back_attrs->ca_maxresponsesize_cached = info->srv_recvsize; \ back_attrs->ca_maxoperations = NB_MAX_OPERATIONS; \ back_attrs->ca_maxrequests = NB_RPC_SLOT; \ back_attrs->ca_rdma_ird.ca_rdma_ird_len = 0; \ back_attrs->ca_rdma_ird.ca_rdma_ird_val = NULL; \ opcreate_session->csa_cb_program = info->srv_prognum; \ opcreate_session->csa_sec_parms.csa_sec_parms_len = 1; \ (sec_parms4)->cb_secflavor = AUTH_NONE; \ opcreate_session->csa_sec_parms.csa_sec_parms_val = \ (sec_parms4); \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_PUTROOTFH(opcnt, argarray) \ do { \ argarray[opcnt].argop = NFS4_OP_PUTROOTFH; \ opcnt++; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_OPEN_CONFIRM(argcompound, __openseqid, __other, \ __seqid) \ do { \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .argop = NFS4_OP_OPEN_CONFIRM; \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .nfs_argop4_u.opopen_confirm.seqid = __seqid; \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .nfs_argop4_u.opopen_confirm.open_stateid.seqid = \ __openseqid; \ memcpy(argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .nfs_argop4_u.opopen_confirm.open_stateid.other, \ _other, 12); \ argcompound.argarray.argarray_len += 1; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_OPEN_NOCREATE(argcompound, __seqid, inclientid, \ inaccess, inname, __owner_val, \ __owner_len) \ do { \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .argop = NFS4_OP_OPEN; \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .nfs_argop4_u.opopen.seqid = __seqid; \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .nfs_argop4_u.opopen.share_access = \ OPEN4_SHARE_ACCESS_BOTH; \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .nfs_argop4_u.opopen.share_deny = \ OPEN4_SHARE_DENY_NONE; \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .nfs_argop4_u.opopen.owner.clientid = inclientid; \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .nfs_argop4_u.opopen.owner.owner.owner_len = \ __owner_len; \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .nfs_argop4_u.opopen.owner.owner.owner_val = \ __owner_val; \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .nfs_argop4_u.opopen.openhow.opentype = \ OPEN4_NOCREATE; \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .nfs_argop4_u.opopen.claim.claim = CLAIM_NULL; \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .nfs_argop4_u.opopen.claim.open_claim4_u.file = \ inname; \ argcompound.argarray.argarray_len += 1; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_CLOSE_4_1(opcnt, argarray, __stateid) \ do { \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_CLOSE; \ op->nfs_argop4_u.opclose.open_stateid.seqid = __stateid.seqid; \ memcpy(op->nfs_argop4_u.opclose.open_stateid.other, \ __stateid.other, 12); \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_CLOSE_4_1_STATELESS(opcnt, argarray) \ do { \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_CLOSE; \ memset(&op->nfs_argop4_u.opclose.open_stateid, 0, \ sizeof(stateid4)); \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argarray, bitmap) \ do { \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_GETATTR; \ op->nfs_argop4_u.opgetattr.attr_request = bitmap; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_SETATTR(opcnt, argarray, inattr, __other) \ do { \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_SETATTR; \ op->nfs_argop4_u.opsetattr.stateid.seqid = 0; \ memcpy(op->nfs_argop4_u.opsetattr.stateid.other, __other, \ sizeof(op->nfs_argop4_u.opsetattr.stateid.other)); \ op->nfs_argop4_u.opsetattr.obj_attributes = inattr; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_SETATTR_STATELESS(opcnt, argarray, inattr) \ do { \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_SETATTR; \ memset(&op->nfs_argop4_u.opsetattr.stateid, 0, \ sizeof(stateid4)); \ op->nfs_argop4_u.opsetattr.obj_attributes = inattr; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_GETFH(opcnt, argarray) \ do { \ argarray[opcnt].argop = NFS4_OP_GETFH; \ opcnt++; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argarray, nfs4fh) \ do { \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_PUTFH; \ op->nfs_argop4_u.opputfh.object = nfs4fh; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_LOOKUP(opcnt, argarray, name) \ do { \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_LOOKUP; \ op->nfs_argop4_u.oplookup.objname.utf8string_val = \ (char *)name; \ op->nfs_argop4_u.oplookup.objname.utf8string_len = \ strlen(name); \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_LOOKUPP(opcnt, argarray) \ do { \ argarray[opcnt].argop = NFS4_OP_LOOKUPP; \ opcnt++; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_SETCLIENTID(argcompound, inclient, incallback) \ do { \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .argop = NFS4_OP_SETCLIENTID; \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .nfs_argop4_u.opsetclientid.client = inclient; \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .nfs_argop4_u.opsetclientid.callback = incallback; \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .nfs_argop4_u.opsetclientid.callback_ident = 0; \ argcompound.argarray.argarray_len += 1; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_SETCLIENTID_CONFIRM(argcompound, inclientid, \ inverifier) \ do { \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .argop = NFS4_OP_SETCLIENTID_CONFIRM; \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .nfs_argop4_u.opsetclientid_confirm.clientid = \ inclientid; \ strncpy(argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .nfs_argop4_u.opsetclientid_confirm \ .setclientid_confirm, \ inverifier, NFS4_VERIFIER_SIZE); \ argcompound.argarray.argarray_len += 1; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_ACCESS(argcompound, inaccessflag) \ do { \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .argop = NFS4_OP_ACCESS; \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .nfs_argop4_u.opaccess.access = inaccessflag; \ argcompound.argarray.argarray_len += 1; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_READDIR(opcnt, args, c4, inbitmap) \ do { \ nfs_argop4 *op = args + opcnt; \ opcnt++; \ op->argop = NFS4_OP_READDIR; \ op->nfs_argop4_u.opreaddir.cookie = c4; \ memset(&op->nfs_argop4_u.opreaddir.cookieverf, 0, \ NFS4_VERIFIER_SIZE); \ op->nfs_argop4_u.opreaddir.dircount = 2048; \ op->nfs_argop4_u.opreaddir.maxcount = 4096; \ op->nfs_argop4_u.opreaddir.attr_request = inbitmap; \ } while (0) #define COMPOUNDV4_ARGS_ADD_OP_OPEN_4_1(opcnt, args, __share_access, \ __share_deny, __owner_val, \ __owner_len, __openhow, __claim) \ do { \ nfs_argop4 *op = args + opcnt; \ opcnt++; \ op->argop = NFS4_OP_OPEN; \ op->nfs_argop4_u.opopen.share_access = __share_access; \ op->nfs_argop4_u.opopen.share_deny = __share_deny; \ op->nfs_argop4_u.opopen.owner.owner.owner_len = __owner_len; \ op->nfs_argop4_u.opopen.owner.owner.owner_val = __owner_val; \ op->nfs_argop4_u.opopen.openhow = __openhow; \ op->nfs_argop4_u.opopen.claim = __claim; \ } while (0) #define COMPOUNDV4_ARGS_ADD_OP_OPEN(opcnt, args, oo_seqid, __share_access, \ __share_deny, inclientid, __owner_val, \ __owner_len, __openhow, __claim) \ do { \ nfs_argop4 *op = args + opcnt; \ opcnt++; \ op->argop = NFS4_OP_OPEN; \ op->nfs_argop4_u.opopen.seqid = oo_seqid; \ op->nfs_argop4_u.opopen.share_access = __share_access; \ op->nfs_argop4_u.opopen.share_deny = __share_deny; \ op->nfs_argop4_u.opopen.owner.clientid = inclientid; \ op->nfs_argop4_u.opopen.owner.owner.owner_len = __owner_len; \ op->nfs_argop4_u.opopen.owner.owner.owner_val = __owner_val; \ op->nfs_argop4_u.opopen.openhow = __openhow; \ op->nfs_argop4_u.opopen.claim = __claim; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_OPEN_CREATE( \ opcnt, args, inname, inattrs, inclientid, __owner_val, __owner_len) \ do { \ nfs_argop4 *op = args + opcnt; \ opcnt++; \ op->argop = NFS4_OP_OPEN; \ op->nfs_argop4_u.opopen.share_access = \ OPEN4_SHARE_ACCESS_BOTH; \ op->nfs_argop4_u.opopen.share_deny = OPEN4_SHARE_DENY_NONE; \ op->nfs_argop4_u.opopen.owner.clientid = inclientid; \ op->nfs_argop4_u.opopen.owner.owner.owner_len = __owner_len; \ op->nfs_argop4_u.opopen.owner.owner.owner_val = __owner_val; \ op->nfs_argop4_u.opopen.openhow.opentype = OPEN4_CREATE; \ op->nfs_argop4_u.opopen.openhow.openflag4_u.how.mode = \ GUARDED4; \ op->nfs_argop4_u.opopen.openhow.openflag4_u.how.createhow4_u \ .createattrs = inattrs; \ op->nfs_argop4_u.opopen.claim.claim = CLAIM_NULL; \ op->nfs_argop4_u.opopen.claim.open_claim4_u.file \ .utf8string_val = inname; \ op->nfs_argop4_u.opopen.claim.open_claim4_u.file \ .utf8string_len = strlen(inname); \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_MKDIR(opcnt, argarray, inname, inattrs) \ do { \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_CREATE; \ op->nfs_argop4_u.opcreate.objtype.type = NF4DIR; \ op->nfs_argop4_u.opcreate.objname.utf8string_val = inname; \ op->nfs_argop4_u.opcreate.objname.utf8string_len = \ strlen(inname); \ op->nfs_argop4_u.opcreate.createattrs = inattrs; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_CREATE(opcnt, arg, inname, nf4typ, inattrs, \ specd) \ do { \ nfs_argop4 *op = arg + opcnt; \ opcnt++; \ op->argop = NFS4_OP_CREATE; \ op->nfs_argop4_u.opcreate.objtype.type = nf4typ; \ op->nfs_argop4_u.opcreate.objtype.createtype4_u.devdata = \ specd; \ op->nfs_argop4_u.opcreate.objname.utf8string_val = inname; \ op->nfs_argop4_u.opcreate.objname.utf8string_len = \ strlen(inname); \ op->nfs_argop4_u.opcreate.createattrs = inattrs; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_SYMLINK(opcnt, args, inname, incontent, inattrs) \ do { \ nfs_argop4 *op = args + opcnt; \ opcnt++; \ op->argop = NFS4_OP_CREATE; \ op->nfs_argop4_u.opcreate.objtype.type = NF4LNK; \ op->nfs_argop4_u.opcreate.objtype.createtype4_u.linkdata \ .utf8string_val = incontent; \ op->nfs_argop4_u.opcreate.objtype.createtype4_u.linkdata \ .utf8string_len = strlen(incontent); \ op->nfs_argop4_u.opcreate.objname.utf8string_val = inname; \ op->nfs_argop4_u.opcreate.objname.utf8string_len = \ strlen(inname); \ op->nfs_argop4_u.opcreate.createattrs = inattrs; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_LINK(opcnt, argarray, inname) \ do { \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_LINK; \ op->nfs_argop4_u.oplink.newname.utf8string_val = inname; \ op->nfs_argop4_u.oplink.newname.utf8string_len = \ strlen(inname); \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_REMOVE(opcnt, argarray, inname) \ do { \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_REMOVE; \ op->nfs_argop4_u.opremove.target.utf8string_val = inname; \ op->nfs_argop4_u.opremove.target.utf8string_len = \ strlen(inname); \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_RENAME(opcnt, argarray, inoldname, innewname) \ do { \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_RENAME; \ op->nfs_argop4_u.oprename.oldname.utf8string_val = inoldname; \ op->nfs_argop4_u.oprename.oldname.utf8string_len = \ strlen(inoldname); \ op->nfs_argop4_u.oprename.newname.utf8string_val = innewname; \ op->nfs_argop4_u.oprename.newname.utf8string_len = \ strlen(innewname); \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_READLINK(opcnt, argarray) \ do { \ argarray[opcnt].argop = NFS4_OP_READLINK; \ opcnt++; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_SAVEFH(opcnt, argarray) \ do { \ argarray[opcnt].argop = NFS4_OP_SAVEFH; \ opcnt++; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_RESTOREFH(argcompound) \ do { \ argcompound.argarray \ .argarray_val[argcompound.argarray.argarray_len] \ .argop = NFS4_OP_RESTOREFH; \ argcompound.argarray.argarray_len += 1; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_READ(opcnt, argarray, inoffset, incount, \ __other) \ do { \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_READ; \ op->nfs_argop4_u.opread.stateid.seqid = 0; \ memcpy(op->nfs_argop4_u.opread.stateid.other, __other, \ sizeof(op->nfs_argop4_u.opread.stateid.other)); \ op->nfs_argop4_u.opread.offset = inoffset; \ op->nfs_argop4_u.opread.count = incount; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_READ_STATELESS(opcnt, argarray, inoffset, \ incount) \ do { \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_READ; \ memset(&op->nfs_argop4_u.opread.stateid, 0, sizeof(stateid4)); \ op->nfs_argop4_u.opread.offset = inoffset; \ op->nfs_argop4_u.opread.count = incount; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_READ_BYPASS(opcnt, argarray, inoffset, incount) \ do { \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_READ; \ memset(&op->nfs_argop4_u.opread.stateid, 0xff, \ sizeof(stateid4)); \ op->nfs_argop4_u.opread.offset = inoffset; \ op->nfs_argop4_u.opread.count = incount; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_WRITE(opcnt, argarray, inoffset, iniovcnt, \ iniov, inlen, instable, __other) \ do { \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_WRITE; \ op->nfs_argop4_u.opwrite.stable = instable; \ op->nfs_argop4_u.opread.stateid.seqid = 0; \ memcpy(op->nfs_argop4_u.opwrite.stateid.other, __other, \ sizeof(op->nfs_argop4_u.opwrite.stateid.other)); \ op->nfs_argop4_u.opwrite.offset = inoffset; \ op->nfs_argop4_u.opwrite.data.data_len = inlen; \ op->nfs_argop4_u.opwrite.data.iovcnt = iniovcnt; \ op->nfs_argop4_u.opwrite.data.iov = iniov; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_WRITE_STATELESS( \ opcnt, argarray, inoffset, iniovcnt, iniov, inlen, instable) \ do { \ nfs_argop4 *op = argarray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_WRITE; \ op->nfs_argop4_u.opwrite.stable = instable; \ memset(&op->nfs_argop4_u.opwrite.stateid, 0, \ sizeof(stateid4)); \ op->nfs_argop4_u.opwrite.offset = inoffset; \ op->nfs_argop4_u.opwrite.data.data_len = inlen; \ op->nfs_argop4_u.opwrite.data.iovcnt = iniovcnt; \ op->nfs_argop4_u.opwrite.data.iov = iniov; \ } while (0) #define COMPOUNDV4_ARG_ADD_OP_COMMIT(opcnt, argoparray, inoffset, inlen) \ do { \ nfs_argop4 *op = argoparray + opcnt; \ opcnt++; \ op->argop = NFS4_OP_COMMIT; \ op->nfs_argop4_u.opcommit.offset = inoffset; \ op->nfs_argop4_u.opcommit.count = inlen; \ } while (0) #define COMPOUNDV4_EXECUTE_SIMPLE(pcontext, argcompound, rescompound) \ clnt_call(pcontext->rpc_client, NFSPROC4_COMPOUND, \ (xdrproc_t)xdr_COMPOUND4args, &argcompound, \ (xdrproc_t)xdr_COMPOUND4res, &rescompound, timeout) #endif /* _FSAL_NFSV4_MACROS_H */ nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V4/handle.c000066400000000000000000002623361473756622300206230ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Max Matveev, 2012 * Copyright CEA/DAM/DIF (2008) * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * This program 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 3 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 * 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 */ /* Proxy handle methods */ #include "config.h" #include "fsal.h" #include #include #include #include #include #include #include "gsh_list.h" #include "abstract_atomic.h" #include "fsal_types.h" #include "FSAL/fsal_commonlib.h" #include "proxyv4_fsal_methods.h" #include "fsal_nfsv4_macros.h" #include "nfs_core.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "export_mgr.h" #include "common_utils.h" #include "fsal_convert.h" #define FSAL_PROXY_NFS_V4 4 #define FSAL_PROXY_NFS_V4_MINOR 1 #define NB_RPC_SLOT 16 #define NB_MAX_OPERATIONS 10 /* NB! nfs_prog is just an easy way to get this info into the call * It should really be fetched via export pointer */ /** * We mutualize rpc_context and slot NFSv4.1. */ struct proxyv4_rpc_io_context { pthread_mutex_t iolock; pthread_cond_t iowait; struct glist_head calls; uint32_t rpc_xid; bool iodone; int ioresult; unsigned int nfs_prog; unsigned int sendbuf_sz; unsigned int recvbuf_sz; char *sendbuf; char *recvbuf; slotid4 slotid; sequenceid4 seqid; }; /* Use this to estimate storage requirements for fattr4 blob */ struct proxyv4_fattr_storage { fattr4_type type; fattr4_change change_time; fattr4_size size; fattr4_fsid fsid; fattr4_filehandle filehandle; fattr4_fileid fileid; fattr4_mode mode; fattr4_numlinks numlinks; fattr4_owner owner; fattr4_owner_group owner_group; fattr4_space_used space_used; fattr4_time_access time_access; fattr4_time_metadata time_metadata; fattr4_time_modify time_modify; fattr4_rawdev rawdev; char padowner[MAXNAMLEN + 1]; char padgroup[MAXNAMLEN + 1]; char padfh[NFS4_FHSIZE]; }; #define FATTR_BLOB_SZ sizeof(struct proxyv4_fattr_storage) /* * This is what becomes an opaque FSAL handle for the upper layers. * * The type is a placeholder for future expansion. */ struct proxyv4_handle_blob { uint8_t len; uint8_t type; uint8_t bytes[0]; }; struct proxyv4_obj_handle { struct fsal_obj_handle obj; nfs_fh4 fh4; #ifdef PROXYV4_HANDLE_MAPPING nfs23_map_handle_t h23; #endif fsal_openflags_t openflags; struct proxyv4_handle_blob blob; }; static struct proxyv4_obj_handle * proxyv4_alloc_handle(struct fsal_export *exp, const nfs_fh4 *fh, fattr4 *obj_attributes, struct fsal_attrlist *attrs_out); struct proxyv4_state { /** state MUST be first to use default free_state */ struct state_t state; stateid4 stateid; }; struct state_t *proxyv4_alloc_state(struct fsal_export *exp_hdl, enum state_type state_type, struct state_t *related_state) { return init_state(gsh_calloc(1, sizeof(struct proxyv4_state)), NULL, state_type, related_state); } #define FSAL_VERIFIER_T_TO_VERIFIER4(verif4, fsal_verif) \ do { \ BUILD_BUG_ON(sizeof(fsal_verifier_t) != sizeof(verifier4)); \ memcpy(verif4, fsal_verif, sizeof(fsal_verifier_t)); \ } while (0) static fsal_status_t nfsstat4_to_fsal(nfsstat4 nfsstatus) { switch (nfsstatus) { case NFS4ERR_SAME: case NFS4ERR_NOT_SAME: case NFS4_OK: return fsalstat(ERR_FSAL_NO_ERROR, (int)nfsstatus); case NFS4ERR_PERM: return fsalstat(ERR_FSAL_PERM, (int)nfsstatus); case NFS4ERR_NOENT: return fsalstat(ERR_FSAL_NOENT, (int)nfsstatus); case NFS4ERR_IO: return fsalstat(ERR_FSAL_IO, (int)nfsstatus); case NFS4ERR_NXIO: return fsalstat(ERR_FSAL_NXIO, (int)nfsstatus); case NFS4ERR_EXPIRED: case NFS4ERR_LOCKED: case NFS4ERR_SHARE_DENIED: case NFS4ERR_LOCK_RANGE: case NFS4ERR_OPENMODE: case NFS4ERR_FILE_OPEN: case NFS4ERR_ACCESS: case NFS4ERR_DENIED: return fsalstat(ERR_FSAL_ACCESS, (int)nfsstatus); case NFS4ERR_EXIST: return fsalstat(ERR_FSAL_EXIST, (int)nfsstatus); case NFS4ERR_XDEV: return fsalstat(ERR_FSAL_XDEV, (int)nfsstatus); case NFS4ERR_NOTDIR: return fsalstat(ERR_FSAL_NOTDIR, (int)nfsstatus); case NFS4ERR_ISDIR: return fsalstat(ERR_FSAL_ISDIR, (int)nfsstatus); case NFS4ERR_FBIG: return fsalstat(ERR_FSAL_FBIG, 0); case NFS4ERR_NOSPC: return fsalstat(ERR_FSAL_NOSPC, (int)nfsstatus); case NFS4ERR_ROFS: return fsalstat(ERR_FSAL_ROFS, (int)nfsstatus); case NFS4ERR_MLINK: return fsalstat(ERR_FSAL_MLINK, (int)nfsstatus); case NFS4ERR_NAMETOOLONG: return fsalstat(ERR_FSAL_NAMETOOLONG, (int)nfsstatus); case NFS4ERR_NOTEMPTY: return fsalstat(ERR_FSAL_NOTEMPTY, (int)nfsstatus); case NFS4ERR_DQUOT: return fsalstat(ERR_FSAL_DQUOT, (int)nfsstatus); case NFS4ERR_STALE: return fsalstat(ERR_FSAL_STALE, (int)nfsstatus); case NFS4ERR_NOFILEHANDLE: case NFS4ERR_BADHANDLE: return fsalstat(ERR_FSAL_BADHANDLE, (int)nfsstatus); case NFS4ERR_BAD_COOKIE: return fsalstat(ERR_FSAL_BADCOOKIE, (int)nfsstatus); case NFS4ERR_NOTSUPP: return fsalstat(ERR_FSAL_NOTSUPP, (int)nfsstatus); case NFS4ERR_TOOSMALL: return fsalstat(ERR_FSAL_TOOSMALL, (int)nfsstatus); case NFS4ERR_SERVERFAULT: return fsalstat(ERR_FSAL_SERVERFAULT, (int)nfsstatus); case NFS4ERR_BADTYPE: return fsalstat(ERR_FSAL_BADTYPE, (int)nfsstatus); case NFS4ERR_GRACE: case NFS4ERR_DELAY: return fsalstat(ERR_FSAL_DELAY, (int)nfsstatus); case NFS4ERR_FHEXPIRED: return fsalstat(ERR_FSAL_FHEXPIRED, (int)nfsstatus); case NFS4ERR_WRONGSEC: return fsalstat(ERR_FSAL_SEC, (int)nfsstatus); case NFS4ERR_SYMLINK: return fsalstat(ERR_FSAL_SYMLINK, (int)nfsstatus); case NFS4ERR_ATTRNOTSUPP: return fsalstat(ERR_FSAL_ATTRNOTSUPP, (int)nfsstatus); case NFS4ERR_BADNAME: return fsalstat(ERR_FSAL_BADNAME, (int)nfsstatus); case NFS4ERR_INVAL: case NFS4ERR_CLID_INUSE: case NFS4ERR_MOVED: case NFS4ERR_RESOURCE: case NFS4ERR_MINOR_VERS_MISMATCH: case NFS4ERR_STALE_CLIENTID: case NFS4ERR_STALE_STATEID: case NFS4ERR_OLD_STATEID: case NFS4ERR_BAD_STATEID: case NFS4ERR_BAD_SEQID: case NFS4ERR_RESTOREFH: case NFS4ERR_LEASE_MOVED: case NFS4ERR_NO_GRACE: case NFS4ERR_RECLAIM_BAD: case NFS4ERR_RECLAIM_CONFLICT: case NFS4ERR_BADXDR: case NFS4ERR_BADCHAR: case NFS4ERR_BAD_RANGE: case NFS4ERR_BADOWNER: case NFS4ERR_OP_ILLEGAL: case NFS4ERR_LOCKS_HELD: case NFS4ERR_LOCK_NOTSUPP: case NFS4ERR_DEADLOCK: case NFS4ERR_ADMIN_REVOKED: case NFS4ERR_CB_PATH_DOWN: default: return fsalstat(ERR_FSAL_INVAL, (int)nfsstatus); } } #define PROXYV4_ATTR_BIT(b) (1U << b) #define PROXYV4_ATTR_BIT2(b) (1U << (b - 32)) static struct bitmap4 proxyv4_bitmap_getattr = { .map[0] = (PROXYV4_ATTR_BIT(FATTR4_SUPPORTED_ATTRS) | PROXYV4_ATTR_BIT(FATTR4_TYPE) | PROXYV4_ATTR_BIT(FATTR4_CHANGE) | PROXYV4_ATTR_BIT(FATTR4_SIZE) | PROXYV4_ATTR_BIT(FATTR4_FSID) | PROXYV4_ATTR_BIT(FATTR4_FILEID)), .map[1] = (PROXYV4_ATTR_BIT2(FATTR4_MODE) | PROXYV4_ATTR_BIT2(FATTR4_NUMLINKS) | PROXYV4_ATTR_BIT2(FATTR4_OWNER) | PROXYV4_ATTR_BIT2(FATTR4_OWNER_GROUP) | PROXYV4_ATTR_BIT2(FATTR4_SPACE_USED) | PROXYV4_ATTR_BIT2(FATTR4_TIME_ACCESS) | PROXYV4_ATTR_BIT2(FATTR4_TIME_METADATA) | PROXYV4_ATTR_BIT2(FATTR4_TIME_MODIFY) | PROXYV4_ATTR_BIT2(FATTR4_RAWDEV)), .bitmap4_len = 2 }; static struct bitmap4 proxyv4_bitmap_fsinfo = { .map[0] = (PROXYV4_ATTR_BIT(FATTR4_FILES_AVAIL) | PROXYV4_ATTR_BIT(FATTR4_FILES_FREE) | PROXYV4_ATTR_BIT(FATTR4_FILES_TOTAL)), .map[1] = (PROXYV4_ATTR_BIT2(FATTR4_SPACE_AVAIL) | PROXYV4_ATTR_BIT2(FATTR4_SPACE_FREE) | PROXYV4_ATTR_BIT2(FATTR4_SPACE_TOTAL)), .bitmap4_len = 2 }; static struct bitmap4 lease_bits = { .map[0] = PROXYV4_ATTR_BIT( FATTR4_LEASE_TIME), .bitmap4_len = 1 }; static struct bitmap4 proxyv4_bitmap_per_fs = { .map[0] = (PROXYV4_ATTR_BIT(FATTR4_MAXREAD) | PROXYV4_ATTR_BIT(FATTR4_MAXWRITE)), .bitmap4_len = 1 }; #undef PROXYV4_ATTR_BIT #undef PROXYV4_ATTR_BIT2 static struct { attrmask_t mask; int fattr_bit; } fsal_mask2bit[] = { { ATTR_SIZE, FATTR4_SIZE }, { ATTR_MODE, FATTR4_MODE }, { ATTR_OWNER, FATTR4_OWNER }, { ATTR_GROUP, FATTR4_OWNER_GROUP }, { ATTR_ATIME, FATTR4_TIME_ACCESS_SET }, { ATTR_ATIME_SERVER, FATTR4_TIME_ACCESS_SET }, { ATTR_MTIME, FATTR4_TIME_MODIFY_SET }, { ATTR_MTIME_SERVER, FATTR4_TIME_MODIFY_SET }, { ATTR_CTIME, FATTR4_TIME_METADATA } }; static struct bitmap4 empty_bitmap = { .map[0] = 0, .map[1] = 0, .map[2] = 0, .bitmap4_len = 2 }; static int proxyv4_fsalattr_to_fattr4(const struct fsal_attrlist *attrs, fattr4 *data) { int i; struct bitmap4 bmap = empty_bitmap; struct xdr_attrs_args args; for (i = 0; i < ARRAY_SIZE(fsal_mask2bit); i++) { if (FSAL_TEST_MASK(attrs->valid_mask, fsal_mask2bit[i].mask)) { if (fsal_mask2bit[i].fattr_bit > 31) { bmap.map[1] |= 1U << (fsal_mask2bit[i].fattr_bit - 32); bmap.bitmap4_len = 2; } else { bmap.map[0] |= 1U << fsal_mask2bit[i].fattr_bit; } } } memset(&args, 0, sizeof(args)); args.attrs = (struct fsal_attrlist *)attrs; args.data = NULL; return nfs4_FSALattr_To_Fattr(&args, &bmap, data); } static GETATTR4resok *proxyv4_fill_getattr_reply(nfs_resop4 *resop, char *blob, size_t blob_sz) { GETATTR4resok *a = &resop->nfs_resop4_u.opgetattr.GETATTR4res_u.resok4; a->obj_attributes.attrmask = empty_bitmap; a->obj_attributes.attr_vals.attrlist4_val = blob; a->obj_attributes.attr_vals.attrlist4_len = blob_sz; return a; } static int proxyv4_got_rpc_reply(struct proxyv4_rpc_io_context *ctx, int sock, int sz, u_int xid) { char *repbuf = ctx->recvbuf; int size; if (sz > ctx->recvbuf_sz) return -E2BIG; PTHREAD_MUTEX_lock(&ctx->iolock); memcpy(repbuf, &xid, sizeof(xid)); /* * sz includes 4 bytes of xid which have been processed * together with record mark - reduce the read to avoid * gobbing up next record mark. */ repbuf += 4; ctx->ioresult = 4; sz -= 4; while (sz > 0) { /* TODO: handle timeouts - use poll(2) */ int bc = read(sock, repbuf, sz); if (bc <= 0) { ctx->ioresult = -((bc < 0) ? errno : ETIMEDOUT); break; } repbuf += bc; ctx->ioresult += bc; sz -= bc; } ctx->iodone = true; size = ctx->ioresult; pthread_cond_signal(&ctx->iowait); PTHREAD_MUTEX_unlock(&ctx->iolock); return size; } static int proxyv4_rpc_read_reply(struct proxyv4_export *proxyv4_exp) { struct { uint recmark; uint xid; } h; char *buf = (char *)&h; struct glist_head *c; char sink[256]; int cnt = 0; struct proxyv4_export_rpc *rpc = &proxyv4_exp->rpc; while (cnt < 8) { int bc = read(rpc->rpc_sock, buf + cnt, 8 - cnt); if (bc < 0) return -errno; cnt += bc; } h.recmark = ntohl(h.recmark); /* TODO: check for final fragment */ h.xid = ntohl(h.xid); LogDebug(COMPONENT_FSAL, "Recmark %x, xid %u\n", h.recmark, h.xid); h.recmark &= ~(1U << 31); PTHREAD_MUTEX_lock(&rpc->listlock); glist_for_each(c, &rpc->rpc_calls) { struct proxyv4_rpc_io_context *ctx = container_of(c, struct proxyv4_rpc_io_context, calls); if (ctx->rpc_xid == h.xid) { glist_del(c); PTHREAD_MUTEX_unlock(&rpc->listlock); return proxyv4_got_rpc_reply(ctx, rpc->rpc_sock, h.recmark, h.xid); } } PTHREAD_MUTEX_unlock(&rpc->listlock); cnt = h.recmark - 4; LogDebug(COMPONENT_FSAL, "xid %u is not on the list, skip %d bytes\n", h.xid, cnt); while (cnt > 0) { int rb = (cnt > sizeof(sink)) ? sizeof(sink) : cnt; rb = read(rpc->rpc_sock, sink, rb); if (rb <= 0) return -errno; cnt -= rb; } return 0; } /* called with listlock */ static void proxyv4_new_socket_ready(struct proxyv4_export *proxyv4_exp) { struct glist_head *nxt; struct glist_head *c; struct proxyv4_export_rpc *rpc = &proxyv4_exp->rpc; /* If there are any outstanding calls then tell them to resend */ glist_for_each_safe(c, nxt, &rpc->rpc_calls) { struct proxyv4_rpc_io_context *ctx = container_of(c, struct proxyv4_rpc_io_context, calls); glist_del(c); PTHREAD_MUTEX_lock(&ctx->iolock); ctx->iodone = true; ctx->ioresult = -EAGAIN; pthread_cond_signal(&ctx->iowait); PTHREAD_MUTEX_unlock(&ctx->iolock); } /* If there is anyone waiting for the socket then tell them * it's ready */ pthread_cond_broadcast(&rpc->sockless); } /* called with listlock */ static int proxyv4_connect(struct proxyv4_export *proxyv4_exp, sockaddr_t *dest, uint16_t port) { int sock; int socklen; switch (dest->ss_family) { case AF_INET: ((struct sockaddr_in *)dest)->sin_port = htons(port); socklen = sizeof(struct sockaddr_in); break; case AF_INET6: ((struct sockaddr_in6 *)dest)->sin6_port = htons(port); socklen = sizeof(struct sockaddr_in6); break; default: LogCrit(COMPONENT_FSAL, "Unknown address family %d", dest->ss_family); return -1; } if (proxyv4_exp->info.use_privileged_client_port) { int priv_port = 0; sock = rresvport_af(&priv_port, dest->ss_family); if (sock < 0) LogCrit(COMPONENT_FSAL, "Cannot create TCP socket on privileged port"); } else { sock = socket(dest->ss_family, SOCK_STREAM, IPPROTO_TCP); if (sock < 0) LogCrit(COMPONENT_FSAL, "Cannot create TCP socket - %d", errno); } if (sock >= 0) { if (connect(sock, (struct sockaddr *)dest, socklen) < 0) { close(sock); sock = -1; } else { proxyv4_new_socket_ready(proxyv4_exp); } } return sock; } /* * NB! rpc_sock can be closed by the sending thread but it will not be * changing its value. Only this function will change rpc_sock which * means that it can look at the value without holding the lock. */ static void *proxyv4_rpc_recv(void *arg) { struct proxyv4_export *proxyv4_exp = arg; struct proxyv4_export_rpc *rpc = &proxyv4_exp->rpc; pthread_mutex_t *list_lock = &rpc->listlock; struct pollfd pfd; int millisec = proxyv4_exp->info.srv_timeout * 1000; SetNameFunction("proxyv4_rcv_thread"); rcu_register_thread(); while (!rpc->close_thread) { int nsleeps = 0; PTHREAD_MUTEX_lock(list_lock); do { sockaddr_t *srv_addr = &proxyv4_exp->info.srv_addr; uint16_t srv_port = proxyv4_exp->info.srv_port; rpc->rpc_sock = proxyv4_connect(proxyv4_exp, srv_addr, srv_port); /* early stop test */ if (rpc->close_thread) { PTHREAD_MUTEX_unlock(list_lock); goto out; } if (rpc->rpc_sock < 0) { if (nsleeps == 0) { char addr[SOCK_NAME_MAX]; struct display_buffer dspbuf = { sizeof(addr), addr, addr }; display_sockaddr(&dspbuf, srv_addr); LogCrit(COMPONENT_FSAL, "Cannot connect to server %s:%u", addr, srv_port); } PTHREAD_MUTEX_unlock(list_lock); sleep(proxyv4_exp->info.retry_sleeptime); nsleeps++; PTHREAD_MUTEX_lock(list_lock); } else { LogDebug( COMPONENT_FSAL, "Connected after %d sleeps, resending outstanding calls", nsleeps); } } while (rpc->rpc_sock < 0 && !rpc->close_thread); PTHREAD_MUTEX_unlock(list_lock); /* early stop test */ if (rpc->close_thread) goto out; pfd.fd = rpc->rpc_sock; pfd.events = POLLIN | POLLRDHUP; while (rpc->rpc_sock >= 0) { switch (poll(&pfd, 1, millisec)) { case 0: LogDebug(COMPONENT_FSAL, "Timeout, wait again..."); continue; case -1: break; default: if (pfd.revents & POLLRDHUP) { LogEvent( COMPONENT_FSAL, "Other end has closed connection, reconnecting..."); } else if (pfd.revents & POLLNVAL) { LogEvent(COMPONENT_FSAL, "Socket is closed"); } if (proxyv4_rpc_read_reply(proxyv4_exp) >= 0) continue; break; } PTHREAD_MUTEX_lock(list_lock); close(rpc->rpc_sock); rpc->rpc_sock = -1; PTHREAD_MUTEX_unlock(list_lock); } } out: rcu_unregister_thread(); return NULL; } static enum clnt_stat proxyv4_process_reply(struct proxyv4_rpc_io_context *ctx, COMPOUND4res *res) { enum clnt_stat rc = RPC_CANTRECV; struct timespec ts; PTHREAD_MUTEX_lock(&ctx->iolock); ts.tv_sec = time(NULL) + 60; ts.tv_nsec = 0; while (!ctx->iodone) { int w = pthread_cond_timedwait(&ctx->iowait, &ctx->iolock, &ts); if (w == ETIMEDOUT) { PTHREAD_MUTEX_unlock(&ctx->iolock); return RPC_TIMEDOUT; } } ctx->iodone = false; PTHREAD_MUTEX_unlock(&ctx->iolock); if (ctx->ioresult > 0) { struct rpc_msg reply; XDR x; memset(&reply, 0, sizeof(reply)); reply.RPCM_ack.ar_results.proc = (xdrproc_t)xdr_COMPOUND4res; reply.RPCM_ack.ar_results.where = res; memset(&x, 0, sizeof(x)); xdrmem_create(&x, ctx->recvbuf, ctx->ioresult, XDR_DECODE); /* macro is defined, GCC 4.7.2 ignoring */ if (xdr_replymsg(&x, &reply)) { if (reply.rm_reply.rp_stat == MSG_ACCEPTED) { switch (reply.rm_reply.rp_acpt.ar_stat) { case SUCCESS: rc = RPC_SUCCESS; break; case PROG_UNAVAIL: rc = RPC_PROGUNAVAIL; break; case PROG_MISMATCH: rc = RPC_PROGVERSMISMATCH; break; case PROC_UNAVAIL: rc = RPC_PROCUNAVAIL; break; case GARBAGE_ARGS: rc = RPC_CANTDECODEARGS; break; case SYSTEM_ERR: rc = RPC_SYSTEMERROR; break; default: rc = RPC_FAILED; break; } } else { switch (reply.rm_reply.rp_rjct.rj_stat) { case RPC_MISMATCH: rc = RPC_VERSMISMATCH; break; case AUTH_ERROR: rc = RPC_AUTHERROR; break; default: rc = RPC_FAILED; break; } } } else { rc = RPC_CANTDECODERES; } reply.RPCM_ack.ar_results.proc = (xdrproc_t)xdr_void; reply.RPCM_ack.ar_results.where = NULL; xdr_free((xdrproc_t)xdr_replymsg, &reply); } return rc; } static inline int proxyv4_rpc_need_sock(struct proxyv4_export *proxyv4_exp) { struct proxyv4_export_rpc *rpc = &proxyv4_exp->rpc; PTHREAD_MUTEX_lock(&rpc->listlock); while (rpc->rpc_sock < 0 && !rpc->close_thread) pthread_cond_wait(&rpc->sockless, &rpc->listlock); PTHREAD_MUTEX_unlock(&rpc->listlock); return rpc->close_thread; } static inline int proxyv4_rpc_renewer_wait(int timeout, struct proxyv4_export *proxyv4_exp) { struct timespec ts; int rc; struct proxyv4_export_rpc *rpc = &proxyv4_exp->rpc; PTHREAD_MUTEX_lock(&rpc->listlock); ts.tv_sec = time(NULL) + timeout; ts.tv_nsec = 0; rc = pthread_cond_timedwait(&rpc->sockless, &rpc->listlock, &ts); PTHREAD_MUTEX_unlock(&rpc->listlock); return (rc == ETIMEDOUT); } static int proxyv4_compoundv4_call(struct proxyv4_rpc_io_context *pcontext, const struct user_cred *cred, COMPOUND4args *args, COMPOUND4res *res, struct proxyv4_export *proxyv4_exp) { XDR x; struct rpc_msg rmsg; AUTH *au; enum clnt_stat rc; struct proxyv4_export_rpc *rpc = &proxyv4_exp->rpc; PTHREAD_MUTEX_lock(&rpc->listlock); rmsg.rm_xid = rpc->rpc_xid++; PTHREAD_MUTEX_unlock(&rpc->listlock); rmsg.rm_direction = CALL; rmsg.rm_call.cb_rpcvers = RPC_MSG_VERSION; rmsg.cb_prog = pcontext->nfs_prog; rmsg.cb_vers = FSAL_PROXY_NFS_V4; rmsg.cb_proc = NFSPROC4_COMPOUND; if (cred) { au = authunix_ncreate(rpc->proxyv4_hostname, cred->caller_uid, cred->caller_gid, cred->caller_glen, cred->caller_garray); } else { au = authunix_ncreate_default(); } if (AUTH_FAILURE(au)) { char *err = rpc_sperror(&au->ah_error, "failed"); LogDebug(COMPONENT_FSAL, "%s", err); gsh_free(err); AUTH_DESTROY(au); return RPC_AUTHERROR; } rmsg.cb_cred = au->ah_cred; rmsg.cb_verf = au->ah_verf; memset(&x, 0, sizeof(x)); xdrmem_create(&x, pcontext->sendbuf + 4, pcontext->sendbuf_sz, XDR_ENCODE); if (xdr_callmsg(&x, &rmsg) && xdr_COMPOUND4args(&x, args)) { u_int pos = xdr_getpos(&x); u_int recmark = ntohl(pos | (1U << 31)); int first_try = 1; pcontext->rpc_xid = rmsg.rm_xid; memcpy(pcontext->sendbuf, &recmark, sizeof(recmark)); pos += 4; do { int bc = 0; char *buf = pcontext->sendbuf; LogDebug(COMPONENT_FSAL, "%ssend XID %u with %d bytes", (first_try ? "First attempt to " : "Re"), rmsg.rm_xid, pos); PTHREAD_MUTEX_lock(&rpc->listlock); while (bc < pos) { int wc = write(rpc->rpc_sock, buf, pos - bc); if (wc <= 0) { close(rpc->rpc_sock); break; } bc += wc; buf += wc; } if (bc == pos) { if (first_try) { glist_add_tail(&rpc->rpc_calls, &pcontext->calls); first_try = 0; } } else { if (!first_try) glist_del(&pcontext->calls); } PTHREAD_MUTEX_unlock(&rpc->listlock); if (bc == pos) rc = proxyv4_process_reply(pcontext, res); else rc = RPC_CANTSEND; } while (rc == RPC_TIMEDOUT); } else { rc = RPC_CANTENCODEARGS; } auth_destroy(au); return rc; } int proxyv4_compoundv4_execute(const char *caller, const struct user_cred *creds, uint32_t cnt, nfs_argop4 *argoparray, nfs_resop4 *resoparray, struct proxyv4_export *proxyv4_exp) { enum clnt_stat rc; struct proxyv4_rpc_io_context *ctx; COMPOUND4args arg = { .minorversion = FSAL_PROXY_NFS_V4_MINOR, .argarray.argarray_val = argoparray, .argarray.argarray_len = cnt }; COMPOUND4res res = { .resarray.resarray_val = resoparray, .resarray.resarray_len = cnt }; struct proxyv4_export_rpc *rpc = &proxyv4_exp->rpc; PTHREAD_MUTEX_lock(&rpc->context_lock); while (glist_empty(&rpc->free_contexts)) pthread_cond_wait(&rpc->need_context, &rpc->context_lock); ctx = glist_first_entry(&rpc->free_contexts, struct proxyv4_rpc_io_context, calls); glist_del(&ctx->calls); PTHREAD_MUTEX_unlock(&rpc->context_lock); /* fill slotid and sequenceid */ if (argoparray->argop == NFS4_OP_SEQUENCE) { SEQUENCE4args *opsequence = &argoparray->nfs_argop4_u.opsequence; /* set slotid */ opsequence->sa_slotid = ctx->slotid; /* increment and set sequence id */ opsequence->sa_sequenceid = ++ctx->seqid; } do { rc = proxyv4_compoundv4_call(ctx, creds, &arg, &res, proxyv4_exp); if (rc != RPC_SUCCESS) LogDebug(COMPONENT_FSAL, "%s failed with %d", caller, rc); if (rc == RPC_CANTSEND) if (proxyv4_rpc_need_sock(proxyv4_exp)) return -1; } while ((rc == RPC_CANTRECV && (ctx->ioresult == -EAGAIN)) || (rc == RPC_CANTSEND)); PTHREAD_MUTEX_lock(&rpc->context_lock); pthread_cond_signal(&rpc->need_context); glist_add(&rpc->free_contexts, &ctx->calls); PTHREAD_MUTEX_unlock(&rpc->context_lock); if (rc == RPC_SUCCESS) return res.status; return rc; } static inline int proxyv4_nfsv4_call(const struct user_cred *creds, uint32_t cnt, nfs_argop4 *args, nfs_resop4 *resp) { struct proxyv4_export *proxyv4_exp = container_of(op_ctx->fsal_export, struct proxyv4_export, exp); return proxyv4_compoundv4_execute(__func__, creds, cnt, args, resp, proxyv4_exp); } static inline void proxyv4_get_clientid(struct proxyv4_export *proxyv4_exp, clientid4 *ret) { struct proxyv4_export_rpc *rpc = &proxyv4_exp->rpc; PTHREAD_MUTEX_lock(&rpc->proxyv4_clientid_mutex); *ret = rpc->proxyv4_clientid; PTHREAD_MUTEX_unlock(&rpc->proxyv4_clientid_mutex); } static inline void proxyv4_get_client_sessionid(sessionid4 ret) { struct proxyv4_export *proxyv4_exp = container_of(op_ctx->fsal_export, struct proxyv4_export, exp); struct proxyv4_export_rpc *rpc = &proxyv4_exp->rpc; PTHREAD_MUTEX_lock(&rpc->proxyv4_clientid_mutex); while (rpc->no_sessionid) pthread_cond_wait(&rpc->cond_sessionid, &rpc->proxyv4_clientid_mutex); memcpy(ret, rpc->proxyv4_client_sessionid, sizeof(sessionid4)); PTHREAD_MUTEX_unlock(&rpc->proxyv4_clientid_mutex); } static inline void proxyv4_get_client_sessionid_export(sessionid4 ret, struct proxyv4_export *proxyv4_exp) { struct proxyv4_export_rpc *rpc = &proxyv4_exp->rpc; PTHREAD_MUTEX_lock(&rpc->proxyv4_clientid_mutex); while (rpc->no_sessionid) pthread_cond_wait(&rpc->cond_sessionid, &rpc->proxyv4_clientid_mutex); memcpy(ret, rpc->proxyv4_client_sessionid, sizeof(sessionid4)); PTHREAD_MUTEX_unlock(&rpc->proxyv4_clientid_mutex); } static inline void proxyv4_get_client_seqid(struct proxyv4_export *proxyv4_exp, sequenceid4 *ret) { struct proxyv4_export_rpc *rpc = &proxyv4_exp->rpc; PTHREAD_MUTEX_lock(&rpc->proxyv4_clientid_mutex); *ret = rpc->proxyv4_client_seqid; PTHREAD_MUTEX_unlock(&rpc->proxyv4_clientid_mutex); } /** * Confirm proxyv4_clientid to set a new session. * * @param[out] new_sessionid The new session id * @param[out] new_lease_time Lease time from the background NFSv4.1 server * * @return 0 on success or NFS error code */ static int proxyv4_setsessionid(sessionid4 new_sessionid, uint32_t *lease_time, struct proxyv4_export *proxyv4_exp) { int rc; int opcnt = 0; /* CREATE_SESSION to set session id */ /* SEQUENCE RECLAIM_COMPLETE PUTROOTFH GETATTR to get lease time */ #define FSAL_SESSIONID_NB_OP_ALLOC 4 nfs_argop4 arg[FSAL_SESSIONID_NB_OP_ALLOC]; nfs_resop4 res[FSAL_SESSIONID_NB_OP_ALLOC]; clientid4 cid; sequenceid4 seqid; CREATE_SESSION4res *s_res; CREATE_SESSION4resok *res_ok; callback_sec_parms4 sec_parms4; uint32_t fore_ca_rdma_ird_val_sink; uint32_t back_ca_rdma_ird_val_sink; proxyv4_get_clientid(proxyv4_exp, &cid); proxyv4_get_client_seqid(proxyv4_exp, &seqid); LogDebug(COMPONENT_FSAL, "Getting new session id for client id %" PRIx64 " with sequence id %" PRIx32, cid, seqid); s_res = &res->nfs_resop4_u.opcreate_session; res_ok = &s_res->CREATE_SESSION4res_u.csr_resok4; res_ok->csr_fore_chan_attrs.ca_rdma_ird.ca_rdma_ird_len = 0; res_ok->csr_fore_chan_attrs.ca_rdma_ird.ca_rdma_ird_val = &fore_ca_rdma_ird_val_sink; res_ok->csr_back_chan_attrs.ca_rdma_ird.ca_rdma_ird_len = 0; res_ok->csr_back_chan_attrs.ca_rdma_ird.ca_rdma_ird_val = &back_ca_rdma_ird_val_sink; COMPOUNDV4_ARG_ADD_OP_CREATE_SESSION( opcnt, arg, cid, seqid, (&(proxyv4_exp->info)), &sec_parms4); rc = proxyv4_compoundv4_execute(__func__, NULL, opcnt, arg, res, proxyv4_exp); if (rc != NFS4_OK) return -1; /*get session_id in res*/ if (s_res->csr_status != NFS4_OK) return -1; memcpy(new_sessionid, res_ok->csr_sessionid, sizeof(sessionid4)); /* Get the lease time */ opcnt = 0; COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, arg, new_sessionid, NB_RPC_SLOT); COMPOUNDV4_ARG_ADD_OP_GLOBAL_RECLAIM_COMPLETE(opcnt, arg); COMPOUNDV4_ARG_ADD_OP_PUTROOTFH(opcnt, arg); proxyv4_fill_getattr_reply(res + opcnt, (char *)lease_time, sizeof(*lease_time)); COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, arg, lease_bits); rc = proxyv4_compoundv4_execute(__func__, NULL, opcnt, arg, res, proxyv4_exp); if (rc != NFS4_OK) { *lease_time = 60; LogDebug(COMPONENT_FSAL, "Setting new lease_time to default %d", *lease_time); } else { *lease_time = ntohl(*lease_time); LogDebug(COMPONENT_FSAL, "Getting new lease %d", *lease_time); } return 0; } static int proxyv4_setclientid(clientid4 *new_clientid, sequenceid4 *new_seqid, struct proxyv4_export *proxyv4_exp) { int rc; #define FSAL_EXCHANGE_ID_NB_OP_ALLOC 1 nfs_argop4 arg[FSAL_EXCHANGE_ID_NB_OP_ALLOC]; nfs_resop4 res[FSAL_EXCHANGE_ID_NB_OP_ALLOC]; client_owner4 clientid; char clientid_name[MAXNAMLEN + 1]; uint64_t temp_verifier; EXCHANGE_ID4args opexchange_id; EXCHANGE_ID4res *ei_res; EXCHANGE_ID4resok *ei_resok; char so_major_id_val[NFS4_OPAQUE_LIMIT]; char eir_server_scope_val[NFS4_OPAQUE_LIMIT]; nfs_impl_id4 eir_server_impl_id_val; struct sockaddr_in sin; socklen_t slen = sizeof(sin); char addrbuf[sizeof("255.255.255.255")]; struct proxyv4_export_rpc *rpc = &proxyv4_exp->rpc; LogEvent(COMPONENT_FSAL, "Negotiating a new ClientId with the remote server"); /* prepare input */ if (getsockname(rpc->rpc_sock, &sin, &slen)) return -errno; rc = snprintf(clientid_name, sizeof(clientid_name), "%s(%d, %d) - GANESHA NFSv4 Proxy", inet_ntop(AF_INET, &sin.sin_addr, addrbuf, sizeof(addrbuf)), getpid(), proxyv4_exp->exp.export_id); if (rc < 0) { return -errno; } else if (rc >= sizeof(clientid_name)) { return -EINVAL; } clientid.co_ownerid.co_ownerid_len = strlen(clientid_name); clientid.co_ownerid.co_ownerid_val = clientid_name; /* copy to intermediate uint64_t to 0-fill or truncate as needed */ temp_verifier = (uint64_t)nfs_ServerBootTime.tv_sec; BUILD_BUG_ON(sizeof(clientid.co_verifier) != sizeof(uint64_t)); memcpy(&clientid.co_verifier, &temp_verifier, sizeof(uint64_t)); arg[0].argop = NFS4_OP_EXCHANGE_ID; opexchange_id.eia_clientowner = clientid; opexchange_id.eia_flags = 0; opexchange_id.eia_state_protect.spa_how = SP4_NONE; opexchange_id.eia_client_impl_id.eia_client_impl_id_len = 0; opexchange_id.eia_client_impl_id.eia_client_impl_id_val = NULL; arg[0].nfs_argop4_u.opexchange_id = opexchange_id; /* prepare reply */ ei_res = &res->nfs_resop4_u.opexchange_id; ei_resok = &ei_res->EXCHANGE_ID4res_u.eir_resok4; ei_resok->eir_server_owner.so_major_id.so_major_id_val = so_major_id_val; ei_resok->eir_server_scope.eir_server_scope_val = eir_server_scope_val; ei_resok->eir_server_impl_id.eir_server_impl_id_val = &eir_server_impl_id_val; rc = proxyv4_compoundv4_execute(__func__, NULL, 1, arg, res, proxyv4_exp); if (rc != NFS4_OK) { LogDebug(COMPONENT_FSAL, "Compound setclientid res request returned %d", rc); return -1; } /* Keep the confirmed client id and sequence id*/ if (ei_res->eir_status != NFS4_OK) { LogDebug(COMPONENT_FSAL, "EXCHANGE_ID res status is %d", ei_res->eir_status); return -1; } *new_clientid = ei_resok->eir_clientid; *new_seqid = ei_resok->eir_sequenceid; return 0; } static void *proxyv4_clientid_renewer(void *arg) { struct proxyv4_export *proxyv4_exp = arg; struct proxyv4_export_rpc *rpc = &proxyv4_exp->rpc; int clientid_needed = 1; int sessionid_needed = 1; uint32_t lease_time = 60; SetNameFunction("proxyv4_clientid_renewer"); rcu_register_thread(); while (!rpc->close_thread) { clientid4 newcid = 0; sequenceid4 newseqid = 0; if (!sessionid_needed && proxyv4_rpc_renewer_wait(lease_time - 5, proxyv4_exp)) { /* Simply renew the session id you've got */ nfs_argop4 seq_arg; nfs_resop4 res; int opcnt = 0; int rc; sessionid4 sid; clientid4 cid; SEQUENCE4res *s_res; SEQUENCE4resok *s_resok; proxyv4_get_clientid(proxyv4_exp, &cid); proxyv4_get_client_sessionid_export(sid, proxyv4_exp); LogDebug(COMPONENT_FSAL, "Try renew session id for client id %" PRIx64, cid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, &seq_arg, sid, NB_RPC_SLOT); s_res = &res.nfs_resop4_u.opsequence; s_resok = &s_res->SEQUENCE4res_u.sr_resok4; s_resok->sr_status_flags = 0; rc = proxyv4_compoundv4_execute( __func__, NULL, 1, &seq_arg, &res, proxyv4_exp); if (rc == NFS4_OK && !s_resok->sr_status_flags) { LogDebug( COMPONENT_FSAL, "New session id for client id %" PRIu64, cid); continue; } else if (rc == NFS4_OK && s_resok->sr_status_flags) { LogEvent( COMPONENT_FSAL, "sr_status_flags received on renewing session with seqop : %" PRIu32, s_resok->sr_status_flags); continue; } else if (rc != NFS4_OK) { LogEvent(COMPONENT_FSAL, "Failed to renew session"); } } /* early stop test */ if (rpc->close_thread) break; /* We've either failed to renew or rpc socket has been * reconnected and we need new clientid or sessionid. */ if (proxyv4_rpc_need_sock(proxyv4_exp)) /* early stop test */ break; /* We need a new session_id */ if (!clientid_needed) { sessionid4 new_sessionid; LogDebug(COMPONENT_FSAL, "Need %d new session id", sessionid_needed); sessionid_needed = proxyv4_setsessionid( new_sessionid, &lease_time, proxyv4_exp); if (!sessionid_needed) { pthread_mutex_t *client_lock = &rpc->proxyv4_clientid_mutex; PTHREAD_MUTEX_lock(client_lock); /* Set new session id */ memcpy(rpc->proxyv4_client_sessionid, new_sessionid, sizeof(sessionid4)); rpc->no_sessionid = false; pthread_cond_broadcast(&rpc->cond_sessionid); /* * We finish our create session request next * one will use the next client sequence id. */ rpc->proxyv4_client_seqid++; PTHREAD_MUTEX_unlock(client_lock); continue; } } LogDebug(COMPONENT_FSAL, "Need %d new client id", clientid_needed); clientid_needed = proxyv4_setclientid(&newcid, &newseqid, proxyv4_exp); if (!clientid_needed) { PTHREAD_MUTEX_lock(&rpc->proxyv4_clientid_mutex); rpc->proxyv4_clientid = newcid; rpc->proxyv4_client_seqid = newseqid; PTHREAD_MUTEX_unlock(&rpc->proxyv4_clientid_mutex); } } rcu_unregister_thread(); return NULL; } void free_io_contexts(struct proxyv4_export *proxyv4_exp) { struct glist_head *cur, *n; struct proxyv4_export_rpc *rpc = &proxyv4_exp->rpc; glist_for_each_safe(cur, n, &rpc->free_contexts) { struct proxyv4_rpc_io_context *c = container_of(cur, struct proxyv4_rpc_io_context, calls); glist_del(cur); PTHREAD_MUTEX_destroy(&c->iolock); PTHREAD_COND_destroy(&c->iowait); gsh_free(c); } } void proxyv4_close_thread(struct proxyv4_export *proxyv4_exp) { int rc; struct proxyv4_export_rpc *rpc = &proxyv4_exp->rpc; /* setting boolean to stop thread */ rpc->close_thread = true; /* waiting threads ends */ /* * proxyv4_clientid_renewer and proxyv4_rpc_recv are usually waiting on * cond and rpc_sock respectively. Wake them up. */ PTHREAD_MUTEX_lock(&rpc->listlock); pthread_cond_broadcast(&rpc->sockless); close(rpc->rpc_sock); PTHREAD_MUTEX_unlock(&rpc->listlock); if (rpc->proxyv4_renewer_thread) { int rc = pthread_join(rpc->proxyv4_renewer_thread, NULL); if (rc) { LogWarn(COMPONENT_FSAL, "Error on waiting for the proxyv4_renewer_thread: %s (%d)", strerror(rc), rc); } } if (rpc->proxyv4_recv_thread) { rc = pthread_join(rpc->proxyv4_recv_thread, NULL); if (rc) { LogWarn(COMPONENT_FSAL, "Error on waiting for the proxyv4_recv_thread: %s (%d)", strerror(rc), rc); } } } int proxyv4_init_rpc(struct proxyv4_export *proxyv4_exp) { int rc; struct proxyv4_export_rpc *rpc = &proxyv4_exp->rpc; int i = NB_RPC_SLOT - 1; PTHREAD_MUTEX_lock(&rpc->listlock); glist_init(&rpc->rpc_calls); PTHREAD_MUTEX_unlock(&rpc->listlock); PTHREAD_MUTEX_lock(&rpc->context_lock); glist_init(&rpc->free_contexts); PTHREAD_MUTEX_unlock(&rpc->context_lock); /** * @todo this lock is not really necessary so long as we can * only do one export at a time. This is a reminder that * there is work to do to get this fnctn to truly be * per export. */ PTHREAD_MUTEX_lock(&rpc->listlock); if (rpc->rpc_xid == 0) rpc->rpc_xid = getpid() ^ time(NULL); PTHREAD_MUTEX_unlock(&rpc->listlock); if (gethostname(rpc->proxyv4_hostname, sizeof(rpc->proxyv4_hostname))) { if (strlcpy(rpc->proxyv4_hostname, "NFS-GANESHA/Proxy", sizeof(rpc->proxyv4_hostname)) >= sizeof(rpc->proxyv4_hostname)) { LogCrit(COMPONENT_FSAL, "Cannot set proxy hostname"); return -1; } } for (i = NB_RPC_SLOT - 1; i >= 0; i--) { struct proxyv4_rpc_io_context *c = gsh_malloc(sizeof(*c) + proxyv4_exp->info.srv_sendsize + proxyv4_exp->info.srv_recvsize); PTHREAD_MUTEX_init(&c->iolock, NULL); PTHREAD_COND_init(&c->iowait, NULL); c->nfs_prog = proxyv4_exp->info.srv_prognum; c->sendbuf_sz = proxyv4_exp->info.srv_sendsize; c->recvbuf_sz = proxyv4_exp->info.srv_recvsize; c->sendbuf = (char *)(c + 1); c->recvbuf = c->sendbuf + c->sendbuf_sz; c->slotid = i; c->seqid = 0; c->iodone = false; PTHREAD_MUTEX_lock(&rpc->context_lock); glist_add(&rpc->free_contexts, &c->calls); PTHREAD_MUTEX_unlock(&rpc->context_lock); } rc = pthread_create(&rpc->proxyv4_recv_thread, NULL, proxyv4_rpc_recv, (void *)proxyv4_exp); if (rc) { LogCrit(COMPONENT_FSAL, "Cannot create proxy rpc receiver thread - %s (%d)", strerror(rc), rc); /* Cleanup handled by caller. */ return rc; } rc = pthread_create(&rpc->proxyv4_renewer_thread, NULL, proxyv4_clientid_renewer, (void *)proxyv4_exp); if (rc) { LogCrit(COMPONENT_FSAL, "Cannot create proxy clientid renewer thread - %s (%d)", strerror(rc), rc); /* Cleanup handled by caller. */ } return rc; } static fsal_status_t proxyv4_make_object(struct fsal_export *export, fattr4 *obj_attributes, const nfs_fh4 *fh, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { struct proxyv4_obj_handle *proxyv4_hdl; proxyv4_hdl = proxyv4_alloc_handle(export, fh, obj_attributes, attrs_out); if (proxyv4_hdl == NULL) return fsalstat(ERR_FSAL_FAULT, 0); *handle = &proxyv4_hdl->obj; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* * cap maxread and maxwrite config values to background server values */ static void proxyv4_check_maxread_maxwrite(struct fsal_export *export, fattr4 *f4) { fsal_dynamicfsinfo_t info; int rc; rc = nfs4_Fattr_To_fsinfo(&info, f4); if (rc != NFS4_OK) { LogWarn(COMPONENT_FSAL, "Unable to get maxread and maxwrite from backend NFS server : %d", rc); } else { struct proxyv4_fsal_module *pm = container_of( export->fsal, struct proxyv4_fsal_module, module); if (info.maxread != 0 && pm->module.fs_info.maxread > info.maxread) { LogWarn(COMPONENT_FSAL, "Reduced maxread from %" PRIu64 " to align with remote server %" PRIu64, pm->module.fs_info.maxread, info.maxread); pm->module.fs_info.maxread = info.maxread; } if (info.maxwrite != 0 && pm->module.fs_info.maxwrite > info.maxwrite) { LogWarn(COMPONENT_FSAL, "Reduced maxwrite from %" PRIu64 " to align with remote server %" PRIu64, pm->module.fs_info.maxwrite, info.maxwrite); pm->module.fs_info.maxwrite = info.maxwrite; } } } /* * NULL parent pointer is only used by lookup_path when it starts * from the root handle and has its own export pointer, everybody * else is supposed to provide a real parent pointer and matching * export */ static fsal_status_t proxyv4_lookup_impl(struct fsal_obj_handle *parent, struct fsal_export *export, const struct user_cred *cred, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { int rc; uint32_t opcnt = 0; GETATTR4resok *atok; GETATTR4resok *atok_per_file_system_attr = NULL; GETFH4resok *fhok; sessionid4 sid; /* SEQUENCE PUTROOTFH/PUTFH LOOKUP GETFH GETATTR (GETATTR) */ #define FSAL_LOOKUP_NB_OP_ALLOC 6 nfs_argop4 argoparray[FSAL_LOOKUP_NB_OP_ALLOC]; nfs_resop4 resoparray[FSAL_LOOKUP_NB_OP_ALLOC]; char fattr_blob[FATTR_BLOB_SZ]; char fattr_blob_per_fs[FATTR_BLOB_SZ]; char padfilehandle[NFS4_FHSIZE]; if (!handle) return fsalstat(ERR_FSAL_INVAL, 0); /* SEQUENCE */ proxyv4_get_client_sessionid(sid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argoparray, sid, NB_RPC_SLOT); if (!parent) { COMPOUNDV4_ARG_ADD_OP_PUTROOTFH(opcnt, argoparray); } else { struct proxyv4_obj_handle *proxyv4_obj = container_of(parent, struct proxyv4_obj_handle, obj); switch (parent->type) { case DIRECTORY: break; default: return fsalstat(ERR_FSAL_NOTDIR, 0); } COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, proxyv4_obj->fh4); } if (path) { if (!strcmp(path, ".")) { if (!parent) return fsalstat(ERR_FSAL_FAULT, 0); } else if (!strcmp(path, "..")) { if (!parent) return fsalstat(ERR_FSAL_FAULT, 0); COMPOUNDV4_ARG_ADD_OP_LOOKUPP(opcnt, argoparray); } else { COMPOUNDV4_ARG_ADD_OP_LOOKUP(opcnt, argoparray, path); } } fhok = &resoparray[opcnt].nfs_resop4_u.opgetfh.GETFH4res_u.resok4; COMPOUNDV4_ARG_ADD_OP_GETFH(opcnt, argoparray); atok = proxyv4_fill_getattr_reply(resoparray + opcnt, fattr_blob, sizeof(fattr_blob)); COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, proxyv4_bitmap_getattr); /* Dynamic ask of server per file system attr */ if (!parent) { atok_per_file_system_attr = proxyv4_fill_getattr_reply( resoparray + opcnt, fattr_blob_per_fs, sizeof(fattr_blob_per_fs)); COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, proxyv4_bitmap_per_fs); } fhok->object.nfs_fh4_val = (char *)padfilehandle; fhok->object.nfs_fh4_len = sizeof(padfilehandle); rc = proxyv4_nfsv4_call(cred, opcnt, argoparray, resoparray); if (rc != NFS4_OK) return nfsstat4_to_fsal(rc); /* Dynamic check of server per file system attr */ if (!parent) { /* maxread and maxwrite */ fattr4 *obj_attrs = &atok_per_file_system_attr->obj_attributes; proxyv4_check_maxread_maxwrite(export, obj_attrs); } return proxyv4_make_object(export, &atok->obj_attributes, &fhok->object, handle, attrs_out); } static fsal_status_t proxyv4_lookup(struct fsal_obj_handle *parent, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { return proxyv4_lookup_impl(parent, op_ctx->fsal_export, &op_ctx->creds, path, handle, attrs_out); } /* TODO: make this per-export */ static uint64_t fcnt; static fsal_status_t proxyv4_mkdir(struct fsal_obj_handle *dir_hdl, const char *name, struct fsal_attrlist *attrib, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { int rc; int opcnt = 0; fattr4 input_attr; char padfilehandle[NFS4_FHSIZE]; struct proxyv4_obj_handle *ph; char fattr_blob[FATTR_BLOB_SZ]; GETATTR4resok *atok; GETFH4resok *fhok; fsal_status_t st; sessionid4 sid; #define FSAL_MKDIR_NB_OP_ALLOC 5 /* SEQUENCE PUTFH CREATE GETFH GETATTR */ nfs_argop4 argoparray[FSAL_MKDIR_NB_OP_ALLOC]; nfs_resop4 resoparray[FSAL_MKDIR_NB_OP_ALLOC]; /* * The caller gives us partial attributes which include mode and owner * and expects the full attributes back at the end of the call. */ attrib->valid_mask &= ATTR_MODE | ATTR_OWNER | ATTR_GROUP; if (proxyv4_fsalattr_to_fattr4(attrib, &input_attr) == -1) return fsalstat(ERR_FSAL_INVAL, -1); ph = container_of(dir_hdl, struct proxyv4_obj_handle, obj); /* SEQUENCE */ proxyv4_get_client_sessionid(sid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argoparray, sid, NB_RPC_SLOT); COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4); resoparray[opcnt].nfs_resop4_u.opcreate.CREATE4res_u.resok4.attrset = empty_bitmap; COMPOUNDV4_ARG_ADD_OP_MKDIR(opcnt, argoparray, (char *)name, input_attr); fhok = &resoparray[opcnt].nfs_resop4_u.opgetfh.GETFH4res_u.resok4; fhok->object.nfs_fh4_val = padfilehandle; fhok->object.nfs_fh4_len = sizeof(padfilehandle); COMPOUNDV4_ARG_ADD_OP_GETFH(opcnt, argoparray); atok = proxyv4_fill_getattr_reply(resoparray + opcnt, fattr_blob, sizeof(fattr_blob)); COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, proxyv4_bitmap_getattr); rc = proxyv4_nfsv4_call(&op_ctx->creds, opcnt, argoparray, resoparray); nfs4_Fattr_Free(&input_attr); if (rc != NFS4_OK) return nfsstat4_to_fsal(rc); st = proxyv4_make_object(op_ctx->fsal_export, &atok->obj_attributes, &fhok->object, handle, attrs_out); if (FSAL_IS_ERROR(st)) return st; return (*handle)->obj_ops->getattrs(*handle, attrib); } static fsal_status_t proxyv4_mknod(struct fsal_obj_handle *dir_hdl, const char *name, object_file_type_t nodetype, struct fsal_attrlist *attrib, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { int rc; int opcnt = 0; fattr4 input_attr; char padfilehandle[NFS4_FHSIZE]; struct proxyv4_obj_handle *ph; char fattr_blob[FATTR_BLOB_SZ]; GETATTR4resok *atok; GETFH4resok *fhok; fsal_status_t st; enum nfs_ftype4 nf4type; specdata4 specdata = { 0, 0 }; sessionid4 sid; #define FSAL_MKNOD_NB_OP_ALLOC 5 /* SEQUENCE PUTFH CREATE GETFH GETATTR */ nfs_argop4 argoparray[FSAL_MKNOD_NB_OP_ALLOC]; nfs_resop4 resoparray[FSAL_MKNOD_NB_OP_ALLOC]; switch (nodetype) { case CHARACTER_FILE: specdata.specdata1 = attrib->rawdev.major; specdata.specdata2 = attrib->rawdev.minor; nf4type = NF4CHR; break; case BLOCK_FILE: specdata.specdata1 = attrib->rawdev.major; specdata.specdata2 = attrib->rawdev.minor; nf4type = NF4BLK; break; case SOCKET_FILE: nf4type = NF4SOCK; break; case FIFO_FILE: nf4type = NF4FIFO; break; default: return fsalstat(ERR_FSAL_FAULT, EINVAL); } /* * The caller gives us partial attributes which include mode and owner * and expects the full attributes back at the end of the call. */ attrib->valid_mask &= ATTR_MODE | ATTR_OWNER | ATTR_GROUP; if (proxyv4_fsalattr_to_fattr4(attrib, &input_attr) == -1) return fsalstat(ERR_FSAL_INVAL, -1); ph = container_of(dir_hdl, struct proxyv4_obj_handle, obj); /* SEQUENCE */ proxyv4_get_client_sessionid(sid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argoparray, sid, NB_RPC_SLOT); COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4); resoparray[opcnt].nfs_resop4_u.opcreate.CREATE4res_u.resok4.attrset = empty_bitmap; COMPOUNDV4_ARG_ADD_OP_CREATE(opcnt, argoparray, (char *)name, nf4type, input_attr, specdata); fhok = &resoparray[opcnt].nfs_resop4_u.opgetfh.GETFH4res_u.resok4; fhok->object.nfs_fh4_val = padfilehandle; fhok->object.nfs_fh4_len = sizeof(padfilehandle); COMPOUNDV4_ARG_ADD_OP_GETFH(opcnt, argoparray); atok = proxyv4_fill_getattr_reply(resoparray + opcnt, fattr_blob, sizeof(fattr_blob)); COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, proxyv4_bitmap_getattr); rc = proxyv4_nfsv4_call(&op_ctx->creds, opcnt, argoparray, resoparray); nfs4_Fattr_Free(&input_attr); if (rc != NFS4_OK) return nfsstat4_to_fsal(rc); st = proxyv4_make_object(op_ctx->fsal_export, &atok->obj_attributes, &fhok->object, handle, attrs_out); if (FSAL_IS_ERROR(st)) return st; return (*handle)->obj_ops->getattrs(*handle, attrib); } static fsal_status_t proxyv4_symlink(struct fsal_obj_handle *dir_hdl, const char *name, const char *link_path, struct fsal_attrlist *attrib, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { int rc; int opcnt = 0; fattr4 input_attr; char padfilehandle[NFS4_FHSIZE]; char fattr_blob[FATTR_BLOB_SZ]; sessionid4 sid; #define FSAL_SYMLINK_NB_OP_ALLOC 5 /* SEQUENCE PUTFH CREATE GETFH GETATTR */ nfs_argop4 argoparray[FSAL_SYMLINK_NB_OP_ALLOC]; nfs_resop4 resoparray[FSAL_SYMLINK_NB_OP_ALLOC]; GETATTR4resok *atok; GETFH4resok *fhok; fsal_status_t st; struct proxyv4_obj_handle *ph; /* Tests if symlinking is allowed by configuration. */ if (!op_ctx->fsal_export->exp_ops.fs_supports(op_ctx->fsal_export, fso_symlink_support)) return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); attrib->valid_mask = ATTR_MODE; if (proxyv4_fsalattr_to_fattr4(attrib, &input_attr) == -1) return fsalstat(ERR_FSAL_INVAL, -1); ph = container_of(dir_hdl, struct proxyv4_obj_handle, obj); /* SEQUENCE */ proxyv4_get_client_sessionid(sid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argoparray, sid, NB_RPC_SLOT); COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4); resoparray[opcnt].nfs_resop4_u.opcreate.CREATE4res_u.resok4.attrset = empty_bitmap; COMPOUNDV4_ARG_ADD_OP_SYMLINK(opcnt, argoparray, (char *)name, (char *)link_path, input_attr); fhok = &resoparray[opcnt].nfs_resop4_u.opgetfh.GETFH4res_u.resok4; fhok->object.nfs_fh4_val = padfilehandle; fhok->object.nfs_fh4_len = sizeof(padfilehandle); COMPOUNDV4_ARG_ADD_OP_GETFH(opcnt, argoparray); atok = proxyv4_fill_getattr_reply(resoparray + opcnt, fattr_blob, sizeof(fattr_blob)); COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, proxyv4_bitmap_getattr); rc = proxyv4_nfsv4_call(&op_ctx->creds, opcnt, argoparray, resoparray); nfs4_Fattr_Free(&input_attr); if (rc != NFS4_OK) return nfsstat4_to_fsal(rc); st = proxyv4_make_object(op_ctx->fsal_export, &atok->obj_attributes, &fhok->object, handle, attrs_out); if (FSAL_IS_ERROR(st)) return st; return (*handle)->obj_ops->getattrs(*handle, attrib); } static fsal_status_t proxyv4_readlink(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *link_content, bool refresh) { int rc; int opcnt = 0; struct proxyv4_obj_handle *ph; sessionid4 sid; #define FSAL_READLINK_NB_OP_ALLOC 3 /* SEQUENCE PUTFH READLINK */ nfs_argop4 argoparray[FSAL_READLINK_NB_OP_ALLOC]; nfs_resop4 resoparray[FSAL_READLINK_NB_OP_ALLOC]; READLINK4resok *rlok; ph = container_of(obj_hdl, struct proxyv4_obj_handle, obj); /* SEQUENCE */ proxyv4_get_client_sessionid(sid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argoparray, sid, NB_RPC_SLOT); COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4); /* This saves us from having to do one allocation for the XDR, another allocation for the return, and a copy just to get the \NUL terminator. The link length should be cached in the file handle. */ link_content->len = fsal_default_linksize; link_content->addr = gsh_malloc(link_content->len); rlok = &resoparray[opcnt].nfs_resop4_u.opreadlink.READLINK4res_u.resok4; rlok->link.utf8string_val = link_content->addr; rlok->link.utf8string_len = link_content->len; COMPOUNDV4_ARG_ADD_OP_READLINK(opcnt, argoparray); rc = proxyv4_nfsv4_call(&op_ctx->creds, opcnt, argoparray, resoparray); if (rc != NFS4_OK) { gsh_free(link_content->addr); link_content->addr = NULL; link_content->len = 0; return nfsstat4_to_fsal(rc); } rlok->link.utf8string_val[rlok->link.utf8string_len] = '\0'; link_content->len = rlok->link.utf8string_len + 1; return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t proxyv4_link(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *destdir_hdl, const char *name, struct fsal_attrlist *destdir_pre_attrs_out, struct fsal_attrlist *destdir_post_attrs_out) { int rc; struct proxyv4_obj_handle *tgt; struct proxyv4_obj_handle *dst; sessionid4 sid; #define FSAL_LINK_NB_OP_ALLOC 5 /* SEQUENCE PUTFH SAVEFH PUTFH LINK */ nfs_argop4 argoparray[FSAL_LINK_NB_OP_ALLOC]; nfs_resop4 resoparray[FSAL_LINK_NB_OP_ALLOC]; int opcnt = 0; /* Tests if hardlinking is allowed by configuration. */ if (!op_ctx->fsal_export->exp_ops.fs_supports(op_ctx->fsal_export, fso_link_support)) return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); tgt = container_of(obj_hdl, struct proxyv4_obj_handle, obj); dst = container_of(destdir_hdl, struct proxyv4_obj_handle, obj); /* SEQUENCE */ proxyv4_get_client_sessionid(sid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argoparray, sid, NB_RPC_SLOT); COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, tgt->fh4); COMPOUNDV4_ARG_ADD_OP_SAVEFH(opcnt, argoparray); COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, dst->fh4); COMPOUNDV4_ARG_ADD_OP_LINK(opcnt, argoparray, (char *)name); rc = proxyv4_nfsv4_call(&op_ctx->creds, opcnt, argoparray, resoparray); return nfsstat4_to_fsal(rc); } #define FSAL_READDIR_NB_OP_ALLOC 3 /* SEQUENCE PUTFH READDIR */ static bool xdr_readdirres(XDR *x, nfs_resop4 *rdres) { int i; int res = true; for (i = 0; i < FSAL_READDIR_NB_OP_ALLOC; i++) { res = xdr_nfs_resop4(x, rdres + i); if (res != true) return res; } return res; } /* * Trying to guess how many entries can fit into a readdir buffer * is complicated and usually results in either gross over-allocation * of the memory for results or under-allocation (on large directories) * and buffer overruns - just pay the price of allocating the memory * inside XDR decoding and free it when done */ static fsal_status_t proxyv4_do_readdir(struct proxyv4_obj_handle *ph, nfs_cookie4 *cookie, fsal_readdir_cb cb, void *cbarg, attrmask_t attrmask, bool *eof, bool *again) { uint32_t opcnt = 0; int rc; entry4 *e4; sessionid4 sid; nfs_argop4 argoparray[FSAL_READDIR_NB_OP_ALLOC]; nfs_resop4 resoparray[FSAL_READDIR_NB_OP_ALLOC]; READDIR4res *result; READDIR4resok *rdok; fsal_status_t st = { ERR_FSAL_NO_ERROR, 0 }; /* SEQUENCE */ proxyv4_get_client_sessionid(sid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argoparray, sid, NB_RPC_SLOT); /* PUTFH */ COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4); /* Before pushing the READDIR op, grab the result pointer */ result = &resoparray[opcnt].nfs_resop4_u.opreaddir; /* Make sure our result object is clean */ memset(result, 0, sizeof(*result)); rdok = &result->READDIR4res_u.resok4; /* READDIR */ COMPOUNDV4_ARG_ADD_OP_READDIR(opcnt, argoparray, *cookie, proxyv4_bitmap_getattr); rc = proxyv4_nfsv4_call(&op_ctx->creds, opcnt, argoparray, resoparray); if (rc != NFS4_OK) return nfsstat4_to_fsal(rc); *eof = rdok->reply.eof; for (e4 = rdok->reply.entries; e4; e4 = e4->nextentry) { struct fsal_attrlist attrs; struct fsal_obj_handle *handle; enum fsal_dir_result cb_rc; /* UTF8 name does not include trailing 0 */ if (e4->name.utf8string_len > MAXNAMLEN) return fsalstat(ERR_FSAL_SERVERFAULT, E2BIG); if (nfs4_Fattr_To_FSAL_attr(&attrs, &e4->attrs, NULL)) return fsalstat(ERR_FSAL_FAULT, 0); /* * If *again==false : we are in readahead, * we only call cb for next entries but don't update result * for calling readdir. */ if (*again) { *cookie = e4->cookie; *eof = rdok->reply.eof && !e4->nextentry; } /** @todo FSF: this could be handled by getting handle as part * of readdir attributes. However, if acl is * requested, we might get it separately to * avoid over large READDIR response. */ st = proxyv4_lookup_impl(&ph->obj, op_ctx->fsal_export, &op_ctx->creds, e4->name.utf8string_val, &handle, NULL); if (FSAL_IS_ERROR(st)) break; cb_rc = cb(e4->name.utf8string_val, handle, &attrs, cbarg, e4->cookie); fsal_release_attrs(&attrs); if (cb_rc >= DIR_TERMINATE) { *again = false; break; } /* * Read ahead is supported by this FSAL * but limited to the current background readdir request. */ if (cb_rc >= DIR_READAHEAD && *again) { *again = false; } } xdr_free((xdrproc_t)xdr_readdirres, resoparray); return st; } /** * @todo We might add a verifier to the cookie provided * if server needs one ... */ static fsal_status_t proxyv4_readdir(struct fsal_obj_handle *dir_hdl, fsal_cookie_t *whence, void *cbarg, fsal_readdir_cb cb, attrmask_t attrmask, bool *eof) { nfs_cookie4 cookie = 0; struct proxyv4_obj_handle *ph; bool again = true; if (whence) cookie = (nfs_cookie4)*whence; ph = container_of(dir_hdl, struct proxyv4_obj_handle, obj); do { fsal_status_t st; st = proxyv4_do_readdir(ph, &cookie, cb, cbarg, attrmask, eof, &again); if (FSAL_IS_ERROR(st)) return st; } while (*eof == false && again); return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t proxyv4_rename(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *olddir_hdl, const char *old_name, struct fsal_obj_handle *newdir_hdl, const char *new_name, struct fsal_attrlist *olddir_pre_attrs_out, struct fsal_attrlist *olddir_post_attrs_out, struct fsal_attrlist *newdir_pre_attrs_out, struct fsal_attrlist *newdir_post_attrs_out) { int rc; int opcnt = 0; sessionid4 sid; #define FSAL_RENAME_NB_OP_ALLOC 5 /* SEQUENCE PUTFH SAVEFH PUTFH RENAME */ nfs_argop4 argoparray[FSAL_RENAME_NB_OP_ALLOC]; nfs_resop4 resoparray[FSAL_RENAME_NB_OP_ALLOC]; struct proxyv4_obj_handle *src; struct proxyv4_obj_handle *tgt; src = container_of(olddir_hdl, struct proxyv4_obj_handle, obj); tgt = container_of(newdir_hdl, struct proxyv4_obj_handle, obj); /* SEQUENCE */ proxyv4_get_client_sessionid(sid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argoparray, sid, NB_RPC_SLOT); COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, src->fh4); COMPOUNDV4_ARG_ADD_OP_SAVEFH(opcnt, argoparray); COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, tgt->fh4); COMPOUNDV4_ARG_ADD_OP_RENAME(opcnt, argoparray, (char *)old_name, (char *)new_name); rc = proxyv4_nfsv4_call(&op_ctx->creds, opcnt, argoparray, resoparray); return nfsstat4_to_fsal(rc); } static inline int nfs4_Fattr_To_FSAL_attr_savreqmask(struct fsal_attrlist *FSAL_attr, fattr4 *Fattr, compound_data_t *data) { int rc = 0; attrmask_t saved_request_mask = FSAL_attr->request_mask; rc = nfs4_Fattr_To_FSAL_attr(FSAL_attr, Fattr, data); FSAL_attr->request_mask = saved_request_mask; return rc; } static fsal_status_t proxyv4_getattrs(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs) { struct proxyv4_obj_handle *ph; int rc; uint32_t opcnt = 0; sessionid4 sid; #define FSAL_GETATTR_NB_OP_ALLOC 3 /* SEQUENCE PUTFH GETATTR */ nfs_argop4 argoparray[FSAL_GETATTR_NB_OP_ALLOC]; nfs_resop4 resoparray[FSAL_GETATTR_NB_OP_ALLOC]; GETATTR4resok *atok; char fattr_blob[FATTR_BLOB_SZ]; ph = container_of(obj_hdl, struct proxyv4_obj_handle, obj); /* SEQUENCE */ proxyv4_get_client_sessionid(sid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argoparray, sid, NB_RPC_SLOT); COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4); atok = proxyv4_fill_getattr_reply(resoparray + opcnt, fattr_blob, sizeof(fattr_blob)); COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, proxyv4_bitmap_getattr); rc = proxyv4_nfsv4_call(&op_ctx->creds, opcnt, argoparray, resoparray); if (rc != NFS4_OK) { if (attrs->request_mask & ATTR_RDATTR_ERR) { /* Caller asked for error to be visible. */ attrs->valid_mask = ATTR_RDATTR_ERR; } return nfsstat4_to_fsal(rc); } if (nfs4_Fattr_To_FSAL_attr_savreqmask(attrs, &atok->obj_attributes, NULL) != NFS4_OK) return fsalstat(ERR_FSAL_INVAL, 0); return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t proxyv4_unlink(struct fsal_obj_handle *dir_hdl, struct fsal_obj_handle *obj_hdl, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { int opcnt = 0; int rc; struct proxyv4_obj_handle *ph; sessionid4 sid; #define FSAL_UNLINK_NB_OP_ALLOC 4 /* SEQUENCE PUTFH REMOVE GETATTR */ nfs_argop4 argoparray[FSAL_UNLINK_NB_OP_ALLOC]; nfs_resop4 resoparray[FSAL_UNLINK_NB_OP_ALLOC]; #if GETATTR_AFTER GETATTR4resok *atok; char fattr_blob[FATTR_BLOB_SZ]; struct fsal_attrlist dirattr; #endif ph = container_of(dir_hdl, struct proxyv4_obj_handle, obj); /* SEQUENCE */ proxyv4_get_client_sessionid(sid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argoparray, sid, NB_RPC_SLOT); COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4); COMPOUNDV4_ARG_ADD_OP_REMOVE(opcnt, argoparray, (char *)name); #if GETATTR_AFTER atok = proxyv4_fill_getattr_reply(resoparray + opcnt, fattr_blob, sizeof(fattr_blob)); COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, proxyv4_bitmap_getattr); #endif rc = proxyv4_nfsv4_call(&op_ctx->creds, opcnt, argoparray, resoparray); if (rc != NFS4_OK) return nfsstat4_to_fsal(rc); #if GETATTR_AFTER if (nfs4_Fattr_To_FSAL_attr(&dirattr, &atok->obj_attributes, NULL) == NFS4_OK) ph->attributes = dirattr; #endif return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t proxyv4_handle_to_wire(const struct fsal_obj_handle *obj_hdl, fsal_digesttype_t output_type, struct gsh_buffdesc *fh_desc) { struct proxyv4_obj_handle *ph = container_of(obj_hdl, struct proxyv4_obj_handle, obj); size_t fhs; void *data; /* sanity checks */ if (!fh_desc || !fh_desc->addr) return fsalstat(ERR_FSAL_FAULT, 0); switch (output_type) { case FSAL_DIGEST_NFSV3: #ifdef PROXYV4_HANDLE_MAPPING fhs = sizeof(ph->h23); data = &ph->h23; break; #endif case FSAL_DIGEST_NFSV4: fhs = ph->blob.len; data = &ph->blob; break; default: return fsalstat(ERR_FSAL_SERVERFAULT, 0); } if (fh_desc->len < fhs) return fsalstat(ERR_FSAL_TOOSMALL, 0); memcpy(fh_desc->addr, data, fhs); fh_desc->len = fhs; return fsalstat(ERR_FSAL_NO_ERROR, 0); } static void proxyv4_handle_to_key(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *fh_desc) { struct proxyv4_obj_handle *ph = container_of(obj_hdl, struct proxyv4_obj_handle, obj); fh_desc->addr = &ph->blob; fh_desc->len = ph->blob.len; } static void proxyv4_hdl_release(struct fsal_obj_handle *obj_hdl) { struct proxyv4_obj_handle *ph = container_of(obj_hdl, struct proxyv4_obj_handle, obj); fsal_obj_handle_fini(obj_hdl, true); gsh_free(ph); } /* * In this first FSAL_PROXY_V4 support_ex version without state * nothing to do on close. */ static fsal_status_t proxyv4_close(struct fsal_obj_handle *obj_hdl) { return fsalstat(ERR_FSAL_NOT_OPENED, 0); } /* * support_ex methods * * This first dirty version of support_ex in FSAL_PROXY_V4 doesn't take care of * any state. * * The goal achieves by this first dirty version is only to be compliant with * support_ex fsal api. */ /** * @brief Fill share_access and share_deny fields of an OPEN4args struct * * This function fills share_access and share_deny fields of an OPEN4args * struct to prepare an OPEN v4.1 call. * * @param[in] openflags fsal open flags * @param[in,out] share_access share_access field to be filled * @param[in,out] share_deny share_deny field to be filled * * @return FSAL status. */ static fsal_status_t fill_share_OPEN4args(uint32_t *share_access, uint32_t *share_deny, fsal_openflags_t openflags) { /* share access */ *share_access = 0; if (openflags & FSAL_O_READ) *share_access |= OPEN4_SHARE_ACCESS_READ; if (openflags & FSAL_O_WRITE) *share_access |= OPEN4_SHARE_ACCESS_WRITE; /* share write */ *share_deny = OPEN4_SHARE_DENY_NONE; if (openflags & FSAL_O_DENY_READ) *share_deny |= OPEN4_SHARE_DENY_READ; if (openflags & FSAL_O_DENY_WRITE || openflags & FSAL_O_DENY_WRITE_MAND) *share_deny |= OPEN4_SHARE_DENY_WRITE; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Fill openhow field of an OPEN4args struct * * This function fills an openflags4 openhow field of an OPEN4args struct * to prepare an OPEN v4.1 call. * * @param[in] attrs_in open attributes * @param[in] create_mode open create mode * @param[in] verifier open verifier * @param[in,out] openhow openhow field to be filled * @param[in,out] inattrs created inattrs (need to be freed by calling * nfs4_Fattr_Free) * @param[out] setattr_needed an additional setattr op is needed * * @return FSAL status. */ static fsal_status_t fill_openhow_OPEN4args(openflag4 *openhow, fattr4 inattrs, enum fsal_create_mode createmode, fsal_verifier_t verifier, bool *setattr_needed, const char *name, fsal_openflags_t openflags) { if (openhow == NULL) return fsalstat(ERR_FSAL_INVAL, -1); /* openhow */ /* not an open by handle and flag create */ if (name && createmode != FSAL_NO_CREATE) { createhow4 *how = &(openhow->openflag4_u.how); verifier4 *verf; openhow->opentype = OPEN4_CREATE; switch (createmode) { case FSAL_UNCHECKED: how->mode = UNCHECKED4; how->createhow4_u.createattrs = inattrs; break; case FSAL_GUARDED: case FSAL_EXCLUSIVE_9P: how->mode = GUARDED4; how->createhow4_u.createattrs = inattrs; break; case FSAL_EXCLUSIVE: how->mode = EXCLUSIVE4; verf = &how->createhow4_u.createverf; FSAL_VERIFIER_T_TO_VERIFIER4(verf, verifier); /* no way to set attr in same op than old EXCLUSIVE4 */ if (inattrs.attrmask.bitmap4_len > 0) { int i = 0; for (i = 0; i < inattrs.attrmask.bitmap4_len; i++) { if (inattrs.attrmask.map[i]) { *setattr_needed = true; break; } } } break; case FSAL_EXCLUSIVE_41: how->mode = EXCLUSIVE4_1; verf = &how->createhow4_u.ch_createboth.cva_verf; FSAL_VERIFIER_T_TO_VERIFIER4(verf, verifier); how->createhow4_u.ch_createboth.cva_attrs = inattrs; /* * We assume verifier is stored in time metadata. * * We had better check suppattr_exclcreat from * background server. */ if (inattrs.attrmask.bitmap4_len >= 2 && (inattrs.attrmask.map[1] & (1U << (FATTR4_TIME_METADATA - 32)))) { *setattr_needed = true; // cva_attrs was set to inattrs above. inattrs.attrmask.map[1] &= ~(1U << (FATTR4_TIME_METADATA - 32)); } break; default: return fsalstat(ERR_FSAL_FAULT, EINVAL); } } else openhow->opentype = OPEN4_NOCREATE; /* include open by handle and TRUNCATE case in setattr_needed */ if (!name && openflags & FSAL_O_TRUNC) *setattr_needed = true; return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t proxyv4_open2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attrs_in, fsal_verifier_t verifier, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct proxyv4_obj_handle *ph; int rc; /* return code of nfs call */ int opcnt = 0; /* nfs arg counter */ fsal_status_t st; /* return code of fsal call */ char padfilehandle[NFS4_FHSIZE]; /* gotten FH */ char owner_val[128]; int owner_len = 0; uint32_t share_access = 0; uint32_t share_deny = 0; openflag4 openhow; fattr4 inattrs; open_claim4 claim; sessionid4 sid; /* SEQUENCE, PUTFH, OPEN, GETFH, GETATTR */ #define FSAL_OPEN_NB_OP 5 nfs_argop4 argoparray[FSAL_OPEN_NB_OP]; nfs_resop4 resoparray[FSAL_OPEN_NB_OP]; /* SEQUENCE, PUTFH, SETATTR, GETATTR */ #define FSAL_OPEN_SETATTR_NB_OP 4 nfs_argop4 setattr_argoparray[FSAL_OPEN_SETATTR_NB_OP]; nfs_resop4 setattr_resoparray[FSAL_OPEN_SETATTR_NB_OP]; nfs_resop4 *resop; OPEN4resok *opok; GETFH4resok *fhok; GETATTR4resok *atok; char fattr_blob[FATTR_BLOB_SZ]; bool setattr_needed = false; /* we have not done yet any check */ *caller_perm_check = true; /* get back proxy handle */ ph = container_of(obj_hdl, struct proxyv4_obj_handle, obj); /* include TRUNCATE case in attrs_in */ if (openflags & FSAL_O_TRUNC) { attrs_in->valid_mask |= ATTR_SIZE; attrs_in->filesize = 0; } /* fill inattrs */ if (proxyv4_fsalattr_to_fattr4(attrs_in, &inattrs) == -1) return fsalstat(ERR_FSAL_INVAL, -1); /* Three cases need to do an open : * -open by name to get an handle * -open by handle to get attrs_out * -open by handle to truncate */ if (name || attrs_out || openflags & FSAL_O_TRUNC) { /* * We do the open to get handle, check perm, check share, trunc, * create if needed ... */ /* open call will do the perm check */ *caller_perm_check = false; /* prepare open call */ /* SEQUENCE */ proxyv4_get_client_sessionid(sid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argoparray, sid, NB_RPC_SLOT); /* PUTFH */ COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4); /* OPEN */ /* prepare answer */ opok = &resoparray[opcnt].nfs_resop4_u.opopen.OPEN4res_u.resok4; opok->rflags = 0; /* set to NULL for safety */ opok->attrset = empty_bitmap; /* set to empty for safety */ /* prepare open input args */ /* share_access and share_deny */ st = fill_share_OPEN4args(&share_access, &share_deny, openflags); if (FSAL_IS_ERROR(st)) { nfs4_Fattr_Free(&inattrs); return st; } /* owner */ owner_len = snprintf(owner_val, sizeof(owner_val), "GANESHA/PROXY: pid=%u %" PRIu64, getpid(), atomic_inc_uint64_t(&fcnt)); if (owner_len < 0) { int error = errno; LogCrit(COMPONENT_FSAL, "Unexpected return from snprintf %d error %s (%d)", owner_len, strerror(error), error); return posix2fsal_status(error); } else if (owner_len >= sizeof(owner_val)) { LogMajor(COMPONENT_FSAL, "Owner length too long"); return posix2fsal_status(EINVAL); } /* inattrs and openhow */ st = fill_openhow_OPEN4args(&openhow, inattrs, createmode, verifier, &setattr_needed, name, openflags); if (FSAL_IS_ERROR(st)) { nfs4_Fattr_Free(&inattrs); return st; } /* claim : first support_ex version, no state -> no claim */ if (name) { /* open by name */ claim.claim = CLAIM_NULL; claim.open_claim4_u.file.utf8string_val = (char *)name; claim.open_claim4_u.file.utf8string_len = strlen(name); } else { /* open by handle */ claim.claim = CLAIM_FH; } /* add open */ COMPOUNDV4_ARGS_ADD_OP_OPEN_4_1(opcnt, argoparray, share_access, share_deny, owner_val, owner_len, openhow, claim); /* GETFH */ /* prepare answer */ // NOTE(boulos): This is here to make this line than 80 chars. resop = resoparray + opcnt; fhok = &resop->nfs_resop4_u.opgetfh.GETFH4res_u.resok4; fhok->object.nfs_fh4_val = padfilehandle; fhok->object.nfs_fh4_len = sizeof(padfilehandle); COMPOUNDV4_ARG_ADD_OP_GETFH(opcnt, argoparray); if (!setattr_needed && (new_obj || attrs_out)) { /* GETATTR */ atok = proxyv4_fill_getattr_reply(resoparray + opcnt, fattr_blob, sizeof(fattr_blob)); COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, proxyv4_bitmap_getattr); } /* nfs call*/ rc = proxyv4_nfsv4_call(&op_ctx->creds, opcnt, argoparray, resoparray); if (rc != NFS4_OK) { nfs4_Fattr_Free(&inattrs); return nfsstat4_to_fsal(rc); } /* update stateid in current state */ if (state) { struct proxyv4_state *proxyv4_state_id = container_of( state, struct proxyv4_state, state); proxyv4_state_id->stateid = opok->stateid; } } if (setattr_needed) { opcnt = 0; /* SEQUENCE */ proxyv4_get_client_sessionid(sid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, setattr_argoparray, sid, NB_RPC_SLOT); /* PUTFH */ COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, setattr_argoparray, fhok->object); /* SETATTR for truncate */ setattr_resoparray[opcnt].nfs_resop4_u.opsetattr.attrsset = empty_bitmap; /* We have a stateid */ /* cause we did an open when we set setattr_needed. */ COMPOUNDV4_ARG_ADD_OP_SETATTR(opcnt, setattr_argoparray, inattrs, opok->stateid.other); if (new_obj || attrs_out) { /* GETATTR */ nfs_resop4 *setattr_res = setattr_resoparray + opcnt; atok = proxyv4_fill_getattr_reply( setattr_res, fattr_blob, sizeof(fattr_blob)); COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, setattr_argoparray, proxyv4_bitmap_getattr); } /* nfs call*/ rc = proxyv4_nfsv4_call(&op_ctx->creds, opcnt, setattr_argoparray, setattr_resoparray); if (rc != NFS4_OK) { nfs4_Fattr_Free(&inattrs); return nfsstat4_to_fsal(rc); } } /* clean inattrs */ nfs4_Fattr_Free(&inattrs); /* create a new object if asked and attrs_out by the way */ if (new_obj) { if (name) { /* create new_obj and set attrs_out*/ st = proxyv4_make_object(op_ctx->fsal_export, &atok->obj_attributes, &fhok->object, new_obj, attrs_out); if (FSAL_IS_ERROR(st)) return st; } else { *new_obj = obj_hdl; } } /* set attrs_out if needed and not yet done by creating new object */ if (attrs_out && (!new_obj || (new_obj && !name))) { rc = nfs4_Fattr_To_FSAL_attr(attrs_out, &atok->obj_attributes, NULL); if (rc != NFS4_OK) return nfsstat4_to_fsal(rc); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* XXX Note that this only currently supports a vector size of 1 */ static void proxyv4_read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg) { int maxReadSize; int rc; int opcnt = 0; struct proxyv4_obj_handle *ph; sessionid4 sid; #define FSAL_READ2_NB_OP_ALLOC 3 /* SEQUENCE + PUTFH + READ */ nfs_argop4 argoparray[FSAL_READ2_NB_OP_ALLOC]; nfs_resop4 resoparray[FSAL_READ2_NB_OP_ALLOC]; READ4resok *resok; size_t iov_len; ph = container_of(obj_hdl, struct proxyv4_obj_handle, obj); maxReadSize = op_ctx->fsal_export->exp_ops.fs_maxread(op_ctx->fsal_export); iov_len = read_arg->iov[0].iov_len; if (iov_len > maxReadSize) iov_len = maxReadSize; /* SEQUENCE */ proxyv4_get_client_sessionid(sid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argoparray, sid, NB_RPC_SLOT); /* prepare PUTFH */ COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4); /* prepare READ */ resok = &resoparray[opcnt].nfs_resop4_u.opread.READ4res_u.resok4; /* * Setup the resok struct with iovec using iov0. Because we are * decoding from an xdr_mem stream, there will only be one buffer. * We won't copy that buffer on decode. */ resok->data.data_len = read_arg->io_request; resok->data.iovcnt = 1; resok->data.iov = &resok->iov0; /* Issue the read. */ if (bypass) COMPOUNDV4_ARG_ADD_OP_READ_BYPASS(opcnt, argoparray, read_arg->offset, iov_len); else { if (read_arg->state) { struct proxyv4_state *proxyv4_state_id = container_of( read_arg->state, struct proxyv4_state, state); char *other = proxyv4_state_id->stateid.other; COMPOUNDV4_ARG_ADD_OP_READ(opcnt, argoparray, read_arg->offset, iov_len, other); } else { COMPOUNDV4_ARG_ADD_OP_READ_STATELESS( opcnt, argoparray, read_arg->offset, iov_len); } } /* nfs call */ rc = proxyv4_nfsv4_call(&op_ctx->creds, opcnt, argoparray, resoparray); if (rc != NFS4_OK) { done_cb(obj_hdl, nfsstat4_to_fsal(rc), read_arg, caller_arg); return; } /* Copy the read buffer - unfortunately we can't avoid a data copy * here... */ assert(resok->data.iovcnt == 1); assert(read_arg->iov_count == 1); memcpy(read_arg->iov[0].iov_base, resok->data.iov[0].iov_base, resok->data.iov[0].iov_len); read_arg->iov[0].iov_len = resok->data.iov[0].iov_len; /* Fill in the rest of the return */ read_arg->end_of_file = resok->eof; read_arg->io_amount = resok->data.iov[0].iov_len; if (read_arg->info) { read_arg->info->io_content.what = NFS4_CONTENT_DATA; read_arg->info->io_content.data.d_offset = read_arg->offset + read_arg->io_amount; read_arg->info->io_content.data.d_data.data_len = read_arg->io_amount; read_arg->info->io_content.data.d_data.data_val = read_arg->iov[0].iov_base; } done_cb(obj_hdl, fsalstat(0, 0), read_arg, caller_arg); } static void proxyv4_write2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *caller_arg) { int rc; int opcnt = 0; sessionid4 sid; #define FSAL_WRITE_NB_OP_ALLOC 3 /* SEQUENCE + PUTFH + WRITE */ nfs_argop4 argoparray[FSAL_WRITE_NB_OP_ALLOC]; nfs_resop4 resoparray[FSAL_WRITE_NB_OP_ALLOC]; WRITE4resok *wok; struct proxyv4_obj_handle *ph; stable_how4 stable_how; ph = container_of(obj_hdl, struct proxyv4_obj_handle, obj); /* SEQUENCE */ proxyv4_get_client_sessionid(sid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argoparray, sid, NB_RPC_SLOT); /* prepare PUTFH */ COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4); /* prepare write */ wok = &resoparray[opcnt].nfs_resop4_u.opwrite.WRITE4res_u.resok4; if (write_arg->fsal_stable) stable_how = DATA_SYNC4; else stable_how = UNSTABLE4; if (write_arg->state) { struct proxyv4_state *proxyv4_state_id = container_of( write_arg->state, struct proxyv4_state, state); COMPOUNDV4_ARG_ADD_OP_WRITE(opcnt, argoparray, write_arg->offset, write_arg->iov_count, write_arg->iov, write_arg->io_request, stable_how, proxyv4_state_id->stateid.other); } else { COMPOUNDV4_ARG_ADD_OP_WRITE_STATELESS( opcnt, argoparray, write_arg->offset, write_arg->iov_count, write_arg->iov, write_arg->io_request, stable_how); } /* nfs call */ rc = proxyv4_nfsv4_call(&op_ctx->creds, opcnt, argoparray, resoparray); if (rc != NFS4_OK) { done_cb(obj_hdl, nfsstat4_to_fsal(rc), write_arg, caller_arg); return; } /* get res */ write_arg->io_amount = wok->count; if (wok->committed == UNSTABLE4) write_arg->fsal_stable = false; else write_arg->fsal_stable = true; done_cb(obj_hdl, fsalstat(ERR_FSAL_NO_ERROR, 0), write_arg, caller_arg); } static fsal_status_t proxyv4_close2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { struct proxyv4_obj_handle *ph; int rc; int opcnt = 0; sessionid4 sessionid; /* SEQUENCE, PUTFH, CLOSE */ #define FSAL_CLOSE_NB_OP_ALLOC 3 nfs_argop4 argoparray[FSAL_CLOSE_NB_OP_ALLOC]; nfs_resop4 resoparray[FSAL_CLOSE_NB_OP_ALLOC]; char All_Zero[] = "\0\0\0\0\0\0\0\0\0\0\0\0"; /* 12 times \0 */ struct proxyv4_state *proxyv4_state_id = NULL; ph = container_of(obj_hdl, struct proxyv4_obj_handle, obj); /* Check if this was a "stateless" open, * then nothing is to be done at close */ if (!state) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } else { proxyv4_state_id = container_of(state, struct proxyv4_state, state); if (!memcmp(proxyv4_state_id->stateid.other, All_Zero, 12)) return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* SEQUENCE */ proxyv4_get_client_sessionid(sessionid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argoparray, sessionid, NB_RPC_SLOT); /* PUTFH */ COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4); /* CLOSE */ if (state) COMPOUNDV4_ARG_ADD_OP_CLOSE_4_1(opcnt, argoparray, proxyv4_state_id->stateid); else COMPOUNDV4_ARG_ADD_OP_CLOSE_4_1_STATELESS(opcnt, argoparray); rc = proxyv4_nfsv4_call(&op_ctx->creds, opcnt, argoparray, resoparray); if (rc != NFS4_OK) { return nfsstat4_to_fsal(rc); } /* We clean local saved stateid. */ if (state) memset(&proxyv4_state_id->stateid, 0, sizeof(stateid4)); return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t proxyv4_setattr2(struct fsal_obj_handle *obj_hdl, bool bypass, struct state_t *state, struct fsal_attrlist *attrib_set) { int rc; fattr4 input_attr; uint32_t opcnt = 0; struct proxyv4_obj_handle *ph; sessionid4 sid; #define FSAL_SETATTR2_NB_OP_ALLOC 3 /* SEQUENCE PUTFH SETATTR */ nfs_argop4 argoparray[FSAL_SETATTR2_NB_OP_ALLOC]; nfs_resop4 resoparray[FSAL_SETATTR2_NB_OP_ALLOC]; /*prepare attributes */ /* * No way to update CTIME using a NFSv4 SETATTR. * Server will return NFS4ERR_INVAL (22). * time_metadata is a readonly attribute in NFSv4 and NFSv4.1. * (section 5.7 in RFC7530 or RFC5651) * Nevermind : this update is useless, we prevent it. */ FSAL_UNSET_MASK(attrib_set->valid_mask, ATTR_CTIME); if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MODE)) { struct fsal_export *exp = op_ctx->fsal_export; attrib_set->mode &= ~exp->exp_ops.fs_umask(exp); } ph = container_of(obj_hdl, struct proxyv4_obj_handle, obj); if (proxyv4_fsalattr_to_fattr4(attrib_set, &input_attr) == -1) return fsalstat(ERR_FSAL_INVAL, EINVAL); /* SEQUENCE */ proxyv4_get_client_sessionid(sid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argoparray, sid, NB_RPC_SLOT); /* prepare PUTFH */ COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4); /* prepare SETATTR */ resoparray[opcnt].nfs_resop4_u.opsetattr.attrsset = empty_bitmap; /* We don't use the special "bypass" stateid. */ /* Indeed, bypass state will be treated like anonymous value. */ /* RFC 5661, section 8.2.3 */ if (state) { struct proxyv4_state *proxyv4_state_id = container_of(state, struct proxyv4_state, state); COMPOUNDV4_ARG_ADD_OP_SETATTR(opcnt, argoparray, input_attr, proxyv4_state_id->stateid.other); } else { COMPOUNDV4_ARG_ADD_OP_SETATTR_STATELESS(opcnt, argoparray, input_attr); } /* nfs call */ rc = proxyv4_nfsv4_call(&op_ctx->creds, opcnt, argoparray, resoparray); nfs4_Fattr_Free(&input_attr); if (rc != NFS4_OK) return nfsstat4_to_fsal(rc); return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_openflags_t proxyv4_status2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { /* first version of support_ex, no state, no saved openflags */ fsal_openflags_t null_flags = 0; /* closed and deny_none*/ return null_flags; } static fsal_status_t proxyv4_reopen2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags) { /* no way to open by handle in v4 */ /* waiting for v4.1 or solid state to really do the job */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t proxyv4_commit2(struct fsal_obj_handle *obj_hdl, off_t offset, size_t len) { struct proxyv4_obj_handle *ph; int rc; /* return code of nfs call */ int opcnt = 0; /* nfs arg counter */ sessionid4 sid; #define FSAL_COMMIT2_NB_OP 3 /* SEQUENCE, PUTFH, COMMIT */ nfs_argop4 argoparray[FSAL_COMMIT2_NB_OP]; nfs_resop4 resoparray[FSAL_COMMIT2_NB_OP]; ph = container_of(obj_hdl, struct proxyv4_obj_handle, obj); /* SEQUENCE */ proxyv4_get_client_sessionid(sid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argoparray, sid, NB_RPC_SLOT); /* prepare PUTFH */ COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4); /* prepare COMMIT */ COMPOUNDV4_ARG_ADD_OP_COMMIT(opcnt, argoparray, offset, len); /* nfs call */ rc = proxyv4_nfsv4_call(&op_ctx->creds, opcnt, argoparray, resoparray); if (rc != NFS4_OK) return nfsstat4_to_fsal(rc); return fsalstat(ERR_FSAL_NO_ERROR, 0); } void proxyv4_handle_ops_init(struct fsal_obj_ops *ops) { fsal_default_obj_ops_init(ops); ops->release = proxyv4_hdl_release; ops->lookup = proxyv4_lookup; ops->readdir = proxyv4_readdir; ops->mkdir = proxyv4_mkdir; ops->mknode = proxyv4_mknod; ops->symlink = proxyv4_symlink; ops->readlink = proxyv4_readlink; ops->getattrs = proxyv4_getattrs; ops->link = proxyv4_link; ops->rename = proxyv4_rename; ops->unlink = proxyv4_unlink; ops->close = proxyv4_close; ops->handle_to_wire = proxyv4_handle_to_wire; ops->handle_to_key = proxyv4_handle_to_key; ops->open2 = proxyv4_open2; ops->read2 = proxyv4_read2; ops->write2 = proxyv4_write2; ops->close2 = proxyv4_close2; ops->setattr2 = proxyv4_setattr2; ops->status2 = proxyv4_status2; ops->reopen2 = proxyv4_reopen2; ops->commit2 = proxyv4_commit2; } #ifdef PROXYV4_HANDLE_MAPPING static unsigned int hash_nfs_fh4(const nfs_fh4 *fh, unsigned int cookie) { const char *cpt; unsigned int sum = cookie; unsigned int extract; unsigned int mod = fh->nfs_fh4_len % sizeof(unsigned int); for (cpt = fh->nfs_fh4_val; cpt - fh->nfs_fh4_val < fh->nfs_fh4_len - mod; cpt += sizeof(unsigned int)) { memcpy(&extract, cpt, sizeof(unsigned int)); sum = (3 * sum + 5 * extract + 1999); } /* * If the handle is not 32 bits-aligned, the last loop will * get uninitialized chars after the end of the handle. We * must avoid this by skipping the last loop and doing a * special processing for the last bytes */ if (mod) { extract = 0; while (cpt - fh->nfs_fh4_val < fh->nfs_fh4_len) { extract <<= 8; extract |= (uint8_t)(*cpt++); } sum = (3 * sum + 5 * extract + 1999); } return sum; } #endif static struct proxyv4_obj_handle * proxyv4_alloc_handle(struct fsal_export *exp, const nfs_fh4 *fh, fattr4 *obj_attributes, struct fsal_attrlist *attrs_out) { struct proxyv4_obj_handle *n = gsh_calloc(1, sizeof(*n) + fh->nfs_fh4_len); compound_data_t data; struct fsal_attrlist attributes; memset(&attributes, 0, sizeof(attributes)); memset(&data, 0, sizeof(data)); data.current_obj = &n->obj; if (nfs4_Fattr_To_FSAL_attr(&attributes, obj_attributes, &data) != NFS4_OK) { gsh_free(n); return NULL; } n->fh4 = *fh; n->fh4.nfs_fh4_val = n->blob.bytes; memcpy(n->blob.bytes, fh->nfs_fh4_val, fh->nfs_fh4_len); n->blob.len = fh->nfs_fh4_len + sizeof(n->blob); n->blob.type = attributes.type; #ifdef PROXYV4_HANDLE_MAPPING int rc; memset(&n->h23, 0, sizeof(n->h23)); n->h23.len = sizeof(n->h23); n->h23.type = PROXYV4_HANDLE_MAPPED; n->h23.object_id = n->obj.fileid; n->h23.handle_hash = hash_nfs_fh4(fh, n->obj.fileid); rc = HandleMap_SetFH(&n->h23, &n->blob, n->blob.len); if ((rc != HANDLEMAP_SUCCESS) && (rc != HANDLEMAP_EXISTS)) { gsh_free(n); return NULL; } #endif fsal_obj_handle_init(&n->obj, exp, attributes.type, true); n->obj.fs = NULL; n->obj.state_hdl = NULL; n->obj.fsid = attributes.fsid; n->obj.fileid = attributes.fileid; n->obj.obj_ops = &PROXY_V4.handle_ops; if (attrs_out != NULL) { /* We aren't keeping ACL ref ourself, so pass it * to the caller. */ fsal_copy_attrs(attrs_out, &attributes, true); } else { /* Make sure we release the attributes. */ fsal_release_attrs(&attributes); } return n; } /* export methods that create object handles */ fsal_status_t proxyv4_lookup_path(struct fsal_export *exp_hdl, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { struct fsal_obj_handle *next; struct fsal_obj_handle *parent = NULL; char *saved; char *pcopy; char *p, *pnext; struct user_cred *creds = &op_ctx->creds; pcopy = gsh_strdup(path); p = strtok_r(pcopy, "/", &saved); if (!p) { fsal_status_t st = proxyv4_lookup_impl(parent, exp_hdl, creds, NULL, &next, attrs_out); if (FSAL_IS_ERROR(st)) { gsh_free(pcopy); return st; } } while (p) { if (strcmp(p, "..") == 0) { /* Don't allow lookup of ".." */ LogInfo(COMPONENT_FSAL, "Attempt to use \"..\" element in path %s", path); gsh_free(pcopy); return fsalstat(ERR_FSAL_ACCESS, EACCES); } /* Get the next token now, so we know if we are at the * terminal token or not. */ pnext = strtok_r(NULL, "/", &saved); /* Note that if any element is a symlink, the following will * fail, thus no security exposure. Only pass back the * attributes of the terminal lookup. */ fsal_status_t st = proxyv4_lookup_impl(parent, exp_hdl, creds, p, &next, pnext == NULL ? attrs_out : NULL); if (FSAL_IS_ERROR(st)) { gsh_free(pcopy); return st; } p = pnext; parent = next; } /* The final element could be a symlink, but either way we are called * will not work with a symlink, so no security exposure there. */ gsh_free(pcopy); *handle = next; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* * Create an FSAL 'object' from the handle - used * to construct objects from a handle which has been * 'extracted' by .wire_to_host. */ fsal_status_t proxyv4_create_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *hdl_desc, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { nfs_fh4 fh4; struct proxyv4_obj_handle *ph; struct proxyv4_handle_blob *blob; int rc; uint32_t opcnt = 0; sessionid4 sid; #define FSAL_CREATE_HANDLE_NB_OP_ALLOC 3 /* SEQUENCE PUTFH GETATTR */ nfs_argop4 argoparray[FSAL_CREATE_HANDLE_NB_OP_ALLOC]; nfs_resop4 resoparray[FSAL_CREATE_HANDLE_NB_OP_ALLOC]; GETATTR4resok *atok; char fattr_blob[FATTR_BLOB_SZ]; blob = (struct proxyv4_handle_blob *)hdl_desc->addr; if (blob->len != hdl_desc->len) return fsalstat(ERR_FSAL_INVAL, 0); fh4.nfs_fh4_val = blob->bytes; fh4.nfs_fh4_len = blob->len - sizeof(*blob); /* SEQUENCE */ proxyv4_get_client_sessionid(sid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argoparray, sid, NB_RPC_SLOT); COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, fh4); atok = proxyv4_fill_getattr_reply(resoparray + opcnt, fattr_blob, sizeof(fattr_blob)); COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, proxyv4_bitmap_getattr); rc = proxyv4_nfsv4_call(&op_ctx->creds, opcnt, argoparray, resoparray); if (rc != NFS4_OK) return nfsstat4_to_fsal(rc); ph = proxyv4_alloc_handle(exp_hdl, &fh4, &atok->obj_attributes, attrs_out); if (!ph) return fsalstat(ERR_FSAL_FAULT, 0); *handle = &ph->obj; return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t proxyv4_get_dynamic_info(struct fsal_export *exp_hdl, struct fsal_obj_handle *obj_hdl, fsal_dynamicfsinfo_t *infop) { int rc; int opcnt = 0; sessionid4 sid; #define FSAL_FSINFO_NB_OP_ALLOC 3 /* SEQUENCE PUTFH GETATTR */ nfs_argop4 argoparray[FSAL_FSINFO_NB_OP_ALLOC]; nfs_resop4 resoparray[FSAL_FSINFO_NB_OP_ALLOC]; GETATTR4resok *atok; char fattr_blob[48]; /* 6 values, 8 bytes each */ struct proxyv4_obj_handle *ph; ph = container_of(obj_hdl, struct proxyv4_obj_handle, obj); /* SEQUENCE */ proxyv4_get_client_sessionid(sid); COMPOUNDV4_ARG_ADD_OP_SEQUENCE(opcnt, argoparray, sid, NB_RPC_SLOT); COMPOUNDV4_ARG_ADD_OP_PUTFH(opcnt, argoparray, ph->fh4); atok = proxyv4_fill_getattr_reply(resoparray + opcnt, fattr_blob, sizeof(fattr_blob)); COMPOUNDV4_ARG_ADD_OP_GETATTR(opcnt, argoparray, proxyv4_bitmap_fsinfo); rc = proxyv4_nfsv4_call(&op_ctx->creds, opcnt, argoparray, resoparray); if (rc != NFS4_OK) return nfsstat4_to_fsal(rc); if (nfs4_Fattr_To_fsinfo(infop, &atok->obj_attributes) != NFS4_OK) return fsalstat(ERR_FSAL_INVAL, 0); /* At the moment, we don't actually decode time_delta from the * proxied server, so let's just hardcode. */ infop->time_delta.tv_sec = 0; infop->time_delta.tv_nsec = FSAL_DEFAULT_TIME_DELTA_NSEC; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* Convert of-the-wire digest into unique 'handle' which * can be used to identify the object */ fsal_status_t proxyv4_wire_to_host(struct fsal_export *exp_hdl, fsal_digesttype_t in_type, struct gsh_buffdesc *fh_desc, int flags) { struct proxyv4_handle_blob *proxyv4_blob; size_t fh_size; if (!fh_desc || !fh_desc->addr) return fsalstat(ERR_FSAL_FAULT, EINVAL); proxyv4_blob = (struct proxyv4_handle_blob *)fh_desc->addr; fh_size = proxyv4_blob->len; #ifdef PROXYV4_HANDLE_MAPPING if (in_type == FSAL_DIGEST_NFSV3) fh_size = sizeof(nfs23_map_handle_t); #endif if (fh_desc->len != fh_size) { LogMajor(COMPONENT_FSAL, "Size mismatch for handle. should be %zu, got %zu", fh_size, fh_desc->len); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } #ifdef PROXYV4_HANDLE_MAPPING if (in_type == FSAL_DIGEST_NFSV3) { nfs23_map_handle_t *h23 = (nfs23_map_handle_t *)fh_desc->addr; if (h23->type != PROXYV4_HANDLE_MAPPED) return fsalstat(ERR_FSAL_STALE, ESTALE); /* As long as HandleMap_GetFH copies nfs23 handle into * the key before lookup I can get away with using * the same buffer for input and output */ if (HandleMap_GetFH(h23, fh_desc) != HANDLEMAP_SUCCESS) return fsalstat(ERR_FSAL_STALE, 0); fh_size = fh_desc->len; } #endif fh_desc->len = fh_size; return fsalstat(ERR_FSAL_NO_ERROR, 0); } nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V4/handle_mapping/000077500000000000000000000000001473756622300221565ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V4/handle_mapping/CMakeLists.txt000066400000000000000000000055201473756622300247200ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- ########### next target ############### SET(handlemapping_STAT_SRCS handle_mapping.c handle_mapping.h handle_mapping_db.c handle_mapping_db.h handle_mapping_internal.h ) add_library(handlemapping STATIC ${handlemapping_STAT_SRCS}) add_sanitizers(handlemapping) if (USE_LTTNG) add_dependencies(handlemapping gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) ########### next target ############### SET(test_handle_mapping_db_SRCS test_handle_mapping_db.c ) add_executable(test_handle_mapping_db ${test_handle_mapping_db_SRCS}) target_link_libraries(test_handle_mapping_db handlemapping hashtable log common_utils rwlock sqlite3) ########### next target ############### SET(test_handle_mapping_SRCS test_handle_mapping.c ) add_executable(test_handle_mapping ${test_handle_mapping_SRCS}) target_link_libraries(test_handle_mapping handlemapping hashtable log common_utils rwlock sqlite3) ########### install files ############### #original Makefile.am contents follow: #AM_CFLAGS = $(SQLITE_CFLAGS) # #noinst_LTLIBRARIES = libhandlemapping.la # #libhandlemapping_la_SOURCES = handle_mapping.c handle_mapping.h handle_mapping_db.c handle_mapping_db.h handle_mapping_internal.h # # #check_PROGRAMS = test_handle_mapping_db test_handle_mapping #test_handle_mapping_db_SOURCES = test_handle_mapping_db.c #test_handle_mapping_db_LDADD = libhandlemapping.la $(top_srcdir)/HashTable/libhashtable.la $(top_srcdir)/Log/liblog.la \ # $(top_srcdir)/Common/libcommon_utils.la -lsqlite3 # # #test_handle_mapping_SOURCES = test_handle_mapping.c #test_handle_mapping_LDADD = libhandlemapping.la $(top_srcdir)/HashTable/libhashtable.la $(top_srcdir)/Log/liblog.la \ # $(top_srcdir)/Common/libcommon_utils.la -lsqlite3 # #new: clean all # nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V4/handle_mapping/handle_mapping.c000066400000000000000000000254001473756622300252710ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: */ /* * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file handle_mapping.c * * \brief This module is used for managing a persistent map * between PROXY_V4 FSAL handles (including NFSv4 handles from server) * and nfsv3 handles digests (sent to client). */ #include "config.h" #include "fsal.h" #include "nfs4.h" #include "handle_mapping.h" #include "handle_mapping_db.h" #include "handle_mapping_internal.h" static hash_table_t *handle_map_hash; /* memory pool definitions */ typedef struct digest_pool_entry__ { nfs23_map_handle_t nfs23_digest; } digest_pool_entry_t; typedef struct handle_pool_entry__ { uint32_t fh_len; /* The fh_data structure is not simply a file handle, * but is a proxyv4_handle_blob data structure that * includes two uint8_t values and then the actual * NFS file handle data. */ char fh_data[PROXYV4_HANDLE_MAXLEN]; } handle_pool_entry_t; pool_t *digest_pool; pool_t *handle_pool; /* helpers for pool allocation */ static digest_pool_entry_t *digest_alloc(void) { digest_pool_entry_t *p_new; p_new = pool_alloc(digest_pool); return p_new; } static void digest_free(digest_pool_entry_t *p_digest) { memset(p_digest, 0, sizeof(digest_pool_entry_t)); pool_free(digest_pool, p_digest); } static handle_pool_entry_t *handle_alloc(void) { handle_pool_entry_t *p_new; p_new = pool_alloc(handle_pool); return p_new; } static void handle_free(handle_pool_entry_t *p_handle) { memset(p_handle, 0, sizeof(handle_pool_entry_t)); pool_free(handle_pool, p_handle); } /* hash table functions */ static uint32_t hash_digest_idx(hash_parameter_t *p_conf, struct gsh_buffdesc *p_key) { uint32_t hash; digest_pool_entry_t *p_digest = (digest_pool_entry_t *)p_key->addr; hash = ((unsigned long)p_digest->nfs23_digest.object_id ^ (unsigned int)p_digest->nfs23_digest.handle_hash); hash = (743 * hash + 1999) % p_conf->index_size; return hash; } static unsigned long hash_digest_rbt(hash_parameter_t *p_conf, struct gsh_buffdesc *p_key) { unsigned long hash; digest_pool_entry_t *p_digest = (digest_pool_entry_t *)p_key->addr; hash = (257 * p_digest->nfs23_digest.object_id + 541); return hash; } static int cmp_digest(struct gsh_buffdesc *p_key1, struct gsh_buffdesc *p_key2) { digest_pool_entry_t *p_digest1 = (digest_pool_entry_t *)p_key1->addr; digest_pool_entry_t *p_digest2 = (digest_pool_entry_t *)p_key2->addr; /* compare object_id and handle_hash */ if (p_digest1->nfs23_digest.object_id != p_digest2->nfs23_digest.object_id) return (int)(p_digest1->nfs23_digest.object_id - p_digest2->nfs23_digest.object_id); else if (p_digest1->nfs23_digest.handle_hash != p_digest2->nfs23_digest.handle_hash) return (int)p_digest1->nfs23_digest.handle_hash - (int)p_digest2->nfs23_digest.handle_hash; else /* same */ return 0; } /** * @brief Display the handle digest (key) * * @param[in] dspbuf display buffer to display into * @param[in] buff Buffer to display */ static int display_digest(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { digest_pool_entry_t *p_digest = (digest_pool_entry_t *)buff->addr; return display_printf(dspbuf, "%" PRIu64 ", %u", p_digest->nfs23_digest.object_id, p_digest->nfs23_digest.handle_hash); } /** * @brief Display the handle (val) * * @param[in] dspbuf display buffer to display into * @param[in] buff Buffer to display */ static int display_handle(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { handle_pool_entry_t *p_handle = (handle_pool_entry_t *)buff->addr; return display_opaque_bytes(dspbuf, p_handle->fh_data, p_handle->fh_len); } int handle_mapping_hash_add(hash_table_t *p_hash, uint64_t object_id, unsigned int handle_hash, const void *data, uint32_t datalen) { int rc; struct gsh_buffdesc buffkey; struct gsh_buffdesc buffval; digest_pool_entry_t *digest; handle_pool_entry_t *handle; if (datalen >= sizeof(handle->fh_data)) return HANDLEMAP_INVALID_PARAM; digest = digest_alloc(); if (!digest) return HANDLEMAP_SYSTEM_ERROR; handle = handle_alloc(); if (!handle) { digest_free(digest); return HANDLEMAP_SYSTEM_ERROR; } digest->nfs23_digest.object_id = object_id; digest->nfs23_digest.handle_hash = handle_hash; memset(handle->fh_data, 0, sizeof(handle->fh_data)); memcpy(handle->fh_data, data, datalen); handle->fh_len = datalen; buffkey.addr = digest; buffkey.len = sizeof(digest_pool_entry_t); buffval.addr = handle; buffval.len = sizeof(handle_pool_entry_t); rc = hashtable_test_and_set(handle_map_hash, &buffkey, &buffval, HASHTABLE_SET_HOW_SET_NO_OVERWRITE); if (rc != HASHTABLE_SUCCESS) { digest_free(digest); handle_free(handle); if (rc != HASHTABLE_ERROR_KEY_ALREADY_EXISTS) { LogCrit(COMPONENT_FSAL, "ERROR %d inserting entry to handle mapping hash table", rc); return HANDLEMAP_HASHTABLE_ERROR; } else { return HANDLEMAP_EXISTS; } } return HANDLEMAP_SUCCESS; } /* DEFAULT PARAMETERS for hash table */ static hash_parameter_t handle_hash_config = { .index_size = 67, .hash_func_key = hash_digest_idx, .hash_func_rbt = hash_digest_rbt, .compare_key = cmp_digest, .display_key = display_digest, .display_val = display_handle }; /** * Init handle mapping module. * Reloads the content of the mapping files it they exist, * else it creates them. * \return 0 if OK, a posix error code else. */ int HandleMap_Init(const handle_map_param_t *p_param) { int rc; /* first check database count */ rc = handlemap_db_count(p_param->databases_directory); if ((rc > 0) && (rc != p_param->database_count)) { LogCrit(COMPONENT_FSAL, "ERROR: The number of existing databases (%u) does not match the requested DB thread count (%u)", rc, p_param->database_count); return HANDLEMAP_INVALID_PARAM; } else if (rc < 0) return -rc; /* init database module */ rc = handlemap_db_init(p_param->databases_directory, p_param->temp_directory, p_param->database_count, p_param->synchronous_insert); if (rc) { LogCrit(COMPONENT_FSAL, "ERROR %d initializing database access", rc); return rc; } /* initialize memory pool of digests and handles */ digest_pool = pool_basic_init("digest_pool", sizeof(digest_pool_entry_t)); handle_pool = pool_basic_init("handle_pool", sizeof(handle_pool_entry_t)); /* create hash table */ handle_hash_config.index_size = p_param->hashtable_size; handle_map_hash = hashtable_init(&handle_hash_config); if (!handle_map_hash) { LogCrit(COMPONENT_FSAL, "ERROR creating hash table for handle mapping"); return HANDLEMAP_INTERNAL_ERROR; } /* reload previous data */ rc = handlemap_db_reaload_all(handle_map_hash); if (rc) { LogCrit(COMPONENT_FSAL, "ERROR %d reloading handle mapping from database", rc); return rc; } return HANDLEMAP_SUCCESS; } /** * @brief Retrieves a full fsal_handle from a NFS3 digest. * * @param[in] nfs23_digest The NFS3 handle digest * @param[out] fsal_handle The fsal handle to be retrieved * * @note The caller must provide storage for nfs_fh4_val. * * @retval HANDLEMAP_SUCCESS if the handle is available * @retval HANDLEMAP_STALE if the disgest is unknown or the handle has been * deleted */ int HandleMap_GetFH(const nfs23_map_handle_t *nfs23_digest, struct gsh_buffdesc *fsal_handle) { int rc; struct gsh_buffdesc buffkey; struct gsh_buffdesc buffval; digest_pool_entry_t digest; struct hash_latch hl; digest.nfs23_digest = *nfs23_digest; buffkey.addr = &digest; buffkey.len = sizeof(digest_pool_entry_t); rc = hashtable_getlatch(handle_map_hash, &buffkey, &buffval, 0, &hl); if (rc == HASHTABLE_SUCCESS) { handle_pool_entry_t *h = (handle_pool_entry_t *)buffval.addr; if (h->fh_len < PROXYV4_HANDLE_MAXLEN) { fsal_handle->len = h->fh_len; memcpy(fsal_handle->addr, h->fh_data, h->fh_len); rc = HANDLEMAP_SUCCESS; } else { rc = HANDLEMAP_INTERNAL_ERROR; } hashtable_releaselatched(handle_map_hash, &hl); return rc; } if (rc == HASHTABLE_ERROR_NO_SUCH_KEY) hashtable_releaselatched(handle_map_hash, &hl); return HANDLEMAP_STALE; } /* HandleMap_GetFH */ /** * Save the handle association if it was unknown. */ int HandleMap_SetFH(nfs23_map_handle_t *p_in_nfs23_digest, const void *data, uint32_t len) { int rc; /* first, try to insert it to the hash table */ rc = handle_mapping_hash_add(handle_map_hash, p_in_nfs23_digest->object_id, p_in_nfs23_digest->handle_hash, data, len); if ((rc != 0) && (rc != HANDLEMAP_EXISTS)) /* error */ return rc; else if (rc == HANDLEMAP_EXISTS) /* already in database */ return HANDLEMAP_EXISTS; else { /* insert it to DB */ return handlemap_db_insert(p_in_nfs23_digest, data, len); } } /** * Remove a handle from the map * when it was removed from the filesystem * or when it is stale. */ int HandleMap_DelFH(nfs23_map_handle_t *p_in_nfs23_digest) { int rc; struct gsh_buffdesc buffkey, stored_buffkey; struct gsh_buffdesc stored_buffval; digest_pool_entry_t digest; digest_pool_entry_t *p_stored_digest; handle_pool_entry_t *p_stored_handle; /* first, delete it from hash table */ digest.nfs23_digest = *p_in_nfs23_digest; buffkey.addr = &digest; buffkey.len = sizeof(digest_pool_entry_t); rc = HashTable_Del(handle_map_hash, &buffkey, &stored_buffkey, &stored_buffval); if (rc != HASHTABLE_SUCCESS) return HANDLEMAP_STALE; p_stored_digest = (digest_pool_entry_t *)stored_buffkey.addr; p_stored_handle = (handle_pool_entry_t *)stored_buffval.addr; digest_free(p_stored_digest); handle_free(p_stored_handle); /* then, submit the request to the database */ return handlemap_db_delete(p_in_nfs23_digest); } /** * Flush pending database operations (before stopping the server). */ int HandleMap_Flush(void) { return handlemap_db_flush(); } nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V4/handle_mapping/handle_mapping.h000066400000000000000000000060221473756622300252750ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: */ /* * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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-130 USA * * --------------------------------------- */ /** * @file handle_mapping.h * * @brief This module is used for managing a persistent map * between PROXY_V4 FSAL handles (including NFSv4 handles from server) * and nfsv3 handles digests (sent to client). */ #ifndef _HANDLE_MAPPING_H #define _HANDLE_MAPPING_H #include "fsal.h" /* parameters for Handle Map module */ typedef struct handle_map_param__ { /* path where database files are located */ char *databases_directory; /* temp dir for database work */ char *temp_directory; /* number of databases */ unsigned int database_count; /* hash table size */ unsigned int hashtable_size; /* synchronous insert mode */ int synchronous_insert; } handle_map_param_t; /* this describes a handle digest for nfsv3 */ #define PROXYV4_HANDLE_MAPPED 0x23 typedef struct nfs23_map_handle__ { uint8_t len; uint8_t type; /* Must be PROXYV4_HANDLE_MAPPED */ /* to avoid reusing handles, when object_id is reused */ unsigned int handle_hash; /* object id */ uint64_t object_id; } nfs23_map_handle_t; /* The maximum length of a v4 proxy file handle. This is the * length of an NFSv4 File Handle, plus 2 bytes to account * for the fact that the handle is pulled from the * proxyv4_handle_blob data structure, allowing enough * room in memory to pull that entire structure and then * parse out the actual file handle. */ #define PROXYV4_HANDLE_MAXLEN (NFS4_FHSIZE + 2) /* Error codes */ #define HANDLEMAP_SUCCESS 0 #define HANDLEMAP_STALE 1 #define HANDLEMAP_INCONSISTENCY 2 #define HANDLEMAP_DB_ERROR 3 #define HANDLEMAP_SYSTEM_ERROR 4 #define HANDLEMAP_INTERNAL_ERROR 5 #define HANDLEMAP_INVALID_PARAM 6 #define HANDLEMAP_HASHTABLE_ERROR 7 #define HANDLEMAP_EXISTS 8 int HandleMap_Init(const handle_map_param_t *p_param); int HandleMap_GetFH(const nfs23_map_handle_t *, struct gsh_buffdesc *); int HandleMap_SetFH(nfs23_map_handle_t *p_in_nfs23_digest, const void *p_in_handle, uint32_t len); int HandleMap_DelFH(nfs23_map_handle_t *p_in_nfs23_digest); int HandleMap_Flush(void); #endif nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V4/handle_mapping/handle_mapping_db.c000066400000000000000000000701421473756622300257410ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: */ /* * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ #include "config.h" #include "handle_mapping.h" #include "handle_mapping_db.h" #include "handle_mapping_internal.h" #include #include #include #include #include #include #include #include /* sqlite check macros */ #define CheckTable(_p_conn_, _code_, _msg_str_, _result_) \ do { \ if ((_code_) != SQLITE_OK) { \ LogCrit(COMPONENT_FSAL, \ "SQLite command failed in %s line %i", \ __func__, __LINE__); \ LogCrit(COMPONENT_FSAL, "%s (%d)", \ (_msg_str_ ? _msg_str_ : \ sqlite3_errmsg(_p_conn_)), \ _code_); \ if (_msg_str_) { \ sqlite3_free(_msg_str_); \ _msg_str_ = NULL; \ } \ if (_result_) { \ sqlite3_free_table(_result_); \ _result_ = NULL; \ } \ return HANDLEMAP_DB_ERROR; \ } \ } while (0) #define CheckCommand(_p_conn_, _code_, _msg_str_) \ do { \ if ((_code_) != SQLITE_OK) { \ LogCrit(COMPONENT_FSAL, \ "SQLite command failed in %s line %i", \ __func__, __LINE__); \ LogCrit(COMPONENT_FSAL, "%s (%d)", \ (_msg_str_ ? _msg_str_ : \ sqlite3_errmsg(_p_conn_)), \ _code_); \ if (_msg_str_) { \ sqlite3_free(_msg_str_); \ _msg_str_ = NULL; \ } \ return HANDLEMAP_DB_ERROR; \ } \ } while (0) #define CheckPrepare(_p_conn_, _code_) \ do { \ if ((_code_) != SQLITE_OK) { \ LogCrit(COMPONENT_FSAL, \ "SQLite prepare statement failed in %s line %i", \ __func__, __LINE__); \ LogCrit(COMPONENT_FSAL, "%s (%d)", \ sqlite3_errmsg(_p_conn_), _code_); \ return HANDLEMAP_DB_ERROR; \ } \ } while (0) #define CheckBind(_p_conn_, _code_, _stmt_) \ do { \ if ((_code_) != SQLITE_OK) { \ LogCrit(COMPONENT_FSAL, \ "SQLite parameter binding failed in %s line %i", \ __func__, __LINE__); \ LogCrit(COMPONENT_FSAL, "%s (%d)", \ sqlite3_errmsg(_p_conn_), _code_); \ sqlite3_clear_bindings(_stmt_); \ return HANDLEMAP_DB_ERROR; \ } \ } while (0) #define CheckStep(_p_conn_, _code_, _stmt_) \ do { \ if ((_code_) != SQLITE_OK && (_code_) != SQLITE_ROW && \ (_code_) != SQLITE_DONE) { \ LogCrit(COMPONENT_FSAL, \ "SQLite command failed in %s line %i", \ __func__, __LINE__); \ LogCrit(COMPONENT_FSAL, "%s (%d)", \ sqlite3_errmsg(_p_conn_), _code_); \ sqlite3_reset(_stmt_); \ return HANDLEMAP_DB_ERROR; \ } \ } while (0) /* Type of DB operations */ typedef enum { LOAD = 1, INSERT, DELETE } db_op_type; /* DB operation arguments */ typedef struct db_op_item__ { db_op_type op_type; /* operation info */ union { struct hdlmap_tuple { nfs23_map_handle_t nfs23_digest; uint8_t fh4_len; char fh4_data[NFS4_FHSIZE]; } fh_info; hash_table_t *hash; } op_arg; /* for chained list */ struct db_op_item__ *p_next; } db_op_item_t; /* the queue for each DB flusher thread */ typedef struct flusher_queue__ { /* the queue for high priority operations */ db_op_item_t *highprio_first; db_op_item_t *highprio_last; /* the queue for low priority operations */ db_op_item_t *lowprio_first; db_op_item_t *lowprio_last; /* number of operations pending */ unsigned int nb_waiting; pthread_mutex_t queues_mutex; pthread_cond_t work_avail_condition; pthread_cond_t work_done_condition; /* status (used for work_done_condition) */ enum { NOT_READY, IDLE, WORKING, FINISHED } status; } flusher_queue_t; #define LOAD_ALL_STATEMENT 0 #define INSERT_STATEMENT 1 #define DELETE_STATEMENT 2 #define STATEMENT_COUNT 3 /* thread info */ typedef struct db_thread_info__ { pthread_t thr_id; unsigned int thr_index; flusher_queue_t work_queue; /* SQLite database connection */ sqlite3 *db_conn; /* prepared statement table */ sqlite3_stmt *prep_stmt[STATEMENT_COUNT]; /* this pool is accessed by submitter * and by the db thread */ pthread_mutex_t pool_mutex; pool_t *dbop_pool; } db_thread_info_t; static char dbmap_dir[MAXPATHLEN]; static char db_tmpdir[MAXPATHLEN]; static unsigned int nb_db_threads; static int synchronous; /* used for clean shutdown */ static int do_terminate; /* all information and context for threads */ static db_thread_info_t db_thread[MAX_DB]; /* test if a letter is hexa */ #define IS_HEXA(c) \ ((((c) >= '0') && ((c) <= '9')) || (((c) >= 'A') && ((c) <= 'F')) || \ (((c) >= 'a') && ((c) <= 'f'))) /* converts an hexa letter */ #define HEXA2BYTE(c) \ ((unsigned char)(((c) >= '0') && ((c) <= '9') ? \ ((c) - '0') : \ (((c) >= 'A') && ((c) <= 'F') ? \ ((c) - 'A' + 10) : \ (((c) >= 'a') && ((c) <= 'f') ? \ ((c) - 'a' + 10) : \ 0)))) /** * @brief Read a hexadecimal string into memory * * @param[out] target Where memory is to be written * @param[in] tgt_size Size of the target buffer * @param[in] str_source Hexadecimal string * * @retval The number of bytes read in the source string. * @retval -1 on error. */ int sscanmem(void *target, size_t tgt_size, const char *str_source) { unsigned char *mem; /* the current byte to be set */ const char *src; /* pointer to the current char to be read. */ int nb_read = 0; src = str_source; for (mem = (unsigned char *)target; mem < ((unsigned char *)target + tgt_size); mem++) { unsigned char tmp_val; /* we must read 2 bytes (written in hexa) to have 1 target byte value. */ if ((*src == '\0') || (*(src + 1) == '\0')) { /* error, the source string is too small */ return -1; } /* they must be hexa values */ if (!IS_HEXA(*src) || !IS_HEXA(*(src + 1))) return -1; /* we read hexa values. */ tmp_val = (HEXA2BYTE(*src) << 4) + HEXA2BYTE(*(src + 1)); /* we had them to the target buffer */ (*mem) = tmp_val; src += 2; nb_read += 2; } return nb_read; } /* Initialize basic structures for a thread */ static int init_db_thread_info(db_thread_info_t *p_thr_info, unsigned int nb_dbop_prealloc) { unsigned int i; if (!p_thr_info) return HANDLEMAP_INTERNAL_ERROR; memset(p_thr_info, 0, sizeof(db_thread_info_t)); p_thr_info->work_queue.highprio_first = NULL; p_thr_info->work_queue.highprio_last = NULL; p_thr_info->work_queue.lowprio_first = NULL; p_thr_info->work_queue.lowprio_last = NULL; p_thr_info->work_queue.nb_waiting = 0; PTHREAD_MUTEX_init(&p_thr_info->work_queue.queues_mutex, NULL); PTHREAD_COND_init(&p_thr_info->work_queue.work_avail_condition, NULL); PTHREAD_COND_init(&p_thr_info->work_queue.work_done_condition, NULL); /* init thread status */ p_thr_info->work_queue.status = NOT_READY; p_thr_info->db_conn = NULL; for (i = 0; i < STATEMENT_COUNT; i++) p_thr_info->prep_stmt[i] = NULL; /* init memory pool */ PTHREAD_MUTEX_init(&p_thr_info->pool_mutex, NULL); p_thr_info->dbop_pool = pool_basic_init("drop_pool", sizeof(db_op_item_t)); return HANDLEMAP_SUCCESS; } /* Called by a thread to initialize its database access. * After this call: * - database connection is established * - schema is created * - prepared statements are ready to be used */ static int init_database_access(db_thread_info_t *p_thr_info) { char db_file[MAXPATHLEN]; int rc; char **result = NULL; int rows, cols; char *errmsg = NULL; const char *unparsed; /* first open the database file */ rc = snprintf(db_file, sizeof(db_file), "%s/%s.%u", dbmap_dir, DB_FILE_PREFIX, p_thr_info->thr_index); if (rc < 0) { LogCrit(COMPONENT_FSAL, "Unexpected return from snprintf %d error %s (%d)", rc, strerror(errno), errno); return HANDLEMAP_DB_ERROR; } else if (rc >= sizeof(db_file)) { LogCrit(COMPONENT_FSAL, "PROXY_V4 HANDLE DB path %s/%s.%u too long", dbmap_dir, DB_FILE_PREFIX, p_thr_info->thr_index); return HANDLEMAP_DB_ERROR; } rc = sqlite3_open(db_file, &p_thr_info->db_conn); if (rc != 0) { if (p_thr_info->db_conn) { LogCrit(COMPONENT_FSAL, "ERROR: could not connect to SQLite3 database (file %s): %s", db_file, sqlite3_errmsg(p_thr_info->db_conn)); sqlite3_close(p_thr_info->db_conn); } else { LogCrit(COMPONENT_FSAL, "ERROR: could not connect to SQLite3 database (file %s): status=%d", db_file, rc); } return HANDLEMAP_DB_ERROR; } /* Now check, that the map table exists */ rc = sqlite3_get_table( p_thr_info->db_conn, "SELECT name FROM sqlite_master WHERE type = 'table' AND name = '" MAP_TABLE "'", &result, &rows, &cols, &errmsg); CheckTable(p_thr_info->db_conn, rc, errmsg, result); /* no need for the result, just the number of rows returned */ sqlite3_free_table(result); if (rows != 1) { /* table must be created */ rc = sqlite3_exec(p_thr_info->db_conn, "CREATE TABLE " MAP_TABLE " ( " OBJID_FIELD " BIGINT NOT NULL, " HASH_FIELD " INT NOT NULL, " HANDLE_FIELD " TEXT, PRIMARY KEY(" OBJID_FIELD ", " HASH_FIELD ") )", NULL, NULL, &errmsg); CheckCommand(p_thr_info->db_conn, rc, errmsg); } /* Now, create prepared statements */ rc = sqlite3_prepare_v2(p_thr_info->db_conn, "SELECT " OBJID_FIELD "," HASH_FIELD "," HANDLE_FIELD " FROM " MAP_TABLE, -1, &(p_thr_info->prep_stmt[LOAD_ALL_STATEMENT]), &unparsed); CheckPrepare(p_thr_info->db_conn, rc); rc = sqlite3_prepare_v2(p_thr_info->db_conn, "INSERT INTO " MAP_TABLE "(" OBJID_FIELD "," HASH_FIELD "," HANDLE_FIELD ") VALUES (?1, ?2, ?3 )", -1, &(p_thr_info->prep_stmt[INSERT_STATEMENT]), &unparsed); CheckPrepare(p_thr_info->db_conn, rc); rc = sqlite3_prepare_v2(p_thr_info->db_conn, "DELETE FROM " MAP_TABLE " WHERE " OBJID_FIELD "=?1 AND " HASH_FIELD "=?2", -1, &(p_thr_info->prep_stmt[DELETE_STATEMENT]), &unparsed); CheckPrepare(p_thr_info->db_conn, rc); /* Everything is OK now ! */ return HANDLEMAP_SUCCESS; } /* init_database_access */ static int db_load_operation(db_thread_info_t *p_info, hash_table_t *p_hash) { /* the object id to be inserted to hash table */ uint64_t object_id; unsigned int handle_hash; const char *fsal_handle_str; char fh4_data[NFS4_FHSIZE]; unsigned int nb_loaded = 0; int rc; struct timeval t1; struct timeval t2; struct timeval tdiff; gettimeofday(&t1, NULL); rc = sqlite3_step(p_info->prep_stmt[LOAD_ALL_STATEMENT]); CheckStep(p_info->db_conn, rc, p_info->prep_stmt[LOAD_ALL_STATEMENT]); /* something to read */ while (rc == SQLITE_ROW) { object_id = sqlite3_column_int64( p_info->prep_stmt[LOAD_ALL_STATEMENT], 0); handle_hash = sqlite3_column_int( p_info->prep_stmt[LOAD_ALL_STATEMENT], 1); fsal_handle_str = sqlite3_column_text( p_info->prep_stmt[LOAD_ALL_STATEMENT], 2); if (fsal_handle_str) { int len = strlen(fsal_handle_str); if ((len & 1) || len > NFS4_FHSIZE * 2) { LogEvent( COMPONENT_FSAL, "Bogus handle '%s' - wrong number of symbols", fsal_handle_str); } else { /* convert hexa string representation * to binary data */ if (sscanmem(fh4_data, len / 2, fsal_handle_str) != len) { LogEvent( COMPONENT_FSAL, "Bogus entry '%s' - cannot convert", fsal_handle_str); } else { /* now insert it to the hash table */ rc = handle_mapping_hash_add( p_hash, object_id, handle_hash, fh4_data, len / 2); if (rc == 0) nb_loaded++; else LogCrit(COMPONENT_FSAL, "ERROR %d adding entry to hash table ", rc, (unsigned long long) object_id, handle_hash, fsal_handle_str); } } } else { LogEvent(COMPONENT_FSAL, "Empty handle in object %lld, hash %d", (unsigned long long)object_id, handle_hash); } rc = sqlite3_step(p_info->prep_stmt[LOAD_ALL_STATEMENT]); CheckStep(p_info->db_conn, rc, p_info->prep_stmt[LOAD_ALL_STATEMENT]); } /* clear results */ sqlite3_reset(p_info->prep_stmt[LOAD_ALL_STATEMENT]); /* print time and item count */ gettimeofday(&t2, NULL); timersub(&t2, &t1, &tdiff); LogEvent(COMPONENT_FSAL, "Reloaded %u items in %d.%06ds", nb_loaded, (int)tdiff.tv_sec, (int)tdiff.tv_usec); return HANDLEMAP_SUCCESS; } /* db_load_operation */ static int db_insert_operation(db_thread_info_t *p_info, struct hdlmap_tuple *data) { int rc; char handle_str[OPAQUE_BYTES_SIZE(NFS4_FHSIZE)]; struct display_buffer dspbuf = { sizeof(handle_str), handle_str, handle_str }; rc = sqlite3_bind_int64(p_info->prep_stmt[INSERT_STATEMENT], 1, data->nfs23_digest.object_id); CheckBind(p_info->db_conn, rc, p_info->prep_stmt[INSERT_STATEMENT]); rc = sqlite3_bind_int(p_info->prep_stmt[INSERT_STATEMENT], 2, data->nfs23_digest.handle_hash); CheckBind(p_info->db_conn, rc, p_info->prep_stmt[INSERT_STATEMENT]); if (display_opaque_bytes_flags(&dspbuf, data->fh4_data, data->fh4_len, OPAQUE_BYTES_UPPER) <= 0) { LogCrit(COMPONENT_FSAL, "Invalid file handle %s", handle_str); return HANDLEMAP_DB_ERROR; } rc = sqlite3_bind_text(p_info->prep_stmt[INSERT_STATEMENT], 3, handle_str, -1, SQLITE_STATIC); CheckBind(p_info->db_conn, rc, p_info->prep_stmt[INSERT_STATEMENT]); rc = sqlite3_step(p_info->prep_stmt[INSERT_STATEMENT]); CheckStep(p_info->db_conn, rc, p_info->prep_stmt[INSERT_STATEMENT]); /* clear results */ sqlite3_reset(p_info->prep_stmt[INSERT_STATEMENT]); return HANDLEMAP_SUCCESS; } /* db_insert_operation */ static int db_delete_operation(db_thread_info_t *p_info, nfs23_map_handle_t *p_nfs23_digest) { int rc; rc = sqlite3_bind_int64(p_info->prep_stmt[DELETE_STATEMENT], 1, p_nfs23_digest->object_id); CheckBind(p_info->db_conn, rc, p_info->prep_stmt[DELETE_STATEMENT]); rc = sqlite3_bind_int(p_info->prep_stmt[DELETE_STATEMENT], 2, p_nfs23_digest->handle_hash); CheckBind(p_info->db_conn, rc, p_info->prep_stmt[DELETE_STATEMENT]); rc = sqlite3_step(p_info->prep_stmt[DELETE_STATEMENT]); CheckStep(p_info->db_conn, rc, p_info->prep_stmt[DELETE_STATEMENT]); /* clear results */ sqlite3_reset(p_info->prep_stmt[DELETE_STATEMENT]); return HANDLEMAP_SUCCESS; } /* db_delete_operation */ /* push a task to the queue */ static int dbop_push(flusher_queue_t *p_queue, db_op_item_t *p_op) { PTHREAD_MUTEX_lock(&p_queue->queues_mutex); /* add an item at the end of the queue */ switch (p_op->op_type) { case LOAD: case INSERT: /* high priority operations */ p_op->p_next = NULL; if (p_queue->highprio_last == NULL) { /* first operation */ p_queue->highprio_first = p_op; p_queue->highprio_last = p_op; } else { p_queue->highprio_last->p_next = p_op; p_queue->highprio_last = p_op; } p_queue->nb_waiting++; break; case DELETE: /* low priority operation */ p_op->p_next = NULL; if (p_queue->lowprio_last == NULL) { /* first operation */ p_queue->lowprio_first = p_op; p_queue->lowprio_last = p_op; } else { p_queue->lowprio_last->p_next = p_op; p_queue->lowprio_last = p_op; } p_queue->nb_waiting++; break; default: LogCrit(COMPONENT_FSAL, "ERROR in dbop push: Invalid operation type %d", p_op->op_type); } /* there now some work available */ pthread_cond_signal(&p_queue->work_avail_condition); PTHREAD_MUTEX_unlock(&p_queue->queues_mutex); return HANDLEMAP_SUCCESS; } static void *database_worker_thread(void *arg) { db_thread_info_t *p_info = (db_thread_info_t *)arg; int rc; db_op_item_t *to_be_done = NULL; char thread_name[32]; /* initialize logging */ /* We don't care about too long string, truncated is fine and we don't * expect EOVERRUN or EINVAL. */ (void)snprintf(thread_name, sizeof(thread_name), "DB thread #%u", p_info->thr_index); SetNameFunction(thread_name); /* initialize memory management */ rc = init_database_access(p_info); if (rc != HANDLEMAP_SUCCESS) { /* Failed init */ LogFatal(COMPONENT_FSAL, "ERROR: Database initialization error %d", rc); } /* main loop */ while (1) { /* Is "work done" or "work available" condition verified ? */ PTHREAD_MUTEX_lock(&p_info->work_queue.queues_mutex); /* nothing to be done ? */ while (p_info->work_queue.highprio_first == NULL && p_info->work_queue.lowprio_first == NULL) { to_be_done = NULL; p_info->work_queue.status = IDLE; pthread_cond_signal( &p_info->work_queue.work_done_condition); /* if termination is requested, exit */ if (do_terminate) { p_info->work_queue.status = FINISHED; PTHREAD_MUTEX_unlock( &p_info->work_queue.queues_mutex); return (void *)p_info; } /* else, wait for something to do */ pthread_cond_wait( &p_info->work_queue.work_avail_condition, &p_info->work_queue.queues_mutex); } /* there is something to do: * first check the highest priority list, * then the lower priority. */ if (p_info->work_queue.highprio_first != NULL) { /* take the next item in the list */ to_be_done = p_info->work_queue.highprio_first; p_info->work_queue.highprio_first = to_be_done->p_next; /* still any entries in the list ? */ if (p_info->work_queue.highprio_first == NULL) p_info->work_queue.highprio_last = NULL; /* it is the last entry ? */ else if (p_info->work_queue.highprio_first->p_next == NULL) p_info->work_queue.highprio_last = p_info->work_queue.highprio_first; /* something to do */ p_info->work_queue.status = WORKING; } else if (p_info->work_queue.lowprio_first != NULL) { /* take the next item in the list */ to_be_done = p_info->work_queue.lowprio_first; p_info->work_queue.lowprio_first = to_be_done->p_next; /* still any entries in the list ? */ if (p_info->work_queue.lowprio_first == NULL) p_info->work_queue.lowprio_last = NULL; /* it is the last entry ? */ else if (p_info->work_queue.lowprio_first->p_next == NULL) p_info->work_queue.lowprio_last = p_info->work_queue.lowprio_first; /* something to do */ p_info->work_queue.status = WORKING; } p_info->work_queue.nb_waiting--; PTHREAD_MUTEX_unlock(&p_info->work_queue.queues_mutex); /* PROCESS THE REQUEST */ switch (to_be_done->op_type) { case LOAD: db_load_operation(p_info, to_be_done->op_arg.hash); break; case INSERT: db_insert_operation(p_info, &to_be_done->op_arg.fh_info); break; case DELETE: db_delete_operation( p_info, &to_be_done->op_arg.fh_info.nfs23_digest); break; default: LogCrit(COMPONENT_FSAL, "ERROR: Invalid operation type %d", to_be_done->op_type); } /* free the db operation item */ PTHREAD_MUTEX_lock(&p_info->pool_mutex); pool_free(p_info->dbop_pool, to_be_done); PTHREAD_MUTEX_unlock(&p_info->pool_mutex); } /* loop forever */ return (void *)p_info; } /** * count the number of database instances in a given directory * (this is used for checking that the number of db * matches the number of threads) */ int handlemap_db_count(const char *dir) { DIR *dir_hdl; struct dirent *direntry; char db_pattern[MAXPATHLEN]; int rc; unsigned int count = 0; int end_of_dir = false; rc = snprintf(db_pattern, sizeof(db_pattern), "%s.*[0-9]", DB_FILE_PREFIX); if (rc < 0) { LogCrit(COMPONENT_FSAL, "Unexpected return from snprintf %d error %s (%d)", rc, strerror(errno), errno); return -HANDLEMAP_SYSTEM_ERROR; } else if (rc >= sizeof(db_pattern)) { LogCrit(COMPONENT_FSAL, "ERROR: db_pattern too long %s.*[0-9]", DB_FILE_PREFIX); return -HANDLEMAP_SYSTEM_ERROR; } dir_hdl = opendir(dir); if (dir_hdl == NULL) { LogCrit(COMPONENT_FSAL, "ERROR: could not access directory %s: %s", dir, strerror(errno)); return -HANDLEMAP_SYSTEM_ERROR; } do { errno = 0; direntry = readdir(dir_hdl); if (direntry != NULL) { /* go to the next loop if the entry is . or .. */ if (!strcmp(".", direntry->d_name) || !strcmp("..", direntry->d_name)) continue; /* does it match the expected db pattern ? */ if (!fnmatch(db_pattern, direntry->d_name, FNM_PATHNAME)) count++; } else if (errno == 0) { /* end of dir */ end_of_dir = true; } else { /* error */ LogCrit(COMPONENT_FSAL, "ERROR: error reading directory %s: %s", dir, strerror(errno)); closedir(dir_hdl); return -HANDLEMAP_SYSTEM_ERROR; } } while (!end_of_dir); closedir(dir_hdl); return count; } /* handlemap_db_count */ unsigned int select_db_queue(const nfs23_map_handle_t *p_nfs23_digest) { unsigned int h = ((p_nfs23_digest->object_id * 1049) ^ p_nfs23_digest->handle_hash) % 2477; h = h % nb_db_threads; return h; } /** * Initialize databases access * - init DB queues * - start threads * - establish DB connections * - create db schema if it was empty */ int handlemap_db_init(const char *db_dir, const char *tmp_dir, unsigned int db_count, int synchronous_insert) { unsigned int i; int rc; /* first, save the parameters */ if (strlcpy(dbmap_dir, db_dir, sizeof(dbmap_dir)) >= sizeof(dbmap_dir)) return HANDLEMAP_INVALID_PARAM; if (strlcpy(db_tmpdir, tmp_dir, sizeof(db_tmpdir)) >= sizeof(db_tmpdir)) return HANDLEMAP_INVALID_PARAM; if (db_count > MAX_DB) return HANDLEMAP_INVALID_PARAM; nb_db_threads = db_count; synchronous = synchronous_insert; /* set global database engine info */ sqlite3_temp_directory = db_tmpdir; /* initialize structures for each thread and launch it */ for (i = 0; i < nb_db_threads; i++) { rc = init_db_thread_info(&db_thread[i], 100); if (rc) return rc; db_thread[i].thr_index = i; rc = pthread_create(&db_thread[i].thr_id, NULL, database_worker_thread, &db_thread[i]); if (rc) return HANDLEMAP_SYSTEM_ERROR; } /* I'm ready to serve, my Lord ! */ return HANDLEMAP_SUCCESS; } /* wait that a thread has done all its jobs */ static void wait_thread_jobs_finished(db_thread_info_t *p_thr_info) { PTHREAD_MUTEX_lock(&p_thr_info->work_queue.queues_mutex); /* wait until the thread has no more tasks in its queue * and it is no more working */ while (p_thr_info->work_queue.highprio_first != NULL || p_thr_info->work_queue.lowprio_first != NULL || p_thr_info->work_queue.status == WORKING) pthread_cond_wait(&p_thr_info->work_queue.work_done_condition, &p_thr_info->work_queue.queues_mutex); PTHREAD_MUTEX_unlock(&p_thr_info->work_queue.queues_mutex); } /** * Gives the order to each DB thread to reload * the content of its database and insert it * to the hash table. * The function blocks until all threads have loaded their data. */ int handlemap_db_reaload_all(hash_table_t *target_hash) { unsigned int i; db_op_item_t *new_task; int rc; /* give the job to all threads */ for (i = 0; i < nb_db_threads; i++) { /* get a new db operation */ PTHREAD_MUTEX_lock(&db_thread[i].pool_mutex); new_task = pool_alloc(db_thread[i].dbop_pool); PTHREAD_MUTEX_unlock(&db_thread[i].pool_mutex); /* can you fill it ? */ new_task->op_type = LOAD; new_task->op_arg.hash = target_hash; rc = dbop_push(&db_thread[i].work_queue, new_task); if (rc) return rc; } /* wait for all threads to finish their job */ for (i = 0; i < nb_db_threads; i++) wait_thread_jobs_finished(&db_thread[i]); return HANDLEMAP_SUCCESS; } /* handlemap_db_reaload_all */ /** * Submit a db 'insert' request. * The request is inserted in the appropriate db queue. */ int handlemap_db_insert(nfs23_map_handle_t *p_in_nfs23_digest, const void *data, uint32_t len) { unsigned int i; db_op_item_t *new_task; int rc; if (!synchronous) { /* which thread is going to handle this inode ? */ i = select_db_queue(p_in_nfs23_digest); /* get a new db operation */ PTHREAD_MUTEX_lock(&db_thread[i].pool_mutex); new_task = pool_alloc(db_thread[i].dbop_pool); PTHREAD_MUTEX_unlock(&db_thread[i].pool_mutex); /* fill the task info */ new_task->op_type = INSERT; new_task->op_arg.fh_info.nfs23_digest = *p_in_nfs23_digest; memcpy(new_task->op_arg.fh_info.fh4_data, data, len); new_task->op_arg.fh_info.fh4_len = len; rc = dbop_push(&db_thread[i].work_queue, new_task); if (rc) return rc; } /* else: @todo not supported yet */ return HANDLEMAP_SUCCESS; } /** * Submit a db 'delete' request. * The request is inserted in the appropriate db queue. * (always asynchronous) */ int handlemap_db_delete(nfs23_map_handle_t *p_in_nfs23_digest) { unsigned int i; db_op_item_t *new_task; int rc; /* which thread is going to handle this inode ? */ i = select_db_queue(p_in_nfs23_digest); /* get a new db operation */ PTHREAD_MUTEX_lock(&db_thread[i].pool_mutex); new_task = pool_alloc(db_thread[i].dbop_pool); PTHREAD_MUTEX_unlock(&db_thread[i].pool_mutex); /* fill the task info */ new_task->op_type = DELETE; new_task->op_arg.fh_info.nfs23_digest = *p_in_nfs23_digest; rc = dbop_push(&db_thread[i].work_queue, new_task); if (rc) return rc; return HANDLEMAP_SUCCESS; } /** * Wait for all queues to be empty * and all current DB request to be done. */ int handlemap_db_flush(void) { unsigned int i; struct timeval t1; struct timeval t2; struct timeval tdiff; unsigned int to_sync = 0; for (i = 0; i < nb_db_threads; i++) to_sync += db_thread[i].work_queue.nb_waiting; LogEvent(COMPONENT_FSAL, "Waiting for database synchronization (%u operations pending)", to_sync); gettimeofday(&t1, NULL); /* wait for all threads to finish their job */ for (i = 0; i < nb_db_threads; i++) wait_thread_jobs_finished(&db_thread[i]); gettimeofday(&t2, NULL); timersub(&t2, &t1, &tdiff); LogEvent(COMPONENT_FSAL, "Database synchronized in %d.%06ds", (int)tdiff.tv_sec, (int)tdiff.tv_usec); return HANDLEMAP_SUCCESS; } nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V4/handle_mapping/handle_mapping_db.h000066400000000000000000000050351473756622300257450ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: */ /* * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ #ifndef _HANDLE_MAPPING_DB_H #define _HANDLE_MAPPING_DB_H #include "handle_mapping.h" #include "hashtable.h" #define DB_FILE_PREFIX "handlemap.sqlite" /* Database definition */ #define MAP_TABLE "HandleMap" #define OBJID_FIELD "ObjectId" #define HASH_FIELD "HandleHash" #define HANDLE_FIELD "FSALHandle" #define MAX_DB 32 /** * count the number of database instances in a given directory * (this is used for checking that the number of db * matches the number of threads) */ int handlemap_db_count(const char *dir); /** * Initialize databases access * (init DB queues, start threads, establish DB connections, * and create db schema if it was empty). */ int handlemap_db_init(const char *db_dir, const char *tmp_dir, unsigned int db_count, int synchronous_insert); /** * Gives the order to each DB thread to reload * the content of its database and insert it * to the hash table. * The function blocks until all threads have loaded their data. */ int handlemap_db_reaload_all(hash_table_t *target_hash); /** * Submit a db 'insert' request. * The request is inserted in the appropriate db queue. */ int handlemap_db_insert(nfs23_map_handle_t *p_in_nfs23_digest, const void *data, uint32_t len); /** * Submit a db 'delete' request. * The request is inserted in the appropriate db queue. * (always asynchronous) */ int handlemap_db_delete(nfs23_map_handle_t *p_in_nfs23_digest); /** * Wait for all queues to be empty * and all current DB request to be done. */ int handlemap_db_flush(void); #endif nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V4/handle_mapping/handle_mapping_internal.h000066400000000000000000000025301473756622300271710ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: */ /* * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ #ifndef _HANDLE_MAPPING_INTERNAL_H #define _HANDLE_MAPPING_INTERNAL_H #include "hashtable.h" int handle_mapping_hash_add(hash_table_t *p_hash, uint64_t object_id, unsigned int handle_hash, const void *data, uint32_t datalen); int sscanmem(void *target, size_t tgt_size, const char *str_source); #endif nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V4/handle_mapping/test_handle_mapping.c000066400000000000000000000066101473756622300263320ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: */ /* * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ #include "config.h" #include "handle_mapping_db.h" #include "abstract_mem.h" #include int main(int argc, char **argv) { unsigned int i; struct timeval tv1, tv2, tv3, tvdiff; int count, rc; char *dir; handle_map_param_t param; time_t now; /* Init logging */ SetNamePgm("test_handle_mapping"); SetDefaultLogging("TEST"); SetNameFunction("main"); SetNameHost("localhost"); InitLogging(); if (argc != 3) { LogTest("usage: test_handle_mapping "); exit(1); } count = atoi(argv[2]) if (count == 0) { LogTest("usage: test_handle_mapping "); exit(1); } dir = argv[1]; param.databases_directory = gsh_strdup(dir); param.temp_directory = gsh_strdup("/tmp"); param.database_count = count; param.hashtable_size = 27; param.nb_handles_prealloc = 1024; param.nb_db_op_prealloc = 1024; param.synchronous_insert = false; rc = HandleMap_Init(¶m); LogTest("HandleMap_Init() = %d", rc); if (rc) exit(rc); gettimeofday(&tv1, NULL); /* Now insert a set of handles */ now = time(NULL); for (i = 0; i < 10000; i++) { nfs23_map_handle_t nfs23_digest; fsal_handle_t handle; memset(&handle, i, sizeof(fsal_handle_t)); nfs23_digest.object_id = 12345 + i; nfs23_digest.handle_hash = (1999 * i + now) % 479001599; rc = HandleMap_SetFH(&nfs23_digest, &handle); if (rc && (rc != HANDLEMAP_EXISTS)) exit(rc); } gettimeofday(&tv2, NULL); timersub(&tv2, &tv1, &tvdiff); LogTest("%u threads inserted 10000 handles in %d.%06ds", count, (int)tvdiff.tv_sec, (int)tvdiff.tv_usec); /* Now get them ! */ for (i = 0; i < 10000; i++) { nfs23_map_handle_t nfs23_digest; fsal_handle_t handle; nfs23_digest.object_id = 12345 + i; nfs23_digest.handle_hash = (1999 * i + now) % 479001599; rc = HandleMap_GetFH(&nfs23_digest, &handle); if (rc) { LogTest("Error %d retrieving handle !", rc); exit(rc); } rc = HandleMap_DelFH(&nfs23_digest); if (rc) { LogTest("Error %d deleting handle !", rc); exit(rc); } } gettimeofday(&tv3, NULL); timersub(&tv3, &tv2, &tvdiff); LogTest("Retrieved and deleted 10000 handles in %d.%06ds", (int)tvdiff.tv_sec, (int)tvdiff.tv_usec); rc = HandleMap_Flush(); gettimeofday(&tv3, NULL); timersub(&tv3, &tv1, &tvdiff); LogTest("Total time with %u threads (including flush): %d.%06ds", count, (int)tvdiff.tv_sec, (int)tvdiff.tv_usec); exit(0); } nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V4/handle_mapping/test_handle_mapping_db.c000066400000000000000000000067031473756622300270020ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: */ /* * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ #include "config.h" #include "handle_mapping_db.h" #include int main(int argc, char **argv) { unsigned int i; struct timeval tv1, tv2, tv3, tvdiff; int count, rc; char *dir; time_t now; if (argc != 3) { LogTest("usage: test_handle_mapping_db "); exit(1); } count = atoi(argv[2]) if (count == 0) { LogTest("usage: test_handle_mapping_db "); exit(1); } dir = argv[1]; /* Init logging */ SetNamePgm("test_handle_mapping"); SetNameFileLog("/dev/tty"); SetNameFunction("main"); SetNameHost("localhost"); /* count databases */ rc = handlemap_db_count(dir); LogTest("handlemap_db_count(%s)=%d", dir, rc); if (rc != 0 && count != rc) { LogTest("Warning: incompatible thread count %d <> database count %d", count, rc); } rc = handlemap_db_init(dir, "/tmp", count, 1024, false); LogTest("handlemap_db_init() = %d", rc); if (rc) exit(rc); rc = handlemap_db_reaload_all(NULL); LogTest("handlemap_db_reaload_all() = %d", rc); if (rc) exit(rc); gettimeofday(&tv1, NULL); /* Now insert a set of handles */ now = time(NULL); for (i = 0; i < 10000; i++) { nfs23_map_handle_t nfs23_digest; fsal_handle_t handle; memset(&handle, i, sizeof(fsal_handle_t)); nfs23_digest.object_id = 12345 + i; nfs23_digest.handle_hash = (1999 * i + now) % 479001599; rc = handlemap_db_insert(&nfs23_digest, &handle); if (rc) exit(rc); } gettimeofday(&tv2, NULL); timersub(&tv2, &tv1, &tvdiff); LogTest("%u threads inserted 10000 handles in %d.%06ds", count, (int)tvdiff.tv_sec, (int)tvdiff.tv_usec); rc = handlemap_db_flush(); gettimeofday(&tv3, NULL); timersub(&tv3, &tv1, &tvdiff); LogTest("Total time with %u threads (including flush): %d.%06ds", count, (int)tvdiff.tv_sec, (int)tvdiff.tv_usec); LogTest("Now, delete operations"); for (i = 0; i < 10000; i++) { nfs23_map_handle_t nfs23_digest; nfs23_digest.object_id = 12345 + i; nfs23_digest.handle_hash = (1999 * i + now) % 479001599; rc = handlemap_db_delete(&nfs23_digest); if (rc) exit(rc); } gettimeofday(&tv2, NULL); timersub(&tv2, &tv3, &tvdiff); LogTest("%u threads deleted 10000 handles in %d.%06ds", count, (int)tvdiff.tv_sec, (int)tvdiff.tv_usec); rc = handlemap_db_flush(); gettimeofday(&tv1, NULL); timersub(&tv1, &tv3, &tvdiff); LogTest("Delete time with %u threads (including flush): %d.%06ds", count, (int)tvdiff.tv_sec, (int)tvdiff.tv_usec); exit(0); } nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V4/main.c000066400000000000000000000107431473756622300203050ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Max Matveev, 2012 * * Copyright CEA/DAM/DIF (2008) * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 */ #include "config.h" #include #include #include #include #include #include #include "fsal.h" #include "FSAL/fsal_init.h" #include "proxyv4_fsal_methods.h" #define PROXY_V4_SUPPORTED_ATTRS ((const attrmask_t)(ATTRS_POSIX)) /* filesystem info for PROXY_V4 */ struct proxyv4_fsal_module PROXY_V4 = { .module = { .fs_info = { .maxfilesize = INT64_MAX, .maxlink = _POSIX_LINK_MAX, .maxnamelen = 1024, .maxpathlen = 1024, .no_trunc = true, .chown_restricted = true, .case_preserving = true, .lock_support = false, .named_attr = true, .unique_handles = true, .acl_support = FSAL_ACLSUPPORT_ALLOW, .homogenous = true, .supported_attrs = PROXY_V4_SUPPORTED_ATTRS, .link_supports_permission_checks = true, .expire_time_parent = -1, } } }; /** * @brief Validate and commit the proxy params * * This is also pretty simple. Just a NOP in both cases. * * @param link_mem - pointer to the link_mem struct memory. * @param self_struct - NULL for init parent, not NULL for attaching */ static struct config_item proxyv4_params[] = { CONF_ITEM_BOOL("link_support", true, proxyv4_fsal_module, module.fs_info.link_support), CONF_ITEM_BOOL("symlink_support", true, proxyv4_fsal_module, module.fs_info.symlink_support), CONF_ITEM_BOOL("cansettime", true, proxyv4_fsal_module, module.fs_info.cansettime), CONF_ITEM_UI64("maxread", 512, FSAL_MAXIOSIZE - SEND_RECV_HEADER_SPACE, DEFAULT_MAX_WRITE_READ, proxyv4_fsal_module, module.fs_info.maxread), CONF_ITEM_UI64("maxwrite", 512, FSAL_MAXIOSIZE - SEND_RECV_HEADER_SPACE, DEFAULT_MAX_WRITE_READ, proxyv4_fsal_module, module.fs_info.maxwrite), CONF_ITEM_MODE("umask", 0, proxyv4_fsal_module, module.fs_info.umask), CONF_ITEM_BOOL("auth_xdev_export", false, proxyv4_fsal_module, module.fs_info.auth_exportpath_xdev), CONFIG_EOL }; struct config_block proxy_param_v4 = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.proxy", .blk_desc.name = "PROXY_V4", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = proxyv4_params, .blk_desc.u.blk.commit = noop_conf_commit }; static fsal_status_t proxyv4_init_config(struct fsal_module *fsal_hdl, config_file_t config_struct, struct config_error_type *err_type) { struct proxyv4_fsal_module *proxyv4 = container_of(fsal_hdl, struct proxyv4_fsal_module, module); (void)load_config_from_parse(config_struct, &proxy_param_v4, proxyv4, true, err_type); if (!config_error_is_harmless(err_type)) return fsalstat(ERR_FSAL_INVAL, 0); display_fsinfo(&proxyv4->module); return fsalstat(ERR_FSAL_NO_ERROR, 0); } MODULE_INIT void proxyv4_init(void) { if (register_fsal(&PROXY_V4.module, "PROXY_V4", FSAL_MAJOR_VERSION, FSAL_MINOR_VERSION, FSAL_ID_NO_PNFS) != 0) return; PROXY_V4.module.m_ops.init_config = proxyv4_init_config; PROXY_V4.module.m_ops.create_export = proxyv4_create_export; /* Initialize the fsal_obj_handle ops for FSAL PROXY */ proxyv4_handle_ops_init(&PROXY_V4.handle_ops); } MODULE_FINI void proxyv4_unload(void) { int retval; retval = unregister_fsal(&PROXY_V4.module); if (retval != 0) { fprintf(stderr, "PROXY_V4 module failed to unregister : %d", retval); return; } } nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V4/proxyv4_fsal_methods.h000066400000000000000000000127271473756622300235550ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Max Matveev, 2012 * Copyright CEA/DAM/DIF (2008) * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * This program 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 3 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 * 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 */ /* Proxy handle methods */ #ifndef _PROXYV4_FSAL_METHODS_H #define _PROXYV4_FSAL_METHODS_H #ifdef PROXYV4_HANDLE_MAPPING #include "handle_mapping/handle_mapping.h" #endif /*512 bytes to store header*/ #define SEND_RECV_HEADER_SPACE 512 /*1MB of default maxsize*/ #define DEFAULT_MAX_WRITE_READ 1048576 #include #include #include struct proxyv4_fsal_module { struct fsal_module module; struct fsal_obj_ops handle_ops; }; extern struct proxyv4_fsal_module PROXY_V4; struct proxyv4_client_params { uint32_t retry_sleeptime; sockaddr_t srv_addr; uint32_t srv_prognum; uint64_t srv_sendsize; uint64_t srv_recvsize; uint32_t srv_timeout; uint16_t srv_port; bool use_privileged_client_port; char *remote_principal; char *keytab; unsigned int cred_lifetime; unsigned int sec_type; bool active_krb5; /* initialization info for handle mapping */ bool enable_handle_mapping; #ifdef PROXYV4_HANDLE_MAPPING handle_map_param_t hdlmap; #endif }; struct proxyv4_export_rpc { /** * proxyv4_clientid_mutex protects proxyv4_clientid, proxyv4_client_seqid, * proxyv4_client_sessionid, no_sessionid and cond_sessionid. */ clientid4 proxyv4_clientid; sequenceid4 proxyv4_client_seqid; sessionid4 proxyv4_client_sessionid; bool no_sessionid; pthread_cond_t cond_sessionid; pthread_mutex_t proxyv4_clientid_mutex; char proxyv4_hostname[MAXNAMLEN + 1]; pthread_t proxyv4_recv_thread; pthread_t proxyv4_renewer_thread; /** * listlock protects rpc_sock and rpc_xid values, sockless condition and * rpc_calls list. */ struct glist_head rpc_calls; int rpc_sock; uint32_t rpc_xid; pthread_mutex_t listlock; pthread_cond_t sockless; bool close_thread; /* * context_lock protects free_contexts list and need_context condition. */ struct glist_head free_contexts; pthread_cond_t need_context; pthread_mutex_t context_lock; }; struct proxyv4_export { struct fsal_export exp; struct proxyv4_client_params info; struct proxyv4_export_rpc rpc; }; void proxyv4_handle_ops_init(struct fsal_obj_ops *ops); void free_io_contexts(struct proxyv4_export *proxyv4_exp); void proxyv4_close_thread(struct proxyv4_export *proxyv4_exp); int proxyv4_init_rpc(struct proxyv4_export *); fsal_status_t proxyv4_list_ext_attrs(struct fsal_obj_handle *obj_hdl, unsigned int cookie, fsal_xattrent_t *xattrs_tab, unsigned int xattrs_tabsize, unsigned int *p_nb_returned, int *end_of_list); fsal_status_t proxyv4_getextattr_id_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, unsigned int *pxattr_id); fsal_status_t proxyv4_getextattr_value_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, size_t *len); fsal_status_t proxyv4_getextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buf, size_t sz, size_t *len); fsal_status_t proxyv4_setextattr_value(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buf, size_t sz, int create); fsal_status_t proxyv4_setextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buf, size_t sz); fsal_status_t proxyv4_getextattr_attrs(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, struct fsal_attrlist *attrs); fsal_status_t proxyv4_remove_extattr_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id); fsal_status_t proxyv4_remove_extattr_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name); fsal_status_t proxyv4_lookup_path(struct fsal_export *exp_hdl, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); fsal_status_t proxyv4_create_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *hdl_desc, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); fsal_status_t proxyv4_create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops); fsal_status_t proxyv4_get_dynamic_info(struct fsal_export *, struct fsal_obj_handle *, fsal_dynamicfsinfo_t *); fsal_status_t proxyv4_wire_to_host(struct fsal_export *, fsal_digesttype_t, struct gsh_buffdesc *, int); struct state_t *proxyv4_alloc_state(struct fsal_export *exp_hdl, enum state_type state_type, struct state_t *related_state); #endif nfs-ganesha-6.5/src/FSAL/FSAL_PROXY_V4/xattrs.c000066400000000000000000000054501473756622300207050ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Max Matveev, 2012 * * This program 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 3 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 * 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 * * ------------- */ #include "config.h" #include "fsal.h" #include "proxyv4_fsal_methods.h" fsal_status_t proxyv4_list_ext_attrs(struct fsal_obj_handle *obj_hdl, unsigned int cookie, fsal_xattrent_t *xattrs_tab, unsigned int xattrs_tabsize, unsigned int *p_nb_returned, int *end_of_list) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } fsal_status_t proxyv4_getextattr_id_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, unsigned int *pxattr_id) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } fsal_status_t proxyv4_getextattr_value_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, size_t *p_output_size) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } fsal_status_t proxyv4_getextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buffer_addr, size_t buffer_size, size_t *p_output_size) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } fsal_status_t proxyv4_setextattr_value(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, int create) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } fsal_status_t proxyv4_setextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buffer_addr, size_t buffer_size) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } fsal_status_t proxyv4_getextattr_attrs(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, struct fsal_attrlist *p_attrs) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } fsal_status_t proxyv4_remove_extattr_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } fsal_status_t proxyv4_remove_extattr_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } nfs-ganesha-6.5/src/FSAL/FSAL_PSEUDO/000077500000000000000000000000001473756622300167155ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_PSEUDO/CMakeLists.txt000066400000000000000000000030061473756622300214540ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- add_definitions( -D__USE_GNU ) set( LIB_PREFIX 64) ########### next target ############### SET(fsalpseudo_LIB_SRCS handle.c pseudofs_methods.h main.c export.c ) add_library(fsalpseudo OBJECT ${fsalpseudo_LIB_SRCS}) add_sanitizers(fsalpseudo) set_target_properties(fsalpseudo PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(fsalpseudo gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) ########### install files ############### nfs-ganesha-6.5/src/FSAL/FSAL_PSEUDO/export.c000066400000000000000000000147321473756622300204110ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /* export.c * PSEUDO FSAL export object */ #include "config.h" #include "fsal.h" #include /* used for 'bswap*' */ #include /* used for 'dirname' */ #include #include #include #include #include #include #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_config.h" #include "pseudofs_methods.h" #include "nfs_exports.h" #include "export_mgr.h" #include "mdcache.h" #include /* helpers to/from other PSEUDO objects */ /* export object methods */ static void release(struct fsal_export *exp_hdl) { struct pseudofs_fsal_export *myself; myself = container_of(exp_hdl, struct pseudofs_fsal_export, export); if (myself->root_handle != NULL) { fsal_obj_handle_fini(&myself->root_handle->obj_handle, true); LogDebug(COMPONENT_FSAL, "Releasing hdl=%p, name=%s", myself->root_handle, myself->root_handle->name); if (myself->root_handle->name != NULL) gsh_free(myself->root_handle->name); gsh_free(myself->root_handle); myself->root_handle = NULL; } fsal_detach_export(exp_hdl->fsal, &exp_hdl->exports); free_export_ops(exp_hdl); if (myself->export_path != NULL) gsh_free(myself->export_path); gsh_free(myself); } static fsal_status_t get_dynamic_info(struct fsal_export *exp_hdl, struct fsal_obj_handle *obj_hdl, fsal_dynamicfsinfo_t *infop) { infop->total_bytes = 0; infop->free_bytes = 0; infop->avail_bytes = 0; infop->total_files = 0; infop->free_files = 0; infop->avail_files = 0; infop->time_delta.tv_sec = 0; infop->time_delta.tv_nsec = FSAL_DEFAULT_TIME_DELTA_NSEC; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* get_quota * return quotas for this export. * path could cross a lower mount boundary which could * mask lower mount values with those of the export root * if this is a real issue, we can scan each time with setmntent() * better yet, compare st_dev of the file with st_dev of root_fd. * on linux, can map st_dev -> /proc/partitions name -> /dev/ */ static fsal_status_t get_quota(struct fsal_export *exp_hdl, const char *filepath, int quota_type, int quota_id, fsal_quota_t *pquota) { /* PSEUDOFS doesn't support quotas */ return fsalstat(ERR_FSAL_NOTSUPP, 0); } /* set_quota * same lower mount restriction applies */ static fsal_status_t set_quota(struct fsal_export *exp_hdl, const char *filepath, int quota_type, int quota_id, fsal_quota_t *pquota, fsal_quota_t *presquota) { /* PSEUDOFS doesn't support quotas */ return fsalstat(ERR_FSAL_NOTSUPP, 0); } /* extract a file handle from a buffer. * do verification checks and flag any and all suspicious bits. * Return an updated fh_desc into whatever was passed. The most * common behavior, done here is to just reset the length. */ static fsal_status_t wire_to_host(struct fsal_export *exp_hdl, fsal_digesttype_t in_type, struct gsh_buffdesc *fh_desc, int flags) { size_t fh_min; uint64_t *hashkey; ushort *len; fh_min = 1; if (fh_desc->len < fh_min) { LogMajor(COMPONENT_FSAL, "Size mismatch for handle. should be >= %zu, got %zu", fh_min, fh_desc->len); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } hashkey = (uint64_t *)fh_desc->addr; len = (ushort *)((char *)hashkey + sizeof(uint64_t)); if (flags & FH_FSAL_BIG_ENDIAN) { #if (BYTE_ORDER != BIG_ENDIAN) *len = bswap_16(*len); *hashkey = bswap_64(*hashkey); #endif } else { #if (BYTE_ORDER == BIG_ENDIAN) *len = bswap_16(*len); *hashkey = bswap_64(*hashkey); #endif } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* pseudofs_export_ops_init * overwrite vector entries with the methods that we support */ void pseudofs_export_ops_init(struct export_ops *ops) { ops->release = release; ops->lookup_path = pseudofs_lookup_path; ops->wire_to_host = wire_to_host; ops->create_handle = pseudofs_create_handle; ops->get_fs_dynamic_info = get_dynamic_info; ops->get_quota = get_quota; ops->set_quota = set_quota; } /* create_export * Create an export point and return a handle to it to be kept * in the export list. * First lookup the fsal, then create the export and then put the fsal back. * returns the export with one reference taken. */ fsal_status_t pseudofs_create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops) { struct pseudofs_fsal_export *myself; int retval = 0; myself = gsh_calloc(1, sizeof(struct pseudofs_fsal_export)); if (myself == NULL) { LogMajor(COMPONENT_FSAL, "Could not allocate export"); return fsalstat(posix2fsal_error(errno), errno); } fsal_export_init(&myself->export); pseudofs_export_ops_init(&myself->export.exp_ops); retval = fsal_attach_export(fsal_hdl, &myself->export.exports); if (retval != 0) { /* seriously bad */ LogMajor(COMPONENT_FSAL, "Could not attach export"); gsh_free(myself->export_path); gsh_free(myself->root_handle); free_export_ops(&myself->export); gsh_free(myself); /* elvis has left the building */ return fsalstat(posix2fsal_error(retval), retval); } myself->export.fsal = fsal_hdl; /* Save the export path. */ myself->export_path = CTX_FULLPATH(op_ctx); if (myself->export_path != NULL) myself->export_path = gsh_strdup(myself->export_path); op_ctx->fsal_export = &myself->export; LogDebug(COMPONENT_FSAL, "Created exp %p - %s", myself, myself->export_path); return fsalstat(ERR_FSAL_NO_ERROR, 0); } nfs-ganesha-6.5/src/FSAL/FSAL_PSEUDO/handle.c000066400000000000000000000547601473756622300203300ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /* handle.c */ #include "config.h" #include "fsal.h" #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "pseudofs_methods.h" #include "city.h" #include "nfs_file_handle.h" #include "display.h" #include "common_utils.h" /* Atomic uint64_t that is used to generate inode numbers in the Pseudo FS */ uint64_t inode_number = 1; #define V4_FH_OPAQUE_SIZE (NFS4_FHSIZE - sizeof(struct file_handle_v4)) /* helpers */ static inline int pseudofs_n_cmpf(const struct avltree_node *lhs, const struct avltree_node *rhs) { struct pseudo_fsal_obj_handle *lk, *rk; lk = avltree_container_of(lhs, struct pseudo_fsal_obj_handle, avl_n); rk = avltree_container_of(rhs, struct pseudo_fsal_obj_handle, avl_n); return strcmp(lk->name, rk->name); } static inline int pseudofs_i_cmpf(const struct avltree_node *lhs, const struct avltree_node *rhs) { struct pseudo_fsal_obj_handle *lk, *rk; lk = avltree_container_of(lhs, struct pseudo_fsal_obj_handle, avl_i); rk = avltree_container_of(rhs, struct pseudo_fsal_obj_handle, avl_i); if (lk->index < rk->index) return -1; if (lk->index == rk->index) return 0; return 1; } static inline struct avltree_node * avltree_inline_name_lookup(const struct avltree_node *key, const struct avltree *tree) { return avltree_inline_lookup(key, tree, pseudofs_n_cmpf); } /** * @brief Construct the fs opaque part of a pseudofs nfsv4 handle * * Given the components of a pseudofs nfsv4 handle, the nfsv4 handle is * created by concatenating the components. This is the fs opaque piece * of struct file_handle_v4 and what is sent over the wire. * * @param[in] pathbuf Full patch of the pseudofs node * @param[in] hashkey a 64 bit hash of the pseudopath parameter * * @return The nfsv4 pseudofs file handle as a char * */ static void package_pseudo_handle(char *buff, struct display_buffer *pathbuf) { ushort len = display_buffer_len(pathbuf); int opaque_bytes_used = 0, pathlen = 0; uint64_t hashkey = CityHash64(pathbuf->b_start, display_buffer_len(pathbuf)); memcpy(buff, &hashkey, sizeof(hashkey)); opaque_bytes_used += sizeof(hashkey); /* include length of the path in the handle. * MAXPATHLEN=4096 ... max path length can be contained in a short int. */ memcpy(buff + opaque_bytes_used, &len, sizeof(len)); opaque_bytes_used += sizeof(len); /* Either the nfsv4 fh opaque size or the length of the pseudopath. * Ideally we can include entire pseudofs pathname for guaranteed * uniqueness of pseudofs handles. */ pathlen = MIN(V4_FH_OPAQUE_SIZE - opaque_bytes_used, len); memcpy(buff + opaque_bytes_used, pathbuf->b_start, pathlen); opaque_bytes_used += pathlen; /* If there is more space in the opaque handle due to a short pseudofs * path ... zero it. */ if (opaque_bytes_used < V4_FH_OPAQUE_SIZE) { memset(buff + opaque_bytes_used, 0, V4_FH_OPAQUE_SIZE - opaque_bytes_used); } } /** * @brief Concatenate a number of pseudofs tokens into a string * * When reading pseudofs paths from export entries, we divide the * path into tokens. This function will recombine a specific number * of those tokens into a string. * * @param[in/out] pathbuf Must be not NULL. Tokens are copied to here. * @param[in] node for which a full pseudopath needs to be formed. * @param[in] maxlen maximum number of chars to copy to pathbuf * * @return void */ static int create_fullpath(struct display_buffer *pathbuf, struct pseudo_fsal_obj_handle *this_node) { int b_left; if (this_node->parent != NULL) b_left = create_fullpath(pathbuf, this_node->parent); else b_left = display_start(pathbuf); /* Add slash for all but root node */ if (b_left > 0 && this_node->parent != NULL) b_left = display_cat(pathbuf, "/"); /* Append the node's name. * Note that a Pseudo FS root's name is it's full path. */ if (b_left > 0) b_left = display_cat(pathbuf, this_node->name); return b_left; } /* alloc_handle * allocate and fill in a handle */ static struct pseudo_fsal_obj_handle * alloc_directory_handle(struct pseudo_fsal_obj_handle *parent, const char *name, struct fsal_export *exp_hdl, struct fsal_attrlist *attrs) { struct pseudo_fsal_obj_handle *hdl; char path[MAXPATHLEN] = "\0"; struct display_buffer pathbuf = { sizeof(path), path, path }; uint64_t fileid = 0; int rc; hdl = gsh_calloc(1, sizeof(struct pseudo_fsal_obj_handle) + V4_FH_OPAQUE_SIZE); /* Establish tree details for this directory */ hdl->name = gsh_strdup(name); hdl->parent = parent; if (hdl->name == NULL) { LogDebug(COMPONENT_FSAL, "Could not name"); goto spcerr; } /* Create the handle */ hdl->handle = (char *)&hdl[1]; /* Create the full path */ rc = create_fullpath(&pathbuf, hdl); if (rc < 0) { LogDebug(COMPONENT_FSAL, "Could not create handle"); goto spcerr; } package_pseudo_handle(hdl->handle, &pathbuf); hdl->obj_handle.type = DIRECTORY; /* Fills the output struct */ hdl->attributes.type = DIRECTORY; hdl->attributes.filesize = 0; /* fsid will be supplied later */ hdl->obj_handle.fsid.major = 0; hdl->obj_handle.fsid.minor = 0; hdl->attributes.fsid.major = 0; hdl->attributes.fsid.minor = 0; if (strcmp(name, "/") != 0) fileid = atomic_postinc_uint64_t(&inode_number); hdl->obj_handle.fileid = fileid; hdl->attributes.fileid = hdl->obj_handle.fileid; hdl->attributes.mode = attrs->mode & (~S_IFMT & 0xFFFF) & ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export); hdl->attributes.numlinks = 2; hdl->numlinks = 2; if ((attrs->valid_mask & ATTR_OWNER) != 0) hdl->attributes.owner = attrs->owner; else hdl->attributes.owner = op_ctx->creds.caller_uid; if ((attrs->valid_mask & ATTR_GROUP) != 0) hdl->attributes.group = attrs->group; else hdl->attributes.group = op_ctx->creds.caller_gid; /* Use full timer resolution */ now(&hdl->attributes.ctime); if ((attrs->valid_mask & ATTR_ATIME) != 0) hdl->attributes.atime = attrs->atime; else hdl->attributes.atime = hdl->attributes.ctime; if ((attrs->valid_mask & ATTR_MTIME) != 0) hdl->attributes.mtime = attrs->mtime; else hdl->attributes.mtime = hdl->attributes.ctime; hdl->attributes.change = timespec_to_nsecs(&hdl->attributes.ctime); hdl->attributes.spaceused = 0; hdl->attributes.rawdev.major = 0; hdl->attributes.rawdev.minor = 0; /* Set the mask at the end. */ hdl->attributes.valid_mask = PSEUDO_SUPPORTED_ATTRS; hdl->attributes.supported = PSEUDO_SUPPORTED_ATTRS; fsal_obj_handle_init(&hdl->obj_handle, exp_hdl, DIRECTORY, true); hdl->obj_handle.obj_ops = &PSEUDOFS.handle_ops; avltree_init(&hdl->avl_name, pseudofs_n_cmpf, 0 /* flags */); avltree_init(&hdl->avl_index, pseudofs_i_cmpf, 0 /* flags */); hdl->next_i = 2; if (parent != NULL) { /* Attach myself to my parent */ PTHREAD_RWLOCK_wrlock(&parent->obj_handle.obj_lock); avltree_insert(&hdl->avl_n, &parent->avl_name); hdl->index = (parent->next_i)++; avltree_insert(&hdl->avl_i, &parent->avl_index); hdl->inavl = true; now(&parent->attributes.mtime); parent->attributes.ctime = parent->attributes.mtime; parent->attributes.change = timespec_to_nsecs(&parent->attributes.mtime); PTHREAD_RWLOCK_unlock(&parent->obj_handle.obj_lock); } return hdl; spcerr: if (hdl->name != NULL) gsh_free(hdl->name); gsh_free(hdl); /* elvis has left the building */ return NULL; } /* handle methods */ /* lookup * deprecated NULL parent && NULL path implies root handle */ static fsal_status_t lookup(struct fsal_obj_handle *parent, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { struct pseudo_fsal_obj_handle *myself, *hdl = NULL; struct pseudo_fsal_obj_handle key[1]; struct avltree_node *node; fsal_errors_t error = ERR_FSAL_NOENT; myself = container_of(parent, struct pseudo_fsal_obj_handle, obj_handle); /* Check if this context already holds the lock on * this directory. */ if (op_ctx->fsal_private != parent) PTHREAD_RWLOCK_rdlock(&parent->obj_lock); else LogFullDebug(COMPONENT_FSAL, "Skipping lock for %s", myself->name); if (strcmp(path, "..") == 0) { /* lookup parent - lookupp */ if (myself->parent != NULL) { hdl = myself->parent; *handle = &hdl->obj_handle; error = ERR_FSAL_NO_ERROR; LogFullDebug(COMPONENT_FSAL, "Found %s/%s hdl=%p", myself->name, path, hdl); } goto out; } key->name = (char *)path; node = avltree_inline_name_lookup(&key->avl_n, &myself->avl_name); if (node) { hdl = avltree_container_of(node, struct pseudo_fsal_obj_handle, avl_n); *handle = &hdl->obj_handle; error = ERR_FSAL_NO_ERROR; LogFullDebug(COMPONENT_FSAL, "Found %s/%s hdl=%p", myself->name, path, hdl); } out: /* op_ctx->pseudo_fsal_internal_lookup signals that this is a lookup * request from the export update process and so we should answer it * with the current state */ if (is_export_update_in_progress() && !op_ctx->flags.pseudo_fsal_internal_lookup) { /* An export update is in progress, we can't trust our entries. * Tell the client to retry. */ LogDebug(COMPONENT_EXPORT, "PseudoFS LOOKUP of %s failed due to export update", path); error = ERR_FSAL_DELAY; } if (op_ctx->fsal_private != parent) PTHREAD_RWLOCK_unlock(&parent->obj_lock); if (error == ERR_FSAL_NO_ERROR && attrs_out != NULL) { /* This is unlocked, however, for the most part, attributes * are read-only. Come back later and do some lock protection. */ fsal_copy_attrs(attrs_out, &hdl->attributes, false); } return fsalstat(error, 0); } /** * @brief Create a directory * * This function creates a new directory. * * While FSAL_PSEUDO is a support_ex FSAL, it doesn't actually support * setting attributes, so only the mode attribute is relevant. Any other * attributes set on creation will be ignored. The owner and group will be * set from the active credentials. * * @param[in] dir_hdl Directory in which to create the * directory * @param[in] name Name of directory to create * @param[in] attrs_in Attributes to set on newly created * object * @param[out] handle Newly created object * @param[in,out] attrs_out Optional attributes for newly created * object * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @note On success, @a new_obj has been ref'd * * @return FSAL status. */ static fsal_status_t makedir(struct fsal_obj_handle *dir_hdl, const char *name, struct fsal_attrlist *attrs_in, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct pseudo_fsal_obj_handle *myself, *hdl; uint32_t numlinks; LogDebug(COMPONENT_FSAL, "create %s", name); *handle = NULL; /* poison it */ if (!fsal_obj_handle_is(dir_hdl, DIRECTORY)) { LogCrit(COMPONENT_FSAL, "Parent handle is not a directory. hdl = 0x%p", dir_hdl); return fsalstat(ERR_FSAL_NOTDIR, 0); } myself = container_of(dir_hdl, struct pseudo_fsal_obj_handle, obj_handle); /* allocate an obj_handle and fill it up */ hdl = alloc_directory_handle(myself, name, op_ctx->fsal_export, attrs_in); numlinks = atomic_inc_uint32_t(&myself->numlinks); LogFullDebug(COMPONENT_FSAL, "%s numlinks %" PRIu32, myself->name, numlinks); *handle = &hdl->obj_handle; if (attrs_out != NULL) fsal_copy_attrs(attrs_out, &hdl->attributes, false); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * read_dirents * read the directory and call through the callback function for * each entry. * @param dir_hdl [IN] the directory to read * @param whence [IN] where to start (next) * @param dir_state [IN] pass thru of state to callback * @param cb [IN] callback function * @param eof [OUT] eof marker true == end of dir */ static fsal_status_t read_dirents(struct fsal_obj_handle *dir_hdl, fsal_cookie_t *whence, void *dir_state, fsal_readdir_cb cb, attrmask_t attrmask, bool *eof) { struct pseudo_fsal_obj_handle *myself, *hdl; struct avltree_node *node; fsal_cookie_t seekloc; struct fsal_attrlist attrs; enum fsal_dir_result cb_rc; fsal_errors_t error = ERR_FSAL_NO_ERROR; if (whence != NULL) seekloc = *whence; else seekloc = 2; /* start from index 2, if no cookie */ *eof = true; myself = container_of(dir_hdl, struct pseudo_fsal_obj_handle, obj_handle); LogDebug(COMPONENT_FSAL, "hdl=%p, name=%s", myself, myself->name); PTHREAD_RWLOCK_rdlock(&dir_hdl->obj_lock); /* Use fsal_private to signal to lookup that we hold * the lock. */ op_ctx->fsal_private = dir_hdl; for (node = avltree_first(&myself->avl_index); node != NULL; node = avltree_next(node)) { hdl = avltree_container_of(node, struct pseudo_fsal_obj_handle, avl_i); if (is_export_update_in_progress()) { error = ERR_FSAL_DELAY; goto out; } /* skip entries before seekloc */ if (hdl->index < seekloc) continue; fsal_prepare_attrs(&attrs, attrmask); fsal_copy_attrs(&attrs, &hdl->attributes, false); cb_rc = cb(hdl->name, &hdl->obj_handle, &attrs, dir_state, hdl->index + 1); fsal_release_attrs(&attrs); /* Read ahead not supported by this FSAL. */ if (cb_rc >= DIR_READAHEAD) { *eof = false; break; } } out: op_ctx->fsal_private = NULL; PTHREAD_RWLOCK_unlock(&dir_hdl->obj_lock); return fsalstat(error, 0); } static fsal_status_t getattrs(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *outattrs) { struct pseudo_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct pseudo_fsal_obj_handle, obj_handle); if (myself->parent != NULL && !myself->inavl) { /* Removed entry - stale */ LogDebug(COMPONENT_FSAL, "Requesting attributes for removed entry %p, name=%s", myself, myself->name); return fsalstat(ERR_FSAL_STALE, ESTALE); } /* We need to update the numlinks under attr lock. */ myself->attributes.numlinks = atomic_fetch_uint32_t(&myself->numlinks); *outattrs = myself->attributes; LogFullDebug(COMPONENT_FSAL, "hdl=%p, name=%s numlinks %" PRIu32, myself, myself->name, myself->attributes.numlinks); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* file_unlink * unlink the named file in the directory */ static fsal_status_t file_unlink(struct fsal_obj_handle *dir_hdl, struct fsal_obj_handle *obj_hdl, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct pseudo_fsal_obj_handle *myself, *hdl; fsal_errors_t error = ERR_FSAL_NOENT; uint32_t numlinks; myself = container_of(dir_hdl, struct pseudo_fsal_obj_handle, obj_handle); hdl = container_of(obj_hdl, struct pseudo_fsal_obj_handle, obj_handle); PTHREAD_RWLOCK_wrlock(&dir_hdl->obj_lock); /* Check if directory is empty */ numlinks = atomic_fetch_uint32_t(&hdl->numlinks); if (numlinks != 2) { LogFullDebug(COMPONENT_FSAL, "%s numlinks %" PRIu32, hdl->name, numlinks); error = ERR_FSAL_NOTEMPTY; goto unlock; } /* We need to update the numlinks. */ numlinks = atomic_dec_uint32_t(&myself->numlinks); LogFullDebug(COMPONENT_FSAL, "%s numlinks %" PRIu32, myself->name, numlinks); /* Remove from directory's name and index avls */ avltree_remove(&hdl->avl_n, &myself->avl_name); avltree_remove(&hdl->avl_i, &myself->avl_index); hdl->inavl = false; error = ERR_FSAL_NO_ERROR; now(&myself->attributes.mtime); myself->attributes.ctime = myself->attributes.mtime; myself->attributes.change = timespec_to_nsecs(&myself->attributes.mtime); unlock: PTHREAD_RWLOCK_unlock(&dir_hdl->obj_lock); return fsalstat(error, 0); } /* handle_to_wire * fill in the opaque f/s file handle part. * we zero the buffer to length first. This MAY already be done above * at which point, remove memset here because the caller is zeroing * the whole struct. */ static fsal_status_t handle_to_wire(const struct fsal_obj_handle *obj_hdl, fsal_digesttype_t output_type, struct gsh_buffdesc *fh_desc) { const struct pseudo_fsal_obj_handle *myself; myself = container_of(obj_hdl, const struct pseudo_fsal_obj_handle, obj_handle); switch (output_type) { case FSAL_DIGEST_NFSV3: case FSAL_DIGEST_NFSV4: if (fh_desc->len < V4_FH_OPAQUE_SIZE) { LogMajor( COMPONENT_FSAL, "Space too small for handle. need %lu, have %zu", ((unsigned long)V4_FH_OPAQUE_SIZE), fh_desc->len); return fsalstat(ERR_FSAL_TOOSMALL, 0); } memcpy(fh_desc->addr, myself->handle, V4_FH_OPAQUE_SIZE); fh_desc->len = V4_FH_OPAQUE_SIZE; break; default: return fsalstat(ERR_FSAL_SERVERFAULT, 0); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * handle_to_key * return a handle descriptor into the handle in this object handle * @TODO reminder. make sure things like hash keys don't point here * after the handle is released. */ static void handle_to_key(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *fh_desc) { struct pseudo_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct pseudo_fsal_obj_handle, obj_handle); fh_desc->addr = myself->handle; fh_desc->len = V4_FH_OPAQUE_SIZE; } /** * @brief release object handle * * release our export first so they know we are gone */ static void release(struct fsal_obj_handle *obj_hdl) { struct pseudo_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct pseudo_fsal_obj_handle, obj_handle); if (myself->parent == NULL || myself->inavl) { /* Entry is still live */ LogDebug(COMPONENT_FSAL, "Releasing live hdl=%p, name=%s, don't deconstruct it", myself, myself->name); return; } fsal_obj_handle_fini(obj_hdl, true); LogDebug(COMPONENT_FSAL, "Releasing obj_hdl=%p, myself=%p, name=%s", obj_hdl, myself, myself->name); if (myself->name != NULL) gsh_free(myself->name); gsh_free(myself); } void pseudofs_handle_ops_init(struct fsal_obj_ops *ops) { fsal_default_obj_ops_init(ops); ops->release = release; ops->lookup = lookup; ops->readdir = read_dirents; ops->mkdir = makedir; ops->getattrs = getattrs; ops->unlink = file_unlink; ops->handle_to_wire = handle_to_wire; ops->handle_to_key = handle_to_key; } /* export methods that create object handles */ /* lookup_path * modeled on old api except we don't stuff attributes. * KISS */ fsal_status_t pseudofs_lookup_path(struct fsal_export *exp_hdl, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { struct pseudofs_fsal_export *myself; struct fsal_attrlist attrs; myself = container_of(exp_hdl, struct pseudofs_fsal_export, export); if (strcmp(path, myself->export_path) != 0) { /* Lookup of a path other than the export's root. */ LogCrit(COMPONENT_FSAL, "Attempt to lookup non-root path %s", path); return fsalstat(ERR_FSAL_NOENT, ENOENT); } attrs.valid_mask = ATTR_MODE; attrs.mode = 0755; if (myself->root_handle == NULL) { myself->root_handle = alloc_directory_handle( NULL, myself->export_path, exp_hdl, &attrs); } *handle = &myself->root_handle->obj_handle; if (attrs_out != NULL) fsal_copy_attrs(attrs_out, &myself->root_handle->attributes, false); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* create_handle * Does what original FSAL_ExpandHandle did (sort of) * returns a ref counted handle to be later used in mdcache etc. * NOTE! you must release this thing when done with it! * BEWARE! Thanks to some holes in the *AT syscalls implementation, * we cannot get an fd on an AF_UNIX socket, nor reliably on block or * character special devices. Sorry, it just doesn't... * we could if we had the handle of the dir it is in, but this method * is for getting handles off the wire for cache entries that have LRU'd. * Ideas and/or clever hacks are welcome... */ fsal_status_t pseudofs_create_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *hdl_desc, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { struct glist_head *glist; struct fsal_obj_handle *hdl; struct pseudo_fsal_obj_handle *my_hdl; *handle = NULL; if (hdl_desc->len != V4_FH_OPAQUE_SIZE) { LogCrit(COMPONENT_FSAL, "Invalid handle size %zu expected %lu", hdl_desc->len, ((unsigned long)V4_FH_OPAQUE_SIZE)); return fsalstat(ERR_FSAL_BADHANDLE, 0); } PTHREAD_RWLOCK_rdlock(&exp_hdl->fsal->fsm_lock); glist_for_each(glist, &exp_hdl->fsal->handles) { hdl = glist_entry(glist, struct fsal_obj_handle, handles); my_hdl = container_of(hdl, struct pseudo_fsal_obj_handle, obj_handle); if (memcmp(my_hdl->handle, hdl_desc->addr, V4_FH_OPAQUE_SIZE) == 0) { LogDebug(COMPONENT_FSAL, "Found hdl=%p name=%s", my_hdl, my_hdl->name); *handle = hdl; PTHREAD_RWLOCK_unlock(&exp_hdl->fsal->fsm_lock); if (attrs_out != NULL) { fsal_copy_attrs(attrs_out, &my_hdl->attributes, false); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } } if (is_export_update_in_progress()) { /* An export update may be the cause of the failure. Tell the * client to retry. */ PTHREAD_RWLOCK_unlock(&exp_hdl->fsal->fsm_lock); LogDebug( COMPONENT_EXPORT, "PseudoFS create handle may have failed due to export update"); return fsalstat(ERR_FSAL_DELAY, 0); } LogDebug(COMPONENT_FSAL, "Could not find handle"); PTHREAD_RWLOCK_unlock(&exp_hdl->fsal->fsm_lock); return fsalstat(ERR_FSAL_STALE, ESTALE); } nfs-ganesha-6.5/src/FSAL/FSAL_PSEUDO/main.c000066400000000000000000000071261473756622300200130ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /* main.c * Module core functions */ #include "config.h" #include "fsal.h" #include /* used for 'dirname' */ #include #include #include #include #include "FSAL/fsal_init.h" #include "pseudofs_methods.h" #include "../fsal_private.h" static const char pseudoname[] = "PSEUDO"; /* my module private storage */ struct pseudo_fsal_module PSEUDOFS = { .module = { .fs_info = { .maxfilesize = 512, .maxlink = 0, .maxnamelen = MAXNAMLEN, .maxpathlen = MAXPATHLEN, .no_trunc = true, .chown_restricted = true, .case_insensitive = false, .case_preserving = true, .link_support = false, .symlink_support = false, .lock_support = false, .lock_support_async_block = false, .named_attr = false, .unique_handles = true, .acl_support = 0, .cansettime = true, .homogenous = true, .supported_attrs = PSEUDO_SUPPORTED_ATTRS, .maxread = FSAL_MAXIOSIZE, .maxwrite = FSAL_MAXIOSIZE, .umask = 0, .auth_exportpath_xdev = false, .link_supports_permission_checks = false, .expire_time_parent = -1, } } }; /* private helper for export object */ /* Initialize pseudo fs info */ static void init_config(struct fsal_module *pseudo_fsal_module) { /* if we have fsal specific params, do them here * fsal_hdl->name is used to find the block containing the * params. */ display_fsinfo(pseudo_fsal_module); LogDebug(COMPONENT_FSAL, "FSAL INIT: Supported attributes mask = 0x%" PRIx64, pseudo_fsal_module->fs_info.supported_attrs); } /* Module initialization. * Called by dlopen() to register the module * keep a private pointer to me in myself */ /* linkage to the exports and handle ops initializers */ int unload_pseudo_fsal(struct fsal_module *fsal_hdl) { int retval; retval = unregister_fsal(&PSEUDOFS.module); if (retval != 0) fprintf(stderr, "PSEUDO module failed to unregister"); return retval; } void pseudo_fsal_init(void) { int retval; struct fsal_module *myself = &PSEUDOFS.module; retval = register_fsal(myself, pseudoname, FSAL_MAJOR_VERSION, FSAL_MINOR_VERSION, FSAL_ID_NO_PNFS); if (retval != 0) { fprintf(stderr, "PSEUDO module failed to register"); return; } myself->m_ops.create_export = pseudofs_create_export; myself->m_ops.unload = unload_pseudo_fsal; /* Initialize the fsal_obj_handle ops for FSAL PSEUDO */ pseudofs_handle_ops_init(&PSEUDOFS.handle_ops); /* initialize our config */ init_config(myself); } nfs-ganesha-6.5/src/FSAL/FSAL_PSEUDO/pseudofs_methods.h000066400000000000000000000060311473756622300224410ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:expandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /* PSEUDOFS methods for handles */ #include "avltree.h" #include "gsh_list.h" #define PSEUDO_SUPPORTED_ATTRS ((const attrmask_t)(ATTRS_POSIX)) struct pseudo_fsal_obj_handle; struct pseudo_fsal_module { struct fsal_module module; struct fsal_obj_ops handle_ops; }; extern struct pseudo_fsal_module PSEUDOFS; /* * PSEUDOFS internal export */ struct pseudofs_fsal_export { struct fsal_export export; char *export_path; struct pseudo_fsal_obj_handle *root_handle; }; fsal_status_t pseudofs_lookup_path(struct fsal_export *exp_hdl, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); fsal_status_t pseudofs_create_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *hdl_desc, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); /** * @brief PSEUDOFS internal object handle * * The handle is a pointer because * a) the last element of file_handle is a char[] meaning variable len... * b) we cannot depend on it *always* being last or being the only * variable sized struct here... a pointer is safer. */ struct pseudo_fsal_obj_handle { struct fsal_obj_handle obj_handle; struct fsal_attrlist attributes; char *handle; struct pseudo_fsal_obj_handle *parent; struct avltree avl_name; struct avltree avl_index; struct avltree_node avl_n; struct avltree_node avl_i; uint32_t index; /* index in parent */ uint32_t next_i; /* next child index */ uint32_t numlinks; char *name; bool inavl; }; static inline bool pseudofs_unopenable_type(object_file_type_t type) { if ((type == SOCKET_FILE) || (type == CHARACTER_FILE) || (type == BLOCK_FILE)) { return true; } else { return false; } } void pseudofs_handle_ops_init(struct fsal_obj_ops *ops); /* Internal PSEUDOFS method linkage to export object */ fsal_status_t pseudofs_create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops); nfs-ganesha-6.5/src/FSAL/FSAL_RGW/000077500000000000000000000000001473756622300163555ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_RGW/CMakeLists.txt000066400000000000000000000031021473756622300211110ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- add_definitions( -D_FILE_OFFSET_BITS=64 ) SET(fsalrgw_LIB_SRCS up.c main.c export.c handle.c internal.c internal.h ) message("RGW_INCLUDE_DIR ${RGW_INCLUDE_DIR}") include_directories(${RGW_INCLUDE_DIR}) add_library(fsalrgw MODULE ${fsalrgw_LIB_SRCS}) add_sanitizers(fsalrgw) target_link_libraries(fsalrgw ganesha_nfsd ${RGW_LIBRARIES} ${SYSTEM_LIBRARIES} ${LDFLAG_DISALLOW_UNDEF} ) set_target_properties(fsalrgw PROPERTIES VERSION 4.2.0 SOVERSION 4) install(TARGETS fsalrgw COMPONENT fsal DESTINATION ${FSAL_DESTINATION} ) ########### install files ############### nfs-ganesha-6.5/src/FSAL/FSAL_RGW/export.c000066400000000000000000000226721473756622300200530ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Red Hat, 2015 * Author: Orit Wasserman * * This program 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 3 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 * 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 * * ------------- */ /* export.c * RGW FSAL export object */ #include #include #include #include "abstract_mem.h" #include "fsal.h" #include "fsal_types.h" #include "fsal_api.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_config.h" #include "internal.h" /** * @brief Clean up an export * * This function cleans up an export after the last reference is * released. * * @param[in,out] export The export to be released */ static void release(struct fsal_export *export_pub) { /* The private, expanded export */ struct rgw_export *export = container_of(export_pub, struct rgw_export, export); int rc = rgw_umount(export->rgw_fs, RGW_UMOUNT_FLAG_NONE); assert(rc == 0); deconstruct_handle(export->root); export->rgw_fs = NULL; export->root = NULL; fsal_detach_export(export->export.fsal, &export->export.exports); free_export_ops(&export->export); /* XXX we might need/want an rgw_unmount here, but presently, * it wouldn't do anything */ gsh_free(export); export = NULL; } /** * @brief Return a handle corresponding to a path * * This function looks up the given path and supplies an FSAL object * handle. * * @param[in] export_pub The export in which to look up the file * @param[in] path The path to look up * @param[out] pub_handle The created public FSAL handle * * @return FSAL status. */ static fsal_status_t lookup_path(struct fsal_export *export_pub, const char *path, struct fsal_obj_handle **pub_handle, struct fsal_attrlist *attrs_out) { /* The 'private' full export handle */ struct rgw_export *export = container_of(export_pub, struct rgw_export, export); /* The 'private' full object handle */ struct rgw_handle *handle = NULL; /* FSAL status structure */ fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; /* The buffer in which to store stat info */ struct stat st; /* Return code from Ceph */ int rc; /* temp filehandle */ struct rgw_file_handle *rgw_fh; /* bucket name */ const char *bucket_name = NULL; /* global directory */ const char *global_dir = NULL; *pub_handle = NULL; /* pattern like "bucket_name/" or "bucket_name/dir/" should be avoid */ if (strcmp(path, "/") && !strcmp((path + strlen(path) - 1), "/")) { status.major = ERR_FSAL_INVAL; return status; } /* should only be "/" or "bucket_name" or "bucket_name/dir "*/ if (strcmp(path, "/") && strchr(path, '/') && (strchr(path, '/') - path) > 1) { /* case : "bucket_name/dir" */ char *cp_path = strdup(path); bucket_name = strsep(&cp_path, "/"); global_dir = path + strlen(bucket_name) + 1; } else if (strcmp(path, "/") && strchr(path, '/') && (strchr(path, '/') - path) == 0) { /* case : "/bucket_name" */ bucket_name = path + 1; } else { /* case : "/" or "bucket_name" */ bucket_name = path; } /* XXX in FSAL_CEPH, the equivalent code here looks for path == "/" * and returns the root handle with no extra ref. That seems * suspicious, so let RGW figure it out (hopefully, that does not * leak refs) */ #ifndef USE_FSAL_RGW_MOUNT2 if (global_dir == NULL) { rc = rgw_lookup(export->rgw_fs, export->rgw_fs->root_fh, bucket_name, &rgw_fh, NULL, 0, RGW_LOOKUP_FLAG_NONE); if (rc < 0) return rgw2fsal_error(rc); } #else if (global_dir == NULL) { rgw_fh = export->rgw_fs->root_fh; } #endif if (global_dir != NULL) { /* search fh of bucket */ struct rgw_file_handle *rgw_dh; rc = rgw_lookup(export->rgw_fs, export->rgw_fs->root_fh, bucket_name, &rgw_dh, NULL, 0, RGW_LOOKUP_FLAG_NONE); if (rc < 0) return rgw2fsal_error(rc); /* search fh of global directory */ rc = rgw_lookup(export->rgw_fs, rgw_dh, global_dir, &rgw_fh, NULL, 0, RGW_LOOKUP_FLAG_RCB /* XXX why RCB? */); if (rc < 0) return rgw2fsal_error(rc); if (rgw_fh->fh_type == RGW_FS_TYPE_FILE) { /* only directory can be an global fh */ status.major = ERR_FSAL_INVAL; return status; } } /* get Unix attrs */ if (global_dir == NULL) { rc = rgw_getattr(export->rgw_fs, export->rgw_fs->root_fh, &st, RGW_GETATTR_FLAG_NONE); if (rc < 0) { return rgw2fsal_error(rc); } } else { rc = rgw_getattr(export->rgw_fs, rgw_fh, &st, RGW_GETATTR_FLAG_NONE); if (rc < 0) { return rgw2fsal_error(rc); } } #ifndef USE_FSAL_RGW_MOUNT2 struct stat st_root; /* fixup export fsid */ rc = rgw_getattr(export->rgw_fs, export->rgw_fs->root_fh, &st_root, RGW_GETATTR_FLAG_NONE); if (rc < 0) { return rgw2fsal_error(rc); } st.st_dev = st_root.st_dev; #endif (void)construct_handle(export, rgw_fh, &st, &handle); *pub_handle = &handle->handle; if (attrs_out != NULL) { posix2fsal_attributes_all(&st, attrs_out); } return status; } /** * @brief Decode a digested handle * * This function decodes a previously digested handle. * * @param[in] exp_handle Handle of the relevant fs export * @param[in] in_type The type of digest being decoded * @param[out] fh_desc Address and length of key */ static fsal_status_t wire_to_host(struct fsal_export *exp_hdl, fsal_digesttype_t in_type, struct gsh_buffdesc *fh_desc, int flags) { switch (in_type) { /* Digested Handles */ case FSAL_DIGEST_NFSV3: case FSAL_DIGEST_NFSV4: /* wire handles */ fh_desc->len = sizeof(struct rgw_fh_hk); break; default: return fsalstat(ERR_FSAL_SERVERFAULT, 0); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Create a handle object from a wire handle * * The wire handle is given in a buffer outlined by desc, which it * looks like we shouldn't modify. * * @param[in] export_pub Public export * @param[in] desc Handle buffer descriptor * @param[out] pub_handle The created handle * * @return FSAL status. */ static fsal_status_t create_handle(struct fsal_export *export_pub, struct gsh_buffdesc *desc, struct fsal_obj_handle **pub_handle, struct fsal_attrlist *attrs_out) { /* Full 'private' export structure */ struct rgw_export *export = container_of(export_pub, struct rgw_export, export); /* FSAL status to return */ fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; /* The FSAL specific portion of the handle received by the client */ int rc = 0; /* Stat buffer */ struct stat st; /* Handle to be created */ struct rgw_handle *handle = NULL; /* RGW fh hash key */ struct rgw_fh_hk fh_hk; /* RGW file handle instance */ struct rgw_file_handle *rgw_fh; *pub_handle = NULL; if (desc->len != sizeof(struct rgw_fh_hk)) { status.major = ERR_FSAL_INVAL; return status; } memcpy((char *)&fh_hk, desc->addr, desc->len); rc = rgw_lookup_handle(export->rgw_fs, &fh_hk, &rgw_fh, RGW_LOOKUP_FLAG_NONE); if (rc < 0) return rgw2fsal_error(-ESTALE); rc = rgw_getattr(export->rgw_fs, rgw_fh, &st, RGW_GETATTR_FLAG_NONE); if (rc < 0) return rgw2fsal_error(rc); (void)construct_handle(export, rgw_fh, &st, &handle); *pub_handle = &handle->handle; if (attrs_out != NULL) { posix2fsal_attributes_all(&st, attrs_out); } return status; } /** * @brief Get dynamic filesystem info * * This function returns dynamic filesystem information for the given * export. * * @param[in] export_pub The public export handle * @param[out] info The dynamic FS information * * @return FSAL status. */ static fsal_status_t get_fs_dynamic_info(struct fsal_export *export_pub, struct fsal_obj_handle *obj_hdl, fsal_dynamicfsinfo_t *info) { /* Full 'private' export */ struct rgw_export *export = container_of(export_pub, struct rgw_export, export); int rc = 0; /* Filesystem stat */ struct rgw_statvfs vfs_st; rc = rgw_statfs(export->rgw_fs, export->rgw_fs->root_fh, &vfs_st, RGW_STATFS_FLAG_NONE); if (rc < 0) return rgw2fsal_error(rc); /* TODO: implement in rgw_file */ memset(info, 0, sizeof(fsal_dynamicfsinfo_t)); info->total_bytes = vfs_st.f_frsize * vfs_st.f_blocks; info->free_bytes = vfs_st.f_frsize * vfs_st.f_bfree; info->avail_bytes = vfs_st.f_frsize * vfs_st.f_bavail; info->total_files = vfs_st.f_files; info->free_files = vfs_st.f_ffree; info->avail_files = vfs_st.f_favail; info->time_delta.tv_sec = 0; info->time_delta.tv_nsec = FSAL_DEFAULT_TIME_DELTA_NSEC; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Set operations for exports * * This function overrides operations that we've implemented, leaving * the rest for the default. * * @param[in,out] ops Operations vector */ void export_ops_init(struct export_ops *ops) { ops->release = release; ops->lookup_path = lookup_path; ops->wire_to_host = wire_to_host; ops->create_handle = create_handle; ops->get_fs_dynamic_info = get_fs_dynamic_info; ops->alloc_state = rgw_alloc_state; } nfs-ganesha-6.5/src/FSAL/FSAL_RGW/handle.c000066400000000000000000001526451473756622300177710ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Red Hat Inc., 2015 * Author: Orit Wasserman * * * This program 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 3 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 * 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 * * ------------- */ /* handle.c * RGW object (file|dir) handle object */ #include #include "fsal.h" #include "fsal_types.h" #include "fsal_convert.h" #include "fsal_api.h" #include "internal.h" #include "nfs_exports.h" #include "FSAL/fsal_commonlib.h" /** * @brief Release an object * * @param[in] obj_hdl The object to release * * @return FSAL status codes. */ static void release(struct fsal_obj_handle *obj_hdl) { struct rgw_handle *obj = container_of(obj_hdl, struct rgw_handle, handle); struct rgw_export *export = obj->export; if (obj->rgw_fh != export->rgw_fs->root_fh) { /* release RGW ref */ (void)rgw_fh_rele(export->rgw_fs, obj->rgw_fh, 0 /* flags */); } deconstruct_handle(obj); } /** * @brief Look up an object by name * * This function looks up an object by name in a directory. * * @param[in] dir_hdl The directory in which to look up the object. * @param[in] path The name to look up. * @param[in,out] obj_hdl The looked up object. * @param[in,out] attrs_out Optional attributes for newly created object * * @return FSAL status codes. */ static fsal_status_t lookup_int(struct fsal_obj_handle *dir_hdl, const char *path, struct fsal_obj_handle **obj_hdl, struct fsal_attrlist *attrs_out, struct stat *rcb_st, uint32_t rcb_st_mask, uint32_t flags) { int rc; struct stat st; struct rgw_file_handle *rgw_fh; struct rgw_handle *obj; struct rgw_export *export = container_of(op_ctx->fsal_export, struct rgw_export, export); struct rgw_handle *dir = container_of(dir_hdl, struct rgw_handle, handle); LogFullDebug(COMPONENT_FSAL, "%s enter dir_hdl %p path %s", __func__, dir_hdl, path); rc = rgw_lookup(export->rgw_fs, dir->rgw_fh, path, &rgw_fh, rcb_st, rcb_st_mask, flags); if (rc < 0) return rgw2fsal_error(rc); rc = rgw_getattr(export->rgw_fs, rgw_fh, &st, RGW_GETATTR_FLAG_NONE); if (rc < 0) return rgw2fsal_error(rc); (void)construct_handle(export, rgw_fh, &st, &obj); *obj_hdl = &obj->handle; if (attrs_out != NULL) { posix2fsal_attributes_all(&st, attrs_out); } return fsalstat(0, 0); } static fsal_status_t lookup(struct fsal_obj_handle *dir_hdl, const char *path, struct fsal_obj_handle **obj_hdl, struct fsal_attrlist *attrs_out) { return lookup_int(dir_hdl, path, obj_hdl, attrs_out, NULL, 0, RGW_LOOKUP_FLAG_NONE); } struct rgw_cb_arg { fsal_readdir_cb cb; void *fsal_arg; struct fsal_obj_handle *dir_hdl; attrmask_t attrmask; }; static int rgw_cb(const char *name, void *arg, uint64_t offset, struct stat *st, uint32_t st_mask, uint32_t flags) { struct rgw_cb_arg *rgw_cb_arg = arg; struct fsal_obj_handle *obj = NULL; fsal_status_t status; struct fsal_attrlist attrs; enum fsal_dir_result cb_rc; fsal_prepare_attrs(&attrs, rgw_cb_arg->attrmask); /* rgw_lookup now accepts type hints */ status = lookup_int( rgw_cb_arg->dir_hdl, name, &obj, &attrs, st, st_mask, RGW_LOOKUP_FLAG_RCB | (flags & (RGW_LOOKUP_FLAG_DIR | RGW_LOOKUP_FLAG_FILE))); if (FSAL_IS_ERROR(status)) { LogWarn(COMPONENT_FSAL, "%s attempt to lookup %s after rgw_readdir() failed " "(%d, %d)", __func__, name, status.major, status.minor); fsal_release_attrs(&attrs); return true; /* no entry is provided to ganesha, so the * readahead state is unchanged */ } /** @todo FSF - when rgw gains mark capability, need to change this * code... */ cb_rc = rgw_cb_arg->cb(name, obj, &attrs, rgw_cb_arg->fsal_arg, offset); fsal_release_attrs(&attrs); return cb_rc <= DIR_READAHEAD; } /** * @brief Read a directory * * This function reads the contents of a directory (excluding . and * .., which is ironic since the Ceph readdir call synthesizes them * out of nothing) and passes dirent information to the supplied * callback. * * @param[in] dir_hdl The directory to read * @param[in] whence The cookie indicating resumption, NULL to start * @param[in] dir_state Opaque, passed to cb * @param[in] cb Callback that receives directory entries * @param[out] eof True if there are no more entries * * @return FSAL status. */ static fsal_status_t rgw_fsal_readdir(struct fsal_obj_handle *dir_hdl, fsal_cookie_t *whence, void *cb_arg, fsal_readdir_cb cb, attrmask_t attrmask, bool *eof) { int rc; fsal_status_t fsal_status = { ERR_FSAL_NO_ERROR, 0 }; struct rgw_cb_arg rgw_cb_arg = { cb, cb_arg, dir_hdl, attrmask }; /* when whence_is_name, whence is a char pointer cast to * fsal_cookie_t */ const char *r_whence = (const char *)whence; struct rgw_export *export = container_of(op_ctx->fsal_export, struct rgw_export, export); struct rgw_handle *dir = container_of(dir_hdl, struct rgw_handle, handle); LogFullDebug(COMPONENT_FSAL, "%s enter dir_hdl %p", __func__, dir_hdl); rc = 0; *eof = false; rc = rgw_readdir2(export->rgw_fs, dir->rgw_fh, r_whence, rgw_cb, &rgw_cb_arg, eof, RGW_READDIR_FLAG_NONE); if (rc < 0) return rgw2fsal_error(rc); return fsal_status; } /** * @brief Project cookie offset for a dirent name * * This optional API function produces the stable offset which * corresponds to a given dirent name (FSALs for which there is * no stable mapping will not implement). * * @param[in] dir_hdl The containing directory * @param[in] name The dirent name * * @return FSAL status. */ static fsal_cookie_t rgw_fsal_compute_cookie(struct fsal_obj_handle *dir_hdl, const char *name) { uint64_t offset = 0; /* XXX */ struct rgw_export *export = container_of(op_ctx->fsal_export, struct rgw_export, export); struct rgw_handle *dir = container_of(dir_hdl, struct rgw_handle, handle); LogFullDebug(COMPONENT_FSAL, "%s enter dir_hdl %p name %s", __func__, dir_hdl, name); if (unlikely(!strcmp(name, ".."))) { return 1; } if (unlikely(!strcmp(name, "."))) { return 2; } (void)rgw_dirent_offset(export->rgw_fs, dir->rgw_fh, name, &offset, RGW_DIRENT_OFFSET_FLAG_NONE); return offset; } /** * @brief Help sort dirents. * * For FSALs that are able to compute the cookie for a filename * deterministically from the filename, there must also be a defined order of * entries in a directory based on the name (could be strcmp sort, could be * strict alpha sort, could be deterministic order based on cookie). * * Although the cookies could be computed, the caller will already have them * and thus will provide them to save compute time. * * @param[in] parent Directory entries belong to. * @param[in] name1 File name of first dirent * @param[in] cookie1 Cookie of first dirent * @param[in] name2 File name of second dirent * @param[in] cookie2 Cookie of second dirent * * @retval < 0 if name1 sorts before name2 * @retval == 0 if name1 sorts the same as name2 * @retval >0 if name1 sorts after name2 */ int rgw_fsal_dirent_cmp(struct fsal_obj_handle *parent, const char *name1, fsal_cookie_t cookie1, const char *name2, fsal_cookie_t cookie2) { return strcmp(name1, name2); } /** * @brief Create a directory * * This function creates a new directory. * * For support_ex, this method will handle attribute setting. The caller * MUST include the mode attribute and SHOULD NOT include the owner or * group attributes if they are the same as the op_ctx->cred. * * @param[in] dir_hdl Directory in which to create the directory * @param[in] name Name of directory to create * @param[in] attrib Attributes to set on newly created object * @param[out] new_obj Newly created object * * @note On success, @a new_obj has been ref'd * * @return FSAL status. */ static fsal_status_t rgw_fsal_mkdir(struct fsal_obj_handle *dir_hdl, const char *name, struct fsal_attrlist *attrs_in, struct fsal_obj_handle **obj_hdl, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { int rc; struct rgw_file_handle *rgw_fh; struct rgw_handle *obj; struct stat st; struct rgw_export *export = container_of(op_ctx->fsal_export, struct rgw_export, export); struct rgw_handle *dir = container_of(dir_hdl, struct rgw_handle, handle); LogFullDebug(COMPONENT_FSAL, "%s enter dir_hdl %p name %s", __func__, dir_hdl, name); memset(&st, 0, sizeof(struct stat)); st.st_uid = op_ctx->creds.caller_uid; st.st_gid = op_ctx->creds.caller_gid; st.st_mode = fsal2unix_mode(attrs_in->mode) & ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export); uint32_t create_mask = RGW_SETATTR_UID | RGW_SETATTR_GID | RGW_SETATTR_MODE; rc = rgw_mkdir(export->rgw_fs, dir->rgw_fh, name, &st, create_mask, &rgw_fh, RGW_MKDIR_FLAG_NONE); if (rc < 0) return rgw2fsal_error(rc); (void)construct_handle(export, rgw_fh, &st, &obj); *obj_hdl = &obj->handle; if (attrs_out != NULL) { posix2fsal_attributes_all(&st, attrs_out); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Freshen and return attributes * * This function freshens and returns the attributes of the given * file. * * @param[in] obj_hdl Object to interrogate * * @return FSAL status. */ static fsal_status_t getattrs(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs) { int rc; struct stat st; struct rgw_export *export = container_of(op_ctx->fsal_export, struct rgw_export, export); struct rgw_handle *handle = container_of(obj_hdl, struct rgw_handle, handle); LogFullDebug(COMPONENT_FSAL, "%s enter obj_hdl %p", __func__, obj_hdl); rc = rgw_getattr(export->rgw_fs, handle->rgw_fh, &st, RGW_GETATTR_FLAG_NONE); if (rc < 0) { if (attrs->request_mask & ATTR_RDATTR_ERR) { /* Caller asked for error to be visible. */ attrs->valid_mask = ATTR_RDATTR_ERR; } return rgw2fsal_error(rc); } posix2fsal_attributes_all(&st, attrs); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Set attributes on an object * * This function sets attributes on an object. Which attributes are * set is determined by attrib_set->valid_mask. The FSAL must manage bypass * or not of share reservations, and a state may be passed. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] attrib_set Attributes to set * * @return FSAL status. */ fsal_status_t rgw_fsal_setattr2(struct fsal_obj_handle *obj_hdl, bool bypass, struct state_t *state, struct fsal_attrlist *attrib_set) { fsal_status_t status = { 0, 0 }; int rc = 0; bool has_share = false; struct stat st; /* Mask of attributes to set */ uint32_t mask = 0; struct rgw_export *export = container_of(op_ctx->fsal_export, struct rgw_export, export); struct rgw_handle *handle = container_of(obj_hdl, struct rgw_handle, handle); LogFullDebug(COMPONENT_FSAL, "%s enter obj_hdl %p state %p", __func__, obj_hdl, state); if (attrib_set->valid_mask & ~RGW_SETTABLE_ATTRIBUTES) { LogDebug(COMPONENT_FSAL, "bad mask %" PRIx64 " not settable %" PRIx64, attrib_set->valid_mask, attrib_set->valid_mask & ~RGW_SETTABLE_ATTRIBUTES); return fsalstat(ERR_FSAL_INVAL, 0); } LogAttrlist(COMPONENT_FSAL, NIV_FULL_DEBUG, "attrs ", attrib_set, false); /* apply umask, if mode attribute is to be changed */ if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MODE)) attrib_set->mode &= ~op_ctx->fsal_export->exp_ops.fs_umask( op_ctx->fsal_export); /* Test if size is being set, make sure file is regular and if so, * require a read/write file descriptor. */ if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_SIZE)) { if (obj_hdl->type != REGULAR_FILE) { LogFullDebug(COMPONENT_FSAL, "Setting size on non-regular file"); return fsalstat(ERR_FSAL_INVAL, EINVAL); } if (state == NULL) { /* Check share reservation and if OK, update the * counters. */ status = check_share_conflict_and_update_locked( obj_hdl, &handle->share, FSAL_O_CLOSED, FSAL_O_WRITE, bypass); if (FSAL_IS_ERROR(status)) { LogDebug( COMPONENT_FSAL, "check_share_conflict_and_update_locked failed with %s", fsal_err_txt(status)); return status; } has_share = true; } } memset(&st, 0, sizeof(struct stat)); if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_SIZE)) { rc = rgw_truncate(export->rgw_fs, handle->rgw_fh, attrib_set->filesize, RGW_TRUNCATE_FLAG_NONE); if (rc < 0) { status = rgw2fsal_error(rc); LogDebug(COMPONENT_FSAL, "truncate returned %s (%d)", strerror(-rc), -rc); goto out; } } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MODE)) { mask |= RGW_SETATTR_MODE; st.st_mode = fsal2unix_mode(attrib_set->mode); } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_OWNER)) { mask |= RGW_SETATTR_UID; st.st_uid = attrib_set->owner; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_GROUP)) { mask |= RGW_SETATTR_GID; st.st_gid = attrib_set->group; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_ATIME)) { mask |= RGW_SETATTR_ATIME; st.st_atim = attrib_set->atime; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_ATIME_SERVER)) { mask |= RGW_SETATTR_ATIME; struct timespec timestamp; rc = clock_gettime(CLOCK_REALTIME, ×tamp); if (rc != 0) { LogDebug(COMPONENT_FSAL, "clock_gettime returned %s (%d)", strerror(-rc), -rc); status = rgw2fsal_error(rc); goto out; } st.st_atim = timestamp; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MTIME)) { mask |= RGW_SETATTR_MTIME; st.st_mtim = attrib_set->mtime; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MTIME_SERVER)) { mask |= RGW_SETATTR_MTIME; struct timespec timestamp; rc = clock_gettime(CLOCK_REALTIME, ×tamp); if (rc != 0) { LogDebug(COMPONENT_FSAL, "clock_gettime returned %s (%d)", strerror(-rc), -rc); status = rgw2fsal_error(rc); goto out; } st.st_mtim = timestamp; } if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_CTIME)) { mask |= RGW_SETATTR_CTIME; st.st_ctim = attrib_set->ctime; } rc = rgw_setattr(export->rgw_fs, handle->rgw_fh, &st, mask, RGW_SETATTR_FLAG_NONE); if (rc < 0) { LogDebug(COMPONENT_FSAL, "setattr returned %s (%d)", strerror(-rc), -rc); status = rgw2fsal_error(rc); } else { /* Success */ status = fsalstat(ERR_FSAL_NO_ERROR, 0); } out: if (has_share) { /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &handle->share, FSAL_O_WRITE, FSAL_O_CLOSED); } return status; } /** * @brief Rename a file * * This function renames a file, possibly moving it into another * directory. We assume most checks are done by the caller. * * @param[in] olddir_hdl Source directory * @param[in] old_name Original name * @param[in] newdir_hdl Destination directory * @param[in] new_name New name * * @return FSAL status. */ static fsal_status_t rgw_fsal_rename(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *olddir_hdl, const char *old_name, struct fsal_obj_handle *newdir_hdl, const char *new_name, struct fsal_attrlist *olddir_pre_attrs_out, struct fsal_attrlist *olddir_post_attrs_out, struct fsal_attrlist *newdir_pre_attrs_out, struct fsal_attrlist *newdir_post_attrs_out) { int rc; struct rgw_export *export = container_of(op_ctx->fsal_export, struct rgw_export, export); struct rgw_handle *olddir = container_of(olddir_hdl, struct rgw_handle, handle); struct rgw_handle *newdir = container_of(newdir_hdl, struct rgw_handle, handle); LogFullDebug( COMPONENT_FSAL, "%s enter obj_hdl %p olddir_hdl %p oname %s newdir_hdl %p nname %s", __func__, obj_hdl, olddir_hdl, old_name, newdir_hdl, new_name); rc = rgw_rename(export->rgw_fs, olddir->rgw_fh, old_name, newdir->rgw_fh, new_name, RGW_RENAME_FLAG_NONE); if (rc < 0) return rgw2fsal_error(rc); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Remove a name * * This function removes a name from the filesystem and possibly * deletes the associated file. Directories must be empty to be * removed. * * @param[in] dir_hdl The directory from which to remove the name * @param[in] obj_hdl The object being removed * @param[in] name The name to remove * * @return FSAL status. */ static fsal_status_t rgw_fsal_unlink(struct fsal_obj_handle *dir_hdl, struct fsal_obj_handle *obj_hdl, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { int rc; struct rgw_export *export = container_of(op_ctx->fsal_export, struct rgw_export, export); struct rgw_handle *dir = container_of(dir_hdl, struct rgw_handle, handle); LogFullDebug(COMPONENT_FSAL, "%s enter dir_hdl %p obj_hdl %p name %s", __func__, dir_hdl, obj_hdl, name); rc = rgw_unlink(export->rgw_fs, dir->rgw_fh, name, RGW_UNLINK_FLAG_NONE); if (rc < 0) return rgw2fsal_error(rc); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Merge a duplicate handle with an original handle * * This function is used if an upper layer detects that a duplicate * object handle has been created. It allows the FSAL to merge anything * from the duplicate back into the original. * * The caller must release the object (the caller may have to close * files if the merge is unsuccessful). * * @param[in] orig_hdl Original handle * @param[in] dupe_hdl Handle to merge into original * * @return FSAL status. * */ fsal_status_t rgw_merge(struct fsal_obj_handle *orig_hdl, struct fsal_obj_handle *dupe_hdl) { fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; if (orig_hdl->type == REGULAR_FILE && dupe_hdl->type == REGULAR_FILE) { /* We need to merge the share reservations on this file. * This could result in ERR_FSAL_SHARE_DENIED. */ struct rgw_handle *orig, *dupe; orig = container_of(orig_hdl, struct rgw_handle, handle); dupe = container_of(dupe_hdl, struct rgw_handle, handle); /* This can block over an I/O operation. */ status = merge_share(orig_hdl, &orig->share, &dupe->share); } return status; } /** * @brief Open a file descriptor for read or write and possibly create * * This function opens a file for read or write, possibly creating it. * If the caller is passing a state, it must hold the state_lock * exclusive. * * state can be NULL which indicates a stateless open (such as via the * NFS v3 CREATE operation), in which case the FSAL must assure protection * of any resources. If the file is being created, such protection is * simple since no one else will have access to the object yet, however, * in the case of an exclusive create, the common resources may still need * protection. * * If Name is NULL, obj_hdl is the file itself, otherwise obj_hdl is the * parent directory. * * On an exclusive create, the upper layer may know the object handle * already, so it MAY call with name == NULL. In this case, the caller * expects just to check the verifier. * * On a call with an existing object handle for an UNCHECKED create, * we can set the size to 0. * * If attributes are not set on create, the FSAL will set some minimal * attributes (for example, mode might be set to 0600). * * If an open by name succeeds and did not result in Ganesha creating a file, * the caller will need to do a subsequent permission check to confirm the * open. This is because the permission attributes were not available * beforehand. * * @param[in] obj_hdl File to open or parent directory * @param[in,out] state state_t to use for this operation * @param[in] openflags Mode for open * @param[in] createmode Mode for create * @param[in] name Name for file if being created or opened * @param[in] attrib_set Attributes to set on created file * @param[in] verifier Verifier to use for exclusive create * @param[in,out] new_obj Newly created object * @param[in,out] caller_perm_check The caller must do a permission check * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @return FSAL status. */ fsal_status_t rgw_fsal_open2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attrib_set, fsal_verifier_t verifier, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { int posix_flags = 0; int rc; mode_t unix_mode; fsal_status_t status = { 0, 0 }; struct stat st; bool truncated; bool setattrs = attrib_set != NULL; bool created = false; struct fsal_attrlist verifier_attr; struct rgw_open_state *open_state = NULL; struct rgw_file_handle *rgw_fh; struct rgw_handle *obj; struct rgw_export *export = container_of(op_ctx->fsal_export, struct rgw_export, export); struct rgw_handle *handle = container_of(obj_hdl, struct rgw_handle, handle); LogFullDebug(COMPONENT_FSAL, "%s enter obj_hdl %p state %p", __func__, obj_hdl, open_state); if (state) { open_state = (struct rgw_open_state *)state; } if (setattrs) LogAttrlist(COMPONENT_FSAL, NIV_FULL_DEBUG, "attrs ", attrib_set, false); fsal2posix_openflags(openflags, &posix_flags); truncated = (posix_flags & O_TRUNC) != 0; /* Now fixup attrs for verifier if exclusive create */ if (createmode >= FSAL_EXCLUSIVE) { if (!setattrs) { /* We need to use verifier_attr */ attrib_set = &verifier_attr; memset(&verifier_attr, 0, sizeof(verifier_attr)); } set_common_verifier(attrib_set, verifier, false); } if (!name) { /* This is an open by handle */ if (state) { /* Prepare to take the share reservation, but only if we * are called with a valid state (if state is NULL the * caller is a stateless create such as NFS v3 CREATE). */ status = check_share_conflict_and_update_locked( obj_hdl, &handle->share, FSAL_O_CLOSED, openflags, false); if (FSAL_IS_ERROR(status)) return status; } else { /* RGW doesn't have a file descriptor/open abstraction, * and actually forbids concurrent opens; This is * where more advanced FSALs would fall back to using * a "global" fd--what we always use; We still need * to take the lock expected by ULP */ #if 0 my_fd = &hdl->fd; #endif PTHREAD_RWLOCK_wrlock(&obj_hdl->obj_lock); } rc = rgw_open(export->rgw_fs, handle->rgw_fh, posix_flags, (!state) ? RGW_OPEN_FLAG_V3 : RGW_OPEN_FLAG_NONE); if (rc < 0) { if (!state) { /* Release the lock taken above, and return * since there is nothing to undo. */ PTHREAD_RWLOCK_unlock(&obj_hdl->obj_lock); return rgw2fsal_error(rc); } else { /* Error - need to release the share */ goto undo_share; } } if (state) { open_state->openflags = FSAL_O_NFS_FLAGS(openflags); } else { handle->openflags = FSAL_O_NFS_FLAGS(openflags); } if (createmode >= FSAL_EXCLUSIVE || truncated) { /* refresh attributes */ rc = rgw_getattr(export->rgw_fs, handle->rgw_fh, &st, RGW_GETATTR_FLAG_NONE); if (rc < 0) { status = rgw2fsal_error(rc); } else { LogFullDebug(COMPONENT_FSAL, "New size = %" PRIx64, st.st_size); /* Now check verifier for exclusive, but not for * FSAL_EXCLUSIVE_9P. */ if (createmode >= FSAL_EXCLUSIVE && createmode != FSAL_EXCLUSIVE_9P && !obj_hdl->obj_ops->check_verifier( obj_hdl, verifier)) { /* Verifier didn't match */ status = fsalstat( posix2fsal_error(EEXIST), EEXIST); } else if (attrs_out) { posix2fsal_attributes_all(&st, attrs_out); } } } else if (attrs_out && attrs_out->request_mask & ATTR_RDATTR_ERR) { attrs_out->valid_mask = ATTR_RDATTR_ERR; } if (!state) { /* If no state, release the lock taken above and return * status. If success, we haven't done any permission * check so ask the caller to do so. */ PTHREAD_RWLOCK_unlock(&obj_hdl->obj_lock); *caller_perm_check = !FSAL_IS_ERROR(status); return status; } if (!FSAL_IS_ERROR(status)) { /* Return success. We haven't done any permission * check so ask the caller to do so. */ *caller_perm_check = true; return status; } /* close on error */ (void)rgw_close(export->rgw_fs, handle->rgw_fh, RGW_CLOSE_FLAG_NONE); undo_share: /* Can only get here with state not NULL and an error */ /* On error we need to release our share reservation * and undo the update of the share counters. * This can block over an I/O operation. */ update_share_counters_locked(obj_hdl, &handle->share, openflags, FSAL_O_CLOSED); return status; } /* !name */ /* In this path where we are opening by name, we can't check share * reservation yet since we don't have an object_handle yet. If we * indeed create the object handle (there is no race with another * open by name), then there CAN NOT be a share conflict, otherwise * the share conflict will be resolved when the object handles are * merged. */ if (createmode == FSAL_NO_CREATE) { /* Non creation case, librgw doesn't have open by name so we * have to do a lookup and then handle as an open by handle. */ struct fsal_obj_handle *temp = NULL; /* We don't have open by name... */ status = obj_hdl->obj_ops->lookup(obj_hdl, name, &temp, NULL); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "lookup returned %s", fsal_err_txt(status)); return status; } if (temp->type != REGULAR_FILE) { if (temp->type == DIRECTORY) { /* Trying to open2 a directory */ status = fsalstat(ERR_FSAL_ISDIR, 0); } else { /* Trying to open2 any other non-regular file */ status = fsalstat(ERR_FSAL_SYMLINK, 0); } /* Release the object we found by lookup. */ temp->obj_ops->release(temp); LogFullDebug(COMPONENT_FSAL, "open2 returning %s", fsal_err_txt(status)); return status; } /* Now call ourselves without name and attributes to open. */ status = obj_hdl->obj_ops->open2( temp, state, openflags, FSAL_NO_CREATE, NULL, NULL, verifier, new_obj, attrs_out, caller_perm_check, parent_pre_attrs_out, parent_post_attrs_out); if (FSAL_IS_ERROR(status)) { /* Release the object we found by lookup. */ temp->obj_ops->release(temp); LogFullDebug(COMPONENT_FSAL, "open returned %s", fsal_err_txt(status)); } return status; } /* Now add in O_CREAT and O_EXCL. * Even with FSAL_UNGUARDED we try exclusive create first so * we can safely set attributes. */ if (createmode != FSAL_NO_CREATE) { posix_flags |= O_CREAT; if (createmode >= FSAL_GUARDED || setattrs) posix_flags |= O_EXCL; } if (setattrs && FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MODE)) { unix_mode = fsal2unix_mode(attrib_set->mode) & ~op_ctx->fsal_export->exp_ops.fs_umask( op_ctx->fsal_export); /* Don't set the mode if we later set the attributes */ FSAL_UNSET_MASK(attrib_set->valid_mask, ATTR_MODE); } else { /* Default to mode 0600 */ unix_mode = 0600; } memset(&st, 0, sizeof(struct stat)); /* XXX needed? */ st.st_uid = op_ctx->creds.caller_uid; st.st_gid = op_ctx->creds.caller_gid; st.st_mode = unix_mode; uint32_t create_mask = RGW_SETATTR_UID | RGW_SETATTR_GID | RGW_SETATTR_MODE; rc = rgw_create(export->rgw_fs, handle->rgw_fh, name, &st, create_mask, &rgw_fh, posix_flags, RGW_CREATE_FLAG_NONE); if (rc < 0) { LogFullDebug(COMPONENT_FSAL, "Create %s failed with %s", name, strerror(-rc)); } /* XXX won't get here, but maybe someday */ if (rc == -EEXIST && createmode == FSAL_UNCHECKED) { /* We tried to create O_EXCL to set attributes and failed. * Remove O_EXCL and retry, also remember not to set attributes. * We still try O_CREAT again just in case file disappears out * from under us. */ posix_flags &= ~O_EXCL; rc = rgw_create(export->rgw_fs, handle->rgw_fh, name, &st, create_mask, &rgw_fh, posix_flags, RGW_CREATE_FLAG_NONE); if (rc < 0) { LogFullDebug(COMPONENT_FSAL, "Non-exclusive Create %s failed with %s", name, strerror(-rc)); } } if (rc < 0) { return rgw2fsal_error(rc); } /* Remember if we were responsible for creating the file. * Note that in an UNCHECKED retry we MIGHT have re-created the * file and won't remember that. Oh well, so in that rare case we * leak a partially created file if we have a subsequent error in here. * Since we were able to do the permission check even if we were not * creating the file, let the caller know the permission check has * already been done. Note it IS possible in the case of a race between * an UNCHECKED open and an external unlink, we did create the file. */ created = (posix_flags & O_EXCL) != 0; *caller_perm_check = false; (void)construct_handle(export, rgw_fh, &st, &obj); /* Check if the opened file is not a regular file. */ if (posix2fsal_type(st.st_mode) == DIRECTORY) { /* Trying to open2 a directory */ status = fsalstat(ERR_FSAL_ISDIR, 0); goto fileerr; } if (posix2fsal_type(st.st_mode) != REGULAR_FILE) { /* Trying to open2 any other non-regular file */ status = fsalstat(ERR_FSAL_SYMLINK, 0); goto fileerr; } /* here FSAL_CEPH operates on its (for RGW non-existent) global * fd */ #if 0 /* If we didn't have a state above, use the global fd. At this point, * since we just created the global fd, no one else can have a * reference to it, and thus we can mamnipulate unlocked which is * handy since we can then call setattr2 which WILL take the lock * without a double locking deadlock. */ if (my_fd == NULL) { LogFullDebug(COMPONENT_FSAL, "Using global fd"); my_fd = &hdl->fd; /* Need to LRU track global fd including incrementing * fsal_fd_global_counter. */ insert_fd_lru(&my_fd->fsal_fd); } my_fd->fd = fd; #endif if (state) { open_state->openflags = FSAL_O_NFS_FLAGS(openflags); } else { obj->openflags = FSAL_O_NFS_FLAGS(openflags); } *new_obj = &obj->handle; rc = rgw_open(export->rgw_fs, rgw_fh, posix_flags, (!state) ? RGW_OPEN_FLAG_V3 : RGW_OPEN_FLAG_NONE); if (rc < 0) { goto fileerr; } if (created && setattrs && attrib_set->valid_mask != 0) { /* Set attributes using our newly opened file descriptor as the * share_fd if there are any left to set (mode and truncate * have already been handled). * * Note that we only set the attributes if we were responsible * for creating the file. */ status = (*new_obj)->obj_ops->setattr2(*new_obj, false, state, attrib_set); if (FSAL_IS_ERROR(status)) goto fileerr; if (attrs_out != NULL) { status = (*new_obj)->obj_ops->getattrs(*new_obj, attrs_out); if (FSAL_IS_ERROR(status) && (attrs_out->request_mask & ATTR_RDATTR_ERR) == 0) { /* Get attributes failed and caller expected * to get the attributes. Otherwise continue * with attrs_out indicating ATTR_RDATTR_ERR. */ goto fileerr; } } } else if (attrs_out != NULL) { /* Since we haven't set any attributes other than what was set * on create (if we even created), just use the stat results * we used to create the fsal_obj_handle. */ posix2fsal_attributes_all(&st, attrs_out); } if (state != NULL) { /* Prepare to take the share reservation, but only if we are * called with a valid state (if state is NULL the caller is * a stateless create such as NFS v3 CREATE). */ /* Take the share reservation now by updating the counters. */ update_share_counters_locked(*new_obj, &obj->share, FSAL_O_CLOSED, openflags); } return fsalstat(ERR_FSAL_NO_ERROR, 0); fileerr: /* Close the file we just opened. */ (void)rgw_close(export->rgw_fs, obj->rgw_fh, RGW_CLOSE_FLAG_NONE); if (created) { /* Remove the file we just created */ (void)rgw_unlink(export->rgw_fs, obj->rgw_fh, name, RGW_UNLINK_FLAG_NONE); } /* Release the handle we just allocated. */ (*new_obj)->obj_ops->release(*new_obj); *new_obj = NULL; return status; } /** * @brief Return open status of a state. * * This function returns open flags representing the current open * status for a state. The st_lock must be held. * * @param[in] obj_hdl File on which to operate * @param[in] state File state to interrogate * * @retval Flags representing current open status */ fsal_openflags_t rgw_fsal_status2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { struct rgw_handle *handle = container_of(obj_hdl, struct rgw_handle, handle); /* normal FSALs recover open state in "state" */ return handle->openflags; } /** * @brief Re-open a file that may be already opened * * This function supports changing the access mode of a share reservation and * thus should only be called with a share state. The st_lock must be held. * * This MAY be used to open a file the first time if there is no need for * open by name or create semantics. One example would be 9P lopen. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] openflags Mode for re-open * * @return FSAL status. */ fsal_status_t rgw_fsal_reopen2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags) { fsal_status_t status = { 0, 0 }; int posix_flags = 0; fsal_openflags_t old_openflags; struct rgw_open_state *open_state = NULL; struct rgw_export *export = container_of(op_ctx->fsal_export, struct rgw_export, export); struct rgw_handle *handle = container_of(obj_hdl, struct rgw_handle, handle); LogFullDebug(COMPONENT_FSAL, "%s enter obj_hdl %p state %p", __func__, obj_hdl, open_state); /* RGW fsal does not permit concurrent opens, so openflags * are recovered from handle */ if (state) { /* a conceptual open state exists */ open_state = (struct rgw_open_state *)state; LogFullDebug(COMPONENT_FSAL, "%s called w/open_state %p", __func__, open_state); } fsal2posix_openflags(openflags, &posix_flags); old_openflags = handle->openflags; /* We can conflict with old share, so go ahead and check now. If OK * set up the new share so we can drop the lock and not have a * conflicting share be asserted, updating the share counters. */ status = check_share_conflict_and_update_locked( obj_hdl, &handle->share, old_openflags, openflags, false); if (FSAL_IS_ERROR(status)) return status; /* perform a provider open iff not already open */ if (true) { /* XXX also, how do we know the ULP tracks opens? * 9P does, V3 does not */ int rc = rgw_open(export->rgw_fs, handle->rgw_fh, posix_flags, (!state) ? RGW_OPEN_FLAG_V3 : RGW_OPEN_FLAG_NONE); if (rc < 0) { /* We had a failure on open - we need to revert the * share. */ update_share_counters_locked(obj_hdl, &handle->share, openflags, old_openflags); } status = rgw2fsal_error(rc); } return status; } /** * @brief Read data from a file * * This function reads data from the given file. The FSAL must be able to * perform the read whether a state is presented or not. This function also * is expected to handle properly bypassing or not share reservations. This is * an (optionally) asynchronous call. When the I/O is complete, the done * callback is called with the results. * * @param[in] obj_hdl File on which to operate * @param[in] bypass If state doesn't indicate a share reservation, * bypass any deny read * @param[in,out] done_cb Callback to call when I/O is done * @param[in,out] read_arg Info about read, passed back in callback * @param[in,out] caller_arg Opaque arg from the caller for callback * * @return Nothing; results are in callback */ void rgw_fsal_read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg) { struct rgw_export *export = container_of(op_ctx->fsal_export, struct rgw_export, export); struct rgw_handle *handle = container_of(obj_hdl, struct rgw_handle, handle); uint64_t offset = read_arg->offset; int i; LogFullDebug(COMPONENT_FSAL, "%s enter obj_hdl %p state %p", __func__, obj_hdl, read_arg->state); if (read_arg->info != NULL) { /* Currently we don't support READ_PLUS */ done_cb(obj_hdl, fsalstat(ERR_FSAL_NOTSUPP, 0), read_arg, caller_arg); return; } /* RGW does not support a file descriptor abstraction--so * reads are handle based */ for (i = 0; i < read_arg->iov_count; i++) { size_t nb_read; int rc = rgw_read(export->rgw_fs, handle->rgw_fh, offset, read_arg->iov[i].iov_len, &nb_read, read_arg->iov[i].iov_base, RGW_READ_FLAG_NONE); if (rc < 0) { done_cb(obj_hdl, rgw2fsal_error(rc), read_arg, caller_arg); return; } read_arg->io_amount += nb_read; offset += nb_read; } read_arg->end_of_file = (read_arg->io_amount == 0); done_cb(obj_hdl, fsalstat(0, 0), read_arg, caller_arg); } /** * @brief Write data to a file * * This function writes data to a file. The FSAL must be able to * perform the write whether a state is presented or not. This function also * is expected to handle properly bypassing or not share reservations. Even * with bypass == true, it will enforce a mandatory (NFSv4) deny_write if * an appropriate state is not passed). * * The FSAL is expected to enforce sync if necessary. * * @param[in] obj_hdl File on which to operate * @param[in] bypass If state doesn't indicate a share reservation, * bypass any non-mandatory deny write * @param[in,out] done_cb Callback to call when I/O is done * @param[in,out] write_arg Info about write, passed back in callback * @param[in,out] caller_arg Opaque arg from the caller for callback */ void rgw_fsal_write2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *caller_arg) { struct rgw_export *export = container_of(op_ctx->fsal_export, struct rgw_export, export); struct rgw_handle *handle = container_of(obj_hdl, struct rgw_handle, handle); int rc, i; uint64_t offset = write_arg->offset; LogFullDebug(COMPONENT_FSAL, "%s enter obj_hdl %p state %p", __func__, obj_hdl, write_arg->state); /* XXX note no call to fsal_find_fd (or wrapper) */ for (i = 0; i < write_arg->iov_count; i++) { size_t nb_write; rc = rgw_write(export->rgw_fs, handle->rgw_fh, offset, write_arg->iov[i].iov_len, &nb_write, write_arg->iov[i].iov_base, (!write_arg->state) ? RGW_OPEN_FLAG_V3 : RGW_OPEN_FLAG_NONE); if (rc < 0) { done_cb(obj_hdl, rgw2fsal_error(rc), write_arg, caller_arg); return; } write_arg->io_amount += nb_write; offset += nb_write; } if (write_arg->fsal_stable) { rc = rgw_fsync(export->rgw_fs, handle->rgw_fh, RGW_WRITE_FLAG_NONE); if (rc < 0) { write_arg->fsal_stable = false; done_cb(obj_hdl, rgw2fsal_error(rc), write_arg, caller_arg); return; } } done_cb(obj_hdl, fsalstat(ERR_FSAL_NO_ERROR, 0), write_arg, caller_arg); } /** * @brief Commit written data * * This function flushes possibly buffered data to a file. This method * differs from commit due to the need to interact with share reservations * and the fact that the FSAL manages the state of "file descriptors". The * FSAL must be able to perform this operation without being passed a specific * state. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] offset Start of range to commit * @param[in] len Length of range to commit * * @return FSAL status. */ fsal_status_t rgw_fsal_commit2(struct fsal_obj_handle *obj_hdl, off_t offset, size_t length) { int rc; struct rgw_export *export = container_of(op_ctx->fsal_export, struct rgw_export, export); struct rgw_handle *handle = container_of(obj_hdl, struct rgw_handle, handle); LogFullDebug(COMPONENT_FSAL, "%s enter obj_hdl %p offset %" PRIx64 " length %zx", __func__, obj_hdl, (uint64_t)offset, length); rc = rgw_commit(export->rgw_fs, handle->rgw_fh, offset, length, RGW_FSYNC_FLAG_NONE); if (rc < 0) return rgw2fsal_error(rc); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Allocate a state_t structure * * Note that this is not expected to fail since memory allocation is * expected to abort on failure. * * @param[in] exp_hdl Export state_t will be associated with * @param[in] state_type Type of state to allocate * @param[in] related_state Related state if appropriate * * @returns a state structure. */ struct state_t *rgw_alloc_state(struct fsal_export *exp_hdl, enum state_type state_type, struct state_t *related_state) { return init_state(gsh_calloc(1, sizeof(struct rgw_open_state)), NULL, state_type, related_state); } /** * @brief Manage closing a file when a state is no longer needed. * * When the upper layers are ready to dispense with a state, this method is * called to allow the FSAL to close any file descriptors or release any other * resources associated with the state. A call to free_state should be assumed * to follow soon. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * * @return FSAL status. */ fsal_status_t rgw_fsal_close2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { int rc; struct rgw_open_state *open_state; fsal_status_t status; fsal_openflags_t *openflags; struct rgw_export *export = container_of(op_ctx->fsal_export, struct rgw_export, export); struct rgw_handle *handle = container_of(obj_hdl, struct rgw_handle, handle); LogFullDebug(COMPONENT_FSAL, "%s enter obj_hdl %p state %p", __func__, obj_hdl, state); PTHREAD_RWLOCK_wrlock(&obj_hdl->obj_lock); if (state) { open_state = (struct rgw_open_state *)state; LogFullDebug(COMPONENT_FSAL, "%s called w/open_state %p", __func__, open_state); if (state->state_type == STATE_TYPE_SHARE || state->state_type == STATE_TYPE_NLM_SHARE || state->state_type == STATE_TYPE_9P_FID) { /* This is a share state, we must update the share * counters. This can block over an I/O operation. */ update_share_counters(&handle->share, handle->openflags, FSAL_O_CLOSED); } openflags = &open_state->openflags; } else { openflags = &handle->openflags; } if (unlikely(*openflags == FSAL_O_CLOSED)) { status = fsalstat(ERR_FSAL_NOT_OPENED, 0); } else { rc = rgw_close(export->rgw_fs, handle->rgw_fh, RGW_CLOSE_FLAG_NONE); if (rc < 0) { status = rgw2fsal_error(rc); } else { *openflags = FSAL_O_CLOSED; status = fsalstat(ERR_FSAL_NO_ERROR, 0); } } PTHREAD_RWLOCK_unlock(&obj_hdl->obj_lock); return status; } /** * @brief Close the global FD for a file * * This function closes a file, freeing resources used for read/write * access and releasing capabilities. * * @param[in] handle_pub File to close * * @return FSAL status. */ static fsal_status_t rgw_fsal_close(struct fsal_obj_handle *handle_pub) { return rgw_fsal_close2(handle_pub, NULL); } /** * @brief Write wire handle * * This function writes a 'wire' handle to be sent to clients and * received from the. * * @param[in] obj_hdl Handle to digest * @param[in] output_type Type of digest requested * @param[in,out] fh_desc Location/size of buffer for * digest/Length modified to digest length * * @return FSAL status. */ static fsal_status_t handle_to_wire(const struct fsal_obj_handle *obj_hdl, uint32_t output_type, struct gsh_buffdesc *fh_desc) { /* The private 'full' object handle */ const struct rgw_handle *handle = container_of(obj_hdl, const struct rgw_handle, handle); switch (output_type) { /* Digested Handles */ case FSAL_DIGEST_NFSV3: case FSAL_DIGEST_NFSV4: if (fh_desc->len < sizeof(struct rgw_fh_hk)) { LogMajor( COMPONENT_FSAL, "RGW digest_handle: space too small for handle. Need %zu, have %zu", sizeof(handle->rgw_fh), fh_desc->len); return fsalstat(ERR_FSAL_TOOSMALL, 0); } else { memcpy(fh_desc->addr, &(handle->rgw_fh->fh_hk), sizeof(struct rgw_fh_hk)); fh_desc->len = sizeof(struct rgw_fh_hk); } break; default: return fsalstat(ERR_FSAL_SERVERFAULT, 0); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Give a hash key for file handle * * This function locates a unique hash key for a given file. * * @param[in] obj_hdl The file whose key is to be found * @param[out] fh_desc Address and length of key */ static void handle_to_key(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *fh_desc) { /* The private 'full' object handle */ struct rgw_handle *handle = container_of(obj_hdl, struct rgw_handle, handle); fh_desc->addr = &(handle->rgw_fh->fh_hk); fh_desc->len = sizeof(struct rgw_fh_hk); } #ifdef USE_FSAL_RGW_XATTRS static int getxattr_cb(rgw_xattrlist *attrs, void *arg, uint32_t flags) { xattrvalue4 *cb_arg = (xattrvalue4 *)arg; cb_arg->utf8string_val = gsh_strldup(attrs->xattrs->val.val, attrs->xattrs->val.len, &cb_arg->utf8string_len); cb_arg->utf8string_len = attrs->xattrs->val.len; return 0; } /** * @brief Get Extended Attribute * * The function fetches an extended attr of the object. * * @param[in] obj_hdl Input object to query * @param[in] xa_name Input xattr name * @param[out] xa_value Output xattr value */ static fsal_status_t getxattrs(struct fsal_obj_handle *obj_hdl, xattrvalue4 *xa_name, xattrvalue4 *xa_value) { int rc = 0; int errsv = 0; fsal_status_t status; struct rgw_export *export = container_of(op_ctx->fsal_export, struct rgw_export, export); struct rgw_handle *handle = container_of(obj_hdl, struct rgw_handle, handle); rgw_xattrstr xattr_k = { xa_name->utf8string_val, xa_name->utf8string_len }; rgw_xattrstr xattr_v = { NULL, 0 }; rgw_xattr xattrs[1] = { { xattr_k, xattr_v } }; rgw_xattrlist xattrlist = { xattrs, 1 }; rc = rgw_getxattrs(export->rgw_fs, handle->rgw_fh, &xattrlist, getxattr_cb, xa_value, RGW_GETXATTR_FLAG_NONE); if (rc < 0) { errsv = errno; LogDebug(COMPONENT_FSAL, "GETEXATTRS returned rc %d errsv %d", rc, errsv); if (errsv == ERANGE) { status = fsalstat(ERR_FSAL_TOOSMALL, 0); goto out; } if (errsv == ENODATA) { status = fsalstat(ERR_FSAL_NOENT, 0); goto out; } status = fsalstat(posix2fsal_error(errsv), errsv); goto out; } LogDebug(COMPONENT_FSAL, "GETXATTRS returned value %s length %d rc %d", xa_value->utf8string_val, xa_value->utf8string_len, rc); status = fsalstat(ERR_FSAL_NO_ERROR, 0); out: return status; } /** * @brief Set Extended Attribute * * The function sets an extended attr of the object. * * @param[in] obj_hdl The input object * @param[in] sa_type Input xattr type * @param[in] xa_name Input xattr name * @param[in] xa_value Input xattr value to be set */ static fsal_status_t setxattrs(struct fsal_obj_handle *obj_hdl, setxattr_option4 sa_type, xattrvalue4 *xa_name, xattrvalue4 *xa_value) { int rc = 0; int errsv = 0; fsal_status_t status = { 0, 0 }; struct rgw_export *export = container_of(op_ctx->fsal_export, struct rgw_export, export); struct rgw_handle *handle = container_of(obj_hdl, struct rgw_handle, handle); rgw_xattrstr xattr_k = { xa_name->utf8string_val, xa_name->utf8string_len }; rgw_xattrstr xattr_v = { xa_value->utf8string_val, xa_value->utf8string_len }; rgw_xattr xattr = { xattr_k, xattr_v }; rgw_xattrlist xattrlist = { &xattr, 1 }; rc = rgw_setxattrs(export->rgw_fs, handle->rgw_fh, &xattrlist, RGW_SETXATTR_FLAG_NONE); if (rc < 0) { errsv = errno; LogDebug(COMPONENT_FSAL, "SETXATTRS returned rc %d errsv %d", rc, errsv); status = fsalstat(posix2fsal_error(errsv), errsv); goto out; } status = fsalstat(ERR_FSAL_NO_ERROR, 0); out: return status; } /** * @brief Remove Extended Attribute * * This function removes an extended attribute of the object. * * @param[in] obj_hdl The input object * @param[in] xa_name Input xattr name to be removed * * @return FSAL status. */ static fsal_status_t removexattrs(struct fsal_obj_handle *obj_hdl, xattrvalue4 *xa_name) { int rc = 0; int errsv = 0; fsal_status_t status = { 0, 0 }; struct rgw_export *export = container_of(op_ctx->fsal_export, struct rgw_export, export); struct rgw_handle *handle = container_of(obj_hdl, struct rgw_handle, handle); rgw_xattrstr xattr_k = { xa_name->utf8string_val, xa_name->utf8string_len }; rgw_xattrstr xattr_v = { NULL, 0 }; rgw_xattr xattr = { xattr_k, xattr_v }; rgw_xattrlist xattrlist = { &xattr, 1 }; rc = rgw_rmxattrs(export->rgw_fs, handle->rgw_fh, &xattrlist, RGW_RMXATTR_FLAG_NONE); if (rc < 0) { errsv = errno; LogDebug(COMPONENT_FSAL, "REMOVEXATTRS returned rc %d errsv %d", rc, errsv); status = fsalstat(posix2fsal_error(errsv), errsv); goto out; } status = fsalstat(ERR_FSAL_NO_ERROR, 0); out: return status; } struct lxattr_cb_arg { count4 max; xattrlist4 *names; }; #define XATTR_USER_PREFIX "user." #define XATTR_USER_PREFIX_LEN (sizeof(XATTR_USER_PREFIX) - 1) static int lsxattr_cb(rgw_xattrlist *attrs, void *arg, uint32_t flag) { struct lxattr_cb_arg *cb_arg = (struct lxattr_cb_arg *)arg; for (uint32_t ix = 0; ix < attrs->xattr_cnt; ++ix) { rgw_xattr *xattr = &(attrs->xattrs[ix]); component4 *entry = &(cb_arg->names->xl4_entries[cb_arg->names->xl4_count]); /* defensive checks borrowed from Jeff Layton's helper */ if (xattr->key.len < XATTR_USER_PREFIX_LEN + 1) return 0; if (strncmp(xattr->key.val, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) return 0; entry->utf8string_val = gsh_strldup( xattr->key.val, xattr->key.len, &entry->utf8string_len); cb_arg->names->xl4_count++; if (cb_arg->names->xl4_count == cb_arg->max) { return RGW_LSXATTR_FLAG_STOP; } } return 0; } /** * @brief List Extended Attribute * * The function fetches the list of extended attribute of the object. * * @param[in] obj_hdl The input object * @param[in] la_maxcount Input max number of bytes for names * @param[in,out] la_cookie In/out cookie * @param[out] lr_eof Output eof set if no more xattrs * @param[out] lr_names Output list of xattr names this buffer * size is double the size of la_maxcount * to allow for component4 overhead * * @return FSAL status. */ static fsal_status_t listxattrs(struct fsal_obj_handle *obj_hdl, count4 la_maxcount, nfs_cookie4 *la_cookie, bool_t *lr_eof, xattrlist4 *lr_names) { int rc = 0; int errsv = 0; fsal_status_t status = { 0, 0 }; struct rgw_export *export = container_of(op_ctx->fsal_export, struct rgw_export, export); struct rgw_handle *handle = container_of(obj_hdl, struct rgw_handle, handle); struct lxattr_cb_arg cb_arg; /* XXX: ffilz: the doc in fsal_api strongly implies this buffer is pre-allocated, * but it's not */ lr_names->xl4_entries = gsh_calloc(la_maxcount, sizeof(xattrlist4)); lr_names->xl4_count = 0; /* defensive */ cb_arg.names = lr_names; cb_arg.max = la_maxcount; rc = rgw_lsxattrs(export->rgw_fs, handle->rgw_fh, NULL /* filter_prefix not yet implemented in RGW */, lsxattr_cb, &cb_arg, RGW_LSXATTR_FLAG_NONE); if (rc < 0) { errsv = errno; LogDebug(COMPONENT_FSAL, "LISTXATTRS returned rc %d errsv %d", rc, errsv); status = fsalstat(posix2fsal_error(errsv), errsv); goto out; } /* Setting lr_eof true unconditionally since cookie isn't implemented yet */ *la_cookie = cb_arg.names->xl4_count; *lr_eof = true; status = fsalstat(ERR_FSAL_NO_ERROR, 0); out: return status; } #endif /** * @brief Override functions in ops vector * * This function overrides implemented functions in the ops vector * with versions for this FSAL. * * @param[in] ops Handle operations vector */ void handle_ops_init(struct fsal_obj_ops *ops) { fsal_default_obj_ops_init(ops); ops->release = release; ops->merge = rgw_merge; ops->lookup = lookup; #ifdef USE_FSAL_RGW_XATTRS ops->getxattrs = getxattrs; ops->setxattrs = setxattrs; ops->removexattrs = removexattrs; ops->listxattrs = listxattrs; #endif ops->mkdir = rgw_fsal_mkdir; ops->readdir = rgw_fsal_readdir; ops->compute_readdir_cookie = rgw_fsal_compute_cookie; ops->dirent_cmp = rgw_fsal_dirent_cmp; ops->getattrs = getattrs; ops->rename = rgw_fsal_rename; ops->unlink = rgw_fsal_unlink; ops->close = rgw_fsal_close; ops->handle_to_wire = handle_to_wire; ops->handle_to_key = handle_to_key; ops->open2 = rgw_fsal_open2; ops->status2 = rgw_fsal_status2; ops->reopen2 = rgw_fsal_reopen2; ops->read2 = rgw_fsal_read2; ops->write2 = rgw_fsal_write2; ops->commit2 = rgw_fsal_commit2; ops->setattr2 = rgw_fsal_setattr2; ops->close2 = rgw_fsal_close2; } nfs-ganesha-6.5/src/FSAL/FSAL_RGW/internal.c000066400000000000000000000113761473756622300203450ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright © Red Hat, 2015 * Author: ORit Wasserman * * This program 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 3 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 * 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 * * ------------- */ /** * @file internal.c * @brief Internal definitions for the RGW FSAL * * This file includes internal function definitions, constants, and * variable declarations used to implement the RGW FSAL, but not * exposed as part of the API. */ #include #include "fsal_types.h" #include "fsal.h" #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #define RGW_INTERNAL_C #include "internal.h" /** * @brief FSAL status from RGW error * * This function returns a fsal_status_t with the FSAL error as the * major, and the posix error as minor. (RGW's error codes are just * negative signed versions of POSIX error codes.) * * @param[in] rgw_errorcode RGW error (negative Posix) * * @return FSAL status. */ fsal_status_t rgw2fsal_error(const int rgw_errorcode) { fsal_status_t status; status.minor = -rgw_errorcode; switch (-rgw_errorcode) { case 0: status.major = ERR_FSAL_NO_ERROR; break; case EPERM: status.major = ERR_FSAL_PERM; break; case ENOENT: status.major = ERR_FSAL_NOENT; break; case ECONNREFUSED: case ECONNABORTED: case ECONNRESET: case EIO: case ENFILE: case EMFILE: case EPIPE: status.major = ERR_FSAL_IO; break; case ENODEV: case ENXIO: status.major = ERR_FSAL_NXIO; break; case EBADF: /** * @todo: The EBADF error also happens when file is * opened for reading, and we try writing in * it. In this case, we return * ERR_FSAL_NOT_OPENED, but it doesn't seems to * be a correct error translation. */ status.major = ERR_FSAL_NOT_OPENED; break; case ENOMEM: status.major = ERR_FSAL_NOMEM; break; case EACCES: status.major = ERR_FSAL_ACCESS; break; case EFAULT: status.major = ERR_FSAL_FAULT; break; case EEXIST: status.major = ERR_FSAL_EXIST; break; case EXDEV: status.major = ERR_FSAL_XDEV; break; case ENOTDIR: status.major = ERR_FSAL_NOTDIR; break; case EISDIR: status.major = ERR_FSAL_ISDIR; break; case EINVAL: status.major = ERR_FSAL_INVAL; break; case EFBIG: status.major = ERR_FSAL_FBIG; break; case ENOSPC: status.major = ERR_FSAL_NOSPC; break; case EMLINK: status.major = ERR_FSAL_MLINK; break; case EDQUOT: status.major = ERR_FSAL_DQUOT; break; case ENAMETOOLONG: status.major = ERR_FSAL_NAMETOOLONG; break; case ENOTEMPTY: status.major = ERR_FSAL_NOTEMPTY; break; case ESTALE: status.major = ERR_FSAL_STALE; break; case EAGAIN: case EBUSY: status.major = ERR_FSAL_DELAY; break; default: status.major = ERR_FSAL_SERVERFAULT; break; } return status; } /** * @brief Construct a new filehandle * * This function constructs a new RGW FSAL object handle and attaches * it to the export. After this call the attributes have been filled * in and the handle is up-to-date and usable. * * @param[in] export The export on which the object lives * @param[in] rgw_fh Concise representation of the object name, * in RGW notation * @param[inout] st Object attributes * @param[out] obj Object created * * @return 0 on success, negative error codes on failure. */ int construct_handle(struct rgw_export *export, struct rgw_file_handle *rgw_fh, struct stat *st, struct rgw_handle **obj) { /* Pointer to the handle under construction */ struct rgw_handle *constructing = NULL; *obj = NULL; constructing = gsh_calloc(1, sizeof(struct rgw_handle)); constructing->rgw_fh = rgw_fh; constructing->up_ops = export->export.up_ops; /* XXXX going away */ fsal_obj_handle_init(&constructing->handle, &export->export, posix2fsal_type(st->st_mode), true); constructing->handle.obj_ops = &RGWFSM.handle_ops; constructing->handle.fsid = posix2fsal_fsid(st->st_dev); constructing->handle.fileid = st->st_ino; constructing->export = export; *obj = constructing; return 0; } void deconstruct_handle(struct rgw_handle *obj) { fsal_obj_handle_fini(&obj->handle, true); gsh_free(obj); } nfs-ganesha-6.5/src/FSAL/FSAL_RGW/internal.h000066400000000000000000000107201473756622300203420ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright © Red Hat 2015 * Author: Orit Wasserman * * This program 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 3 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 * 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 * * ------------- */ /** * @file internal.h * @brief Internal declarations for the RGW FSAL * * This file includes declarations of data types, functions, * variables, and constants for the RGW FSAL. */ #ifndef FSAL_RGW_INTERNAL_INTERNAL #define FSAL_RGW_INTERNAL_INTERNAL #include #include #include /* NAME_MAX */ #include "fsal.h" #include "fsal_types.h" #include "fsal_api.h" #include "fsal_convert.h" #include "sal_data.h" #include #include #if (!GSH_CHECK_VERSION(1, 2, 1, LIBRGW_FILE_VER_MAJOR, LIBRGW_FILE_VER_MINOR, \ LIBRGW_FILE_VER_EXTRA)) #error rados/rgw_file.h version unsupported (require >= 1.2.1) #endif /** * RGW Main (global) module object */ struct rgw_fsal_module { struct fsal_module fsal; struct fsal_obj_ops handle_ops; char *conf_path; char *name; char *cluster; char *init_args; librgw_t rgw; }; extern struct rgw_fsal_module RGWFSM; #define MAXUIDLEN 32 #define MAXKEYLEN 20 #define MAXSECRETLEN 40 /** * RGW internal export object */ struct rgw_export { struct fsal_export export; /*< The public export object */ struct rgw_fs *rgw_fs; /*< "Opaque" fs handle */ struct rgw_handle *root; /*< root handle */ char *rgw_name; char *rgw_user_id; char *rgw_access_key_id; char *rgw_secret_access_key; }; /** * The RGW FSAL internal handle */ struct rgw_handle { struct fsal_obj_handle handle; /*< The public handle */ struct rgw_file_handle *rgw_fh; /*< RGW-internal file handle */ /* XXXX remove ptr to up-ops--we can always follow export! */ const struct fsal_up_vector *up_ops; /*< Upcall operations */ struct rgw_export *export; /*< The first export this handle *< belongs to */ struct fsal_share share; fsal_openflags_t openflags; }; /** * RGW "file descriptor" */ struct rgw_open_state { struct state_t gsh_open; fsal_openflags_t openflags; }; /** * The attributes this FSAL can interpret or supply. * Currently FSAL_RGW uses posix2fsal_attributes, so we should indicate support * for at least those attributes. */ #ifdef USE_FSAL_RGW_XATTRS #define RGW_SUPPORTED_ATTRIBUTES ((ATTRS_POSIX | ATTR4_XATTR)) #else #define RGW_SUPPORTED_ATTRIBUTES (ATTRS_POSIX) #endif /** * The attributes this FSAL can set. */ #ifdef USE_FSAL_RGW_XATTRS #define RGW_SETTABLE_ATTRIBUTES \ ((const attrmask_t)(ATTR_MODE | ATTR_OWNER | ATTR_GROUP | ATTR_ATIME | \ ATTR_CTIME | ATTR_MTIME | ATTR_SIZE | \ ATTR_MTIME_SERVER | ATTR_ATIME_SERVER | \ ATTR4_XATTR)) #else #define RGW_SETTABLE_ATTRIBUTES \ ((const attrmask_t)(ATTR_MODE | ATTR_OWNER | ATTR_GROUP | ATTR_ATIME | \ ATTR_CTIME | ATTR_MTIME | ATTR_SIZE | \ ATTR_MTIME_SERVER | ATTR_ATIME_SERVER)) #endif /** * Linux supports a stripe pattern with no more than 4096 stripes, but * for now we stick to 1024 to keep them da_addrs from being too * gigantic. */ static const size_t BIGGEST_PATTERN = 1024; /* Prototypes */ int construct_handle(struct rgw_export *export, struct rgw_file_handle *rgw_file_handle, struct stat *st, struct rgw_handle **obj); void deconstruct_handle(struct rgw_handle *obj); fsal_status_t rgw2fsal_error(const int errorcode); void export_ops_init(struct export_ops *ops); void handle_ops_init(struct fsal_obj_ops *ops); struct state_t *rgw_alloc_state(struct fsal_export *exp_hdl, enum state_type state_type, struct state_t *related_state); void rgw_fs_invalidate(void *handle, struct rgw_fh_hk fh_hk); #endif /* !FSAL_RGW_INTERNAL_INTERNAL */ nfs-ganesha-6.5/src/FSAL/FSAL_RGW/main.c000066400000000000000000000263161473756622300174550ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Red Hat, 2015 * Author: Orit Wasserman * * This program 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 3 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 * 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 * * ------------- */ /* main.c * Module core functions */ #include #include #include "gsh_list.h" #include "fsal.h" #include "fsal_types.h" #include "FSAL/fsal_init.h" #include "FSAL/fsal_commonlib.h" #include "fsal_api.h" #include "internal.h" #include "abstract_mem.h" #include "nfs_exports.h" #include "export_mgr.h" static const char *module_name = "RGW"; /** * RGW global module object. */ struct rgw_fsal_module RGWFSM = { .fsal = { .fs_info = { .maxfilesize = INT64_MAX, .maxlink = _POSIX_LINK_MAX, .maxnamelen = 1024, .maxpathlen = 1024, .no_trunc = true, .chown_restricted = false, .case_insensitive = false, .case_preserving = true, .link_support = false, .symlink_support = false, .lock_support = false, .lock_support_async_block = false, .named_attr = true, /* XXX */ .unique_handles = true, .acl_support = 0, .cansettime = true, .homogenous = true, .supported_attrs = RGW_SUPPORTED_ATTRIBUTES, .maxread = FSAL_MAXIOSIZE, .maxwrite = FSAL_MAXIOSIZE, .umask = 0, .rename_changes_key = true, .compute_readdir_cookie = true, .whence_is_name = true, .expire_time_parent = -1, #ifdef USE_FSAL_RGW_XATTRS .xattr_support = true, #endif } } }; static struct config_item rgw_items[] = { CONF_ITEM_PATH("ceph_conf", 1, MAXPATHLEN, NULL, rgw_fsal_module, conf_path), CONF_ITEM_STR("name", 1, MAXPATHLEN, NULL, rgw_fsal_module, name), CONF_ITEM_STR("cluster", 1, MAXPATHLEN, NULL, rgw_fsal_module, cluster), CONF_ITEM_STR("init_args", 1, MAXPATHLEN, NULL, rgw_fsal_module, init_args), CONF_ITEM_MODE("umask", 0, rgw_fsal_module, fsal.fs_info.umask), CONFIG_EOL }; struct config_block rgw_block = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.rgw", .blk_desc.name = "RGW", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = rgw_items, .blk_desc.u.blk.commit = noop_conf_commit }; static pthread_mutex_t init_mtx; /* Module methods */ /* init_config * must be called with a reference taken (via lookup_fsal) */ static fsal_status_t init_config(struct fsal_module *module_in, config_file_t config_struct, struct config_error_type *err_type) { struct rgw_fsal_module *myself = container_of(module_in, struct rgw_fsal_module, fsal); LogDebug(COMPONENT_FSAL, "RGW module setup."); (void)load_config_from_parse(config_struct, &rgw_block, myself, true, err_type); if (!config_error_is_harmless(err_type)) return fsalstat(ERR_FSAL_INVAL, 0); display_fsinfo(&myself->fsal); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Create a new export under this FSAL * * This function creates a new export object for the RGW FSAL. * * @todo ACE: We do not handle re-exports of the same cluster in a * sane way. Currently we create multiple handles and cache objects * pointing to the same one. This is not necessarily wrong, but it is * inefficient. It may also not be something we expect to use enough * to care about. * * @param[in] module_in The supplied module handle * @param[in] path The path to export * @param[in] options Export specific options for the FSAL * @param[in,out] list_entry Our entry in the export list * @param[in] next_fsal Next stacked FSAL * @param[out] pub_export Newly created FSAL export object * * @return FSAL status. */ static struct config_item export_params[] = { CONF_ITEM_NOOP("name"), CONF_MAND_STR("user_id", 0, MAXUIDLEN, NULL, rgw_export, rgw_user_id), CONF_MAND_STR("access_key_id", 0, MAXKEYLEN, NULL, rgw_export, rgw_access_key_id), CONF_MAND_STR("secret_access_key", 0, MAXSECRETLEN, NULL, rgw_export, rgw_secret_access_key), CONFIG_EOL }; static struct config_block export_param_block = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.rgw-export%d", .blk_desc.name = "FSAL", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = export_params, .blk_desc.u.blk.commit = noop_conf_commit }; static fsal_status_t create_export(struct fsal_module *module_in, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops) { /* The status code to return */ fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; /* The internal export object */ struct rgw_export *export = NULL; /* The 'private' root handle */ struct rgw_handle *handle = NULL; /* Stat for root */ struct stat st; /* Return code */ int rc = 0; /* Return code from RGW calls */ int rgw_status; /* True if we have called fsal_export_init */ bool initialized = false; /* once */ if (!RGWFSM.rgw) { PTHREAD_MUTEX_lock(&init_mtx); if (!RGWFSM.rgw) { char *conf_path = NULL; char *inst_name = NULL; char *cluster = NULL; int argc = 1; char *argv[5] = { "nfs-ganesha", NULL, NULL, NULL, NULL }; int clen; if (RGWFSM.conf_path) { if (access(RGWFSM.conf_path, F_OK)) { LogCrit(COMPONENT_FSAL, "ceph.conf path does not exist"); } clen = strlen(RGWFSM.conf_path) + 8; conf_path = (char *)gsh_malloc(clen); (void)sprintf(conf_path, "--conf=%s", RGWFSM.conf_path); argv[argc] = conf_path; ++argc; } if (RGWFSM.name) { clen = strlen(RGWFSM.name) + 8; inst_name = (char *)gsh_malloc(clen); (void)sprintf(inst_name, "--name=%s", RGWFSM.name); argv[argc] = inst_name; ++argc; } if (RGWFSM.cluster) { clen = strlen(RGWFSM.cluster) + 8; cluster = (char *)gsh_malloc(clen); (void)sprintf(cluster, "--cluster=%s", RGWFSM.cluster); argv[argc] = cluster; ++argc; } if (RGWFSM.init_args) { argv[argc] = RGWFSM.init_args; ++argc; } rc = librgw_create(&RGWFSM.rgw, argc, argv); if (rc != 0) { LogCrit(COMPONENT_FSAL, "RGW module: librgw init failed (%d)", rc); } gsh_free(conf_path); gsh_free(inst_name); gsh_free(cluster); } PTHREAD_MUTEX_unlock(&init_mtx); } if (rc != 0) { status.major = ERR_FSAL_BAD_INIT; goto error; } export = gsh_calloc(1, sizeof(struct rgw_export)); fsal_export_init(&export->export); export_ops_init(&export->export.exp_ops); /* get params for this export, if any */ if (parse_node) { rc = load_config_from_node(parse_node, &export_param_block, export, true, err_type); if (rc != 0) { gsh_free(export); return fsalstat(ERR_FSAL_INVAL, 0); } } initialized = true; #ifndef USE_FSAL_RGW_MOUNT2 rgw_status = rgw_mount(RGWFSM.rgw, export->rgw_user_id, export->rgw_access_key_id, export->rgw_secret_access_key, &export->rgw_fs, RGW_MOUNT_FLAG_NONE); #else const char *rgw_fullpath = CTX_FULLPATH(op_ctx); if (strcmp(rgw_fullpath, "/") && strchr(rgw_fullpath, '/') && (strchr(rgw_fullpath, '/') - rgw_fullpath) > 1) { /* case : "bucket_name/dir" */ rgw_status = rgw_mount(RGWFSM.rgw, export->rgw_user_id, export->rgw_access_key_id, export->rgw_secret_access_key, &export->rgw_fs, RGW_MOUNT_FLAG_NONE); } else { /* case : "/" of "bucket_name" or "/bucket_name" */ if (strcmp(rgw_fullpath, "/") && strchr(rgw_fullpath, '/') && (strchr(rgw_fullpath, '/') - rgw_fullpath) == 0) { rgw_fullpath = rgw_fullpath + 1; } rgw_status = rgw_mount2(RGWFSM.rgw, export->rgw_user_id, export->rgw_access_key_id, export->rgw_secret_access_key, rgw_fullpath, &export->rgw_fs, RGW_MOUNT_FLAG_NONE); } #endif if (rgw_status != 0) { status.major = ERR_FSAL_SERVERFAULT; LogCrit(COMPONENT_FSAL, "Unable to mount RGW cluster for %s.", CTX_FULLPATH(op_ctx)); if (rgw_status == -EINVAL) { LogCrit(COMPONENT_FSAL, "Authorization Failed for user %s ", export->rgw_user_id); } goto error; } if (fsal_attach_export(module_in, &export->export.exports) != 0) { status.major = ERR_FSAL_SERVERFAULT; LogCrit(COMPONENT_FSAL, "Unable to attach export for %s.", CTX_FULLPATH(op_ctx)); goto error; } if (rgw_register_invalidate(export->rgw_fs, rgw_fs_invalidate, up_ops->up_fsal_export, RGW_REG_INVALIDATE_FLAG_NONE) != 0) { LogCrit(COMPONENT_FSAL, "Unable to register invalidates for %s.", CTX_FULLPATH(op_ctx)); goto error; } export->export.fsal = module_in; LogDebug(COMPONENT_FSAL, "RGW module export %s.", CTX_FULLPATH(op_ctx)); rc = rgw_getattr(export->rgw_fs, export->rgw_fs->root_fh, &st, RGW_GETATTR_FLAG_NONE); if (rc < 0) return rgw2fsal_error(rc); rc = construct_handle(export, export->rgw_fs->root_fh, &st, &handle); if (rc < 0) { status = rgw2fsal_error(rc); goto error; } op_ctx->fsal_export = &export->export; export->root = handle; export->export.up_ops = up_ops; return status; error: gsh_free(export); if (initialized) initialized = false; return status; } /** * @brief Initialize and register the FSAL * * This function initializes the FSAL module handle, being called * before any configuration or even mounting of a RGW cluster. It * exists solely to produce a properly constructed FSAL module * handle. */ MODULE_INIT void init(void) { struct fsal_module *myself = &RGWFSM.fsal; PTHREAD_MUTEX_init(&init_mtx, NULL); LogDebug(COMPONENT_FSAL, "RGW module registering."); if (register_fsal(myself, module_name, FSAL_MAJOR_VERSION, FSAL_MINOR_VERSION, FSAL_ID_RGW) != 0) { /* The register_fsal function prints its own log message if it fails */ LogCrit(COMPONENT_FSAL, "RGW module failed to register."); } /* Set up module operations */ myself->m_ops.create_export = create_export; myself->m_ops.init_config = init_config; /* Initialize the fsal_obj_handle ops for FSAL RGW */ handle_ops_init(&RGWFSM.handle_ops); } /** * @brief Release FSAL resources * * This function unregisters the FSAL and frees its module handle. The * FSAL also has an open instance of the rgw library, so we also need to * release that. */ MODULE_FINI void finish(void) { int ret; LogDebug(COMPONENT_FSAL, "RGW module finishing."); ret = unregister_fsal(&RGWFSM.fsal); if (ret != 0) { LogCrit(COMPONENT_FSAL, "RGW: unregister_fsal failed (%d)", ret); } /* release the library */ if (RGWFSM.rgw) { librgw_shutdown(RGWFSM.rgw); } PTHREAD_MUTEX_destroy(&init_mtx); } nfs-ganesha-6.5/src/FSAL/FSAL_RGW/up.c000066400000000000000000000051311473756622300171450ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright © 2017, Red Hat, Inc. * Author: Matt Benjamin * * This program 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 3 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 * 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 * * ------------- */ /** * @file FSAL_RGW/up.c * @author Matt Benjamin * @date Fri Jan 19 18:07:01 2017 * * @brief Upcalls * * Use new generic invalidate hook to drive upcalls. */ #include #include "fsal.h" #include "fsal_types.h" #include "fsal_convert.h" #include "fsal_api.h" #include "internal.h" #include "nfs_exports.h" #include "FSAL/fsal_commonlib.h" /** * @brief Invalidate an inode (dispatch upcall) * * This function terminates an invalidate upcall from librgw. Since * upcalls are asynchronous, no upcall thread is required. * * @param[in] cmount The mount context * @param[in] fh_hk The object being invalidated * @param[in] arg Opaque argument, currently a pointer to export * * @return FSAL status codes. */ void rgw_fs_invalidate(void *handle, struct rgw_fh_hk fh_hk) { struct rgw_export *export = (struct rgw_export *)handle; const struct fsal_up_vector *up_ops; LogFullDebug(COMPONENT_FSAL_UP, "%s: invalidate on fh_hk %" PRIu64 ":%" PRIu64 "\n", __func__, fh_hk.bucket, fh_hk.object); if (!export) { LogMajor(COMPONENT_FSAL_UP, "up/invalidate: called w/nil export"); return; } up_ops = export->export.up_ops; if (!up_ops) { LogMajor(COMPONENT_FSAL_UP, "up/invalidate: nil FSAL_UP ops vector"); return; } fsal_status_t status; struct gsh_buffdesc fh_desc; fh_desc.addr = &fh_hk; fh_desc.len = sizeof(struct rgw_fh_hk); /* invalidate me, my man */ status = up_ops->invalidate(up_ops, &fh_desc, FSAL_UP_INVALIDATE_CACHE); if (FSAL_IS_ERROR(status)) { LogMajor(COMPONENT_FSAL_UP, "up/invalidate: error invalidating fh_hk %" PRIu64 ":%" PRIu64 "\n", fh_hk.bucket, fh_hk.object); } } nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/000077500000000000000000000000001473756622300170165ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/CMakeLists.txt000066400000000000000000000033361473756622300215630ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # 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 3 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 library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # #------------------------------------------------------------------------------- add_definitions(-D__USE_MISC) set( LIB_PREFIX 64) SET(fsalsaunafs_LIB_SRCS context_wrap.c context_wrap.h ds.c export.c fileinfo_cache.c fileinfo_cache.h handle.c main.c mds_export.c mds_handle.c saunafs_acl.c saunafs_fsal_types.h saunafs_internal.c saunafs_internal.h ) add_library(fsalsaunafs MODULE ${fsalsaunafs_LIB_SRCS}) add_sanitizers(fsalsaunafs) if (USE_LTTNG) add_dependencies(fsalsaunafs gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) target_link_libraries(fsalsaunafs ${SAUNAFS_CLIENT_LIB}) set_target_properties(fsalsaunafs PROPERTIES VERSION 4.0.0 SOVERSION 4) install(TARGETS fsalsaunafs COMPONENT fsal DESTINATION ${FSAL_DESTINATION}) nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/context_wrap.c000066400000000000000000000241741473756622300217070ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* Copyright 2017 Skytechnology sp. z o.o. Copyright 2023 Leil Storage OÜ This file is part of SaunaFS. SaunaFS 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, version 3. SaunaFS 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 SaunaFS. If not, see . */ #include "context_wrap.h" #include "saunafs_internal.h" int saunafs_lookup(sau_t *instance, struct user_cred *cred, sau_inode_t parent, const char *path, struct sau_entry *entry) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_lookup(instance, context, parent, path, entry); } int saunafs_mknode(sau_t *instance, struct user_cred *cred, sau_inode_t parent, const char *path, mode_t mode, dev_t rdev, struct sau_entry *entry) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_mknod(instance, context, parent, path, mode, rdev, entry); } fileinfo_t *saunafs_open(sau_t *instance, struct user_cred *cred, sau_inode_t inode, int flags) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return NULL; return sau_open(instance, context, inode, flags); } ssize_t saunafs_read(sau_t *instance, struct user_cred *cred, fileinfo_t *fileinfo, off_t offset, size_t size, char *buffer) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_read(instance, context, fileinfo, offset, size, buffer); } ssize_t saunafs_write(sau_t *instance, struct user_cred *cred, fileinfo_t *fileinfo, off_t offset, size_t size, const char *buffer) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_write(instance, context, fileinfo, offset, size, buffer); } int saunafs_flush(sau_t *instance, struct user_cred *cred, fileinfo_t *fileinfo) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_flush(instance, context, fileinfo); } int saunafs_getattr(sau_t *instance, struct user_cred *cred, sau_inode_t inode, struct sau_attr_reply *reply) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_getattr(instance, context, inode, reply); } fileinfo_t *saunafs_opendir(sau_t *instance, struct user_cred *cred, sau_inode_t inode) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return NULL; return sau_opendir(instance, context, inode); } int saunafs_readdir(sau_t *instance, struct user_cred *cred, struct sau_fileinfo *fileinfo, off_t offset, size_t max_entries, struct sau_direntry *buf, size_t *num_entries) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_readdir(instance, context, fileinfo, offset, max_entries, buf, num_entries); } int saunafs_mkdir(sau_t *instance, struct user_cred *cred, sau_inode_t parent, const char *name, mode_t mode, struct sau_entry *out_entry) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_mkdir(instance, context, parent, name, mode, out_entry); } int saunafs_rmdir(sau_t *instance, struct user_cred *cred, sau_inode_t parent, const char *name) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_rmdir(instance, context, parent, name); } int saunafs_unlink(sau_t *instance, struct user_cred *cred, sau_inode_t parent, const char *name) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_unlink(instance, context, parent, name); } int saunafs_setattr(sau_t *instance, struct user_cred *cred, sau_inode_t inode, struct stat *stbuf, int to_set, struct sau_attr_reply *reply) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_setattr(instance, context, inode, stbuf, to_set, reply); } int saunafs_fsync(sau_t *instance, struct user_cred *cred, struct sau_fileinfo *fileinfo) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_fsync(instance, context, fileinfo); } int saunafs_rename(sau_t *instance, struct user_cred *cred, sau_inode_t parent, const char *name, sau_inode_t new_parent, const char *new_name) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_rename(instance, context, parent, name, new_parent, new_name); } int saunafs_symlink(sau_t *instance, struct user_cred *cred, const char *link, sau_inode_t parent, const char *name, struct sau_entry *entry) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_symlink(instance, context, link, parent, name, entry); } int saunafs_readlink(sau_t *instance, struct user_cred *cred, sau_inode_t inode, char *buf, size_t size) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_readlink(instance, context, inode, buf, size); } int saunafs_link(sau_t *instance, struct user_cred *cred, sau_inode_t inode, sau_inode_t parent, const char *name, struct sau_entry *entry) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_link(instance, context, inode, parent, name, entry); } int saunafs_get_chunks_info(sau_t *instance, struct user_cred *cred, sau_inode_t inode, uint32_t chunk_index, sau_chunk_info_t *buff, uint32_t buffer_size, uint32_t *reply_size) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_get_chunks_info(instance, context, inode, chunk_index, buff, buffer_size, reply_size); } int saunafs_setacl(sau_t *instance, struct user_cred *cred, sau_inode_t inode, sau_acl_t *acl) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_setacl(instance, context, inode, acl); } int saunafs_getacl(sau_t *instance, struct user_cred *cred, sau_inode_t inode, sau_acl_t **acl) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_getacl(instance, context, inode, acl); } int saunafs_setlock(sau_t *instance, struct user_cred *cred, fileinfo_t *fileinfo, const sau_lock_info_t *lock) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_setlk(instance, context, fileinfo, lock, NULL, NULL); } int saunafs_getlock(sau_t *instance, struct user_cred *cred, fileinfo_t *fileinfo, sau_lock_info_t *lock) { sau_context_t *context = createContext(instance, cred); if (context == NULL) return -1; return sau_getlk(instance, context, fileinfo, lock); } int saunafs_getxattr(sau_t *instance, struct user_cred *cred, sau_inode_t ino, const char *name, size_t size, size_t *out_size, uint8_t *buf) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_getxattr(instance, context, ino, name, size, out_size, buf); } int saunafs_setxattr(sau_t *instance, struct user_cred *cred, sau_inode_t ino, const char *name, const uint8_t *value, size_t size, int flags) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_setxattr(instance, context, ino, name, value, size, flags); } int saunafs_listxattr(sau_t *instance, struct user_cred *cred, sau_inode_t ino, size_t size, size_t *out_size, char *buf) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_listxattr(instance, context, ino, size, out_size, buf); } int saunafs_removexattr(sau_t *instance, struct user_cred *cred, sau_inode_t ino, const char *name) { sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(instance, cred); if (context == NULL) return -1; return sau_removexattr(instance, context, ino, name); } nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/context_wrap.h000066400000000000000000000103761473756622300217130ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* Copyright 2017 Skytechnology sp. z o.o. Copyright 2023 Leil Storage OÜ This file is part of SaunaFS. SaunaFS 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, version 3. SaunaFS 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 SaunaFS. If not, see . */ #pragma once #include #include "saunafs_fsal_types.h" int saunafs_lookup(sau_t *instance, struct user_cred *cred, sau_inode_t parent, const char *path, struct sau_entry *entry); int saunafs_mknode(sau_t *instance, struct user_cred *cred, sau_inode_t parent, const char *path, mode_t mode, dev_t rdev, struct sau_entry *entry); fileinfo_t *saunafs_open(sau_t *instance, struct user_cred *cred, sau_inode_t inode, int flags); ssize_t saunafs_read(sau_t *instance, struct user_cred *cred, fileinfo_t *fileinfo, off_t offset, size_t size, char *buffer); ssize_t saunafs_write(sau_t *instance, struct user_cred *cred, fileinfo_t *fileinfo, off_t offset, size_t size, const char *buffer); int saunafs_flush(sau_t *instance, struct user_cred *cred, fileinfo_t *fileinfo); int saunafs_getattr(sau_t *instance, struct user_cred *cred, sau_inode_t inode, struct sau_attr_reply *reply); fileinfo_t *saunafs_opendir(sau_t *instance, struct user_cred *cred, sau_inode_t inode); int saunafs_readdir(sau_t *instance, struct user_cred *cred, struct sau_fileinfo *fileinfo, off_t offset, size_t max_entries, struct sau_direntry *buf, size_t *num_entries); int saunafs_mkdir(sau_t *instance, struct user_cred *cred, sau_inode_t parent, const char *name, mode_t mode, struct sau_entry *out_entry); int saunafs_rmdir(sau_t *instance, struct user_cred *cred, sau_inode_t parent, const char *name); int saunafs_unlink(sau_t *instance, struct user_cred *cred, sau_inode_t parent, const char *name); int saunafs_setattr(sau_t *instance, struct user_cred *cred, sau_inode_t inode, struct stat *stbuf, int to_set, struct sau_attr_reply *reply); int saunafs_fsync(sau_t *instance, struct user_cred *cred, struct sau_fileinfo *fileinfo); int saunafs_rename(sau_t *instance, struct user_cred *cred, sau_inode_t parent, const char *name, sau_inode_t new_parent, const char *new_name); int saunafs_symlink(sau_t *instance, struct user_cred *cred, const char *link, sau_inode_t parent, const char *name, struct sau_entry *entry); int saunafs_readlink(sau_t *instance, struct user_cred *cred, sau_inode_t inode, char *buf, size_t size); int saunafs_link(sau_t *instance, struct user_cred *cred, sau_inode_t inode, sau_inode_t parent, const char *name, struct sau_entry *entry); int saunafs_get_chunks_info(sau_t *instance, struct user_cred *cred, sau_inode_t inode, uint32_t chunk_index, sau_chunk_info_t *buff, uint32_t buffer_size, uint32_t *reply_size); int saunafs_setacl(sau_t *instance, struct user_cred *cred, sau_inode_t inode, sau_acl_t *acl); int saunafs_getacl(sau_t *instance, struct user_cred *cred, sau_inode_t inode, sau_acl_t **acl); int saunafs_setlock(sau_t *instance, struct user_cred *cred, fileinfo_t *fileinfo, const sau_lock_info_t *lock); int saunafs_getlock(sau_t *instance, struct user_cred *cred, fileinfo_t *fileinfo, sau_lock_info_t *lock); int saunafs_getxattr(sau_t *instance, struct user_cred *cred, sau_inode_t ino, const char *name, size_t size, size_t *out_size, uint8_t *buf); int saunafs_setxattr(sau_t *instance, struct user_cred *cred, sau_inode_t ino, const char *name, const uint8_t *value, size_t size, int flags); int saunafs_listxattr(sau_t *instance, struct user_cred *cred, sau_inode_t ino, size_t size, size_t *out_size, char *buf); int saunafs_removexattr(sau_t *instance, struct user_cred *cred, sau_inode_t ino, const char *name); nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/ds.c000066400000000000000000000325521473756622300175770ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* Copyright 2017 Skytechnology sp. z o.o. Copyright 2023 Leil Storage OÜ This file is part of SaunaFS. SaunaFS 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, version 3. SaunaFS 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 SaunaFS. If not, see . */ #include "fsal.h" #include "fsal_api.h" #include "fsal_types.h" #include "../FSAL/fsal_private.h" #include "nfs_exports.h" #include "context_wrap.h" #include "fileinfo_cache.h" #include "saunafs_fsal_types.h" #include "saunafs_internal.h" /** * @brief Remove count expired instances from cache. * * @param[in] export export that store the cache * @param[in] count number of instances to be removed */ static void clearFileInfoCache(struct SaunaFSExport *export, int count) { if (export == NULL) return; for (int i = 0; i < count; ++i) { FileInfoEntry_t *cacheHandle = NULL; cacheHandle = popExpiredFileInfoCache(export->cache); if (cacheHandle == NULL) break; fileinfo_t *fileHandle = extractFileInfo(cacheHandle); sau_release(export->fsInstance, fileHandle); fileInfoEntryFree(cacheHandle); } } /** * @brief Clean up a DS handle. * * DS handle Lifecycle management. * This function cleans up private resources associated with a filehandle * and deallocates it. Implement this method or you will leak. This * function should not be called directly. * * @param[in] dataServerHandle Handle to release */ static void dsh_release(struct fsal_ds_handle *const dataServerHandle) { struct SaunaFSExport *export; struct DataServerHandle *dataServer; export = container_of(op_ctx->ctx_pnfs_ds->mds_fsal_export, struct SaunaFSExport, export); dataServer = container_of(dataServerHandle, struct DataServerHandle, handle); assert(export->cache); if (dataServer->cacheHandle != NULL) releaseFileInfoCache(export->cache, dataServer->cacheHandle); gsh_free(dataServer); clearFileInfoCache(export, 5); } /** * @brief Open a file from DataServerHandle. * * Auxiliar function to open files in the syscalls related with Data * Server. * * @param[in] export Handle to release * @param[in] dataServer Data Server handle * * @returns: nfsstat4 status returned after opening the file */ static nfsstat4 openfile(struct SaunaFSExport *export, struct DataServerHandle *dataServer) { if (export == NULL) return NFS4ERR_IO; if (dataServer->cacheHandle != NULL) return NFS4_OK; clearFileInfoCache(export, 2); struct FileInfoEntry *entry = NULL; entry = acquireFileInfoCache(export->cache, dataServer->inode); dataServer->cacheHandle = entry; if (dataServer->cacheHandle == NULL) return NFS4ERR_IO; fileinfo_t *fileHandle = extractFileInfo(dataServer->cacheHandle); if (fileHandle != NULL) return NFS4_OK; fileHandle = saunafs_open(export->fsInstance, NULL, dataServer->inode, O_RDWR); if (fileHandle == NULL) { eraseFileInfoCache(export->cache, dataServer->cacheHandle); dataServer->cacheHandle = NULL; return NFS4ERR_IO; } attachFileInfo(dataServer->cacheHandle, fileHandle); return NFS4_OK; } /** * @brief Read from a data-server handle. * * DS handle I/O Functions * * NFSv4.1 data server handles are disjount from normal filehandles (in * Ganesha, there is a ds_flag in the filehandle_v4_t structure) and do not * get loaded into mdcache or processed the normal way. * * @param[in] dataServerHandle Handle to release * @param[in] stateid The stateid supplied with the READ * operation, for validation * @param[in] offset The offset at which to read * @param[in] requestedLength Length of read requested (and size of * buffer) * @param[out] buffer The buffer to which to store read data * @param[out] suppliedLength Length of data read * @param[out] eof true on end of file * * @returns: An NFSv4.1 status code. */ static nfsstat4 dsh_read(struct fsal_ds_handle *const dataServerHandle, const stateid4 *stateid, const offset4 offset, const count4 requestedLength, void *const buffer, count4 *const suppliedLength, bool *const eof) { (void)stateid; struct SaunaFSExport *export = NULL; struct DataServerHandle *dataServer = NULL; fileinfo_t *fileHandle = NULL; export = container_of(op_ctx->ctx_pnfs_ds->mds_fsal_export, struct SaunaFSExport, export); dataServer = container_of(dataServerHandle, struct DataServerHandle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIu32 " offset=%" PRIu64 " size=%" PRIu32, export->export.export_id, dataServer->inode, offset, requestedLength); nfsstat4 status = openfile(export, dataServer); if (status != NFS4_OK) return status; fileHandle = extractFileInfo(dataServer->cacheHandle); ssize_t bytes = saunafs_read(export->fsInstance, NULL, fileHandle, offset, requestedLength, buffer); if (bytes < 0) return nfs4LastError(); *suppliedLength = bytes; *eof = (bytes == 0); return NFS4_OK; } /** * @brief Write to a data-server handle. * * NFSv4.1 data server filehandles are disjount from normal filehandles (in * Ganesha, there is a ds_flag in the filehandle_v4_t structure) and do not * get loaded into mdcache or processed the normal way. * * @param[in] dataServerHandle FSAL DS handle * @param[in] stateid The stateid supplied with the READ * operation, for validation * @param[in] offset The offset at which to read * @param[in] writeLength Length of write requested (and size of * buffer) * @param[out] buffer The buffer to which to store read data * @param[in] stability wanted Stability of write * @param[out] writtenLength Length of data written * @param[out] writeVerifier Write verifier * @param[out] stabilityGot Stability used for write (must be as or * more stable than request) * * @returns: An NFSv4.1 status code. */ static nfsstat4 dsh_write(struct fsal_ds_handle *const dataServerHandle, const stateid4 *stateid, const offset4 offset, const count4 writeLength, const void *buffer, const stable_how4 stability, count4 *const writtenLength, verifier4 *const writeVerifier, stable_how4 *const stabilityGot) { (void)stateid; (void)writeVerifier; struct SaunaFSExport *export = NULL; struct DataServerHandle *dataServer = NULL; fileinfo_t *fileHandle = NULL; int status = 0; export = container_of(op_ctx->ctx_pnfs_ds->mds_fsal_export, struct SaunaFSExport, export); dataServer = container_of(dataServerHandle, struct DataServerHandle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIu32 " offset=%" PRIu64 " size=%" PRIu32, export->export.export_id, dataServer->inode, offset, writeLength); nfsstat4 nfsStatus = openfile(export, dataServer); if (nfsStatus != NFS4_OK) return nfsStatus; fileHandle = extractFileInfo(dataServer->cacheHandle); ssize_t bytes = saunafs_write(export->fsInstance, NULL, fileHandle, offset, writeLength, buffer); if (bytes < 0) return nfs4LastError(); if (stability != UNSTABLE4) status = saunafs_flush(export->fsInstance, NULL, fileHandle); *writtenLength = bytes; *stabilityGot = (status < 0) ? UNSTABLE4 : stability; return NFS4_OK; } /** * @brief Commit a byte range to a DS handle. * * NFSv4.1 data server filehandles are disjount from normal filehandles (in * Ganesha, there is a ds_flag in the filehandle_v4_t structure) and do not * get loaded into mdcache or processed the normal way. * * @param[in] dataServerHandle FSAL DS handle * @param[in] offset Start of commit window * @param[in] count Length of commit window * @param[out] writeVerifier Write verifier * * @returns: An NFSv4.1 status code. */ static nfsstat4 dsh_commit(struct fsal_ds_handle *const dataServerHandle, const offset4 offset, const count4 count, verifier4 *const writeVerifier) { struct SaunaFSExport *export = NULL; struct DataServerHandle *dataServer = NULL; fileinfo_t *fileHandle = NULL; memset(writeVerifier, 0, NFS4_VERIFIER_SIZE); export = container_of(op_ctx->ctx_pnfs_ds->mds_fsal_export, struct SaunaFSExport, export); dataServer = container_of(dataServerHandle, struct DataServerHandle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIu32 " offset=%" PRIu64 " size=%" PRIu32, export->export.export_id, dataServer->inode, offset, count); nfsstat4 nfsStatus = openfile(export, dataServer); if (nfsStatus != NFS4_OK) { /* If we failed here then there is no opened SaunaFS file * descriptor, which implies that we don't need to flush * anything */ return NFS4_OK; } fileHandle = extractFileInfo(dataServer->cacheHandle); int status = saunafs_flush(export->fsInstance, NULL, fileHandle); if (status < 0) { LogMajor(COMPONENT_PNFS, "ds_commit() failed '%s'", sau_error_string(sau_last_err())); return NFS4ERR_INVAL; } return NFS4_OK; } /** * @brief Read plus from a data-server handle. * * NFSv4.2 data server handles are disjount from normal filehandles (in * Ganesha, there is a ds_flag in the filehandle_v4_t structure) and do not * get loaded into mdcache or processed the normal way. * * @param[in] dataServerHandle FSAL DS handle * @param[in] stateid Stateid supplied with the READ * operation, for validation * @param[in] offset The offset at which to read * @param[in] requestedLength Length of read requested (and size of * buffer) * @param[out] buffer Buffer to which to store read data * @param[out] suppliedLength Length of data read * @param[out] eof true on end of file * @param[out] info IO info * * @returns: An NFSv4.2 status code. */ static nfsstat4 dsh_read_plus(struct fsal_ds_handle *const dataServerHandle, const stateid4 *stateid, const offset4 offset, const count4 requestedLength, void *const buffer, const count4 suppliedLength, bool *const eof, struct io_info *info) { (void)dataServerHandle; (void)stateid; (void)offset; (void)requestedLength; (void)buffer; (void)suppliedLength; (void)eof; (void)info; LogCrit(COMPONENT_PNFS, "Unimplemented DS read_plus!"); return NFS4ERR_NOTSUPP; } /** * @brief Create a FSAL data server handle from a wire handle. * * This function creates a FSAL data server handle from a client supplied * "wire" handle. * * @param[in] pnfsDataServer FSAL pNFS DS * @param[in] buffer Buffer from which to create the struct * @param[out] handle FSAL DS handle * @param[out] flags Flags used to create the FSAL data * server handle * * @returns: NFSv4.1 error codes. */ static nfsstat4 make_ds_handle(struct fsal_pnfs_ds *const pnfsDataServer, const struct gsh_buffdesc *const buffer, struct fsal_ds_handle **const handle, int flags) { (void)pnfsDataServer; struct DSWire *dataServerWire = NULL; dataServerWire = (struct DSWire *)buffer->addr; struct DataServerHandle *dataServerHandle = NULL; *handle = NULL; if (buffer->len != sizeof(struct DSWire)) return NFS4ERR_BADHANDLE; if (dataServerWire->inode == 0) return NFS4ERR_BADHANDLE; dataServerHandle = gsh_calloc(1, sizeof(struct DataServerHandle)); *handle = &dataServerHandle->handle; if (flags & FH_FSAL_BIG_ENDIAN) { #if (BYTE_ORDER != BIG_ENDIAN) dataServerHandle->inode = bswap_32(dataServerWire->inode); #else dataServerHandle->inode = dataServerWire->inode; #endif } else { #if (BYTE_ORDER == BIG_ENDIAN) dataServerHandle->inode = bswap_32(dataServerWire->inode); #else dataServerHandle->inode = dataServerWire->inode; #endif } return NFS4_OK; } /** * @brief Initialize FSAL specific permissions per pNFS DS. * * @param[in] pnfsDataServer FSAL pNFS DS * @param[in] request Incoming request * * @returns: NFSv4.1 error codes: NFS4_OK, NFS4ERR_ACCESS, NFS4ERR_WRONGSEC */ static nfsstat4 ds_permissions(struct fsal_pnfs_ds *const pnfsDataServer, struct svc_req *request) { (void)pnfsDataServer; (void)request; op_ctx->export_perms.set = root_op_export_set; op_ctx->export_perms.options = root_op_export_options; return NFS4_OK; } /** * @brief Initialize FSAL specific values for pNFS Data Server. * * @param[in] ops FSAL pNFS Data Server operations vector */ void pnfsDsOperationsInit(struct fsal_pnfs_ds_ops *ops) { memcpy(ops, &def_pnfs_ds_ops, sizeof(struct fsal_pnfs_ds_ops)); ops->make_ds_handle = make_ds_handle; ops->dsh_release = dsh_release; ops->dsh_read = dsh_read; ops->dsh_write = dsh_write; ops->dsh_commit = dsh_commit; ops->dsh_read_plus = dsh_read_plus; ops->ds_permissions = ds_permissions; } nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/export.c000066400000000000000000000336011473756622300205060ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* Copyright 2017 Skytechnology sp. z o.o. Copyright 2023 Leil Storage OÜ This file is part of SaunaFS. SaunaFS 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, version 3. SaunaFS 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 SaunaFS. If not, see . */ #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_config.h" #include "fsal_convert.h" #include "nfs_exports.h" #include "saunafs_fsal_types.h" #include "context_wrap.h" #include "saunafs_internal.h" /* Flags to determine if ACLs are supported */ #define NFSv4_ACL_SUPPORT (!op_ctx_export_has_option(EXPORT_OPTION_DISABLE_ACL)) /** * @brief Finalize an export * * This function is called as part of cleanup when the last reference to an * export is released and it is no longer part of the list. * * It should clean up all private resources and destroy the object. * * @param[in] exportHandle The export to release */ static void release(struct fsal_export *exportHandle) { struct SaunaFSExport *export = NULL; export = container_of(exportHandle, struct SaunaFSExport, export); deleteHandle(export->root); export->root = NULL; fsal_detach_export(export->export.fsal, &export->export.exports); free_export_ops(&export->export); if (export->cache) { resetFileInfoCacheParameters(export->cache, 0, 0); while (1) { FileInfoEntry_t *cacheHandle = NULL; fileinfo_t *fileHandle = NULL; cacheHandle = popExpiredFileInfoCache(export->cache); if (!cacheHandle) break; fileHandle = extractFileInfo(cacheHandle); sau_release(export->fsInstance, fileHandle); fileInfoEntryFree(cacheHandle); } destroyFileInfoCache(export->cache); export->cache = NULL; } sau_destroy(export->fsInstance); export->fsInstance = NULL; gsh_free((char *)export->parameters.subfolder); gsh_free(export); } /** * @brief Look up a path. * * Create an object handles within this export. * * This function looks up a path within the export, it is now exclusively * used to get a handle for the root directory of the export. * * @param [in] exportHandle The export in which to look up * @param [in] path The path to look up * @param [out] handle The object found * @param [in,out] attributes Optional attributes for newly created object * * \see fsal_api.h for more information * * @returns: FSAL status */ fsal_status_t lookup_path(struct fsal_export *exportHandle, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attributes) { static const char *rootDirPath = "/"; struct SaunaFSExport *export = NULL; struct SaunaFSHandle *objectHandle = NULL; const char *realPath = NULL; LogFullDebug(COMPONENT_FSAL, "export_id=%" PRIu16 " path=%s", exportHandle->export_id, path); export = container_of(exportHandle, struct SaunaFSExport, export); *handle = NULL; /* set the real path to the path without the prefix from * ctx_export->fullpath */ if (*path != '/') { realPath = strchr(path, ':'); if (realPath == NULL) return fsalstat(ERR_FSAL_INVAL, 0); ++realPath; if (*realPath != '/') return fsalstat(ERR_FSAL_INVAL, 0); } else { realPath = path; } if (strstr(realPath, CTX_FULLPATH(op_ctx)) != realPath) return fsalstat(ERR_FSAL_SERVERFAULT, 0); realPath += strlen(CTX_FULLPATH(op_ctx)); if (*realPath == '\0') realPath = rootDirPath; LogFullDebug(COMPONENT_FSAL, "real path = %s", realPath); /* special case the root */ if (strcmp(realPath, "/") == 0) { assert(export->root); *handle = &export->root->handle; if (attributes == NULL) return fsalstat(ERR_FSAL_NO_ERROR, 0); } sau_entry_t entry; int status = saunafs_lookup(export->fsInstance, &op_ctx->creds, SPECIAL_INODE_ROOT, realPath, &entry); if (status < 0) return fsalLastError(); if (attributes != NULL) posix2fsal_attributes_all(&entry.attr, attributes); if (*handle == NULL) { objectHandle = allocateHandle(&entry.attr, export); *handle = &objectHandle->handle; } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Get dynamic filesystem statistics and configuration for this * filesystem. * * This function gets information on inodes and space in use and free for a * filesystem. See fsal_dynamicfsinfo_t for details of what to fill out. * * @param[in] exportHandle Export handle to interrogate * @param[in] objectHandle Directory * @param[out] info Buffer to fill with information * * @returns: FSAL status. */ static fsal_status_t get_dynamic_info(struct fsal_export *exportHandle, struct fsal_obj_handle *objectHandle, fsal_dynamicfsinfo_t *info) { (void)objectHandle; struct SaunaFSExport *export = NULL; export = container_of(exportHandle, struct SaunaFSExport, export); sau_stat_t statfsEntry; int status = sau_statfs(export->fsInstance, &statfsEntry); if (status < 0) return fsalLastError(); memset(info, 0, sizeof(fsal_dynamicfsinfo_t)); info->total_bytes = statfsEntry.total_space; info->free_bytes = statfsEntry.avail_space; info->avail_bytes = statfsEntry.avail_space; info->total_files = MAX_REGULAR_INODE; info->free_files = MAX_REGULAR_INODE - statfsEntry.inodes; info->avail_files = MAX_REGULAR_INODE - statfsEntry.inodes; info->time_delta.tv_sec = 0; info->time_delta.tv_nsec = FSAL_DEFAULT_TIME_DELTA_NSEC; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Free a state_t structure. * * @param[in] state state_t structure to free */ void fs_free_state(struct state_t *state) { struct SaunaFSFd *fd = NULL; fd = &container_of(state, struct SaunaFSStateFd, state)->saunafsFd; destroy_fsal_fd(&fd->fsalFd); gsh_free(state); } /** * @brief Allocate a state_t structure. * * Note that this is not expected to fail since memory allocation is * expected to abort on failure. * * @param [in] export Export state_t will be associated with * @param [in] stateType Type of state to allocate * @param [in] relatedState Related state if appropriate * * @returns: a state structure. */ struct state_t *allocate_state(struct fsal_export *export, enum state_type stateType, struct state_t *relatedState) { (void)export; /* Not used */ struct state_t *state = NULL; struct SaunaFSFd *fileDescriptor = NULL; state = init_state(gsh_calloc(1, sizeof(struct SaunaFSStateFd)), fs_free_state, stateType, relatedState); fileDescriptor = &container_of(state, struct SaunaFSStateFd, state)->saunafsFd; init_fsal_fd(&fileDescriptor->fsalFd, FSAL_FD_STATE, op_ctx->fsal_export); fileDescriptor->fd = NULL; return state; } /** * @brief Convert a wire handle to a host handle. * * This function extracts a host handle from a wire handle. That is, when * given a handle as passed to a client, this method will extract the handle * to create objects. * * @param[in] export Export handle. * @param[in] protocol Protocol through which buffer was received. * @param[in] flags Flags to describe the wire handle. Example, * if the handle is a big endian handle. * @param[in,out] buffer Buffer descriptor. The address of the * buffer is given in bufferDescriptor->buf * and must not be changed. * bufferDescriptor->len is the length of the * data contained in the buffer, * bufferDescriptor->len must be updated to * the correct host handle size. * * @returns: FSAL type. */ static fsal_status_t wire_to_host(struct fsal_export *export, fsal_digesttype_t protocol, struct gsh_buffdesc *buffer, int flags) { (void)protocol; (void)export; if (!buffer || !buffer->addr) return fsalstat(ERR_FSAL_FAULT, 0); sau_inode_t *inode = (sau_inode_t *)buffer->addr; if (flags & FH_FSAL_BIG_ENDIAN) { #if (BYTE_ORDER != BIG_ENDIAN) static_assert(sizeof(sau_inode_t) == 4, ""); *inode = bswap_32(*inode); #endif } else { #if (BYTE_ORDER == BIG_ENDIAN) assert(sizeof(sau_inode_t) == 4); *inode = bswap_32(*inode); #endif } if (buffer->len != sizeof(sau_inode_t)) { LogMajor(COMPONENT_FSAL, "Size mismatch for handle. Should be %zu, got %zu", sizeof(sau_inode_t), buffer->len); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Extract "key" from a host handle * * This function extracts a "key" from a host handle. That is, when given a * handle that is extracted from wire_to_host() above, this method will extract * the unique bits used to index the inode cache. * * @param[in] export Export handle * @param[in,out] buffer Buffer descriptor. The address of the buffer is * given in @c buffer->addr and must not be changed. * @c buffer->len is the length of the data * contained in the buffer, @c buffer->len must be * updated to the correct size. In other words, the * key has to be placed at the beginning of the * buffer. */ fsal_status_t host_to_key(struct fsal_export *export, struct gsh_buffdesc *buffer) { (void)export; struct SaunaFSHandleKey *key = buffer->addr; /* * Ganesha automatically mixes the export_id in with the actual wire * filehandle and strips that out before transforming it to a host * handle. * This method is called on a host handle which doesn't have the * export_id. */ key->exportId = op_ctx->ctx_export->export_id; buffer->len = sizeof(*key); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Create a FSAL object handle from a host handle. * * This function creates a FSAL object handle from a host handle (when an * object is no longer in cache but the client still remembers the handle). * * @param[in] exportHandle The export in which to create the handle. * @param[in] buffer Buffer descriptor for the host handle. * @param[in] publicHandle FSAL object handle. * @param[in,out] attributes Optional attributes for newly created object. * * \see fsal_api.h for more information * * @returns: FSAL status */ fsal_status_t create_handle(struct fsal_export *exportHandle, struct gsh_buffdesc *buffer, struct fsal_obj_handle **publicHandle, struct fsal_attrlist *attributes) { struct SaunaFSExport *export = NULL; struct SaunaFSHandle *handle = NULL; sau_inode_t *inode = NULL; export = container_of(exportHandle, struct SaunaFSExport, export); inode = (sau_inode_t *)buffer->addr; *publicHandle = NULL; if (buffer->len != sizeof(sau_inode_t)) return fsalstat(ERR_FSAL_INVAL, 0); sau_attr_reply_t result; int status = saunafs_getattr(export->fsInstance, &op_ctx->creds, *inode, &result); if (status < 0) return fsalLastError(); handle = allocateHandle(&result.attr, export); if (attributes != NULL) posix2fsal_attributes_all(&result.attr, attributes); *publicHandle = &handle->handle; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Get supported ACL types. * * This function returns a bitmask indicating whether it supports * ALLOW, DENY, neither, or both types of ACL. * * @param[in] export Filesystem to interrogate. * * @returns: supported ACL types. */ static fsal_aclsupp_t fs_acl_support(struct fsal_export *export) { return fsal_acl_support(&export->fsal->fs_info); } /** * @brief Get supported attributes. * * This function returns a list of all attributes that this FSAL will support. * Be aware that this is specifically the attributes in struct fsal_attrlist, * other NFS attributes (fileid and so forth) are supported through other means. * * @param[in] export Filesystem to interrogate. * * @returns: supported attributes. */ static attrmask_t fs_supported_attrs(struct fsal_export *export) { attrmask_t supported_mask = 0; supported_mask = fsal_supported_attrs(&export->fsal->fs_info); /* Fixup supported_mask to indicate if ACL is supported for * this export */ if (NFSv4_ACL_SUPPORT) supported_mask |= (attrmask_t)ATTR_ACL; else supported_mask &= ~(attrmask_t)ATTR_ACL; return supported_mask; } /** * @brief Function to get the fsal_obj_handle that has fsal_fd as its global fd. * * @param[in] export The export in which the handle exists * @param[in] fd File descriptor in question * @param[out] handle FSAL object handle * * @return the fsal_obj_handle. */ void get_fsal_obj_hdl(struct fsal_export *export, struct fsal_fd *fd, struct fsal_obj_handle **handle) { (void)export; /* Not used */ struct SaunaFSFd *saunafsFd = NULL; struct SaunaFSHandle *myself = NULL; saunafsFd = container_of(fd, struct SaunaFSFd, fsalFd); myself = container_of(saunafsFd, struct SaunaFSHandle, fd); *handle = &myself->handle; } /** * @brief Set operations for exports * * This function overrides operations that we've implemented, leaving the rest * for the default. * * @param[in,out] ops Operations vector */ void exportOperationsInit(struct export_ops *ops) { ops->release = release; ops->lookup_path = lookup_path; ops->wire_to_host = wire_to_host; ops->host_to_key = host_to_key; ops->create_handle = create_handle; ops->get_fs_dynamic_info = get_dynamic_info; ops->fs_supported_attrs = fs_supported_attrs; ops->fs_acl_support = fs_acl_support; ops->alloc_state = allocate_state; ops->get_fsal_obj_hdl = get_fsal_obj_hdl; exportOperationsPnfs(ops); } nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/fileinfo_cache.c000066400000000000000000000131361473756622300221040ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* Copyright 2017 Skytechnology sp. z o.o. Copyright 2023 Leil Storage OÜ This file is part of SaunaFS. SaunaFS 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, version 3. SaunaFS 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 SaunaFS. If not, see . */ #include "fileinfo_cache.h" #include "saunafs_fsal_types.h" #include #include #include const uint64_t kMillisecondsInOneSecond = 1000LL; const uint64_t kNanosecondsInOneMicrosecond = 1000LL; struct FileInfoEntry { struct glist_head list_hook; struct avltree_node tree_hook; sau_inode_t inode; fileinfo_t *fileinfo; uint64_t timestamp; bool is_used; bool lookup; } __attribute__((packed)) __attribute__((aligned(64))); struct FileInfoCache { struct glist_head lru_list; struct glist_head used_list; struct avltree entry_lookup; int entry_count; unsigned int max_entries; int min_timeout_ms; pthread_mutex_t lock; } __attribute__((aligned(128))); static uint64_t get_time_ms(void) { struct timespec time_; timespec_get(&time_, TIME_UTC); return (uint64_t)time_.tv_sec * kMillisecondsInOneSecond + (uint64_t)time_.tv_nsec / kNanosecondsInOneMicrosecond; } static int cacheEntryCompareFunction(const struct avltree_node *nodeA, const struct avltree_node *nodeB) { FileInfoEntry_t *entryA = avltree_container_of(nodeA, FileInfoEntry_t, tree_hook); FileInfoEntry_t *entryB = avltree_container_of(nodeB, FileInfoEntry_t, tree_hook); if (entryA->inode < entryB->inode) return -1; if (entryA->inode > entryB->inode) return 1; if (entryA->lookup || entryB->lookup) return 0; if (entryA < entryB) return -1; if (entryA > entryB) return 1; return 0; } FileInfoCache_t *createFileInfoCache(unsigned int maxEntries, int minTimeoutMilliseconds) { FileInfoCache_t *cache = gsh_calloc(1, sizeof(FileInfoCache_t)); cache->max_entries = maxEntries; cache->min_timeout_ms = minTimeoutMilliseconds; PTHREAD_MUTEX_init(&cache->lock, NULL); glist_init(&cache->lru_list); glist_init(&cache->used_list); avltree_init(&cache->entry_lookup, &cacheEntryCompareFunction, 0); return cache; } void resetFileInfoCacheParameters(FileInfoCache_t *cache, unsigned int maxEntries, int minTimeoutMilliseconds) { PTHREAD_MUTEX_lock(&cache->lock); cache->max_entries = maxEntries; cache->min_timeout_ms = minTimeoutMilliseconds; PTHREAD_MUTEX_unlock(&cache->lock); } void destroyFileInfoCache(FileInfoCache_t *cache) { FileInfoEntry_t *entry = NULL; if (!cache) return; while ((entry = glist_first_entry(&cache->used_list, FileInfoEntry_t, list_hook))) { glist_del(&entry->list_hook); gsh_free(entry); } while ((entry = glist_first_entry(&cache->lru_list, FileInfoEntry_t, list_hook))) { glist_del(&entry->list_hook); gsh_free(entry); } gsh_free(cache); } FileInfoEntry_t *acquireFileInfoCache(FileInfoCache_t *cache, sau_inode_t inode) { FileInfoEntry_t key; FileInfoEntry_t *entry = NULL; key.inode = inode; key.lookup = true; PTHREAD_MUTEX_lock(&cache->lock); struct avltree_node *node = avltree_lookup(&key.tree_hook, &cache->entry_lookup); if (node) { entry = avltree_container_of(node, FileInfoEntry_t, tree_hook); assert(!entry->is_used); glist_del(&entry->list_hook); glist_add(&cache->used_list, &entry->list_hook); avltree_remove(node, &cache->entry_lookup); } else { entry = gsh_calloc(1, sizeof(FileInfoEntry_t)); glist_add(&cache->used_list, &entry->list_hook); cache->entry_count++; } entry->is_used = true; entry->inode = inode; entry->timestamp = get_time_ms(); PTHREAD_MUTEX_unlock(&cache->lock); return entry; } void releaseFileInfoCache(FileInfoCache_t *cache, FileInfoEntry_t *entry) { PTHREAD_MUTEX_lock(&cache->lock); assert(entry->is_used); entry->is_used = false; entry->timestamp = get_time_ms(); glist_del(&entry->list_hook); glist_add_tail(&cache->lru_list, &entry->list_hook); avltree_insert(&entry->tree_hook, &cache->entry_lookup); PTHREAD_MUTEX_unlock(&cache->lock); } void eraseFileInfoCache(FileInfoCache_t *cache, FileInfoEntry_t *entry) { PTHREAD_MUTEX_lock(&cache->lock); assert(entry->is_used); glist_del(&entry->list_hook); cache->entry_count--; PTHREAD_MUTEX_unlock(&cache->lock); gsh_free(entry); } FileInfoEntry_t *popExpiredFileInfoCache(FileInfoCache_t *cache) { PTHREAD_MUTEX_lock(&cache->lock); FileInfoEntry_t *entry = glist_first_entry(&cache->lru_list, FileInfoEntry_t, list_hook); if (!entry) { PTHREAD_MUTEX_unlock(&cache->lock); return NULL; } bool isCacheFull = cache->entry_count > cache->max_entries; int timeout = isCacheFull ? 0 : cache->min_timeout_ms; uint64_t currentTime = get_time_ms(); if ((currentTime - entry->timestamp) >= timeout) { glist_del(&entry->list_hook); avltree_remove(&entry->tree_hook, &cache->entry_lookup); cache->entry_count--; } else { entry = NULL; } PTHREAD_MUTEX_unlock(&cache->lock); return entry; } void fileInfoEntryFree(FileInfoEntry_t *entry) { assert(!entry->is_used); gsh_free(entry); } fileinfo_t *extractFileInfo(FileInfoEntry_t *entry) { return entry->fileinfo; } void attachFileInfo(FileInfoEntry_t *entry, fileinfo_t *fileinfo) { entry->fileinfo = fileinfo; } nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/fileinfo_cache.h000066400000000000000000000101231473756622300221020ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* Copyright 2017 Skytechnology sp. z o.o. Copyright 2023 Leil Storage OÜ This file is part of SaunaFS. SaunaFS 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, version 3. SaunaFS 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 SaunaFS. If not, see . */ #pragma once #include "saunafs/saunafs_c_api.h" #ifdef __cplusplus extern "C" { #endif typedef sau_fileinfo_t fileinfo_t; typedef struct FileInfoCache FileInfoCache_t; typedef struct FileInfoEntry FileInfoEntry_t; /*! * \brief Create fileinfo cache * \param maxEntries Max number of entries to be stored in cache * \param minTimeoutMilliseconds Entries will not be removed until at least * minTimeoutMilliseconds has passed * \return pointer to FileInfoCache_t structure * \post Destroy with destroyFileInfoCache function */ FileInfoCache_t *createFileInfoCache(unsigned int maxEntries, int minTimeoutMilliseconds); /*! * \brief Reset cache parameters * \param cache Cache to be modified * \param maxEntries Maximum number of entries to store in cache * \param minTimeoutMilliseconds Entries will not be removed until at least * minTimeoutMilliseconds has passed */ void resetFileInfoCacheParameters(FileInfoCache_t *cache, unsigned int maxEntries, int minTimeoutMilliseconds); /*! * \brief Destroy fileinfo cache * \param cache Pointer returned from createFileInfoCache */ void destroyFileInfoCache(FileInfoCache_t *cache); /*! * \brief Acquire fileinfo from cache * \param cache Cache to be modified * \param inode Inode of a file * \return Cache entry if succeeded, * NULL if cache is full * \attention entry->fileinfo will be NULL if file still needs to be open first * \post Set fileinfo to a valid pointer after opening a file with * sau_attach_fileinfo */ FileInfoEntry_t *acquireFileInfoCache(FileInfoCache_t *cache, sau_inode_t inode); /*! * \brief Release fileinfo from cache * \param cache Cache to be modified * \param entry Pointer returned from previous acquire() call */ void releaseFileInfoCache(FileInfoCache_t *cache, FileInfoEntry_t *entry); /*! * \brief Erase acquired entry * \param cache Cache to be modified * \param entry Pointer returned from previous acquire() call * \attention This function should be used if entry should not reside in cache * (i.e. opening a file failed) */ void eraseFileInfoCache(FileInfoCache_t *cache, FileInfoEntry_t *entry); /*! * \brief Get expired fileinfo from cache * \param cache Cache to be modified * \return entry removed from cache * \post use this entry to call release() on entry->fileinfo and free entry * afterwards with fileInfoEntryFree */ FileInfoEntry_t *popExpiredFileInfoCache(FileInfoCache_t *cache); /*! * \brief Free unused fileinfo cache entry * \param entry Entry to be freed */ void fileInfoEntryFree(FileInfoEntry_t *entry); /*! * \brief Get fileinfo from cache entry * \param cache Cache to be modified * \return fileinfo extracted from entry */ fileinfo_t *extractFileInfo(FileInfoEntry_t *entry); /*! * \brief Attach fileinfo to an existing cache entry * \param entry Entry to be modified * \param fileinfo Fileinfo to be attached to entry */ void attachFileInfo(FileInfoEntry_t *entry, fileinfo_t *fileinfo); #ifdef __cplusplus } #endif nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/handle.c000066400000000000000000002421331473756622300204220ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* Copyright 2017 Skytechnology sp. z o.o. Copyright 2023 Leil Storage OÜ This file is part of SaunaFS. SaunaFS 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, version 3. SaunaFS 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 SaunaFS. If not, see . */ #include "config.h" #include "fsal_types.h" #include "saunafs_fsal_types.h" #ifdef LINUX #include /* for fallocate */ #include /* for makedev(3) */ #endif #include "FSAL/fsal_commonlib.h" #include "fsal.h" #include "fsal_convert.h" #include "context_wrap.h" #include "saunafs/saunafs_error_codes.h" #include "saunafs_internal.h" /** * @brief Clean up a filehandle. * * This function cleans up private resources associated with a filehandle * and deallocates it. * * Implement this method or you will leak. Refcount (if used) should be 1. * * @param[in] objectHandle Handle to release */ static void release(struct fsal_obj_handle *objectHandle) { struct SaunaFSHandle *handle = NULL; handle = container_of(objectHandle, struct SaunaFSHandle, handle); if (handle->handle.type == REGULAR_FILE) destroy_fsal_fd(&handle->fd.fsalFd); if (handle != handle->export->root) deleteHandle(handle); } /** * @brief Look up a filename. * * Directory operations. * This function looks up the given name in the supplied directory. * * @param [in] dirHandle Directory to search * @param [in] path Name to look up * @param [out] objectHandle Object found * @param [in,out] attributes Optional attributes for newly created * object * * \see fsal_api.h for more information * * @returns: FSAL status */ static fsal_status_t lookup(struct fsal_obj_handle *dirHandle, const char *path, struct fsal_obj_handle **objectHandle, struct fsal_attrlist *attributes) { struct SaunaFSExport *export = NULL; struct SaunaFSHandle *directory = NULL; struct sau_entry node; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); directory = container_of(dirHandle, struct SaunaFSHandle, handle); int status = saunafs_lookup(export->fsInstance, &op_ctx->creds, directory->inode, path, &node); if (status < 0) return fsalLastError(); if (attributes != NULL) posix2fsal_attributes_all(&node.attr, attributes); struct SaunaFSHandle *handle = allocateHandle(&node.attr, export); *objectHandle = &handle->handle; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Read a directory. * * This function reads directory entries from the FSAL and supplies them to * a callback. * * @param [in] dirHandle Directory to read * @param [in] whence Point at which to start reading. NULL to * start at beginning * @param [in] dirState Opaque pointer to be passed to callback * @param [in] readdirCb Callback to receive names * @param [in] attributesMask Indicate which attributes the caller is * interested in * @param [out] eof true if the last entry was reached * * @returns: FSAL status */ static fsal_status_t readdir_(struct fsal_obj_handle *dirHandle, fsal_cookie_t *whence, void *dirState, fsal_readdir_cb readdirCb, attrmask_t attributesMask, bool *eof) { static const int batchSize = 100; struct sau_direntry buffer[batchSize]; struct SaunaFSExport *export = NULL; struct SaunaFSHandle *directory = NULL; struct SaunaFSHandle *handle = NULL; struct fsal_attrlist attributes; off_t direntryOffset = 0; enum fsal_dir_result result = DIR_CONTINUE; int status = 0; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); directory = container_of(dirHandle, struct SaunaFSHandle, handle); sau_context_t *context __attribute__((cleanup(sau_destroy_context))) = NULL; context = createContext(export->fsInstance, &op_ctx->creds); struct sau_fileinfo *saunafsFd = NULL; saunafsFd = sau_opendir(export->fsInstance, context, directory->inode); if (!saunafsFd) return fsalLastError(); if (whence != NULL) direntryOffset = *whence; while (true) { size_t totalEntries = 0; size_t entry = 0; status = sau_readdir(export->fsInstance, context, saunafsFd, direntryOffset, batchSize, buffer, &totalEntries); if (status < 0) return fsalLastError(); result = DIR_CONTINUE; for (entry = 0; entry < totalEntries && result != DIR_TERMINATE; ++entry) { if (strcmp(buffer[entry].name, ".") == 0 || strcmp(buffer[entry].name, "..") == 0) { continue; } handle = allocateHandle(&buffer[entry].attr, export); fsal_prepare_attrs(&attributes, attributesMask); posix2fsal_attributes_all(&buffer[entry].attr, &attributes); direntryOffset = buffer[entry].next_entry_offset; result = readdirCb(buffer[entry].name, &handle->handle, &attributes, dirState, direntryOffset + 1); fsal_release_attrs(&attributes); } sau_destroy_direntry(buffer, totalEntries); *eof = (totalEntries < batchSize) && (entry == totalEntries); if (result != DIR_CONTINUE || totalEntries < batchSize) break; } status = sau_releasedir(export->fsInstance, saunafsFd); if (status < 0) return fsalLastError(); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Get attributes. * * This function fetches the attributes for the object. * The attributes requested in the mask are copied out * (though other attributes might be copied out). * * @param [in] objectHandle Object to query * @param [out] attributes Attribute list for file * * \see fsal_api.h for more information * * @returns: FSAL status */ static fsal_status_t getattrs(struct fsal_obj_handle *objectHandle, struct fsal_attrlist *attributes) { struct SaunaFSExport *export = NULL; struct SaunaFSHandle *handle = NULL; struct sau_attr_reply posixAttributes; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); handle = container_of(objectHandle, struct SaunaFSHandle, handle); LogFullDebug(COMPONENT_FSAL, " export = %" PRIu16 " inode = %" PRIu32, export->export.export_id, handle->inode); int status = saunafs_getattr(export->fsInstance, &op_ctx->creds, handle->inode, &posixAttributes); if (status < 0) { if (attributes->request_mask & ATTR_RDATTR_ERR) attributes->valid_mask = ATTR_RDATTR_ERR; return fsalLastError(); } posix2fsal_attributes_all(&posixAttributes.attr, attributes); #ifdef ENABLE_NFS_ACL_SUPPORT if (attributes->request_mask & (attrmask_t)ATTR_ACL) { fsal_status_t status = getACL(export, handle->inode, posixAttributes.attr.st_uid, &attributes->acl); if (!FSAL_IS_ERROR(status)) attributes->valid_mask |= (attrmask_t)ATTR_ACL; } #endif return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Write wire handle. * * Handle operations. * This function writes a "wire" handle or file ID to the given buffer. * * @param [in] objectHandle The handle to digest * @param [in] outputType The type of digest to write * @param [in,out] buffer Buffer descriptor to which to write * digest. Set fh_desc->len to final output length. * * @returns: FSAL status */ static fsal_status_t handle_to_wire(const struct fsal_obj_handle *objectHandle, uint32_t outputType, struct gsh_buffdesc *buffer) { (void)outputType; struct SaunaFSHandle *handle = NULL; handle = container_of(objectHandle, struct SaunaFSHandle, handle); sau_inode_t inode = handle->inode; if (buffer->len < sizeof(sau_inode_t)) { LogMajor(COMPONENT_FSAL, "Space too small for handle. Need %zu, have %zu", sizeof(sau_inode_t), buffer->len); return fsalstat(ERR_FSAL_TOOSMALL, 0); } memcpy(buffer->addr, &inode, sizeof(sau_inode_t)); buffer->len = sizeof(inode); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Get key for handle. * * Indicate the unique part of the handle that should be used for hashing. * * @param [in] objectHandle Handle whose key is to be got * @param [out] buffer Address and length giving sub-region of * handle to be used as key. * * @returns: FSAL status */ static void handle_to_key(struct fsal_obj_handle *objectHandle, struct gsh_buffdesc *buffer) { struct SaunaFSHandle *handle = NULL; handle = container_of(objectHandle, struct SaunaFSHandle, handle); buffer->addr = &handle->key; buffer->len = sizeof(struct SaunaFSHandleKey); } /** * @brief Close a SaunaFS file descriptor. * * @param [in] handle SaunaFS internal object handle * @param [in] saunafsFd SaunaFS file descriptor to open * * @returns: FSAL status */ static fsal_status_t closeFileDescriptor(struct SaunaFSHandle *handle, struct SaunaFSFd *saunafsFd) { fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0); if (saunafsFd->fd != NULL && saunafsFd->fsalFd.openflags != FSAL_O_CLOSED) { int status = sau_release(handle->export->fsInstance, saunafsFd->fd); saunafsFd->fd = NULL; saunafsFd->fsalFd.openflags = FSAL_O_CLOSED; if (status < 0) return fsalLastError(); } else { status = fsalstat(ERR_FSAL_NOT_OPENED, 0); } return status; } /** * @brief Function to open or reopen a fsal_fd. * * @param[in] objectHandle File on which to operate * @param[in] openflags New mode for open * @param[out] fsalFd File descriptor that is to be used * * @return FSAL status. */ fsal_status_t reopen_func(struct fsal_obj_handle *objectHandle, fsal_openflags_t openflags, struct fsal_fd *fsalFd) { struct SaunaFSHandle *handle = NULL; struct SaunaFSFd *fileDescriptor = NULL; struct sau_fileinfo *saunafsFD = NULL; struct SaunaFSExport *export = NULL; int posixFlags = 0; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; handle = container_of(objectHandle, struct SaunaFSHandle, handle); fileDescriptor = container_of(fsalFd, struct SaunaFSFd, fsalFd); fsal2posix_openflags(openflags, &posixFlags); export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); LogFullDebug(COMPONENT_FSAL, "fd = %p fd->fd = %p openflags = %x, posixFlags = %x", fileDescriptor, fileDescriptor->fd, openflags, posixFlags); assert(fileDescriptor->fd == NULL && fileDescriptor->fsalFd.openflags == FSAL_O_CLOSED && openflags != 0); saunafsFD = saunafs_open(export->fsInstance, &op_ctx->creds, handle->inode, posixFlags); if (saunafsFD == NULL) { LogFullDebug(COMPONENT_FSAL, "open failed with %s", sau_error_string(sau_last_err())); return fsalLastError(); } if (fileDescriptor->fd != NULL && fileDescriptor->fsalFd.openflags != FSAL_O_CLOSED) { int retvalue = sau_release(handle->export->fsInstance, fileDescriptor->fd); if (retvalue < 0) { LogFullDebug(COMPONENT_FSAL, "close failed with %s", sau_error_string(sau_last_err())); status = fsalLastError(); } } fileDescriptor->fd = saunafsFD; fileDescriptor->fsalFd.openflags = FSAL_O_NFS_FLAGS(openflags); return status; } /** * @brief Open a file using its handle. * * @param [in] objectHandle File handle to open * @param [in] state state_t to use for this * operation * @param [in] openflags Mode for open * @param [in] createmode Mode for create * @param [in] verifier Verifier to use for exclusive * create * @param [in] attributes Attributes to set on created * file * * @returns: FSAL status */ static fsal_status_t openByHandle(struct fsal_obj_handle *objectHandle, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, fsal_verifier_t verifier, struct fsal_attrlist *attributes) { struct SaunaFSExport *export = NULL; struct SaunaFSHandle *handle = NULL; struct SaunaFSFd *saunafsFd = NULL; struct fsal_fd *fsalFd = NULL; fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0); int posixFlags = 0; fsal_openflags_t oldOpenflags = 0; bool truncated = openflags & FSAL_O_TRUNC; handle = container_of(objectHandle, struct SaunaFSHandle, handle); export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); if (state != NULL) { saunafsFd = &container_of(state, struct SaunaFSStateFd, state) ->saunafsFd; } else { /* We need to use the global file descriptor to continue */ saunafsFd = &handle->fd; } fsalFd = &saunafsFd->fsalFd; /* Indicate we want to do fd work (can't fail since not reclaiming) */ fsal_start_fd_work_no_reclaim(fsalFd); oldOpenflags = saunafsFd->fsalFd.openflags; if (state != NULL) { /* Prepare to take the share reservation, but only if we are * called with a valid state (if state is NULL the caller is a * stateless create such as NFS v3 CREATE and we're just going * to ignore share reservation stuff). */ /* Now that we have the mutex, and no I/O is in progress so we * have exclusive access to the share's fsal_fd, we can look * at its openflags. We also need to work the share reservation * so take the obj_lock. * NOTE: This is the ONLY sequence where both a work_mutex and * the obj_lock are taken, so there is no opportunity for ABBA * deadlock. * * Note that we do hold the obj_lock over an open and a close * which is longer than normal, but the previous iteration * of the code held the obj lock (read granted) over whole * I/O operations. We don't block over I/O because we've assured * that no I/O is in progress or can start before proceeding * past the above while loop. */ PTHREAD_RWLOCK_wrlock(&objectHandle->obj_lock); /* Now check the new share. */ status = check_share_conflict(&handle->share, openflags, false); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "check_share_conflict returned %s", fsal_err_txt(status)); if (state != NULL) { if (!FSAL_IS_ERROR(status)) { /* Success, establish the new share. */ update_share_counters(&handle->share, oldOpenflags, openflags); } /* Release obj_lock. */ PTHREAD_RWLOCK_unlock(&objectHandle->obj_lock); } /* Indicate we are done with fd work and signal * any waiters. */ fsal_complete_fd_work(fsalFd); return status; } } /* Check for a genuine no-op open. That means we aren't trying to * create, the file is already open in the same mode with the same deny * flags, and we aren't trying to truncate. In this case we want to * avoid bouncing the fd. In the case of JUST changing the deny mode or * a replayed exclusive create, we might bounce the fd when we could * have avoided that, but those scenarios are much less common. */ if (FSAL_O_NFS_FLAGS(openflags) == FSAL_O_NFS_FLAGS(oldOpenflags) && truncated == false && createmode == FSAL_NO_CREATE) { LogFullDebug(COMPONENT_FSAL, "no-op reopen2 saunafsFd->fd = %p openflags = %x", saunafsFd->fd, openflags); if (state != NULL) { if (!FSAL_IS_ERROR(status)) { /* Success, establish the new share. */ update_share_counters(&handle->share, oldOpenflags, openflags); } /* Release obj_lock. */ PTHREAD_RWLOCK_unlock(&objectHandle->obj_lock); } /* Indicate we are done with fd work and signal any waiters. */ fsal_complete_fd_work(fsalFd); return status; } /* No share conflict, re-open the share fd */ status = reopen_func(objectHandle, openflags, fsalFd); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "reopen_func returned %s", fsal_err_txt(status)); if (state != NULL) { if (!FSAL_IS_ERROR(status)) { /* Success, establish the new share. */ update_share_counters(&handle->share, oldOpenflags, openflags); } /* Release obj_lock. */ PTHREAD_RWLOCK_unlock(&objectHandle->obj_lock); } /* Indicate we are done with fd work and signal any waiters. */ fsal_complete_fd_work(fsalFd); return status; } fsal2posix_openflags(openflags, &posixFlags); if (createmode >= FSAL_EXCLUSIVE || attributes) { /* NOTE: won't come in here when called from saunafs_reopen2... * truncated might be set, but attrs_out will be NULL. * We don't need to look at truncated since other callers * are interested in attrs_out. */ /* Refresh the attributes */ struct sau_attr_reply attributesValues; int retvalue = saunafs_getattr(export->fsInstance, &op_ctx->creds, handle->inode, &attributesValues); if (retvalue == 0) { LogFullDebug(COMPONENT_FSAL, "New size = %" PRIx64, (int64_t)attributesValues.attr.st_size); } else { status = fsalLastError(); } if (!FSAL_IS_ERROR(status) && createmode >= FSAL_EXCLUSIVE && createmode != FSAL_EXCLUSIVE_9P && !check_verifier_stat(&attributesValues.attr, verifier, false)) { /* Verifier didn't match, return EEXIST */ status = fsalstat(posix2fsal_error(EEXIST), EEXIST); } if (!FSAL_IS_ERROR(status) && attributes) { posix2fsal_attributes_all(&attributesValues.attr, attributes); } } if (FSAL_IS_ERROR(status)) { /* close fd */ (void)closeFileDescriptor(handle, saunafsFd); } if (state != NULL) { if (!FSAL_IS_ERROR(status)) { /* Success, establish the new share. */ update_share_counters(&handle->share, oldOpenflags, openflags); } /* Release obj_lock. */ PTHREAD_RWLOCK_unlock(&objectHandle->obj_lock); } /* Indicate we are done with fd work and signal any waiters. */ fsal_complete_fd_work(fsalFd); return status; } /** * @brief Open a file using its name. * * @param [in] objectHandle File handle to open * @param [in] state state_t to use for this * operation * @param [in] openflags Mode for open * @param [in] name Name of the file * @param [in] verifier Verifier to use for exclusive * create * @param [in] attributes Attributes to set on created * file * * @returns: FSAL status */ static fsal_status_t openByName(struct fsal_obj_handle *objectHandle, struct state_t *state, fsal_openflags_t openflags, const char *name, fsal_verifier_t verifier, struct fsal_attrlist *attributes) { struct fsal_obj_handle *temp = NULL; fsal_status_t status; /* Ganesha doesn't has open by name, so we need to get the name with * lookup */ status = objectHandle->obj_ops->lookup(objectHandle, name, &temp, NULL); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "lookup returned %s", fsal_err_txt(status)); return status; } if (temp->type != REGULAR_FILE) { if (temp->type == DIRECTORY) { /* Trying to open2 a directory */ status = fsalstat(ERR_FSAL_ISDIR, 0); } else { /* Trying to open2 any other non-regular file */ status = fsalstat(ERR_FSAL_SYMLINK, 0); } /* Release the object we found by lookup */ temp->obj_ops->release(temp); LogFullDebug(COMPONENT_FSAL, "open2 returning %s", fsal_err_txt(status)); return status; } status = openByHandle(temp, state, openflags, FSAL_NO_CREATE, verifier, attributes); if (FSAL_IS_ERROR(status)) { temp->obj_ops->release(temp); LogFullDebug(COMPONENT_FSAL, "open returned %s", fsal_err_txt(status)); } return status; } /** * @brief Open a file descriptor for read or write and possibly create. * * Extended API functions. * With these new operations, the FSAL becomes responsible for managing * share reservations. The FSAL is also granted more control over the state of * a "file descriptor" and has more control of what a "file descriptor" even is. * Ultimately, it is whatever the FSAL needs in order to manage the share * reservations and lock state. * * The open2 method also allows atomic create/setattr/open. This function * opens a file for read or write, possibly creating it. * If the caller is passing a state, it must hold the state_lock exclusive. * * @param [in] objectHandle File to open or parent * directory * @param [in] state state_t to use for this * operation * @param [in] openflags Mode for open * @param [in] createmode Mode for create * @param [in] name Name for file if being created * or opened * @param [in] attributesToSet Attributes to set on created * file * @param [in] verifier Verifier to use for exclusive * create * @param [in,out] createdObject Newly created object * @param [in,out] attributes Optional attributes for newly * created object * @param [in,out] callerPermissionCheck The caller must do a * permission check * @param[in,out] parentPreAttributes Optional attributes for parent dir * before the operation. * Should be atomic. * @param[in,out] parentPostAttributes Optional attributes for parent dir * after the operation. * Should be atomic. * * @returns: FSAL status */ static fsal_status_t open2(struct fsal_obj_handle *objectHandle, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attributesToSet, fsal_verifier_t verifier, struct fsal_obj_handle **createdObject, struct fsal_attrlist *attributes, bool *callerPermissionCheck, struct fsal_attrlist *parentPreAttributes, struct fsal_attrlist *parentPostAttributes) { struct SaunaFSExport *export = NULL; struct SaunaFSHandle *handle = NULL; fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0); LogAttrlist(COMPONENT_FSAL, NIV_FULL_DEBUG, "attrs ", attributesToSet, false); if (createmode >= FSAL_EXCLUSIVE) { /* Now fixup attrs for verifier if exclusive create */ set_common_verifier(attributesToSet, verifier, false); } if (name == NULL) { status = openByHandle(objectHandle, state, openflags, createmode, verifier, attributes); *callerPermissionCheck = FSAL_IS_SUCCESS(status); return status; } *callerPermissionCheck = (createmode == FSAL_NO_CREATE); if (createmode == FSAL_NO_CREATE) { return openByName(objectHandle, state, openflags, name, verifier, attributes); } /* Create file */ export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); handle = container_of(objectHandle, struct SaunaFSHandle, handle); /* Fetch the mode attribute to use in the openat system call */ mode_t unix_mode = fsal2unix_mode(attributesToSet->mode) & ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export); /* Don't set the mode if we later set the attributes */ FSAL_UNSET_MASK(attributesToSet->valid_mask, (attrmask_t)ATTR_MODE); struct sau_entry posixAttributes; int retval = saunafs_mknode(export->fsInstance, &op_ctx->creds, handle->inode, name, unix_mode, 0, &posixAttributes); if (retval < 0 && sau_last_err() == SAUNAFS_ERROR_EEXIST && createmode == FSAL_UNCHECKED) { return openByName(objectHandle, state, openflags, name, verifier, attributes); } if (retval < 0) return fsalLastError(); /* File has been created by us. */ *callerPermissionCheck = false; struct SaunaFSHandle *newHandle = allocateHandle(&posixAttributes.attr, export); if (newHandle == NULL) { (*createdObject)->obj_ops->release(*createdObject); *createdObject = NULL; retval = saunafs_unlink(export->fsInstance, &op_ctx->creds, handle->inode, name); if (retval < 0) return fsalLastError(); return fsalstat(posix2fsal_error(ENOMEM), ENOMEM); } *createdObject = &newHandle->handle; if (attributesToSet->valid_mask != 0) { status = (*createdObject) ->obj_ops->setattr2(*createdObject, false, state, attributesToSet); if (FSAL_IS_ERROR(status)) { (*createdObject)->obj_ops->release(*createdObject); *createdObject = NULL; retval = saunafs_unlink(export->fsInstance, &op_ctx->creds, handle->inode, name); if (retval < 0) return fsalLastError(); return status; } if (attributes != NULL) { status = (*createdObject) ->obj_ops->getattrs(*createdObject, attributes); if (FSAL_IS_ERROR(status) && (attributes->request_mask & ATTR_RDATTR_ERR) == 0) { (*createdObject) ->obj_ops->release(*createdObject); *createdObject = NULL; retval = saunafs_unlink(export->fsInstance, &op_ctx->creds, handle->inode, name); if (retval < 0) return fsalLastError(); return status; } attributes = NULL; } } if (attributes != NULL) posix2fsal_attributes_all(&posixAttributes.attr, attributes); return openByHandle(*createdObject, state, openflags, createmode, verifier, NULL); } /** * @brief Read data from a file. * * This function reads data from the given file. The FSAL must be able to * perform the read whether a state is presented or not. * * This function also is expected to handle properly bypassing or not share * reservations. This is an (optionally) asynchronous call. When the I/O is * complete, the done callback is called with the results. * * @param [in] objectHandle File on which to operate * @param [in] bypass If state doesn't indicate a share * reservation, bypass any deny read * @param [in,out] doneCb Callback to call when I/O is done * @param [in,out] readArg Info about read, passed back in * callback * @param [in,out] callerArg Opaque arg from the caller for callback */ static void read2(struct fsal_obj_handle *objectHandle, bool bypass, fsal_async_cb doneCb, struct fsal_io_arg *readArg, void *callerArg) { struct SaunaFSExport *export = NULL; struct SaunaFSHandle *handle = NULL; struct SaunaFSFd *saunafsFd = NULL; struct SaunaFSFd emptyFileDescriptor = { FSAL_FD_INIT, NULL }; struct fsal_fd *outFileDescriptor = NULL; fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0); fsal_status_t status2; ssize_t bytes = 0; uint64_t offset = readArg->offset; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); handle = container_of(objectHandle, struct SaunaFSHandle, handle); LogFullDebug(COMPONENT_FSAL, "export = %" PRIu16 " inode = %" PRIu32 " offset=%" PRIu64, export->export.export_id, handle->inode, offset); if (readArg->info != NULL) { /* Currently we don't support READ_PLUS */ doneCb(objectHandle, fsalstat(ERR_FSAL_NOTSUPP, 0), readArg, callerArg); return; } /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&outFileDescriptor, objectHandle, &handle->fd.fsalFd, &emptyFileDescriptor.fsalFd, readArg->state, FSAL_O_READ, false, NULL, bypass, &handle->share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); doneCb(objectHandle, status, readArg, callerArg); return; } saunafsFd = container_of(outFileDescriptor, struct SaunaFSFd, fsalFd); readArg->io_amount = 0; for (int i = 0; i < readArg->iov_count; i++) { bytes = saunafs_read(export->fsInstance, &op_ctx->creds, saunafsFd->fd, offset, readArg->iov[i].iov_len, readArg->iov[i].iov_base); if (bytes == 0) { readArg->end_of_file = true; break; } if (bytes < 0) { status = fsalLastError(); status2 = fsal_complete_io(objectHandle, outFileDescriptor); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (readArg->state == NULL) { /* We did I/O without a state so we need * to release the temp share * reservation acquired. */ /* Release the share reservation now by * updating the counters. */ update_share_counters_locked(objectHandle, &handle->share, FSAL_O_READ, FSAL_O_CLOSED); } doneCb(objectHandle, status, readArg, callerArg); return; } readArg->io_amount += bytes; offset += bytes; } status2 = fsal_complete_io(objectHandle, outFileDescriptor); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (readArg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating * the counters. */ update_share_counters_locked(objectHandle, &handle->share, FSAL_O_READ, FSAL_O_CLOSED); } doneCb(objectHandle, status, readArg, callerArg); } /** @brief Create a directory. * * Creation operations. This function creates a new directory. * * @param [in] directoryHandle Directory in which to create the * directory * @param [in] name Name of directory to create * @param [in] attributesToSet Attributes to set on newly created * object * @param [out] createdObject Newly created object * @param [in,out] attributes Optional attributes for newly * created object * @param[in,out] parentPreAttributes Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parentPostAttributes Optional attributes for parent dir * after the operation. Should be atomic. * * \see fsal_api.h for more information * * @returns: FSAL status */ static fsal_status_t mkdir_(struct fsal_obj_handle *directoryHandle, const char *name, struct fsal_attrlist *attributesToSet, struct fsal_obj_handle **createdObject, struct fsal_attrlist *attributes, struct fsal_attrlist *parentPreAttributes, struct fsal_attrlist *parentPostAttributes) { struct SaunaFSExport *export = NULL; struct SaunaFSHandle *directory = NULL; struct SaunaFSHandle *handle = NULL; struct sau_entry directoryEntry; fsal_status_t status; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); directory = container_of(directoryHandle, struct SaunaFSHandle, handle); LogFullDebug(COMPONENT_FSAL, "export = %" PRIu16 " parent_inode = %" PRIu32 " mode = %" PRIo32 " name = %s", export->export.export_id, directory->inode, attributesToSet->mode, name); mode_t unix_mode = fsal2unix_mode(attributesToSet->mode) & ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export); int retvalue = saunafs_mkdir(export->fsInstance, &op_ctx->creds, directory->inode, name, unix_mode, &directoryEntry); if (retvalue < 0) return fsalLastError(); handle = allocateHandle(&directoryEntry.attr, export); *createdObject = &handle->handle; FSAL_UNSET_MASK(attributesToSet->valid_mask, (attrmask_t)ATTR_MODE); if (attributesToSet->valid_mask) { status = (*createdObject) ->obj_ops->setattr2(*createdObject, false, NULL, attributesToSet); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "setattr2 status=%s", fsal_err_txt(status)); /* Release the handle we just allocate */ (*createdObject)->obj_ops->release(*createdObject); *createdObject = NULL; } else if (attributes != NULL) { /* We ignore errors here. The mkdir and setattr * succeeded, so we don't want to return error * if the getattrs fails. * We'll just return no attributes in that case. */ (*createdObject) ->obj_ops->getattrs(*createdObject, attributes); } } else if (attributes != NULL) { /* Since we haven't set any attributes other than what * was set on create, just use the stat results we used * to create the fsal_obj_handle. */ posix2fsal_attributes_all(&directoryEntry.attr, attributes); } FSAL_SET_MASK(attributesToSet->valid_mask, (attrmask_t)ATTR_MODE); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Create a new link. * * This function creates a new name for an existing object. * * @param [in] objectHandle Object to be linked to * @param [in] destinationDirHandle Directory in which to create the * link * @param [in] name Name for link * * @returns: FSAL status */ static fsal_status_t link_(struct fsal_obj_handle *objectHandle, struct fsal_obj_handle *destinationDirHandle, const char *name, struct fsal_attrlist *destdirPreAttributes, struct fsal_attrlist *destdirPostAttributes) { struct SaunaFSExport *export = NULL; struct SaunaFSHandle *handle = NULL; struct SaunaFSHandle *destinationHandle = NULL; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); handle = container_of(objectHandle, struct SaunaFSHandle, handle); destinationHandle = container_of(destinationDirHandle, struct SaunaFSHandle, handle); LogFullDebug(COMPONENT_FSAL, "export = %" PRIu16 " inode = %" PRIu32 " dest_inode = %" PRIu32 " name = %s", export->export.export_id, handle->inode, destinationHandle->inode, name); sau_entry_t entry; int retvalue = saunafs_link(export->fsInstance, &op_ctx->creds, handle->inode, destinationHandle->inode, name, &entry); if (retvalue < 0) return fsalLastError(); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Rename a file. * * This function renames a file (technically it changes the name of one * link, which may be the only link to the file.) * * @param [in] oldParentHandle Source directory * @param [in] oldName Original name * @param [in] newParentHandle Destination directory * @param [in] newName New name * @param[in,out] oldParentPreAttributes Optional attributes for olddParent * before the operation. Should be atomic. * @param[in,out] oldParentPostAttributes Optional attributes for oldParent * after the operation. Should be atomic. * @param[in,out] newParentPreAttributes Optional attributes for newParent * before the operation. Should be atomic. * @param[in,out] newParentPostAttributes Optional attributes for newParent * after the operation. Should be atomic. * * @returns: FSAL status */ static fsal_status_t rename_(struct fsal_obj_handle *objectHandle, struct fsal_obj_handle *oldParentHandle, const char *oldName, struct fsal_obj_handle *newParentHandle, const char *newName, struct fsal_attrlist *oldParentPreAttributes, struct fsal_attrlist *oldParentPostAttributes, struct fsal_attrlist *newParentPreAttributes, struct fsal_attrlist *newParentPostAttributes) { (void)objectHandle; struct SaunaFSExport *export = NULL; struct SaunaFSHandle *oldDir = NULL; struct SaunaFSHandle *newDir = NULL; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); oldDir = container_of(oldParentHandle, struct SaunaFSHandle, handle); newDir = container_of(newParentHandle, struct SaunaFSHandle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " old_inode=%" PRIu32 " new_inode=%" PRIu32 " old_name=%s new_name=%s", export->export.export_id, oldDir->inode, newDir->inode, oldName, newName); int status = saunafs_rename(export->fsInstance, &op_ctx->creds, oldDir->inode, oldName, newDir->inode, newName); if (status < 0) return fsalLastError(); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Remove a name from a directory. * * This function removes a name from a directory and possibly deletes the * file so named. * * @param [in] directoryHandle The directory from which to remove the * name * @param [in] objectHandle The object being removed * @param [in] name The name to remove * @param[in,out] parentPreAttributes Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parentPostAttributes Optional attributes for parent dir * after the operation. Should be atomic. * * @returns: FSAL status */ static fsal_status_t unlink_(struct fsal_obj_handle *directoryHandle, struct fsal_obj_handle *objectHandle, const char *name, struct fsal_attrlist *parentPreAttributes, struct fsal_attrlist *parentPostAttributes) { struct SaunaFSExport *export = NULL; struct SaunaFSHandle *directory = NULL; int status = 0; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); directory = container_of(directoryHandle, struct SaunaFSHandle, handle); LogFullDebug(COMPONENT_FSAL, "export = %" PRIu16 " parent_inode = %" PRIu32 " name = %s type = %s", export->export.export_id, directory->inode, name, object_file_type_to_str(objectHandle->type)); if (objectHandle->type != DIRECTORY) { status = saunafs_unlink(export->fsInstance, &op_ctx->creds, directory->inode, name); } else { status = saunafs_rmdir(export->fsInstance, &op_ctx->creds, directory->inode, name); } if (status < 0) return fsalLastError(); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Close a file. * * This function closes a file. This should return ERR_FSAL_NOT_OPENED if * the global FD for this obj was not open. * * @param [in] objectHandle File to close * * @returns: FSAL status */ static fsal_status_t close_(struct fsal_obj_handle *objectHandle) { struct SaunaFSHandle *handle = NULL; handle = container_of(objectHandle, struct SaunaFSHandle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIu32, handle->key.exportId, handle->inode); return close_fsal_fd(objectHandle, &handle->fd.fsalFd, false); } /** * @brief Write data to a file. * * This function writes data to a file. The FSAL must be able to perform * the write whether a state is presented or not. This function also is * expected to handle properly bypassing or not share reservations. * * Even with bypass == true, it will enforce a mandatory (NFSv4) deny_write * if an appropriate state is not passed). * The FSAL is expected to enforce sync if necessary. This is an * (optionally) asynchronous call. When the I/O is complete, the done_cb * callback is called. * * @param [in] objectHandle File on which to operate * @param [in] bypass If state doesn't indicate a share * reservation, * bypass any non-mandatory deny write * @param [in,out] doneCb Callback to call when I/O is done * @param [in,out] writeArg Info about write, passed back in * callback * @param [in,out] callerArg Opaque arg from the caller for callback */ static void write2(struct fsal_obj_handle *objectHandle, bool bypass, fsal_async_cb doneCb, struct fsal_io_arg *writeArg, void *callerArg) { struct SaunaFSExport *export = NULL; struct SaunaFSHandle *handle = NULL; struct SaunaFSFd *saunafsFd = NULL; struct SaunaFSFd emptyFileDescriptor = { FSAL_FD_INIT, NULL }; struct fsal_fd *outFileDescriptor = NULL; fsal_status_t status; fsal_status_t status2; ssize_t bytes = 0; uint64_t offset = writeArg->offset; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); handle = container_of(objectHandle, struct SaunaFSHandle, handle); LogFullDebug(COMPONENT_FSAL, "export=%" PRIu16 " inode=%" PRIu32 " offset=%" PRIu64, export->export.export_id, handle->inode, offset); if (writeArg->info) { doneCb(objectHandle, fsalstat(ERR_FSAL_NOTSUPP, 0), writeArg, callerArg); return; } /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&outFileDescriptor, objectHandle, &handle->fd.fsalFd, &emptyFileDescriptor.fsalFd, writeArg->state, FSAL_O_WRITE, false, NULL, bypass, &handle->share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); doneCb(objectHandle, status, writeArg, callerArg); return; } saunafsFd = container_of(outFileDescriptor, struct SaunaFSFd, fsalFd); for (int i = 0; i < writeArg->iov_count; i++) { bytes = saunafs_write(export->fsInstance, &op_ctx->creds, saunafsFd->fd, offset, writeArg->iov[i].iov_len, writeArg->iov[i].iov_base); if (bytes == 0) break; if (bytes < 0) { status = fsalLastError(); status2 = fsal_complete_io(objectHandle, outFileDescriptor); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (writeArg->state == NULL) { /* We did I/O without a state so we need * to release the temp share reservation * acquired. */ /* Release the share reservation now by * updating the counters. */ update_share_counters_locked(objectHandle, &handle->share, FSAL_O_WRITE, FSAL_O_CLOSED); } doneCb(objectHandle, status, writeArg, callerArg); return; } writeArg->io_amount += bytes; offset += bytes; } if (writeArg->fsal_stable) { int retvalue = saunafs_fsync(export->fsInstance, &op_ctx->creds, saunafsFd->fd); if (retvalue < 0) { status = fsalLastError(); writeArg->fsal_stable = false; } } status2 = fsal_complete_io(objectHandle, outFileDescriptor); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (writeArg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating * the counters. */ update_share_counters_locked(objectHandle, &handle->share, FSAL_O_WRITE, FSAL_O_CLOSED); } doneCb(objectHandle, status, writeArg, callerArg); } /** * @brief Commit written data. * * This function flushes possibly buffered data to a file. This method * differs from commit due to the need to interact with share reservations * and the fact that the FSAL manages the state of "file descriptors". The * FSAL must be able to perform this operation without being passed a * specific state. * * @param [in] objectHandle File on which to operate * @param [in] offset Start of range to commit * @param [in] length Length of range to commit * * @returns: FSAL status */ static fsal_status_t commit2(struct fsal_obj_handle *objectHandle, off_t offset, size_t length) { struct SaunaFSExport *export = NULL; struct SaunaFSHandle *handle = NULL; fsal_status_t status; fsal_status_t status2; struct SaunaFSFd emptyFd = { FSAL_FD_INIT, NULL }; struct SaunaFSFd *saunafsFd = NULL; struct fsal_fd *outFileDescriptor = NULL; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); handle = container_of(objectHandle, struct SaunaFSHandle, handle); LogFullDebug(COMPONENT_FSAL, "export = %" PRIu16 " inode = %" PRIu32 " offset = %lli len = %zu", export->export.export_id, handle->inode, (long long)offset, length); /* Make sure file is open in appropriate mode. * Do not check share reservation. */ status = fsal_start_global_io(&outFileDescriptor, objectHandle, &handle->fd.fsalFd, &emptyFd.fsalFd, FSAL_O_ANY, false, NULL); if (FSAL_IS_ERROR(status)) return status; saunafsFd = container_of(outFileDescriptor, struct SaunaFSFd, fsalFd); int retvalue = saunafs_fsync(export->fsInstance, &op_ctx->creds, saunafsFd->fd); if (retvalue < 0) status = fsalLastError(); status2 = fsal_complete_io(objectHandle, outFileDescriptor); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); /* We did not do share reservation stuff... */ return status; } /** * @brief Set attributes on an object. * * This function sets attributes on an object. Which attributes are set is * determined by attrib_set->mask. The FSAL must manage bypass or not of share * reservations, and a state may be passed. * The caller is expected to invoke fsal_release_attrs to release any * resources held by the set attributes. The FSAL layer MAY have added an * inherited ACL. * * @param [in] objectHandle File on which to operate * @param [in] bypass If state doesn't indicate a share * reservation, * bypass any non-mandatory deny write * @param [in] state state_t to use for this operation * @param [in] attributes Attributes to set * * @returns: FSAL status */ static fsal_status_t setattr2(struct fsal_obj_handle *objectHandle, bool bypass, struct state_t *state, struct fsal_attrlist *attributes) { struct SaunaFSExport *export = NULL; struct SaunaFSHandle *handle = NULL; fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0); bool hasShare = false; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); handle = container_of(objectHandle, struct SaunaFSHandle, handle); LogAttrlist(COMPONENT_FSAL, NIV_FULL_DEBUG, "attrs ", attributes, false); if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_MODE)) { attributes->mode &= ~op_ctx->fsal_export->exp_ops.fs_umask( op_ctx->fsal_export); } if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_SIZE)) { if (objectHandle->type != REGULAR_FILE) { LogFullDebug(COMPONENT_FSAL, "Setting size on non-regular file"); return fsalstat(ERR_FSAL_INVAL, EINVAL); } if (state == NULL) { /* Check share reservation and if OK, * update the counters. */ status = check_share_conflict_and_update_locked( objectHandle, &handle->share, FSAL_O_CLOSED, FSAL_O_WRITE, bypass); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "check_share_conflict failed with %s", fsal_err_txt(status)); return status; } hasShare = true; } } struct stat posixAttributes; uint mask = 0; memset(&posixAttributes, 0, sizeof(posixAttributes)); if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_SIZE)) { mask |= SAU_SET_ATTR_SIZE; posixAttributes.st_size = (__off_t)(attributes->filesize); LogFullDebug(COMPONENT_FSAL, "setting size to %lld", (long long)posixAttributes.st_size); } if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_MODE)) { mask |= SAU_SET_ATTR_MODE; posixAttributes.st_mode = fsal2unix_mode(attributes->mode); } if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_OWNER)) { mask |= SAU_SET_ATTR_UID; posixAttributes.st_uid = attributes->owner; } if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_GROUP)) { mask |= SAU_SET_ATTR_GID; posixAttributes.st_gid = attributes->group; } if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_ATIME)) { mask |= SAU_SET_ATTR_ATIME; posixAttributes.st_atim = attributes->atime; } if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_ATIME_SERVER)) { mask |= SAU_SET_ATTR_ATIME_NOW; } if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_MTIME)) { mask |= SAU_SET_ATTR_MTIME; posixAttributes.st_mtim = attributes->mtime; } if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_MTIME_SERVER)) { mask |= SAU_SET_ATTR_MTIME_NOW; } sau_attr_reply_t reply; int retvalue = saunafs_setattr(export->fsInstance, &op_ctx->creds, handle->inode, &posixAttributes, mask, &reply); if (retvalue < 0) { status = fsalLastError(); if (hasShare) { /* Release the share reservation now by updating * the counters. */ update_share_counters_locked(objectHandle, &handle->share, FSAL_O_RDWR, FSAL_O_CLOSED); } return status; } #ifdef ENABLE_NFS_ACL_SUPPORT if (FSAL_TEST_MASK(attributes->valid_mask, (attrmask_t)ATTR_ACL)) { status = setACL(export, handle->inode, attributes->acl, reply.attr.st_mode); } #endif if (hasShare) { /* Release the share reservation now by updating * the counters. */ update_share_counters_locked(objectHandle, &handle->share, FSAL_O_RDWR, FSAL_O_CLOSED); } return status; } /** * @brief Manage closing a file when a state is no longer needed. * * When the upper layers are ready to dispense with a state, this method is * called to allow the FSAL to close any file descriptors or release any * other resources associated with the state. A call to free_state should * be assumed to follow soon. * * @param [in] objectHandle File on which to operate * @param [in] state state_t to use for this operation * * @returns: FSAL status */ static fsal_status_t close2(struct fsal_obj_handle *objectHandle, struct state_t *state) { struct SaunaFSHandle *handle = NULL; handle = container_of(objectHandle, struct SaunaFSHandle, handle); struct SaunaFSFd *fileDescriptor = &container_of(state, struct SaunaFSStateFd, state)->saunafsFd; LogFullDebug(COMPONENT_FSAL, "export = %" PRIu16 " inode = %" PRIu32, handle->key.exportId, handle->inode); if (state->state_type == STATE_TYPE_SHARE || state->state_type == STATE_TYPE_NLM_SHARE || state->state_type == STATE_TYPE_9P_FID) { update_share_counters_locked(objectHandle, &handle->share, handle->fd.fsalFd.openflags, FSAL_O_CLOSED); } return close_fsal_fd(objectHandle, &fileDescriptor->fsalFd, false); } /** * @brief Create a symbolic link. * * This function creates a new symbolic link. * * @param [in] directoryHandle Directory in which to create the object * @param [in] name Name of object to create * @param [in] symbolicLinkPath Content of symbolic link * @param [in] attributesToSet Attributes to set on newly created * object * @param [out] createdObject Newly created object * @param [in,out] attributes Optional attributes for newly created * object * @param[in,out] parentPreAttributes Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parentPostAttributes Optional attributes for parent dir * after the operation. Should be atomic. * * \see fsal_api.h for more information * * @returns: FSAL status */ static fsal_status_t symlink_(struct fsal_obj_handle *directoryHandle, const char *name, const char *symbolicLinkPath, struct fsal_attrlist *attributesToSet, struct fsal_obj_handle **createdObject, struct fsal_attrlist *attributes, struct fsal_attrlist *parentPreAttributes, struct fsal_attrlist *parentPostAttributes) { struct SaunaFSExport *export = NULL; struct SaunaFSHandle *directory = NULL; struct sau_entry entry; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); directory = container_of(directoryHandle, struct SaunaFSHandle, handle); LogFullDebug(COMPONENT_FSAL, "export = %" PRIu16 " parent_inode = %" PRIu32 " name = %s", export->export.export_id, directory->inode, name); int retvalue = saunafs_symlink(export->fsInstance, &op_ctx->creds, symbolicLinkPath, directory->inode, name, &entry); if (retvalue < 0) return fsalLastError(); struct SaunaFSHandle *handle = allocateHandle(&entry.attr, export); *createdObject = &handle->handle; /* We handled the mode above */ FSAL_UNSET_MASK(attributesToSet->valid_mask, (attrmask_t)ATTR_MODE); if (attributesToSet->valid_mask) { fsal_status_t status; /* Now per support_ex API, if there are any other * attributes set, go ahead and get them set now */ status = (*createdObject) ->obj_ops->setattr2(*createdObject, false, NULL, attributesToSet); if (FSAL_IS_ERROR(status)) { /* Release the handle we just allocated */ LogFullDebug(COMPONENT_FSAL, "setattr2 status = %s", fsal_err_txt(status)); (*createdObject)->obj_ops->release(*createdObject); *createdObject = NULL; } } else if (attributes != NULL) { posix2fsal_attributes_all(&entry.attr, attributes); } FSAL_SET_MASK(attributesToSet->valid_mask, (attrmask_t)ATTR_MODE); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Perform a lock operation. * * This function performs a lock operation (lock, unlock, test) on a file. * This method assumes the FSAL is able to support lock owners, though it * need not support asynchronous blocking locks. Passing the lock state * allows the FSAL to associate information with a specific lock owner * for each file (which may include use of a "file descriptor". * * @param [in] objectHandle File on which to operate * @param [in] state state_t to use for this operation * @param [in] owner Lock owner * @param [in] lockOperation Operation to perform * @param [in] requestedLock Lock to take/release/test * @param [out] conflictingLock Conflicting lock * * @returns: FSAL status */ fsal_status_t lock_op2(struct fsal_obj_handle *objectHandle, struct state_t *state, void *owner, fsal_lock_op_t lockOperation, fsal_lock_param_t *requestedLock, fsal_lock_param_t *conflictingLock) { struct SaunaFSHandle *handle = NULL; struct SaunaFSExport *export = NULL; sau_err_t lastError = 0; fileinfo_t *fileinfo = NULL; sau_lock_info_t lockInfo; fsal_status_t status = { 0, 0 }; fsal_status_t status2 = { 0, 0 }; int retval = 0; struct SaunaFSFd *saunafsFd = NULL; fsal_openflags_t openflags = FSAL_O_RDWR; struct SaunaFSFd emptyFileDescriptor = { FSAL_FD_INIT, NULL }; struct fsal_fd *outFileDescriptor = NULL; bool bypass = false; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); handle = container_of(objectHandle, struct SaunaFSHandle, handle); LogFullDebug(COMPONENT_FSAL, "op:%d type:%d start:%" PRIu64 " length:%" PRIu64 " ", lockOperation, requestedLock->lock_type, requestedLock->lock_start, requestedLock->lock_length); if (objectHandle == NULL) { LogCrit(COMPONENT_FSAL, "objectHandle arg is NULL."); return fsalstat(ERR_FSAL_FAULT, 0); } if (owner == NULL) { LogCrit(COMPONENT_FSAL, "owner arg is NULL."); return fsalstat(ERR_FSAL_FAULT, 0); } if (lockOperation == FSAL_OP_LOCKT) { /* We may end up using global fd, don't fail on a deny mode */ bypass = true; openflags = FSAL_O_ANY; } else if (lockOperation == FSAL_OP_LOCK) { if (requestedLock->lock_type == FSAL_LOCK_R) openflags = FSAL_O_READ; else if (requestedLock->lock_type == FSAL_LOCK_W) openflags = FSAL_O_WRITE; } else if (lockOperation == FSAL_OP_UNLOCK) { openflags = FSAL_O_ANY; } else { LogFullDebug( COMPONENT_FSAL, "ERROR: Lock operation requested was not TEST, READ, or WRITE."); return fsalstat(ERR_FSAL_NOTSUPP, 0); } if (lockOperation != FSAL_OP_LOCKT && state == NULL) { LogCrit(COMPONENT_FSAL, "Non TEST operation with NULL state"); return posix2fsal_status(EINVAL); } if (requestedLock->lock_type == FSAL_LOCK_R) { lockInfo.l_type = F_RDLCK; } else if (requestedLock->lock_type == FSAL_LOCK_W) { lockInfo.l_type = F_WRLCK; } else { LogFullDebug( COMPONENT_FSAL, "ERROR: The requested lock type was not read or write."); return fsalstat(ERR_FSAL_NOTSUPP, 0); } if (lockOperation == FSAL_OP_UNLOCK) lockInfo.l_type = F_UNLCK; lockInfo.l_pid = 0; lockInfo.l_len = requestedLock->lock_length; lockInfo.l_start = requestedLock->lock_start; /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&outFileDescriptor, objectHandle, &handle->fd.fsalFd, &emptyFileDescriptor.fsalFd, state, openflags, true, NULL, bypass, &handle->share); if (FSAL_IS_ERROR(status)) { LogCrit(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); return status; } saunafsFd = container_of(outFileDescriptor, struct SaunaFSFd, fsalFd); fileinfo = saunafsFd->fd; sau_set_lock_owner(fileinfo, (uint64_t)owner); if (lockOperation == FSAL_OP_LOCKT) { retval = saunafs_getlock(export->fsInstance, &op_ctx->creds, fileinfo, &lockInfo); } else { retval = saunafs_setlock(export->fsInstance, &op_ctx->creds, fileinfo, &lockInfo); } if (retval < 0) { lastError = sau_last_err(); LogFullDebug(COMPONENT_FSAL, "Returning error %d", lastError); status2 = fsal_complete_io(objectHandle, outFileDescriptor); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (state == NULL) { /* We did I/O without a state so we need to release * the temp share reservation acquired. */ /* Release the share reservation now by updating * the counters. */ update_share_counters_locked(objectHandle, &handle->share, openflags, FSAL_O_CLOSED); } return saunafsToFsalError(lastError); } /* F_UNLCK is returned then the tested operation would be possible */ if (conflictingLock != NULL) { if (lockOperation == FSAL_OP_LOCKT && lockInfo.l_type != F_UNLCK) { conflictingLock->lock_length = lockInfo.l_len; conflictingLock->lock_start = lockInfo.l_start; conflictingLock->lock_type = lockInfo.l_type; } else { conflictingLock->lock_length = 0; conflictingLock->lock_start = 0; conflictingLock->lock_type = FSAL_NO_LOCK; } } lastError = sau_last_err(); status2 = fsal_complete_io(objectHandle, outFileDescriptor); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating * the counters. */ update_share_counters_locked(objectHandle, &handle->share, openflags, FSAL_O_CLOSED); } return status; } /** * @brief Re-open a file that may be already opened. * * This function supports changing the access mode of a share reservation * and thus should only be called with a share state. The st_lock must be * held. * * This MAY be used to open a file the first time if there is no need for * open by name or create semantics. One example would be 9P lopen. * * @param [in] objectHandle File on which to operate * @param [in] state state_t to use for this operation * @param [in] openflags Mode for re-open * * @returns: FSAL status */ static fsal_status_t reopen2(struct fsal_obj_handle *objectHandle, struct state_t *state, fsal_openflags_t openflags) { return openByHandle(objectHandle, state, openflags, FSAL_NO_CREATE, NULL, NULL); } /** * @brief Create a special file. * * Create a special file. This function creates a new special file. * * @param [in] directoryHandle Directory in which to create the object * @param [in] name Name of object to create * @param [in] nodeType Type of special file to create * @param [in] attributesToSet Attributes to set on newly created * object * @param [in] createdObject Newly created object * @param [in] attributes Optional attributes for newly created * object * @param[in,out] parentPreAttributes Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parentPostAttributes Optional attributes for parent dir * after the operation. Should be atomic. * * \see fsal_api.h for more information * * @returns: FSAL status */ static fsal_status_t mknode(struct fsal_obj_handle *directoryHandle, const char *name, object_file_type_t nodeType, struct fsal_attrlist *attributesToSet, struct fsal_obj_handle **createdObject, struct fsal_attrlist *attributes, struct fsal_attrlist *parentPreAttributes, struct fsal_attrlist *parentPostAttributes) { struct SaunaFSExport *export = NULL; struct SaunaFSHandle *directory = NULL; struct SaunaFSHandle *handle = NULL; struct sau_entry entry; mode_t unixMode = 0; dev_t unixDev = 0; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); directory = container_of(directoryHandle, struct SaunaFSHandle, handle); LogFullDebug(COMPONENT_FSAL, "export = %" PRIu16 " parent_inode = %" PRIu32 " mode = %" PRIo32 " name = %s", export->export.export_id, directory->inode, attributesToSet->mode, name); unixMode = fsal2unix_mode(attributesToSet->mode) & ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export); switch (nodeType) { case BLOCK_FILE: unixMode |= S_IFBLK; unixDev = makedev(attributesToSet->rawdev.major, attributesToSet->rawdev.minor); break; case CHARACTER_FILE: unixMode |= S_IFCHR; unixDev = makedev(attributesToSet->rawdev.major, attributesToSet->rawdev.minor); break; case FIFO_FILE: unixMode |= S_IFIFO; break; case SOCKET_FILE: unixMode |= S_IFSOCK; break; default: LogMajor(COMPONENT_FSAL, "Invalid node type in FSAL_mknode: %d", nodeType); return fsalstat(ERR_FSAL_INVAL, EINVAL); } int retvalue = saunafs_mknode(export->fsInstance, &op_ctx->creds, directory->inode, name, unixMode, unixDev, &entry); if (retvalue < 0) return fsalLastError(); handle = allocateHandle(&entry.attr, export); *createdObject = &handle->handle; /* We handled the mode above */ FSAL_UNSET_MASK(attributesToSet->valid_mask, (attrmask_t)ATTR_MODE); if (attributesToSet->valid_mask) { fsal_status_t status; /* Setting attributes for the created object */ status = (*createdObject) ->obj_ops->setattr2(*createdObject, false, NULL, attributesToSet); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "setattr2 status = %s", fsal_err_txt(status)); /* Release the handle we just allocated */ (*createdObject)->obj_ops->release(*createdObject); *createdObject = NULL; } } else if (attributes != NULL) { /* Since we haven't set any attributes other than what was set * on create, just use the stat results we used to create the * fsal_obj_handle */ posix2fsal_attributes_all(&entry.attr, attributes); } FSAL_SET_MASK(attributesToSet->valid_mask, (attrmask_t)ATTR_MODE); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Read the content of a link. * * File object operations. * This function reads the content of a symbolic link. The FSAL will * allocate a buffer and store its address and the link length in the * link_content gsh_buffdesc. The caller must free this buffer with * gsh_free. * * The symlink content passed back must be null terminated and the length * indicated in the buffer description must include the terminator. * * @param [in] objectHandle Link to read * @param [out] buffer Buffer descriptor to which the FSAL will * store the address of the buffer holding the link * and the link length * @param [out] refresh true if the content are to be retrieved from * the underlying filesystem rather than cache * * @returns: FSAL status */ static fsal_status_t readlink_(struct fsal_obj_handle *objectHandle, struct gsh_buffdesc *buffer, bool refresh) { (void)refresh; struct SaunaFSExport *export = NULL; struct SaunaFSHandle *handle = NULL; char result[SAUNAFS_MAX_READLINK_LENGTH]; if (objectHandle->type != SYMBOLIC_LINK) return fsalstat(ERR_FSAL_FAULT, 0); export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); handle = container_of(objectHandle, struct SaunaFSHandle, handle); LogFullDebug(COMPONENT_FSAL, "export = %" PRIu16 " inode = %" PRIu32, export->export.export_id, handle->inode); int size = saunafs_readlink(export->fsInstance, &op_ctx->creds, handle->inode, result, SAUNAFS_MAX_READLINK_LENGTH); /* saunafs_readlink() returns the size of the read link if succeed. * Otherwise returns -1 to indicate an error occurred */ if (size < 0) return fsalLastError(); size = MIN(size, SAUNAFS_MAX_READLINK_LENGTH); buffer->addr = gsh_strldup(result, size, &buffer->len); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Return open status of a state. * * This function returns open flags representing the current open status * for a state. The st_lock must be held. * * @param [in] objectHandle File owning state * @param [in] state File state to interrogate * * @returns: Flags representing current open status */ static fsal_openflags_t status2(struct fsal_obj_handle *objectHandle, struct state_t *state) { (void)objectHandle; struct SaunaFSFd *sfsFd = &((struct SaunaFSStateFd *)state)->saunafsFd; return sfsFd->fsalFd.openflags; } /** * @brief Merge a duplicate handle with an original handle. * * This function is used if an upper layer detects that a duplicate object * handle has been created. It allows the FSAL to merge anything from the * duplicate back into the original. * * The caller must release the object (the caller may have to close files * if the merge is unsuccessful). * * @param [in] originalHandle Original handle * @param [in] toMergeHandle Handle to merge into original * * @returns: FSAL status */ static fsal_status_t merge(struct fsal_obj_handle *originalHandle, struct fsal_obj_handle *toMergeHandle) { fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0); if (originalHandle->type == REGULAR_FILE && toMergeHandle->type == REGULAR_FILE) { /* We need to merge the share reservations on this file. * This could result in ERR_FSAL_SHARE_DENIED. */ struct SaunaFSHandle *original = NULL; struct SaunaFSHandle *toMerge = NULL; original = container_of(originalHandle, struct SaunaFSHandle, handle); toMerge = container_of(toMergeHandle, struct SaunaFSHandle, handle); /* This can block over an I/O operation */ status = merge_share(originalHandle, &original->share, &toMerge->share); } return status; } /** * @brief Reserve/Deallocate space in a region of a file. * * @param [in] objectHandle File to which bytes should be allocated * @param [in] state Open stateid under which to do the * allocation * @param [in] offset Offset at which to begin the allocation * @param [in] length Length of the data to be allocated * @param [in] allocate Should space be allocated or deallocated? * * @returns: FSAL status */ static fsal_status_t fallocate_(struct fsal_obj_handle *objectHandle, struct state_t *state, uint64_t offset, uint64_t length, bool allocate) { struct SaunaFSExport *export = NULL; struct SaunaFSHandle *handle = NULL; fsal_status_t status = { 0, 0 }; fsal_status_t status2 = { 0, 0 }; struct SaunaFSFd *fileDescriptor = NULL; struct SaunaFSFd emptyFileDescriptor = { FSAL_FD_INIT, NULL }; struct fsal_fd *outFileDescriptor = NULL; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); handle = container_of(objectHandle, struct SaunaFSHandle, handle); /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&outFileDescriptor, objectHandle, &handle->fd.fsalFd, &emptyFileDescriptor.fsalFd, state, FSAL_O_WRITE, false, NULL, false, &handle->share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); return status; } struct stat posixAttributes; memset(&posixAttributes, 0, sizeof(posixAttributes)); posixAttributes.st_mode = allocate ? 0 : FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE; /* Get stat to obtain the current size */ sau_attr_reply_t currentStats; sau_attr_reply_t reply; int retvalue = saunafs_getattr(export->fsInstance, &op_ctx->creds, handle->inode, ¤tStats); if (retvalue < 0) { status2 = fsal_complete_io(objectHandle, outFileDescriptor); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (state == NULL) { /* We did I/O without a state so we need to release * the temp share reservation acquired. */ /* Release the share reservation now by updating * the counters. */ update_share_counters_locked(objectHandle, &handle->share, FSAL_O_WRITE, FSAL_O_CLOSED); } return fsalLastError(); } fileDescriptor = container_of(outFileDescriptor, struct SaunaFSFd, fsalFd); if (allocate) { /* Allocate */ if (offset + length > currentStats.attr.st_size) { posixAttributes.st_size = offset + length; retvalue = saunafs_setattr(export->fsInstance, &op_ctx->creds, handle->inode, &posixAttributes, SAU_SET_ATTR_SIZE, &reply); if (retvalue < 0) { status2 = fsal_complete_io(objectHandle, outFileDescriptor); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (state == NULL) { /* We did I/O without a state so we need * to release the * temp share reservation acquired. */ /* Release the share reservation now by * updating the counters. */ update_share_counters_locked( objectHandle, &handle->share, FSAL_O_WRITE, FSAL_O_CLOSED); } return fsalLastError(); } retvalue = saunafs_fsync(export->fsInstance, &op_ctx->creds, fileDescriptor->fd); if (retvalue < 0) status = fsalLastError(); } } else if (allocate == false) { /* Deallocate */ /* Initialize the zero-buffer */ void *buffer = malloc(length); memset(buffer, 0, length); /* Write the interval [offset..offset + length] with zeros */ ssize_t bytes = saunafs_write(export->fsInstance, &op_ctx->creds, fileDescriptor->fd, offset, length, buffer); free(buffer); if (bytes < 0) { status2 = fsal_complete_io(objectHandle, outFileDescriptor); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (state == NULL) { /* We did I/O without a state so we need * to release the * temp share reservation acquired. */ /* Release the share reservation now by * updating the counters. */ update_share_counters_locked(objectHandle, &handle->share, FSAL_O_WRITE, FSAL_O_CLOSED); } return fsalLastError(); } /* Set the original size because deallocation must not change * file size */ posixAttributes.st_size = currentStats.attr.st_size; retvalue = saunafs_setattr(export->fsInstance, &op_ctx->creds, handle->inode, &posixAttributes, SAU_SET_ATTR_SIZE, &reply); if (retvalue < 0) { status2 = fsal_complete_io(objectHandle, outFileDescriptor); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (state == NULL) { /* We did I/O without a state so we need * to release the * temp share reservation acquired. */ /* Release the share reservation now by * updating the counters. */ update_share_counters_locked(objectHandle, &handle->share, FSAL_O_WRITE, FSAL_O_CLOSED); } return fsalLastError(); } retvalue = saunafs_fsync(export->fsInstance, &op_ctx->creds, fileDescriptor->fd); if (retvalue < 0) status = fsalLastError(); } status2 = fsal_complete_io(objectHandle, outFileDescriptor); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating * the counters. */ update_share_counters_locked(objectHandle, &handle->share, FSAL_O_WRITE, FSAL_O_CLOSED); } return status; } /** * @brief Function to close a file for a given handle. * * @param[in] objectHandle File on which to operate * @param[in] fd File descriptor to be closed * * @return FSAL status. */ static fsal_status_t close_func(struct fsal_obj_handle *objectHandle, struct fsal_fd *fd) { struct SaunaFSHandle *handle = NULL; handle = container_of(objectHandle, struct SaunaFSHandle, handle); return closeFileDescriptor(handle, (struct SaunaFSFd *)fd); } /** * @brief Get extended attribute. * * This function gets an extended attribute of an object. * * @param [in] objectHandle Input object to query * @param [in] xattributeName Input extended attribute name * @param [out] xattributeValue Output extended attribute value * * @returns: FSAL status */ static fsal_status_t getxattrs(struct fsal_obj_handle *objectHandle, xattrkey4 *xattributeName, xattrvalue4 *xattributeValue) { struct SaunaFSExport *export = NULL; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); struct SaunaFSHandle *handle = NULL; handle = container_of(objectHandle, struct SaunaFSHandle, handle); size_t curr_size = 0; int retvalue = saunafs_getxattr( export->fsInstance, &op_ctx->creds, handle->inode, xattributeName->utf8string_val, xattributeValue->utf8string_len, &curr_size, (uint8_t *)xattributeValue->utf8string_val); if (retvalue < 0) { LogFullDebug(COMPONENT_FSAL, "GETXATTRS failed returned rc = %d ", retvalue); return saunafsToFsalError(retvalue); } if (curr_size && curr_size <= xattributeValue->utf8string_len) { /* Updating the real size */ xattributeValue->utf8string_len = curr_size; /* Make sure utf8string is NULL terminated */ xattributeValue->utf8string_val[curr_size] = '\0'; } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Set extended attribute. * * This function sets an extended attribute of an object. * * @param [in] objectHandle Input object to set * @param [in] option Input extended attribute type * @param [in] xattributeName Input extended attribute name to set * @param [in] xattributeValue Input extended attribute value to set * * @returns: FSAL status */ static fsal_status_t setxattrs(struct fsal_obj_handle *objectHandle, setxattr_option4 option, xattrkey4 *xattributeName, xattrvalue4 *xattributeValue) { struct SaunaFSExport *export = NULL; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); struct SaunaFSHandle *handle = NULL; handle = container_of(objectHandle, struct SaunaFSHandle, handle); int retvalue = saunafs_setxattr( export->fsInstance, &op_ctx->creds, handle->inode, xattributeName->utf8string_val, (const uint8_t *)xattributeValue->utf8string_val, xattributeValue->utf8string_len, option); if (retvalue < 0) { LogDebug(COMPONENT_FSAL, "SETXATTRS returned rc %d", retvalue); return saunafsToFsalError(retvalue); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief List extended attributes. * * This function lists the extended attributes of an object. * * @param [in] objectHandle Input object to list * @param [in] maximumNameSize Input maximum number of bytes for * names * @param [in,out] cookie In/out cookie * @param [out] eof Output eof set if no more extended * attributes * @param [out] xattributesNames Output list of extended attribute * names this buffer size is double the * size of maximumNameSize to allow * for component4 overhead * * @returns: FSAL status */ static fsal_status_t listxattrs(struct fsal_obj_handle *objectHandle, count4 maximumNameSize, nfs_cookie4 *cookie, bool_t *eof, xattrlist4 *xattributesNames) { char *buffer = NULL; fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0); struct SaunaFSExport *export = NULL; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); struct SaunaFSHandle *handle = NULL; handle = container_of(objectHandle, struct SaunaFSHandle, handle); LogFullDebug(COMPONENT_FSAL, "in cookie %llu length %d", (unsigned long long)*cookie, maximumNameSize); /* Size of list of extended attributes */ size_t curr_size = 0; /* First time, the function is called to get the size of xattr list */ int retvalue = saunafs_listxattr(export->fsInstance, &op_ctx->creds, handle->inode, 0, &curr_size, NULL); if (retvalue < 0) { LogDebug(COMPONENT_FSAL, "LISTXATTRS returned rc %d", retvalue); return saunafsToFsalError(retvalue); } /* If xattr were retrieved and they can be allocated */ if (curr_size && curr_size < maximumNameSize) { buffer = gsh_malloc(curr_size); /* Second time the function is called to retrieve * the xattr list */ retvalue = saunafs_listxattr(export->fsInstance, &op_ctx->creds, handle->inode, curr_size, &curr_size, buffer); if (retvalue < 0) { LogDebug(COMPONENT_FSAL, "LISTXATTRS returned rc %d", retvalue); gsh_free(buffer); return saunafsToFsalError(retvalue); } /* Setting retrieved extended attributes to Ganesha */ status = fsal_listxattr_helper(buffer, curr_size, maximumNameSize, cookie, eof, xattributesNames); /* Releasing allocated buffer */ gsh_free(buffer); } return status; } /** * @brief Remove extended attribute. * * This function removes an extended attribute of an object. * * @param [in] objectHandle Input object to set * @param [in] xattributeName Input xattr name to remove * * @returns: FSAL status */ static fsal_status_t removexattrs(struct fsal_obj_handle *objectHandle, xattrkey4 *xattributeName) { struct SaunaFSExport *export = NULL; export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); struct SaunaFSHandle *handle = NULL; handle = container_of(objectHandle, struct SaunaFSHandle, handle); int retvalue = saunafs_removexattr(export->fsInstance, &op_ctx->creds, handle->inode, xattributeName->utf8string_val); if (retvalue < 0) { LogFullDebug(COMPONENT_FSAL, "REMOVEXATTR returned rc %d", retvalue); return saunafsToFsalError(retvalue); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } void handleOperationsInit(struct fsal_obj_ops *ops) { fsal_default_obj_ops_init(ops); ops->release = release; ops->lookup = lookup; ops->readdir = readdir_; ops->getattrs = getattrs; ops->handle_to_wire = handle_to_wire; ops->handle_to_key = handle_to_key; ops->open2 = open2; ops->read2 = read2; ops->mkdir = mkdir_; ops->link = link_; ops->rename = rename_; ops->unlink = unlink_; ops->close = close_; ops->write2 = write2; ops->commit2 = commit2; ops->setattr2 = setattr2; ops->close2 = close2; ops->symlink = symlink_; ops->lock_op2 = lock_op2; ops->close_func = close_func; ops->reopen_func = reopen_func; ops->reopen2 = reopen2; ops->mknode = mknode; ops->readlink = readlink_; ops->status2 = status2; ops->merge = merge; ops->fallocate = fallocate_; ops->getxattrs = getxattrs; ops->setxattrs = setxattrs; ops->listxattrs = listxattrs; ops->removexattrs = removexattrs; } /** * @brief Allocate a new file handle. * * This function constructs a new SaunaFS FSAL object handle and attaches * it to the export. After this call the attributes have been filled * in and the handdle is up-to-date and usable. * * @param[in] attribute stat attributes for the handle * @param[in] export The export on which the object lives * * @returns: saunafs_handle instance or NULL. */ struct SaunaFSHandle *allocateHandle(const struct stat *attribute, struct SaunaFSExport *export) { struct SaunaFSHandle *result = NULL; result = gsh_calloc(1, sizeof(struct SaunaFSHandle)); result->inode = attribute->st_ino; result->key.moduleId = FSAL_ID_SAUNAFS; result->key.exportId = export->export.export_id; result->key.inode = attribute->st_ino; fsal_obj_handle_init(&result->handle, &export->export, posix2fsal_type(attribute->st_mode), true); result->handle.obj_ops = &SaunaFS.handleOperations; result->handle.fsid = posix2fsal_fsid(attribute->st_dev); result->handle.fileid = attribute->st_ino; result->export = export; if (result->handle.type == REGULAR_FILE) { init_fsal_fd(&result->fd.fsalFd, FSAL_FD_GLOBAL, op_ctx->fsal_export); } return result; } /** * @brief Release all resources for a handle. * * @param[in] object Handle to release */ void deleteHandle(struct SaunaFSHandle *object) { fsal_obj_handle_fini(&object->handle, true); gsh_free(object); } nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/main.c000066400000000000000000000321411473756622300201070ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* Copyright 2017 Skytechnology sp. z o.o. Copyright 2023 Leil Storage OÜ This file is part of SaunaFS. SaunaFS 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, version 3. SaunaFS 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 SaunaFS. If not, see . */ #include "fsal_types.h" #include "FSAL/fsal_init.h" #include "FSAL/fsal_commonlib.h" #include "fsal_api.h" #include "pnfs_utils.h" #include "context_wrap.h" #include "saunafs_fsal_types.h" #include "saunafs_internal.h" /* FSAL name determines name of shared library: libfsalsaunafs.so */ static const char *const module = "SaunaFS"; static const int millisecondsInOneSecond = 1000; /** * my module private storage */ struct SaunaFSModule SaunaFS = { .fsal = { .fs_info = { .maxfilesize = UINT64_MAX, .maxlink = _POSIX_LINK_MAX, .maxnamelen = SFS_NAME_MAX, .maxpathlen = MAXPATHLEN, .no_trunc = true, .chown_restricted = false, .case_insensitive = false, .case_preserving = true, .link_support = true, .symlink_support = true, .lock_support = true, .lock_support_async_block = false, .named_attr = true, .unique_handles = true, #ifdef ENABLE_NFS_ACL_SUPPORT .acl_support = (unsigned int)FSAL_ACLSUPPORT_ALLOW | (unsigned int)FSAL_ACLSUPPORT_DENY, #else .acl_support = 0, #endif .cansettime = true, .homogenous = true, .supported_attrs = SAUNAFS_SUPPORTED_ATTRS, .maxread = (uint64_t)FSAL_MAXIOSIZE, .maxwrite = (uint64_t)FSAL_MAXIOSIZE, .umask = 0, .auth_exportpath_xdev = false, .pnfs_mds = true, .pnfs_ds = true, .fsal_trace = false, .fsal_grace = false, .link_supports_permission_checks = true, .xattr_support = true, } } }; static struct config_item export_params[] = { CONF_ITEM_MODE("umask", 0, fsal_staticfsinfo_t, umask), CONF_ITEM_BOOL("link_support", true, fsal_staticfsinfo_t, link_support), CONF_ITEM_BOOL("symlink_support", true, fsal_staticfsinfo_t, symlink_support), CONF_ITEM_BOOL("cansettime", true, fsal_staticfsinfo_t, cansettime), CONF_ITEM_BOOL("auth_xdev_export", false, fsal_staticfsinfo_t, auth_exportpath_xdev), CONF_ITEM_UI64("maxread", 512, (uint64_t)FSAL_MAXIOSIZE, (uint64_t)FSAL_MAXIOSIZE, fsal_staticfsinfo_t, maxread), CONF_ITEM_UI64("maxwrite", 512, (uint64_t)FSAL_MAXIOSIZE, (uint64_t)FSAL_MAXIOSIZE, fsal_staticfsinfo_t, maxwrite), CONF_ITEM_BOOL("PNFS_MDS", false, fsal_staticfsinfo_t, pnfs_mds), CONF_ITEM_BOOL("PNFS_DS", false, fsal_staticfsinfo_t, pnfs_ds), CONF_ITEM_BOOL("fsal_trace", true, fsal_staticfsinfo_t, fsal_trace), CONF_ITEM_BOOL("fsal_grace", false, fsal_staticfsinfo_t, fsal_grace), CONFIG_EOL }; static struct config_block export_param = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.saunafs", .blk_desc.name = "SaunaFS", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = export_params, .blk_desc.u.blk.commit = noop_conf_commit }; static struct config_item fsal_export_params[] = { CONF_ITEM_NOOP("name"), CONF_MAND_STR("hostname", 1, MAXPATHLEN, NULL, SaunaFSExport, parameters.host), CONF_ITEM_STR("port", 1, MAXPATHLEN, "9421", SaunaFSExport, parameters.port), CONF_ITEM_STR("mountpoint", 1, MAXPATHLEN, "nfs-ganesha", SaunaFSExport, parameters.mountpoint), CONF_ITEM_STR("subfolder", 1, MAXPATHLEN, "/", SaunaFSExport, parameters.subfolder), CONF_ITEM_BOOL("delayed_init", false, SaunaFSExport, parameters.delayed_init), CONF_ITEM_UI32("io_retries", 0, 1024, 30, SaunaFSExport, parameters.io_retries), CONF_ITEM_UI32("chunkserver_round_time_ms", 0, 65536, 200, SaunaFSExport, parameters.chunkserver_round_time_ms), CONF_ITEM_UI32("chunkserver_connect_timeout_ms", 0, 65536, 2000, SaunaFSExport, parameters.chunkserver_connect_timeout_ms), CONF_ITEM_UI32("chunkserver_wave_read_timeout_ms", 0, 65536, 500, SaunaFSExport, parameters.chunkserver_wave_read_timeout_ms), CONF_ITEM_UI32("total_read_timeout_ms", 0, 65536, 2000, SaunaFSExport, parameters.total_read_timeout_ms), CONF_ITEM_UI32("cache_expiration_time_ms", 0, 65536, 1000, SaunaFSExport, parameters.cache_expiration_time_ms), CONF_ITEM_UI32("readahead_max_window_size_kB", 0, 65536, 16384, SaunaFSExport, parameters.readahead_max_window_size_kB), CONF_ITEM_UI32("write_cache_size", 0, 1024, 64, SaunaFSExport, parameters.write_cache_size), CONF_ITEM_UI32("write_workers", 0, 32, 10, SaunaFSExport, parameters.write_workers), CONF_ITEM_UI32("write_window_size", 0, 256, 32, SaunaFSExport, parameters.write_window_size), CONF_ITEM_UI32("chunkserver_write_timeout_ms", 0, 60000, 5000, SaunaFSExport, parameters.chunkserver_write_timeout_ms), CONF_ITEM_UI32("cache_per_inode_percentage", 0, 80, 25, SaunaFSExport, parameters.cache_per_inode_percentage), CONF_ITEM_UI32("symlink_cache_timeout_s", 0, 60000, 3600, SaunaFSExport, parameters.symlink_cache_timeout_s), CONF_ITEM_BOOL("debug_mode", false, SaunaFSExport, parameters.debug_mode), CONF_ITEM_I32("keep_cache", 0, 2, 0, SaunaFSExport, parameters.keep_cache), CONF_ITEM_BOOL("verbose", false, SaunaFSExport, parameters.verbose), CONF_ITEM_UI32("fileinfo_cache_timeout", 1, 3600, 60, SaunaFSExport, cacheTimeout), CONF_ITEM_UI32("fileinfo_cache_max_size", 100, 1000000, 1000, SaunaFSExport, cacheMaximumSize), CONF_ITEM_STR("password", 1, 128, NULL, SaunaFSExport, parameters.password), CONF_ITEM_STR("md5_pass", 32, 32, NULL, SaunaFSExport, parameters.md5_pass), CONFIG_EOL }; static struct config_block fsal_export_param_block = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.saunafs-export%d", .blk_desc.name = "FSAL", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = fsal_export_params, .blk_desc.u.blk.commit = noop_conf_commit }; /** * @brief Release an export * * @param[in] export SaunaFS export */ static inline void releaseExport(struct SaunaFSExport *export) { if (export) { if (export->fsInstance) sau_destroy(export->fsInstance); if (export->cache) destroyFileInfoCache(export->cache); gsh_free(export); } } /** * @brief Create a new export. * * This function creates a new export in the FSAL using the supplied path and * options. The function is expected to allocate its own export (the full, * private structure). * * @param [in] module FSAL module. * @param [in] parseNode Opaque pointer to parse tree node for * export options to be passed to * load_config_from_node. * @param [out] errorType Config processing error reporting. * @param [in] operations Upcall operations. * * \see fsal_api.h for more information * * @returns: FSAL status */ static fsal_status_t createExport(struct fsal_module *module, void *parseNode, struct config_error_type *errorType, const struct fsal_up_vector *operations) { fsal_status_t status; struct fsal_pnfs_ds *pnfsDs = NULL; int retvalue = 0; struct SaunaFSExport *export = gsh_calloc(1, sizeof(struct SaunaFSExport)); fsal_export_init(&export->export); exportOperationsInit(&export->export.exp_ops); /* parse parameters for this export */ sau_set_default_init_params(&export->parameters, "", "", ""); if (parseNode) { retvalue = load_config_from_node(parseNode, &fsal_export_param_block, export, true, errorType); if (retvalue != 0) { LogCrit(COMPONENT_FSAL, "Failed to parse export configuration for %s", CTX_FULLPATH(op_ctx)); releaseExport(export); return fsalstat(ERR_FSAL_INVAL, 0); } } export->parameters.subfolder = gsh_strdup(CTX_FULLPATH(op_ctx)); export->fsInstance = sau_init_with_params(&export->parameters); if (export->fsInstance == NULL) { LogCrit(COMPONENT_FSAL, "Unable to mount SaunaFS cluster for %s.", CTX_FULLPATH(op_ctx)); releaseExport(export); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } if (fsal_attach_export(module, &export->export.exports) != 0) { LogCrit(COMPONENT_FSAL, "Unable to attach export for %s.", CTX_FULLPATH(op_ctx)); releaseExport(export); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } export->export.fsal = module; export->export.up_ops = operations; export->pnfsDsEnabled = export->export.exp_ops.fs_supports( &export->export, fso_pnfs_ds_supported); if (export->pnfsDsEnabled) { export->cache = createFileInfoCache( export->cacheMaximumSize, (int)export->cacheTimeout * millisecondsInOneSecond); if (export->cache == NULL) { LogCrit(COMPONENT_FSAL, "Unable to create fileinfo cache for %s.", CTX_FULLPATH(op_ctx)); releaseExport(export); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } status = module->m_ops.create_fsal_pnfs_ds(module, parseNode, &pnfsDs); if (status.major != ERR_FSAL_NO_ERROR) { releaseExport(export); return status; } /* special case: server_id matches export_id */ pnfsDs->id_servers = op_ctx->ctx_export->export_id; pnfsDs->mds_export = op_ctx->ctx_export; pnfsDs->mds_fsal_export = &export->export; if (!pnfs_ds_insert(pnfsDs)) { LogCrit(COMPONENT_CONFIG, "Server id %d already in use.", pnfsDs->id_servers); status.major = ERR_FSAL_EXIST; /* Return the ref taken by create_fsal_pnfs_ds */ pnfs_ds_put(pnfsDs); releaseExport(export); return status; } LogDebug(COMPONENT_PNFS, "pnfs ds was enabled for [%s]", CTX_FULLPATH(op_ctx)); } export->pnfsMdsEnabled = export->export.exp_ops.fs_supports( &export->export, fso_pnfs_mds_supported); if (export->pnfsMdsEnabled) { LogDebug(COMPONENT_PNFS, "pnfs mds was enabled for [%s]", CTX_FULLPATH(op_ctx)); exportOperationsPnfs(&export->export.exp_ops); } /* get attributes for root inode */ sau_attr_reply_t reply; retvalue = saunafs_getattr(export->fsInstance, &op_ctx->creds, SPECIAL_INODE_ROOT, &reply); if (retvalue < 0) { status = fsalLastError(); if (pnfsDs != NULL) { /* Remove and destroy the fsal_pnfs_ds */ pnfs_ds_remove(pnfsDs->id_servers); } if (pnfsDs != NULL) { /* Return the ref taken by create_fsal_pnfs_ds */ pnfs_ds_put(pnfsDs); } releaseExport(export); return status; } export->root = allocateHandle(&reply.attr, export); op_ctx->fsal_export = &export->export; LogDebug(COMPONENT_FSAL, "SaunaFS module export %s.", CTX_FULLPATH(op_ctx)); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Initialize the configuration. * * @param [in] module FSAL module. * @param [in] configFile Parsed ganesha configuration file. * @param [out] errorType config processing error reporting. * * @returns: FSAL status */ static fsal_status_t initialize(struct fsal_module *module, config_file_t configFile, struct config_error_type *errorType) { struct SaunaFSModule *myself = NULL; myself = container_of(module, struct SaunaFSModule, fsal); (void)load_config_from_parse(configFile, &export_param, &myself->filesystemInfo, true, errorType); if (!config_error_is_harmless(errorType)) { LogDebug(COMPONENT_FSAL, "config_error_is_harmless failed."); return fsalstat(ERR_FSAL_INVAL, 0); } display_fsinfo(&myself->fsal); LogDebug(COMPONENT_FSAL, "FSAL INIT: Supported attributes mask = 0x%" PRIx64, myself->fsal.fs_info.supported_attrs); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Initialize and register SaunaFS FSAL * * Module initialization. * Called by dlopen() to register the module * keep a private pointer to me in myself */ MODULE_INIT void initializeSaunaFS(void) { struct fsal_module *myself = &SaunaFS.fsal; int retval = register_fsal(myself, module, FSAL_MAJOR_VERSION, FSAL_MINOR_VERSION, FSAL_ID_SAUNAFS); if (retval) { LogCrit(COMPONENT_FSAL, "SaunaFS module failed to register."); return; } /* Set up module operations */ myself->m_ops.create_export = createExport; myself->m_ops.init_config = initialize; myself->m_ops.fsal_pnfs_ds_ops = pnfsDsOperationsInit; pnfsMdsOperationsInit(&myself->m_ops); /* Initialize fsal_obj_handle ops for FSAL SaunaFS */ handleOperationsInit(&SaunaFS.handleOperations); } /** * @brief Release FSAL resources * * This function unregisters the FSAL and frees its module handle. */ MODULE_FINI void finish(void) { int retval = unregister_fsal(&SaunaFS.fsal); if (retval != 0) { LogCrit(COMPONENT_FSAL, "Unable to unload SaunaFS FSAL. Dying with extreme prejudice."); abort(); } } nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/mds_export.c000066400000000000000000000523541473756622300213570ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* Copyright 2017 Skytechnology sp. z o.o. Copyright 2023 Leil Storage OÜ This file is part of SaunaFS. SaunaFS 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, version 3. SaunaFS 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 SaunaFS. If not, see . */ #include "gsh_config.h" #include "saunafs_fsal_types.h" #include "pnfs_utils.h" #include #include "context_wrap.h" #include "saunafs/saunafs_c_api.h" const int MaxBufferSize = 0x100; const int ChunkAddressSizeInBytes = 40; const int ChunkDataOverhead = 32; /** * @brief Comparison function for based-ip sorting of chunkservers. * * This function sorts chunkserver in ascending order of their ip attribute. * * @param[in] chunkserverA void* pointer to chunkserver A * @param[in] chunkserverB void* pointer to chunkserver B * * @returns: Integer number representing the natural order of chunkserver * * @retval value < 0: chunkserverA's ip is less than chunkserverB's ip * @retval value > 0: chunkserverA's ip is greater than chunkserverB's ip * @retval 0: Both chunkservers have the same ip */ static int ascendingIpCompare(const void *chunkserverA, const void *chunkserverB) { uint32_t ipFromChunkserverA = ((const sau_chunkserver_info_t *)chunkserverA)->ip; uint32_t ipFromChunkserverB = ((const sau_chunkserver_info_t *)chunkserverB)->ip; return ipFromChunkserverA - ipFromChunkserverB; } /** * @brief Check if one chunkserver is disconnected. * * @param[in] chunkserver void* pointer to chunkserver instance * * @returns: Integer number representing if chunkserver is disconnected * * @retval 0: chunkserver is connected * @retval 1: chunkserver is disconnected */ static int isChunkserverDisconnected(const void *chunkserver, void *unused) { (void)unused; return ((const sau_chunkserver_info_t *)chunkserver)->version == kDisconnectedChunkServerVersion; } /** * @brief Check if two adjacent chunkservers have the same ip. * * @param[in] chunkserver void* pointer to chunkserver instance * @param[in] base void* pointer to the first chunkserver of the * collection * * @returns: Integer number representing if chunkserver instance and * its predecessor have the same ip. * * @retval 0: chunkserver and its predecessor doesn't have the same ip * @retval 1: chunkserver and its predecessor have the same ip */ static int adjacentChunkserversWithSameIp(const void *chunkserver, void *base) { if (chunkserver == base) return 0; return ((const sau_chunkserver_info_t *)chunkserver)->ip == ((const sau_chunkserver_info_t *)chunkserver - 1)->ip; } /** * @brief Remove chunkservers if predicate is successfully evaluated. * * @param[in,out] data void* pointer to the chunkservers collection * @param[in] size Size of the collection * @param[in] itemSize Size of each item of the collection * @param[in] predicate Predicate to evaluate * @param[in] targetData void* pointer to targetData * * @returns: Number of removed elements. */ static size_t remove_if(void *data, size_t size, size_t itemSize, int (*predicate)(const void *chunkserver, void *targetData), void *targetData) { size_t step = 0; for (size_t i = 0; i < size; ++i) { if (!predicate((uint8_t *)data + i * itemSize, targetData)) { memcpy((uint8_t *)data + i * itemSize, (uint8_t *)data + step * itemSize, itemSize); step++; } } return step; } /** * @brief Randomly rearrange the elements of a collection. * * @param[in,out] data void* pointer to the collection * @param[in] size Size of the collection * @param[in] itemSize Size of each item of the collection */ static void shuffle(void *data, size_t size, size_t itemSize) { uint8_t temp[itemSize]; if (size == 0) return; srandom(time(NULL)); for (size_t index = 0; index < size - 1; ++index) { size_t start = index + random() % (size - index); memcpy(temp, (uint8_t *)data + index * itemSize, itemSize); memcpy((uint8_t *)data + index * itemSize, (uint8_t *)data + start * itemSize, itemSize); memcpy((uint8_t *)data + start * itemSize, temp, itemSize); } } /** * @brief Randomly rearrange the list of chunkservers. * * @param[in] export Export where the chunkservers live on * @param[in] chunkserverCount Pointer to store the number of chunkservers * * @returns: Randomized chunkservers list. */ static sau_chunkserver_info_t * randomizedChunkserverList(struct SaunaFSExport *export, uint32_t *chunkserverCount) { sau_chunkserver_info_t *chunkserverInfo = NULL; chunkserverInfo = gsh_malloc(SAUNAFS_BIGGEST_STRIPE_COUNT * sizeof(sau_chunkserver_info_t)); int retvalue = sau_get_chunkservers_info(export->fsInstance, chunkserverInfo, SAUNAFS_BIGGEST_STRIPE_COUNT, chunkserverCount); if (retvalue < 0) { *chunkserverCount = 0; gsh_free(chunkserverInfo); return NULL; } /* Free labels, we don't need them. */ sau_destroy_chunkservers_info(chunkserverInfo); /* remove disconnected */ *chunkserverCount = remove_if(chunkserverInfo, *chunkserverCount, sizeof(sau_chunkserver_info_t), isChunkserverDisconnected, NULL); /* sorting chunkservers based on its ip attribute */ qsort(chunkserverInfo, *chunkserverCount, sizeof(sau_chunkserver_info_t), ascendingIpCompare); /* remove entries with the same ip */ *chunkserverCount = remove_if(chunkserverInfo, *chunkserverCount, sizeof(sau_chunkserver_info_t), adjacentChunkserversWithSameIp, chunkserverInfo); /* randomize */ shuffle(chunkserverInfo, *chunkserverCount, sizeof(sau_chunkserver_info_t)); return chunkserverInfo; } /** * @brief Fill Data Server list with entries corresponding to chunks. * * @param[in] export Export where the chunkservers live on * @param[in] chunkserverCount Pointer to store the number of chunkservers * * @returns: Operation status. * * @retval 0: Successful operation * @retval -1: Failing operation */ static int fillChunkDataServerList(XDR *da_addr_body, sau_chunk_info_t *chunkInfo, sau_chunkserver_info_t *chunkserverInfo, uint32_t chunkCount, uint32_t stripeCount, uint32_t chunkserverCount, uint32_t *chunkserverIndex) { fsal_multipath_member_t host[SAUNAFS_EXPECTED_BACKUP_DS_COUNT]; uint32_t size = MIN(chunkCount, stripeCount); const int upperBound = SAUNAFS_EXPECTED_BACKUP_DS_COUNT; for (uint32_t chunkIndex = 0; chunkIndex < size; ++chunkIndex) { sau_chunk_info_t *chunk = &chunkInfo[chunkIndex]; int serverCount = 0; memset(host, 0, upperBound * sizeof(fsal_multipath_member_t)); /* prefer std chunk part type */ for (size_t i = 0; i < chunk->parts_size && serverCount < upperBound; ++i) { if (chunk->parts[i].part_type_id != SAUNAFS_STD_CHUNK_PART_TYPE) { continue; } host[serverCount].proto = TCP_PROTO_NUMBER; host[serverCount].addr = chunk->parts[i].addr; host[serverCount].port = NFS_PORT; ++serverCount; } for (size_t i = 0; i < chunk->parts_size && serverCount < upperBound; ++i) { if (chunk->parts[i].part_type_id == SAUNAFS_STD_CHUNK_PART_TYPE) { continue; } host[serverCount].proto = TCP_PROTO_NUMBER; host[serverCount].addr = chunk->parts[i].addr; host[serverCount].port = NFS_PORT; ++serverCount; } /* fill unused entries with the servers from randomized * chunkserver list */ while (serverCount < upperBound) { host[serverCount].proto = TCP_PROTO_NUMBER; host[serverCount].addr = chunkserverInfo[*chunkserverIndex].ip; host[serverCount].port = NFS_PORT; ++serverCount; *chunkserverIndex = (*chunkserverIndex + 1) % chunkserverCount; } /* encode ds entry */ nfsstat4 status = FSAL_encode_v4_multipath(da_addr_body, serverCount, host); if (status != NFS4_OK) return kNFS4_ERROR; } return NFS4_OK; } /** * @brief Fill unused part of DS list with servers from randomized chunkserver * list. * * @param[in] xdrStream XDR stream * @param[in] chunkserverInfo Collection of chunkservers * @param[in] chunkCount Number of chunks * @param[in] stripeCount Number of stripe * @param[in] chunkserverCount Number of chunkservers * @param[in] chunkserverIndex Index of chunkserver * * @returns: Operation status. * * @retval 0: Successful operation * @retval -1: Failing operation */ static int fillUnusedDataServerList(XDR *xdrStream, sau_chunkserver_info_t *chunkserverInfo, uint32_t chunkCount, uint32_t stripeCount, uint32_t chunkserverCount, uint32_t *chunkserverIndex) { fsal_multipath_member_t host[SAUNAFS_EXPECTED_BACKUP_DS_COUNT]; uint32_t size = MIN(chunkCount, stripeCount); const int upperBound = SAUNAFS_EXPECTED_BACKUP_DS_COUNT; for (uint32_t chunkIndex = size; chunkIndex < stripeCount; ++chunkIndex) { int serverCount = 0; int index = 0; memset(host, 0, upperBound * sizeof(fsal_multipath_member_t)); while (serverCount < SAUNAFS_EXPECTED_BACKUP_DS_COUNT) { index = (*chunkserverIndex + serverCount) % chunkserverCount; host[serverCount].proto = TCP_PROTO_NUMBER; host[serverCount].addr = chunkserverInfo[index].ip; host[serverCount].port = NFS_PORT; ++serverCount; } *chunkserverIndex = (*chunkserverIndex + 1) % chunkserverCount; nfsstat4 status = FSAL_encode_v4_multipath(xdrStream, serverCount, host); if (status != NFS4_OK) return -1; } return NFS4_OK; } /** * @brief Release resources. * * @param[in] chunkInfo Chunk information including id, type and all * parts * @param[in] chunkserverInfo Chunkserver information including version, * ip, port, used space and total space */ static void releaseResources(sau_chunk_info_t *chunkInfo, sau_chunkserver_info_t *chunkserverInfo) { if (chunkInfo) { sau_destroy_chunks_info(chunkInfo); gsh_free(chunkInfo); } if (chunkserverInfo) gsh_free(chunkserverInfo); } /** * @brief Get information about a pNFS device. * * pNFS functions. When this function is called, the FSAL should write device * information to the da_addr_body stream. * * The function converts SaunaFS file's chunk information to pNFS device info. * * Linux pNFS client imposes limit on stripe size (SAUNAFS_BIGGEST_STRIPE_COUNT * = 4096). If we would use straight forward approach of converting each chunk * to stripe entry, we would be limited to file size of 256 GB (4096 * 64MB). * * To avoid this problem each DS can read/write data from any chunk (Remember * that pNFS client takes DS address from DS list in round robin fashion). Of * course it's more efficient if DS is answering queries about chunks residing * locally. * * To achieve the best performance we fill the DS list in a following way: * * First we prepare randomized list of all chunkservers (RCSL). * Then for each chunk we fill multipath DS list entry with addresses of * chunkservers storing this chunk. If there is less chunkservers than * SAUNAFS_EXPECTED_BACKUP_DS_COUNT then we use chunkservers from RCSL. * * If we didn't use all the possible space in DS list * (SAUNAFS_BIGGEST_STRIPE_COUNT), then we fill rest of the stripe entries with * addresses from RCSL (again SAUNAFS_EXPECTED_BACKUP_DS_COUNT addresses for * each stripe entry). * * @param[in] module FSAL module * @param[out] xdrStream XDR stream to which the FSAL is to write the * layout type-specific information corresponding to * the deviceid. * @param[in] type The type of layout that specified the device * @param[in] deviceid The device to look up * * @returns: Valid error codes in RFC 5661, p. 365. */ static nfsstat4 getdeviceinfo(struct fsal_module *module, XDR *xdrStream, const layouttype4 type, const struct pnfs_deviceid *deviceid) { struct fsal_export *exportHandle = NULL; struct SaunaFSExport *export = NULL; sau_chunk_info_t *chunkInfo = NULL; sau_chunkserver_info_t *chunkserverInfo = NULL; uint32_t chunkCount = 0; uint32_t chunkserverCount = 0; uint32_t stripeCount = 0; uint32_t chunkserverIndex = 0; struct glist_head *glist = NULL; struct glist_head *glistn = NULL; if (type != LAYOUT4_NFSV4_1_FILES) { LogCrit(COMPONENT_PNFS, "Unsupported layout type: %x", type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } uint16_t export_id = deviceid->device_id2; glist_for_each_safe(glist, glistn, &module->exports) { exportHandle = glist_entry(glist, struct fsal_export, exports); if (exportHandle->export_id == export_id) { export = container_of(exportHandle, struct SaunaFSExport, export); break; } } if (!export) { LogCrit(COMPONENT_PNFS, "Couldn't find export with id: %" PRIu16, export_id); return NFS4ERR_SERVERFAULT; } /* get the chunk list for file */ chunkInfo = gsh_malloc(SAUNAFS_BIGGEST_STRIPE_COUNT * sizeof(sau_chunk_info_t)); int retvalue = saunafs_get_chunks_info( export->fsInstance, &op_ctx->creds, deviceid->devid, 0, chunkInfo, SAUNAFS_BIGGEST_STRIPE_COUNT, &chunkCount); if (retvalue < 0) { LogCrit(COMPONENT_PNFS, "Failed to get SaunaFS layout for export = %" PRIu16 " inode = %" PRIu64, export_id, deviceid->devid); releaseResources(chunkInfo, chunkserverInfo); return NFS4ERR_SERVERFAULT; } chunkserverInfo = randomizedChunkserverList(export, &chunkserverCount); if (chunkserverInfo == NULL || chunkserverCount == 0) { LogCrit(COMPONENT_PNFS, "Failed to get SaunaFS layout for export = %" PRIu16 " inode = %" PRIu64, export_id, deviceid->devid); releaseResources(chunkInfo, chunkserverInfo); return NFS4ERR_SERVERFAULT; } chunkserverIndex = 0; stripeCount = MIN(chunkCount + chunkserverCount, SAUNAFS_BIGGEST_STRIPE_COUNT); if (!inline_xdr_u_int32_t(xdrStream, &stripeCount)) { LogCrit(COMPONENT_PNFS, "Failed to encode device information for export = %" PRIu16 " inode = %" PRIu64, export_id, deviceid->devid); releaseResources(chunkInfo, chunkserverInfo); return NFS4ERR_SERVERFAULT; } for (uint32_t chunkIndex = 0; chunkIndex < stripeCount; ++chunkIndex) { if (!inline_xdr_u_int32_t(xdrStream, &chunkIndex)) { LogCrit(COMPONENT_PNFS, "Failed to encode device information for export = %" PRIu16 " inode = %" PRIu64, export_id, deviceid->devid); releaseResources(chunkInfo, chunkserverInfo); return NFS4ERR_SERVERFAULT; } } if (!inline_xdr_u_int32_t(xdrStream, &stripeCount)) { LogCrit(COMPONENT_PNFS, "Failed to encode device information for export = %" PRIu16 " inode = %" PRIu64, export_id, deviceid->devid); releaseResources(chunkInfo, chunkserverInfo); return NFS4ERR_SERVERFAULT; } retvalue = fillChunkDataServerList(xdrStream, chunkInfo, chunkserverInfo, chunkCount, stripeCount, chunkserverCount, &chunkserverIndex); if (retvalue < 0) { LogCrit(COMPONENT_PNFS, "Failed to encode device information for export = %" PRIu16 " inode = %" PRIu64, export_id, deviceid->devid); releaseResources(chunkInfo, chunkserverInfo); return NFS4ERR_SERVERFAULT; } retvalue = fillUnusedDataServerList(xdrStream, chunkserverInfo, chunkCount, stripeCount, chunkserverCount, &chunkserverIndex); if (retvalue < 0) { LogCrit(COMPONENT_PNFS, "Failed to encode device information for export = %" PRIu16 " inode = %" PRIu64, export_id, deviceid->devid); releaseResources(chunkInfo, chunkserverInfo); return NFS4ERR_SERVERFAULT; } sau_destroy_chunks_info(chunkInfo); gsh_free(chunkInfo); gsh_free(chunkserverInfo); return NFS4_OK; } /** * @brief Get list of available devices. * * This function should populate calls cb values representing the low quad of * deviceids it wishes to make the available to the caller. it should continue * calling cb until cb returns false or it runs out of deviceids to make * available. * * If cb returns false, it should assume that cb has not stored the most recent * deviceid and set res->cookie to a value that will begin with the most * recently provided. * * If it wishes to return no deviceids, it may set res->eof to true without * calling cb at all. * * @param[in] exportHandle Export handle * @param[in] type Type of layout to get devices for * @param[in] opaque Opaque pointer to be passed to callback * @param[in] cb Function taking device ID halves * @param[in] res In/out and output arguments of the function * * @returns: Valid error codes in RFC 5661, p. 365-6. */ static nfsstat4 getdevicelist(struct fsal_export *exportHandle, layouttype4 type, void *opaque, bool (*callback)(void *opaque, const uint64_t identifier), struct fsal_getdevicelist_res *res) { (void)exportHandle; (void)type; (void)opaque; (void)callback; res->eof = true; return NFS4_OK; } /** * @brief Get layout types supported by export. * * This function is the handler of the NFS4.1 FATTR4_FS_LAYOUT_TYPES file * attribute. * * @param[in] exportHandle Filesystem to interrogate * @param[out] count Number of layout types in array * @param[out] types Static array of layout types that must not be * freed or modified and must not be dereferenced * after export reference is relinquished. */ static void fs_layouttypes(struct fsal_export *exportHandle, int32_t *count, const layouttype4 **types) { (void)exportHandle; static const layouttype4 supportedLayoutType = LAYOUT4_NFSV4_1_FILES; *types = &supportedLayoutType; *count = 1; } /** * @brief Get layout block size for export. * * This function is the handler of the NFS4.1 FATTR4_LAYOUT_BLKSIZE f-attribute. * * This is the preferred read/write block size. Clients are requested (but * don't have to) read and write in multiples. * * NOTE: The Linux client only asks for this in blocks-layout, where this is * the filesystem wide block-size. (Minimum write size and alignment). * * @param[in] export Filesystem to interrogate * * @returns: The preferred layout block size. */ static uint32_t fs_layout_blocksize(struct fsal_export *export) { (void)export; return SFSCHUNKSIZE; } /** * @brief Maximum number of segments we will use. * * This function returns the maximum number of segments that will be used to * construct the response to any single layoutget call. Bear in mind that * current clients only support 1 segment. * * @param[in] export Filesystem to interrogate * * @returns: The Maximum number of layout segments in a compound layoutget. */ static uint32_t fs_maximum_segments(struct fsal_export *export) { (void)export; return 1; } /** * @brief Size of the buffer needed for loc_body at layoutget. * * This function sets policy for XDR buffer allocation in layoutget * vector below. * If FSAL has a const size, just return it here. If it is dependent on what the * client can take return ~0UL. In any case the buffer allocated will not be * bigger than client's requested maximum. * * @param[in] export Filesystem to interrogate * * @returns: Max size of the buffer needed for a loc_body. */ static size_t fs_loc_body_size(struct fsal_export *export) { (void)export; return MaxBufferSize; /* typical value in NFS FSAL plugins */ } /** * @brief Max Size of the buffer needed for da_addr_body in getdeviceinfo. * * This function sets policy for XDR buffer allocation in getdeviceinfo. * If FSAL has a const size, just return it here. If it is dependent on * what the client can take return ~0UL. In any case the buffer allocated * will not be bigger than client's requested maximum. * * @param[in] module FSAL Module * * @returns: Max size of the buffer needed for a da_addr_body. */ static size_t fs_da_addr_size(struct fsal_module *module) { (void)module; /* one stripe index + number of addresses + * SAUNAFS_EXPECTED_BACKUP_DS_COUNT addresses per chunk each address * takes 37 bytes (we use 40 for safety) we add 32 bytes of overhead * (includes stripe count and DS count) */ return SAUNAFS_BIGGEST_STRIPE_COUNT * (4 + (4 + SAUNAFS_EXPECTED_BACKUP_DS_COUNT * ChunkAddressSizeInBytes)) + ChunkDataOverhead; } /** * @brief Initialize pNFS export vector operations * * @param[in] ops Export operations vector */ void exportOperationsPnfs(struct export_ops *ops) { ops->getdevicelist = getdevicelist; ops->fs_layouttypes = fs_layouttypes; ops->fs_layout_blocksize = fs_layout_blocksize; ops->fs_maximum_segments = fs_maximum_segments; ops->fs_loc_body_size = fs_loc_body_size; } /** * @brief Initialize pNFS related operations * * @param[in] ops Export operations vector */ void pnfsMdsOperationsInit(struct fsal_ops *ops) { ops->getdeviceinfo = getdeviceinfo; ops->fs_da_addr_size = fs_da_addr_size; } nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/mds_handle.c000066400000000000000000000217341473756622300212670ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* Copyright 2017 Skytechnology sp. z o.o. Copyright 2023 Leil Storage OÜ This file is part of SaunaFS. SaunaFS 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, version 3. SaunaFS 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 SaunaFS. If not, see . */ #include "pnfs_utils.h" #include "context_wrap.h" #include "saunafs_internal.h" /** * @brief Grant a layout segment. * * pNFS functions. * * This function is called by nfs41_op_layoutget. * It may be called multiple times, to satisfy a request with multiple segments. * The FSAL may track state (what portion of the request has been or remains * to be satisfied or any other information it wishes) in the bookkeeper member * of res. Each segment may have FSAL-specific information associated with its * segid. * This segid will be supplied to the FSAL when the segment is committed or * returned. * * When the granting the last segment it intends to grant, the FSAL must set the * last_segment flag in res. * * @param[in] objectHandle The handle of the file on which the layout is * requested * @param[out] xdrStream An XDR stream to which the FSAL must encode the * layout * specific portion of the granted layout segment * @param[in] arguments Input arguments of the function * @param[in,out] output In/out and output arguments of the function * * @returns: Valid error codes in RFC 5661, pp. 366-7. */ static nfsstat4 layoutget(struct fsal_obj_handle *objectHandle, XDR *xdrStream, const struct fsal_layoutget_arg *arguments, struct fsal_layoutget_res *output) { struct SaunaFSHandle *handle = NULL; struct DSWire dataServerWire; struct gsh_buffdesc dsBuffer = { .addr = &dataServerWire, .len = sizeof(struct DSWire) }; struct pnfs_deviceid deviceid = DEVICE_ID_INIT_ZERO(FSAL_ID_SAUNAFS); nfl_util4 layoutUtil = 0; nfsstat4 status = NFS4_OK; handle = container_of(objectHandle, struct SaunaFSHandle, handle); if (arguments->type != LAYOUT4_NFSV4_1_FILES) { LogMajor(COMPONENT_PNFS, "Unsupported layout type: %x", arguments->type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } LogDebug(COMPONENT_PNFS, "will issue layout offset: %" PRIu64 " length: %" PRIu64, output->segment.offset, output->segment.length); deviceid.device_id2 = handle->export->export.export_id; deviceid.devid = handle->inode; dataServerWire.inode = handle->inode; layoutUtil = SFSCHUNKSIZE; status = FSAL_encode_file_layout(xdrStream, &deviceid, layoutUtil, 0, 0, &op_ctx->ctx_export->export_id, 1, &dsBuffer, false); if (status) { LogMajor(COMPONENT_PNFS, "Failed to encode nfsv4_1_file_layout."); return status; } output->return_on_close = true; output->last_segment = true; return status; } /** * @brief Potentially return one layout segment. * * This function is called once on each segment matching the IO mode and * intersecting the range specified in a LAYOUTRETURN operation or for all * layouts corresponding to a given stateid on last close, lease expiry, or * a layoutreturn with a return-type of FSID or ALL. Whether it is called in * the former or latter case is indicated by the synthetic flag in the arg * structure, with synthetic being true in the case of last-close or lease * expiry. * * If arg->dispose is true, all resources associated with the layout must be * freed. * * @param[in] objectHandle The object on which a segment is to be returned * @param[in] xdrStream In the case of a non-synthetic return, this is * an XDR stream corresponding to the layout * type-specific argument to LAYOUTRETURN. In the * case of a synthetic or bulk return, this is a * NULL pointer. * @param[in] arguments Input arguments of the function * * @returns: Valid error codes in RFC 5661, p. 367. */ static nfsstat4 layoutreturn(struct fsal_obj_handle *objectHandle, XDR *xdrStream, const struct fsal_layoutreturn_arg *arguments) { (void)objectHandle; (void)xdrStream; if (arguments->lo_type != LAYOUT4_NFSV4_1_FILES) { LogDebug(COMPONENT_PNFS, "Unsupported layout type: %x", arguments->lo_type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } return NFS4_OK; } /** * @brief Function to know if the client set a new offset * * @param[in] arguments Input parameters to FSAL layoutcommit * @param[in] previousReply Attributes returned by getattr() * * @retval true : If the client set a new offset * @retval false: Otherwise */ bool isOffsetChangedByClient(const struct fsal_layoutcommit_arg *arguments, struct sau_attr_reply previousReply) { return arguments->new_offset && previousReply.attr.st_size < (long)arguments->last_write + 1; } /** * @brief Function to know if the client provided a new value for mtime * * @param[in] arguments Input parameters to FSAL layoutcommit * @param[in] previousReply Attributes returned by getattr() * * @retval true : If the client provided a new value for mtime * @retval false: Otherwise */ bool hasRecentModificationTime(const struct fsal_layoutcommit_arg *arguments, struct sau_attr_reply previousReply) { return arguments->time_changed && (arguments->new_time.seconds > previousReply.attr.st_mtim.tv_sec || (arguments->new_time.seconds == previousReply.attr.st_mtim.tv_sec && arguments->new_time.nseconds > previousReply.attr.st_mtim.tv_nsec)); } /** * @brief Commit a segment of a layout. * * This function is called once on every segment of a layout. * The FSAL may avoid being called again after it has finished all tasks * necessary for the commit by setting res->commit_done to true. * * The calling function does not inspect or act on the value of size_supplied * or new_size until after the last call to FSAL_layoutcommit. * * @param[in] objectHandle The object on which to commit * @param[in] xdrStream An XDR stream containing the layout * type-specific portion of the LAYOUTCOMMIT * arguments * @param[in] arguments Input arguments of the function * @param[in,out] output In/out and output arguments of the function * * @returns: Valid error codes in RFC 5661, p. 366. */ static nfsstat4 layoutcommit(struct fsal_obj_handle *objectHandle, XDR *xdrStream, const struct fsal_layoutcommit_arg *arguments, struct fsal_layoutcommit_res *output) { (void)xdrStream; struct SaunaFSExport *export = NULL; struct SaunaFSHandle *handle = NULL; struct sau_attr_reply previousReply; if (arguments->type != LAYOUT4_NFSV4_1_FILES) { LogCrit(COMPONENT_PNFS, "Unsupported layout type: %x", arguments->type); return NFS4ERR_UNKNOWN_LAYOUTTYPE; } export = container_of(op_ctx->fsal_export, struct SaunaFSExport, export); handle = container_of(objectHandle, struct SaunaFSHandle, handle); int retvalue = saunafs_getattr(export->fsInstance, &op_ctx->creds, handle->inode, &previousReply); if (retvalue < 0) { LogCrit(COMPONENT_PNFS, "Error '%s' in attempt to get attributes of file %lli.", sau_error_string(sau_last_err()), (long long)handle->inode); return nfs4LastError(); } struct stat posixAttributes; uint64_t mask = 0; memset(&posixAttributes, 0, sizeof(posixAttributes)); if (isOffsetChangedByClient(arguments, previousReply)) { mask |= SAU_SET_ATTR_SIZE; posixAttributes.st_size = (__off_t)arguments->last_write + 1; output->size_supplied = true; output->new_size = arguments->last_write + 1; } if (hasRecentModificationTime(arguments, previousReply)) { posixAttributes.st_mtim.tv_sec = arguments->new_time.seconds; posixAttributes.st_mtim.tv_sec = arguments->new_time.nseconds; mask |= SAU_SET_ATTR_MTIME; mask = (unsigned int)mask | SAU_SET_ATTR_MTIME; } sau_attr_reply_t reply; retvalue = saunafs_setattr(export->fsInstance, &op_ctx->creds, handle->inode, &posixAttributes, (int)mask, &reply); if (retvalue < 0) { LogCrit(COMPONENT_PNFS, "Error '%s' in attempt to set attributes of file %lli.", sau_error_string(sau_last_err()), (long long)handle->inode); return nfs4LastError(); } output->commit_done = true; return NFS4_OK; } /** * @brief Initialize pNFS related operations * * @param[in] ops FSAL object operations vector */ void handleOperationsPnfs(struct fsal_obj_ops *ops) { ops->layoutget = layoutget; ops->layoutreturn = layoutreturn; ops->layoutcommit = layoutcommit; } nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/saunafs/000077500000000000000000000000001473756622300204565ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/saunafs/saunafs_c_api.h000066400000000000000000000774321473756622300234370ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright 2017 Skytechnology sp. z o.o. * Copyright 2023 Leil Storage OÜ * Author: Piotr Sarna * * SaunaFS C API * * This library can be used to communicate with SaunaFS metadata and data * servers from C/C++ code. * * Compile with -lsaunafs-client and SaunaFS C/C++ library installed. */ #include #include #include #include #include #ifndef __SAUNAFS_C_API_H #define __SAUNAFS_C_API_H #ifdef __cplusplus extern "C" { #else #include #endif enum sau_sugid_clear_mode { SAUNAFS_SUGID_CLEAR_MODE_NEVER, SAUNAFS_SUGID_CLEAR_MODE_ALWAYS, SAUNAFS_SUGID_CLEAR_MODE_OSX, SAUNAFS_SUGID_CLEAR_MODE_BSD, SAUNAFS_SUGID_CLEAR_MODE_EXT, SAUNAFS_SUGID_CLEAR_MODE_SFS, SAUNAFS_SUGID_CLEAR_MODE_END_ }; typedef struct sau_init_params { const char *bind_host; const char *host; const char *port; bool meta; const char *mountpoint; const char *subfolder; const char *password; const char *md5_pass; bool do_not_remember_password; bool delayed_init; unsigned int report_reserved_period; unsigned int io_retries; unsigned int chunkserver_round_time_ms; unsigned int chunkserver_connect_timeout_ms; unsigned int chunkserver_wave_read_timeout_ms; unsigned int total_read_timeout_ms; unsigned int cache_expiration_time_ms; unsigned int readahead_max_window_size_kB; bool prefetch_xor_stripes; double bandwidth_overuse; unsigned int write_cache_size; unsigned int write_workers; unsigned int write_window_size; unsigned int chunkserver_write_timeout_ms; unsigned int cache_per_inode_percentage; unsigned int symlink_cache_timeout_s; bool debug_mode; int keep_cache; double direntry_cache_timeout; unsigned int direntry_cache_size; double entry_cache_timeout; double attr_cache_timeout; bool mkdir_copy_sgid; enum sau_sugid_clear_mode sugid_clear_mode; bool use_rw_lock; double acl_cache_timeout; unsigned int acl_cache_size; bool verbose; const char *io_limits_config_file; } sau_init_params_t; #define SAUNAFS_MAX_GOAL_NAME 64 #define SAUNAFS_MAX_READLINK_LENGTH 65535 typedef uint32_t sau_inode_t; typedef int sau_err_t; struct sau; typedef struct sau sau_t; struct sau_fileinfo; typedef struct sau_fileinfo sau_fileinfo_t; struct sau_context; typedef struct sau_context sau_context_t; typedef struct sau_acl sau_acl_t; #define SAU_SET_ATTR_MODE (1u << 0u) #define SAU_SET_ATTR_UID (1u << 1u) #define SAU_SET_ATTR_GID (1u << 2u) #define SAU_SET_ATTR_SIZE (1u << 3u) #define SAU_SET_ATTR_ATIME (1u << 4u) #define SAU_SET_ATTR_MTIME (1u << 5u) #define SAU_SET_ATTR_ATIME_NOW (1u << 7u) #define SAU_SET_ATTR_MTIME_NOW (1u << 8u) /* ACL flags */ #define SAU_ACL_AUTO_INHERIT 0x01 #define SAU_ACL_PROTECTED 0x02 #define SAU_ACL_DEFAULTED 0x04 #define SAU_ACL_WRITE_THROUGH 0x40 #define SAU_ACL_MASKED 0x80 /* ACL ace types */ #define SAU_ACL_ACCESS_ALLOWED_ACE_TYPE 0x0000 #define SAU_ACL_ACCESS_DENIED_ACE_TYPE 0x0001 /* ACL ace flags bits */ #define SAU_ACL_FILE_INHERIT_ACE 0x0001 #define SAU_ACL_DIRECTORY_INHERIT_ACE 0x0002 #define SAU_ACL_NO_PROPAGATE_INHERIT_ACE 0x0004 #define SAU_ACL_INHERIT_ONLY_ACE 0x0008 #define SAU_ACL_SUCCESSFUL_ACCESS_ACE_FLAG 0x00000010 #define SAU_ACL_FAILED_ACCESS_ACE_FLAG 0x00000020 #define SAU_ACL_IDENTIFIER_GROUP 0x0040 #define SAU_ACL_INHERITED_ACE 0x0080 /* non nfs4 */ #define SAU_ACL_SPECIAL_WHO 0x0100 /* saunafs */ /* ACL ace mask bits */ #define SAU_ACL_READ_DATA 0x00000001 #define SAU_ACL_LIST_DIRECTORY 0x00000001 #define SAU_ACL_WRITE_DATA 0x00000002 #define SAU_ACL_ADD_FILE 0x00000002 #define SAU_ACL_APPEND_DATA 0x00000004 #define SAU_ACL_ADD_SUBDIRECTORY 0x00000004 #define SAU_ACL_READ_NAMED_ATTRS 0x00000008 #define SAU_ACL_WRITE_NAMED_ATTRS 0x00000010 #define SAU_ACL_EXECUTE 0x00000020 #define SAU_ACL_DELETE_CHILD 0x00000040 #define SAU_ACL_READ_ATTRIBUTES 0x00000080 #define SAU_ACL_WRITE_ATTRIBUTES 0x00000100 #define SAU_ACL_WRITE_RETENTION 0x00000200 #define SAU_ACL_WRITE_RETENTION_HOLD 0x00000400 #define SAU_ACL_DELETE 0x00010000 #define SAU_ACL_READ_ACL 0x00020000 #define SAU_ACL_WRITE_ACL 0x00040000 #define SAU_ACL_WRITE_OWNER 0x00080000 #define SAU_ACL_SYNCHRONIZE 0x00100000 /* ACL ace special ids */ #define SAU_ACL_OWNER_SPECIAL_ID 0x0 #define SAU_ACL_GROUP_SPECIAL_ID 0x1 #define SAU_ACL_EVERYONE_SPECIAL_ID 0x2 /* ACL helper macros */ #define SAU_ACL_POSIX_MODE_READ (SAU_ACL_READ_DATA | SAU_ACL_LIST_DIRECTORY) #define SAU_ACL_POSIX_MODE_WRITE \ (SAU_ACL_WRITE_DATA | SAU_ACL_ADD_FILE | SAU_ACL_APPEND_DATA | \ SAU_ACL_ADD_SUBDIRECTORY | SAU_ACL_DELETE_CHILD) #define SAU_ACL_POSIX_MODE_EXECUTE (EXECUTE) #define SAU_ACL_POSIX_MODE_ALL \ (SAU_ACL_POSIX_MODE_READ | SAU_ACL_POSIX_MODE_WRITE | \ SAU_POSIX_MODE_EXEC) enum sau_special_ino { SAUNAFS_INODE_ERROR = 0, SAUNAFS_INODE_ROOT = 1, }; enum sau_setxattr_mode { XATTR_SMODE_CREATE_OR_REPLACE = 0, XATTR_SMODE_CREATE_ONLY = 1, XATTR_SMODE_REPLACE_ONLY = 2, XATTR_SMODE_REMOVE = 3, }; /* Basic attributes of a file */ typedef struct sau_entry { sau_inode_t ino; unsigned long generation; struct stat attr; double attr_timeout; double entry_timeout; } sau_entry_t; /* Result of setattr/getattr operations */ typedef struct sau_attr_reply { struct stat attr; double attr_timeout; } sau_attr_reply_t; /* Basic attributes of a directory */ typedef struct sau_direntry { char *name; struct stat attr; off_t next_entry_offset; } sau_direntry_t; typedef struct sau_namedinode_entry { sau_inode_t ino; char *name; } sau_namedinode_entry_t; /* Result of getxattr, setxattr and listattr operations */ typedef struct sau_xattr_reply { uint32_t value_length; uint8_t *value_buffer; } sau_xattr_reply_t; /* Result of statfs operation * total_space - total space * avail_space - available space * trash_space - space occupied by trash files * reserved_space - space occupied by reserved files * inodes - number of inodes */ typedef struct sau_stat { uint64_t total_space; uint64_t avail_space; uint64_t trash_space; uint64_t reserved_space; uint32_t inodes; } sau_stat_t; /* Server location for a chunk part */ typedef struct sau_chunk_part_info { uint32_t addr; uint16_t port; uint16_t part_type_id; char *label; } sau_chunk_part_info_t; /* Chunk information including id, type and all parts */ typedef struct sau_chunk_info { uint64_t chunk_id; uint32_t chunk_version; uint32_t parts_size; sau_chunk_part_info_t *parts; } sau_chunk_info_t; typedef struct sau_chunkserver_info { uint32_t version; uint32_t ip; uint16_t port; uint64_t used_space; uint64_t total_space; uint32_t chunks_count; uint32_t error_counter; char *label; } sau_chunkserver_info_t; typedef struct sau_acl_ace { uint16_t type; uint16_t flags; uint32_t mask; uint32_t id; } sau_acl_ace_t; typedef struct sau_lock_info { int16_t l_type; /* Type of lock: F_RDLCK, F_WRLCK, or F_UNLCK. */ int64_t l_start; /* Offset where the lock begins. */ int64_t l_len; /* Size of the locked area; zero means until EOF. */ int32_t l_pid; /* Process holding the lock. */ } sau_lock_info_t; typedef struct sau_lock_interrupt_info { uint64_t owner; uint32_t ino; uint32_t reqid; } sau_lock_interrupt_info_t; /*! * \brief Function that registers lock interrupt data. * \param info lock info to be remembered somewhere * \param priv private data potentially needed by caller */ typedef int (*sau_lock_register_interrupt_t)( struct sau_lock_interrupt_info *info, void *priv); /*! * \brief Create a context for SaunaFS operations * Flavor 1: create default context with current uid/gid/pid * Flavor 2: create context with custom uid/gid/pid * * \warning Creating context with secondary groups involves calling * sau_update_groups on a created context. It is the case because metadata * server needs to be notified that new group set was created. * If secondary groups are registered by calling * sau_update_groups(ctx, instance), context is bound to instance it was * registered with and should not be used with other instances. */ sau_context_t *sau_create_context(void); sau_context_t *sau_create_user_context(uid_t uid, gid_t gid, pid_t pid, mode_t umask); /*! * \brief Set lock owner inside a fileinfo structure * \param fileinfo descriptor to an open file * \param lock_owner lock owner token */ void sau_set_lock_owner(sau_fileinfo_t *fileinfo, uint64_t lock_owner); /*! * \brief Returns last error code set by specific calls (see below) */ sau_err_t sau_last_err(void); /*! * \brief Converts native SaunaFS error code to POSIX error code. */ int sau_error_conv(sau_err_t saunafs_error_code); /*! * \brief Returns human-readable description of SaunaFS error code */ const char *sau_error_string(sau_err_t saunafs_error_code); /*! * \brief Destroy a context for SaunaFS operations */ void sau_destroy_context(sau_context_t **ctx); /*! * \brief Initialize init params to defaults * \param host master server connection host * \param port master server connection port * \param mountpoint a human-readable name for 'mountpoint' created by * a connection */ void sau_set_default_init_params(struct sau_init_params *params, const char *host, const char *port, const char *mountpoint); /*! * \brief Initialize a connection with master server * \param host master server connection host * \param port master server connection port * \param mountpoint a human-readable name for 'mountpoint' created by * a connection * \return a SaunaFS client instance, nullptr if connection is impossible, * sets last error code (check with sau_last_err()) */ sau_t *sau_init(const char *host, const char *port, const char *mountpoint); /*! * \brief Initialize a connection with master server * \param params init params initialized via sau_set_default_init_params() * and possibly tweaked * \return a SaunaFS client instance, nullptr if connection is impossible, * sets last error code (check with sau_last_err()) */ sau_t *sau_init_with_params(struct sau_init_params *params); /*! * \brief Update secondary group information in context * \param instance returned from sau_init * \param ctx context to be updated * \param gids array of new group ids to be set * \param gid_num length of gids array * \return 0 on success, -1 if failed, sets last error code */ int sau_update_groups(sau_t *instance, sau_context_t *ctx, gid_t *gids, int gid_num); /*! \brief Find inode in parent directory by name * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param parent inode * \param path name to be looked up * \param entry structure to be filled with lookup result * \return 0 on success, -1 if failed, sets last error code */ int sau_lookup(sau_t *instance, sau_context_t *ctx, sau_inode_t parent, const char *path, struct sau_entry *entry); /*! \brief Create a file with given parent and name * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param parent inode * \param path name to be looked up * \param mode file permissions and node type * \param rdev major/minor numbers for block devices, otherwise ignored * \param entry filled upon successful creation * \return 0 on success, -1 if failed, sets last error code */ int sau_mknod(sau_t *instance, sau_context_t *ctx, sau_inode_t parent, const char *path, mode_t mode, dev_t rdev, struct sau_entry *entry); /*! \brief Create a link with given parent and name * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param inode target inode * \param parent inode * \param name link name (no paths allowed) * \param entry filled upon successful creation * \return 0 on success, -1 if failed, sets last error code */ int sau_link(sau_t *instance, sau_context_t *ctx, sau_inode_t inode, sau_inode_t parent, const char *name, struct sau_entry *entry); /*! \brief Create a symlink with given parent and name * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param link contents (path it points to) * \param parent inode * \param name link name (no paths allowed) * \param entry filled upon successful creation * \return 0 on success, -1 if failed, sets last error code */ int sau_symlink(sau_t *instance, sau_context_t *ctx, const char *link, sau_inode_t parent, const char *name, struct sau_entry *entry); /*! \brief Open a file by inode * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param inode of a file * \param flags open flags * \return fileinfo descriptor of an open file, * if failed - nullptr and sets last error code (check with sau_last_err()) */ sau_fileinfo_t *sau_open(sau_t *instance, sau_context_t *ctx, sau_inode_t inode, int flags); /*! \brief Read bytes from open file * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param fileinfo descriptor of an open file * \param offset read offset * \param size read size * \param buffer to be read to * \return number of bytes read on success, * -1 if failed and sets last error code (check with sau_last_err()) */ ssize_t sau_read(sau_t *instance, sau_context_t *ctx, sau_fileinfo_t *fileinfo, off_t offset, size_t size, char *buffer); ssize_t sau_readv(sau_t *instance, sau_context_t *ctx, sau_fileinfo_t *fileinfo, off_t offset, size_t size, const struct iovec *iov, int iovcnt); /*! \brief Write bytes to open file * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param fileinfo descriptor of an open file * \param offset write offset * \param size write size * \param buffer to be written from * \return number of bytes written on success, * -1 if failed and sets last error code (check with sau_last_err()) */ ssize_t sau_write(sau_t *instance, sau_context_t *ctx, sau_fileinfo_t *fileinfo, off_t offset, size_t size, const char *buffer); /*! \brief Release a previously open file * \param instance returned from sau_init * \param fileinfo descriptor of an open file * \return 0 on success, -1 if failed, sets last error code */ int sau_release(sau_t *instance, sau_fileinfo_t *fileinfo); /*! \brief Flush data written to an open file * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param fileinfo descriptor of an open file * \return 0 on success, -1 if failed, sets last error code */ int sau_flush(sau_t *instance, sau_context_t *ctx, sau_fileinfo_t *fileinfo); /*! \brief Get attributes by inode * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param inode of a file * \param reply structure to be filled with getattr result * \return 0 on success, -1 if failed, sets last error code */ int sau_getattr(sau_t *instance, sau_context_t *ctx, sau_inode_t inode, struct sau_attr_reply *reply); /*! \brief End a connection with master server * \param instance returned from sau_init */ void sau_destroy(sau_t *instance); /*! \brief Open a directory * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param inode of a directory * \return fileinfo descriptor on success, nullptr if failed, * sets last error code (check with sau_last_err()) */ struct sau_fileinfo *sau_opendir(sau_t *instance, sau_context_t *ctx, sau_inode_t inode); /*! \brief Read directory entries * \param instance returned from sau_init * \param fileinfo descriptor of an open directory * \param offset directory entry offset * \param buf buffer to be filled with readdir data * \param max_entries max number of entries to be returned * \param num_entries upon success set to number of entries returned in buf * \return 0 on success, -1 if failed, sets last error code */ int sau_readdir(sau_t *instance, sau_context_t *ctx, struct sau_fileinfo *fileinfo, off_t offset, size_t max_entries, struct sau_direntry *buf, size_t *num_entries); /*! \brief Destroy dir entries placed in an array * \param buf argument to previous successful call to sau_readdir * \param num_entries positive *num_entries value after respective * sau_readdir() call */ void sau_destroy_direntry(struct sau_direntry *buf, size_t num_entries); /*! \brief Release a directory * \param instance returned from sau_init * \param fileinfo descriptor of an open directory * \return 0 on success, -1 if failed, sets last error code */ int sau_releasedir(sau_t *instance, struct sau_fileinfo *fileinfo); /*! \brief Read link contents * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param inode link inode * \param buf filled with result on success (no trailing '\0'), * should be at least SAUNAFS_MAX_READLINK_LENGTH characters long * \param size allocated buf size * \return true link size on success, -1 if failed, sets last error code */ int sau_readlink(sau_t *instance, sau_context_t *ctx, sau_inode_t inode, char *buf, size_t size); /*! \brief Get reserved file inodes and names * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param offset 0-based index of the first wanted entry * \param max_entries maximum number of entries to retrieve * \param out_entries array entries are placed in * \param num_entries number of entries placed in out_entries * \return 0 on success, -1 if failed, sets last error code * \post sau_free_namedinode_entries(out_entries, result) must be called * if (returned 0 && num_entries > 0) to dispose of returned entries */ int sau_readreserved(sau_t *instance, sau_context_t *ctx, uint32_t offset, uint32_t max_entries, sau_namedinode_entry_t *out_entries, uint32_t *num_entries); /*! \brief Get trash file inodes and names * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param offset 0-based index of the first wanted entry * \param max_entries maximum number of entries to retrieve * \param out_entries array entries are placed in * \param num_entries number of entries placed in out_entries * \return 0 on success, -1 if failed, sets last error code * \post sau_free_namedinode_entry(out_entries, result) must be called * if (returned 0 && num_entries > 0) to dispose of returned entries */ int sau_readtrash(sau_t *instance, sau_context_t *ctx, uint32_t offset, uint32_t max_entries, sau_namedinode_entry_t *out_entries, uint32_t *num_entries); /*! \brief Destroy named inode entries placed in an array * \param entries out_entries argument to previous call to either * sau_readreserved or sau_readtrash * \param num_entries positive number of entries returned by the * respective call */ void sau_free_namedinode_entries(struct sau_namedinode_entry *entries, uint32_t num_entries); /*! \brief Create a directory * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param parent directory inode * \param name directory name * \param mode directory attributes * \param out_entry entry to be filled with new directory data * \return 0 on success, -1 if failed, sets last error code */ int sau_mkdir(sau_t *instance, sau_context_t *ctx, sau_inode_t parent, const char *name, mode_t mode, struct sau_entry *out_entry); /*! \brief Remove a directory * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param parent directory inode * \param name directory name * \return 0 on success, -1 if failed, sets last error code */ int sau_rmdir(sau_t *instance, sau_context_t *ctx, sau_inode_t parent, const char *name); /*! \brief Make a snapshot of a file * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param inode of a file * \param dst_parent inode of a new parent directory for a snapshot * \param dst_name name of a newly created snapshot * \param can_overwrite if true, snapshot creation will be able to overwrite * existing files * \param job_id id of makesnapshot task, can be used to cancel it, can be NULL * \return 0 on success, -1 if failed, sets last error code */ int sau_makesnapshot(sau_t *instance, sau_context_t *ctx, sau_inode_t inode, sau_inode_t dst_parent, const char *dst_name, int can_overwrite, uint32_t *job_id); /*! \brief Get file goal * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param inode of a file * \param goal_name buffer to be filled with goal, must be at least * SAUNAFS_MAX_GOAL_NAME long * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_getgoal(sau_t *instance, sau_context_t *ctx, sau_inode_t inode, char *goal_name); /*! \brief Set file goal * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param inode of a file * \param goal_name goal name to be set * \param is_recursive if true, operation will apply to all subdirectories * and files within them * \param job_id id of setgoal task, can be used to cancel it, can be NULL * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_setgoal(sau_t *instance, sau_context_t *ctx, sau_inode_t inode, const char *goal_name, int is_recursive); /*! \brief Unlink a file * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param parent directory inode * \param name file name * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_unlink(sau_t *instance, sau_context_t *ctx, sau_inode_t parent, const char *name); /*! \brief Restore file from trash * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param inode of the file * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_undel(sau_t *instance, sau_context_t *ctx, sau_inode_t inode); /*! \brief Set file attributes * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param inode of a file * \param stbuf attributes to be set * \param to_set flag which attributes should be set * \param reply returned value * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_setattr(sau_t *instance, sau_context_t *ctx, sau_inode_t inode, struct stat *stbuf, int to_set, struct sau_attr_reply *reply); /*! \brief Synchronize file data * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param fileinfo descriptor of an open file * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_fsync(sau_t *instance, sau_context_t *ctx, struct sau_fileinfo *fileinfo); /*! \brief Rename a file * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param parent current parent of a file to be moved * \param name of a file to be moved * \param new_parent inode of a new directory * \param new_name new name of a file to be moved * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_rename(sau_t *instance, sau_context_t *ctx, sau_inode_t parent, const char *name, sau_inode_t new_parent, const char *new_name); /*! \brief Retrieve file system statistics * \param instance returned from sau_init * \param buf structure to be filled with file system statistics * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_statfs(sau_t *instance, sau_stat_t *buf); /*! \brief Set extended attribute of a file * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param ino inode of a file * \param name attribute name * \param value attribute value * \param size of attribute value * \param mode one of enum sau_setxattr_mode values * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_setxattr(sau_t *instance, sau_context_t *ctx, sau_inode_t ino, const char *name, const uint8_t *value, size_t size, enum sau_setxattr_mode mode); /*! \brief Get extended attribute of a file * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param ino inode of a file * \param name attribute name * \param size of the provided buffer * \param out_size filled with actual size of xattr value * \param buf buffer to be filled with xattr value * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_getxattr(sau_t *instance, sau_context_t *ctx, sau_inode_t ino, const char *name, size_t size, size_t *out_size, uint8_t *buf); /*! \brief Get extended attributes list of a file * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param ino inode of a file * \param size of the provided buffer * \param buf buffer to be filled with listed attributes * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_listxattr(sau_t *instance, sau_context_t *ctx, sau_inode_t ino, size_t size, size_t *out_size, char *buf); /*! \brief Remove extended attribute from a file * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param ino inode of a file * \param name attribute name * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_removexattr(sau_t *instance, sau_context_t *ctx, sau_inode_t ino, const char *name); /*! * \brief Create acl * \return acl entry * \post free memory with sau_destroy_acl call */ sau_acl_t *sau_create_acl(void); /*! * \brief Create acl * \param mode mode used to create the acl and set POSIX permission flags * \return acl entry * \post free memory with sau_destroy_acl call */ sau_acl_t *sau_create_acl_from_mode(unsigned int mode); /*! * \brief Destroy acl * \param acl access control list */ void sau_destroy_acl(sau_acl_t *acl); /*! * \brief Print acl in human readable format * \param acl access control list * \param buf buffer to be filled with acl representation * \param size buffer size * \param reply_size size needed to store acl representation * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_print_acl(sau_acl_t *acl, char *buf, size_t size, size_t *reply_size); /*! * \brief Add access control entry to acl * \param acl access control list * \param ace prepared acl entry */ void sau_add_acl_entry(sau_acl_t *acl, const sau_acl_ace_t *ace); /*! * \brief Get nth acl entry * \param acl access control list * \param n entry index * \param ace entry to be filled with data * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_get_acl_entry(const sau_acl_t *acl, int n, sau_acl_ace_t *ace); /*! * \brief Get number of acl entries * \param acl access control list * \return number of entries */ size_t sau_get_acl_size(const sau_acl_t *acl); /*! * \brief Set acl for a file * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param ino target inode * \param type acl type (access, default) * \param acl to be set * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_setacl(sau_t *instance, sau_context_t *ctx, sau_inode_t ino, sau_acl_t *acl); /*! * \brief Get acl from a file * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param ino target inode * \param type acl type (access, default) * \param acl pointer to be filled with acl * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_getacl(sau_t *instance, sau_context_t *ctx, sau_inode_t ino, sau_acl_t **acl); /*! * \brief Apply rich acl masks to aces * \param acl to be modified * \param owner id * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_acl_apply_masks(sau_acl_t *acl, uint32_t owner); /*! \brief Gather chunks information for a file * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param inode of a file * \param chunk_index index of first chunk to return * \param buffer preallocated buffer for chunk info * \param buffer_size size of preallocated buffer in number of elements * \param reply_size number of sau_chunk_info_t structures returned from master * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) * \post retrieved chunks information should be freed with * sau_destroy_chunks_info call */ int sau_get_chunks_info(sau_t *instance, sau_context_t *ctx, sau_inode_t inode, uint32_t chunk_index, sau_chunk_info_t *buffer, uint32_t buffer_size, uint32_t *reply_size); /*! \brief Free data allocated in sau_get_chunks_info * \param buffer used in a successful sau_get_chunks_info call */ void sau_destroy_chunks_info(sau_chunk_info_t *buffer); /*! \brief Gather information on chunkservers present in the cluster * \param instance returned from sau_init * \param servers buffer to be filled with server info * \param size buffer size in the number of elements * \param reply_size number of server entries returned from master * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) * \post retrieved chunkservers information should be freed with * sau_destroy_chunkservers_info call */ int sau_get_chunkservers_info(sau_t *instance, sau_chunkserver_info_t *servers, uint32_t size, uint32_t *reply_size); /*! \brief Free data allocated in sau_get_chunkservers_info * \param buffer used in a successful sau_get_chunkservers_info call */ void sau_destroy_chunkservers_info(sau_chunkserver_info_t *buffer); /*! \brief Put a lock on a file (semantics based on POSIX setlk) * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param fileinfo descriptor of an open file * \param lock information * \param handler function used to register lock interrupt data, can be NULL * \param priv private user data passed to handler * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) * \post interrupt data registered by handler can be later passed to * sau_setlk_interrupt in order to cancel a lock request */ int sau_setlk(sau_t *instance, sau_context_t *ctx, sau_fileinfo_t *fileinfo, const sau_lock_info_t *lock, sau_lock_register_interrupt_t handler, void *priv); /*! \brief Get lock information from a file (semantics based on POSIX getlk) * \param instance returned from sau_init * \param ctx context returned from sau_create_context * \param fileinfo descriptor of an open file * \param lock buffer to be filled with lock information * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_getlk(sau_t *instance, sau_context_t *ctx, sau_fileinfo_t *fileinfo, sau_lock_info_t *lock); /*! \brief Cancel a lock request * \param instance returned from sau_init * \param interrupt_info interrupt data saved by a function passed to setlk * \return 0 on success, -1 if failed, sets last error code * (check with sau_last_err()) */ int sau_setlk_interrupt(sau_t *instance, const sau_lock_interrupt_info_t *interrupt_info); #ifdef __cplusplus } /* extern "C" */ #endif #endif nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/saunafs/saunafs_error_codes.h000066400000000000000000000052051473756622300246570ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* Copyright 2017 Skytechnology sp. z o.o. Copyright 2023 Leil Storage OÜ This file is part of SaunaFS. SaunaFS 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, version 3. SaunaFS 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 SaunaFS If not, see . */ #ifndef __SAUNAFS_ERROR_CODES_H #define __SAUNAFS_ERROR_CODES_H #include enum saunafs_error_code { SAUNAFS_STATUS_OK = 0, SAUNAFS_ERROR_EPERM = 1, SAUNAFS_ERROR_ENOTDIR = 2, SAUNAFS_ERROR_ENOENT = 3, SAUNAFS_ERROR_EACCES = 4, SAUNAFS_ERROR_EEXIST = 5, SAUNAFS_ERROR_EINVAL = 6, SAUNAFS_ERROR_ENOTEMPTY = 7, SAUNAFS_ERROR_CHUNKLOST = 8, SAUNAFS_ERROR_OUTOFMEMORY = 9, SAUNAFS_ERROR_INDEXTOOBIG = 10, SAUNAFS_ERROR_LOCKED = 11, SAUNAFS_ERROR_NOCHUNKSERVERS = 12, SAUNAFS_ERROR_NOCHUNK = 13, SAUNAFS_ERROR_CHUNKBUSY = 14, SAUNAFS_ERROR_REGISTER = 15, SAUNAFS_ERROR_NOTDONE = 16, SAUNAFS_ERROR_GROUPNOTREGISTERED = 17, SAUNAFS_ERROR_NOTSTARTED = 18, SAUNAFS_ERROR_WRONGVERSION = 19, SAUNAFS_ERROR_CHUNKEXIST = 20, SAUNAFS_ERROR_NOSPACE = 21, SAUNAFS_ERROR_IO = 22, SAUNAFS_ERROR_BNUMTOOBIG = 23, SAUNAFS_ERROR_WRONGSIZE = 24, SAUNAFS_ERROR_WRONGOFFSET = 25, SAUNAFS_ERROR_CANTCONNECT = 26, SAUNAFS_ERROR_WRONGCHUNKID = 27, SAUNAFS_ERROR_DISCONNECTED = 28, SAUNAFS_ERROR_CRC = 29, SAUNAFS_ERROR_DELAYED = 30, SAUNAFS_ERROR_CANTCREATEPATH = 31, SAUNAFS_ERROR_MISMATCH = 32, SAUNAFS_ERROR_EROFS = 33, SAUNAFS_ERROR_QUOTA = 34, SAUNAFS_ERROR_BADSESSIONID = 35, SAUNAFS_ERROR_NOPASSWORD = 36, SAUNAFS_ERROR_BADPASSWORD = 37, SAUNAFS_ERROR_ENOATTR = 38, SAUNAFS_ERROR_ENOTSUP = 39, SAUNAFS_ERROR_ERANGE = 40, SAUNAFS_ERROR_TIMEOUT = 41, SAUNAFS_ERROR_BADMETADATACHECKSUM = 42, SAUNAFS_ERROR_CHANGELOGINCONSISTENT = 43, SAUNAFS_ERROR_PARSE = 44, SAUNAFS_ERROR_METADATAVERSIONMISMATCH = 45, SAUNAFS_ERROR_NOTLOCKED = 46, SAUNAFS_ERROR_WRONGLOCKID = 47, SAUNAFS_ERROR_NOTPOSSIBLE = 48, SAUNAFS_ERROR_TEMP_NOTPOSSIBLE = 49, SAUNAFS_ERROR_WAITING = 50, SAUNAFS_ERROR_UNKNOWN = 51, SAUNAFS_ERROR_ENAMETOOLONG = 52, SAUNAFS_ERROR_EFBIG = 53, SAUNAFS_ERROR_EBADF = 54, SAUNAFS_ERROR_ENODATA = 55, SAUNAFS_ERROR_E2BIG = 56, SAUNAFS_ERROR_MAX = 57 }; const char *saunafs_error_string(uint8_t status); #endif nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/saunafs_acl.c000066400000000000000000000150321473756622300214420ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* Copyright 2017 Skytechnology sp. z o.o. Copyright 2023 Leil Storage OÜ This file is part of SaunaFS. SaunaFS 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, version 3. SaunaFS 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 SaunaFS. If not, see . */ #include "context_wrap.h" #include "saunafs_internal.h" const uint ByteMaxValue = 0xFF; /** * @brief Convert a given ACL in FSAL format to the corresponding SaunaFS ACL. * The mode is used to create a default ACL and set POSIX permission flags. * The new ACL is filled with the ACEs from the original ACL to have the same * permissions and flags. * * @param[in] fsalACL FSAL ACL * @param[in] mode Mode used to create the acl and set POSIX permission * flags * * @returns: SaunaFS ACL */ sau_acl_t *convertFsalACLToSaunafsACL(const fsal_acl_t *fsalACL, unsigned int mode) { sau_acl_t *saunafsACL = NULL; if (!fsalACL || (!fsalACL->aces && fsalACL->naces > 0)) return NULL; saunafsACL = sau_create_acl_from_mode(mode); if (!saunafsACL) return NULL; for (unsigned int i = 0; i < fsalACL->naces; ++i) { fsal_ace_t *fsalACE = fsalACL->aces + i; if (!(IS_FSAL_ACE_ALLOW(*fsalACE) || IS_FSAL_ACE_DENY(*fsalACE))) { continue; } sau_acl_ace_t ace; ace.flags = fsalACE->flag & ByteMaxValue; ace.mask = fsalACE->perm; ace.type = fsalACE->type; if (IS_FSAL_ACE_GROUP_ID(*fsalACE)) ace.id = GET_FSAL_ACE_GROUP(*fsalACE); else ace.id = GET_FSAL_ACE_USER(*fsalACE); if (IS_FSAL_ACE_SPECIAL_ID(*fsalACE)) { ace.flags |= (uint)SAU_ACL_SPECIAL_WHO; switch (GET_FSAL_ACE_USER(*fsalACE)) { case FSAL_ACE_SPECIAL_OWNER: ace.id = SAU_ACL_OWNER_SPECIAL_ID; break; case FSAL_ACE_SPECIAL_GROUP: ace.id = SAU_ACL_GROUP_SPECIAL_ID; break; case FSAL_ACE_SPECIAL_EVERYONE: ace.id = SAU_ACL_EVERYONE_SPECIAL_ID; break; default: LogFullDebug( COMPONENT_FSAL, "Invalid FSAL ACE special id type (%d)", (int)GET_FSAL_ACE_USER(*fsalACE)); continue; } } sau_add_acl_entry(saunafsACL, &ace); } return saunafsACL; } /** * @brief Convert a given SaunaFS ACL to the corresponding ACL in FSAL format. * This function copies the ACEs from the original SaunaFS ACL to have the same * permissions and flags in the new ACL. * * @param[in] saunafsACL SaunaFS ACL * * @returns: ACL in FSAL format */ fsal_acl_t *convertSaunafsACLToFsalACL(const sau_acl_t *saunafsACL) { fsal_acl_data_t aclData; fsal_acl_status_t status = 0; if (!saunafsACL) return NULL; aclData.naces = sau_get_acl_size(saunafsACL); aclData.aces = nfs4_ace_alloc(aclData.naces); if (!aclData.aces) return NULL; for (size_t aceEntry = 0; aceEntry < aclData.naces; ++aceEntry) { fsal_ace_t *fsalACE = aclData.aces + aceEntry; sau_acl_ace_t saunafsACE; int retvalue = sau_get_acl_entry(saunafsACL, aceEntry, &saunafsACE); (void)retvalue; assert(retvalue == 0); fsalACE->type = saunafsACE.type; fsalACE->flag = saunafsACE.flags & ByteMaxValue; fsalACE->iflag = (saunafsACE.flags & (unsigned int)SAU_ACL_SPECIAL_WHO) ? FSAL_ACE_IFLAG_SPECIAL_ID : 0; fsalACE->perm = saunafsACE.mask; if (IS_FSAL_ACE_GROUP_ID(*fsalACE)) fsalACE->who.gid = saunafsACE.id; else fsalACE->who.uid = saunafsACE.id; if (IS_FSAL_ACE_SPECIAL_ID(*fsalACE)) { switch (saunafsACE.id) { case SAU_ACL_OWNER_SPECIAL_ID: fsalACE->who.uid = FSAL_ACE_SPECIAL_OWNER; break; case SAU_ACL_GROUP_SPECIAL_ID: fsalACE->who.uid = FSAL_ACE_SPECIAL_GROUP; break; case SAU_ACL_EVERYONE_SPECIAL_ID: fsalACE->who.uid = FSAL_ACE_SPECIAL_EVERYONE; break; default: fsalACE->who.uid = FSAL_ACE_NORMAL_WHO; LogWarn(COMPONENT_FSAL, "Invalid SaunaFS ACE special id type (%u)", (unsigned int)saunafsACE.id); } } } return nfs4_acl_new_entry(&aclData, &status); } /** * @brief Get ACL from a file. * * This function returns the ACL of the file in FSAL format. * * @param[in] export SaunaFS export instance * @param[in] inode Inode of the file * @param[in] ownerId Owner id of the file * @param[in] acl Buffer to fill with information * * @returns: FSAL status. */ fsal_status_t getACL(struct SaunaFSExport *export, uint32_t inode, uint32_t ownerId, fsal_acl_t **acl) { if (*acl) { nfs4_acl_release_entry(*acl); *acl = NULL; } sau_acl_t *saunafsACL = NULL; int status = saunafs_getacl(export->fsInstance, &op_ctx->creds, inode, &saunafsACL); if (status < 0) { LogFullDebug(COMPONENT_FSAL, "getacl status = %s export=%" PRIu16 " inode=%" PRIu32, sau_error_string(sau_last_err()), export->export.export_id, inode); return fsalLastError(); } sau_acl_apply_masks(saunafsACL, ownerId); *acl = convertSaunafsACLToFsalACL(saunafsACL); sau_destroy_acl(saunafsACL); if (*acl == NULL) { LogFullDebug( COMPONENT_FSAL, "Failed to convert saunafs acl to nfs4 acl, export=%" PRIu16 " inode=%" PRIu32, export->export.export_id, inode); return fsalstat(ERR_FSAL_FAULT, 0); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Set ACL to a file. * * This function receives an ACL in FSAL format, transform it to a SaunaFS ACL * using the given mode and then set it to the file with the given inode. * * @param[in] export SaunaFS export instance * @param[in] inode Inode of the file * @param[in] acl FSAL ACL to set * @param[in] mode Mode used to create the acl and set POSIX permission * flags * * @returns: FSAL status. */ fsal_status_t setACL(struct SaunaFSExport *export, uint32_t inode, const fsal_acl_t *acl, unsigned int mode) { if (!acl) return fsalstat(ERR_FSAL_NO_ERROR, 0); sau_acl_t *saunafsACL = convertFsalACLToSaunafsACL(acl, mode); if (!saunafsACL) { LogFullDebug(COMPONENT_FSAL, "Failed to convert acl"); return fsalstat(ERR_FSAL_FAULT, 0); } int status = saunafs_setacl(export->fsInstance, &op_ctx->creds, inode, saunafsACL); sau_destroy_acl(saunafsACL); if (status < 0) return fsalLastError(); return fsalstat(ERR_FSAL_NO_ERROR, 0); } nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/saunafs_fsal_types.h000066400000000000000000000112421473756622300230600ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* Copyright 2023 Leil Storage OÜ This file is part of SaunaFS. SaunaFS 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, version 3. SaunaFS 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 SaunaFS. If not, see . */ #pragma once #include "fsal_api.h" #include "fileinfo_cache.h" #include "saunafs/saunafs_c_api.h" #define SAUNAFS_VERSION(major, minor, micro) \ (0x010000 * major + 0x0100 * minor + micro) #define kDisconnectedChunkServerVersion SAUNAFS_VERSION(256, 0, 0) #define SFS_NAME_MAX 255 static const int kNFS4_ERROR = -1; /* Global SaunaFS constants */ #define SFSBLOCKSIZE 65536 #define SFSBLOCKSINCHUNK 1024 #define SFSCHUNKSIZE (SFSBLOCKSIZE * SFSBLOCKSINCHUNK) #define SPECIAL_INODE_BASE 0xFFFFFFF0U #define SPECIAL_INODE_ROOT 0x01U #define MAX_REGULAR_INODE (SPECIAL_INODE_BASE - 0x01U) #define SAUNAFS_SUPPORTED_ATTRS \ (ATTR_TYPE | ATTR_SIZE | ATTR_FSID | ATTR_FILEID | ATTR_MODE | \ ATTR_NUMLINKS | ATTR_OWNER | ATTR_GROUP | ATTR_ATIME | ATTR_CTIME | \ ATTR_MTIME | ATTR_CHANGE | ATTR_SPACEUSED | ATTR_RAWDEV | ATTR_ACL | \ ATTR4_XATTR) #define SAUNAFS_BIGGEST_STRIPE_COUNT 4096 #define SAUNAFS_STD_CHUNK_PART_TYPE 0 #define SAUNAFS_EXPECTED_BACKUP_DS_COUNT 3 #define TCP_PROTO_NUMBER 6 typedef sau_fileinfo_t fileinfo_t; /** * @struct SaunaFSModule saunafs_fsal_types.h [saunafs_fsal_types.h] * * @brief SaunaFS Main global module object. * * SaunaFSModule contains the global module object * operations vector and parameters of the filesystem info. */ struct SaunaFSModule { struct fsal_module fsal; struct fsal_obj_ops handleOperations; fsal_staticfsinfo_t filesystemInfo; }; extern struct SaunaFSModule SaunaFS; struct SaunaFSHandle; /** * @struct SaunaFSExport saunafs_fsal_types.h [saunafs_fsal_types.h] * * @brief SaunaFS private export object. * * SaunaFSExport contains information related with the export, * the filesystem operations, the parameters used to connect * to the master server, the cache used and the pNFS support. */ struct SaunaFSExport { struct fsal_export export; /* Export object */ struct SaunaFSHandle *root; /* root handle of export */ sau_t *fsInstance; /* Filesystem instance */ sau_init_params_t parameters; /* Initial parameters */ FileInfoCache_t *cache; /* Export cache */ bool pnfsMdsEnabled; /* pNFS Metadata Server enabled */ bool pnfsDsEnabled; /* pNFS Data Server enabled */ uint32_t cacheTimeout; /* Timeout for entries at cache */ uint32_t cacheMaximumSize; /* Maximum size of cache */ }; /** * @struct SaunaFSFd saunafs_fsal_types.h [saunafs_fsal_types.h] * * @brief SaunaFS FSAL file descriptor. * * SaunaFSFd works as a container to manage the information of a SaunaFS * file descriptor and its flags associated like open and share mode. */ struct SaunaFSFd { struct fsal_fd fsalFd; /* The open and share mode plus fd management */ struct sau_fileinfo *fd; /* SaunaFS file descriptor */ }; /** * @struct SaunaFSStateFd saunafs_fsal_types.h [saunafs_fsal_types.h] * * @brief Associates a single NFSv4 state structure with a file descriptor. */ struct SaunaFSStateFd { /* state MUST be first to use default free_state */ struct state_t state; /* Structure representing a single NFSv4 state */ struct SaunaFSFd saunafsFd; /* SaunaFS file descriptor */ }; struct SaunaFSHandleKey { uint16_t moduleId; /* module id */ uint16_t exportId; /* export id */ sau_inode_t inode; /* inode */ }; /** * @struct SaunaFSHandle saunafs_fsal_types.h [saunafs_fsal_types.h] * * @brief SaunaFS FSAL handle. * * SaunaFSHandle contains information related with the public structure of the * filesystem and its operations. */ struct SaunaFSHandle { struct fsal_obj_handle handle; /* Public handle */ struct SaunaFSFd fd; /* SaunaFS FSAL file descriptor */ sau_inode_t inode; /* inode of file */ struct SaunaFSHandleKey key; /* Handle key */ struct SaunaFSExport *export; /* Export to which the handle belongs */ struct fsal_share share; /* The ref counted share reservation state */ }; struct DSWire { uint32_t inode; /* inode */ }; struct DataServerHandle { struct fsal_ds_handle handle; /* Public Data Server handle */ uint32_t inode; /* inode */ FileInfoEntry_t *cacheHandle; /* Cache entry for inode */ }; nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/saunafs_internal.c000066400000000000000000000042621473756622300225220ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* Copyright 2023 Leil Storage OÜ This file is part of SaunaFS. SaunaFS 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, version 3. SaunaFS 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 SaunaFS. If not, see . */ #include "fsal_convert.h" #include "pnfs_utils.h" #include "saunafs_internal.h" sau_context_t *createContext(sau_t *instance, struct user_cred *cred) { if (cred == NULL) return sau_create_user_context(0, 0, 0, 0); uid_t uid = (cred->caller_uid == op_ctx->export_perms.anonymous_uid) ? 0 : cred->caller_uid; gid_t gid = (cred->caller_gid == op_ctx->export_perms.anonymous_gid) ? 0 : cred->caller_gid; sau_context_t *ctx = sau_create_user_context(uid, gid, 0, 0); if (!ctx) return NULL; if (cred->caller_glen > 0) { gid_t *garray = gsh_malloc((cred->caller_glen + 1) * sizeof(gid_t)); if (garray != NULL) { garray[0] = gid; size_t size = sizeof(gid_t) * cred->caller_glen; memcpy(garray + 1, cred->caller_garray, size); sau_update_groups(instance, ctx, garray, cred->caller_glen + 1); free(garray); } } return ctx; } nfsstat4 saunafsToNfs4Error(int errorCode) { if (!errorCode) { LogWarn(COMPONENT_FSAL, "appropriate errno not set"); errorCode = EINVAL; } return posix2nfs4_error(sau_error_conv(errorCode)); } fsal_status_t saunafsToFsalError(int errorCode) { fsal_status_t status; if (!errorCode) { LogWarn(COMPONENT_FSAL, "appropriate errno not set"); errorCode = EINVAL; } status.minor = errorCode; status.major = posix2fsal_error(sau_error_conv(errorCode)); return status; } fsal_status_t fsalLastError(void) { return saunafsToFsalError(sau_last_err()); } nfsstat4 nfs4LastError(void) { return saunafsToNfs4Error(sau_last_err()); } nfs-ganesha-6.5/src/FSAL/FSAL_SAUNAFS/saunafs_internal.h000066400000000000000000000034051473756622300225250ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* Copyright 2023 Leil Storage OÜ This file is part of SaunaFS. SaunaFS 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, version 3. SaunaFS 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 SaunaFS. If not, see . */ #pragma once #include "FSAL/fsal_localfs.h" #include "saunafs_fsal_types.h" sau_context_t *createContext(sau_t *instance, struct user_cred *cred); void exportOperationsInit(struct export_ops *ops); void handleOperationsInit(struct fsal_obj_ops *ops); /* Functions for allocating/deleting handles */ struct SaunaFSHandle *allocateHandle(const struct stat *attribute, struct SaunaFSExport *export); void deleteHandle(struct SaunaFSHandle *object); /* Functions for ACL */ fsal_status_t getACL(struct SaunaFSExport *export, uint32_t inode, uint32_t ownerId, fsal_acl_t **acl); fsal_status_t setACL(struct SaunaFSExport *export, uint32_t inode, const fsal_acl_t *acl, unsigned int mode); /* Functions for handling errors */ fsal_status_t saunafsToFsalError(sau_err_t errorCode); fsal_status_t fsalLastError(void); nfsstat4 nfs4LastError(void); /* Functions for pNFS */ void pnfsMdsOperationsInit(struct fsal_ops *ops); void exportOperationsPnfs(struct export_ops *ops); void pnfsDsOperationsInit(struct fsal_pnfs_ds_ops *ops); void handleOperationsPnfs(struct fsal_obj_ops *ops); nfs-ganesha-6.5/src/FSAL/FSAL_VFS/000077500000000000000000000000001473756622300163545ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_VFS/CMakeLists.txt000066400000000000000000000027121473756622300211160ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- add_definitions( -D__USE_GNU ) # We explicitly need to include this directory for linking vfs/lustre subfsals # main.c that is generated by cmake in compilation tree outside of source tree. include_directories( "." ) set(LIB_PREFIX 64) add_subdirectory(os) if(USE_FSAL_VFS OR USE_FSAL_LUSTRE) add_subdirectory(vfs) endif(USE_FSAL_VFS OR USE_FSAL_LUSTRE) if(USE_FSAL_XFS) add_subdirectory(xfs) endif(USE_FSAL_XFS) ########### install files ############### nfs-ganesha-6.5/src/FSAL/FSAL_VFS/empty_check_hsm.c000066400000000000000000000020641473756622300216640ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2012) * contributeur : Patrice LUCAS patrice.lucas@cea.fr * * This program 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 3 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 * 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 * * ------------- */ #include "config.h" #include "vfs_methods.h" fsal_status_t check_hsm_by_fd(int fd) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } nfs-ganesha-6.5/src/FSAL/FSAL_VFS/export.c000066400000000000000000000361511473756622300200470ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /* export.c * VFS Super-FSAL export object */ #include "config.h" #include "fsal.h" #include /* used for 'dirname' */ #include #include #include #include #include #include #include #include "gsh_list.h" #include "fsal_convert.h" #include "config_parsing.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_config.h" #include "FSAL/fsal_localfs.h" #include "fsal_handle_syscalls.h" #include "vfs_methods.h" #include "nfs_exports.h" #include "export_mgr.h" #include "subfsal.h" #include "gsh_config.h" /* helpers to/from other VFS objects */ /* export object methods */ static void release(struct fsal_export *exp_hdl) { struct vfs_fsal_export *myself; myself = EXPORT_VFS_FROM_FSAL(exp_hdl); if (op_ctx != NULL && op_ctx->ctx_export != NULL) { LogDebug(COMPONENT_FSAL, "Releasing VFS export %" PRIu16 " for %s", exp_hdl->export_id, ctx_export_path(op_ctx)); } else { LogDebug(COMPONENT_FSAL, "Releasing VFS export %" PRIu16 " on filesystem %s", exp_hdl->export_id, exp_hdl->root_fs->path); } vfs_sub_fini(myself); unclaim_all_export_maps(exp_hdl); fsal_detach_export(exp_hdl->fsal, &exp_hdl->exports); free_export_ops(exp_hdl); gsh_free(myself); /* elvis has left the building */ } static fsal_status_t get_dynamic_info(struct fsal_export *exp_hdl, struct fsal_obj_handle *obj_hdl, fsal_dynamicfsinfo_t *infop) { struct statvfs buffstatvfs; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct fsal_fd *out_fd = NULL; struct vfs_fd *my_fd; struct vfs_fd temp_fd = { FSAL_FD_INIT, -1 }; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int retval = 0; LogFullDebug(COMPONENT_FSAL, "About to check obj %p fs %p", obj_hdl, obj_hdl->fs); if (obj_hdl->fsal != obj_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", obj_hdl->fsal->name, obj_hdl->fs->fsal->name); retval = EXDEV; fsal_error = posix2fsal_error(retval); goto exit; } status = find_fd(&out_fd, obj_hdl, &temp_fd.fsal_fd, NULL, FSAL_O_ANY, false); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct vfs_fd, fsal_fd); retval = fstatvfs(my_fd->fd, &buffstatvfs); if (retval < 0) { fsal_error = posix2fsal_error(errno); retval = errno; goto out; } infop->total_bytes = buffstatvfs.f_frsize * buffstatvfs.f_blocks; infop->free_bytes = buffstatvfs.f_frsize * buffstatvfs.f_bfree; infop->avail_bytes = buffstatvfs.f_frsize * buffstatvfs.f_bavail; infop->total_files = buffstatvfs.f_files; infop->free_files = buffstatvfs.f_ffree; infop->avail_files = buffstatvfs.f_favail; infop->time_delta.tv_sec = 0; infop->time_delta.tv_nsec = FSAL_DEFAULT_TIME_DELTA_NSEC; out: status = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status)); exit: return fsalstat(fsal_error, retval); } /* get_quota * return quotas for this export. * path could cross a lower mount boundary which could * mask lower mount values with those of the export root * if this is a real issue, we can scan each time with setmntent() * better yet, compare st_dev of the file with st_dev of root_fd. * on linux, can map st_dev -> /proc/partitions name -> /dev/ */ static fsal_status_t get_quota(struct fsal_export *exp_hdl, const char *filepath, int quota_type, int quota_id, fsal_quota_t *pquota) { struct dqblk fs_quota; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int retval; int errsv; /** @todo if we later have a config to disallow crossmnt, check * that the quota is in the same file system as export. * Otherwise, the fact that the quota path will have * made the longest match means the path MUST be exported * by this export. */ memset((char *)&fs_quota, 0, sizeof(struct dqblk)); if (!vfs_set_credentials(&op_ctx->creds, exp_hdl->fsal)) { fsal_error = ERR_FSAL_PERM; retval = EPERM; goto out; } /** @todo need to get the right file system... */ retval = QUOTACTL(QCMD(Q_GETQUOTA, quota_type), exp_hdl->root_fs->device, quota_id, (caddr_t)&fs_quota); errsv = errno; vfs_restore_ganesha_credentials(exp_hdl->fsal); if (retval < 0) { fsal_error = posix2fsal_error(errsv); retval = errsv; goto out; } pquota->bhardlimit = fs_quota.dqb_bhardlimit; pquota->bsoftlimit = fs_quota.dqb_bsoftlimit; pquota->curblocks = fs_quota.dqb_curspace; pquota->fhardlimit = fs_quota.dqb_ihardlimit; pquota->fsoftlimit = fs_quota.dqb_isoftlimit; pquota->curfiles = fs_quota.dqb_curinodes; pquota->btimeleft = fs_quota.dqb_btime; pquota->ftimeleft = fs_quota.dqb_itime; pquota->bsize = DEV_BSIZE; out: return fsalstat(fsal_error, retval); } /* set_quota * same lower mount restriction applies */ static fsal_status_t set_quota(struct fsal_export *exp_hdl, const char *filepath, int quota_type, int quota_id, fsal_quota_t *pquota, fsal_quota_t *presquota) { struct dqblk fs_quota; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int retval; int errsv; /** @todo if we later have a config to disallow crossmnt, check * that the quota is in the same file system as export. * Otherwise, the fact that the quota path will have * made the longest match means the path MUST be exported * by this export. */ memset((char *)&fs_quota, 0, sizeof(struct dqblk)); if (pquota->bhardlimit != 0) fs_quota.dqb_bhardlimit = pquota->bhardlimit; if (pquota->bsoftlimit != 0) fs_quota.dqb_bsoftlimit = pquota->bsoftlimit; if (pquota->fhardlimit != 0) fs_quota.dqb_ihardlimit = pquota->fhardlimit; if (pquota->fsoftlimit != 0) fs_quota.dqb_isoftlimit = pquota->fsoftlimit; if (pquota->btimeleft != 0) fs_quota.dqb_btime = pquota->btimeleft; if (pquota->ftimeleft != 0) fs_quota.dqb_itime = pquota->ftimeleft; #ifdef LINUX if (pquota->bhardlimit != 0) fs_quota.dqb_valid |= QIF_BLIMITS; if (pquota->bsoftlimit != 0) fs_quota.dqb_valid |= QIF_BLIMITS; if (pquota->fhardlimit != 0) fs_quota.dqb_valid |= QIF_ILIMITS; if (pquota->btimeleft != 0) fs_quota.dqb_valid |= QIF_BTIME; if (pquota->ftimeleft != 0) fs_quota.dqb_valid |= QIF_ITIME; #endif if (!vfs_set_credentials(&op_ctx->creds, exp_hdl->fsal)) { fsal_error = ERR_FSAL_PERM; retval = EPERM; goto err; } /** @todo need to get the right file system... */ retval = QUOTACTL(QCMD(Q_SETQUOTA, quota_type), exp_hdl->root_fs->device, quota_id, (caddr_t)&fs_quota); errsv = errno; vfs_restore_ganesha_credentials(exp_hdl->fsal); if (retval < 0) { fsal_error = posix2fsal_error(errsv); retval = errsv; goto err; } if (presquota != NULL) return get_quota(exp_hdl, filepath, quota_type, quota_id, presquota); err: return fsalstat(fsal_error, retval); } /* extract a file handle from a buffer. * do verification checks and flag any and all suspicious bits. * Return an updated fh_desc into whatever was passed. The most * common behavior, done here is to just reset the length. * * Setting the length to sizeof(vfs_file_handle_t) coerces all handles * to a value too large for some applications (e.g., ESXi), and much * larger than necessary. (On my Linux system, I'm seeing 12 byte file * handles (EXT4). Since this routine has no idea what the internal * length was, it should not set the value (the length comes from us * anyway, it's up to us to get it right elsewhere). */ static fsal_status_t wire_to_host(struct fsal_export *exp_hdl, fsal_digesttype_t in_type, struct gsh_buffdesc *fh_desc, int flags) { struct fsal_filesystem *fs; bool dummy; vfs_file_handle_t *fh = NULL; vfs_alloc_handle(fh); return vfs_check_handle(exp_hdl, fh_desc, &fs, fh, &dummy); } /** * @brief Function to get the fasl_obj_handle that has fsal_fd as its global fd. * * @param[in] exp_hdl The export in which the handle exists * @param[in] fd File descriptor in question * @param[out] handle FSAL object handle * * @return the fsal_obj_handle. */ void get_fsal_obj_hdl(struct fsal_export *exp_hdl, struct fsal_fd *fd, struct fsal_obj_handle **handle) { struct vfs_fd *my_fd = NULL; struct vfs_fsal_obj_handle *myself = NULL; my_fd = container_of(fd, struct vfs_fd, fsal_fd); myself = container_of(my_fd, struct vfs_fsal_obj_handle, u.file.fd); *handle = &myself->obj_handle; } /* vfs_export_ops_init * overwrite vector entries with the methods that we support */ void vfs_export_ops_init(struct export_ops *ops) { ops->release = release; ops->lookup_path = vfs_lookup_path; ops->wire_to_host = wire_to_host; ops->create_handle = vfs_create_handle; ops->get_fs_dynamic_info = get_dynamic_info; ops->get_quota = get_quota; ops->set_quota = set_quota; ops->alloc_state = vfs_alloc_state; ops->get_fsal_obj_hdl = get_fsal_obj_hdl; } int vfs_claim_filesystem(struct fsal_filesystem *fs, struct fsal_export *exp, void **private_data) { int retval = 0, fd; struct vfs_fsal_export *myself; LogFilesystem("VFS CLAIM FS", "", fs); if (*private_data != NULL) { /* Already claimed, and private_data is already set, nothing to * do here. */ LogDebug( COMPONENT_FSAL, "file system %s is already claimed with fd %d private_data %p", fs->path, (int)(long)*private_data, *private_data); return 0; } myself = EXPORT_VFS_FROM_FSAL(exp); retval = vfs_get_root_handle(fs, myself, &fd); if (retval != 0) { if (retval == ENOTTY) { LogInfo(COMPONENT_FSAL, "file system %s is not exportable with %s", fs->path, exp->fsal->name); retval = ENXIO; } goto errout; } *private_data = (void *)(long)fd; LogDebug(COMPONENT_FSAL, "claiming file system %s fd %d (private_data %p)", fs->path, fd, *private_data); errout: return retval; } void vfs_unclaim_filesystem(struct fsal_filesystem *fs) { LogFilesystem("VFS UNCLAIM FS", "", fs); if (root_fd(fs) > 0) close(root_fd(fs)); LogInfo(COMPONENT_FSAL, "VFS Unclaiming %s", fs->path); } /* create_export * Create an export point and return a handle to it to be kept * in the export list. * First lookup the fsal, then create the export and then put the fsal back. * returns the export with one reference taken. */ fsal_status_t vfs_create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops) { struct vfs_fsal_export *myself; int retval = 0; fsal_status_t fsal_status = { 0, 0 }; vfs_state_init(); myself = gsh_calloc(1, sizeof(struct vfs_fsal_export)); fsal_export_init(&myself->export); vfs_export_ops_init(&myself->export.exp_ops); retval = load_config_from_node(parse_node, vfs_sub_export_param, myself, true, err_type); if (retval != 0) { fsal_status = posix2fsal_status(EINVAL); goto err_free; } myself->export.fsal = fsal_hdl; vfs_sub_init_export_ops(myself, CTX_FULLPATH(op_ctx)); retval = fsal_attach_export(fsal_hdl, &myself->export.exports); if (retval != 0) { fsal_status = posix2fsal_status(retval); goto err_free; /* seriously bad */ } retval = resolve_posix_filesystem(CTX_FULLPATH(op_ctx), fsal_hdl, &myself->export, vfs_claim_filesystem, vfs_unclaim_filesystem, &myself->export.root_fs); if (retval != 0) { LogCrit(COMPONENT_FSAL, "resolve_posix_filesystem(%s) returned %s (%d)", CTX_FULLPATH(op_ctx), strerror(retval), retval); fsal_status = posix2fsal_status(retval); goto err_cleanup; } retval = vfs_sub_init_export(myself); if (retval != 0) { fsal_status = posix2fsal_status(retval); goto err_cleanup; } op_ctx->fsal_export = &myself->export; myself->export.up_ops = up_ops; return fsalstat(ERR_FSAL_NO_ERROR, 0); err_cleanup: unclaim_all_export_maps(&myself->export); fsal_detach_export(fsal_hdl, &myself->export.exports); err_free: free_export_ops(&myself->export); gsh_free(myself); /* elvis has left the building */ return fsal_status; } /** * @brief Update an existing export * * This will result in a temporary fsal_export being created, and built into * a stacked export. * * On entry, op_ctx has the original gsh_export and no fsal_export. * * The caller passes the original fsal_export, as well as the new super_export's * FSAL when there is a stacked export. This will allow the underlying export to * validate that the stacking has not changed. * * This function does not actually create a new fsal_export, the only purpose is * to validate and update the config. * * @param[in] fsal_hdl FSAL module * @param[in] parse_node opaque pointer to parse tree node for * export options to be passed to * load_config_from_node * @param[out] err_type config processing error reporting * @param[in] original The original export that is being updated * @param[in] updated_super The updated super_export's FSAL * * @return FSAL status. */ fsal_status_t vfs_update_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, struct fsal_export *original, struct fsal_module *updated_super) { struct vfs_fsal_export myself; int retval = 0; bool invalid = false; struct vfs_fsal_export *orig = container_of(original, struct vfs_fsal_export, export); fsal_status_t status; /* Check for changes in stacking by calling default update_export. */ status = update_export(fsal_hdl, parse_node, err_type, original, updated_super); if (FSAL_IS_ERROR(status)) return status; memset(&myself, 0, sizeof(myself)); retval = load_config_from_node(parse_node, vfs_sub_export_param, &myself, true, err_type); if (retval != 0) { return posix2fsal_status(EINVAL); } if (orig->fsid_type != myself.fsid_type) { LogCrit(COMPONENT_FSAL, "Can not change fsid_type without restart."); invalid = true; } if (orig->async_hsm_restore != myself.async_hsm_restore) { LogCrit(COMPONENT_FSAL, "Can not change async_hsm_restore without restart."); invalid = true; } return invalid ? posix2fsal_status(EINVAL) : fsalstat(ERR_FSAL_NO_ERROR, 0); } nfs-ganesha-6.5/src/FSAL/FSAL_VFS/file.c000066400000000000000000002062341473756622300174460ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /* file.c * File I/O methods for VFS module */ #include "config.h" #include #include "fsal.h" #include "FSAL/access_check.h" #include "fsal_convert.h" #include #include #include #include "vfs_methods.h" #include "os/subr.h" #include "sal_data.h" fsal_status_t vfs_open_my_fd(struct vfs_fsal_obj_handle *myself, fsal_openflags_t openflags, int posix_flags, struct vfs_fd *my_fd) { int fd; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int retval = 0; LogFullDebug(COMPONENT_FSAL, "my_fd->fd = %d openflags = %x, posix_flags = %x", my_fd->fd, openflags, posix_flags); assert(my_fd->fd == -1 && my_fd->fsal_fd.openflags == FSAL_O_CLOSED && openflags != 0); LogFullDebug(COMPONENT_FSAL, "openflags = %x, posix_flags = %x", openflags, posix_flags); fd = vfs_fsal_open(myself, posix_flags, &fsal_error); if (fd < 0) { retval = -fd; } else { /* Save the file descriptor, make sure we only save the * open modes that actually represent the open file. */ LogFullDebug(COMPONENT_FSAL, "fd = %d, new openflags = %x", fd, openflags); if (fd == 0) LogCrit(COMPONENT_FSAL, "fd = %d, new openflags = %x", fd, openflags); my_fd->fd = fd; my_fd->fsal_fd.openflags = FSAL_O_NFS_FLAGS(openflags); } return fsalstat(fsal_error, retval); } fsal_status_t vfs_close_my_fd(struct vfs_fd *my_fd) { fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int retval = 0; if (my_fd->fd >= 0 && my_fd->fsal_fd.openflags != FSAL_O_CLOSED) { LogFullDebug( COMPONENT_FSAL, "Closing Opened fd %d for fsal_fd(%p) with type(%d)", my_fd->fd, &my_fd->fsal_fd, my_fd->fsal_fd.fd_type); retval = close(my_fd->fd); if (retval < 0) { retval = errno; fsal_error = posix2fsal_error(retval); } my_fd->fd = -1; my_fd->fsal_fd.openflags = FSAL_O_CLOSED; } else { fsal_error = ERR_FSAL_NOT_OPENED; } return fsalstat(fsal_error, retval); } /** * @brief VFS Function to open or reopen a fsal_fd. * * @param[in] obj_hdl File on which to operate * @param[in] openflags New mode for open * @param[out] fsal_fd File descriptor that is to be used * * @return FSAL status. */ fsal_status_t vfs_reopen_func(struct fsal_obj_handle *obj_hdl, fsal_openflags_t openflags, struct fsal_fd *fsal_fd) { struct vfs_fsal_obj_handle *myself; struct vfs_fd *my_fd; int fd; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int retval = 0; int posix_flags = 0; myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); my_fd = container_of(fsal_fd, struct vfs_fd, fsal_fd); fsal2posix_openflags(openflags, &posix_flags); LogFullDebug(COMPONENT_FSAL, "my_fd->fd = %d openflags = %x, posix_flags = %x", my_fd->fd, openflags, posix_flags); fd = vfs_fsal_open(myself, posix_flags, &fsal_error); if (fd < 0) { retval = -fd; } else { if (my_fd->fd != -1) { /* File was previously open, close old fd */ retval = close(my_fd->fd); if (retval < 0) { retval = errno; LogFullDebug(COMPONENT_FSAL, "close failed with %s", strerror(retval)); /** @todo - what to do about error here... */ } } /* Save the file descriptor, make sure we only save the * open modes that actually represent the open file. */ LogFullDebug(COMPONENT_FSAL, "fd = %d, new openflags = %x", fd, openflags); if (fd == 0) LogCrit(COMPONENT_FSAL, "fd = %d, new openflags = %x", fd, openflags); my_fd->fd = fd; my_fd->fsal_fd.openflags = FSAL_O_NFS_FLAGS(openflags); } return fsalstat(fsal_error, retval); } /** * @brief VFS Function to close a fsal_fd. * * @param[in] obj_hdl File on which to operate * @param[in] fd File handle to close * * @return FSAL status. */ fsal_status_t vfs_close_func(struct fsal_obj_handle *obj_hdl, struct fsal_fd *fd) { return vfs_close_my_fd(container_of(fd, struct vfs_fd, fsal_fd)); } /* vfs_close * Close the file if it is still open. */ fsal_status_t vfs_close(struct fsal_obj_handle *obj_hdl) { struct vfs_fsal_obj_handle *myself; fsal_status_t status; assert(obj_hdl->type == REGULAR_FILE); myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); if (obj_hdl->fsal != obj_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", obj_hdl->fsal->name, obj_hdl->fs->fsal->name); return fsalstat(posix2fsal_error(EXDEV), EXDEV); } status = close_fsal_fd(obj_hdl, &myself->u.file.fd.fsal_fd, false); if (FSAL_IS_ERROR(status)) return status; /** @todo don't merge me... hack to avoid legacy open_fd_count */ return fsalstat(ERR_FSAL_NOT_OPENED, 0); } void vfs_free_state(struct state_t *state) { struct vfs_fd *my_fd; my_fd = &container_of(state, struct vfs_state_fd, state)->vfs_fd; LogFullDebug(COMPONENT_FSAL, "Destroying fd %d for fsal_fd(%p) with type(%d)", my_fd->fd, &my_fd->fsal_fd, my_fd->fsal_fd.fd_type); destroy_fsal_fd(&my_fd->fsal_fd); gsh_free(state); } /** * @brief Allocate a state_t structure * * Note that this is not expected to fail since memory allocation is * expected to abort on failure. * * @param[in] exp_hdl Export state_t will be associated with * @param[in] state_type Type of state to allocate * @param[in] related_state Related state if appropriate * * @returns a state structure. */ struct state_t *vfs_alloc_state(struct fsal_export *exp_hdl, enum state_type state_type, struct state_t *related_state) { struct state_t *state; struct vfs_fd *my_fd; state = init_state(gsh_calloc(1, sizeof(struct vfs_state_fd)), vfs_free_state, state_type, related_state); my_fd = &container_of(state, struct vfs_state_fd, state)->vfs_fd; init_fsal_fd(&my_fd->fsal_fd, FSAL_FD_STATE, op_ctx->fsal_export); my_fd->fd = -1; return state; } /** * @brief Merge a duplicate handle with an original handle * * This function is used if an upper layer detects that a duplicate * object handle has been created. It allows the FSAL to merge anything * from the duplicate back into the original. * * The caller must release the object (the caller may have to close * files if the merge is unsuccessful). * * @param[in] orig_hdl Original handle * @param[in] dupe_hdl Handle to merge into original * * @return FSAL status. * */ fsal_status_t vfs_merge(struct fsal_obj_handle *orig_hdl, struct fsal_obj_handle *dupe_hdl) { fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; if (orig_hdl->type == REGULAR_FILE && dupe_hdl->type == REGULAR_FILE) { /* We need to merge the share reservations on this file. * This could result in ERR_FSAL_SHARE_DENIED. */ struct vfs_fsal_obj_handle *orig, *dupe; orig = container_of(orig_hdl, struct vfs_fsal_obj_handle, obj_handle); dupe = container_of(dupe_hdl, struct vfs_fsal_obj_handle, obj_handle); /* This can block over an I/O operation. */ status = merge_share(orig_hdl, &orig->u.file.share, &dupe->u.file.share); } return status; } static fsal_status_t fetch_attrs(struct vfs_fsal_obj_handle *myself, int my_fd, struct fsal_attrlist *attrs) { struct stat stat; int retval = 0; fsal_status_t status = { 0, 0 }; const char *func = "unknown"; #ifdef __FreeBSD__ struct fhandle *handle; #endif /* Now stat the file as appropriate */ switch (myself->obj_handle.type) { case SOCKET_FILE: case CHARACTER_FILE: case BLOCK_FILE: retval = fstatat(my_fd, myself->u.unopenable.name, &stat, AT_SYMLINK_NOFOLLOW); func = "fstatat"; break; case REGULAR_FILE: retval = fstat(my_fd, &stat); func = "fstat"; break; case SYMBOLIC_LINK: #ifdef __FreeBSD__ handle = v_to_fhandle(myself->handle->handle_data); retval = fhstat(handle, &stat); func = "fhstat"; break; #endif case FIFO_FILE: case DIRECTORY: retval = vfs_stat_by_handle(my_fd, &stat); func = "vfs_stat_by_handle"; break; case NO_FILE_TYPE: case EXTENDED_ATTR: /* Caught during open with EINVAL */ break; } if (retval < 0) { if (errno == ENOENT) retval = ESTALE; else retval = errno; LogDebug(COMPONENT_FSAL, "%s failed with %s", func, strerror(retval)); if (attrs->request_mask & ATTR_RDATTR_ERR) { /* Caller asked for error to be visible. */ attrs->valid_mask = ATTR_RDATTR_ERR; } return fsalstat(posix2fsal_error(retval), retval); } posix2fsal_attributes_all(&stat, attrs); /* Get correct fsid from the fsal_filesystem, it may * not be the device major/minor from stat. */ attrs->fsid = myself->obj_handle.fs->fsid; if (myself->sub_ops && myself->sub_ops->getattrs) { status = myself->sub_ops->getattrs(myself, my_fd, attrs->request_mask, attrs); if (FSAL_IS_ERROR(status) && (attrs->request_mask & ATTR_RDATTR_ERR) != 0) { /* Caller asked for error to be visible. */ attrs->valid_mask = ATTR_RDATTR_ERR; } } return status; } static fsal_status_t vfs_open2_by_handle(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, fsal_verifier_t verifier, struct fsal_attrlist *attrs_out) { struct vfs_fd *my_fd = NULL; struct fsal_fd *fsal_fd; struct vfs_fsal_obj_handle *myself; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; fsal_openflags_t old_openflags; bool truncated = openflags & FSAL_O_TRUNC; bool is_fresh_open = false; myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); if (state != NULL) my_fd = &container_of(state, struct vfs_state_fd, state)->vfs_fd; else my_fd = &myself->u.file.fd; fsal_fd = &my_fd->fsal_fd; if (obj_hdl->fsal != obj_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", obj_hdl->fsal->name, obj_hdl->fs->fsal->name); return fsalstat(posix2fsal_error(EXDEV), EXDEV); } /* Indicate we want to do fd work (can't fail since not reclaiming) */ fsal_start_fd_work_no_reclaim(fsal_fd); old_openflags = my_fd->fsal_fd.openflags; if (state != NULL) { /* Prepare to take the share reservation, but only if we are * called with a valid state (if state is NULL the caller is a * stateless create such as NFS v3 CREATE and we're just going * to ignore share reservation stuff). */ /* Now that we have the mutex, and no I/O is in progress so we * have exclusive access to the share's fsal_fd, we can look at * its openflags. We also need to work the share reservation so * take the obj_lock. NOTE: This is the ONLY sequence where both * a work_mutex and the obj_lock are taken, so there is no * opportunity for ABBA deadlock. * * Note that we do hold the obj_lock over an open and a close * which is longer than normal, but the previous iteration of * the code held the obj lock (read granted) over whole I/O * operations... We don't block over I/O because we've assured * that no I/O is in progress or can start before proceeding * past the above while loop. */ PTHREAD_RWLOCK_wrlock(&obj_hdl->obj_lock); /* Now check the new share. */ status = check_share_conflict(&myself->u.file.share, openflags, false); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "check_share_conflict returned %s", fsal_err_txt(status)); goto exit; } } /* Check for a genuine no-op open. That means we aren't trying to * create, the file is already open in the same mode with the same * deny flags, and we aren't trying to truncate. In this case we want * to avoid bouncing the fd. In the case of JUST changing the deny mode * or an replayed exclusive create, we might bounce the fd when we could * have avoided that, but those scenarios are much less common. */ if (FSAL_O_NFS_FLAGS(openflags) == FSAL_O_NFS_FLAGS(old_openflags) && truncated == false && createmode == FSAL_NO_CREATE) { LogFullDebug(COMPONENT_FSAL, "no-op reopen2 my_fd->fd = %d openflags = %x", my_fd->fd, openflags); goto exit; } /* Tracking if we were going to reopen a fd that was * closed by another thread before we got here. */ is_fresh_open = ((old_openflags == FSAL_O_CLOSED) && (my_fd->fd < 0)); /* No share conflict, re-open the share fd */ status = vfs_reopen_func(obj_hdl, openflags, fsal_fd); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "vfs_reopen_func returned %s", fsal_err_txt(status)); goto exit; } /* Inserts to fd_lru only if open succeeds */ if (is_fresh_open) { /* This is actually an open, need to increment * appropriate counter and insert into LRU. */ insert_fd_lru(fsal_fd); } else { /* Bump up the FD in fd_lru as it was already in fd lru. */ bump_fd_lru(fsal_fd); } /* Check HSM status */ status = check_hsm_by_fd(my_fd->fd); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "check_hsm_by_fd returned %s", fsal_err_txt(status)); if (status.major == ERR_FSAL_DELAY) { LogInfo(COMPONENT_FSAL, "HSM restore at open for fd=%d", my_fd->fd); } goto out; } if (createmode >= FSAL_EXCLUSIVE || (truncated && attrs_out)) { /* NOTE: won't come in here when called from vfs_reopen2... * truncated might be set, but attrs_out will be NULL. */ /* Refresh the attributes */ struct fsal_attrlist attrs; attrmask_t attrs_mask = ATTR_ATIME | ATTR_MTIME; if (attrs_out) attrs_mask |= attrs_out->request_mask; fsal_prepare_attrs(&attrs, attrs_mask); status = fetch_attrs(myself, my_fd->fd, &attrs); if (FSAL_IS_SUCCESS(status)) { LogFullDebug(COMPONENT_FSAL, "New size = %" PRIx64, attrs.filesize); if (createmode >= FSAL_EXCLUSIVE && createmode != FSAL_EXCLUSIVE_9P && !check_verifier_attrlist(&attrs, verifier, obj_hdl->fs->trunc_verif)) { /* Verifier didn't match, return EEXIST */ status = fsalstat(posix2fsal_error(EEXIST), EEXIST); } else if (attrs_out) { fsal_copy_attrs(attrs_out, &attrs, true); } } fsal_release_attrs(&attrs); } else if (attrs_out && attrs_out->request_mask & ATTR_RDATTR_ERR) { attrs_out->valid_mask = ATTR_RDATTR_ERR; } out: if (FSAL_IS_ERROR(status)) { if (is_fresh_open) { /* Now that we have decided to close this FD, * let's clean it off from fd_lru and * ensure counters are decremented. */ remove_fd_lru(fsal_fd); } /* Close fd */ (void)vfs_close_my_fd(my_fd); } exit: if (state != NULL) { if (!FSAL_IS_ERROR(status)) { /* Success, establish the new share. */ update_share_counters(&myself->u.file.share, old_openflags, openflags); } /* Release obj_lock. */ PTHREAD_RWLOCK_unlock(&obj_hdl->obj_lock); } /* Indicate we are done with fd work and signal any waiters. */ fsal_complete_fd_work(fsal_fd); return status; } /** * @brief Open a file descriptor for read or write and possibly create * * This function opens a file for read or write, possibly creating it. * If the caller is passing a state, it must hold the state_lock * exclusive. * * state can be NULL which indicates a stateless open (such as via the * NFS v3 CREATE operation), in which case the FSAL must assure protection * of any resources. If the file is being created, such protection is * simple since no one else will have access to the object yet, however, * in the case of an exclusive create, the common resources may still need * protection. * * If Name is NULL, obj_hdl is the file itself, otherwise obj_hdl is the * parent directory. * * On an exclusive create, the upper layer may know the object handle * already, so it MAY call with name == NULL. In this case, the caller * expects just to check the verifier. * * On a call with an existing object handle for an UNCHECKED create, * we can set the size to 0. * * At least the mode attribute must be set if createmode is not FSAL_NO_CREATE. * Some FSALs may still have to pass a mode on a create call for exclusive, * and even with FSAL_NO_CREATE, and empty set of attributes MUST be passed. * * If an open by name succeeds and did not result in Ganesha creating a file, * the caller will need to do a subsequent permission check to confirm the * open. This is because the permission attributes were not available * beforehand. * * The caller is expected to invoke fsal_release_attrs to release any * resources held by the set attributes. The FSAL layer MAY have added an * inherited ACL. * * The mask should be set in attrs_out indicating which attributes are * desired. Note that since this implies a new object is created, if * the attributes are not fetched, the fsal_obj_handle itself would not * be able to be created and the whole request will fail. * * The attributes will not be returned if this is an open by object as * opposed to an open by name. * * @note If the file was created, @a new_obj has been ref'd * * @param[in] obj_hdl File to open or parent directory * @param[in,out] state state_t to use for this operation * @param[in] openflags Mode for open * @param[in] createmode Mode for create * @param[in] name Name for file if being created or * opened * @param[in] attrs_in Attributes to set on created file * @param[in] verifier Verifier to use for exclusive create * @param[in,out] new_obj Newly created object * @param[in,out] attrs_out Optional attributes for newly created * object * @param[in,out] caller_perm_check The caller must do a permission check * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @return FSAL status. */ fsal_status_t vfs_open2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attrib_set, fsal_verifier_t verifier, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { int posix_flags = 0; int fd, dir_fd; int retval = 0; mode_t unix_mode = 0000; fsal_status_t status = { 0, 0 }; struct vfs_fd *my_fd = NULL; struct vfs_fsal_obj_handle *myself, *hdl = NULL; struct stat stat; vfs_file_handle_t *fh = NULL; bool created = false; if (state != NULL) my_fd = &container_of(state, struct vfs_state_fd, state)->vfs_fd; myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); LogAttrlist(COMPONENT_FSAL, NIV_FULL_DEBUG, "attrib_set ", attrib_set, false); fsal2posix_openflags(openflags, &posix_flags); if (createmode >= FSAL_EXCLUSIVE) { /* Now fixup attrs for verifier if exclusive create */ set_common_verifier(attrib_set, verifier, obj_hdl->fs->trunc_verif); } if (name == NULL) { status = vfs_open2_by_handle(obj_hdl, state, openflags, createmode, verifier, attrs_out); *caller_perm_check = FSAL_IS_SUCCESS(status); return status; } /* In this path where we are opening by name, we can't check share * reservation yet since we don't have an object_handle yet. If we * indeed create the object handle (there is no race with another * open by name), then there CAN NOT be a share conflict, otherwise * the share conflict will be resolved when the object handles are * merged. */ #ifdef ENABLE_VFS_DEBUG_ACL if (createmode != FSAL_NO_CREATE) { /* Need to amend attributes for inherited ACL, these will be * set later. We also need to test for permission to create * since there might be an ACL. */ struct fsal_attrlist attrs; fsal_accessflags_t access_type; access_type = FSAL_MODE_MASK_SET(FSAL_W_OK) | FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_ADD_FILE); status = obj_hdl->obj_ops->test_access(obj_hdl, access_type, NULL, NULL, false); if (FSAL_IS_ERROR(status)) return status; fsal_prepare_attrs(&attrs, ATTR_ACL); status = obj_hdl->obj_ops->getattrs(obj_hdl, &attrs); if (FSAL_IS_ERROR(status)) return status; status.major = fsal_inherit_acls(attrib_set, attrs.acl, FSAL_ACE_FLAG_FILE_INHERIT); /* Done with the attrs */ fsal_release_attrs(&attrs); if (FSAL_IS_ERROR(status)) return status; } #endif /* ENABLE_VFS_DEBUG_ACL */ if (createmode != FSAL_NO_CREATE) { /* Now add in O_CREAT and O_EXCL. */ posix_flags |= O_CREAT; /* And if we are at least FSAL_GUARDED, do an O_EXCL create. */ if (createmode >= FSAL_GUARDED) posix_flags |= O_EXCL; /* Fetch the mode attribute to use in the openat system call. */ unix_mode = fsal2unix_mode(attrib_set->mode) & ~op_ctx->fsal_export->exp_ops.fs_umask( op_ctx->fsal_export); /* Don't set the mode if we later set the attributes */ FSAL_UNSET_MASK(attrib_set->valid_mask, ATTR_MODE); } if (createmode == FSAL_UNCHECKED && (attrib_set->valid_mask != 0)) { /* If we have FSAL_UNCHECKED and want to set more attributes * than the mode, we attempt an O_EXCL create first, if that * succeeds, then we will be allowed to set the additional * attributes, otherwise, we don't know we created the file * and this can NOT set the attributes. */ posix_flags |= O_EXCL; } dir_fd = vfs_fsal_open(myself, O_PATH | O_NOACCESS, &status.major); if (dir_fd < 0) return fsalstat(status.major, -dir_fd); /** @todo: not sure what this accomplishes... */ retval = vfs_stat_by_handle(dir_fd, &stat); if (retval < 0) { retval = errno; status = posix2fsal_status(retval); goto direrr; } /* Become the user because we are creating an object in this dir. */ if (createmode != FSAL_NO_CREATE) if (!vfs_set_credentials(&op_ctx->creds, obj_hdl->fsal)) { status = posix2fsal_status(EPERM); goto direrr; } if ((posix_flags & O_CREAT) != 0) fd = openat(dir_fd, name, posix_flags, unix_mode); else fd = openat(dir_fd, name, posix_flags); if (fd == -1 && errno == EEXIST && createmode == FSAL_UNCHECKED) { /* We tried to create O_EXCL to set attributes and failed. * Remove O_EXCL and retry. We still try O_CREAT again just in * case file disappears out from under us. * * Note that because we have dropped O_EXCL, later on we will * not assume we created the file, and thus will not set * additional attributes. We don't need to separately track * the condition of not wanting to set attributes. */ posix_flags &= ~O_EXCL; fd = openat(dir_fd, name, posix_flags, unix_mode); /* Preserve errno */ retval = errno; /* If we were creating, restore credentials now. */ if (createmode != FSAL_NO_CREATE) vfs_restore_ganesha_credentials(obj_hdl->fsal); LogFullDebug( COMPONENT_FSAL, "File %s exists, retried UNCHECKED create with out O_EXCL, returned %d (%s)", name, retval, strerror(retval)); } else { /* Preserve errno */ retval = errno; /* If we were creating, restore credentials now. */ if (createmode != FSAL_NO_CREATE) vfs_restore_ganesha_credentials(obj_hdl->fsal); } if (fd < 0) { status = posix2fsal_status(retval); goto direrr; } LogFullDebug(COMPONENT_FSAL, "Opened fd=%d for file %s", fd, name); /* Check HSM status */ status = check_hsm_by_fd(fd); if (FSAL_IS_ERROR(status)) { if (status.major == ERR_FSAL_DELAY) { LogInfo(COMPONENT_FSAL, "HSM restore at open for fd=%d for file %s", fd, name); status = posix2fsal_status(EAGAIN); } goto fileerr; } /* Remember if we were responsible for creating the file. * Note that in an UNCHECKED retry we MIGHT have re-created the * file and won't remember that. Oh well, so in that rare case we * leak a partially created file if we have a subsequent error in here. */ created = (posix_flags & O_EXCL) != 0; /** @todo FSF: If we are running with ENABLE_VFS_DEBUG_ACL or a * VFS sub-FSAL that supports ACLs but doesn't permission * check using those ACLs during openat, then there may be * permission differences here... * * There are three cases at issue: * 1. If the ACL is more permissive for the caller than * the mode, and the ACLs are not evaluated by openat * then a create might fail when the ACL would allow it. * There's nothing to be done there. Ganesha doesn't * evaluate directory permissions for create. * 2. An UNCHECKED create where the file already exists * and the ACL is more permissive then the mode could * fail. This COULD have been permission checked by * Ganesha... * 3. An UNCHECKED create where the file already exists * and the ACL is less permissive then the mode could * succeed. This COULD have been permission checked by * Ganesha... * * These cases are only relevant for create, since if * create is not in effect, we don't do openat using * the caller's credentials and instead force Ganesha to * perform the permission check. */ /* Do a permission check if we were not attempting to create. If we * were attempting any sort of create, then the openat call was made * with the caller's credentials active and as such was permission * checked. */ *caller_perm_check = createmode == FSAL_NO_CREATE; vfs_alloc_handle(fh); retval = vfs_name_to_handle(dir_fd, obj_hdl->fs, name, fh); if (retval < 0) { retval = errno; status = posix2fsal_status(retval); goto fileerr; } retval = fstat(fd, &stat); if (retval < 0) { retval = errno; status = posix2fsal_status(retval); goto fileerr; } /* Check if the opened file is not a regular file. */ if (posix2fsal_type(stat.st_mode) == DIRECTORY) { /* Trying to open2 a directory */ status = fsalstat(ERR_FSAL_ISDIR, 0); goto fileerr; } if (posix2fsal_type(stat.st_mode) != REGULAR_FILE) { /* Trying to open2 any other non-regular file */ status = fsalstat(ERR_FSAL_SYMLINK, 0); goto fileerr; } /* allocate an obj_handle and fill it up */ hdl = alloc_handle(dir_fd, fh, obj_hdl->fs, &stat, myself->handle, name, op_ctx->fsal_export); if (hdl == NULL) { status = posix2fsal_status(ENOMEM); goto fileerr; } /* If we didn't have a state above, use the global fd. At this point, * since we just created the global fd, no one else can have a * reference to it, and thus we can mamnipulate unlocked which is * handy since we can then call setattr2 which WILL take the lock * without a double locking deadlock. */ if (my_fd == NULL) { my_fd = &hdl->u.file.fd; LogFullDebug( COMPONENT_FSAL, "Using global fd with fsal_fd(%p) for fd(%d/%d) type(%d)", &my_fd->fsal_fd, my_fd->fd, fd, my_fd->fsal_fd.fd_type); /* Need to LRU track global fd including incrementing * fsal_fd_global_counter. */ insert_fd_lru(&my_fd->fsal_fd); } my_fd->fd = fd; my_fd->fsal_fd.openflags = FSAL_O_NFS_FLAGS(openflags); *new_obj = &hdl->obj_handle; if (created && attrib_set->valid_mask != 0) { retry_attr: /* Set attributes using our newly opened file descriptor as the * share_fd if there are any left to set (mode and truncate * have already been handled). * * Note that we only set the attributes if we were responsible * for creating the file and we have attributes to set. * * Note if we have ENABLE_VFS_DEBUG_ACL an inherited ACL might * be part of the attributes we are setting here. */ status = (*new_obj)->obj_ops->setattr2(*new_obj, false, state, attrib_set); if (FSAL_IS_ERROR(status)) goto fileerr; if (attrs_out != NULL) { status = (*new_obj)->obj_ops->getattrs(*new_obj, attrs_out); if (FSAL_IS_ERROR(status) && (attrs_out->request_mask & ATTR_RDATTR_ERR) == 0) { /* Get attributes failed and caller expected * to get the attributes. Otherwise continue * with attrs_out indicating ATTR_RDATTR_ERR. */ goto fileerr; } LogFullDebug(COMPONENT_FSAL, "Set atime %llx %llx mtime %llx %llx", (long long)attrs_out->atime.tv_sec, (long long)attrs_out->atime.tv_nsec, (long long)attrs_out->mtime.tv_sec, (long long)attrs_out->mtime.tv_nsec); if ((createmode >= FSAL_EXCLUSIVE) && (!(*new_obj)->fs->trunc_verif) && ((attrs_out->atime.tv_sec != attrib_set->atime.tv_sec) || (attrs_out->mtime.tv_sec != attrib_set->mtime.tv_sec))) { LogInfo(COMPONENT_FSAL, "Verifier was not stored correctly for filesystem %s, trying again with truncated verifier", (*new_obj)->fs->path); (*new_obj)->fs->trunc_verif = true; FSAL_UNSET_MASK(attrib_set->valid_mask, ATTR_ATIME | ATTR_MTIME); set_common_verifier(attrib_set, verifier, true); goto retry_attr; } } } else if (attrs_out != NULL) { /* Since we haven't set any attributes other than what was set * on create (if we even created), just use the stat results * we used to create the fsal_obj_handle. */ posix2fsal_attributes_all(&stat, attrs_out); /* Get correct fsid from the fsal_filesystem, it may * not be the device major/minor from stat. */ attrs_out->fsid = myself->obj_handle.fs->fsid; } LogFullDebug(COMPONENT_FSAL, "Closing Opened dir fd %d", dir_fd); close(dir_fd); if (state != NULL) { /* Prepare to take the share reservation, but only if we are * called with a valid state (if state is NULL the caller is * a stateless create such as NFS v3 CREATE). */ /* Take the share reservation now by updating the counters. */ update_share_counters_locked(*new_obj, &hdl->u.file.share, FSAL_O_CLOSED, openflags); } return fsalstat(ERR_FSAL_NO_ERROR, 0); fileerr: /* hdl->u.file.fd will be close in obj_ops->release */ if (my_fd == &hdl->u.file.fd) { LogFullDebug(COMPONENT_FSAL, "Closing Opened fd %d", fd); close(fd); } if (*new_obj) { /* Release the handle we just allocated. */ (*new_obj)->obj_ops->release(*new_obj); *new_obj = NULL; } /* Delete the file if we actually created it. */ if (created) unlinkat(dir_fd, name, 0); direrr: LogFullDebug(COMPONENT_FSAL, "Closing Opened dir fd %d", dir_fd); close(dir_fd); return status; } /** * @brief Return open status of a state. * * This function returns open flags representing the current open * status for a state. The st_lock must be held. * * @param[in] obj_hdl File on which to operate * @param[in] state File state to interrogate * * @retval Flags representing current open status */ fsal_openflags_t vfs_status2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { struct vfs_fd *my_fd = &((struct vfs_state_fd *)state)->vfs_fd; return my_fd->fsal_fd.openflags; } /** * @brief Re-open a file that may be already opened * * This function supports changing the access mode of a share reservation and * thus should only be called with a share state. The st_lock must be held. * * This MAY be used to open a file the first time if there is no need for * open by name or create semantics. One example would be 9P lopen. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] openflags Mode for re-open * * @return FSAL status. */ fsal_status_t vfs_reopen2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags) { return vfs_open2_by_handle(obj_hdl, state, openflags, FSAL_NO_CREATE, NULL, NULL); } /** * @brief Find a usable fd for getattr and setattr. * * NOTE: If an fd is returned, fsal_complete_io must be called to close it. * If the global or state fd is used, fsal_start_io will have done its * thing. * * @param[in,out] out_fd The fd to be used * @param[in] obj_hdl The object being worked on * @param[in] tmp_fd A temp fd the caller passes in to be used if * a state fd or the global fd is not usable. Will * be used for all non-regular files. * @param[in] state The state if any. * @param[in] openflags How the getattr or setattr needs the fd open * @param[in] bypass If state doesn't indicate a share reservation, * bypass any deny read * * @return FSAL status. */ fsal_status_t find_fd(struct fsal_fd **out_fd, struct fsal_obj_handle *obj_hdl, struct fsal_fd *tmp_fd, struct state_t *state, fsal_openflags_t openflags, bool bypass) { struct vfs_fsal_obj_handle *myself; struct vfs_fd *my_fd; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; int rc, posix_flags; myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); if (obj_hdl->type == REGULAR_FILE) { status = fsal_start_io(out_fd, obj_hdl, &myself->u.file.fd.fsal_fd, tmp_fd, state, openflags, false, NULL, bypass, &myself->u.file.share); return status; } /* Setup to use tmp_fd for non-regiular files. The way we set it up, * it's OK to call fsal_complete_io() to close the file which means * our caller doesn't have to do anything special with the found fd... */ my_fd = container_of(tmp_fd, struct vfs_fd, fsal_fd); if (openflags == FSAL_O_ANY) { /* Since this is a temp file, we will open READ */ openflags = FSAL_O_READ; } fsal2posix_openflags(openflags, &posix_flags); /* Handle nom-regular files */ switch (obj_hdl->type) { case SOCKET_FILE: case CHARACTER_FILE: case BLOCK_FILE: rc = vfs_open_by_handle(myself->obj_handle.fs, myself->u.unopenable.dir, O_PATH | O_NOACCESS, &status.major); if (rc < 0) { LogDebug(COMPONENT_FSAL, "Failed with %s openflags 0x%08x", strerror(-rc), O_PATH | O_NOACCESS); return posix2fsal_status(-rc); } goto success; case REGULAR_FILE: /* Already handled above */ assert(false); break; case SYMBOLIC_LINK: posix_flags |= (O_PATH | O_RDWR | O_NOFOLLOW); break; case FIFO_FILE: posix_flags |= O_NONBLOCK; break; case DIRECTORY: break; case NO_FILE_TYPE: case EXTENDED_ATTR: return posix2fsal_status(EINVAL); } /* Open file descriptor for non-regular files. */ rc = vfs_fsal_open(myself, posix_flags, &status.major); if (rc < 0) { LogDebug(COMPONENT_FSAL, "Failed with %s openflags 0x%08x", strerror(-rc), openflags); return posix2fsal_status(-rc); } success: /* We will want to close the temp_fd. */ tmp_fd->close_on_complete = true; LogFullDebug( COMPONENT_FSAL, "Opened fd=%d for file %p of type %s with open flags 0x%08x", rc, myself, object_file_type_to_str(obj_hdl->type), openflags); /* Finish setting up tmp_fd. */ my_fd->fd = rc; my_fd->fsal_fd.openflags = FSAL_O_NFS_FLAGS(openflags); *out_fd = tmp_fd; return status; } /** * @brief Read data from a file * * This function reads data from the given file. The FSAL must be able to * perform the read whether a state is presented or not. This function also * is expected to handle properly bypassing or not share reservations. This is * an (optionally) asynchronous call. When the I/O is complete, the done * callback is called with the results. * * @param[in] obj_hdl File on which to operate * @param[in] bypass If state doesn't indicate a share reservation, * bypass any deny read * @param[in,out] done_cb Callback to call when I/O is done * @param[in,out] read_arg Info about read, passed back in callback * @param[in,out] caller_arg Opaque arg from the caller for callback * * @return Nothing; results are in callback */ void vfs_read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg) { ssize_t nb_read; fsal_status_t status = { 0, 0 }, status2; struct vfs_fd *my_fd; struct vfs_fd temp_fd = { FSAL_FD_INIT, -1 }; struct fsal_fd *out_fd; struct vfs_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); if (read_arg->info != NULL) { /* Currently we don't support READ_PLUS */ status = posix2fsal_status(ENOTSUP); goto exit; } if (obj_hdl->fsal != obj_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", obj_hdl->fsal->name, obj_hdl->fs->fsal->name); status = posix2fsal_status(EXDEV); goto exit; } /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->u.file.fd.fsal_fd, &temp_fd.fsal_fd, read_arg->state, FSAL_O_READ, false, NULL, bypass, &myself->u.file.share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct vfs_fd, fsal_fd); nb_read = preadv(my_fd->fd, read_arg->iov, read_arg->iov_count, read_arg->offset); if (read_arg->offset == -1 || nb_read == -1) { status = posix2fsal_status(errno); LogFullDebug(COMPONENT_FSAL, "preadv failed returning %s", fsal_err_txt(status)); goto out; } read_arg->io_amount = nb_read; read_arg->end_of_file = (nb_read == 0); #if 0 /** @todo * * Is this all we really need to do to support READ_PLUS? Will anyone * ever get upset that we don't return holes, even for blocks of all * zeroes? * */ if (info != NULL) { info->io_content.what = NFS4_CONTENT_DATA; info->io_content.data.d_offset = offset + nb_read; info->io_content.data.d_data.data_len = nb_read; info->io_content.data.d_data.data_val = buffer; } #endif out: status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (read_arg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->u.file.share, FSAL_O_READ, FSAL_O_CLOSED); } exit: done_cb(obj_hdl, status, read_arg, caller_arg); } /** * @brief Write data to a file * * This function writes data to a file. The FSAL must be able to * perform the write whether a state is presented or not. This function also * is expected to handle properly bypassing or not share reservations. Even * with bypass == true, it will enforce a mandatory (NFSv4) deny_write if * an appropriate state is not passed). * * The FSAL is expected to enforce sync if necessary. * * @param[in] obj_hdl File on which to operate * @param[in] bypass If state doesn't indicate a share reservation, * bypass any non-mandatory deny write * @param[in,out] done_cb Callback to call when I/O is done * @param[in,out] write_arg Info about write, passed back in callback * @param[in,out] caller_arg Opaque arg from the caller for callback * * @return FSAL status. */ void vfs_write2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *caller_arg) { ssize_t nb_written; fsal_status_t status, status2; int retval = 0; struct vfs_fd *my_fd; struct vfs_fd temp_fd = { FSAL_FD_INIT, -1 }; struct fsal_fd *out_fd; struct vfs_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); if (obj_hdl->fsal != obj_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", obj_hdl->fsal->name, obj_hdl->fs->fsal->name); status = posix2fsal_status(EXDEV); goto exit; } /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->u.file.fd.fsal_fd, &temp_fd.fsal_fd, write_arg->state, FSAL_O_WRITE, false, NULL, bypass, &myself->u.file.share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct vfs_fd, fsal_fd); if (!vfs_set_credentials(&op_ctx->creds, obj_hdl->fsal)) { status = posix2fsal_status(EPERM); LogFullDebug(COMPONENT_FSAL, "vfs_set_credentials failed returning %s", fsal_err_txt(status)); goto out2; } nb_written = pwritev(my_fd->fd, write_arg->iov, write_arg->iov_count, write_arg->offset); if (nb_written == -1) { status = posix2fsal_status(errno); LogFullDebug(COMPONENT_FSAL, "pwritev failed returning %s", fsal_err_txt(status)); goto out; } write_arg->io_amount = nb_written; if (write_arg->fsal_stable) { retval = fsync(my_fd->fd); if (retval == -1) { status2 = posix2fsal_status(errno); write_arg->fsal_stable = false; LogFullDebug(COMPONENT_FSAL, "fsync returned %s", fsal_err_txt(status2)); } } out: vfs_restore_ganesha_credentials(obj_hdl->fsal); out2: status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (write_arg->state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->u.file.share, FSAL_O_WRITE, FSAL_O_CLOSED); } exit: done_cb(obj_hdl, status, write_arg, caller_arg); } /** * @brief Seek to data or hole * * This function seek to data or hole in a file. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in,out] info Information about the data * * @return FSAL status. */ #ifdef __USE_GNU fsal_status_t vfs_seek2(struct fsal_obj_handle *obj_hdl, struct state_t *state, struct io_info *info) { struct vfs_fsal_obj_handle *myself; off_t ret = 0, offset = info->io_content.hole.di_offset; int what = 0; fsal_status_t status, status2; struct fsal_attrlist attrs; struct vfs_fd *my_fd; struct vfs_fd temp_fd = { FSAL_FD_INIT, -1 }; struct fsal_fd *out_fd; myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); /** @todo: WARNING there's a size check that isn't atomic, Fixing this * would probably mean taking the object lock, but that would * not be entirely safe since we no longer use the object lock * to proect I/O on the object... */ /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->u.file.fd.fsal_fd, &temp_fd.fsal_fd, state, FSAL_O_ANY, false, NULL, true, NULL); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct vfs_fd, fsal_fd); fsal_prepare_attrs(&attrs, (op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) & ~(ATTR_ACL | ATTR4_FS_LOCATIONS))); status = fetch_attrs(myself, my_fd->fd, &attrs); fsal_release_attrs(&attrs); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fetch_attrs failed returning %s", fsal_err_txt(status)); goto out; } /* RFC7862 15.11.3, * If the sa_offset is beyond the end of the file, * then SEEK MUST return NFS4ERR_NXIO. */ if (offset >= attrs.filesize) { status = posix2fsal_status(ENXIO); LogFullDebug(COMPONENT_FSAL, "offset >= file size, returning %s", fsal_err_txt(status)); goto out; } if (info->io_content.what == NFS4_CONTENT_DATA) { what = SEEK_DATA; } else if (info->io_content.what == NFS4_CONTENT_HOLE) { what = SEEK_HOLE; } else { status = fsalstat(ERR_FSAL_UNION_NOTSUPP, 0); goto out; } ret = lseek(my_fd->fd, offset, what); if (ret < 0) { if (errno == ENXIO) { info->io_eof = TRUE; } else { status = posix2fsal_status(errno); } goto out; } else { info->io_eof = (ret >= attrs.filesize); info->io_content.hole.di_offset = ret; } out: status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); /* We did FSAL_O_ANY so no share reservation was acquired */ exit: return status; } #endif /** * @brief Reserve/Deallocate space in a region of a file * * @param[in] obj_hdl File to which bytes should be allocated * @param[in] state open stateid under which to do the allocation * @param[in] offset offset at which to begin the allocation * @param[in] length length of the data to be allocated * @param[in] allocate Should space be allocated or deallocated? * * @return FSAL status. */ #ifdef FALLOC_FL_PUNCH_HOLE fsal_status_t vfs_fallocate(struct fsal_obj_handle *obj_hdl, struct state_t *state, uint64_t offset, uint64_t length, bool allocate) { int ret = 0; fsal_status_t status, status2; struct vfs_fd *my_fd; struct vfs_fd temp_fd = { FSAL_FD_INIT, -1 }; struct fsal_fd *out_fd; struct vfs_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->u.file.fd.fsal_fd, &temp_fd.fsal_fd, state, FSAL_O_WRITE, false, NULL, false, &myself->u.file.share); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct vfs_fd, fsal_fd); if (!vfs_set_credentials(&op_ctx->creds, obj_hdl->fsal)) { status = posix2fsal_status(EPERM); LogFullDebug(COMPONENT_FSAL, "vfs_set_credentials failed returning %s", fsal_err_txt(status)); goto out; } ret = fallocate(my_fd->fd, allocate ? 0 : FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, offset, length); if (ret < 0) { ret = errno; LogFullDebug(COMPONENT_FSAL, "fallocate returned %s (%d)", strerror(ret), ret); status = posix2fsal_status(ret); } vfs_restore_ganesha_credentials(obj_hdl->fsal); out: status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->u.file.share, FSAL_O_WRITE, FSAL_O_CLOSED); } exit: return status; } #endif /** * @brief Commit written data * * This function flushes possibly buffered data to a file. This method * differs from commit due to the need to interact with share reservations * and the fact that the FSAL manages the state of "file descriptors". The * FSAL must be able to perform this operation without being passed a specific * state. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] offset Start of range to commit * @param[in] len Length of range to commit * * @return FSAL status. */ fsal_status_t vfs_commit2(struct fsal_obj_handle *obj_hdl, off_t offset, size_t len) { struct vfs_fsal_obj_handle *myself; fsal_status_t status, status2; int retval; struct vfs_fd temp_fd = { FSAL_FD_INIT, -1 }; struct fsal_fd *out_fd; struct vfs_fd *my_fd; myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); /* Make sure file is open in appropriate mode. * Do not check share reservation. */ status = fsal_start_global_io(&out_fd, obj_hdl, &myself->u.file.fd.fsal_fd, &temp_fd.fsal_fd, FSAL_O_ANY, false, NULL); if (FSAL_IS_ERROR(status)) return status; if (!vfs_set_credentials(&op_ctx->creds, obj_hdl->fsal)) { status = posix2fsal_status(EPERM); goto out; } my_fd = container_of(out_fd, struct vfs_fd, fsal_fd); retval = fsync(my_fd->fd); if (retval == -1) status = posix2fsal_status(errno); vfs_restore_ganesha_credentials(obj_hdl->fsal); out: status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); /* We did not do share reservation stuff... */ return status; } #ifdef F_OFD_GETLK /** * @brief Perform a lock operation * * This function performs a lock operation (lock, unlock, test) on a * file. This method assumes the FSAL is able to support lock owners, * though it need not support asynchronous blocking locks. Passing the * lock state allows the FSAL to associate information with a specific * lock owner for each file (which may include use of a "file descriptor". * * For FSAL_VFS etc. we ignore owner, implicitly we have a lock_fd per * lock owner (i.e. per state). * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] owner Lock owner * @param[in] lock_op Operation to perform * @param[in] request_lock Lock to take/release/test * @param[out] conflicting_lock Conflicting lock * * @return FSAL status. */ fsal_status_t vfs_lock_op2(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *owner, fsal_lock_op_t lock_op, fsal_lock_param_t *request_lock, fsal_lock_param_t *conflicting_lock) { struct flock lock_args; int fcntl_comm; fsal_status_t status, status2; int retval = 0; bool bypass = false; fsal_openflags_t openflags = FSAL_O_RDWR; struct vfs_fd *my_fd; struct vfs_fd temp_fd = { FSAL_FD_INIT, -1 }; struct fsal_fd *out_fd; struct vfs_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); if (obj_hdl->fsal != obj_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", obj_hdl->fsal->name, obj_hdl->fs->fsal->name); return posix2fsal_status(EXDEV); } LogFullDebug(COMPONENT_FSAL, "Locking: op:%d type:%d start:%" PRIu64 " length:%" PRIu64 " ", lock_op, request_lock->lock_type, request_lock->lock_start, request_lock->lock_length); if (lock_op == FSAL_OP_LOCKT) { fcntl_comm = F_OFD_GETLK; /* We may end up using global fd, don't fail on a deny mode */ bypass = true; openflags = FSAL_O_ANY; } else if (lock_op == FSAL_OP_LOCK) { fcntl_comm = F_OFD_SETLK; if (request_lock->lock_type == FSAL_LOCK_R) openflags = FSAL_O_READ; else if (request_lock->lock_type == FSAL_LOCK_W) openflags = FSAL_O_WRITE; } else if (lock_op == FSAL_OP_UNLOCK) { fcntl_comm = F_OFD_SETLK; openflags = FSAL_O_ANY; } else { LogDebug( COMPONENT_FSAL, "ERROR: Lock operation requested was not TEST, READ, or WRITE."); return fsalstat(ERR_FSAL_NOTSUPP, 0); } if (lock_op != FSAL_OP_LOCKT && state == NULL) { LogCrit(COMPONENT_FSAL, "Non TEST operation with NULL state"); return fsalstat(posix2fsal_error(EINVAL), EINVAL); } if (request_lock->lock_type == FSAL_LOCK_R) { lock_args.l_type = F_RDLCK; } else if (request_lock->lock_type == FSAL_LOCK_W) { lock_args.l_type = F_WRLCK; } else { LogDebug( COMPONENT_FSAL, "ERROR: The requested lock type was not read or write."); return fsalstat(ERR_FSAL_NOTSUPP, 0); } if (lock_op == FSAL_OP_UNLOCK) lock_args.l_type = F_UNLCK; lock_args.l_pid = 0; lock_args.l_len = request_lock->lock_length; lock_args.l_start = request_lock->lock_start; lock_args.l_whence = SEEK_SET; /* flock.l_len being signed long integer, larger lock ranges may * get mapped to negative values. As per 'man 3 fcntl', posix * locks can accept negative l_len values which may lead to * unlocking an unintended range. Better bail out to prevent that. */ if (lock_args.l_len < 0) { LogCrit(COMPONENT_FSAL, "The requested lock length is out of range- lock_args.l_len(%" PRId64 "), request_lock_length(%" PRIu64 ")", lock_args.l_len, request_lock->lock_length); return posix2fsal_status(ERANGE); } /* Indicate a desire to start io and get a usable file descritor */ status = fsal_start_io(&out_fd, obj_hdl, &myself->u.file.fd.fsal_fd, &temp_fd.fsal_fd, state, openflags, true, NULL, bypass, &myself->u.file.share); if (FSAL_IS_ERROR(status)) { LogCrit(COMPONENT_FSAL, "fsal_start_io failed returning %s", fsal_err_txt(status)); goto exit; } my_fd = container_of(out_fd, struct vfs_fd, fsal_fd); errno = 0; retval = fcntl(my_fd->fd, fcntl_comm, &lock_args); if (retval) { retval = errno; status = posix2fsal_status(retval); LogDebug(COMPONENT_FSAL, "fcntl returned %d %s", retval, strerror(retval)); if (conflicting_lock != NULL) { /* Get the conflicting lock */ int rc = fcntl(my_fd->fd, F_GETLK, &lock_args); if (rc) { retval = errno; /* we lose the initial error */ status = posix2fsal_status(retval); LogCrit(COMPONENT_FSAL, "After failing a lock request, I couldn't even get the details of who owns the lock."); goto err; } if (conflicting_lock != NULL) { conflicting_lock->lock_length = lock_args.l_len; conflicting_lock->lock_start = lock_args.l_start; conflicting_lock->lock_type = lock_args.l_type; } } goto err; } /* F_UNLCK is returned then the tested operation would be possible. */ if (conflicting_lock != NULL) { if (lock_op == FSAL_OP_LOCKT && lock_args.l_type != F_UNLCK) { conflicting_lock->lock_length = lock_args.l_len; conflicting_lock->lock_start = lock_args.l_start; conflicting_lock->lock_type = lock_args.l_type; } else { conflicting_lock->lock_length = 0; conflicting_lock->lock_start = 0; conflicting_lock->lock_type = FSAL_NO_LOCK; } } /* Fall through (status == SUCCESS) */ err: status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (state == NULL) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->u.file.share, openflags, FSAL_O_CLOSED); } exit: return status; } #endif /** * @brief Get attributes * * This function freshens the cached attributes stored on the handle. * Since the caller can take the attribute lock and read them off the * public filehandle, they are not copied out. * * @param[in] obj_hdl Object to query * * @return FSAL status. */ fsal_status_t vfs_getattr2(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs) { struct vfs_fsal_obj_handle *myself; fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0); fsal_status_t status2; struct vfs_fd *my_fd; struct vfs_fd temp_fd = { FSAL_FD_INIT, -1 }; struct fsal_fd *out_fd = NULL; myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); if (obj_hdl->fsal != obj_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s getattr for handle belonging to FSAL %s, ignoring", obj_hdl->fsal->name, obj_hdl->fs->fsal != NULL ? obj_hdl->fs->fsal->name : "(none)"); goto out; } #ifdef __FreeBSD__ if (obj_hdl->type == SYMBOLIC_LINK) goto fetch; #endif /* Get a usable file descriptor (don't need to bypass - FSAL_O_ANY * won't conflict with any share reservation). */ LogFullDebug(COMPONENT_FSAL, "Calling find_fd, state = NULL"); status = find_fd(&out_fd, obj_hdl, &temp_fd.fsal_fd, NULL, FSAL_O_ANY, false); if (FSAL_IS_ERROR(status)) { if (obj_hdl->type == SYMBOLIC_LINK && status.major == ERR_FSAL_PERM) { /* You cannot open_by_handle (XFS on linux) a symlink * and it throws an EPERM error for it. * open_by_handle_at does not throw that error for * symlinks so we play a game here. Since there is * not much we can do with symlinks anyway, * say that we did it but don't actually * do anything. In this case, return the stat we got * at lookup time. If you *really* want to tweek things * like owners, get a modern linux kernel... */ status = fsalstat(ERR_FSAL_NO_ERROR, 0); } goto exit; } my_fd = container_of(out_fd, struct vfs_fd, fsal_fd); #ifdef __FreeBSD__ fetch: #endif status = fetch_attrs(myself, my_fd->fd, attrs); out: status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); exit: return status; } /** * @brief Set attributes on an object * * This function sets attributes on an object. Which attributes are * set is determined by attrib_set->valid_mask. The FSAL must manage bypass * or not of share reservations, and a state may be passed. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] attrib_set Attributes to set * * @return FSAL status. */ fsal_status_t vfs_setattr2(struct fsal_obj_handle *obj_hdl, bool bypass, struct state_t *state, struct fsal_attrlist *attrib_set) { struct vfs_fsal_obj_handle *myself; fsal_status_t status = { 0, 0 }, status2; int retval = 0; fsal_openflags_t openflags = FSAL_O_ANY; const char *func = "none"; struct vfs_fd *my_fd; struct vfs_fd temp_fd = { FSAL_FD_INIT, -1 }; struct fsal_fd *out_fd; /* apply umask, if mode attribute is to be changed */ if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MODE)) attrib_set->mode &= ~op_ctx->fsal_export->exp_ops.fs_umask( op_ctx->fsal_export); myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); if (obj_hdl->fsal != obj_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", obj_hdl->fsal->name, obj_hdl->fs->fsal != NULL ? obj_hdl->fs->fsal->name : "(none)"); return fsalstat(posix2fsal_error(EXDEV), EXDEV); } #ifdef ENABLE_VFS_DEBUG_ACL #ifdef ENABLE_RFC_ACL if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MODE) && !FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_ACL)) { /* Set ACL from MODE */ struct fsal_attrlist attrs; fsal_prepare_attrs(&attrs, ATTR_ACL); status = obj_hdl->obj_ops->getattrs(obj_hdl, &attrs); if (FSAL_IS_ERROR(status)) return status; status = fsal_mode_to_acl(attrib_set, attrs.acl); /* Done with the attrs */ fsal_release_attrs(&attrs); } else { /* If ATTR_ACL is set, mode needs to be adjusted no matter what. * See 7530 s 6.4.1.3 */ if (!FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MODE)) attrib_set->mode = myself->mode; status = fsal_acl_to_mode(attrib_set); } if (FSAL_IS_ERROR(status)) return status; #endif /* ENABLE_RFC_ACL */ #endif /* This is yet another "you can't get there from here". If this object * is a socket (AF_UNIX), an fd on the socket s useless _period_. * If it is for a symlink, without O_PATH, you will get an ELOOP error * and (f)chmod doesn't work for a symlink anyway - not that it matters * because access checking is not done on the symlink but the final * target. * AF_UNIX sockets are also ozone material. If the socket is already * active listeners et al, you can manipulate the mode etc. If it is * just sitting there as in you made it with a mknod. * (one of those leaky abstractions...) * or the listener forgot to unlink it, it is lame duck. */ /* Test if size is being set, make sure file is regular and if so, * require a read/write file descriptor. */ if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_SIZE)) { if (obj_hdl->type != REGULAR_FILE) { LogFullDebug(COMPONENT_FSAL, "Setting size on non-regular file"); return fsalstat(ERR_FSAL_INVAL, EINVAL); } openflags = FSAL_O_WRITE; } /* Get a usable file descriptor. Share conflict is only possible if * size is being set. */ LogFullDebug(COMPONENT_FSAL, "Calling find_fd, state = %p", state); status = find_fd(&out_fd, obj_hdl, &temp_fd.fsal_fd, state, openflags, bypass); LogFullDebug( COMPONENT_FSAL, "find_fd, state = %p returned status %s using %p (tmp_fd = %p)", state, fsal_err_txt(status), out_fd, &temp_fd); my_fd = container_of(out_fd, struct vfs_fd, fsal_fd); if (FSAL_IS_ERROR(status)) { if (obj_hdl->type == SYMBOLIC_LINK && (status.major == ERR_FSAL_PERM #ifdef __FreeBSD__ || status.major == ERR_FSAL_MLINK #endif )) { /* You cannot open_by_handle (XFS) a symlink and it * throws an EPERM error for it. open_by_handle_at * does not throw that error for symlinks so we play a * game here. Since there is not much we can do with * symlinks anyway, say that we did it * but don't actually do anything. * If you *really* want to tweek things * like owners, get a modern linux kernel... */ status = fsalstat(ERR_FSAL_NO_ERROR, 0); } LogFullDebug(COMPONENT_FSAL, "find_fd status=%s", fsal_err_txt(status)); goto exit; } /** TRUNCATE **/ if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_SIZE)) { #ifdef LINUX /* On Linux at least off_t is signed and offsets < 0 are not * valid for ftruncate. We want to make sure to return EFBIG * not EINVAL. */ if ((off_t)attrib_set->filesize < 0) { errno = EFBIG; func = "truncate"; LogDebug(COMPONENT_FSAL, "filesize %" PRIx64 " as off_t is < 0", attrib_set->filesize); goto fileerr; } #endif retval = ftruncate(my_fd->fd, attrib_set->filesize); if (retval != 0) { /** @todo FSF: is this still necessary? * * XXX ESXi volume creation pattern reliably * reached this point in the past, however now that we * only use the already open file descriptor if it is * open read/write, this may no longer fail. * If there is some other error from ftruncate, then * we will needlessly retry, but without more detail * of the original failure, we can't be sure. * Fortunately permission checking is done by * Ganesha before calling here, so we won't get an * EACCES since this call is done as root. We could * get EFBIG, EPERM, or EINVAL. */ /** @todo FSF: re-open if we really still need this */ retval = ftruncate(my_fd->fd, attrib_set->filesize); if (retval != 0) { func = "truncate"; goto fileerr; } } } /** CHMOD **/ if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MODE)) { /* The POSIX chmod call doesn't affect the symlink object, but * the entry it points to. So we must ignore it. */ if (obj_hdl->type != SYMBOLIC_LINK) { if (vfs_unopenable_type(obj_hdl->type)) retval = fchmodat( my_fd->fd, myself->u.unopenable.name, fsal2unix_mode(attrib_set->mode), 0); else retval = fchmod( my_fd->fd, fsal2unix_mode(attrib_set->mode)); if (retval != 0) { func = "chmod"; goto fileerr; } } } /** CHOWN **/ if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_OWNER | ATTR_GROUP)) { uid_t user = FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_OWNER) ? (int)attrib_set->owner : -1; gid_t group = FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_GROUP) ? (int)attrib_set->group : -1; if (vfs_unopenable_type(obj_hdl->type)) retval = fchownat(my_fd->fd, myself->u.unopenable.name, user, group, AT_SYMLINK_NOFOLLOW); else if (obj_hdl->type == SYMBOLIC_LINK) retval = fchownat(my_fd->fd, "", user, group, AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH); else retval = fchown(my_fd->fd, user, group); if (retval) { func = "chown"; goto fileerr; } } /** UTIME **/ if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTRS_SET_TIME)) { struct timespec timebuf[2]; if (obj_hdl->type == SYMBOLIC_LINK) goto out; /* Setting time on symlinks is illegal */ /* Atime */ if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_ATIME_SERVER)) { timebuf[0].tv_sec = 0; timebuf[0].tv_nsec = UTIME_NOW; } else if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_ATIME)) { timebuf[0] = attrib_set->atime; } else { timebuf[0].tv_sec = 0; timebuf[0].tv_nsec = UTIME_OMIT; } /* Mtime */ if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MTIME_SERVER)) { timebuf[1].tv_sec = 0; timebuf[1].tv_nsec = UTIME_NOW; } else if (FSAL_TEST_MASK(attrib_set->valid_mask, ATTR_MTIME)) { timebuf[1] = attrib_set->mtime; } else { timebuf[1].tv_sec = 0; timebuf[1].tv_nsec = UTIME_OMIT; } LogFullDebug(COMPONENT_FSAL, "Setting atime %lx %lx mtime %lx %lx", timebuf[0].tv_sec, timebuf[0].tv_nsec, timebuf[1].tv_sec, timebuf[1].tv_nsec); if (vfs_unopenable_type(obj_hdl->type)) retval = vfs_utimesat(my_fd->fd, myself->u.unopenable.name, timebuf, AT_SYMLINK_NOFOLLOW); else retval = vfs_utimes(my_fd->fd, timebuf); if (retval != 0) { func = "utimes"; goto fileerr; } } /** SUBFSAL **/ if (myself->sub_ops && myself->sub_ops->setattrs) { status = myself->sub_ops->setattrs( myself, my_fd->fd, attrib_set->valid_mask, attrib_set); if (FSAL_IS_ERROR(status)) goto out; } errno = 0; fileerr: retval = errno; if (retval != 0) { LogDebug(COMPONENT_FSAL, "%s returned %s", func, strerror(retval)); } status = posix2fsal_status(retval); out: status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); if (state == NULL && openflags != FSAL_O_ANY) { /* We did I/O without a state so we need to release the temp * share reservation acquired. */ /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, &myself->u.file.share, openflags, FSAL_O_CLOSED); } exit: return status; } /** * @brief Manage closing a file when a state is no longer needed. * * When the upper layers are ready to dispense with a state, this method is * called to allow the FSAL to close any file descriptors or release any other * resources associated with the state. A call to free_state should be assumed * to follow soon. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * * @return FSAL status. */ fsal_status_t vfs_close2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { struct vfs_fsal_obj_handle *myself = NULL; struct vfs_fd *my_fd = &container_of(state, struct vfs_state_fd, state)->vfs_fd; myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); if (state->state_type == STATE_TYPE_SHARE || state->state_type == STATE_TYPE_NLM_SHARE || state->state_type == STATE_TYPE_9P_FID) { /* This is a share state, we must update the share counters */ update_share_counters_locked(obj_hdl, &myself->u.file.share, my_fd->fsal_fd.openflags, FSAL_O_CLOSED); } return close_fsal_fd(obj_hdl, &my_fd->fsal_fd, false); } nfs-ganesha-6.5/src/FSAL/FSAL_VFS/handle.c000066400000000000000000001541121473756622300177570ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /* handle.c * VFS object (file|dir) handle object */ #include "config.h" #ifdef LINUX #include /* for makedev(3) */ #endif #include /* used for 'dirname' */ #include #include #include #include "gsh_list.h" #include "fsal.h" #include "fsal_convert.h" #include "fsal_handle_syscalls.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_localfs.h" #include "vfs_methods.h" #include #include "subfsal.h" #include "city.h" #include "nfs_core.h" #include "nfs_proto_tools.h" /* helpers */ int vfs_fsal_open(struct vfs_fsal_obj_handle *hdl, int openflags, fsal_errors_t *fsal_error) { return vfs_open_by_handle(hdl->obj_handle.fs, hdl->handle, openflags, fsal_error); } /** * handle_to_key * return a handle descriptor into the handle in this object handle * @TODO reminder. make sure things like hash keys don't point here * after the handle is released. */ static void handle_to_key(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *fh_desc) { struct vfs_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); fh_desc->addr = myself->handle->handle_data; fh_desc->len = myself->handle->handle_len; } /* * @brief Free the VFS OBJ handle * * @param[in] hdl_ref VFS OBJ handle reference */ void free_vfs_fsal_obj_handle(struct vfs_fsal_obj_handle **hdl_ref) { struct vfs_fsal_obj_handle *myself = *hdl_ref; object_file_type_t type = myself->obj_handle.type; if (type == SYMBOLIC_LINK) { gsh_free(myself->u.symlink.link_content); } else if (type == REGULAR_FILE) { struct gsh_buffdesc key; handle_to_key(&myself->obj_handle, &key); vfs_state_release(&key); destroy_fsal_fd(&myself->u.file.fd.fsal_fd); } else if (vfs_unopenable_type(type)) { gsh_free(myself->u.unopenable.name); gsh_free(myself->u.unopenable.dir); } LogDebug(COMPONENT_FSAL, "Releasing obj_hdl=%p, myself=%p", &myself->obj_handle, myself); gsh_free(myself); *hdl_ref = NULL; } /** * @brief Create a VFS OBJ handle * * @param[in] dirfd FD for dir containing new handle * @param[in] fh VFS FH for new handle * @param[in] fs FileSystem containing new handle * @param[in] stat stat(2) resutls for new handle * @param[in] dir_fh VFS FH for dir containing new handle * @param[in] path Path to new handle * @param[in] exp_hdl Export containing new handle * @return VFS OBJ handle on success, NULL on failure */ struct vfs_fsal_obj_handle * alloc_handle(int dirfd, vfs_file_handle_t *fh, struct fsal_filesystem *fs, struct stat *stat, vfs_file_handle_t *dir_fh, const char *path, struct fsal_export *exp_hdl) { struct vfs_fsal_export *myself = container_of(exp_hdl, struct vfs_fsal_export, export); struct vfs_fsal_obj_handle *hdl; struct vfs_fsal_module *my_module = container_of(exp_hdl->fsal, struct vfs_fsal_module, module); hdl = vfs_sub_alloc_handle(); memcpy(hdl->handle, fh, sizeof(vfs_file_handle_t)); hdl->obj_handle.type = posix2fsal_type(stat->st_mode); hdl->dev = posix2fsal_devt(stat->st_dev); hdl->up_ops = exp_hdl->up_ops; hdl->obj_handle.fs = fs; LogFullDebug( COMPONENT_FSAL, "Creating object %p for file %s of type %s on filesystem %p %s", hdl, path, object_file_type_to_str(hdl->obj_handle.type), fs, fs->path); if (hdl->obj_handle.type == REGULAR_FILE) { init_fsal_fd(&hdl->u.file.fd.fsal_fd, FSAL_FD_GLOBAL, op_ctx->fsal_export); hdl->u.file.fd.fd = -1; /* no open on this yet */ } else if (hdl->obj_handle.type == SYMBOLIC_LINK) { ssize_t retlink; size_t len = stat->st_size + 1; char *link_content = gsh_malloc(len); retlink = vfs_readlink_by_handle(fh, dirfd, path, link_content, len); if (retlink < 0 || retlink == len) goto spcerr; link_content[retlink] = '\0'; hdl->u.symlink.link_content = link_content; hdl->u.symlink.link_size = len; } else if (vfs_unopenable_type(hdl->obj_handle.type)) { /* AF_UNIX sockets, character special, and block special files require craziness */ if (dir_fh == NULL) { int retval; vfs_alloc_handle(dir_fh); retval = vfs_fd_to_handle(dirfd, hdl->obj_handle.fs, fh); if (retval < 0) goto spcerr; } hdl->u.unopenable.dir = gsh_malloc(sizeof(vfs_file_handle_t)); memcpy(hdl->u.unopenable.dir, dir_fh, sizeof(vfs_file_handle_t)); hdl->u.unopenable.name = gsh_strdup(path); } fsal_obj_handle_init(&hdl->obj_handle, exp_hdl, posix2fsal_type(stat->st_mode), true); hdl->obj_handle.fsid = fs->fsid; hdl->obj_handle.fileid = stat->st_ino; #ifdef VFS_NO_MDCACHE hdl->obj_handle.state_hdl = vfs_state_locate(&hdl->obj_handle); #endif /* VFS_NO_MDCACHE */ hdl->obj_handle.obj_ops = &my_module->handle_ops; if (vfs_sub_init_handle(myself, hdl, path) < 0) goto spcerr; return hdl; spcerr: free_vfs_fsal_obj_handle(&hdl); return NULL; } static fsal_status_t populate_fs_locations(struct vfs_fsal_obj_handle *hdl, struct fsal_attrlist *attrs_out) { fsal_status_t status; uint64 hash; attrmask_t old_request_mask = attrs_out->request_mask; attrs_out->request_mask = ATTR4_FS_LOCATIONS; status = hdl->sub_ops->getattrs(hdl, -1 /* no fd here */, attrs_out->request_mask, attrs_out); if (FSAL_IS_ERROR(status)) { goto out; } if (FSAL_TEST_MASK(attrs_out->valid_mask, ATTR4_FS_LOCATIONS)) { /* on a referral the filesystem id has to change * it gets calculated via a hash from the referral * and stored in the fsid object of the fsal_obj_handle */ fsal_fs_locations_t *fsloc = attrs_out->fs_locations; int loclen = fsloc->server[0].utf8string_len + strlen(fsloc->rootpath) + 2; char *location = gsh_calloc(1, loclen); (void)snprintf(location, loclen, "%.*s:%s", fsloc->server[0].utf8string_len, fsloc->server[0].utf8string_val, fsloc->rootpath); hash = CityHash64(location, loclen); hdl->obj_handle.fsid.major = hash; hdl->obj_handle.fsid.minor = hash; LogDebug(COMPONENT_NFS_V4, "fsid.major = %" PRIu64 ", fsid.minor = %" PRIu64, hdl->obj_handle.fsid.major, hdl->obj_handle.fsid.minor); gsh_free(location); } out: attrs_out->request_mask |= old_request_mask; return status; } static fsal_status_t check_filesystem(struct vfs_fsal_obj_handle *parent_hdl, int dirfd, const char *path, struct stat *stat, struct fsal_filesystem **filesystem, bool *xfsal) { int retval; fsal_dev_t dev; struct fsal_filesystem *fs = NULL; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; again: retval = fstatat(dirfd, path, stat, AT_SYMLINK_NOFOLLOW); if (retval < 0) { retval = errno; LogDebug(COMPONENT_FSAL, "Failed to open stat %s: %s", path, msg_fsal_err(posix2fsal_error(retval))); if (errno == EAGAIN) { /* autofs can cause EAGAIN if the file system is not * yet mounted. */ /** @todo FSF: should we retry forever * is there any other reason for EAGAIN? */ goto again; } status = posix2fsal_status(retval); goto out; } dev = posix2fsal_devt(stat->st_dev); fs = parent_hdl->obj_handle.fs; LogFilesystem("Parent", "", fs); if ((dev.minor == parent_hdl->dev.minor) && (dev.major == parent_hdl->dev.major)) { /* Filesystem is ok */ goto out; } /* XDEV */ fs = lookup_dev(&dev); if (fs == NULL) { LogInfo(COMPONENT_FSAL, "Lookup of %s crosses filesystem boundary to unknown file system dev=%" PRIu64 ".%" PRIu64 " - reloading filesystems to find it", path, dev.major, dev.minor); retval = populate_posix_file_systems(path); if (retval != 0) { LogCrit(COMPONENT_FSAL, "populate_posix_file_systems returned %s (%d)", strerror(retval), retval); status = posix2fsal_status(EXDEV); goto out; } fs = lookup_dev(&dev); if (fs == NULL) { LogFullDebug(COMPONENT_FSAL, "Filesystem still was not claimed"); status = posix2fsal_status(EXDEV); goto out; } else { /* We have now found the file system, since it was * just added, fs->fsal will be NULL so the next if * block below will be executed to claim the file system * and carry on. */ LogInfo(COMPONENT_FSAL, "Filesystem %s will be added to export %d:%s", fs->path, op_ctx->ctx_export->export_id, op_ctx_export_path(op_ctx)); } } LogFilesystem("XDEV", "", fs); if (fs->fsal == NULL) { /* The filesystem wasn't claimed, it must have been added after * we created this export. Go ahead and try to get it claimed. */ LogInfo(COMPONENT_FSAL, "Lookup of %s crosses filesystem boundary to unclaimed file system %s - attempt to claim it", path, fs->path); retval = claim_posix_filesystems( CTX_FULLPATH(op_ctx), parent_hdl->obj_handle.fsal, op_ctx->fsal_export, vfs_claim_filesystem, vfs_unclaim_filesystem, &op_ctx->fsal_export->root_fs, stat); if (retval != 0) { LogFullDebug(COMPONENT_FSAL, "claim_posix_filesystems failed"); status = posix2fsal_status(EXDEV); goto out; } } if (fs->fsal != parent_hdl->obj_handle.fsal) { *xfsal = true; LogDebug( COMPONENT_FSAL, "Lookup of %s crosses filesystem boundary to file system %s into FSAL %s", path, fs->path, fs->fsal != NULL ? fs->fsal->name : "(none)"); goto out; } else { LogDebug( COMPONENT_FSAL, "Lookup of %s crosses filesystem boundary to file system %s", path, fs->path); goto out; } out: *filesystem = fs; return status; } static fsal_status_t lookup_with_fd(struct vfs_fsal_obj_handle *parent_hdl, int dirfd, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { struct vfs_fsal_obj_handle *hdl; int retval; struct stat stat; vfs_file_handle_t *fh = NULL; struct fsal_filesystem *fs; bool xfsal = false; fsal_status_t status; vfs_alloc_handle(fh); status = check_filesystem(parent_hdl, dirfd, path, &stat, &fs, &xfsal); if (FSAL_IS_ERROR(status)) return status; if (xfsal || vfs_name_to_handle(dirfd, fs, path, fh) < 0) { retval = errno; if (((retval == ENOTTY) || (retval == EOPNOTSUPP) || (retval == ENOTSUP) || xfsal) && (fs != parent_hdl->obj_handle.fs)) { /* Crossed device into territory not handled by * this FSAL (XFS or VFS). Need to invent a handle. * The made up handle will be JUST the fsid, we * do not expect to see the handle on the wire, and * this handle will not be valid for any form of this * FSAL. */ LogDebug( COMPONENT_FSAL, "vfs_name_to_handle %s, inventing FSAL %s handle for FSAL %s filesystem %s", xfsal ? "skipped" : "failed", parent_hdl->obj_handle.fsal->name, fs->fsal != NULL ? fs->fsal->name : "(none)", path); retval = vfs_encode_dummy_handle(fh, fs); if (retval < 0) { retval = errno; status = posix2fsal_status(retval); return status; } retval = 0; } else { /* Some other error */ status = posix2fsal_status(retval); return status; } } /* allocate an obj_handle and fill it up */ hdl = alloc_handle(dirfd, fh, fs, &stat, parent_hdl->handle, path, op_ctx->fsal_export); if (hdl == NULL) { status = fsalstat(ERR_FSAL_NOMEM, ENOMEM); return status; } if (attrs_out != NULL) { posix2fsal_attributes_all(&stat, attrs_out); /* Get correct fsid from the fsal_filesystem, it may not be * the device major/minor from stat. */ attrs_out->fsid = hdl->obj_handle.fs->fsid; } hdl->obj_handle.fsid = hdl->obj_handle.fs->fsid; /* if it is a directory and the sticky bit is set * let's look for referral information */ if (attrs_out != NULL && hdl->obj_handle.obj_ops->is_referral(&hdl->obj_handle, attrs_out, false) && hdl->obj_handle.fs->private_data != NULL && hdl->sub_ops->getattrs) { status = populate_fs_locations(hdl, attrs_out); if (FSAL_IS_ERROR(status)) { LogEvent(COMPONENT_FSAL, "Could not get the referral " "locations the path: %d, %s", dirfd, path); free_vfs_fsal_obj_handle(&hdl); return status; } } *handle = &hdl->obj_handle; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* handle methods */ /* lookup * deprecated NULL parent && NULL path implies root handle */ static fsal_status_t lookup(struct fsal_obj_handle *parent, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { struct vfs_fsal_obj_handle *parent_hdl; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int dirfd; fsal_status_t status; *handle = NULL; /* poison it first */ parent_hdl = container_of(parent, struct vfs_fsal_obj_handle, obj_handle); if (!fsal_obj_handle_is(parent, DIRECTORY)) { LogCrit(COMPONENT_FSAL, "Parent handle is not a directory. hdl = 0x%p", parent); return fsalstat(ERR_FSAL_NOTDIR, 0); } LogFilesystem("About to check FSALs", "", parent->fs); if (parent->fsal != parent->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", parent->fsal->name, parent->fs->fsal != NULL ? parent->fs->fsal->name : "(none)"); return fsalstat(ERR_FSAL_XDEV, EXDEV); } dirfd = vfs_fsal_open(parent_hdl, O_PATH | O_NOACCESS, &fsal_error); if (dirfd < 0) { LogDebug(COMPONENT_FSAL, "Failed to open parent: %s", msg_fsal_err(fsal_error)); status = fsalstat(fsal_error, -dirfd); return status; } status = lookup_with_fd(parent_hdl, dirfd, path, handle, attrs_out); close(dirfd); return status; } static fsal_status_t makedir(struct fsal_obj_handle *dir_hdl, const char *name, struct fsal_attrlist *attrib, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct vfs_fsal_obj_handle *myself, *hdl; int dir_fd; struct stat stat; mode_t unix_mode; fsal_status_t status = { 0, 0 }; int retval = 0; int flags = O_PATH | O_NOACCESS; #ifdef ENABLE_VFS_DEBUG_ACL struct fsal_attrlist attrs; fsal_accessflags_t access_type; #endif /* ENABLE_VFS_DEBUG_ACL */ vfs_file_handle_t *fh = NULL; vfs_alloc_handle(fh); LogDebug(COMPONENT_FSAL, "create %s", name); *handle = NULL; /* poison it */ if (!fsal_obj_handle_is(dir_hdl, DIRECTORY)) { LogCrit(COMPONENT_FSAL, "Parent handle is not a directory. hdl = 0x%p", dir_hdl); return fsalstat(ERR_FSAL_NOTDIR, 0); } myself = container_of(dir_hdl, struct vfs_fsal_obj_handle, obj_handle); if (dir_hdl->fsal != dir_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", dir_hdl->fsal->name, dir_hdl->fs->fsal != NULL ? dir_hdl->fs->fsal->name : "(none)"); retval = EXDEV; goto hdlerr; } #ifdef ENABLE_VFS_DEBUG_ACL access_type = FSAL_MODE_MASK_SET(FSAL_W_OK) | FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_ADD_SUBDIRECTORY); status = dir_hdl->obj_ops->test_access(dir_hdl, access_type, NULL, NULL, false); if (FSAL_IS_ERROR(status)) return status; fsal_prepare_attrs(&attrs, ATTR_ACL); status = dir_hdl->obj_ops->getattrs(dir_hdl, &attrs); if (FSAL_IS_ERROR(status)) return status; status.major = fsal_inherit_acls(attrib, attrs.acl, FSAL_ACE_FLAG_DIR_INHERIT); /* Done with the attrs */ fsal_release_attrs(&attrs); if (FSAL_IS_ERROR(status)) return status; #endif /* ENABLE_VFS_DEBUG_ACL */ unix_mode = fsal2unix_mode(attrib->mode) & ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export); dir_fd = vfs_fsal_open(myself, flags, &status.major); if (dir_fd < 0) { LogFullDebug(COMPONENT_FSAL, "vfs_fsal_open returned %s", strerror(-dir_fd)); return fsalstat(status.major, -dir_fd); } retval = vfs_stat_by_handle(dir_fd, &stat); if (retval < 0) { retval = errno; LogFullDebug(COMPONENT_FSAL, "vfs_stat_by_handle returned %s", strerror(retval)); status = posix2fsal_status(retval); goto direrr; } /* Become the user because we are creating an object in this dir. */ if (!vfs_set_credentials(&op_ctx->creds, dir_hdl->fsal)) { retval = EPERM; status = posix2fsal_status(retval); goto direrr; } retval = mkdirat(dir_fd, name, unix_mode); if (retval < 0) { retval = errno; vfs_restore_ganesha_credentials(dir_hdl->fsal); LogFullDebug(COMPONENT_FSAL, "mkdirat returned %s", strerror(retval)); status = posix2fsal_status(retval); goto direrr; } vfs_restore_ganesha_credentials(dir_hdl->fsal); retval = vfs_name_to_handle(dir_fd, dir_hdl->fs, name, fh); if (retval < 0) { retval = errno; status = posix2fsal_status(retval); goto fileerr; } retval = fstatat(dir_fd, name, &stat, AT_SYMLINK_NOFOLLOW); if (retval < 0) { retval = errno; LogFullDebug(COMPONENT_FSAL, "fstatat returned %s", strerror(retval)); status = posix2fsal_status(retval); goto fileerr; } /* allocate an obj_handle and fill it up */ hdl = alloc_handle(dir_fd, fh, dir_hdl->fs, &stat, myself->handle, name, op_ctx->fsal_export); if (hdl == NULL) { LogFullDebug(COMPONENT_FSAL, "alloc_handle returned %s", strerror(retval)); status = fsalstat(ERR_FSAL_NOMEM, ENOMEM); goto fileerr; } *handle = &hdl->obj_handle; /* We handled the mode above. */ FSAL_UNSET_MASK(attrib->valid_mask, ATTR_MODE); if (attrib->valid_mask) { /* Now per support_ex API, if there are any other attributes * set, go ahead and get them set now. */ status = (*handle)->obj_ops->setattr2(*handle, false, NULL, attrib); if (FSAL_IS_ERROR(status)) { /* Release the handle we just allocated. */ LogFullDebug(COMPONENT_FSAL, "setattr2 status=%s", fsal_err_txt(status)); (*handle)->obj_ops->release(*handle); *handle = NULL; } else if (attrs_out != NULL) { status = (*handle)->obj_ops->getattrs(*handle, attrs_out); if (FSAL_IS_ERROR(status) && (attrs_out->request_mask & ATTR_RDATTR_ERR) == 0) { /* Get attributes failed and caller expected * to get the attributes. */ goto fileerr; } } } else { status.major = ERR_FSAL_NO_ERROR; status.minor = 0; if (attrs_out != NULL) { /* Since we haven't set any attributes other than what * was set on create, just use the stat results we used * to create the fsal_obj_handle. */ posix2fsal_attributes_all(&stat, attrs_out); /* Get correct fsid from the fsal_filesystem, it may * not be the device major/minor from stat. */ attrs_out->fsid = hdl->obj_handle.fs->fsid; } } close(dir_fd); return status; fileerr: unlinkat(dir_fd, name, 0); direrr: close(dir_fd); hdlerr: status.major = posix2fsal_error(retval); return fsalstat(status.major, retval); } static fsal_status_t makenode(struct fsal_obj_handle *dir_hdl, const char *name, object_file_type_t nodetype, /* IN */ struct fsal_attrlist *attrib, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct vfs_fsal_obj_handle *myself, *hdl; int dir_fd = -1; struct stat stat; mode_t unix_mode; fsal_status_t status = { 0, 0 }; int retval = 0; dev_t unix_dev = 0; int flags = O_PATH | O_NOACCESS; #ifdef ENABLE_VFS_DEBUG_ACL struct fsal_attrlist attrs; fsal_accessflags_t access_type; #endif /* ENABLE_VFS_DEBUG_ACL */ vfs_file_handle_t *fh = NULL; vfs_alloc_handle(fh); LogDebug(COMPONENT_FSAL, "create %s", name); *handle = NULL; /* poison it */ if (!fsal_obj_handle_is(dir_hdl, DIRECTORY)) { LogCrit(COMPONENT_FSAL, "Parent handle is not a directory. hdl = 0x%p", dir_hdl); return fsalstat(ERR_FSAL_NOTDIR, 0); } myself = container_of(dir_hdl, struct vfs_fsal_obj_handle, obj_handle); #ifdef ENABLE_VFS_DEBUG_ACL fsal_prepare_attrs(&attrs, ATTR_ACL); status = dir_hdl->obj_ops->getattrs(dir_hdl, &attrs); if (FSAL_IS_ERROR(status)) return status; status.major = fsal_inherit_acls(attrib, attrs.acl, FSAL_ACE_FLAG_FILE_INHERIT); /* Done with the attrs */ fsal_release_attrs(&attrs); if (FSAL_IS_ERROR(status)) return status; #endif /* ENABLE_VFS_DEBUG_ACL */ if (dir_hdl->fsal != dir_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", dir_hdl->fsal->name, dir_hdl->fs->fsal != NULL ? dir_hdl->fs->fsal->name : "(none)"); retval = EXDEV; goto hdlerr; } #ifdef ENABLE_VFS_DEBUG_ACL access_type = FSAL_MODE_MASK_SET(FSAL_W_OK) | FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_ADD_FILE); status = dir_hdl->obj_ops->test_access(dir_hdl, access_type, NULL, NULL, false); if (FSAL_IS_ERROR(status)) return status; #endif /* ENABLE_VFS_DEBUG_ACL */ unix_mode = fsal2unix_mode(attrib->mode) & ~op_ctx->fsal_export->exp_ops.fs_umask(op_ctx->fsal_export); switch (nodetype) { case BLOCK_FILE: unix_mode |= S_IFBLK; unix_dev = makedev(attrib->rawdev.major, attrib->rawdev.minor); break; case CHARACTER_FILE: unix_mode |= S_IFCHR; unix_dev = makedev(attrib->rawdev.major, attrib->rawdev.minor); break; case FIFO_FILE: unix_mode |= S_IFIFO; break; case SOCKET_FILE: unix_mode |= S_IFSOCK; break; default: LogMajor(COMPONENT_FSAL, "Invalid node type in FSAL_mknode: %d", nodetype); status.major = ERR_FSAL_INVAL; goto errout; } dir_fd = vfs_fsal_open(myself, flags, &status.major); if (dir_fd < 0) { retval = -dir_fd; goto errout; } retval = vfs_stat_by_handle(dir_fd, &stat); if (retval < 0) { retval = errno; status = posix2fsal_status(retval); goto direrr; } if (!vfs_set_credentials(&op_ctx->creds, dir_hdl->fsal)) { retval = EPERM; status = posix2fsal_status(retval); goto direrr; } retval = mknodat(dir_fd, name, unix_mode, unix_dev); if (retval < 0) { retval = errno; vfs_restore_ganesha_credentials(dir_hdl->fsal); status = posix2fsal_status(retval); goto direrr; } vfs_restore_ganesha_credentials(dir_hdl->fsal); vfs_alloc_handle(fh); retval = vfs_name_to_handle(dir_fd, myself->obj_handle.fs, name, fh); if (retval < 0) { retval = errno; status = posix2fsal_status(retval); goto fileerr; } retval = fstatat(dir_fd, name, &stat, AT_SYMLINK_NOFOLLOW); if (retval < 0) { retval = errno; status = posix2fsal_status(retval); goto fileerr; } /* allocate an obj_handle and fill it up */ hdl = alloc_handle(dir_fd, fh, myself->obj_handle.fs, &stat, myself->handle, name, op_ctx->fsal_export); if (hdl == NULL) { status = fsalstat(ERR_FSAL_NOMEM, ENOMEM); goto fileerr; } *handle = &hdl->obj_handle; /* We handled the mode above. */ FSAL_UNSET_MASK(attrib->valid_mask, ATTR_MODE); if (attrib->valid_mask) { /* Now per support_ex API, if there are any other attributes * set, go ahead and get them set now. */ status = (*handle)->obj_ops->setattr2(*handle, false, NULL, attrib); if (FSAL_IS_ERROR(status)) { /* Release the handle we just allocated. */ (*handle)->obj_ops->release(*handle); *handle = NULL; } else if (attrs_out != NULL) { status = (*handle)->obj_ops->getattrs(*handle, attrs_out); if (FSAL_IS_ERROR(status) && (attrs_out->request_mask & ATTR_RDATTR_ERR) == 0) { /* Get attributes failed and caller expected * to get the attributes. */ goto fileerr; } } } else { status.major = ERR_FSAL_NO_ERROR; status.minor = 0; if (attrs_out != NULL) { /* Since we haven't set any attributes other than what * was set on create, just use the stat results we used * to create the fsal_obj_handle. */ posix2fsal_attributes_all(&stat, attrs_out); /* Get correct fsid from the fsal_filesystem, it may * not be the device major/minor from stat. */ attrs_out->fsid = hdl->obj_handle.fs->fsid; } } close(dir_fd); return status; fileerr: unlinkat(dir_fd, name, 0); direrr: close(dir_fd); /* done with parent */ hdlerr: status.major = posix2fsal_error(retval); errout: return fsalstat(status.major, retval); } /** makesymlink * Note that we do not set mode bits on symlinks for Linux/POSIX * They are not really settable in the kernel and are not checked * anyway (default is 0777) because open uses that target's mode */ static fsal_status_t makesymlink(struct fsal_obj_handle *dir_hdl, const char *name, const char *link_path, struct fsal_attrlist *attrib, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct vfs_fsal_obj_handle *myself, *hdl; int dir_fd = -1; struct stat stat; fsal_status_t status = { 0, 0 }; int retval = 0; int flags = O_PATH | O_NOACCESS; #ifdef ENABLE_VFS_DEBUG_ACL struct fsal_attrlist attrs; fsal_accessflags_t access_type; #endif /* ENABLE_VFS_DEBUG_ACL */ vfs_file_handle_t *fh = NULL; vfs_alloc_handle(fh); LogDebug(COMPONENT_FSAL, "create %s", name); *handle = NULL; /* poison it first */ if (!fsal_obj_handle_is(dir_hdl, DIRECTORY)) { LogCrit(COMPONENT_FSAL, "Parent handle is not a directory. hdl = 0x%p", dir_hdl); return fsalstat(ERR_FSAL_NOTDIR, 0); } myself = container_of(dir_hdl, struct vfs_fsal_obj_handle, obj_handle); if (dir_hdl->fsal != dir_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", dir_hdl->fsal->name, dir_hdl->fs->fsal != NULL ? dir_hdl->fs->fsal->name : "(none)"); retval = EXDEV; goto hdlerr; } #ifdef ENABLE_VFS_DEBUG_ACL access_type = FSAL_MODE_MASK_SET(FSAL_W_OK) | FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_ADD_FILE); status = dir_hdl->obj_ops->test_access(dir_hdl, access_type, NULL, NULL, false); if (FSAL_IS_ERROR(status)) return status; fsal_prepare_attrs(&attrs, ATTR_ACL); status = dir_hdl->obj_ops->getattrs(dir_hdl, &attrs); if (FSAL_IS_ERROR(status)) return status; status.major = fsal_inherit_acls(attrib, attrs.acl, FSAL_ACE_FLAG_FILE_INHERIT); /* Done with the attrs */ fsal_release_attrs(&attrs); if (FSAL_IS_ERROR(status)) return status; #endif /* ENABLE_VFS_DEBUG_ACL */ dir_fd = vfs_fsal_open(myself, flags, &status.major); if (dir_fd < 0) return fsalstat(status.major, -dir_fd); flags |= O_NOFOLLOW; /* BSD needs O_NOFOLLOW for * fhopen() of symlinks */ retval = vfs_stat_by_handle(dir_fd, &stat); if (retval < 0) { retval = errno; status = posix2fsal_status(retval); goto direrr; } /* Become the user because we are creating an object in this dir. */ if (!vfs_set_credentials(&op_ctx->creds, dir_hdl->fsal)) { retval = EPERM; status = posix2fsal_status(retval); goto direrr; } retval = symlinkat(link_path, dir_fd, name); if (retval < 0) { retval = errno; vfs_restore_ganesha_credentials(dir_hdl->fsal); status = posix2fsal_status(retval); goto direrr; } vfs_restore_ganesha_credentials(dir_hdl->fsal); retval = vfs_name_to_handle(dir_fd, dir_hdl->fs, name, fh); if (retval < 0) { retval = errno; status = posix2fsal_status(retval); goto linkerr; } /* now get attributes info, * being careful to get the link, not the target */ retval = fstatat(dir_fd, name, &stat, AT_SYMLINK_NOFOLLOW); if (retval < 0) { retval = errno; status = posix2fsal_status(retval); goto linkerr; } /* allocate an obj_handle and fill it up */ hdl = alloc_handle(dir_fd, fh, dir_hdl->fs, &stat, NULL, name, op_ctx->fsal_export); if (hdl == NULL) { status = fsalstat(ERR_FSAL_NOMEM, ENOMEM); goto linkerr; } *handle = &hdl->obj_handle; /* We handled the mode above. */ FSAL_UNSET_MASK(attrib->valid_mask, ATTR_MODE); if (attrib->valid_mask) { /* Now per support_ex API, if there are any other attributes * set, go ahead and get them set now. */ status = (*handle)->obj_ops->setattr2(*handle, false, NULL, attrib); if (FSAL_IS_ERROR(status)) { /* Release the handle we just allocated. */ (*handle)->obj_ops->release(*handle); *handle = NULL; } else if (attrs_out != NULL) { status = (*handle)->obj_ops->getattrs(*handle, attrs_out); if (FSAL_IS_ERROR(status) && (attrs_out->request_mask & ATTR_RDATTR_ERR) == 0) { /* Get attributes failed and caller expected * to get the attributes. */ goto linkerr; } } } else { status.major = ERR_FSAL_NO_ERROR; status.minor = 0; if (attrs_out != NULL) { /* Since we haven't set any attributes other than what * was set on create, just use the stat results we used * to create the fsal_obj_handle. */ posix2fsal_attributes_all(&stat, attrs_out); /* Get correct fsid from the fsal_filesystem, it may * not be the device major/minor from stat. */ attrs_out->fsid = hdl->obj_handle.fs->fsid; } } close(dir_fd); return status; linkerr: unlinkat(dir_fd, name, 0); direrr: close(dir_fd); hdlerr: if (retval == ENOENT) status.major = ERR_FSAL_STALE; else status.major = posix2fsal_error(retval); return fsalstat(status.major, retval); } static fsal_status_t readsymlink(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *link_content, bool refresh) { struct vfs_fsal_obj_handle *myself = NULL; int retval = 0; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; if (obj_hdl->type != SYMBOLIC_LINK) { fsal_error = ERR_FSAL_INVAL; goto out; } myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); if (obj_hdl->fsal != obj_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", obj_hdl->fsal->name, obj_hdl->fs->fsal != NULL ? obj_hdl->fs->fsal->name : "(none)"); retval = EXDEV; goto hdlerr; } if (refresh) { /* lazy load or LRU'd storage */ retval = vfs_readlink(myself, &fsal_error); if (retval < 0) { retval = -retval; goto hdlerr; } } if (myself->u.symlink.link_content == NULL) { fsal_error = ERR_FSAL_FAULT; /* probably a better error?? */ goto out; } link_content->len = myself->u.symlink.link_size; link_content->addr = gsh_malloc(myself->u.symlink.link_size); memcpy(link_content->addr, myself->u.symlink.link_content, link_content->len); hdlerr: fsal_error = posix2fsal_error(retval); out: return fsalstat(fsal_error, retval); } static fsal_status_t linkfile(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *destdir_hdl, const char *name, struct fsal_attrlist *destdir_pre_attrs_out, struct fsal_attrlist *destdir_post_attrs_out) { struct vfs_fsal_obj_handle *myself, *destdir; int destdirfd; int retval = 0; int flags = O_PATH | O_NOACCESS | O_NOFOLLOW; fsal_status_t status; fsal_status_t status2 = { ERR_FSAL_NO_ERROR, 0 }; struct vfs_fd *my_fd; struct vfs_fd temp_fd = { FSAL_FD_INIT, -1 }; struct fsal_fd *out_fd; LogFullDebug(COMPONENT_FSAL, "link to %s", name); if (!op_ctx->fsal_export->exp_ops.fs_supports(op_ctx->fsal_export, fso_link_support)) { status = fsalstat(ERR_FSAL_NOTSUPP, 0); goto out; } myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); if (obj_hdl->fsal != obj_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", obj_hdl->fsal->name, obj_hdl->fs->fsal != NULL ? obj_hdl->fs->fsal->name : "(none)"); status = posix2fsal_status(EXDEV); goto out; } /* Get a usable file descriptor (don't need to bypass - FSAL_O_ANY * won't conflict with any share reservation). */ if (obj_hdl->type == REGULAR_FILE) { /* For regular files, use fsal_start_global_io so that we use * the global fd if open otherwise we use temp_fd. We do this * instead of the code from before the re-work that just * used the global fd protected by the obj_lock which is * no longer the way to protect use of the global fd, instead * fsal_start_io uses the new protection mechanism. */ status = fsal_start_global_io(&out_fd, obj_hdl, &myself->u.file.fd.fsal_fd, &temp_fd.fsal_fd, FSAL_O_ANY, false, &myself->u.file.share); } else { /* For all other objects, we open the object itself. We don't * use find_fd because that doesn't always return an fd for the * object itself, For some object types, it returns an open fd * for the object's parent. For doing a link though, we CAN * open the actual object by handle and get an fd that works * for link. This matches the code from before the fsal_fd * re-work. */ temp_fd.fd = vfs_fsal_open(myself, flags, &status.major); out_fd = &temp_fd.fsal_fd; if (temp_fd.fd < 0) { status = posix2fsal_status(-temp_fd.fd); LogDebug(COMPONENT_FSAL, "open myself returned %s (%d)", strerror(-temp_fd.fd), -temp_fd.fd); } else { status = fsalstat(ERR_FSAL_NO_ERROR, 0); } } if (FSAL_IS_ERROR(status)) { if (obj_hdl->type == SYMBOLIC_LINK && status.major == ERR_FSAL_PERM) { /* You cannot open_by_handle (XFS on linux) a symlink * and it throws an EPERM error for it. * open_by_handle_at does not throw that error for * symlinks so we play a game here. Since there is * not much we can do with symlinks anyway, * say that we did it but don't actually * do anything. In this case, return the stat we got * at lookup time. If you *really* want to tweek things * like owners, get a modern linux kernel... */ status = fsalstat(ERR_FSAL_NO_ERROR, 0); } goto out; } my_fd = container_of(out_fd, struct vfs_fd, fsal_fd); destdir = container_of(destdir_hdl, struct vfs_fsal_obj_handle, obj_handle); if (destdir_hdl->fsal != destdir_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", destdir_hdl->fsal->name, destdir_hdl->fs->fsal != NULL ? destdir_hdl->fs->fsal->name : "(none)"); status = posix2fsal_status(EXDEV); goto no_destdir; } destdirfd = vfs_fsal_open(destdir, flags, &status.major); if (destdirfd < 0) { status = posix2fsal_status(-destdirfd); LogDebug(COMPONENT_FSAL, "open destdir returned %d", retval); goto no_destdir; } retval = vfs_link_by_handle(myself->handle, my_fd->fd, destdirfd, name); if (retval < 0) { retval = errno; LogFullDebug(COMPONENT_FSAL, "link returned %d", retval); status = posix2fsal_status(retval); } close(destdirfd); no_destdir: if (obj_hdl->type == REGULAR_FILE) { status2 = fsal_complete_io(obj_hdl, out_fd); LogFullDebug(COMPONENT_FSAL, "fsal_complete_io returned %s", fsal_err_txt(status2)); } else { retval = close(temp_fd.fd); if (retval < 0) status2 = posix2fsal_status(errno); LogFullDebug(COMPONENT_FSAL, "close returned %s", fsal_err_txt(status2)); } out: LogFullDebug(COMPONENT_FSAL, "returning %s", fsal_err_txt(status)); return status; } /* * Use the smallest buffer possible on FreeBSD * to narrow the race due to the d_off workaround. */ #ifndef HAS_DOFF #define BUF_SIZE sizeof(struct dirent) #else #define BUF_SIZE 1024 #endif /** * read_dirents * read the directory and call through the callback function for * each entry. * @param dir_hdl [IN] the directory to read * @param whence [IN] where to start (next) * @param dir_state [IN] pass thru of state to callback * @param cb [IN] callback function * @param eof [OUT] eof marker true == end of dir */ static fsal_status_t read_dirents(struct fsal_obj_handle *dir_hdl, fsal_cookie_t *whence, void *dir_state, fsal_readdir_cb cb, attrmask_t attrmask, bool *eof) { struct vfs_fsal_obj_handle *myself; int dirfd; fsal_status_t status = { 0, 0 }; int retval = 0; off_t seekloc = 0; off_t baseloc = 0; unsigned int bpos; int nread; struct vfs_dirent dentry, *dentryp = &dentry; char buf[BUF_SIZE]; #ifndef HAS_DOFF int nreadent; char entbuf[sizeof(struct dirent)]; off_t rewindloc = 0; off_t entloc = 0; #endif if (whence != NULL) seekloc = (off_t)*whence; myself = container_of(dir_hdl, struct vfs_fsal_obj_handle, obj_handle); if (dir_hdl->fsal != dir_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", dir_hdl->fsal->name, dir_hdl->fs->fsal != NULL ? dir_hdl->fs->fsal->name : "(none)"); retval = EXDEV; status = posix2fsal_status(retval); goto out; } dirfd = vfs_fsal_open(myself, O_RDONLY | O_DIRECTORY, &status.major); if (dirfd < 0) { retval = -dirfd; status = posix2fsal_status(retval); goto out; } seekloc = lseek(dirfd, seekloc, SEEK_SET); if (seekloc < 0) { retval = errno; status = posix2fsal_status(retval); goto done; } do { baseloc = seekloc; nread = vfs_readents(dirfd, buf, BUF_SIZE, &seekloc); if (nread < 0) { retval = errno; status = posix2fsal_status(retval); goto done; } if (nread == 0) break; #ifndef HAS_DOFF /* * Very inefficient workaround to retrieve directory offsets. * We rewind dirfd's to its previous offset in order read the * directory entry by entry and fetch every offset. */ rewindloc = lseek(dirfd, seekloc, SEEK_SET); if (rewindloc < 0) { retval = errno; status = posix2fsal_status(retval); goto done; } #endif for (bpos = 0; bpos < nread;) { struct fsal_obj_handle *hdl; struct fsal_attrlist attrs; enum fsal_dir_result cb_rc; if (!to_vfs_dirent(buf, bpos, dentryp, baseloc)) goto skip; #ifndef HAS_DOFF /* Re-read the entry and fetch its offset. */ nreadent = vfs_readents(dirfd, entbuf, dentryp->vd_reclen, &entloc); if (nreadent < 0) { retval = errno; status = posix2fsal_status(retval); goto done; } entloc = lseek(dirfd, 0, SEEK_CUR); if (entloc < 0) { retval = errno; status = posix2fsal_status(retval); goto done; } dentryp->vd_offset = entloc; #endif if (strcmp(dentryp->vd_name, ".") == 0 || strcmp(dentryp->vd_name, "..") == 0) goto skip; /* must skip '.' and '..' */ fsal_prepare_attrs(&attrs, attrmask); status = lookup_with_fd(myself, dirfd, dentryp->vd_name, &hdl, &attrs); if (FSAL_IS_ERROR(status)) { goto done; } /* callback to MDCACHE */ cb_rc = cb(dentryp->vd_name, hdl, &attrs, dir_state, (fsal_cookie_t)dentryp->vd_offset); fsal_release_attrs(&attrs); /* Read ahead not supported by this FSAL. */ if (cb_rc >= DIR_READAHEAD) goto done; skip: bpos += dentryp->vd_reclen; } } while (nread > 0); *eof = true; done: close(dirfd); out: return status; } static fsal_status_t renamefile(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *olddir_hdl, const char *old_name, struct fsal_obj_handle *newdir_hdl, const char *new_name, struct fsal_attrlist *olddir_pre_attrs_out, struct fsal_attrlist *olddir_post_attrs_out, struct fsal_attrlist *newdir_pre_attrs_out, struct fsal_attrlist *newdir_post_attrs_out) { struct vfs_fsal_obj_handle *olddir, *newdir, *obj; int oldfd = -1, newfd = -1; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int retval = 0; olddir = container_of(olddir_hdl, struct vfs_fsal_obj_handle, obj_handle); if (olddir_hdl->fsal != olddir_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", olddir_hdl->fsal->name, olddir_hdl->fs->fsal != NULL ? olddir_hdl->fs->fsal->name : "(none)"); retval = EXDEV; fsal_error = posix2fsal_error(retval); goto out; } oldfd = vfs_fsal_open(olddir, O_PATH | O_NOACCESS, &fsal_error); if (oldfd < 0) { retval = -oldfd; goto out; } newdir = container_of(newdir_hdl, struct vfs_fsal_obj_handle, obj_handle); if (newdir_hdl->fsal != newdir_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", newdir_hdl->fsal->name, newdir_hdl->fs->fsal != NULL ? newdir_hdl->fs->fsal->name : "(none)"); retval = EXDEV; fsal_error = posix2fsal_error(retval); goto out; } obj = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); newfd = vfs_fsal_open(newdir, O_PATH | O_NOACCESS, &fsal_error); if (newfd < 0) { retval = -newfd; goto out; } /* Become the user because we are creating/removing objects * in these dirs which messes with quotas and perms. */ if (!vfs_set_credentials(&op_ctx->creds, obj_hdl->fsal)) { retval = EPERM; fsal_error = posix2fsal_error(retval); goto out; } retval = renameat(oldfd, old_name, newfd, new_name); if (retval < 0) { retval = errno; fsal_error = posix2fsal_error(retval); vfs_restore_ganesha_credentials(obj_hdl->fsal); LogDebug(COMPONENT_FSAL, "renameat returned %d (%s)", retval, strerror(retval)); } else { if (vfs_unopenable_type(obj->obj_handle.type)) { /* A block, char, or socket has been renamed. Fixup * our information in the handle so we can still stat * it. Go ahead and discard the old name (we will abort * if gsh_strdup fails to copy the new name). */ gsh_free(obj->u.unopenable.name); memcpy(obj->u.unopenable.dir, newdir->handle, sizeof(vfs_file_handle_t)); obj->u.unopenable.name = gsh_strdup(new_name); } vfs_restore_ganesha_credentials(obj_hdl->fsal); } out: if (oldfd >= 0) close(oldfd); if (newfd >= 0) close(newfd); return fsalstat(fsal_error, retval); } /* file_unlink * unlink the named file in the directory */ static fsal_status_t file_unlink(struct fsal_obj_handle *dir_hdl, struct fsal_obj_handle *obj_hdl, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct vfs_fsal_obj_handle *myself; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; struct stat stat; int fd; int retval = 0; myself = container_of(dir_hdl, struct vfs_fsal_obj_handle, obj_handle); if (dir_hdl->fsal != dir_hdl->fs->fsal) { LogDebug( COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s, return EXDEV", dir_hdl->fsal->name, dir_hdl->fs->fsal != NULL ? dir_hdl->fs->fsal->name : "(none)"); retval = EXDEV; fsal_error = posix2fsal_error(retval); goto out; } fd = vfs_fsal_open(myself, O_PATH | O_NOACCESS, &fsal_error); if (fd < 0) { retval = -fd; goto out; } retval = fstatat(fd, name, &stat, AT_SYMLINK_NOFOLLOW); if (retval < 0) { retval = errno; LogDebug(COMPONENT_FSAL, "fstatat %s failed %s", name, strerror(retval)); if (retval == ENOENT) fsal_error = ERR_FSAL_STALE; else fsal_error = posix2fsal_error(retval); goto errout; } if (!vfs_set_credentials(&op_ctx->creds, dir_hdl->fsal)) { retval = EPERM; fsal_error = posix2fsal_error(retval); goto errout; } retval = unlinkat(fd, name, (S_ISDIR(stat.st_mode)) ? AT_REMOVEDIR : 0); if (retval < 0) { retval = errno; if (retval == ENOENT) fsal_error = ERR_FSAL_STALE; else fsal_error = posix2fsal_error(retval); } vfs_restore_ganesha_credentials(dir_hdl->fsal); errout: close(fd); out: return fsalstat(fsal_error, retval); } /* handle_to_wire * fill in the opaque f/s file handle part. * we zero the buffer to length first. This MAY already be done above * at which point, remove memset here because the caller is zeroing * the whole struct. */ static fsal_status_t handle_to_wire(const struct fsal_obj_handle *obj_hdl, fsal_digesttype_t output_type, struct gsh_buffdesc *fh_desc) { const struct vfs_fsal_obj_handle *myself; myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); if (obj_hdl->fsal != obj_hdl->fs->fsal) { /* Log, but allow digest */ LogDebug(COMPONENT_FSAL, "FSAL %s operation for handle belonging to FSAL %s", obj_hdl->fsal->name, obj_hdl->fs->fsal != NULL ? obj_hdl->fs->fsal->name : "(none)"); } switch (output_type) { case FSAL_DIGEST_NFSV3: case FSAL_DIGEST_NFSV4: if (fh_desc->len < myself->handle->handle_len) { LogMajor( COMPONENT_FSAL, "Space too small for handle. need %u, have %zu", myself->handle->handle_len, fh_desc->len); return fsalstat(ERR_FSAL_TOOSMALL, 0); } memcpy(fh_desc->addr, myself->handle->handle_data, myself->handle->handle_len); break; default: return fsalstat(ERR_FSAL_SERVERFAULT, 0); } fh_desc->len = myself->handle->handle_len; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief release object handle * * release our export first so they know we are gone */ static void release(struct fsal_obj_handle *obj_hdl) { struct vfs_fsal_obj_handle *myself; object_file_type_t type = obj_hdl->type; myself = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); if (type == REGULAR_FILE) { fsal_status_t st; st = close_fsal_fd(obj_hdl, &myself->u.file.fd.fsal_fd, false); if (FSAL_IS_ERROR(st)) { LogCrit(COMPONENT_FSAL, "Could not close hdl 0x%p, status %s error %s(%d)", obj_hdl, fsal_err_txt(st), strerror(st.minor), st.minor); } } fsal_obj_handle_fini(obj_hdl, true); free_vfs_fsal_obj_handle(&myself); } void vfs_handle_ops_init(struct fsal_obj_ops *ops) { fsal_default_obj_ops_init(ops); ops->release = release; ops->merge = vfs_merge; ops->lookup = lookup; ops->readdir = read_dirents; ops->mkdir = makedir; ops->mknode = makenode; ops->symlink = makesymlink; ops->readlink = readsymlink; ops->getattrs = vfs_getattr2; ops->link = linkfile; ops->rename = renamefile; ops->unlink = file_unlink; ops->close = vfs_close; #ifdef FALLOC_FL_PUNCH_HOLE ops->fallocate = vfs_fallocate; #endif ops->handle_to_wire = handle_to_wire; ops->handle_to_key = handle_to_key; ops->open2 = vfs_open2; ops->status2 = vfs_status2; ops->reopen2 = vfs_reopen2; ops->read2 = vfs_read2; ops->write2 = vfs_write2; #ifdef __USE_GNU ops->seek2 = vfs_seek2; #endif ops->commit2 = vfs_commit2; #ifdef F_OFD_GETLK ops->lock_op2 = vfs_lock_op2; #endif ops->setattr2 = vfs_setattr2; ops->close2 = vfs_close2; ops->close_func = vfs_close_func; ops->reopen_func = vfs_reopen_func; /* xattr related functions */ ops->list_ext_attrs = vfs_list_ext_attrs; ops->getextattr_id_by_name = vfs_getextattr_id_by_name; ops->getextattr_value_by_name = vfs_getextattr_value_by_name; ops->getextattr_value_by_id = vfs_getextattr_value_by_id; ops->setextattr_value = vfs_setextattr_value; ops->setextattr_value_by_id = vfs_setextattr_value_by_id; ops->remove_extattr_by_id = vfs_remove_extattr_by_id; ops->remove_extattr_by_name = vfs_remove_extattr_by_name; ops->is_referral = fsal_common_is_referral; } /* export methods that create object handles */ /* lookup_path * modeled on old api except we don't stuff attributes. * KISS */ fsal_status_t vfs_lookup_path(struct fsal_export *exp_hdl, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { int dir_fd = -1; struct stat stat; struct vfs_fsal_obj_handle *hdl; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int retval = 0; struct fsal_filesystem *fs; struct fsal_dev__ dev; vfs_file_handle_t *fh = NULL; vfs_alloc_handle(fh); *handle = NULL; /* poison it */ dir_fd = open_dir_by_path_walk(-1, path, &stat); if (dir_fd < 0) { LogDebug(COMPONENT_FSAL, "Could not open directory for path %s", path); retval = -dir_fd; goto errout; } dev = posix2fsal_devt(stat.st_dev); fs = lookup_dev(&dev); if (fs == NULL) { LogInfo(COMPONENT_FSAL, "Could not find file system for path %s", path); retval = ENOENT; goto errout; } if (fs->fsal != exp_hdl->fsal) { LogInfo(COMPONENT_FSAL, "File system for path %s did not belong to FSAL %s", path, exp_hdl->fsal->name); retval = EACCES; goto errout; } LogDebug(COMPONENT_FSAL, "filesystem %s for path %s", fs->path, path); retval = vfs_fd_to_handle(dir_fd, fs, fh); if (retval < 0) { retval = errno; LogCrit(COMPONENT_FSAL, "Could not get handle for path %s, error %s", path, strerror(retval)); goto errout; } /* allocate an obj_handle and fill it up */ hdl = alloc_handle(-1, fh, fs, &stat, NULL, "", exp_hdl); if (hdl == NULL) { retval = ENOMEM; LogCrit(COMPONENT_FSAL, "Could not allocate handle for path %s", path); goto errout; } close(dir_fd); if (attrs_out != NULL) { posix2fsal_attributes_all(&stat, attrs_out); /* Get correct fsid from the fsal_filesystem, it may * not be the device major/minor from stat. */ attrs_out->fsid = hdl->obj_handle.fs->fsid; } /* if it is a directory and the sticky bit is set * let's look for referral information */ if (attrs_out != NULL && hdl->obj_handle.obj_ops->is_referral(&hdl->obj_handle, attrs_out, false) && hdl->obj_handle.fs->private_data != NULL && hdl->sub_ops->getattrs) { fsal_status_t status; status = populate_fs_locations(hdl, attrs_out); if (FSAL_IS_ERROR(status)) { LogEvent(COMPONENT_FSAL, "Could not get the referral " "locations for the exported path: %s", path); free_vfs_fsal_obj_handle(&hdl); return status; } } *handle = &hdl->obj_handle; return fsalstat(ERR_FSAL_NO_ERROR, 0); errout: if (dir_fd >= 0) close(dir_fd); fsal_error = posix2fsal_error(retval); return fsalstat(fsal_error, retval); } fsal_status_t vfs_check_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *hdl_desc, struct fsal_filesystem **fs, vfs_file_handle_t *fh, bool *dummy) { fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int retval = 0; struct fsal_fsid__ fsid; enum fsid_type fsid_type; bool fslocked = false; *fs = NULL; if (!vfs_valid_handle(hdl_desc)) return fsalstat(ERR_FSAL_BADHANDLE, 0); memcpy(fh->handle_data, hdl_desc->addr, hdl_desc->len); fh->handle_len = hdl_desc->len; *dummy = vfs_is_dummy_handle(fh); retval = vfs_extract_fsid(fh, &fsid_type, &fsid); if (retval == 0) { /* Since the root_fs is the most likely one, and it can't * change, we can check without locking. */ if (fsal_fs_compare_fsid(fsid_type, &fsid, exp_hdl->root_fs->fsid_type, &exp_hdl->root_fs->fsid) == 0) { /* This is the root_fs of the export, all good. */ *fs = exp_hdl->root_fs; } else { /* Must lookup fs and check that it is exported by * the export. */ PTHREAD_RWLOCK_rdlock(&fs_lock); fslocked = true; *fs = lookup_fsid_locked(&fsid, fsid_type); } if (*fs == NULL) { LogInfo(COMPONENT_FSAL, "Could not map fsid=0x%016" PRIx64 ".0x%016" PRIx64 " to filesystem", fsid.major, fsid.minor); retval = ESTALE; fsal_error = posix2fsal_error(retval); goto errout; } if (((*fs)->fsal != exp_hdl->fsal) && !(*dummy)) { LogInfo(COMPONENT_FSAL, "fsid=0x%016" PRIx64 ".0x%016" PRIx64 " in handle not a %s filesystem", fsid.major, fsid.minor, exp_hdl->fsal->name); retval = ESTALE; fsal_error = posix2fsal_error(retval); goto errout; } /* If we had to lookup fs, then we must check that it is * exported by the export. */ if (fslocked && !(*dummy) && !is_filesystem_exported(*fs, exp_hdl)) { /* We've got a handle with a spoofed fsid/export_id */ retval = ESTALE; fsal_error = posix2fsal_error(retval); goto errout; } LogDebug(COMPONENT_FSAL, "Found filesystem %s for handle for FSAL %s", (*fs)->path, (*fs)->fsal != NULL ? (*fs)->fsal->name : "(none)"); } else { LogDebug(COMPONENT_FSAL, "Could not map handle to fsid"); fsal_error = ERR_FSAL_BADHANDLE; goto errout; } errout: if (fslocked) PTHREAD_RWLOCK_unlock(&fs_lock); return fsalstat(fsal_error, retval); } /* create_handle * Does what original FSAL_ExpandHandle did (sort of) * returns a ref counted handle to be later used in mdcache etc. * NOTE! you must release this thing when done with it! * BEWARE! Thanks to some holes in the *AT syscalls implementation, * we cannot get an fd on an AF_UNIX socket, nor reliably on block or * character special devices. Sorry, it just doesn't... * we could if we had the handle of the dir it is in, but this method * is for getting handles off the wire for cache entries that have LRU'd. * Ideas and/or clever hacks are welcome... */ fsal_status_t vfs_create_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *hdl_desc, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { fsal_status_t status; struct vfs_fsal_obj_handle *hdl; struct stat obj_stat; vfs_file_handle_t *fh = NULL; fsal_errors_t fsal_error = ERR_FSAL_NO_ERROR; int retval = 0; int fd; int flags = O_PATH | O_NOACCESS | O_NOFOLLOW; struct fsal_filesystem *fs; bool dummy; vfs_alloc_handle(fh); *handle = NULL; /* poison it first */ status = vfs_check_handle(exp_hdl, hdl_desc, &fs, fh, &dummy); if (FSAL_IS_ERROR(status)) return status; if (dummy) { /* We don't need fd here, just stat the fs->path */ fd = -1; retval = stat(fs->path, &obj_stat); } else { fd = vfs_open_by_handle(fs, fh, flags, &fsal_error); if (fd < 0) { #ifdef __FreeBSD__ if (fd == -EMLINK) { fd = -1; fsal_error = ERR_FSAL_NO_ERROR; } else #endif { retval = -fd; goto errout; } } #ifdef __FreeBSD__ if (fd < 0) retval = fhstat(v_to_fhandle(fh->handle_data), &obj_stat); else #endif { retval = vfs_stat_by_handle(fd, &obj_stat); } } /* Test the result of stat */ if (retval != 0) { retval = errno; LogDebug(COMPONENT_FSAL, "%s failed with %s", dummy ? "stat" : "vfs_stat_by_handle", strerror(retval)); fsal_error = posix2fsal_error(retval); if (fd >= 0) close(fd); goto errout; } hdl = alloc_handle(fd, fh, fs, &obj_stat, NULL, "", exp_hdl); if (fd >= 0) close(fd); if (hdl == NULL) { LogDebug(COMPONENT_FSAL, "Could not allocate handle"); fsal_error = ERR_FSAL_NOMEM; goto errout; } if (attrs_out != NULL) { posix2fsal_attributes_all(&obj_stat, attrs_out); /* Get correct fsid from the fsal_filesystem, it may * not be the device major/minor from stat. */ attrs_out->fsid = hdl->obj_handle.fs->fsid; } *handle = &hdl->obj_handle; errout: return fsalstat(fsal_error, retval); } nfs-ganesha-6.5/src/FSAL/FSAL_VFS/handle_syscalls.c000066400000000000000000000073371473756622300217020ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /* handle.c * VFS object (file|dir) handle object */ #include "config.h" #include "fsal.h" #include "fsal_handle_syscalls.h" #include /* used for 'dirname' */ #include #include #include #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_localfs.h" #include "vfs_methods.h" #include int vfs_readlink(struct vfs_fsal_obj_handle *myself, fsal_errors_t *fsal_error) { int retval = 0; int fd = -1; ssize_t retlink; struct stat st; #ifndef __FreeBSD__ int flags = O_PATH | O_NOACCESS | O_NOFOLLOW; #endif if (myself->u.symlink.link_content != NULL) { gsh_free(myself->u.symlink.link_content); myself->u.symlink.link_content = NULL; myself->u.symlink.link_size = 0; } #ifndef __FreeBSD__ fd = vfs_fsal_open(myself, flags, fsal_error); if (fd < 0) return fd; retval = vfs_stat_by_handle(fd, &st); if (retval < 0) goto error; #else struct fhandle *handle = v_to_fhandle(myself->handle->handle_data); retval = fhstat(handle, &st); if (retval < 0) goto error; #endif myself->u.symlink.link_size = st.st_size + 1; myself->u.symlink.link_content = gsh_malloc(myself->u.symlink.link_size); retlink = vfs_readlink_by_handle(myself->handle, fd, "", myself->u.symlink.link_content, myself->u.symlink.link_size); if (retlink < 0) goto error; myself->u.symlink.link_content[retlink] = '\0'; #ifndef __FreeBSD__ close(fd); #endif return retval; error: retval = -errno; *fsal_error = posix2fsal_error(errno); #ifndef __FreeBSD__ close(fd); #endif if (myself->u.symlink.link_content != NULL) { gsh_free(myself->u.symlink.link_content); myself->u.symlink.link_content = NULL; myself->u.symlink.link_size = 0; } return retval; } int vfs_get_root_handle(struct fsal_filesystem *fs, struct vfs_fsal_export *exp, int *root_fd) { int retval = 0; *root_fd = open(fs->path, O_RDONLY | O_DIRECTORY); if (*root_fd < 0) { retval = errno; LogMajor(COMPONENT_FSAL, "Could not open VFS mount point %s: rc = %s (%d)", fs->path, strerror(retval), retval); return retval; } /* Check if we have to re-index the fsid based on config */ if (exp->fsid_type != FSID_NO_TYPE && exp->fsid_type != fs->fsid_type) { retval = -change_fsid_type(fs, exp->fsid_type); if (retval != 0) { LogCrit(COMPONENT_FSAL, "Can not change fsid type of %s to %d, error %s", fs->path, exp->fsid_type, strerror(retval)); (void)close(*root_fd); *root_fd = -1; return retval; } LogInfo(COMPONENT_FSAL, "Reindexed filesystem %s to fsid=0x%016" PRIx64 ".0x%016" PRIx64, fs->path, fs->fsid.major, fs->fsid.minor); } /* May reindex for some platforms */ return vfs_re_index(fs, exp); } nfs-ganesha-6.5/src/FSAL/FSAL_VFS/os/000077500000000000000000000000001473756622300167755ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_VFS/os/CMakeLists.txt000066400000000000000000000030631473756622300215370ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # All we need to do here is control the # build of chosen platform if(FREEBSD) SET(fsal_os_STAT_SRCS freebsd/handle_syscalls.c ) endif(FREEBSD) if(LINUX) SET(fsal_os_STAT_SRCS linux/handle_syscalls.c ) endif(LINUX) add_library(fsal_os OBJECT ${fsal_os_STAT_SRCS}) add_sanitizers(fsal_os) set_target_properties(fsal_os PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(fsal_os gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) ########### install files ############### nfs-ganesha-6.5/src/FSAL/FSAL_VFS/os/freebsd/000077500000000000000000000000001473756622300204075ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_VFS/os/freebsd/handle_syscalls.c000066400000000000000000000155021473756622300237260ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later /* * Copyright (C) Panasas, Inc. 2011 * Author(s): Brent Welch Sachin Bhamare * * 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 can be distributed with a BSD license as well, just * ask. * * 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., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ /** * @file FSAL/FSAL_VFS/os/freebsd/handle_syscalls.c * @brief System calls for the FreeBSD handle calls */ #include /* avoid conflicts with sys/queue.h */ #include #include "fsal_convert.h" #include "fsal_api.h" #include "FSAL/fsal_commonlib.h" #include "../../vfs_methods.h" #include #include "fsal_handle_syscalls.h" static inline size_t vfs_sizeof_handle(struct v_fhandle *fh) { return sizeof(fh->fh_flags) + sizeof(fsid_t) + sizeof(fh->fh_fid.fid_len) + sizeof(fh->fh_fid.fid_reserved) + fh->fh_fid.fid_len; } /* Verify handle size is large enough. * sizeof(fh_flags) == 1 * sizeof(fsid_t) == 8 * sizeof(fid_len) == 2 * sizeof(fid_reserved) == 2 * fid_len == MAXFIDSZ */ #if VFS_HANDLE_LEN < (1 + 8 + 2 + 2 + MAXFIDSZ) #error "VFS_HANDLE_LEN is too small" #endif void display_vfs_handle(struct display_buffer *dspbuf, struct vfs_file_handle *fh) { struct v_fhandle *hdl = (struct v_fhandle *)fh->handle_data; int b_left; b_left = display_printf(dspbuf, "Handle len %hhu: fsid=0x%016" PRIx32 ".0x%016" PRIx32 " fid_len=%" PRIu16 " fid_pad=%" PRIu16, fh->handle_len, hdl->fh_fsid.val[0], hdl->fh_fsid.val[1], hdl->fh_fid.fid_len, hdl->fh_fid.fid_reserved); if (b_left <= 0) return; display_opaque_value(dspbuf, hdl->fh_fid.fid_data, hdl->fh_fid.fid_len); } #define LogVFSHandle(fh) \ do { \ if (isMidDebug(COMPONENT_FSAL)) { \ char buf[256] = "\0"; \ struct display_buffer dspbuf = { sizeof(buf), buf, \ buf }; \ \ display_vfs_handle(&dspbuf, fh); \ \ LogMidDebug(COMPONENT_FSAL, "%s", buf); \ } \ } while (0) int vfs_fd_to_handle(int fd, struct fsal_filesystem *fs, vfs_file_handle_t *fh) { int error; struct v_fhandle *handle = (struct v_fhandle *)fh->handle_data; error = getfhat(fd, NULL, v_to_fhandle(handle), AT_SYMLINK_FOLLOW); if (error == 0) fh->handle_len = vfs_sizeof_handle(handle); return error; } int vfs_name_to_handle(int atfd, struct fsal_filesystem *fs, const char *name, vfs_file_handle_t *fh) { int error; struct v_fhandle *handle = (struct v_fhandle *)fh->handle_data; error = getfhat(atfd, (char *)name, v_to_fhandle(handle), AT_SYMLINK_NOFOLLOW); if (error == 0) fh->handle_len = vfs_sizeof_handle(handle); return error; } int vfs_open_by_handle(struct fsal_filesystem *fs, vfs_file_handle_t *fh, int openflags, fsal_errors_t *fsal_error) { int fd; fd = fhopen(v_to_fhandle(fh->handle_data), openflags); if (fd < 0) { fd = -errno; if (fd == -ENOENT) fd = -ESTALE; *fsal_error = posix2fsal_error(-fd); LogDebug(COMPONENT_FSAL, "Failed with %s", strerror(-fd)); } return fd; } int vfs_extract_fsid(vfs_file_handle_t *fh, enum fsid_type *fsid_type, struct fsal_fsid__ *fsid) { struct v_fhandle *hdl = (struct v_fhandle *)fh->handle_data; LogVFSHandle(fh); *fsid_type = FSID_TWO_UINT32; fsid->major = hdl->fh_fsid.val[0]; fsid->minor = hdl->fh_fsid.val[1]; return 0; } int vfs_encode_dummy_handle(vfs_file_handle_t *fh, struct fsal_filesystem *fs) { struct v_fhandle *hdl = (struct v_fhandle *)fh->handle_data; int rc; hdl->fh_fsid.val[0] = 0; hdl->fh_fsid.val[1] = 0; rc = encode_fsid(hdl->fh_fid.fid_data, sizeof(hdl->fh_fid.fid_data), &fs->fsid, fs->fsid_type); if (rc < 0) { errno = EINVAL; return rc; } hdl->fh_fid.fid_reserved = fs->fsid_type + 1; hdl->fh_fid.fid_len = rc; hdl->fh_flags = HANDLE_DUMMY; fh->handle_len = vfs_sizeof_handle(hdl); LogVFSHandle(fh); return 0; } bool vfs_is_dummy_handle(vfs_file_handle_t *fh) { struct v_fhandle *hdl = (struct v_fhandle *)fh->handle_data; return hdl->fh_flags == HANDLE_DUMMY; } bool vfs_valid_handle(struct gsh_buffdesc *desc) { struct v_fhandle *hdl = (struct v_fhandle *)desc->addr; if ((desc->addr == NULL) || (desc->len < (sizeof(fsid_t) + sizeof(hdl->fh_fid.fid_len) + sizeof(hdl->fh_fid.fid_reserved)))) return false; if (isMidDebug(COMPONENT_FSAL)) { char buf[256] = "\0"; struct display_buffer dspbuf = { sizeof(buf), buf, buf }; int b_left; b_left = display_printf(&dspbuf, "Handle len %d: fsid=0x%016" PRIx32 ".0x%016" PRIx32 " fid_len=%" PRIu16 " fid_pad=%" PRIu16, (int)desc->len, hdl->fh_fsid.val[0], hdl->fh_fsid.val[1], hdl->fh_fid.fid_len, hdl->fh_fid.fid_reserved); if (b_left > 0) { display_opaque_value(&dspbuf, hdl->fh_fid.fid_data, hdl->fh_fid.fid_len); } LogMidDebug(COMPONENT_FSAL, "%s", buf); } return (desc->len >= (sizeof(fsid_t) + sizeof(hdl->fh_fid.fid_len) + sizeof(hdl->fh_fid.fid_reserved))) && (desc->len == vfs_sizeof_handle(hdl)); } int vfs_re_index(struct fsal_filesystem *fs, struct vfs_fsal_export *exp) { enum fsid_type fsid_type; struct fsal_fsid__ fsid; int retval; vfs_file_handle_t *fh; vfs_alloc_handle(fh); retval = vfs_fd_to_handle(root_fd(fs), fs, fh); if (retval != 0) { retval = errno; LogMajor(COMPONENT_FSAL, "Get root handle for %s failed with %s (%d)", fs->path, strerror(retval), retval); goto errout; } /* Extract fsid from the root handle and re-index the filesystem * using it. This is because the file handle already has an fsid in * it. */ (void)vfs_extract_fsid(fh, &fsid_type, &fsid); retval = re_index_fs_fsid(fs, fsid_type, &fsid); if (retval < 0) { LogCrit(COMPONENT_FSAL, "Could not re-index VFS file system fsid for %s", fs->path); retval = -retval; } errout: return retval; } /** @} */ nfs-ganesha-6.5/src/FSAL/FSAL_VFS/os/linux/000077500000000000000000000000001473756622300201345ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_VFS/os/linux/handle_syscalls.c000066400000000000000000000270121473756622300234520ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later /* * Copyright (C) International Business Machines Corp., 2010 * Author(s): Aneesh Kumar K.V * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * @file FSAL/FSAL_VFS/os/linux/handle_syscalls.c * @brief System calls for the Linux handle calls * */ #include "fsal.h" #include "fsal_api.h" #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "../../vfs_methods.h" /* We can at most support 40 byte handles, which are the largest known. * We also expect handles to be at least 4 bytes. */ #define VFS_MAX_HANDLE 48 #define VFS_MIN_HANDLE_SIZE 4 /* Visual handle format * * 1 byte handle_len (doesn't go on wire) * 1 byte flags * 00xxxxxx fsid_type * 01000000 handle_type fits in one byte * 10000000 handle_type fits in two bytes * 11000000 handle_type fits in four bytes */ #define HANDLE_TYPE_8 0x40 #define HANDLE_TYPE_16 0x80 #define HANDLE_TYPE_32 0xC0 #define HANDLE_TYPE_MASK 0xC0 #define HANDLE_DUMMY 0x20 #define HANDLE_FSID_MASK (~(HANDLE_TYPE_MASK | HANDLE_DUMMY)) /* * 0, 8, or 16 bytes fsid type * 1,2 or 4 bytes handle type * 12 to 40 bytes opaque kernel handle * * NOTE: if handle type doesn't fit in 2 bytes or less, 40 byte handles * (BTRFS and GPFS are known file systems that use 40 bytes) are * incompatible with 16 byte uuid form fsids. * If VMware clients are concerned, the limit is 8 bytes less... */ int display_vfs_handle(struct display_buffer *dspbuf, struct vfs_file_handle *fh) { int16_t i16; int32_t i32; uint32_t u32[2]; uint64_t u64[2]; uint8_t handle_cursor = 1; int b_left; b_left = display_printf(dspbuf, "Handle len %hhu 0x%02hhx: ", fh->handle_len, fh->handle_data[0]); if (b_left <= 0) return b_left; switch ((enum fsid_type)fh->handle_data[0] & HANDLE_FSID_MASK) { case FSID_NO_TYPE: b_left = display_cat(dspbuf, "no fsid"); break; case FSID_ONE_UINT64: case FSID_MAJOR_64: memcpy(u64, fh->handle_data + handle_cursor, sizeof(u64[0])); handle_cursor += sizeof(u64[0]); b_left = display_printf(dspbuf, "fsid=0x%016" PRIx64 ".0x0000000000000000", u64[0]); break; case FSID_TWO_UINT64: memcpy(u64, fh->handle_data + handle_cursor, sizeof(u64)); handle_cursor += sizeof(u64); b_left = display_printf(dspbuf, "fsid=0x%016" PRIx64 ".0x%016" PRIx64, u64[0], u64[1]); break; case FSID_TWO_UINT32: case FSID_DEVICE: memcpy(u32, fh->handle_data + handle_cursor, sizeof(u32)); handle_cursor += sizeof(u32); b_left = display_printf(dspbuf, "fsid=0x%016" PRIx32 ".0x%016" PRIx32, u32[0], u32[1]); break; } if (b_left <= 0) return b_left; if ((fh->handle_data[0] & HANDLE_DUMMY) != 0) return display_cat(dspbuf, ", DUMMY"); switch (fh->handle_data[0] & HANDLE_TYPE_MASK) { case 0: b_left = display_cat(dspbuf, ", invalid type"); break; case HANDLE_TYPE_8: b_left = display_printf(dspbuf, ", type 0x%02hhx", fh->handle_data[handle_cursor]); handle_cursor++; break; case HANDLE_TYPE_16: memcpy(&i16, fh->handle_data + handle_cursor, sizeof(i16)); handle_cursor += sizeof(i16); b_left = display_printf(dspbuf, ", type 0x%04h" PRIx16, i16); break; case HANDLE_TYPE_32: memcpy(&i32, fh->handle_data + handle_cursor, sizeof(i32)); handle_cursor += sizeof(i32); b_left = display_printf(dspbuf, ", type 0x%04" PRIx32, i32); break; } if (b_left <= 0) return b_left; b_left = display_cat(dspbuf, ", opaque: "); if (b_left <= 0) return b_left; return display_opaque_value(dspbuf, fh->handle_data + handle_cursor, fh->handle_len - handle_cursor); } #define LogVFSHandle(fh) \ do { \ if (isMidDebug(COMPONENT_FSAL)) { \ char buf[256] = "\0"; \ struct display_buffer dspbuf = { sizeof(buf), buf, \ buf }; \ \ display_vfs_handle(&dspbuf, fh); \ \ LogMidDebug(COMPONENT_FSAL, "%s", buf); \ } \ } while (0) int vfs_map_name_to_handle_at(int fd, struct fsal_filesystem *fs, const char *path, vfs_file_handle_t *fh, int flags) { struct file_handle *kernel_fh; int32_t i32; int rc; int mnt_id; kernel_fh = alloca(sizeof(struct file_handle) + VFS_MAX_HANDLE); kernel_fh->handle_bytes = VFS_MAX_HANDLE; rc = name_to_handle_at(fd, path, kernel_fh, &mnt_id, flags); if (rc < 0) { int err = errno; LogDebug(COMPONENT_FSAL, "Error %s (%d) bytes = %d", strerror(err), err, (int)kernel_fh->handle_bytes); errno = err; return rc; } /* Init flags with fsid type */ fh->handle_data[0] = fs->fsid_type; fh->handle_len = 1; /* Pack fsid into wire handle */ rc = encode_fsid(fh->handle_data + 1, sizeof_fsid(fs->fsid_type), &fs->fsid, fs->fsid_type); if (rc < 0) { errno = EINVAL; return rc; } fh->handle_len += rc; /* Pack handle type into wire handle */ if (kernel_fh->handle_type <= UINT8_MAX) { /* Copy one byte in and advance cursor */ fh->handle_data[fh->handle_len] = kernel_fh->handle_type; fh->handle_len++; fh->handle_data[0] |= HANDLE_TYPE_8; } else if (kernel_fh->handle_type <= INT16_MAX && kernel_fh->handle_type >= INT16_MIN) { /* Type fits in 16 bits */ int16_t handle_type_16 = kernel_fh->handle_type; memcpy(fh->handle_data + fh->handle_len, &handle_type_16, sizeof(handle_type_16)); fh->handle_len += sizeof(handle_type_16); fh->handle_data[0] |= HANDLE_TYPE_16; } else { /* Type needs whole 32 bits */ i32 = kernel_fh->handle_type; memcpy(fh->handle_data + fh->handle_len, &i32, sizeof(i32)); fh->handle_len += sizeof(i32); fh->handle_data[0] |= HANDLE_TYPE_32; } /* Pack opaque handle into wire handle */ if (fh->handle_len + kernel_fh->handle_bytes > VFS_HANDLE_LEN) { /* We just can't fit this handle... */ errno = EOVERFLOW; return -1; } else { memcpy(fh->handle_data + fh->handle_len, kernel_fh->f_handle, kernel_fh->handle_bytes); fh->handle_len += kernel_fh->handle_bytes; } LogVFSHandle(fh); return 0; } int vfs_open_by_handle(struct fsal_filesystem *fs, vfs_file_handle_t *fh, int openflags, fsal_errors_t *fsal_error) { struct file_handle *kernel_fh; uint8_t handle_cursor = sizeof_fsid(fs->fsid_type) + 1; int16_t i16; int32_t i32; int fd; LogFullDebug(COMPONENT_FSAL, "vfs_fs = %s root_fd = %d", fs->path, root_fd(fs)); LogVFSHandle(fh); kernel_fh = alloca(sizeof(struct file_handle) + VFS_MAX_HANDLE); switch (fh->handle_data[0] & HANDLE_TYPE_MASK) { case 0: LogDebug(COMPONENT_FSAL, "Invalid handle type = 0"); errno = EINVAL; fd = -1; goto out; case HANDLE_TYPE_8: kernel_fh->handle_type = fh->handle_data[handle_cursor]; handle_cursor++; break; case HANDLE_TYPE_16: memcpy(&i16, fh->handle_data + handle_cursor, sizeof(i16)); handle_cursor += sizeof(i16); kernel_fh->handle_type = i16; break; case HANDLE_TYPE_32: memcpy(&i32, fh->handle_data + handle_cursor, sizeof(i32)); handle_cursor += sizeof(i32); kernel_fh->handle_type = i32; break; } kernel_fh->handle_bytes = fh->handle_len - handle_cursor; memcpy(kernel_fh->f_handle, fh->handle_data + handle_cursor, kernel_fh->handle_bytes); fd = open_by_handle_at(root_fd(fs), kernel_fh, openflags); out: if (fd < 0) { fd = -errno; if (fd == -ENOENT) fd = -ESTALE; *fsal_error = posix2fsal_error(-fd); LogDebug(COMPONENT_FSAL, "Failed with %s openflags 0x%08x", strerror(-fd), openflags); } else { LogFullDebug(COMPONENT_FSAL, "Opened fd %d", fd); } return fd; } int vfs_fd_to_handle(int fd, struct fsal_filesystem *fs, vfs_file_handle_t *fh) { return vfs_map_name_to_handle_at(fd, fs, "", fh, AT_EMPTY_PATH); } int vfs_name_to_handle(int atfd, struct fsal_filesystem *fs, const char *name, vfs_file_handle_t *fh) { return vfs_map_name_to_handle_at(atfd, fs, name, fh, 0); } int vfs_extract_fsid(vfs_file_handle_t *fh, enum fsid_type *fsid_type, struct fsal_fsid__ *fsid) { LogVFSHandle(fh); *fsid_type = (enum fsid_type)fh->handle_data[0] & HANDLE_FSID_MASK; if (decode_fsid(fh->handle_data + 1, fh->handle_len - 1, fsid, *fsid_type) < 0) return ESTALE; else return 0; } int vfs_encode_dummy_handle(vfs_file_handle_t *fh, struct fsal_filesystem *fs) { int rc; /* Init flags with fsid type */ fh->handle_data[0] = fs->fsid_type | HANDLE_DUMMY; fh->handle_len = 1; /* Pack fsid into wire handle */ rc = encode_fsid(fh->handle_data + 1, sizeof_fsid(fs->fsid_type), &fs->fsid, fs->fsid_type); if (rc < 0) { errno = EINVAL; return rc; } fh->handle_len += rc; LogVFSHandle(fh); return 0; } bool vfs_is_dummy_handle(vfs_file_handle_t *fh) { return (fh->handle_data[0] & HANDLE_DUMMY) != 0; } bool vfs_valid_handle(struct gsh_buffdesc *desc) { uint8_t handle0; int len = 1; /* handle_type */ bool fsid_type_ok = false; bool ok; if (desc->addr == NULL) { LogDebug(COMPONENT_FSAL, "desc->addr == NULL"); return false; } if (desc->len > VFS_HANDLE_LEN) { LogDebug(COMPONENT_FSAL, "desc->len %d > VFS_HANDLE_LEN", (int)desc->len); return false; } handle0 = *((uint8_t *)(desc->addr)); switch ((enum fsid_type)handle0 & HANDLE_FSID_MASK) { case FSID_NO_TYPE: case FSID_ONE_UINT64: case FSID_MAJOR_64: case FSID_TWO_UINT64: case FSID_TWO_UINT32: case FSID_DEVICE: fsid_type_ok = true; len += sizeof_fsid((enum fsid_type)handle0 & HANDLE_FSID_MASK); break; } if (!fsid_type_ok) { LogDebug(COMPONENT_FSAL, "FSID Type %02hhx invalid", (uint8_t)(handle0 & HANDLE_FSID_MASK)); return false; } if ((handle0 & HANDLE_DUMMY) != 0) { ok = len == desc->len; if (!ok) { LogDebug(COMPONENT_FSAL, "Len %d != desc->len %d for DUMMY handle", len, (int)desc->len); } return ok; } /* min kernel handle size */ len += sizeof(uint32_t); switch (handle0 & HANDLE_TYPE_MASK) { case HANDLE_TYPE_8: len += sizeof(uint8_t); break; case HANDLE_TYPE_16: len += sizeof(int16_t); break; case HANDLE_TYPE_32: len += sizeof(int32_t); break; default: LogDebug(COMPONENT_FSAL, "Handle Type %02hhx invalid", (uint8_t)(handle0 & HANDLE_TYPE_MASK)); return false; } ok = (len + VFS_MIN_HANDLE_SIZE) <= desc->len; if (!ok) { LogDebug(COMPONENT_FSAL, "Len %d + VFS_MIN_HANDLE_SIZE %d > desc->len %d", len, len + VFS_MIN_HANDLE_SIZE, (int)desc->len); return false; } ok = (len + VFS_MAX_HANDLE) >= desc->len; if (!ok) { LogDebug(COMPONENT_FSAL, "Len %d + VFS_MAX_HANDLE %d < desc->len %d", len, len + VFS_MAX_HANDLE, (int)desc->len); } return true; } int vfs_re_index(struct fsal_filesystem *fs, struct vfs_fsal_export *exp) { return 0; } /** @} */ nfs-ganesha-6.5/src/FSAL/FSAL_VFS/state.c000066400000000000000000000064731473756622300176520ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) CohortFS Inc., 2015 * Author: Daniel Gryniewicz dang@cohortfs.com * * This program 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 3 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 * 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 * * ------------- */ /* state.c * VFS state management */ #include "config.h" #include #include #include "gsh_types.h" #include "sal_data.h" #include "sal_functions.h" #include "avltree.h" #include "vfs_methods.h" struct vfs_state_entry { struct gsh_buffdesc fs_key; /**< Key for tree */ struct avltree_node fs_node; /**< AVL tree node */ struct state_hdl ostate; /**< Actual file state */ }; static struct avltree vfs_state_tree = { 0 }; /** * @brief VFS state comparator for AVL tree walk * */ static int vfs_state_cmpf(const struct avltree_node *lhs, const struct avltree_node *rhs) { struct vfs_state_entry *lk, *rk; lk = avltree_container_of(lhs, struct vfs_state_entry, fs_node); rk = avltree_container_of(rhs, struct vfs_state_entry, fs_node); if (lk->fs_key.len != rk->fs_key.len) return (lk->fs_key.len < rk->fs_key.len) ? -1 : 1; return memcmp(lk->fs_key.addr, rk->fs_key.addr, lk->fs_key.len); } static struct vfs_state_entry *vfs_state_lookup(struct gsh_buffdesc *key) { struct vfs_state_entry key_entry; struct avltree_node *node; memset(&key_entry, 0, sizeof(key_entry)); key_entry.fs_key = *key; node = avltree_lookup(&key_entry.fs_node, &vfs_state_tree); if (!node) return NULL; return avltree_container_of(node, struct vfs_state_entry, fs_node); } void vfs_state_init(void) { if (vfs_state_tree.cmp_fn == NULL) avltree_init(&vfs_state_tree, vfs_state_cmpf, 0); } void vfs_state_release(struct gsh_buffdesc *key) { struct vfs_state_entry *fs_entry; fs_entry = vfs_state_lookup(key); if (!fs_entry) return; avltree_remove(&fs_entry->fs_node, &vfs_state_tree); gsh_free(fs_entry); } struct state_hdl *vfs_state_locate(struct fsal_obj_handle *obj) { struct vfs_state_entry *fs_entry; struct avltree_node *node; struct gsh_buffdesc key; obj->obj_ops->handle_to_key(obj, &key); fs_entry = vfs_state_lookup(&key); if (fs_entry) { fs_entry->ostate.file.obj = obj; return &fs_entry->ostate; } fs_entry = gsh_calloc(sizeof(struct vfs_state_entry), 1); fs_entry->fs_key = key; node = avltree_insert(&fs_entry->fs_node, &vfs_state_tree); if (unlikely(node)) { /* Race won */ gsh_free(fs_entry); fs_entry = avltree_container_of(node, struct vfs_state_entry, fs_node); } else { state_hdl_init(&fs_entry->ostate, obj->type, obj); } /* Always update with current handle pointer */ fs_entry->ostate.file.obj = obj; return &fs_entry->ostate; } nfs-ganesha-6.5/src/FSAL/FSAL_VFS/subfsal.h000066400000000000000000000036061473756622300201710ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) CohortFS LLC, 2014 * Author: Daniel Gryniewicz dang@cohortfs.com * * * This program 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 3 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 * 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 * * ------------- */ /* subfsal.h * VFS Sub-FSAL API */ #ifndef SUBFSAL_H #define SUBFSAL_H #include "config_parsing.h" /* Export parameters */ extern struct config_block *vfs_sub_export_param; /** Routines for sub-FSALS */ void vfs_sub_fini(struct vfs_fsal_export *myself); void vfs_sub_init_export_ops(struct vfs_fsal_export *myself, const char *export_path); int vfs_sub_init_export(struct vfs_fsal_export *myself); /** * @brief Allocate the SubFSAL object handle * * Allocate the SubFSAL object handle. It must be large enough to hold a * vfs_file_handle_t after the end of the normal handle, and the @a handle field * of the vfs_fsal_obj_handle must point to the correct location for the * vfs_file_handle_t. * * @return VFS object handle on success, NULL on failure */ struct vfs_fsal_obj_handle *vfs_sub_alloc_handle(void); int vfs_sub_init_handle(struct vfs_fsal_export *myself, struct vfs_fsal_obj_handle *hdl, const char *path); #endif /* SUBFSAL_H */ nfs-ganesha-6.5/src/FSAL/FSAL_VFS/subfsal_helpers.c000066400000000000000000000100471473756622300217030ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2017 VMware, Inc. * Copyright 2018 Red Hat, Inc. * Author: Sriram Patil sriramp@vmware.com * * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include "fsal_api.h" #include "vfs_methods.h" #include "fsal_types.h" #include "FSAL/fsal_commonlib.h" #include "fsal_convert.h" #include "nfs_core.h" #include "nfs_proto_tools.h" fsal_status_t vfs_get_fs_locations(struct vfs_fsal_obj_handle *hdl, int fd, struct fsal_attrlist *attrs_out) { char *xattr_content = NULL; size_t attrsize = 0; char proclnk[MAXPATHLEN]; char readlink_buf[MAXPATHLEN]; char *spath; ssize_t r; fsal_status_t st = { ERR_FSAL_NO_ERROR, 0 }; int local_fd = fd; /* the real path of the referral directory is needed. * it gets stored in attrs_out->fs_locations->path */ if (fd < 0) { local_fd = vfs_fsal_open(hdl, O_DIRECTORY, &st.major); if (local_fd < 0) { st.minor = -local_fd; return st; } } (void)snprintf(proclnk, sizeof(proclnk), "/proc/self/fd/%d", local_fd); r = readlink(proclnk, readlink_buf, sizeof(readlink_buf) - 1); if (r < 0) { st = posix2fsal_status(errno); LogEvent(COMPONENT_FSAL, "failed to readlink"); goto out; } readlink_buf[r] = '\0'; LogDebug(COMPONENT_FSAL, "fd -> path: %d -> %s", local_fd, readlink_buf); // Release old fs locations if any nfs4_fs_locations_release(attrs_out->fs_locations); spath = readlink_buf; /* If Path and Pseudo path are not equal replace path with * pseudo path. */ if (strcmp(CTX_FULLPATH(op_ctx), CTX_PSEUDOPATH(op_ctx)) != 0) { size_t pseudo_length = strlen(CTX_PSEUDOPATH(op_ctx)); size_t fullpath_length = strlen(CTX_FULLPATH(op_ctx)); size_t dirpath_len = r - fullpath_length; size_t total_length = pseudo_length + dirpath_len; char *dirpath = spath + fullpath_length; if (total_length >= sizeof(proclnk)) { st = posix2fsal_status(EINVAL); LogCrit(COMPONENT_FSAL, "Fixed up referral path %s%s too long", CTX_PSEUDOPATH(op_ctx), dirpath); goto out; } memcpy(proclnk, CTX_PSEUDOPATH(op_ctx), pseudo_length); memcpy(proclnk + pseudo_length, dirpath, dirpath_len + 1); spath = proclnk; } /* referral configuration is in a xattr "user.fs_location" * on the directory in the form * server:/path/to/referred/directory. * It gets storeded in attrs_out->fs_locations->locations */ xattr_content = gsh_calloc(XATTR_BUFFERSIZE, sizeof(char)); st = vfs_getextattr_value(hdl, local_fd, "user.fs_location", xattr_content, XATTR_BUFFERSIZE, &attrsize); if (!FSAL_IS_ERROR(st)) { char *path = xattr_content; char *server = strsep(&path, ":"); LogDebug(COMPONENT_FSAL, "user.fs_location: %s", xattr_content); if (!path) { attrs_out->fs_locations = NULL; } else { attrs_out->fs_locations = nfs4_fs_locations_new(spath, path, 1); attrs_out->fs_locations->nservers = 1; utf8string_dup(&attrs_out->fs_locations->server[0], server, path - server - 1); FSAL_SET_MASK(attrs_out->valid_mask, ATTR4_FS_LOCATIONS); } } out: gsh_free(xattr_content); // Close the local_fd only if no fd was passed into the function and we // opened the file in this function explicitly. if (fd < 0 && local_fd > 0) { close(local_fd); } return st; } nfs-ganesha-6.5/src/FSAL/FSAL_VFS/vfs/000077500000000000000000000000001473756622300171525ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_VFS/vfs/CMakeLists.txt000066400000000000000000000076421473756622300217230ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- add_definitions( -D__USE_GNU ) SET(fsalvfs_LIB_SRCS_common ../export.c ../handle.c ../handle_syscalls.c ../file.c ../xattrs.c ../vfs_methods.h ../state.c ../subfsal_helpers.c subfsal_vfs.c attrs.c ) set(fsalvfs_OBJS $ ) if(ENABLE_VFS_POSIX_ACL) set(fsalvfs_LIB_SRCS_common ${fsalvfs_LIB_SRCS_common} ../../posix_acls.c ) set(fsalvfs_OBJS ${fsalvfs_OBJS} $ ) endif(ENABLE_VFS_POSIX_ACL) if(USE_FSAL_VFS) set(FSAL_LUSTRE_VFS_NAME "VFS") configure_file(main-c.in.cmake main.c) set(fsalvfs_LIB_SRCS ${fsalvfs_LIB_SRCS_common} ../empty_check_hsm.c main.c ) add_library(fsalvfs MODULE ${fsalvfs_LIB_SRCS} ${fsalvfs_OBJS}) add_sanitizers(fsalvfs) set(fsalvfs_TGT_LINK_LIB ${SYSTEM_LIBRARIES} ) target_link_libraries(fsalvfs ganesha_nfsd ${fsalvfs_TGT_LINK_LIB} ${LDFLAG_DISALLOW_UNDEF}) set_target_properties(fsalvfs PROPERTIES VERSION 4.2.0 SOVERSION 4) install(TARGETS fsalvfs COMPONENT fsal DESTINATION ${FSAL_DESTINATION} ) endif(USE_FSAL_VFS) #FSAL_LUSTRE OVER FSAL_VFS if(USE_FSAL_LUSTRE) if(USE_LLAPI) #TRUE FSAL_LUSTRE set(FSAL_LUSTRE_VFS_NAME "LUSTRE") configure_file(main-c.in.cmake lustre_main.c) set(fsallustre_LIB_SRCS ${fsalvfs_LIB_SRCS_common} lustre_main.c llapi_check_hsm.c ) add_library(fsallustre MODULE ${fsallustre_LIB_SRCS} ${fsalvfs_OBJS}) add_sanitizers(fsallustre) target_link_libraries(fsallustre ganesha_nfsd ${fsalvfs_TGT_LINK_LIB} lustreapi ${LDFLAG_DISALLOW_UNDEF} ) set_target_properties(fsallustre PROPERTIES VERSION 4.2.0 SOVERSION 4 ) install(TARGETS fsallustre COMPONENT fsal DESTINATION ${FSAL_DESTINATION} ) else(USE_LLAPI) #FSAL_DUMMYLUSTRE #Even if we lack a good version of lustreapi, we continue to build and link # a dummy FSAL_LUSTRE to test the build path. set(FSAL_LUSTRE_VFS_NAME "DUMMYLUSTRE") configure_file(main-c.in.cmake dummy_lustre_main.c) set(fsallustre_LIB_SRCS ${fsalvfs_LIB_SRCS_common} dummy_lustre_main.c llapi_check_hsm.c ) add_library(fsaldummylustre MODULE ${fsallustre_LIB_SRCS} ${fsalvfs_OBJS}) add_sanitizers(fsaldummylustre) target_link_libraries(fsaldummylustre ganesha_nfsd ${fsalvfs_TGT_LINK_LIB} ${LDFLAG_DISALLOW_UNDEF} ) set_target_properties(fsaldummylustre PROPERTIES VERSION 4.2.0 SOVERSION 4 ) install(TARGETS fsaldummylustre COMPONENT fsal DESTINATION ${FSAL_DESTINATION} ) endif(USE_LLAPI) endif(USE_FSAL_LUSTRE) ########### install files ############### nfs-ganesha-6.5/src/FSAL/FSAL_VFS/vfs/attrs.c000066400000000000000000000265241473756622300204640ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) CohortFS LLC, 2015 * Author: Daniel Gryniewicz dang@cohortfs.com * * * This program 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 3 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 * 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 * * ------------- */ /* attrs.c * VFS attribute caching handle object */ #include "config.h" #include "avltree.h" #include "fsal.h" #include "fsal_convert.h" #include "FSAL/access_check.h" #include "../vfs_methods.h" #include "attrs.h" #include "nfs4_acls.h" #ifdef ENABLE_VFS_POSIX_ACL #include "os/acl.h" #include "../../posix_acls.h" #endif /* ENABLE_VFS_POSIX_ACL */ void vfs_sub_getattrs_common(struct vfs_fsal_obj_handle *vfs_hdl, int fd, attrmask_t request_mask, struct fsal_attrlist *attrib) { fsal_status_t fsal_st = { ERR_FSAL_NO_ERROR, 0 }; if (FSAL_TEST_MASK(request_mask, ATTR4_FS_LOCATIONS) && vfs_hdl->obj_handle.obj_ops->is_referral( &vfs_hdl->obj_handle, attrib, false /*cache_attrs*/)) { fsal_st = vfs_get_fs_locations(vfs_hdl, fd, attrib); if (FSAL_IS_ERROR(fsal_st)) { /* No error should be returned here, any major error * should have been caught before this */ LogDebug( COMPONENT_FSAL, "Could not get the fs locations for vfs handle: %p", vfs_hdl); } } } void vfs_sub_getattrs_release(struct fsal_attrlist *attrib) { if (attrib->acl != NULL) { /* We should never be passed attributes that have an * ACL attached, but just in case some future code * path changes that assumption, let's release the * old ACL properly. */ nfs4_acl_release_entry(attrib->acl); attrib->acl = NULL; } } #if defined(ENABLE_VFS_DEBUG_ACL) struct vfs_acl_entry { struct gsh_buffdesc fa_key; /**< Key for tree */ struct avltree_node fa_node; /**< AVL tree node */ fsal_acl_data_t fa_acl; /**< Actual ACLs */ }; static struct avltree vfs_acl_tree = { 0 }; /** * @brief VFS acl comparator for AVL tree walk * */ static int vfs_acl_cmpf(const struct avltree_node *lhs, const struct avltree_node *rhs) { struct vfs_acl_entry *lk, *rk; lk = avltree_container_of(lhs, struct vfs_acl_entry, fa_node); rk = avltree_container_of(rhs, struct vfs_acl_entry, fa_node); if (lk->fa_key.len != rk->fa_key.len) return (lk->fa_key.len < rk->fa_key.len) ? -1 : 1; return memcmp(lk->fa_key.addr, rk->fa_key.addr, lk->fa_key.len); } static struct vfs_acl_entry *vfs_acl_lookup(struct gsh_buffdesc *key) { struct vfs_acl_entry key_entry; struct avltree_node *node; memset(&key_entry, 0, sizeof(key_entry)); key_entry.fa_key = *key; node = avltree_lookup(&key_entry.fa_node, &vfs_acl_tree); if (!node) return NULL; return avltree_container_of(node, struct vfs_acl_entry, fa_node); } static struct vfs_acl_entry *vfs_acl_locate(struct fsal_obj_handle *obj) { struct vfs_acl_entry *fa_entry; struct avltree_node *node; struct gsh_buffdesc key; obj->obj_ops->handle_to_key(obj, &key); fa_entry = vfs_acl_lookup(&key); if (fa_entry) { LogDebug(COMPONENT_FSAL, "found"); return fa_entry; } LogDebug(COMPONENT_FSAL, "create"); fa_entry = gsh_calloc(1, sizeof(struct vfs_acl_entry)); fa_entry->fa_key = key; node = avltree_insert(&fa_entry->fa_node, &vfs_acl_tree); if (unlikely(node)) { /* Race won */ gsh_free(fa_entry); fa_entry = avltree_container_of(node, struct vfs_acl_entry, fa_node); } else { fa_entry->fa_acl.aces = (fsal_ace_t *)nfs4_ace_alloc(0); } return fa_entry; } void vfs_acl_init(void) { if (vfs_acl_tree.cmp_fn == NULL) avltree_init(&vfs_acl_tree, vfs_acl_cmpf, 0); } void vfs_acl_release(struct gsh_buffdesc *key) { struct vfs_acl_entry *fa_entry; fa_entry = vfs_acl_lookup(key); if (!fa_entry) return; avltree_remove(&fa_entry->fa_node, &vfs_acl_tree); gsh_free(fa_entry); } fsal_status_t vfs_sub_getattrs(struct vfs_fsal_obj_handle *vfs_hdl, int fd, attrmask_t request_mask, struct fsal_attrlist *attrib) { fsal_acl_status_t status; struct vfs_acl_entry *fa; fsal_acl_data_t acldata; fsal_acl_t *acl; vfs_sub_getattrs_common(vfs_hdl, fd, request_mask, attrib); LogDebug(COMPONENT_FSAL, "Enter"); vfs_sub_getattrs_release(attrib); fa = vfs_acl_locate(&vfs_hdl->obj_handle); if (!fa->fa_acl.naces) { /* No ACLs yet */ FSAL_UNSET_MASK(attrib->valid_mask, ATTR_ACL); return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_print_acl(COMPONENT_FSAL, NIV_FULL_DEBUG, (fsal_acl_t *)&fa->fa_acl); acldata.naces = fa->fa_acl.naces; acldata.aces = (fsal_ace_t *)nfs4_ace_alloc(acldata.naces); memcpy(acldata.aces, fa->fa_acl.aces, acldata.naces * sizeof(fsal_ace_t)); acl = nfs4_acl_new_entry(&acldata, &status); if (!acl) return fsalstat(ERR_FSAL_FAULT, status); fsal_print_acl(COMPONENT_FSAL, NIV_FULL_DEBUG, acl); attrib->acl = acl; FSAL_SET_MASK(attrib->valid_mask, ATTR_ACL); return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t vfs_sub_setattrs(struct vfs_fsal_obj_handle *vfs_hdl, int fd, attrmask_t request_mask, struct fsal_attrlist *attrib) { struct vfs_acl_entry *fa; if (!FSAL_TEST_MASK(request_mask, ATTR_ACL) || !attrib || !attrib->acl) return fsalstat(ERR_FSAL_NO_ERROR, 0); LogDebug(COMPONENT_FSAL, "Enter"); fsal_print_acl(COMPONENT_FSAL, NIV_FULL_DEBUG, attrib->acl); fa = vfs_acl_locate(&vfs_hdl->obj_handle); nfs4_ace_free(fa->fa_acl.aces); fa->fa_acl.naces = attrib->acl->naces; fa->fa_acl.aces = (fsal_ace_t *)nfs4_ace_alloc(fa->fa_acl.naces); memcpy(fa->fa_acl.aces, attrib->acl->aces, fa->fa_acl.naces * sizeof(fsal_ace_t)); fsal_print_acl(COMPONENT_FSAL, NIV_FULL_DEBUG, (fsal_acl_t *)&fa->fa_acl); if (attrib->valid_mask & ATTR_MODE) vfs_hdl->mode = attrib->mode; FSAL_SET_MASK(attrib->valid_mask, ATTR_ACL); return fsalstat(ERR_FSAL_NO_ERROR, 0); } #elif defined(ENABLE_VFS_POSIX_ACL) fsal_status_t vfs_sub_getattrs(struct vfs_fsal_obj_handle *vfs_hdl, int fd, attrmask_t request_mask, struct fsal_attrlist *attrib) { struct fsal_obj_handle *obj_pub = &vfs_hdl->obj_handle; bool is_dir = obj_pub->type == DIRECTORY; acl_t e_acl = NULL, i_acl = NULL; fsal_acl_data_t acldata; fsal_ace_t *pace = NULL; fsal_acl_status_t aclstatus; int e_count = 0, i_count = 0, new_count = 0, new_i_count = 0; fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0); /* * open(2) for these is done with O_PATH, so acl_get_fd(3) * will fail when it calls fgetxattr(2) */ switch (obj_pub->type) { case SYMBOLIC_LINK: case SOCKET_FILE: case CHARACTER_FILE: case BLOCK_FILE: return status; default: break; } vfs_sub_getattrs_common(vfs_hdl, fd, request_mask, attrib); vfs_sub_getattrs_release(attrib); if (fd == -1) { LogDebug(COMPONENT_FSAL, "skipping acl check when called for referrals"); goto out; } e_acl = acl_get_fd(fd); if (e_acl == (acl_t)NULL) { status = fsalstat(posix2fsal_error(errno), errno); goto out; } /* * Adapted from FSAL_CEPH/internal.c:ceph_get_acl() and * FSAL_GLUSTER/gluster_internal.c:glusterfs_get_acl() */ e_count = ace_count(e_acl); if (is_dir) { i_acl = acl_get_fd_np(fd, ACL_TYPE_DEFAULT); if (i_acl == (acl_t)NULL) { status = fsalstat(posix2fsal_error(errno), errno); goto out; } i_count = ace_count(i_acl); } acldata.naces = 2 * (e_count + i_count); LogDebug(COMPONENT_FSAL, "No of aces present in fsal_acl_t = %d", acldata.naces); if (acldata.naces == 0) { status = fsalstat(ERR_FSAL_NO_ERROR, 0); goto out; } acldata.aces = (fsal_ace_t *)nfs4_ace_alloc(acldata.naces); pace = acldata.aces; if (e_count > 0) { new_count = posix_acl_2_fsal_acl(e_acl, is_dir, false, ACL_FOR_V4, &pace); } else { LogDebug(COMPONENT_FSAL, "effective acl is not set for this object"); } if (i_count > 0) { new_i_count = posix_acl_2_fsal_acl(i_acl, true, true, ACL_FOR_V4, &pace); new_count += new_i_count; } else { LogDebug(COMPONENT_FSAL, "Inherit acl is not set for this directory"); } /* Reallocating acldata into the required size */ acldata.aces = (fsal_ace_t *)gsh_realloc( acldata.aces, new_count * sizeof(fsal_ace_t)); acldata.naces = new_count; attrib->acl = nfs4_acl_new_entry(&acldata, &aclstatus); if (attrib->acl == NULL) { LogCrit(COMPONENT_FSAL, "failed to create a new acl entry"); status = fsalstat(posix2fsal_error(EFAULT), EFAULT); goto out; } status = fsalstat(ERR_FSAL_NO_ERROR, 0); FSAL_SET_MASK(attrib->valid_mask, ATTR_ACL); out: if (e_acl) acl_free((void *)e_acl); if (i_acl) acl_free((void *)i_acl); return status; } fsal_status_t vfs_sub_setattrs(struct vfs_fsal_obj_handle *vfs_hdl, int fd, attrmask_t request_mask, struct fsal_attrlist *attrib) { struct fsal_obj_handle *obj_pub = &vfs_hdl->obj_handle; bool is_dir = obj_pub->type == DIRECTORY; acl_t acl = NULL; int ret; fsal_status_t status = fsalstat(ERR_FSAL_NO_ERROR, 0); if (!FSAL_TEST_MASK(request_mask, ATTR_ACL) || !attrib) return fsalstat(ERR_FSAL_NO_ERROR, 0); /* * Adapted from FSAL_CEPH/internal.c:ceph_set_acl() and * FSAL_GLUSTER/gluster_internal.c:glusterfs_set_acl() */ /* * This should not happen. In this case FSAL_GLUSTER does not * warn and just returns OK, as above. However, FSAL_CEPH * warns and returns an error. Adopt a sane middle-ground: * warn only. */ if (!attrib->acl) { LogWarn(COMPONENT_FSAL, "acl is empty"); status = fsalstat(ERR_FSAL_NO_ERROR, 0); goto out; } acl = fsal_acl_2_posix_acl(attrib->acl, ACL_TYPE_ACCESS); if (acl == NULL) { LogMajor(COMPONENT_FSAL, "failed to set access type posix acl"); status = fsalstat(ERR_FSAL_FAULT, 0); goto out; } ret = acl_set_fd(fd, acl); if (ret != 0) { status = fsalstat(errno, 0); LogMajor(COMPONENT_FSAL, "failed to set access type posix acl"); goto out; } acl_free(acl); acl = (acl_t)NULL; if (!is_dir) goto out; acl = fsal_acl_2_posix_acl(attrib->acl, ACL_TYPE_DEFAULT); if (acl == NULL) { LogDebug(COMPONENT_FSAL, "inherited acl is not defined for directory"); goto out; } ret = acl_set_fd_np(fd, acl, ACL_TYPE_DEFAULT); if (ret != 0) { status = fsalstat(errno, 0); LogMajor(COMPONENT_FSAL, "failed to set default type posix acl"); goto out; } status = fsalstat(ERR_FSAL_NO_ERROR, 0); out: if (acl) acl_free((void *)acl); return status; } #else /* NOT(ENABLE_VFS_DEBUG_ACL OR ENABLE_VFS_POSIX_ACL) */ fsal_status_t vfs_sub_getattrs(struct vfs_fsal_obj_handle *vfs_hdl, int fd, attrmask_t request_mask, struct fsal_attrlist *attrib) { vfs_sub_getattrs_common(vfs_hdl, fd, request_mask, attrib); return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t vfs_sub_setattrs(struct vfs_fsal_obj_handle *vfs_hdl, int fd, attrmask_t request_mask, struct fsal_attrlist *attrib) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } #endif /* NOT(ENABLE_VFS_DEBUG_ACL OR ENABLE_VFS_POSIX_ACL) */ nfs-ganesha-6.5/src/FSAL/FSAL_VFS/vfs/attrs.h000066400000000000000000000026321473756622300204630ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) CohortFS LLC, 2015 * Author: Daniel Gryniewicz dang@cohortfs.com * * * This program 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 3 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 * 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 * * ------------- */ /* attrs.h * VFS debug attribute handling */ #ifndef __VFS_ATTRS_H #define __VFS_ATTRS_H #include "../vfs_methods.h" void vfs_acl_init(void); fsal_status_t vfs_sub_getattrs(struct vfs_fsal_obj_handle *vfs_hdl, int fd, attrmask_t request_mask, struct fsal_attrlist *attrs); fsal_status_t vfs_sub_setattrs(struct vfs_fsal_obj_handle *vfs_hdl, int fd, attrmask_t request_mask, struct fsal_attrlist *attrib_set); #endif /* __VFS_ATTRS_H */ nfs-ganesha-6.5/src/FSAL/FSAL_VFS/vfs/llapi_check_hsm.c000066400000000000000000000063731473756622300224340ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2017-2018) * contributeur : Patrice LUCAS patrice.lucas@cea.fr * * This program 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 3 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 * 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 * * ------------- */ #include "config.h" #include "../vfs_methods.h" #include "fsal_convert.h" #ifdef USE_LLAPI #include #endif /** * @brief Call "lustre_hsm restore" if file is released. * * @param[in] fd System file descriptor on open file to check * * @return ERR_FSAL_DELAY if we call a restore, else ERR_FSAL_NO_ERROR. */ fsal_status_t check_hsm_by_fd(int fd) { struct vfs_fsal_export *vfs_export; vfs_export = container_of(op_ctx->fsal_export, struct vfs_fsal_export, export); /* check async_hsm_restore option */ if (!vfs_export->async_hsm_restore) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } #ifndef USE_LLAPI return fsalstat(ERR_FSAL_NO_ERROR, 0); #else struct hsm_user_state hus; int rc = 0; rc = llapi_hsm_state_get_fd(fd, &hus); if (rc) { LogEvent(COMPONENT_FSAL, "Error retrieving lustre_hsm status : %s", strerror(-rc)); return fsalstat(posix2fsal_error(-rc), rc); } if (!(hus.hus_states & HS_RELEASED)) return fsalstat(ERR_FSAL_NO_ERROR, 0); lustre_fid fid; /* restore file in LUSTRE */ struct hsm_user_request *hur; LogInfo(COMPONENT_FSAL, "File is offline: triggering lustre_hsm restore"); /* allocating request : one item, no extra data */ hur = llapi_hsm_user_request_alloc(1, 0); if (hur == NULL) { LogCrit(COMPONENT_FSAL, "Error allocating hsm_user_request"); return fsalstat(ERR_FSAL_NOMEM, ENOMEM); } /* filling request */ hur->hur_request.hr_action = HUA_RESTORE; /*restore action*/ hur->hur_request.hr_archive_id = 0; /*only used for archiving*/ hur->hur_request.hr_flags = 0; /*no flags*/ hur->hur_request.hr_itemcount = 1; /*only one file*/ hur->hur_request.hr_data_len = 0; /*no extra data*/ /* getting fid */ rc = llapi_fd2fid(fd, &fid); if (rc) { LogEvent(COMPONENT_FSAL, "Error retrieving fid from fd : %s", strerror(-rc)); return fsalstat(posix2fsal_error(-rc), rc); } /* filling item */ hur->hur_user_item[0].hui_fid = fid; hur->hur_user_item[0].hui_extent.offset = 0; /*file from start*/ hur->hur_user_item[0].hui_extent.length = -1; /*whole file*/ rc = llapi_hsm_request(op_ctx->fsal_export->root_fs->path, hur); if (rc) { LogEvent(COMPONENT_FSAL, "Error requesting a restore : %s", strerror(-rc)); return fsalstat(posix2fsal_error(-rc), rc); } /* return ERR_FSAL_DELAY */ return fsalstat(ERR_FSAL_DELAY, rc); #endif /*USE_LLAPI*/ } nfs-ganesha-6.5/src/FSAL/FSAL_VFS/vfs/main-c.in.cmake000066400000000000000000000151501473756622300217270ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * Patrice LUCAS patrice.lucas@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /* main.c * Module core functions */ #include "config.h" #include /* used for 'dirname' */ #include #include #include #include #include "gsh_list.h" #include "fsal.h" #include "FSAL/fsal_init.h" #include "fsal_handle_syscalls.h" #include "vfs_methods.h" /* VFS FSAL module private storage */ /* defined the set of attributes supported with POSIX */ #ifndef ENABLE_VFS_ACL #define VFS_SUPPORTED_ATTRIBUTES ((const attrmask_t) (ATTRS_POSIX | \ ATTR4_FS_LOCATIONS)) #else #define VFS_SUPPORTED_ATTRIBUTES ((const attrmask_t) (ATTRS_POSIX | ATTR_ACL | \ ATTR4_FS_LOCATIONS)) #endif const char myname[] = "@FSAL_LUSTRE_VFS_NAME@"; /* my module private storage */ static struct vfs_fsal_module VFS = { .module = { .fs_info = { .maxfilesize = INT64_MAX, .maxlink = _POSIX_LINK_MAX, .maxnamelen = 1024, .maxpathlen = 1024, .no_trunc = true, .chown_restricted = true, .case_insensitive = false, .case_preserving = true, .lock_support = false, .lock_support_async_block = false, .named_attr = true, .unique_handles = true, .acl_support = FSAL_ACLSUPPORT_ALLOW, .homogenous = true, .supported_attrs = VFS_SUPPORTED_ATTRIBUTES, .maxread = FSAL_MAXIOSIZE, .maxwrite = FSAL_MAXIOSIZE, .link_supports_permission_checks = false, .expire_time_parent = -1, } }, .only_one_user = false }; static struct config_item vfs_params[] = { CONF_ITEM_BOOL("link_support", true, vfs_fsal_module, module.fs_info.link_support), CONF_ITEM_BOOL("symlink_support", true, vfs_fsal_module, module.fs_info.symlink_support), CONF_ITEM_BOOL("cansettime", true, vfs_fsal_module, module.fs_info.cansettime), CONF_ITEM_UI64("maxread", 512, FSAL_MAXIOSIZE, FSAL_MAXIOSIZE, vfs_fsal_module, module.fs_info.maxread), CONF_ITEM_UI64("maxwrite", 512, FSAL_MAXIOSIZE, FSAL_MAXIOSIZE, vfs_fsal_module, module.fs_info.maxwrite), CONF_ITEM_MODE("umask", 0, vfs_fsal_module, module.fs_info.umask), CONF_ITEM_BOOL("auth_xdev_export", false, vfs_fsal_module, module.fs_info.auth_exportpath_xdev), CONF_ITEM_BOOL("only_one_user", false, vfs_fsal_module, only_one_user), CONFIG_EOL }; struct config_block vfs_param = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.vfs", .blk_desc.name = "VFS", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = vfs_params, .blk_desc.u.blk.commit = noop_conf_commit }; /* Module methods */ /* init_config * must be called with a reference taken (via lookup_fsal) */ static fsal_status_t init_config(struct fsal_module *vfs_fsal_module, config_file_t config_struct, struct config_error_type *err_type) { struct vfs_fsal_module *vfs_module = container_of(vfs_fsal_module, struct vfs_fsal_module, module); int prev_errors = err_type->errors; #ifdef F_OFD_GETLK int fd, rc; struct flock lock; char *temp_name; #endif #ifdef F_OFD_GETLK /* If on a system that might support OFD locks, verify them. * Only if they exist will we declare lock support. */ LogInfo(COMPONENT_FSAL, "FSAL_VFS testing OFD Locks"); temp_name = gsh_strdup("/tmp/ganesha.nfsd.locktestXXXXXX"); fd = mkstemp(temp_name); if (fd >= 0) { lock.l_whence = SEEK_SET; lock.l_type = F_RDLCK; lock.l_start = 0; lock.l_len = 0; lock.l_pid = 0; rc = fcntl(fd, F_OFD_GETLK, &lock); if (rc == 0) vfs_module->module.fs_info.lock_support = true; else LogInfo(COMPONENT_FSAL, "Could not use OFD locks"); close(fd); unlink(temp_name); } else { LogCrit(COMPONENT_FSAL, "Could not create file %s to test OFD locks", temp_name); } gsh_free(temp_name); #endif if (vfs_module->module.fs_info.lock_support) LogInfo(COMPONENT_FSAL, "FSAL_VFS enabling OFD Locks"); else LogInfo(COMPONENT_FSAL, "FSAL_VFS disabling lock support"); LogFullDebug(COMPONENT_FSAL, "Supported attributes default = 0x%" PRIx64, vfs_module->module.fs_info.supported_attrs); (void) load_config_from_parse(config_struct, &vfs_param, vfs_module, true, err_type); /* Check for actual errors in vfs_param parsing. * err_type could have errors from previous block * parsing also. */ if ((err_type->errors > prev_errors) && !config_error_is_harmless(err_type)) return fsalstat(ERR_FSAL_INVAL, 0); display_fsinfo(&vfs_module->module); LogFullDebug(COMPONENT_FSAL, "Supported attributes constant = 0x%" PRIx64, VFS_SUPPORTED_ATTRIBUTES); LogDebug(COMPONENT_FSAL, "FSAL INIT: Supported attributes mask = 0x%" PRIx64, vfs_module->module.fs_info.supported_attrs); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* Module initialization. * Called by dlopen() to register the module * keep a private pointer to me in myself */ /* linkage to the exports and handle ops initializers */ MODULE_INIT void vfs_init(void) { int retval; struct fsal_module *myself = &VFS.module; retval = register_fsal(myself, myname, FSAL_MAJOR_VERSION, FSAL_MINOR_VERSION, FSAL_ID_VFS); if (retval != 0) { fprintf(stderr, "VFS module failed to register"); return; } myself->m_ops.create_export = vfs_create_export; myself->m_ops.update_export = vfs_update_export; myself->m_ops.init_config = init_config; /* Initialize the fsal_obj_handle ops for FSAL VFS/LUSTRE */ vfs_handle_ops_init(&VFS.handle_ops); } MODULE_FINI void vfs_unload(void) { int retval; retval = unregister_fsal(&VFS.module); if (retval != 0) { fprintf(stderr, "VFS module failed to unregister"); return; } } nfs-ganesha-6.5/src/FSAL/FSAL_VFS/vfs/subfsal_vfs.c000066400000000000000000000057131473756622300216410ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) CohortFS LLC, 2014 * Author: Daniel Gryniewicz dang@cohortfs.com * * * This program 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 3 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 * 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 * * ------------- */ /* export_vfs.c * VFS Sub-FSAL export object */ #include "config.h" #include "fsal_types.h" #include "fsal_api.h" #include "../vfs_methods.h" #include "../subfsal.h" #include "attrs.h" /* Export */ static struct config_item_list fsid_types[] = { CONFIG_LIST_TOK("None", FSID_NO_TYPE), CONFIG_LIST_TOK("One64", FSID_ONE_UINT64), CONFIG_LIST_TOK("Major64", FSID_MAJOR_64), CONFIG_LIST_TOK("Two64", FSID_TWO_UINT64), CONFIG_LIST_TOK("uuid", FSID_TWO_UINT64), CONFIG_LIST_TOK("Two32", FSID_TWO_UINT32), CONFIG_LIST_TOK("Dev", FSID_DEVICE), CONFIG_LIST_TOK("Device", FSID_DEVICE), CONFIG_LIST_EOL }; static struct config_item export_params[] = { CONF_ITEM_NOOP("name"), CONF_ITEM_TOKEN("fsid_type", FSID_NO_TYPE, fsid_types, vfs_fsal_export, fsid_type), CONF_ITEM_BOOL("async_hsm_restore", true, vfs_fsal_export, async_hsm_restore), CONFIG_EOL }; static struct config_block export_param_block = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.vfs-export%d", .blk_desc.name = "FSAL", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = export_params, .blk_desc.u.blk.commit = noop_conf_commit }; struct config_block *vfs_sub_export_param = &export_param_block; /* Handle syscalls */ void vfs_sub_fini(struct vfs_fsal_export *myself) { } void vfs_sub_init_export_ops(struct vfs_fsal_export *myself, const char *export_path) { } int vfs_sub_init_export(struct vfs_fsal_export *myself) { #ifdef ENABLE_VFS_DEBUG_ACL vfs_acl_init(); #endif /* ENABLE_VFS_DEBUG_ACL */ return 0; } struct vfs_fsal_obj_handle *vfs_sub_alloc_handle(void) { struct vfs_fsal_obj_handle *hdl; hdl = gsh_calloc(1, (sizeof(struct vfs_fsal_obj_handle) + sizeof(vfs_file_handle_t))); hdl->handle = (vfs_file_handle_t *)&hdl[1]; return hdl; } struct vfs_subfsal_obj_ops vfs_obj_subops = { vfs_sub_getattrs, vfs_sub_setattrs, }; int vfs_sub_init_handle(struct vfs_fsal_export *myself, struct vfs_fsal_obj_handle *hdl, const char *path) { hdl->sub_ops = &vfs_obj_subops; return 0; } nfs-ganesha-6.5/src/FSAL/FSAL_VFS/vfs_methods.h000066400000000000000000000277671473756622300210710ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) International Business Machines Corp., 2010 * Author(s): Aneesh Kumar K.V * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * @file FSAL/FSAL_VFS/vfs_methods.h * @brief System calls for the FreeBSD handle calls */ /* VFS methods for handles */ #ifndef VFS_METHODS_H #define VFS_METHODS_H #include "fsal_handle_syscalls.h" #include "fsal_api.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_localfs.h" #include "FSAL/access_check.h" struct vfs_fsal_obj_handle; struct vfs_fsal_export; /* * VFS internal module */ struct vfs_fsal_module { struct fsal_module module; struct fsal_obj_ops handle_ops; bool only_one_user; }; /* * VFS internal export */ struct vfs_fsal_export { struct fsal_export export; int fsid_type; bool async_hsm_restore; }; #define EXPORT_VFS_FROM_FSAL(fsal) \ container_of((fsal), struct vfs_fsal_export, export) /* private helpers from export */ void vfs_handle_ops_init(struct fsal_obj_ops *ops); static inline int root_fd(struct fsal_filesystem *fs) { int fd = (long)fs->private_data; return fd; } static inline int vfs_get_root_fd(struct fsal_export *exp_hdl) { return root_fd(exp_hdl->root_fs); } /* Internal VFS method linkage to export object */ int vfs_claim_filesystem(struct fsal_filesystem *fs, struct fsal_export *exp, void **private_data); void vfs_unclaim_filesystem(struct fsal_filesystem *fs); fsal_status_t vfs_create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops); fsal_status_t vfs_update_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, struct fsal_export *original, struct fsal_module *updated_super); /* method proto linkage to handle.c for export */ fsal_status_t vfs_lookup_path(struct fsal_export *exp_hdl, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); fsal_status_t vfs_create_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *hdl_desc, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); struct vfs_subfsal_obj_ops { /** * @brief Gte sub-fsal attributes from an object * * @param[in] vfs_hdl The VFS object to modify * @param[in] fd Open filehandle on object * * @return FSAL status. */ fsal_status_t (*getattrs)(struct vfs_fsal_obj_handle *vfs_hdl, int fd, attrmask_t request_mask, struct fsal_attrlist *attrs); /** * @brief Set sub-fsal attributes on an object * * @param[in] vfs_hdl The VFS object to modify * @param[in] fd Open filehandle on object * @param[in] attrib_set Attributes to set * * @return FSAL status. */ fsal_status_t (*setattrs)(struct vfs_fsal_obj_handle *vfs_hdl, int fd, attrmask_t request_mask, struct fsal_attrlist *attrib_set); }; struct vfs_fd { /** open and share mode plus fd management */ struct fsal_fd fsal_fd; /** The kernel file descriptor. */ int fd; }; struct vfs_state_fd { /** state MUST be first to use default free_state */ struct state_t state; struct vfs_fd vfs_fd; }; /** * @brief VFS internal object handle * * The handle is a pointer because * a) the last element of file_handle is a char[] meaning variable len... * b) we cannot depend on it *always* being last or being the only * variable sized struct here... a pointer is safer. * wrt locks, should this be a lock counter?? * AF_UNIX sockets are strange ducks. I personally cannot see why they * are here except for the ability of a client to see such an animal with * an 'ls' or get rid of one with an 'rm'. You can't open them in the * usual file way so open_by_handle_at leads to a deadend. To work around * this, we save the args that were used to mknod or lookup the socket. */ struct vfs_fsal_obj_handle { struct fsal_obj_handle obj_handle; fsal_dev_t dev; vfs_file_handle_t *handle; #ifdef ENABLE_VFS_DEBUG_ACL uint32_t mode; /*< POSIX access mode */ #endif struct vfs_subfsal_obj_ops *sub_ops; /*< Optional subfsal ops */ const struct fsal_up_vector *up_ops; /*< Upcall operations */ union { struct { struct fsal_share share; struct vfs_fd fd; } file; struct { unsigned char *link_content; int link_size; } symlink; struct { vfs_file_handle_t *dir; char *name; } unopenable; } u; }; #define OBJ_VFS_FROM_FSAL(fsal) \ container_of((fsal), struct vfs_fsal_obj_handle, obj_handle) /* default vex ops */ int vfs_fd_to_handle(int fd, struct fsal_filesystem *fs, vfs_file_handle_t *fh); int vfs_name_to_handle(int atfd, struct fsal_filesystem *fs, const char *name, vfs_file_handle_t *fh); int vfs_open_by_handle(struct fsal_filesystem *fs, vfs_file_handle_t *fh, int openflags, fsal_errors_t *fsal_error); int vfs_encode_dummy_handle(vfs_file_handle_t *fh, struct fsal_filesystem *fs); bool vfs_is_dummy_handle(vfs_file_handle_t *fh); fsal_status_t vfs_check_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *hdl_desc, struct fsal_filesystem **fs, vfs_file_handle_t *fh, bool *dummy); bool vfs_valid_handle(struct gsh_buffdesc *desc); int vfs_readlink(struct vfs_fsal_obj_handle *myself, fsal_errors_t *fsal_error); int vfs_extract_fsid(vfs_file_handle_t *fh, enum fsid_type *fsid_type, struct fsal_fsid__ *fsid); int vfs_get_root_handle(struct fsal_filesystem *fs, struct vfs_fsal_export *exp, int *root_fd); int vfs_re_index(struct fsal_filesystem *fs, struct vfs_fsal_export *exp); /* * VFS structure to tell subfunctions whether they should close the * returned fd or not */ struct closefd { int fd; int close_fd; }; int vfs_fsal_open(struct vfs_fsal_obj_handle *hdl, int openflags, fsal_errors_t *fsal_error); fsal_status_t vfs_close_func(struct fsal_obj_handle *obj_hdl, struct fsal_fd *fd); fsal_status_t vfs_reopen_func(struct fsal_obj_handle *obj_hdl, fsal_openflags_t openflags, struct fsal_fd *fsal_fd); struct vfs_fsal_obj_handle * alloc_handle(int dirfd, vfs_file_handle_t *fh, struct fsal_filesystem *fs, struct stat *stat, vfs_file_handle_t *dir_fh, const char *path, struct fsal_export *exp_hdl); void free_vfs_fsal_obj_handle(struct vfs_fsal_obj_handle **hdl); static inline bool vfs_unopenable_type(object_file_type_t type) { if ((type == SOCKET_FILE) || (type == CHARACTER_FILE) || (type == BLOCK_FILE)) { return true; } else { return false; } } /* State storage */ void vfs_state_init(void); void vfs_state_release(struct gsh_buffdesc *key); struct state_hdl *vfs_state_locate(struct fsal_obj_handle *obj); /* I/O management */ fsal_status_t vfs_close_my_fd(struct vfs_fd *my_fd); fsal_status_t vfs_close(struct fsal_obj_handle *obj_hdl); /* Multiple file descriptor methods */ struct state_t *vfs_alloc_state(struct fsal_export *exp_hdl, enum state_type state_type, struct state_t *related_state); fsal_status_t vfs_merge(struct fsal_obj_handle *orig_hdl, struct fsal_obj_handle *dupe_hdl); fsal_status_t vfs_open2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attrib_set, fsal_verifier_t verifier, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out); fsal_openflags_t vfs_status2(struct fsal_obj_handle *obj_hdl, struct state_t *state); fsal_status_t vfs_reopen2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags); void vfs_read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg); void vfs_write2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *caller_arg); #ifdef __USE_GNU fsal_status_t vfs_seek2(struct fsal_obj_handle *obj_hdl, struct state_t *state, struct io_info *info); #endif #ifdef FALLOC_FL_PUNCH_HOLE fsal_status_t vfs_fallocate(struct fsal_obj_handle *obj_hdl, struct state_t *state, uint64_t offset, uint64_t length, bool allocate); #endif fsal_status_t vfs_commit2(struct fsal_obj_handle *obj_hdl, off_t offset, size_t len); fsal_status_t vfs_lock_op2(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *owner, fsal_lock_op_t lock_op, fsal_lock_param_t *request_lock, fsal_lock_param_t *conflicting_lock); fsal_status_t getattr2(struct fsal_obj_handle *obj_hdl); fsal_status_t vfs_getattr2(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs); fsal_status_t vfs_setattr2(struct fsal_obj_handle *obj_hdl, bool bypass, struct state_t *state, struct fsal_attrlist *attrib_set); fsal_status_t vfs_close2(struct fsal_obj_handle *obj_hdl, struct state_t *state); /* extended attributes management */ fsal_status_t vfs_list_ext_attrs(struct fsal_obj_handle *obj_hdl, unsigned int cookie, fsal_xattrent_t *xattrs_tab, unsigned int xattrs_tabsize, unsigned int *p_nb_returned, int *end_of_list); fsal_status_t vfs_getextattr_id_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, unsigned int *pxattr_id); fsal_status_t vfs_getextattr_value(struct vfs_fsal_obj_handle *vfs_hdl, int fd, const char *xattr_name, void *buffer_addr, size_t buffer_size, size_t *p_output_size); fsal_status_t vfs_getextattr_value_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, size_t *p_output_size); fsal_status_t vfs_getextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buffer_addr, size_t buffer_size, size_t *p_output_size); fsal_status_t vfs_setextattr_value(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, int create); fsal_status_t vfs_setextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buffer_addr, size_t buffer_size); fsal_status_t vfs_remove_extattr_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id); fsal_status_t vfs_remove_extattr_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name); fsal_status_t check_hsm_by_fd(int fd); fsal_status_t vfs_get_fs_locations(struct vfs_fsal_obj_handle *hdl, int fd, struct fsal_attrlist *attrs_out); static inline bool vfs_set_credentials(const struct user_cred *creds, const struct fsal_module *fsal_module) { bool only_one_user = container_of(fsal_module, struct vfs_fsal_module, module) ->only_one_user; if (only_one_user) return fsal_set_credentials_only_one_user(creds); else { fsal_set_credentials(creds); return true; } } static inline void vfs_restore_ganesha_credentials(const struct fsal_module *fsal_module) { bool only_one_user = container_of(fsal_module, struct vfs_fsal_module, module) ->only_one_user; if (!only_one_user) fsal_restore_ganesha_credentials(); } fsal_status_t find_fd(struct fsal_fd **out_fd, struct fsal_obj_handle *obj_hdl, struct fsal_fd *tmp_fd, struct state_t *state, fsal_openflags_t openflags, bool bypass); #endif /* VFS_METHODS_H */ nfs-ganesha-6.5/src/FSAL/FSAL_VFS/xattrs.c000066400000000000000000000363031473756622300200520ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /* xattrs.c * VFS object (file|dir) handle object extended attributes */ #include "config.h" #include /* used for 'dirname' */ #include #include #include #include #include "os/xattr.h" #include "gsh_list.h" #include "fsal_api.h" #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "vfs_methods.h" #include "common_utils.h" typedef int (*xattr_getfunc_t)(struct fsal_obj_handle *, /* object handle */ void *, /* output buff */ size_t, /* output buff size */ size_t *, /* output size */ void *arg); /* optional argument */ typedef int (*xattr_setfunc_t)(struct fsal_obj_handle *, /* object handle */ void *, /* input buff */ size_t, /* input size */ int, /* creation flag */ void *arg); /* optional argument */ struct fsal_xattr_def { char xattr_name[XATTR_NAME_SIZE]; xattr_getfunc_t get_func; xattr_setfunc_t set_func; int flags; void *arg; }; /* * DEFINE GET/SET FUNCTIONS */ int print_vfshandle(struct fsal_obj_handle *obj_hdl, void *buffer_addr, size_t buffer_size, size_t *p_output_size, void *arg) { *p_output_size = snprintf(buffer_addr, buffer_size, "(not yet implemented)"); if (*p_output_size >= buffer_size) return posix2fsal_error(ERANGE); return 0; } /* print_fid */ /* DEFINE HERE YOUR ATTRIBUTES LIST */ static struct fsal_xattr_def xattr_list[] = { { "vfshandle", print_vfshandle, NULL, XATTR_FOR_ALL | XATTR_RO, NULL }, }; #define XATTR_COUNT 1 #define XATTR_SYSTEM (INT_MAX - 1) /* we assume that this number is < 254 */ #if (XATTR_COUNT > 254) #error "ERROR: xattr count > 254" #endif /* test if an object has a given attribute */ static int do_match_type(int xattr_flag, object_file_type_t obj_type) { switch (obj_type) { case REGULAR_FILE: return ((xattr_flag & XATTR_FOR_FILE) == XATTR_FOR_FILE); case DIRECTORY: return ((xattr_flag & XATTR_FOR_DIR) == XATTR_FOR_DIR); case SYMBOLIC_LINK: return ((xattr_flag & XATTR_FOR_SYMLINK) == XATTR_FOR_SYMLINK); default: return ((xattr_flag & XATTR_FOR_ALL) == XATTR_FOR_ALL); } } static int attr_is_read_only(unsigned int attr_index) { if (attr_index < XATTR_COUNT) { if (xattr_list[attr_index].flags & XATTR_RO) return true; } /* else : standard xattr */ return false; } static int xattr_id_to_name(int fd, unsigned int xattr_id, char *name, int maxname) { unsigned int index; unsigned int curr_idx; char names[MAXPATHLEN], *ptr; ssize_t namesize; size_t len = 0; if (xattr_id < XATTR_COUNT) return ERR_FSAL_INVAL; index = xattr_id - XATTR_COUNT; /* get xattrs */ namesize = flistxattr(fd, names, sizeof(names)); if (namesize < 0) return ERR_FSAL_NOENT; errno = 0; if (xattr_id == XATTR_SYSTEM) { if (strlcpy(name, "system.posix_acl_access", maxname) >= maxname) return ERR_FSAL_INVAL; return ERR_FSAL_NO_ERROR; } for (ptr = names, curr_idx = 0; ptr < names + namesize; curr_idx++, ptr += len + 1) { len = strlen(ptr); if (curr_idx == index) { if (len >= maxname) return ERR_FSAL_INVAL; memcpy(name, ptr, len + 1); return ERR_FSAL_NO_ERROR; } } return ERR_FSAL_NOENT; } /** * return index if found, * negative value on error. */ static int xattr_name_to_id(int fd, const char *name) { unsigned int i; char names[MAXPATHLEN], *ptr; ssize_t namesize; /* get xattrs */ namesize = flistxattr(fd, names, sizeof(names)); if (namesize < 0) return -ERR_FSAL_NOENT; if (!strcmp(name, "system.posix_acl_access")) return XATTR_SYSTEM; for (ptr = names, i = 0; ptr < names + namesize; i++, ptr += strlen(ptr) + 1) { if (!strcmp(name, ptr)) return i + XATTR_COUNT; } return -ERR_FSAL_NOENT; } fsal_status_t vfs_list_ext_attrs(struct fsal_obj_handle *obj_hdl, unsigned int argcookie, fsal_xattrent_t *xattrs_tab, unsigned int xattrs_tabsize, unsigned int *p_nb_returned, int *end_of_list) { unsigned int index; unsigned int out_index; unsigned int cookie = argcookie; struct vfs_fsal_obj_handle *obj_handle = NULL; int fd = -1; fsal_errors_t fe; char names[MAXPATHLEN], *ptr; ssize_t namesize; int xattr_idx; obj_handle = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); /* Deal with special cookie */ if (cookie == XATTR_RW_COOKIE) cookie = XATTR_COUNT; for (index = cookie, out_index = 0; index < XATTR_COUNT && out_index < xattrs_tabsize; index++) { if (do_match_type(xattr_list[index].flags, obj_handle->obj_handle.type)) { /* fills an xattr entry */ xattrs_tab[out_index].xattr_id = index; xattrs_tab[out_index].xattr_cookie = index + 1; if (strlcpy(xattrs_tab[out_index].xattr_name, xattr_list[index].xattr_name, sizeof(xattrs_tab[out_index].xattr_name)) >= sizeof(xattrs_tab[out_index].xattr_name)) { LogCrit(COMPONENT_FSAL, "xattr_name %s didn't fit", xattr_list[index].xattr_name); } /* next output slot */ out_index++; } } /* save a call if output array is full */ if (out_index == xattrs_tabsize) { *end_of_list = false; *p_nb_returned = out_index; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* get the path of the file in file system */ fd = (obj_hdl->type == DIRECTORY) ? vfs_fsal_open(obj_handle, O_DIRECTORY, &fe) : vfs_fsal_open(obj_handle, O_RDWR, &fe); if (fd < 0) return fsalstat(fe, -fd); /* get xattrs */ namesize = flistxattr(fd, names, sizeof(names)); if (namesize >= 0) { size_t len = 0; errno = 0; for (ptr = names, xattr_idx = 0; (ptr < names + namesize) && (out_index < xattrs_tabsize); xattr_idx++, ptr += len + 1) { len = strlen(ptr); index = XATTR_COUNT + xattr_idx; /* skip if index is before cookie */ if (index < cookie) continue; /* fills an xattr entry */ xattrs_tab[out_index].xattr_id = index; xattrs_tab[out_index].xattr_cookie = index + 1; if (strlcpy(xattrs_tab[out_index].xattr_name, ptr, sizeof(xattrs_tab[out_index].xattr_name)) >= sizeof(xattrs_tab[out_index].xattr_name)) { LogCrit(COMPONENT_FSAL, "xattr_name %s didn't fit", ptr); } /* next output slot */ out_index++; } /* all xattrs are in the output array */ if (ptr >= names + namesize) *end_of_list = true; else *end_of_list = false; } else /* no xattrs */ *end_of_list = true; *p_nb_returned = out_index; close(fd); return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t vfs_getextattr_id_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, unsigned int *pxattr_id) { unsigned int index; int rc; bool found = false; struct vfs_fsal_obj_handle *obj_handle = NULL; int fd = -1; obj_handle = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); for (index = 0; index < XATTR_COUNT; index++) { if (!strcmp(xattr_list[index].xattr_name, xattr_name)) { found = true; break; } } /* search in xattrs */ if (!found) { fsal_errors_t fe; int openflags; switch (obj_hdl->type) { case DIRECTORY: openflags = O_DIRECTORY; break; case SYMBOLIC_LINK: return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); default: openflags = O_RDWR; } fd = vfs_fsal_open(obj_handle, openflags, &fe); if (fd < 0) return fsalstat(fe, -fd); errno = 0; rc = xattr_name_to_id(fd, xattr_name); if (rc < 0) { int minor = errno; close(fd); return fsalstat(-rc, minor); } else { index = rc; found = true; } close(fd); } *pxattr_id = index; return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t vfs_getextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buffer_addr, size_t buffer_size, size_t *p_output_size) { struct vfs_fsal_obj_handle *obj_handle = NULL; int fd = -1; int rc = 0; obj_handle = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); /* check that this index match the type of entry */ if ((xattr_id < XATTR_COUNT) && !do_match_type(xattr_list[xattr_id].flags, obj_handle->obj_handle.type)) { return fsalstat(ERR_FSAL_INVAL, 0); } else if (xattr_id >= XATTR_COUNT) { char attr_name[MAXPATHLEN]; fsal_errors_t fe; fd = (obj_hdl->type == DIRECTORY) ? vfs_fsal_open(obj_handle, O_DIRECTORY, &fe) : vfs_fsal_open(obj_handle, O_RDWR, &fe); if (fd < 0) return fsalstat(fe, -fd); /* get the name for this attr */ rc = xattr_id_to_name(fd, xattr_id, attr_name, sizeof(attr_name)); if (rc) { int minor = errno; close(fd); return fsalstat(rc, minor); } rc = fgetxattr(fd, attr_name, buffer_addr, buffer_size); if (rc < 0) { rc = errno; close(fd); return fsalstat(posix2fsal_error(rc), rc); } /* the xattr value can be a binary, or a string. * trying to determine its type... */ *p_output_size = rc; close(fd); return fsalstat(ERR_FSAL_NO_ERROR, 0); } else { /* built-in attr */ /* get the value */ rc = xattr_list[xattr_id].get_func(obj_hdl, buffer_addr, buffer_size, p_output_size, xattr_list[xattr_id].arg); return fsalstat(rc, 0); } } fsal_status_t vfs_getextattr_value(struct vfs_fsal_obj_handle *vfs_hdl, int fd, const char *xattr_name, void *buffer_addr, size_t buffer_size, size_t *p_output_size) { struct fsal_obj_handle *obj_hdl = &vfs_hdl->obj_handle; int local_fd = fd; int rc = 0; fsal_status_t st = { ERR_FSAL_NO_ERROR, 0 }; if (fd < 0) { int openflags; switch (obj_hdl->type) { case DIRECTORY: openflags = O_DIRECTORY; break; case SYMBOLIC_LINK: return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); default: openflags = O_RDWR; } local_fd = vfs_fsal_open(vfs_hdl, openflags, &st.major); if (local_fd < 0) { st.minor = -local_fd; return st; } } /* is it an xattr? */ rc = fgetxattr(local_fd, xattr_name, buffer_addr, buffer_size); if (rc < 0) { st = fsalstat(posix2fsal_error(errno), errno); goto out; } /* the xattr value can be a binary, or a string. * trying to determine its type... */ *p_output_size = rc; out: // Close the local_fd only if no fd was passed into the function and we // opened the file in this function explicitly. if (fd < 0 && local_fd > 0) { close(local_fd); } return st; } fsal_status_t vfs_getextattr_value_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, size_t *p_output_size) { struct vfs_fsal_obj_handle *obj_handle = NULL; unsigned int index; obj_handle = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); /* sanity checks */ if (!obj_hdl || !p_output_size || !buffer_addr || !xattr_name) return fsalstat(ERR_FSAL_FAULT, 0); /* look for this name */ for (index = 0; index < XATTR_COUNT; index++) { if (do_match_type(xattr_list[index].flags, obj_handle->obj_handle.type) && !strcmp(xattr_list[index].xattr_name, xattr_name)) { return vfs_getextattr_value_by_id(obj_hdl, index, buffer_addr, buffer_size, p_output_size); } } return vfs_getextattr_value(obj_handle, -1 /*fd*/, xattr_name, buffer_addr, buffer_size, p_output_size); } fsal_status_t vfs_setextattr_value(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, int create) { struct vfs_fsal_obj_handle *obj_handle = NULL; int fd = -1; fsal_errors_t fe; int rc = 0; obj_handle = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); fd = (obj_hdl->type == DIRECTORY) ? vfs_fsal_open(obj_handle, O_DIRECTORY, &fe) : vfs_fsal_open(obj_handle, O_RDWR, &fe); if (fd < 0) return fsalstat(fe, -fd); if (buffer_size == 0) rc = fsetxattr(fd, xattr_name, "", 1, create ? XATTR_CREATE : XATTR_REPLACE); else rc = fsetxattr(fd, xattr_name, (char *)buffer_addr, buffer_size, create ? XATTR_CREATE : XATTR_REPLACE); if (rc != 0) { rc = errno; close(fd); return fsalstat(posix2fsal_error(rc), rc); } close(fd); return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t vfs_setextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buffer_addr, size_t buffer_size) { char name[MAXNAMLEN]; struct vfs_fsal_obj_handle *obj_handle = NULL; int fd = -1; fsal_errors_t fe; int rc = 0; obj_handle = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); if (attr_is_read_only(xattr_id)) return fsalstat(ERR_FSAL_PERM, 0); else if (xattr_id < XATTR_COUNT) return fsalstat(ERR_FSAL_PERM, 0); fd = (obj_hdl->type == DIRECTORY) ? vfs_fsal_open(obj_handle, O_DIRECTORY, &fe) : vfs_fsal_open(obj_handle, O_RDWR, &fe); if (fd < 0) return fsalstat(fe, -fd); rc = xattr_id_to_name(fd, xattr_id, name, sizeof(name)); if (rc) { int minor = errno; close(fd); return fsalstat(rc, minor); } close(fd); return vfs_setextattr_value(obj_hdl, name, buffer_addr, buffer_size, false); } fsal_status_t vfs_remove_extattr_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id) { int rc; char name[MAXNAMLEN]; struct vfs_fsal_obj_handle *obj_handle = NULL; int fd = -1; fsal_errors_t fe; obj_handle = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); fd = (obj_hdl->type == DIRECTORY) ? vfs_fsal_open(obj_handle, O_DIRECTORY, &fe) : vfs_fsal_open(obj_handle, O_RDWR, &fe); if (fd < 0) return fsalstat(fe, -fd); rc = xattr_id_to_name(fd, xattr_id, name, sizeof(name)); if (rc) { int minor = errno; close(fd); return fsalstat(rc, minor); } rc = fremovexattr(fd, name); if (rc) { rc = errno; close(fd); return fsalstat(posix2fsal_error(rc), rc); } close(fd); return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t vfs_remove_extattr_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name) { struct vfs_fsal_obj_handle *obj_handle = NULL; int fd = -1; int rc = 0; fsal_errors_t fe; obj_handle = container_of(obj_hdl, struct vfs_fsal_obj_handle, obj_handle); fd = (obj_hdl->type == DIRECTORY) ? vfs_fsal_open(obj_handle, O_DIRECTORY, &fe) : vfs_fsal_open(obj_handle, O_RDWR, &fe); if (fd < 0) return fsalstat(fe, -fd); rc = fremovexattr(fd, xattr_name); if (rc) { rc = errno; close(fd); return fsalstat(posix2fsal_error(rc), rc); } close(fd); return fsalstat(ERR_FSAL_NO_ERROR, 0); } nfs-ganesha-6.5/src/FSAL/FSAL_VFS/xfs/000077500000000000000000000000001473756622300171545ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/FSAL_VFS/xfs/.gitignore000066400000000000000000000000171473756622300211420ustar00rootroot00000000000000libxfsfdhdl.so nfs-ganesha-6.5/src/FSAL/FSAL_VFS/xfs/CMakeLists.txt000066400000000000000000000033641473756622300217220ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- add_definitions( -D__USE_GNU ) SET(fsalxfs_LIB_SRCS main.c ../export.c ../handle.c handle_syscalls.c ../file.c ../xattrs.c ../state.c ../vfs_methods.h ../empty_check_hsm.c subfsal_xfs.c ) add_library(fsalxfs MODULE ${fsalxfs_LIB_SRCS}) add_sanitizers(fsalxfs) if(PATH_LIBHANDLE) add_library(handle SHARED IMPORTED) set_target_properties(handle PROPERTIES IMPORTED_LOCATION ${PATH_LIBHANDLE}) endif(PATH_LIBHANDLE) target_link_libraries(fsalxfs ganesha_nfsd ${SYSTEM_LIBRARIES} ${LDFLAG_DISALLOW_UNDEF} ) target_link_libraries(fsalxfs handle) set_target_properties(fsalxfs PROPERTIES VERSION 4.2.0 SOVERSION 4) install(TARGETS fsalxfs COMPONENT fsal DESTINATION ${FSAL_DESTINATION} ) ########### install files ############### nfs-ganesha-6.5/src/FSAL/FSAL_VFS/xfs/handle_syscalls.c000066400000000000000000000240131473756622300224700ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "fsal.h" #include "fsal_handle_syscalls.h" #include #include #include #include #include "gsh_list.h" #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_localfs.h" #include "../vfs_methods.h" #include "handle_syscalls.h" void display_xfs_handle(struct display_buffer *dspbuf, struct vfs_file_handle *fh) { xfs_handle_t *hdl = (xfs_handle_t *)fh->handle_data; (void)display_printf(dspbuf, "Handle len %hhu: fsid=0x%016" PRIx32 ".0x%016" PRIx32 " fid_len=%" PRIu16 " fid_pad=%" PRIu16 " fid_gen=%" PRIu32 " fid_ino=%" PRIu64, fh->handle_len, hdl->ha_fsid.val[0], hdl->ha_fsid.val[1], hdl->ha_fid.fid_len, hdl->ha_fid.fid_pad, hdl->ha_fid.fid_gen, hdl->ha_fid.fid_ino); } #define LogXFSHandle(fh) \ do { \ if (isMidDebug(COMPONENT_FSAL)) { \ char buf[256] = "\0"; \ struct display_buffer dspbuf = { sizeof(buf), buf, \ buf }; \ \ display_xfs_handle(&dspbuf, fh); \ \ LogMidDebug(COMPONENT_FSAL, "%s", buf); \ } \ } while (0) static int xfs_fsal_bulkstat_inode(int fd, xfs_ino_t ino, struct xfs_bstat *bstat) { struct xfs_fsop_bulkreq bulkreq; __u64 i = ino; bulkreq.lastip = &i; bulkreq.icount = 1; bulkreq.ubuffer = bstat; bulkreq.ocount = NULL; return ioctl(fd, XFS_IOC_FSBULKSTAT_SINGLE, &bulkreq); } static int xfs_fsal_inode2handle(int fd, ino_t ino, vfs_file_handle_t *fh) { struct xfs_bstat bstat; xfs_handle_t *hdl = (xfs_handle_t *)fh->handle_data; void *data; size_t sz; if (fh->handle_len < sizeof(*hdl)) { errno = E2BIG; return -1; } /* Get the information pertinent to this inode, and * the file handle for the reference fd. */ if ((xfs_fsal_bulkstat_inode(fd, ino, &bstat) < 0) || (fd_to_handle(fd, &data, &sz) < 0)) return -1; /* Copy the fsid from the reference fd */ memcpy(&hdl->ha_fsid, data, sizeof(xfs_fsid_t)); /* Fill in the rest of the handle with the information * pertinent to this inode. */ hdl->ha_fid.fid_len = sizeof(xfs_handle_t) - sizeof(xfs_fsid_t) - sizeof(hdl->ha_fid.fid_len); hdl->ha_fid.fid_pad = 0; hdl->ha_fid.fid_gen = bstat.bs_gen; hdl->ha_fid.fid_ino = bstat.bs_ino; fh->handle_len = sizeof(*hdl); free_handle(data, sz); return 0; } int vfs_open_by_handle(struct fsal_filesystem *fs, vfs_file_handle_t *fh, int openflags, fsal_errors_t *fsal_error) { int fd; LogXFSHandle(fh); if (openflags == (O_PATH | O_NOACCESS)) openflags = O_DIRECTORY; fd = open_by_handle(fh->handle_data, fh->handle_len, openflags); if (fd < 0) { fd = -errno; if (fd == -ENOENT) *fsal_error = posix2fsal_error(ESTALE); else *fsal_error = posix2fsal_error(-fd); } return fd; } int vfs_fd_to_handle(int fd, struct fsal_filesystem *fs, vfs_file_handle_t *fh) { void *data; size_t sz; int rv = 0; if (fd_to_handle(fd, &data, &sz) < 0) return -1; if (sz >= fh->handle_len) { errno = E2BIG; rv = -1; } else { memcpy(fh->handle_data, data, sz); fh->handle_len = sz; LogXFSHandle(fh); } free_handle(data, sz); return rv; } int vfs_name_to_handle(int fd, struct fsal_filesystem *fs, const char *name, vfs_file_handle_t *fh) { int retval; struct stat stat; if (fstatat(fd, name, &stat, AT_SYMLINK_NOFOLLOW) < 0) return -1; if (S_ISDIR(stat.st_mode) || S_ISREG(stat.st_mode)) { int e; int tmpfd = openat(fd, name, O_RDONLY | O_NOFOLLOW, 0600); if (tmpfd < 0) return -1; retval = vfs_fd_to_handle(tmpfd, fs, fh); e = errno; close(tmpfd); errno = e; } else { retval = xfs_fsal_inode2handle(fd, stat.st_ino, fh); } LogXFSHandle(fh); return retval; } int vfs_readlink(struct vfs_fsal_obj_handle *hdl, fsal_errors_t *ferr) { char ldata[MAXPATHLEN + 1]; int retval; LogXFSHandle(hdl->handle); retval = readlink_by_handle(hdl->handle->handle_data, hdl->handle->handle_len, ldata, sizeof(ldata)); if (retval < 0) { retval = -errno; *ferr = posix2fsal_error(retval); goto out; } ldata[retval] = '\0'; hdl->u.symlink.link_content = gsh_strdup(ldata); hdl->u.symlink.link_size = retval + 1; retval = 0; out: return retval; } int vfs_extract_fsid(vfs_file_handle_t *fh, enum fsid_type *fsid_type, struct fsal_fsid__ *fsid) { xfs_handle_t *hdl = (xfs_handle_t *)fh->handle_data; LogXFSHandle(fh); if (hdl->ha_fid.fid_pad != 0) { char handle_data[sizeof(struct fsal_fsid__)]; int rc; *fsid_type = (enum fsid_type)(hdl->ha_fid.fid_pad - 1); memcpy(handle_data, &hdl->ha_fsid, sizeof(hdl->ha_fsid)); memcpy(handle_data + sizeof(hdl->ha_fsid), &hdl->ha_fid.fid_ino, sizeof(hdl->ha_fid.fid_ino)); rc = decode_fsid(handle_data, sizeof(handle_data), fsid, *fsid_type); if (rc < 0) { errno = EINVAL; return rc; } return 0; } *fsid_type = FSID_TWO_UINT32; fsid->major = hdl->ha_fsid.val[0]; fsid->minor = hdl->ha_fsid.val[1]; return 0; } int vfs_encode_dummy_handle(vfs_file_handle_t *fh, struct fsal_filesystem *fs) { xfs_handle_t *hdl = (xfs_handle_t *)fh->handle_data; char handle_data[sizeof(struct fsal_fsid__)]; int rc; memset(handle_data, 0, sizeof(handle_data)); /* Pack fsid into handle_data */ rc = encode_fsid(handle_data, sizeof(handle_data), &fs->fsid, fs->fsid_type); if (rc < 0) { errno = EINVAL; return rc; } assert(sizeof(handle_data) == (sizeof(hdl->ha_fsid) + sizeof(hdl->ha_fid.fid_ino))); memcpy(&hdl->ha_fsid, handle_data, sizeof(hdl->ha_fsid)); memcpy(&hdl->ha_fid.fid_ino, handle_data + sizeof(hdl->ha_fsid), sizeof(hdl->ha_fid.fid_ino)); hdl->ha_fid.fid_len = sizeof(xfs_handle_t) - sizeof(xfs_fsid_t) - sizeof(hdl->ha_fid.fid_len); hdl->ha_fid.fid_pad = fs->fsid_type + 1; hdl->ha_fid.fid_gen = 0; fh->handle_len = sizeof(*hdl); LogXFSHandle(fh); return 0; } bool vfs_is_dummy_handle(vfs_file_handle_t *fh) { xfs_handle_t *hdl = (xfs_handle_t *)fh->handle_data; return hdl->ha_fid.fid_pad != 0; } bool vfs_valid_handle(struct gsh_buffdesc *desc) { xfs_handle_t *hdl = (xfs_handle_t *)desc->addr; bool fsid_type_ok = false; if ((desc->addr == NULL) || (desc->len != sizeof(xfs_handle_t))) return false; if (isMidDebug(COMPONENT_FSAL)) { char buf[256] = "\0"; struct display_buffer dspbuf = { sizeof(buf), buf, buf }; (void)display_printf(&dspbuf, "Handle len %d: fsid=0x%016" PRIx32 ".0x%016" PRIx32 " fid_len=%" PRIu16 " fid_pad=%" PRIu16 " fid_gen=%" PRIu32 " fid_ino=%" PRIu64, (int)desc->len, hdl->ha_fsid.val[0], hdl->ha_fsid.val[1], hdl->ha_fid.fid_len, hdl->ha_fid.fid_pad, hdl->ha_fid.fid_gen, hdl->ha_fid.fid_ino); LogMidDebug(COMPONENT_FSAL, "%s", buf); } if (hdl->ha_fid.fid_pad != 0) { switch ((enum fsid_type)(hdl->ha_fid.fid_pad - 1)) { case FSID_NO_TYPE: case FSID_ONE_UINT64: case FSID_MAJOR_64: case FSID_TWO_UINT64: case FSID_TWO_UINT32: case FSID_DEVICE: fsid_type_ok = true; break; } if (!fsid_type_ok) { LogDebug(COMPONENT_FSAL, "FSID Type %02" PRIu16 " invalid", hdl->ha_fid.fid_pad - 1); return false; } if (hdl->ha_fid.fid_gen != 0) return false; } return hdl->ha_fid.fid_len == (sizeof(xfs_handle_t) - sizeof(xfs_fsid_t) - sizeof(hdl->ha_fid.fid_len)); } int vfs_get_root_handle(struct fsal_filesystem *fs, struct vfs_fsal_export *exp, int *root_fd) { enum fsid_type fsid_type; struct fsal_fsid__ fsid; int retval; void *data; size_t sz; vfs_file_handle_t *fh; vfs_alloc_handle(fh); if (path_to_fshandle(fs->path, &data, &sz) < 0) { retval = errno; LogMajor( COMPONENT_FSAL, "Export root %s could not be established for XFS error %s", fs->path, strerror(retval)); return retval; } *root_fd = open(fs->path, O_RDONLY | O_DIRECTORY); if (*root_fd < 0) { retval = errno; LogMajor(COMPONENT_FSAL, "Could not open XFS mount point %s: rc = %s (%d)", fs->path, strerror(retval), retval); return retval; } retval = vfs_fd_to_handle(*root_fd, fs, fh); if (retval != 0) { retval = errno; LogMajor(COMPONENT_FSAL, "Get root handle for %s failed with %s (%d)", fs->path, strerror(retval), retval); goto errout; } /* Extract fsid from the root handle and re-index the filesystem * using it. This is because the file handle already has an fsid in * it. */ (void)vfs_extract_fsid(fh, &fsid_type, &fsid); retval = re_index_fs_fsid(fs, fsid_type, &fsid); if (retval < 0) { LogCrit(COMPONENT_FSAL, "Could not re-index XFS file system fsid for %s", fs->path); retval = -retval; } errout: close(*root_fd); *root_fd = -1; return retval; } nfs-ganesha-6.5/src/FSAL/FSAL_VFS/xfs/handle_syscalls.h000066400000000000000000000022311473756622300224730ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /* defined by libhandle but no prototype in xfs/handle.h */ int fd_to_handle(int fd, void **hanp, size_t *hlen); nfs-ganesha-6.5/src/FSAL/FSAL_VFS/xfs/main.c000066400000000000000000000145031473756622300202470ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * Patrice LUCAS patrice.lucas@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /* main.c * Module core functions */ #include "config.h" #include /* used for 'dirname' */ #include #include #include #include #include "fsal.h" #include "FSAL/fsal_init.h" #include "fsal_handle_syscalls.h" #include "vfs_methods.h" /* VFS FSAL module private storage */ /* defined the set of attributes supported with POSIX */ #ifndef ENABLE_VFS_ACL #define XFS_SUPPORTED_ATTRIBUTES ((const attrmask_t)(ATTRS_POSIX)) #else #define XFS_SUPPORTED_ATTRIBUTES ((const attrmask_t)(ATTRS_POSIX | ATTR_ACL)) #endif static const char myname[] = "XFS"; /* my module private storage */ static struct vfs_fsal_module XFS = { .module = { .fs_info = { .maxfilesize = INT64_MAX, .maxlink = _POSIX_LINK_MAX, .maxnamelen = 1024, .maxpathlen = 1024, .no_trunc = true, .chown_restricted = true, .case_insensitive = false, .case_preserving = true, .lock_support = false, .lock_support_async_block = false, .named_attr = true, .unique_handles = true, .acl_support = FSAL_ACLSUPPORT_ALLOW, .homogenous = true, .supported_attrs = XFS_SUPPORTED_ATTRIBUTES, .maxread = FSAL_MAXIOSIZE, .maxwrite = FSAL_MAXIOSIZE, .link_supports_permission_checks = false, .expire_time_parent = -1, } }, .only_one_user = false }; static struct config_item xfs_params[] = { CONF_ITEM_BOOL("link_support", true, vfs_fsal_module, module.fs_info.link_support), CONF_ITEM_BOOL("symlink_support", true, vfs_fsal_module, module.fs_info.symlink_support), CONF_ITEM_BOOL("cansettime", true, vfs_fsal_module, module.fs_info.cansettime), CONF_ITEM_UI64("maxread", 512, FSAL_MAXIOSIZE, FSAL_MAXIOSIZE, vfs_fsal_module, module.fs_info.maxread), CONF_ITEM_UI64("maxwrite", 512, FSAL_MAXIOSIZE, FSAL_MAXIOSIZE, vfs_fsal_module, module.fs_info.maxwrite), CONF_ITEM_MODE("umask", 0, vfs_fsal_module, module.fs_info.umask), CONF_ITEM_BOOL("auth_xdev_export", false, vfs_fsal_module, module.fs_info.auth_exportpath_xdev), CONF_ITEM_BOOL("only_one_user", false, vfs_fsal_module, only_one_user), CONFIG_EOL }; struct config_block xfs_param = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.xfs", .blk_desc.name = "XFS", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = xfs_params, .blk_desc.u.blk.commit = noop_conf_commit }; /* Module methods */ /* init_config * must be called with a reference taken (via lookup_fsal) */ static fsal_status_t init_config(struct fsal_module *xfs_fsal_module, config_file_t config_struct, struct config_error_type *err_type) { struct vfs_fsal_module *xfs_module = container_of(xfs_fsal_module, struct vfs_fsal_module, module); #ifdef F_OFD_GETLK int fd, rc; struct flock lock; char *temp_name; #endif #ifdef F_OFD_GETLK /* If on a system that might support OFD locks, verify them. * Only if they exist will we declare lock support. */ LogInfo(COMPONENT_FSAL, "FSAL_XFS testing OFD Locks"); temp_name = gsh_strdup("/tmp/ganesha.nfsd.locktestXXXXXX"); fd = mkstemp(temp_name); if (fd >= 0) { lock.l_whence = SEEK_SET; lock.l_type = F_RDLCK; lock.l_start = 0; lock.l_len = 0; lock.l_pid = 0; rc = fcntl(fd, F_OFD_GETLK, &lock); if (rc == 0) xfs_module->module.fs_info.lock_support = true; else LogInfo(COMPONENT_FSAL, "Could not use OFD locks"); close(fd); unlink(temp_name); } else { LogCrit(COMPONENT_FSAL, "Could not create file %s to test OFD locks", temp_name); } gsh_free(temp_name); #endif if (xfs_module->module.fs_info.lock_support) LogInfo(COMPONENT_FSAL, "FSAL_XFS enabling OFD Locks"); else LogInfo(COMPONENT_FSAL, "FSAL_XFS disabling lock support"); LogFullDebug(COMPONENT_FSAL, "Supported attributes default = 0x%" PRIx64, xfs_module->module.fs_info.supported_attrs); (void)load_config_from_parse(config_struct, &xfs_param, xfs_module, true, err_type); if (!config_error_is_harmless(err_type)) return fsalstat(ERR_FSAL_INVAL, 0); display_fsinfo(&xfs_module->module); LogFullDebug(COMPONENT_FSAL, "Supported attributes constant = 0x%" PRIx64, XFS_SUPPORTED_ATTRIBUTES); LogDebug(COMPONENT_FSAL, "FSAL INIT: Supported attributes mask = 0x%" PRIx64, xfs_module->module.fs_info.supported_attrs); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* Module initialization. * Called by dlopen() to register the module * keep a private pointer to me in myself */ /* linkage to the exports and handle ops initializers */ MODULE_INIT void xfs_init(void) { int retval; struct fsal_module *myself = &XFS.module; retval = register_fsal(myself, myname, FSAL_MAJOR_VERSION, FSAL_MINOR_VERSION, FSAL_ID_NO_PNFS); if (retval != 0) { fprintf(stderr, "XFS module failed to register"); return; } myself->m_ops.create_export = vfs_create_export; myself->m_ops.update_export = vfs_update_export; myself->m_ops.init_config = init_config; /* Initialize the fsal_obj_handle ops for FSAL XFS */ vfs_handle_ops_init(&XFS.handle_ops); } MODULE_FINI void xfs_unload(void) { int retval; retval = unregister_fsal(&XFS.module); if (retval != 0) { fprintf(stderr, "XFS module failed to unregister"); return; } } nfs-ganesha-6.5/src/FSAL/FSAL_VFS/xfs/subfsal_xfs.c000066400000000000000000000042621473756622300216430ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) CohortFS LLC, 2014 * Author: Daniel Gryniewicz dang@cohortfs.com * * * This program 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 3 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 * 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 * * ------------- */ /* subfsal_xfs.c * XFS Sub-FSAL export object */ #include "config.h" #include "fsal_types.h" #include "fsal_api.h" #include "../vfs_methods.h" #include "../subfsal.h" /* Export */ static struct config_item export_params[] = { CONF_ITEM_NOOP("name"), CONFIG_EOL }; static struct config_block export_param_block = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.xfs-export%d", .blk_desc.name = "FSAL", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = export_params, .blk_desc.u.blk.commit = noop_conf_commit }; struct config_block *vfs_sub_export_param = &export_param_block; /* Handle syscalls */ void vfs_sub_fini(struct vfs_fsal_export *myself) { } void vfs_sub_init_export_ops(struct vfs_fsal_export *myself, const char *export_path) { } int vfs_sub_init_export(struct vfs_fsal_export *myself) { return 0; } struct vfs_fsal_obj_handle *vfs_sub_alloc_handle(void) { struct vfs_fsal_obj_handle *hdl; hdl = gsh_calloc(1, (sizeof(struct vfs_fsal_obj_handle) + sizeof(vfs_file_handle_t))); hdl->handle = (vfs_file_handle_t *)&hdl[1]; return hdl; } int vfs_sub_init_handle(struct vfs_fsal_export *myself, struct vfs_fsal_obj_handle *hdl, const char *path) { return 0; } nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/000077500000000000000000000000001473756622300177725ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/CMakeLists.txt000066400000000000000000000021251473756622300225320ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- if(USE_FSAL_NULL) add_subdirectory(FSAL_NULL) endif(USE_FSAL_NULL) add_subdirectory(FSAL_MDCACHE) nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/000077500000000000000000000000001473756622300216235ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/CMakeLists.txt000066400000000000000000000034571473756622300243740ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- add_definitions( -D__USE_GNU ) if(USE_DBUS) include_directories( ${DBUS_INCLUDE_DIRS} ) endif(USE_DBUS) set( LIB_PREFIX 64) ########### next target ############### SET(fsalmdcache_LIB_SRCS mdcache_avl.h mdcache_ext.h mdcache_int.h mdcache_hash.h mdcache_lru.h mdcache_handle.c mdcache_file.c mdcache_xattrs.c mdcache_main.c mdcache_export.c mdcache_helpers.c mdcache_lru.c mdcache_hash.c mdcache_avl.c mdcache_read_conf.c mdcache_up.c ) add_library(fsalmdcache OBJECT ${fsalmdcache_LIB_SRCS}) add_sanitizers(fsalmdcache) set_target_properties(fsalmdcache PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(fsalmdcache gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) ########### install files ############### nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_avl.c000066400000000000000000000375751473756622300242360ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2010, The Linux Box Corporation * Contributor : Matt Benjamin * * Some portions Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @addtogroup FSAL_MDCACHE * @{ */ /** * @file mdcache_avl.c * @brief AVL tree for caching directory entries */ #include "config.h" #include "log.h" #include "fsal.h" #include "mdcache_int.h" #include "mdcache_avl.h" #include "mdcache_lru.h" #include "murmur3.h" #include "city.h" #include #include #include #include #include #include void mdcache_avl_init(mdcache_entry_t *entry) { avltree_init(&entry->fsobj.fsdir.avl.t, avl_dirent_name_cmpf, 0 /* flags */); avltree_init(&entry->fsobj.fsdir.avl.ck, avl_dirent_ck_cmpf, 0 /* flags */); avltree_init(&entry->fsobj.fsdir.avl.sorted, avl_dirent_sorted_cmpf, 0 /* flags */); } static inline struct avltree_node * avltree_inline_lookup_hk(const struct avltree_node *key, const struct avltree *tree) { return avltree_inline_lookup(key, tree, avl_dirent_name_cmpf); } void avl_dirent_set_deleted(mdcache_entry_t *entry, mdcache_dir_entry_t *v) { struct avltree_node __attribute__((unused)) * node; mdcache_dir_entry_t *next; LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Delete dir entry %p %s", v, v->name); #ifdef DEBUG_MDCACHE assert(entry->content_lock.__data.__cur_writer); #endif assert(!(v->flags & DIR_ENTRY_FLAG_DELETED)); node = avltree_inline_lookup_hk(&v->node_name, &entry->fsobj.fsdir.avl.t); assert(node); avltree_remove(&v->node_name, &entry->fsobj.fsdir.avl.t); v->flags |= DIR_ENTRY_FLAG_DELETED; mdcache_key_delete(&v->ckey); /* Do stuff if chunked... */ if (v->chunk != NULL) { struct dir_chunk *chunk = v->chunk; mdcache_entry_t *parent = chunk->parent; if (v->ck == parent->fsobj.fsdir.first_ck) { /* This is no longer the first entry in the directory... * Find the first non-deleted entry. */ next = v; while (next != NULL && next->flags & DIR_ENTRY_FLAG_DELETED) { next = glist_next_entry(&chunk->dirents, mdcache_dir_entry_t, chunk_list, &next->chunk_list); if (next != NULL) { /* Evaluate it in the while condition. */ continue; } /* End of the chunk... */ /** @todo FSF This entire chunk is deleted * entries, we really should just free * the chunk... */ /* Look for the next chunk */ if (chunk->next_ck != 0 && mdcache_avl_lookup_ck( parent, chunk->next_ck, &next)) { chunk = next->chunk; /* We don't need the ref, we have the * content lock */ mdcache_lru_unref_chunk(chunk); } } if (next != NULL) { /* This entry is now the first_ck. */ parent->fsobj.fsdir.first_ck = next->ck; } else { /* There are no more cached chunks */ parent->fsobj.fsdir.first_ck = 0; } } /* For now leave the entry in the ck hash so we can re-start * directory from that position, this means that directory * enumeration will have to skip deleted entries. */ } else { mdcache_avl_remove(entry, v); } } /** * @brief Remove a dirent from a chunk. * * @param[in] dirent The dirent to remove * */ void unchunk_dirent(mdcache_dir_entry_t *dirent) { mdcache_entry_t *parent = dirent->chunk->parent; LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Unchunking %p %s", dirent, dirent->name); #ifdef DEBUG_MDCACHE assert(parent->content_lock.__data.__cur_writer); #endif /* Dirent is part of a chunk, must do additional clean * up. */ /* Remove from chunk */ glist_del(&dirent->chunk_list); /* Remove from FSAL cookie AVL tree */ avltree_remove(&dirent->node_ck, &parent->fsobj.fsdir.avl.ck); /* Check if this was the first dirent in the directory. */ if (parent->fsobj.fsdir.first_ck == dirent->ck) { /* The first dirent in the directory is no longer chunked... */ parent->fsobj.fsdir.first_ck = 0; } /* Check if this entry was in the sorted AVL tree */ if (dirent->flags & DIR_ENTRY_SORTED) { /* It was, remove it. */ avltree_remove(&dirent->node_sorted, &parent->fsobj.fsdir.avl.sorted); } /* Just make sure... */ dirent->chunk = NULL; } /** * @brief Remove and free a dirent. * * @note parent content_lock MUST be held for write * * @param[in] parent The directory removing from * @param[in] dirent The dirent to remove * */ void mdcache_avl_remove(mdcache_entry_t *parent, mdcache_dir_entry_t *dirent) { struct dir_chunk *chunk = dirent->chunk; if ((dirent->flags & DIR_ENTRY_FLAG_DELETED) == 0) { /* Remove from active names tree */ avltree_remove(&dirent->node_name, &parent->fsobj.fsdir.avl.t); } if (dirent->mde_entry) { /* We have a ref'd entry, drop our ref */ mdcache_lru_unref(dirent->mde_entry, LRU_ACTIVE_REF); dirent->mde_entry = NULL; } if (dirent->chunk != NULL) { /* Dirent belongs to a chunk so remove it from the chunk. */ unchunk_dirent(dirent); } else { /* The dirent might be a detached dirent on an LRU list */ rmv_detached_dirent(parent, dirent); } if (dirent->ckey.kv.len) mdcache_key_delete(&dirent->ckey); LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Just freed dirent %p from chunk %p parent %p", dirent, chunk, (chunk) ? chunk->parent : NULL); gsh_free(dirent); } /** * @brief Insert a dirent into the lookup by FSAL cookie AVL tree. * * @param[in] entry The directory * @param[in] v The dirent * * @retval 0 Success * @retval -1 Failure * */ int mdcache_avl_insert_ck(mdcache_entry_t *entry, mdcache_dir_entry_t *v) { struct avltree_node *node; LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Insert dirent %p for %s on entry=%p FSAL cookie=%" PRIx64, v, v->name, entry, v->ck); #ifdef DEBUG_MDCACHE assert(entry->content_lock.__data.__cur_writer); #endif node = avltree_inline_insert(&v->node_ck, &entry->fsobj.fsdir.avl.ck, avl_dirent_ck_cmpf); if (!node) { LogDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "inserted dirent %p for %s on entry=%p FSAL cookie=%" PRIx64, v, v->name, entry, v->ck); return 0; } /* already inserted */ LogDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Already existent when inserting dirent %p for %s on entry=%p FSAL cookie=%" PRIx64 ", duplicated directory cookies make READDIR unreliable.", v, v->name, entry, v->ck); return -1; } #define MIN_COOKIE_VAL 3 /* * Insert into avl tree using key combination of hash of name with strcmp * of name to disambiguate hash collision. * * In the case of a name collision, assuming the ckey in the dirents matches, * and the flags are the same, then this will be treated as a success and the * dirent passed in will be freed and the dirent will be set to the found one. * * If any error occurs, the passed in dirent will be freed and the dirent * will be set to NULL. * * @param[in] entry The directory * @param[in] dirent The dirent * * @retval 0 Success * @retval -3 Duplicate file name but different cookie * @retval -4 FSAL cookie collision * **/ int mdcache_avl_insert(mdcache_entry_t *entry, mdcache_dir_entry_t **dirent) { mdcache_dir_entry_t *v = *dirent, *v2; #if AVL_HASH_MURMUR3 uint32_t hk[4]; #endif struct avltree_node *node; int code; LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Insert dir entry %p %s", v, v->name); #ifdef DEBUG_MDCACHE assert(entry->content_lock.__data.__cur_writer); #endif /* compute hash */ #if AVL_HASH_MURMUR3 MurmurHash3_x64_128(v->name, strlen(v->name), 67, hk); memcpy(&v->namehash, hk, 8); #else v->namehash = CityHash64WithSeed(v->name, strlen(v->name), 67); #endif again: node = avltree_insert(&v->node_name, &entry->fsobj.fsdir.avl.t); if (!node) { /* success */ if (v->chunk != NULL) { /* This directory entry is part of a chunked directory * enter it into the "by FSAL cookie" avl also. */ if (mdcache_avl_insert_ck(entry, v) < 0) { /* We failed to insert into FSAL cookie * AVL tree, remove from lookup by name * AVL tree. */ avltree_remove(&v->node_name, &entry->fsobj.fsdir.avl.t); v2 = NULL; code = -4; goto out; } } if (isFullDebug(COMPONENT_MDCACHE) || isFullDebug(COMPONENT_NFS_READDIR)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; (void)display_mdcache_key(&dspbuf, &v->ckey); LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Inserted dirent %s with ckey %s", v->name, str); } return 0; } /* Deal with name collision. */ v2 = avltree_container_of(node, mdcache_dir_entry_t, node_name); /* Same name, probably already inserted. */ LogDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Already existent when inserting new dirent on entry=%p name=%s", entry, v->name); if (mdcache_key_cmp(&v->ckey, &v2->ckey) != 0) { /* The two names don't seem to have the same object * handle digest. Discard the old dirent and try again. */ if (isFullDebug(COMPONENT_MDCACHE) || isFullDebug(COMPONENT_NFS_READDIR)) { char str1[LOG_BUFF_LEN / 2] = "\0"; char str2[LOG_BUFF_LEN / 2] = "\0"; struct display_buffer dspbuf1 = { sizeof(str1), str1, str1 }; struct display_buffer dspbuf2 = { sizeof(str2), str2, str2 }; (void)display_mdcache_key(&dspbuf1, &v->ckey); (void)display_mdcache_key(&dspbuf2, &v2->ckey); LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Keys for %s don't match v=%s v2=%s", v->name, str1, str2); } /* Remove the found dirent. */ mdcache_avl_remove(entry, v2); v2 = NULL; goto again; } /* The v2 entry should NOT be deleted... */ assert((v2->flags & DIR_ENTRY_FLAG_DELETED) == 0); if (v->chunk != NULL && v2->chunk == NULL) { /* This directory entry is part of a chunked directory enter the * old dirent into the "by FSAL cookie" AVL tree also. * We need to update the old dirent for the FSAL cookie * bits... */ v2->chunk = v->chunk; v2->ck = v->ck; v2->eod = v->eod; if (mdcache_avl_insert_ck(entry, v2) < 0) { /* We failed to insert into FSAL cookie AVL * tree, leave in lookup by name AVL tree but * don't return a dirent. Also, undo the changes * to the old dirent. */ v2->chunk = NULL; v2->ck = 0; v2 = NULL; code = -4; } else { if (isFullDebug(COMPONENT_MDCACHE) || isFullDebug(COMPONENT_NFS_READDIR)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; (void)display_mdcache_key(&dspbuf, &v2->ckey); LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Updated dirent %p with ck=%" PRIx64 " and chunk %p eod=%s ckey=%s", v2, v2->ck, v2->chunk, v2->eod ? "true" : "false", str); } /* Remove v2 from the detached entry cache */ rmv_detached_dirent(entry, v2); code = 0; } } else if (v->chunk != NULL && v2->chunk != NULL) { /* Handle cases where existing entry is in a chunk as * well as previous entry. Somehow an entry is showing * up twice. Will prefer existing entry. */ if (v->ck == v2->ck) { /* completely a duplicate entry, ignore it */ LogDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Duplicate filename %s insert into chunk %p, existing was in chunk %p, ignoring", v->name, v->chunk, v2->chunk); code = 0; } else { /* This is an odd case, lets treat it as an * error. */ LogDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Duplicate filename %s with different cookies ckey %" PRIx64 " chunk %p don't match existing ckey %" PRIx64 " chunk %p", v->name, v->ck, v->chunk, v2->ck, v2->chunk); code = -3; v2 = NULL; } } else { /* New entry is not in a chunk, existing entry might * be in a chunk, in any case, the entry already * exists so we are good. */ LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Duplicate insert of %s v->chunk=%p v2->chunk=%p", v->name, v->chunk, v2->chunk); code = 0; } out: mdcache_key_delete(&v->ckey); gsh_free(v); *dirent = v2; return code; } /** * @brief Look up a dirent by FSAL cookie * * Look up a dirent by FSAL cookie. * * @note this takes a ref on the chunk containing @a dirent * * @param[in] entry Directory to search in * @param[in] ck FSAL cookie to find * @param[out] dirent Returned dirent, if found, NULL otherwise * * @retval true if found * @retval false if not found */ bool mdcache_avl_lookup_ck(mdcache_entry_t *entry, uint64_t ck, mdcache_dir_entry_t **dirent) { struct avltree *tck = &entry->fsobj.fsdir.avl.ck; mdcache_dir_entry_t dirent_key[1]; mdcache_dir_entry_t *ent; struct avltree_node *node; *dirent = NULL; dirent_key->ck = ck; node = avltree_inline_lookup(&dirent_key->node_ck, tck, avl_dirent_ck_cmpf); if (node) { struct dir_chunk *chunk; /* This is the entry we are looking for... This function is * passed the cookie of the next entry of interest in the * directory. */ ent = avltree_container_of(node, mdcache_dir_entry_t, node_ck); chunk = ent->chunk; if (chunk == NULL) { /* This entry doesn't belong to a chunk, something * is horribly wrong. */ assert(chunk); return false; } mdcache_lru_ref_chunk(chunk); *dirent = ent; return true; } return false; } mdcache_dir_entry_t *mdcache_avl_lookup(mdcache_entry_t *entry, const char *name) { struct avltree_node *node; mdcache_dir_entry_t *v2; mdcache_dir_entry_t v; #if AVL_HASH_MURMUR3 uint32_t hashbuff[4]; #endif size_t namelen = strlen(name); LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Lookup %s", name); #if AVL_HASH_MURMUR3 MurmurHash3_x64_128(name, namelen, 67, hashbuff); /* This seems to be correct. The avltree_lookup function looks as hk.k, but does no namecmp on its own, so there's no need to allocate space for or copy the name in the key. */ memcpy(&v.namehash, hashbuff, 8); #else v.namehash = CityHash64WithSeed(name, namelen, 67); #endif v.name = name; node = avltree_lookup(&v.node_name, &entry->fsobj.fsdir.avl.t); if (node) { /* return dirent */ v2 = avltree_container_of(node, mdcache_dir_entry_t, node_name); assert(!(v2->flags & DIR_ENTRY_FLAG_DELETED)); return v2; } LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "entry not found %s", name); return NULL; } /** * @brief Remove and free all dirents from the dirent trees for a directory * * @param[in] parent The directory removing from */ void mdcache_avl_clean_trees(mdcache_entry_t *parent) { struct avltree_node *dirent_node; mdcache_dir_entry_t *dirent; #ifdef DEBUG_MDCACHE assert(parent->content_lock.__data.__cur_writer); #endif while ((dirent_node = avltree_first(&parent->fsobj.fsdir.avl.t))) { dirent = avltree_container_of(dirent_node, mdcache_dir_entry_t, node_name); LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Invalidate %p %s", dirent, dirent->name); mdcache_avl_remove(parent, dirent); } } /** @} */ nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_avl.h000066400000000000000000000075721473756622300242350ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright (C) 2010, Linux Box Corporation * All Rights Reserved * * Contributor: Matt Benjamin * * This program 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 3 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 * 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 * * ------------- */ /** * @addtogroup FSAL_MDCACHE * @{ */ /** * @file mdcache_avl.h * @author Matt Benjamin * @brief Definitions supporting AVL dirent representation * */ /** * @page AVLOverview Overview * * Definitions supporting AVL dirent representation. The current * design represents dirents as a single AVL tree ordered by a * collision-resistent hash function (currently, Murmur3, which * appears to be several times faster than lookup3 on x86_64 * architecture). Quadratic probing is used to emulate perfect * hashing. Worst case behavior is challenging to reproduce. * Heuristic methods are used to detect worst-case scenarios and fall * back to tractable (e.g., lookup) algorithms. * */ #ifndef MDCACHE_AVL_H #define MDCACHE_AVL_H #include "config.h" #include "log.h" #include "mdcache_int.h" #include "avltree.h" static inline int avl_dirent_name_cmpf(const struct avltree_node *lhs, const struct avltree_node *rhs) { mdcache_dir_entry_t *lk, *rk; lk = avltree_container_of(lhs, mdcache_dir_entry_t, node_name); rk = avltree_container_of(rhs, mdcache_dir_entry_t, node_name); if (lk->namehash < rk->namehash) return -1; if (lk->namehash > rk->namehash) return 1; return strcmp(lk->name, rk->name); } static inline int avl_dirent_ck_cmpf(const struct avltree_node *lhs, const struct avltree_node *rhs) { mdcache_dir_entry_t *lk, *rk; lk = avltree_container_of(lhs, mdcache_dir_entry_t, node_ck); rk = avltree_container_of(rhs, mdcache_dir_entry_t, node_ck); if (lk->ck < rk->ck) return -1; if (lk->ck == rk->ck) return 0; return 1; } static inline int avl_dirent_sorted_cmpf(const struct avltree_node *lhs, const struct avltree_node *rhs) { mdcache_dir_entry_t *lk, *rk; int rc; lk = avltree_container_of(lhs, mdcache_dir_entry_t, node_sorted); rk = avltree_container_of(rhs, mdcache_dir_entry_t, node_sorted); /* On create a dirent will not yet belong to a chunk, but only * one of the two nodes in comparison can not belong to a chunk. */ subcall(if (lk->chunk != NULL) rc = lk->chunk->parent->sub_handle->obj_ops->dirent_cmp( lk->chunk->parent->sub_handle, lk->name, lk->ck, rk->name, rk->ck); else rc = rk->chunk->parent->sub_handle->obj_ops->dirent_cmp( rk->chunk->parent->sub_handle, lk->name, lk->ck, rk->name, rk->ck)); return rc; } void mdcache_avl_remove(mdcache_entry_t *parent, mdcache_dir_entry_t *dirent); void avl_dirent_set_deleted(mdcache_entry_t *entry, mdcache_dir_entry_t *v); void mdcache_avl_init(mdcache_entry_t *entry); int mdcache_avl_insert(mdcache_entry_t *entry, mdcache_dir_entry_t **dirent); int mdcache_avl_insert_ck(mdcache_entry_t *entry, mdcache_dir_entry_t *v); bool mdcache_avl_lookup_ck(mdcache_entry_t *entry, uint64_t ck, mdcache_dir_entry_t **dirent); mdcache_dir_entry_t *mdcache_avl_lookup(mdcache_entry_t *entry, const char *name); void mdcache_avl_clean_trees(mdcache_entry_t *parent); void unchunk_dirent(mdcache_dir_entry_t *dirent); #endif /* MDCACHE_AVL_H */ /** @} */ nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h000066400000000000000000000036311473756622300245310ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2015-2018 Red Hat, Inc. and/or its affiliates. * Author: Daniel Gryniewicz * * This program 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 3 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 * 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 * */ /** * @file mdcache_debug.h * @brief MDCache debug interface * * This is a debug interface for MDCACHE. It should *not* be used for any * production codepaths, but only for debugging and white-box-testing. */ #ifndef MDCACHE_DEBUG_H #define MDCACHE_DEBUG_H #include "mdcache_int.h" #include "mdcache_lru.h" /** * @brief Get the sub-FSAL handle from an MDCACHE handle * * This allows access down the stack to the sub-FSAL's handle, given an MDCACHE * handle. It can be used to bypass MDCACHE for a debug operation. * * @note Keep a ref on the MDCACHE handle for the duration of the use of the * sub-FSAL's handle, or it might be freed from under you. * * @param[in] obj_hdl MDCACHE handle * @return sub-FSAL handle on success, NULL on failure */ struct fsal_obj_handle *mdcdb_get_sub_handle(struct fsal_obj_handle *obj_hdl) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); return entry->sub_handle; } #endif /* MDCACHE_DEBUG_H */ nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_export.c000066400000000000000000000647021473756622300247650ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2015-2019 Red Hat, Inc. and/or its affiliates. * Author: Daniel Gryniewicz * * This program 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 3 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 * 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 * */ /** * @addtogroup FSAL_MDCACHE * @{ */ /** * @file main.c * @brief FSAL export functions */ #include "config.h" #include "fsal.h" #include /* used for 'dirname' */ #include #include #include #include #include #include #include "gsh_list.h" #include "config_parsing.h" #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_config.h" #include "mdcache_lru.h" #include "mdcache_hash.h" #include "nfs_exports.h" #include "export_mgr.h" #include "gsh_config.h" /* * helpers to/from other NULL objects */ /* * export object methods */ /** * @brief Return the name of the sub-FSAL * * For MDCACHE, we append "/MDC" onto the name. * * @param[in] exp_hdl Our export handle * @return Name of sub-FSAL */ static const char *mdcache_get_name(struct fsal_export *exp_hdl) { struct mdcache_fsal_export *exp = mdc_cur_export(); return exp->name; } /** * @brief Un-export an MDCACHE export * * Clean up all the cache entries on this export. * * @param[in] exp_hdl Export to unexport * @param[in] root_obj Root object for export */ static void mdcache_unexport(struct fsal_export *exp_hdl, struct fsal_obj_handle *root_obj) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; mdcache_entry_t *root_entry = container_of(root_obj, mdcache_entry_t, obj_handle); mdcache_entry_t *entry; struct entry_export_map *expmap; /* Indicate this export is going away so we don't create any new * export map entries. */ atomic_set_uint8_t_bits(&exp->flags, MDC_UNEXPORT); /* Next, clean up our cache entries on the export */ while (true) { PTHREAD_MUTEX_lock(&exp->mdc_exp_lock); expmap = glist_first_entry(&exp->entry_list, struct entry_export_map, entry_per_export); if (unlikely(expmap == NULL)) { PTHREAD_MUTEX_unlock(&exp->mdc_exp_lock); break; } entry = expmap->entry; if (entry == root_entry) { LogDebug( COMPONENT_EXPORT, "About to unmap root entry %p and possibly free it for export %d path %s pseudo %s", root_entry, op_ctx->ctx_export->export_id, CTX_FULLPATH(op_ctx), CTX_PSEUDOPATH(op_ctx)); } else { LogDebug(COMPONENT_EXPORT, "About to unmap entry %p and possibly free it", entry); } /* Get a ref across cleanup. This must be an initial ref, so * that it takes the LRU lane lock, keeping it from racing with * lru_lane_run() */ mdcache_lru_ref(entry, LRU_ACTIVE_REF | LRU_PROMOTE); PTHREAD_MUTEX_unlock(&exp->mdc_exp_lock); /* Must get attr_lock before mdc_exp_lock */ PTHREAD_RWLOCK_wrlock(&entry->attr_lock); PTHREAD_MUTEX_lock(&exp->mdc_exp_lock); mdc_remove_export_map(expmap); expmap = glist_first_entry(&entry->export_list, struct entry_export_map, export_per_entry); if (expmap == NULL) { /* Entry is unmapped, clear first_export_id. This is to * close a race caused by lru_run_lane() taking a ref * before we call mdcache_lru_cleanup_try_push() below. * */ atomic_store_int32_t(&entry->first_export_id, -1); /* We must not hold entry->attr_lock across * try_cleanup_push (LRU lane lock order) */ PTHREAD_MUTEX_unlock(&exp->mdc_exp_lock); PTHREAD_RWLOCK_unlock(&entry->attr_lock); LogFullDebug(COMPONENT_EXPORT, "Disposing of entry %p", entry); /* There are no exports referencing this entry, attempt * to push it to cleanup queue. Note that if the export * root is in fact only used by one export, it will * be unhashed here. */ mdcache_lru_cleanup_try_push(entry); } else { /* Make sure first export pointer is still valid */ atomic_store_int32_t( &entry->first_export_id, (int32_t)expmap->exp->mfe_exp.export_id); PTHREAD_MUTEX_unlock(&exp->mdc_exp_lock); PTHREAD_RWLOCK_unlock(&entry->attr_lock); LogFullDebug( COMPONENT_EXPORT, "entry %p is still exported by export id %d", entry, expmap->exp->mfe_exp.export_id); } /* Release above ref */ mdcache_lru_unref(entry, LRU_ACTIVE_REF); }; /* Last unexport for the sub-FSAL */ subcall_raw(exp, sub_export->exp_ops.unexport(sub_export, root_entry->sub_handle)); /* NOTE: we do NOT need to unhash the root entry, it was unhashed above * (if it was not used by another export) in the loop since it is * an entry that belongs to the export. */ } /** * @brief Handle the unmounting of an export. * * This function is called when the export is unmounted. The FSAL may need * to clean up references to the root_obj and junction_obj and connections * between them. * * Specifically, mdcache must remove the export mapping and schedule for * cleanup the junction node (which may be the same node as the unmounted * export's root node). * * @param[in] parent_exp_hdl The parent export of the mount. * @param[in] junction_obj The junction object the export was mounted on */ static void mdcache_unmount(struct fsal_export *parent_exp_hdl, struct fsal_obj_handle *junction_obj) { struct mdcache_fsal_export *exp = mdc_export(parent_exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; mdcache_entry_t *entry = container_of(junction_obj, mdcache_entry_t, obj_handle); struct glist_head *glist; struct entry_export_map *expmap = NULL; /* Take locks to perform unmap. Must get attr_lock before mdc_exp_lock */ PTHREAD_RWLOCK_wrlock(&entry->attr_lock); PTHREAD_MUTEX_lock(&exp->mdc_exp_lock); glist_for_each(glist, &entry->export_list) { expmap = glist_entry(glist, struct entry_export_map, export_per_entry); if (expmap->exp == exp) { /* Found it. */ break; } /* Not this one... */ expmap = NULL; } if (expmap == NULL) { LogFatal(COMPONENT_EXPORT, "export map not found for export %p", parent_exp_hdl); } /* Next, clean up junction cache entry on the export */ LogDebug(COMPONENT_EXPORT, "About to unmap junction entry %p and possibly free it", entry); /* Now remove the export map */ mdc_remove_export_map(expmap); /* And look at the export map for the junction entry now */ expmap = glist_first_entry(&entry->export_list, struct entry_export_map, export_per_entry); if (expmap == NULL) { /* Entry is unmapped, clear first_export_id. This is to * close a race caused by lru_run_lane() taking a ref * before we call mdcache_lru_cleanup_try_push() below. * */ atomic_store_int32_t(&entry->first_export_id, -1); /* We must not hold entry->attr_lock across * try_cleanup_push (LRU lane lock order) */ PTHREAD_MUTEX_unlock(&exp->mdc_exp_lock); PTHREAD_RWLOCK_unlock(&entry->attr_lock); LogFullDebug(COMPONENT_EXPORT, "Disposing of entry %p", entry); /* There are no exports referencing this entry, attempt * to push it to cleanup queue. Note that if the export * root is in fact only used by one export, it will * be unhashed here. */ mdcache_lru_cleanup_try_push(entry); } else { /* Make sure first export pointer is still valid */ atomic_store_int32_t(&entry->first_export_id, (int32_t)expmap->exp->mfe_exp.export_id); PTHREAD_MUTEX_unlock(&exp->mdc_exp_lock); PTHREAD_RWLOCK_unlock(&entry->attr_lock); LogFullDebug(COMPONENT_EXPORT, "entry %p is still exported by export id %d", entry, expmap->exp->mfe_exp.export_id); } /* Last unmount for the sub-FSAL */ subcall_raw(exp, sub_export->exp_ops.unmount(sub_export, entry->sub_handle)); } /** * @brief Release an MDCACHE export * * @param[in] exp_hdl Export to release */ static void mdcache_exp_release(struct fsal_export *exp_hdl) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; struct fsal_module *fsal_hdl; fsal_hdl = sub_export->fsal; LogInfo(COMPONENT_FSAL, "Releasing %s export %" PRIu16 " for %s", fsal_hdl->name, op_ctx->ctx_export->export_id, ctx_export_path(op_ctx)); /* Stop the dirmap thread */ dirmap_lru_stop(exp); /* Release the sub_export */ subcall_shutdown_raw(exp, sub_export->exp_ops.release(sub_export)); fsal_put(fsal_hdl); LogFullDebug(COMPONENT_FSAL, "FSAL %s fsal_refcount %" PRIu32, fsal_hdl->name, atomic_fetch_int32_t(&fsal_hdl->refcount)); fsal_detach_export(exp_hdl->fsal, &exp_hdl->exports); free_export_ops(exp_hdl); gsh_free(exp->name); PTHREAD_MUTEX_destroy(&exp->mdc_exp_lock); PTHREAD_MUTEX_destroy(&exp->dirent_map.dm_mtx); gsh_free(exp); /* elvis has left the building */ } /** * @brief Get FS information * * Pass through to underlying FSAL. * * Note dang: Should this gather info about MDCACHE? * * @param[in] exp_hdl Export to operate on * @param[in] obj_hdl Object to operate on * @param[out] infop Output information on success * @return FSAL status */ static fsal_status_t mdcache_get_dynamic_info(struct fsal_export *exp_hdl, struct fsal_obj_handle *obj_hdl, fsal_dynamicfsinfo_t *infop) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); fsal_status_t status; subcall_raw(exp, status = sub_export->exp_ops.get_fs_dynamic_info( sub_export, entry->sub_handle, infop)); return status; } /** * @brief See if a feature is supported * * For the moment, MDCACHE supports no features, so just pass through to the * base FSAL. * * @param[in] exp_hdl Export to check * @param[in] option Option to check for support * @return true if supported, false otherwise */ static bool mdcache_fs_supports(struct fsal_export *exp_hdl, fsal_fsinfo_options_t option) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; bool result; subcall_raw(exp, result = sub_export->exp_ops.fs_supports(sub_export, option)); return result; } /** * @brief Find the maximum supported file size * * MDCACHE only caches metadata, so it imposes no restrictions itself. * * @param[in] exp_hdl Export to query * @return Max size in bytes */ static uint64_t mdcache_fs_maxfilesize(struct fsal_export *exp_hdl) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; uint64_t result; subcall_raw(exp, result = sub_export->exp_ops.fs_maxfilesize(sub_export)); return result; } /** * @brief Get the maximum supported read size * * MDCACHE only caches metadata, so it imposes no restrictions itself. * * @param[in] exp_hdl Export to query * @return Max size in bytes */ static uint32_t mdcache_fs_maxread(struct fsal_export *exp_hdl) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; uint32_t result; subcall_raw(exp, result = sub_export->exp_ops.fs_maxread(sub_export)); return result; } /** * @brief Get the maximum supported write size * * MDCACHE only caches metadata, so it imposes no restrictions itself. * * @param[in] exp_hdl Export to query * @return Max size in bytes */ static uint32_t mdcache_fs_maxwrite(struct fsal_export *exp_hdl) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; uint32_t result; subcall_raw(exp, result = sub_export->exp_ops.fs_maxwrite(sub_export)); return result; } /** * @brief Get the maximum supported link count * * MDCACHE only caches metadata, so it imposes no restrictions itself. * * @param[in] exp_hdl Export to query * @return Max number of links to a file */ static uint32_t mdcache_fs_maxlink(struct fsal_export *exp_hdl) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; uint32_t result; subcall_raw(exp, result = sub_export->exp_ops.fs_maxlink(sub_export)); return result; } /** * @brief Get the maximum supported name length * * MDCACHE only caches metadata, so it imposes no restrictions itself. * * @param[in] exp_hdl Export to query * @return Max length of name in bytes */ static uint32_t mdcache_fs_maxnamelen(struct fsal_export *exp_hdl) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; uint32_t result; subcall_raw(exp, result = sub_export->exp_ops.fs_maxnamelen(sub_export)); return result; } /** * @brief Get the maximum supported name length * * MDCACHE only caches metadata, so it imposes no restrictions itself. * * @param[in] exp_hdl Export to query * @return Max length of name in bytes */ static uint32_t mdcache_fs_maxpathlen(struct fsal_export *exp_hdl) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; uint32_t result; subcall_raw(exp, result = sub_export->exp_ops.fs_maxpathlen(sub_export)); return result; } /** * @brief Get the NFSv4 ACLSUPPORT attribute * * MDCACHE does not provide or restrict ACLs * * @param[in] exp_hdl Export to query * @return ACLSUPPORT */ static fsal_aclsupp_t mdcache_fs_acl_support(struct fsal_export *exp_hdl) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; fsal_aclsupp_t result; subcall_raw(exp, result = sub_export->exp_ops.fs_acl_support(sub_export)); return result; } /** * @brief Get the list of supported attributes * * MDCACHE does not provide or restrict attributes * * @param[in] exp_hdl Export to query * @return Mask of supported attributes */ static attrmask_t mdcache_fs_supported_attrs(struct fsal_export *exp_hdl) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; attrmask_t result; subcall_raw(exp, result = sub_export->exp_ops.fs_supported_attrs( sub_export)); return result; } /** * @brief Get the configured umask on the export * * MDCACHE only caches metadata, so it imposes no restrictions itself. * * @param[in] exp_hdl Export to query * @return umask value */ static uint32_t mdcache_fs_umask(struct fsal_export *exp_hdl) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; uint32_t result; subcall_raw(exp, result = sub_export->exp_ops.fs_umask(sub_export)); return result; } /** * @brief Get the configured expiration time for parent handle * * MDCACHE only caches metadata, so it imposes no restrictions itself. * * @param[in] exp_hdl Export to query * @return Expiry time for parent handle */ static int32_t mdcache_fs_expiretimeparent(struct fsal_export *exp_hdl) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; int32_t result; subcall_raw(exp, result = sub_export->exp_ops.fs_expiretimeparent( sub_export)); return result; } /** * @brief Check quota on a file * * MDCACHE only caches metadata, so it imposes no restrictions itself. * * @param[in] exp_hdl Export to query * @param[in] filepath Path to file to query * @param[in] quota_type Type of quota (user or group) * @return FSAL status */ static fsal_status_t mdcache_check_quota(struct fsal_export *exp_hdl, const char *filepath, int quota_type) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; fsal_status_t status; subcall_raw(exp, status = sub_export->exp_ops.check_quota( sub_export, filepath, quota_type)); return status; } /** * @brief Get quota information for a file * * MDCACHE only caches metadata, so it imposes no restrictions itself. * * @param[in] exp_hdl Export to query * @param[in] filepath Path to file to query * @param[in] quota_type Type of quota (user or group) * @param[in] quota_id Id for getting quota information * @param[out] pquota Resulting quota information * @return FSAL status */ static fsal_status_t mdcache_get_quota(struct fsal_export *exp_hdl, const char *filepath, int quota_type, int quota_id, fsal_quota_t *pquota) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; fsal_status_t status; subcall_raw(exp, status = sub_export->exp_ops.get_quota( sub_export, filepath, quota_type, quota_id, pquota)); return status; } /** * @brief Set a quota for a file * * MDCACHE only caches metadata, so it imposes no restrictions itself. * * @param[in] exp_hdl Export to query * @param[in] filepath Path to file to query * @param[in] quota_type Type of quota (user or group) * @param[in] quota_id Id for which quota is set * @param[in] pquota Quota information to set * @param[out] presquota Quota after set * @return FSAL status */ static fsal_status_t mdcache_set_quota(struct fsal_export *exp_hdl, const char *filepath, int quota_type, int quota_id, fsal_quota_t *pquota, fsal_quota_t *presquota) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; fsal_status_t status; subcall_raw(exp, status = sub_export->exp_ops.set_quota( sub_export, filepath, quota_type, quota_id, pquota, presquota)); return status; } /** * @brief List pNFS devices * * MDCACHE only caches metadata, pass it through * * @param[in] exp_hdl Export to query * @param[in] type Layout type for query * @param[in] cb Callback for devices * @param[in] res Devicelist result * @return NFSv4 Status */ static nfsstat4 mdcache_getdevicelist(struct fsal_export *exp_hdl, layouttype4 type, void *opaque, bool (*cb)(void *opaque, const uint64_t id), struct fsal_getdevicelist_res *res) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; nfsstat4 status; subcall_raw(exp, status = sub_export->exp_ops.getdevicelist( sub_export, type, opaque, cb, res)); return status; } /** * @brief List supported pNFS layout types * * MDCACHE only caches metadata, pass it through * * @param[in] exp_hdl Export to query * @param[out] count Number of types returned * @param[out] types Layout types supported */ static void mdcache_fs_layouttypes(struct fsal_export *exp_hdl, int32_t *count, const layouttype4 **types) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; subcall_raw(exp, sub_export->exp_ops.fs_layouttypes(sub_export, count, types)); } /** * @brief Get pNFS layout block size * * MDCACHE only caches metadata, pass it through * * @param[in] exp_hdl Export to query * @return Number of bytes in block */ static uint32_t mdcache_fs_layout_blocksize(struct fsal_export *exp_hdl) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; uint32_t status; subcall_raw(exp, status = sub_export->exp_ops.fs_layout_blocksize( sub_export)); return status; } /** * @brief Get pNFS maximum number of segments * * MDCACHE only caches metadata, pass it through * * @param[in] exp_hdl Export to query * @return Number of segments */ static uint32_t mdcache_fs_maximum_segments(struct fsal_export *exp_hdl) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; uint32_t status; subcall_raw(exp, status = sub_export->exp_ops.fs_maximum_segments( sub_export)); return status; } /** * @brief Get size of pNFS loc_body * * MDCACHE only caches metadata, pass it through * * @param[in] exp_hdl Export to query * @return Size of loc_body */ static size_t mdcache_fs_loc_body_size(struct fsal_export *exp_hdl) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; size_t status; subcall_raw(exp, status = sub_export->exp_ops.fs_loc_body_size(sub_export)); return status; } /** * @brief Get write verifier * * MDCACHE only caches metadata, pass it through * * @param[in] exp_hdl Export to query * @param[in,out] verf_desc Address and length of verifier */ static void mdcache_get_write_verifier(struct fsal_export *exp_hdl, struct gsh_buffdesc *verf_desc) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; subcall_raw(exp, sub_export->exp_ops.get_write_verifier(sub_export, verf_desc)); } /** * @brief Decode the wire handle into something the FSAL can understand * * Wire formats are delegated to the underlying FSAL. * * @param[in] exp_hdl Export to operate on * @param[in] in_type Type of handle to extract * @param[in,out] fh_desc Source/dest for extracted digest * @param[in] flags Related flags (currently endian) * @return FSAL status */ static fsal_status_t mdcache_wire_to_host(struct fsal_export *exp_hdl, fsal_digesttype_t in_type, struct gsh_buffdesc *fh_desc, int flags) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; fsal_status_t status; subcall_raw(exp, status = sub_export->exp_ops.wire_to_host( sub_export, in_type, fh_desc, flags)); return status; } /** * @brief Produce handle-key from host-handle * * delegated to the underlying FSAL. * * @param[in] exp_hdl Export to operate on * @param[in,out] fh_desc Source/dest for extracted digest * @return FSAL status */ static fsal_status_t mdcache_host_to_key(struct fsal_export *exp_hdl, struct gsh_buffdesc *fh_desc) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; fsal_status_t status; subcall_raw(exp, status = sub_export->exp_ops.host_to_key(sub_export, fh_desc)); return status; } /** * @brief Allocate state_t structure * * @param[in] exp_hdl Export to operate on * @param[in] state_type Type of state to allocate * @param[in] related_state Related state if appropriate * @return New state structure */ static struct state_t *mdcache_alloc_state(struct fsal_export *exp_hdl, enum state_type state_type, struct state_t *related_state) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; struct state_t *state; subcall_raw(exp, state = sub_export->exp_ops.alloc_state( sub_export, state_type, related_state)); return state; } /** * @brief Check to see if a user is superuser * * @param[in] exp_hdl Export state_t is associated with * @param[in] creds Credentials to check for superuser * * @returns NULL on failure otherwise a state structure. */ static bool mdcache_is_superuser(struct fsal_export *exp_hdl, const struct user_cred *creds) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; bool status; subcall_raw(exp, status = sub_export->exp_ops.is_superuser(sub_export, creds)); return status; } /** * @brief Prepare an export to be unexported * * @param[in] exp_hdl Export state_t is associated with * * @returns NULL on failure otherwise a state structure. */ static void mdcache_prepare_unexport(struct fsal_export *exp_hdl) { struct mdcache_fsal_export *exp = mdc_export(exp_hdl); struct fsal_export *sub_export = exp->mfe_exp.sub_export; subcall_raw(exp, sub_export->exp_ops.prepare_unexport(sub_export)); } /* mdcache_export_ops_init * overwrite vector entries with the methods that we support */ void mdcache_export_ops_init(struct export_ops *ops) { ops->get_name = mdcache_get_name; ops->prepare_unexport = mdcache_prepare_unexport; ops->unexport = mdcache_unexport; ops->unmount = mdcache_unmount; ops->release = mdcache_exp_release; ops->lookup_path = mdcache_lookup_path; /* lookup_junction unimplemented because deprecated */ ops->wire_to_host = mdcache_wire_to_host; ops->host_to_key = mdcache_host_to_key; ops->create_handle = mdcache_create_handle; ops->get_fs_dynamic_info = mdcache_get_dynamic_info; ops->fs_supports = mdcache_fs_supports; ops->fs_maxfilesize = mdcache_fs_maxfilesize; ops->fs_maxread = mdcache_fs_maxread; ops->fs_maxwrite = mdcache_fs_maxwrite; ops->fs_maxlink = mdcache_fs_maxlink; ops->fs_maxnamelen = mdcache_fs_maxnamelen; ops->fs_maxpathlen = mdcache_fs_maxpathlen; ops->fs_acl_support = mdcache_fs_acl_support; ops->fs_supported_attrs = mdcache_fs_supported_attrs; ops->fs_umask = mdcache_fs_umask; ops->check_quota = mdcache_check_quota; ops->get_quota = mdcache_get_quota; ops->set_quota = mdcache_set_quota; ops->getdevicelist = mdcache_getdevicelist; ops->fs_layouttypes = mdcache_fs_layouttypes; ops->fs_layout_blocksize = mdcache_fs_layout_blocksize; ops->fs_maximum_segments = mdcache_fs_maximum_segments; ops->fs_loc_body_size = mdcache_fs_loc_body_size; ops->get_write_verifier = mdcache_get_write_verifier; ops->alloc_state = mdcache_alloc_state; ops->is_superuser = mdcache_is_superuser; ops->fs_expiretimeparent = mdcache_fs_expiretimeparent; } #if 0 struct mdcache_fsal_args { struct subfsal_args subfsal; }; static struct config_item sub_fsal_params[] = { CONF_ITEM_STR("name", 1, 10, NULL, subfsal_args, name), CONFIG_EOL }; static struct config_item export_params[] = { CONF_ITEM_NOOP("name"), CONF_RELAX_BLOCK("FSAL", sub_fsal_params, noop_conf_init, subfsal_commit, mdcache_fsal_args, subfsal), CONFIG_EOL }; static struct config_block export_param = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.mdcache-export%d", .blk_desc.name = "FSAL", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = export_params, .blk_desc.u.blk.commit = noop_conf_commit }; #endif /** @} */ nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_ext.h000066400000000000000000000135061473756622300242450ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2015-2019 Red Hat, Inc. and/or its affiliates. * Author: Daniel Gryniewicz * * This program 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 3 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 * 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 * */ /** * @file mdcache_ext.h * @brief MDCache external interface * * Stuff that can be accessed outside MDCACHE. Things in here are generally * hacks that should be removed. */ #ifndef MDCACHE_EXT_H #define MDCACHE_EXT_H /** * @defgroup config_mdcache Structure and defaults for MDCACHE * * @{ */ /** * @brief Structure to hold MDCACHE parameters */ struct mdcache_parameter { /** Partitions in the MDCACHE tree. Defaults to 7, * settable with NParts. */ uint32_t nparts; /** Per-partition hash table size. Defaults to 32633, * settable with Cache_Size. */ uint32_t cache_size; /** Use getattr for directory invalidation. Defaults to false. Settable with Use_Getattr_Directory_Invalidation. */ bool getattr_dir_invalidation; struct { /** Size of per-directory dirent cache chunks, 0 means * directory chunking is not enabled. */ uint32_t avl_chunk; /** Size of a dirent chunk at which point the chunk should * be split. Pre-computed for simplicity. */ uint32_t avl_chunk_split; /** Detached dirent multiplier (of avl_chunk) */ uint32_t avl_detached_mult; /** Computed max detached dirents */ uint32_t avl_detached_max; } dir; /** High water mark for cache entries. Defaults to 100000, settable by Entries_HWMark. */ uint32_t entries_hwmark; /** When the handle cache is over the high water mark, attempt to release this number of entries in each pass until it's back below the high water mark. Set it to 0 does not attempt to release entries. Defaults to 100, settable by Entries_Release_Size. */ uint32_t entries_release_size; /** High water mark for chunks. Defaults to 100000, settable by Chunks_HWMark. */ uint32_t chunks_hwmark; /** Low water mark for chunks. Defaults to 10000, settable by Chunks_HWMark. */ uint32_t chunks_lwmark; /** Base interval in seconds between runs of the LRU cleaner thread. Defaults to 90, settable with LRU_Run_Interval. */ uint32_t lru_run_interval; /** If "Cache_FDs" is set to false, the reaper thread aggressively * closes FDs , significantly reducing the number of open FDs. * This will help to maintain a minimal number of open FDs. * * If "Cache_FDs" is set to true (default), FDs are cached, and the * LRU reaper thread closes FDs only when the current open FD count * reaches or exceeds the "fds_lowat" threshold. * * Note, this setting has no effect when Close_Fast is set to true. */ bool Cache_FDs; /** * Whether to close files immediately after opening files and * using them for read/write/commit. Defaults to false, * settable with Close_Fast. */ bool close_fast; /** The percentage of the system-imposed maximum of file descriptors at which Ganesha will deny requests. Defaults to 99, settable with FD_Limit_Percent. */ uint32_t fd_limit_percent; /** The percentage of the system-imposed maximum of file descriptors above which Ganesha will make greater efforts at reaping. Defaults to 90, settable with FD_HWMark_Percent. */ uint32_t fd_hwmark_percent; /** The percentage of the system-imposed maximum of file descriptors below which Ganesha will not reap file descriptors. Defaults to 50, settable with FD_LWMark_Percent. */ uint32_t fd_lwmark_percent; /** Roughly, the amount of work to do on each pass through the thread under normal conditions. (Ideally, a multiple of the number of lanes.) Defaults to 0, settable with Reaper_Work. */ uint32_t reaper_work; /** The amount of work for the reaper thread to do per-lane under normal conditions. Settable with Repaper_Work_Per_Thread */ uint32_t reaper_work_per_lane; /** The largest window (as a percentage of the system-imposed limit on FDs) of work that we will do in extremis. Defaults to 40, settable with Biggest_Window */ uint32_t biggest_window; /** Percentage of progress toward the high water mark required in a pass through the thread when in extremis. Defaults to 5, settable with Required_Progress. */ uint32_t required_progress; /** Number of failures to approach the high watermark before we disable caching, when in extremis. Defaults to 8, settable with Futility_Count */ uint32_t futility_count; /** High water mark for dirent mapping entries. Defaults to 10000, settable by Dirmap_HWMark. */ uint32_t dirmap_hwmark; /** Total number of files ganesha can delegate to clients as a percent of Entries_HWMark */ int32_t files_delegatable_percent; /** This option allows mdcache_test_access to use the cached owner value for an object in case of owner override, even if the cache entry is not up to date. This could potentially lead to the wrong result, but can significantly improve performance saving the need to update attributes on many read/write operations. */ bool use_cached_owner_on_owner_override; }; extern struct mdcache_parameter mdcache_param; /** @} */ #endif /* MDCACHE_EXT_H */ nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_file.c000066400000000000000000000620221473756622300243540ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2015-2019 Red Hat, Inc. and/or its affiliates. * Author: Daniel Gryniewicz * * This program 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 3 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 * 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 */ /* file.c * File I/O methods for NULL module */ #include "config.h" #include #include "fsal.h" #include "FSAL/access_check.h" #include "fsal_convert.h" #include #include #include "FSAL/fsal_commonlib.h" #include "mdcache_int.h" #include "mdcache_lru.h" #include "mdcache.h" /** * @brief Callback arg for MDCACHE async callbacks * * MDCACHE needs to know what its object is related to the sub-FSAL's object. * This wraps the given callback arg with MDCACHE specific info */ struct mdc_async_arg { struct fsal_obj_handle *obj_hdl; /**< MDCACHE's handle */ fsal_async_cb cb; /**< Wrapped callback */ void *cb_arg; /**< Wrapped callback data */ }; /** * * @brief Set a timestamp to the current time * * @param[out] time Pointer to time to be set * * @return true on success, false on failure * */ bool mdc_set_time_current(struct timespec *time) { struct timeval t; if (time == NULL) return false; if (gettimeofday(&t, NULL) != 0) return false; time->tv_sec = t.tv_sec; time->tv_nsec = 1000 * t.tv_usec; return true; } /** * @brief IO Advise * * Delegate to sub-FSAL * * @param[in] obj_hdl File to be written * @param[in,out] info Information about the data * * @return FSAL status. */ fsal_status_t mdcache_io_advise(struct fsal_obj_handle *obj_hdl, struct io_hints *hints) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); fsal_status_t status; subcall(status = entry->sub_handle->obj_ops->io_advise( entry->sub_handle, hints)); return status; } /** * @brief Close a file * * @param[in] obj_hdl File to close * @return FSAL status */ fsal_status_t mdcache_close(struct fsal_obj_handle *obj_hdl) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); fsal_status_t status; /* XXX dang caching FDs? How does it interact with multi-FD */ subcall(status = entry->sub_handle->obj_ops->close(entry->sub_handle)); return status; } static fsal_status_t mdc_open2_by_name(mdcache_entry_t *mdc_parent, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attrib_set, struct fsal_attrlist *attrs_out, fsal_verifier_t verifier, mdcache_entry_t **new_entry, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { fsal_status_t status; bool uncached = createmode >= FSAL_GUARDED; mdcache_entry_t *entry; struct fsal_obj_handle *sub_handle; *new_entry = NULL; if (!name) return fsalstat(ERR_FSAL_INVAL, 0); status = mdc_lookup(mdc_parent, name, uncached, &entry, NULL); if (FSAL_IS_ERROR(status)) { /* Does not exist, or other error, return to open2 to * proceed if not found, otherwise to return the error. */ LogFullDebug(COMPONENT_MDCACHE, "Lookup failed"); return status; } /* Found to exist */ if (createmode == FSAL_GUARDED) { status = fsalstat(ERR_FSAL_EXIST, 0); goto out_unref; } else if (createmode == FSAL_EXCLUSIVE) { /* Exclusive create with entry found, check verifier */ if (!mdcache_check_verifier(&entry->obj_handle, verifier)) { /* Verifier check failed. */ LogFullDebug(COMPONENT_MDCACHE, "Verifier check failed."); status = fsalstat(ERR_FSAL_EXIST, 0); goto out_unref; } /* Verifier matches, go ahead and open the file. */ } /* else UNGUARDED, go ahead and open the file. */ /* Check if the object type is REGULAR_FILE. If not then give error. */ if (entry->obj_handle.type != REGULAR_FILE) { LogDebug(COMPONENT_MDCACHE, "Trying to open a non-regular file"); if (entry->obj_handle.type == DIRECTORY) { /* Trying to open2 a directory */ status = fsalstat(ERR_FSAL_ISDIR, 0); goto out_unref; } else { /* Trying to open2 any other non-regular file */ status = fsalstat(ERR_FSAL_SYMLINK, 0); goto out_unref; } } subcall(status = entry->sub_handle->obj_ops->open2( entry->sub_handle, state, openflags, createmode, NULL, attrib_set, verifier, &sub_handle, attrs_out, caller_perm_check, parent_pre_attrs_out, parent_post_attrs_out)); if (FSAL_IS_ERROR(status)) { /* Open failed. */ LogFullDebug(COMPONENT_MDCACHE, "Open failed %s", msg_fsal_err(status.major)); goto out_unref; } LogFullDebug(COMPONENT_MDCACHE, "Opened entry %p, sub_handle %p", entry, entry->sub_handle); if (openflags & FSAL_O_TRUNC) { /* Invalidate the attributes since we just truncated. */ atomic_clear_uint32_t_bits(&entry->mde_flags, MDCACHE_TRUST_ATTRS); } if (attrs_out) { /* Handle attribute request */ if (!(attrs_out->valid_mask & ATTR_RDATTR_ERR)) { struct fsal_attrlist attrs; /* open2() gave us attributes. Update the cache */ fsal_prepare_attrs( &attrs, op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) | ATTR_RDATTR_ERR); fsal_copy_attrs(&attrs, attrs_out, false); PTHREAD_RWLOCK_wrlock(&entry->attr_lock); mdc_update_attr_cache(entry, &attrs); PTHREAD_RWLOCK_unlock(&entry->attr_lock); /* mdc_update_attr_cache() consumes attrs; the release * is here only for code inspection. */ fsal_release_attrs(&attrs); } else if (attrs_out->request_mask & ATTR_RDATTR_ERR) { /* We didn't get attributes from open2, but the caller * wants them. Try a full getattrs() */ status = entry->obj_handle.obj_ops->getattrs( &entry->obj_handle, attrs_out); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_MDCACHE, "getattrs failed status=%s", fsal_err_txt(status)); } } } *new_entry = entry; return status; out_unref: mdcache_lru_unref(entry, LRU_ACTIVE_REF); return status; } /** * @brief Open a file descriptor for read or write and possibly create * * This function opens a file for read or write, possibly creating it. * If the caller is passing a state, it must hold the state_lock * exclusive. * * state can be NULL which indicates a stateless open (such as via the * NFS v3 CREATE operation), in which case the FSAL must assure protection * of any resources. If the file is being created, such protection is * simple since no one else will have access to the object yet, however, * in the case of an exclusive create, the common resources may still need * protection. * * If Name is NULL, obj_hdl is the file itself, otherwise obj_hdl is the * parent directory. * * On an exclusive create, the upper layer may know the object handle * already, so it MAY call with name == NULL. In this case, the caller * expects just to check the verifier. * * On a call with an existing object handle for an UNCHECKED create, * we can set the size to 0. * * At least the mode attribute must be set if createmode is FSAL_UNCHECKED, * FSAL_GUARDED, FSAL_EXCLUSIVE_41, or FSAL_EXCLUSIVE_9P. * * If an open by name succeeds and did not result in Ganesha creating a file, * the caller will need to do a subsequent permission check to confirm the * open. This is because the permission attributes were not available * beforehand. * * The caller is expected to invoke fsal_release_attrs to release any * resources held by the set attributes. The FSAL layer MAY have added an * inherited ACL. * * The caller will set the request_mask in attrs_out to indicate the attributes * of interest. ATTR_ACL SHOULD NOT be requested and need not be provided. If * not all the requested attributes can be provided, this method MUST return * an error unless the ATTR_RDATTR_ERR bit was set in the request_mask. * * Since this method may instantiate a new fsal_obj_handle, it will be forced * to fetch at least some attributes in order to even know what the object * type is (as well as it's fileid and fsid). For this reason, the operation * as a whole can be expected to fail if the attributes were not able to be * fetched. * * The attributes will not be returned if this is an open by object as * opposed to an open by name. * * @note If the file was created, @a new_obj has been ref'd * * @param[in] obj_hdl File to open or parent directory * @param[in,out] state state_t to use for this operation * @param[in] openflags Mode for open * @param[in] createmode Mode for create * @param[in] name Name for file if being created or opened * @param[in] attrs_in Attributes to set on created file * @param[in] verifier Verifier to use for exclusive create * @param[in,out] new_obj Newly created object * @param[in,out] attrs_out Optional attributes for newly created * object * @param[in,out] caller_perm_check The caller must do a permission check * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @return FSAL status. */ fsal_status_t mdcache_open2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attrs_in, fsal_verifier_t verifier, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { mdcache_entry_t *mdc_parent = container_of(obj_hdl, mdcache_entry_t, obj_handle); mdcache_entry_t *new_entry = NULL; struct fsal_obj_handle *sub_handle = NULL; fsal_status_t status; struct fsal_attrlist attrs; const char *dispname = name != NULL ? name : ""; struct mdcache_fsal_export *export = mdc_cur_export(); bool invalidate; LogAttrlist(COMPONENT_MDCACHE, NIV_FULL_DEBUG, "attrs_in ", attrs_in, false); if (name) { /* Check if we have the file already cached, in which case * we can open by object instead of by name. */ status = mdc_open2_by_name(mdc_parent, state, openflags, createmode, name, attrs_in, attrs_out, verifier, &new_entry, caller_perm_check, parent_pre_attrs_out, parent_post_attrs_out); if (status.major == ERR_FSAL_NO_ERROR) { /* Return the newly opened file. */ *new_obj = &new_entry->obj_handle; return status; } if (status.major != ERR_FSAL_NOENT) { /* Return the error */ *new_obj = NULL; return status; } } /* Ask for all supported attributes except ACL and FS_LOCATIONS (we * defer fetching ACL/FS_LOCATIONS until asked for it (including a * permission check). * * We can survive if we don't actually succeed in fetching the * attributes. */ fsal_prepare_attrs(&attrs, (op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) & ~(ATTR_ACL | ATTR4_FS_LOCATIONS)) | ATTR_RDATTR_ERR); subcall(status = mdc_parent->sub_handle->obj_ops->open2( mdc_parent->sub_handle, state, openflags, createmode, name, attrs_in, verifier, &sub_handle, &attrs, caller_perm_check, parent_pre_attrs_out, parent_post_attrs_out)); if (unlikely(FSAL_IS_ERROR(status))) { LogDebug(COMPONENT_MDCACHE, "open2 %s failed with %s", dispname, fsal_err_txt(status)); if (status.major == ERR_FSAL_STALE) { /* If we got ERR_FSAL_STALE, the previous FSAL call * must have failed with a bad parent. */ mdcache_kill_entry(mdc_parent); } fsal_release_attrs(&attrs); *new_obj = NULL; return status; } if (!name) { /* Wasn't a create and/or entry already found */ if (openflags & FSAL_O_TRUNC) { /* Mark the attributes as not-trusted, so we will * refresh the attributes. */ atomic_clear_uint32_t_bits(&mdc_parent->mde_flags, MDCACHE_TRUST_ATTRS); } LogFullDebug(COMPONENT_MDCACHE, "Open2 of object succeeded."); *new_obj = obj_hdl; /* We didn't actually get any attributes, but release anyway * for code consistency. */ fsal_release_attrs(&attrs); return status; } invalidate = createmode != FSAL_NO_CREATE; PTHREAD_RWLOCK_wrlock(&mdc_parent->content_lock); /* We will invalidate parent attrs if we did any form of create. */ status = mdcache_alloc_and_check_handle(export, sub_handle, new_obj, false, &attrs, attrs_out, "open2 ", mdc_parent, name, &invalidate, state); PTHREAD_RWLOCK_unlock(&mdc_parent->content_lock); fsal_release_attrs(&attrs); if (FSAL_IS_SUCCESS(status) && createmode != FSAL_NO_CREATE && !invalidate) { /* Refresh destination directory attributes without * invalidating dirents. */ status = mdcache_refresh_attrs_no_invalidate(mdc_parent); } return status; } /** * @brief Check the verifier * * @param[in] obj_hdl File to check * @param[in] verifier Verifier to check * @return FSAL status */ bool mdcache_check_verifier(struct fsal_obj_handle *obj_hdl, fsal_verifier_t verifier) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); bool result; /* XXX dang caching FDs? How does it interact with multi-FD */ subcall(result = entry->sub_handle->obj_ops->check_verifier( entry->sub_handle, verifier)); return result; } /** * @brief Get the open status of a file (new style) * * Delegate to sub-FSAL, since this isn't cached metadata currently * * @param[in] obj_hdl Object owning state * @param[in] state Open file state to check * @return Open flags indicating state */ fsal_openflags_t mdcache_status2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); fsal_openflags_t status; subcall(status = entry->sub_handle->obj_ops->status2(entry->sub_handle, state)); return status; } /** * @brief Re-open a file with different flags (new style) * * Delegate to sub-FSAL. This should not be called unless the sub-FSAL supports * reopen2. * * @param[in] obj_hdl Object owning state * @param[in] state Open file state to re-open * @param[in] openflags New open flags * @return FSAL status */ fsal_status_t mdcache_reopen2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); fsal_status_t status; bool truncated = openflags & FSAL_O_TRUNC; subcall(status = entry->sub_handle->obj_ops->reopen2(entry->sub_handle, state, openflags)); if (FSAL_IS_ERROR(status) && (status.major == ERR_FSAL_STALE)) mdcache_kill_entry(entry); if (truncated && !FSAL_IS_ERROR(status)) { atomic_clear_uint32_t_bits(&entry->mde_flags, MDCACHE_TRUST_ATTRS); } return status; } /** * @brief Callback for MDCACHE read calls in MDCACHE context * * Unstack, and call up. * * @param[in] obj Object being acted on * @param[in] ret Return status of call * @param[in] obj_data Data for call * @param[in] caller_data Data for caller */ static void mdc_read_super_cb(struct fsal_obj_handle *obj, fsal_status_t ret, void *obj_data, void *caller_data) { struct mdc_async_arg *arg = caller_data; mdcache_entry_t *entry = container_of(arg->obj_hdl, mdcache_entry_t, obj_handle); /* Fixup FSAL_SHARE_DENIED status */ if (ret.major == ERR_FSAL_SHARE_DENIED) ret = fsalstat(ERR_FSAL_LOCKED, 0); /* * cb will drop the initial ref, take an additional ref to prevent the * entry from getting freed/reaped. */ mdcache_lru_ref(entry, LRU_ACTIVE_REF); arg->cb(arg->obj_hdl, ret, obj_data, arg->cb_arg); if (!FSAL_IS_ERROR(ret)) mdc_set_time_current(&entry->attrs.atime); else if (ret.major == ERR_FSAL_STALE) mdcache_kill_entry(entry); mdcache_lru_unref(entry, LRU_ACTIVE_REF); gsh_free(arg); } /** * @brief Callback for MDCACHE read calls * * Call super callback in MDCACHE context * * @param[in] obj Object being acted on * @param[in] ret Return status of call * @param[in] obj_data Data for call * @param[in] caller_data Data for caller */ static void mdc_read_cb(struct fsal_obj_handle *obj, fsal_status_t ret, void *obj_data, void *caller_data) { supercall(mdc_read_super_cb(obj, ret, obj_data, caller_data);); } /** * @brief Read from a file (new style) * * Delegate to sub-FSAL * * @param[in] obj_hdl File on which to operate * @param[in] bypass If state doesn't indicate a share reservation, * bypass any deny read * @param[in,out] done_cb Callback to call when I/O is done * @param[in,out] read_arg Info about read, passed back in callback * @param[in,out] caller_arg Opaque arg from the caller for callback * * @return Nothing; results are in callback */ void mdcache_read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); struct mdc_async_arg *arg; /* Set up async callback */ arg = gsh_calloc(1, sizeof(*arg)); arg->obj_hdl = obj_hdl; arg->cb = done_cb; arg->cb_arg = caller_arg; subcall(entry->sub_handle->obj_ops->read2(entry->sub_handle, bypass, mdc_read_cb, read_arg, arg)); } /** * @brief Callback for MDCACHE write calls in MDCACHE context * * Unstack, and call up. * * @param[in] obj Object being acted on * @param[in] ret Return status of call * @param[in] obj_data Data for call * @param[in] caller_data Data for caller */ static void mdc_write_super_cb(struct fsal_obj_handle *obj, fsal_status_t ret, void *obj_data, void *caller_data) { struct mdc_async_arg *arg = caller_data; mdcache_entry_t *entry = container_of(arg->obj_hdl, mdcache_entry_t, obj_handle); if (ret.major == ERR_FSAL_STALE) { /* * killing the entry might drop the sentinel ref. Take an * extra ref to prevent this entry for being reused. */ mdcache_lru_ref(entry, LRU_ACTIVE_REF); mdcache_kill_entry(entry); } else { /* * attr_generation must be increased prior to * MDCACHE_TRUST_ATTRS to prevent a case where this flag * was queried before the generation was updated */ atomic_inc_uint32_t(&entry->attr_generation); atomic_clear_uint32_t_bits(&entry->mde_flags, MDCACHE_TRUST_ATTRS); } arg->cb(arg->obj_hdl, ret, obj_data, arg->cb_arg); if (ret.major == ERR_FSAL_STALE) mdcache_lru_unref(entry, LRU_ACTIVE_REF); gsh_free(arg); } /** * @brief Callback for MDCACHE write calls * * Call super callback in MDCACHE context * * @param[in] obj Object being acted on * @param[in] ret Return status of call * @param[in] obj_data Data for call * @param[in] caller_data Data for caller */ static void mdc_write_cb(struct fsal_obj_handle *obj, fsal_status_t ret, void *obj_data, void *caller_data) { supercall(mdc_write_super_cb(obj, ret, obj_data, caller_data);); } /** * @brief Write to a file (new style) * * Delegate to sub-FSAL * * @param[in] obj_hdl Object owning state * @param[in] bypass Bypass any non-mandatory deny write * @param[in,out] done_cb Callback to call when I/O is done * @param[in,out] read_arg Info about read, passed back in callback * @param[in,out] caller_arg Opaque arg from the caller for callback */ void mdcache_write2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *caller_arg) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); struct mdc_async_arg *arg; /* Set up async callback */ arg = gsh_calloc(1, sizeof(*arg)); arg->obj_hdl = obj_hdl; arg->cb = done_cb; arg->cb_arg = caller_arg; subcall(entry->sub_handle->obj_ops->write2( entry->sub_handle, bypass, mdc_write_cb, write_arg, arg)); } /** * @brief Seek within a file (new style) * * Delegate to sub-FSAL * * @param[in] obj_hdl Object owning state * @param[in] state Open file state to seek within * @param[in] info io_info for seek * @return FSAL status */ fsal_status_t mdcache_seek2(struct fsal_obj_handle *obj_hdl, struct state_t *state, struct io_info *info) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); fsal_status_t status; subcall(status = entry->sub_handle->obj_ops->seek2(entry->sub_handle, state, info)); if (status.major == ERR_FSAL_STALE) mdcache_kill_entry(entry); return status; } /** * @brief Advise access pattern for a file (new style) * * Delegate to sub-FSAL * * @param[in] obj_hdl Object owning state * @param[in] state Open file state to advise on * @param[in] hints io_hints containing advice * @return FSAL status */ fsal_status_t mdcache_io_advise2(struct fsal_obj_handle *obj_hdl, struct state_t *state, struct io_hints *hints) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); fsal_status_t status; subcall(status = entry->sub_handle->obj_ops->io_advise2( entry->sub_handle, state, hints)); if (status.major == ERR_FSAL_STALE) mdcache_kill_entry(entry); return status; } /** * @brief Commit to a file (new style) * * Delegate to sub-FSAL * * @param[in] obj_hdl Object to commit * @param[in] offset Offset into file * @param[in] len Length of commit * @return FSAL status */ fsal_status_t mdcache_commit2(struct fsal_obj_handle *obj_hdl, off_t offset, size_t len) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); fsal_status_t status; subcall(status = entry->sub_handle->obj_ops->commit2(entry->sub_handle, offset, len)); if (status.major == ERR_FSAL_STALE) mdcache_kill_entry(entry); else atomic_clear_uint32_t_bits(&entry->mde_flags, MDCACHE_TRUST_ATTRS); return status; } /** * @brief Lock/unlock a range in a file (new style) * * Delegate to sub-FSAL * * @param[in] obj_hdl Object owning state * @param[in] state Open file state to lock/unlock * @param[in] p_owner Private data for lock * @param[in] lock_op Lock operation * @param[in] req_lock Parameters for requested lock * @param[in] conflicting_lock Description of existing conflicting lock * @return FSAL status */ fsal_status_t mdcache_lock_op2(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *p_owner, fsal_lock_op_t lock_op, fsal_lock_param_t *req_lock, fsal_lock_param_t *conflicting_lock) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); fsal_status_t status; subcall(status = entry->sub_handle->obj_ops->lock_op2( entry->sub_handle, state, p_owner, lock_op, req_lock, conflicting_lock)); return status; } /** * @brief Get/Release delegation for a file (new style) * * Delegate to sub-FSAL * * @param[in] obj_hdl Object owning state * @param[in] state Open file state to get/release * @param[in] p_owner Private owner * @param[in] deleg Delegation operation * @return FSAL status */ fsal_status_t mdcache_lease_op2(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *p_owner, fsal_deleg_t deleg) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); fsal_status_t status; subcall(status = entry->sub_handle->obj_ops->lease_op2( entry->sub_handle, state, p_owner, deleg);); return status; } /** * @brief Close a file (new style) * * @param[in] obj_hdl Object owning state * @param[in] state Open file state to close * @return FSAL status */ fsal_status_t mdcache_close2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); fsal_status_t status; subcall(status = entry->sub_handle->obj_ops->close2(entry->sub_handle, state)); if (test_mde_flags(entry, MDCACHE_UNREACHABLE) && !mdc_has_state(entry)) { /* Entry was marked unreachable, and last state is gone */ (void)mdcache_kill_entry(entry); } return status; } fsal_status_t mdcache_fallocate(struct fsal_obj_handle *obj_hdl, struct state_t *state, uint64_t offset, uint64_t length, bool allocate) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); fsal_status_t status; subcall(status = entry->sub_handle->obj_ops->fallocate( entry->sub_handle, state, offset, length, allocate);); if (status.major == ERR_FSAL_STALE) mdcache_kill_entry(entry); else atomic_clear_uint32_t_bits(&entry->mde_flags, MDCACHE_TRUST_ATTRS); return status; } nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_handle.c000066400000000000000000001451611473756622300246760ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2015-2021 Red Hat, Inc. and/or its affiliates. * Author: Daniel Gryniewicz * * This program 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 3 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 * 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 * */ /* handle.c */ #include "config.h" #include "fsal.h" #include /* used for 'dirname' */ #include #include #include #include "gsh_list.h" #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "nfs4_acls.h" #include "nfs_exports.h" #include "sal_functions.h" #include #include "mdcache_lru.h" #include "mdcache_hash.h" #include "mdcache_avl.h" #ifdef USE_MONITORING #include "monitoring.h" #endif /* USE_MONITORING */ /* * handle methods */ /** * Attempts to create a new mdcache handle, or cleanup memory if it fails. * * This function is a wrapper of mdcache_alloc_handle. It adds error checking * and logging. It also cleans objects allocated in the subfsal if it fails. * * @note the caller must hold the content lock on the parent. * * This does not cause an ABBA lock conflict with the potential getattrs * if we lose a race to create the cache entry since our caller CAN NOT hold * any locks on the cache entry created. * * Invalidate can be changed from true to false if mdcache is able to add * the new dirent to a chunk. In this case, the caller MUST refresh the * parent's attributes (we can't do it here due to lock ordering) in a way that * does not invalidate the dirent cache. * * @param[in] export The mdcache export used by the handle. * @param[in,out] sub_handle The handle used by the subfsal. * @param[in] fs The filesystem of the new handle. * @param[in] new_handle Address where the new allocated pointer should * be written. * @param[in] new_directory Indicates a new directory has been created. * @param[in,out] attrs_out Optional attributes for newly created object. * @param[in] parent Parent directory to add dirent to. * @param[in] name Name of the dirent to add. * @param[in,out] invalidate Invalidate parent attr (and chunk cache) * @param[in] state Optional state_t representing open file. * * @note This returns an INITIAL ref'd entry on success * * @return An error code for the function. */ fsal_status_t mdcache_alloc_and_check_handle( struct mdcache_fsal_export *export, struct fsal_obj_handle *sub_handle, struct fsal_obj_handle **new_obj, bool new_directory, struct fsal_attrlist *attrs_in, struct fsal_attrlist *attrs_out, const char *tag, mdcache_entry_t *parent, const char *name, bool *invalidate, struct state_t *state) { fsal_status_t status; mdcache_entry_t *new_entry; status = mdcache_new_entry(export, sub_handle, attrs_in, false, attrs_out, new_directory, &new_entry, state, LRU_ACTIVE_REF | LRU_PROMOTE); if (FSAL_IS_ERROR(status)) { *new_obj = NULL; return status; } LogFullDebug(COMPONENT_MDCACHE, "%sCreated entry %p FSAL %s for %s", tag, new_entry, new_entry->sub_handle->fsal->name, name); if (*invalidate) { /* This function is called after a create, so go ahead * and invalidate the parent directory attributes. */ atomic_clear_uint32_t_bits(&parent->mde_flags, MDCACHE_TRUST_ATTRS); } if (mdcache_param.dir.avl_chunk != 0) { /* Add this entry to the directory (also takes an internal ref) */ status = mdcache_dirent_add(parent, name, new_entry, invalidate); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_MDCACHE, "%s%s failed because add dirent failed", tag, name); mdcache_lru_unref(new_entry, LRU_ACTIVE_REF); *new_obj = NULL; return status; } } if (new_entry->obj_handle.type == DIRECTORY) { /* Insert Parent's key */ PTHREAD_RWLOCK_wrlock(&new_entry->content_lock); mdc_dir_add_parent(new_entry, parent); PTHREAD_RWLOCK_unlock(&new_entry->content_lock); } *new_obj = &new_entry->obj_handle; if (attrs_out != NULL) { LogAttrlist(COMPONENT_MDCACHE, NIV_FULL_DEBUG, tag, attrs_out, true); } return status; } /** * @brief Lookup a name * * Lookup a name relative to another object * * @param[in] parent Handle of parent * @param[in] name Name to look up * @param[out] handle Handle of found object, on success * @param[in,out] attrs_out Optional attributes for newly created object * * @note This returns an INITIAL ref'd entry on success * @return FSAL status */ static fsal_status_t mdcache_lookup(struct fsal_obj_handle *parent, const char *name, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { mdcache_entry_t *mdc_parent = container_of(parent, mdcache_entry_t, obj_handle); mdcache_entry_t *entry = NULL; fsal_status_t status; *handle = NULL; status = mdc_lookup(mdc_parent, name, true, &entry, attrs_out); if (entry) *handle = &entry->obj_handle; return status; } /** * @brief Make a directory * * @param[in] dir_hdl Parent directory handle * @param[in] name Name for new directory * @param[in] attrib Attributes for new directory * @param[out] handle Resulting handle on success * * @note This returns an INITIAL ref'd entry on success * @return FSAL status */ static fsal_status_t mdcache_mkdir(struct fsal_obj_handle *dir_hdl, const char *name, struct fsal_attrlist *attrib, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { mdcache_entry_t *parent = container_of(dir_hdl, mdcache_entry_t, obj_handle); struct mdcache_fsal_export *export = mdc_cur_export(); struct fsal_obj_handle *sub_handle; fsal_status_t status; struct fsal_attrlist attrs; bool invalidate = true; *handle = NULL; /* Ask for all supported attributes except ACL (we defer fetching ACL * until asked for it (including a permission check). */ fsal_prepare_attrs(&attrs, op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) & ~ATTR_ACL); subcall_raw(export, status = parent->sub_handle->obj_ops->mkdir( parent->sub_handle, name, attrib, &sub_handle, &attrs, parent_pre_attrs_out, parent_post_attrs_out)); if (unlikely(FSAL_IS_ERROR(status))) { LogDebug(COMPONENT_MDCACHE, "mkdir %s failed with %s", name, fsal_err_txt(status)); if (status.major == ERR_FSAL_STALE) { /* If we got ERR_FSAL_STALE, the previous FSAL call * must have failed with a bad parent. */ LogEvent(COMPONENT_MDCACHE, "FSAL returned STALE on mkdir"); mdcache_kill_entry(parent); } *handle = NULL; fsal_release_attrs(&attrs); return status; } PTHREAD_RWLOCK_wrlock(&parent->content_lock); status = mdcache_alloc_and_check_handle(export, sub_handle, handle, true, &attrs, attrs_out, "mkdir ", parent, name, &invalidate, NULL); PTHREAD_RWLOCK_unlock(&parent->content_lock); fsal_release_attrs(&attrs); if (FSAL_IS_SUCCESS(status) && !invalidate) { /* Refresh destination directory attributes without * invalidating dirents. */ status = mdcache_refresh_attrs_no_invalidate(parent); } return status; } /** * @brief Make a device node * * @param[in] dir_hdl Parent directory handle * @param[in] name Name of new node * @param[in] nodetype Type of new node * @param[in] attrib Attributes for new node * @param[out] handle New object handle on success * @param[in,out] attrs_out Optional attributes for newly created * object * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @note This returns an INITIAL ref'd entry on success * @return FSAL status */ static fsal_status_t mdcache_mknode(struct fsal_obj_handle *dir_hdl, const char *name, object_file_type_t nodetype, struct fsal_attrlist *attrib, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { mdcache_entry_t *parent = container_of(dir_hdl, mdcache_entry_t, obj_handle); struct mdcache_fsal_export *export = mdc_cur_export(); struct fsal_obj_handle *sub_handle; fsal_status_t status; struct fsal_attrlist attrs; bool invalidate = true; *handle = NULL; /* Ask for all supported attributes except ACL (we defer fetching ACL * until asked for it (including a permission check). */ fsal_prepare_attrs(&attrs, op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) & ~ATTR_ACL); subcall_raw(export, status = parent->sub_handle->obj_ops->mknode( parent->sub_handle, name, nodetype, attrib, &sub_handle, &attrs, parent_pre_attrs_out, parent_post_attrs_out)); if (unlikely(FSAL_IS_ERROR(status))) { LogDebug(COMPONENT_MDCACHE, "mknod %s failed with %s", name, fsal_err_txt(status)); if (status.major == ERR_FSAL_STALE) { /* If we got ERR_FSAL_STALE, the previous FSAL call * must have failed with a bad parent. */ LogEvent(COMPONENT_MDCACHE, "FSAL returned STALE on mknod"); mdcache_kill_entry(parent); } *handle = NULL; fsal_release_attrs(&attrs); return status; } PTHREAD_RWLOCK_wrlock(&parent->content_lock); status = mdcache_alloc_and_check_handle(export, sub_handle, handle, false, &attrs, attrs_out, "mknode ", parent, name, &invalidate, NULL); PTHREAD_RWLOCK_unlock(&parent->content_lock); fsal_release_attrs(&attrs); if (FSAL_IS_SUCCESS(status) && !invalidate) { /* Refresh destination directory attributes without * invalidating dirents. */ status = mdcache_refresh_attrs_no_invalidate(parent); } return status; } /** * @brief Make a symlink * * @param[in] dir_hdl Parent directory handle * @param[in] name Name of new node * @param[in] link_path Contents of symlink * @param[in] attrib Attributes for new simlink * @param[out] handle New object handle on success * @param[in,out] attrs_out Optional attributes for newly created * object * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @note This returns an INITIAL ref'd entry on success * @return FSAL status */ static fsal_status_t mdcache_symlink(struct fsal_obj_handle *dir_hdl, const char *name, const char *link_path, struct fsal_attrlist *attrib, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { mdcache_entry_t *parent = container_of(dir_hdl, mdcache_entry_t, obj_handle); struct mdcache_fsal_export *export = mdc_cur_export(); struct fsal_obj_handle *sub_handle; fsal_status_t status; struct fsal_attrlist attrs; bool invalidate = true; *handle = NULL; /* Ask for all supported attributes except ACL (we defer fetching ACL * until asked for it (including a permission check). */ fsal_prepare_attrs(&attrs, op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) & ~ATTR_ACL); subcall_raw(export, status = parent->sub_handle->obj_ops->symlink( parent->sub_handle, name, link_path, attrib, &sub_handle, &attrs, parent_pre_attrs_out, parent_post_attrs_out)); if (unlikely(FSAL_IS_ERROR(status))) { LogDebug(COMPONENT_MDCACHE, "symlink %s failed with %s", name, fsal_err_txt(status)); if (status.major == ERR_FSAL_STALE) { /* If we got ERR_FSAL_STALE, the previous FSAL call * must have failed with a bad parent. */ LogEvent(COMPONENT_MDCACHE, "FSAL returned STALE on symlink"); mdcache_kill_entry(parent); } *handle = NULL; fsal_release_attrs(&attrs); return status; } PTHREAD_RWLOCK_wrlock(&parent->content_lock); status = mdcache_alloc_and_check_handle(export, sub_handle, handle, false, &attrs, attrs_out, "symlink ", parent, name, &invalidate, NULL); PTHREAD_RWLOCK_unlock(&parent->content_lock); fsal_release_attrs(&attrs); if (FSAL_IS_SUCCESS(status) && !invalidate) { /* Refresh destination directory attributes without * invalidating dirents. */ status = mdcache_refresh_attrs_no_invalidate(parent); } return status; } /** * @brief Read a symlink * * @param[in] obj_hdl Handle for symlink * @param[out] link_content Buffer to fill with link contents * @param[in] refresh If true, refresh attributes on symlink * @return FSAL status */ static fsal_status_t mdcache_readlink(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *link_content, bool refresh) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); fsal_status_t status; PTHREAD_RWLOCK_rdlock(&entry->content_lock); if (!refresh && !test_mde_flags(entry, MDCACHE_TRUST_CONTENT)) { /* Our data are stale. Drop the lock, get a write-lock, load in new data, and copy it out to the caller. */ PTHREAD_RWLOCK_unlock(&entry->content_lock); PTHREAD_RWLOCK_wrlock(&entry->content_lock); /* Make sure nobody updated the content while we were waiting. */ refresh = !test_mde_flags(entry, MDCACHE_TRUST_CONTENT); } subcall(status = entry->sub_handle->obj_ops->readlink( entry->sub_handle, link_content, refresh)); if (refresh && !(FSAL_IS_ERROR(status))) atomic_set_uint32_t_bits(&entry->mde_flags, MDCACHE_TRUST_CONTENT); PTHREAD_RWLOCK_unlock(&entry->content_lock); return status; } /** * @brief Create a hard link * * @param[in] obj_hdl Object to link to. * @param[in] destdir_hdl Destination directory into which to * link * @param[in] name Name of new link * @param[in,out] destdir_pre_attrs_out Optional attributes for destdir dir * before the operation. Should be atomic. * @param[in,out] destdir_post_attrs_out Optional attributes for destdir dir * after the operation. Should be atomic. * * @return FSAL status */ static fsal_status_t mdcache_link(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *destdir_hdl, const char *name, struct fsal_attrlist *destdir_pre_attrs_out, struct fsal_attrlist *destdir_post_attrs_out) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); mdcache_entry_t *dest = container_of(destdir_hdl, mdcache_entry_t, obj_handle); fsal_status_t status; bool invalidate = true; subcall(status = entry->sub_handle->obj_ops->link( entry->sub_handle, dest->sub_handle, name, destdir_pre_attrs_out, destdir_post_attrs_out)); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_MDCACHE, "link failed %s", fsal_err_txt(status)); return status; } if (mdcache_param.dir.avl_chunk != 0) { PTHREAD_RWLOCK_wrlock(&dest->content_lock); /* Add this entry to the directory (also takes an internal ref) */ status = mdcache_dirent_add(dest, name, entry, &invalidate); PTHREAD_RWLOCK_unlock(&dest->content_lock); } /* Invalidate attributes, so refresh will be forced */ atomic_clear_uint32_t_bits(&entry->mde_flags, MDCACHE_TRUST_ATTRS); if (FSAL_IS_SUCCESS(status) && !invalidate) { /* Refresh destination directory attributes without * invalidating dirents. */ status = mdcache_refresh_attrs_no_invalidate(dest); } return status; } /** * Read the contents of a directory * * If necessary, populate the dirent cache from the underlying FSAL. Then, walk * the dirent cache calling the callback. * * @note The object passed into the callback is ref'd and must be unref'd by the * callback. * * @param[in] dir_hdl the directory to read * @param[in] whence where to start (next) * @param[in] dir_state pass thru of state to callback * @param[in] cb callback function * @param[in] attrmask Which attributes to fill * @param[out] eod_met eod marker true == end of dir * * @return FSAL status */ static fsal_status_t mdcache_readdir(struct fsal_obj_handle *dir_hdl, fsal_cookie_t *whence, void *dir_state, fsal_readdir_cb cb, attrmask_t attrmask, bool *eod_met) { mdcache_entry_t *directory = container_of(dir_hdl, mdcache_entry_t, obj_handle); if (!(directory->obj_handle.type == DIRECTORY)) return fsalstat(ERR_FSAL_NOTDIR, 0); if (mdcache_param.dir.avl_chunk == 0) { /* Not caching dirents; pass through directly to FSAL */ return mdcache_readdir_uncached(directory, whence, dir_state, cb, attrmask, eod_met); } else { /* Dirent chunking is enabled. */ LogDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Calling mdcache_readdir_chunked whence=%" PRIx64, whence ? *whence : (uint64_t)0); return mdcache_readdir_chunked(directory, whence ? *whence : (uint64_t)0, dir_state, cb, attrmask, eod_met); } } /** * @brief Check access for a given user against a given object * * Currently, all FSALs use the default method. We call the default method * directly, so that the test uses cached attributes, rather than having the * lower level need to query attributes each call. This works as long as all * FSALs call the default method. This should be revisited if a FSAL wants to * override test_access(). * * @note If @a owner_skip is provided, and * mdcache_param.use_cached_owner_on_owner_override is true, we test against the * cached owner. This is because doing a getattrs() potentially on each read * and write (writes invalidate cached attributes) is a huge performance hit. * Eventually, finer grained attribute validity would be a better solution * * @param[in] obj_hdl Handle to check * @param[in] access_type Access requested * @param[out] allowed Returned access that could be granted * @param[out] denied Returned access that would be granted * @param[in] owner_skip Skip test if op_ctx->creds is owner * * @return FSAL status. */ static fsal_status_t mdcache_test_access(struct fsal_obj_handle *obj_hdl, fsal_accessflags_t access_type, fsal_accessflags_t *allowed, fsal_accessflags_t *denied, bool owner_skip) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); if (owner_skip && entry->attrs.owner == op_ctx->creds.caller_uid && mdcache_param.use_cached_owner_on_owner_override) return fsalstat(ERR_FSAL_NO_ERROR, 0); return fsal_test_access(obj_hdl, access_type, allowed, denied, owner_skip); } /** * @brief Rename an object * * Rename the given object from @a old_name in @a olddir_hdl to @a new_name in * @a newdir_hdl. The old and new directories may be the same. * * @param[in] obj_hdl Object to rename * @param[in] olddir_hdl Directory containing @a obj_hdl * @param[in] old_name Current name of @a obj_hdl * @param[in] newdir_hdl Directory to move @a obj_hdl to * @param[in] new_name Name to rename @a obj_hdl to * @return FSAL status */ static fsal_status_t mdcache_rename(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *olddir_hdl, const char *old_name, struct fsal_obj_handle *newdir_hdl, const char *new_name, struct fsal_attrlist *olddir_pre_attrs_out, struct fsal_attrlist *olddir_post_attrs_out, struct fsal_attrlist *newdir_pre_attrs_out, struct fsal_attrlist *newdir_post_attrs_out) { mdcache_entry_t *mdc_olddir = container_of(olddir_hdl, mdcache_entry_t, obj_handle); mdcache_entry_t *mdc_newdir = container_of(newdir_hdl, mdcache_entry_t, obj_handle); mdcache_entry_t *mdc_obj = container_of(obj_hdl, mdcache_entry_t, obj_handle); mdcache_entry_t *mdc_lookup_dst = NULL; struct fsal_export *sub_export = op_ctx->fsal_export->sub_export; bool refresh = false; bool rename_change_key; fsal_status_t status = { 0, 0 }; status = mdc_lookup(mdc_newdir, new_name, true, &mdc_lookup_dst, NULL); if (!FSAL_IS_ERROR(status)) { if ((mdc_obj == mdc_lookup_dst) && !strcmp(old_name, new_name) && olddir_hdl->obj_ops->handle_cmp(olddir_hdl, newdir_hdl)) { LogDebug( COMPONENT_MDCACHE, "Rename (%p,%s)->(%p,%s): same source and destination", mdc_olddir, old_name, mdc_newdir, new_name); /* Same source and destination */ goto out; } if (obj_is_junction(&mdc_lookup_dst->obj_handle)) { LogDebug( COMPONENT_MDCACHE, "Rename (%p,%s)->(%p,%s): Cannot rename on top of junction", mdc_olddir, old_name, mdc_newdir, new_name); /* Cannot rename on top of junction */ status = fsalstat(ERR_FSAL_XDEV, 0); goto out; } if (state_deleg_conflict(&mdc_lookup_dst->obj_handle, true)) { LogDebug(COMPONENT_MDCACHE, "Found an existing delegation for %s", new_name); status = fsalstat(ERR_FSAL_DELAY, 0); goto out; } } /* Now update cached dirents. Must take locks in the correct order */ mdcache_src_dest_lock(mdc_olddir, mdc_newdir); subcall(status = mdc_olddir->sub_handle->obj_ops->rename( mdc_obj->sub_handle, mdc_olddir->sub_handle, old_name, mdc_newdir->sub_handle, new_name, olddir_pre_attrs_out, olddir_post_attrs_out, newdir_pre_attrs_out, newdir_post_attrs_out)); if (FSAL_IS_ERROR(status)) goto unlock; if (mdc_lookup_dst != NULL) { /* Mark target file attributes as invalid */ atomic_clear_uint32_t_bits(&mdc_lookup_dst->mde_flags, MDCACHE_TRUST_ATTRS); } /* Mark renamed file attributes as invalid */ atomic_clear_uint32_t_bits(&mdc_obj->mde_flags, MDCACHE_TRUST_ATTRS); /* Mark directory attributes as invalid */ atomic_clear_uint32_t_bits(&mdc_olddir->mde_flags, MDCACHE_TRUST_ATTRS); if (olddir_hdl != newdir_hdl) { atomic_clear_uint32_t_bits(&mdc_newdir->mde_flags, MDCACHE_TRUST_ATTRS); } /* NOTE: Below we mostly don't check if the directory is not * cached. The cache manipulation functions we call already * bail out if we aren't cached. However, for rename into a * new directory, we need to bypass if not cached even if * chunking is enabled, so we check in that case to make * chunk management simpler. */ if (mdc_lookup_dst) { /* Remove the entry from parent dir_entries avl */ mdcache_dirent_remove(mdc_newdir, new_name); /* Mark unreachable */ mdc_unreachable(mdc_lookup_dst); } subcall(rename_change_key = sub_export->exp_ops.fs_supports( sub_export, fso_rename_changes_key)); if (rename_change_key) { LogDebug(COMPONENT_MDCACHE, "Rename (%p,%s)->(%p,%s) : key changing", mdc_olddir, old_name, mdc_newdir, new_name); /* FSAL changes keys on rename. Just remove the dirent(s) */ /* Old dirent first */ mdcache_dirent_remove(mdc_olddir, old_name); /** @todo: With chunking and compute cookie, we can actually * figure out which chunk the new dirent belongs to * without a lookup, so we could just invalidate that * chunk and the rest of the directory can remain * cached. */ /* Now new directory. Here, we just need to invalidate dirents, * since we have a known missing dirent */ mdcache_dirent_invalidate_all(mdc_newdir); /* Handle key is changing. This means the old handle is * useless. Mark it unreachable, forcing a lookup next time */ mdc_unreachable(mdc_obj); } else if (mdcache_param.dir.avl_chunk != 0) { bool invalidate = true; LogDebug(COMPONENT_MDCACHE, "Rename (%p,%s)->(%p,%s) : moving entry", mdc_olddir, old_name, mdc_newdir, new_name); /* Remove the old entry */ mdcache_dirent_remove(mdc_olddir, old_name); /* We may have a cache entry for the destination * filename. If we do, we must delete it : it is stale. */ mdcache_dirent_remove(mdc_newdir, new_name); status = mdcache_dirent_add(mdc_newdir, new_name, mdc_obj, &invalidate); if (FSAL_IS_ERROR(status)) { /* We're obviously out of date. Throw out the cached directory */ LogCrit(COMPONENT_MDCACHE, "Add dirent returned %s", fsal_err_txt(status)); /* Protected by mdcache_src_dst_lock() above */ mdcache_dirent_invalidate_all(mdc_newdir); } else if (!invalidate) { /* Refresh destination directory attributes without * invalidating dirents. */ refresh = true; } } unlock: /* unlock entries */ mdcache_src_dest_unlock(mdc_olddir, mdc_newdir); out: /* Refresh, if necessary. Must be done without lock held */ if (FSAL_IS_SUCCESS(status)) { /* If we're moving a directory out, update parent hash. * Since we already dropped the src_desk lock, things * may have changed, so just free the parent fh. */ if (mdc_olddir != mdc_newdir && obj_hdl->type == DIRECTORY) { PTHREAD_RWLOCK_wrlock(&mdc_obj->content_lock); mdcache_free_fh(&mdc_obj->fsobj.fsdir.parent); PTHREAD_RWLOCK_unlock(&mdc_obj->content_lock); } if (refresh) status = mdcache_refresh_attrs_no_invalidate(mdc_newdir); } if (mdc_lookup_dst) mdcache_lru_unref(mdc_lookup_dst, LRU_ACTIVE_REF); return status; } /** * @brief Refresh the attributes for an mdcache entry. * * The caller must also call mdcache_kill_entry after releasing the * attr_lock if ERR_FSAL_STALE is returned. * * @note The caller must hold the attribute lock for WRITE * * @param[in] entry The mdcache entry to refresh attributes for. * @param[in] need_acl Indicates if the ACL needs updating. * @param[in] need_fslocations Indicates if the fslocations are needed. * @param[in] need_seclabel Indicates if security label is needed. * @param[in/out] invalidate Caller wants to know if it should Invalidate the * dirent cache. */ fsal_status_t mdcache_refresh_attrs(mdcache_entry_t *entry, bool need_acl, bool need_fslocations, bool need_seclabel, bool *invalidate) { struct fsal_attrlist attrs; fsal_status_t status = { 0, 0 }; struct timespec oldmtime; bool file_deleg = false; cbgetattr_t *cbgetattr; uint32_t original_generation; /* Assume no invalidation. */ if (invalidate != NULL) *invalidate = false; /* Use this to detect if we should invalidate a directory. */ oldmtime = entry->attrs.mtime; file_deleg = (entry->obj_handle.state_hdl && entry->obj_handle.state_hdl->file.fdeleg_stats .fds_curr_delegations); /* We always ask for all regular attributes, even if the caller was * only interested in the ACL unless the file is delegated. */ fsal_prepare_attrs(&attrs, op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) | ATTR_RDATTR_ERR); if (!need_acl) { /* Don't request the ACL if not necessary. */ attrs.request_mask &= ~ATTR_ACL; } if (!need_fslocations) { /* Don't request FS LOCATIONS if not required */ attrs.request_mask &= ~ATTR4_FS_LOCATIONS; } if (!need_seclabel) { attrs.request_mask &= ~ATTR4_SEC_LABEL; } if (file_deleg && entry->attrs.expire_time_attr) { /* If the file is delegated, then we can trust * the attributes already fetched (i.e, which * are in entry->attrs.valid_mask). Hence mask * them out. */ attrs.request_mask = (attrs.request_mask & ~entry->attrs.valid_mask); /* Bail out if ATTR_RDATTR_ERR is the only remaining * attr set */ if ((attrs.request_mask & ~ATTR_RDATTR_ERR) == 0) goto out; } /* We will want all the requested attributes in the entry */ entry->attrs.request_mask = attrs.request_mask; if (entry->attrs.acl != NULL) { /* request_mask & ATTR_ACL must match attrs.acl */ entry->attrs.request_mask |= ATTR_ACL; } if (entry->attrs.fs_locations != NULL) { /* request_mask & ATTR_FS_LOCATIONS must match * attrs.fs_locations */ entry->attrs.request_mask |= ATTR4_FS_LOCATIONS; } if (entry->attrs.sec_label.slai_data.slai_data_val != NULL) { /* request_mask & ATTR_ACL must match * attrs.sec_label.slai_data.slai_data_val */ entry->attrs.request_mask |= ATTR4_SEC_LABEL; } original_generation = atomic_fetch_int32_t(&entry->attr_generation); subcall(status = entry->sub_handle->obj_ops->getattrs(entry->sub_handle, &attrs)); if (FSAL_IS_ERROR(status)) { /* Done with the attrs */ fsal_release_attrs(&attrs); return status; } mdc_update_attr_cache(entry, &attrs); if (atomic_fetch_int32_t(&entry->attr_generation) != original_generation) { atomic_clear_uint32_t_bits(&entry->mde_flags, MDCACHE_TRUST_ATTRS); } out: /* Done with the attrs (we didn't need to call this since the * fsal_copy_attrs preceding consumed all the references, but we * release them anyway to make it easy to scan the code for correctness. */ fsal_release_attrs(&attrs); /* Always save copy of latest change and filesize * to compare with values returned in cbgetattr response */ if (file_deleg) { cbgetattr = &entry->obj_handle.state_hdl->file.cbgetattr; cbgetattr->change = entry->attrs.change; cbgetattr->filesize = entry->attrs.filesize; } LogAttrlist(COMPONENT_MDCACHE, NIV_FULL_DEBUG, "attrs ", &entry->attrs, true); if (invalidate != NULL && entry->obj_handle.type == DIRECTORY && gsh_time_cmp(&oldmtime, &entry->attrs.mtime) != 0) { /* Request caller to invalidate */ *invalidate = true; } return status; } /** * @brief Get the attributes for an object * * If the attribute cache is valid, just return them. Otherwise, resfresh the * cache. * * @param[in] obj_hdl Object to get attributes from * @param[in,out] attrs_out Attributes fetched * @return FSAL status */ static fsal_status_t mdcache_getattrs(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs_out) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); fsal_status_t status = { 0, 0 }; bool invalidate = false; #ifdef USE_MONITORING const char *OPERATION = "getattr"; struct fsal_export *export = op_ctx->fsal_export; const uint16_t export_id = (export == NULL ? 0 : export->export_id); #endif /* USE_MONITORING */ if (op_ctx->export_perms.expire_time_attr == 0) { /* Attribute caching is disabled. No reason to lock the entry * and serialize getattr access. Use subcall directly */ #ifdef USE_MONITORING monitoring__dynamic_mdcache_cache_miss(OPERATION, export_id); #endif /* USE_MONITORING */ subcall(status = entry->sub_handle->obj_ops->getattrs( entry->sub_handle, attrs_out);); LogAttrlist(COMPONENT_MDCACHE, NIV_FULL_DEBUG, "attrs ", attrs_out, true); return status; } PTHREAD_RWLOCK_rdlock(&entry->attr_lock); if (mdcache_is_attrs_valid(entry, attrs_out->request_mask)) { /* Up-to-date */ #ifdef USE_MONITORING monitoring__dynamic_mdcache_cache_hit(OPERATION, export_id); #endif /* USE_MONITORING */ goto unlock; } /* Promote to write lock */ PTHREAD_RWLOCK_unlock(&entry->attr_lock); PTHREAD_RWLOCK_wrlock(&entry->attr_lock); if (mdcache_is_attrs_valid(entry, attrs_out->request_mask)) { /* Someone beat us to it */ #ifdef USE_MONITORING monitoring__dynamic_mdcache_cache_hit(OPERATION, export_id); #endif /* USE_MONITORING */ goto unlock; } #ifdef USE_MONITORING monitoring__dynamic_mdcache_cache_miss(OPERATION, export_id); #endif /* USE_MONITORING */ status = mdcache_refresh_attrs( entry, (attrs_out->request_mask & ATTR_ACL) != 0, (attrs_out->request_mask & ATTR4_FS_LOCATIONS) != 0, (attrs_out->request_mask & ATTR4_SEC_LABEL) != 0, &invalidate); if (FSAL_IS_ERROR(status)) { /* We failed to fetch any attributes. Pass that fact back to * the caller. We do not change the validity of the current * entry attributes. */ if (attrs_out->request_mask & ATTR_RDATTR_ERR) attrs_out->valid_mask = ATTR_RDATTR_ERR; goto unlock_no_attrs; } unlock: /* Struct copy */ fsal_copy_attrs(attrs_out, &entry->attrs, false); unlock_no_attrs: PTHREAD_RWLOCK_unlock(&entry->attr_lock); if (invalidate) { PTHREAD_RWLOCK_wrlock(&entry->content_lock); mdcache_dirent_invalidate_all(entry); PTHREAD_RWLOCK_unlock(&entry->content_lock); } if (FSAL_IS_ERROR(status) && (status.major == ERR_FSAL_STALE)) mdcache_kill_entry(entry); LogAttrlist(COMPONENT_MDCACHE, NIV_FULL_DEBUG, "attrs ", attrs_out, true); return status; } /** * @brief Set attributes on an object (new style) * * @param[in] obj_hdl Object owning state * @param[in] state Open file state to set attributes on * @param[in] attrs Attributes to set * @return FSAL status */ static fsal_status_t mdcache_setattr2(struct fsal_obj_handle *obj_hdl, bool bypass, struct state_t *state, struct fsal_attrlist *attrs) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); fsal_status_t status, status2; uint64_t change; bool need_acl = false, kill_entry = false; change = entry->attrs.change; subcall(status = entry->sub_handle->obj_ops->setattr2( entry->sub_handle, bypass, state, attrs)); if (FSAL_IS_ERROR(status)) { if (status.major == ERR_FSAL_STALE) kill_entry = true; goto out; } if (op_ctx->export_perms.expire_time_attr == 0) { /* Attribute caching is disabled. No need to refresh the * attributes */ goto out; } /* In case of ACL enabled, any of the below attribute changes * result in change of ACL set as well. */ if (!op_ctx_export_has_option(EXPORT_OPTION_DISABLE_ACL) && (FSAL_TEST_MASK(attrs->valid_mask, ATTR_MODE | ATTR_OWNER | ATTR_GROUP | ATTR_ACL))) { need_acl = true; } PTHREAD_RWLOCK_wrlock(&entry->attr_lock); status2 = mdcache_refresh_attrs(entry, need_acl, false, false, NULL); if (FSAL_IS_ERROR(status2)) { /* Assume that the cache is bogus now */ atomic_clear_uint32_t_bits(&entry->mde_flags, MDCACHE_TRUST_ATTRS | MDCACHE_TRUST_ACL | MDCACHE_TRUST_FS_LOCATIONS | MDCACHE_TRUST_SEC_LABEL); if (status2.major == ERR_FSAL_STALE) kill_entry = true; } else if (change == entry->attrs.change) { LogDebug( COMPONENT_MDCACHE, "setattr2 did not change attribute before %lld after = %lld", (long long)change, (long long)entry->attrs.change); entry->attrs.change = change + 1; } PTHREAD_RWLOCK_unlock(&entry->attr_lock); out: if (kill_entry) mdcache_kill_entry(entry); return status; } /** * @brief Unlink an object * * Does some junction handling * * @param[in] dir_hdl Parent directory handle * @param[in] obj_hdl Object being removed * @param[in] name Name of object to remove * @return FSAL status */ static fsal_status_t mdcache_unlink(struct fsal_obj_handle *dir_hdl, struct fsal_obj_handle *obj_hdl, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { mdcache_entry_t *parent = container_of(dir_hdl, mdcache_entry_t, obj_handle); mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); fsal_status_t status; LogFullDebug(COMPONENT_MDCACHE, "Unlink %p/%s (%p)", parent, name, entry); if (obj_is_junction(&entry->obj_handle)) { /* Cannot remove a junction */ return fsalstat(ERR_FSAL_XDEV, 0); } subcall(status = parent->sub_handle->obj_ops->unlink( parent->sub_handle, entry->sub_handle, name, parent_pre_attrs_out, parent_post_attrs_out)); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_MDCACHE, "unlink %s returned %s", name, fsal_err_txt(status)); if (status.major == ERR_FSAL_STALE) (void)mdcache_kill_entry(parent); else if (status.major == ERR_FSAL_NOTEMPTY && (obj_hdl->type == DIRECTORY)) { PTHREAD_RWLOCK_wrlock(&entry->content_lock); mdcache_dirent_invalidate_all(entry); PTHREAD_RWLOCK_unlock(&entry->content_lock); } else { /* Some real error. Bail. */ return status; } } else { PTHREAD_RWLOCK_wrlock(&parent->content_lock); mdcache_dirent_remove(parent, name); PTHREAD_RWLOCK_unlock(&parent->content_lock); /* Invalidate attributes of parent and entry */ atomic_clear_uint32_t_bits(&parent->mde_flags, MDCACHE_TRUST_ATTRS); atomic_clear_uint32_t_bits(&entry->mde_flags, MDCACHE_TRUST_ATTRS); if (entry->obj_handle.type == DIRECTORY) { PTHREAD_RWLOCK_wrlock(&entry->content_lock); mdcache_free_fh(&entry->fsobj.fsdir.parent); PTHREAD_RWLOCK_unlock(&entry->content_lock); } mdc_unreachable(entry); } LogFullDebug(COMPONENT_MDCACHE, "Unlink %s %p/%s (%p)", FSAL_IS_ERROR(status) ? "failed" : "done", parent, name, entry); return status; } /** * @brief Get the wire version of a handle * * Just pass through to the underlying FSAL * * @param[in] obj_hdl Handle to digest * @param[in] out_type Type of digest to get * @param[out] fh_desc Buffer to write digest into * @return FSAL status */ static fsal_status_t mdcache_handle_to_wire(const struct fsal_obj_handle *obj_hdl, fsal_digesttype_t out_type, struct gsh_buffdesc *fh_desc) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); fsal_status_t status; subcall(status = entry->sub_handle->obj_ops->handle_to_wire( entry->sub_handle, out_type, fh_desc)); return status; } /** * @brief Get the unique key for a handle * * Just pass through to the underlying FSAL * * @param[in] obj_hdl Handle to digest * @param[out] fh_desc Buffer to write key into */ static void mdcache_handle_to_key(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *fh_desc) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); subcall(entry->sub_handle->obj_ops->handle_to_key(entry->sub_handle, fh_desc)); } /** * @brief Compare two handles * * All FSALs currently use the default, but delegate in case a FSAL wants to * override. * * @param[in] obj_hdl1 The first handle to compare * @param[in] obj_hdl2 The second handle to compare * * @return True if match, false otherwise */ static bool mdcache_handle_cmp(struct fsal_obj_handle *obj_hdl1, struct fsal_obj_handle *obj_hdl2) { mdcache_entry_t *entry1 = container_of(obj_hdl1, mdcache_entry_t, obj_handle); mdcache_entry_t *entry2 = container_of(obj_hdl2, mdcache_entry_t, obj_handle); bool status; subcall(status = entry1->sub_handle->obj_ops->handle_cmp( entry1->sub_handle, entry2->sub_handle)); return status; } /** * @brief Grant a layout segment. * * Delegate to sub-FSAL * * @param[in] obj_hdl The handle of the file on which the layout is * requested. * @param[out] loc_body An XDR stream to which the FSAL must encode * the layout specific portion of the granted * layout segment. * @param[in] arg Input arguments of the function * @param[in,out] res In/out and output arguments of the function * * @return Valid error codes in RFC 5661, pp. 366-7. */ static nfsstat4 mdcache_layoutget(struct fsal_obj_handle *obj_hdl, XDR *loc_body, const struct fsal_layoutget_arg *arg, struct fsal_layoutget_res *res) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); nfsstat4 status; subcall(status = entry->sub_handle->obj_ops->layoutget( entry->sub_handle, loc_body, arg, res)); return status; } /** * @brief Potentially return one layout segment * * Delegate to sub-FSAL * * @param[in] obj_hdl The object on which a segment is to be returned * @param[in] lrf_body In the case of a non-synthetic return, this is * an XDR stream corresponding to the layout * type-specific argument to LAYOUTRETURN. In * the case of a synthetic or bulk return, * this is a NULL pointer. * @param[in] arg Input arguments of the function * * @return Valid error codes in RFC 5661, p. 367. */ static nfsstat4 mdcache_layoutreturn(struct fsal_obj_handle *obj_hdl, XDR *lrf_body, const struct fsal_layoutreturn_arg *arg) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); nfsstat4 status; subcall(status = entry->sub_handle->obj_ops->layoutreturn( entry->sub_handle, lrf_body, arg)); return status; } /** * @brief Commit a segment of a layout * * Delegate to sub-FSAL * * @param[in] obj_hdl The object on which to commit * @param[in] lou_body An XDR stream containing the layout * type-specific portion of the LAYOUTCOMMIT * arguments. * @param[in] arg Input arguments of the function * @param[in,out] res In/out and output arguments of the function * * @return Valid error codes in RFC 5661, p. 366. */ static nfsstat4 mdcache_layoutcommit(struct fsal_obj_handle *obj_hdl, XDR *lou_body, const struct fsal_layoutcommit_arg *arg, struct fsal_layoutcommit_res *res) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); nfsstat4 status; subcall(status = entry->sub_handle->obj_ops->layoutcommit( entry->sub_handle, lou_body, arg, res)); if (status == NFS4_OK) atomic_clear_uint32_t_bits(&entry->mde_flags, MDCACHE_TRUST_ATTRS); return status; } /** * @brief Get a reference to the handle * * @param[in] obj_hdl Handle to ref */ static void mdcache_get_ref(struct fsal_obj_handle *obj_hdl) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); mdcache_lru_ref(entry, LRU_ACTIVE_REF | LRU_PROMOTE); } /** * @brief Put a reference to the handle * * @param[in] obj_hdl Handle to unref */ static void mdcache_put_ref(struct fsal_obj_handle *obj_hdl) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); mdcache_lru_unref(entry, LRU_ACTIVE_REF); } /** * @brief Release an object handle * * This force cleans-up. * * @param[in] obj_hdl Handle to release * @return FSAL status */ static void mdcache_hdl_release(struct fsal_obj_handle *obj_hdl) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); LogDebug(COMPONENT_MDCACHE, "Releasing obj_hdl=%p, entry=%p", obj_hdl, entry); mdcache_kill_entry(entry); } /** * @brief Merge a duplicate handle with an original handle * * Delegate to sub-FSAL. This should not happen, because of the cache, but * handle it anyway. * * @param[in] orig_hdl Original handle * @param[in] dupe_hdl Handle to merge into original * @return FSAL status */ static fsal_status_t mdcache_merge(struct fsal_obj_handle *orig_hdl, struct fsal_obj_handle *dupe_hdl) { mdcache_entry_t *entry = container_of(orig_hdl, mdcache_entry_t, obj_handle); fsal_status_t status; subcall(status = entry->sub_handle->obj_ops->merge(entry->sub_handle, dupe_hdl)); return status; } static bool mdcache_is_referral(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *unused, bool cache_attrs) { mdcache_entry_t *entry = container_of(obj_hdl, mdcache_entry_t, obj_handle); bool result, locked, write_locked; attrmask_t valid_request_mask = 0; struct fsal_attrlist attrs[1]; fsal_prepare_attrs(attrs, ATTR_MODE | ATTR_TYPE); PTHREAD_RWLOCK_rdlock(&entry->attr_lock); locked = true; if (mdcache_is_attrs_valid(entry, attrs->request_mask)) { /* Up-to-date */ goto copy_and_unlock; } /* Promote to write lock */ PTHREAD_RWLOCK_unlock(&entry->attr_lock); PTHREAD_RWLOCK_wrlock(&entry->attr_lock); write_locked = true; if (!mdcache_is_attrs_valid(entry, attrs->request_mask)) { /* attrs are not valid, let the subfsal take care of it */ goto invoke_subfsal; } copy_and_unlock: valid_request_mask = attrs->request_mask; fsal_copy_attrs(attrs, &entry->attrs, false); PTHREAD_RWLOCK_unlock(&entry->attr_lock); locked = false; write_locked = false; invoke_subfsal: subcall(result = entry->sub_handle->obj_ops->is_referral( entry->sub_handle, attrs, cache_attrs);); /* If the valid request mask before subcall and after subcall are same * then we can skip attr updates. This should ideally be the most common * case. */ if (!cache_attrs || valid_request_mask == attrs->request_mask || attrs->valid_mask == 0) { goto out; } /* Need to take a read lock again to check if cached attrs are valid */ if (!locked) { PTHREAD_RWLOCK_rdlock(&entry->attr_lock); locked = true; assert(!write_locked); } /* Check if is_referral added any new attrs and update them in the * cache */ if (!mdcache_is_attrs_valid(entry, attrs->request_mask)) { if (!write_locked) { /* Promote to write lock to update the cached attrs */ PTHREAD_RWLOCK_unlock(&entry->attr_lock); PTHREAD_RWLOCK_wrlock(&entry->attr_lock); } mdc_update_attr_cache(entry, attrs); } assert(locked); out: if (locked) { PTHREAD_RWLOCK_unlock(&entry->attr_lock); } fsal_release_attrs(attrs); return result; } void mdcache_handle_ops_init(struct fsal_obj_ops *ops) { fsal_default_obj_ops_init(ops); ops->get_ref = mdcache_get_ref; ops->put_ref = mdcache_put_ref; ops->release = mdcache_hdl_release; ops->merge = mdcache_merge; ops->lookup = mdcache_lookup; ops->readdir = mdcache_readdir; ops->mkdir = mdcache_mkdir; ops->mknode = mdcache_mknode; ops->symlink = mdcache_symlink; ops->readlink = mdcache_readlink; ops->test_access = mdcache_test_access; ops->getattrs = mdcache_getattrs; ops->link = mdcache_link; ops->rename = mdcache_rename; ops->unlink = mdcache_unlink; ops->io_advise = mdcache_io_advise; ops->close = mdcache_close; ops->handle_to_wire = mdcache_handle_to_wire; ops->handle_to_key = mdcache_handle_to_key; ops->handle_cmp = mdcache_handle_cmp; /* pNFS */ ops->layoutget = mdcache_layoutget; ops->layoutreturn = mdcache_layoutreturn; ops->layoutcommit = mdcache_layoutcommit; /* Multi-FD */ ops->open2 = mdcache_open2; ops->check_verifier = mdcache_check_verifier; ops->status2 = mdcache_status2; ops->reopen2 = mdcache_reopen2; ops->read2 = mdcache_read2; ops->write2 = mdcache_write2; ops->seek2 = mdcache_seek2; ops->io_advise2 = mdcache_io_advise2; ops->commit2 = mdcache_commit2; ops->lock_op2 = mdcache_lock_op2; ops->lease_op2 = mdcache_lease_op2; ops->setattr2 = mdcache_setattr2; ops->close2 = mdcache_close2; ops->fallocate = mdcache_fallocate; /* xattr related functions */ ops->list_ext_attrs = mdcache_list_ext_attrs; ops->getextattr_id_by_name = mdcache_getextattr_id_by_name; ops->getextattr_value_by_name = mdcache_getextattr_value_by_name; ops->getextattr_value_by_id = mdcache_getextattr_value_by_id; ops->setextattr_value = mdcache_setextattr_value; ops->setextattr_value_by_id = mdcache_setextattr_value_by_id; ops->remove_extattr_by_id = mdcache_remove_extattr_by_id; ops->remove_extattr_by_name = mdcache_remove_extattr_by_name; ops->getxattrs = mdcache_getxattrs; ops->setxattrs = mdcache_setxattrs; ops->removexattrs = mdcache_removexattrs; ops->listxattrs = mdcache_listxattrs; ops->is_referral = mdcache_is_referral; } /* * export methods that create object handles */ /** * @brief Lookup a path from the export * * Lookup in the sub-FSAL, and wrap with a MDCACHE entry. This is the * equivalent of ...lookup_path() followed by mdcache_new_entry() * * @param[in] exp_hdl FSAL export to look in * @param[in] path Path to find * @param[out] handle Resulting object handle * @param[in,out] attrs_out Optional attributes for newly created object * * @note This returns an INITIAL ref'd entry on success * @return FSAL status */ fsal_status_t mdcache_lookup_path(struct fsal_export *exp_hdl, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { struct fsal_obj_handle *sub_handle = NULL; struct mdcache_fsal_export *export = container_of(exp_hdl, struct mdcache_fsal_export, mfe_exp); struct fsal_export *sub_export = export->mfe_exp.sub_export; fsal_status_t status; struct fsal_attrlist attrs; mdcache_entry_t *new_entry; *handle = NULL; /* Ask for all supported attributes except ACL (we defer fetching ACL * until asked for it (including a permission check). */ fsal_prepare_attrs(&attrs, op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) & ~ATTR_ACL); subcall_raw(export, status = sub_export->exp_ops.lookup_path( sub_export, path, &sub_handle, &attrs)); if (unlikely(FSAL_IS_ERROR(status))) { LogDebug(COMPONENT_MDCACHE, "lookup_path %s failed with %s", path, fsal_err_txt(status)); fsal_release_attrs(&attrs); return status; } status = mdcache_new_entry(export, sub_handle, &attrs, false, attrs_out, false, &new_entry, NULL, LRU_ACTIVE_REF | LRU_PROMOTE); fsal_release_attrs(&attrs); if (!FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_MDCACHE, "lookup_path Created entry %p FSAL %s", new_entry, new_entry->sub_handle->fsal->name); /* Make sure this entry has a parent pointer */ mdc_get_parent(export, new_entry, NULL); *handle = &new_entry->obj_handle; } if (attrs_out != NULL) { LogAttrlist(COMPONENT_MDCACHE, NIV_FULL_DEBUG, "lookup_path ", attrs_out, true); } return status; } /** * @brief Find or create a cache entry from a host-handle * * This is the equivalent of mdcache_get(). It returns a ref'd entry that * must be put using obj_ops->release(). * * @param[in] exp_hdl The export in which to create the handle * @param[in] hdl_desc Buffer descriptor for the host handle * @param[out] handle FSAL object handle * @param[in,out] attrs_out Optional attributes for newly created object * * @return FSAL status */ fsal_status_t mdcache_create_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *fh_desc, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { struct mdcache_fsal_export *export = container_of(exp_hdl, struct mdcache_fsal_export, mfe_exp); mdcache_entry_t *entry; fsal_status_t status; *handle = NULL; status = mdcache_locate_host(fh_desc, export, &entry, attrs_out); if (FSAL_IS_ERROR(status)) return status; /* Make sure this entry has a parent pointer */ mdc_get_parent(export, entry, NULL); if (attrs_out != NULL) { LogAttrlist(COMPONENT_MDCACHE, NIV_FULL_DEBUG, "create_handle ", attrs_out, true); } *handle = &entry->obj_handle; return fsalstat(ERR_FSAL_NO_ERROR, 0); } nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_hash.c000066400000000000000000000057301473756622300243630ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2013, The Linux Box Corporation * Contributor : Matt Benjamin * * Some portions Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #include "config.h" #include #include #include #include #include #include #include #include #include "fsal.h" #include "nfs_core.h" #include "log.h" #include "mdcache_int.h" #include "mdcache_hash.h" /** * @addtogroup FSAL_MDCACHE * @{ */ /** * * @file mdcache_hash.c * @author Matt Benjamin * @brief MDCACHE hashed dictionary package * * @section Description * * This module exports an interface for efficient lookup of cache entries * by file handle. Refactored from the prior abstract HashTable * implementation. */ struct cih_lookup_table cih_fhcache; static bool initialized; /** * @brief Initialize the package. */ void cih_pkginit(void) { cih_partition_t *cp; int ix; /* avoid writer starvation */ cih_fhcache.npart = mdcache_param.nparts; cih_fhcache.partition = gsh_calloc(cih_fhcache.npart, sizeof(cih_partition_t)); cih_fhcache.cache_sz = mdcache_param.cache_size; for (ix = 0; ix < cih_fhcache.npart; ++ix) { cp = &cih_fhcache.partition[ix]; cp->part_ix = ix; PTHREAD_MUTEX_init(&cp->cih_lock, NULL); avltree_init(&cp->t, cih_fh_cmpf, 0 /* must be 0 */); cp->cache = gsh_calloc(cih_fhcache.cache_sz, sizeof(struct avltree_node *)); } initialized = true; } /** * @brief Destroy the package. */ void cih_pkgdestroy(void) { /* Index over partitions */ int ix = 0; /* Destroy the partitions, warning if not empty */ for (ix = 0; ix < cih_fhcache.npart; ++ix) { if (avltree_first(&cih_fhcache.partition[ix].t) != NULL) LogMajor(COMPONENT_MDCACHE, "MDCACHE AVL tree not empty"); PTHREAD_MUTEX_destroy(&cih_fhcache.partition[ix].cih_lock); gsh_free(cih_fhcache.partition[ix].cache); } /* Destroy the partition table */ gsh_free(cih_fhcache.partition); cih_fhcache.partition = NULL; initialized = false; } /** @} */ nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_hash.h000066400000000000000000000276021473756622300243720ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2013, The Linux Box Corporation * Contributor : Matt Benjamin * * Some portions Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @addtogroup FSAL_MDCACHE * @{ */ /** * @file mdcache_hash.h * @brief MDCACHE hashed dictionary package * * This module exports an interface for efficient lookup of cache entries * by file handle, (etc?). Refactored from the prior abstract HashTable * implementation. */ #ifndef MDCACHE_HASH_H #define MDCACHE_HASH_H #include "config.h" #include "log.h" #include "abstract_atomic.h" #include "mdcache_int.h" #include "gsh_intrinsic.h" #include "mdcache_lru.h" #include "city.h" #include #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/mdcache.h" #endif /** * @brief The table partition * * Each tree is independent, having its own lock, thus reducing thread * contention. */ typedef struct cih_partition { uint32_t part_ix; pthread_mutex_t cih_lock; struct avltree t; struct avltree_node **cache; #ifdef ENABLE_LOCKTRACE struct { char *func; uint32_t line; } locktrace; #endif GSH_CACHE_PAD(0); } cih_partition_t; /** * @brief The weakref table structure * * This is the structure corresponding to a single table of weakrefs. */ struct cih_lookup_table { GSH_CACHE_PAD(0); cih_partition_t *partition; uint32_t npart; uint32_t cache_sz; }; /* Support inline lookups */ extern struct cih_lookup_table cih_fhcache; /** * @brief Initialize the package. */ void cih_pkginit(void); /** * @brief Destroy the package. */ void cih_pkgdestroy(void); /** * @brief Find the correct partition for a pointer * * To lower thread contention, the table is composed of multiple * trees, with the tree that receives a pointer determined by a * modulus. This macro yields an expression that yields a pointer to * the correct partition. */ #define cih_partition_of_scalar(lt, k) \ (((lt)->partition) + (((uint64_t)k) % (lt)->npart)) /** * @brief Compute cache slot for an entry * * This function computes a hash slot, taking an address modulo the * number of cache slots (which should be prime). * * @param wt [in] The table * @param ptr [in] Entry address * * @return The computed offset. */ static inline uint32_t cih_cache_offsetof(struct cih_lookup_table *lt, uint64_t k) { return k % lt->cache_sz; } /** * @brief MDCACHE FH hashed comparison function. * * Entries are ordered by integer hash first, and second by bitwise * comparison of the corresponding file handle. * * For key prototypes, which have no object handle, the buffer pointed to * by fh_k.fh_desc_k is taken to be the file handle. Further, ONLY key * prototype entries may have a non-NULL value for fh_k.fh_desc_k. * * @param lhs [in] First node * @param rhs [in] Second node * * @retval -1: lhs compares as less than rhs * @retval 0: lhs and rhs compare equal * @retval 1: lhs is greater than rhs */ static inline int cih_fh_cmpf(const struct avltree_node *lhs, const struct avltree_node *rhs) { mdcache_entry_t *lk, *rk; lk = avltree_container_of(lhs, mdcache_entry_t, fh_hk.node_k); rk = avltree_container_of(rhs, mdcache_entry_t, fh_hk.node_k); return mdcache_key_cmp(&lk->fh_hk.key, &rk->fh_hk.key); } /** * @brief Open-coded avltree lookup * * Search for an entry matching key in avltree tree. * * @todo dang this should be in the avltree implementation. It's dangerous to * open-code an avltree lookup elsewhere. * * @param tree [in] The avltree to search * @param key [in] Entry being searched for, as an avltree node * * @return Pointer to node if found, else NULL. */ static inline struct avltree_node * cih_fhcache_inline_lookup(const struct avltree *tree, const struct avltree_node *key) { return avltree_inline_lookup(key, tree, cih_fh_cmpf); } #define CIH_HASH_NONE 0x0000 #define CIH_HASH_KEY_PROTOTYPE 0x0001 /** * @brief Convenience function to compute hash for mdcache_entry_t * * Computes hash of entry using input fh_desc. If entry is not a * disposable key prototype, fh_desc is duplicated in entry. * * @param entry [in] Entry to be hashed * @param fh_desc [in] Hash input bytes * * @return (void) */ static inline void cih_hash_key(mdcache_key_t *key, struct fsal_module *fsal, struct gsh_buffdesc *fh_desc, uint32_t flags) { key->fsal = fsal; /* fh prototype fixup */ if (flags & CIH_HASH_KEY_PROTOTYPE) { key->kv = *fh_desc; } else { /* XXX dups fh_desc */ key->kv.len = fh_desc->len; key->kv.addr = gsh_malloc(fh_desc->len); memcpy(key->kv.addr, fh_desc->addr, fh_desc->len); } /* hash it */ key->hk = CityHash64WithSeed(fh_desc->addr, fh_desc->len, 557); } #define CIH_GET_NONE 0x0000 #define CIH_GET_UNLOCK_ON_MISS 0x0004 /** * @brief Hash latch structure. * * Used to memoize a partition and its lock state between calls. Nod * to Adam's precursor in HashTable. */ typedef struct cih_latch { cih_partition_t *cp; } cih_latch_t; static inline void cih_hash_release(cih_latch_t *latch) { PTHREAD_MUTEX_unlock(&(latch->cp->cih_lock)); } /** * @brief Latch the partition of key. * * @param key [in] The key * @param latch [inout] Latch * @param flags [in] Flags */ static inline void cih_latch_entry(mdcache_key_t *key, cih_latch_t *latch, const char *func, int line) { cih_partition_t *cp; latch->cp = cp = cih_partition_of_scalar(&cih_fhcache, key->hk); PTHREAD_MUTEX_lock(&cp->cih_lock); /* SUBTREE_LOCK */ #ifdef ENABLE_LOCKTRACE cp->locktrace.func = (char *)func; cp->locktrace.line = line; #endif } /** * @brief Lookup cache entry by key * * Lookup cache entry by fh, optionally return with hash partition shared * or exclusive locked. Differs from the fh variant in using the precomputed * hash stored with key. * * @param key [in] Key being searched * @param latch [out] Pointer to partition * @param flags [in] Flags * * @return Pointer to cache entry if found, else NULL */ static inline mdcache_entry_t *cih_get_by_key_latch(mdcache_key_t *key, cih_latch_t *latch, uint32_t flags, const char *func, int line) { mdcache_entry_t k_entry, *entry = NULL; struct avltree_node *node; void **cache_slot; cih_latch_entry(key, latch, func, line); k_entry.fh_hk.key = *key; /* check cache */ cache_slot = (void **)&( latch->cp->cache[cih_cache_offsetof(&cih_fhcache, key->hk)]); node = (struct avltree_node *)atomic_fetch_voidptr(cache_slot); if (node) { if (cih_fh_cmpf(&k_entry.fh_hk.node_k, node) == 0) { /* got it in 1 */ LogDebug(COMPONENT_HASHTABLE_CACHE, "cih cache hit slot %d", cih_cache_offsetof(&cih_fhcache, key->hk)); goto found; } } /* check AVL */ node = cih_fhcache_inline_lookup(&latch->cp->t, &k_entry.fh_hk.node_k); if (!node) { if (flags & CIH_GET_UNLOCK_ON_MISS) cih_hash_release(latch); LogDebug(COMPONENT_HASHTABLE_CACHE, "fdcache MISS"); goto out; } /* update cache */ atomic_store_voidptr(cache_slot, node); LogDebug(COMPONENT_HASHTABLE_CACHE, "cih AVL hit slot %d", cih_cache_offsetof(&cih_fhcache, key->hk)); found: entry = avltree_container_of(node, mdcache_entry_t, fh_hk.node_k); if (atomic_fetch_int32_t(&entry->lru.refcnt) == 0) { /* If refcount is 0, this entry is being freed, but has not yet * been removed from the hashtable. Don't return it */ LogDebug(COMPONENT_HASHTABLE_CACHE, "entry %p being freed", entry); entry = NULL; if (flags & CIH_GET_UNLOCK_ON_MISS) cih_hash_release(latch); } out: return entry; } #define CIH_SET_NONE 0x0000 #define CIH_SET_HASHED 0x0001 /* previously hashed entry */ #define CIH_SET_UNLOCK 0x0002 /** * @brief Insert cache entry on partition previously locked. * * Insert cache entry on partition previously locked. * * @param entry [in] Entry to be inserted * @param latch [in] latch held for set * @param fsak [in] FSAL used to hash the key * @param fh_desc [in] Hash input bytes (MUST be those used previously) * @param flags [in] Flags * * @return void */ static inline void cih_set_latched(mdcache_entry_t *entry, cih_latch_t *latch, struct fsal_module *fsal, struct gsh_buffdesc *fh_desc, uint32_t flags) { cih_partition_t *cp = latch->cp; /* Omit hash if you are SURE we hashed it, and that the * hash remains valid */ if (unlikely(!(flags & CIH_SET_HASHED))) cih_hash_key(&entry->fh_hk.key, fsal, fh_desc, CIH_HASH_NONE); (void)avltree_insert(&entry->fh_hk.node_k, &cp->t); entry->fh_hk.inavl = true; GSH_AUTO_TRACEPOINT(mdcache, mdc_lru_insert, TRACE_DEBUG, "LRU insert. Obj: {}, refcnt: {}", &entry->obj_handle, entry->lru.refcnt); if (likely(flags & CIH_SET_UNLOCK)) cih_hash_release(latch); } /** * @brief Remove cache entry with existence check. * * Remove cache entry with existence check. The entry is assumed to * be hashed. * * @param entry [in] Entry to be removed. * * @return (void) */ static inline bool cih_remove_checked(mdcache_entry_t *entry) { struct avltree_node *node; cih_partition_t *cp = cih_partition_of_scalar(&cih_fhcache, entry->fh_hk.key.hk); bool unref = false; bool freed = false; PTHREAD_MUTEX_lock(&cp->cih_lock); node = cih_fhcache_inline_lookup(&cp->t, &entry->fh_hk.node_k); if (entry->fh_hk.inavl && node) { LogFullDebug(COMPONENT_MDCACHE, "Unhashing entry %p", entry); GSH_AUTO_TRACEPOINT(mdcache, mdc_lru_remove_checked, TRACE_DEBUG, "LRU remove. Obj: {}, refcnt: {}", &entry->obj_handle, entry->lru.refcnt); avltree_remove(node, &cp->t); cp->cache[cih_cache_offsetof(&cih_fhcache, entry->fh_hk.key.hk)] = NULL; entry->fh_hk.inavl = false; /* return sentinel ref */ unref = true; } PTHREAD_MUTEX_unlock(&cp->cih_lock); if (unref) { /* We can't unref with the lock held, in case this is the last * ref. That will recurse into here, and try to take the lock * again. */ freed = mdcache_lru_unref(entry, LRU_FLAG_SENTINEL); } return freed; } /** * @brief Remove cache entry protected by latch * * Remove cache entry. * * @note Must NOT be called with qlane lock held. * * @param entry [in] Entry to be removed.0 * * @return true if entry is invalid */ #define CIH_REMOVE_NONE 0x0000 #define CIH_REMOVE_UNLOCK 0x0001 static inline bool cih_remove_latched(mdcache_entry_t *entry, cih_latch_t *latch, uint32_t flags) { cih_partition_t *cp = cih_partition_of_scalar(&cih_fhcache, entry->fh_hk.key.hk); if (entry->fh_hk.inavl) { LogFullDebug(COMPONENT_MDCACHE, "Unhashing entry %p", entry); GSH_AUTO_TRACEPOINT(mdcache, mdc_lru_remove_latched, TRACE_DEBUG, "LRU remove. Obj: {}, refcnt: {}", &entry->obj_handle, entry->lru.refcnt); avltree_remove(&entry->fh_hk.node_k, &cp->t); cp->cache[cih_cache_offsetof(&cih_fhcache, entry->fh_hk.key.hk)] = NULL; entry->fh_hk.inavl = false; mdcache_lru_unref(entry, LRU_FLAG_SENTINEL); if (flags & CIH_REMOVE_UNLOCK) cih_hash_release(latch); return true; } if (flags & CIH_REMOVE_UNLOCK) cih_hash_release(latch); return false; } #endif /* MDCACHE_HASH_H */ /** @} */ nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_helpers.c000066400000000000000000003302341473756622300251020ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2015-2021 Red Hat, Inc. and/or its affiliates. * Author: Daniel Gryniewicz * * This program 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 3 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 * 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 * */ /** * @addtogroup FSAL_MDCACHE * @{ */ /** * @file mdcache_helpers.c * @brief Miscellaneous helper functions */ #include "config.h" #include "sal_functions.h" #include "fsal.h" #include "FSAL/fsal_commonlib.h" #include "fsal_convert.h" #include #include #include #include #include "nfs_exports.h" #include "mdcache_lru.h" #include "mdcache_hash.h" #include "mdcache_avl.h" #ifdef USE_MONITORING #include "monitoring.h" #endif /* USE_MONITORING */ #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/mdcache.h" #endif #define mdc_chunk_first_dirent(c) \ glist_first_entry(&(c)->dirents, mdcache_dir_entry_t, chunk_list) /** * @brief Drop refs for state chunks * * Drop a ref from each chunk, making sure keep has one * * @param[in] a First chunk * @param[in] b Second chunk * @param[in] c Third chunk * @param[in] keep Chunk to keep a ref */ static void drop_state_chunk_refs(struct dir_chunk *a, struct dir_chunk *b, struct dir_chunk *c, struct dir_chunk *keep) { if (keep) { mdcache_lru_ref_chunk(keep); } mdcache_lru_unref_chunk(a); mdcache_lru_unref_chunk(b); mdcache_lru_unref_chunk(c); } static inline bool trust_negative_cache(mdcache_entry_t *parent) { bool trust = op_ctx_export_has_option( EXPORT_OPTION_TRUST_READIR_NEGATIVE_CACHE) && test_mde_flags(parent, MDCACHE_DIR_POPULATED); if (trust) LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Entry %p Trust negative cache", parent); else LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Entry %p Don't Trust negative cache", parent); return trust; } /** * @brief Add a detached dirent to the LRU list (in the MRU position). * * If rhe maximum number of detached dirents would be exceeded, remove the * LRU dirent. * * @note mdc_parent MUST have it's content_lock held for writing * * @param[in] parent Parent entry * @param[in] dirent Dirent to move to MRU */ static inline void add_detached_dirent(mdcache_entry_t *parent, mdcache_dir_entry_t *dirent) { #ifdef DEBUG_MDCACHE assert(parent->content_lock.__data.__cur_writer); #endif if (parent->fsobj.fsdir.detached_count == mdcache_param.dir.avl_detached_max) { /* Need to age out oldest detached dirent. */ mdcache_dir_entry_t *removed; /* Find the oldest detached dirent and remove it. * We just hold the spin lock for the list operation. * Technically we don't need it since the content lock is held * for write, there can be no conflicting threads. Since we * don't have a racing thread, it's ok that the list is * unprotected by spin lock while we make the AVL call. */ PTHREAD_SPIN_lock(&parent->fsobj.fsdir.fsd_spin); removed = glist_last_entry(&parent->fsobj.fsdir.detached, mdcache_dir_entry_t, chunk_list); PTHREAD_SPIN_unlock(&parent->fsobj.fsdir.fsd_spin); /* Remove from active names tree */ mdcache_avl_remove(parent, removed); } /* Add new entry to MRU (head) of list */ PTHREAD_SPIN_lock(&parent->fsobj.fsdir.fsd_spin); glist_add(&parent->fsobj.fsdir.detached, &dirent->chunk_list); parent->fsobj.fsdir.detached_count++; PTHREAD_SPIN_unlock(&parent->fsobj.fsdir.fsd_spin); } /** * Allocate and initialize a new mdcache handle. * * This function doesn't free the sub_handle if the allocation fails. It must * be done in the calling function. * * @param[in] export The mdcache export used by the handle. * @param[in] sub_handle The handle used by the subfsal. * @param[in] fs The filesystem of the new handle. * @param[in] flags The flags for the caller's initial reference, MUST * include LRU_ACTIVE_REF * @param[in] func The caller's function name * @param[in] line The line number of the invocation * * @return The new handle, or NULL if the unexport in progress. */ static mdcache_entry_t *mdcache_alloc_handle(struct mdcache_fsal_export *export, struct fsal_obj_handle *sub_handle, struct fsal_filesystem *fs, uint32_t flags, const char *func, int line) { mdcache_entry_t *result; fsal_status_t status; result = mdcache_lru_get(sub_handle, flags); /* mdcache_lru_get never returns NULL */ assert(result); /* Base data */ result->sub_handle = sub_handle; result->obj_handle.type = sub_handle->type; result->obj_handle.fsid = sub_handle->fsid; result->obj_handle.fileid = sub_handle->fileid; result->obj_handle.fs = fs; result->obj_handle.exp_refcnt = 0; /* default handlers */ fsal_obj_handle_init(&result->obj_handle, &export->mfe_exp, sub_handle->type, true); /* mdcache handlers */ result->obj_handle.obj_ops = &MDCACHE.handle_ops; /* state */ if (sub_handle->type == DIRECTORY) { result->obj_handle.state_hdl = &result->fsobj.fsdir.dhdl; /* init avl tree */ mdcache_avl_init(result); /* init chunk list and detached dirents list */ glist_init(&result->fsobj.fsdir.chunks); glist_init(&result->fsobj.fsdir.detached); PTHREAD_SPIN_init(&result->fsobj.fsdir.fsd_spin, PTHREAD_PROCESS_PRIVATE); } else { result->obj_handle.state_hdl = &result->fsobj.hdl; } state_hdl_init(result->obj_handle.state_hdl, result->obj_handle.type, &result->obj_handle); /* Initialize common fields */ result->mde_flags = 0; glist_init(&result->export_list); atomic_store_int32_t(&result->first_export_id, -1); /* Map the export before we put this entry into the LRU, but after it's * well enough set up to be able to be unrefed by unexport should there * be a race. */ status = mdc_check_mapping(result); if (unlikely(FSAL_IS_ERROR(status))) { /* The current export is in process to be unexported, don't * create new mdcache entries. */ LogDebug( COMPONENT_MDCACHE, "Trying to allocate a new entry %p for export id %" PRIi16 " that is in the process of being unexported", result, op_ctx->ctx_export->export_id); /* sub_handle will be freed by the caller */ result->sub_handle = NULL; /* Handle is not yet in hash / LRU, so just put the sentinel * ref and the additional ref returned by mdcache_lru_get. */ mdcache_lru_unref(result, flags); mdcache_lru_unref(result, LRU_FLAG_SENTINEL); return NULL; } /* We are going live with this one, the additional ref with flags has * already been made by mdcache_lru_get. */ return result; } /** * @brief Drop ref on remaining dirents in chunk * * @note content_lock of parent dir must be held for WRITE * * @param[in] dirent First ref'd dirent */ static void mdc_unref_chunk_dirents(struct dir_chunk *chunk, mdcache_dir_entry_t *dirent) { for (; dirent != NULL; dirent = glist_next_entry(&chunk->dirents, mdcache_dir_entry_t, chunk_list, &dirent->chunk_list)) { if (dirent->mde_entry) { mdcache_lru_unref(dirent->mde_entry, LRU_ACTIVE_REF); dirent->mde_entry = NULL; } } } /** * @brief Cleans up an entry so it can be reused * * @param[in] entry The cache entry to clean */ void mdc_clean_entry(mdcache_entry_t *entry) { struct glist_head *glist; struct glist_head *glistn; /* Must get attr_lock before mdc_exp_lock */ PTHREAD_RWLOCK_wrlock(&entry->attr_lock); glist_for_each_safe(glist, glistn, &entry->export_list) { struct entry_export_map *expmap; struct mdcache_fsal_export *export; expmap = glist_entry(glist, struct entry_export_map, export_per_entry); export = expmap->exp; PTHREAD_MUTEX_lock(&export->mdc_exp_lock); mdc_remove_export_map(expmap); PTHREAD_MUTEX_unlock(&export->mdc_exp_lock); } /* Clear out first_export */ atomic_store_int32_t(&entry->first_export_id, -1); PTHREAD_RWLOCK_unlock(&entry->attr_lock); if (entry->obj_handle.type == DIRECTORY) { PTHREAD_RWLOCK_wrlock(&entry->content_lock); /* Clean up dirents */ mdcache_dirent_invalidate_all(entry); /* Clean up parent key */ mdcache_free_fh(&entry->fsobj.fsdir.parent); PTHREAD_RWLOCK_unlock(&entry->content_lock); } cih_remove_checked(entry); } /** * * Check the active export mapping for this entry and update if necessary. * * If the entry does not have a mapping for the active export, add one. * * If an unexport is in progress, return ERR_FSAL_STALE to prevent the caller * from proceeding. * * @param[in] entry The cache entry * * @return FSAL Status * */ fsal_status_t mdc_check_mapping(mdcache_entry_t *entry) { struct mdcache_fsal_export *export = mdc_cur_export(); struct glist_head *glist; struct entry_export_map *expmap; bool try_write = false; if (atomic_fetch_uint8_t(&export->flags) & MDC_UNEXPORT) { /* In the process of unexporting, don't check export mapping. * Return a stale error. */ return fsalstat(ERR_FSAL_STALE, ESTALE); } /* Fast path check to see if this export is already mapped */ if (atomic_fetch_int32_t(&entry->first_export_id) == (int32_t)op_ctx->ctx_export->export_id) return fsalstat(ERR_FSAL_NO_ERROR, 0); PTHREAD_RWLOCK_rdlock(&entry->attr_lock); again: (void)atomic_inc_uint64_t(&cache_stp->inode_mapping); glist_for_each(glist, &entry->export_list) { expmap = glist_entry(glist, struct entry_export_map, export_per_entry); /* Found active export on list */ if (expmap->exp == export) { PTHREAD_RWLOCK_unlock(&entry->attr_lock); return fsalstat(ERR_FSAL_NO_ERROR, 0); } } if (!try_write) { /* Now take write lock and try again in * case another thread has raced with us. */ PTHREAD_RWLOCK_unlock(&entry->attr_lock); PTHREAD_RWLOCK_wrlock(&entry->attr_lock); try_write = true; goto again; } /* We have the write lock and did not find * this export on the list, add it. */ PTHREAD_MUTEX_lock(&export->mdc_exp_lock); /* Check for unexport again, this prevents an interlock issue where * we passed above, but now unexport is in progress. This is required * because the various locks are acquired, dropped, and re-acquired * in such a way that unexport may have started after we made the * check at the top. */ if (atomic_fetch_uint8_t(&export->flags) & MDC_UNEXPORT) { /* In the process of unexporting, don't allow creating a new * export mapping. Return a stale error. */ PTHREAD_MUTEX_unlock(&export->mdc_exp_lock); PTHREAD_RWLOCK_unlock(&entry->attr_lock); return fsalstat(ERR_FSAL_STALE, ESTALE); } expmap = gsh_calloc(1, sizeof(*expmap)); /* If export_list is empty, store this export as first */ if (glist_empty(&entry->export_list)) { atomic_store_int32_t(&entry->first_export_id, (int32_t)op_ctx->ctx_export->export_id); } expmap->exp = export; expmap->entry = entry; glist_add_tail(&entry->export_list, &expmap->export_per_entry); glist_add_tail(&export->entry_list, &expmap->entry_per_export); PTHREAD_MUTEX_unlock(&export->mdc_exp_lock); PTHREAD_RWLOCK_unlock(&entry->attr_lock); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* entry's content_lock must be held in exclusive mode */ fsal_status_t mdc_get_parent_handle(struct mdcache_fsal_export *export, mdcache_entry_t *entry, struct fsal_obj_handle *sub_parent) { char buf[NFS4_FHSIZE]; struct gsh_buffdesc fh_desc = { buf, NFS4_FHSIZE }; fsal_status_t status; int32_t expire_time_parent; #ifdef DEBUG_MDCACHE assert(entry->content_lock.__data.__cur_writer); #endif /* Get a wire handle that can be used with create_handle() */ subcall_raw(export, status = sub_parent->obj_ops->handle_to_wire( sub_parent, FSAL_DIGEST_NFSV4, &fh_desc)); if (FSAL_IS_ERROR(status)) return status; /* And store in the parent host-handle */ mdcache_copy_fh(&entry->fsobj.fsdir.parent, &fh_desc); expire_time_parent = op_ctx->fsal_export->exp_ops.fs_expiretimeparent( op_ctx->fsal_export); if (expire_time_parent != -1) entry->fsobj.fsdir.parent_time = time(NULL) + expire_time_parent; else entry->fsobj.fsdir.parent_time = 0; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* entry's content_lock must not be held, this function will get the content_lock in exclusive mode */ fsal_status_t mdc_get_parent(struct mdcache_fsal_export *export, mdcache_entry_t *entry, struct gsh_buffdesc *parent_out) { struct fsal_obj_handle *sub_handle = NULL; struct fsal_obj_handle *root_obj = NULL; fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; PTHREAD_RWLOCK_wrlock(&entry->content_lock); if (entry->obj_handle.type != DIRECTORY) { /* Parent pointer only for directories */ status.major = ERR_FSAL_INVAL; goto out; } /* First check if the entry->obj_handle points to a root object. * Never lookup for parent of a root object. */ status = nfs_export_get_root_entry(op_ctx->ctx_export, &root_obj); if (!FSAL_IS_ERROR(status)) { /* Put the ref on root_obj; we're only doing a pointer compare, * so it's okay */ root_obj->obj_ops->put_ref(root_obj); if (&entry->obj_handle == root_obj) { /* This entry is the root of the current export, so we * should not do lookup of parent at root. */ goto copy_parent_out; } } if (entry->fsobj.fsdir.parent.len != 0) { /* Already has a parent pointer */ if (entry->fsobj.fsdir.parent_time == 0 || mdcache_is_parent_valid(entry)) { goto copy_parent_out; } } subcall_raw(export, status = entry->sub_handle->obj_ops->lookup( entry->sub_handle, "..", &sub_handle, NULL)); if (FSAL_IS_SUCCESS(status)) { /* if we already had a parent handle, then we are * going to refresh it. */ mdcache_free_fh(&entry->fsobj.fsdir.parent); mdc_get_parent_handle(export, entry, sub_handle); } else if (entry->fsobj.fsdir.parent.len != 0) { /* Lookup of (..) failed, but if we had a cached * parent handle then we will keep the same and * not fail this request for getting parent. */ LogDebug(COMPONENT_MDCACHE, "Lookup for (..) failed for entry: %p, but we have a " "cached parent handle so we will keep it", entry); status.major = ERR_FSAL_NO_ERROR; } else goto out; copy_parent_out: if (parent_out != NULL) { /* Copy the parent handle to parent_out */ mdcache_copy_fh(parent_out, &entry->fsobj.fsdir.parent); } out: PTHREAD_RWLOCK_unlock(&entry->content_lock); if (sub_handle != NULL) { /* Release parent handle */ subcall_raw(export, sub_handle->obj_ops->release(sub_handle)); } return status; } /** * @brief Cleans all the dirents belonging to a directory chunk. * * @note The content lock MUST be held for write * * @param[in,out] chunk The chunk being cleaned. * */ void mdcache_clean_dirent_chunk(struct dir_chunk *chunk) { struct glist_head *glist, *glistn; struct mdcache_fsal_obj_handle *parent = chunk->parent; #ifdef DEBUG_MDCACHE assert(parent->content_lock.__data.__cur_writer); #endif glist_for_each_safe(glist, glistn, &chunk->dirents) { mdcache_dir_entry_t *dirent; dirent = glist_entry(glist, mdcache_dir_entry_t, chunk_list); /* Remove from deleted or active names tree */ mdcache_avl_remove(parent, dirent); } /* Remove chunk from directory. */ glist_del(&chunk->chunks); /* At this point the following is true about the chunk: * * chunks is {NULL, NULL} do to the glist_del * dirents is {&dirents, &dirents}, i.e. empty as a result of the * glist_for_each_safe above * the other fields are untouched. */ /* This chunk is about to be freed or reused, clean up a few more * things. */ chunk->parent = NULL; chunk->next_ck = 0; chunk->num_entries = 0; } /** * @brief Cleans all the dirent chunks belonging to a directory. * * @note The content lock MUST be held for write * * @param[in,out] emtry The directory being cleaned. * */ void mdcache_clean_dirent_chunks(mdcache_entry_t *entry) { struct glist_head *glist, *glistn; #ifdef DEBUG_MDCACHE assert(entry->content_lock.__data.__cur_writer); #endif glist_for_each_safe(glist, glistn, &entry->fsobj.fsdir.chunks) { mdcache_lru_unref_chunk( glist_entry(glist, struct dir_chunk, chunks)); } } /** * @brief Invalidates and releases all cached entries for a directory * * Invalidates all the entries for a cached directory. * * @note The content lock MUST be held for write * * @param[in,out] entry The directory to be managed * */ void mdcache_dirent_invalidate_all(mdcache_entry_t *entry) { LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Invalidating directory for %p, clearing MDCACHE_DIR_POPULATED setting MDCACHE_TRUST_CONTENT and MDCACHE_TRUST_DIR_CHUNKS", entry); /* Clean the chunks first, that will clean most of the active * entries also. */ mdcache_clean_dirent_chunks(entry); /* Clean the active and deleted trees */ mdcache_avl_clean_trees(entry); atomic_clear_uint32_t_bits(&entry->mde_flags, MDCACHE_DIR_POPULATED); atomic_set_uint32_t_bits(&entry->mde_flags, MDCACHE_TRUST_CONTENT | MDCACHE_TRUST_DIR_CHUNKS); } /** * @brief Adds a new entry to the cache * * This function adds a new entry to the cache. It will allocate * entries of any kind. * * The caller is responsible for releasing attrs_in, however, the references * will have been transferred to the new mdcache entry. fsal_copy_attrs leaves * the state of the source attributes still safe to call fsal_release_attrs, * so all will be well. * * If flags does not include LRU_PROMOTE any new entry created will be inserted * into the tail (LRU) of L2, and if the entry already exists (we are racing * with another thread), the entry will not be promoted. * * If flags includes LRU_PROMOTE any new entry will be inserted into the tail * (LRU) of L1, and if the entry already exists (we are racing with another * thread), the entry will be promoted. * * @param[in] export Export for this cache * @param[in] sub_handle sub-FSAL's new obj handle * @param[in] attrs_in Attributes provided for the object * @param[in] prefer_attrs_in Whether prefer attrs_in * @param[in,out] attrs_out Attributes requested for the object * @param[in] new_directory Indicate a new directory was created * @param[out] entry Newly instantiated cache entry * @param[in] state Optional state_t representing open file. * @param[in] flags Flags for LRU management, expects * LRU_ACTIVE_REF * * @note This returns an ACTIVE ref'd entry on success * * @return FSAL status */ fsal_status_t mdcache_new_entry(struct mdcache_fsal_export *export, struct fsal_obj_handle *sub_handle, struct fsal_attrlist *attrs_in, bool prefer_attrs_in, struct fsal_attrlist *attrs_out, bool new_directory, mdcache_entry_t **entry, struct state_t *state, uint32_t flags) { fsal_status_t status; mdcache_entry_t *oentry, *nentry = NULL; struct gsh_buffdesc fh_desc; cih_latch_t latch; mdcache_key_t key; assert(flags & LRU_ACTIVE_REF); *entry = NULL; /* Get FSAL-specific key */ subcall_raw(export, sub_handle->obj_ops->handle_to_key(sub_handle, &fh_desc)); cih_hash_key(&key, export->mfe_exp.sub_export->fsal, &fh_desc, CIH_HASH_KEY_PROTOTYPE); /* Check if the entry already exists. We allow the following race * because mdcache_lru_get has a slow path, and the latch is a * shared lock. */ status = mdcache_find_keyed_reason(&key, entry, flags); if (!FSAL_IS_ERROR(status)) { LogDebug( COMPONENT_MDCACHE, "Trying to add an already existing entry. Found entry %p type: %d, New type: %d", *entry, (*entry)->obj_handle.type, sub_handle->type); /* If it was unreachable before, mark it reachable */ atomic_clear_uint32_t_bits(&(*entry)->mde_flags, MDCACHE_UNREACHABLE); /* Don't need a new sub_handle ref */ goto out_no_new_entry_yet; } else if (status.major != ERR_FSAL_NOENT) { /* Real error , don't need a new sub_handle ref */ goto out_no_new_entry_yet; } /* !LATCHED */ /* We did not find the object. Pull an entry off the LRU. The entry * will already be mapped. */ nentry = mdcache_alloc_handle(export, sub_handle, sub_handle->fs, flags, __func__, __LINE__); if (nentry == NULL) { /* We didn't get an entry because of unexport in progress, * go ahead and bail out now. */ status = fsalstat(ERR_FSAL_STALE, 0); goto out_no_new_entry_yet; } /* See if someone raced us. */ oentry = cih_get_by_key_latch(&key, &latch, CIH_GET_NONE, __func__, __LINE__); if (oentry) { /* Entry is already in the cache, do not add it. */ LogDebug(COMPONENT_MDCACHE, "lost race to add entry %p type: %d, New type: %d", oentry, oentry->obj_handle.type, sub_handle->type); *entry = oentry; /* Ref it */ mdcache_lru_ref(*entry, flags); /* It it was unreachable before, mark it reachable */ atomic_clear_uint32_t_bits(&(*entry)->mde_flags, MDCACHE_UNREACHABLE); /* Release the subtree hash table lock */ cih_hash_release(&latch); /* We found the entry in cache, let's reset the status */ status = fsalstat(ERR_FSAL_NO_ERROR, 0); goto out_release_new_entry; } /* We won the race. */ /* Set cache key */ cih_hash_key(&nentry->fh_hk.key, export->mfe_exp.sub_export->fsal, &fh_desc, CIH_HASH_NONE); switch (nentry->obj_handle.type) { case REGULAR_FILE: LogDebug(COMPONENT_MDCACHE, "Adding a REGULAR_FILE, entry=%p", nentry); /* Init statistics used for intelligently granting delegations*/ init_deleg_heuristics(&nentry->obj_handle); break; case DIRECTORY: LogDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Adding a DIRECTORY, entry=%p setting MDCACHE_TRUST_CONTENT %s", nentry, new_directory ? "setting MDCACHE_DIR_POPULATED" : "clearing MDCACHE_DIR_POPULATED"); atomic_set_uint32_t_bits(&nentry->mde_flags, MDCACHE_TRUST_CONTENT); /* If the directory is newly created, it is empty. Because we know its content, we consider it read. */ if (new_directory) { atomic_set_uint32_t_bits(&nentry->mde_flags, MDCACHE_DIR_POPULATED); } else { atomic_clear_uint32_t_bits(&nentry->mde_flags, MDCACHE_DIR_POPULATED); } break; case SYMBOLIC_LINK: case SOCKET_FILE: case FIFO_FILE: case BLOCK_FILE: case CHARACTER_FILE: LogDebug(COMPONENT_MDCACHE, "Adding a special file of type %d entry=%p", nentry->obj_handle.type, nentry); break; default: /* Should never happen */ cih_hash_release(&latch); status = fsalstat(ERR_FSAL_INVAL, 0); LogMajor(COMPONENT_MDCACHE, "unknown type %u provided", nentry->obj_handle.type); goto out_release_new_entry; } /* nentry not reachable yet; no need to lock */ /* Copy over the attributes and pass off the ACL reference. We also * copy the output attrs at this point to avoid needing the attr_lock. */ if (attrs_out != NULL) fsal_copy_attrs(attrs_out, attrs_in, false); /* Use the attrs_in request_mask because it will know if ACL was * requested or not (anyone calling mdcache_new_entry will have * requested all supported attributes including ACL). */ nentry->attrs.request_mask = attrs_in->request_mask; fsal_copy_attrs(&nentry->attrs, attrs_in, true); if (nentry->attrs.expire_time_attr == 0) { nentry->attrs.expire_time_attr = op_ctx->export_perms.expire_time_attr; } /* Validate the attributes we just set. */ mdc_fixup_md(nentry, &nentry->attrs); /* Insert and hash entry, after this would need attr_lock to * access attributes. The entry is inserted into the ACTIVE queue. */ mdcache_lru_insert_active(nentry); cih_set_latched(nentry, &latch, op_ctx->fsal_export->fsal, &fh_desc, CIH_SET_UNLOCK | CIH_SET_HASHED); if (isFullDebug(COMPONENT_MDCACHE)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; (void)display_mdcache_key(&dspbuf, &nentry->fh_hk.key); LogFullDebug(COMPONENT_MDCACHE, "New entry %p added with fh_hk.key %s", nentry, str); } else { LogDebug(COMPONENT_MDCACHE, "New entry %p added", nentry); } *entry = nentry; (void)atomic_inc_uint64_t(&cache_stp->inode_added); return fsalstat(ERR_FSAL_NO_ERROR, 0); out_release_new_entry: /* We raced or failed, release the new entry we acquired, this will * result in inline deconstruction. This will release the attributes, we * may not have copied yet, in which case mask and acl are 0/NULL. This * entry is not yet in the hash or LRU, so just put it's sentinel ref. */ nentry->sub_handle = NULL; /* Remove the reference for use using flags. */ mdcache_lru_unref(nentry, flags); /* Remove the sentinel reference. */ mdcache_lru_unref(nentry, LRU_FLAG_SENTINEL); out_no_new_entry_yet: /* If perfer attrs_in, we think attrs_in is latest. So use attrs_in to * override attr of existed entry, and copy attrs_in to attrs_out. * If don't prefer attrs_in, we don't think attrs_in is latest. * So when attributes were requested, fetch them now if we still have a * success return since we did not actually create a new object and * use the provided attributes. * * NOTE: There can not be an ABBA lock ordering issue since our caller * does not hold a lock on the "new" entry. */ if (prefer_attrs_in && !FSAL_IS_ERROR(status)) { PTHREAD_RWLOCK_wrlock(&(*entry)->attr_lock); mdc_update_attr_cache(*entry, attrs_in); PTHREAD_RWLOCK_unlock(&(*entry)->attr_lock); if (attrs_out != NULL) { fsal_copy_attrs(attrs_out, attrs_in, false); } } else if (!FSAL_IS_ERROR(status) && attrs_out != NULL) { status = get_optional_attrs(&(*entry)->obj_handle, attrs_out); if (FSAL_IS_ERROR(status)) { /* Oops, failed to get attributes and ATTR_RDATTR_ERR * was not requested, so we are failing and thus must * drop the object reference we got. */ mdcache_lru_unref(*entry, flags); *entry = NULL; } else { PTHREAD_RWLOCK_wrlock(&(*entry)->attr_lock); mdc_update_attr_cache(*entry, attrs_out); PTHREAD_RWLOCK_unlock(&(*entry)->attr_lock); } } if (!FSAL_IS_ERROR(status)) { /* Give the FSAL a chance to merge new_obj into * oentry->obj_handle since we will be using * oentry->obj_handle for all access to the object. */ struct fsal_obj_handle *old_sub_handle = (*entry)->sub_handle; subcall_raw(export, status = old_sub_handle->obj_ops->merge( old_sub_handle, sub_handle)); if (FSAL_IS_ERROR(status)) { /* Report this error and unref the entry */ LogDebug( COMPONENT_MDCACHE, "Merge of object handles after race returned %s", fsal_err_txt(status)); mdcache_lru_unref(*entry, flags); *entry = NULL; } } if (FSAL_IS_ERROR(status) && state != NULL) { /* Our caller passed in a state for an open file, since * there is not a valid entry to use, or a merge failed * we must close that file before disposing of new_obj. */ fsal_status_t cstatus; subcall_raw(export, cstatus = sub_handle->obj_ops->close2( sub_handle, state)); LogDebug(COMPONENT_MDCACHE, "Close of state during error processing returned %s", fsal_err_txt(cstatus)); } /* must free sub_handle if no new entry was created to reference it. */ subcall_raw(export, sub_handle->obj_ops->release(sub_handle)); return status; } int display_mdcache_key(struct display_buffer *dspbuf, mdcache_key_t *key) { int b_left = display_printf( dspbuf, "hk=%" PRIx64 " fsal=%p key=", key->hk, key->fsal); if (b_left <= 0) return b_left; return display_opaque_bytes(dspbuf, key->kv.addr, key->kv.len); } /** * @brief Find a cache entry by it's key * * Lookup a cache entry by key. If it is not in the cache, it is not returned. * * @param[in] key Cache key to use for lookup * @param[out] entry Entry, if found * @param[in] flags Flags to pass to mdcache_lru_ref * * @note This returns ref'd entry on success, INITIAL if @a reason is not SCAN * * @return Status */ fsal_status_t mdcache_find_keyed_reason(mdcache_key_t *key, mdcache_entry_t **entry, uint32_t flags) { cih_latch_t latch; if (key->kv.addr == NULL) { LogDebug(COMPONENT_MDCACHE, "Attempt to use NULL key"); return fsalstat(ERR_FSAL_INVAL, 0); } if (isFullDebug(COMPONENT_MDCACHE)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; (void)display_mdcache_key(&dspbuf, key); LogFullDebug(COMPONENT_MDCACHE, "Looking for %s", str); } *entry = cih_get_by_key_latch(key, &latch, CIH_GET_UNLOCK_ON_MISS, __func__, __LINE__); if (likely(*entry)) { fsal_status_t status; /* Initial Ref on entry */ mdcache_lru_ref(*entry, flags); /* Release the subtree hash table lock */ cih_hash_release(&latch); status = mdc_check_mapping(*entry); if (unlikely(FSAL_IS_ERROR(status))) { /* Export is in the process of being removed, don't * add this entry to the export, and bail out of the * operation sooner than later. */ mdcache_lru_unref(*entry, flags); *entry = NULL; return status; } LogFullDebug(COMPONENT_MDCACHE, "Found entry %p", *entry); (void)atomic_inc_uint64_t(&cache_stp->inode_hit); return fsalstat(ERR_FSAL_NO_ERROR, 0); } return fsalstat(ERR_FSAL_NOENT, 0); } /** * @brief Find or create a cache entry by it's host-handle * * Locate a cache entry by host-handle. If it is not in the cache, an attempt * will be made to create it and insert it in the cache. * * @param[in] key Cache key to use for lookup * @param[in] export Export for this cache * @param[out] entry Entry, if found * @param[in,out] attrs_out Optional attributes for newly created object * * @note This returns an INITIAL ref'd entry on success * * @return Status */ fsal_status_t mdcache_locate_host(struct gsh_buffdesc *fh_desc, struct mdcache_fsal_export *export, mdcache_entry_t **entry, struct fsal_attrlist *attrs_out) { struct fsal_export *sub_export = export->mfe_exp.sub_export; mdcache_key_t key; struct fsal_obj_handle *sub_handle; struct fsal_attrlist attrs; fsal_status_t status; /* Copy the fh_desc into key, todo: is there a function for this? */ /* We want to save fh_desc so allocate a stack buffer. Allow for * FSAL_KEY_EXTRA_BYTES. */ key.kv.len = fh_desc->len; key.kv.addr = alloca(fh_desc->len + FSAL_KEY_EXTRA_BYTES); memcpy(key.kv.addr, fh_desc->addr, key.kv.len); subcall_raw(export, status = sub_export->exp_ops.host_to_key(sub_export, &key.kv)); if (FSAL_IS_ERROR(status)) return status; assert(key.kv.len <= fh_desc->len + FSAL_KEY_EXTRA_BYTES); cih_hash_key(&key, sub_export->fsal, &key.kv, CIH_HASH_KEY_PROTOTYPE); status = mdcache_find_keyed_reason(&key, entry, LRU_ACTIVE_REF | LRU_PROMOTE); if (!FSAL_IS_ERROR(status)) { status = get_optional_attrs(&(*entry)->obj_handle, attrs_out); return status; } else if (status.major != ERR_FSAL_NOENT) { /* Actual error */ return status; } /* Ask for all supported attributes except ACL (we defer fetching ACL * until asked for it (including a permission check). */ fsal_prepare_attrs(&attrs, op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) & ~ATTR_ACL); sub_export = export->mfe_exp.sub_export; subcall_raw(export, status = sub_export->exp_ops.create_handle( sub_export, fh_desc, &sub_handle, &attrs)); if (unlikely(FSAL_IS_ERROR(status))) { LogDebug(COMPONENT_MDCACHE, "create_handle failed with %s", fsal_err_txt(status)); *entry = NULL; fsal_release_attrs(&attrs); return status; } status = mdcache_new_entry(export, sub_handle, &attrs, false, attrs_out, false, entry, NULL, LRU_ACTIVE_REF | LRU_PROMOTE); fsal_release_attrs(&attrs); if (!FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_MDCACHE, "create_handle Created entry %p FSAL %s", (*entry), (*entry)->sub_handle->fsal->name); } return status; } /** * @brief Try to get a cached child * * Get the cached entry child of @a mdc_parent If the cached entry cannot be * found, for whatever reason, return ERR_FSAL_STALE * * @note Caller MUST hold the content_lock for read * * @param[in] mdc_parent Parent directory * @param[in] name Name of child * @param[out] entry Child entry, on success * * @note This returns an INITIAL ref'd entry on success * @return FSAL status */ fsal_status_t mdc_try_get_cached(mdcache_entry_t *mdc_parent, const char *name, mdcache_entry_t **entry) { mdcache_dir_entry_t *dirent = NULL; fsal_status_t status = { 0, 0 }; LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Look in cache %s, trust content %s", name, test_mde_flags(mdc_parent, MDCACHE_TRUST_CONTENT) ? "yes" : "no"); #ifdef DEBUG_MDCACHE assert(mdc_parent->content_lock.__data.__readers || mdc_parent->content_lock.__data.__cur_writer); #endif *entry = NULL; /* If aren't caching dirents, return stale */ if (mdcache_param.dir.avl_chunk == 0) return fsalstat(ERR_FSAL_STALE, 0); /* If the dirent cache is untrustworthy, don't even ask it */ if (!test_mde_flags(mdc_parent, MDCACHE_TRUST_CONTENT)) return fsalstat(ERR_FSAL_STALE, 0); dirent = mdcache_avl_lookup(mdc_parent, name); if (dirent) { if (dirent->chunk != NULL) { /* Bump the chunk in the LRU */ lru_bump_chunk(dirent->chunk); } else { /* Bump the detached dirent. */ bump_detached_dirent(mdc_parent, dirent); } status = mdcache_find_keyed_reason( &dirent->ckey, entry, LRU_ACTIVE_REF | LRU_PROMOTE); if (!FSAL_IS_ERROR(status)) return status; LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "mdcache_find_keyed %s failed %s", name, fsal_err_txt(status)); } else { /* ! dirent */ LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "mdcache_avl_lookup %s failed trust negative %s", name, trust_negative_cache(mdc_parent) ? "yes" : "no"); if (trust_negative_cache(mdc_parent)) { /* If the dirent cache is both fully populated and * valid, it can serve negative lookups. */ return fsalstat(ERR_FSAL_NOENT, 0); } } return fsalstat(ERR_FSAL_STALE, 0); } /** * @brief Lookup a name (helper) * * Lookup a name relative to another object. If @a uncached is true and a cache * miss occurs, then the underlying file is looked up and added to the cache, if * it exists. * * The caller will set the request_mask in attrs_out to indicate the attributes * of interest. ATTR_ACL SHOULD NOT be requested and need not be provided. If * not all the requested attributes can be provided, this method MUST return * an error unless the ATTR_RDATTR_ERR bit was set in the request_mask. * * Since this method instantiates a new fsal_obj_handle, it will be forced * to fetch at least some attributes in order to even know what the object * type is (as well as it's fileid and fsid). For this reason, the operation * as a whole can be expected to fail if the attributes were not able to be * fetched. * * @param[in] parent Handle of container * @param[in] name Name to look up * @param[in] uncached If true, do an uncached lookup on cache failure * @param[out] handle Handle of found object, on success * @param[in,out] attrs_out Optional attributes for newly created object * * @note This returns an INITIAL ref'd entry on success * @return FSAL status */ fsal_status_t mdc_lookup(mdcache_entry_t *mdc_parent, const char *name, bool uncached, mdcache_entry_t **new_entry, struct fsal_attrlist *attrs_out) { *new_entry = NULL; fsal_status_t status; #ifdef USE_MONITORING const char *OPERATION = "lookup"; struct fsal_export *export = op_ctx->fsal_export; const uint16_t export_id = (export == NULL ? 0 : export->export_id); #endif /* USE_MONITORING */ LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Lookup %s", name); /* ".." doesn't end up in the cache */ if (!strcmp(name, "..")) { struct mdcache_fsal_export *export = mdc_cur_export(); struct gsh_buffdesc tmpfh; LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Lookup parent (..) of %p", mdc_parent); status = mdc_get_parent(export, mdc_parent, &tmpfh); if (FSAL_IS_SUCCESS(status)) { status = mdcache_locate_host(&tmpfh, export, new_entry, attrs_out); mdcache_free_fh(&tmpfh); } if (status.major == ERR_FSAL_STALE) status.major = ERR_FSAL_NOENT; #ifdef USE_MONITORING monitoring__dynamic_mdcache_cache_miss(OPERATION, export_id); #endif /* USE_MONITORING */ return status; } PTHREAD_RWLOCK_rdlock(&mdc_parent->content_lock); if (mdcache_param.dir.avl_chunk == 0) { /* We aren't caching dirents; call directly. * NOTE: Technically we will call mdc_lookup_uncached not * holding the content_lock write as required, however * since we are operating uncached here, ultimately there * will be no addition to the dirent cache, and thus no * need to hold the write lock. */ goto uncached; } /* We first try avltree_lookup by name. If that fails, we dispatch to * the FSAL. */ status = mdc_try_get_cached(mdc_parent, name, new_entry); if (status.major == ERR_FSAL_STALE) { /* Get a write lock and try again */ PTHREAD_RWLOCK_unlock(&mdc_parent->content_lock); LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Try again %s", name); PTHREAD_RWLOCK_wrlock(&mdc_parent->content_lock); status = mdc_try_get_cached(mdc_parent, name, new_entry); } if (!FSAL_IS_ERROR(status)) { /* Success! Now fetch attr if requested, drop content_lock * to avoid ABBA locking situation. */ PTHREAD_RWLOCK_unlock(&mdc_parent->content_lock); LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Found, possible getattrs %s (%s)", name, attrs_out != NULL ? "yes" : "no"); status = get_optional_attrs(&(*new_entry)->obj_handle, attrs_out); if (FSAL_IS_ERROR(status)) { /* Oops, failed to get attributes and ATTR_RDATTR_ERR * was not requested, so we are failing lookup and * thus must drop the object reference we got. */ mdcache_lru_unref(*new_entry, LRU_ACTIVE_REF); *new_entry = NULL; } #ifdef USE_MONITORING monitoring__dynamic_mdcache_cache_hit(OPERATION, export_id); #endif /* USE_MONITORING */ return status; } else if (!uncached) { /* Was only looking in cache, so don't bother looking further */ goto out; } else if (status.major != ERR_FSAL_STALE) { /* Actual failure */ LogDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Lookup %s failed %s", name, fsal_err_txt(status)); goto out; } /* Need to look up. */ if (!test_mde_flags(mdc_parent, MDCACHE_TRUST_CONTENT)) { /* We have the write lock and the content is * still invalid. Empty it out and mark it * valid in preparation for caching the result of this lookup. */ mdcache_dirent_invalidate_all(mdc_parent); } LogDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Cache Miss detected for %s", name); uncached: status = mdc_lookup_uncached(mdc_parent, name, new_entry, attrs_out); out: PTHREAD_RWLOCK_unlock(&mdc_parent->content_lock); if (status.major == ERR_FSAL_STALE) status.major = ERR_FSAL_NOENT; #ifdef USE_MONITORING monitoring__dynamic_mdcache_cache_miss(OPERATION, export_id); #endif /* USE_MONITORING */ return status; } /** * @brief Lookup an uncached entry from the sub-FSAL * * The entry has already been determined to not be cached, and the parent is * already write-locked. Lookup the child and create a cached entry for it. * * @note mdc_parent MUST have it's content_lock held for writing * * @param[in] mdc_parent Parent entry * @param[in] name Name of entry to find * @param[out] new_entry New entry to return; * @param[in,out] attrs_out Optional attributes for entry * * @note This returns an INITIAL ref'd entry on success * * @return FSAL status */ fsal_status_t mdc_lookup_uncached(mdcache_entry_t *mdc_parent, const char *name, mdcache_entry_t **new_entry, struct fsal_attrlist *attrs_out) { struct fsal_obj_handle *sub_handle = NULL, *new_obj = NULL; fsal_status_t status; struct mdcache_fsal_export *export = mdc_cur_export(); struct fsal_attrlist attrs; bool invalidate = false; /* Ask for all supported attributes except ACL (we defer fetching ACL * until asked for it (including a permission check). */ fsal_prepare_attrs(&attrs, op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) & ~ATTR_ACL); subcall(status = mdc_parent->sub_handle->obj_ops->lookup( mdc_parent->sub_handle, name, &sub_handle, &attrs)); if (unlikely(FSAL_IS_ERROR(status))) { LogDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "lookup %s failed with %s", name, fsal_err_txt(status)); *new_entry = NULL; fsal_release_attrs(&attrs); return status; } /* We are only called to fill cache, we should not need to invalidate * parents attributes (or dirents if chunked). * * NOTE: This does mean that a pure lookup of a file that had been added * external to this Ganesha instance could cause us to not dump * the dirent cache, however, that should still result in an * attribute change which should dump the cache. */ status = mdcache_alloc_and_check_handle(export, sub_handle, &new_obj, false, &attrs, attrs_out, "lookup ", mdc_parent, name, &invalidate, NULL); fsal_release_attrs(&attrs); if (FSAL_IS_ERROR(status)) { *new_entry = NULL; } else { *new_entry = container_of(new_obj, mdcache_entry_t, obj_handle); } return status; } /** * @brief Lock two directories in order * * This function gets the locks on both entries. If src and dest are * the same, it takes only one lock. Locks are acquired with lowest * cache_entry first to avoid deadlocks. * * @param[in] src Source directory to lock * @param[in] dest Destination directory to lock */ void mdcache_src_dest_lock(mdcache_entry_t *src, mdcache_entry_t *dest) { int rc; /* * A problem found in this order * 1. mdcache_readdir holds A's content_lock, and tries to * grab B's attr_lock. * 2. mdcache_remove holds B's attr_lock, and tries to grab B's * content_lock * 3. mdcache_rename holds B's content_lock, and tries to grab the * A's content_lock (which is held by thread 1). * This change is to avoid this deadlock. */ retry_lock: if (src == dest) PTHREAD_RWLOCK_wrlock(&src->content_lock); else if (src < dest) { PTHREAD_RWLOCK_wrlock(&src->content_lock); rc = pthread_rwlock_trywrlock(&dest->content_lock); if (rc) { LogDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "retry dest %p lock, src %p", dest, src); PTHREAD_RWLOCK_unlock(&src->content_lock); sleep(1); goto retry_lock; } } else { PTHREAD_RWLOCK_wrlock(&dest->content_lock); rc = pthread_rwlock_trywrlock(&src->content_lock); if (rc) { LogDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "retry src %p lock, dest %p", src, dest); PTHREAD_RWLOCK_unlock(&dest->content_lock); sleep(1); goto retry_lock; } } } /** * @brief Unlock two directories in order * * This function releases the locks on both entries. If src and dest * are the same, it releases the lock and returns. Locks are released * with lowest cache_entry first. * * @param[in] src Source directory to lock * @param[in] dest Destination directory to lock */ void mdcache_src_dest_unlock(mdcache_entry_t *src, mdcache_entry_t *dest) { if (src == dest) PTHREAD_RWLOCK_unlock(&src->content_lock); else if (src < dest) { PTHREAD_RWLOCK_unlock(&dest->content_lock); PTHREAD_RWLOCK_unlock(&src->content_lock); } else { PTHREAD_RWLOCK_unlock(&src->content_lock); PTHREAD_RWLOCK_unlock(&dest->content_lock); } } /** * * @brief Adds a directory entry to a cached directory. * * This function adds a new directory entry to a directory. Directory * entries have only weak references, so they do not prevent recycling * or freeing the entry they locate. This function may be called * either once (for handling creation) or iteratively in directory * population. * * @note Caller MUST hold the content_lock for write and must only call if * dirents are being cached. * * @param[in,out] parent Cache entry of the directory being updated * @param[in] name The name to add to the entry * @param[in] entry The cache entry associated with name * @param[in,out] invalidate Invalidate the parent directory contents if * adding to a chunk fails, if adding to a chunk * succeeds, invalidate will be reset to false * and the caller MUST refresh the attributes * without invalidating the dirent cache. * * @return FSAL status */ fsal_status_t mdcache_dirent_add(mdcache_entry_t *parent, const char *name, mdcache_entry_t *entry, bool *invalidate) { mdcache_dir_entry_t *new_dir_entry, *allocated_dir_entry; size_t namesize = strlen(name) + 1; int code = 0; LogFullDebug(COMPONENT_MDCACHE, "Add dir entry %s", name); if (name[0] == '\0') { /* An empty dirent name is invalid */ LogInfo(COMPONENT_MDCACHE, "Invalid dirent with empty name"); return fsalstat(ERR_FSAL_INVAL, 0); } #ifdef DEBUG_MDCACHE assert(parent->content_lock.__data.__cur_writer); #endif /* in cache avl, we always insert on pentry_parent */ new_dir_entry = gsh_calloc(1, sizeof(mdcache_dir_entry_t) + namesize); new_dir_entry->flags = DIR_ENTRY_FLAG_NONE; allocated_dir_entry = new_dir_entry; memcpy(&new_dir_entry->name_buffer, name, namesize); new_dir_entry->name = new_dir_entry->name_buffer; mdcache_key_dup(&new_dir_entry->ckey, &entry->fh_hk.key); /* add to avl */ code = mdcache_avl_insert(parent, &new_dir_entry); if (code < 0) { /** @todo: maybe we should actually invalidate the dirent cache * at this point? * * This indicates an odd condition in the tree, just treat * as an EEXIST condition. */ LogDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Returning EEXIST for %s code %d", name, code); return fsalstat(ERR_FSAL_EXIST, 0); } /* we're going to succeed */ if (new_dir_entry == allocated_dir_entry) { /* Place new dirent into a chunk or as detached. */ place_new_dirent(parent, new_dir_entry); /* Since we are chunking, we can preserve the dirent cache for * the purposes of lookups even if we could not add the new * dirent to a chunk, so we don't want to invalidate the parent * directory. */ *invalidate = false; } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Remove a cached directory entry * * @note Caller MUST hold the content_lock for write * * @param[in] parent Parent directory * @param[in] name Name to remove */ void mdcache_dirent_remove(mdcache_entry_t *parent, const char *name) { #ifdef DEBUG_MDCACHE assert(parent->content_lock.__data.__cur_writer); #endif /* Don't remove if we aren't doing dirent caching or the cache is empty */ if (mdcache_param.dir.avl_chunk != 0 && avltree_size(&parent->fsobj.fsdir.avl.t) != 0) { mdcache_dir_entry_t *dirent; LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Remove dir entry %s", name); dirent = mdcache_avl_lookup(parent, name); if (dirent != NULL) avl_dirent_set_deleted(parent, dirent); } } /** * @brief State to be passed to FSAL readdir callbacks */ struct mdcache_populate_cb_state { struct mdcache_fsal_export *export; mdcache_entry_t *dir; fsal_status_t *status; fsal_readdir_cb cb; void *dir_state; /**< For unchunked */ /** First chunk of this cycle */ struct dir_chunk *first_chunk; /** Current chunk of this cycle */ struct dir_chunk *cur_chunk; /** Chunk previous to cur_chunk, if known */ struct dir_chunk *prev_chunk; /** dirent to be filled in when whence_is_name */ mdcache_dir_entry_t **dirent; /** Cookie is what we are actually searching for */ fsal_cookie_t cookie; /** Indicates if FSAL expects whence to be a name. */ bool whence_is_name; /** If whence_is_name, indicate if we are looking for caller's cookie. */ bool whence_search; /** First hit on a search is the dirent we're looking for */ bool first_hit; }; /** * @brief Handle a readdir callback on uncache dir * * Cache a sindle object, passing it up the stack to the caller. This is for * handling readdir on a directory that is not being cached, for example because * is too big. Dirents are not created by this callback, just objects. * * @param[in] name Name of the directory entry * @param[in] sub_handle Object for entry * @param[in] attrs Attributes requested for the object * @param[in,out] dir_state Callback state * @param[in] cookie Directory cookie * * @returns fsal_dir_result */ static enum fsal_dir_result mdc_readdir_uncached_cb(const char *name, struct fsal_obj_handle *sub_handle, struct fsal_attrlist *attrs, void *dir_state, fsal_cookie_t cookie) { struct mdcache_populate_cb_state *state = dir_state; fsal_status_t status = { 0, 0 }; mdcache_entry_t *directory = container_of(&state->dir->obj_handle, mdcache_entry_t, obj_handle); mdcache_entry_t *new_entry = NULL; enum fsal_dir_result rv; /* This is in the middle of a subcall. Do a supercall */ supercall_raw(state->export, status = mdcache_new_entry( state->export, sub_handle, attrs, true, NULL, false, &new_entry, NULL, LRU_ACTIVE_REF)); if (FSAL_IS_ERROR(status)) { *state->status = status; if (status.major == ERR_FSAL_XDEV) { LogInfoAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Ignoring XDEV entry %s", name); *state->status = fsalstat(ERR_FSAL_NO_ERROR, 0); return DIR_CONTINUE; } LogInfoAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Lookup failed on %s in dir %p with %s", name, directory, fsal_err_txt(*state->status)); return DIR_TERMINATE; } /* Call up the stack. Do a supercall */ supercall_raw(state->export, rv = state->cb(name, &new_entry->obj_handle, &new_entry->attrs, state->dir_state, cookie)); return rv; } /** * Perform an uncached readdir * * Large directories do not have their dirents cached. This performs readdir on * such directories, by passing the sub-FSAL's results back up through the * stack. * * @note The object passed into the callback is ref'd and must be unref'd by the * callback. * * @param[in] directory the directory to read * @param[in] whence where to start (next) * @param[in] dir_state pass thru of state to callback * @param[in] cb callback function * @param[in] attrmask Which attributes to fill * @param[out] eod_met eod marker true == end of dir * * @return FSAL status */ fsal_status_t mdcache_readdir_uncached(mdcache_entry_t *directory, fsal_cookie_t *whence, void *dir_state, fsal_readdir_cb cb, attrmask_t attrmask, bool *eod_met) { fsal_status_t status = { 0, 0 }; fsal_status_t readdir_status = { 0, 0 }; struct mdcache_populate_cb_state state; state.export = mdc_cur_export(); state.dir = directory; state.status = &status; state.cb = cb; state.dir_state = dir_state; subcall(readdir_status = directory->sub_handle->obj_ops->readdir( directory->sub_handle, whence, &state, mdc_readdir_uncached_cb, attrmask, eod_met)); if (FSAL_IS_ERROR(readdir_status)) return readdir_status; return status; } /** * @brief Place a new dirent from create, lookup, or rename into a chunk if * possible, otherwise place as a detached dirent. * * If addition is not possible because the entry does not belong to an * active dirent chunk, nothing happens. The dirent may still be inserted * into the by name lookup as a detached dirent. * * @note If we can't insert the dirent into a chunk because we can't figure * out which chunk it belongs to, we can still trust the chunks, the new dirent * is not within their range, and if inserted between two non-adjacent chunks, * a subsequent readdir that enumerates that part of the directory will pick up * the new dirent since it will have to populate at least one new chunk in the * gap. * * @note parent_dir MUST have it's content_lock held for writing * * @param[in] parent_dir The directory this dir entry is part of * @param[in] new_dir_entry The dirent to add. * */ void place_new_dirent(mdcache_entry_t *parent_dir, mdcache_dir_entry_t *new_dir_entry) { mdcache_dir_entry_t *left; mdcache_dir_entry_t *right; struct avltree_node *node, *parent, *unbalanced, *other; int is_left, code; fsal_cookie_t ck, nck; struct dir_chunk *chunk; bool invalidate_chunks = true; #ifdef DEBUG_MDCACHE assert(parent_dir->content_lock.__data.__cur_writer); #endif subcall(ck = parent_dir->sub_handle->obj_ops->compute_readdir_cookie( parent_dir->sub_handle, new_dir_entry->name)); if (ck == 0) { /* FSAL does not support computing readdir cookie, so we can't * add this entry to a chunk, nor can we trust the chunks. */ LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Could not add %s to chunk for directory %p, compute_readdir_cookie failed", new_dir_entry->name, parent_dir); goto out; } new_dir_entry->ck = ck; node = avltree_do_lookup(&new_dir_entry->node_sorted, &parent_dir->fsobj.fsdir.avl.sorted, &parent, &unbalanced, &is_left, avl_dirent_sorted_cmpf); if (isFullDebug(COMPONENT_MDCACHE) || isFullDebug(COMPONENT_NFS_READDIR)) { if (node) { right = avltree_container_of(node, mdcache_dir_entry_t, node_sorted); } LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "avltree_do_lookup returned node=%p (name=%s, ck=%" PRIx64 ") parent=%p unbalanced=%p is_left=%s", node, node ? right->name : "", node ? right->ck : 0, parent, unbalanced, is_left ? "true" : "false"); } if (node) { right = avltree_container_of(node, mdcache_dir_entry_t, node_sorted); if (ck == FIRST_COOKIE && right->ck == FIRST_COOKIE) { /* Special case of inserting a new first entry. * We should only have to do this for FSALs that * sort dirents by cookie value that support * compute_readdir_cookie and are unable to actually * compute the cookie for the very first directory * entry. */ subcall(nck = parent_dir->sub_handle->obj_ops ->compute_readdir_cookie( parent_dir->sub_handle, right->name)); if (nck == 0) { /* Oops, could not compute new cookie... * We can no longer trust the chunks. */ LogCrit(COMPONENT_MDCACHE, "Could not compute new cookie for %s in directory %p", right->name, parent_dir); goto out; } /* Just change up the old first entries cookie, which * will leave room to insert the new entry with cookie * of FIRST_COOKIE. */ right->ck = nck; } else { /* This should not happen... Let's no longer trust the * chunks. */ LogCrit(COMPONENT_MDCACHE, "Could not add %s to chunk for directory %p, node %s found withck=%" PRIx64, new_dir_entry->name, parent_dir, right->name, right->ck); goto out; } } if (parent == NULL) { /* The tree must be empty, there are no chunks to add this * entry to. There are no chunks to trust... */ LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Could not add %s to chunk for directory %p, tree was empty", new_dir_entry->name, parent_dir); goto out; } if (is_left) { /* Parent will be to the right of the key. */ right = avltree_container_of(parent, mdcache_dir_entry_t, node_sorted); other = avltree_prev(parent); if (other) { left = avltree_container_of(other, mdcache_dir_entry_t, node_sorted); LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "%s is between %s and parent %s", new_dir_entry->name, left->name, right->name); } else { left = NULL; if (parent_dir->fsobj.fsdir.first_ck == right->ck) { /* The right node is the first entry in the * directory. Add this key to the beginning of * the first chunk and fixup the chunk. */ LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Adding %s as new first entry", new_dir_entry->name); } else { /* The right entry is not the first entry in * the directory, so the key is a dirent * somewhere before the first chunked dirent. * we can't insert this key into a chunk, * however, we can still trust the chunks since * the new entry is part of the directory we * don't have cached, a readdir that wants that * part of the directory will populate a new * chunk. */ LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Could not add %s to chunk for directory %p, somewhere before first chunk", new_dir_entry->name, parent_dir); invalidate_chunks = false; goto out; } } } else { /* Parent will be to the left of the key. */ left = avltree_container_of(parent, mdcache_dir_entry_t, node_sorted); other = avltree_next(parent); if (other) { right = avltree_container_of(other, mdcache_dir_entry_t, node_sorted); LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "%s is between parent %s and %s", new_dir_entry->name, left->name, right->name); } else { right = NULL; if (left->eod) { /* The right node is the last entry in the * directory. Add this key to the end of the * last chunk and fixup the chunk. */ LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Adding %s as new last entry", new_dir_entry->name); } else { /* The left entry is not the last entry in * the directory, so the key is a dirent * somewhere after the last chunked dirent. * we can't insert this key into a chunk, * however, we can still trust the chunks since * the new entry is part of the directory we * don't have cached, a readdir that wants that * part of the directory will populate a new * chunk. */ LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Could not add %s to chunk for directory %p, somewhere after last chunk", new_dir_entry->name, parent_dir); invalidate_chunks = false; goto out; } } } /* Note in the following, every dirent that is in the sorted tree MUST * be in a chunk, so we don't check for chunk != NULL. */ /* Set up to add to chunk and by cookie AVL tree. */ if (right == NULL) { /* Will go at end of left chunk. */ chunk = new_dir_entry->chunk = left->chunk; } else { /* Will go at begin of right chunk. */ chunk = new_dir_entry->chunk = right->chunk; } code = mdcache_avl_insert_ck(parent_dir, new_dir_entry); if (code < 0) { /* We failed to insert into FSAL cookie AVL tree, will fail. * Nothing to clean up since we haven't done anything * unreversible, and we no longer trust the chunks. */ goto out; } /* Get the node into the actual tree... */ avltree_do_insert(&new_dir_entry->node_sorted, &parent_dir->fsobj.fsdir.avl.sorted, parent, unbalanced, is_left); LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Inserted %s into sorted tree left=%p right=%p", new_dir_entry->name, new_dir_entry->node_sorted.left, new_dir_entry->node_sorted.right); LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Adding %s to chunk %p between %s and %s for directory %p", new_dir_entry->name, right ? right->chunk : left->chunk, left ? left->name : "BEGIN", right ? right->name : "END", parent_dir); /* And now add it to the chunk */ if (right == NULL) { /* Insert node at END of the chunk represented by left. */ glist_add_tail(&left->chunk->dirents, &new_dir_entry->chunk_list); /* Make the new entry the eod entry. */ new_dir_entry->eod = true; left->eod = false; } else { /* Insert to left of right, which if left and right are * different chunks, inserts into the right hand chunk. * * NOTE: This looks weird, normally we pass the list head to * glist_add_tail, but glist_add_tail really just * inserts the entry before the first parameter, recall * that the list head is just a member of the list... * * If left == NULL, then the "list node" to the left of * right is the actual list head, and this all works out... */ glist_add_tail(&right->chunk_list, &new_dir_entry->chunk_list); if (left != NULL) { /* Fixup left chunk's next cookie */ left->chunk->next_ck = new_dir_entry->ck; LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Fixup next_ck=%" PRIx64, left->chunk->next_ck); } else { /* New first entry in directory */ LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Setting directory first_ck=%" PRIx64, new_dir_entry->ck); parent_dir->fsobj.fsdir.first_ck = new_dir_entry->ck; } } /* And now increment the number of entries in the chunk. */ chunk->num_entries++; /* And bump the chunk in the LRU */ lru_bump_chunk(chunk); if (chunk->num_entries == mdcache_param.dir.avl_chunk_split) { /* Create a new chunk */ struct dir_chunk *split; struct glist_head *glist; mdcache_dir_entry_t *here = NULL; int i = 0; uint32_t split_count = mdcache_param.dir.avl_chunk_split / 2; split = mdcache_get_chunk(parent_dir, chunk, 0); split->next_ck = chunk->next_ck; LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Split next_ck=%" PRIx64, split->next_ck); /* Make sure this chunk is in the MRU of L1 */ lru_bump_chunk(split); /* Scan the list to find what will be the first dirent in the * new split chunk. */ glist_for_each(glist, &chunk->dirents) { if (++i > (split_count)) { /* Got past the halfway point. */ here = glist_entry(glist, mdcache_dir_entry_t, chunk_list); break; } } assert(here != NULL); LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Splitting chunk %p for directory %p at %s", chunk, parent_dir, here->name); /* Split chunk->dirents into split->dirents at here */ glist_split(&chunk->dirents, &split->dirents, glist); chunk->num_entries = split_count; split->num_entries = split_count; chunk->reload_ck = glist_last_entry(&chunk->dirents, mdcache_dir_entry_t, chunk_list) ->ck; /* Update the chunk pointer on all the dirents */ glist_for_each(glist, &split->dirents) { mdcache_dir_entry_t *dirent; dirent = glist_entry(glist, mdcache_dir_entry_t, chunk_list); dirent->chunk = split; } /* Fill in the first chunk's next_ck to be the cookie of the * first dirent in the new split chunk. */ chunk->next_ck = here->ck; mdcache_lru_unref_chunk(split); LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Chunk next_ck=%" PRIx64, chunk->next_ck); } new_dir_entry->flags |= DIR_ENTRY_SORTED; invalidate_chunks = false; out: if (invalidate_chunks) { /* Indicate we not longer trust the chunk cache. */ LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Entry %p clearing MDCACHE_DIR_POPULATED, MDCACHE_TRUST_DIR_CHUNKS", parent_dir); atomic_clear_uint32_t_bits(&parent_dir->mde_flags, MDCACHE_DIR_POPULATED | MDCACHE_TRUST_DIR_CHUNKS); } if (new_dir_entry->chunk == NULL) { /* This is a detached directory entry, add it to the LRU list of * detached directory entries. This is the one and only place a * detached dirent can be added. */ add_detached_dirent(parent_dir, new_dir_entry); } } /** * @brief Handle adding an element to a dirent chunk * * Cache a sindle object, and add it to the directory chunk in progress. * * @param[in] name Name of the directory entry * @param[in] sub_handle Object for entry * @param[in] attrs Attributes requested for the object * @param[in,out] dir_state Callback state * @param[in] cookie Directory cookie * * @returns fsal_dir_result */ static enum fsal_dir_result mdc_readdir_chunk_object(const char *name, struct fsal_obj_handle *sub_handle, struct fsal_attrlist *attrs_in, void *dir_state, fsal_cookie_t cookie) { struct mdcache_populate_cb_state *state = dir_state; struct mdcache_fsal_export *export = mdc_cur_export(); mdcache_entry_t *new_entry = NULL; mdcache_dir_entry_t *new_dir_entry = NULL, *allocated_dir_entry = NULL; size_t namesize = strlen(name) + 1; int code = 0; fsal_status_t status; enum fsal_dir_result result = DIR_CONTINUE; #ifdef DEBUG_MDCACHE assert(state->dir->content_lock.__data.__cur_writer); #endif if (state->cur_chunk->num_entries == mdcache_param.dir.avl_chunk) { /* We are being called readahead. */ LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Readdir readahead first entry in new chunk %s", name); mdcache_lru_unref_chunk(state->prev_chunk); state->prev_chunk = state->cur_chunk; state->prev_chunk->next_ck = cookie; /* Chunk is added to the chunks list before being passed in */ /* Now start a new chunk, passing this chunk as prev_chunk. */ state->cur_chunk = mdcache_get_chunk(state->prev_chunk->parent, state->prev_chunk, 0); LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Chunk %p Prev chunk %p next_ck=%" PRIx64, state->cur_chunk, state->prev_chunk, state->prev_chunk->next_ck); /* And start accepting entries into the new chunk. */ } LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Creating cache entry for %s cookie=0x%" PRIx64 " sub_handle=0x%p", name, cookie, sub_handle); status = mdcache_new_entry(export, sub_handle, attrs_in, false, NULL, false, &new_entry, NULL, LRU_ACTIVE_REF); if (FSAL_IS_ERROR(status)) { *state->status = status; LogInfoAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "mdcache_new_entry failed on %s in dir %p with %s", name, state->dir, fsal_err_txt(status)); return DIR_TERMINATE; } /* Entry was found in the FSAL, add this entry to the parent directory */ LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Add mdcache entry %p for %s for FSAL %s", new_entry, name, new_entry->sub_handle->fsal->name); /* in cache avl, we always insert on state->dir */ new_dir_entry = gsh_calloc(1, sizeof(mdcache_dir_entry_t) + namesize); new_dir_entry->flags = DIR_ENTRY_FLAG_NONE; new_dir_entry->chunk = state->cur_chunk; new_dir_entry->ck = cookie; allocated_dir_entry = new_dir_entry; /** @todo FSF - we could eventually try and support duplicated FSAL * cookies assuming they come sequentially (which they * would from EXT4 as far as I can tell from the EXT4 * code). We could never start a chunk with a duplicate * so we would have to put all of them into the same * chunk, possibly making the chunk larger than normal. */ memcpy(&new_dir_entry->name_buffer, name, namesize); new_dir_entry->name = new_dir_entry->name_buffer; mdcache_key_dup(&new_dir_entry->ckey, &new_entry->fh_hk.key); /* add to avl */ code = mdcache_avl_insert(state->dir, &new_dir_entry); if (code < 0) { /* We can get here with the following possibilities: * * - FSAL cookie collision, nothing we can do about this, but * also really should never happen. * - Name collision, something is broken and the FSAL has * given us multiple directory entries with the same name * but for different objects. Again, not much we can do. * * In any case, we will just ignore this entry. */ mdcache_lru_unref(new_entry, LRU_ACTIVE_REF); /* Check for return code -3 and/or -4. * -3: This indicates the file name is duplicate but FSAL * cookie is different. This may happen in case lots of new * entries got added to the directory while running readdir. * -4: This indicates that it is FSAL cookie duplication / * collision. This could happen due to fast mutating directory. * In both cases already cached contents are stale/invalid. * Need to invalidate the cache and inform client to re-read * the directory. */ if (code == -3 || code == -4) { atomic_clear_uint32_t_bits(&state->dir->mde_flags, MDCACHE_TRUST_CONTENT); state->status->major = ERR_FSAL_DELAY; state->status->minor = 0; return DIR_TERMINATE; } LogCrit(COMPONENT_MDCACHE, "Collision while adding dirent for %s", name); return DIR_CONTINUE; } /* Note that if this dirent was already in the lookup by name AVL * tree (state->dir->fsobj.fsdir.avl.t), then mdcache_avl_qp_insert * freed the dirent we allocated above, and returned the one that was * in tree. It will have set chunk, ck, and nk. * * The existing dirent might or might not be part of a chunk already. */ if (new_dir_entry != allocated_dir_entry) { LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Swapped %s using %p instead of %p, new_dir_entry->chunk=%p chunk=%p", new_dir_entry->name, new_dir_entry, allocated_dir_entry, new_dir_entry->chunk, state->cur_chunk); } assert(new_dir_entry->chunk); if (state->whence_search) { if (new_dir_entry->ck == state->cookie) { /* This is the cookie we were looking for, but we want * the next dirent */ state->first_hit = true; return DIR_CONTINUE; } else if (state->first_hit) { /* We have found the dirent the caller wanted */ LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Found dirent %s caller is looking for cookie = %" PRIx64, name, state->cookie); *(state->dirent) = new_dir_entry; state->first_hit = false; } } if (op_ctx->fsal_export->exp_ops.fs_supports( op_ctx->fsal_export, fso_compute_readdir_cookie)) { struct avltree_node *node; node = avltree_inline_insert( &new_dir_entry->node_sorted, &state->dir->fsobj.fsdir.avl.sorted, avl_dirent_sorted_cmpf); if (node != NULL) { if (node == &new_dir_entry->node_sorted) { LogDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "New entry %s was already in sorted tree", name); } else if (isDebug(COMPONENT_MDCACHE) || isDebug(COMPONENT_NFS_READDIR)) { mdcache_dir_entry_t *other; other = avltree_container_of( node, mdcache_dir_entry_t, node_sorted); LogDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "New entry %s collided with entry %s already in sorted tree", name, other->name); } } else { LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Inserted %s into sorted tree left=%p right=%p", name, new_dir_entry->node_sorted.left, new_dir_entry->node_sorted.right); new_dir_entry->flags |= DIR_ENTRY_SORTED; } } /* Add this dirent to the chunk if not already added. */ if (glist_null(&new_dir_entry->chunk_list)) { /* If this dirent is not already on a chunk_list, then we add * it. It could be the allocated_dir_entry or it could be an * old dirent that was not part of a chunk, but it is NOT the * same dirent that was already part of some other chunk. */ glist_add_tail(&state->cur_chunk->dirents, &new_dir_entry->chunk_list); state->cur_chunk->num_entries++; } if (new_dir_entry->chunk != state->cur_chunk) { /* We have the situation where we have collided with a * previously used chunk (and thus we have a partial chunk). * Since dirent is pointing to the existing dirent and the one * we allocated above has been freed we don't need to do any * cleanup. * * Don't allow readahead in this case just indicate this * directory is terminated. */ result = DIR_TERMINATE; LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Collision old chunk %p next_ck=%" PRIx64 " new chunk %p next_ck=%" PRIx64, new_dir_entry->chunk, new_dir_entry->chunk->next_ck, state->cur_chunk, state->cur_chunk->next_ck); if (state->cur_chunk->num_entries == 0) { LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Nuking empty Chunk %p", state->cur_chunk); /* We read-ahead into an existing chunk, and this chunk * is empty. Just ditch it now, to avoid any issue. */ mdcache_lru_unref_chunk(state->cur_chunk); if (state->first_chunk == state->cur_chunk) { /* Drop the first_chunk ref */ mdcache_lru_unref_chunk(state->first_chunk); state->first_chunk = new_dir_entry->chunk; /* And take the first_chunk ref */ mdcache_lru_ref_chunk(state->first_chunk); } state->cur_chunk = new_dir_entry->chunk; mdcache_lru_ref_chunk(state->cur_chunk); if (new_dir_entry->mde_entry) { /* This was ref'd already; drop extra ref */ mdcache_lru_unref(new_dir_entry->mde_entry, LRU_ACTIVE_REF); new_dir_entry->mde_entry = NULL; } if (state->prev_chunk && state->prev_chunk != state->cur_chunk) { state->prev_chunk->next_ck = new_dir_entry->ck; } } else { LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "keeping non-empty Chunk %p", state->cur_chunk); state->cur_chunk->next_ck = new_dir_entry->ck; } } else if (state->cur_chunk->num_entries == mdcache_param.dir.avl_chunk) { /* Chunk is full. Since dirent is pointing to the existing * dirent and the one we allocated above has been freed we don't * need to do any cleanup. * * Allow readahead. * * If there's actually any readahead, chunk->next_ck will get * filled in. */ result = DIR_READAHEAD; } else if (state->cur_chunk->num_entries == 1 && state->prev_chunk) { state->prev_chunk->next_ck = cookie; } if (new_entry->obj_handle.type == DIRECTORY) { /* Insert Parent's key */ PTHREAD_RWLOCK_wrlock(&new_entry->content_lock); mdc_dir_add_parent(new_entry, state->dir); PTHREAD_RWLOCK_unlock(&new_entry->content_lock); } LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "About to put entry %p refcnt=%" PRIi32, new_entry, atomic_fetch_int32_t(&new_entry->lru.refcnt)); if ((new_dir_entry != allocated_dir_entry && new_dir_entry->mde_entry) || (new_dir_entry->chunk != state->cur_chunk)) { /* This was swapped and already has a refcounted entry. Drop our * ref. */ mdcache_lru_unref(new_entry, LRU_ACTIVE_REF); } else { /* This is a new dirent, or doesn't have an entry. */ assert(!new_dir_entry->mde_entry); new_dir_entry->mde_entry = new_entry; } return result; } /** * @brief Handle a readdir callback for a chunked directory. * * This is a supercall wrapper around the function above that actually does * the work. * * @param[in] name Name of the directory entry * @param[in] sub_handle Object for entry * @param[in] attrs Attributes requested for the object * @param[in,out] dir_state Callback state * @param[in] cookie Directory cookie * * @returns fsal_dir_result */ static enum fsal_dir_result mdc_readdir_chunked_cb(const char *name, struct fsal_obj_handle *sub_handle, struct fsal_attrlist *attrs, void *dir_state, fsal_cookie_t cookie) { struct mdcache_populate_cb_state *state = dir_state; enum fsal_dir_result result; /* This is in the middle of a subcall. Do a supercall */ supercall_raw(state->export, result = mdc_readdir_chunk_object(name, sub_handle, attrs, dir_state, cookie)); return result; } /** * @brief Skip directory chunks while re-filling dirent cache in search of * a specific cookie that is not in cache. * * This will return with a ref on the returned chunk * * @note @a chunk will have it's ref dropped * @note The content lock MUST be held for write * * @param[in] directory The directory being read * @param[in] chunk Current chunk to skip * * @returns The chunk found, or NULL. */ static struct dir_chunk *mdcache_skip_chunks(mdcache_entry_t *directory, struct dir_chunk *chunk) { mdcache_dir_entry_t *dirent = NULL; /* We need to skip chunks that are already cached. */ while (chunk->next_ck != 0 && mdcache_avl_lookup_ck(directory, chunk->next_ck, &dirent)) { /* Drop ref on chunk; dirent->chunk has a new ref from above */ mdcache_lru_unref_chunk(chunk); chunk = dirent->chunk; } /* At this point, we have the last cached chunk before a gap. */ return chunk; } /** * @brief Read the next chunk of a directory * * If called for an FSAL that only supports whence as the dirent name to * continue from, and @a prev_chunk is NULL, we must scan the directory from the * beginning. If @a prev_chunk is not NULL, we can scan the directory starting * with the last dirent name in @a prev_chunk, but we must still scan the * directory until we find whence. * * @a prev_chunk must have a ref taken on it, and this ref will be released * * @note this returns a ref on the chunk containing @a dirent * * @param[in] directory The directory to read * @param[in] whence Where to start (next) * @param[in,out] dirent The first dirent of the chunk * @param[in] prev_chunk The previous chunk populated * @param[in,out] eod_met The end of directory has been hit. * * @return FSAL status */ fsal_status_t mdcache_populate_dir_chunk(mdcache_entry_t *directory, fsal_cookie_t whence, mdcache_dir_entry_t **dirent, struct dir_chunk *prev_chunk, bool *eod_met) { fsal_status_t status = { 0, 0 }; fsal_status_t readdir_status = { 0, 0 }; struct mdcache_populate_cb_state state; attrmask_t attrmask; fsal_cookie_t *whence_ptr = &whence; bool free_whence = false; bool rescan = false; attrmask = op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) | ATTR_RDATTR_ERR; state.export = mdc_cur_export(); state.dir = directory; state.status = &status; state.cb = NULL; /* We don't use the call back during chunking. */ state.cookie = whence; state.dirent = dirent; state.whence_is_name = op_ctx->fsal_export->exp_ops.fs_supports( op_ctx->fsal_export, fso_whence_is_name); state.whence_search = state.whence_is_name && whence != 0; state.first_hit = false; /* Set up chunks */ state.first_chunk = mdcache_get_chunk(directory, prev_chunk, whence); state.cur_chunk = state.first_chunk; mdcache_lru_ref_chunk(state.cur_chunk); /* prev_chunk was passed in with a ref on it */ state.prev_chunk = prev_chunk; if (state.whence_is_name) { LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "whence_is_name %s cookie %" PRIx64, state.whence_search ? "search" : "no search", state.cookie); } again: /* In whence_is_name case, we may need to do another FSAL readdir * call to continue scanning for the desired cookie, so we will jump * back to here to accomplish that. chunk is newly allocated and * prev_chunk has been updated to point to the last cached chunk. */ if (state.whence_is_name) { free_whence = false; if (state.prev_chunk != NULL) { /* Start from end of prev_chunk */ /* If end of directory, mark last dirent as eod. */ mdcache_dir_entry_t *last; last = glist_last_entry(&state.prev_chunk->dirents, mdcache_dir_entry_t, chunk_list); whence_ptr = (fsal_cookie_t *)last->name; if (!rescan) { /* We have a name, we're not going to get this * one, but the next one */ state.first_hit = true; } if (state.whence_search) { LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Calling FSAL readdir whence = %s, search %" PRIx64 " dirent %s", last->name, state.cookie, *dirent != NULL ? (*dirent)->name : ""); } else { LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Calling FSAL readdir whence = %s, no search", last->name); } } else { /* This is a tri-choice. That is, it's if, else-if, * else. But, it can't be coded that way because * checkpatch won't allow assignment in an if. */ whence_ptr = mdc_lru_unmap_dirent(whence); if (whence_ptr) { free_whence = true; state.first_hit = true; } else { /* Signal start from beginning by passing NULL * pointer. */ whence_ptr = NULL; if (state.whence_search) { LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Calling FSAL readdir whence = NULL, search %" PRIx64, state.cookie); } else { LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Calling FSAL readdir whence = NULL, no search"); } } } } else { LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Calling FSAL readdir whence = 0x%" PRIx64, whence); } GSH_AUTO_TRACEPOINT( mdcache, mdc_readdir_populate, TRACE_DEBUG, "Readdir poulate for obj handle: {}, sub handle: {}, whence: {}", &directory->obj_handle, directory->sub_handle, whence); subcall(readdir_status = directory->sub_handle->obj_ops->readdir( directory->sub_handle, whence_ptr, &state, mdc_readdir_chunked_cb, attrmask, eod_met)); if (free_whence) { gsh_free(whence_ptr); } if (FSAL_IS_ERROR(readdir_status)) { LogDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "FSAL readdir status=%s", fsal_err_txt(readdir_status)); *dirent = NULL; drop_state_chunk_refs(state.first_chunk, state.cur_chunk, state.prev_chunk, NULL); return readdir_status; } if (FSAL_IS_ERROR(status)) { LogDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "status=%s", fsal_err_txt(status)); *dirent = NULL; drop_state_chunk_refs(state.first_chunk, state.cur_chunk, state.prev_chunk, NULL); return status; } if (state.cur_chunk->num_entries == 0) { /* cur_chunk is empty - should only happen for an empty * directory but could happen if the FSAL failed to indicate end * of directory. This COULD happen on a readahead chunk, but it * would be unusual. */ LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Empty chunk"); /* Put our ref */ mdcache_lru_unref_chunk(state.cur_chunk); /* Put sentinel ref */ mdcache_lru_unref_chunk(state.cur_chunk); if (state.cur_chunk == state.first_chunk) { /* We really got nothing on this readdir, so don't * return a dirent. */ *dirent = NULL; mdcache_lru_unref_chunk(state.first_chunk); /* cur_chunk was freed */ mdcache_lru_unref_chunk(state.prev_chunk); LogDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "status=%s", fsal_err_txt(status)); return status; } /* If the empty chunk wasn't first, then prev_chunk is valid */ state.cur_chunk = state.prev_chunk; mdcache_lru_ref_chunk(state.cur_chunk); } if (*eod_met) { /* If end of directory, mark last dirent as eod. */ mdcache_dir_entry_t *last; last = glist_last_entry(&state.cur_chunk->dirents, mdcache_dir_entry_t, chunk_list); last->eod = true; } LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Chunk first entry %s%s", *dirent != NULL ? (*dirent)->name : "", *eod_met ? " EOD" : ""); if (state.whence_search && *dirent == NULL) { if (*eod_met) { /* Did not find cookie. */ drop_state_chunk_refs(state.first_chunk, state.cur_chunk, state.prev_chunk, NULL); status = fsalstat(ERR_FSAL_BADCOOKIE, 0); LogDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Could not find search cookie status=%s", fsal_err_txt(status)); return status; } /* We are re-scanning directory, and we have not found our * cookie yet, we either used up the FSAL's readdir (with any * readahead) or we collided with an already cached chunk, * which we know DOES NOT have our cookie (because otherwise we * would have found it on lookup), so we will start from where * we left off. * * chunk points to the last valid chunk of what we just read, * but we also have to check if we must skip chunks that had * already been in cache. * * If chunk->next_ck is 0, then we didn't collide, so there are * no chunks to skip. */ LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Rescan dir to find cookie needs to continue search for %" PRIx64, state.cookie); if (state.cur_chunk->next_ck != 0) { /* In the collision case, chunk->next_ck was set, * so now start skipping. */ LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Search skipping from cookie %" PRIx64, state.cur_chunk->next_ck); /* Pass the ref on cur_chunk; it will be dropped, and * the returned chunk will have a ref. */ state.cur_chunk = mdcache_skip_chunks(directory, state.cur_chunk); } /* drop refs on state, except cur_chunk which we're saving */ drop_state_chunk_refs(state.first_chunk, NULL, state.prev_chunk, NULL); /* We need to start a new FSAL readdir call, but we don't just * want to call mdcache_populate_dir_chunk raw, so set up a few * things and jump to again... */ /* The chunk we just dealt with is now prev_chunk. */ state.prev_chunk = state.cur_chunk; LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "About to allocate a new chunk to continue search, prev chunk = %p", state.prev_chunk); /* And we need to allocate a fresh chunk. */ state.first_chunk = mdcache_get_chunk(directory, state.prev_chunk, 0); state.cur_chunk = state.first_chunk; mdcache_lru_ref_chunk(state.cur_chunk); /* And go start a new FSAL readdir call. */ rescan = true; goto again; } if (*dirent == NULL) { /* We haven't set dirent yet, return the first entry of the * first chunk. */ *dirent = glist_first_entry(&state.first_chunk->dirents, mdcache_dir_entry_t, chunk_list); } /* At this point, we have ref's on first_chunk, cur_chunk, and * prev_chunk. Drop the refs and make sure the chunk containing dirent * is ref'd */ drop_state_chunk_refs(state.first_chunk, state.cur_chunk, state.prev_chunk, (*dirent)->chunk); /* At this point, only dirent->chunk should be ref'd */ LogDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "status=%s", fsal_err_txt(status)); return status; } /** * @brief Read the contents of a directory * * If necessary, populate dirent cache chunks from the underlying FSAL. Then, * walk the dirent cache chunks calling the callback. * * Interactions between readdir and entry LRU lifetime is complicated. We want * the LRU to be scan resistant, so that readdir() doesn't empty useful entries * from the LRU. However, the readdir() has to work in such a way that it's * entries are still in the cache when they're used. To achieve this, we do two * things: * * First, we insert objects created during a scan into the MRU of L2, rather * than the LRU of L1. This allows them to be recycled in FIFO order rather * than LIFO order. Observed behavior was that, when we are over the hi-water * mark, readdir() of a large directory would empty the L2 by recycling entries. * Then, it would start recycling the LRU of L1. However, the LRU of L1 * contained entries created during the readdir(). This means that, after the * chunk loaded, and it's entries need to be returned to the upper layer, they * have been recycled, and need to be re-created via a lookup() and getattr() * pair, causing large numbers of round-trips to the cluster. Inserting into * the MRU of L2 keeps the L2 from being emptied, and causes the entries to be * recycled FIFO, making it likely that the entries for a chunk are still in the * cache when needed. * * The second important thing to do is to *not* take an INITIAL ref on entries * when they are used during the scan. An INITIAL ref promotes the entry in the * LRU, which would put it at LRU of L1, recreating the above situation. To * avoid this, and keep scan resistance, we take a non-initial ref during * readdir(). * * @note The object passed into the callback is ref'd and must be unref'd by the * callback. * * @param[in] directory The directory to read * @param[in] whence Where to start (next) * @param[in] dir_state Pass thru of state to callback * @param[in] cb Callback function * @param[in] attrmask Which attributes to fill * @param[out] eod_met eod marker true == end of dir * * @return FSAL status */ fsal_status_t mdcache_readdir_chunked(mdcache_entry_t *directory, fsal_cookie_t whence, void *dir_state, fsal_readdir_cb cb, attrmask_t attrmask, bool *eod_met) { mdcache_dir_entry_t *dirent = NULL; bool has_write, set_first_ck; fsal_cookie_t next_ck = whence, look_ck = whence; fsal_cookie_t save_ck = 0; struct dir_chunk *chunk = NULL; bool eod = false; bool reload_chunk = false; bool whence_is_name = op_ctx->fsal_export->exp_ops.fs_supports( op_ctx->fsal_export, fso_whence_is_name); GSH_AUTO_TRACEPOINT(mdcache, mdc_readdir, TRACE_DEBUG, "Readdir for obj handle: {}", &directory->obj_handle); LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Starting chunked READDIR for %p, MDCACHE_TRUST_CONTENT %s, MDCACHE_TRUST_DIR_CHUNKS %s", directory, test_mde_flags(directory, MDCACHE_TRUST_CONTENT) ? "true" : "false", test_mde_flags(directory, MDCACHE_TRUST_DIR_CHUNKS) ? "true" : "false"); /* Dirent's are being chunked; check to see if it needs updating */ if (!test_mde_flags(directory, MDCACHE_TRUST_CONTENT | MDCACHE_TRUST_DIR_CHUNKS)) { /* Clean out the existing entries in the directory. */ LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Flushing invalid dirent cache"); PTHREAD_RWLOCK_wrlock(&directory->content_lock); mdcache_dirent_invalidate_all(directory); has_write = true; } else { PTHREAD_RWLOCK_rdlock(&directory->content_lock); has_write = false; } if (whence_is_name) { /* While we're getting active readdirs, we don't want to * invalidate a whence-is-name directory. This will cause the * entire directory to be reloaded, causing a huge delay that * can cause the readdir to time out on the client. To avoid * this, bump the expire time on the directory */ directory->attr_time = time(NULL); } restart: if (look_ck == 0) { /* If starting from beginning, use the first_ck from the * directory instead, this is only non-zero if the first * chunk of the directory is still present. */ look_ck = directory->fsobj.fsdir.first_ck; } /* We need to know if we need to set first_ck. */ set_first_ck = whence == 0 && look_ck == 0; again: /* Get here on first pass, retry if we don't hold the write lock, * and repeated passes if we need to fetch another chunk. */ LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Readdir chunked next_ck=0x%" PRIx64 " look_ck=%" PRIx64, next_ck, look_ck); if (look_ck == 0 || !mdcache_avl_lookup_ck(directory, look_ck, &dirent)) { fsal_status_t status; /* This starting position isn't in our cache... * Go populate the cache and process from there. */ if (!has_write) { /* Upgrade to write lock and retry just in case * another thread managed to populate this cookie * in the meantime. We need to drop our ref on chunk * (if any) since it could be nuked when we drop the * lock. */ if (chunk) { save_ck = glist_last_entry(&chunk->dirents, mdcache_dir_entry_t, chunk_list) ->ck; LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "chunk %p save_ck=0x%" PRIx64, chunk, save_ck); mdcache_lru_unref_chunk(chunk); chunk = NULL; } PTHREAD_RWLOCK_unlock(&directory->content_lock); PTHREAD_RWLOCK_wrlock(&directory->content_lock); has_write = true; goto again; } if (save_ck) { /* Try to get the chunk back for whence_is_name */ if (mdcache_avl_lookup_ck(directory, save_ck, &dirent)) { chunk = dirent->chunk; LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "found saved chunk %p", chunk); } save_ck = 0; } /* Assure that dirent is NULL */ dirent = NULL; if (look_ck != 0 && look_ck == directory->fsobj.fsdir.first_ck) { /* We failed to find the first dentry in the directory, * and will load this chunk. Make sure we save * whatever is the new first_ck. */ set_first_ck = true; } LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Readdir chunked about to populate chunk %p next_ck=0x%" PRIx64, chunk, next_ck); /* No, we need to populate a chunk using this cookie. * * NOTE: empty directory can result in dirent being NULL, and * we will ALWAYS re-read an empty directory every time. * Although we do end up setting MDCACHE_DIR_POPULATED on * an empty directory, we don't consider that here, and * will re-read the directory. */ status = mdcache_populate_dir_chunk(directory, next_ck, &dirent, chunk, &eod); if (FSAL_IS_ERROR(status)) { PTHREAD_RWLOCK_unlock(&directory->content_lock); LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "mdcache_populate_dir_chunk failed status=%s", fsal_err_txt(status)); if (status.major == ERR_FSAL_STALE) mdcache_kill_entry(directory); return status; } if (dirent == NULL) { /* We must have reached the end of the directory, or the * directory was empty. In any case, there is no next * chunk or dirent. */ *eod_met = true; if (whence == 0) { /* Since eod is true and whence is 0, we know * the entire directory is populated. This can * indicate that an empty directory may be * considered "populated." */ atomic_set_uint32_t_bits(&directory->mde_flags, MDCACHE_DIR_POPULATED); } PTHREAD_RWLOCK_unlock(&directory->content_lock); LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "readdir completed, eod = %s", *eod_met ? "true" : "false"); return status; } if ((whence == 0) && eod) { /* We started at the beginning of the directory and * populated through to end of directory, thus we can * indicate the directory is fully populated. */ atomic_set_uint32_t_bits(&directory->mde_flags, MDCACHE_DIR_POPULATED); } else { /* Since we just populated a chunk and have not * determined that we read the entire directory, make * sure the MDCACHE_DIR_POPULATED is cleared. */ atomic_clear_uint32_t_bits(&directory->mde_flags, MDCACHE_DIR_POPULATED); } chunk = dirent->chunk; LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "mdcache_populate_dir_chunk finished chunk %p dirent %p %s", chunk, dirent, dirent->name); if (set_first_ck) { /* We just populated the first dirent in the directory, * save it's cookie as first_ck. */ LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Setting directory first_ck=%" PRIx64, dirent->ck); directory->fsobj.fsdir.first_ck = dirent->ck; set_first_ck = false; } } else { fsal_cookie_t *name; if (chunk) { mdcache_lru_unref_chunk(chunk); } /* We found the dirent... If next_ck is NOT whence, we SHOULD * have found the first dirent in the chunk, if not, then * something went wrong at some point. That chunk is valid, */ chunk = dirent->chunk; name = mdc_lru_unmap_dirent(dirent->ck); if (name) gsh_free(name); LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "found dirent in cached chunk %p dirent %p %s", chunk, dirent, dirent->name); } /* Bump the chunk in the LRU */ lru_bump_chunk(chunk); LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "About to read directory=%p cookie=%" PRIx64, directory, next_ck); /* Now satisfy the request from the cached readdir--stop when either * the requested sequence or dirent sequence is exhausted */ for (; dirent != NULL; dirent = glist_next_entry(&chunk->dirents, mdcache_dir_entry_t, chunk_list, &dirent->chunk_list)) { fsal_status_t status; enum fsal_dir_result cb_result; mdcache_entry_t *entry = NULL; struct fsal_attrlist attrs; if (dirent->flags & DIR_ENTRY_FLAG_DELETED) { /* Skip deleted entries */ continue; } status.major = ERR_FSAL_NO_ERROR; /* We have the content_lock for at least read. */ if (dirent->mde_entry) { /* Take a ref for our use */ entry = dirent->mde_entry; mdcache_lru_ref(entry, LRU_ACTIVE_REF); } else { /* Not cached, get actual entry using the dirent ckey */ status = mdcache_find_keyed_reason( &dirent->ckey, &entry, LRU_ACTIVE_REF); } if (FSAL_IS_ERROR(status)) { /* Failed using ckey, do full lookup. */ LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Lookup by key for %s failed, lookup by name now", dirent->name); /* mdc_lookup_uncached needs write lock, dropping the * read lock means we can no longer trust the dirent or * the chunk. */ if (!has_write) { /* We will have to re-find this dirent after we * re-acquire the lock. */ look_ck = dirent->ck; /* Dropping the content_lock may invalidate some * or all of the dirents and/or chunks in this * directory. We need to start over from this * point. look_ck is now correct if the dirent * is still cached, and we haven't changed * next_ck, so it's still correct for reloading * the chunk. */ mdcache_lru_unref_chunk(chunk); chunk = NULL; PTHREAD_RWLOCK_unlock(&directory->content_lock); PTHREAD_RWLOCK_wrlock(&directory->content_lock); has_write = true; /* Now we need to look for this dirent again. * We haven't updated next_ck for this dirent * yet, so it is the right whence to use for a * repopulation readdir if the chunk is * discarded. */ goto again; } else if (op_ctx->fsal_export->exp_ops.fs_supports( op_ctx->fsal_export, fso_readdir_plus)) { /* If the FSAL supports readdir_plus, then a * single round-trip for the chunk is preferable * to lookups for every missing obj. Nuke the * chunk, and reload it using readdir_plus */ look_ck = dirent->ck; next_ck = chunk->reload_ck; reload_chunk = true; LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Reloading chunk %p look_ck %" PRIx64 " next_ck %" PRIx64, chunk, look_ck, next_ck); /* In order to get here, we passed the has_write * check above, and took the write lock. */ mdcache_lru_unref_chunk(chunk); mdcache_lru_unref_chunk(chunk); chunk = NULL; goto again; } /* Save look_ck in case dirent is nuked */ look_ck = dirent->ck; status = mdc_lookup_uncached(directory, dirent->name, &entry, NULL); if (FSAL_IS_ERROR(status)) { mdcache_lru_unref_chunk(chunk); PTHREAD_RWLOCK_unlock(&directory->content_lock); LogFullDebugAlt( COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "lookup by name failed status=%s", fsal_err_txt(status)); if (status.major == ERR_FSAL_STALE) mdcache_kill_entry(directory); return status; } if (!test_mde_flags(directory, MDCACHE_TRUST_CONTENT | MDCACHE_TRUST_DIR_CHUNKS)) { /* mdc_lookup_uncached() replaced the dirent, * because the key for the old and new entries * don't match. If the new dirent couldn't be * placed, we need to restart readdir. */ mdcache_lru_unref_chunk(chunk); chunk = NULL; mdcache_lru_unref(entry, LRU_ACTIVE_REF); goto restart; } } if (has_write && dirent->mde_entry) { /* If we get here, we have the write lock, have an * entry, and took a ref on it above. The dirent also * has a ref on the entry. Drop that ref now. This can * only be done under the write lock. If we don't have * the write lock, then this was not the readdir that * took the ref, and another readdir will drop the ref, * or it will be dropped when the dirent is cleaned up. * */ mdcache_lru_unref(dirent->mde_entry, LRU_ACTIVE_REF); dirent->mde_entry = NULL; } if (reload_chunk && look_ck != 0 && dirent->ck != look_ck) { LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Skipping already used dirent %s (%p)", dirent->name, &entry->obj_handle); /* This chunk was reloaded, but some dirents were * already consumed. Deref and continue */ mdcache_lru_unref(entry, LRU_ACTIVE_REF); continue; } next_ck = dirent->ck; LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Setting next_ck=%" PRIx64, next_ck); if (dirent->ck == whence) { /* When called with whence, the caller always wants the * next entry, skip this entry. */ mdcache_lru_unref(entry, LRU_ACTIVE_REF); reload_chunk = false; continue; } /* Ensure the attribute cache is valid. The simplest way to do * this is to call getattrs(). We need a copy anyway, to ensure * thread safety. */ fsal_prepare_attrs(&attrs, attrmask); status = entry->obj_handle.obj_ops->getattrs(&entry->obj_handle, &attrs); if (FSAL_IS_ERROR(status)) { mdcache_lru_unref_chunk(chunk); PTHREAD_RWLOCK_unlock(&directory->content_lock); LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "getattrs failed status=%s", fsal_err_txt(status)); mdcache_lru_unref(entry, LRU_ACTIVE_REF); return status; } GSH_AUTO_TRACEPOINT( mdcache, mdc_readdir_cb, TRACE_DEBUG, "Calling readdir callback. Obj handle: {}, sub handle: {}, refcount: {}", &directory->obj_handle, entry->sub_handle, entry->lru.refcnt); cb_result = cb(dirent->name, &entry->obj_handle, &attrs, dir_state, dirent->ck); fsal_release_attrs(&attrs); if (whence_is_name) { /* Save the dirmap for the dirents so that * whence-is-name doesn't need to restart a readdir to * find a missing mapping */ mdc_lru_map_dirent(dirent); } /* The ref on entry was put by the callback. Don't use it * anymore */ LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "dirent = %p %s, cb_result = %s, eod = %s", dirent, dirent->name, fsal_dir_result_str(cb_result), dirent->eod ? "true" : "false"); if (cb_result >= DIR_TERMINATE || dirent->eod) { /* Caller is done, or we have reached the end of * the directory, no need to get another dirent. */ /* If cb_result is DIR_TERMINATE, the callback did * not consume this entry, so we can not have reached * end of directory. */ *eod_met = cb_result != DIR_TERMINATE && dirent->eod; if (*eod_met && whence == 0) { /* Since eod is true and whence is 0, we know * the entire directory is populated. */ atomic_set_uint32_t_bits(&directory->mde_flags, MDCACHE_DIR_POPULATED); } if (has_write) { /* We need to drop the ref on the rest of the * entries in this chunk, so that they don't * hang around until the directory is * invalidated. */ mdc_unref_chunk_dirents(chunk, dirent); } LogDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "readdir completed, eod = %s", *eod_met ? "true" : "false"); mdcache_lru_unref_chunk(chunk); PTHREAD_RWLOCK_unlock(&directory->content_lock); return status; } reload_chunk = false; } if (chunk->next_ck != 0) { /* If the chunk has a known chunk following it, use the first * cookie in that chunk for AVL tree lookup, which will succeed * rather than having to do a readdir to find the next entry. * * If the chunk is no longer present, the lookup will fail, in * which case next_ck is the right cookie to use as the whence * for the next readdir. */ look_ck = chunk->next_ck; LogFullDebugAlt(COMPONENT_NFS_READDIR, COMPONENT_MDCACHE, "Setting look_ck from next_ck=%" PRIx64, chunk->next_ck); } else { /* The next chunk is not resident, or we don't know what the * next_ck is. Skip right to populating the next chunk. next_ck * is the right cookie to use as the whence for the next * readdir. */ look_ck = 0; } /* Due to the conditions we return from inside the loop, we know that if * we reach the end of the chunk we must fetch another chunk to satisfy * the directory read. The next_ck is the cookie for the next dirent to * find, which should be the first dirent of the next chunk. */ /* NOTE: An FSAL that does not return 0 or LAST_COOKIE * as the cookie for the last directory entry will * result in our attempting to find one more * chunk, which will not succeed and then the eod * condition detected above before the while loop * will kick in. */ /* NOTE: We also keep the write lock if we already had * it. Most likely we will need to populate the * next chunk also. It's probably not worth * dropping the write lock and taking the read * lock just in case the next chunk actually * happens to be populated. */ /* Note: We're passing our ref on chunk into * mdcache_populate_dir_chunk(), so don't drop it here. */ goto again; } /** * @brief Forcibly remove an entry from the cache (top half) * * This function is used to invalidate a cache entry when it * has become unusable (for example, when the FSAL declares it to be * stale). * * To simplify interaction with the SAL, this function no longer * finalizes the entry, but schedules the entry for out-of-line * cleanup, after first making it unreachable. * * @param[in] entry The entry to be killed */ void _mdcache_kill_entry(mdcache_entry_t *entry, char *file, int line, char *function) { bool freed; if (isDebug(COMPONENT_MDCACHE)) { DisplayLogComponentLevel( COMPONENT_MDCACHE, file, line, function, NIV_DEBUG, "Kill %s entry %p obj_handle %p", object_file_type_to_str(entry->obj_handle.type), entry, &entry->obj_handle); } freed = cih_remove_checked(entry); /* !reachable, drop sentinel ref */ GSH_AUTO_TRACEPOINT( mdcache, mdc_kill_entry, TRACE_DEBUG, "Removed entry. Obj handle: {}, refcount: {}, freed: {}", &entry->obj_handle, entry->lru.refcnt, freed); if (!freed) { /* queue for cleanup */ mdcache_lru_cleanup_push(entry); } } /** * @brief Update the cached attributes * * Update the cached attributes on @a entry with the attributes in @a attrs * * @note The caller must hold the attribute lock for WRITE * * @param[in] entry Entry to update * @param[in] attrs New attributes to cache * @return FSAL status */ void mdc_update_attr_cache(mdcache_entry_t *entry, struct fsal_attrlist *attrs) { if (entry->attrs.acl != NULL) { /* We used to have an ACL... */ if (attrs->acl != NULL) { /* We got an ACL from the sub FSAL whether we asked for * it or not, given that we had an ACL before, and we * got a new one, update the ACL, so release the old * one. */ nfs4_acl_release_entry(entry->attrs.acl); } else { /* A new ACL wasn't provided, so move the old one * into the new attributes so it will be preserved * by the fsal_copy_attrs. */ attrs->acl = entry->attrs.acl; attrs->valid_mask |= ATTR_ACL; } /* NOTE: Because we already had an ACL, * entry->attrs.request_mask MUST have the ATTR_ACL bit set. * This will assure that fsal_copy_attrs below will copy the * selected ACL (old or new) into entry->attrs. */ /* ACL was released or moved to new attributes. */ entry->attrs.acl = NULL; } else if (attrs->acl != NULL) { /* We didn't have an ACL before, but we got a new one. We may * not have asked for it, but receive it anyway. */ entry->attrs.request_mask |= ATTR_ACL; } // Same as above but for fs_locations if (entry->attrs.fs_locations != NULL) { if (attrs->fs_locations != NULL) { nfs4_fs_locations_release(entry->attrs.fs_locations); } else { attrs->fs_locations = entry->attrs.fs_locations; attrs->valid_mask |= ATTR4_FS_LOCATIONS; } entry->attrs.fs_locations = NULL; } else if (attrs->fs_locations != NULL) { entry->attrs.request_mask |= ATTR4_FS_LOCATIONS; } // Same as above but for sec_label if (entry->attrs.sec_label.slai_data.slai_data_val != NULL) { char *secdata = entry->attrs.sec_label.slai_data.slai_data_val; if (attrs->sec_label.slai_data.slai_data_val != NULL) { gsh_free(secdata); } else { attrs->sec_label.slai_data.slai_data_len = entry->attrs.sec_label.slai_data.slai_data_len; attrs->sec_label.slai_data.slai_data_val = secdata; attrs->valid_mask |= ATTR4_SEC_LABEL; } entry->attrs.sec_label.slai_data.slai_data_len = 0; entry->attrs.sec_label.slai_data.slai_data_val = NULL; } else if (attrs->sec_label.slai_data.slai_data_val != NULL) { entry->attrs.request_mask |= ATTR4_SEC_LABEL; } if (attrs->expire_time_attr == 0) { /* FSAL did not set this, retain what was in the entry. */ attrs->expire_time_attr = entry->attrs.expire_time_attr; } /* Now move the new attributes into the entry. */ fsal_copy_attrs(&entry->attrs, attrs, true); /* Note that we use &entry->attrs here in case attrs.request_mask was * modified by the FSAL. entry->attrs.request_mask reflects the * attributes we requested, and was updated to "request" ACL if the * FSAL provided one for us gratis. */ mdc_fixup_md(entry, &entry->attrs); } /** @} */ nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_int.h000066400000000000000000001074711473756622300242440ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2015-2019 Red Hat, Inc. and/or its affiliates. * Author: Daniel Gryniewicz * * This program 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 3 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 * 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 * */ /** * @file mdcache_int.h * @brief MDCache main internal interface. * * Main data structures and profiles for MDCache */ #ifndef MDCACHE_INT_H #define MDCACHE_INT_H #include #include #include "config.h" #include "mdcache_ext.h" #include "sal_data.h" #include "fsal_up.h" #include "fsal_convert.h" #include "display.h" #include "common_utils.h" typedef struct mdcache_fsal_obj_handle mdcache_entry_t; struct mdcache_fsal_module { struct fsal_module module; struct fsal_obj_ops handle_ops; }; extern struct mdcache_fsal_module MDCACHE; #define MDC_UNEXPORT 1 typedef struct mdcache_dmap_entry__ { /** AVL node in tree by cookie */ struct avltree_node node; /** Entry in LRU */ struct glist_head lru_entry; /** Cookie */ uint64_t ck; /** Name */ char *name; /** Timestamp on entry */ struct timespec timestamp; } mdcache_dmap_entry_t; typedef struct { /** Lock protecting this structure */ pthread_mutex_t dm_mtx; /** Mapping of ck -> name for whence-is-name */ struct avltree map; /** LRU of dirent map entries */ struct glist_head lru; /** Count of entries in LRU */ uint32_t count; } mdc_dirmap_t; /* * MDCACHE internal export */ struct mdcache_fsal_export { struct fsal_export mfe_exp; char *name; /** My up_ops */ struct fsal_up_vector up_ops; /** Higher level up_ops for ops we don't consume */ struct fsal_up_vector super_up_ops; /** The list of cache entries belonging to this export */ struct glist_head entry_list; /** Lock protecting entry_list */ pthread_mutex_t mdc_exp_lock; /** Flags for the export. */ uint8_t flags; /** Mapping of ck -> name for whence-is-name */ mdc_dirmap_t dirent_map; /** Thread for dirmap processing */ struct fridgethr *dirmap_fridge; }; /** * @brief Structure representing a cache key. * * Wraps an underlying FSAL-specific key. */ typedef struct mdcache_key { uint64_t hk; /* hash key */ void *fsal; /*< sub-FSAL module */ struct gsh_buffdesc kv; /*< fsal handle */ } mdcache_key_t; int display_mdcache_key(struct display_buffer *dspbuf, mdcache_key_t *key); static inline int mdcache_key_cmp(const struct mdcache_key *k1, const struct mdcache_key *k2) { if (likely(k1->hk < k2->hk)) return -1; if (likely(k1->hk > k2->hk)) return 1; if (unlikely(k1->kv.len < k2->kv.len)) return -1; if (unlikely(k1->kv.len > k2->kv.len)) return 1; if (unlikely(k1->fsal < k2->fsal)) return -1; if (unlikely(k1->fsal > k2->fsal)) return 1; /* deep compare */ return memcmp(k1->kv.addr, k2->kv.addr, k1->kv.len); } /** * Data for tracking a cache entry's position the LRU. */ /** * Valid LRU queues. */ enum lru_q_id { LRU_ENTRY_NONE = 0, /* entry not queued */ LRU_ENTRY_L1, LRU_ENTRY_L2, LRU_ENTRY_CLEANUP, LRU_ENTRY_ACTIVE, }; #define LRU_CLEANUP 0x00000001 /* Entry is on cleanup queue */ #define LRU_CLEANED 0x00000002 /* Entry has been cleaned */ #define LRU_EVER_PROMOTED \ 0x00000004 /* Entry will be promoted after releasing * last active reference (never cleared). */ #define LRU_SENTINEL_HELD 0x00000008 /* true if sentinel reference is held */ typedef struct mdcache_lru__ { struct glist_head q; /*< Link in the physical deque implementing a portion of the logical LRU. */ enum lru_q_id qid; /*< Queue identifier */ int32_t refcnt; /*< Reference count. This is signed to make mistakes easy to see. */ int32_t active_refcnt; /*< Active Reference count. This is signed to make mistakes easy to see. */ uint32_t flags; /*< Status flags; MUST use atomic ops */ uint32_t lane; /*< The lane in which an entry currently *< resides, so we can lock the deque and *< decrement the correct counter when moving *< or deleting the entry. */ uint32_t cf; /*< Confounder */ } mdcache_lru_t; /** * MDCACHE statistics. */ struct mdcache_stats { uint64_t inode_req; uint64_t inode_hit; uint64_t inode_miss; uint64_t inode_conf; uint64_t inode_added; uint64_t inode_mapping; }; extern struct mdcache_stats *cache_stp; /** * @brief Represents one of the many-many links between inodes and exports. * */ struct entry_export_map { /** The relevant cache entry */ mdcache_entry_t *entry; /** The export the entry belongs to */ struct mdcache_fsal_export *exp; /** List of entries per export */ struct glist_head entry_per_export; /** List of exports per entry */ struct glist_head export_per_entry; }; /** * Flags */ /** Trust stored attributes */ #define MDCACHE_TRUST_ATTRS FSAL_UP_INVALIDATE_ATTRS /** Trust stored ACL */ #define MDCACHE_TRUST_ACL FSAL_UP_INVALIDATE_ACL /** Trust inode content (for the moment, directory and symlink) */ #define MDCACHE_TRUST_CONTENT FSAL_UP_INVALIDATE_CONTENT /** The directory has been populated (negative lookups are meaningful) */ #define MDCACHE_DIR_POPULATED FSAL_UP_INVALIDATE_DIR_POPULATED /** The directory chunks are considered valid */ #define MDCACHE_TRUST_DIR_CHUNKS FSAL_UP_INVALIDATE_DIR_CHUNKS /** The fs_locations are considered valid */ #define MDCACHE_TRUST_FS_LOCATIONS FSAL_UP_INVALIDATE_FS_LOCATIONS /** The sec_labels are considered valid */ #define MDCACHE_TRUST_SEC_LABEL FSAL_UP_INVALIDATE_SEC_LABEL /** The entry has been removed, but not unhashed due to state */ static const uint32_t MDCACHE_UNREACHABLE = 0x100; /** * @brief Represents a cached inode * * Information representing a cached file (inode) including metadata, * and for directories and symlinks, pointers to cached content. This * is also the anchor for state held on a file. * * Regarding the locking discipline: * (1) attr_lock protects the attrs field, the export_list, and attr_time * * (2) content_lock must be held for WRITE when modifying the AVL tree * of a directory or any dirent contained therein. It must be * held for READ when accessing any of this information. * * (3) content_lock must be held for WRITE when updating the cached * content of a symlink or when NULLing the object.symlink pointer * preparatory to freeing the link structure. It must be held for * READ when dereferencing the object.symlink pointer or reading * cached content. XXX dang symlink content is in FSAL now * * The handle, cache key, and type fields are unprotected, as they are * considered to be immutable throughout the life of the object. * * The flags field is unprotected, however it should be modified only * through the functions atomic_set_uint32_t_bits and * atomic_clear_uint32_t_bits. * * The lru field has its own mutex to protect it. * * The attributes and symlink contents are stored in the handle for * api simplicity but these locks apply around their access methods. * * @note As part of the transition to the new api, the handle was * moved out of the union and made a pointer to a separately allocated * object. However, locking applies everywhere except handle object * creation time (nobody knows about it yet). The symlink content * cache has moved into the handle as, well. The mdcache_entry and * fsal_obj_handle are two parts of the same thing, a cached inode. * mdcache_entry holds the cache stuff and fsal_obj_handle holds the * stuff the fsal has to manage, i.e. filesystem bits. */ struct mdcache_fsal_obj_handle { /** Reader-writer lock for attributes */ pthread_rwlock_t attr_lock; /** MDCache FSAL Handle */ struct fsal_obj_handle obj_handle; /** Sub-FSAL handle */ struct fsal_obj_handle *sub_handle; /** Cached attributes */ struct fsal_attrlist attrs; /** Attribute generation, increased for every write */ uint32_t attr_generation; /** FH hash linkage */ struct { struct avltree_node node_k; /*< AVL node in tree */ mdcache_key_t key; /*< Key of this entry */ bool inavl; } fh_hk; /** Flags for this entry */ uint32_t mde_flags; /** Time at which we last refreshed attributes. */ time_t attr_time; /** Time at which we last refreshed acl. */ time_t acl_time; /** Time at which we last refreshed fs locations */ time_t fs_locations_time; /** New style LRU link */ mdcache_lru_t lru; /** Exports per entry (protected by attr_lock) */ struct glist_head export_list; /** ID of the first mapped export for fast path * This is an int32_t because we need it to be -1 to indicate * no mapped export. */ int32_t first_export_id; /** Lock on type-specific cached content. See locking discipline for details. */ pthread_rwlock_t content_lock; /** Filetype specific data, discriminated by the type field. Note that data for special files is in attributes.rawdev */ union mdcache_fsobj { struct state_hdl hdl; struct { /** List of chunks in this directory, ordered */ struct glist_head chunks; /** List of detached directory entries. */ struct glist_head detached; /** Spin lock to protect the detached list. */ pthread_spinlock_t fsd_spin; /** Count of detached directory entries. */ int detached_count; /** @todo FSF * * This is somewhat fragile, however, a reorganization * is possible. If state_lock was to be moved into * state_file and state_dir, and the state code was * made clear which it was working with, dhdl could * be replaced with a state_dir which would be * smaller than state_file, and then the additional * members of fsdir would basically overlay * the larger state_file that hdl is. * * Such a reorg could save memory AND make for a * crisper interface. */ struct state_hdl dhdl; /**< Storage for dir state */ /** The parent host-handle of this directory ('..') */ struct gsh_buffdesc parent; /** Time at which we last refreshed parent * host-handle. */ time_t parent_time; /** The first dirent cookie in this directory. * 0 if not known. */ fsal_cookie_t first_ck; struct { /** Children by name hash */ struct avltree t; /** Table of dirents by FSAL cookie */ struct avltree ck; /** Table of dirents in sorted order. */ struct avltree sorted; /** Heuristic. Expect 0. */ uint32_t collisions; } avl; } fsdir; /**< DIRECTORY data */ } fsobj; }; struct dir_chunk { /** This chunk is part of a directory */ struct glist_head chunks; /** List of dirents in this chunk */ struct glist_head dirents; /** Directory this chunk belongs to */ struct mdcache_fsal_obj_handle *parent; /** LRU link */ mdcache_lru_t chunk_lru; /** Cookie to use to reload this chunk */ fsal_cookie_t reload_ck; /** Cookie of first entry in sequentially next chunk, will be set to * 0 if there is no sequentially next chunk. */ fsal_cookie_t next_ck; /** Number of entries in chunk */ int num_entries; }; /** * @brief Represents a cached directory entry * * This is a cached directory entry that associates a name and cookie * with a cache entry. */ #define DIR_ENTRY_FLAG_NONE 0x0000 #define DIR_ENTRY_FLAG_DELETED 0x0001 #define DIR_ENTRY_SORTED 0x0004 typedef struct mdcache_dir_entry__ { /** This dirent is part of a chunk */ struct glist_head chunk_list; /** The chunk this entry belongs to */ struct dir_chunk *chunk; /** node in tree by name */ struct avltree_node node_name; /** AVL node in tree by cookie */ struct avltree_node node_ck; /** AVL node in tree by sorted order */ struct avltree_node node_sorted; /** Cookie value from FSAL * This is the coookie that is the "key" to find THIS entry, however * a readdir with whence will be looking for the NEXT entry. */ uint64_t ck; /** Indicates if this dirent is the last dirent in a chunked directory. */ bool eod; /** Name Hash */ uint64_t namehash; /** Key of cache entry */ mdcache_key_t ckey; /** Flags * Protected by write content_lock or atomics. */ uint32_t flags; /** Temporary entry pointer * Only valid while the entry is ref'd. Must be NULL otherwise. * Protected by the parent content_lock */ mdcache_entry_t *mde_entry; const char *name; /** The NUL-terminated filename */ char name_buffer[]; } mdcache_dir_entry_t; /** * @brief Move a detached dirent to MRU position in LRU list. * * @param[in] parent Parent entry * @param[in] dirent Dirent to move to MRU */ static inline void bump_detached_dirent(mdcache_entry_t *parent, mdcache_dir_entry_t *dirent) { PTHREAD_SPIN_lock(&parent->fsobj.fsdir.fsd_spin); if (glist_first_entry(&parent->fsobj.fsdir.detached, mdcache_dir_entry_t, chunk_list) != dirent) { glist_del(&dirent->chunk_list); glist_add(&parent->fsobj.fsdir.detached, &dirent->chunk_list); } PTHREAD_SPIN_unlock(&parent->fsobj.fsdir.fsd_spin); } /** * @brief Remove a detached dirent from the LRU list. * * @param[in] parent Parent entry * @param[in] dirent Dirent to remove */ static inline void rmv_detached_dirent(mdcache_entry_t *parent, mdcache_dir_entry_t *dirent) { PTHREAD_SPIN_lock(&parent->fsobj.fsdir.fsd_spin); /* Note that the dirent might not be on the detached list if it * was being reaped by another thread. All is well here... */ if (!glist_null(&dirent->chunk_list)) { glist_del(&dirent->chunk_list); parent->fsobj.fsdir.detached_count--; } PTHREAD_SPIN_unlock(&parent->fsobj.fsdir.fsd_spin); } /* Helpers */ fsal_status_t mdcache_alloc_and_check_handle( struct mdcache_fsal_export *exp, struct fsal_obj_handle *sub_handle, struct fsal_obj_handle **new_obj, bool new_directory, struct fsal_attrlist *attrs_in, struct fsal_attrlist *attrs_out, const char *tag, mdcache_entry_t *parent, const char *name, bool *invalidate, struct state_t *state); fsal_status_t mdcache_refresh_attrs(mdcache_entry_t *entry, bool need_acl, bool need_fslocations, bool need_seclabel, bool *invalidate); fsal_status_t mdcache_new_entry(struct mdcache_fsal_export *exp, struct fsal_obj_handle *sub_handle, struct fsal_attrlist *attrs_in, bool prefer_attrs_in, struct fsal_attrlist *attrs_out, bool new_directory, mdcache_entry_t **entry, struct state_t *state, uint32_t flags); fsal_status_t mdcache_find_keyed_reason(mdcache_key_t *key, mdcache_entry_t **entry, uint32_t flags); fsal_status_t mdcache_locate_host(struct gsh_buffdesc *fh_desc, struct mdcache_fsal_export *exp, mdcache_entry_t **entry, struct fsal_attrlist *attrs_out); fsal_status_t mdc_try_get_cached(mdcache_entry_t *mdc_parent, const char *name, mdcache_entry_t **entry); fsal_status_t mdc_lookup(mdcache_entry_t *mdc_parent, const char *name, bool uncached, mdcache_entry_t **new_entry, struct fsal_attrlist *attrs_out); fsal_status_t mdc_lookup_uncached(mdcache_entry_t *mdc_parent, const char *name, mdcache_entry_t **new_entry, struct fsal_attrlist *attrs_out); void mdcache_src_dest_lock(mdcache_entry_t *src, mdcache_entry_t *dest); void mdcache_src_dest_unlock(mdcache_entry_t *src, mdcache_entry_t *dest); void mdcache_dirent_remove(mdcache_entry_t *parent, const char *name); fsal_status_t mdcache_dirent_add(mdcache_entry_t *parent, const char *name, mdcache_entry_t *entry, bool *invalidate); void mdcache_dirent_invalidate_all(mdcache_entry_t *entry); fsal_status_t mdcache_readdir_uncached(mdcache_entry_t *directory, fsal_cookie_t *whence, void *dir_state, fsal_readdir_cb cb, attrmask_t attrmask, bool *eod_met); void mdcache_clean_dirent_chunk(struct dir_chunk *chunk); void place_new_dirent(mdcache_entry_t *parent_dir, mdcache_dir_entry_t *new_dir_entry); fsal_status_t mdcache_readdir_chunked(mdcache_entry_t *directory, fsal_cookie_t whence, void *dir_state, fsal_readdir_cb cb, attrmask_t attrmask, bool *eod_met); fsal_status_t mdc_get_parent(struct mdcache_fsal_export *exp, mdcache_entry_t *entry, struct gsh_buffdesc *parent_out); void mdc_update_attr_cache(mdcache_entry_t *entry, struct fsal_attrlist *attrs); static inline void mdcache_free_fh(struct gsh_buffdesc *fh_desc); /** * @brief Atomically test the bits in mde_flags. * * @param[in] entry The mdcache entry to test * @param[in] bits The bits to test if set * * @returns true if all the bits are set. * */ static inline bool test_mde_flags(mdcache_entry_t *entry, uint32_t bits) { return (atomic_fetch_uint32_t(&entry->mde_flags) & bits) == bits; } static inline struct mdcache_fsal_export * mdc_export(struct fsal_export *fsal_export) { return container_of(fsal_export, struct mdcache_fsal_export, mfe_exp); } static inline struct mdcache_fsal_export *mdc_cur_export(void) { return mdc_export(op_ctx->fsal_export); } void mdc_clean_entry(mdcache_entry_t *entry); fsal_status_t mdc_check_mapping(mdcache_entry_t *entry); void _mdcache_kill_entry(mdcache_entry_t *entry, char *file, int line, char *function); #define mdcache_kill_entry(entry) \ _mdcache_kill_entry(entry, (char *)__FILE__, __LINE__, (char *)__func__) fsal_status_t mdc_get_parent_handle(struct mdcache_fsal_export *exp, mdcache_entry_t *entry, struct fsal_obj_handle *sub_parent); extern struct config_block mdcache_param_blk; /* Call a sub-FSAL function using it's export, safe for use during shutdown */ #define subcall_shutdown_raw(myexp, call) \ do { \ if (op_ctx) \ op_ctx->fsal_export = (myexp)->mfe_exp.sub_export; \ call; \ if (op_ctx) \ op_ctx->fsal_export = &(myexp)->mfe_exp; \ } while (0) /* Call a sub-FSAL function using it's export */ #define subcall_raw(myexp, call) \ do { \ op_ctx->fsal_export = (myexp)->mfe_exp.sub_export; \ call; \ op_ctx->fsal_export = &(myexp)->mfe_exp; \ } while (0) /* Call a sub-FSAL function using it's export */ #define subcall(call) \ do { \ struct mdcache_fsal_export *__export = mdc_cur_export(); \ subcall_raw(__export, call); \ } while (0) /* Call an async sub-FSAL function using it's export. * op_ctx is undefined after this call. */ #define subcall_async_raw(myexp, call) \ do { \ op_ctx->fsal_export = (myexp)->mfe_exp.sub_export; \ call; \ } while (0) /* During a callback from a sub-FSAL, call using MDCACHE's export */ #define supercall_raw(myexp, call) \ do { \ LogFullDebug(COMPONENT_MDCACHE, "supercall %s", myexp->name); \ op_ctx->fsal_export = &(myexp)->mfe_exp; \ call; \ op_ctx->fsal_export = (myexp)->mfe_exp.sub_export; \ } while (0) #define supercall(call) \ do { \ struct fsal_export *save_exp = op_ctx->fsal_export; \ op_ctx->fsal_export = save_exp->super_export; \ call; \ op_ctx->fsal_export = save_exp; \ } while (0) /** * @brief Lock context for content lock recursion * * long description */ typedef struct { mdcache_entry_t *entry; bool iswrite; int count; } mdc_lock_context_t; /** * @brief Dup a cache key. * * Deep copies the key passed in src, to tgt. On return, tgt->kv.addr * is overwritten with a new buffer of length src->kv.len, and the buffer * is copied. * * @param tgt [inout] Destination of copy * @param src [in] Source of copy * * @return 0 on success. */ static inline void mdcache_key_dup(mdcache_key_t *tgt, mdcache_key_t *src) { tgt->kv.len = src->kv.len; tgt->kv.addr = gsh_malloc(src->kv.len); memcpy(tgt->kv.addr, src->kv.addr, src->kv.len); tgt->hk = src->hk; tgt->fsal = src->fsal; } /** * @brief Check if the parent key of an entry has expired. * * If the parent key is valid return true else return false. * * @param[in] entry Entry whose parent key may have expired. * @return Return true if valid, false if invalid. */ static inline bool mdcache_is_parent_valid(mdcache_entry_t *entry) { time_t current_time = time(NULL); if (current_time > entry->fsobj.fsdir.parent_time) return false; return true; } /** * @brief Set the parent key of an entry * * If the parent key is not set, set it. This keeps keys from being leaked. * * @param[in] entry Entry to set * @return Return description */ static inline void mdc_dir_add_parent(mdcache_entry_t *entry, mdcache_entry_t *mdc_parent) { if (entry->fsobj.fsdir.parent.len != 0) { /* Already has a parent pointer */ if (entry->fsobj.fsdir.parent_time == 0 || mdcache_is_parent_valid(entry)) { return; } else { /* Clean up parent key */ mdcache_free_fh(&entry->fsobj.fsdir.parent); } } /* The parent key must be a host-handle so that * create_handle() works in all cases. */ mdc_get_parent_handle(mdc_cur_export(), entry, mdc_parent->sub_handle); } /** * @brief Delete a cache key. * * Delete a cache key. Safe to call even if key was not allocated. * * @param key [in] The key to delete * * @return void. */ static inline void mdcache_key_delete(mdcache_key_t *key) { key->kv.len = 0; gsh_free(key->kv.addr); key->kv.addr = NULL; } /* Create a copy of host-handle */ static inline void mdcache_copy_fh(struct gsh_buffdesc *dest, struct gsh_buffdesc *src) { dest->len = src->len; dest->addr = gsh_malloc(dest->len); (void)memcpy(dest->addr, src->addr, dest->len); } /* Delete stored parent host-handle */ static inline void mdcache_free_fh(struct gsh_buffdesc *fh_desc) { fh_desc->len = 0; gsh_free(fh_desc->addr); fh_desc->addr = NULL; } /** * @brief Update entry metadata from its attributes * * This function, to be used after a FSAL_getattr, updates the * attribute trust flag and time, and stores the refresh time * in the main mdcache_entry_t. * * @note the caller MUST hold attr_lock for write * * @param[in,out] entry The entry on which we operate. * @param[in] attrs The attributes that have just been updated * (we actually only care about the masks) */ static inline void mdc_fixup_md(mdcache_entry_t *entry, struct fsal_attrlist *attrs) { uint32_t flags = 0; /* As long as the ACL was requested, and we get here, we assume no * failure to fetch ACL (differentiated from no ACL to fetch), and * thus we only look at the fact that ACL was requested to determine * that we can trust the ACL. */ if (attrs->request_mask & ATTR_ACL) flags |= MDCACHE_TRUST_ACL; /* If the other attributes were requested, we can trust the other * attributes. Note that if not all could be provided, we assumed * that an error occurred. */ if (attrs->request_mask & ~(ATTR_ACL | ATTR4_FS_LOCATIONS | ATTR4_SEC_LABEL)) flags |= MDCACHE_TRUST_ATTRS; if (attrs->valid_mask == ATTR_RDATTR_ERR) { /* The attribute fetch failed, mark the attributes and ACL as * untrusted. */ atomic_clear_uint32_t_bits(&entry->mde_flags, MDCACHE_TRUST_ACL | MDCACHE_TRUST_ATTRS); return; } if (attrs->request_mask & ATTR4_FS_LOCATIONS && attrs->fs_locations != NULL) { flags |= MDCACHE_TRUST_FS_LOCATIONS; } if (attrs->request_mask & ATTR4_SEC_LABEL && attrs->sec_label.slai_data.slai_data_val != NULL) { flags |= MDCACHE_TRUST_SEC_LABEL; } time_t cur_time = time(NULL); /* Set the refresh time for the cache entry */ if (flags & MDCACHE_TRUST_ACL) { if (entry->attrs.expire_time_attr > 0) entry->acl_time = cur_time; else entry->acl_time = 0; } if (flags & MDCACHE_TRUST_ATTRS) { if (entry->attrs.expire_time_attr > 0) entry->attr_time = cur_time; else entry->attr_time = 0; } if (flags & MDCACHE_TRUST_FS_LOCATIONS) { if (entry->attrs.expire_time_attr > 0) entry->fs_locations_time = cur_time; else entry->fs_locations_time = 0; } /* We have just loaded the attributes from the FSAL. */ atomic_set_uint32_t_bits(&entry->mde_flags, flags); } static inline bool mdcache_test_attrs_trust(mdcache_entry_t *entry, attrmask_t mask) { uint32_t flags = 0; /* Check what attributes were originally requested for refresh. */ if (mask & ATTR_ACL) flags |= MDCACHE_TRUST_ACL; if (mask & ~ATTR_ACL) flags |= MDCACHE_TRUST_ATTRS; if (mask & ATTR4_FS_LOCATIONS) flags |= MDCACHE_TRUST_FS_LOCATIONS; if (mask & ATTR4_SEC_LABEL) flags |= MDCACHE_TRUST_SEC_LABEL; /* If any of the requested attributes are not valid, return. */ if (!test_mde_flags(entry, flags)) return false; if ((entry->attrs.valid_mask & mask) != (mask & ~ATTR_RDATTR_ERR)) return false; return true; } /** * @brief Check if attributes are valid * * @note the caller MUST hold attr_lock for read * * @param[in] entry The entry to check */ static inline bool mdcache_is_attrs_valid(mdcache_entry_t *entry, attrmask_t mask) { bool file_deleg = false; attrmask_t orig_mask = mask; if (!mdcache_test_attrs_trust(entry, mask)) return false; if (entry->attrs.valid_mask == ATTR_RDATTR_ERR) return false; if (entry->obj_handle.type == DIRECTORY && mdcache_param.getattr_dir_invalidation) return false; file_deleg = (entry->obj_handle.state_hdl && entry->obj_handle.state_hdl->file.fdeleg_stats .fds_curr_delegations); if (file_deleg) { /* If the file is delegated, then we can trust * the attributes already fetched (i.e, which * are in entry->attrs.valid_mask), unless * expire_time_attr is set to '0'. */ mask = (mask & ~entry->attrs.valid_mask); } if ((orig_mask & ~ATTR_ACL) != 0 && entry->attrs.expire_time_attr == 0) return false; if ((mask & ~ATTR_ACL) != 0 && entry->attrs.expire_time_attr > 0) { time_t current_time = time(NULL); if (current_time - entry->attr_time > entry->attrs.expire_time_attr) return false; } if ((orig_mask & ATTR_ACL) != 0 && entry->attrs.expire_time_attr == 0) return false; if ((mask & ATTR_ACL) != 0 && entry->attrs.expire_time_attr > 0) { time_t current_time = time(NULL); if (current_time - entry->acl_time > entry->attrs.expire_time_attr) return false; } return true; } /** * @brief Remove an export <-> entry mapping * * @param[in] expmap Mapping to remove * * @note must be called with the mdc_exp_lock and attr_lock held */ static inline void mdc_remove_export_map(struct entry_export_map *expmap) { glist_del(&expmap->export_per_entry); glist_del(&expmap->entry_per_export); gsh_free(expmap); } /** * @brief Check to see if an entry has state * * long description * * @param[in] entry Entry to check * @return true if has state, false otherwise */ static inline bool mdc_has_state(mdcache_entry_t *entry) { switch (entry->obj_handle.type) { case REGULAR_FILE: if (!glist_empty(&entry->fsobj.hdl.file.list_of_states)) return true; if (!glist_empty(&entry->fsobj.hdl.file.layoutrecall_list)) return true; if (!glist_empty(&entry->fsobj.hdl.file.lock_list)) return true; if (!glist_empty(&entry->fsobj.hdl.file.nlm_share_list)) return true; return false; case DIRECTORY: if (entry->fsobj.fsdir.dhdl.dir.junction_export) return true; if (entry->fsobj.fsdir.dhdl.dir.exp_root_refcount) return true; return false; default: /* No state for these types */ return false; } } /** * @brief Mark an entry as unreachable * * An entry has become unreachable. If it has no state, kill it. Otherwise, * mark it unreachable so that it can be killed when state is freed. * * @param[in] entry Entry to mark */ static inline void _mdc_unreachable(mdcache_entry_t *entry, char *file, int line, char *function) { if (isDebug(COMPONENT_MDCACHE)) { DisplayLogComponentLevel( COMPONENT_MDCACHE, file, line, function, NIV_DEBUG, "Unreachable %s entry %p %s state", object_file_type_to_str(entry->obj_handle.type), entry, mdc_has_state(entry) ? "has" : "doesn't have"); } if (!mdc_has_state(entry)) { mdcache_kill_entry(entry); return; } atomic_set_uint32_t_bits(&entry->mde_flags, MDCACHE_UNREACHABLE); } #define mdc_unreachable(entry) \ _mdc_unreachable(entry, (char *)__FILE__, __LINE__, (char *)__func__) /* Handle methods */ /** * Structure used to store data for read_dirents callback. * * Before executing the upper level callback (it might be another * stackable fsal or the inode cache), the context has to be restored. */ struct mdcache_readdir_state { fsal_readdir_cb cb; /*< Callback to the upper layer. */ struct mdcache_fsal_export *exp; /*< Export of the current mdcache. */ void *dir_state; /*< State to be sent to the next callback. */ }; fsal_status_t mdcache_lookup_path(struct fsal_export *exp_hdl, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); fsal_status_t mdcache_create_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *hdl_desc, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); int mdcache_fsal_open(struct mdcache_fsal_obj_handle *, int, fsal_errors_t *); int mdcache_fsal_readlink(struct mdcache_fsal_obj_handle *, fsal_errors_t *); static inline bool mdcache_unopenable_type(object_file_type_t type) { if ((type == SOCKET_FILE) || (type == CHARACTER_FILE) || (type == BLOCK_FILE)) { return true; } else { return false; } } /* I/O management */ fsal_status_t mdcache_seek(struct fsal_obj_handle *obj_hdl, struct io_info *info); fsal_status_t mdcache_io_advise(struct fsal_obj_handle *obj_hdl, struct io_hints *hints); fsal_status_t mdcache_close(struct fsal_obj_handle *obj_hdl); fsal_status_t mdcache_open2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attrib_set, fsal_verifier_t verifier, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out); bool mdcache_check_verifier(struct fsal_obj_handle *obj_hdl, fsal_verifier_t verifier); fsal_openflags_t mdcache_status2(struct fsal_obj_handle *obj_hdl, struct state_t *state); fsal_status_t mdcache_reopen2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags); void mdcache_read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg); void mdcache_write2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *caller_arg); fsal_status_t mdcache_seek2(struct fsal_obj_handle *obj_hdl, struct state_t *state, struct io_info *info); fsal_status_t mdcache_io_advise2(struct fsal_obj_handle *obj_hdl, struct state_t *state, struct io_hints *hints); fsal_status_t mdcache_commit2(struct fsal_obj_handle *obj_hdl, off_t offset, size_t len); fsal_status_t mdcache_lock_op2(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *p_owner, fsal_lock_op_t lock_op, fsal_lock_param_t *req_lock, fsal_lock_param_t *conflicting_lock); fsal_status_t mdcache_lease_op2(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *owner, fsal_deleg_t deleg); fsal_status_t mdcache_close2(struct fsal_obj_handle *obj_hdl, struct state_t *state); fsal_status_t mdcache_fallocate(struct fsal_obj_handle *obj_hdl, struct state_t *state, uint64_t offset, uint64_t length, bool allocate); /* extended attributes management */ fsal_status_t mdcache_list_ext_attrs(struct fsal_obj_handle *obj_hdl, unsigned int cookie, fsal_xattrent_t *xattrs_tab, unsigned int xattrs_tabsize, unsigned int *p_nb_returned, int *end_of_list); fsal_status_t mdcache_getextattr_id_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, unsigned int *pxattr_id); fsal_status_t mdcache_getextattr_value_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, size_t *p_output_size); fsal_status_t mdcache_getextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buffer_addr, size_t buffer_size, size_t *p_output_size); fsal_status_t mdcache_setextattr_value(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, int create); fsal_status_t mdcache_setextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buffer_addr, size_t buffer_size); fsal_status_t mdcache_remove_extattr_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id); fsal_status_t mdcache_remove_extattr_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name); fsal_status_t mdcache_getxattrs(struct fsal_obj_handle *obj_hdl, xattrkey4 *name, xattrvalue4 *value); fsal_status_t mdcache_setxattrs(struct fsal_obj_handle *obj_hdl, setxattr_option4 option, xattrkey4 *name, xattrvalue4 *value); fsal_status_t mdcache_removexattrs(struct fsal_obj_handle *obj_hdl, xattrkey4 *name); fsal_status_t mdcache_listxattrs(struct fsal_obj_handle *obj_hdl, count4 len, nfs_cookie4 *cookie, bool_t *eof, xattrlist4 *names); /* Handle functions */ void mdcache_handle_ops_init(struct fsal_obj_ops *ops); /* Export functions */ void mdcache_export_ops_init(struct export_ops *ops); /* Upcall functions */ fsal_status_t mdcache_export_up_ops_init(struct fsal_up_vector *my_up_ops, const struct fsal_up_vector *super_up_ops); /* Debug functions */ #define MDC_LOG_KEY(key) \ do { \ LogFullDebugOpaque(COMPONENT_MDCACHE, "FSAL key: %s", 128, \ (key)->kv.addr, (key)->kv.len); \ LogFullDebug(COMPONENT_MDCACHE, "hash key: %lx", (key)->hk); \ } while (0) static inline fsal_status_t mdcache_refresh_attrs_no_invalidate(mdcache_entry_t *entry) { fsal_status_t status; if (op_ctx->export_perms.expire_time_attr == 0) { /* Attribute cache is disabled, no need to refresh the * attributes */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } PTHREAD_RWLOCK_wrlock(&entry->attr_lock); status = mdcache_refresh_attrs(entry, false, false, false, NULL); PTHREAD_RWLOCK_unlock(&entry->attr_lock); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_MDCACHE, "Refresh attributes failed %s", fsal_err_txt(status)); if (status.major == ERR_FSAL_STALE) mdcache_kill_entry(entry); } return status; } static inline int avl_dmap_ck_cmpf(const struct avltree_node *lhs, const struct avltree_node *rhs) { mdcache_dmap_entry_t *lk, *rk; lk = avltree_container_of(lhs, mdcache_dmap_entry_t, node); rk = avltree_container_of(rhs, mdcache_dmap_entry_t, node); if (lk->ck < rk->ck) return -1; if (lk->ck == rk->ck) return 0; return 1; } #endif /* MDCACHE_INT_H */ nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_lru.c000066400000000000000000002002721473756622300242400ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2010, The Linux Box Corporation * Contributor : Matt Benjamin * * Some portions Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @addtogroup FSAL_MDCACHE * @{ */ #include "config.h" #include "nfs_init.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "fsal.h" #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "nfs_core.h" #include "log.h" #include "mdcache_lru.h" #include "mdcache_hash.h" #include "abstract_atomic.h" #include "atomic_utils.h" #include "gsh_intrinsic.h" #include "sal_functions.h" #include "nfs_exports.h" #include "sys_resource.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/mdcache.h" #endif /** * * @file mdcache_lru.c * @author Matt Benjamin * @brief Constant-time MDCACHE cache management implementation */ /** * @page LRUOverview LRU Overview * * This module implements a constant-time cache management strategy * based on LRU. Some ideas are taken from 2Q [Johnson and Shasha 1994] * and MQ [Zhou, Chen, Li 2004]. In this system, cache management does * interact with cache entry lifecycle, but the lru queue is not a garbage * collector. Most importantly, cache management operations execute in constant * time, as expected with LRU (and MQ). * * Cache entries in use by a currently-active protocol request (or other * operation) have a positive refcount, and therefore should not be present * at the cold end of an lru queue if the cache is well-sized. * * As noted below, initial references to cache entries may only be granted * under the MDCACHE hash table latch. Likewise, entries must first be * made unreachable to the MDCACHE hash table, then independently reach * a refcnt of 0, before they may be disposed or recycled. */ struct lru_state lru_state; /** * A single queue structure. */ struct lru_q { struct glist_head q; /* LRU is at HEAD, MRU at tail */ enum lru_q_id id; uint64_t size; }; /** * A single queue lane, holding all entries. */ struct lru_q_lane { struct lru_q L1; struct lru_q L2; struct lru_q cleanup; /* deferred cleanup */ struct lru_q ACTIVE; /* active references */ pthread_mutex_t ql_mtx; CACHE_PAD(0); }; /* The queue lock and the partition lock interact. The partition lock must * always be taken before the queue lock to avoid deadlock */ #ifdef USE_LTTNG #define QLOCK(qlane) \ do { \ PTHREAD_MUTEX_lock(&(qlane)->ql_mtx); \ GSH_UNIQUE_AUTO_TRACEPOINT(mdcache, qlock, TRACE_DEBUG, \ "QLOCK. qlane: {}", qlane); \ } while (0) #define QUNLOCK(qlane) \ do { \ GSH_UNIQUE_AUTO_TRACEPOINT(mdcache, qunlock, TRACE_DEBUG, \ "QUNLOCK. qlane: {}", qlane); \ PTHREAD_MUTEX_unlock(&(qlane)->ql_mtx); \ } while (0) #else #define QLOCK(qlane) PTHREAD_MUTEX_lock(&(qlane)->ql_mtx) #define QUNLOCK(qlane) PTHREAD_MUTEX_unlock(&(qlane)->ql_mtx) #endif /** * A multi-level LRU algorithm inspired by MQ [Zhou]. Transition from * L1 to L2 implies various checks (open files, etc) have been * performed, so ensures they are performed only once. A * correspondence to the "scan resistance" property of 2Q and MQ is * accomplished by recycling/clean loads onto the LRU of L1. Async * processing onto L2 constrains oscillation in this algorithm. */ static struct lru_q_lane LRU[LRU_N_Q_LANES]; static struct lru_q_lane CHUNK_LRU[LRU_N_Q_LANES]; /** * The refcount mechanism distinguishes 3 key object states: * * 1. unreferenced (unreachable) * 2. unincremented, but reachable * 3. incremented * * It seems most convenient to make unreferenced correspond to refcount==0. * Then refcount==1 is a SENTINEL_REFCOUNT in which the only reference to * the entry is the set of functions which can grant new references. An * object with refcount > 1 has been referenced by some thread, which must * release its reference at some point. * * More specifically, in the current implementation, reachability is * serialized by the cache lookup table latch. * * Currently, I propose to distinguish between objects with positive refcount * and objects with state. The latter could be evicted, in the normal case, * only with loss of protocol correctness, but may have only the sentinel * refcount. To preserve constant time operation, they are stored in an * independent partition of the LRU queue. */ static struct fridgethr *lru_fridge; static const uint32_t FD_FALLBACK_LIMIT = 0x400; /* Some helper macros */ #define LRU_NEXT(n) (atomic_inc_uint32_t(&(n)) % LRU_N_Q_LANES) /* Delete lru, use iif the current thread is not the LRU * thread. The node being removed is lru, glist a pointer to L1's q, * qlane its lane. */ #define LRU_DQ(lru, q) \ do { \ glist_del(&(lru)->q); \ --((q)->size); \ } while (0) #define CHUNK_LRU_DQ(lru, qq) \ do { \ glist_del(&(lru)->q); \ --((qq)->size); \ } while (0) #define LRU_ENTRY_L1_OR_L2(e) \ (((e)->lru.qid == LRU_ENTRY_L2) || ((e)->lru.qid == LRU_ENTRY_L1)) #define LRU_ENTRY_RECLAIMABLE(e, n) \ (LRU_ENTRY_L1_OR_L2(e) && ((n) == LRU_SENTINEL_REFCOUNT + 1) && \ ((e)->fh_hk.inavl)) /** * @brief Initialize a single base queue. * * This function initializes a single queue partition (L1, L2, * etc) */ static inline void lru_init_queue(struct lru_q *q, enum lru_q_id qid) { glist_init(&q->q); q->id = qid; q->size = 0; } static inline void lru_init_queues(void) { int ix; for (ix = 0; ix < LRU_N_Q_LANES; ++ix) { struct lru_q_lane *qlane; /* Initialize mdcache_entry_t LRU */ qlane = &LRU[ix]; /* one mutex per lane */ PTHREAD_MUTEX_init(&qlane->ql_mtx, NULL); /* init lane queues */ lru_init_queue(&LRU[ix].L1, LRU_ENTRY_L1); lru_init_queue(&LRU[ix].L2, LRU_ENTRY_L2); lru_init_queue(&LRU[ix].cleanup, LRU_ENTRY_CLEANUP); lru_init_queue(&LRU[ix].ACTIVE, LRU_ENTRY_ACTIVE); /* Initialize dir_chunk LRU */ qlane = &CHUNK_LRU[ix]; /* one mutex per lane */ PTHREAD_MUTEX_init(&qlane->ql_mtx, NULL); /* init lane queues */ lru_init_queue(&CHUNK_LRU[ix].L1, LRU_ENTRY_L1); lru_init_queue(&CHUNK_LRU[ix].L2, LRU_ENTRY_L2); lru_init_queue(&CHUNK_LRU[ix].cleanup, LRU_ENTRY_CLEANUP); } } static inline void lru_destroy_queues(void) { int ix; for (ix = 0; ix < LRU_N_Q_LANES; ++ix) { struct lru_q_lane *qlane; /* Destroy mdcache_entry_t LRU */ qlane = &LRU[ix]; PTHREAD_MUTEX_destroy(&qlane->ql_mtx); /* Destroy dir_chunk LRU */ qlane = &CHUNK_LRU[ix]; PTHREAD_MUTEX_destroy(&qlane->ql_mtx); } } /** * @brief Return a pointer to the current queue of entry * * This function returns a pointer to the queue on which entry is linked, * or NULL if entry is not on any queue. * * @note The caller @a MUST hold the lane lock * * @param[in] entry The entry. * * @return A pointer to entry's current queue, NULL if none. */ static inline struct lru_q *lru_queue_of(mdcache_entry_t *entry) { struct lru_q *q; switch (entry->lru.qid) { case LRU_ENTRY_L1: q = &LRU[(entry->lru.lane)].L1; break; case LRU_ENTRY_L2: q = &LRU[(entry->lru.lane)].L2; break; case LRU_ENTRY_CLEANUP: q = &LRU[(entry->lru.lane)].cleanup; break; case LRU_ENTRY_ACTIVE: q = &LRU[(entry->lru.lane)].ACTIVE; break; default: /* LRU_NO_LANE */ q = NULL; break; } /* switch */ return q; } /** * @brief Return a pointer to the current queue of chunk * * This function returns a pointer to the queue on which a chunk is linked, * or NULL if chunk is not on any queue. * * @note The caller @a MUST hold the lane lock * * @param[in] chunk The chunk. * * @return A pointer to chunk's current queue, NULL if none. */ static inline struct lru_q *chunk_lru_queue_of(struct dir_chunk *chunk) { struct lru_q *q; switch (chunk->chunk_lru.qid) { case LRU_ENTRY_L1: q = &CHUNK_LRU[(chunk->chunk_lru.lane)].L1; break; case LRU_ENTRY_L2: q = &CHUNK_LRU[(chunk->chunk_lru.lane)].L2; break; case LRU_ENTRY_CLEANUP: q = &CHUNK_LRU[(chunk->chunk_lru.lane)].cleanup; break; case LRU_ENTRY_ACTIVE: /* Should never happen... */ q = &CHUNK_LRU[(chunk->chunk_lru.lane)].ACTIVE; break; default: /* LRU_NO_LANE */ q = NULL; break; } /* switch */ return q; } /** * @brief Get the appropriate lane for a LRU chunk or entry * * This function gets the LRU lane by taking the modulus of the * supplied pointer. * * @param[in] entry A pointer to a LRU chunk or entry * * @return The LRU lane in which that entry should be stored. */ static inline uint32_t lru_lane_of(void *entry) { return (uint32_t)((((uintptr_t)entry) / 2 * sizeof(uintptr_t)) % LRU_N_Q_LANES); } /** * @brief Insert an entry into the specified queue and lane * * This function determines the queue corresponding to the supplied * lane and flags, inserts the entry into that queue, and updates the * entry to hold the flags and lane. * * @note The caller MUST hold a lock on the queue lane. * * @param[in] lru The LRU entry to insert * @param[in] q The queue to insert on */ static inline void lru_insert(mdcache_lru_t *lru, struct lru_q *q) { lru->qid = q->id; /* initial */ if (lru->qid == LRU_ENTRY_CLEANUP) { atomic_set_uint32_t_bits(&lru->flags, LRU_CLEANUP); /* Add to tail of cleanup queue */ glist_add_tail(&q->q, &lru->q); } else { glist_add(&q->q, &lru->q); } ++(q->size); } /** * @brief Insert a chunk into the specified queue and lane with locking * * This function determines the queue corresponding to the supplied * lane and flags, inserts the chunk into that queue, and updates the * chunk to hold the flags and lane. * * @note The caller MUST NOT hold a lock on the queue lane. * * @param[in] lru The LRU chunk to insert * @param[in] q The queue to insert on */ static inline void lru_insert_chunk(struct dir_chunk *chunk, struct lru_q *q) { mdcache_lru_t *lru = &chunk->chunk_lru; struct lru_q_lane *qlane = &CHUNK_LRU[lru->lane]; QLOCK(qlane); lru_insert(lru, q); QUNLOCK(qlane); } /* * @brief Move an entry from LRU L1 or L2 queues to the ACTIVE queue. * * @param [in] entry Entry to adjust. */ static inline void make_active_lru(mdcache_entry_t *entry) { mdcache_lru_t *lru = &entry->lru; struct lru_q_lane *qlane = &LRU[lru->lane]; struct lru_q *q; QLOCK(qlane); switch (lru->qid) { case LRU_ENTRY_L1: q = lru_queue_of(entry); /* move entry to MRU of ACTIVE */ LRU_DQ(lru, q); q = &qlane->ACTIVE; lru_insert(lru, q); break; case LRU_ENTRY_L2: q = lru_queue_of(entry); /* move entry to MRU of ACTIVE */ LRU_DQ(lru, q); q = &qlane->ACTIVE; lru_insert(lru, q); break; case LRU_ENTRY_ACTIVE: q = lru_queue_of(entry); /* advance entry to MRU (of ACTIVE) */ LRU_DQ(lru, q); lru_insert(lru, q); break; default: /* do nothing */ break; } /* switch qid */ QUNLOCK(qlane); } /* * @brief Move an active entry from ACTIVE queue to MRU of L1 or L2. * * If the entry is in the cleanup queue, do nothing. * * Assumes qlane lock is held. * * @param [in] entry Entry to adjust. */ static inline void make_inactive_lru(mdcache_entry_t *entry) { mdcache_lru_t *lru = &entry->lru; struct lru_q_lane *qlane = &LRU[lru->lane]; struct lru_q *q; switch (lru->qid) { case LRU_ENTRY_L1: case LRU_ENTRY_L2: assert(false); break; case LRU_ENTRY_ACTIVE: /* Move entry to MRU of L1 or L2 */ q = lru_queue_of(entry); LRU_DQ(&entry->lru, q); if (atomic_fetch_uint32_t(&entry->lru.flags) & LRU_EVER_PROMOTED) { /* If entry was ever promoted, insert into L1. */ q = &qlane->L1; } else { /* Entry was never promoted, only ever used in a * directory scan, return to L2. */ q = &qlane->L2; } lru_insert(&entry->lru, q); break; case LRU_ENTRY_CLEANUP: default: /* do nothing */ break; } /* switch qid */ } /** * @brief Clean an entry for recycling. * * This function cleans an entry up before it's recycled or freed. * * @param[in] entry The entry to clean */ static inline void mdcache_lru_clean(mdcache_entry_t *entry) { fsal_status_t status = { 0, 0 }; /* Free SubFSAL resources */ if (entry->sub_handle) { /* There are four basic paths to get here. * * One path is that this cache entry is being reaped. In that * case, if an unexport is in progress removing the last export * this entry was mapped to, in the process of being completely * detached from an export, it also became unreapable (placed on * the LRU_ENTRY_CLEANUP queue not L1 or L2). Therefore, if we * get here with a reaped entry, it MUST still be attached to * an export. * * Another path to get here is the export is still valid, and * this entry is being killed. In that case, all the export * stuff is fine. * * Another path is that we have removed the final export, and * unexport is releasing the last reference. In that case, * the unexport process has the export in question in the op_ctx * so we are fine. * * The final case is that this entry was referenced by a thread * other than the unexport, and the operational thread is the * one releasing the last LRU reference. In that case, the * caller's op_ctx must have the correct export. * * This is true even for operations that require two handles. * NFS v3 checks for xdev before converting from a handle to an * LRU reference. NFS v4 holds an LRU reference for the saved FH * so the last reference can only be dropped when the saved FH * is cleaned up, which will be done with the correct op_ctx. 9P * also assures that LRU references are released with the proper * op_ctx. * * So in all cases, we can either trust the current export, or * we can use the first_export_id to get a valid export for * a reaping case. */ struct req_op_context op_context; bool used_ctx = false; int32_t export_id; struct gsh_export *export; /* Find the first export id. */ export_id = atomic_fetch_int32_t(&entry->first_export_id); /* Check if we have a valid op_ctx */ if (export_id >= 0 && (op_ctx == NULL || op_ctx->ctx_export == NULL || op_ctx->ctx_export->export_id != export_id)) { /* If the entry's first_export_id is valid and does not * match the current op_ctx, set up a new context * using first_export_id to ensure the op_ctx export is * valid for the entry. * * Get a reference to the first_export_id. */ export = get_gsh_export(export_id); if (export == NULL) { /* This really should not happen, if an unexport * is in progress, the export_id is now not * removed until after mdcache has detached all * entries from the export. An entry that is * actually in the process of being detached has * an LRU reference which prevents it from being * reaped, so there is no path to get into * mdcache_lru_clean without the export still * being valid. */ LogFatal( COMPONENT_MDCACHE, "An entry (%p) having an unmappable export_id (%" PRIi32 ") is unexpected", entry, export_id); } LogFullDebug( COMPONENT_MDCACHE, "Creating a new context with export id%" PRIi32, export_id); init_op_context_simple(&op_context, export, export->fsal_export); used_ctx = true; } else { /* We MUST have a valid op_ctx based on the conditions * we could get here. first_export_id coild be -1 or it * could match the current op_ctx export. In either case * we will trust the current op_ctx. */ assert(op_ctx); assert(op_ctx->ctx_export); LogFullDebug(COMPONENT_MDCACHE, "Trusting op_ctx export id %" PRIu16, op_ctx->ctx_export->export_id); } /* Make sure any FSAL global file descriptor is closed. * Don't bother with the content_lock since we have exclusive * ownership of this entry. */ status = fsal_close(&entry->obj_handle); if (FSAL_IS_ERROR(status)) { LogCrit(COMPONENT_MDCACHE_LRU, "Error closing file in cleanup: %s", fsal_err_txt(status)); } subcall(entry->sub_handle->obj_ops->release(entry->sub_handle)); entry->sub_handle = NULL; if (used_ctx) { /* We had to use our own op_ctx, clean it up and revert * to the saved op_ctx. */ release_op_context(); } } /* Done with the attrs */ fsal_release_attrs(&entry->attrs); /* Clean out the export mapping before deconstruction */ mdc_clean_entry(entry); /* Clean our handle */ fsal_obj_handle_fini(&entry->obj_handle, true); /* Finalize last bits of the cache entry, delete the key if any and * destroy the rw locks. */ mdcache_key_delete(&entry->fh_hk.key); PTHREAD_RWLOCK_destroy(&entry->content_lock); PTHREAD_RWLOCK_destroy(&entry->attr_lock); state_hdl_cleanup(entry->obj_handle.state_hdl, entry->obj_handle.type); if (entry->obj_handle.type == DIRECTORY) PTHREAD_SPIN_destroy(&entry->fsobj.fsdir.fsd_spin); } /** * @brief Try to pull an entry off the queue * * This function examines the end of the specified queue and if the * entry found there can be re-used, it returns with the entry * locked. Otherwise, it returns NULL. * * This function follows the locking discipline detailed above. It * returns an lru entry removed from the queue system and which we are * permitted to dispose or recycle. * * @note The caller @a MUST @a NOT hold the lane lock * * @param[in] qid Queue to reap * @return Available entry if found, NULL otherwisem the reference held on * the object is a LRU_TEMP_REF. */ static uint32_t reap_lane; static inline mdcache_lru_t *lru_reap_impl(enum lru_q_id qid) { uint32_t lane; struct lru_q_lane *qlane; struct lru_q *lq; mdcache_lru_t *lru; mdcache_entry_t *entry; uint32_t refcnt; cih_latch_t latch; int ix; lane = LRU_NEXT(reap_lane); for (ix = 0; ix < LRU_N_Q_LANES; ++ix, lane = LRU_NEXT(reap_lane)) { qlane = &LRU[lane]; lq = (qid == LRU_ENTRY_L1) ? &qlane->L1 : &qlane->L2; QLOCK(qlane); lru = glist_first_entry(&lq->q, mdcache_lru_t, q); if (!lru) { QUNLOCK(qlane); continue; } #if 0 /* effectively do... this compiled out to help cross reference */ mdcache_lru_ref(entry, LRU_TEMP_REF); #endif refcnt = atomic_inc_int32_t(&lru->refcnt); entry = container_of(lru, mdcache_entry_t, lru); const int32_t active_refcnt = atomic_fetch_int32_t(&entry->lru.active_refcnt); GSH_UNIQUE_AUTO_TRACEPOINT( mdcache, mdc_lru_ref, TRACE_DEBUG, "lru ref. handle: {}, sub handle: {}, refcnt: {}, active_refcnt: {}", &entry->obj_handle, entry->sub_handle, refcnt, active_refcnt); QUNLOCK(qlane); if (unlikely(refcnt != (LRU_SENTINEL_REFCOUNT + 1))) { /* can't use it. */ mdcache_lru_unref(entry, LRU_TEMP_REF); continue; } /* potentially reclaimable */ /* entry must be unreachable from CIH when recycled */ cih_latch_entry(&entry->fh_hk.key, &latch, __func__, __LINE__); QLOCK(qlane); refcnt = atomic_fetch_int32_t(&entry->lru.refcnt); /* there are two cases which permit reclaim, * entry is: * 1. reachable but unref'd (refcnt==2) * 2. unreachable, being removed (plus refcnt==0) * for safety, take only the former */ if (LRU_ENTRY_RECLAIMABLE(entry, refcnt)) { /* it worked */ struct lru_q *q = lru_queue_of(entry); GSH_AUTO_TRACEPOINT(mdcache, mdc_lru_reap, TRACE_DEBUG, "lru unref. handle: {}, refcnt: {}", &entry->obj_handle, entry->lru.refcnt); LRU_DQ(lru, q); entry->lru.qid = LRU_ENTRY_NONE; QUNLOCK(qlane); cih_remove_latched(entry, &latch, CIH_REMOVE_UNLOCK); /* Note, we're not releasing our ref here. * cih_remove_latched() called * mdcache_lru_unref(), which released the * sentinel ref, leaving just the one ref we * took earlier. Returning this as is leaves it * with a ref of 1 (ie, just the temp ref) * */ goto out; } cih_hash_release(&latch); QUNLOCK(qlane); /* return the ref we took above--unref deals * correctly with reclaim case */ mdcache_lru_unref(entry, LRU_TEMP_REF); } /* foreach lane */ /* ! reclaimable */ lru = NULL; out: return lru; } static inline mdcache_lru_t *lru_try_reap_entry(uint32_t flags) { mdcache_lru_t *lru; if (atomic_fetch_uint64_t(&lru_state.entries_used) < lru_state.entries_hiwat) return NULL; /* XXX dang why not start with the cleanup list? */ lru = lru_reap_impl(LRU_ENTRY_L2); if (!lru) lru = lru_reap_impl(LRU_ENTRY_L1); return lru; } /** * @brief Try to pull an chunk off the queue * * This function examines the end of the specified queue and if the * chunk found there can be re-used. Otherwise, it returns NULL. * * This function follows the locking discipline detailed above. It * returns an lru object removed from the queue system and which we are * permitted to dispose or recycle. * * This function can reap a chunk from the directory a chunk is requested * for. In that case, since the content_lock is already held, we can * proceed somewhat easier. * * @note The caller @a MUST @a NOT hold the lane lock * * @param[in] qid Queue to reap * @param[in] parent The directory we desire a chunk for * * @return Available chunk if found, NULL otherwise */ static uint32_t chunk_reap_lane; static inline mdcache_lru_t *lru_reap_chunk_impl(enum lru_q_id qid, mdcache_entry_t *parent) { uint32_t lane; struct lru_q_lane *qlane; struct lru_q *lq; mdcache_lru_t *lru; mdcache_entry_t *entry; struct dir_chunk *chunk; int ix; int32_t refcnt; lane = LRU_NEXT(chunk_reap_lane); for (ix = 0; ix < LRU_N_Q_LANES; ++ix, lane = LRU_NEXT(chunk_reap_lane)) { qlane = &CHUNK_LRU[lane]; lq = (qid == LRU_ENTRY_L1) ? &qlane->L1 : &qlane->L2; QLOCK(qlane); lru = glist_first_entry(&lq->q, mdcache_lru_t, q); if (!lru) { QUNLOCK(qlane); continue; } refcnt = atomic_fetch_int32_t(&lru->refcnt); assert(refcnt); if (refcnt != (LRU_SENTINEL_REFCOUNT)) { /* We can't reap a chunk with a ref */ QUNLOCK(qlane); continue; } /* Get the chunk and parent entry that owns the chunk, all of * this is valid because we hold the QLANE lock, the chunk was * in the LRU, and thus the chunk is not yet being destroyed, * and thus the parent entry must still also be valid. */ chunk = container_of(lru, struct dir_chunk, chunk_lru); entry = chunk->parent; /* We need entry's content_lock to clean this chunk. * The usual lock order is content_lock followed by * chunk QLANE lock. Here we already have chunk QLANE * lock, so we try to acquire content_lock. If we fail * to acquire it, we just look for another chunk to * reap! * * Note that the entry is valid but it could be in the * process of getting destroyed (refcnt could be 0)! If * we do get the content_lock, it should continue to be * valid until we release the lock! * * If the entry is same as parent, we should already * have the content lock. */ if (entry == parent || pthread_rwlock_trywrlock(&entry->content_lock) == 0) { /* Dequeue the chunk so it won't show up anymore */ CHUNK_LRU_DQ(lru, lq); chunk->chunk_lru.qid = LRU_ENTRY_NONE; GSH_AUTO_TRACEPOINT(mdcache, mdc_lru_reap_chunk, TRACE_DEBUG, "lru unref. handle: {}, chunk: {}", &entry->obj_handle, chunk); /* Clean the chunk out and indicate the directory * is no longer completely populated. We don't * need to hold a ref on the entry as we hold its * content_lock and the chunk is valid under QLANE * lock. */ mdcache_clean_dirent_chunk(chunk); atomic_clear_uint32_t_bits(&entry->mde_flags, MDCACHE_DIR_POPULATED); if (entry != parent) { /* And now we're done with the parent of the * chunk if it wasn't the directory we are * acquiring a new chunk for. */ PTHREAD_RWLOCK_unlock(&entry->content_lock); } QUNLOCK(qlane); return lru; } /* Couldn't get the content_lock, the parent is busy * doing something with dirents... This chunk is not * eligible for reaping. Try the next lane... */ QUNLOCK(qlane); } /* foreach lane */ /* ! reclaimable */ return NULL; } /** * @brief Re-use or allocate a chunk * * This function repurposes a resident chunk in the LRU system if the system is * above the high-water mark, and allocates a new one otherwise. The resulting * chunk is inserted into the chunk list. * * @note The caller must hold the content_lock of the parent for write. * * @param[in] parent The parent directory we desire a chunk for * @param[in] prev_chunk If non-NULL, the previous chunk in this directory * @param[in] whence If @a prev_chunk is NULL, the starting whence of chunk * * @return reused or allocated chunk with a ref taken for the caller */ struct dir_chunk *mdcache_get_chunk(mdcache_entry_t *parent, struct dir_chunk *prev_chunk, fsal_cookie_t whence) { mdcache_lru_t *lru = NULL; struct dir_chunk *chunk = NULL; /* Get a ref on prev_chunk, so that it's not reaped */ if (prev_chunk) mdcache_lru_ref_chunk(prev_chunk); if (lru_state.chunks_used >= lru_state.chunks_hiwat) { lru = lru_reap_chunk_impl(LRU_ENTRY_L2, parent); if (!lru) lru = lru_reap_chunk_impl(LRU_ENTRY_L1, parent); } if (lru) { /* we uniquely hold chunk, it has already been cleaned up. * The dirents list is effectively properly initialized. */ chunk = container_of(lru, struct dir_chunk, chunk_lru); LogFullDebug(COMPONENT_MDCACHE, "Recycling chunk at %p.", chunk); } else { /* alloc chunk (if fails, aborts) */ chunk = gsh_calloc(1, sizeof(struct dir_chunk)); glist_init(&chunk->dirents); LogFullDebug(COMPONENT_MDCACHE, "New chunk %p.", chunk); (void)atomic_inc_int64_t(&lru_state.chunks_used); } /* Set the chunk's parent and insert */ chunk->parent = parent; glist_add_tail(&chunk->parent->fsobj.fsdir.chunks, &chunk->chunks); if (prev_chunk) { chunk->reload_ck = glist_last_entry(&prev_chunk->dirents, mdcache_dir_entry_t, chunk_list) ->ck; /* unref prev_chunk as we had got a ref on prev_chunk * at the beginning of this function */ mdcache_lru_unref_chunk(prev_chunk); } else { chunk->reload_ck = whence; } chunk->chunk_lru.refcnt = 2; chunk->chunk_lru.cf = 0; chunk->chunk_lru.lane = lru_lane_of(chunk); /* Enqueue into MRU of L2. * * NOTE: A newly allocated and filled chunk will be promoted to L1 LRU * when readdir_chunked starts passing entries up to the caller. * This gets us the expected positioning for a new chunk that is * utilized to form a readdir response. * * The benefit of this mechanism comes when the FSAL supports * readahead. In that case, the chunks that are readahead will * be left in L2 MRU. This helps keep the chunks associated with * a particular FSAL readdir call including readahead from being * immediate candidates for reaping, thus keeping the readahead * from cannibalizing itself. Of course if the L2 queue is * empty due to activity, and the readahead is significant, it * is possible to cannibalize the chunks. */ lru_insert_chunk(chunk, &CHUNK_LRU[chunk->chunk_lru.lane].L2); return chunk; } /** * @brief Push a killed entry to the cleanup queue for out-of-line cleanup * * This function appends entry to the appropriate lane of the global cleanup * queue, and marks the entry. * * @param[in] entry The entry to clean */ void mdcache_lru_cleanup_push(mdcache_entry_t *entry) { mdcache_lru_t *lru = &entry->lru; struct lru_q_lane *qlane = &LRU[lru->lane]; QLOCK(qlane); if (lru->qid != LRU_ENTRY_CLEANUP) { struct lru_q *q; /* out with the old queue */ q = lru_queue_of(entry); LRU_DQ(lru, q); /* in with the new */ q = &qlane->cleanup; lru_insert(lru, q); } QUNLOCK(qlane); } /** * @brief Push an entry to the cleanup queue that may be unexported * for out-of-line cleanup * * This routine is used to try pushing a cache entry into the cleanup * queue. If the entry ends up with another LRU reference before this * is accomplished, then don't push it to cleanup. * * This will be used when unexporting an export. Any cache entry * that only belonged to that export is a candidate for cleanup. * However, it is possible the entry is still accessible via another * export, and an LRU reference might be gained before we can lock the * AVL tree. In that case, the entry must be left alone (thus * mdcache_kill_entry is NOT suitable for this purpose). * * @param[in] entry The entry to clean */ void mdcache_lru_cleanup_try_push(mdcache_entry_t *entry) { mdcache_lru_t *lru = &entry->lru; struct lru_q_lane *qlane = &LRU[lru->lane]; cih_latch_t latch; cih_latch_entry(&entry->fh_hk.key, &latch, __func__, __LINE__); QLOCK(qlane); /* Take the attr lock, so we can check that this entry is still * not in any export */ PTHREAD_RWLOCK_rdlock(&entry->attr_lock); /* Make sure that the entry is not reaped by the time this * thread got the QLOCK */ if (glist_empty(&entry->export_list) && entry->lru.qid != LRU_ENTRY_NONE) { /* it worked */ struct lru_q *q = lru_queue_of(entry); LRU_DQ(lru, q); entry->lru.qid = LRU_ENTRY_CLEANUP; atomic_set_uint32_t_bits(&entry->lru.flags, LRU_CLEANUP); /* Note: we didn't take a ref here, so the only ref left * in this callpath is the one owned by * mdcache_unexport(). When it unref's, that may free * this object; otherwise, it will be freed when the * last op is done, as it's unreachable. */ /* It's safe to drop the attr lock here, as we have the * latch, so no one can look up the entry */ PTHREAD_RWLOCK_unlock(&entry->attr_lock); QUNLOCK(qlane); /* Drop the sentinel reference */ cih_remove_latched(entry, &latch, CIH_REMOVE_NONE); } else { PTHREAD_RWLOCK_unlock(&entry->attr_lock); QUNLOCK(qlane); } cih_hash_release(&latch); } /** * @brief Function that executes in the lru thread to process one lane * * @param[in] lane The lane to process * * @returns the number of files worked on (workdone) * */ static inline int lru_run_lane(int lane) { struct lru_q *q; /* The amount of work done on this lane on this pass. */ size_t workdone = 0; /* Current queue lane */ struct lru_q_lane *qlane = &LRU[lane]; struct glist_head *glist, *glistn; q = &qlane->L1; LogDebug(COMPONENT_MDCACHE_LRU, "Reaping up to %d entries from lane %d", lru_state.per_lane_work, lane); /* ACTIVE */ QLOCK(qlane); glist_for_each_safe(glist, glistn, &q->q) { /* The entry being examined */ mdcache_lru_t *lru = NULL; /* a cache entry */ mdcache_entry_t *entry; /* entry refcnt */ uint32_t refcnt; /* check per-lane work */ if (workdone >= lru_state.per_lane_work) break; lru = glist_entry(glist, mdcache_lru_t, q); /* get entry early. This is safe without a ref, because we have * the QLANE lock */ entry = container_of(lru, mdcache_entry_t, lru); /* Get refcount of the entry now */ refcnt = atomic_fetch_int32_t(&entry->lru.refcnt); const int32_t active_refcnt = atomic_fetch_int32_t(&entry->lru.active_refcnt); GSH_UNIQUE_AUTO_TRACEPOINT( mdcache, mdc_lru_ref, TRACE_DEBUG, "lru ref. handle: {}, sub handle: {}, refcnt: {}, active_refcnt: {}", &entry->obj_handle, entry->sub_handle, refcnt, active_refcnt); /* check refcnt in range */ if (unlikely(refcnt == 1)) { struct lru_q *q; /* Move entry to MRU of L2 */ q = &qlane->L1; LRU_DQ(lru, q); q = &qlane->L2; lru_insert(lru, q); ++workdone; } } /* for_each_safe lru */ QUNLOCK(qlane); LogDebug(COMPONENT_MDCACHE_LRU, "Actually processed %zd entries on lane %d", workdone, lane); return workdone; } /** * @brief Function that executes in the lru thread * * This function performs long-term reorganization, compaction, and * other operations that are not performed in-line with referencing * and dereferencing. * * This function is responsible for deferred cleanup of cache entries * killed in request or upcall (or most other) contexts. * * This function is responsible for cleaning the FD cache. It works * by the following rules: * * - If the number of open FDs is below the low water mark, do * nothing. * * - If the number of open FDs is between the low and high water * mark, make one pass through the queues, and exit. Each pass * consists of taking an entry from L1, examining to see if it is a * regular file not bearing state with an open FD, closing the open * FD if it is, and then moving it to L2. The advantage of the two * level system is twofold: First, seldom used entries congregate * in L2 and the promotion behaviour provides some scan * resistance. Second, once an entry is examined, it is moved to * L2, so we won't examine the same cache entry repeatedly. * * - If the number of open FDs is greater than the high water mark, * we consider ourselves to be in extremis. In this case we make a * number of passes through the queue not to exceed the number of * passes that would be required to process the number of entries * equal to a biggest_window percent of the system specified * maximum. * * - If we are in extremis, and performing the maximum amount of work * allowed has not moved the open FD count required_progress% * toward the high water mark, increment lru_state.futility. If * lru_state.futility reaches futility_count, temporarily disable * FD caching. * * - Every time we wake through timeout, reset futility_count to 0. * * - If we fall below the low water mark and FD caching has been * temporarily disabled, re-enable it. * * This function uses the lock discipline for functions accessing LRU * entries through a queue partition. * * @param[in] ctx Fridge context */ static void lru_run(struct fridgethr_context *ctx) { /** @todo FSF - this could use some additional effort on finding a * new scheduling algorithm and tracking progress. */ /* Index */ int lane = 0; time_t threadwait = mdcache_param.lru_run_interval; /* Total work done in all passes so far. */ int totalwork = 0; static bool first_time = TRUE; time_t curr_time; if (first_time) { /* Wait for NFS server to properly initialize */ nfs_init_wait(); first_time = FALSE; } SetNameFunction("cache_lru"); LogFullDebug(COMPONENT_MDCACHE_LRU, "LRU awakes."); LogFullDebug(COMPONENT_MDCACHE_LRU, "lru entries: %" PRIu64, atomic_fetch_uint64_t(&lru_state.entries_used)); curr_time = time(NULL); if ((curr_time >= lru_state.prev_time) && (curr_time - lru_state.prev_time < fridgethr_getwait(ctx))) threadwait = curr_time - lru_state.prev_time; /* Loop over all lanes to perform L1 to L2 demotion. Track the work * done for logging. */ for (lane = 0; lane < LRU_N_Q_LANES; ++lane) { LogDebug(COMPONENT_MDCACHE_LRU, "Demoting up to %d entries from lane %d", lru_state.per_lane_work, lane); LogFullDebug(COMPONENT_MDCACHE_LRU, "totalwork=%d", totalwork); totalwork += lru_run_lane(lane); } /* We're trying to release the entry cache if the amount * used is higher than the water level. every time we can * try best to release the number of entries until entries * cache below the high water mark. the max number of entries * released per time is entries_release_size. */ if (lru_state.entries_release_size > 0) { if (atomic_fetch_uint64_t(&lru_state.entries_used) > lru_state.entries_hiwat) { size_t released = 0; LogFullDebug( COMPONENT_MDCACHE_LRU, "Entries used is %" PRIu64 " and above water mark, LRU want release %d entries", atomic_fetch_uint64_t(&lru_state.entries_used), lru_state.entries_release_size); EXPORT_ADMIN_LOCK(); released = mdcache_lru_release_entries( lru_state.entries_release_size); EXPORT_ADMIN_UNLOCK(); LogFullDebug(COMPONENT_MDCACHE_LRU, "Actually release %zd entries", released); } else { LogFullDebug( COMPONENT_MDCACHE_LRU, "Entries used is %" PRIu64 " and low water mark: not releasing", atomic_fetch_uint64_t(&lru_state.entries_used)); } } if (atomic_fetch_uint64_t(&lru_state.entries_used) > lru_state.entries_hiwat) { /* If we are still over the high water mark, try and reap * sooner. */ threadwait = threadwait / 2; } fridgethr_setwait(ctx, threadwait); LogDebug(COMPONENT_MDCACHE_LRU, "After work, count:%" PRIu64 " new_thread_wait=%" PRIu64, atomic_fetch_uint64_t(&lru_state.entries_used), ((uint64_t)threadwait)); LogFullDebug(COMPONENT_MDCACHE_LRU, "totalwork=%d lanes=%d", totalwork, LRU_N_Q_LANES); } /** * @brief Function that executes in the lru thread to process one lane * * This function really just demotes chunks from L1 to L2, so very simple. * * @param[in] lane The lane to process * * @returns the number of chunks worked on (workdone) * */ static inline size_t chunk_lru_run_lane(size_t lane) { struct lru_q *q; /* The amount of work done on this lane on this pass. */ size_t workdone = 0; /* The lru object being examined */ mdcache_lru_t *lru = NULL; /* Current queue lane */ struct lru_q_lane *qlane = &CHUNK_LRU[lane]; struct dir_chunk *chunk; uint32_t refcnt; struct glist_head *glist, *glistn; q = &qlane->L1; LogFullDebug(COMPONENT_MDCACHE_LRU, "Reaping up to %d chunks from lane %zd", lru_state.per_lane_work, lane); /* ACTIVE */ QLOCK(qlane); glist_for_each_safe(glist, glistn, &q->q) { struct lru_q *q; /* check per-lane work */ if (workdone >= lru_state.per_lane_work) break; lru = glist_entry(glist, mdcache_lru_t, q); chunk = container_of(lru, struct dir_chunk, chunk_lru); refcnt = atomic_fetch_int32_t(&chunk->chunk_lru.refcnt); assert(refcnt); if (unlikely(refcnt > LRU_SENTINEL_REFCOUNT)) { workdone++; continue; } /* Move lru object to MRU of L2 */ q = &qlane->L1; CHUNK_LRU_DQ(lru, q); q = &qlane->L2; lru_insert(lru, q); } /* for_each_safe lru */ QUNLOCK(qlane); LogFullDebug(COMPONENT_MDCACHE_LRU, "Actually processed %zd chunks on lane %zd", workdone, lane); return workdone; } /** * @brief Function that executes in the lru thread * * This function reorganizes the L1 and L2 queues, demoting least recently * used L1 chunks to L2. * * This function uses the lock discipline for functions accessing LRU * entries through a queue partition. * * @param[in] ctx Fridge context */ static void chunk_lru_run(struct fridgethr_context *ctx) { /* Index */ size_t lane; /* A ratio computed to adjust wait time based on how close to high * water mark for number of chunks we are. */ float wait_ratio; /* The computed wait time. */ time_t new_thread_wait; /* Total work done (number of chunks demoted) across all lanes. */ size_t totalwork = 0; static bool first_time = true; size_t target_release = 0, actual_release = 0; if (first_time) { /* Wait for NFS server to properly initialize */ nfs_init_wait(); first_time = false; } SetNameFunction("chunk_lru"); LogFullDebug(COMPONENT_MDCACHE_LRU, "LRU awakes, lru chunks used: %" PRIu64, lru_state.chunks_used); /* Total chunks demoted to L2 between all lanes and all current runs. */ for (lane = 0; lane < LRU_N_Q_LANES; ++lane) { LogFullDebug( COMPONENT_MDCACHE_LRU, "Reaping up to %d chunks from lane %zd totalwork=%zd", lru_state.per_lane_work, lane, totalwork); totalwork += chunk_lru_run_lane(lane); } if (lru_state.chunks_used > lru_state.chunks_hiwat) { /* If chunks are over high water mark, target to reap 1% of the * chunks in use. */ target_release += lru_state.chunks_used / 100; } if (atomic_fetch_uint64_t(&lru_state.entries_used) > lru_state.entries_hiwat) { /* If the inode cache is over high water mark, target to reap an * additional 1% of the chunks in use. */ target_release += lru_state.chunks_used / 100; } /* We may need to reap chunks */ if (lru_state.chunks_used > lru_state.chunks_lowat) { /* If chunks are over low water mark, target to reap an * additional 1% of the chunks in use. Minimum of 1. */ target_release += lru_state.chunks_used / 100; if (target_release == 0) target_release = 1; } while (actual_release < target_release) { mdcache_lru_t *lru = NULL; struct dir_chunk *chunk; lru = lru_reap_chunk_impl(LRU_ENTRY_L2, NULL); if (lru == NULL) lru = lru_reap_chunk_impl(LRU_ENTRY_L1, NULL); if (lru == NULL) { /* No more progress possible. */ break; } actual_release++; /* we uniquely hold chunk, it has already been cleaned up. * The dirents list is effectively properly initialized. */ chunk = container_of(lru, struct dir_chunk, chunk_lru); LogFullDebug(COMPONENT_MDCACHE, "Releasing chunk at %p.", chunk); mdcache_lru_unref_chunk(chunk); } /* Run more frequently the closer to max number of chunks we are. */ wait_ratio = 1.0 - (lru_state.chunks_used / lru_state.chunks_hiwat); if (wait_ratio < 0.1) { /* wait_ratio could even be negative if chunks_used is greater * than chunks_hiwat. Never have an interval shorter than 10% * of the lru_run_interval. */ wait_ratio = 0.1; } if (actual_release < (target_release / 2)) { /* We wanted to release chunks and did not release enough. */ wait_ratio = wait_ratio / 2; } new_thread_wait = mdcache_param.lru_run_interval * wait_ratio; /* if new_thread_wait is 0, chunk_lru_run would not be scheduled (just * in case the lru_run_interval is really small we want to make sure to * run at least every second). */ if (new_thread_wait == 0) new_thread_wait = 1; fridgethr_setwait(ctx, new_thread_wait); LogDebug(COMPONENT_MDCACHE_LRU, "After work, threadwait=%" PRIu64 " totalwork=%zd target_release = %zd actual_release = %zd", ((uint64_t)new_thread_wait), totalwork, target_release, actual_release); } /* @brief Release reapable entries until we are below the high-water mark * * If something refs a lot of entries at the same time, this can put the number * of entries above the high water mark. Every time we want try best to release * the number of entries, the max number is want_release. * * Normally, want_release equals Entries_Relesase_Size, If it is set to -1 * or negative, this going to be a big hammer, that will clean up anything it * can until either it can't anymore, or we're back below the high water mark. * * @param[in] want_release Maximum number of entries released. Note that if set * negative number, it indicates release all until can't release * @return Return the number of really released */ size_t mdcache_lru_release_entries(int32_t want_release) { mdcache_lru_t *lru; mdcache_entry_t *entry = NULL; size_t released = 0; /*release nothing*/ if (want_release == 0) return released; while ((lru = lru_try_reap_entry(LRU_TEMP_REF))) { entry = container_of(lru, mdcache_entry_t, lru); /* Release the reference taken by lru_try_reap_entry. The * entry has already been unhashed and the sentinel reference * released. */ mdcache_lru_unref(entry, LRU_TEMP_REF); ++released; if (want_release > 0 && released >= want_release) break; } return released; } /* Public functions */ void init_fds_limit(void) { struct fd_lru_parameter fd_lru_parameter; fd_lru_parameter.lru_run_interval = mdcache_param.lru_run_interval; fd_lru_parameter.Cache_FDs = mdcache_param.Cache_FDs; fd_lru_parameter.close_fast = mdcache_param.close_fast; fd_lru_parameter.fd_limit_percent = mdcache_param.fd_limit_percent; fd_lru_parameter.fd_hwmark_percent = mdcache_param.fd_hwmark_percent; fd_lru_parameter.fd_lwmark_percent = mdcache_param.fd_lwmark_percent; fd_lru_parameter.reaper_work = mdcache_param.reaper_work; fd_lru_parameter.reaper_work_per_lane = mdcache_param.reaper_work_per_lane; fd_lru_parameter.biggest_window = mdcache_param.biggest_window; fd_lru_parameter.required_progress = mdcache_param.required_progress; fd_lru_parameter.futility_count = mdcache_param.futility_count; fd_lru_parameter.fd_fallback_limit = FD_FALLBACK_LIMIT; fsal_init_fds_limit(&fd_lru_parameter); } /** * Initialize subsystem */ fsal_status_t mdcache_lru_pkginit(void) { /* Return code from system calls */ int code = 0; struct fridgethr_params frp; fsal_status_t status; struct fd_lru_parameter fd_lru_parameter; memset(&frp, 0, sizeof(struct fridgethr_params)); frp.thr_max = 2; frp.thr_min = 2; frp.thread_delay = mdcache_param.lru_run_interval; frp.flavor = fridgethr_flavor_looper; if (mdcache_param.reaper_work) { /* Backwards compatibility */ lru_state.per_lane_work = (mdcache_param.reaper_work + LRU_N_Q_LANES - 1) / LRU_N_Q_LANES; } else { /* New parameter */ lru_state.per_lane_work = mdcache_param.reaper_work_per_lane; } /* Set high watermark for cache entries. */ lru_state.entries_hiwat = mdcache_param.entries_hwmark; lru_state.entries_used = 0; /* set lru release entries size */ lru_state.entries_release_size = mdcache_param.entries_release_size; /* Set high and low watermark for chunks. */ lru_state.chunks_hiwat = mdcache_param.chunks_hwmark; lru_state.chunks_lowat = mdcache_param.chunks_lwmark; lru_state.chunks_used = 0; /* init queue complex */ lru_init_queues(); /* spawn LRU background thread */ code = fridgethr_init(&lru_fridge, "LRU_fridge", &frp); if (code != 0) { LogMajor(COMPONENT_MDCACHE_LRU, "Unable to initialize LRU fridge, error code %d.", code); return fsalstat(posix2fsal_error(code), code); } code = fridgethr_submit(lru_fridge, lru_run, NULL); if (code != 0) { LogMajor(COMPONENT_MDCACHE_LRU, "Unable to start Entry LRU thread, error code %d.", code); return fsalstat(posix2fsal_error(code), code); } code = fridgethr_submit(lru_fridge, chunk_lru_run, NULL); if (code != 0) { LogMajor(COMPONENT_MDCACHE_LRU, "Unable to start Chunk LRU thread, error code %d.", code); return fsalstat(posix2fsal_error(code), code); } fd_lru_parameter.lru_run_interval = mdcache_param.lru_run_interval; fd_lru_parameter.Cache_FDs = mdcache_param.Cache_FDs; fd_lru_parameter.close_fast = mdcache_param.close_fast; fd_lru_parameter.fd_limit_percent = mdcache_param.fd_limit_percent; fd_lru_parameter.fd_hwmark_percent = mdcache_param.fd_hwmark_percent; fd_lru_parameter.fd_lwmark_percent = mdcache_param.fd_lwmark_percent; fd_lru_parameter.reaper_work = mdcache_param.reaper_work; fd_lru_parameter.reaper_work_per_lane = mdcache_param.reaper_work_per_lane; fd_lru_parameter.biggest_window = mdcache_param.biggest_window; fd_lru_parameter.required_progress = mdcache_param.required_progress; fd_lru_parameter.futility_count = mdcache_param.futility_count; fd_lru_parameter.fd_fallback_limit = FD_FALLBACK_LIMIT; status = fd_lru_pkginit(&fd_lru_parameter); return status; } /** * Shutdown subsystem * * @return 0 on success, POSIX errors on failure. */ fsal_status_t mdcache_lru_pkgshutdown(void) { fsal_status_t status; int rc = fridgethr_sync_command(lru_fridge, fridgethr_comm_stop, 120); if (rc == ETIMEDOUT) { LogMajor(COMPONENT_MDCACHE_LRU, "Shutdown timed out, cancelling threads."); fridgethr_cancel(lru_fridge); } else if (rc != 0) { LogMajor(COMPONENT_MDCACHE_LRU, "Failed shutting down LRU thread: %d", rc); } if (rc == 0) status = fd_lru_pkgshutdown(); else status = fsalstat(posix2fsal_error(rc), rc); lru_destroy_queues(); return status; } static inline void init_rw_locks(mdcache_entry_t *entry) { /* Initialize the entry locks */ PTHREAD_RWLOCK_init(&entry->attr_lock, NULL); PTHREAD_RWLOCK_init(&entry->content_lock, NULL); } mdcache_entry_t *alloc_cache_entry(void) { mdcache_entry_t *nentry; nentry = pool_alloc(mdcache_entry_pool); /* Initialize the entry locks */ init_rw_locks(nentry); (void)atomic_inc_int64_t(&lru_state.entries_used); return nentry; } /** * @brief Re-use or allocate an entry * * This function repurposes a resident entry in the LRU system if the system is * above the high-water mark, and allocates a new one otherwise. On success, * this function always returns an entry with two references (one for the * sentinel and an active one for the caller's use). * * The caller MUST call mdcache_lru_insert with the same flags when the entry is * sufficiently constructed. * * @param[in] sub_handle The underlying FSAL's fsal_obj_handle * @param[in] flags The flags for the caller's initial reference, MUST * include LRU_ACTIVE_REF * * @return a usable entry or NULL if unexport is in progress. */ mdcache_entry_t *mdcache_lru_get(struct fsal_obj_handle *sub_handle, uint32_t flags) { mdcache_lru_t *lru; mdcache_entry_t *nentry = NULL; assert(flags & LRU_ACTIVE_REF); lru = lru_try_reap_entry(LRU_TEMP_REF); if (lru) { /* we uniquely hold entry with a temp ref that we will * discard (with no negative consequence) below when we remake * the entry. */ nentry = container_of(lru, mdcache_entry_t, lru); mdcache_lru_clean(nentry); memset(&nentry->attrs, 0, sizeof(nentry->attrs)); init_rw_locks(nentry); } else { /* alloc entry (if fails, aborts) */ nentry = alloc_cache_entry(); } nentry->attr_generation = 0; /* Since the entry isn't in a queue, nobody can bump refcnt. Set both * the sentinel reference and the active reference. The caller is * responsible for inserting the entry into the LRU queue. */ nentry->lru.refcnt = 2; nentry->lru.active_refcnt = 1; nentry->lru.cf = 0; nentry->lru.lane = lru_lane_of(nentry); nentry->lru.flags = LRU_SENTINEL_HELD; nentry->sub_handle = sub_handle; if (flags & LRU_PROMOTE) { /* If entry is ever promoted, remember that. */ nentry->lru.flags |= LRU_EVER_PROMOTED; } GSH_AUTO_TRACEPOINT(mdcache, mdc_lru_get, TRACE_DEBUG, "lru unref. handle: {}, sub handle: {}, refcnt: {}", &nentry->obj_handle, sub_handle, nentry->lru.refcnt); return nentry; } /** * @brief Insert a new entry into the LRU in the ACTIVE queue. * * @param [in] entry Entry to insert. */ void mdcache_lru_insert_active(mdcache_entry_t *entry) { mdcache_lru_t *lru = &entry->lru; struct lru_q_lane *qlane = &LRU[lru->lane]; QLOCK(qlane); /* Enqueue. */ lru_insert(lru, &LRU[entry->lru.lane].ACTIVE); QUNLOCK(qlane); } /** * @brief Get a reference * * This function acquires a reference on the given cache entry. * * @param[in] entry The entry on which to get a reference * @param[in] flags One of LRU_PROMOTE, LRU_FLAG_NONE, or LRU_ACTIVE_REF * * A flags value of LRU_PROMOTE indicates an initial * reference. A non-initial reference is an "extra" reference in some call * path, hence does not influence LRU, and is lockless. * * A flags value of LRU_PROMOTE indicates an ordinary initial reference, * and strongly influences LRU. Essentially, the first ref during a callpath * should take an LRU_PROMOTE ref, and all subsequent callpaths should take * LRU_FLAG_NONE refs. */ void _mdcache_lru_ref(mdcache_entry_t *entry, uint32_t flags, const char *func, int line) { int32_t refcnt, active_refcnt = -999; /* Always take a normal reference so unref to 0 works right */ refcnt = atomic_inc_int32_t(&entry->lru.refcnt); if (flags & LRU_ACTIVE_REF) { /* Each active reference is in addition to a normal * reference. This allows the possibility of the final reference * to an entry being a active reference such that when that * active reference is dropped, cleanup will occur. */ active_refcnt = atomic_inc_int32_t(&entry->lru.active_refcnt); } GSH_UNIQUE_AUTO_TRACEPOINT( mdcache, mdc_lru_ref, TRACE_DEBUG, "lru ref. handle: {}, sub handle: {}, refcnt: {}, active_refcnt: {}", &entry->obj_handle, entry->sub_handle, refcnt, active_refcnt); if (flags & LRU_PROMOTE) { /* If entry is ever promoted, remember that. */ atomic_set_uint32_t_bits(&entry->lru.flags, LRU_EVER_PROMOTED); assert(flags & LRU_ACTIVE_REF); } if (flags & LRU_ACTIVE_REF) { /* Move into ACTIVE queue or adjust to MRU */ make_active_lru(entry); } } /** * @brief Relinquish a reference * * This function relinquishes a reference on the given cache entry. * It follows the disposal/recycling lock discipline given at the * beginning of the file. * * The supplied entry is always either unlocked or destroyed by the * time this function returns. * * @param[in] entry The entry on which to release a reference * @param[in] flags Currently significant are and LRU_FLAG_LOCKED * (indicating that the caller holds the LRU mutex * lock for this entry.) * NOTE: LRU_PROMOTE is ignored so safe to be set * @return true if entry freed, false otherwise */ bool _mdcache_lru_unref(mdcache_entry_t *entry, uint32_t flags, const char *func, int line) { bool do_cleanup = false; uint32_t lane = entry->lru.lane; struct lru_q_lane *qlane = &LRU[lane]; bool other_lock_held = entry->fsobj.hdl.no_cleanup; bool freed = false; if (!other_lock_held) { /* pre-check about qid to avoid LOCK every time */ if (entry->lru.qid == LRU_ENTRY_CLEANUP) { QLOCK(qlane); /* Locked, check again with lock */ if (((atomic_fetch_uint32_t(&entry->lru.flags) & LRU_CLEANED) == 0) && (entry->lru.qid == LRU_ENTRY_CLEANUP)) { do_cleanup = true; atomic_set_uint32_t_bits(&entry->lru.flags, LRU_CLEANED); } QUNLOCK(qlane); } if (do_cleanup) { LogDebug(COMPONENT_MDCACHE, "LRU_ENTRY_CLEANUP of entry %p", entry); state_wipe_file(&entry->obj_handle); } } if (flags & LRU_FLAG_SENTINEL) { /* Caller is intending to release the sentinel reference */ if ((atomic_fetch_uint32_t(&entry->lru.flags) & LRU_SENTINEL_HELD) == 0) { /* oops... */ LogFatal(COMPONENT_MDCACHE, "Sentinel reference already released"); } atomic_clear_uint32_t_bits(&entry->lru.flags, LRU_SENTINEL_HELD); } const int32_t refcnt = atomic_fetch_int32_t(&entry->lru.refcnt); const int32_t active_refcnt = atomic_fetch_int32_t(&entry->lru.active_refcnt); GSH_AUTO_TRACEPOINT( mdcache, mdc_lru_ref, TRACE_DEBUG, "lru unref. handle: {}, sub handle: {}, refcnt: {}, active_refcnt: {}", &entry->obj_handle, entry->sub_handle, refcnt, active_refcnt); /* Each active reference is in addition to a normal reference. This * allows the possibility of the final reference to an entry being an * active reference such that when that active reference is dropped, * cleanup will occur. */ /* Handle active unref first */ if (flags & LRU_ACTIVE_REF && PTHREAD_MUTEX_dec_int32_t_and_lock(&entry->lru.active_refcnt, &qlane->ql_mtx)) { /* active_refcnt is zero and we hold the QLOCK. */ #if 0 /* For clarity... */ QLOCK(qlane); #endif /* Move entry to MRU of L1 or L2 or leave in cleanup queue. */ make_inactive_lru(entry); QUNLOCK(qlane); } /* Handle normal unref next for all unrefs. */ if (PTHREAD_MUTEX_dec_int32_t_and_lock(&entry->lru.refcnt, &qlane->ql_mtx)) { struct lru_q *q; /* refcnt is zero and we hold the QLOCK. */ #if 0 /* For clarity... */ QLOCK(qlane); #endif /* * The cih table holds a non-weak reference, so entry should no * longer be in it. */ assert(!entry->fh_hk.inavl); /* Remove entry and mark it as dead. */ q = lru_queue_of(entry); if (q) { /* as of now, entries leaving the cleanup queue * are LRU_ENTRY_NONE */ LRU_DQ(&entry->lru, q); } QUNLOCK(qlane); mdcache_lru_clean(entry); pool_free(mdcache_entry_pool, entry); freed = true; (void)atomic_dec_int64_t(&lru_state.entries_used); } return freed; } /** * @brief Remove a chunk from LRU, and clean it * * QLane must be locked * * @param[in] chunk The chunk to be removed from LRU */ static void lru_clean_chunk(struct dir_chunk *chunk) { struct lru_q *lq; LogFullDebug(COMPONENT_MDCACHE, "Removing chunk %p", chunk); /* Remove chunk and mark it as dead. */ lq = chunk_lru_queue_of(chunk); if (lq) { /* dequeue the chunk */ CHUNK_LRU_DQ(&chunk->chunk_lru, lq); } (void)atomic_dec_int64_t(&lru_state.chunks_used); /* Then do the actual cleaning work. */ mdcache_clean_dirent_chunk(chunk); } void _mdcache_lru_ref_chunk(struct dir_chunk *chunk, const char *func, int line) { atomic_inc_int32_t(&chunk->chunk_lru.refcnt); } /** * @brief Unref a dirent chunk * Should be called with content_lock held in write mode. * @param [in] chunk The chunk to unref */ void _mdcache_lru_unref_chunk(struct dir_chunk *chunk, const char *func, int line) { int refcnt; uint32_t lane; struct lru_q_lane *qlane; if (!chunk) return; lane = chunk->chunk_lru.lane; qlane = &CHUNK_LRU[lane]; QLOCK(qlane); refcnt = atomic_dec_int32_t(&chunk->chunk_lru.refcnt); assert(refcnt >= 0); if (refcnt == 0) { lru_clean_chunk(chunk); /* And now we can free the chunk. */ LogFullDebug(COMPONENT_MDCACHE, "Freeing chunk %p", chunk); gsh_free(chunk); } QUNLOCK(qlane); } /** * @brief Indicate that a chunk is being used, bump it up in the LRU * */ void lru_bump_chunk(struct dir_chunk *chunk) { mdcache_lru_t *lru = &chunk->chunk_lru; struct lru_q_lane *qlane = &CHUNK_LRU[lru->lane]; struct lru_q *q; QLOCK(qlane); q = chunk_lru_queue_of(chunk); switch (lru->qid) { case LRU_ENTRY_L1: /* advance chunk to MRU (of L1) */ CHUNK_LRU_DQ(lru, q); lru_insert(lru, q); break; case LRU_ENTRY_L2: /* move chunk to MRU of L1 */ CHUNK_LRU_DQ(lru, q); q = &qlane->L1; lru_insert(lru, q); break; default: /* do nothing */ break; } QUNLOCK(qlane); } static inline void mdc_lru_dirmap_add(struct mdcache_fsal_export *exp, mdcache_dmap_entry_t *dmap) { avltree_insert(&dmap->node, &exp->dirent_map.map); /* MRU is the tail; Mdd to MRU of list */ glist_add_tail(&exp->dirent_map.lru, &dmap->lru_entry); exp->dirent_map.count++; } static inline void mdc_lru_dirmap_del(struct mdcache_fsal_export *exp, mdcache_dmap_entry_t *dmap) { glist_del(&dmap->lru_entry); avltree_remove(&dmap->node, &exp->dirent_map.map); exp->dirent_map.count--; } /** * @brief Add a dirent to the dirmap * * Add this dirent to the dirmap. The dirmap is a mapping of cookies to names * that allows whence-is-name to restart where it left off if the chunk was * reaped, instead of reloading the whole directory to find the cookie. * * @param[in] dirent Dirent to add */ void mdc_lru_map_dirent(mdcache_dir_entry_t *dirent) { struct mdcache_fsal_export *exp = mdc_cur_export(); mdcache_dmap_entry_t key, *dmap; struct avltree_node *node; PTHREAD_MUTEX_lock(&exp->dirent_map.dm_mtx); key.ck = dirent->ck; node = avltree_lookup(&key.node, &exp->dirent_map.map); if (node) { LogFullDebug(COMPONENT_NFS_READDIR, "Already map for %s -> %" PRIx64, dirent->name, dirent->ck); /* Move to MRU */ dmap = avltree_container_of(node, mdcache_dmap_entry_t, node); now(&dmap->timestamp); glist_move_tail(&exp->dirent_map.lru, &dmap->lru_entry); PTHREAD_MUTEX_unlock(&exp->dirent_map.dm_mtx); return; } if (exp->dirent_map.count > mdcache_param.dirmap_hwmark) { /* LRU end is the head; grab the LRU entry */ dmap = glist_first_entry(&exp->dirent_map.lru, mdcache_dmap_entry_t, lru_entry); mdc_lru_dirmap_del(exp, dmap); /* Free name */ gsh_free(dmap->name); } else { dmap = gsh_malloc(sizeof(*dmap)); } dmap->ck = dirent->ck; dmap->name = gsh_strdup(dirent->name); now(&dmap->timestamp); LogFullDebug(COMPONENT_NFS_READDIR, "Mapping %s -> %" PRIx64 " %p:%d", dmap->name, dmap->ck, exp, exp->dirent_map.count); mdc_lru_dirmap_add(exp, dmap); PTHREAD_MUTEX_unlock(&exp->dirent_map.dm_mtx); } /** * @brief Look up and remove an entry from the dirmap * * This looks up the cookie in the dirmap, and returns the associated name, if * it's in the cache. The entry is removed from the cache and freed, and the * name is returned. * * @note the returned name must be freed by the caller * * @param[in] ck Cookie to look up * @return Name, if found, or NULL otherwise */ fsal_cookie_t *mdc_lru_unmap_dirent(uint64_t ck) { struct mdcache_fsal_export *exp = mdc_cur_export(); struct avltree_node *node; mdcache_dmap_entry_t key, *dmap; char *name; PTHREAD_MUTEX_lock(&exp->dirent_map.dm_mtx); key.ck = ck; node = avltree_lookup(&key.node, &exp->dirent_map.map); if (!node) { LogFullDebug(COMPONENT_NFS_READDIR, "No map for %" PRIx64, ck); PTHREAD_MUTEX_unlock(&exp->dirent_map.dm_mtx); return NULL; } dmap = avltree_container_of(node, mdcache_dmap_entry_t, node); mdc_lru_dirmap_del(exp, dmap); PTHREAD_MUTEX_unlock(&exp->dirent_map.dm_mtx); name = dmap->name; LogFullDebug(COMPONENT_NFS_READDIR, "Unmapping %s -> %" PRIx64, dmap->name, dmap->ck); /* Don't free name, we're passing it back to the caller */ gsh_free(dmap); return (fsal_cookie_t *)name; } #define DIRMAP_MAX_PER_SCAN 1000 #define DIRMAP_KEEP_NS (60 * NS_PER_SEC) static void dirmap_lru_run(struct fridgethr_context *ctx) { struct mdcache_fsal_export *exp = ctx->arg; mdcache_dmap_entry_t *cur, *next; int i; struct timespec curtime; nsecs_elapsed_t age; static bool first_time = true; /* XXX dang this needs to be here or this will hijack another thread, * causing that one to never run again. */ if (first_time) { /* Wait for NFS server to properly initialize */ nfs_init_wait(); first_time = false; } PTHREAD_MUTEX_lock(&exp->dirent_map.dm_mtx); now(&curtime); cur = glist_last_entry(&exp->dirent_map.lru, mdcache_dmap_entry_t, lru_entry); for (i = 0; i < DIRMAP_MAX_PER_SCAN && cur != NULL; ++i) { next = glist_prev_entry(&exp->dirent_map.lru, mdcache_dmap_entry_t, lru_entry, &cur->lru_entry); age = timespec_diff(&cur->timestamp, &curtime); if (age < DIRMAP_KEEP_NS) { /* LRU is in timestamp order; done */ goto out; } mdc_lru_dirmap_del(exp, cur); gsh_free(cur->name); gsh_free(cur); cur = next; } out: PTHREAD_MUTEX_unlock(&exp->dirent_map.dm_mtx); fridgethr_setwait(ctx, mdcache_param.lru_run_interval); } fsal_status_t dirmap_lru_init(struct mdcache_fsal_export *exp) { struct fridgethr_params frp; int rc; if (!exp->mfe_exp.exp_ops.fs_supports(&exp->mfe_exp, fso_whence_is_name)) { LogDebug(COMPONENT_NFS_READDIR, "Skipping dirmap %s", exp->name); return fsalstat(0, 0); } avltree_init(&exp->dirent_map.map, avl_dmap_ck_cmpf, 0 /* flags */); glist_init(&exp->dirent_map.lru); PTHREAD_MUTEX_init(&exp->dirent_map.dm_mtx, NULL); memset(&frp, 0, sizeof(struct fridgethr_params)); frp.thr_max = 1; frp.thr_min = 1; frp.thread_delay = mdcache_param.lru_run_interval; frp.flavor = fridgethr_flavor_looper; rc = fridgethr_init(&exp->dirmap_fridge, exp->name, &frp); if (rc != 0) { LogMajor( COMPONENT_NFS_READDIR, "Unable to initialize %s dirmap fridge, error code %d.", exp->name, rc); return posix2fsal_status(rc); } rc = fridgethr_submit(exp->dirmap_fridge, dirmap_lru_run, exp); if (rc != 0) { LogMajor(COMPONENT_NFS_READDIR, "Unable to start %s dirmap thread, error code %d.", exp->name, rc); return posix2fsal_status(rc); } LogDebug(COMPONENT_NFS_READDIR, "started dirmap %s", exp->name); return fsalstat(0, 0); } void dirmap_lru_stop(struct mdcache_fsal_export *exp) { if (!exp->dirmap_fridge) { /* Wasn't running */ return; } int rc = fridgethr_sync_command(exp->dirmap_fridge, fridgethr_comm_stop, 10); if (rc == ETIMEDOUT) { LogDebug(COMPONENT_NFS_READDIR, "Shutdown timed out, cancelling threads."); fridgethr_cancel(exp->dirmap_fridge); } else if (rc != 0) { LogMajor(COMPONENT_NFS_READDIR, "Failed shutting down LRU thread: %d", rc); } fridgethr_destroy(exp->dirmap_fridge); LogDebug(COMPONENT_NFS_READDIR, "stopped dirmap %s", exp->name); } /** @} */ nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_lru.h000066400000000000000000000130331473756622300242420ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2012, The Linux Box Corporation * Contributor: Matt Benjamin * * Some portions Copyright CEA/DAM/DIF (2008) * contributeur: Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @addtogroup FSAL_MDCACHE * @{ */ #ifndef MDCACHE_LRU_H #define MDCACHE_LRU_H #include "config.h" #include "log.h" #include "mdcache_int.h" /** * @file mdcache_lru.h * @author Matt Benjamin * @brief Constant-time MDCACHE cache management implementation * * @section DESCRIPTION * * This module implements a constant-time cache management strategy * based on LRU. Some ideas are taken from 2Q [Johnson and Shasha 1994] * and MQ [Zhou, Chen, Li 2004]. In this system, cache management does * interact with cache entry lifecycle. Also, the cache size high- and * low- water mark management is maintained, but executes asynchronously * to avoid inline request delay. Cache management operations execute in * constant time, as expected with LRU (and MQ). * * Cache entries in use by a currently-active protocol request (or other * operation) have a positive refcount, and threfore should not be present * at the cold end of an lru queue if the cache is well-sized. * * Cache entries with lock and open state are not eligible for collection * under ordinary circumstances, so are kept on a separate lru_pinned * list to retain constant time. * */ struct lru_state { uint64_t entries_hiwat; uint64_t entries_used; uint32_t entries_release_size; uint64_t chunks_hiwat; uint64_t chunks_lowat; uint64_t chunks_used; uint32_t per_lane_work; time_t prev_time; /* previous time the gc thread was run. */ }; extern struct lru_state lru_state; /** Cache entries pool */ extern pool_t *mdcache_entry_pool; /** * Reference type Flags for functions in the LRU package */ #define LRU_ACTIVE_REF 0x0004 #define LRU_PROMOTE 0x0008 #define LRU_FLAG_SENTINEL 0x0001 #define LRU_TEMP_REF 0x0002 /** * The minimum reference count for a cache entry not being recycled. */ #define LRU_SENTINEL_REFCOUNT 1 /** * The number of lanes comprising a logical queue. This must be * prime. */ #define LRU_N_Q_LANES 17 fsal_status_t mdcache_lru_pkginit(void); fsal_status_t mdcache_lru_pkgshutdown(void); mdcache_entry_t *mdcache_lru_get(struct fsal_obj_handle *sub_handle, uint32_t flags); void mdcache_lru_insert_active(mdcache_entry_t *entry); #define mdcache_lru_ref(e, f) _mdcache_lru_ref(e, f, __func__, __LINE__) /** * * @brief Get a logical reference to a cache entry * * @param[in] entry Cache entry being returned * @param[in] flags Set of flags to specify type of reference */ void _mdcache_lru_ref(mdcache_entry_t *entry, uint32_t flags, const char *func, int line); /* XXX */ void mdcache_lru_kill(mdcache_entry_t *entry); void mdcache_lru_cleanup_push(mdcache_entry_t *entry); void mdcache_lru_cleanup_try_push(mdcache_entry_t *entry); size_t mdcache_lru_release_entries(int32_t want_release); #define mdcache_lru_unref(e, f) _mdcache_lru_unref(e, f, __func__, __LINE__) /** * * @brief Release logical reference to a cache entry * * This function releases a logical reference to a cache entry * acquired by a previous mdcache handle op (such as lookup, create, etc.) * * The result is typically to decrement the reference count on entry, * but additional side effects include LRU adjustment, movement * to/from the protected LRU partition, or recycling if the caller has * raced an operation which made entry unreachable (and this current * caller has the last reference). Caller MUST NOT make further * accesses to the memory pointed to by entry. * * @param[in] entry Cache entry being returned * @param[in] flags Flag to indicate what kind of reference is to be released */ bool _mdcache_lru_unref(mdcache_entry_t *entry, uint32_t flags, const char *func, int line); void mdcache_lru_kill_for_shutdown(mdcache_entry_t *entry); #define mdcache_lru_ref_chunk(chunk) \ _mdcache_lru_ref_chunk(chunk, __func__, __LINE__) void _mdcache_lru_ref_chunk(struct dir_chunk *chunk, const char *func, int line); #define mdcache_lru_unref_chunk(chunk) \ _mdcache_lru_unref_chunk(chunk, __func__, __LINE__) void _mdcache_lru_unref_chunk(struct dir_chunk *chunk, const char *func, int line); struct dir_chunk *mdcache_get_chunk(mdcache_entry_t *parent, struct dir_chunk *prev_chunk, fsal_cookie_t whence); void lru_bump_chunk(struct dir_chunk *chunk); void mdc_lru_map_dirent(mdcache_dir_entry_t *dirent); fsal_cookie_t *mdc_lru_unmap_dirent(uint64_t ck); fsal_status_t dirmap_lru_init(struct mdcache_fsal_export *exp); void dirmap_lru_stop(struct mdcache_fsal_export *exp); #endif /* MDCACHE_LRU_H */ /** @} */ nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_main.c000066400000000000000000000330351473756622300243630ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2015-2019 Red Hat, Inc. and/or its affiliates. * Author: Daniel Gryniewicz * * This program 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 3 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 * 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 * */ /** * @addtogroup FSAL_MDCACHE * @{ */ /** * @file main.c * @brief FSAL entry functions */ #include "config.h" #include "fsal.h" #include /* used for 'dirname' */ #include #include #include #include #include "gsh_list.h" #ifdef USE_DBUS #include "gsh_dbus.h" #endif #include "FSAL/fsal_init.h" #include "FSAL/fsal_commonlib.h" #include "mdcache_hash.h" #include "mdcache_lru.h" pool_t *mdcache_entry_pool; struct mdcache_stats cache_st; struct mdcache_stats *cache_stp = &cache_st; /* FSAL name determines name of shared library: libfsal.so */ static const char mdcachename[] = "MDCACHE"; /* my module private storage */ struct mdcache_fsal_module MDCACHE = { .module = { .fs_info = { .maxfilesize = UINT64_MAX, .maxlink = _POSIX_LINK_MAX, .maxnamelen = 1024, .maxpathlen = 1024, .no_trunc = true, .chown_restricted = true, .case_insensitive = false, .case_preserving = true, .link_support = true, .symlink_support = true, .lock_support = true, .lock_support_async_block = false, .named_attr = true, .unique_handles = true, .acl_support = FSAL_ACLSUPPORT_ALLOW, .cansettime = true, .homogenous = true, .supported_attrs = ALL_ATTRIBUTES, .maxread = FSAL_MAXIOSIZE, .maxwrite = FSAL_MAXIOSIZE, .umask = 0, .auth_exportpath_xdev = false, .link_supports_permission_checks = true, .expire_time_parent = -1, } } }; /* private helper for export object */ /* Module methods */ /* mdcache_fsal_init_config * must be called with a reference taken (via lookup_fsal) */ static fsal_status_t mdcache_fsal_init_config(struct fsal_module *mdcache_fsal_module, config_file_t config_struct, struct config_error_type *err_type) { /* Configuration setting options: * 1. there are none that are changeable. (this case) * * 2. we set some here. These must be independent of whatever * may be set by lower level fsals. * * If there is any filtering or change of parameters in the stack, * this must be done in export data structures, not fsal params because * a stackable could be configured above multiple fsals for multiple * diverse exports. */ display_fsinfo(mdcache_fsal_module); LogDebug(COMPONENT_FSAL, "FSAL INIT: Supported attributes mask = 0x%" PRIx64, mdcache_fsal_module->fs_info.supported_attrs); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Clean up caching for a FSAL export on error * * If init has an error after @ref mdcache_export_init is called, this should be * called to clean up any MDCACHE state on the export. This is only intended to * be called on startup error. * */ void mdcache_export_uninit(void) { struct mdcache_fsal_export *exp = mdc_cur_export(); struct fsal_export *sub_export = exp->mfe_exp.sub_export; fsal_put(sub_export->fsal); LogFullDebug(COMPONENT_FSAL, "FSAL %s fsal_refcount %" PRIu32, sub_export->fsal->name, atomic_fetch_int32_t(&sub_export->fsal->refcount)); fsal_detach_export(op_ctx->fsal_export->fsal, &op_ctx->fsal_export->exports); free_export_ops(op_ctx->fsal_export); up_ready_destroy(&exp->up_ops); gsh_free(exp); /* Put back sub export */ op_ctx->fsal_export = sub_export; op_ctx->fsal_module = sub_export->fsal; } /** * @brief Create an export for MDCACHE * * Create the stacked export for MDCACHE to allow metadata caching on another * export. Unlike other Stackable FSALs, this one is created @b after the FSAL * underneath. It assumes the sub-FSAL's export is already created and * available via the @e fsal_export member of @link op_ctx @endlink, the same * way that this export is returned. * * There is currently no config; * * @param[in] sub_fsal Sub-FSAL module handle * @param[in] parse_node Config node for export * @param[out] err_type Parse errors * @param[in] super_up_ops Upcall ops for export * @return FSAL status */ fsal_status_t mdcache_fsal_create_export(struct fsal_module *sub_fsal, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *super_up_ops) { fsal_status_t status = { 0, 0 }; struct mdcache_fsal_export *myself; myself = gsh_calloc(1, sizeof(struct mdcache_fsal_export)); myself->name = gsh_concat(sub_fsal->name, "/MDC"); fsal_export_init(&myself->mfe_exp); mdcache_export_ops_init(&myself->mfe_exp.exp_ops); myself->super_up_ops = *super_up_ops; /* Struct copy */ mdcache_export_up_ops_init(&myself->up_ops, super_up_ops); myself->up_ops.up_gsh_export = op_ctx->ctx_export; myself->up_ops.up_fsal_export = &myself->mfe_exp; myself->mfe_exp.up_ops = &myself->up_ops; myself->mfe_exp.fsal = &MDCACHE.module; glist_init(&myself->entry_list); PTHREAD_MUTEX_init(&myself->mdc_exp_lock, NULL); PTHREAD_MUTEX_init(&myself->dirent_map.dm_mtx, NULL); status = sub_fsal->m_ops.create_export(sub_fsal, parse_node, err_type, &myself->up_ops); if (FSAL_IS_ERROR(status)) { LogMajor(COMPONENT_FSAL, "Failed to call create_export on underlying FSAL %s", sub_fsal->name); gsh_free(myself->name); gsh_free(myself); return status; } /* Get ref for sub-FSAL */ fsal_get(myself->mfe_exp.fsal); LogFullDebug(COMPONENT_FSAL, "FSAL %s fsal_refcount %" PRIu32, myself->mfe_exp.fsal->name, atomic_fetch_int32_t(&myself->mfe_exp.fsal->refcount)); fsal_export_stack(op_ctx->fsal_export, &myself->mfe_exp); status = dirmap_lru_init(myself); if (FSAL_IS_ERROR(status)) { LogMajor(COMPONENT_FSAL, "Failed to call dirmap_lru_init"); gsh_free(myself->name); gsh_free(myself); return status; } /* Set up op_ctx */ op_ctx->fsal_export = &myself->mfe_exp; op_ctx->fsal_module = &MDCACHE.module; /* Stacking is setup and ready to take upcalls now */ up_ready_set(&myself->up_ops); return status; } /** * @brief Update an export for MDCACHE * * Create the stacked export for MDCACHE to allow metadata caching on another * export. Unlike other Stackable FSALs, this one is created @b after the FSAL * underneath. It assumes the sub-FSAL's export is already created and * available via the @e fsal_export member of @link op_ctx @endlink, the same * way that this export is returned. * * There is currently no config; * * @param[in] sub_fsal Sub-FSAL module handle * @param[in] parse_node Config node for export * @param[out] err_type Parse errors * @param[in] existing_export The existing export that is being updated * @return FSAL status */ fsal_status_t mdcache_fsal_update_export(struct fsal_module *sub_fsal, void *parse_node, struct config_error_type *err_type, struct fsal_export *original) { fsal_status_t status = { 0, 0 }; #if 0 /* We currently don't actually have any MDCACHE EXPORT FSAL parameters * so we don't need an mdcache_fsal_export to fill in. */ struct mdcache_fsal_export myself; memset(&myself, 0, sizeof(myself)); /* Here's where we would parse the FSAL block to get our params and * then validate them. */ #endif /* Now update the sub-fsal */ status = sub_fsal->m_ops.update_export(sub_fsal, parse_node, err_type, original->sub_export, &MDCACHE.module); if (FSAL_IS_ERROR(status)) { LogMajor(COMPONENT_FSAL, "Failed to call update_export on underlying FSAL %s", sub_fsal->name); } else { /* And here's where we would actually update the parameters. */ } /* We don't do any of the stuff mdcache_fsal_create_export does after * calling the sub_fsal's update_export because we aren't actually * creating a fsal_export stack. */ return status; } /* Module initialization. * Called by dlopen() to register the module * keep a private pointer to me in myself */ /* linkage to the exports and handle ops initializers */ static int mdcache_fsal_unload(struct fsal_module *fsal_hdl) { fsal_status_t status; int retval; /* Destroy the MDCACHE AVL tree */ cih_pkgdestroy(); status = mdcache_lru_pkgshutdown(); if (FSAL_IS_ERROR(status)) fprintf(stderr, "MDCACHE LRU failed to shut down"); /* Destroy the MDCACHE entry pool */ pool_destroy(mdcache_entry_pool); mdcache_entry_pool = NULL; retval = unregister_fsal(&MDCACHE.module); if (retval != 0) fprintf(stderr, "MDCACHE module failed to unregister"); if (FSAL_IS_ERROR(status)) return status.major; return retval; } void mdcache_fsal_init(void) { int retval; struct fsal_module *myself = &MDCACHE.module; retval = register_fsal(myself, mdcachename, FSAL_MAJOR_VERSION, FSAL_MINOR_VERSION, FSAL_ID_NO_PNFS); if (retval != 0) { fprintf(stderr, "MDCACHE module failed to register"); return; } /*myself->m_ops.create_export = mdcache_fsal_create_export;*/ myself->m_ops.init_config = mdcache_fsal_init_config; myself->m_ops.unload = mdcache_fsal_unload; /* Initialize the fsal_obj_handle ops for FSAL MDCACHE */ mdcache_handle_ops_init(&MDCACHE.handle_ops); } /** * @brief Initialize the MDCACHE package. * * This should be called once at startup, after parsing config * * @return FSAL status */ fsal_status_t mdcache_pkginit(void) { fsal_status_t status; if (mdcache_entry_pool) return fsalstat(ERR_FSAL_NO_ERROR, 0); mdcache_entry_pool = pool_basic_init("MDCACHE Entry Pool", sizeof(mdcache_entry_t)); status = mdcache_lru_pkginit(); if (FSAL_IS_ERROR(status)) { pool_destroy(mdcache_entry_pool); mdcache_entry_pool = NULL; return status; } cih_pkginit(); return status; } #ifdef USE_DBUS void mdcache_dbus_show(DBusMessageIter *iter) { DBusMessageIter struct_iter; char *type; dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); type = " Cache Requests: "; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &cache_st.inode_req); type = " Cache Hits: "; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &cache_st.inode_hit); type = " Cache Misses: "; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &cache_st.inode_miss); type = " Cache Conflicts: "; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &cache_st.inode_conf); type = " Cache Adds: "; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &cache_st.inode_added); type = " Cache Mapping: "; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &cache_st.inode_mapping); dbus_message_iter_close_container(iter, &struct_iter); } /* lru data utilization */ void mdcache_utilization(DBusMessageIter *iter) { DBusMessageIter struct_iter; char *type; uint64_t entries_used, chunks_used; uint32_t fd_limit, fd_state; size_t open_fds; dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); /* Gather the data */ open_fds = atomic_fetch_int32_t(&fsal_fd_global_counter); entries_used = atomic_fetch_uint64_t(&lru_state.entries_used); chunks_used = atomic_fetch_uint64_t(&lru_state.chunks_used); fd_state = atomic_fetch_uint32_t(&fd_lru_state.fd_state); fd_limit = atomic_fetch_uint32_t(&fd_lru_state.fds_system_imposed); type = " FSAL opened FD count : "; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &open_fds); type = " System limit on FDs : "; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &fd_limit); type = " FD usage : "; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); switch (fd_state) { case FD_LOW: type = " Below Low Water Mark "; break; case FD_MIDDLE: type = " Below High Water Mark "; break; case FD_HIGH: type = " Above High Water Mark "; break; case FD_LIMIT: type = " Hard Limit reached "; break; } dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); type = " LRU entries in use : "; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &entries_used); type = " Chunks in use : "; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &chunks_used); dbus_message_iter_close_container(iter, &struct_iter); } #endif /* USE_DBUS */ /** @} */ nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_read_conf.c000066400000000000000000000132341473756622300253560ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @addtogroup mdcache * @{ */ /** * @file mdcache_read_conf.c * @brief MDCACHE configuration parameter tables. */ #include "config.h" #include "log.h" #include "hashtable.h" #include "fsal.h" #include "mdcache_int.h" #include "config_parsing.h" #include #include #include #include #include #include #include /** File cache configuration, settable in the CacheInode/MDCACHE stanza. */ struct mdcache_parameter mdcache_param; static struct config_item mdcache_params[] = { CONF_ITEM_UI32("NParts", 1, 32633, 7, mdcache_parameter, nparts), CONF_ITEM_UI32("Cache_Size", 1, UINT32_MAX, 32633, mdcache_parameter, cache_size), CONF_ITEM_BOOL("Use_Getattr_Directory_Invalidation", false, mdcache_parameter, getattr_dir_invalidation), CONF_ITEM_UI32("Dir_Chunk", 0, UINT32_MAX, 128, mdcache_parameter, dir.avl_chunk), CONF_ITEM_UI32("Detached_Mult", 1, UINT32_MAX, 1, mdcache_parameter, dir.avl_detached_mult), CONF_ITEM_UI32("Entries_HWMark", 1, UINT32_MAX, 100000, mdcache_parameter, entries_hwmark), CONF_ITEM_UI32("Entries_Release_Size", 0, UINT32_MAX, 100, mdcache_parameter, entries_release_size), CONF_ITEM_UI32("Chunks_HWMark", 1, UINT32_MAX, 1000, mdcache_parameter, chunks_hwmark), CONF_ITEM_UI32("Chunks_LWMark", 1, UINT32_MAX, 1000, mdcache_parameter, chunks_lwmark), CONF_ITEM_UI32("LRU_Run_Interval", 1, 24 * 3600, 90, mdcache_parameter, lru_run_interval), CONF_ITEM_BOOL("Cache_FDs", true, mdcache_parameter, Cache_FDs), CONF_ITEM_BOOL("Close_Fast", false, mdcache_parameter, close_fast), CONF_ITEM_UI32("FD_Limit_Percent", 0, 100, 99, mdcache_parameter, fd_limit_percent), CONF_ITEM_UI32("FD_HWMark_Percent", 0, 100, 90, mdcache_parameter, fd_hwmark_percent), CONF_ITEM_UI32("FD_LWMark_Percent", 0, 100, 50, mdcache_parameter, fd_lwmark_percent), CONF_ITEM_UI32("Reaper_Work", 1, 2000, 0, mdcache_parameter, reaper_work), CONF_ITEM_UI32("Reaper_Work_Per_Lane", 1, UINT32_MAX, 50, mdcache_parameter, reaper_work_per_lane), CONF_ITEM_UI32("Biggest_Window", 1, 100, 40, mdcache_parameter, biggest_window), CONF_ITEM_UI32("Required_Progress", 1, 50, 5, mdcache_parameter, required_progress), CONF_ITEM_UI32("Futility_Count", 1, 50, 8, mdcache_parameter, futility_count), CONF_ITEM_UI32("Dirmap_HWMark", 1, UINT32_MAX, 10000, mdcache_parameter, dirmap_hwmark), CONF_ITEM_I32("Files_Delegatable_Percent", 10, INT32_MAX, 90, mdcache_parameter, files_delegatable_percent), CONF_ITEM_BOOL("Use_Cached_Owner_On_Owner_Override", true, mdcache_parameter, use_cached_owner_on_owner_override), CONFIG_EOL }; static void *mdcache_param_init(void *link_mem, void *self_struct) { if (self_struct == NULL) return &mdcache_param; else return NULL; } static int mdcache_param_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { int errcnt = 0; struct mdcache_parameter *param = self_struct; if (param->chunks_lwmark > param->chunks_hwmark) { /* The chunks low water mark must be less than the high water * mark. */ LogCrit(COMPONENT_CONFIG, "Chunks_LWMark (%" PRIi32 ") is larger than Chunks_HWMark (%" PRIi32 ")", param->chunks_lwmark, param->chunks_hwmark); err_type->invalid = true; errcnt++; } return errcnt; } struct config_block mdcache_param_blk = { .dbus_interface_name = "org.ganesha.nfsd.config.mdcache", .blk_desc.name = "MDCACHE", .blk_desc.altname = "CacheInode", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = mdcache_param_init, .blk_desc.u.blk.params = mdcache_params, .blk_desc.u.blk.commit = mdcache_param_commit }; int mdcache_set_param_from_conf(config_file_t parse_tree, struct config_error_type *err_type) { (void)load_config_from_parse(parse_tree, &mdcache_param_blk, NULL, true, err_type); if (!config_error_is_harmless(err_type)) { LogCrit(COMPONENT_INIT, "Error while parsing MDCACHE specific configuration"); return -1; } /* Compute avl_chunk_split after reading config, make sure it's a * multiple of two. */ mdcache_param.dir.avl_chunk_split = ((mdcache_param.dir.avl_chunk * 3) / 2) & (UINT32_MAX - 1); /* Compute avl_detached_max from avl_chunk and avl_detached_mult */ mdcache_param.dir.avl_detached_max = mdcache_param.dir.avl_chunk * mdcache_param.dir.avl_detached_mult; g_max_files_delegatable = (mdcache_param.files_delegatable_percent * mdcache_param.entries_hwmark) / 100; return 0; } /** @} */ nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_up.c000066400000000000000000000411371473756622300240650ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2015-2019 Red Hat, Inc. and/or its affiliates. * Author: Daniel Gryniewicz * * This program 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 3 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 * 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 * */ /** * @addtogroup FSAL_MDCACHE * @{ */ /** * @file mdcache_helpers.c * @brief Miscellaneous helper functions */ #include "config.h" #include "fsal.h" #include "nfs4_acls.h" #include "mdcache_hash.h" #include "mdcache_int.h" #include "nfs_core.h" #include "nfs4_fs_locations.h" static fsal_status_t mdc_up_invalidate(const struct fsal_up_vector *vec, struct gsh_buffdesc *handle, uint32_t flags) { mdcache_entry_t *entry; fsal_status_t status; struct req_op_context op_context; mdcache_key_t key; /* Get a ref to the vec->up_gsh_export and initialize op_context for the * upcall */ get_gsh_export_ref(vec->up_gsh_export); init_op_context_simple(&op_context, vec->up_gsh_export, vec->up_fsal_export); key.fsal = vec->up_fsal_export->sub_export->fsal; cih_hash_key(&key, vec->up_fsal_export->sub_export->fsal, handle, CIH_HASH_KEY_PROTOTYPE); status = mdcache_find_keyed_reason(&key, &entry, LRU_ACTIVE_REF | LRU_PROMOTE); if (status.major == ERR_FSAL_NOENT) { /* Not cached, so invalidate is a success */ status = fsalstat(ERR_FSAL_NO_ERROR, 0); goto out; } else if (FSAL_IS_ERROR(status)) { /* Real error */ goto out; } atomic_clear_uint32_t_bits(&entry->mde_flags, flags & FSAL_UP_INVALIDATE_CACHE); if (flags & FSAL_UP_INVALIDATE_CLOSE) status = fsal_close(&entry->obj_handle); if (flags & FSAL_UP_INVALIDATE_PARENT && entry->obj_handle.type == DIRECTORY) { PTHREAD_RWLOCK_wrlock(&entry->content_lock); /* Clean up parent key */ mdcache_free_fh(&entry->fsobj.fsdir.parent); PTHREAD_RWLOCK_unlock(&entry->content_lock); } mdcache_lru_unref(entry, LRU_ACTIVE_REF); out: release_op_context(); return status; } /** Release a cache entry if it's otherwise idle. * * @param[in] vec Up ops vector * @param[in] handle Handle-key that should be vetted and possibly removed * @param[in] flags Unused, for future expansion * * @return FSAL status. (ERR_FSAL_NO_ERROR indicates that one was released) */ static fsal_status_t mdc_up_try_release(const struct fsal_up_vector *vec, struct gsh_buffdesc *handle, uint32_t flags) { mdcache_entry_t *entry; mdcache_key_t key; cih_latch_t latch; int32_t refcnt; fsal_status_t ret; struct req_op_context op_context; /* flags are for future expansion. For now, we don't accept any. */ if (flags) return fsalstat(ERR_FSAL_INVAL, 0); /* Get a ref to the vec->up_gsh_export and initialize op_context for the * upcall */ get_gsh_export_ref(vec->up_gsh_export); init_op_context_simple(&op_context, vec->up_gsh_export, vec->up_fsal_export); /* * Find the entry, and keep the wrlock on the partition. This ensures * that no other caller can find this entry in the hashtable and * race in to take a reference. */ key.fsal = vec->up_fsal_export->sub_export->fsal; cih_hash_key(&key, vec->up_fsal_export->sub_export->fsal, handle, CIH_HASH_KEY_PROTOTYPE); entry = cih_get_by_key_latch(&key, &latch, CIH_GET_UNLOCK_ON_MISS, __func__, __LINE__); if (!entry) { LogDebug(COMPONENT_MDCACHE, "no entry found"); ret = fsalstat(ERR_FSAL_STALE, 0); goto out; } /* * We can remove it if the only ref is the sentinel. We can't put * the last ref while holding the latch though, so we must take an * extra reference, remove it and then put the extra ref after * releasing the latch. */ refcnt = atomic_fetch_int32_t(&entry->lru.refcnt); LogDebug(COMPONENT_MDCACHE, "entry %p has refcnt of %d", entry, refcnt); if (refcnt == 1) { mdcache_lru_ref(entry, LRU_TEMP_REF); cih_remove_latched(entry, &latch, 0); ret = fsalstat(ERR_FSAL_NO_ERROR, 0); } else { ret = fsalstat(ERR_FSAL_STILL_IN_USE, 0); } cih_hash_release(&latch); if (refcnt == 1) mdcache_lru_unref(entry, LRU_TEMP_REF); out: release_op_context(); return ret; } /** * @brief Update cached attributes * * @param[in] vec Up ops vector * @param[in] handle Export containing object * @param[in] attr New attributes * @param[in] flags Flags to govern update * * @return FSAL status */ static fsal_status_t mdc_up_update(const struct fsal_up_vector *vec, struct gsh_buffdesc *handle, struct fsal_attrlist *attr, uint32_t flags) { mdcache_entry_t *entry; fsal_status_t status; /* Have necessary changes been made? */ bool mutatis_mutandis = false; struct req_op_context op_context; mdcache_key_t key; attrmask_t mask_set = 0; /* These cannot be updated, changing any of them is tantamount to destroying and recreating the file. */ if (FSAL_TEST_MASK(attr->valid_mask, ATTR_TYPE | ATTR_FSID | ATTR_FILEID | ATTR_RAWDEV | ATTR_RDATTR_ERR | ATTR_GENERATION)) { return fsalstat(ERR_FSAL_INVAL, 0); } /* Filter out garbage flags */ if (flags & ~(fsal_up_update_filesize_inc | fsal_up_update_atime_inc | fsal_up_update_creation_inc | fsal_up_update_ctime_inc | fsal_up_update_mtime_inc | fsal_up_update_spaceused_inc | fsal_up_nlink)) { return fsalstat(ERR_FSAL_INVAL, 0); } /* Get a ref to the vec->up_gsh_export and initialize op_context for the * upcall */ get_gsh_export_ref(vec->up_gsh_export); init_op_context_simple(&op_context, vec->up_gsh_export, vec->up_fsal_export); key.fsal = vec->up_fsal_export->sub_export->fsal; cih_hash_key(&key, vec->up_fsal_export->sub_export->fsal, handle, CIH_HASH_KEY_PROTOTYPE); status = mdcache_find_keyed_reason(&key, &entry, LRU_ACTIVE_REF | LRU_PROMOTE); if (status.major == ERR_FSAL_NOENT) { /* Not cached, so invalidate is a success */ status = fsalstat(ERR_FSAL_NO_ERROR, 0); goto out; } else if (FSAL_IS_ERROR(status)) { /* Real error */ goto out; } /* Knock things out if the link count falls to 0. */ if ((flags & fsal_up_nlink) && (attr->numlinks == 0)) { LogFullDebug( COMPONENT_MDCACHE, "Entry %p Clearing MDCACHE_TRUST_ATTRS, MDCACHE_TRUST_CONTENT, MDCACHE_DIR_POPULATED", entry); atomic_clear_uint32_t_bits(&entry->mde_flags, MDCACHE_TRUST_ATTRS | MDCACHE_TRUST_CONTENT | MDCACHE_DIR_POPULATED); status = fsal_close(&entry->obj_handle); if (FSAL_IS_ERROR(status)) goto put; } if (attr->valid_mask == 0) { /* Done */ goto put; } /* If the attributes are invalid, we can't update a subset. Just bail, * and update them on demand */ if (!mdcache_test_attrs_trust(entry, attr->valid_mask)) { goto put; } PTHREAD_RWLOCK_wrlock(&entry->attr_lock); if (attr->expire_time_attr != 0) entry->attrs.expire_time_attr = attr->expire_time_attr; if (FSAL_TEST_MASK(attr->valid_mask, ATTR_SIZE)) { if (flags & fsal_up_update_filesize_inc) { if (attr->filesize > entry->attrs.filesize) { entry->attrs.filesize = attr->filesize; mutatis_mutandis = true; mask_set |= ATTR_SIZE; } } else { entry->attrs.filesize = attr->filesize; mutatis_mutandis = true; mask_set |= ATTR_SIZE; } } if (FSAL_TEST_MASK(attr->valid_mask, ATTR_SPACEUSED)) { if (flags & fsal_up_update_spaceused_inc) { if (attr->spaceused > entry->attrs.spaceused) { entry->attrs.spaceused = attr->spaceused; mutatis_mutandis = true; mask_set |= ATTR_SPACEUSED; } } else { entry->attrs.spaceused = attr->spaceused; mutatis_mutandis = true; mask_set |= ATTR_SPACEUSED; } } if (FSAL_TEST_MASK(attr->valid_mask, ATTR_ACL)) { /** * @todo Someone who knows the ACL code, please look * over this. We assume that the FSAL takes a * reference on the supplied ACL that we can then hold * onto. This seems the most reasonable approach in * an asynchronous call. */ nfs4_acl_release_entry(entry->attrs.acl); entry->attrs.acl = attr->acl; mutatis_mutandis = true; mask_set |= ATTR_ACL; } if (FSAL_TEST_MASK(attr->valid_mask, ATTR_MODE)) { entry->attrs.mode = attr->mode; mutatis_mutandis = true; mask_set |= ATTR_MODE; } if (FSAL_TEST_MASK(attr->valid_mask, ATTR_NUMLINKS)) { entry->attrs.numlinks = attr->numlinks; mutatis_mutandis = true; mask_set |= ATTR_NUMLINKS; } if (FSAL_TEST_MASK(attr->valid_mask, ATTR_OWNER)) { entry->attrs.owner = attr->owner; mutatis_mutandis = true; mask_set |= ATTR_OWNER; } if (FSAL_TEST_MASK(attr->valid_mask, ATTR_GROUP)) { entry->attrs.group = attr->group; mutatis_mutandis = true; mask_set |= ATTR_GROUP; } if (FSAL_TEST_MASK(attr->valid_mask, ATTR_ATIME) && ((flags & ~fsal_up_update_atime_inc) || (gsh_time_cmp(&attr->atime, &entry->attrs.atime) == 1))) { entry->attrs.atime = attr->atime; mutatis_mutandis = true; mask_set |= ATTR_ATIME; } if (FSAL_TEST_MASK(attr->valid_mask, ATTR_CREATION) && ((flags & ~fsal_up_update_creation_inc) || (gsh_time_cmp(&attr->creation, &entry->attrs.creation) == 1))) { entry->attrs.creation = attr->creation; mutatis_mutandis = true; mask_set |= ATTR_CREATION; } if (FSAL_TEST_MASK(attr->valid_mask, ATTR_CTIME) && ((flags & ~fsal_up_update_ctime_inc) || (gsh_time_cmp(&attr->ctime, &entry->attrs.ctime) == 1))) { entry->attrs.ctime = attr->ctime; mutatis_mutandis = true; mask_set |= ATTR_CTIME; } if (FSAL_TEST_MASK(attr->valid_mask, ATTR_MTIME) && ((flags & ~fsal_up_update_mtime_inc) || (gsh_time_cmp(&attr->mtime, &entry->attrs.mtime) == 1))) { entry->attrs.mtime = attr->mtime; mutatis_mutandis = true; mask_set |= ATTR_MTIME; } if (FSAL_TEST_MASK(attr->valid_mask, ATTR_CHANGE)) { entry->attrs.change = attr->change; mutatis_mutandis = true; mask_set |= ATTR_CHANGE; } if (FSAL_TEST_MASK(attr->valid_mask, ATTR4_FS_LOCATIONS)) { nfs4_fs_locations_release(entry->attrs.fs_locations); entry->attrs.fs_locations = attr->fs_locations; mutatis_mutandis = true; mask_set |= ATTR4_FS_LOCATIONS; } if (FSAL_TEST_MASK(attr->valid_mask, ATTR4_SEC_LABEL)) { gsh_free(entry->attrs.sec_label.slai_data.slai_data_val); entry->attrs.sec_label = attr->sec_label; attr->sec_label.slai_data.slai_data_len = 0; attr->sec_label.slai_data.slai_data_val = NULL; mutatis_mutandis = true; mask_set |= ATTR4_SEC_LABEL; } if (mutatis_mutandis) { mdc_fixup_md(entry, attr); entry->attrs.valid_mask |= mask_set; /* If directory can not trust content anymore. */ if (entry->obj_handle.type == DIRECTORY) { LogFullDebug( COMPONENT_MDCACHE, "Entry %p Clearing MDCACHE_TRUST_CONTENT, MDCACHE_DIR_POPULATED", entry); atomic_clear_uint32_t_bits( &entry->mde_flags, MDCACHE_TRUST_CONTENT | MDCACHE_DIR_POPULATED); } status = fsalstat(ERR_FSAL_NO_ERROR, 0); } else { atomic_clear_uint32_t_bits(&entry->mde_flags, MDCACHE_TRUST_ATTRS); status = fsalstat(ERR_FSAL_INVAL, 0); } PTHREAD_RWLOCK_unlock(&entry->attr_lock); put: mdcache_lru_unref(entry, LRU_ACTIVE_REF); out: release_op_context(); return status; } /** * @brief Invalidate a cached entry * * @note doesn't need op_ctx, handled in mdc_up_invalidate * * @param[in] vec Up ops vector * @param[in] key Key to specify object * @param[in] flags FSAL_UP_INVALIDATE* * * @return FSAL status */ static fsal_status_t mdc_up_invalidate_close(const struct fsal_up_vector *vec, struct gsh_buffdesc *key, uint32_t flags) { fsal_status_t status; status = up_async_invalidate(general_fridge, vec, key, flags | FSAL_UP_INVALIDATE_CLOSE, NULL, NULL); return status; } /** Grant a lock to a client * * Pass up to upper layer * * @param[in] vec Up ops vector * @param[in] file The file in question * @param[in] owner The lock owner * @param[in] lock_param A description of the lock * */ state_status_t mdc_up_lock_grant(const struct fsal_up_vector *vec, struct gsh_buffdesc *file, void *owner, fsal_lock_param_t *lock_param) { struct mdcache_fsal_export *myself = mdc_export(vec->up_fsal_export); state_status_t rc; struct req_op_context op_context; /* Get a ref to the vec->up_gsh_export and initialize op_context for the * upcall */ get_gsh_export_ref(vec->up_gsh_export); init_op_context_simple(&op_context, vec->up_gsh_export, vec->up_fsal_export); rc = myself->super_up_ops.lock_grant(vec, file, owner, lock_param); release_op_context(); return rc; } /** Signal lock availability * * Pass up to upper layer * * @param[in] vec Up ops vector * @param[in] file The file in question * @param[in] owner The lock owner * @param[in] lock_param A description of the lock * */ state_status_t mdc_up_lock_avail(const struct fsal_up_vector *vec, struct gsh_buffdesc *file, void *owner, fsal_lock_param_t *lock_param) { struct mdcache_fsal_export *myself = mdc_export(vec->up_fsal_export); state_status_t rc; struct req_op_context op_context; /* Initialize op context */ /* Get a ref to the vec->up_gsh_export and initialize op_context for the * upcall */ get_gsh_export_ref(vec->up_gsh_export); init_op_context_simple(&op_context, vec->up_gsh_export, vec->up_fsal_export); rc = myself->super_up_ops.lock_avail(vec, file, owner, lock_param); release_op_context(); return rc; } /** Perform a layoutrecall on a single file * * Pass to upper layer * * @param[in] vec Up ops vector * @param[in] handle Handle on which the layout is held * @param[in] layout_type The type of layout to recall * @param[in] changed Whether the layout has changed and the * client ought to finish writes through MDS * @param[in] segment Segment to recall * @param[in] cookie A cookie returned with the return that * completely satisfies a recall * @param[in] spec Lets us be fussy about what clients we send * to. May beNULL. * */ state_status_t mdc_up_layoutrecall(const struct fsal_up_vector *vec, struct gsh_buffdesc *handle, layouttype4 layout_type, bool changed, const struct pnfs_segment *segment, void *cookie, struct layoutrecall_spec *spec) { struct mdcache_fsal_export *myself = mdc_export(vec->up_fsal_export); state_status_t rc; struct req_op_context op_context; /* Get a ref to the vec->up_gsh_export and initialize op_context for the * upcall */ get_gsh_export_ref(vec->up_gsh_export); init_op_context_simple(&op_context, vec->up_gsh_export, vec->up_fsal_export); rc = myself->super_up_ops.layoutrecall(vec, handle, layout_type, changed, segment, cookie, spec); release_op_context(); return rc; } /** Recall a delegation * * Pass to upper layer * * @param[in] vec Up ops vector * @param[in] handle The handle on which the delegation is held */ state_status_t mdc_up_delegrecall(const struct fsal_up_vector *vec, struct gsh_buffdesc *handle) { struct mdcache_fsal_export *myself = mdc_export(vec->up_fsal_export); state_status_t rc; struct req_op_context op_context; /* Get a ref to the vec->up_gsh_export and initialize op_context for the * upcall */ get_gsh_export_ref(vec->up_gsh_export); init_op_context_simple(&op_context, vec->up_gsh_export, vec->up_fsal_export); rc = myself->super_up_ops.delegrecall(vec, handle); release_op_context(); return rc; } fsal_status_t mdcache_export_up_ops_init(struct fsal_up_vector *my_up_ops, const struct fsal_up_vector *super_up_ops) { /* Init with super ops. Struct copy */ *my_up_ops = *super_up_ops; up_ready_init(my_up_ops); /* Replace cache-related calls */ my_up_ops->invalidate = mdc_up_invalidate; my_up_ops->update = mdc_up_update; my_up_ops->invalidate_close = mdc_up_invalidate_close; my_up_ops->try_release = mdc_up_try_release; /* These are pass-through calls that set op_ctx */ my_up_ops->lock_grant = mdc_up_lock_grant; my_up_ops->lock_avail = mdc_up_lock_avail; my_up_ops->layoutrecall = mdc_up_layoutrecall; /* notify_device cannot call into MDCACHE */ my_up_ops->delegrecall = mdc_up_delegrecall; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** @} */ nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_xattrs.c000066400000000000000000000223071473756622300247640ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2015-2016 Red Hat, Inc. and/or its affiliates. * Author: Daniel Gryniewicz * * This program 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 3 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 * 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 * */ /* xattrs.c * NULL object (file|dir) handle object extended attributes */ #include "config.h" #include "fsal.h" #include /* used for 'dirname' */ #include #include #include #include #include "os/xattr.h" #include "gsh_list.h" #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "mdcache_int.h" /** * @brief List extended attributes on a file * * Pass through to sub-FSAL * * @param[in] obj_hdl File to walk * @param[in] argcookie Cookie for caller * @param[out] xattrs_tab Output buffer for xattrs * @param[in] xattrs_tabsize Size of @a xattrs_tab * @param[out] p_nb_returned Number of xattrs returned * @param[out] end_of_list True if reached end of list * @return FSAL status */ fsal_status_t mdcache_list_ext_attrs(struct fsal_obj_handle *obj_hdl, unsigned int argcookie, fsal_xattrent_t *xattrs_tab, unsigned int xattrs_tabsize, unsigned int *p_nb_returned, int *end_of_list) { struct mdcache_fsal_obj_handle *handle = container_of( obj_hdl, struct mdcache_fsal_obj_handle, obj_handle); fsal_status_t status; subcall(status = handle->sub_handle->obj_ops->list_ext_attrs( handle->sub_handle, argcookie, xattrs_tab, xattrs_tabsize, p_nb_returned, end_of_list)); return status; } /** * @brief Get ID of xattr by name * * Pass through to sub-FSAL * * @param[in] obj_hdl File to search * @param[in] name Name of xattr to look up * @param[out] p_id ID of xattr, if found * @return FSAL status */ fsal_status_t mdcache_getextattr_id_by_name(struct fsal_obj_handle *obj_hdl, const char *name, unsigned int *p_id) { struct mdcache_fsal_obj_handle *handle = container_of( obj_hdl, struct mdcache_fsal_obj_handle, obj_handle); fsal_status_t status; subcall(status = handle->sub_handle->obj_ops->getextattr_id_by_name( handle->sub_handle, name, p_id)); return status; } /** * @brief Get contents of xattr by ID * * Pass through to sub-FSAL * * @param[in] obj_hdl File to search * @param[in] id ID of xattr to read * @param[out] buf Output buffer * @param[in] buf_size Size of @a buf * @param[out] p_output_size Amount written to buf * @return FSAL status */ fsal_status_t mdcache_getextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int id, void *buf, size_t buf_size, size_t *p_output_size) { struct mdcache_fsal_obj_handle *handle = container_of( obj_hdl, struct mdcache_fsal_obj_handle, obj_handle); fsal_status_t status; subcall(status = handle->sub_handle->obj_ops->getextattr_value_by_id( handle->sub_handle, id, buf, buf_size, p_output_size)); return status; } /** * @brief Get contents of xattr by name * * Pass through to sub-FSAL * * @param[in] obj_hdl File to search * @param[in] name Name of xattr to look up * @param[out] buf Output buffer * @param[in] buf_size Size of @a buf * @param[out] p_output_size Amount written to buf * @return FSAL status */ fsal_status_t mdcache_getextattr_value_by_name(struct fsal_obj_handle *obj_hdl, const char *name, void *buf, size_t buf_size, size_t *p_output_size) { struct mdcache_fsal_obj_handle *handle = container_of( obj_hdl, struct mdcache_fsal_obj_handle, obj_handle); fsal_status_t status; subcall(status = handle->sub_handle->obj_ops->getextattr_value_by_name( handle->sub_handle, name, buf, buf_size, p_output_size)); return status; } /** * @brief Set contents of xattr by name * * Pass through to sub-FSAL * * @param[in] obj_hdl File to search * @param[in] name Name of xattr to set * @param[out] buf Output buffer * @param[in] buf_size Size of @a buf * @param[in] create If true, create xattr * @return FSAL status */ fsal_status_t mdcache_setextattr_value(struct fsal_obj_handle *obj_hdl, const char *name, void *buf, size_t buf_size, int create) { struct mdcache_fsal_obj_handle *handle = container_of( obj_hdl, struct mdcache_fsal_obj_handle, obj_handle); fsal_status_t status; subcall(status = handle->sub_handle->obj_ops->setextattr_value( handle->sub_handle, name, buf, buf_size, create)); return status; } /** * @brief Set contents of xattr by ID * * Pass through to sub-FSAL * * @param[in] obj_hdl File to search * @param[in] id ID of xattr to set * @param[out] buf Output buffer * @param[in] buf_size Size of @a buf * @return FSAL status */ fsal_status_t mdcache_setextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int id, void *buf, size_t buf_size) { struct mdcache_fsal_obj_handle *handle = container_of( obj_hdl, struct mdcache_fsal_obj_handle, obj_handle); fsal_status_t status; subcall(status = handle->sub_handle->obj_ops->setextattr_value_by_id( handle->sub_handle, id, buf, buf_size)); return status; } /** * @brief Remove an xattr by ID * * Pass through to sub-FSAL * * @param[in] obj_hdl File to search * @param[in] id ID of xattr to remove * @return FSAL status */ fsal_status_t mdcache_remove_extattr_by_id(struct fsal_obj_handle *obj_hdl, unsigned int id) { struct mdcache_fsal_obj_handle *handle = container_of( obj_hdl, struct mdcache_fsal_obj_handle, obj_handle); fsal_status_t status; subcall(status = handle->sub_handle->obj_ops->remove_extattr_by_id( handle->sub_handle, id)); return status; } /** * @brief Remove an xattr by name * * Pass through to sub-FSAL * * @param[in] obj_hdl File to search * @param[in] name Name of xattr to remove * @return FSAL status */ fsal_status_t mdcache_remove_extattr_by_name(struct fsal_obj_handle *obj_hdl, const char *name) { struct mdcache_fsal_obj_handle *handle = container_of( obj_hdl, struct mdcache_fsal_obj_handle, obj_handle); fsal_status_t status; subcall(status = handle->sub_handle->obj_ops->remove_extattr_by_name( handle->sub_handle, name)); return status; } /** * @brief Get an Extended Attribute * * Pass through to sub-FSAL * * @param[in] obj_hdl File to search * @param[in] name Name of attribute * @param[out] value Value of attribute * @return FSAL status */ fsal_status_t mdcache_getxattrs(struct fsal_obj_handle *obj_hdl, xattrkey4 *name, xattrvalue4 *value) { struct mdcache_fsal_obj_handle *handle = container_of( obj_hdl, struct mdcache_fsal_obj_handle, obj_handle); fsal_status_t status; subcall(status = handle->sub_handle->obj_ops->getxattrs( handle->sub_handle, name, value)); return status; } /** * @brief Set an Extended Attribute * * Pass through to sub-FSAL * * @param[in] obj_hdl File to search * @param[in] type Type of attribute * @param[in] name Name of attribute * @param[in] value Value of attribute * @return FSAL status */ fsal_status_t mdcache_setxattrs(struct fsal_obj_handle *obj_hdl, setxattr_option4 option, xattrkey4 *name, xattrvalue4 *value) { struct mdcache_fsal_obj_handle *handle = container_of( obj_hdl, struct mdcache_fsal_obj_handle, obj_handle); fsal_status_t status; subcall(status = handle->sub_handle->obj_ops->setxattrs( handle->sub_handle, option, name, value)); return status; } /** * @brief Remove an Extended Attribute * * Pass through to sub-FSAL * * @param[in] obj_hdl File to search * @param[in] name Type of attribute * @return FSAL status */ fsal_status_t mdcache_removexattrs(struct fsal_obj_handle *obj_hdl, xattrkey4 *name) { struct mdcache_fsal_obj_handle *handle = container_of( obj_hdl, struct mdcache_fsal_obj_handle, obj_handle); fsal_status_t status; subcall(status = handle->sub_handle->obj_ops->removexattrs( handle->sub_handle, name)); return status; } /** * @brief List Extended Attributes * * Pass through to sub-FSAL * * @param[in] obj_hdl File to search * @param[in] len Length of names buffer * @param[in,out] cookie The cookie for list * @param[in,out] verf cookie verifier * @param[out] eof set if no more extended attributes * @param[out] names list of extended attribute names * @return FSAL status */ fsal_status_t mdcache_listxattrs(struct fsal_obj_handle *obj_hdl, count4 len, nfs_cookie4 *cookie, bool_t *eof, xattrlist4 *names) { struct mdcache_fsal_obj_handle *handle = container_of( obj_hdl, struct mdcache_fsal_obj_handle, obj_handle); fsal_status_t status; subcall(status = handle->sub_handle->obj_ops->listxattrs( handle->sub_handle, len, cookie, eof, names)); return status; } nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_NULL/000077500000000000000000000000001473756622300213515ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_NULL/CMakeLists.txt000066400000000000000000000032511473756622300241120ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- add_definitions( -D__USE_GNU ) set( LIB_PREFIX 64) ########### next target ############### SET(fsalnull_LIB_SRCS handle.c file.c xattrs.c nullfs_methods.h main.c export.c ) add_library(fsalnull MODULE ${fsalnull_LIB_SRCS}) add_sanitizers(fsalnull) if (USE_LTTNG) add_dependencies(fsalnull gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) target_link_libraries(fsalnull ganesha_nfsd ${LDFLAG_DISALLOW_UNDEF} ) set_target_properties(fsalnull PROPERTIES VERSION 4.2.0 SOVERSION 4) install(TARGETS fsalnull COMPONENT fsal DESTINATION ${FSAL_DESTINATION} ) ########### install files ############### nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_NULL/export.c000066400000000000000000000363501473756622300230450ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * */ /* export.c * NULL FSAL export object */ #include "config.h" #include "fsal.h" #include /* used for 'dirname' */ #include #include #include #include #include #include #include "gsh_list.h" #include "config_parsing.h" #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_config.h" #include "nullfs_methods.h" #include "nfs_exports.h" #include "export_mgr.h" /* helpers to/from other NULL objects */ /* export object methods */ static void release(struct fsal_export *exp_hdl) { struct nullfs_fsal_export *myself; struct fsal_module *sub_fsal; myself = container_of(exp_hdl, struct nullfs_fsal_export, export); sub_fsal = myself->export.sub_export->fsal; /* Release the sub_export */ myself->export.sub_export->exp_ops.release(myself->export.sub_export); fsal_put(sub_fsal); LogFullDebug(COMPONENT_FSAL, "FSAL %s fsal_refcount %" PRIu32, sub_fsal->name, atomic_fetch_int32_t(&sub_fsal->refcount)); fsal_detach_export(exp_hdl->fsal, &exp_hdl->exports); free_export_ops(exp_hdl); gsh_free(myself); /* elvis has left the building */ } static fsal_status_t get_dynamic_info(struct fsal_export *exp_hdl, struct fsal_obj_handle *obj_hdl, fsal_dynamicfsinfo_t *infop) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); /* calling subfsal method */ op_ctx->fsal_export = exp->export.sub_export; fsal_status_t status = op_ctx->fsal_export->exp_ops.get_fs_dynamic_info( op_ctx->fsal_export, handle->sub_handle, infop); op_ctx->fsal_export = &exp->export; return status; } static bool fs_supports(struct fsal_export *exp_hdl, fsal_fsinfo_options_t option) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); op_ctx->fsal_export = exp->export.sub_export; bool result = exp->export.sub_export->exp_ops.fs_supports( exp->export.sub_export, option); op_ctx->fsal_export = &exp->export; return result; } static uint64_t fs_maxfilesize(struct fsal_export *exp_hdl) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); op_ctx->fsal_export = exp->export.sub_export; uint64_t result = exp->export.sub_export->exp_ops.fs_maxfilesize( exp->export.sub_export); op_ctx->fsal_export = &exp->export; return result; } static uint32_t fs_maxread(struct fsal_export *exp_hdl) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); op_ctx->fsal_export = exp->export.sub_export; uint32_t result = exp->export.sub_export->exp_ops.fs_maxread( exp->export.sub_export); op_ctx->fsal_export = &exp->export; return result; } static uint32_t fs_maxwrite(struct fsal_export *exp_hdl) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); op_ctx->fsal_export = exp->export.sub_export; uint32_t result = exp->export.sub_export->exp_ops.fs_maxwrite( exp->export.sub_export); op_ctx->fsal_export = &exp->export; return result; } static uint32_t fs_maxlink(struct fsal_export *exp_hdl) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); op_ctx->fsal_export = exp->export.sub_export; uint32_t result = exp->export.sub_export->exp_ops.fs_maxlink( exp->export.sub_export); op_ctx->fsal_export = &exp->export; return result; } static uint32_t fs_maxnamelen(struct fsal_export *exp_hdl) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); op_ctx->fsal_export = exp->export.sub_export; uint32_t result = exp->export.sub_export->exp_ops.fs_maxnamelen( exp->export.sub_export); op_ctx->fsal_export = &exp->export; return result; } static uint32_t fs_maxpathlen(struct fsal_export *exp_hdl) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); op_ctx->fsal_export = exp->export.sub_export; uint32_t result = exp->export.sub_export->exp_ops.fs_maxpathlen( exp->export.sub_export); op_ctx->fsal_export = &exp->export; return result; } static fsal_aclsupp_t fs_acl_support(struct fsal_export *exp_hdl) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); op_ctx->fsal_export = exp->export.sub_export; fsal_aclsupp_t result = exp->export.sub_export->exp_ops.fs_acl_support( exp->export.sub_export); op_ctx->fsal_export = &exp->export; return result; } static attrmask_t fs_supported_attrs(struct fsal_export *exp_hdl) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); op_ctx->fsal_export = exp->export.sub_export; attrmask_t result = exp->export.sub_export->exp_ops.fs_supported_attrs( exp->export.sub_export); op_ctx->fsal_export = &exp->export; return result; } static uint32_t fs_umask(struct fsal_export *exp_hdl) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); op_ctx->fsal_export = exp->export.sub_export; uint32_t result = exp->export.sub_export->exp_ops.fs_umask( exp->export.sub_export); op_ctx->fsal_export = &exp->export; return result; } static int32_t fs_expiretimeparent(struct fsal_export *exp_hdl) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); op_ctx->fsal_export = exp->export.sub_export; uint32_t result = exp->export.sub_export->exp_ops.fs_expiretimeparent( exp->export.sub_export); op_ctx->fsal_export = &exp->export; return result; } /* get_quota * return quotas for this export. * path could cross a lower mount boundary which could * mask lower mount values with those of the export root * if this is a real issue, we can scan each time with setmntent() * better yet, compare st_dev of the file with st_dev of root_fd. * on linux, can map st_dev -> /proc/partitions name -> /dev/ */ static fsal_status_t get_quota(struct fsal_export *exp_hdl, const char *filepath, int quota_type, int quota_id, fsal_quota_t *pquota) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); op_ctx->fsal_export = exp->export.sub_export; fsal_status_t result = exp->export.sub_export->exp_ops.get_quota( exp->export.sub_export, filepath, quota_type, quota_id, pquota); op_ctx->fsal_export = &exp->export; return result; } /* set_quota * same lower mount restriction applies */ static fsal_status_t set_quota(struct fsal_export *exp_hdl, const char *filepath, int quota_type, int quota_id, fsal_quota_t *pquota, fsal_quota_t *presquota) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); op_ctx->fsal_export = exp->export.sub_export; fsal_status_t result = exp->export.sub_export->exp_ops.set_quota( exp->export.sub_export, filepath, quota_type, quota_id, pquota, presquota); op_ctx->fsal_export = &exp->export; return result; } static struct state_t *nullfs_alloc_state(struct fsal_export *exp_hdl, enum state_type state_type, struct state_t *related_state) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); op_ctx->fsal_export = exp->export.sub_export; state_t *state = exp->export.sub_export->exp_ops.alloc_state( exp->export.sub_export, state_type, related_state); op_ctx->fsal_export = exp_hdl; return state; } static bool nullfs_is_superuser(struct fsal_export *exp_hdl, const struct user_cred *creds) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); bool rv; op_ctx->fsal_export = exp->export.sub_export; rv = exp->export.sub_export->exp_ops.is_superuser( exp->export.sub_export, creds); op_ctx->fsal_export = &exp->export; return rv; } /* extract a file handle from a buffer. * do verification checks and flag any and all suspicious bits. * Return an updated fh_desc into whatever was passed. The most * common behavior, done here is to just reset the length. */ static fsal_status_t wire_to_host(struct fsal_export *exp_hdl, fsal_digesttype_t in_type, struct gsh_buffdesc *fh_desc, int flags) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); op_ctx->fsal_export = exp->export.sub_export; fsal_status_t result = exp->export.sub_export->exp_ops.wire_to_host( exp->export.sub_export, in_type, fh_desc, flags); op_ctx->fsal_export = &exp->export; return result; } static fsal_status_t nullfs_host_to_key(struct fsal_export *exp_hdl, struct gsh_buffdesc *fh_desc) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); op_ctx->fsal_export = exp->export.sub_export; fsal_status_t result = exp->export.sub_export->exp_ops.host_to_key( exp->export.sub_export, fh_desc); op_ctx->fsal_export = &exp->export; return result; } static void nullfs_prepare_unexport(struct fsal_export *exp_hdl) { struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); op_ctx->fsal_export = exp->export.sub_export; exp->export.sub_export->exp_ops.prepare_unexport( exp->export.sub_export); op_ctx->fsal_export = &exp->export; } /* nullfs_export_ops_init * overwrite vector entries with the methods that we support */ void nullfs_export_ops_init(struct export_ops *ops) { ops->release = release; ops->prepare_unexport = nullfs_prepare_unexport; ops->lookup_path = nullfs_lookup_path; ops->wire_to_host = wire_to_host; ops->host_to_key = nullfs_host_to_key; ops->create_handle = nullfs_create_handle; ops->get_fs_dynamic_info = get_dynamic_info; ops->fs_supports = fs_supports; ops->fs_maxfilesize = fs_maxfilesize; ops->fs_maxread = fs_maxread; ops->fs_maxwrite = fs_maxwrite; ops->fs_maxlink = fs_maxlink; ops->fs_maxnamelen = fs_maxnamelen; ops->fs_maxpathlen = fs_maxpathlen; ops->fs_acl_support = fs_acl_support; ops->fs_supported_attrs = fs_supported_attrs; ops->fs_umask = fs_umask; ops->fs_expiretimeparent = fs_expiretimeparent; ops->get_quota = get_quota; ops->set_quota = set_quota; ops->alloc_state = nullfs_alloc_state; ops->is_superuser = nullfs_is_superuser; } struct nullfsal_args { struct subfsal_args subfsal; }; static struct config_item sub_fsal_params[] = { CONF_ITEM_STR("name", 1, 10, NULL, subfsal_args, name), CONFIG_EOL }; static struct config_item export_params[] = { CONF_ITEM_NOOP("name"), CONF_RELAX_BLOCK("FSAL", sub_fsal_params, noop_conf_init, subfsal_commit, nullfsal_args, subfsal), CONFIG_EOL }; static struct config_block export_param = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal.nullfs-export%d", .blk_desc.name = "FSAL", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = export_params, .blk_desc.u.blk.commit = noop_conf_commit }; /* create_export * Create an export point and return a handle to it to be kept * in the export list. * First lookup the fsal, then create the export and then put the fsal back. * returns the export with one reference taken. */ fsal_status_t nullfs_create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops) { fsal_status_t expres; struct fsal_module *fsal_stack; struct nullfs_fsal_export *myself; struct nullfsal_args nullfsal; int retval; /* process our FSAL block to get the name of the fsal * underneath us. */ retval = load_config_from_node(parse_node, &export_param, &nullfsal, true, err_type); if (retval != 0) return fsalstat(ERR_FSAL_INVAL, 0); fsal_stack = lookup_fsal(nullfsal.subfsal.name); if (fsal_stack == NULL) { LogMajor(COMPONENT_FSAL, "nullfs create export failed to lookup for FSAL %s", nullfsal.subfsal.name); return fsalstat(ERR_FSAL_INVAL, EINVAL); } myself = gsh_calloc(1, sizeof(struct nullfs_fsal_export)); expres = fsal_stack->m_ops.create_export( fsal_stack, nullfsal.subfsal.fsal_node, err_type, up_ops); fsal_put(fsal_stack); LogFullDebug(COMPONENT_FSAL, "FSAL %s fsal_refcount %" PRIu32, fsal_stack->name, atomic_fetch_int32_t(&fsal_stack->refcount)); if (FSAL_IS_ERROR(expres)) { LogMajor(COMPONENT_FSAL, "Failed to call create_export on underlying FSAL %s", nullfsal.subfsal.name); gsh_free(myself); return expres; } fsal_export_stack(op_ctx->fsal_export, &myself->export); fsal_export_init(&myself->export); nullfs_export_ops_init(&myself->export.exp_ops); #ifdef EXPORT_OPS_INIT /*** FIX ME!!! * Need to iterate through the lists to save and restore. */ nullfs_handle_ops_init(myself->export.obj_ops); #endif /* EXPORT_OPS_INIT */ myself->export.up_ops = up_ops; myself->export.fsal = fsal_hdl; /* lock myself before attaching to the fsal. * keep myself locked until done with creating myself. */ op_ctx->fsal_export = &myself->export; return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t nullfs_update_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, struct fsal_export *original, struct fsal_module *updated_super) { fsal_status_t status; struct fsal_module *fsal_stack; struct nullfsal_args nullfsal; int retval; /* Check for changes in stacking by calling default update_export. */ status = update_export(fsal_hdl, parse_node, err_type, original, updated_super); if (FSAL_IS_ERROR(status)) return status; /* process our FSAL block to get the name of the fsal * underneath us. */ retval = load_config_from_node(parse_node, &export_param, &nullfsal, true, err_type); if (retval != 0) return fsalstat(ERR_FSAL_INVAL, 0); fsal_stack = lookup_fsal(nullfsal.subfsal.name); if (fsal_stack == NULL) { LogMajor(COMPONENT_FSAL, "nullfs update export failed to lookup for FSAL %s", nullfsal.subfsal.name); return fsalstat(ERR_FSAL_INVAL, EINVAL); } status = fsal_stack->m_ops.update_export(fsal_stack, nullfsal.subfsal.fsal_node, err_type, original->sub_export, fsal_hdl); fsal_put(fsal_stack); if (FSAL_IS_ERROR(status)) { LogMajor(COMPONENT_FSAL, "Failed to call update_export on underlying FSAL %s", nullfsal.subfsal.name); } return status; } nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_NULL/file.c000066400000000000000000000252721473756622300224440ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 */ /* file.c * File I/O methods for NULL module */ #include "config.h" #include #include "fsal.h" #include "FSAL/access_check.h" #include "fsal_convert.h" #include #include #include "FSAL/fsal_commonlib.h" #include "nullfs_methods.h" /** * @brief Callback arg for NULL async callbacks * * NULL needs to know what its object is related to the sub-FSAL's object. * This wraps the given callback arg with NULL specific info */ struct null_async_arg { struct fsal_obj_handle *obj_hdl; /**< NULL's handle */ fsal_async_cb cb; /**< Wrapped callback */ void *cb_arg; /**< Wrapped callback data */ }; /** * @brief Callback for NULL async calls * * Unstack, and call up. * * @param[in] obj Object being acted on * @param[in] ret Return status of call * @param[in] obj_data Data for call * @param[in] caller_data Data for caller */ void null_async_cb(struct fsal_obj_handle *obj, fsal_status_t ret, void *obj_data, void *caller_data) { struct fsal_export *save_exp = op_ctx->fsal_export; struct null_async_arg *arg = caller_data; op_ctx->fsal_export = save_exp->super_export; arg->cb(arg->obj_hdl, ret, obj_data, arg->cb_arg); op_ctx->fsal_export = save_exp; gsh_free(arg); } /* nullfs_close * Close the file if it is still open. */ fsal_status_t nullfs_close(struct fsal_obj_handle *obj_hdl) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->close(handle->sub_handle); op_ctx->fsal_export = &export->export; return status; } fsal_status_t nullfs_open2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attrs_in, fsal_verifier_t verifier, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); struct fsal_obj_handle *sub_handle = NULL; /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->open2( handle->sub_handle, state, openflags, createmode, name, attrs_in, verifier, &sub_handle, attrs_out, caller_perm_check, parent_pre_attrs_out, parent_post_attrs_out); op_ctx->fsal_export = &export->export; if (sub_handle) { /* wrap the subfsal handle in a nullfs handle. */ return nullfs_alloc_and_check_handle( export, sub_handle, obj_hdl->fs, new_obj, status); } return status; } bool nullfs_check_verifier(struct fsal_obj_handle *obj_hdl, fsal_verifier_t verifier) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; bool result = handle->sub_handle->obj_ops->check_verifier( handle->sub_handle, verifier); op_ctx->fsal_export = &export->export; return result; } fsal_openflags_t nullfs_status2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_openflags_t result = handle->sub_handle->obj_ops->status2(handle->sub_handle, state); op_ctx->fsal_export = &export->export; return result; } fsal_status_t nullfs_reopen2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->reopen2( handle->sub_handle, state, openflags); op_ctx->fsal_export = &export->export; return status; } void nullfs_read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); struct null_async_arg *arg; /* Set up async callback */ arg = gsh_calloc(1, sizeof(*arg)); arg->obj_hdl = obj_hdl; arg->cb = done_cb; arg->cb_arg = caller_arg; /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; handle->sub_handle->obj_ops->read2(handle->sub_handle, bypass, null_async_cb, read_arg, arg); op_ctx->fsal_export = &export->export; } void nullfs_write2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *caller_arg) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); struct null_async_arg *arg; /* Set up async callback */ arg = gsh_calloc(1, sizeof(*arg)); arg->obj_hdl = obj_hdl; arg->cb = done_cb; arg->cb_arg = caller_arg; /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; handle->sub_handle->obj_ops->write2(handle->sub_handle, bypass, null_async_cb, write_arg, arg); op_ctx->fsal_export = &export->export; } fsal_status_t nullfs_seek2(struct fsal_obj_handle *obj_hdl, struct state_t *state, struct io_info *info) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->seek2( handle->sub_handle, state, info); op_ctx->fsal_export = &export->export; return status; } fsal_status_t nullfs_io_advise2(struct fsal_obj_handle *obj_hdl, struct state_t *state, struct io_hints *hints) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->io_advise2( handle->sub_handle, state, hints); op_ctx->fsal_export = &export->export; return status; } fsal_status_t nullfs_commit2(struct fsal_obj_handle *obj_hdl, off_t offset, size_t len) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->commit2( handle->sub_handle, offset, len); op_ctx->fsal_export = &export->export; return status; } fsal_status_t nullfs_lock_op2(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *p_owner, fsal_lock_op_t lock_op, fsal_lock_param_t *req_lock, fsal_lock_param_t *conflicting_lock) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->lock_op2( handle->sub_handle, state, p_owner, lock_op, req_lock, conflicting_lock); op_ctx->fsal_export = &export->export; return status; } fsal_status_t nullfs_close2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->close2(handle->sub_handle, state); op_ctx->fsal_export = &export->export; return status; } fsal_status_t nullfs_fallocate(struct fsal_obj_handle *obj_hdl, struct state_t *state, uint64_t offset, uint64_t length, bool allocate) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); fsal_status_t status; /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; status = handle->sub_handle->obj_ops->fallocate( handle->sub_handle, state, offset, length, allocate); op_ctx->fsal_export = &export->export; return status; } nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_NULL/handle.c000066400000000000000000000613531473756622300227600ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * */ /* handle.c */ #include "config.h" #include "fsal.h" #include /* used for 'dirname' */ #include #include #include #include "gsh_list.h" #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "nullfs_methods.h" #include "nfs4_acls.h" #include /* helpers */ /* handle methods */ /** * Allocate and initialize a new nullfs handle. * * This function doesn't free the sub_handle if the allocation fails. It must * be done in the calling function. * * @param[in] export The nullfs export used by the handle. * @param[in] sub_handle The handle used by the subfsal. * @param[in] fs The filesystem of the new handle. * * @return The new handle, or NULL if the allocation failed. */ static struct nullfs_fsal_obj_handle * nullfs_alloc_handle(struct nullfs_fsal_export *export, struct fsal_obj_handle *sub_handle, struct fsal_filesystem *fs) { struct nullfs_fsal_obj_handle *result; result = gsh_calloc(1, sizeof(struct nullfs_fsal_obj_handle)); /* default handlers */ fsal_obj_handle_init(&result->obj_handle, &export->export, sub_handle->type, true); /* nullfs handlers */ result->obj_handle.obj_ops = &NULLFS.handle_ops; result->sub_handle = sub_handle; result->obj_handle.type = sub_handle->type; result->obj_handle.fsid = sub_handle->fsid; result->obj_handle.fileid = sub_handle->fileid; result->obj_handle.fs = fs; result->obj_handle.state_hdl = sub_handle->state_hdl; result->refcnt = 1; return result; } /** * Attempts to create a new nullfs handle, or cleanup memory if it fails. * * This function is a wrapper of nullfs_alloc_handle. It adds error checking * and logging. It also cleans objects allocated in the subfsal if it fails. * * @param[in] export The nullfs export used by the handle. * @param[in,out] sub_handle The handle used by the subfsal. * @param[in] fs The filesystem of the new handle. * @param[in] new_handle Address where the new allocated pointer should be * written. * @param[in] subfsal_status Result of the allocation of the subfsal handle. * * @return An error code for the function. */ fsal_status_t nullfs_alloc_and_check_handle(struct nullfs_fsal_export *export, struct fsal_obj_handle *sub_handle, struct fsal_filesystem *fs, struct fsal_obj_handle **new_handle, fsal_status_t subfsal_status) { /** Result status of the operation. */ fsal_status_t status = subfsal_status; if (!FSAL_IS_ERROR(subfsal_status)) { struct nullfs_fsal_obj_handle *null_handle; null_handle = nullfs_alloc_handle(export, sub_handle, fs); *new_handle = &null_handle->obj_handle; } return status; } /* lookup * deprecated NULL parent && NULL path implies root handle */ static fsal_status_t lookup(struct fsal_obj_handle *parent, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { /** Parent as nullfs handle.*/ struct nullfs_fsal_obj_handle *null_parent = container_of(parent, struct nullfs_fsal_obj_handle, obj_handle); /** Handle given by the subfsal. */ struct fsal_obj_handle *sub_handle = NULL; *handle = NULL; /* call to subfsal lookup with the good context. */ fsal_status_t status; /** Current nullfs export. */ struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); op_ctx->fsal_export = export->export.sub_export; status = null_parent->sub_handle->obj_ops->lookup( null_parent->sub_handle, path, &sub_handle, attrs_out); op_ctx->fsal_export = &export->export; /* wrapping the subfsal handle in a nullfs handle. */ return nullfs_alloc_and_check_handle(export, sub_handle, parent->fs, handle, status); } static fsal_status_t makedir(struct fsal_obj_handle *dir_hdl, const char *name, struct fsal_attrlist *attrs_in, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { *new_obj = NULL; /** Parent directory nullfs handle. */ struct nullfs_fsal_obj_handle *parent_hdl = container_of( dir_hdl, struct nullfs_fsal_obj_handle, obj_handle); /** Current nullfs export. */ struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /** Subfsal handle of the new directory.*/ struct fsal_obj_handle *sub_handle; /* Creating the directory with a subfsal handle. */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = parent_hdl->sub_handle->obj_ops->mkdir( parent_hdl->sub_handle, name, attrs_in, &sub_handle, attrs_out, parent_pre_attrs_out, parent_post_attrs_out); op_ctx->fsal_export = &export->export; /* wrapping the subfsal handle in a nullfs handle. */ return nullfs_alloc_and_check_handle(export, sub_handle, dir_hdl->fs, new_obj, status); } static fsal_status_t makenode(struct fsal_obj_handle *dir_hdl, const char *name, object_file_type_t nodetype, struct fsal_attrlist *attrs_in, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { /** Parent directory nullfs handle. */ struct nullfs_fsal_obj_handle *nullfs_dir = container_of( dir_hdl, struct nullfs_fsal_obj_handle, obj_handle); /** Current nullfs export. */ struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /** Subfsal handle of the new node.*/ struct fsal_obj_handle *sub_handle; *new_obj = NULL; /* Creating the node with a subfsal handle. */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = nullfs_dir->sub_handle->obj_ops->mknode( nullfs_dir->sub_handle, name, nodetype, attrs_in, &sub_handle, attrs_out, parent_pre_attrs_out, parent_post_attrs_out); op_ctx->fsal_export = &export->export; /* wrapping the subfsal handle in a nullfs handle. */ return nullfs_alloc_and_check_handle(export, sub_handle, dir_hdl->fs, new_obj, status); } /** makesymlink * Note that we do not set mode bits on symlinks for Linux/POSIX * They are not really settable in the kernel and are not checked * anyway (default is 0777) because open uses that target's mode */ static fsal_status_t makesymlink(struct fsal_obj_handle *dir_hdl, const char *name, const char *link_path, struct fsal_attrlist *attrs_in, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { /** Parent directory nullfs handle. */ struct nullfs_fsal_obj_handle *nullfs_dir = container_of( dir_hdl, struct nullfs_fsal_obj_handle, obj_handle); /** Current nullfs export. */ struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /** Subfsal handle of the new link.*/ struct fsal_obj_handle *sub_handle; *new_obj = NULL; /* creating the file with a subfsal handle. */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = nullfs_dir->sub_handle->obj_ops->symlink( nullfs_dir->sub_handle, name, link_path, attrs_in, &sub_handle, attrs_out, parent_pre_attrs_out, parent_post_attrs_out); op_ctx->fsal_export = &export->export; /* wrapping the subfsal handle in a nullfs handle. */ return nullfs_alloc_and_check_handle(export, sub_handle, dir_hdl->fs, new_obj, status); } static fsal_status_t readsymlink(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *link_content, bool refresh) { struct nullfs_fsal_obj_handle *handle = (struct nullfs_fsal_obj_handle *)obj_hdl; struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->readlink( handle->sub_handle, link_content, refresh); op_ctx->fsal_export = &export->export; return status; } static fsal_status_t linkfile(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *destdir_hdl, const char *name, struct fsal_attrlist *destdir_pre_attrs_out, struct fsal_attrlist *destdir_post_attrs_out) { struct nullfs_fsal_obj_handle *handle = (struct nullfs_fsal_obj_handle *)obj_hdl; struct nullfs_fsal_obj_handle *nullfs_dir = (struct nullfs_fsal_obj_handle *)destdir_hdl; struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->link( handle->sub_handle, nullfs_dir->sub_handle, name, destdir_pre_attrs_out, destdir_post_attrs_out); op_ctx->fsal_export = &export->export; return status; } /** * Callback function for read_dirents. * * See fsal_readdir_cb type for more details. * * This function restores the context for the upper stacked fsal or inode. * * @param name Directly passed to upper layer. * @param dir_state A nullfs_readdir_state struct. * @param cookie Directly passed to upper layer. * * @return Result coming from the upper layer. */ static enum fsal_dir_result nullfs_readdir_cb(const char *name, struct fsal_obj_handle *sub_handle, struct fsal_attrlist *attrs, void *dir_state, fsal_cookie_t cookie) { struct nullfs_readdir_state *state = (struct nullfs_readdir_state *)dir_state; struct fsal_obj_handle *new_obj; if (FSAL_IS_ERROR(nullfs_alloc_and_check_handle( state->exp, sub_handle, sub_handle->fs, &new_obj, fsalstat(ERR_FSAL_NO_ERROR, 0)))) { return false; } op_ctx->fsal_export = &state->exp->export; enum fsal_dir_result result = state->cb(name, new_obj, attrs, state->dir_state, cookie); op_ctx->fsal_export = state->exp->export.sub_export; return result; } /** * read_dirents * read the directory and call through the callback function for * each entry. * @param dir_hdl [IN] the directory to read * @param whence [IN] where to start (next) * @param dir_state [IN] pass thru of state to callback * @param cb [IN] callback function * @param eof [OUT] eof marker true == end of dir */ static fsal_status_t read_dirents(struct fsal_obj_handle *dir_hdl, fsal_cookie_t *whence, void *dir_state, fsal_readdir_cb cb, attrmask_t attrmask, bool *eof) { struct nullfs_fsal_obj_handle *handle = container_of( dir_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); struct nullfs_readdir_state cb_state = { .cb = cb, .dir_state = dir_state, .exp = export }; /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->readdir( handle->sub_handle, whence, &cb_state, nullfs_readdir_cb, attrmask, eof); op_ctx->fsal_export = &export->export; return status; } /** * @brief Compute the readdir cookie for a given filename. * * Some FSALs are able to compute the cookie for a filename deterministically * from the filename. They also have a defined order of entries in a directory * based on the name (could be strcmp sort, could be strict alpha sort, could * be deterministic order based on cookie - in any case, the dirent_cmp method * will also be provided. * * The returned cookie is the cookie that can be passed as whence to FIND that * directory entry. This is different than the cookie passed in the readdir * callback (which is the cookie of the NEXT entry). * * @param[in] parent Directory file name belongs to. * @param[in] name File name to produce the cookie for. * * @retval 0 if not supported. * @returns The cookie value. */ fsal_cookie_t compute_readdir_cookie(struct fsal_obj_handle *parent, const char *name) { fsal_cookie_t cookie; struct nullfs_fsal_obj_handle *handle = container_of(parent, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; cookie = handle->sub_handle->obj_ops->compute_readdir_cookie( handle->sub_handle, name); op_ctx->fsal_export = &export->export; return cookie; } /** * @brief Help sort dirents. * * For FSALs that are able to compute the cookie for a filename * deterministically from the filename, there must also be a defined order of * entries in a directory based on the name (could be strcmp sort, could be * strict alpha sort, could be deterministic order based on cookie). * * Although the cookies could be computed, the caller will already have them * and thus will provide them to save compute time. * * @param[in] parent Directory entries belong to. * @param[in] name1 File name of first dirent * @param[in] cookie1 Cookie of first dirent * @param[in] name2 File name of second dirent * @param[in] cookie2 Cookie of second dirent * * @retval < 0 if name1 sorts before name2 * @retval == 0 if name1 sorts the same as name2 * @retval >0 if name1 sorts after name2 */ int dirent_cmp(struct fsal_obj_handle *parent, const char *name1, fsal_cookie_t cookie1, const char *name2, fsal_cookie_t cookie2) { int rc; struct nullfs_fsal_obj_handle *handle = container_of(parent, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; rc = handle->sub_handle->obj_ops->dirent_cmp(handle->sub_handle, name1, cookie1, name2, cookie2); op_ctx->fsal_export = &export->export; return rc; } static fsal_status_t renamefile(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *olddir_hdl, const char *old_name, struct fsal_obj_handle *newdir_hdl, const char *new_name, struct fsal_attrlist *olddir_pre_attrs_out, struct fsal_attrlist *olddir_post_attrs_out, struct fsal_attrlist *newdir_pre_attrs_out, struct fsal_attrlist *newdir_post_attrs_out) { struct nullfs_fsal_obj_handle *nullfs_olddir = container_of( olddir_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_obj_handle *nullfs_newdir = container_of( newdir_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_obj_handle *nullfs_obj = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = nullfs_olddir->sub_handle->obj_ops->rename( nullfs_obj->sub_handle, nullfs_olddir->sub_handle, old_name, nullfs_newdir->sub_handle, new_name, olddir_pre_attrs_out, olddir_post_attrs_out, newdir_pre_attrs_out, newdir_post_attrs_out); op_ctx->fsal_export = &export->export; return status; } static fsal_status_t getattrs(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrib_get) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->getattrs( handle->sub_handle, attrib_get); op_ctx->fsal_export = &export->export; return status; } static fsal_status_t nullfs_setattr2(struct fsal_obj_handle *obj_hdl, bool bypass, struct state_t *state, struct fsal_attrlist *attrs) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->setattr2( handle->sub_handle, bypass, state, attrs); op_ctx->fsal_export = &export->export; return status; } /* file_unlink * unlink the named file in the directory */ static fsal_status_t file_unlink(struct fsal_obj_handle *dir_hdl, struct fsal_obj_handle *obj_hdl, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct nullfs_fsal_obj_handle *nullfs_dir = container_of( dir_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_obj_handle *nullfs_obj = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = nullfs_dir->sub_handle->obj_ops->unlink( nullfs_dir->sub_handle, nullfs_obj->sub_handle, name, parent_pre_attrs_out, parent_post_attrs_out); op_ctx->fsal_export = &export->export; return status; } /* handle_to_wire * fill in the opaque f/s file handle part. * we zero the buffer to length first. This MAY already be done above * at which point, remove memset here because the caller is zeroing * the whole struct. */ static fsal_status_t handle_to_wire(const struct fsal_obj_handle *obj_hdl, fsal_digesttype_t output_type, struct gsh_buffdesc *fh_desc) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->handle_to_wire( handle->sub_handle, output_type, fh_desc); op_ctx->fsal_export = &export->export; return status; } /** * handle_to_key * return a handle descriptor into the handle in this object handle * @TODO reminder. make sure things like hash keys don't point here * after the handle is released. */ static void handle_to_key(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *fh_desc) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; handle->sub_handle->obj_ops->handle_to_key(handle->sub_handle, fh_desc); op_ctx->fsal_export = &export->export; } /** * @brief release object handle * * release our handle first so they know we are gone */ static void release(struct fsal_obj_handle *obj_hdl) { struct nullfs_fsal_obj_handle *hdl = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; hdl->sub_handle->obj_ops->release(hdl->sub_handle); op_ctx->fsal_export = &export->export; /* cleaning data allocated by nullfs */ fsal_obj_handle_fini(&hdl->obj_handle, true); gsh_free(hdl); } static bool nullfs_is_referral(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs, bool cache_attrs) { struct nullfs_fsal_obj_handle *hdl = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); bool result; /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; result = hdl->sub_handle->obj_ops->is_referral(hdl->sub_handle, attrs, cache_attrs); op_ctx->fsal_export = &export->export; return result; } void nullfs_handle_ops_init(struct fsal_obj_ops *ops) { fsal_default_obj_ops_init(ops); ops->release = release; ops->lookup = lookup; ops->readdir = read_dirents; ops->compute_readdir_cookie = compute_readdir_cookie, ops->dirent_cmp = dirent_cmp, ops->mkdir = makedir; ops->mknode = makenode; ops->symlink = makesymlink; ops->readlink = readsymlink; ops->getattrs = getattrs; ops->link = linkfile; ops->rename = renamefile; ops->unlink = file_unlink; ops->close = nullfs_close; ops->handle_to_wire = handle_to_wire; ops->handle_to_key = handle_to_key; /* Multi-FD */ ops->open2 = nullfs_open2; ops->check_verifier = nullfs_check_verifier; ops->status2 = nullfs_status2; ops->reopen2 = nullfs_reopen2; ops->read2 = nullfs_read2; ops->write2 = nullfs_write2; ops->seek2 = nullfs_seek2; ops->io_advise2 = nullfs_io_advise2; ops->commit2 = nullfs_commit2; ops->lock_op2 = nullfs_lock_op2; ops->setattr2 = nullfs_setattr2; ops->close2 = nullfs_close2; ops->fallocate = nullfs_fallocate; /* xattr related functions */ ops->list_ext_attrs = nullfs_list_ext_attrs; ops->getextattr_id_by_name = nullfs_getextattr_id_by_name; ops->getextattr_value_by_name = nullfs_getextattr_value_by_name; ops->getextattr_value_by_id = nullfs_getextattr_value_by_id; ops->setextattr_value = nullfs_setextattr_value; ops->setextattr_value_by_id = nullfs_setextattr_value_by_id; ops->remove_extattr_by_id = nullfs_remove_extattr_by_id; ops->remove_extattr_by_name = nullfs_remove_extattr_by_name; ops->is_referral = nullfs_is_referral; } /* export methods that create object handles */ /* lookup_path * modeled on old api except we don't stuff attributes. * KISS */ fsal_status_t nullfs_lookup_path(struct fsal_export *exp_hdl, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { /** Handle given by the subfsal. */ struct fsal_obj_handle *sub_handle = NULL; *handle = NULL; /* call underlying FSAL ops with underlying FSAL handle */ struct nullfs_fsal_export *exp = container_of(exp_hdl, struct nullfs_fsal_export, export); /* call to subfsal lookup with the good context. */ fsal_status_t status; op_ctx->fsal_export = exp->export.sub_export; status = exp->export.sub_export->exp_ops.lookup_path( exp->export.sub_export, path, &sub_handle, attrs_out); op_ctx->fsal_export = &exp->export; /* wrapping the subfsal handle in a nullfs handle. */ /* Note : nullfs filesystem = subfsal filesystem or NULL ? */ return nullfs_alloc_and_check_handle(exp, sub_handle, NULL, handle, status); } /* create_handle * Does what original FSAL_ExpandHandle did (sort of) * returns a ref counted handle to be later used in mdcache etc. * NOTE! you must release this thing when done with it! * BEWARE! Thanks to some holes in the *AT syscalls implementation, * we cannot get an fd on an AF_UNIX socket, nor reliably on block or * character special devices. Sorry, it just doesn't... * we could if we had the handle of the dir it is in, but this method * is for getting handles off the wire for cache entries that have LRU'd. * Ideas and/or clever hacks are welcome... */ fsal_status_t nullfs_create_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *hdl_desc, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { /** Current nullfs export. */ struct nullfs_fsal_export *export = container_of(exp_hdl, struct nullfs_fsal_export, export); struct fsal_obj_handle *sub_handle; /*< New subfsal handle.*/ *handle = NULL; /* call to subfsal lookup with the good context. */ fsal_status_t status; op_ctx->fsal_export = export->export.sub_export; status = export->export.sub_export->exp_ops.create_handle( export->export.sub_export, hdl_desc, &sub_handle, attrs_out); op_ctx->fsal_export = &export->export; /* wrapping the subfsal handle in a nullfs handle. */ /* Note : nullfs filesystem = subfsal filesystem or NULL ? */ return nullfs_alloc_and_check_handle(export, sub_handle, NULL, handle, status); } nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_NULL/main.c000066400000000000000000000102271473756622300224430ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * */ /* main.c * Module core functions */ #include "config.h" #include "fsal.h" #include /* used for 'dirname' */ #include #include #include #include #include "gsh_list.h" #include "FSAL/fsal_init.h" #include "nullfs_methods.h" /* FSAL name determines name of shared library: libfsal.so */ static const char myname[] = "NULL"; /* my module private storage */ struct null_fsal_module NULLFS = { .module = { .fs_info = { .maxfilesize = UINT64_MAX, .maxlink = _POSIX_LINK_MAX, .maxnamelen = 1024, .maxpathlen = 1024, .no_trunc = true, .chown_restricted = true, .case_insensitive = false, .case_preserving = true, .link_support = true, .symlink_support = true, .lock_support = true, .lock_support_async_block = false, .named_attr = true, .unique_handles = true, .acl_support = FSAL_ACLSUPPORT_ALLOW, .cansettime = true, .homogenous = true, .supported_attrs = ALL_ATTRIBUTES, .maxread = FSAL_MAXIOSIZE, .maxwrite = FSAL_MAXIOSIZE, .umask = 0, .auth_exportpath_xdev = false, .link_supports_permission_checks = true, .expire_time_parent = -1, } } }; /* Module methods */ /* init_config * must be called with a reference taken (via lookup_fsal) */ static fsal_status_t init_config(struct fsal_module *nullfs_fsal_module, config_file_t config_struct, struct config_error_type *err_type) { /* Configuration setting options: * 1. there are none that are changeable. (this case) * * 2. we set some here. These must be independent of whatever * may be set by lower level fsals. * * If there is any filtering or change of parameters in the stack, * this must be done in export data structures, not fsal params because * a stackable could be configured above multiple fsals for multiple * diverse exports. */ display_fsinfo(nullfs_fsal_module); LogDebug(COMPONENT_FSAL, "FSAL INIT: Supported attributes mask = 0x%" PRIx64, nullfs_fsal_module->fs_info.supported_attrs); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* Module initialization. * Called by dlopen() to register the module * keep a private pointer to me in myself */ /* linkage to the exports and handle ops initializers */ MODULE_INIT void nullfs_init(void) { int retval; struct fsal_module *myself = &NULLFS.module; retval = register_fsal(myself, myname, FSAL_MAJOR_VERSION, FSAL_MINOR_VERSION, FSAL_ID_NO_PNFS); if (retval != 0) { fprintf(stderr, "NULLFS module failed to register"); return; } myself->m_ops.create_export = nullfs_create_export; myself->m_ops.update_export = nullfs_update_export; myself->m_ops.init_config = init_config; /* Initialize the fsal_obj_handle ops for FSAL NULL */ nullfs_handle_ops_init(&NULLFS.handle_ops); } MODULE_FINI void nullfs_unload(void) { int retval; retval = unregister_fsal(&NULLFS.module); if (retval != 0) { fprintf(stderr, "NULLFS module failed to unregister"); return; } } nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_NULL/nullfs_methods.h000066400000000000000000000165021473756622300245540ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * @brief NULLFS methods for handles */ /* NULLFS methods for handles */ #ifndef NULLFS_METHODS_H #define NULLFS_METHODS_H struct null_fsal_module { struct fsal_module module; struct fsal_obj_ops handle_ops; }; extern struct null_fsal_module NULLFS; struct nullfs_fsal_obj_handle; /* Internal NULLFS method linkage to export object */ fsal_status_t nullfs_create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops); fsal_status_t nullfs_update_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, struct fsal_export *original, struct fsal_module *updated_super); /** * Structure used to store data for read_dirents callback. * * Before executing the upper level callback (it might be another * stackable fsal or the inode cache), the context has to be restored. */ struct nullfs_readdir_state { fsal_readdir_cb cb; /*< Callback to the upper layer. */ struct nullfs_fsal_export *exp; /*< Export of the current nullfsal. */ void *dir_state; /*< State to be sent to the next callback. */ }; extern struct fsal_up_vector fsal_up_top; void nullfs_handle_ops_init(struct fsal_obj_ops *ops); /* * NULLFS internal export */ struct nullfs_fsal_export { struct fsal_export export; /* Other private export data goes here */ }; fsal_status_t nullfs_lookup_path(struct fsal_export *exp_hdl, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); fsal_status_t nullfs_create_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *hdl_desc, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); fsal_status_t nullfs_alloc_and_check_handle(struct nullfs_fsal_export *export, struct fsal_obj_handle *sub_handle, struct fsal_filesystem *fs, struct fsal_obj_handle **new_handle, fsal_status_t subfsal_status); /* * NULLFS internal object handle * * It contains a pointer to the fsal_obj_handle used by the subfsal. * * AF_UNIX sockets are strange ducks. I personally cannot see why they * are here except for the ability of a client to see such an animal with * an 'ls' or get rid of one with an 'rm'. You can't open them in the * usual file way so open_by_handle_at leads to a deadend. To work around * this, we save the args that were used to mknod or lookup the socket. */ struct nullfs_fsal_obj_handle { struct fsal_obj_handle obj_handle; /*< Handle containing nullfs data.*/ struct fsal_obj_handle *sub_handle; /*< Handle of the sub fsal.*/ int32_t refcnt; /*< Reference count. This is signed to make mistakes easy to see. */ }; int nullfs_fsal_open(struct nullfs_fsal_obj_handle *, int, fsal_errors_t *); int nullfs_fsal_readlink(struct nullfs_fsal_obj_handle *, fsal_errors_t *); static inline bool nullfs_unopenable_type(object_file_type_t type) { if ((type == SOCKET_FILE) || (type == CHARACTER_FILE) || (type == BLOCK_FILE)) { return true; } else { return false; } } /* I/O management */ fsal_status_t nullfs_close(struct fsal_obj_handle *obj_hdl); /* Multi-FD */ fsal_status_t nullfs_open2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attrs_in, fsal_verifier_t verifier, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out); bool nullfs_check_verifier(struct fsal_obj_handle *obj_hdl, fsal_verifier_t verifier); fsal_openflags_t nullfs_status2(struct fsal_obj_handle *obj_hdl, struct state_t *state); fsal_status_t nullfs_reopen2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags); void nullfs_read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg); void nullfs_write2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *caller_arg); fsal_status_t nullfs_seek2(struct fsal_obj_handle *obj_hdl, struct state_t *state, struct io_info *info); fsal_status_t nullfs_io_advise2(struct fsal_obj_handle *obj_hdl, struct state_t *state, struct io_hints *hints); fsal_status_t nullfs_commit2(struct fsal_obj_handle *obj_hdl, off_t offset, size_t len); fsal_status_t nullfs_lock_op2(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *p_owner, fsal_lock_op_t lock_op, fsal_lock_param_t *req_lock, fsal_lock_param_t *conflicting_lock); fsal_status_t nullfs_close2(struct fsal_obj_handle *obj_hdl, struct state_t *state); fsal_status_t nullfs_fallocate(struct fsal_obj_handle *obj_hdl, struct state_t *state, uint64_t offset, uint64_t length, bool allocate); /* extended attributes management */ fsal_status_t nullfs_list_ext_attrs(struct fsal_obj_handle *obj_hdl, unsigned int cookie, fsal_xattrent_t *xattrs_tab, unsigned int xattrs_tabsize, unsigned int *p_nb_returned, int *end_of_list); fsal_status_t nullfs_getextattr_id_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, unsigned int *pxattr_id); fsal_status_t nullfs_getextattr_value_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, size_t *p_output_size); fsal_status_t nullfs_getextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buffer_addr, size_t buffer_size, size_t *p_output_size); fsal_status_t nullfs_setextattr_value(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, int create); fsal_status_t nullfs_setextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buffer_addr, size_t buffer_size); fsal_status_t nullfs_remove_extattr_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id); fsal_status_t nullfs_remove_extattr_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name); #endif /* NULLFS_METHODS_H */ nfs-ganesha-6.5/src/FSAL/Stackable_FSALs/FSAL_NULL/xattrs.c000066400000000000000000000151431473756622300230460ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * */ /* xattrs.c * NULL object (file|dir) handle object extended attributes */ #include "config.h" #include "fsal.h" #include /* used for 'dirname' */ #include #include #include #include #include "os/xattr.h" #include "gsh_list.h" #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "nullfs_methods.h" fsal_status_t nullfs_list_ext_attrs(struct fsal_obj_handle *obj_hdl, unsigned int argcookie, fsal_xattrent_t *xattrs_tab, unsigned int xattrs_tabsize, unsigned int *p_nb_returned, int *end_of_list) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->list_ext_attrs( handle->sub_handle, argcookie, xattrs_tab, xattrs_tabsize, p_nb_returned, end_of_list); op_ctx->fsal_export = &export->export; return status; } fsal_status_t nullfs_getextattr_id_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, unsigned int *pxattr_id) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->getextattr_id_by_name( handle->sub_handle, xattr_name, pxattr_id); op_ctx->fsal_export = &export->export; return status; } fsal_status_t nullfs_getextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buffer_addr, size_t buffer_size, size_t *p_output_size) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->getextattr_value_by_id( handle->sub_handle, xattr_id, buffer_addr, buffer_size, p_output_size); op_ctx->fsal_export = &export->export; return status; } fsal_status_t nullfs_getextattr_value_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, size_t *p_output_size) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->getextattr_value_by_name( handle->sub_handle, xattr_name, buffer_addr, buffer_size, p_output_size); op_ctx->fsal_export = &export->export; return status; } fsal_status_t nullfs_setextattr_value(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, int create) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->setextattr_value( handle->sub_handle, xattr_name, buffer_addr, buffer_size, create); op_ctx->fsal_export = &export->export; return status; } fsal_status_t nullfs_setextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buffer_addr, size_t buffer_size) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->setextattr_value_by_id( handle->sub_handle, xattr_id, buffer_addr, buffer_size); op_ctx->fsal_export = &export->export; return status; } fsal_status_t nullfs_remove_extattr_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->remove_extattr_by_id( handle->sub_handle, xattr_id); op_ctx->fsal_export = &export->export; return status; } fsal_status_t nullfs_remove_extattr_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name) { struct nullfs_fsal_obj_handle *handle = container_of( obj_hdl, struct nullfs_fsal_obj_handle, obj_handle); struct nullfs_fsal_export *export = container_of( op_ctx->fsal_export, struct nullfs_fsal_export, export); /* calling subfsal method */ op_ctx->fsal_export = export->export.sub_export; fsal_status_t status = handle->sub_handle->obj_ops->remove_extattr_by_name( handle->sub_handle, xattr_name); op_ctx->fsal_export = &export->export; return status; } nfs-ganesha-6.5/src/FSAL/access_check.c000066400000000000000000000607141473756622300176630ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:expandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @addtogroup FSAL * @{ */ /** * @file access_check.c * @brief File/object access checking */ #include "config.h" #include "fsal.h" #include "nfs_core.h" #include #include "FSAL/access_check.h" #include #include #include #include #include #include static bool fsal_check_ace_owner(uid_t uid, struct user_cred *creds) { return (creds->caller_uid == uid); } static bool fsal_check_ace_group(gid_t gid, struct user_cred *creds) { int i; if (creds->caller_gid == gid) return true; for (i = 0; i < creds->caller_glen; i++) { if (creds->caller_garray[i] == gid) return true; } return false; } static bool fsal_check_ace_matches(fsal_ace_t *pace, struct user_cred *creds, bool is_owner, bool is_group) { bool result = false; char *cause = ""; if (IS_FSAL_ACE_SPECIAL_ID(*pace)) switch (pace->who.uid) { case FSAL_ACE_SPECIAL_OWNER: if (is_owner) { result = true; cause = "special owner"; } break; case FSAL_ACE_SPECIAL_GROUP: if (is_group) { result = true; cause = "special group"; } break; case FSAL_ACE_SPECIAL_EVERYONE: result = true; cause = "special everyone"; break; default: break; } else if (IS_FSAL_ACE_GROUP_ID(*pace)) { if (fsal_check_ace_group(pace->who.gid, creds)) { result = true; cause = "group"; } } else { if (fsal_check_ace_owner(pace->who.uid, creds)) { result = true; cause = "owner"; } } LogFullDebug(COMPONENT_NFS_V4_ACL, "result: %d, cause: %s, flag: 0x%X, who: %d", result, cause, pace->flag, GET_FSAL_ACE_WHO(*pace)); return result; } static bool fsal_check_ace_applicable(fsal_ace_t *pace, struct user_cred *creds, bool is_dir, bool is_owner, bool is_group, bool is_root) { bool is_applicable = false; bool is_file = !is_dir; /* To be applicable, the entry should not be INHERIT_ONLY. */ if (IS_FSAL_ACE_INHERIT_ONLY(*pace)) { LogFullDebug(COMPONENT_NFS_V4_ACL, "Not applicable, inherit only"); return false; } /* Use internal flag to further check the entry is applicable * to this object type. */ if (is_file) { if (!IS_FSAL_FILE_APPLICABLE(*pace)) { LogFullDebug(COMPONENT_NFS_V4_ACL, "Not applicable to file"); return false; } } else { /* directory */ if (!IS_FSAL_DIR_APPLICABLE(*pace)) { LogFullDebug(COMPONENT_NFS_V4_ACL, "Not applicable to dir"); return false; } } /* The user should match who value. */ is_applicable = is_root || fsal_check_ace_matches(pace, creds, is_owner, is_group); if (is_applicable) LogFullDebug(COMPONENT_NFS_V4_ACL, "Applicable, flag=0X%x", pace->flag); else LogFullDebug(COMPONENT_NFS_V4_ACL, "Not applicable to given user"); return is_applicable; } static const char *fsal_ace_type(fsal_acetype_t type) { switch (type) { case FSAL_ACE_TYPE_ALLOW: return "A"; case FSAL_ACE_TYPE_DENY: return "D "; case FSAL_ACE_TYPE_AUDIT: return "U"; case FSAL_ACE_TYPE_ALARM: return "L"; default: return "unknown"; } } static const char *fsal_ace_perm(fsal_aceperm_t perm) { static char buf[64]; char *c = buf; if (perm & FSAL_ACE_PERM_READ_DATA) *c++ = 'r'; if (perm & FSAL_ACE_PERM_WRITE_DATA) *c++ = 'w'; if (perm & FSAL_ACE_PERM_APPEND_DATA) *c++ = 'a'; if (perm & FSAL_ACE_PERM_EXECUTE) *c++ = 'x'; if (perm & FSAL_ACE_PERM_DELETE) *c++ = 'd'; if (perm & FSAL_ACE_PERM_DELETE_CHILD) *c++ = 'D'; if (perm & FSAL_ACE_PERM_READ_ATTR) *c++ = 't'; if (perm & FSAL_ACE_PERM_WRITE_ATTR) *c++ = 'T'; if (perm & FSAL_ACE_PERM_READ_NAMED_ATTR) *c++ = 'n'; if (perm & FSAL_ACE_PERM_WRITE_NAMED_ATTR) *c++ = 'N'; if (perm & FSAL_ACE_PERM_READ_ACL) *c++ = 'c'; if (perm & FSAL_ACE_PERM_WRITE_ACL) *c++ = 'C'; if (perm & FSAL_ACE_PERM_WRITE_OWNER) *c++ = 'o'; if (perm & FSAL_ACE_PERM_SYNCHRONIZE) *c++ = 'y'; *c = '\0'; return buf; } static const char *fsal_ace_flag(char *buf, fsal_aceflag_t flag) { char *c = buf; if (flag & FSAL_ACE_FLAG_GROUP_ID) *c++ = 'g'; if (flag & FSAL_ACE_FLAG_FILE_INHERIT) *c++ = 'f'; if (flag & FSAL_ACE_FLAG_DIR_INHERIT) *c++ = 'd'; if (flag & FSAL_ACE_FLAG_NO_PROPAGATE) *c++ = 'n'; if (flag & FSAL_ACE_FLAG_INHERIT_ONLY) *c++ = 'i'; if (flag & FSAL_ACE_FLAG_SUCCESSFUL) *c++ = 'S'; if (flag & FSAL_ACE_FLAG_FAILED) *c++ = 'F'; if (flag & FSAL_ACE_FLAG_INHERITED) *c++ = 'I'; if (flag & FSAL_ACE_IFLAG_EXCLUDE_FILES) *c++ = 'x'; if (flag & FSAL_ACE_IFLAG_EXCLUDE_DIRS) *c++ = 'X'; if (flag & FSAL_ACE_IFLAG_SPECIAL_ID) *c++ = 'S'; *c = '\0'; return buf; } void fsal_print_ace_int(log_components_t component, log_levels_t debug, fsal_ace_t *ace, char *file, int line, char *function) { char fbuf[16]; char ibuf[16]; if (!isLevel(component, debug)) return; DisplayLogComponentLevel(component, file, line, function, debug, "ACE %s:%s(%s):%u:%s", fsal_ace_type(ace->type), fsal_ace_flag(fbuf, ace->flag), fsal_ace_flag(ibuf, ace->iflag), ace->who.uid, fsal_ace_perm(ace->perm)); } void fsal_print_acl_int(log_components_t component, log_levels_t debug, fsal_acl_t *acl, char *file, int line, char *function) { fsal_ace_t *ace = NULL; if (!isLevel(component, debug)) return; DisplayLogComponentLevel(component, file, line, function, debug, "ACL naces: %u aces:", acl->naces); for (ace = acl->aces; ace < acl->aces + acl->naces; ace++) fsal_print_ace_int(component, debug, ace, file, line, function); } int display_fsal_inherit_flags(struct display_buffer *dspbuf, fsal_ace_t *pace) { if (!pace) return display_cat(dspbuf, "NULL"); return display_printf( dspbuf, "Inherit:%s%s%s%s", IS_FSAL_ACE_FILE_INHERIT(*pace) ? " file" : "", IS_FSAL_ACE_DIR_INHERIT(*pace) ? " dir" : "", IS_FSAL_ACE_INHERIT_ONLY(*pace) ? " inherit_only" : "", IS_FSAL_ACE_NO_PROPAGATE(*pace) ? " no_propagate" : ""); } int display_fsal_ace(struct display_buffer *dspbuf, int ace_number, fsal_ace_t *pace, bool is_dir) { int b_left; if (!pace) return display_cat(dspbuf, "ACE: "); /* Print the entire ACE. */ b_left = display_printf(dspbuf, "ACE %d:", ace_number); /* ACE type. */ if (b_left > 0) b_left = display_cat(dspbuf, IS_FSAL_ACE_ALLOW(*pace) ? " allow" : IS_FSAL_ACE_DENY(*pace) ? " deny" : IS_FSAL_ACE_AUDIT(*pace) ? " audit" : " ?"); /* ACE who and its type. */ if (b_left > 0 && IS_FSAL_ACE_SPECIAL_ID(*pace)) b_left = display_cat( dspbuf, IS_FSAL_ACE_SPECIAL_OWNER(*pace) ? " owner@" : IS_FSAL_ACE_SPECIAL_GROUP(*pace) ? " group@" : IS_FSAL_ACE_SPECIAL_EVERYONE(*pace) ? " everyone@" : ""); if (b_left > 0 && !IS_FSAL_ACE_SPECIAL_ID(*pace)) { if (IS_FSAL_ACE_GROUP_ID(*pace)) b_left = display_printf(dspbuf, " gid %d", pace->who.gid); else b_left = display_printf(dspbuf, " uid %d", pace->who.uid); } /* ACE mask. */ if (b_left > 0) b_left = display_fsal_v4mask(dspbuf, pace->perm, is_dir); /* ACE Inherit flags. */ if (b_left > 0 && IS_FSAL_ACE_INHERIT(*pace)) b_left = display_fsal_inherit_flags(dspbuf, pace); return b_left; } int display_fsal_v4mask(struct display_buffer *dspbuf, fsal_aceperm_t v4mask, bool is_dir) { int b_left = display_printf(dspbuf, "0x%06x", v4mask); if (b_left > 0 && IS_FSAL_ACE_BIT(v4mask, FSAL_ACE_PERM_READ_DATA)) b_left = display_cat(dspbuf, " READ"); if (b_left > 0 && IS_FSAL_ACE_BIT(v4mask, FSAL_ACE_PERM_WRITE_DATA) && is_dir) b_left = display_cat(dspbuf, " ADD_FILE"); if (b_left > 0 && IS_FSAL_ACE_BIT(v4mask, FSAL_ACE_PERM_WRITE_DATA) && !is_dir) b_left = display_cat(dspbuf, " WRITE"); if (b_left > 0 && IS_FSAL_ACE_BIT(v4mask, FSAL_ACE_PERM_APPEND_DATA) && is_dir) b_left = display_cat(dspbuf, " ADD_SUBDIR"); if (b_left > 0 && IS_FSAL_ACE_BIT(v4mask, FSAL_ACE_PERM_APPEND_DATA) && !is_dir) b_left = display_cat(dspbuf, " APPEND"); if (b_left > 0 && IS_FSAL_ACE_BIT(v4mask, FSAL_ACE_PERM_READ_NAMED_ATTR)) b_left = display_cat(dspbuf, " READ_NAMED"); if (b_left > 0 && IS_FSAL_ACE_BIT(v4mask, FSAL_ACE_PERM_WRITE_NAMED_ATTR)) b_left = display_cat(dspbuf, " WRITE_NAMED"); if (b_left > 0 && IS_FSAL_ACE_BIT(v4mask, FSAL_ACE_PERM_EXECUTE)) b_left = display_cat(dspbuf, " EXECUTE"); if (b_left > 0 && IS_FSAL_ACE_BIT(v4mask, FSAL_ACE_PERM_DELETE_CHILD)) b_left = display_cat(dspbuf, " DELETE_CHILD"); if (b_left > 0 && IS_FSAL_ACE_BIT(v4mask, FSAL_ACE_PERM_READ_ATTR)) b_left = display_cat(dspbuf, " READ_ATTR"); if (b_left > 0 && IS_FSAL_ACE_BIT(v4mask, FSAL_ACE_PERM_WRITE_ATTR)) b_left = display_cat(dspbuf, " WRITE_ATTR"); if (b_left > 0 && IS_FSAL_ACE_BIT(v4mask, FSAL_ACE_PERM_DELETE)) b_left = display_cat(dspbuf, " DELETE"); if (b_left > 0 && IS_FSAL_ACE_BIT(v4mask, FSAL_ACE_PERM_READ_ACL)) b_left = display_cat(dspbuf, " READ_ACL"); if (b_left > 0 && IS_FSAL_ACE_BIT(v4mask, FSAL_ACE_PERM_WRITE_ACL)) b_left = display_cat(dspbuf, " WRITE_ACL"); if (b_left > 0 && IS_FSAL_ACE_BIT(v4mask, FSAL_ACE_PERM_WRITE_OWNER)) b_left = display_cat(dspbuf, " WRITE_OWNER"); if (b_left > 0 && IS_FSAL_ACE_BIT(v4mask, FSAL_ACE_PERM_SYNCHRONIZE)) b_left = display_cat(dspbuf, " SYNCHRONIZE"); if (b_left > 0 && IS_FSAL_ACE_BIT(v4mask, FSAL_ACE4_PERM_CONTINUE)) b_left = display_cat(dspbuf, " CONTINUE"); return b_left; } static void fsal_print_access_by_acl(int naces, int ace_number, fsal_ace_t *pace, fsal_aceperm_t perm, enum fsal_errors_t access_result, bool is_dir, struct user_cred *creds) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; int b_left; if (!isFullDebug(COMPONENT_NFS_V4_ACL)) return; if (access_result == ERR_FSAL_NO_ERROR) b_left = display_cat(&dspbuf, "access granted"); else if (access_result == ERR_FSAL_PERM) b_left = display_cat(&dspbuf, "access denied (EPERM)"); else b_left = display_cat(&dspbuf, "access denied (EACCESS)"); if (b_left > 0) b_left = display_printf(&dspbuf, " uid %u gid %u Access req:", creds->caller_uid, creds->caller_gid); if (b_left > 0) b_left = display_fsal_v4mask(&dspbuf, perm, is_dir); if (b_left > 0 && (naces != ace_number)) b_left = display_fsal_ace(&dspbuf, ace_number, pace, is_dir); LogFullDebug(COMPONENT_NFS_V4_ACL, "%s", str); } /** * @brief Check access using v4 ACL list * * @param[in] creds * @param[in] v4mask * @param[in] allowed * @param[in] denied * @param[in] p_object_attributes * * @return ERR_FSAL_NO_ERROR, ERR_FSAL_ACCESS, or ERR_FSAL_NO_ACE */ static fsal_status_t fsal_check_access_acl(struct user_cred *creds, fsal_aceperm_t v4mask, fsal_accessflags_t *allowed, fsal_accessflags_t *denied, struct fsal_attrlist *p_object_attributes) { fsal_aceperm_t missing_access; fsal_aceperm_t tperm; uid_t uid; gid_t gid; fsal_acl_t *pacl = NULL; fsal_ace_t *pace = NULL; int ace_number = 0; bool is_dir = false; bool is_owner = false; bool is_group = false; bool is_root = false; if (allowed != NULL) *allowed = 0; if (denied != NULL) *denied = 0; if (!p_object_attributes->acl) { /* Means that FSAL_ACE4_REQ_FLAG was set, but no ACLs */ LogFullDebug(COMPONENT_NFS_V4_ACL, "Allow ACE required, but no ACLs"); return fsalstat(ERR_FSAL_NO_ACE, 0); } /* unsatisfied flags */ missing_access = v4mask & ~(FSAL_ACE4_PERM_CONTINUE | FSAL_ACE4_REQ_FLAG); if (!missing_access) { LogFullDebug(COMPONENT_NFS_V4_ACL, "Nothing was requested"); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* Get file ownership information. */ uid = p_object_attributes->owner; gid = p_object_attributes->group; pacl = p_object_attributes->acl; is_dir = (p_object_attributes->type == DIRECTORY); is_root = op_ctx->fsal_export->exp_ops.is_superuser(op_ctx->fsal_export, creds); if (is_root) { if (is_dir) { if (allowed != NULL) *allowed = v4mask; /* On a directory, allow root anything. */ LogFullDebug(COMPONENT_NFS_V4_ACL, "Met root privileges on directory"); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* Otherwise, allow root anything but execute. */ missing_access &= FSAL_ACE_PERM_EXECUTE; if (allowed != NULL) *allowed = v4mask & ~FSAL_ACE_PERM_EXECUTE; if (!missing_access) { LogFullDebug(COMPONENT_NFS_V4_ACL, "Met root privileges"); return fsalstat(ERR_FSAL_NO_ERROR, 0); } } LogFullDebug(COMPONENT_NFS_V4_ACL, "file acl=%p, file uid=%u, file gid=%u, ", pacl, uid, gid); if (isFullDebug(COMPONENT_NFS_V4_ACL)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; (void)display_fsal_v4mask(&dspbuf, v4mask, p_object_attributes->type == DIRECTORY); LogFullDebug(COMPONENT_NFS_V4_ACL, "user uid=%u, user gid= %u, v4mask=%s", creds->caller_uid, creds->caller_gid, str); } is_owner = fsal_check_ace_owner(uid, creds); is_group = fsal_check_ace_group(gid, creds); /* Always grant READ_ACL, WRITE_ACL and READ_ATTR, WRITE_ATTR * to the file owner. */ if (is_owner) { if (allowed != NULL) *allowed |= v4mask & (FSAL_ACE_PERM_WRITE_ACL | FSAL_ACE_PERM_READ_ACL | FSAL_ACE_PERM_WRITE_ATTR | FSAL_ACE_PERM_READ_ATTR); missing_access &= ~(FSAL_ACE_PERM_WRITE_ACL | FSAL_ACE_PERM_READ_ACL); missing_access &= ~(FSAL_ACE_PERM_WRITE_ATTR | FSAL_ACE_PERM_READ_ATTR); if (!missing_access) { LogFullDebug(COMPONENT_NFS_V4_ACL, "Met owner privileges"); return fsalstat(ERR_FSAL_NO_ERROR, 0); } } /** @todo Even if user is admin, audit/alarm checks should be done. */ for (pace = pacl->aces; pace < pacl->aces + pacl->naces; pace++) { ace_number += 1; LogFullDebug( COMPONENT_NFS_V4_ACL, "ace number: %d ace type 0x%X perm 0x%X flag 0x%X who %u", ace_number, pace->type, pace->perm, pace->flag, GET_FSAL_ACE_WHO(*pace)); /* Process Allow and Deny entries. */ if (!IS_FSAL_ACE_ALLOW(*pace) && !IS_FSAL_ACE_DENY(*pace)) { LogFullDebug(COMPONENT_NFS_V4_ACL, "not allow or deny"); continue; } LogFullDebug(COMPONENT_NFS_V4_ACL, "allow or deny"); /* Check if this ACE is applicable. */ if (fsal_check_ace_applicable(pace, creds, is_dir, is_owner, is_group, is_root)) { if (IS_FSAL_ACE_ALLOW(*pace)) { /* Do not set bits which are already denied */ if (denied) tperm = pace->perm & ~*denied; else tperm = pace->perm; LogFullDebug( COMPONENT_NFS_V4_ACL, "allow perm 0x%X remainingPerms 0x%X", tperm, missing_access); if (allowed != NULL) *allowed |= v4mask & tperm; missing_access &= ~(tperm & missing_access); if (!missing_access) { fsal_print_access_by_acl( pacl->naces, ace_number, pace, v4mask, ERR_FSAL_NO_ERROR, is_dir, creds); break; } } else if ((pace->perm & missing_access) && !is_root) { fsal_print_access_by_acl( pacl->naces, ace_number, pace, v4mask, #ifndef ENABLE_RFC_ACL (pace->perm & missing_access & (FSAL_ACE_PERM_WRITE_ATTR | FSAL_ACE_PERM_WRITE_ACL | FSAL_ACE_PERM_WRITE_OWNER)) != 0 ? ERR_FSAL_PERM : #endif /* ENABLE_RFC_ACL */ ERR_FSAL_ACCESS, is_dir, creds); if (denied != NULL) *denied |= v4mask & pace->perm; if (denied == NULL || (v4mask & FSAL_ACE4_PERM_CONTINUE) == 0) { #ifndef ENABLE_RFC_ACL if ((pace->perm & missing_access & (FSAL_ACE_PERM_WRITE_ATTR | FSAL_ACE_PERM_WRITE_ACL | FSAL_ACE_PERM_WRITE_OWNER)) != 0) { LogDebug( COMPONENT_NFS_V4_ACL, "access denied (EPERM)"); return fsalstat(ERR_FSAL_PERM, 0); } else { LogDebug( COMPONENT_NFS_V4_ACL, "access denied (EACCESS)"); return fsalstat(ERR_FSAL_ACCESS, 0); } #else /* ENABLE_RFC_ACL */ LogDebug(COMPONENT_NFS_V4_ACL, "access denied (EACCESS)"); return fsalstat(ERR_FSAL_ACCESS, 0); #endif /* ENABLE_RFC_ACL */ } missing_access &= ~(pace->perm & missing_access); /* If this DENY ACE blocked the last * remaining requested access * bits, break out of the loop because * we're done and don't * want to evaluate any more ACEs. */ if (!missing_access) break; } } } if (missing_access || (denied != NULL && *denied != 0)) { #ifndef ENABLE_RFC_ACL if ((missing_access & (FSAL_ACE_PERM_WRITE_ATTR | FSAL_ACE_PERM_WRITE_ACL | FSAL_ACE_PERM_WRITE_OWNER)) != 0) { LogDebug(COMPONENT_NFS_V4_ACL, "final access denied (EPERM)"); return fsalstat(ERR_FSAL_PERM, 0); } else { LogDebug(COMPONENT_NFS_V4_ACL, "final access denied (EACCESS)"); return fsalstat(ERR_FSAL_ACCESS, 0); } #else /* ENABLE_RFC_ACL */ LogDebug(COMPONENT_NFS_V4_ACL, "final access denied (EACCESS)"); return fsalstat(ERR_FSAL_ACCESS, 0); #endif /* ENABLE_RFC_ACL */ } else { LogFullDebug(COMPONENT_NFS_V4_ACL, "access granted"); return fsalstat(ERR_FSAL_NO_ERROR, 0); } } /** * @brief Check access using mode bits only * * @param[in] creds * @param[in] access_type * @param[in] allowed * @param[in] denied * @param[in] p_object_attributes * * @return ERR_FSAL_NO_ERROR or ERR_FSAL_ACCESS */ static fsal_status_t fsal_check_access_no_acl( struct user_cred *creds, fsal_accessflags_t access_type, fsal_accessflags_t *allowed, fsal_accessflags_t *denied, struct fsal_attrlist *p_object_attributes) { uid_t uid; gid_t gid; mode_t mode; fsal_accessflags_t mask; bool rc; if (allowed != NULL) *allowed = 0; if (denied != NULL) *denied = 0; if (!access_type) { LogFullDebug(COMPONENT_NFS_V4_ACL, "Nothing was requested"); return fsalstat(ERR_FSAL_NO_ERROR, 0); } uid = p_object_attributes->owner; gid = p_object_attributes->group; mode = p_object_attributes->mode; LogFullDebug( COMPONENT_NFS_V4_ACL, "file Mode=%#o, file uid=%u, file gid= %u, user uid=%u, user gid= %u, access_type=0X%x", mode, uid, gid, creds->caller_uid, creds->caller_gid, access_type); if (op_ctx->fsal_export->exp_ops.is_superuser(op_ctx->fsal_export, creds)) { if (p_object_attributes->type == DIRECTORY) { if (allowed != NULL) *allowed = access_type; LogFullDebug(COMPONENT_NFS_V4_ACL, "Root has full access on directories."); return fsalstat(ERR_FSAL_NO_ERROR, 0); } rc = ((access_type & FSAL_X_OK) == 0) || ((mode & (S_IXOTH | S_IXUSR | S_IXGRP)) != 0); if (!rc) { if (allowed != NULL) *allowed = access_type & ~FSAL_X_OK; if (denied != NULL) *denied = access_type & FSAL_X_OK; LogFullDebug( COMPONENT_NFS_V4_ACL, "Root is not allowed execute access unless at least one user is allowed execute access."); } else { if (allowed != NULL) *allowed = access_type; LogFullDebug(COMPONENT_NFS_V4_ACL, "Root is granted access."); } return rc ? fsalstat(ERR_FSAL_NO_ERROR, 0) : fsalstat(ERR_FSAL_ACCESS, 0); } /* If the uid of the file matches the uid of the user, * then the uid mode bits take precedence. */ if (creds->caller_uid == uid) { LogFullDebug(COMPONENT_NFS_V4_ACL, "Using owner mode %#o", mode & S_IRWXU); mode >>= 6; } else { /* followed by group(s) */ if (creds->caller_gid == gid) { LogFullDebug(COMPONENT_NFS_V4_ACL, "Using group mode %#o", mode & S_IRWXG); mode >>= 3; } else { /* Test if file belongs to alt user's groups */ int i; for (i = 0; i < creds->caller_glen; i++) { if (creds->caller_garray[i] == gid) { LogFullDebug( COMPONENT_NFS_V4_ACL, "Using group mode %#o for alt group #%d", mode & S_IRWXG, i); mode >>= 3; break; } } } } /* others fall out the bottom... */ /* Convert the shifted mode into an access_type mask */ mask = ((mode & S_IROTH) ? FSAL_R_OK : 0) | ((mode & S_IWOTH) ? FSAL_W_OK : 0) | ((mode & S_IXOTH) ? FSAL_X_OK : 0); LogFullDebug(COMPONENT_NFS_V4_ACL, "Mask=0X%x, Access Type=0X%x Allowed=0X%x Denied=0X%x %s", mask, access_type, mask & access_type, ~mask & access_type, (mask & access_type) == access_type ? "ALLOWED" : "DENIED"); if (allowed != NULL) *allowed = mask & access_type; if (denied != NULL) *denied = ~mask & access_type; /* Success if mask covers all the requested bits */ return (mask & access_type) == access_type ? fsalstat(ERR_FSAL_NO_ERROR, 0) : fsalstat(ERR_FSAL_ACCESS, 0); } /* test_access * common (default) access check method for fsal_obj_handle objects. * NOTE: A fsal can replace this method with their own custom access * checker. If so and they wish to have an option to switch * between their custom and this one, it their test_access * method's responsibility to do that test and select this one. */ fsal_status_t fsal_test_access(struct fsal_obj_handle *obj_hdl, fsal_accessflags_t access_type, fsal_accessflags_t *allowed, fsal_accessflags_t *denied, bool owner_skip) { struct fsal_attrlist attrs; fsal_status_t status; fsal_prepare_attrs(&attrs, op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) & (ATTRS_CREDS | ATTR_MODE | ATTR_ACL)); status = obj_hdl->obj_ops->getattrs(obj_hdl, &attrs); if (FSAL_IS_ERROR(status)) goto out; if (owner_skip && attrs.owner == op_ctx->creds.caller_uid) { status = fsalstat(ERR_FSAL_NO_ERROR, 0); goto out; } if (IS_FSAL_ACE4_REQ(access_type) || (attrs.acl != NULL && IS_FSAL_ACE4_MASK_VALID(access_type))) { status = fsal_check_access_acl(&op_ctx->creds, FSAL_ACE4_MASK(access_type), allowed, denied, &attrs); } else { /* fall back to use mode to check access. */ status = fsal_check_access_no_acl(&op_ctx->creds, FSAL_MODE_MASK(access_type), allowed, denied, &attrs); } out: /* Done with the attrs */ fsal_release_attrs(&attrs); return status; } uid_t ganesha_uid; gid_t ganesha_gid; int ganesha_ngroups; gid_t *ganesha_groups; #if GSH_CAN_HOST_LOCAL_FS void fsal_set_credentials(const struct user_cred *creds) { if (set_threadgroups(creds->caller_glen, creds->caller_garray) != 0) LogFatal(COMPONENT_FSAL, "set_threadgroups() returned %s (%d)", strerror(errno), errno); setgroup(creds->caller_gid); setuser(creds->caller_uid); } void fsal_restore_ganesha_credentials(void) { setuser(ganesha_uid); setgroup(ganesha_gid); if (set_threadgroups(ganesha_ngroups, ganesha_groups) != 0) LogFatal(COMPONENT_FSAL, "Could not set Ganesha credentials"); } #endif /* GSH_CAN_HOST_LOCAL_FS */ bool fsal_set_credentials_only_one_user(const struct user_cred *creds) { if (creds->caller_uid == ganesha_uid && creds->caller_gid == ganesha_gid) return true; else return false; } void fsal_save_ganesha_credentials(void) { int i, b_left; char buffer[1024]; struct display_buffer dspbuf = { sizeof(buffer), buffer, buffer }; ganesha_uid = getuser(); ganesha_gid = getgroup(); ganesha_ngroups = getgroups(0, NULL); if (ganesha_ngroups > 0) { ganesha_groups = gsh_malloc(ganesha_ngroups * sizeof(gid_t)); if (getgroups(ganesha_ngroups, ganesha_groups) != ganesha_ngroups) { LogFatal(COMPONENT_FSAL, "Could not get list of ganesha groups"); } } if (!isInfo(COMPONENT_FSAL)) return; b_left = display_printf(&dspbuf, "Ganesha uid=%d gid=%d ngroups=%d", (int)ganesha_uid, (int)ganesha_gid, ganesha_ngroups); if (b_left > 0 && ganesha_ngroups != 0) b_left = display_cat(&dspbuf, " ("); for (i = 0; b_left > 0 && i < ganesha_ngroups; i++) { b_left = display_printf(&dspbuf, "%s%d", i == 0 ? "" : " ", (int)ganesha_groups[i]); } if (b_left > 0 && ganesha_ngroups != 0) (void)display_cat(&dspbuf, ")"); LogInfo(COMPONENT_FSAL, "%s", buffer); } /** @} */ nfs-ganesha-6.5/src/FSAL/common_pnfs.c000066400000000000000000000452041473756622300176000ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2011 The Linux Box Corporation * Author: Adam C. Emerson * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @addtogroup FSAL * @{ */ #include "config.h" #include #include #include #include #include #include #include #include "log.h" #include "fsal.h" #include "fsal_pnfs.h" #include "pnfs_utils.h" #include "nfs4.h" #include "nfs_exports.h" #include "nfs_file_handle.h" #include "nfs_proto_functions.h" struct fsal_module *pnfs_fsal[FSAL_ID_COUNT]; /** * @file common_pnfs.c * @author Adam C. Emerson * @brief Utility functions for pNFS * * Utility functions expected to be used by more than one FSAL * * implementing pNFS. */ /* * Functions potentially useful to all MDSs of layout types */ /** * * @brief Encode/decode an fsal_deviceid_t * * The difference between this and xdr_deviceid4 is that this function * treats the deviceid as two 64-bit integers (putting them in network * byte order) while xdr_deviceid4 treats the deviceid as an opaque * string of 16 bytes. This function may be more convenient given * that we expect the high quad to be in network byte order and assign * significance to it in nfs4_op_getdeviceinfo. * * @param[in,out] xdrs The XDR stream * @param[in,out] deviceid The deviceid to encode/decode * * @retval true on success. * @retval false on failure. */ bool xdr_fsal_deviceid(XDR *xdrs, struct pnfs_deviceid *deviceid) { if (!xdr_opaque(xdrs, (char *)deviceid, NFS4_DEVICEID4_SIZE)) return false; return true; } /** * @brief Encode most IPv4 netaddrs * * This convenience function writes an encoded netaddr4 to an XDR * stream given a protocol, IP address, and port. * * @param[in,out] xdrs The XDR stream * @param[in] proto The protocol identifier. Currently this most * be one of 6 (TCP), 17 (UDP), or 132 (SCTP) * in host byte order * @param[in] addr The IPv4 address in host byte order * @param[in] port The port address in host byte order * * @return NFSv4 status codes. */ nfsstat4 FSAL_encode_ipv4_netaddr(XDR *xdrs, uint16_t proto, uint32_t addr, uint16_t port) { /* The address family mark string */ const char *mark = NULL; /* Six groups of up to three digits each, five dots, and a null */ const size_t v4_addrbuff_len = 24; /* The buffer to which we output the string form of the address */ char addrbuff[v4_addrbuff_len]; /* Pointer to the beginning of the buffer, the reference of which is passed to xdr_string */ char *buffptr = &addrbuff[0]; /* Return value from snprintf to check for overflow or error */ size_t written_length = 0; /* First, we output the correct netid for the protocol */ switch (proto) { case 6: mark = "tcp"; break; case 17: mark = "udp"; break; case 123: mark = "sctp"; break; default: LogCrit(COMPONENT_FSAL, "Caller supplied invalid protocol %u", proto); return NFS4ERR_SERVERFAULT; } if (!xdr_string(xdrs, (char **)&mark, 5)) { LogCrit(COMPONENT_FSAL, "Unable to encode protocol mark."); return NFS4ERR_SERVERFAULT; } /* Then we convert the address and port to a string and encode it. */ written_length = snprintf(addrbuff, v4_addrbuff_len, "%u.%u.%u.%u.%u.%u", (unsigned int)((addr & 0xff000000) >> 0x18), (unsigned int)((addr & 0x00ff0000) >> 0x10), (unsigned int)((addr & 0x0000ff00) >> 0x08), (unsigned int)(addr & 0x000000ff), (unsigned int)((port & 0xff00) >> 0x08), (unsigned int)(port & 0x00ff)); if (written_length >= v4_addrbuff_len) { LogCrit(COMPONENT_FSAL, "Programming error at %s:%u %s causing snprintf to overflow address buffer.", __FILE__, __LINE__, __func__); return NFS4ERR_SERVERFAULT; } if (!xdr_string(xdrs, &buffptr, v4_addrbuff_len)) { LogCrit(COMPONENT_FSAL, "Unable to encode address."); return NFS4ERR_SERVERFAULT; } return NFS4_OK; } /* * Functions specific to NFSV4_1_FILES layouts */ /** * * @brief Internal function to convert file handles * * This function creates a filehandle (that will be recognizes by * Ganesha as a DS filehandle) containing the supplied opaque. * * @param[in] fh_desc FSAL specific DS handle * @param[in] export_id Export ID (So we don't have to require that * the MDS export and DS export share IDs in all * cases.) * @param[out] v4_handle An nfs_fh4 descriptor for the handle. * * @return NFSv4 error codes */ static nfsstat4 make_file_handle_ds(const struct gsh_buffdesc *fh_desc, const uint16_t server_id, nfs_fh4 *wirehandle) { /* The v4_handle being constructed */ file_handle_v4_t *v4_handle = (file_handle_v4_t *)wirehandle->nfs_fh4_val; if ((offsetof(struct file_handle_v4, fsopaque) + fh_desc->len) > wirehandle->nfs_fh4_len) { LogMajor(COMPONENT_PNFS, "DS handle too big to encode!"); return NFS4ERR_SERVERFAULT; } wirehandle->nfs_fh4_len = offsetof(struct file_handle_v4, fsopaque) + fh_desc->len; v4_handle->fhversion = GANESHA_FH_VERSION; v4_handle->fs_len = fh_desc->len; memcpy(v4_handle->fsopaque, fh_desc->addr, fh_desc->len); v4_handle->id.servers = htons(server_id); #if (BYTE_ORDER == BIG_ENDIAN) v4_handle->fhflags1 = FILE_HANDLE_V4_FLAG_DS | FH_FSAL_BIG_ENDIAN; #else v4_handle->fhflags1 = FILE_HANDLE_V4_FLAG_DS; #endif return NFS4_OK; } /** * @brief Convenience function to encode loc_body * * This function allows the FSAL to encode an nfsv4_1_files_layout4 * without having to allocate and construct all the components of the * structure, including file handles. * * To encode a completed nfsv4_1_file_layout4 structure, call * xdr_nfsv4_1_file_layout4. * * @note This function encodes Ganesha data server handles in the * loc_body, it does not use the FSAL's DS handle unadorned. * * @param[out] xdrs XDR stream * @param[in] deviceid The deviceid for the layout * @param[in] util Stripe width and flags for the layout * @param[in] first_idx First stripe index * @param[in] ptrn_ofst Pattern offset * @param[in] ds_ids Server IDs of DSs for each file handle * @param[in] num_fhs Number of file handles in array * @param[in] fhs Array if buffer descriptors holding opaque DS * handles * @return NFS status codes. */ nfsstat4 FSAL_encode_file_layout(XDR *xdrs, const struct pnfs_deviceid *deviceid, nfl_util4 util, const uint32_t first_idx, const offset4 ptrn_ofst, const uint16_t *ds_ids, const uint32_t num_fhs, const struct gsh_buffdesc *fhs, const bool_t same_fh) { /* Index for traversing FH array */ size_t i = 0; /* NFS status code */ nfsstat4 nfs_status = 0; offset4 offset = ptrn_ofst; offset4 *p_ofst = &offset; if (!xdr_fsal_deviceid(xdrs, (struct pnfs_deviceid *)deviceid)) { LogMajor(COMPONENT_PNFS, "Failed encoding deviceid."); return NFS4ERR_SERVERFAULT; } if (!xdr_nfl_util4(xdrs, &util)) { LogMajor(COMPONENT_PNFS, "Failed encoding nfl_util4."); return NFS4ERR_SERVERFAULT; } if (!xdr_uint32_t(xdrs, (uint32_t *)&first_idx)) { LogMajor(COMPONENT_PNFS, "Failed encoding first_stripe_index."); return NFS4ERR_SERVERFAULT; } if (!xdr_offset4(xdrs, p_ofst)) { LogMajor(COMPONENT_PNFS, "Failed encoding pattern_offset."); return NFS4ERR_SERVERFAULT; } if (!xdr_uint32_t(xdrs, (int32_t *)&num_fhs)) { LogMajor(COMPONENT_PNFS, "Failed encoding length of FH array."); return NFS4ERR_SERVERFAULT; } for (i = 0; i < num_fhs; i++) { nfs_fh4 handle; char buffer[NFS4_FHSIZE]; handle.nfs_fh4_val = buffer; handle.nfs_fh4_len = sizeof(buffer); memset(buffer, 0, sizeof(buffer)); if (same_fh) nfs_status = make_file_handle_ds(fhs, *(ds_ids), &handle); else nfs_status = make_file_handle_ds(fhs + i, *(ds_ids + i), &handle); if (nfs_status != NFS4_OK) { LogMajor(COMPONENT_PNFS, "Failed converting FH %zu.", i); return nfs_status; } if (!xdr_bytes(xdrs, (char **)&handle.nfs_fh4_val, &handle.nfs_fh4_len, handle.nfs_fh4_len)) { LogMajor(COMPONENT_PNFS, "Failed encoding FH %zu.", i); return NFS4ERR_SERVERFAULT; } } return NFS4_OK; } /** * @brief Convenience function to encode one multipath_list * * This function writes a multipath list representation of an array of * hosts accessed through most IPv4 protocols. * * @param[in,out] xdrs The XDR stream * @param[in] num_hosts Number of hosts in array * @param[in] hosts Array of hosts * * @return NFSv4 Status code * */ nfsstat4 FSAL_encode_v4_multipath(XDR *xdrs, const uint32_t num_hosts, const fsal_multipath_member_t *hosts) { /* Index for traversing host array */ size_t i = 0; /* NFS status */ nfsstat4 nfs_status = 0; if (!xdr_uint32_t(xdrs, (uint32_t *)&num_hosts)) { LogMajor(COMPONENT_PNFS, "Failed encoding length of FH array."); return NFS4ERR_SERVERFAULT; } for (i = 0; i < num_hosts; i++) { nfs_status = FSAL_encode_ipv4_netaddr( xdrs, hosts[i].proto, hosts[i].addr, hosts[i].port); if (nfs_status != NFS4_OK) return nfs_status; } return NFS4_OK; } /** * @brief Convenience function to encode a single ff_data_server4 * * @param[out] xdrs XDR stream * @param[in] deviceid The deviceid for the layout * @param[in] num_fhs Number of file handles and length of ds_ids array. * @param[in] ds_ids Server IDs of DSs for each file handle * @param[in] fhs Array if buffer descriptors holding opaque DS * handles * @param[in] ffds_efficiency MDS evaluation of mirror's effectiveness * @param[in] ffds_user Synthetic uid to be used for RPC call to DS * @param[in] ffds_group Synthetic gid to be used for RPC call to DS * @return NFS status codes. */ static nfsstat4 FSAL_encode_data_server( XDR *xdrs, const struct pnfs_deviceid *deviceid, const uint32_t num_fhs, const uint16_t *ds_ids, const struct gsh_buffdesc *fhs, const uint32_t ffds_efficiency, const fattr4_owner ffds_user, const fattr4_owner_group ffds_group) { nfsstat4 nfs_status = 0; size_t i = 0; /* Encode ffds_deviceid */ if (!xdr_fsal_deviceid(xdrs, (struct pnfs_deviceid *)deviceid)) { LogMajor(COMPONENT_PNFS, "Failed encoding deviceid."); return NFS4ERR_SERVERFAULT; } /* Encode ffds_efficiency */ if (!xdr_uint32_t(xdrs, (int32_t *)&ffds_efficiency)) { LogMajor(COMPONENT_PNFS, "Failed encoding ffds_efficiency."); return NFS4ERR_SERVERFAULT; } /* Encode ffds_stateid * For now, we assume only loosely coupled setup. * Hence set stateid to anonymous. */ stateid4 ffds_stateid; ffds_stateid.seqid = 0; memset(&ffds_stateid.other, '\0', sizeof(ffds_stateid.other)); if (!xdr_stateid4(xdrs, &ffds_stateid)) { LogMajor(COMPONENT_PNFS, "Failed encoding ffds_stateid."); return NFS4ERR_SERVERFAULT; } if (!xdr_uint32_t(xdrs, (int32_t *)&num_fhs)) { LogMajor(COMPONENT_PNFS, "Failed encoding length of FH array."); return NFS4ERR_SERVERFAULT; } /* Encode ffds_fh_vers */ for (i = 0; i < num_fhs; i++) { nfs_fh4 handle; char buffer[NFS4_FHSIZE]; handle.nfs_fh4_val = buffer; handle.nfs_fh4_len = sizeof(buffer); memset(buffer, 0, sizeof(buffer)); nfs_status = make_file_handle_ds(fhs + i, *(ds_ids + i), &handle); if (nfs_status != NFS4_OK) { LogMajor(COMPONENT_PNFS, "Failed converting FH %zu.", i); return nfs_status; } if (!xdr_bytes(xdrs, (char **)&handle.nfs_fh4_val, &handle.nfs_fh4_len, handle.nfs_fh4_len)) { LogMajor(COMPONENT_PNFS, "Failed encoding FH %zu.", i); return NFS4ERR_SERVERFAULT; } } if (!xdr_fattr4_owner(xdrs, (fattr4_owner *)&ffds_user)) { LogMajor(COMPONENT_PNFS, "Failed encoding ffds_user."); return NFS4ERR_SERVERFAULT; } if (!xdr_fattr4_owner(xdrs, (fattr4_owner_group *)&ffds_group)) { LogMajor(COMPONENT_PNFS, "Failed encoding ffds_group."); return NFS4ERR_SERVERFAULT; } return NFS4_OK; } /** * @brief Convenience function to encode loc_body * * This function allows the FSAL to encode ff_layout4 * without having to allocate and construct all the components of the * structure, including file handles. * * To encode a completed ff_layout4 structure, call * xdr_ff_layout4. * * @param[out] xdrs XDR stream * @param[in] deviceid The deviceid for the layout * @param[in] ffl_stripe_unit Stripe unit for current layout segment * @param[in] ffl_mirrors_len Number of mirrored storage servers. * @param[in] stripes Number of stripes in layout * @param[in] num_fhs Number of file handles and length of ds_ids array. * @param[in] ds_ids Server IDs of DSs for each file handle * @param[in] fhs Array if buffer descriptors holding opaque DS * handles * @param[in] ffds_efficiency MDS evaluation of mirror's effectiveness * @param[in] ffds_user Synthetic uid to be used for RPC call to DS * @param[in] ffds_group Synthetic gid to be used for RPC call to DS * @param[in] ffl_flags Bitmap flags * @param[in] ffl_stats_collect_hint Hint to client on how often the server wants it * to report LAYOUTSTATS for a file. The time is in seconds. * @return NFS status codes. */ nfsstat4 FSAL_encode_flex_file_layout( XDR *xdrs, const struct pnfs_deviceid *deviceid, const uint64_t ffl_stripe_unit, const uint32_t ffl_mirrors_len, u_int stripes, const uint32_t num_fhs, const uint16_t *ds_ids, const struct gsh_buffdesc *fhs, const uint32_t ffds_efficiency, const fattr4_owner ffds_user, const fattr4_owner_group ffds_group, const ff_flags4 ffl_flags, const uint32_t ffl_stats_collect_hint) { nfsstat4 nfs_status = NFS4_OK; size_t i = 0; /* Stripe_unit */ if (!xdr_length4(xdrs, (uint64_t *)&ffl_stripe_unit)) { LogMajor(COMPONENT_PNFS, "Failed encoding ffl_stripe_unit."); return NFS4ERR_SERVERFAULT; } /* ffl_mirrors_len */ if (!xdr_uint32_t(xdrs, (uint32_t *)&ffl_mirrors_len)) { LogMajor(COMPONENT_PNFS, "Failed encoding ffl_mirrors_len."); return NFS4ERR_SERVERFAULT; } /* ffl_mirrors_val */ for (i = 0; i < ffl_mirrors_len; i++) { size_t j = 0; /* stripes = ffm_data_servers_len */ if (!xdr_uint32_t(xdrs, (u_int *)&stripes)) { LogMajor(COMPONENT_PNFS, "Failed encoding ffm_data_servers_len."); return NFS4ERR_SERVERFAULT; } /* Encode ff_data_server4 elements */ for (j = 0; j < stripes; j++) { nfs_status = FSAL_encode_data_server( xdrs, deviceid, num_fhs, ds_ids, fhs, ffds_efficiency, ffds_user, ffds_group); } } /* FFL_FLAGS */ if (!xdr_ff_flags(xdrs, (ff_flags4 *)&ffl_flags)) { LogMajor(COMPONENT_PNFS, "Failed encoding ffl_flags."); return NFS4ERR_SERVERFAULT; } /* Stats collect hint */ if (!xdr_uint32_t(xdrs, (uint32_t *)&ffl_stats_collect_hint)) { LogMajor(COMPONENT_PNFS, "Failed encoding ffl_stats_collect_hint."); return NFS4ERR_SERVERFAULT; } return nfs_status; } /** * @brief Convenience function to encode ff_device_addr4 * * * @param[in,out] xdrs The XDR stream * @param[in] hosts Array of hosts * @return NFSv4 Status code * */ nfsstat4 FSAL_encode_ff_device_versions4( XDR *xdrs, const u_int multipath_list4_len, const u_int ffda_versions_len, const fsal_multipath_member_t *hosts, const uint32_t ffdv_version, const uint32_t ffdv_minorversion, const uint32_t ffdv_rsize, const uint32_t ffdv_wsize, const bool_t ffdv_tightly_coupled) { size_t i = 0; nfsstat4 nfs_status = 0; /* multipath_list4_len */ if (!xdr_u_int(xdrs, (u_int *)&multipath_list4_len)) { LogMajor(COMPONENT_PNFS, "Failed encoding multipath_list4_len."); return NFS4ERR_SERVERFAULT; } for (i = 0; i < multipath_list4_len; i++) { nfs_status = FSAL_encode_ipv4_netaddr( xdrs, hosts[i].proto, hosts[i].addr, hosts[i].port); if (nfs_status != NFS4_OK) return nfs_status; } if (!xdr_uint32_t(xdrs, (uint32_t *)&ffda_versions_len)) { LogMajor(COMPONENT_PNFS, "Failed encoding ffda_versions_len."); return NFS4ERR_SERVERFAULT; } for (i = 0; i < ffda_versions_len; i++) { if (!xdr_uint32_t(xdrs, (uint32_t *)&ffdv_version)) { LogMajor(COMPONENT_PNFS, "Failed encoding ffdv_version."); return NFS4ERR_SERVERFAULT; } if (!xdr_uint32_t(xdrs, (uint32_t *)&ffdv_minorversion)) { LogMajor(COMPONENT_PNFS, "Failed encoding ffdv_minorversion."); return NFS4ERR_SERVERFAULT; } if (!xdr_uint32_t(xdrs, (uint32_t *)&ffdv_rsize)) { LogMajor(COMPONENT_PNFS, "Failed encoding ffdv_rsize."); return NFS4ERR_SERVERFAULT; } if (!xdr_uint32_t(xdrs, (uint32_t *)&ffdv_wsize)) { LogMajor(COMPONENT_PNFS, "Failed encoding ffdv_wsize."); return NFS4ERR_SERVERFAULT; } if (!xdr_bool(xdrs, (bool_t *)&ffdv_tightly_coupled)) { LogMajor(COMPONENT_PNFS, "Failed encoding ffdv_tightly_coupled."); return NFS4ERR_SERVERFAULT; } } return NFS4_OK; } /** * @brief Convert POSIX error codes to NFS 4 error codes * * @param[in] posix_errorcode The error code returned from POSIX. * * @return The NFSv4 error code associated to posix_errorcode. * */ nfsstat4 posix2nfs4_error(const int posix_errorcode) { switch (posix_errorcode) { case EPERM: return NFS4ERR_PERM; case ENOENT: return NFS4ERR_NOENT; case ECONNREFUSED: case ECONNABORTED: case ECONNRESET: case EIO: case ENFILE: case EMFILE: case EPIPE: return NFS4ERR_IO; case ENODEV: case ENXIO: return NFS4ERR_NXIO; case EBADF: return NFS4ERR_OPENMODE; case ENOMEM: return NFS4ERR_SERVERFAULT; case EACCES: return NFS4ERR_ACCESS; case EFAULT: return NFS4ERR_SERVERFAULT; case EEXIST: return NFS4ERR_EXIST; case EXDEV: return NFS4ERR_XDEV; case ENOTDIR: return NFS4ERR_NOTDIR; case EISDIR: return NFS4ERR_ISDIR; case EINVAL: return NFS4ERR_INVAL; case EFBIG: return NFS4ERR_FBIG; case ENOSPC: return NFS4ERR_NOSPC; case EMLINK: return NFS4ERR_MLINK; case EDQUOT: return NFS4ERR_DQUOT; case ENAMETOOLONG: return NFS4ERR_NAMETOOLONG; case ENOTEMPTY: return NFS4ERR_NOTEMPTY; case ESTALE: return NFS4ERR_STALE; case ENOTSUP: return NFS4ERR_NOTSUPP; default: return NFS4ERR_SERVERFAULT; } } /** @} */ nfs-ganesha-6.5/src/FSAL/commonlib.c000066400000000000000000003241121473756622300172370ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @addtogroup FSAL * @{ */ /** * @file fsal_commonlib.c * @author Jim Lieb * @brief Common functions for and private to FSAL modules. * * The prime requirement for functions to be here is that they operate only * on the public part of the FSAL api and are therefore shareable by all fsal * implementations. */ #include "config.h" #include /* avoid conflicts with sys/queue.h */ #include /* used for 'dirname' */ #include #include #include #include #include #include #include #include #include #include "common_utils.h" #include "gsh_config.h" #include "gsh_list.h" #include "fsal.h" #include "fsal_api.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/access_check.h" #include "fsal_private.h" #include "fsal_convert.h" #include "nfs4_acls.h" #include "sal_data.h" #include "nfs_init.h" #include "mdcache.h" #include "nfs_proto_tools.h" #include "idmapper.h" #include "pnfs_utils.h" #include "atomic_utils.h" #include "sys_resource.h" #ifdef USE_DBUS #include "gsh_dbus.h" #endif /* fsal_attach_export * called from the FSAL's create_export method with a reference on the fsal. */ int fsal_attach_export(struct fsal_module *fsal_hdl, struct glist_head *obj_link) { int retval = 0; if (atomic_fetch_int32_t(&fsal_hdl->refcount) > 0) { glist_add(&fsal_hdl->exports, obj_link); } else { LogCrit(COMPONENT_CONFIG, "Attaching export with out holding a reference!. hdl= = 0x%p", fsal_hdl); retval = EINVAL; } return retval; } /* fsal_detach_export * called by an export when it is releasing itself. * does not require a reference to be taken. The list has * kept the fsal "busy". */ void fsal_detach_export(struct fsal_module *fsal_hdl, struct glist_head *obj_link) { PTHREAD_RWLOCK_wrlock(&fsal_hdl->fsm_lock); glist_del(obj_link); PTHREAD_RWLOCK_unlock(&fsal_hdl->fsm_lock); } /** * @brief Initialize export ops vectors * * @param[in] exp Export handle * */ void fsal_export_init(struct fsal_export *exp) { memcpy(&exp->exp_ops, &def_export_ops, sizeof(struct export_ops)); exp->export_id = op_ctx->ctx_export->export_id; exp->owning_export = op_ctx->ctx_export; glist_init(&exp->filesystems); } /** * @brief Stack an export on top of another * * Set up export stacking for stackable FSALs * * @param[in] sub_export Export being stacked on * @param[in] super_export Export stacking on top * @return Return description */ void fsal_export_stack(struct fsal_export *sub_export, struct fsal_export *super_export) { sub_export->super_export = super_export; super_export->sub_export = sub_export; } /** * @brief Free export ops vectors * * Free the memory allocated by init_export_ops. Poison pointers. * * @param[in] exp_hdl Export handle * */ void free_export_ops(struct fsal_export *exp_hdl) { memset(&exp_hdl->exp_ops, 0, sizeof(exp_hdl->exp_ops)); /* poison */ } /* fsal_export to fsal_obj_handle helpers */ void fsal_default_obj_ops_init(struct fsal_obj_ops *obj_ops) { *obj_ops = def_handle_ops; } void fsal_obj_handle_init(struct fsal_obj_handle *obj, struct fsal_export *exp, object_file_type_t type, bool add_to_fsal_handle) { obj->fsal = exp->fsal; obj->type = type; PTHREAD_RWLOCK_init(&obj->obj_lock, NULL); if (add_to_fsal_handle) { PTHREAD_RWLOCK_wrlock(&obj->fsal->fsm_lock); glist_add(&obj->fsal->handles, &obj->handles); PTHREAD_RWLOCK_unlock(&obj->fsal->fsm_lock); } } void fsal_obj_handle_fini(struct fsal_obj_handle *obj, bool added_to_fsal_handle) { if (added_to_fsal_handle) { PTHREAD_RWLOCK_wrlock(&obj->fsal->fsm_lock); glist_del(&obj->handles); PTHREAD_RWLOCK_unlock(&obj->fsal->fsm_lock); } PTHREAD_RWLOCK_destroy(&obj->obj_lock); memset(&obj->obj_ops, 0, sizeof(obj->obj_ops)); /* poison myself */ obj->fsal = NULL; } /* fsal_module to fsal_pnfs_ds helpers */ void fsal_pnfs_ds_init(struct fsal_pnfs_ds *pds, struct fsal_module *fsal) { pds->ds_refcount = 1; /* we start out with a reference */ fsal->m_ops.fsal_pnfs_ds_ops(&pds->s_ops); pds->fsal = fsal; fsal_get(fsal); /* Take a reference for the FSAL */ } void fsal_pnfs_ds_fini(struct fsal_pnfs_ds *pds) { assert(pds->fsal); PTHREAD_RWLOCK_wrlock(&pds->fsal->fsm_lock); glist_del(&pds->server); PTHREAD_RWLOCK_unlock(&pds->fsal->fsm_lock); memset(&pds->s_ops, 0, sizeof(pds->s_ops)); /* poison myself */ fsal_put(pds->fsal); pds->fsal = NULL; } /** * @brief FSAL error code to error message * * @param[in] fsal_err Error code * * @return Error message, empty string if not found. */ const char *msg_fsal_err(fsal_errors_t fsal_err) { switch (fsal_err) { case ERR_FSAL_NO_ERROR: return "No error"; case ERR_FSAL_PERM: return "Forbidden action"; case ERR_FSAL_NOENT: return "No such file or directory"; case ERR_FSAL_IO: return "I/O error"; case ERR_FSAL_NXIO: return "No such device or address"; case ERR_FSAL_NOMEM: return "Not enough memory"; case ERR_FSAL_ACCESS: return "Permission denied"; case ERR_FSAL_FAULT: return "Bad address"; case ERR_FSAL_STILL_IN_USE: return "Device or resource busy"; case ERR_FSAL_EXIST: return "This object already exists"; case ERR_FSAL_XDEV: return "This operation can't cross filesystems"; case ERR_FSAL_NOTDIR: return "This object is not a directory"; case ERR_FSAL_ISDIR: return "Directory used in a nondirectory operation"; case ERR_FSAL_INVAL: return "Invalid object type"; case ERR_FSAL_FBIG: return "File exceeds max file size"; case ERR_FSAL_NOSPC: return "No space left on filesystem"; case ERR_FSAL_ROFS: return "Read-only filesystem"; case ERR_FSAL_MLINK: return "Too many hard links"; case ERR_FSAL_DQUOT: return "Quota exceeded"; case ERR_FSAL_NAMETOOLONG: return "Max name length exceeded"; case ERR_FSAL_NOTEMPTY: return "The directory is not empty"; case ERR_FSAL_STALE: return "The file no longer exists"; case ERR_FSAL_BADHANDLE: return "Illegal filehandle"; case ERR_FSAL_BADCOOKIE: return "Invalid cookie"; case ERR_FSAL_NOTSUPP: return "Operation not supported"; case ERR_FSAL_TOOSMALL: return "Output buffer too small"; case ERR_FSAL_SERVERFAULT: return "Undefined server error"; case ERR_FSAL_BADTYPE: return "Invalid type for create operation"; case ERR_FSAL_DELAY: return "File busy, retry"; case ERR_FSAL_FHEXPIRED: return "Filehandle expired"; case ERR_FSAL_SYMLINK: return "This is a symbolic link, should be file/directory"; case ERR_FSAL_ATTRNOTSUPP: return "Attribute not supported"; case ERR_FSAL_NOT_INIT: return "Filesystem not initialized"; case ERR_FSAL_ALREADY_INIT: return "Filesystem already initialised"; case ERR_FSAL_BAD_INIT: return "Filesystem initialisation error"; case ERR_FSAL_SEC: return "Security context error"; case ERR_FSAL_NO_QUOTA: return "No Quota available"; case ERR_FSAL_NOT_OPENED: return "File/directory not opened"; case ERR_FSAL_DEADLOCK: return "Deadlock"; case ERR_FSAL_OVERFLOW: return "Overflow"; case ERR_FSAL_INTERRUPT: return "Operation Interrupted"; case ERR_FSAL_BLOCKED: return "Lock Blocked"; case ERR_FSAL_SHARE_DENIED: return "Share Denied"; case ERR_FSAL_LOCKED: return "Locked"; case ERR_FSAL_TIMEOUT: return "Timeout"; case ERR_FSAL_FILE_OPEN: return "File Open"; case ERR_FSAL_UNION_NOTSUPP: return "Union Not Supported"; case ERR_FSAL_IN_GRACE: return "Server in Grace"; case ERR_FSAL_NO_DATA: return "No Data"; case ERR_FSAL_NO_ACE: return "No matching ACE"; case ERR_FSAL_BAD_RANGE: return "Lock not in allowable range"; case ERR_FSAL_CROSS_JUNCTION: return "Crossed Junction"; case ERR_FSAL_BADNAME: return "Invalid Name"; case ERR_FSAL_NOXATTR: return "No such xattr"; case ERR_FSAL_XATTR2BIG: return "Xattr too big"; } return "Unknown FSAL error"; } const char *fsal_dir_result_str(enum fsal_dir_result result) { switch (result) { case DIR_CONTINUE: return "DIR_CONTINUE"; case DIR_READAHEAD: return "DIR_READAHEAD"; case DIR_TERMINATE: return "DIR_TERMINATE"; } return ""; } /** * @brief Dump and fsal_staticfsinfo_t to a log * * This is used for debugging * * @param[in] info The info to dump */ void display_fsinfo(struct fsal_module *fsal) { LogDebug(COMPONENT_FSAL, "FileSystem info for FSAL %s {", fsal->name); LogDebug(COMPONENT_FSAL, " maxfilesize = %" PRIX64 " ", (uint64_t)fsal->fs_info.maxfilesize); LogDebug(COMPONENT_FSAL, " maxlink = %" PRIu32, fsal->fs_info.maxlink); LogDebug(COMPONENT_FSAL, " maxnamelen = %" PRIu32, fsal->fs_info.maxnamelen); LogDebug(COMPONENT_FSAL, " maxpathlen = %" PRIu32, fsal->fs_info.maxpathlen); LogDebug(COMPONENT_FSAL, " no_trunc = %d ", fsal->fs_info.no_trunc); LogDebug(COMPONENT_FSAL, " chown_restricted = %d ", fsal->fs_info.chown_restricted); LogDebug(COMPONENT_FSAL, " case_insensitive = %d ", fsal->fs_info.case_insensitive); LogDebug(COMPONENT_FSAL, " case_preserving = %d ", fsal->fs_info.case_preserving); LogDebug(COMPONENT_FSAL, " link_support = %d ", fsal->fs_info.link_support); LogDebug(COMPONENT_FSAL, " symlink_support = %d ", fsal->fs_info.symlink_support); LogDebug(COMPONENT_FSAL, " lock_support = %d ", fsal->fs_info.lock_support); LogDebug(COMPONENT_FSAL, " lock_support_async_block = %d ", fsal->fs_info.lock_support_async_block); LogDebug(COMPONENT_FSAL, " named_attr = %d ", fsal->fs_info.named_attr); LogDebug(COMPONENT_FSAL, " unique_handles = %d ", fsal->fs_info.unique_handles); LogDebug(COMPONENT_FSAL, " acl_support = %hu ", fsal->fs_info.acl_support); LogDebug(COMPONENT_FSAL, " cansettime = %d ", fsal->fs_info.cansettime); LogDebug(COMPONENT_FSAL, " homogenous = %d ", fsal->fs_info.homogenous); LogDebug(COMPONENT_FSAL, " supported_attrs = %" PRIX64, fsal->fs_info.supported_attrs); LogDebug(COMPONENT_FSAL, " maxread = %" PRIu64, fsal->fs_info.maxread); LogDebug(COMPONENT_FSAL, " maxwrite = %" PRIu64, fsal->fs_info.maxwrite); LogDebug(COMPONENT_FSAL, " umask = %X ", fsal->fs_info.umask); LogDebug(COMPONENT_FSAL, " auth_exportpath_xdev = %d ", fsal->fs_info.auth_exportpath_xdev); LogDebug(COMPONENT_FSAL, " delegations = %d ", fsal->fs_info.delegations); LogDebug(COMPONENT_FSAL, " pnfs_mds = %d ", fsal->fs_info.pnfs_mds); LogDebug(COMPONENT_FSAL, " pnfs_ds = %d ", fsal->fs_info.pnfs_ds); LogDebug(COMPONENT_FSAL, " fsal_trace = %d ", fsal->fs_info.fsal_trace); LogDebug(COMPONENT_FSAL, " fsal_grace = %d ", fsal->fs_info.fsal_grace); LogDebug(COMPONENT_FSAL, " expire_time_parent = %d ", fsal->fs_info.expire_time_parent); LogDebug(COMPONENT_FSAL, " xattr_support = %d ", fsal->fs_info.xattr_support); LogDebug(COMPONENT_FSAL, "}"); } int display_attrlist(struct display_buffer *dspbuf, struct fsal_attrlist *attr, bool is_obj) { int b_left = display_start(dspbuf); if (attr->request_mask == 0 && attr->valid_mask == 0 && attr->supported == 0) return display_cat(dspbuf, "No attributes"); if (b_left > 0 && attr->request_mask != 0) b_left = display_printf(dspbuf, "Request Mask=%08x ", (unsigned int)attr->request_mask); if (b_left > 0 && attr->valid_mask != 0) b_left = display_printf(dspbuf, "Valid Mask=%08x ", (unsigned int)attr->valid_mask); if (b_left > 0 && attr->supported != 0) b_left = display_printf(dspbuf, "Supported Mask=%08x ", (unsigned int)attr->supported); if (b_left > 0 && is_obj) b_left = display_printf(dspbuf, "%s", object_file_type_to_str(attr->type)); if (b_left > 0 && FSAL_TEST_MASK(attr->valid_mask, ATTR_NUMLINKS)) b_left = display_printf(dspbuf, " numlinks=0x%" PRIx32, attr->numlinks); if (b_left > 0 && FSAL_TEST_MASK(attr->valid_mask, ATTR_SIZE)) b_left = display_printf(dspbuf, " size=0x%" PRIx64, attr->filesize); if (b_left > 0 && FSAL_TEST_MASK(attr->valid_mask, ATTR_MODE)) b_left = display_printf(dspbuf, " mode=0%" PRIo32, attr->mode); if (b_left > 0 && FSAL_TEST_MASK(attr->valid_mask, ATTR_OWNER)) b_left = display_printf(dspbuf, " owner=0x%" PRIx64, attr->owner); if (b_left > 0 && FSAL_TEST_MASK(attr->valid_mask, ATTR_GROUP)) b_left = display_printf(dspbuf, " group=0x%" PRIx64, attr->group); if (b_left > 0 && FSAL_TEST_MASK(attr->valid_mask, ATTR_ATIME_SERVER)) b_left = display_cat(dspbuf, " atime=SERVER"); if (b_left > 0 && FSAL_TEST_MASK(attr->valid_mask, ATTR_MTIME_SERVER)) b_left = display_cat(dspbuf, " mtime=SERVER"); if (b_left > 0 && FSAL_TEST_MASK(attr->valid_mask, ATTR_ATIME)) { b_left = display_cat(dspbuf, " atime="); if (b_left > 0) b_left = display_timespec(dspbuf, &attr->atime); } if (b_left > 0 && FSAL_TEST_MASK(attr->valid_mask, ATTR_MTIME)) { b_left = display_cat(dspbuf, " mtime="); if (b_left > 0) b_left = display_timespec(dspbuf, &attr->mtime); } return b_left; } void log_attrlist(log_components_t component, log_levels_t level, const char *reason, struct fsal_attrlist *attr, bool is_obj, char *file, int line, char *function) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; (void)display_attrlist(&dspbuf, attr, is_obj); DisplayLogComponentLevel(component, file, line, function, level, "%s %s attributes %s", reason, is_obj ? "obj" : "set", str); } int encode_fsid(char *buf, int max, struct fsal_fsid__ *fsid, enum fsid_type fsid_type) { uint32_t u32; if (sizeof_fsid(fsid_type) > max) return -1; /* Pack fsid into the bytes */ switch (fsid_type) { case FSID_NO_TYPE: break; case FSID_ONE_UINT64: case FSID_MAJOR_64: memcpy(buf, &fsid->major, sizeof(fsid->major)); break; case FSID_TWO_UINT64: memcpy(buf, fsid, sizeof(*fsid)); break; case FSID_TWO_UINT32: case FSID_DEVICE: u32 = fsid->major; memcpy(buf, &u32, sizeof(u32)); u32 = fsid->minor; memcpy(buf + sizeof(u32), &u32, sizeof(u32)); } return sizeof_fsid(fsid_type); } int decode_fsid(char *buf, int max, struct fsal_fsid__ *fsid, enum fsid_type fsid_type) { uint32_t u32; if (sizeof_fsid(fsid_type) > max) return -1; switch (fsid_type) { case FSID_NO_TYPE: memset(fsid, 0, sizeof(*fsid)); break; case FSID_ONE_UINT64: case FSID_MAJOR_64: memcpy(&fsid->major, buf, sizeof(fsid->major)); fsid->minor = 0; break; case FSID_TWO_UINT64: memcpy(fsid, buf, sizeof(*fsid)); break; case FSID_TWO_UINT32: case FSID_DEVICE: memcpy(&u32, buf, sizeof(u32)); fsid->major = u32; memcpy(&u32, buf + sizeof(u32), sizeof(u32)); fsid->minor = u32; break; } return sizeof_fsid(fsid_type); } static inline bool is_dup_ace(fsal_ace_t *ace, fsal_aceflag_t inherit) { if (!IS_FSAL_ACE_INHERIT(*ace)) return false; if (inherit != FSAL_ACE_FLAG_DIR_INHERIT) /* Only dup on directories */ return false; if (IS_FSAL_ACE_NO_PROPAGATE(*ace)) return false; if (IS_FSAL_ACE_FILE_INHERIT(*ace) && !IS_FSAL_ACE_DIR_INHERIT(*ace)) return false; if (!IS_FSAL_ACE_PERM(*ace)) return false; return true; } static fsal_errors_t dup_ace(fsal_ace_t *sace, fsal_ace_t *dace) { *dace = *sace; GET_FSAL_ACE_FLAG(*sace) |= FSAL_ACE_FLAG_INHERIT_ONLY; GET_FSAL_ACE_FLAG(*dace) &= ~(FSAL_ACE_FLAG_INHERIT | FSAL_ACE_FLAG_NO_PROPAGATE); return ERR_FSAL_NO_ERROR; } fsal_errors_t fsal_inherit_acls(struct fsal_attrlist *attrs, fsal_acl_t *sacl, fsal_aceflag_t inherit) { int naces; fsal_ace_t *sace, *dace; if (!sacl || !sacl->aces || sacl->naces == 0) return ERR_FSAL_NO_ERROR; if (attrs->acl && attrs->acl->aces && attrs->acl->naces > 0) return ERR_FSAL_EXIST; naces = 0; for (sace = sacl->aces; sace < sacl->aces + sacl->naces; sace++) { if (IS_FSAL_ACE_FLAG(*sace, inherit)) naces++; if (is_dup_ace(sace, inherit)) naces++; } if (naces == 0) return ERR_FSAL_NO_ERROR; if (attrs->acl != NULL) { /* We should never be passed attributes that have an * ACL attached, but just in case some future code * path changes that assumption, let's not release the * old ACL properly. */ nfs4_acl_release_entry(attrs->acl); } attrs->acl = nfs4_acl_alloc(); attrs->acl->aces = (fsal_ace_t *)nfs4_ace_alloc(naces); dace = attrs->acl->aces; for (sace = sacl->aces; sace < sacl->aces + sacl->naces; sace++) { if (IS_FSAL_ACE_FLAG(*sace, inherit)) { *dace = *sace; if (IS_FSAL_ACE_NO_PROPAGATE(*dace)) GET_FSAL_ACE_FLAG(*dace) &= ~(FSAL_ACE_FLAG_INHERIT | FSAL_ACE_FLAG_NO_PROPAGATE); else if (inherit == FSAL_ACE_FLAG_DIR_INHERIT && IS_FSAL_ACE_FILE_INHERIT(*dace) && !IS_FSAL_ACE_DIR_INHERIT(*dace)) GET_FSAL_ACE_FLAG(*dace) |= FSAL_ACE_FLAG_NO_PROPAGATE; else if (is_dup_ace(dace, inherit)) { dup_ace(dace, dace + 1); dace++; } dace++; } } attrs->acl->naces = naces; FSAL_SET_MASK(attrs->valid_mask, ATTR_ACL); return ERR_FSAL_NO_ERROR; } fsal_status_t fsal_remove_access(struct fsal_obj_handle *dir_hdl, struct fsal_obj_handle *rem_hdl, bool isdir) { fsal_status_t fsal_status = { 0, 0 }; fsal_status_t del_status = { 0, 0 }; /* draft-ietf-nfsv4-acls section 12 */ /* If no execute on dir, deny */ fsal_status = dir_hdl->obj_ops->test_access( dir_hdl, FSAL_MODE_MASK_SET(FSAL_X_OK) | FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_EXECUTE), NULL, NULL, false); if (FSAL_IS_ERROR(fsal_status)) { LogFullDebug( COMPONENT_FSAL, "Could not delete: No execute permession on parent: %s", msg_fsal_err(fsal_status.major)); return fsal_status; } /* We can delete if we have *either* ACE_PERM_DELETE or * ACE_PERM_DELETE_CHILD. 7530 - 6.2.1.3.2 */ del_status = rem_hdl->obj_ops->test_access( rem_hdl, FSAL_MODE_MASK_SET(FSAL_W_OK) | FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_DELETE) | FSAL_ACE4_REQ_FLAG, NULL, NULL, false); fsal_status = dir_hdl->obj_ops->test_access( dir_hdl, FSAL_MODE_MASK_SET(FSAL_W_OK) | FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_DELETE_CHILD) | FSAL_ACE4_REQ_FLAG, NULL, NULL, false); if (FSAL_IS_ERROR(fsal_status) && FSAL_IS_ERROR(del_status)) { /* Neither was explicitly allowed */ if (fsal_status.major != ERR_FSAL_NO_ACE) { /* Explicitly denied */ LogFullDebug(COMPONENT_FSAL, "Could not delete (DELETE_CHILD) %s", msg_fsal_err(fsal_status.major)); return fsal_status; } if (del_status.major != ERR_FSAL_NO_ACE) { /* Explicitly denied */ LogFullDebug(COMPONENT_FSAL, "Could not delete (DELETE) %s", msg_fsal_err(del_status.major)); return del_status; } /* Neither ACE_PERM_DELETE nor ACE_PERM_DELETE_CHILD are set. * Check for ADD_FILE in parent */ fsal_status = dir_hdl->obj_ops->test_access( dir_hdl, FSAL_MODE_MASK_SET(FSAL_W_OK) | FSAL_ACE4_MASK_SET( isdir ? FSAL_ACE_PERM_ADD_SUBDIRECTORY : FSAL_ACE_PERM_ADD_FILE), NULL, NULL, false); if (FSAL_IS_ERROR(fsal_status)) { LogFullDebug(COMPONENT_FSAL, "Could not delete (ADD_CHILD) %s", msg_fsal_err(fsal_status.major)); return fsal_status; } /* Allowed; fall through */ } return fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_status_t fsal_rename_access(struct fsal_obj_handle *src_dir_hdl, struct fsal_obj_handle *src_obj_hdl, struct fsal_obj_handle *dst_dir_hdl, struct fsal_obj_handle *dst_obj_hdl, bool isdir) { fsal_status_t status = { 0, 0 }; fsal_accessflags_t access_type; status = fsal_remove_access(src_dir_hdl, src_obj_hdl, isdir); if (FSAL_IS_ERROR(status)) return status; if (dst_obj_hdl) { status = fsal_remove_access(dst_dir_hdl, dst_obj_hdl, isdir); if (FSAL_IS_ERROR(status)) return status; } access_type = FSAL_MODE_MASK_SET(FSAL_W_OK); if (isdir) access_type |= FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_ADD_SUBDIRECTORY); else access_type |= FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_ADD_FILE); status = dst_dir_hdl->obj_ops->test_access(dst_dir_hdl, access_type, NULL, NULL, false); if (FSAL_IS_ERROR(status)) return status; return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t fsal_mode_set_ace(fsal_ace_t *deny, fsal_ace_t *allow, uint32_t mode) { GET_FSAL_ACE_TYPE(*allow) = FSAL_ACE_TYPE_ALLOW; GET_FSAL_ACE_TYPE(*deny) = FSAL_ACE_TYPE_DENY; if (mode & S_IRUSR) GET_FSAL_ACE_PERM(*allow) |= FSAL_ACE_PERM_READ_DATA; else GET_FSAL_ACE_PERM(*deny) |= FSAL_ACE_PERM_READ_DATA; if (mode & S_IWUSR) GET_FSAL_ACE_PERM(*allow) |= FSAL_ACE_PERM_WRITE_DATA | FSAL_ACE_PERM_APPEND_DATA; else GET_FSAL_ACE_PERM(*deny) |= FSAL_ACE_PERM_WRITE_DATA | FSAL_ACE_PERM_APPEND_DATA; if (mode & S_IXUSR) GET_FSAL_ACE_PERM(*allow) |= FSAL_ACE_PERM_EXECUTE; else GET_FSAL_ACE_PERM(*deny) |= FSAL_ACE_PERM_EXECUTE; return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t fsal_mode_gen_set(fsal_ace_t *ace_owner_group, fsal_ace_t *ace_everyone, uint32_t mode) { fsal_ace_t *allow, *deny; /* All should have the READ_ATTR & READ_ACL allowed by default. * Owner in addition Should have the write attr, acl & owner * (actually owner may just change the gid to group it belongs to) */ const fsal_aceperm_t default_attr_acl_read_perm = FSAL_ACE_PERM_READ_ATTR | FSAL_ACE_PERM_READ_ACL; /* @OWNER */ deny = ace_owner_group; allow = deny + 1; GET_FSAL_ACE_USER(*allow) = FSAL_ACE_SPECIAL_OWNER; GET_FSAL_ACE_IFLAG(*allow) |= FSAL_ACE_IFLAG_SPECIAL_ID; GET_FSAL_ACE_USER(*deny) = FSAL_ACE_SPECIAL_OWNER; GET_FSAL_ACE_IFLAG(*deny) |= FSAL_ACE_IFLAG_SPECIAL_ID; GET_FSAL_ACE_PERM(*allow) |= default_attr_acl_read_perm; GET_FSAL_ACE_PERM(*allow) |= FSAL_ACE_PERM_WRITE_ATTR | FSAL_ACE_PERM_WRITE_ACL | FSAL_ACE_PERM_WRITE_OWNER; fsal_mode_set_ace(deny, allow, mode & S_IRWXU); /* @GROUP */ deny += 2; allow = deny + 1; GET_FSAL_ACE_USER(*allow) = FSAL_ACE_SPECIAL_GROUP; GET_FSAL_ACE_IFLAG(*allow) |= FSAL_ACE_IFLAG_SPECIAL_ID; GET_FSAL_ACE_USER(*deny) = FSAL_ACE_SPECIAL_GROUP; GET_FSAL_ACE_IFLAG(*deny) |= FSAL_ACE_IFLAG_SPECIAL_ID; GET_FSAL_ACE_PERM(*allow) |= default_attr_acl_read_perm; fsal_mode_set_ace(deny, allow, (mode & S_IRWXG) << 3); /* @EVERYONE */ deny = ace_everyone; allow = deny + 1; GET_FSAL_ACE_USER(*allow) = FSAL_ACE_SPECIAL_EVERYONE; GET_FSAL_ACE_IFLAG(*allow) |= FSAL_ACE_IFLAG_SPECIAL_ID; GET_FSAL_ACE_USER(*deny) = FSAL_ACE_SPECIAL_EVERYONE; GET_FSAL_ACE_IFLAG(*deny) |= FSAL_ACE_IFLAG_SPECIAL_ID; GET_FSAL_ACE_PERM(*allow) |= default_attr_acl_read_perm; fsal_mode_set_ace(deny, allow, (mode & S_IRWXO) << 6); return fsalstat(ERR_FSAL_NO_ERROR, 0); } static fsal_status_t fsal_mode_gen_acl(struct fsal_attrlist *attrs) { fsal_acl_data_t acl_data; fsal_acl_status_t acl_status; if (attrs->acl != NULL) { /* We should never be passed attributes that have an * ACL attached, but just in case some future code * path changes that assumption, let's not release the * old ACL properly. */ nfs4_acl_release_entry(attrs->acl); } acl_data.naces = 6; acl_data.aces = nfs4_ace_alloc(acl_data.naces); fsal_mode_gen_set(acl_data.aces, acl_data.aces + 4, attrs->mode); attrs->acl = nfs4_acl_new_entry(&acl_data, &acl_status); if (attrs->acl == NULL) LogFatal(COMPONENT_FSAL, "Failed in nfs4_acl_new_entry, acl_status %d", acl_status); FSAL_SET_MASK(attrs->valid_mask, ATTR_ACL); return fsalstat(ERR_FSAL_NO_ERROR, 0); } static bool fsal_check_ace_couple(const fsal_ace_t *aces, uid_t who) { for (uint32_t i = 0; i < 2; i++) { if (!IS_FSAL_ACE_SPECIAL_ID(aces[i]) || !IS_FSAL_ACE_USER(aces[i], who)) return false; if (IS_FSAL_ACE_INHERIT(aces[i])) return false; if (i == 0) { if (!IS_FSAL_ACE_DENY(aces[i])) return false; } else { if (!IS_FSAL_ACE_ALLOW(aces[i])) return false; } } return true; } bool fsal_can_reuse_mode_to_acl(const fsal_acl_t *sacl) { /* * Identify whether existing aces can be reused for representing mode. * Can't rely on the FSAL_ACE_IFLAG_MODE_GEN because if the client * calls another SETATTR with ACL, the internal flag info will be lost. */ const fsal_ace_t *ace; if (!sacl || sacl->naces < 6) return false; /* OWNER@ */ ace = &sacl->aces[0]; if (!fsal_check_ace_couple(ace, FSAL_ACE_SPECIAL_OWNER)) return false; ace = &sacl->aces[2]; if (!fsal_check_ace_couple(ace, FSAL_ACE_SPECIAL_GROUP)) return false; ace = &sacl->aces[sacl->naces - 2]; if (!fsal_check_ace_couple(ace, FSAL_ACE_SPECIAL_EVERYONE)) return false; return true; } static bool fsal_can_skip_ace(const fsal_ace_t *ace, uint32_t indx, uint32_t naces, bool can_reuse) { /* We can skip the copy of special ID aces which don't hold permission * flags such as DELETE or DELETE-CHILD which are not covered by mode * bits. Those special ID aces are going to be generated (or reused) * by the mode to acl mechanism. */ /* The first 4 aces and last 2 aces are going to be reused. */ if (can_reuse && (indx < 4 || indx >= naces - 2)) return false; if (!IS_FSAL_ACE_SPECIAL_ID(*ace)) return false; if (IS_FSAL_ACE_INHERIT_ONLY(*ace)) return false; if (IS_FSAL_ACE_DELETE(*ace) || IS_FSAL_ACE_DELETE_CHILD(*ace)) return false; return true; } fsal_status_t fsal_mode_to_acl(struct fsal_attrlist *attrs, fsal_acl_t *sacl) { int naces; fsal_ace_t *sace, *dace; bool can_reuse; if (!FSAL_TEST_MASK(attrs->valid_mask, ATTR_MODE)) return fsalstat(ERR_FSAL_NO_ERROR, 0); if (!sacl || sacl->naces == 0) return fsal_mode_gen_acl(attrs); can_reuse = fsal_can_reuse_mode_to_acl(sacl); LogFullDebug(COMPONENT_FSAL, "Can reuse aces for mode: %d", can_reuse); naces = 0; for (sace = sacl->aces; sace < sacl->aces + sacl->naces; sace++) { if (fsal_can_skip_ace(sace, sace - sacl->aces, sacl->naces, can_reuse)) continue; naces++; if (IS_FSAL_ACE_INHERIT_ONLY(*sace)) continue; if (!IS_FSAL_ACE_PERM(*sace)) continue; /* XXX dang dup for non-special case */ } if (naces == 0) { /* Only mode generate aces */ return fsal_mode_gen_acl(attrs); } /* Space for generated ACEs - OWNER, GROUP at start and EVERYONE * at the end */ if (!can_reuse) { naces += 6; } if (attrs->acl != NULL) { /* We should never be passed attributes that have an * ACL attached, but just in case some future code * path changes that assumption, let's not release the * old ACL properly. */ nfs4_acl_release_entry(attrs->acl); } LogFullDebug(COMPONENT_FSAL, "naces: %d", naces); fsal_acl_data_t acl_data; acl_data.aces = nfs4_ace_alloc(naces); acl_data.naces = 0; dace = can_reuse ? acl_data.aces : acl_data.aces + 4; for (sace = sacl->aces; sace < sacl->aces + sacl->naces; sace++) { if (fsal_can_skip_ace(sace, sace - sacl->aces, sacl->naces, can_reuse)) continue; *dace = *sace; acl_data.naces++; if (IS_FSAL_ACE_INHERIT_ONLY(*dace) || (!IS_FSAL_ACE_PERM(*dace))) { dace++; continue; } if (IS_FSAL_ACE_SPECIAL_ID(*dace)) GET_FSAL_ACE_PERM(*dace) &= ~(FSAL_ACE_PERM_READ_DATA | FSAL_ACE_PERM_LIST_DIR | FSAL_ACE_PERM_WRITE_DATA | FSAL_ACE_PERM_ADD_FILE | FSAL_ACE_PERM_APPEND_DATA | FSAL_ACE_PERM_ADD_SUBDIRECTORY | FSAL_ACE_PERM_EXECUTE); else if (IS_FSAL_ACE_ALLOW(*dace)) { /* Do non-special stuff */ if ((attrs->mode & S_IRGRP) == 0) GET_FSAL_ACE_PERM(*dace) &= ~(FSAL_ACE_PERM_READ_DATA | FSAL_ACE_PERM_LIST_DIR); if ((attrs->mode & S_IWGRP) == 0) GET_FSAL_ACE_PERM(*dace) &= ~(FSAL_ACE_PERM_WRITE_DATA | FSAL_ACE_PERM_ADD_FILE | FSAL_ACE_PERM_APPEND_DATA | FSAL_ACE_PERM_ADD_SUBDIRECTORY); if ((attrs->mode & S_IXGRP) == 0) GET_FSAL_ACE_PERM(*dace) &= ~FSAL_ACE_PERM_EXECUTE; } dace++; } if ((!can_reuse && naces - acl_data.naces != 6) || (can_reuse && naces != acl_data.naces)) { LogDebug(COMPONENT_FSAL, "Bad naces: %d not %d", acl_data.naces, naces - 6); return fsalstat(ERR_FSAL_SERVERFAULT, 0); } /* 4 aces for OWNER & GROUP shall be set in the beginning, and 2 aces * for EVERYONE shall be placed at the end. */ fsal_mode_gen_set(acl_data.aces, acl_data.aces + naces - 2, attrs->mode); fsal_acl_status_t acl_status; acl_data.naces = naces; attrs->acl = nfs4_acl_new_entry(&acl_data, &acl_status); LogFullDebug(COMPONENT_FSAL, "acl_status after nfs4_acl_new_entry: %d", acl_status); if (attrs->acl == NULL) LogFatal(COMPONENT_FSAL, "Failed in nfs4_acl_new_entry"); FSAL_SET_MASK(attrs->valid_mask, ATTR_ACL); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* fsal_acl_to_mode helpers */ static uint32_t ace_modes[3][3] = { { /* owner */ S_IRUSR, S_IWUSR, S_IXUSR }, { /* group */ S_IRGRP, S_IWGRP, S_IXGRP }, { /* other */ S_IROTH, S_IWOTH, S_IXOTH } }; fsal_status_t fsal_acl_to_mode(struct fsal_attrlist *attrs) { uint32_t *modes; uint32_t who; if (!FSAL_TEST_MASK(attrs->valid_mask, ATTR_ACL)) return fsalstat(ERR_FSAL_NO_ERROR, 0); if (!attrs->acl || attrs->acl->naces == 0) return fsalstat(ERR_FSAL_NO_ERROR, 0); /* Clear all mode bits except the first 3 special bits */ attrs->mode &= (S_ISUID | S_ISGID | S_ISVTX); /* Compute the mode bits according to RFC 8881 section 6.3.2 */ for (who = FSAL_ACE_SPECIAL_OWNER; who <= FSAL_ACE_SPECIAL_EVERYONE; who++) { uint32_t allowed = 0, denied = 0; uint32_t i; for (i = 0; i < attrs->acl->naces; i++) { const fsal_ace_t *ace = &attrs->acl->aces[i]; if (!IS_FSAL_ACE_PERM(*ace) || IS_FSAL_ACE_INHERIT_ONLY(*ace)) continue; if (!IS_FSAL_ACE_SPECIAL_ID(*ace)) continue; if (!IS_FSAL_ACE_USER(*ace, who) && !IS_FSAL_ACE_SPECIAL_EVERYONE(*ace)) continue; modes = ace_modes[who - FSAL_ACE_SPECIAL_OWNER]; if (IS_FSAL_ACE_READ_DATA(*ace)) { if (IS_FSAL_ACE_ALLOW(*ace) && (denied & modes[0]) == 0) allowed |= modes[0]; else denied |= modes[0]; } if (IS_FSAL_ACE_WRITE_DATA(*ace) || IS_FSAL_ACE_APPEND_DATA(*ace)) { if (IS_FSAL_ACE_ALLOW(*ace) && (denied & modes[1]) == 0) allowed |= modes[1]; else denied |= modes[1]; } if (IS_FSAL_ACE_EXECUTE(*ace)) { if (IS_FSAL_ACE_ALLOW(*ace) && (denied & modes[2]) == 0) allowed |= modes[2]; else denied |= modes[2]; } } FSAL_SET_MASK(attrs->mode, allowed); } FSAL_SET_MASK(attrs->valid_mask, ATTR_MODE); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Set up a common verifier using atime and mtime. * * @param[in] attrs Attributes for the file * @param[in] verifier Verifier to use for exclusive create * @param[in] trunc_verif Use onlu 31 bits of each half of the verifier * * @retval true if verifier matches */ void set_common_verifier(struct fsal_attrlist *attrs, fsal_verifier_t verifier, bool trunc_verif) { uint32_t verf_hi = 0, verf_lo = 0; memcpy(&verf_hi, verifier, sizeof(uint32_t)); memcpy(&verf_lo, verifier + sizeof(uint32_t), sizeof(uint32_t)); LogFullDebug(COMPONENT_FSAL, "Passed verifier %" PRIx32 " %" PRIx32, verf_hi, verf_lo); if (trunc_verif) { verf_hi &= INT32_MAX; verf_lo &= INT32_MAX; } if (isDebug(COMPONENT_FSAL) && (FSAL_TEST_MASK(attrs->valid_mask, ATTR_ATIME) || (FSAL_TEST_MASK(attrs->valid_mask, ATTR_MTIME)))) { LogWarn(COMPONENT_FSAL, "atime or mtime was already set in attributes%" PRIx32 " %" PRIx32, (uint32_t)attrs->atime.tv_sec, (uint32_t)attrs->mtime.tv_sec); } LogFullDebug(COMPONENT_FSAL, "Setting verifier atime %" PRIx32 " mtime %" PRIx32, verf_hi, verf_lo); attrs->atime.tv_sec = verf_hi; attrs->atime.tv_nsec = 0; attrs->mtime.tv_sec = verf_lo; attrs->mtime.tv_nsec = 0; FSAL_SET_MASK(attrs->valid_mask, ATTR_ATIME | ATTR_MTIME); } /** * @brief Update the ref counter of share state * * The caller is responsible for protecting the share. * * @param[in] share Share to update * @param[in] old_openflags Previous access/deny mode * @param[in] new_openflags Current access/deny mode */ void update_share_counters(struct fsal_share *share, fsal_openflags_t old_openflags, fsal_openflags_t new_openflags) { int access_read_inc = ((int)(new_openflags & FSAL_O_READ) != 0) - ((int)(old_openflags & FSAL_O_READ) != 0); int access_write_inc = ((int)(new_openflags & FSAL_O_WRITE) != 0) - ((int)(old_openflags & FSAL_O_WRITE) != 0); int deny_read_inc = ((int)(new_openflags & FSAL_O_DENY_READ) != 0) - ((int)(old_openflags & FSAL_O_DENY_READ) != 0); /* Combine both FSAL_O_DENY_WRITE and FSAL_O_DENY_WRITE_MAND */ int deny_write_inc = ((int)(new_openflags & FSAL_O_DENY_WRITE) != 0) - ((int)(old_openflags & FSAL_O_DENY_WRITE) != 0) + ((int)(new_openflags & FSAL_O_DENY_WRITE_MAND) != 0) - ((int)(old_openflags & FSAL_O_DENY_WRITE_MAND) != 0); int deny_write_mand_inc = ((int)(new_openflags & FSAL_O_DENY_WRITE_MAND) != 0) - ((int)(old_openflags & FSAL_O_DENY_WRITE_MAND) != 0); share->share_access_read += access_read_inc; share->share_access_write += access_write_inc; share->share_deny_read += deny_read_inc; share->share_deny_write += deny_write_inc; share->share_deny_write_mand += deny_write_mand_inc; LogFullDebug( COMPONENT_FSAL, "share counter: access_read %u, access_write %u, deny_read %u, deny_write %u, deny_write_v4 %u", share->share_access_read, share->share_access_write, share->share_deny_read, share->share_deny_write, share->share_deny_write_mand); } /** * @brief Check for share conflict * * The caller is responsible for protecting the share. * * This function is NOT called if the caller holds a share reservation covering * the requested access. * * @param[in] share File to query * @param[in] openflags Desired access and deny mode * @param[in] bypass Bypasses share_deny_read and share_deny_write but * not share_deny_write_mand * * @retval ERR_FSAL_SHARE_DENIED - a conflict occurred. * */ fsal_status_t check_share_conflict(struct fsal_share *share, fsal_openflags_t openflags, bool bypass) { char *cause = ""; if ((openflags & FSAL_O_READ) != 0 && share->share_deny_read > 0 && !bypass) { cause = "access read denied by existing deny read"; goto out_conflict; } if ((openflags & FSAL_O_WRITE) != 0 && (share->share_deny_write_mand > 0 || (!bypass && share->share_deny_write > 0))) { cause = "access write denied by existing deny write"; goto out_conflict; } if ((openflags & FSAL_O_DENY_READ) != 0 && share->share_access_read > 0) { cause = "deny read denied by existing access read"; goto out_conflict; } if (((openflags & FSAL_O_DENY_WRITE) != 0 || (openflags & FSAL_O_DENY_WRITE_MAND) != 0) && share->share_access_write > 0) { cause = "deny write denied by existing access write"; goto out_conflict; } return fsalstat(ERR_FSAL_NO_ERROR, 0); out_conflict: LogDebugAlt(COMPONENT_STATE, COMPONENT_FSAL, "Share conflict detected: %s openflags=%d bypass=%s", cause, (int)openflags, bypass ? "yes" : "no"); LogFullDebugAlt( COMPONENT_STATE, COMPONENT_FSAL, "share->share_deny_read=%d share->share_deny_write=%d share->share_access_read=%d share->share_access_write=%d", share->share_deny_read, share->share_deny_write, share->share_access_read, share->share_access_write); return fsalstat(ERR_FSAL_SHARE_DENIED, 0); } /** * @brief Check two shares for conflict and merge. * * The caller is responsible for protecting the share. * * When two object handles are merged that both contain shares, we must * check if the duplicate has a share conflict with the original. If * so, we will return ERR_FSAL_SHARE_DENIED. * * NOTE: dupe_share should belong to a fsal_obj_handle that has just been * created and is not accessible. As such, each of the share counters * MUST be 0 or 1, and it MUST be ok to access them without holding the * obj_lock. * * @param[in] orig_hdl fsal_obj_handle the orig_share belongs to * @param[in] orig_share Original share * @param[in] dupe_share Duplicate share * * @retval ERR_FSAL_SHARE_DENIED - a conflict occurred. * */ fsal_status_t merge_share(struct fsal_obj_handle *orig_hdl, struct fsal_share *orig_share, struct fsal_share *dupe_share) { fsal_status_t status = { ERR_FSAL_SHARE_DENIED, 0 }; /* Check if dupe_share represents no share reservation at all, if * so, we can trivially exit. There's nothing to do and we don't * need the obj_lock. */ if (dupe_share->share_deny_read == 0 && dupe_share->share_deny_write == 0 && dupe_share->share_deny_write_mand == 0 && dupe_share->share_access_read == 0 && dupe_share->share_access_write == 0) { /* No conflict and no update, just return success. */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } PTHREAD_RWLOCK_wrlock(&orig_hdl->obj_lock); if (dupe_share->share_access_read > 0 && orig_share->share_deny_read > 0) { LogDebug( COMPONENT_STATE, "Share conflict detected: access read denied by existing deny read"); goto out_conflict; } if (dupe_share->share_deny_read > 0 && orig_share->share_access_read > 0) { LogDebug( COMPONENT_STATE, "Share conflict detected: deny read denied by existing access read"); goto out_conflict; } /* When checking deny write, we ONLY need to look at share_deny_write * since it counts BOTH FSAL_O_DENY_WRITE and FSAL_O_DENY_WRITE_MAND. */ if (dupe_share->share_access_write > 0 && orig_share->share_deny_write > 0) { LogDebug( COMPONENT_STATE, "Share conflict detected: access write denied by existing deny write"); goto out_conflict; } if (dupe_share->share_deny_write > 0 && orig_share->share_access_write > 0) { LogDebug( COMPONENT_STATE, "Share conflict detected: deny write denied by existing access write"); goto out_conflict; } /* Now that we are ok, merge the share counters in the original */ orig_share->share_access_read += dupe_share->share_access_read; orig_share->share_access_write += dupe_share->share_access_write; orig_share->share_deny_read += dupe_share->share_deny_read; orig_share->share_deny_write += dupe_share->share_deny_write; orig_share->share_deny_write_mand += dupe_share->share_deny_write_mand; status = fsalstat(ERR_FSAL_NO_ERROR, 0); out_conflict: PTHREAD_RWLOCK_unlock(&orig_hdl->obj_lock); return status; } /** this refinement gets rid of the rw lock in the I/O path and provides a bit more fairness to open/upgrade/downgrade/close and uses dec_and_lock to better prevent spurious wakeup due to a race that is not handled above increment io_work if fd_work != 0 lock mutex if fd_work != 0 // fd work is waiting, block until it's done if decrement io_work == 0 signal condition var while fd_work != 0 wait on condition var // we are done waiting for fd work, so resume I/O increment io_work unlock mutex // now we know no fd work is waiting or in progress and can't start initiate I/O on I/O complete (async or immediate): ------------------------------------- if decrement_and_lock io_work if fd_work != 0 signal condition var unlock mutex open/upgrade/downgrade/close: ----------------------------- increment fd_work lock mutex while io_work != 0 // now fd_work is non-zero and io_work is zero, any I/O that // tries to start will block until fd_work goes to zero wait on cond var // io_work was zero, and because we checked while holding the mutex, but made // fd_work non-zero before taking the mutex any I/O threads that try to start // after we took the mutex MUST be blocked, proceed unlock mutex // serialize any open/upgrade/downgrade/close take write lock re-open fd in new mode or close it release write lock // now ready to allow I/O to resume if decrement_and_lock fd_work if fd_work == 0 signal cond var unlock mutex */ pthread_mutex_t fsal_fd_mutex; pthread_cond_t fsal_fd_cond; struct glist_head fsal_fd_global_lru = GLIST_HEAD_INIT(fsal_fd_global_lru); int32_t fsal_fd_global_counter; int32_t fsal_fd_state_counter; int32_t fsal_fd_temp_counter; time_t lru_run_interval; bool Cache_FDs; bool close_fast; static struct fridgethr *fd_lru_fridge; uint32_t lru_try_one(void) { struct fsal_fd *fsal_fd; fsal_status_t status; struct req_op_context op_context; struct fsal_obj_handle *obj_hdl; int work = 0; PTHREAD_MUTEX_lock(&fsal_fd_mutex); fsal_fd = glist_last_entry(&fsal_fd_global_lru, struct fsal_fd, fd_lru); if (fsal_fd != NULL) { /* Protect the fsal_fd until we can get it's lock. */ atomic_inc_int32_t(&fsal_fd->lru_reclaim); /* Drop the fsal_fd_mutex so we can take the work_mutex. */ PTHREAD_MUTEX_unlock(&fsal_fd_mutex); get_gsh_export_ref(fsal_fd->fsal_export->owning_export); /* Now we can safely work on the object, we want to close it. */ init_op_context_simple(&op_context, fsal_fd->fsal_export->owning_export, fsal_fd->fsal_export); fsal_fd->fsal_export->exp_ops.get_fsal_obj_hdl( fsal_fd->fsal_export, fsal_fd, &obj_hdl); status = close_fsal_fd(obj_hdl, fsal_fd, true); if (!FSAL_IS_ERROR(status)) work = 1; release_op_context(); /* Now reacquire the fsal_fd_mutex */ PTHREAD_MUTEX_lock(&fsal_fd_mutex); /* And drop the flag */ atomic_dec_int32_t(&fsal_fd->lru_reclaim); /* And let anyone waiting know we're ok... */ PTHREAD_COND_signal(&fsal_fd_cond); } PTHREAD_MUTEX_unlock(&fsal_fd_mutex); return work; } struct fd_lru_state fd_lru_state; uint32_t futility_count; uint32_t required_progress; uint32_t reaper_work; /** * @brief Function that executes in the fd_lru thread * * This function is responsible for cleaning the FD cache. It works * by the following rules: * * - If the number of open FDs is below the low water mark, do * nothing. * * - If the number of open FDs is between the low and high water * mark, make one pass through... * * - If the number of open FDs is greater than the high water mark, * we consider ourselves to be in extremis. In this case we make a * number of passes through the queue not to exceed the number of * passes that would be required to process the number of entries * equal to a biggest_window percent of the system specified * maximum. * * - If we are in extremis, and performing the maximum amount of work * allowed has not moved the open FD count required_progress% * toward the high water mark, increment fd_lru_state.futility. If * fd_lru_state.futility reaches futility_count, temporarily disable * FD caching. * * - Every time we wake through timeout, reset futility_count to 0. * * - If we fall below the low water mark and FD caching has been * temporarily disabled, re-enable it. * * @param[in] ctx Fridge context */ void fd_lru_run(struct fridgethr_context *ctx) { /* True if we were explicitly awakened. */ bool woke = ctx->woke; /* Finalized */ uint32_t fdratepersec = 1, fds_avg, fddelta; float fdnorm, fdwait_ratio, fdmulti; time_t threadwait = lru_run_interval; /* True if we are taking extreme measures to reclaim FDs */ bool extremis = false; /* Total work done in all passes so far. If this exceeds the * window, stop. */ uint32_t totalwork = 0; /* The current count (after reaping) of open FDs */ int32_t currentopen = 0; time_t new_thread_wait; static bool first_time = TRUE; if (first_time) { /* Wait for NFS server to properly initialize */ nfs_init_wait(); first_time = FALSE; } SetNameFunction("fd_lru"); fds_avg = (fd_lru_state.fds_hiwat - fd_lru_state.fds_lowat) / 2; currentopen = atomic_fetch_int32_t(&fsal_fd_global_counter); extremis = currentopen > fd_lru_state.fds_hiwat; LogFullDebug(COMPONENT_FSAL, "FD LRU awakes."); if (!woke) { /* If we make it all the way through a timed sleep without being woken, we assume we aren't racing against the impossible. */ if (fd_lru_state.futility >= futility_count) LogInfo(COMPONENT_FSAL, "Leaving FD futility mode."); fd_lru_state.futility = 0; } /* Check for fd_state transitions */ LogDebug(COMPONENT_FSAL, "FD count fsal_fd_global_counter is %" PRIi32 " and low water mark is %" PRIi32 " and high water mark is %" PRIi32 " %s", currentopen, fd_lru_state.fds_lowat, fd_lru_state.fds_hiwat, ((currentopen >= fd_lru_state.fds_lowat) || (Cache_FDs == false)) ? "(reaping)" : "(not reaping)"); if (currentopen < fd_lru_state.fds_lowat) { if (atomic_fetch_uint32_t(&fd_lru_state.fd_state) > FD_LOW) { LogEvent(COMPONENT_FSAL, "Return to normal fd reaping."); atomic_store_uint32_t(&fd_lru_state.fd_state, FD_LOW); } } else if (currentopen < fd_lru_state.fds_hiwat && atomic_fetch_uint32_t(&fd_lru_state.fd_state) == FD_LIMIT) { LogEvent(COMPONENT_FSAL, "Count of fd is below high water mark."); atomic_store_uint32_t(&fd_lru_state.fd_state, FD_MIDDLE); } /* Reap file descriptors. This is a preliminary example of the * L2 functionality rather than something we expect to be * permanent. (It will have to adapt heavily to the new FSAL * API, for example.) */ if ((currentopen >= fd_lru_state.fds_lowat) || (Cache_FDs == false)) { /* The count of open file descriptors before this run of the reaper. */ int32_t formeropen = currentopen; /* Work done in the most recent pass of all queues. if value is less than the work to do in a single queue, don't spin through more passes. */ uint32_t workpass = 0; time_t curr_time = time(NULL); if ((curr_time >= fd_lru_state.prev_time) && (curr_time - fd_lru_state.prev_time < fridgethr_getwait(ctx))) threadwait = curr_time - fd_lru_state.prev_time; fdratepersec = ((curr_time <= fd_lru_state.prev_time) || (formeropen < fd_lru_state.prev_fd_count)) ? 1 : (formeropen - fd_lru_state.prev_fd_count) / (curr_time - fd_lru_state.prev_time); LogFullDebug(COMPONENT_FSAL, "fdrate:%u fdcount:%" PRIu32 " slept for %" PRIu64 " sec", fdratepersec, formeropen, ((uint64_t)(curr_time - fd_lru_state.prev_time))); if (extremis) { LogDebug( COMPONENT_FSAL, "Open FDs over high water mark, reaping aggressively."); } /* Attempt to close fds. */ do { int i; workpass = 0; LogDebug(COMPONENT_FSAL, "Reaping up to %" PRIu32 " fds", reaper_work); LogFullDebug(COMPONENT_FSAL, "formeropen=%" PRIu32 " totalwork=%" PRIu32, formeropen, totalwork); for (i = 0; i < reaper_work; ++i) workpass += lru_try_one(); totalwork += workpass; } while (extremis && (workpass >= reaper_work) && (totalwork < fd_lru_state.biggest_window)); currentopen = atomic_fetch_int32_t(&fsal_fd_global_counter); if (extremis && ((currentopen > formeropen) || (formeropen - currentopen < (((formeropen - fd_lru_state.fds_hiwat) * required_progress) / 100)))) { if (++fd_lru_state.futility == futility_count) { LogWarn(COMPONENT_FSAL, "Futility count exceeded. Client load is opening FDs faster than the LRU thread can close them. current_open = %" PRIi32 ", former_open = %" PRIi32, currentopen, formeropen); } } } /* The following calculation will progressively garbage collect * more frequently as these two factors increase: * 1. current number of open file descriptors * 2. rate at which file descriptors are being used. * * When there is little activity, this thread will sleep at the * "LRU_Run_Interval" from the config. * * When there is a lot of activity, the thread will sleep for a * much shorter time. */ fd_lru_state.prev_fd_count = currentopen; fd_lru_state.prev_time = time(NULL); fdnorm = (fdratepersec + fds_avg) / fds_avg; fddelta = (currentopen > fd_lru_state.fds_lowat) ? (currentopen - fd_lru_state.fds_lowat) : 0; fdmulti = (fddelta * 10) / fds_avg; fdmulti = fdmulti ? fdmulti : 1; fdwait_ratio = fd_lru_state.fds_hiwat / ((fd_lru_state.fds_hiwat + fdmulti * fddelta) * fdnorm); new_thread_wait = threadwait * fdwait_ratio; if (new_thread_wait < lru_run_interval / 10) new_thread_wait = lru_run_interval / 10; /* if new_thread_wait is 0, lru_run will not be scheduled */ if (new_thread_wait == 0) new_thread_wait = 1; fridgethr_setwait(ctx, new_thread_wait); LogDebug(COMPONENT_FSAL, "After work, fsal_fd_global_counter:%" PRIi32 " fdrate:%u new_thread_wait=%" PRIu64, atomic_fetch_int32_t(&fsal_fd_global_counter), fdratepersec, (uint64_t)new_thread_wait); LogFullDebug(COMPONENT_FSAL, "currentopen=%" PRIu32 " futility=%d totalwork=%" PRIu32 " biggest_window=%d extremis=%d fds_lowat=%d ", currentopen, fd_lru_state.futility, totalwork, fd_lru_state.biggest_window, extremis, fd_lru_state.fds_lowat); } /** * @brief Bump this fsal_fd in the fd LRU if this is a global fd. * * @param[in] fsal_fd The fsal_fd to insert. * */ void bump_fd_lru(struct fsal_fd *fsal_fd) { if (fsal_fd->fd_type == FSAL_FD_GLOBAL) { PTHREAD_MUTEX_lock(&fsal_fd_mutex); glist_del(&fsal_fd->fd_lru); glist_add(&fsal_fd_global_lru, &fsal_fd->fd_lru); PTHREAD_MUTEX_unlock(&fsal_fd_mutex); LogFullDebug( COMPONENT_FSAL, "Inserted fsal_fd(%p) to fd_global_lru with count(%d)", fsal_fd, atomic_fetch_int32_t(&fsal_fd_global_counter)); } } /** * @brief Increment the appropriate fd counter and insert into fd LRU if * this is a global fd. * * @param[in] fsal_fd The fsal_fd to insert. * */ void insert_fd_lru(struct fsal_fd *fsal_fd) { LogFullDebug( COMPONENT_FSAL, "Inserting fsal_fd(%p) to fd_lru for type(%d) count(%d/%d/%d)", fsal_fd, fsal_fd->fd_type, atomic_fetch_int32_t(&fsal_fd_global_counter), atomic_fetch_int32_t(&fsal_fd_state_counter), atomic_fetch_int32_t(&fsal_fd_temp_counter)); switch (fsal_fd->fd_type) { case FSAL_FD_OLD_STYLE: /* OOPS - we shouldn't get here... */ assert(fsal_fd->fd_type < FSAL_FD_GLOBAL); break; case FSAL_FD_GLOBAL: atomic_inc_int32_t(&fsal_fd_global_counter); bump_fd_lru(fsal_fd); break; case FSAL_FD_STATE: atomic_inc_int32_t(&fsal_fd_state_counter); break; case FSAL_FD_TEMP: atomic_inc_int32_t(&fsal_fd_temp_counter); break; } } /** * @brief Decrement the appropriate fd counter and remove from fd LRU if * this is a global fd. * * @param[in] fsal_fd The fsal_fd to insert. * */ void remove_fd_lru(struct fsal_fd *fsal_fd) { int32_t count; LogFullDebug( COMPONENT_FSAL, "Removing fsal_fd(%p) from fd_lru for type(%d) count(%d/%d/%d)", fsal_fd, fsal_fd->fd_type, atomic_fetch_int32_t(&fsal_fd_global_counter), atomic_fetch_int32_t(&fsal_fd_state_counter), atomic_fetch_int32_t(&fsal_fd_temp_counter)); switch (fsal_fd->fd_type) { case FSAL_FD_OLD_STYLE: /* OOPS - we shouldn't get here... */ assert(fsal_fd->fd_type < FSAL_FD_GLOBAL); break; case FSAL_FD_GLOBAL: count = atomic_dec_int32_t(&fsal_fd_global_counter); if (count < 0) { LogCrit(COMPONENT_FSAL, "fsal_fd_global_counter is negative: %" PRIi32, count); abort(); } PTHREAD_MUTEX_lock(&fsal_fd_mutex); glist_del(&fsal_fd->fd_lru); PTHREAD_MUTEX_unlock(&fsal_fd_mutex); break; case FSAL_FD_STATE: atomic_dec_int32_t(&fsal_fd_state_counter); break; case FSAL_FD_TEMP: atomic_dec_int32_t(&fsal_fd_temp_counter); break; } } void fsal_init_fds_limit(struct fd_lru_parameter *params) { int code = 0; /* Rlimit for open file descriptors */ struct rlimit rlim = { .rlim_cur = RLIM_INFINITY, .rlim_max = RLIM_INFINITY }; fd_lru_state.fd_fallback_limit = params->fd_fallback_limit; /* Find out the system-imposed file descriptor limit */ if (get_open_file_limit(&rlim) != 0) { code = errno; LogCrit(COMPONENT_MDCACHE_LRU, "Call to getrlimit failed with error %d. This should not happen. Assigning default of %d.", code, fd_lru_state.fd_fallback_limit); fd_lru_state.fds_system_imposed = fd_lru_state.fd_fallback_limit; } else { if (rlim.rlim_cur < rlim.rlim_max) { /* Save the old soft value so we can fall back to it if setrlimit fails. */ rlim_t old_soft = rlim.rlim_cur; LogInfo(COMPONENT_MDCACHE_LRU, "Attempting to increase soft limit from %" PRIu64 " to hard limit of %" PRIu64, (uint64_t)rlim.rlim_cur, (uint64_t)rlim.rlim_max); rlim.rlim_cur = rlim.rlim_max; if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) { code = errno; LogWarn(COMPONENT_MDCACHE_LRU, "Attempt to raise soft FD limit to hard FD limit failed with error %d. Sticking to soft limit.", code); rlim.rlim_cur = old_soft; } } if (rlim.rlim_cur == RLIM_INFINITY) { FILE *nr_open; nr_open = fopen("/proc/sys/fs/nr_open", "r"); if (nr_open == NULL) { code = errno; LogWarn(COMPONENT_MDCACHE_LRU, "Attempt to open /proc/sys/fs/nr_open failed (%d)", code); goto err_open; } code = fscanf(nr_open, "%" SCNu32 "\n", &fd_lru_state.fds_system_imposed); if (code != 1) { code = errno; LogMajor( COMPONENT_MDCACHE_LRU, "The rlimit on open file descriptors is infinite and the attempt to find the system maximum failed with error %d.", code); LogMajor( COMPONENT_MDCACHE_LRU, "Assigning the default fallback of %d which is almost certainly too small.", fd_lru_state.fd_fallback_limit); LogMajor( COMPONENT_MDCACHE_LRU, "If you are on a Linux system, this should never happen."); LogMajor( COMPONENT_MDCACHE_LRU, "If you are running some other system, please set an rlimit on file descriptors (for example, with ulimit) for this process and consider editing " __FILE__ "to add support for finding your system's maximum."); fd_lru_state.fds_system_imposed = fd_lru_state.fd_fallback_limit; } fclose(nr_open); err_open:; } else { fd_lru_state.fds_system_imposed = rlim.rlim_cur; } } LogEvent(COMPONENT_MDCACHE_LRU, "Setting the system-imposed limit on FDs to %d.", fd_lru_state.fds_system_imposed); fd_lru_state.fds_hard_limit = (params->fd_limit_percent * fd_lru_state.fds_system_imposed) / 100; fd_lru_state.fds_hiwat = (params->fd_hwmark_percent * fd_lru_state.fds_system_imposed) / 100; fd_lru_state.fds_lowat = (params->fd_lwmark_percent * fd_lru_state.fds_system_imposed) / 100; fd_lru_state.futility = 0; if (params->reaper_work) { /* Backwards compatibility */ reaper_work = (params->reaper_work + 16) / 17; } else { /* New parameter */ reaper_work = params->reaper_work_per_lane; } fd_lru_state.biggest_window = (params->biggest_window * fd_lru_state.fds_system_imposed) / 100; } /** * Initialize subsystem */ fsal_status_t fd_lru_pkginit(struct fd_lru_parameter *params) { /* Return code from system calls */ int code = 0; struct fridgethr_params frp; PTHREAD_MUTEX_init(&fsal_fd_mutex, NULL); PTHREAD_COND_init(&fsal_fd_cond, NULL); futility_count = params->futility_count; required_progress = params->required_progress; lru_run_interval = params->lru_run_interval; Cache_FDs = params->Cache_FDs; close_fast = params->close_fast; memset(&frp, 0, sizeof(struct fridgethr_params)); frp.thr_max = 1; frp.thr_min = 1; frp.thread_delay = params->lru_run_interval; frp.flavor = fridgethr_flavor_looper; atomic_store_int32_t(&fsal_fd_global_counter, 0); fd_lru_state.prev_fd_count = 0; atomic_store_uint32_t(&fd_lru_state.fd_state, FD_LOW); fsal_init_fds_limit(params); /* spawn LRU background thread */ code = fridgethr_init(&fd_lru_fridge, "FD_LRU_fridge", &frp); if (code != 0) { LogMajor(COMPONENT_MDCACHE_LRU, "Unable to initialize FD LRU fridge, error code %d.", code); return fsalstat(posix2fsal_error(code), code); } code = fridgethr_submit(fd_lru_fridge, fd_lru_run, NULL); if (code != 0) { LogMajor(COMPONENT_MDCACHE_LRU, "Unable to start Entry LRU thread, error code %d.", code); return fsalstat(posix2fsal_error(code), code); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * Shutdown subsystem * * @return 0 on success, POSIX errors on failure. */ fsal_status_t fd_lru_pkgshutdown(void) { int rc; rc = fridgethr_sync_command(fd_lru_fridge, fridgethr_comm_stop, 120); if (rc == ETIMEDOUT) { LogMajor(COMPONENT_MDCACHE_LRU, "Shutdown timed out, cancelling threads."); fridgethr_cancel(fd_lru_fridge); } else if (rc != 0) { LogMajor(COMPONENT_MDCACHE_LRU, "Failed shutting down LRU thread: %d", rc); } PTHREAD_MUTEX_destroy(&fsal_fd_mutex); PTHREAD_COND_destroy(&fsal_fd_cond); return fsalstat(posix2fsal_error(rc), rc); } /** * @brief Function to close a fsal_fd while protecting it. * * @param[in] obj_hdl File on which to operate * @param[in] fsal_fd File handle to close * @param[in] is_reclaiming Indicates we are closing files to reclaim fd * * @return FSAL status. */ fsal_status_t close_fsal_fd(struct fsal_obj_handle *obj_hdl, struct fsal_fd *fsal_fd, bool is_reclaiming) { fsal_status_t status; bool is_globalfd = fsal_fd->fd_type == FSAL_FD_GLOBAL; /* Assure that is_reclaiming is only set when it's a global fd */ assert(is_reclaiming ? is_globalfd : true); /* Indicate we want to do fd work */ status = fsal_start_fd_work(fsal_fd, is_reclaiming); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "fsal_start_fd_work returned %s", fsal_err_txt(status)); return status; } /* Now we hold the mutex and no one is doing I/O so we can safely * close the fd. */ status = obj_hdl->obj_ops->close_func(obj_hdl, fsal_fd); if (status.major != ERR_FSAL_NOT_OPENED) { if (is_globalfd) { /* Need to decrement the appropriate counter and remove * from LRU for globalfd. */ remove_fd_lru(fsal_fd); } } else { /* Wasn't open. Not an error, but shouldn't remove from LRU. */ status = fsalstat(ERR_FSAL_NO_ERROR, 0); } fsal_complete_fd_work(fsal_fd); if (is_reclaiming) { PTHREAD_MUTEX_lock(&fsal_fd_mutex); PTHREAD_COND_signal(&fsal_fd_cond); PTHREAD_MUTEX_unlock(&fsal_fd_mutex); } else if (is_globalfd) { while (atomic_fetch_int32_t(&fsal_fd->lru_reclaim)) { /* Just in case FD LRU is trying to work on this fd, * wait until its done. Note it really won't have * anything to do since we have just closed the fd, but * this assures the lifetime of the fsal_fd is * maintained while LRU does its work. */ PTHREAD_MUTEX_lock(&fsal_fd_mutex); PTHREAD_COND_wait(&fsal_fd_cond, &fsal_fd_mutex); PTHREAD_MUTEX_unlock(&fsal_fd_mutex); } } return status; } /** * @brief Function to open or reopen a fsal_fd. * * NOTE: Assumes fsal_fd->work_mutex is held and that fd_work has been * incremented. After re_opening if necessary, fd_work will be * decremented, fd_work_cond will be signaled, and io_work_cond will be * broadcast. * * NOTE: We should not come in here with openflags of FSAL_O_ANY * * @param[in] obj_hdl File on which to operate * @param[in] openflags New mode for open * @param[out] fd File descriptor that is to be used * @param[in] can_start We know for sure we can start * * @return FSAL status. */ fsal_status_t reopen_fsal_fd(struct fsal_obj_handle *obj_hdl, fsal_openflags_t openflags, struct fsal_fd *fsal_fd, bool can_start) { fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; /* Wait for lull in io work */ while (!can_start && atomic_fetch_int32_t(&fsal_fd->io_work) != 0) { LogFullDebug(COMPONENT_FSAL, "%p wait for lull - io_work = %" PRIi32 " fd_work = %" PRIi32, fsal_fd, atomic_fetch_int32_t(&fsal_fd->io_work), atomic_fetch_int32_t(&fsal_fd->fd_work)); /* io work is in progress or trying to start, * wait for it to complete (or not start) */ PTHREAD_COND_wait(&fsal_fd->fd_work_cond, &fsal_fd->work_mutex); } fsal_openflags_t old_openflags = fsal_fd->openflags; /* Now that we are actually about to open or re-open, let's * make sure we get the file opened however desired. * * Take the requested mode and combine with the existing mode. * Then, in case any thread is asking for read combine that, * an in case any thread is asking for write, combine that. */ openflags |= fsal_fd->openflags & FSAL_O_RDWR; if (atomic_fetch_int32_t(&fsal_fd->want_read)) openflags |= FSAL_O_READ; if (atomic_fetch_int32_t(&fsal_fd->want_write)) openflags |= FSAL_O_WRITE; /* And THEN check the combined mode against the current mode. */ if (!open_correct(fsal_fd->openflags, openflags)) { status = fsal_reopen_fd(obj_hdl, openflags, fsal_fd); LogDebug(COMPONENT_FSAL, "fsal_reopen_fd returned %s", fsal_err_txt(status)); if (FSAL_IS_SUCCESS(status)) { if (old_openflags == FSAL_O_CLOSED) { /* This is actually an open, need to increment * appropriate counter and insert into LRU. */ insert_fd_lru(fsal_fd); } else { /* We are touching the file so bump it in the * LRU to help keep the LRU tail free of * unreapable fds. */ bump_fd_lru(fsal_fd); } } } /* Indicate we are done with fd work and signal any waiters. */ bool io_can_start = (atomic_dec_int32_t(&fsal_fd->fd_work) == 0); LogFullDebug(COMPONENT_FSAL, "%p done fd work - io_work = %" PRIi32 " fd_work = %" PRIi32, fsal_fd, atomic_fetch_int32_t(&fsal_fd->io_work), atomic_fetch_int32_t(&fsal_fd->fd_work)); /* Wake up at least one thread waiting to do fd work */ PTHREAD_COND_signal(&fsal_fd->fd_work_cond); if (io_can_start) { /* Wake up all threads waiting to do io work */ PTHREAD_COND_broadcast(&fsal_fd->io_work_cond); } return status; } /** * @brief Check if allowed to open or re-open. * * @param[in] fsal_fd The fsal_fd we are checking * @param[in] may_open Allowed to open * @param[in] may_reopen Allowed to re-open * * @return true if not allowed to open or re-open * @return false otherwise * */ static inline bool cant_reopen(struct fsal_fd *fsal_fd, bool may_open, bool may_reopen) { int32_t open_fds = atomic_fetch_int32_t(&fsal_fd_global_counter); if (fsal_fd->fd_type == FSAL_FD_GLOBAL && open_fds >= fd_lru_state.fds_hard_limit) { LogAtLevel(COMPONENT_FSAL, atomic_fetch_uint32_t(&fd_lru_state.fd_state) != FD_LIMIT ? NIV_CRIT : NIV_DEBUG, "FD Hard Limit (%" PRIu32 ") Exceeded (fsal_fd_global_counter = %" PRIi32 "), waking LRU thread.", fd_lru_state.fds_hard_limit, open_fds); atomic_store_uint32_t(&fd_lru_state.fd_state, FD_LIMIT); fridgethr_wake(fd_lru_fridge); /* Too many open files, don't open any more. */ return true; } if (fsal_fd->fd_type == FSAL_FD_GLOBAL && open_fds >= fd_lru_state.fds_hiwat) { LogAtLevel(COMPONENT_FSAL, atomic_fetch_uint32_t(&fd_lru_state.fd_state) == FD_LOW ? NIV_INFO : NIV_DEBUG, "FDs above high water mark (%" PRIu32 ", fsal_fd_global_counter = %" PRIi32 "), waking LRU thread.", fd_lru_state.fds_hiwat, open_fds); atomic_store_uint32_t(&fd_lru_state.fd_state, FD_HIGH); fridgethr_wake(fd_lru_fridge); } if (may_open && fsal_fd->openflags == 0) { /* Can open and was closed */ return false; } /* Assumed the openflags were wrong, reverse sense of may_reopen */ return !may_reopen; } /** * @brief Wait to start I/O. Returns with io_work counter incremented. * * NOTE: If openflags is FSAL_O_ANY then may_open and may_reopen MUST be false. * * @param[in] obj_hdl The objet we are working on * @param[in] fsal_fd The file descriptor to do I/O on * @param[in] openflags The open mode required for the I/O desired * @param[in] may_open Allowed to open the file * @param[in] may_reopen Allowed to re-open the file * */ fsal_status_t wait_to_start_io(struct fsal_obj_handle *obj_hdl, struct fsal_fd *fsal_fd, fsal_openflags_t openflags, bool may_open, bool may_reopen) { fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; bool retried = false; int32_t open_fds = atomic_fetch_int32_t(&fsal_fd_global_counter); if (fsal_fd->fd_type == FSAL_FD_GLOBAL && open_fds >= fd_lru_state.fds_hard_limit) { LogAtLevel(COMPONENT_FSAL, atomic_fetch_uint32_t(&fd_lru_state.fd_state) != FD_LIMIT ? NIV_CRIT : NIV_DEBUG, "FD Hard Limit (%" PRIu32 ") Exceeded (fsal_fd_global_counter = %" PRIi32 "), waking LRU thread.", fd_lru_state.fds_hard_limit, open_fds); atomic_store_uint32_t(&fd_lru_state.fd_state, FD_LIMIT); fridgethr_wake(fd_lru_fridge); /* Too many open files, don't open any more. */ status = fsalstat(ERR_FSAL_DELAY, EBUSY); return status; } retry: /* Indicate we want to do I/O work */ atomic_inc_int32_t(&fsal_fd->io_work); LogFullDebug(COMPONENT_FSAL, "%p try io_work = %" PRIi32 " fd_work = %" PRIi32, fsal_fd, atomic_fetch_int32_t(&fsal_fd->io_work), atomic_fetch_int32_t(&fsal_fd->fd_work)); /* The following two checks make sure that if multiple threads are * contending to do fd_work, whoever actually opens or re-opens the * file will open it in a mode that satisfies everyone. Note that we * only want to do this once, thus the check for retried == false. */ if (openflags & FSAL_O_READ && retried == false) { /* Indicate we want to read */ atomic_inc_int32_t(&fsal_fd->want_read); } if (openflags & FSAL_O_WRITE && retried == false) { /* Indicate we want to read */ atomic_inc_int32_t(&fsal_fd->want_write); } /* Check if fd_work is in progress (or trying to start). We do this * check unlocked because fd work will check io work in a way that * assures that io work always wins any race. */ while (atomic_fetch_int32_t(&fsal_fd->fd_work) != 0) { /* We need to back off on trying to do I/O so the fd work can * complete. */ LogFullDebug(COMPONENT_FSAL, "%p back off io_work (-1) = %" PRIi32 " fd_work = %" PRIi32, fsal_fd, atomic_fetch_int32_t(&fsal_fd->io_work) - 1, atomic_fetch_int32_t(&fsal_fd->fd_work)); if (PTHREAD_MUTEX_dec_int32_t_and_lock(&fsal_fd->io_work, &fsal_fd->work_mutex)) { /* Let the thread waiting to do fd work know it can * proceed. */ PTHREAD_COND_signal(&fsal_fd->fd_work_cond); } else { /* need the mutex anyway... */ PTHREAD_MUTEX_lock(&fsal_fd->work_mutex); } /* Now we need to wait on the io_work condition variable... */ while (atomic_fetch_int32_t(&fsal_fd->fd_work) != 0) { LogFullDebug(COMPONENT_FSAL, "%p wait for fd work - io_work = %" PRIi32 " fd_work = %" PRIi32, fsal_fd, atomic_fetch_int32_t(&fsal_fd->io_work), atomic_fetch_int32_t(&fsal_fd->fd_work)); PTHREAD_COND_wait(&fsal_fd->io_work_cond, &fsal_fd->work_mutex); } /* At this point the fd's open flags are protected because we * hold the mutex so we can check them and see if we need to * open or re-open. We do this here even though it's somewhat * redundant with the similar check outside the lock so we * don't thrash the mutex. */ LogFullDebug(COMPONENT_FSAL, "Open mode = %x, desired mode = %x", (int)fsal_fd->openflags, (int)openflags); if (!open_correct(fsal_fd->openflags, openflags)) { if (cant_reopen(fsal_fd, may_open, may_reopen)) { /* fsal_fd is in wrong mode and we aren't * allowed to reopen, so return EBUSY. */ /* Wake up at least one thread waiting to do fd * work */ PTHREAD_COND_signal(&fsal_fd->fd_work_cond); /* Wake up all threads waiting to do io work */ PTHREAD_COND_broadcast(&fsal_fd->io_work_cond); PTHREAD_MUTEX_unlock(&fsal_fd->work_mutex); /* Use fsalstat to avoid LogInfo... */ status = fsalstat(ERR_FSAL_DELAY, EBUSY); goto out; } /* Indicate we want to do fd work */ atomic_inc_int32_t(&fsal_fd->fd_work); LogFullDebug(COMPONENT_FSAL, "%p try fd work - io_work = %" PRIi32 " fd_work = %" PRIi32, fsal_fd, atomic_fetch_int32_t(&fsal_fd->io_work), atomic_fetch_int32_t(&fsal_fd->fd_work)); status = reopen_fsal_fd(obj_hdl, openflags, fsal_fd, false); if (FSAL_IS_ERROR(status)) { PTHREAD_MUTEX_unlock(&fsal_fd->work_mutex); goto out; } } /* Try again to indicate we want to do I/O work. Note that this * WILL prevent the fd from being closed, which means that * since reopen_fsal_fd() combines all desired open modes, we * WILL be able to use the fd once we actually get to start * I/O. */ atomic_inc_int32_t(&fsal_fd->io_work); LogFullDebug(COMPONENT_FSAL, "%p try io_work = %" PRIi32 " fd_work = %" PRIi32, fsal_fd, atomic_fetch_int32_t(&fsal_fd->io_work), atomic_fetch_int32_t(&fsal_fd->fd_work)); PTHREAD_MUTEX_unlock(&fsal_fd->work_mutex); } /* At this point the fd's open flags are protected so we can check * them. If we had to wait for other threads to do fd_work, then * because reopen_fsal_fd() combines modes, the file MUST now be in a * usable state, and open_correct() will return true and we are on our * way. Otherwise, we had no contention for the fd and may need to * open or re-open it. */ LogFullDebug(COMPONENT_FSAL, "Open mode = %x, desired mode = %x", (int)fsal_fd->openflags, (int)openflags); if (!open_correct(fsal_fd->openflags, openflags)) { bool can_start = false; if (retried || cant_reopen(fsal_fd, may_open, may_reopen)) { /* fsal_fd is in wrong mode and we aren't * allowed to reopen, so return EBUSY. * OR we've already been here. */ /* Use fsalstat to avoid LogInfo... */ status = fsalstat(ERR_FSAL_DELAY, EBUSY); /* We no longer need to claim io work on fsal_fd * because we are going to continue with a temporary fd. */ LogFullDebug( COMPONENT_FSAL, "%p we don't need to claim io_work (-1) = %" PRIi32 " fd_work = %" PRIi32, fsal_fd, atomic_fetch_int32_t(&fsal_fd->io_work) - 1, atomic_fetch_int32_t(&fsal_fd->fd_work)); if (PTHREAD_MUTEX_dec_int32_t_and_lock( &fsal_fd->io_work, &fsal_fd->work_mutex)) { /* Let the thread waiting to do fd work know it * can proceed. */ PTHREAD_COND_signal(&fsal_fd->fd_work_cond); PTHREAD_MUTEX_unlock(&fsal_fd->work_mutex); } goto out; } /* Indicate we want to do fd work */ atomic_inc_int32_t(&fsal_fd->fd_work); LogFullDebug(COMPONENT_FSAL, "%p back off io_work (-1) = %" PRIi32 " fd_work = %" PRIi32, fsal_fd, atomic_fetch_int32_t(&fsal_fd->io_work) - 1, atomic_fetch_int32_t(&fsal_fd->fd_work)); if (PTHREAD_MUTEX_dec_int32_t_and_lock(&fsal_fd->io_work, &fsal_fd->work_mutex)) { /* We can proceed, we hold the work_mutex. */ can_start = true; } else { /* need the mutex anyway... */ PTHREAD_MUTEX_lock(&fsal_fd->work_mutex); } status = reopen_fsal_fd(obj_hdl, openflags, fsal_fd, can_start); PTHREAD_MUTEX_unlock(&fsal_fd->work_mutex); if (FSAL_IS_ERROR(status)) goto out; /* Now that we have the file open, we need to try and start i/o * again since we dropped our io_work count we might have to * wait for more fd work. * * Note that reopen_fsal_fd opens with the union of desired * modes so we should be good here. * * Also note we've already been here so we don't get stuck in an * infinite loop in case something is trying to close the fd. */ retried = true; goto retry; } /* Now we have the file open in the correct mode, and I/O has started. * Allow our caller to proceed. */ out: if (openflags & FSAL_O_READ) { /* Indicate we want to read */ atomic_dec_int32_t(&fsal_fd->want_read); } if (openflags & FSAL_O_WRITE) { /* Indicate we want to read */ atomic_dec_int32_t(&fsal_fd->want_write); } return status; } /** * @brief Start I/O on the global fd for the object. * * If the global fd is busy, tmp_fd will be opened to be used for the I/O. * * This function assures that the fd is open in the mode requested. If * the fd was already open, it closes it and reopens with the OR of the * requested modes. * * Optionally, out_fd can be NULL in which case a file is not actually * opened, in this case, all that actually happens is the share reservation * check. * * If openflags is FSAL_O_ANY, the caller will utilize the global file * descriptor if it is open, otherwise it will use a temporary file descriptor. * The primary use of this is to not open long lasting global file descriptors * for getattr and setattr calls. The other users of FSAL_O_ANY are NFSv3 LOCKT * for which this behavior is also desireable and NFSv3 UNLOCK where there * SHOULD be an open file descriptor attached to state, but if not, a temporary * file descriptor will work fine (and the resulting unlock won't do anything * since we just opened the temporary file descriptor). * * @param[in,out] out_fd File descriptor that is to be used * @param[in] obj_hdl File on which to operate * @param[in] my_fd The file descriptor associated with the object * @param[in] tmp_fd A temporary file descriptor * @param[in] openflags Mode for open * @param[in] bypass Indicates to bypass share_deny_read and * share_deny_write but not share_deny_write_mand * @param[in] share The fsal_share associated with the object * * @return FSAL status. */ fsal_status_t fsal_start_global_io(struct fsal_fd **out_fd, struct fsal_obj_handle *obj_hdl, struct fsal_fd *my_fd, struct fsal_fd *tmp_fd, fsal_openflags_t openflags, bool bypass, struct fsal_share *share) { fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; bool open_any = openflags == FSAL_O_ANY; if (!open_any && share != NULL) { status = check_share_conflict_and_update_locked( obj_hdl, share, FSAL_O_CLOSED, openflags, bypass); if (FSAL_IS_ERROR(status)) { LogDebug( COMPONENT_FSAL, "check_share_conflict_and_update_locked failed with %s", fsal_err_txt(status)); return status; } } if (close_fast) goto skip_global_fd; status = wait_to_start_io(obj_hdl, my_fd, openflags, !open_any, !open_any); if (!FSAL_IS_ERROR(status)) { *out_fd = my_fd; return status; } LogDebug(COMPONENT_FSAL, "wait_to_start_io failed with %s", fsal_err_txt(status)); skip_global_fd: if (status.major == ERR_FSAL_DELAY || close_fast) { /* We can't use the global fd, use the tmp_fd. We don't need * any synchronization as the tmp_fd is private to the * particular operation. */ status = fsal_reopen_fd( obj_hdl, open_any ? FSAL_O_READ : openflags, tmp_fd); if (!FSAL_IS_ERROR(status)) { tmp_fd->close_on_complete = true; *out_fd = tmp_fd; return status; } LogDebug(COMPONENT_FSAL, "fsal_reopen_fd failed with %s", fsal_err_txt(status)); } /* Can't proceed... */ if (!open_any && share != NULL) { /* Release the share reservation now by updating the counters. */ update_share_counters_locked(obj_hdl, share, openflags, FSAL_O_CLOSED); } *out_fd = NULL; return status; } /** * @brief Indicate to start I/O on an object, returning a usable fsal_fd. * * This function will find a usable fsal_fd to operate on and protect it * for I/O. If necessary, the fsal_fd will be opened or re-opend which may * entail waiting for io_work to complete. If a lock state is provided, there * is an option to return the fsal_fd from the open state instead, in which * case reusing_open_state_fd will be set true if the pointer is not NULL. * * Unlike the previous implemnentation of fsal_find_fd, this implementation does * not handle share reservation only requests and out_fd MUST be non-NULL. * * Note that FSAL_O_ANY may be passed on to fsal_start_global_io, see the * documentation of that function for the implications. * * @param[in,out] out_fd File descriptor that is to be used * @param[in] obj_hdl File on which to operate * @param[in] obj_fd The file descriptor associated with the object * @param[in] tmp_fd A temporary file descriptor * @param[in] state state_t to use for this operation * @param[in] openflags Mode for open * @param[in] open_for_locks Indicates file is open for locks * @param[out] reusing_open_state_fd Indicates whether already opened fd * @param[in] bypass If state doesn't indicate a share reservation, * bypass any deny read * @param[in] share The fsal_share associated with the object * * @return FSAL status. */ fsal_status_t fsal_start_io(struct fsal_fd **out_fd, struct fsal_obj_handle *obj_hdl, struct fsal_fd *obj_fd, struct fsal_fd *tmp_fd, struct state_t *state, fsal_openflags_t openflags, bool open_for_locks, bool *reusing_open_state_fd, bool bypass, struct fsal_share *share) { fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; struct fsal_fd *state_fd; if (state == NULL) goto global; /* Check if we can use the fd in the state */ state_fd = (struct fsal_fd *)(state + 1); LogFullDebug(COMPONENT_FSAL, "state_fd->openflags = %d openflags = %d%s", state_fd->openflags, openflags, open_for_locks ? " Open For Locks" : ""); /* We don't want to open it if it's not yet open, see below... Note that * the fd associated with a non-lock state_t will already be open. * * Thus the return should be ERR_FSAL_DELAY because the fd is not yet * open OR we have success because the fd is open and useable. */ /** @todo FSF - oops, what about a delegation? */ status = wait_to_start_io(obj_hdl, state_fd, openflags, false, false); if (FSAL_IS_SUCCESS(status)) { /* It was valid, return it. * Since we found a valid fd in the state, no need to * check deny modes. */ LogFullDebug(COMPONENT_FSAL, "Use state_fd %p", state_fd); if (out_fd) *out_fd = state_fd; return status; } LogFullDebug(COMPONENT_FSAL, "wait_to_start_io failed returned %s", fsal_err_txt(status)); assert(status.major == ERR_FSAL_DELAY); if (open_for_locks) { /* This is being opened for locks, we will not be able to * re-open so open for read/write. If that fails permission * check and openstate is available, retry with that state's * access mode. */ status = wait_to_start_io(obj_hdl, state_fd, FSAL_O_RDWR, true, false); if (status.major == ERR_FSAL_ACCESS && state->state_type == STATE_TYPE_LOCK) { /* Got an EACCESS and openstate may be available, try * again with it's openflags. Otherwise leave the * access error as is. */ struct state_t *openstate; /** @todo FSF - interesting question - what do we do if * openstate is being re-opened??? I think * we may also need to start I/O on the * open state while we do this reopen of * the lock state. * * Partial answer - operations on a * stateid SHOULD be serialized. If we are * opening for locks, it's the first lock, * so the caller used the open stateid. */ openstate = nfs4_State_Get_Pointer( state->state_data.lock.openstate_key); if (openstate != NULL) { struct fsal_fd *related_fd; related_fd = (struct fsal_fd *)(openstate + 1); status = wait_to_start_io( obj_hdl, state_fd, related_fd->openflags & FSAL_O_RDWR, true, false); dec_state_t_ref(openstate); } } else if (status.major == ERR_FSAL_DELAY) { /* Try with actual openflags which SHOULD succeed. */ status = wait_to_start_io(obj_hdl, state_fd, openflags, false, false); if (status.major == ERR_FSAL_DELAY) { LogCrit(COMPONENT_FSAL, "Conflicting open, can not re-open fd with locks"); status = posix2fsal_status(EINVAL); } } if (FSAL_IS_ERROR(status)) { LogCrit(COMPONENT_FSAL, "Open for locking failed for access %s", openflags == FSAL_O_RDWR ? "Read/Write" : openflags == FSAL_O_READ ? "Read" : "Write"); } else { LogFullDebug(COMPONENT_FSAL, "Opened state_fd %p", state_fd); *out_fd = state_fd; } return status; } /* At this point, we have a state_t we are trying to use in a wrong * mode or it isn't open at all. If it's a lock state with an associated * open state, use the fd from that (this mode is used by some FSALs * that don't want to open a separate fd for lock states). */ if (state->state_type == STATE_TYPE_LOCK) { struct state_t *openstate; struct fsal_fd *related_fd; openstate = nfs4_State_Get_Pointer( state->state_data.lock.openstate_key); if (openstate == NULL) { /* The open state was not usable so use the global fd. */ goto global; } related_fd = (struct fsal_fd *)(openstate + 1); LogFullDebug(COMPONENT_FSAL, "related_fd->openflags = %d openflags = %d", related_fd->openflags, openflags); status = wait_to_start_io(obj_hdl, related_fd, openflags, false, false); /* We either succeeded in starting I/O on the openstate's * fsal_fd, in which case the fsal_fd will NOT go away while we * are using it so we can safely drop the reference on * openstate. * * If we failed to use the openstate's fsal_fd then we don't * need the reference. */ dec_state_t_ref(openstate); if (FSAL_IS_SUCCESS(status)) { /* It was valid, return it. * Since we found a valid fd in the open state, no * need to check deny modes. */ LogFullDebug(COMPONENT_FSAL, "Use related_fd %p", related_fd); if (out_fd) { *out_fd = related_fd; /* The associated open state has an open fd, * however some FSALs can not use it and must * need to dup the fd into the lock state * instead. So to signal this to the caller * function the following flag */ if (reusing_open_state_fd != NULL) *reusing_open_state_fd = true; } return status; } } /* At this point, we have an ERR_FSAL_DELAY because an open or * delegation state was being used the wrong way. This should only * happen for READs where we always allow a READ on a write-only open * or delegation. The READ will proceed effectively as an anonymous READ * including all share reservation activity including being blocked by * deny read. * * Note that we do not allow write locks on read-only opens and read * locks on write only opens. */ global: /* No useable state_t so return the global file descriptor. */ LogFullDebug(COMPONENT_FSAL, "Use global fd openflags = %x", openflags); /* Make sure global is open as necessary otherwise return a * temporary file descriptor. Check share reservation if not * opening FSAL_O_ANY. If we were passed a state, then we won't * need to check share reservation. */ return fsal_start_global_io(out_fd, obj_hdl, obj_fd, tmp_fd, openflags, bypass, state == NULL ? share : NULL); } fsal_status_t fsal_complete_io(struct fsal_obj_handle *obj_hdl, struct fsal_fd *fsal_fd) { fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; bool got_mutex; if (fsal_fd->close_on_complete) { LogFullDebug(COMPONENT_FSAL, "closing temp fd %p", fsal_fd); /* Close the temp fd, no need to do the rest since this fsal_fd * was only being used for this operation. */ return fsal_close_fd(obj_hdl, fsal_fd); } /* Indicate I/O done, and if we were last I/O, signal fd_work_cond * condition in case any threads are waiting to do fd work. */ LogFullDebug(COMPONENT_FSAL, "%p done io_work (-1) = %" PRIi32 " fd_work = %" PRIi32, fsal_fd, atomic_fetch_int32_t(&fsal_fd->io_work) - 1, atomic_fetch_int32_t(&fsal_fd->fd_work)); got_mutex = PTHREAD_MUTEX_dec_int32_t_and_lock(&fsal_fd->io_work, &fsal_fd->work_mutex); if (got_mutex) PTHREAD_COND_signal(&fsal_fd->fd_work_cond); /* We choose to bump the fd at the completion of I/O so we don't have * to introduce new locking. */ bump_fd_lru(fsal_fd); if (got_mutex) PTHREAD_MUTEX_unlock(&fsal_fd->work_mutex); return status; } /** * @brief Manage starting fsal_fd fd work (open/close). * * NOTE: This function takes the fsal_fd work_mutex. It should be used in * conjunction with fsal_complete_fd_work. * * @param[in] fsal_fd The fd to do work on. * @param[in] is_reclaiming Indicates we are closing files to reclaim fd * */ fsal_status_t fsal_start_fd_work(struct fsal_fd *fsal_fd, bool is_reclaiming) { /* Indicate we want to do fd work */ atomic_inc_int32_t(&fsal_fd->fd_work); PTHREAD_MUTEX_lock(&fsal_fd->work_mutex); if ((atomic_fetch_int32_t(&fsal_fd->want_read) != 0 || atomic_fetch_int32_t(&fsal_fd->want_write) != 0) && is_reclaiming) { /* I/O is trying to start but needs to re-open, and we're trying * to reclaim, this isn't a good candidate to reclaim, bump it * so we can skip it. */ bump_fd_lru(fsal_fd); fsal_complete_fd_work(fsal_fd); /* Use fsalstat to avoid LogInfo... */ return fsalstat(ERR_FSAL_DELAY, EBUSY); } LogFullDebug(COMPONENT_FSAL, "%p try fd work - io_work = %" PRIi32 " fd_work = %" PRIi32, fsal_fd, atomic_fetch_int32_t(&fsal_fd->io_work), atomic_fetch_int32_t(&fsal_fd->fd_work)); /* Wait for lull in io work */ while (atomic_fetch_int32_t(&fsal_fd->io_work) != 0) { LogFullDebug(COMPONENT_FSAL, "%p wait for lull - io_work = %" PRIi32 " fd_work = %" PRIi32, fsal_fd, atomic_fetch_int32_t(&fsal_fd->io_work), atomic_fetch_int32_t(&fsal_fd->fd_work)); if (is_reclaiming) { /* We are trying to reclaim an in-use fsal_fd, bump * it so we skip it. */ bump_fd_lru(fsal_fd); fsal_complete_fd_work(fsal_fd); /* Use fsalstat to avoid LogInfo... */ return fsalstat(ERR_FSAL_DELAY, EBUSY); } /* io work is in progress or trying to start, wait for it to * complete (or not start) */ PTHREAD_COND_wait(&fsal_fd->fd_work_cond, &fsal_fd->work_mutex); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Manage completeion of fsal_fd fd work (open/close). * * NOTE: This function releases the fsal_fd work_mutex. It should be used in * conjunction with fsal_start_fd_work. * * @param[in] fsal_fd The fd to do work on. * */ void fsal_complete_fd_work(struct fsal_fd *fsal_fd) { /* Indicate we are done with fd work and signal any waiters. */ bool io_can_start = (atomic_dec_int32_t(&fsal_fd->fd_work) == 0); LogFullDebug(COMPONENT_FSAL, "%p done fd work io_work = %" PRIi32 " fd_work = %" PRIi32, fsal_fd, atomic_fetch_int32_t(&fsal_fd->io_work), atomic_fetch_int32_t(&fsal_fd->fd_work)); /* Wake up at least one thread waiting to do fd work */ PTHREAD_COND_signal(&fsal_fd->fd_work_cond); if (io_can_start) { /* Wake up all threads waiting to do io work */ PTHREAD_COND_broadcast(&fsal_fd->io_work_cond); } /* Completely done, release the mutex. */ PTHREAD_MUTEX_unlock(&fsal_fd->work_mutex); } /** * @brief Check the exclusive create verifier for a file. * * The default behavior is to check verifier against atime and mtime. * * @param[in] st POSIX attributes for the file (from stat) * @param[in] verifier Verifier to use for exclusive create * @param[in] trunc_verif Use onlu 31 bits of each half of the verifier * * @retval true if verifier matches */ bool check_verifier_stat(struct stat *st, fsal_verifier_t verifier, bool trunc_verif) { uint32_t verf_hi = 0, verf_lo = 0; memcpy(&verf_hi, verifier, sizeof(uint32_t)); memcpy(&verf_lo, verifier + sizeof(uint32_t), sizeof(uint32_t)); if (trunc_verif) { verf_hi &= INT32_MAX; verf_lo &= INT32_MAX; } LogFullDebug(COMPONENT_FSAL, "Passed verifier %" PRIx32 " %" PRIx32 " file verifier %" PRIx32 " %" PRIx32, verf_hi, verf_lo, (uint32_t)st->st_atim.tv_sec, (uint32_t)st->st_mtim.tv_sec); return st->st_atim.tv_sec == verf_hi && st->st_mtim.tv_sec == verf_lo; } /** * @brief Check the exclusive create verifier for a file. * * The default behavior is to check verifier against atime and mtime. * * @param[in] attrs Attributes for the file * @param[in] verifier Verifier to use for exclusive create * @param[in] trunc_verif Use onlu 31 bits of each half of the verifier * * @retval true if verifier matches */ bool check_verifier_attrlist(struct fsal_attrlist *attrs, fsal_verifier_t verifier, bool trunc_verif) { uint32_t verf_hi = 0, verf_lo = 0; memcpy(&verf_hi, verifier, sizeof(uint32_t)); memcpy(&verf_lo, verifier + sizeof(uint32_t), sizeof(uint32_t)); if (trunc_verif) { verf_hi &= INT32_MAX; verf_lo &= INT32_MAX; } LogFullDebug(COMPONENT_FSAL, "Passed verifier %" PRIx32 " %" PRIx32 " file verifier %" PRIx32 " %" PRIx32, verf_hi, verf_lo, (uint32_t)attrs->atime.tv_sec, (uint32_t)attrs->mtime.tv_sec); return attrs->atime.tv_sec == verf_hi && attrs->mtime.tv_sec == verf_lo; } /** * @brief Common is_referral routine for FSALs that use the special mode * * @param[in] obj_hdl Handle on which to operate * @param[in|out] attrs Attributes of the handle * @param[in] cache_attrs Cache the received attrs * * Most FSALs don't support referrals, but those that do often use a special * mode bit combination on a directory for a junction. This routine tests for * that and returns true if it is a referral. */ bool fsal_common_is_referral(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs, bool cache_attrs) { attrmask_t req_mask = ATTR_TYPE | ATTR_MODE; LogDebug(COMPONENT_FSAL, "Checking attrs for referral, handle: %p, valid_mask: %" PRIx64 ", request_mask: %" PRIx64 ", supported: %" PRIx64, obj_hdl, attrs->valid_mask, attrs->request_mask, attrs->supported); if ((attrs->valid_mask & req_mask) != req_mask) { /* Required attributes are not available, need to fetch them */ fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; attrs->request_mask |= req_mask; status = obj_hdl->obj_ops->getattrs(obj_hdl, attrs); if (FSAL_IS_ERROR(status)) { /* Drop the message level to debug if referral belongs * to deleted file to avoid flood of messages. */ if (status.major == ERR_FSAL_STALE) { LogDebug( COMPONENT_FSAL, "Failed to get attrs for referral, handle: %p (probably deleted), valid_mask: %" PRIx64 ", request_mask: %" PRIx64 ", supported: %" PRIx64 ", error: %s", obj_hdl, attrs->valid_mask, attrs->request_mask, attrs->supported, fsal_err_txt(status)); } else { LogEventLimited( COMPONENT_FSAL, "Failed to get attrs for referral, handle: %p, valid_mask: %" PRIx64 ", request_mask: %" PRIx64 ", supported: %" PRIx64 ", error: %s", obj_hdl, attrs->valid_mask, attrs->request_mask, attrs->supported, fsal_err_txt(status)); } return false; } } if (!fsal_obj_handle_is(obj_hdl, DIRECTORY)) return false; if (!is_sticky_bit_set(obj_hdl, attrs)) return false; LogDebug(COMPONENT_FSAL, "Referral found for handle: %p", obj_hdl); return true; } struct gsh_refstr *no_export; void init_ctx_refstr(void) { no_export = gsh_refstr_dup("No Export"); } void destroy_ctx_refstr(void) { gsh_refstr_put(no_export); } /** * @brief Set an export into an op_context (could be NULL). * * This is the core function that sets up an op_context. It makes no assumptions * of what is already in the op_context. It will set up a refstr for both * op_ctx->ctx_fullpath and op_ctx->ctx_pseudopath. If no export is set, or * those strings are not available from the export, the refstr will reference * the special "no_export" refstr. * * @param[in] exp The gsh_export to set, can be NULL. * @param[in] fsal_exp The fsal_export to set, can be NULL. * @param[in] pds The pnfs ds to set, can be NULL. */ static void set_op_context_export_fsal_no_release(struct gsh_export *exp, struct fsal_export *fsal_exp, struct fsal_pnfs_ds *pds) { op_ctx->ctx_export = exp; op_ctx->fsal_export = fsal_exp; op_ctx->ctx_pnfs_ds = pds; rcu_read_lock(); if (op_ctx->ctx_pnfs_ds) { LogDebug(COMPONENT_FILEHANDLE, "need an extra hold for DS"); pnfs_ds_get_ref(op_ctx->ctx_pnfs_ds); } if (op_ctx->ctx_export != NULL && op_ctx->ctx_export->fullpath) { op_ctx->ctx_fullpath = gsh_refstr_get( rcu_dereference(op_ctx->ctx_export->fullpath)); } else { /* Normally an export always has a fullpath refstr, however * we might be called from free_export_resources before the * refstr is set up. Or we may be called with no export. */ op_ctx->ctx_fullpath = gsh_refstr_get(no_export); } if (op_ctx->ctx_export != NULL && op_ctx->ctx_export->pseudopath != NULL) { op_ctx->ctx_pseudopath = gsh_refstr_get( rcu_dereference(op_ctx->ctx_export->pseudopath)); } else { /* Normally an export always has a pseudopath refstr, however * we might be called from free_export_resources before the * refstr is set up. Or we may be called with no export. */ op_ctx->ctx_pseudopath = gsh_refstr_get(no_export); } rcu_read_unlock(); if (fsal_exp) op_ctx->fsal_module = fsal_exp->fsal; else if (!op_ctx->fsal_module && op_ctx->saved_op_ctx) op_ctx->fsal_module = op_ctx->saved_op_ctx->fsal_module; } /** @brief Remove the current export from the op_context so the op_context has * no export. * * If an export is referenced by the op_context, the reference will be * dropped. * * If the op_context had a valid ctx_fullpath or ctx_pseudopath they will * be unreferenced but no new refstr will be attached. * * This function should only be called from functions that will either be * freeing the op_context, or will be overwriting the export data. * * Functions that are just removing the export data, but not freeing the * op_context or overwriting the export data should call * clear_op_context_export() instead. * * The following callers do call this function directly for the reasons * explained below. * * When called by set_op_context_export() or set_op_context_pnfs_ds(), * a new export (even if NULL) is about to be set. * * When called by release_op_context(), we are "destructing" the op_context. * * When called by restore_op_context_export(), we are replacing with the saved * op_context so all the references that had been saved will be restored. * * No parameters, operates on the op_context directly. * */ static inline void clear_op_context_export_impl(void) { if (op_ctx->ctx_export != NULL) put_gsh_export(op_ctx->ctx_export); if (op_ctx->ctx_pnfs_ds != NULL) pnfs_ds_put(op_ctx->ctx_pnfs_ds); /* Note these are NEVER NULL in an active op_context. When an op_context * is initialized, if there is no export, these strings will be set to * reference the special "no_export" string. These refstr will ONLY be * set to NULL when release_op_context is called to "destroy" the * op_context. */ gsh_refstr_put(op_ctx->ctx_fullpath); gsh_refstr_put(op_ctx->ctx_pseudopath); } /** * @brief Remove the current export from the op_context so the op_context * has no export. * * If an export is referenced by the op_context, the reference will be * dropped. * * If the op_context had a valid ctx_fullpath or ctx_pseudopath they will * be unreferenced and references to the "no_export" string will be attached. * * No parameters, operates on the op_context directly. * */ void clear_op_context_export(void) { clear_op_context_export_impl(); /* Clear the ctx_export and fsal_export */ op_ctx->ctx_export = NULL; op_ctx->fsal_export = NULL; /* An active op context will always have refstr */ op_ctx->ctx_fullpath = gsh_refstr_get(no_export); op_ctx->ctx_pseudopath = gsh_refstr_get(no_export); } /** * @brief Set an export into the op_context. * * @param[in] exp The gsh_export to set, can be NULL. * * Will set a NULL pnfs ds and sets fsal_exp from exp. * */ void set_op_context_export(struct gsh_export *exp) { struct fsal_export *fsal_exp = exp ? exp->fsal_export : NULL; clear_op_context_export_impl(); set_op_context_export_fsal_no_release(exp, fsal_exp, NULL); } /** * @brief Set a pnfs ds into the op_context. * * @param[in] pds The pnfs ds to set, can be NULL. * * Will set export and fsal_export from pnfs ds. * */ void set_op_context_pnfs_ds(struct fsal_pnfs_ds *pds) { clear_op_context_export_impl(); set_op_context_export_fsal_no_release(pds->mds_export, pds->mds_fsal_export, pds); } /** * @brief Save the op_context export and all its references. * * This is used when the caller will re-use the op_context to perform operations * on other exports, and will then want to restore the original export. * * @param[in] saved Pointer to a saved_export_context to save the data into. * */ static void save_op_context_export(struct saved_export_context *saved) { saved->saved_export = op_ctx->ctx_export; saved->saved_fullpath = op_ctx->ctx_fullpath; saved->saved_pseudopath = op_ctx->ctx_pseudopath; saved->saved_fsal_export = op_ctx->fsal_export; saved->saved_fsal_module = op_ctx->fsal_module; saved->saved_pnfs_ds = op_ctx->ctx_pnfs_ds; saved->saved_export_perms = op_ctx->export_perms; } /** * @brief Save the op_context export and all its references and then set a new * export. * * @param[in] saved Pointer to a saved_export_context to save the data into. * @param[in] exp The new export to set. */ void save_op_context_export_and_set_export(struct saved_export_context *saved, struct gsh_export *exp) { save_op_context_export(saved); /* Don't release op_ctx->ctx_export or the refstr since it's saved */ set_op_context_export_fsal_no_release(exp, exp->fsal_export, NULL); } /** * @brief Save the op_context export and all its references and then clear the * export from the op_context. * * This is used when the caller will re-use the op_context to perform operations * on other exports, and will then want to restore the original export. * * @param[in] saved Pointer to a saved_export_context to save the data into. * */ void save_op_context_export_and_clear(struct saved_export_context *saved) { save_op_context_export(saved); op_ctx->ctx_export = NULL; op_ctx->fsal_export = NULL; op_ctx->ctx_pnfs_ds = NULL; /* An active op context will always have refstr but the original * refstr are saved off, so replace with new refs to no_export. */ op_ctx->ctx_fullpath = gsh_refstr_get(no_export); op_ctx->ctx_pseudopath = gsh_refstr_get(no_export); } /** * @brief Restore a saved op_context export. * * @param[in] saved Pointer to a saved_export_context to restore the data from. * */ void restore_op_context_export(struct saved_export_context *saved) { /* Since we are about to restore op_ctx->ctx_fullpath and * op_ctx->ctx_pseudopath, we don't want to fill them in with * references to the no_export gsh_refstr. */ clear_op_context_export_impl(); op_ctx->ctx_export = saved->saved_export; op_ctx->ctx_fullpath = saved->saved_fullpath; op_ctx->ctx_pseudopath = saved->saved_pseudopath; op_ctx->fsal_export = saved->saved_fsal_export; op_ctx->fsal_module = saved->saved_fsal_module; op_ctx->ctx_pnfs_ds = saved->saved_pnfs_ds; op_ctx->export_perms = saved->saved_export_perms; } /** * @brief Discard the saved export data from an op_context. * * This is used when the caller determines it does not need to restore the * saved op_context export data. * * @param[in] saved Pointer to a saved_export_context to discard the data from. * */ void discard_op_context_export(struct saved_export_context *saved) { if (saved->saved_export) put_gsh_export(saved->saved_export); if (saved->saved_pnfs_ds != NULL) pnfs_ds_put(saved->saved_pnfs_ds); if (saved->saved_fullpath != NULL) gsh_refstr_put(saved->saved_fullpath); if (saved->saved_pseudopath != NULL) gsh_refstr_put(saved->saved_pseudopath); } /** * @brief Initialize an op_context. * * This initializes all the fields in an op_context including setting up all the * references if an export is provided. After this call, the op_context is * totally valid even if NULL was passed for the export. * * If the thread's op_ctx is non-NULL, that will be saved in ctx->saved_op_ctx. * * The thread's op_ctx will be set to ctx. * * The export permissions and options will be set to root defaults. * * This is basically the "constructor" for an op_context. * * @param[in] ctx The op_context to initialoze * @param[in] exp The gsh_export if any to reference * @param[in] fsal_exp The fsal_export if any to reference * @param[in] caller_data The caller address data, can be NULL * @param[in] nfs_vers The NFS version * @param[in] nfs_minorvers The minor version for 4.x * @param[in] req_type UNKNOWN_REQUEST, NFS_REQUEST, _9P_REQUEST, etc. * */ static uint32_t op_id; void init_op_context(struct req_op_context *ctx, struct gsh_export *exp, struct fsal_export *fsal_exp, nfs_request_t *nfs_reqdata, sockaddr_t *caller_data, uint32_t nfs_vers, uint32_t nfs_minorvers, enum request_type req_type) { /* Initialize ctx. * Note that a zeroed creds works just fine as root creds. */ memset(ctx, 0, sizeof(*ctx)); ctx->nfs_reqdata = nfs_reqdata; ctx->saved_op_ctx = op_ctx; op_ctx = ctx; ctx->nfs_vers = nfs_vers; ctx->nfs_minorvers = nfs_minorvers; ctx->req_type = req_type; ctx->caller_addr = caller_data; /* Since this is a brand new op context, no need to release anything. */ set_op_context_export_fsal_no_release(exp, fsal_exp, NULL); ctx->export_perms.set = root_op_export_set; ctx->export_perms.options = root_op_export_options; ctx->op_id = atomic_postadd_uint32_t(&op_id, 1); ctx->flags.pseudo_fsal_internal_lookup = false; } /** * @brief Release an op_context and its references. * * This will release the thread's op_ctx and restore any previous one. * * The op_context will be set such that all the export bits are NULL and all * references released. * * This is basically the "destructor" for an op_context. * */ void release_op_context(void) { struct req_op_context *cur_ctx = op_ctx; clear_op_context_export_impl(); /* Clear the ctx_export and fsal_export */ op_ctx->ctx_export = NULL; op_ctx->fsal_export = NULL; /* And now we're done with the gsh_refstr (the refs just got released so * we really can NULL them out. This op_context is now done for. * This is analogous to a destructor. */ op_ctx->ctx_fullpath = NULL; op_ctx->ctx_pseudopath = NULL; /* And restore the saved op_ctx */ op_ctx = op_ctx->saved_op_ctx; cur_ctx->saved_op_ctx = NULL; } /** * @brief Suspend an op_context so the thread may suspend and the operation be * resumed later. * * When a thread is processing an async request, the op_context must be * preserved to resume that request. The request actually contains the * op_context, so all this does is set the thread's op_ctx to NULL. * * NOTE: A suspended op_context better be a top level one, i.e. saved_op_ctx is * NULL. That will be asserted by resume_op_context(). */ void suspend_op_context(void) { /* We cannot touch the contents of op_ctx, because it may be already * freed by the async callback. Just NULL out op_ctx here. */ op_ctx = NULL; } /** * @brief Resume an op_context that had been suspended. * * When the request resumes, the op_context will be restored. The current * op_ctx will be saved. * * The resume will also reset the client IP address string. * * NOTE: The caller must not have an op_ctx in use. Any op_context that may be * suspended and resumed MUST be a top level op_context with a NULL * saved_op_ctx. This is because a resumed op_context may be suspended * again, and suspend has no way to revert to a saved_op_ctx. * */ void resume_op_context(struct req_op_context *ctx) { assert(ctx->saved_op_ctx == NULL); assert(op_ctx == NULL); op_ctx = ctx; if (op_ctx->client != NULL) { /* Set the Client IP for this thread */ SetClientIP(op_ctx->client->hostaddr_str); } } #ifdef USE_DBUS void fd_usage_summarize_dbus(DBusMessageIter *iter) { DBusMessageIter struct_iter; char *type; uint32_t global_fds, state_fds; uint32_t fd_limit, fd_state; uint32_t fds_lowat, fds_hiwat, fds_hard_limit; uint64_t v4_open_states_count; dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); /* Gather the data */ fd_limit = atomic_fetch_uint32_t(&fd_lru_state.fds_system_imposed); fds_lowat = atomic_fetch_uint32_t(&fd_lru_state.fds_lowat); fds_hiwat = atomic_fetch_uint32_t(&fd_lru_state.fds_hiwat); fds_hard_limit = atomic_fetch_uint32_t(&fd_lru_state.fds_hard_limit); fd_state = atomic_fetch_uint32_t(&fd_lru_state.fd_state); global_fds = atomic_fetch_int32_t(&fsal_fd_global_counter); state_fds = atomic_fetch_int32_t(&fsal_fd_state_counter); /* Gets open v4 FD counts by traversing all the HT * to get all clientIDs and count their open states */ v4_open_states_count = get_total_count_of_open_states(); type = "System limit on FDs"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &fd_limit); type = "FD Low WaterMark"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &fds_lowat); type = "FD High WaterMark"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &fds_hiwat); type = "FD Hard Limt"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &fds_hard_limit); type = "FD usage"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); switch (fd_state) { case FD_LOW: type = " Below Low Water Mark "; break; case FD_MIDDLE: type = " Below High Water Mark "; break; case FD_HIGH: type = " Above High Water Mark "; break; case FD_LIMIT: type = " Hard Limit reached "; break; } dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); type = "FSAL opened Global FD count"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &global_fds); type = "FSAL opened State FD count"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &state_fds); type = "NFSv4 open state count"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &type); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &v4_open_states_count); dbus_message_iter_close_container(iter, &struct_iter); } #endif /* USE_DBUS */ /** @} */ nfs-ganesha-6.5/src/FSAL/default_methods.c000066400000000000000000001237731473756622300204410ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @addtogroup FSAL * @{ */ /** * @file default_methods.c * @author Jim Lieb * @brief System wide default FSAL methods */ #include "config.h" #include #include #include #include #include #include #include #include "log.h" #include "fsal.h" #include "FSAL/fsal_localfs.h" #include "nfs_core.h" #include "config_parsing.h" #include "gsh_types.h" #include "FSAL/fsal_commonlib.h" #include "fsal_private.h" #include "pnfs_utils.h" #include "nfs_creds.h" #include "sal_data.h" #include "FSAL/fsal_config.h" /** fsal module method defaults and common methods */ /** NOTE * These are the common and default methods. One important requirement * is that older fsals must safely run with newer ganesha core. * This is observed by the following rules: * * 1. New methods are *always* appended to the m_ops vector in fsal_api.h * * 2. This file is updated to add the default method. * * 3. The version numbers are bumped in fsal_api.h appropriately so * version detection is correct. */ /* unload fsal * called while holding the last remaining reference * remove from list and dlclose the module * if references are held, return EBUSY * if it is a static, return EACCES */ static int unload_fsal(struct fsal_module *fsal_hdl) { int retval = EBUSY; /* someone still has a reference */ int32_t refcount = atomic_fetch_int32_t(&fsal_hdl->refcount); LogDebug(COMPONENT_FSAL, "fsal_refcount = %" PRIi32, refcount); PTHREAD_MUTEX_lock(&fsal_lock); if (refcount != 0 || !glist_empty(&fsal_hdl->exports)) { LogCrit(COMPONENT_FSAL, "Can not unload FSAL %s fsal_refcount=%" PRIi32, fsal_hdl->name, refcount); goto err; } if (fsal_hdl->dl_handle == NULL) { LogCrit(COMPONENT_FSAL, "Can not unload static linked FSAL %s", fsal_hdl->name); retval = EACCES; goto err; } glist_del(&fsal_hdl->fsals); PTHREAD_RWLOCK_destroy(&fsal_hdl->fsm_lock); retval = dlclose(fsal_hdl->dl_handle); PTHREAD_MUTEX_unlock(&fsal_lock); return retval; err: PTHREAD_RWLOCK_unlock(&fsal_hdl->fsm_lock); PTHREAD_MUTEX_unlock(&fsal_lock); return retval; } /* init_config * default case is we have no config so return happy */ static fsal_status_t init_config(struct fsal_module *fsal_hdl, config_file_t config_struct, struct config_error_type *err_type) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* update_config * default case is we are not need update config so return happy */ static fsal_status_t update_config(struct fsal_module *fsal_hdl, config_file_t config_struct, struct config_error_type *err_type) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* dump_config * default is to do nothing */ static void dump_config(struct fsal_module *fsal_hdl, int log_fd) { /* return */ } /* create_export * default is we cannot create an export */ static fsal_status_t create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* update_export * default is do nothing other than check stacking. This works for a * bottom end FSAL that has no updatable parameters (and doesn't want to * bother checking that none of its parameters actually changed). */ fsal_status_t update_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, struct fsal_export *original, struct fsal_module *updated_super) { if (original->super_export->fsal != updated_super || original->fsal != fsal_hdl) { /* We have a stacking error. */ LogCrit(COMPONENT_FSAL, "Export stacking has changed for export %d FSAL %s from super was %s to %s", original->export_id, fsal_hdl->name, original->super_export->fsal->name, updated_super->name); return fsalstat(ERR_FSAL_INVAL, EINVAL); } LogFullDebugAlt(COMPONENT_FSAL, COMPONENT_EXPORT, "Updating export %p", op_ctx->fsal_export); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Default emergency cleanup method * * Do nothing. */ static void emergency_cleanup(void) { /* return */ } /** * @brief Be uninformative about a device */ static nfsstat4 getdeviceinfo(struct fsal_module *fsal_hdl, XDR *da_addr_body, const layouttype4 type, const struct pnfs_deviceid *deviceid) { return NFS4ERR_NOTSUPP; } /** * No da_addr. */ static size_t fs_da_addr_size(struct fsal_module *fsal_hdl) { return 0; } /** * @brief Try to create a FSAL pNFS data server * * @param[in] fsal_hdl FSAL module * @param[in] parse_node opaque pointer to parse tree node for * export options to be passed to * load_config_from_node * @param[out] handle FSAL pNFS DS * * @return FSAL status. */ static fsal_status_t create_fsal_pnfs_ds(struct fsal_module *const fsal_hdl, void *parse_node, struct fsal_pnfs_ds **const handle) { LogDebug(COMPONENT_PNFS, "Default pNFS DS creation!"); if (*handle == NULL) *handle = pnfs_ds_alloc(); fsal_pnfs_ds_init(*handle, fsal_hdl); return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Initialize FSAL specific values for pNFS data server * * @param[in] ops FSAL pNFS Data Server operations vector */ static void fsal_pnfs_ds_ops(struct fsal_pnfs_ds_ops *ops) { memcpy(ops, &def_pnfs_ds_ops, sizeof(struct fsal_pnfs_ds_ops)); } /** * @brief Provides function to extract FSAL stats * * @param[in] fsal_hdl FSAL module * @param[in] iter opaque pointer to DBusMessageIter */ static void fsal_extract_stats(struct fsal_module *const fsal_hdl, void *iter) { LogDebug(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); } /** * @brief FSAL function to reset FSAL stats * * @param[in] fsal_hdl FSAL module */ static void fsal_reset_stats(struct fsal_module *const fsal_hdl) { LogDebug(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); } /* Default fsal module method vector. * copied to allocated vector at register time */ struct fsal_ops def_fsal_ops = { .unload = unload_fsal, .init_config = init_config, .update_config = update_config, .dump_config = dump_config, .create_export = create_export, .update_export = update_export, .emergency_cleanup = emergency_cleanup, .getdeviceinfo = getdeviceinfo, .fs_da_addr_size = fs_da_addr_size, .create_fsal_pnfs_ds = create_fsal_pnfs_ds, .fsal_pnfs_ds_ops = fsal_pnfs_ds_ops, .fsal_extract_stats = fsal_extract_stats, .fsal_reset_stats = fsal_reset_stats, }; /* get_name * default case is to return the name of the FSAL */ static const char *get_name(struct fsal_export *exp_hdl) { return exp_hdl->fsal->name; } /* export_prepare_unexport * Nothing to do in the default case */ static void export_prepare_unexport(struct fsal_export *exp_hdl) { /* return */ } /* export_unexport * Nothing to do in the default case */ static void export_unexport(struct fsal_export *exp_hdl, struct fsal_obj_handle *root_obj) { /* return */ } /* export_unmount * Nothing to do in the default case */ static void export_unmount(struct fsal_export *parent_exp_hdl, struct fsal_obj_handle *junction_obj) { /* return */ } /* export_release * Nothing to do in the default case */ static void export_release(struct fsal_export *exp_hdl) { /* return */ } /* lookup_path * default case is not supported, note that with lookup_path ONLY being * used to instantiate the export's root obj_handle, there can not be any * useful default method. */ fsal_status_t lookup_path(struct fsal_export *exp_hdl, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } static fsal_status_t lookup_junction(struct fsal_export *exp_hdl, struct fsal_obj_handle *junction, struct fsal_obj_handle **handle) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* wire_to_host * default case is not supported */ static fsal_status_t wire_to_host(struct fsal_export *exp_hdl, fsal_digesttype_t in_type, struct gsh_buffdesc *fh_desc, int flags) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* exp_host_to_key Produce handle-key from host-handle * * default case is that "handle-key" is same as host-handle! */ static fsal_status_t exp_host_to_key(struct fsal_export *exp_hdl, struct gsh_buffdesc *fh_desc) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* create_handle * default case is not supported */ static fsal_status_t create_handle(struct fsal_export *exp_hdl, struct gsh_buffdesc *hdl_desc, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* get_dynamic_info * default case is not supported */ static fsal_status_t get_dynamic_info(struct fsal_export *exp_hdl, struct fsal_obj_handle *obj_hdl, fsal_dynamicfsinfo_t *infop) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /** * @brief Query the FSAL's capabilities * * This function queries the capabilities of an FSAL export. * * @param[in] exp_hdl The public export handle * @param[in] option The option to check * * @retval true if the option is supported. * @retval false if the option is unsupported (or unknown). */ static bool fs_supports(struct fsal_export *exp_hdl, fsal_fsinfo_options_t option) { return fsal_supports(&exp_hdl->fsal->fs_info, option); } /** * @brief Return the longest file supported * * This function returns the length of the longest file supported. * * @param[in] exp_hdl The public export * */ static uint64_t fs_maxfilesize(struct fsal_export *exp_hdl) { return fsal_maxfilesize(&exp_hdl->fsal->fs_info); } /** * @brief Return the longest read supported * * This function returns the length of the longest read supported. * * @param[in] exp_hdl The public export * */ static uint32_t fs_maxread(struct fsal_export *exp_hdl) { return fsal_maxread(&exp_hdl->fsal->fs_info); } /** * @brief Return the longest write supported * * This function returns the length of the longest write supported. * * @param[in] exp_hdl The public export * */ static uint32_t fs_maxwrite(struct fsal_export *exp_hdl) { return fsal_maxwrite(&exp_hdl->fsal->fs_info); } /** * @brief Return the maximum number of hard links to a file * * This function returns the maximum number of hard links supported to * any file. * * @param[in] exp_hdl The public export * */ static uint32_t fs_maxlink(struct fsal_export *exp_hdl) { return fsal_maxlink(&exp_hdl->fsal->fs_info); } /** * @brief Return the maximum size of a Ceph filename * * This function returns the maximum filename length. * * @param[in] exp_hdl The public export * */ static uint32_t fs_maxnamelen(struct fsal_export *exp_hdl) { return fsal_maxnamelen(&exp_hdl->fsal->fs_info); } /** * @brief Return the maximum length of a Ceph path * * This function returns the maximum path length. * * @param[in] exp_hdl The public export * */ static uint32_t fs_maxpathlen(struct fsal_export *exp_hdl) { return fsal_maxpathlen(&exp_hdl->fsal->fs_info); } /** * @brief Return ACL support * * This function returns the export's ACL support. * * @param[in] export_pub The public export * */ static fsal_aclsupp_t fs_acl_support(struct fsal_export *exp_hdl) { return fsal_acl_support(&exp_hdl->fsal->fs_info); } /** * @brief Return the attributes supported by this FSAL * * This function returns the mask of attributes this FSAL can support. * * @param[in] exp_hdl The public export * */ static attrmask_t fs_supported_attrs(struct fsal_export *exp_hdl) { return fsal_supported_attrs(&exp_hdl->fsal->fs_info); } /** * @brief Return the mode under which the FSAL will create files * * This function modifies the default mode on any new file created. * * @param[in] export_pub The public export * * @return 0 (usually). Bits set here turn off bits in created files. */ static uint32_t fs_umask(struct fsal_export *exp_hdl) { return fsal_umask(&exp_hdl->fsal->fs_info); } /** * @brief Get the expiration time for parent handle. * * This function gets the expiration time for parent handle. * * @param[in] exp_hdl The public export * * @return Expiration time for parent handle */ static int32_t fs_expiretimeparent(struct fsal_export *exp_hdl) { return fsal_expiretimeparent(&exp_hdl->fsal->fs_info); } /* check_quota * return happiness for now. */ static fsal_status_t check_quota(struct fsal_export *exp_hdl, const char *filepath, int quota_type) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* get_quota * default case not supported */ static fsal_status_t get_quota(struct fsal_export *exp_hdl, const char *filepath, int quota_type, int quota_id, fsal_quota_t *pquota) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* set_quota * default case not supported */ static fsal_status_t set_quota(struct fsal_export *exp_hdl, const char *filepath, int quota_type, int quota_id, fsal_quota_t *pquota, fsal_quota_t *presquota) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /** * @brief Be uninformative about all devices */ static nfsstat4 getdevicelist(struct fsal_export *exp_hdl, layouttype4 type, void *opaque, bool (*cb)(void *opaque, const uint64_t id), struct fsal_getdevicelist_res *res) { return NFS4ERR_NOTSUPP; } /** * @brief Support no layout types */ static void fs_layouttypes(struct fsal_export *exp_hdl, int32_t *count, const layouttype4 **types) { *count = 0; *types = NULL; } /** * @brief Read no bytes through layouts */ static uint32_t fs_layout_blocksize(struct fsal_export *exp_hdl) { return 0; } /** * @brief No segments */ static uint32_t fs_maximum_segments(struct fsal_export *exp_hdl) { return 0; } /** * @brief No loc_body */ static size_t fs_loc_body_size(struct fsal_export *exp_hdl) { return 0; } /** * @brief Get write verifier * * This function is called by write and commit to match the commit verifier * with the one returned on write. * * @param[in,out] verf_desc Address and length of verifier * * @return No errors */ static void global_verifier(struct fsal_export *exp_hdl, struct gsh_buffdesc *verf_desc) { memcpy(verf_desc->addr, &NFS4_write_verifier, verf_desc->len); } /** * @brief Allocate a state_t structure * * Note that this is not expected to fail since memory allocation is * expected to abort on failure. * * @param[in] exp_hdl Export state_t will be associated with * @param[in] state_type Type of state to allocate * @param[in] related_state Related state if appropriate * * @returns a state structure. */ static struct state_t *alloc_state(struct fsal_export *exp_hdl, enum state_type state_type, struct state_t *related_state) { /* MUST be overridden. */ return NULL; } /** * @brief Check to see if a user is superuser * * @param[in] exp_hdl Export state_t is associated with * @param[in] creds Credentials to check for superuser * * @returns NULL on failure otherwise a state structure. */ bool is_superuser(struct fsal_export *exp_hdl, const struct user_cred *creds) { return (creds->caller_uid == 0); } /* Default fsal export method vector. * copied to allocated vector at register time */ struct export_ops def_export_ops = { .get_name = get_name, .prepare_unexport = export_prepare_unexport, .unexport = export_unexport, .unmount = export_unmount, .release = export_release, .lookup_path = lookup_path, .lookup_junction = lookup_junction, .wire_to_host = wire_to_host, .host_to_key = exp_host_to_key, .create_handle = create_handle, .get_fs_dynamic_info = get_dynamic_info, .fs_supports = fs_supports, .fs_maxfilesize = fs_maxfilesize, .fs_maxread = fs_maxread, .fs_maxwrite = fs_maxwrite, .fs_maxlink = fs_maxlink, .fs_maxnamelen = fs_maxnamelen, .fs_maxpathlen = fs_maxpathlen, .fs_acl_support = fs_acl_support, .fs_supported_attrs = fs_supported_attrs, .fs_umask = fs_umask, .check_quota = check_quota, .get_quota = get_quota, .set_quota = set_quota, .getdevicelist = getdevicelist, .fs_layouttypes = fs_layouttypes, .fs_layout_blocksize = fs_layout_blocksize, .fs_maximum_segments = fs_maximum_segments, .fs_loc_body_size = fs_loc_body_size, .get_write_verifier = global_verifier, .alloc_state = alloc_state, .is_superuser = is_superuser, .fs_expiretimeparent = fs_expiretimeparent, }; /* fsal_obj_handle common methods */ /* get_ref * This MUST be handled by someone. For many FSALs, it is handled by * FSAL_MDCACHE. */ static void handle_get_ref(struct fsal_obj_handle *obj_hdl) { /* return */ } /* put_ref * This MUST be handled by someone. For many FSALs, it is handled by * FSAL_MDCACHE. */ static void handle_put_ref(struct fsal_obj_handle *obj_hdl) { /* return */ } /* handle_release * default case is to throw a fault error. * creating an handle is not supported so getting here is bad */ static void handle_release(struct fsal_obj_handle *obj_hdl) { /* return */ } /** * @brief Merge a duplicate handle with an original handle * * This function is used if an upper layer detects that a duplicate * object handle has been created. It allows the FSAL to merge anything * from the duplicate back into the original. * * The caller must release the object (the caller may have to close * files if the merge is unsuccessful). * * @param[in] orig_hdl Original handle * @param[in] dupe_hdl Handle to merge into original * * @return FSAL status. * */ static fsal_status_t handle_merge(struct fsal_obj_handle *orig_hdl, struct fsal_obj_handle *dupe_hdl) { /* Default is to do nothing. */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* lookup * default case not supported */ static fsal_status_t lookup(struct fsal_obj_handle *parent, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* read_dirents * default case not supported */ static fsal_status_t read_dirents(struct fsal_obj_handle *dir_hdl, fsal_cookie_t *whence, void *dir_state, fsal_readdir_cb cb, attrmask_t attrmask, bool *eof) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* compute_readdir_cookie * default is to return 0 which indicates not supported */ fsal_cookie_t compute_readdir_cookie(struct fsal_obj_handle *parent, const char *name) { return 0; } /* dirent_cmp * Sort dirents by name. */ int dirent_cmp(struct fsal_obj_handle *parent, const char *name1, fsal_cookie_t cookie1, const char *name2, fsal_cookie_t cookie2) { /* Not supported by default. */ assert(false); return 0; } /* makedir * default case not supported */ static fsal_status_t makedir(struct fsal_obj_handle *dir_hdl, const char *name, struct fsal_attrlist *attrib, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* makenode * default case not supported */ static fsal_status_t makenode(struct fsal_obj_handle *dir_hdl, const char *name, object_file_type_t nodetype, struct fsal_attrlist *attrib, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* makesymlink * default case not supported */ static fsal_status_t makesymlink(struct fsal_obj_handle *dir_hdl, const char *name, const char *link_path, struct fsal_attrlist *attrib, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* readsymlink * default case not supported */ static fsal_status_t readsymlink(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *link_content, bool refresh) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* getxattrs * default case not supported */ static fsal_status_t getxattrs(struct fsal_obj_handle *obj_hdl, xattrkey4 *xa_name, xattrvalue4 *xa_value) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* setxattrs * default case not supported */ static fsal_status_t setxattrs(struct fsal_obj_handle *obj_hdl, setxattr_option4 option, xattrkey4 *xa_name, xattrvalue4 *xa_value) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* removexattrs * default case not supported */ static fsal_status_t removexattrs(struct fsal_obj_handle *obj_hdl, xattrkey4 *xa_name) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* listxattrs * default case not supported */ static fsal_status_t listxattrs(struct fsal_obj_handle *obj_hdl, count4 la_maxcount, nfs_cookie4 *la_cookie, bool_t *lr_eof, xattrlist4 *lr_names) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* getattrs * default case not supported */ static fsal_status_t getattrs(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* linkfile * default case not supported */ static fsal_status_t linkfile(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *destdir_hdl, const char *name, struct fsal_attrlist *destdir_pre_attrs_out, struct fsal_attrlist *destdir_post_attrs_out) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* renamefile * default case not supported */ static fsal_status_t renamefile(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *olddir_hdl, const char *old_name, struct fsal_obj_handle *newdir_hdl, const char *new_name, struct fsal_attrlist *olddir_pre_attrs_out, struct fsal_attrlist *olddir_post_attrs_out, struct fsal_attrlist *newdir_pre_attrs_out, struct fsal_attrlist *newdir_post_attrs_out) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* file_unlink * default case not supported */ static fsal_status_t file_unlink(struct fsal_obj_handle *dir_hdl, struct fsal_obj_handle *obj_hdl, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* io advise * default case not supported */ static fsal_status_t file_io_advise(struct fsal_obj_handle *obj_hdl, struct io_hints *hints) { hints->hints = 0; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* file_close * default case not supported */ static fsal_status_t file_close(struct fsal_obj_handle *obj_hdl) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* fallocate * default case not supported */ static fsal_status_t file_fallocate(struct fsal_obj_handle *obj_hdl, struct state_t *state, uint64_t offset, uint64_t length, bool allocate) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* list_ext_attrs * default case not supported */ static fsal_status_t list_ext_attrs(struct fsal_obj_handle *obj_hdl, unsigned int cookie, fsal_xattrent_t *xattrs_tab, unsigned int xattrs_tabsize, unsigned int *p_nb_returned, int *end_of_list) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* getextattr_id_by_name * default case not supported */ static fsal_status_t getextattr_id_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, unsigned int *pxattr_id) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* getextattr_value_by_name * default case not supported */ static fsal_status_t getextattr_value_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, size_t *p_output_size) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* getextattr_value_by_id * default case not supported */ static fsal_status_t getextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buffer_addr, size_t buffer_size, size_t *p_output_size) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* setextattr_value * default case not supported */ static fsal_status_t setextattr_value(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, int create) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* setextattr_value_by_id * default case not supported */ static fsal_status_t setextattr_value_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buffer_addr, size_t buffer_size) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* remove_extattr_by_id * default case not supported */ static fsal_status_t remove_extattr_by_id(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* remove_extattr_by_name * default case not supported */ static fsal_status_t remove_extattr_by_name(struct fsal_obj_handle *obj_hdl, const char *xattr_name) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* handle_to_wire * default case server fault */ static fsal_status_t handle_to_wire(const struct fsal_obj_handle *obj_hdl, fsal_digesttype_t output_type, struct gsh_buffdesc *fh_desc) { return fsalstat(ERR_FSAL_SERVERFAULT, 0); } /** * handle_cmp * default case compare with handle_to_key */ static bool handle_cmp(struct fsal_obj_handle *obj_hdl1, struct fsal_obj_handle *obj_hdl2) { struct gsh_buffdesc key1, key2; if (obj_hdl1 == NULL || obj_hdl2 == NULL) return false; if (obj_hdl1 == obj_hdl2) return true; obj_hdl1->obj_ops->handle_to_key(obj_hdl1, &key1); obj_hdl2->obj_ops->handle_to_key(obj_hdl2, &key2); if (key1.len != key2.len) return false; return !memcmp(key1.addr, key2.addr, key1.len); } /** * handle_to_key * default case return a safe empty key */ static void handle_to_key(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *fh_desc) { fh_desc->addr = obj_hdl; fh_desc->len = 0; } /** * @brief Fail to grant a layout segment. * * @param[in] obj_hdl The handle of the file on which the layout is * requested. * @param[out] loc_body An XDR stream to which the FSAL must encode * the layout specific portion of the granted * layout segment. * @param[in] arg Input arguments of the function * @param[in,out] res In/out and output arguments of the function * * @return NFS4ERR_LAYOUTUNAVAILABLE */ static nfsstat4 layoutget(struct fsal_obj_handle *obj_hdl, XDR *loc_body, const struct fsal_layoutget_arg *arg, struct fsal_layoutget_res *res) { return NFS4ERR_LAYOUTUNAVAILABLE; } /** * @brief Don't return a layout segment * * @param[in] obj_hdl The object on which a segment is to be returned * @param[in] lrf_body In the case of a non-synthetic return, this is * an XDR stream corresponding to the layout * type-specific argument to LAYOUTRETURN. In * the case of a synthetic or bulk return, * this is a NULL pointer. * @param[in] arg Input arguments of the function * * @return NFS4ERR_NOTSUPP */ static nfsstat4 layoutreturn(struct fsal_obj_handle *obj_hdl, XDR *lrf_body, const struct fsal_layoutreturn_arg *arg) { return NFS4ERR_NOTSUPP; } /** * @brief Fail to commit a segment of a layout * * @param[in] obj_hdl The object on which to commit * @param[in] lou_body An XDR stream containing the layout * type-specific portion of the LAYOUTCOMMIT * arguments. * @param[in] arg Input arguments of the function * @param[in,out] res In/out and output arguments of the function * * @return Valid error codes in RFC 5661, p. 366. */ static nfsstat4 layoutcommit(struct fsal_obj_handle *obj_hdl, XDR *lou_body, const struct fsal_layoutcommit_arg *arg, struct fsal_layoutcommit_res *res) { return NFS4ERR_NOTSUPP; } /* open2 * default case not supported */ static fsal_status_t open2(struct fsal_obj_handle *obj_hdl, struct state_t *fd, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attrib_set, fsal_verifier_t verifier, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /** * @brief Check the exclusive create verifier for a file. * * The default behavior is to check verifier against atime and mtime. * * @param[in] obj_hdl File to check verifier * @param[in] verifier Verifier to use for exclusive create * * @retval true if verifier matches */ static bool check_verifier(struct fsal_obj_handle *obj_hdl, fsal_verifier_t verifier) { struct fsal_attrlist attrs; bool result; bool trunc_verif = false; #if GSH_CAN_HOST_LOCAL_FS if (obj_hdl->fs != NULL) trunc_verif = obj_hdl->fs->trunc_verif; #endif fsal_prepare_attrs(&attrs, ATTR_ATIME | ATTR_MTIME); if (FSAL_IS_ERROR(obj_hdl->obj_ops->getattrs(obj_hdl, &attrs))) return false; result = check_verifier_attrlist(&attrs, verifier, trunc_verif); /* Done with the attrs */ fsal_release_attrs(&attrs); return result; } /* status2 * default case return openflags */ static fsal_openflags_t status2(struct fsal_obj_handle *obj_hdl, struct state_t *state) { /* default does nothing, MUST be overridden */ return 0; } /* reopen2 * default case not supported */ static fsal_status_t reopen2(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* read2 * default case not supported */ static void read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); done_cb(obj_hdl, fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP), read_arg, caller_arg); } /* write2 * default case not supported */ static void write2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *caller_arg) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); done_cb(obj_hdl, fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP), write_arg, caller_arg); } /* seek2 * default case not supported */ static fsal_status_t seek2(struct fsal_obj_handle *obj_hdl, struct state_t *fd, struct io_info *info) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* io io_advise2 * default case not supported */ static fsal_status_t io_advise2(struct fsal_obj_handle *obj_hdl, struct state_t *fd, struct io_hints *hints) { hints->hints = 0; return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* commit2 * default case not supported */ static fsal_status_t commit2(struct fsal_obj_handle *obj_hdl, off_t offset, size_t len) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* lock_op2 * default case not supported */ static fsal_status_t lock_op2(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *p_owner, fsal_lock_op_t lock_op, fsal_lock_param_t *request_lock, fsal_lock_param_t *conflicting_lock) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* lease_op2 * default case not supported */ static fsal_status_t lease_op2(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *owner, fsal_deleg_t deleg) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* setattr2 * default case not supported */ static fsal_status_t setattr2(struct fsal_obj_handle *obj_hdl, bool bypass, struct state_t *state, struct fsal_attrlist *attrs) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* close2 * default case not supported */ static fsal_status_t close2(struct fsal_obj_handle *obj_hdl, struct state_t *fd) { LogCrit(COMPONENT_FSAL, "Invoking unsupported FSAL operation"); return fsalstat(ERR_FSAL_NOTSUPP, ENOTSUP); } /* is_referral * default case not a referral */ static bool is_referral(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs, bool cache_attrs) { return false; } /* Default fsal handle object method vector. * copied to allocated vector at register time */ struct fsal_obj_ops def_handle_ops = { .get_ref = handle_get_ref, .put_ref = handle_put_ref, .release = handle_release, .merge = handle_merge, .lookup = lookup, .readdir = read_dirents, .compute_readdir_cookie = compute_readdir_cookie, .dirent_cmp = dirent_cmp, .mkdir = makedir, .mknode = makenode, .symlink = makesymlink, .readlink = readsymlink, .test_access = fsal_test_access, /* default is use common test */ .getattrs = getattrs, .link = linkfile, .rename = renamefile, .unlink = file_unlink, .io_advise = file_io_advise, .fallocate = file_fallocate, .close = file_close, .list_ext_attrs = list_ext_attrs, .getextattr_id_by_name = getextattr_id_by_name, .getextattr_value_by_name = getextattr_value_by_name, .getextattr_value_by_id = getextattr_value_by_id, .setextattr_value = setextattr_value, .setextattr_value_by_id = setextattr_value_by_id, .remove_extattr_by_id = remove_extattr_by_id, .remove_extattr_by_name = remove_extattr_by_name, .handle_to_wire = handle_to_wire, .handle_cmp = handle_cmp, .handle_to_key = handle_to_key, .layoutget = layoutget, .layoutreturn = layoutreturn, .layoutcommit = layoutcommit, .getxattrs = getxattrs, .setxattrs = setxattrs, .removexattrs = removexattrs, .listxattrs = listxattrs, .open2 = open2, .check_verifier = check_verifier, .status2 = status2, .reopen2 = reopen2, .read2 = read2, .write2 = write2, .seek2 = seek2, .io_advise2 = io_advise2, .commit2 = commit2, .lock_op2 = lock_op2, .lease_op2 = lease_op2, .setattr2 = setattr2, .close2 = close2, .is_referral = is_referral, }; /* fsal_pnfs_ds common methods */ /** * @brief Clean up a pNFS Data Server * * Getting here is normal! * * @param[in] pds Handle to release */ static void pds_release(struct fsal_pnfs_ds *const pds) { LogDebug(COMPONENT_PNFS, "Default pNFS DS release!"); fsal_pnfs_ds_fini(pds); pnfs_ds_free(pds); } /** * @brief Initialize FSAL specific permissions per pNFS DS * * @param[in] pds FSAL pNFS DS * @param[in] req Incoming request. * * @retval NFS4_OK, NFS4ERR_ACCESS, NFS4ERR_WRONGSEC. */ static nfsstat4 pds_permissions(struct fsal_pnfs_ds *const pds, struct svc_req *req) { /* FIX ME!!! Replace with a non-export dependent system. * For now, reset per init_op_context() */ op_ctx->export_perms.set = root_op_export_set; op_ctx->export_perms.options = root_op_export_options; return NFS4_OK; } /** * @brief Try to create a FSAL data server handle * * @param[in] pds FSAL pNFS DS * @param[in] hdl_desc Buffer from which to create the struct * @param[out] handle FSAL DS handle * * @retval NFS4_OK, NFS4ERR_SERVERFAULT. */ static nfsstat4 pds_handle(struct fsal_pnfs_ds *const pds, const struct gsh_buffdesc *const hdl_desc, struct fsal_ds_handle **const handle, int flags) { LogCrit(COMPONENT_PNFS, "Unimplemented DS handle creation!"); *handle = gsh_calloc(1, sizeof(struct fsal_ds_handle)); return NFS4_OK; } /* fsal_ds_handle common methods */ /** * @brief Fail to clean up a DS handle * * Getting here is bad, it means we support but have not completely * implemented DS handles. * * @param[in] release Handle to release */ static void ds_handle_release(struct fsal_ds_handle *const ds_hdl) { LogCrit(COMPONENT_PNFS, "Unimplemented DS handle release!"); gsh_free(ds_hdl); } /** * @brief Fail to read from a data-server handle. * * @param[in] ds_hdl FSAL DS handle * @param[in] stateid The stateid supplied with the READ operation, * for validation * @param[in] offset The offset at which to read * @param[in] requested_length Length of read requested (and size of buffer) * @param[out] buffer The buffer to which to store read data * @param[out] supplied_length Length of data read * @param[out] eof true on end of file * * @return NFS4ERR_NOTSUPP. */ static nfsstat4 ds_read(struct fsal_ds_handle *const ds_hdl, const stateid4 *stateid, const offset4 offset, const count4 requested_length, void *const buffer, count4 *const supplied_length, bool *const end_of_file) { LogCrit(COMPONENT_PNFS, "Unimplemented DS read!"); return NFS4ERR_NOTSUPP; } static nfsstat4 ds_read_plus(struct fsal_ds_handle *const ds_hdl, const stateid4 *stateid, const offset4 offset, const count4 requested_length, void *const buffer, const count4 supplied_length, bool *const end_of_file, struct io_info *info) { LogCrit(COMPONENT_PNFS, "Unimplemented DS read_plus!"); return NFS4ERR_NOTSUPP; } /** * @brief Fail to write to a data-server handle. * * @param[in] ds_hdl FSAL DS handle * @param[in] stateid The stateid supplied with the READ operation, * for validation * @param[in] offset The offset at which to read * @param[in] write_length Length of write requested (and size of buffer) * @param[out] buffer The buffer to which to store read data * @param[in] stability wanted Stability of write * @param[out] written_length Length of data written * @param[out] writeverf Write verifier * @param[out] stability_got Stability used for write (must be as * or more stable than request) * * @return An NFSv4.1 status code. */ static nfsstat4 ds_write(struct fsal_ds_handle *const ds_hdl, const stateid4 *stateid, const offset4 offset, const count4 write_length, const void *buffer, const stable_how4 stability_wanted, count4 *const written_length, verifier4 *const writeverf, stable_how4 *const stability_got) { LogCrit(COMPONENT_PNFS, "Unimplemented DS write!"); return NFS4ERR_NOTSUPP; } /** * @brief Fail to commit a byte range on a DS handle. * * @param[in] ds_hdl FSAL DS handle * @param[in] offset Start of commit window * @param[in] count Length of commit window * @param[out] writeverf Write verifier * * @return An NFSv4.1 status code. */ static nfsstat4 ds_commit(struct fsal_ds_handle *const ds_hdl, const offset4 offset, const count4 count, verifier4 *const writeverf) { LogCrit(COMPONENT_PNFS, "Unimplemented DS commit!"); return NFS4ERR_NOTSUPP; } struct fsal_pnfs_ds_ops def_pnfs_ds_ops = { .ds_release = pds_release, .ds_permissions = pds_permissions, .make_ds_handle = pds_handle, .dsh_release = ds_handle_release, .dsh_read = ds_read, .dsh_read_plus = ds_read_plus, .dsh_write = ds_write, .dsh_commit = ds_commit }; /** @} */ nfs-ganesha-6.5/src/FSAL/fsal_config.c000066400000000000000000000104211473756622300175250ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @addtogroup FSAL * @{ */ /** * @file fsal_config.c * @author Jim Lieb * @brief Initialize configuration parameters */ #include "config.h" #include #include #include #include #include #include #include #include "log.h" #include "fsal.h" #include "FSAL/fsal_init.h" /* filesystem info handlers * common functions for fsal info methods */ bool fsal_supports(struct fsal_staticfsinfo_t *info, fsal_fsinfo_options_t option) { switch (option) { case fso_no_trunc: return !!info->no_trunc; case fso_chown_restricted: return !!info->chown_restricted; case fso_case_insensitive: return !!info->case_insensitive; case fso_case_preserving: return !!info->case_preserving; case fso_link_support: return !!info->link_support; case fso_symlink_support: return !!info->symlink_support; case fso_lock_support: return !!info->lock_support; case fso_lock_support_async_block: return !!info->lock_support_async_block; case fso_lock_full_control: return !!info->lock_full_control; case fso_named_attr: return !!info->named_attr; case fso_unique_handles: return !!info->unique_handles; case fso_cansettime: return !!info->cansettime; case fso_homogenous: return !!info->homogenous; case fso_auth_exportpath_xdev: return !!info->auth_exportpath_xdev; case fso_delegations_r: return !!(info->delegations & FSAL_OPTION_FILE_READ_DELEG); case fso_delegations_w: return !!(info->delegations & FSAL_OPTION_FILE_WRITE_DELEG); case fso_pnfs_mds_supported: return !!info->pnfs_mds; case fso_pnfs_ds_supported: return !!info->pnfs_ds; case fso_grace_method: return !!info->fsal_grace; case fso_link_supports_permission_checks: return !!info->link_supports_permission_checks; case fso_rename_changes_key: return !!info->rename_changes_key; case fso_compute_readdir_cookie: return !!info->compute_readdir_cookie; case fso_whence_is_name: return !!info->whence_is_name; case fso_readdir_plus: return !!info->readdir_plus; case fso_compliant_eof_behavior: return !!info->compliant_eof_behavior; case fso_xattr_support: return !!info->xattr_support; case fso_allocate_own_read_buffer: return !!info->allocate_own_read_buffer; default: return false; /* whatever I don't know about, * you can't do */ } } uint64_t fsal_maxfilesize(struct fsal_staticfsinfo_t *info) { return info->maxfilesize; } uint32_t fsal_maxlink(struct fsal_staticfsinfo_t *info) { return info->maxlink; } uint32_t fsal_maxnamelen(struct fsal_staticfsinfo_t *info) { return info->maxnamelen; } uint32_t fsal_maxpathlen(struct fsal_staticfsinfo_t *info) { return info->maxpathlen; } fsal_aclsupp_t fsal_acl_support(struct fsal_staticfsinfo_t *info) { return info->acl_support; } attrmask_t fsal_supported_attrs(struct fsal_staticfsinfo_t *info) { return info->supported_attrs; } uint32_t fsal_maxread(struct fsal_staticfsinfo_t *info) { return info->maxread; } uint32_t fsal_maxwrite(struct fsal_staticfsinfo_t *info) { return info->maxwrite; } uint32_t fsal_umask(struct fsal_staticfsinfo_t *info) { return info->umask; } int32_t fsal_expiretimeparent(struct fsal_staticfsinfo_t *info) { return info->expire_time_parent; } /** @} */ nfs-ganesha-6.5/src/FSAL/fsal_convert.c000066400000000000000000000244261473756622300177520ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:expandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2006) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @addtogroup FSAL * @{ */ /** * @file FSAL/fsal_convert.c * @brief FSAL type translation functions. */ #include "config.h" #ifdef LINUX #include /* for major(3), minor(3) */ #endif #include #include #include #include #include #include #include "fsal.h" #include "fsal_convert.h" #include "common_utils.h" /** * posix2fsal_error : * Convert POSIX error codes to FSAL error codes. * * \param posix_errorcode (input): * The error code returned from POSIX. * * \return The FSAL error code associated * to posix_errorcode. * */ fsal_errors_t posix2fsal_error(int posix_errorcode) { struct rlimit rlim = { .rlim_cur = RLIM_INFINITY, .rlim_max = RLIM_INFINITY }; switch (posix_errorcode) { case 0: return ERR_FSAL_NO_ERROR; case EPERM: return ERR_FSAL_PERM; case ENOENT: return ERR_FSAL_NOENT; /* connection error */ #ifdef _AIX_5 case ENOCONNECT: #elif defined linux case ECONNREFUSED: case ECONNABORTED: case ECONNRESET: #endif /* IO error */ case EIO: /* too many open files */ case ENFILE: case EMFILE: /* broken pipe */ case EPIPE: /* all shown as IO errors */ if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) { LogInfo(COMPONENT_FSAL, "Mapping %d to ERR_FSAL_IO, getrlimit failed", posix_errorcode); } else { LogInfo(COMPONENT_FSAL, "Mapping %d to ERR_FSAL_IO, rlim_cur=%" PRId64 " rlim_max=%" PRId64, +posix_errorcode, rlim.rlim_cur, rlim.rlim_max); } return ERR_FSAL_IO; /* no such device */ case ENOTTY: case ENODEV: case ENXIO: LogInfo(COMPONENT_FSAL, "Mapping %d to ERR_FSAL_NXIO", posix_errorcode); return ERR_FSAL_NXIO; /* invalid file descriptor : */ case EBADF: /* we suppose it was not opened... */ /** * @todo: The EBADF error also happens when file * is opened for reading, and we try writing in it. * In this case, we return ERR_FSAL_NOT_OPENED, * but it doesn't seems to be a correct error translation. */ return ERR_FSAL_NOT_OPENED; case ENOMEM: case ENOLCK: LogInfo(COMPONENT_FSAL, "Mapping %d to ERR_FSAL_NOMEM", posix_errorcode); return ERR_FSAL_NOMEM; case EACCES: return ERR_FSAL_ACCESS; case EFAULT: return ERR_FSAL_FAULT; case EEXIST: return ERR_FSAL_EXIST; case EXDEV: return ERR_FSAL_XDEV; case ENOTDIR: return ERR_FSAL_NOTDIR; case EISDIR: return ERR_FSAL_ISDIR; case EINVAL: return ERR_FSAL_INVAL; case EROFS: return ERR_FSAL_ROFS; case ETXTBSY: return ERR_FSAL_SHARE_DENIED; case EFBIG: return ERR_FSAL_FBIG; case ENOSPC: return ERR_FSAL_NOSPC; case EMLINK: return ERR_FSAL_MLINK; case EDQUOT: return ERR_FSAL_DQUOT; case ESRCH: /* Returned by quotaclt */ return ERR_FSAL_NO_QUOTA; case ENAMETOOLONG: return ERR_FSAL_NAMETOOLONG; /** * @warning * AIX returns EEXIST where BSD uses ENOTEMPTY; * We want ENOTEMPTY to be interpreted anyway on AIX plateforms. * Thus, we explicitly write its value (87). */ #ifdef _AIX case 87: #else case ENOTEMPTY: case -ENOTEMPTY: #endif return ERR_FSAL_NOTEMPTY; case ESTALE: return ERR_FSAL_STALE; /* Error code that needs a retry */ case EAGAIN: case EBUSY: case ETIMEDOUT: #ifdef ETIME case ETIME: #endif LogInfo(COMPONENT_FSAL, "Mapping %d to ERR_FSAL_DELAY", posix_errorcode); return ERR_FSAL_DELAY; case ENOTSUP: return ERR_FSAL_NOTSUPP; case EOVERFLOW: return ERR_FSAL_OVERFLOW; case EDEADLK: return ERR_FSAL_DEADLOCK; case EINTR: return ERR_FSAL_INTERRUPT; #ifdef ENODATA case ENODATA: return ERR_FSAL_NO_DATA; #endif case ERANGE: return ERR_FSAL_BAD_RANGE; default: LogCrit(COMPONENT_FSAL, "Default case mapping %s (%d) to ERR_FSAL_SERVERFAULT", strerror(posix_errorcode), posix_errorcode); /* other unexpected errors */ return ERR_FSAL_SERVERFAULT; } } /** * @brief Convert FSAL permission flags to Posix permission flags. * * @param[in] testperm FSAL permission flags to be tested * * @return POSIX permission flags to be tested. */ int fsal2posix_testperm(fsal_accessflags_t testperm) { int posix_testperm = 0; if (testperm & FSAL_R_OK) posix_testperm |= R_OK; if (testperm & FSAL_W_OK) posix_testperm |= W_OK; if (testperm & FSAL_X_OK) posix_testperm |= X_OK; return posix_testperm; } /** * @brief Convert POSIX object type to an FSAL object type * * @param[in] posix_type_in POSIX object type * * @retval The FSAL node type associated to @c posix_type_in. * @retval -1 if the input type is unknown. */ object_file_type_t posix2fsal_type(mode_t posix_type_in) { switch (posix_type_in & S_IFMT) { case S_IFIFO: return FIFO_FILE; case S_IFCHR: return CHARACTER_FILE; case S_IFDIR: return DIRECTORY; case S_IFBLK: return BLOCK_FILE; case S_IFREG: case S_IFMT: return REGULAR_FILE; case S_IFLNK: return SYMBOLIC_LINK; case S_IFSOCK: return SOCKET_FILE; default: LogWarn(COMPONENT_FSAL, "Unknown object type: %d", posix_type_in); return -1; } } /** * @brief Convert a stat(2) style dev_t to an FSAL fsid * * @param[in] posix_devid The device id * * @return The FSAL fsid. */ fsal_fsid_t posix2fsal_fsid(dev_t posix_devid) { fsal_fsid_t fsid; fsid.major = major(posix_devid); fsid.minor = minor(posix_devid); return fsid; } /** * @brief Convert a stat(2) style dev_t to an fsal_dev_t * * @param[in] posix_devid The device id * * @return The FSAL device. */ fsal_dev_t posix2fsal_devt(dev_t posix_devid) { fsal_dev_t dev; dev.major = major(posix_devid); dev.minor = minor(posix_devid); return dev; } /** * @brief Convert FSAL open flags to POSIX open flags * * @param[in] fsal_flags FSAL open flags to be translated * @param[out] p_posix_flags POSIX open flags. * * @retval ERR_FSAL_NO_ERROR, no error. * @retval ERR_FSAL_INVAL, invalid or incompatible input flags. */ void fsal2posix_openflags(fsal_openflags_t fsal_flags, int *p_posix_flags) { /* Ignore any flags that are not actually used, there are flags * that are passed to FSAL operations that don't convert to * POSIX open flags, which is fine. */ /* conversion */ *p_posix_flags = 0; if ((fsal_flags & FSAL_O_RDWR) == FSAL_O_RDWR) *p_posix_flags |= O_RDWR; else if ((fsal_flags & FSAL_O_RDWR) == FSAL_O_READ) *p_posix_flags |= O_RDONLY; else if ((fsal_flags & FSAL_O_RDWR) == FSAL_O_WRITE) *p_posix_flags |= O_WRONLY; else if ((fsal_flags & FSAL_O_ANY) != 0) *p_posix_flags |= O_RDONLY; if (fsal_flags & FSAL_O_TRUNC) *p_posix_flags |= O_TRUNC; } /** * @brief Return string for object type * * @param[in] type The FSAL object type * * @return A string naming the type or "unexpected type". */ const char *object_file_type_to_str(object_file_type_t type) { switch (type) { case NO_FILE_TYPE: return "NO_FILE_TYPE"; case REGULAR_FILE: return "REGULAR_FILE"; case CHARACTER_FILE: return "CHARACTER_FILE"; case BLOCK_FILE: return "BLOCK_FILE"; case SYMBOLIC_LINK: return "SYMBOLIC_LINK"; case SOCKET_FILE: return "SOCKET_FILE"; case FIFO_FILE: return "FIFO_FILE"; case DIRECTORY: return "DIRECTORY"; case EXTENDED_ATTR: return "EXTENDED_ATTR"; } return "unexpected type"; } void posix2fsal_attributes_all(const struct stat *buffstat, struct fsal_attrlist *fsalattr) { fsalattr->valid_mask |= ATTRS_POSIX; posix2fsal_attributes(buffstat, fsalattr); } /* fsalattr->valid_mask should be set to POSIX attributes that need to * be filled in. buffstat is expected to have those attributes filled in * correctly for converting the attributes from POSIX to FSAL. */ void posix2fsal_attributes(const struct stat *buffstat, struct fsal_attrlist *fsalattr) { fsalattr->supported = op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export); /* Fills the output struct */ if (FSAL_TEST_MASK(fsalattr->valid_mask, ATTR_TYPE)) fsalattr->type = posix2fsal_type(buffstat->st_mode); if (FSAL_TEST_MASK(fsalattr->valid_mask, ATTR_SIZE)) fsalattr->filesize = buffstat->st_size; if (FSAL_TEST_MASK(fsalattr->valid_mask, ATTR_FSID)) fsalattr->fsid = posix2fsal_fsid(buffstat->st_dev); if (FSAL_TEST_MASK(fsalattr->valid_mask, ATTR_FILEID)) fsalattr->fileid = (uint64_t)buffstat->st_ino; if (FSAL_TEST_MASK(fsalattr->valid_mask, ATTR_MODE)) fsalattr->mode = unix2fsal_mode(buffstat->st_mode); if (FSAL_TEST_MASK(fsalattr->valid_mask, ATTR_NUMLINKS)) fsalattr->numlinks = buffstat->st_nlink; if (FSAL_TEST_MASK(fsalattr->valid_mask, ATTR_OWNER)) fsalattr->owner = buffstat->st_uid; if (FSAL_TEST_MASK(fsalattr->valid_mask, ATTR_GROUP)) fsalattr->group = buffstat->st_gid; if (FSAL_TEST_MASK(fsalattr->valid_mask, ATTR_ATIME)) fsalattr->atime = buffstat->st_atim; if (FSAL_TEST_MASK(fsalattr->valid_mask, ATTR_CTIME)) fsalattr->ctime = buffstat->st_ctim; if (FSAL_TEST_MASK(fsalattr->valid_mask, ATTR_MTIME)) fsalattr->mtime = buffstat->st_mtim; if (FSAL_TEST_MASK(fsalattr->valid_mask, ATTR_CHANGE)) { fsalattr->change = gsh_time_cmp(&fsalattr->mtime, &fsalattr->ctime) > 0 ? timespec_to_nsecs(&fsalattr->mtime) : timespec_to_nsecs(&fsalattr->ctime); } if (FSAL_TEST_MASK(fsalattr->valid_mask, ATTR_SPACEUSED)) fsalattr->spaceused = buffstat->st_blocks * S_BLKSIZE; if (FSAL_TEST_MASK(fsalattr->valid_mask, ATTR_RAWDEV)) fsalattr->rawdev = posix2fsal_devt(buffstat->st_rdev); } /** @} */ nfs-ganesha-6.5/src/FSAL/fsal_destroyer.c000066400000000000000000000126061473756622300203070ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2011 The Linux Box Corporation * Author: Adam C. Emerson * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @addtogroup FSAL * @{ */ #include "gsh_list.h" #include "fsal.h" #include "fsal_api.h" #include "nfs_exports.h" #include "nfs_core.h" #include "fsal_private.h" #include "FSAL/fsal_commonlib.h" #include "FSAL/fsal_localfs.h" /** * @file fsal_destroyer.c * @author Adam C. Emerson * @brief Kill the FSAL with prejudice */ /** * @brief Dispose of lingering file handles * * @param[in] fsal The fsal module to clean up */ static void shutdown_handles(struct fsal_module *fsal) { /* Handle iterator */ struct glist_head *hi = NULL; /* Next pointer in handle iteration */ struct glist_head *hn = NULL; if (glist_empty(&fsal->handles)) return; LogDebug(COMPONENT_FSAL, "Extra file handles hanging around."); glist_for_each_safe(hi, hn, &fsal->handles) { struct fsal_obj_handle *h = glist_entry(hi, struct fsal_obj_handle, handles); LogDebug(COMPONENT_FSAL, "Releasing handle"); h->obj_ops->release(h); } } /** * @brief Dispose of lingering pNFS Data Servers * * @param[in] fsal The fsal module to clean up */ static void shutdown_pnfs_ds(struct fsal_module *fsal) { /* Handle iterator */ struct glist_head *glist = NULL; /* Next pointer in handle iteration */ struct glist_head *glistn = NULL; if (glist_empty(&fsal->servers)) return; LogDebug(COMPONENT_FSAL, "Extra pNFS Data Servers hanging around."); glist_for_each_safe(glist, glistn, &fsal->servers) { struct fsal_pnfs_ds *ds = glist_entry(glist, struct fsal_pnfs_ds, server); int32_t refcount; refcount = atomic_fetch_int32_t(&ds->ds_refcount); if (refcount != 0) { LogDebug(COMPONENT_FSAL, "Extra ds refs (%" PRIi32 ") hanging around.", refcount); atomic_store_int32_t(&ds->ds_refcount, 0); } ds->s_ops.ds_release(ds); } } /** * @brief Shut down an individual export * * @param[in] export The export to shut down */ static void shutdown_export(struct fsal_export *export) { struct fsal_module *fsal = export->fsal; LogDebug(COMPONENT_FSAL, "Releasing export"); export->exp_ops.release(export); fsal_put(fsal); LogFullDebug(COMPONENT_FSAL, "FSAL %s fsal_refcount %" PRIu32, fsal->name, atomic_fetch_int32_t(&fsal->refcount)); } /** * @brief Destroy FSALs */ void destroy_fsals(void) { /* Module iterator */ struct glist_head *mi = NULL; /* Next module */ struct glist_head *mn = NULL; int rc = 0; char *fsal_name; glist_for_each_safe(mi, mn, &fsal_list) { /* The module to destroy */ struct fsal_module *m = glist_entry(mi, struct fsal_module, fsals); /* Iterator over exports */ struct glist_head *ei = NULL; /* Next export */ struct glist_head *en = NULL; int32_t refcount = atomic_fetch_int32_t(&m->refcount); LogEvent(COMPONENT_FSAL, "Shutting down handles for FSAL %s", m->name); shutdown_handles(m); LogEvent(COMPONENT_FSAL, "Shutting down DS handles for FSAL %s", m->name); shutdown_pnfs_ds(m); LogEvent(COMPONENT_FSAL, "Shutting down exports for FSAL %s", m->name); glist_for_each_safe(ei, en, &m->exports) { /* The module to destroy */ struct fsal_export *e = glist_entry(ei, struct fsal_export, exports); shutdown_export(e); } LogEvent(COMPONENT_FSAL, "Exports for FSAL %s shut down", m->name); if (refcount != 0) { LogCrit(COMPONENT_FSAL, "Extra fsal references (%" PRIi32 ") hanging around to FSAL %s", refcount, m->name); /** * @todo Forcibly blowing away all references * should work fine on files and objects if * we're shutting down, however it will cause * trouble once we have stackable FSALs. As a * practical matter, though, if the system is * working properly we shouldn't even reach * this point. */ atomic_store_int32_t(&m->refcount, 0); } fsal_name = gsh_strdupa(m->name); LogEvent(COMPONENT_FSAL, "Unloading FSAL %s", fsal_name); rc = m->m_ops.unload(m); if (rc != 0) { LogMajor(COMPONENT_FSAL, "Unload of %s failed with error %d", fsal_name, rc); } LogEvent(COMPONENT_FSAL, "FSAL %s unloaded", fsal_name); } release_posix_file_systems(); destroy_ctx_refstr(); destroy_fsal_lock(); } /** * @brief Emergency Halt FSALs */ void emergency_cleanup_fsals(void) { /* Module iterator */ struct glist_head *mi = NULL; /* Next module */ struct glist_head *mn = NULL; glist_for_each_safe(mi, mn, &fsal_list) { /* The module to destroy */ struct fsal_module *m = glist_entry(mi, struct fsal_module, fsals); m->m_ops.emergency_cleanup(); } } /** @} */ nfs-ganesha-6.5/src/FSAL/fsal_helper.c000066400000000000000000001761321473756622300175530ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) CohrotFS Inc., 2015 * Author: Daniel Gryniewicz * * * This program 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 3 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 * 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 * * ------------- */ /** * @addtogroup FSAL * @{ */ /** * @file fsal_helper.c * @author Daniel Gryniewicz * @brief FSAL helper for clients */ #include "config.h" #include #include #include #include #include #include "gsh_config.h" #include "log.h" #include "fsal.h" #include "fsal_convert.h" #include "nfs_convert.h" #include "nfs_exports.h" #include "nfs4_acls.h" #include "sal_data.h" #include "sal_functions.h" #include "FSAL/fsal_commonlib.h" #include "sal_functions.h" static bool fsal_not_in_group_list(gid_t gid) { int i; if (op_ctx->creds.caller_gid == gid) { LogDebug(COMPONENT_FSAL, "User %u is has active group %u", op_ctx->creds.caller_uid, gid); return false; } for (i = 0; i < op_ctx->creds.caller_glen; i++) { if (op_ctx->creds.caller_garray[i] == gid) { LogDebug(COMPONENT_FSAL, "User %u is member of group %u", op_ctx->creds.caller_uid, gid); return false; } } LogDebug(COMPONENT_FSAL, "User %u IS NOT member of group %u", op_ctx->creds.caller_uid, gid); return true; } /** * @brief Check permissions on opening a file. * * @param[in] obj The file being opened * @param[in] openflags The access requested on opening the file * @param[in] exclusive_create Indicates the file is being exclusive create * @param[out] reason Description of why the access failed. * * @returns Status of the permission check. */ static fsal_status_t check_open_permission(struct fsal_obj_handle *obj, fsal_openflags_t openflags, bool exclusive_create, char **reason) { fsal_status_t status = { 0, 0 }; fsal_accessflags_t access_mask = 0; if (openflags & FSAL_O_READ) access_mask |= FSAL_READ_ACCESS; if (openflags & FSAL_O_WRITE) access_mask |= FSAL_WRITE_ACCESS; /* Ask for owner_skip on exclusive create (we will be checking the * verifier later, so this allows a replay of * open("foo", O_RDWR | O_CREAT | O_EXCL, 0) to succeed). * For open reclaims ask for owner_skip. */ status = obj->obj_ops->test_access( obj, access_mask, NULL, NULL, exclusive_create || (openflags & FSAL_O_RECLAIM)); if (!FSAL_IS_ERROR(status)) { *reason = ""; return status; } LogDebug(COMPONENT_FSAL, "test_access got %s", fsal_err_txt(status)); /* If non-permission error, return it. */ if (status.major != ERR_FSAL_ACCESS) { *reason = "fsal_access failed - "; return status; } /* If WRITE access is requested, return permission * error */ if (openflags & FSAL_O_WRITE) { *reason = "fsal_access failed with WRITE_ACCESS - "; return status; } /* If just a permission error and file was opened read * only, try execute permission. * * NOTE: We don't do anything special for exclusive create here, if an * exclusive create replay failed the above permission check, it * presumably is no longer exclusively the creator of the file * because somehow the owner changed. * */ status = fsal_access(obj, FSAL_EXECUTE_ACCESS); LogDebug(COMPONENT_FSAL, "fsal_access got %s", fsal_err_txt(status)); if (!FSAL_IS_ERROR(status)) *reason = ""; else *reason = "fsal_access failed with EXECUTE_ACCESS - "; return status; } /* When creating a file, we must check that the owner and group to be set is * OK for the caller to set. */ static fsal_status_t fsal_check_create_owner(struct fsal_attrlist *attr) { fsal_status_t status = { 0, 0 }; LogFullDebug(COMPONENT_FSAL, "attr->owner %" PRIu64 " caller_uid %" PRIu64 " attr->group %" PRIu64 " caller_gid %" PRIu64 "", attr->owner, (uint64_t)op_ctx->creds.caller_uid, attr->group, (uint64_t)op_ctx->creds.caller_gid); if (op_ctx->creds.caller_uid == 0) { /* No check for root. */ } else if (FSAL_TEST_MASK(attr->valid_mask, ATTR_OWNER) && attr->owner != op_ctx->creds.caller_uid) { /* non-root is only allowed to set ownership of file to itself. */ status = fsalstat(ERR_FSAL_PERM, 0); LogDebug(COMPONENT_FSAL, "Access check failed (specified OWNER was not user)"); } else if (FSAL_TEST_MASK(attr->valid_mask, ATTR_GROUP) && (attr->group != op_ctx->creds.caller_gid)) { /* non-root is only allowed to set group_owner to a group the * user is a member of. */ int not_in_group = fsal_not_in_group_list(attr->group); if (not_in_group) { status = fsalstat(ERR_FSAL_PERM, 0); LogDebug( COMPONENT_FSAL, "Access check failed (user is not member of specified GROUP)"); } } return status; } /** * @brief Checks permissions on an entry for setattrs * * This function checks if the supplied credentials are sufficient to perform * the required setattrs. * * @param[in] obj The file to be checked * @param[in] attr Attributes to set * @param[in] current The current attributes for object * * @return FSAL status */ static fsal_status_t fsal_check_setattr_perms(struct fsal_obj_handle *obj, struct fsal_attrlist *attr, struct fsal_attrlist *current) { fsal_status_t status = { 0, 0 }; fsal_accessflags_t access_check = 0; bool not_owner; char *note = ""; /* Shortcut, if current user is root, then we can just bail out with * success. */ if (op_ctx->fsal_export->exp_ops.is_superuser(op_ctx->fsal_export, &op_ctx->creds)) { note = " (Ok for root user)"; goto out; } fsal_prepare_attrs(current, op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) & (ATTRS_CREDS | ATTR_MODE | ATTR_ACL)); status = obj->obj_ops->getattrs(obj, current); if (FSAL_IS_ERROR(status)) return status; not_owner = (op_ctx->creds.caller_uid != current->owner); /* Only ownership change need to be checked for owner */ if (FSAL_TEST_MASK(attr->valid_mask, ATTR_OWNER)) { /* non-root is only allowed to "take ownership of file" */ if (attr->owner != op_ctx->creds.caller_uid) { status = fsalstat(ERR_FSAL_PERM, 0); note = " (new OWNER was not user)"; goto out; } /* Owner of file will always be able to "change" the owner to * himself. */ if (not_owner) { access_check |= FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_WRITE_OWNER); LogDebug( COMPONENT_FSAL, "Change OWNER requires FSAL_ACE_PERM_WRITE_OWNER"); } } /* Check if we are changing the owner_group, if owner_group is passed, * but is the current owner_group, then that will be considered a * NO-OP and allowed IF the caller is the owner of the file. */ if (FSAL_TEST_MASK(attr->valid_mask, ATTR_GROUP) && (attr->group != current->group || not_owner)) { /* non-root is only allowed to change group_owner to a group * user is a member of. */ int not_in_group = fsal_not_in_group_list(attr->group); if (not_in_group) { status = fsalstat(ERR_FSAL_PERM, 0); note = " (user is not member of new GROUP)"; goto out; } /* Owner is always allowed to change the group_owner of a file * to a group they are a member of. */ if (not_owner) { access_check |= FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_WRITE_OWNER); LogDebug( COMPONENT_FSAL, "Change GROUP requires FSAL_ACE_PERM_WRITE_OWNER"); } } /* Any attribute after this is always changeable by the owner. * And the above attributes have already been validated as a valid * change for the file owner to make. Note that the owner may be * setting ATTR_OWNER but at this point it MUST be to himself, and * thus is no-op and does not need FSAL_ACE_PERM_WRITE_OWNER. */ if (!not_owner) { note = " (Ok for owner)"; goto out; } if (FSAL_TEST_MASK(attr->valid_mask, ATTR_MODE) || FSAL_TEST_MASK(attr->valid_mask, ATTR_ACL)) { /* Changing mode or ACL requires ACE4_WRITE_ACL */ access_check |= FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_WRITE_ACL); LogDebug(COMPONENT_FSAL, "Change MODE or ACL requires FSAL_ACE_PERM_WRITE_ACL"); } if (FSAL_TEST_MASK(attr->valid_mask, ATTR_SIZE)) { /* Changing size requires owner or write permission */ /** @todo: does FSAL_ACE_PERM_APPEND_DATA allow enlarging the file? */ access_check |= FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_WRITE_DATA); LogDebug(COMPONENT_FSAL, "Change SIZE requires FSAL_ACE_PERM_WRITE_DATA"); } /* Check if just setting atime and mtime to "now" */ if ((FSAL_TEST_MASK(attr->valid_mask, ATTR_MTIME_SERVER) || FSAL_TEST_MASK(attr->valid_mask, ATTR_ATIME_SERVER)) && !FSAL_TEST_MASK(attr->valid_mask, ATTR_MTIME) && !FSAL_TEST_MASK(attr->valid_mask, ATTR_ATIME)) { /* If either atime and/or mtime are set to "now" then need only * have write permission. * * Technically, client should not send atime updates, but if * they really do, we'll let them to make the perm check a bit * simpler. */ access_check |= FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_WRITE_DATA); LogDebug( COMPONENT_FSAL, "Change ATIME and MTIME to NOW requires FSAL_ACE_PERM_WRITE_DATA"); } else if (FSAL_TEST_MASK(attr->valid_mask, ATTR_MTIME_SERVER) || FSAL_TEST_MASK(attr->valid_mask, ATTR_ATIME_SERVER) || FSAL_TEST_MASK(attr->valid_mask, ATTR_MTIME) || FSAL_TEST_MASK(attr->valid_mask, ATTR_ATIME)) { /* Any other changes to atime or mtime require owner, root, or * ACES4_WRITE_ATTRIBUTES. * * NOTE: we explicitly do NOT check for update of atime only to * "now". Section 10.6 of both RFC 3530 and RFC 5661 document * the reasons clients should not do atime updates. */ access_check |= FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_WRITE_ATTR); LogDebug( COMPONENT_FSAL, "Change ATIME and/or MTIME requires FSAL_ACE_PERM_WRITE_ATTR"); } if (isDebug(COMPONENT_FSAL) || isDebug(COMPONENT_NFS_V4_ACL)) { char *need_write_owner = ""; char *need_write_acl = ""; char *need_write_data = ""; char *need_write_attr = ""; if (access_check & FSAL_ACE_PERM_WRITE_OWNER) need_write_owner = " WRITE_OWNER"; if (access_check & FSAL_ACE_PERM_WRITE_ACL) need_write_acl = " WRITE_ACL"; if (access_check & FSAL_ACE_PERM_WRITE_DATA) need_write_data = " WRITE_DATA"; if (access_check & FSAL_ACE_PERM_WRITE_ATTR) need_write_attr = " WRITE_ATTR"; LogDebug(COMPONENT_FSAL, "Requires %s%s%s%s", need_write_owner, need_write_acl, need_write_data, need_write_attr); } if (access_check == 0) { status = fsalstat(ERR_FSAL_NO_ERROR, 0); note = " (No permission required)"; goto out; } if (current->acl) { status = obj->obj_ops->test_access(obj, access_check, NULL, NULL, false); note = " (checked ACL)"; goto out; } if (access_check != FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_WRITE_DATA)) { /* Without an ACL, this user is not allowed some operation */ status = fsalstat(ERR_FSAL_PERM, 0); note = " (no ACL to check)"; goto out; } status = obj->obj_ops->test_access(obj, FSAL_W_OK, NULL, NULL, false); note = " (checked mode)"; out: if (FSAL_IS_ERROR(status)) { /* Done with the current attrs, caller will not expect them. */ fsal_release_attrs(current); } LogDebug(COMPONENT_FSAL, "Access check returned %s%s", fsal_err_txt(status), note); return status; } fsal_status_t open2_by_name(struct fsal_obj_handle *in_obj, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attr, fsal_verifier_t verifier, struct fsal_obj_handle **obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { fsal_status_t status = { 0, 0 }; fsal_status_t close_status = { 0, 0 }; bool caller_perm_check = false; char *reason; if (parent_pre_attrs_out != NULL) parent_pre_attrs_out->valid_mask = 0; if (parent_post_attrs_out != NULL) parent_post_attrs_out->valid_mask = 0; *obj = NULL; if (name == NULL) return fsalstat(ERR_FSAL_INVAL, 0); if (in_obj->type != DIRECTORY) return fsalstat(ERR_FSAL_INVAL, 0); if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { /* Can't open "." or ".."... */ return fsalstat(ERR_FSAL_ISDIR, 0); } /* Check directory permission for LOOKUP */ status = fsal_access(in_obj, FSAL_EXECUTE_ACCESS); if (FSAL_IS_ERROR(status)) return status; status = in_obj->obj_ops->open2(in_obj, state, openflags, createmode, name, attr, verifier, obj, attrs_out, &caller_perm_check, parent_pre_attrs_out, parent_post_attrs_out); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "FSAL %d %s returned %s", (int)op_ctx->ctx_export->export_id, CTX_FULLPATH(op_ctx), fsal_err_txt(status)); return status; } LogFullDebug(COMPONENT_FSAL, "Created entry %p FSAL %s for %s", *obj, (*obj)->fsal->name, name); if (!caller_perm_check) return status; /* Do a permission check on the just opened file. */ status = check_open_permission(*obj, openflags, createmode >= FSAL_EXCLUSIVE, &reason); if (!FSAL_IS_ERROR(status)) return status; LogDebug(COMPONENT_FSAL, "Closing file check_open_permission failed %s-%s", reason, fsal_err_txt(status)); if (state != NULL) close_status = (*obj)->obj_ops->close2(*obj, state); else close_status = fsal_close(*obj); if (FSAL_IS_ERROR(close_status)) { /* Just log but don't return this error (we want to * preserve the error that got us here). */ LogDebug(COMPONENT_FSAL, "FSAL close2 failed with %s", fsal_err_txt(close_status)); } return status; } /** * @brief Set attributes on a file * * The new attributes are copied over @a attr on success. * * The caller is expected to invoke fsal_release_attrs to release any * resources held by the set attributes. The FSAL layer MAY have added an * inherited ACL. * * @param[in] obj File to set attributes on * @param[in] bypass Indicate to bypass share reservation checking * @param[in] state Possible state associated with the entry * @param[in,out] attr Attributes to set * @return FSAL status */ fsal_status_t fsal_setattr(struct fsal_obj_handle *obj, bool bypass, struct state_t *state, struct fsal_attrlist *attr) { fsal_status_t status = { 0, 0 }; struct fsal_attrlist current; bool is_superuser; if ((attr->valid_mask & (ATTR_SIZE | ATTR4_SPACE_RESERVED)) && (obj->type != REGULAR_FILE)) { LogWarn(COMPONENT_FSAL, "Attempt to truncate non-regular file: type=%d", obj->type); return fsalstat(ERR_FSAL_BADTYPE, 0); } if ((attr->valid_mask & (ATTR_SIZE | ATTR_MODE))) { if (state_deleg_conflict(obj, true)) { return fsalstat(ERR_FSAL_DELAY, 0); } } /* Is it allowed to change times ? */ if (!op_ctx->fsal_export->exp_ops.fs_supports(op_ctx->fsal_export, fso_cansettime) && (FSAL_TEST_MASK(attr->valid_mask, (ATTR_ATIME | ATTR_CREATION | ATTR_CTIME | ATTR_MTIME)))) return fsalstat(ERR_FSAL_INVAL, 0); /* Do permission checks, which returns with the attributes for the * object if the caller is not root. */ status = fsal_check_setattr_perms(obj, attr, ¤t); if (FSAL_IS_ERROR(status)) return status; is_superuser = op_ctx->fsal_export->exp_ops.is_superuser( op_ctx->fsal_export, &op_ctx->creds); /* Test for the following condition from chmod(2): * * If the calling process is not privileged (Linux: does not have * the CAP_FSETID capability), and the group of the file does not * match the effective group ID of the process or one of its * supplementary group IDs, the S_ISGID bit will be turned off, * but this will not cause an error to be returned. * * We test the actual mode being set before testing for group * membership since that is a bit more expensive. */ if (!is_superuser && FSAL_TEST_MASK(attr->valid_mask, ATTR_MODE) && (attr->mode & S_ISGID) != 0 && fsal_not_in_group_list(current.group)) { /* Clear S_ISGID */ attr->mode &= ~S_ISGID; } /* Test for the following condition from chown(2) if not operated * on a directory: * * When the owner or group of an executable file are changed by an * unprivileged user the S_ISUID and S_ISGID mode bits are cleared. * POSIX does not specify whether this also should happen when * root does the chown(); the Linux behavior depends on the kernel * version. In case of a non-group-executable file (i.e., one for * which the S_IXGRP bit is not set) the S_ISGID bit indicates * mandatory locking, and is not cleared by a chown(). * * Since chown(2) and POSIX do not mention whether S_ISUID and S_ISGID * mode bits need to be cleared in case when chown() is called by root, * we retain the same behavior (i.e. clear) as linux kernel NFS server. * * Similarly, since chown(2) and POSIX do not mention whether S_ISUID * mode bit needs to be cleared in case of non-executable regular files, * we retain the same behavior (i.e. clear) as linux kernel NFS server. */ if ((obj->type != DIRECTORY) && (FSAL_TEST_MASK(attr->valid_mask, ATTR_OWNER) || FSAL_TEST_MASK(attr->valid_mask, ATTR_GROUP))) { /* The 'current' fsal attrs are not populated during the above call * to fsal_check_setattr_perms() if the caller is root. Populating * it here. */ if (is_superuser) { fsal_prepare_attrs( ¤t, op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) & (ATTR_MODE)); status = obj->obj_ops->getattrs(obj, ¤t); if (FSAL_IS_ERROR(status)) return status; } if ((current.mode & (S_ISUID | S_ISGID)) != 0) { /* User changing ownership on an executable or * non-executable file with S_ISUID or S_ISGID * bit set, need to be cleared. */ if (!FSAL_TEST_MASK(attr->valid_mask, ATTR_MODE)) { /* Mode wasn't being set, so set it now, start with * the current attributes. */ attr->mode = current.mode; FSAL_SET_MASK(attr->valid_mask, ATTR_MODE); } /* Don't clear S_ISGID if the file isn't group * executable. In that case, S_ISGID indicates * mandatory locking and is not cleared by chown. */ if ((current.mode & S_IXGRP) != 0) attr->mode &= ~S_ISGID; /* Clear S_ISUID. */ attr->mode &= ~S_ISUID; } } status = obj->obj_ops->setattr2(obj, bypass, state, attr); if (FSAL_IS_ERROR(status)) { if (status.major == ERR_FSAL_STALE) { LogEvent(COMPONENT_FSAL, "FSAL returned STALE from setattr2"); } return status; } if (!is_superuser) { /* Done with the current attrs */ fsal_release_attrs(¤t); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Read the contents of a symlink * * @param[in] obj Symlink to read * @param[out] link_content Buffer to fill with link contents * @return FSAL status */ fsal_status_t fsal_readlink(struct fsal_obj_handle *obj, struct gsh_buffdesc *link_content) { if (obj->type != SYMBOLIC_LINK) return fsalstat(ERR_FSAL_BADTYPE, 0); /* Never refresh. FSAL_MDCACHE will override for cached FSALs. */ return obj->obj_ops->readlink(obj, link_content, false); } /** * * @brief Links a new name to a file * * This function hard links a new name to an existing file. * * @param[in] obj The file to which to add the new name. * Must not be a directory. * @param[in] dest_dir The directory in which to create the * new name * @param[in] name The new name to add to the file * @param[in,out] destdir_pre_attrs_out Optional attributes for destdir dir * before the operation. Should be atomic. * @param[in,out] destdir_post_attrs_out Optional attributes for destdir dir * after the operation. Should be atomic. * * @return FSAL status * in destination. */ fsal_status_t fsal_link(struct fsal_obj_handle *obj, struct fsal_obj_handle *dest_dir, const char *name, struct fsal_attrlist *destdir_pre_attrs_out, struct fsal_attrlist *destdir_post_attrs_out) { fsal_status_t status = { 0, 0 }; /* The file to be hardlinked can't be a DIRECTORY */ if (obj->type == DIRECTORY) return fsalstat(ERR_FSAL_BADTYPE, 0); /* Is the destination a directory? */ if (dest_dir->type != DIRECTORY) return fsalstat(ERR_FSAL_NOTDIR, 0); /* Must be the same FS */ if (obj->fs != dest_dir->fs) return fsalstat(ERR_FSAL_XDEV, 0); if (!op_ctx->fsal_export->exp_ops.fs_supports( op_ctx->fsal_export, fso_link_supports_permission_checks)) { status = fsal_access( dest_dir, FSAL_MODE_MASK_SET(FSAL_W_OK) | FSAL_MODE_MASK_SET(FSAL_X_OK) | FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_EXECUTE) | FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_ADD_FILE)); if (FSAL_IS_ERROR(status)) return status; } if (state_deleg_conflict(obj, true)) { LogDebug(COMPONENT_FSAL, "Found an existing delegation for %s", name); return fsalstat(ERR_FSAL_DELAY, 0); } /* Rather than performing a lookup first, just try to make the link and return the FSAL's error if it fails. */ status = obj->obj_ops->link(obj, dest_dir, name, destdir_pre_attrs_out, destdir_post_attrs_out); return status; } /** * @brief Look up a name in a directory * * @param[in] parent Handle for the parent directory to be managed. * @param[in] name Name of the file that we are looking up. * @param[out] obj Found file * * @note On success, @a handle has been ref'd * * @return FSAL status */ fsal_status_t fsal_lookup(struct fsal_obj_handle *parent, const char *name, struct fsal_obj_handle **obj, struct fsal_attrlist *attrs_out) { fsal_status_t fsal_status = { 0, 0 }; fsal_accessflags_t access_mask = (FSAL_MODE_MASK_SET(FSAL_X_OK) | FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_EXECUTE)); *obj = NULL; if (parent->type != DIRECTORY) { *obj = NULL; return fsalstat(ERR_FSAL_NOTDIR, 0); } fsal_status = fsal_access(parent, access_mask); if (FSAL_IS_ERROR(fsal_status)) return fsal_status; if (strcmp(name, ".") == 0) { parent->obj_ops->get_ref(parent); *obj = parent; return get_optional_attrs(*obj, attrs_out); } else if (strcmp(name, "..") == 0) return fsal_lookupp(parent, obj, attrs_out); return parent->obj_ops->lookup(parent, name, obj, attrs_out); } /** * @brief Look up a directory using a fully qualified path that is contained * within the export in op_ctx->ctx_export. * * NOTE: This is pretty efficient to use even if the path IS the export. Our * caller would have to do about the same having found the export, so * we might as well have that logic in common code. In fact, we do it * without using strcmp (the function that found the export has already * done that...). * * @param[in] path Relative path to the directory * @param[out] obj Found directory * * @note On success, @a handle has been ref'd * * NOTE: Since this does the path walk through MDCACHE, any intermediary * nodes will be in the cache, since there are not extraneous LRU events * if the cache is full, the intermediary entries are likely to be reaped * as we walk the path, reducing churn in the cache. * * @return FSAL status */ fsal_status_t fsal_lookup_path(const char *path, struct fsal_obj_handle **dirobj) { fsal_status_t fsal_status; struct fsal_obj_handle *parent; char *rest; const char *start, *exppath; int len; /* First we need to strip off the export path, paying heed to * nfs_param.core_param.mount_path_pseudo. Since our callers have used * get_gsh_export_by_pseudo or get_gsh_export_by_path to find the * export, the path MUST be proper. */ exppath = ctx_export_path(op_ctx); len = strlen(exppath); /* For debug builds, assure the above statement is true. */ assert(strncmp(path, exppath, len) == 0); /* So now we can point start at the portion of the path beyond the * export path. This will point start at the '\0' or '/' following the * export path. We will be nice and skip all '/' characters that follow * the export path. */ start = path + len; while (*start == '/') start++; /* Now get the length of the remaining relative path */ len = strlen(start); if (len > MAXPATHLEN) { LogDebug(COMPONENT_FSAL, "Failed due path %s is too long", path); return posix2fsal_status(EINVAL); } /* Initialize parent to root of export and get a ref to it. */ fsal_status = nfs_export_get_root_entry(op_ctx->ctx_export, &parent); if (FSAL_IS_ERROR(fsal_status)) return fsal_status; /* Strip terminating '/' by shrinking length */ while (len > 0 && start[len - 1] == '/') len--; if (len == 0) { /* The path we were passed is effectively the export path, so * just return the export root object with a reference. */ LogDebug(COMPONENT_FSAL, "Returning root of export %s", exppath); *dirobj = parent; return fsal_status; } /* Allocate space for duplicate */ rest = alloca(len + 1); /* Copy the string without any extraneous '/' at begin or end. */ memcpy(rest, start, len); /* Terminate it */ rest[len] = '\0'; while (*rest != '\0') { struct fsal_obj_handle *obj; char *next_slash; /* Skip extra '/'. Note that since we trimmed trailing '/' there * MUST be a non-NUL character and thus another path component * following ANY '/' character in the path, so by skipping any * extraneous '/' characters, we advance to the start of the * next path component. */ while (*rest == '/') rest++; /* Find the end of this path element */ next_slash = strchr(rest, '/'); /* NUL terminate element if not at end of string. */ if (next_slash != NULL) *next_slash = '\0'; /* Disallow .. elements... */ if (strcmp(rest, "..") == 0) { parent->obj_ops->put_ref(parent); LogInfo(COMPONENT_FSAL, "Failed due to '..' element in path %s", path); return posix2fsal_status(EACCES); } /* Skip "." elements... */ if (rest[0] == '.' && rest[1] == '\0') goto skip; /* Open the next directory in the path */ fsal_status = parent->obj_ops->lookup(parent, rest, &obj, NULL); /* No matter what, we're done with the parent reference */ parent->obj_ops->put_ref(parent); if (FSAL_IS_ERROR(fsal_status)) { LogDebug(COMPONENT_FSAL, "Failed due to %s element in path %s error %s", rest, path, fsal_err_txt(fsal_status)); return fsal_status; } if (obj->type != DIRECTORY) { obj->obj_ops->put_ref(obj); LogDebug( COMPONENT_FSAL, "Failed due to %s element in path %s not a directory", rest, path); return posix2fsal_status(ENOTDIR); } /* Set up for next lookup */ parent = obj; skip: /* Done, break out */ if (next_slash == NULL) break; /* Skip the '/' */ rest = next_slash + 1; } /* Now parent is the object we're looking for and we already knmow it's * a directory. Return it with the reference we are holding. */ *dirobj = parent; return fsal_status; } /** * @brief Look up a directory's parent * * @param[in] obj File whose parent is to be obtained. * @param[out] parent Parent directory * * @return FSAL status */ fsal_status_t fsal_lookupp(struct fsal_obj_handle *obj, struct fsal_obj_handle **parent, struct fsal_attrlist *attrs_out) { *parent = NULL; /* Never even think of calling FSAL_lookup on root/.. */ if (obj->type == DIRECTORY) { fsal_status_t status = { 0, 0 }; struct fsal_obj_handle *root_obj = NULL; status = nfs_export_get_root_entry(op_ctx->ctx_export, &root_obj); if (FSAL_IS_ERROR(status)) return status; if (obj == root_obj) { /* This entry is the root of the current export, so if * we get this far, return itself. Note that NFS v4 * LOOKUPP will not come here, it catches the root entry * earlier. */ *parent = obj; if (attrs_out != NULL) { /* Need to return the attributes of the * current object. */ return obj->obj_ops->getattrs(obj, attrs_out); } else { /* Success */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } } else { /* Return entry from nfs_export_get_root_entry */ root_obj->obj_ops->put_ref(root_obj); } } return obj->obj_ops->lookup(obj, "..", parent, attrs_out); } /** * @brief Set the create verifier * * This function sets the mtime/atime attributes according to the create * verifier * * @param[in] sattr fsal_attrlist to be managed. * @param[in] verf_hi High long of verifier * @param[in] verf_lo Low long of verifier * */ void fsal_create_set_verifier(struct fsal_attrlist *sattr, uint32_t verf_hi, uint32_t verf_lo) { sattr->atime.tv_sec = verf_hi; sattr->atime.tv_nsec = 0; FSAL_SET_MASK(sattr->valid_mask, ATTR_ATIME); sattr->mtime.tv_sec = verf_lo; sattr->mtime.tv_nsec = 0; FSAL_SET_MASK(sattr->valid_mask, ATTR_MTIME); } /** * @brief Creates an object in a directory * * This function creates an entry in the FSAL. If the @a name exists, the * returned error is ERR_FSAL_EXIST, and @a obj is set if the existing object * has the same type as the requested one. * * The caller is expected to set the mode. Any other specified attributes * will also be set. * * The caller is expected to invoke fsal_release_attrs to release any * resources held by the set attributes. The FSAL layer MAY have added an * inherited ACL. * * @param[in] parent Parent directory * @param[in] name Name of the object to create * @param[in] type Type of the object to create * @param[in] attrs Attributes to be used at file creation * @param[in] link_content Contents for symlink * @param[out] obj Created file * @param[in,out] attrs_out Optional attributes for the create * object. Should be atomic. * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @note On success, @a obj has been ref'd * * @return FSAL status */ fsal_status_t fsal_create(struct fsal_obj_handle *parent, const char *name, object_file_type_t type, struct fsal_attrlist *attrs, const char *link_content, struct fsal_obj_handle **obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { fsal_status_t status = { 0, 0 }; attrmask_t orig_mask = attrs->valid_mask; /* For support_ex API, turn off owner and/or group attr * if they are the same as the credentials. */ if ((attrs->valid_mask & ATTR_OWNER) && attrs->owner == op_ctx->creds.caller_uid) FSAL_UNSET_MASK(attrs->valid_mask, ATTR_OWNER); if ((attrs->valid_mask & ATTR_GROUP) && attrs->group == op_ctx->creds.caller_gid) FSAL_UNSET_MASK(attrs->valid_mask, ATTR_GROUP); /* Permission checking will be done by the FSAL operation. */ /* Try to create it first */ if (parent_pre_attrs_out != NULL) parent_pre_attrs_out->valid_mask = 0; if (parent_post_attrs_out != NULL) { parent_post_attrs_out->valid_mask = 0; } switch (type) { case REGULAR_FILE: status = fsal_open2(parent, NULL, FSAL_O_RDWR, FSAL_UNCHECKED, name, attrs, NULL, obj, attrs_out, parent_pre_attrs_out, parent_post_attrs_out); if (FSAL_IS_SUCCESS(status)) { /* Close it again; this is just a create */ (void)fsal_close(*obj); } break; case DIRECTORY: status = parent->obj_ops->mkdir(parent, name, attrs, obj, attrs_out, parent_pre_attrs_out, parent_post_attrs_out); break; case SYMBOLIC_LINK: status = parent->obj_ops->symlink(parent, name, link_content, attrs, obj, attrs_out, parent_pre_attrs_out, parent_post_attrs_out); break; case SOCKET_FILE: case FIFO_FILE: case BLOCK_FILE: case CHARACTER_FILE: status = parent->obj_ops->mknode(parent, name, type, attrs, obj, attrs_out, parent_pre_attrs_out, parent_post_attrs_out); break; case NO_FILE_TYPE: case EXTENDED_ATTR: /* we should never go there */ status = fsalstat(ERR_FSAL_BADTYPE, 0); *obj = NULL; LogFullDebug(COMPONENT_FSAL, "create failed because of bad type"); goto out; } /* Check for the result */ if (FSAL_IS_ERROR(status)) { if (status.major == ERR_FSAL_STALE) { LogEvent(COMPONENT_FSAL, "FSAL returned STALE on create type %d", type); } else if (status.major == ERR_FSAL_EXIST) { /* Already exists. Check if type if correct */ status = fsal_lookup(parent, name, obj, attrs_out); if (*obj != NULL) { status = fsalstat(ERR_FSAL_EXIST, 0); LogFullDebug( COMPONENT_FSAL, "create failed because it already exists"); if ((*obj)->type != type) { /* Incompatible types, returns NULL */ (*obj)->obj_ops->put_ref((*obj)); *obj = NULL; goto out; } if ((type == REGULAR_FILE) && (attrs->valid_mask & ATTR_SIZE) && attrs->filesize == 0) { attrs->valid_mask &= ATTR_SIZE; goto out; } } } else { *obj = NULL; } goto out; } out: /* Restore original mask so caller isn't bamboozled... */ attrs->valid_mask = orig_mask; LogFullDebug(COMPONENT_FSAL, "Returning obj=%p status=%s for %s FSAL=%s", *obj, fsal_err_txt(status), name, parent->fsal->name); return status; } /** * @brief Return true if create verifier matches * * This function returns true if the create verifier matches * * @param[in] obj File to be managed. * @param[in] verf_hi High long of verifier * @param[in] verf_lo Low long of verifier * * @return true if verified, false otherwise * */ bool fsal_create_verify(struct fsal_obj_handle *obj, uint32_t verf_hi, uint32_t verf_lo) { /* True if the verifier matches */ bool verified = false; struct fsal_attrlist attrs; fsal_prepare_attrs(&attrs, ATTR_ATIME | ATTR_MTIME); obj->obj_ops->getattrs(obj, &attrs); if (FSAL_TEST_MASK(attrs.valid_mask, ATTR_ATIME) && FSAL_TEST_MASK(attrs.valid_mask, ATTR_MTIME) && attrs.atime.tv_sec == verf_hi && attrs.mtime.tv_sec == verf_lo) verified = true; /* Done with the attrs */ fsal_release_attrs(&attrs); return verified; } struct fsal_populate_cb_state { struct fsal_obj_handle *directory; fsal_status_t *status; helper_readdir_cb cb; fsal_cookie_t last_cookie; enum cb_state cb_state; unsigned int *cb_nfound; attrmask_t attrmask; struct fsal_readdir_cb_parms cb_parms; }; static enum fsal_dir_result populate_dirent(const char *name, struct fsal_obj_handle *obj, struct fsal_attrlist *attrs, void *dir_state, fsal_cookie_t cookie) { struct fsal_populate_cb_state *state = (struct fsal_populate_cb_state *)dir_state; fsal_status_t status = { 0, 0 }; enum fsal_dir_result retval; retval = DIR_CONTINUE; state->cb_parms.name = name; status.major = state->cb(&state->cb_parms, obj, attrs, attrs->fileid, cookie, state->cb_state); if (status.major == ERR_FSAL_CROSS_JUNCTION) { struct fsal_obj_handle *junction_obj; struct gsh_export *junction_export = NULL; struct saved_export_context saved; struct fsal_attrlist attrs2; PTHREAD_RWLOCK_rdlock(&obj->state_hdl->jct_lock); /* Get a reference to the junction_export and remember it * only if the junction export is valid. */ if (obj->state_hdl->dir.junction_export != NULL && export_ready(obj->state_hdl->dir.junction_export)) { junction_export = obj->state_hdl->dir.junction_export; get_gsh_export_ref(junction_export); } PTHREAD_RWLOCK_unlock(&obj->state_hdl->jct_lock); /* Get the root of the export across the junction. */ if (junction_export != NULL) { status = nfs_export_get_root_entry(junction_export, &junction_obj); if (FSAL_IS_ERROR(status)) { struct gsh_refstr *ref_fullpath; rcu_read_lock(); ref_fullpath = gsh_refstr_get(rcu_dereference( junction_export->fullpath)); rcu_read_unlock(); LogMajor( COMPONENT_FSAL, "Failed to get root for %s, id=%d, status = %s", ref_fullpath ? ref_fullpath->gr_val : "", junction_export->export_id, fsal_err_txt(status)); gsh_refstr_put(ref_fullpath); /* Need to signal problem to callback */ state->cb_state = CB_PROBLEM; (void)state->cb(&state->cb_parms, NULL, NULL, 0, cookie, state->cb_state); /* Protocol layers NEVER do readahead. */ retval = DIR_TERMINATE; put_gsh_export(junction_export); goto out; } } else { LogMajor(COMPONENT_FSAL, "A junction became stale"); /* Need to signal problem to callback */ state->cb_state = CB_PROBLEM; (void)state->cb(&state->cb_parms, NULL, NULL, 0, cookie, state->cb_state); /* Protocol layers NEVER do readahead. */ retval = DIR_TERMINATE; goto out; } /* Now we need to get the cross-junction attributes. */ save_op_context_export_and_set_export(&saved, junction_export); fsal_prepare_attrs( &attrs2, op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) | ATTR_RDATTR_ERR); status = junction_obj->obj_ops->getattrs(junction_obj, &attrs2); if (!FSAL_IS_ERROR(status)) { /* Now call the callback again with that. */ state->cb_state = CB_JUNCTION; status.major = state->cb( &state->cb_parms, junction_obj, &attrs2, junction_export->exp_mounted_on_file_id, cookie, state->cb_state); state->cb_state = CB_ORIGINAL; } fsal_release_attrs(&attrs2); /* Release our refs and restore op_context */ junction_obj->obj_ops->put_ref(junction_obj); restore_op_context_export(&saved); /* state->cb (nfs4_readdir_callback) saved op_ctx * ctx_export and fsal_export. Restore them here */ (void)state->cb(&state->cb_parms, NULL, NULL, 0, 0, CB_PROBLEM); } if (!state->cb_parms.in_result) { /* Protocol layers NEVER do readahead. */ retval = DIR_TERMINATE; goto out; } (*state->cb_nfound)++; out: /* Put the ref on obj that readdir took */ obj->obj_ops->put_ref(obj); return retval; } /** * @brief Reads a directory * * This function iterates over the directory entries and invokes a supplied * callback function for each one. * * @param[in] directory The directory to be read * @param[in] cookie Starting cookie for the readdir operation * @param[out] eod_met Whether the end of directory was met * @param[in] attrmask Attributes requested, used for permission checking * really all that matters is ATTR_ACL and any attrs * at all, specifics never actually matter. * @param[in] cb The callback function to receive entries * @param[in] opaque A pointer passed to be passed in * fsal_readdir_cb_parms * * @return FSAL status */ fsal_status_t fsal_readdir(struct fsal_obj_handle *directory, uint64_t cookie, unsigned int *nbfound, bool *eod_met, attrmask_t attrmask, helper_readdir_cb cb, void *opaque) { fsal_status_t fsal_status = { 0, 0 }; fsal_status_t cb_status = { 0, 0 }; struct fsal_populate_cb_state state; *nbfound = 0; /* The access mask corresponding to permission to list directory entries */ fsal_accessflags_t access_mask = (FSAL_MODE_MASK_SET(FSAL_R_OK) | FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_LIST_DIR)); fsal_accessflags_t access_mask_attr = (FSAL_MODE_MASK_SET(FSAL_R_OK) | FSAL_MODE_MASK_SET(FSAL_X_OK) | FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_LIST_DIR) | FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_EXECUTE)); /* readdir can be done only with a directory */ if (directory->type != DIRECTORY) { LogDebug(COMPONENT_NFS_READDIR, "Not a directory"); return fsalstat(ERR_FSAL_NOTDIR, 0); } /* Adjust access mask if ACL is asked for. * NOTE: We intentionally do NOT check ACE4_READ_ATTR. */ if ((attrmask & ATTR_ACL) != 0) { access_mask |= FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_READ_ACL); access_mask_attr |= FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_READ_ACL); } fsal_status = fsal_access(directory, access_mask); if (FSAL_IS_ERROR(fsal_status)) { LogDebug(COMPONENT_NFS_READDIR, "permission check for directory status=%s", fsal_err_txt(fsal_status)); return fsal_status; } if (attrmask != 0) { /* Check for access permission to get attributes */ fsal_status_t attr_status = fsal_access(directory, access_mask_attr); if (FSAL_IS_ERROR(attr_status)) LogDebug(COMPONENT_NFS_READDIR, "permission check for attributes status=%s", fsal_err_txt(attr_status)); state.cb_parms.attr_allowed = !FSAL_IS_ERROR(attr_status); } else { /* No attributes requested. */ state.cb_parms.attr_allowed = false; } state.directory = directory; state.status = &cb_status; state.cb = cb; state.last_cookie = 0; state.cb_parms.opaque = opaque; state.cb_parms.in_result = true; state.cb_parms.name = NULL; state.cb_state = CB_ORIGINAL; state.cb_nfound = nbfound; state.attrmask = attrmask; fsal_status = directory->obj_ops->readdir(directory, &cookie, (void *)&state, populate_dirent, attrmask, eod_met); return fsal_status; } /** * * @brief Remove a name from a directory. * * @param[in] parent Handle for the parent directory to be * managed * @param[in] name Name to be removed * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @retval fsal_status_t */ fsal_status_t fsal_remove(struct fsal_obj_handle *parent, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { struct fsal_obj_handle *to_remove_obj = NULL; fsal_status_t status = { 0, 0 }; if (parent->type != DIRECTORY) { status = fsalstat(ERR_FSAL_NOTDIR, 0); goto out_no_obj; } /* Looks up for the entry to remove */ status = fsal_lookup(parent, name, &to_remove_obj, NULL); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "lookup %s failure %s", name, fsal_err_txt(status)); return status; } /* Do not remove a junction node or an export root. */ if (obj_is_junction(to_remove_obj)) { LogCrit(COMPONENT_FSAL, "Attempt to remove export %s", name); status = fsalstat(ERR_FSAL_NOTEMPTY, 0); goto out; } if (state_deleg_conflict(to_remove_obj, true)) { LogDebug(COMPONENT_FSAL, "Found an existing delegation for %s", name); status = fsalstat(ERR_FSAL_DELAY, 0); goto out; } LogFullDebug(COMPONENT_FSAL, "%s", name); /* Make sure the to_remove_obj is closed since unlink of an * open file results in 'silly rename' on certain platforms. */ status = fsal_close(to_remove_obj); if (FSAL_IS_ERROR(status)) { /* non-fatal error. log the warning and move on */ LogCrit(COMPONENT_FSAL, "Error closing %s before unlink: %s.", name, fsal_err_txt(status)); } #ifdef ENABLE_RFC_ACL status = fsal_remove_access(parent, to_remove_obj, (to_remove_obj->type == DIRECTORY)); if (FSAL_IS_ERROR(status)) goto out; #endif /* ENABLE_RFC_ACL */ status = parent->obj_ops->unlink(parent, to_remove_obj, name, parent_pre_attrs_out, parent_post_attrs_out); if (FSAL_IS_ERROR(status)) { LogFullDebug(COMPONENT_FSAL, "unlink %s failure %s", name, fsal_err_txt(status)); goto out; } out: to_remove_obj->obj_ops->put_ref(to_remove_obj); out_no_obj: LogFullDebug(COMPONENT_FSAL, "remove %s: status=%s", name, fsal_err_txt(status)); return status; } /** * @brief Renames a file * * @param[in] dir_src The source directory * @param[in] oldname The current name of the file * @param[in] dir_dest The destination directory * @param[in] newname The name to be assigned to the object * @param[in,out] olddir_pre_attrs_out Optional attributes for olddir dir * before the operation. Should be atomic. * @param[in,out] olddir_post_attrs_out Optional attributes for olddir dir * after the operation. Should be atomic. * @param[in,out] newdir_pre_attrs_out Optional attributes for newdir dir * before the operation. Should be atomic. * @param[in,out] newdir_post_attrs_out Optional attributes for newdir dir * after the operation. Should be atomic. * * @return FSAL status */ fsal_status_t fsal_rename(struct fsal_obj_handle *dir_src, const char *oldname, struct fsal_obj_handle *dir_dest, const char *newname, struct fsal_attrlist *olddir_pre_attrs_out, struct fsal_attrlist *olddir_post_attrs_out, struct fsal_attrlist *newdir_pre_attrs_out, struct fsal_attrlist *newdir_post_attrs_out) { fsal_status_t fsal_status = { 0, 0 }; struct fsal_obj_handle *lookup_src = NULL; if ((dir_src->type != DIRECTORY) || (dir_dest->type != DIRECTORY)) return fsalstat(ERR_FSAL_NOTDIR, 0); /* Check for . and .. on oldname and newname. */ if (oldname[0] == '\0' || newname[0] == '\0' || !strcmp(oldname, ".") || !strcmp(oldname, "..") || !strcmp(newname, ".") || !strcmp(newname, "..")) { return fsalstat(ERR_FSAL_INVAL, 0); } /* Check for object existence in source directory */ fsal_status = fsal_lookup(dir_src, oldname, &lookup_src, NULL); if (FSAL_IS_ERROR(fsal_status)) { LogDebug(COMPONENT_FSAL, "Rename (%p,%s)->(%p,%s) : source doesn't exist", dir_src, oldname, dir_dest, newname); goto out; } /* Do not rename a junction node or an export root. */ if (obj_is_junction(lookup_src)) { LogCrit(COMPONENT_FSAL, "Attempt to rename export %s", oldname); fsal_status = fsalstat(ERR_FSAL_NOTEMPTY, 0); goto out; } /* Don't allow rename of an object as parent of itself */ if (dir_dest == lookup_src) { fsal_status = fsalstat(ERR_FSAL_INVAL, 0); goto out; } /* * * added conflicts check for destination in MDCACHE layer */ if (state_deleg_conflict(lookup_src, true)) { LogDebug(COMPONENT_FSAL, "Found an existing delegation for %s", oldname); fsal_status = fsalstat(ERR_FSAL_DELAY, 0); goto out; } LogFullDebug(COMPONENT_FSAL, "about to call FSAL rename"); fsal_status = dir_src->obj_ops->rename( lookup_src, dir_src, oldname, dir_dest, newname, olddir_pre_attrs_out, olddir_post_attrs_out, newdir_pre_attrs_out, newdir_post_attrs_out); LogFullDebug(COMPONENT_FSAL, "returned from FSAL rename"); if (FSAL_IS_ERROR(fsal_status)) { LogFullDebug(COMPONENT_FSAL, "FSAL rename failed with %s", fsal_err_txt(fsal_status)); goto out; } out: if (lookup_src) { /* Note that even with a junction, this object is in the same * export since that would be the junction node, NOT the export * root node on the other side of the junction. */ lookup_src->obj_ops->put_ref(lookup_src); } return fsal_status; } /** * @brief Opens a file by name or by handle. * * This function accomplishes both a LOOKUP if necessary and an open. * * Returns with an LRU reference held on the entry. * * state can be NULL which indicates a stateless open (such as via the * NFS v3 CREATE operation). * * At least the mode attribute must be set if createmode is not FSAL_NO_CREATE. * Some FSALs may still have to pass a mode on a create call for exclusive, * and even with FSAL_NO_CREATE, and empty set of attributes MUST be passed. * * The caller is expected to invoke fsal_release_attrs to release any * resources held by the set attributes. The FSAL layer MAY have added an * inherited ACL. * * @param[in] in_obj Parent directory or obj * @param[in,out] state state_t to operate on * @param[in] openflags Details of how to open the file * @param[in] createmode Mode if creating * @param[in] name If name is not NULL, entry is the parent * directory * @param[in] attr Attributes to set on the file * @param[in] verifier Verifier to use with exclusive create * @param[out] obj New entry for the opened file * @param[in,out] attrs_out Optional attributes for the create * object. Should be atomic. * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @return FSAL status */ fsal_status_t fsal_open2(struct fsal_obj_handle *in_obj, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attr, fsal_verifier_t verifier, struct fsal_obj_handle **obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out) { fsal_status_t status = { 0, 0 }; bool caller_perm_check = false; char *reason; *obj = NULL; if (parent_pre_attrs_out != NULL) parent_pre_attrs_out->valid_mask = 0; if (parent_post_attrs_out != NULL) parent_post_attrs_out->valid_mask = 0; if (attr != NULL) { LogAttrlist(COMPONENT_FSAL, NIV_FULL_DEBUG, "attrs ", attr, false); status = fsal_check_create_owner(attr); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "Not opening file - %s", fsal_err_txt(status)); return status; } } /* Handle attribute size = 0 here, normalize to FSAL_O_TRUNC * instead of setting ATTR_SIZE. */ if (attr != NULL && FSAL_TEST_MASK(attr->valid_mask, ATTR_SIZE) && attr->filesize == 0) { LogFullDebug(COMPONENT_FSAL, "Truncate"); /* Handle truncate to zero on open */ openflags |= FSAL_O_TRUNC; /* Don't set the size if we later set the attributes */ FSAL_UNSET_MASK(attr->valid_mask, ATTR_SIZE); } if (createmode >= FSAL_EXCLUSIVE && verifier == NULL) return fsalstat(ERR_FSAL_INVAL, 0); if (name) return open2_by_name(in_obj, state, openflags, createmode, name, attr, verifier, obj, attrs_out, parent_pre_attrs_out, parent_post_attrs_out); /* No name, directories don't make sense */ if (in_obj->type == DIRECTORY) { if (createmode != FSAL_NO_CREATE) return fsalstat(ERR_FSAL_INVAL, 0); return fsalstat(ERR_FSAL_ISDIR, 0); } if (in_obj->type != REGULAR_FILE) return fsalstat(ERR_FSAL_BADTYPE, 0); /* Do a permission check on the file before opening. */ status = check_open_permission(in_obj, openflags, createmode >= FSAL_EXCLUSIVE, &reason); if (FSAL_IS_ERROR(status)) { if (status.major == ERR_FSAL_ACCESS && createmode >= FSAL_EXCLUSIVE) { /* Returning ACCESS err on exclusive open when the file * exists and we don't have permission to open it * doesn't make sense. This means that it is not an * exclusive replay and in this case we should return * EXIST and not ACCESS. */ status = fsalstat(ERR_FSAL_EXIST, 0); } LogDebug(COMPONENT_FSAL, "Not opening file %s%s", reason, fsal_err_txt(status)); return status; } /* Open THIS entry, so name must be NULL. The attr are passed in case * this is a create with size = 0. We pass the verifier because this * might be an exclusive recreate replay and we want the FSAL to * check the verifier. */ status = in_obj->obj_ops->open2(in_obj, state, openflags, createmode, NULL, attr, verifier, obj, attrs_out, &caller_perm_check, parent_pre_attrs_out, parent_post_attrs_out); if (!FSAL_IS_ERROR(status)) { /* Get a reference to the entry. */ *obj = in_obj; in_obj->obj_ops->get_ref(in_obj); } return status; } /** * @brief Re-Opens a file by handle. * * This MAY be used to open a file the first time if there is no need for * open by name or create semantics. * * @param[in] obj File to operate on * @param[in,out] state state_t to operate on * @param[in] openflags Details of how to open the file * @param[in] check_permission Indicate if permission should be checked * * @return FSAL status */ fsal_status_t fsal_reopen2(struct fsal_obj_handle *obj, struct state_t *state, fsal_openflags_t openflags, bool check_permission) { fsal_status_t status = { 0, 0 }; char *reason = "FSAL reopen failed - "; if (check_permission) { /* Do a permission check on the file before re-opening. */ status = check_open_permission(obj, openflags, false, &reason); if (FSAL_IS_ERROR(status)) goto out; } /* Re-open the entry in the FSAL. */ status = obj->obj_ops->reopen2(obj, state, openflags); out: if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "Not re-opening file %s%s", reason, fsal_err_txt(status)); } return status; } fsal_status_t fsal_statfs(struct fsal_obj_handle *obj, fsal_dynamicfsinfo_t *dynamicinfo) { fsal_status_t fsal_status; struct fsal_export *export; export = op_ctx->ctx_export->fsal_export; /* Get FSAL to get dynamic info */ fsal_status = export->exp_ops.get_fs_dynamic_info(export, obj, dynamicinfo); LogFullDebug(COMPONENT_FSAL, "dynamicinfo: {total_bytes = %" PRIu64 ", free_bytes = %" PRIu64 ", avail_bytes = %" PRIu64 ", total_files = %" PRIu64 ", free_files = %" PRIu64 ", avail_files = %" PRIu64 "}", dynamicinfo->total_bytes, dynamicinfo->free_bytes, dynamicinfo->avail_bytes, dynamicinfo->total_files, dynamicinfo->free_files, dynamicinfo->avail_files); return fsal_status; } /** * @brief Verify an exclusive create replay when the file is already open. * * This may not be necessary in real life, however, pynfs definitely has a * test case that walks this path. * * @param[in] obj File to verify * @param[in] verifier Verifier to use with exclusive create * * @return FSAL status */ fsal_status_t fsal_verify2(struct fsal_obj_handle *obj, fsal_verifier_t verifier) { if (!obj->obj_ops->check_verifier(obj, verifier)) { /* Verifier check failed. */ return fsalstat(ERR_FSAL_EXIST, 0); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Fetch optional attributes * * The request_mask should be set in attrs_out indicating which attributes * are desired. If ATTR_RDATTR_ERR is set, and the getattrs fails, * the error ERR_FSAL_NO_ERROR will be returned, however the attributes * valid_mask will be set to ATTR_RDATTR_ERR. Otherwise, if * ATTR_RDATTR_ERR is not set and the getattrs fails, the error returned * by getattrs will be returned. * * @param[in] obj_hdl Object to get attributes for. * @param[in,out] attrs_out Optional attributes for the object * * @return FSAL status. **/ fsal_status_t get_optional_attrs(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs_out) { fsal_status_t status; if (attrs_out == NULL) return fsalstat(ERR_FSAL_NO_ERROR, 0); status = obj_hdl->obj_ops->getattrs(obj_hdl, attrs_out); if (FSAL_IS_ERROR(status)) { if (attrs_out->request_mask & ATTR_RDATTR_ERR) { /* Indicate the failure of requesting attributes by * marking the ATTR_RDATTR_ERR in the mask. */ attrs_out->valid_mask = ATTR_RDATTR_ERR; status = fsalstat(ERR_FSAL_NO_ERROR, 0); } /* otherwise let the error stand. */ } return status; } static void fsal_iov_release(void *release_data) { gsh_free(release_data); } /** * @brief Read data from a file * * This function reads data from the given file. The FSAL must be able to * perform the read whether a state is presented or not. This function also * is expected to handle properly bypassing or not share reservations. This is * an (optionally) asynchronous call. When the I/O is complete, the done * callback is called with the results. * * An iovec MUST be included in the read_arg. If the caller is supplying the * read buffer (for example, RDMA), then the iovec will be passed as is. If * the caller does not need to supply the read buffer, then the iovec MUST * be length 1 and, and iov[0].iov_len must be the requested read size and * iov[0].iov_base MUST be NULL. If the FSAL will allocate its own buffer, * this special iovec will be passed down, otherwise a buffer will be allocated. * * @param[in] obj_hdl File on which to operate * @param[in] bypass If state doesn't indicate a share reservation, * bypass any deny read * @param[in,out] done_cb Callback to call when I/O is done * @param[in,out] read_arg Info about read, passed back in callback * @param[in,out] caller_arg Opaque arg from the caller for callback * * @return Nothing; results are in callback */ void fsal_read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg) { assert(read_arg->iov_count > 0); assert(read_arg->iov != NULL); if (read_arg->iov[0].iov_base != NULL) { /* Buffer's were supplied, use them */ goto call_read2; } /* If buffers aren't supplied, iov_count MUST be 1 */ assert(read_arg->iov_count == 1); /* Check if someone else want's to allocate the buffer */ read_arg->iov[0].iov_base = get_buffer_for_io_response( read_arg->iov[0].iov_len, read_arg->last_iov_buf_size); if (read_arg->iov[0].iov_base != NULL) { /* Someone wanted to allocate the buffer, use it. */ goto call_read2; } /* Check if FSAL will allocate the buffer */ if (!op_ctx->fsal_export->exp_ops.fs_supports( op_ctx->fsal_export, fso_allocate_own_read_buffer)) { /* FSAL will not allocate a buffer, so allocate one. */ read_arg->iov[0].iov_base = gsh_malloc(read_arg->iov[0].iov_len); /* Set up release function */ read_arg->iov_release = fsal_iov_release; read_arg->release_data = read_arg->iov[0].iov_base; } call_read2: return obj_hdl->obj_ops->read2(obj_hdl, bypass, done_cb, read_arg, caller_arg); } /** * @brief Callback to implement synchronous read and write * * @param[in] obj Object being acted on * @param[in] ret Return status of call * @param[in] args Args for read call * @param[in] caller_data Data for caller */ static void sync_cb(struct fsal_obj_handle *obj, fsal_status_t ret, void *args, void *caller_data) { struct async_process_data *data = caller_data; /* Fixup FSAL_SHARE_DENIED status */ if (ret.major == ERR_FSAL_SHARE_DENIED) ret = fsalstat(ERR_FSAL_LOCKED, 0); data->ret = ret; /* Let caller know we are done. */ PTHREAD_MUTEX_lock(data->fsa_mutex); data->done = true; pthread_cond_signal(data->fsa_cond); PTHREAD_MUTEX_unlock(data->fsa_mutex); } void fsal_read(struct fsal_obj_handle *obj_hdl, bool bypass, struct fsal_io_arg *arg, struct async_process_data *data) { again: fsal_read2(obj_hdl, bypass, sync_cb, arg, data); PTHREAD_MUTEX_lock(data->fsa_mutex); while (!data->done) PTHREAD_COND_wait(data->fsa_cond, data->fsa_mutex); PTHREAD_MUTEX_unlock(data->fsa_mutex); if (arg->fsal_resume) { data->done = false; goto again; } } void fsal_write(struct fsal_obj_handle *obj_hdl, bool bypass, struct fsal_io_arg *arg, struct async_process_data *data) { again: obj_hdl->obj_ops->write2(obj_hdl, bypass, sync_cb, arg, data); PTHREAD_MUTEX_lock(data->fsa_mutex); while (!data->done) PTHREAD_COND_wait(data->fsa_cond, data->fsa_mutex); PTHREAD_MUTEX_unlock(data->fsa_mutex); if (arg->fsal_resume) { data->done = false; goto again; } } #define XATTR_USER_PREFIX "user." #define XATTR_USER_PREFIX_LEN (sizeof(XATTR_USER_PREFIX) - 1) /** * @brief Convert a flat list of xattr names to xattrlist4 * * @param[in] buf Populated buffer returned from listxattr() * @param[in] listlen Length of "buf" * @param[in] maxbytes Max size of the returned lxr_names array * @param[in,out] lxa_cookie Cookie from client, and returned cookie * @param[out] lxr_eof Is this is the end of the xattrs? * @param[out] lxr_names pointer to xattrlist4 that should be populated * * Most listxattr() implementations hand back a buffer with a concatenated set * of NULL terminated names. This helper does the work of converting that into * an xattrlist4, and handles the gory details of vetting the cookie and size * limits. */ fsal_status_t fsal_listxattr_helper(const char *buf, size_t listlen, uint32_t maxbytes, nfs_cookie4 *lxa_cookie, bool_t *lxr_eof, xattrlist4 *lxr_names) { int i, count = 0; uint64_t cookie = 0; uint32_t bytes = 0; const char *name, *start; const char *end = buf + listlen; xattrkey4 *names = NULL; fsal_status_t status; /* Figure out how big an array we'll need, and vet the cookie */ name = buf; start = NULL; while (name < end) { size_t len; /* Do we have enough for "user.?" ? */ len = strnlen(name, end - name); if (len <= XATTR_USER_PREFIX_LEN) goto next_name1; /* Does it start with "user." ? */ if (strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) goto next_name1; /* * Valid "user." xattr. Bump the cookie value and compare * previous one to one passed in. */ if (cookie++ < *lxa_cookie) goto next_name1; /* Do we have room to encode this name? */ bytes += 4 + len - XATTR_USER_PREFIX_LEN; if (bytes > maxbytes) { /* Decrement cookie since we can't use this after all */ --cookie; break; } /* We have a usable entry! */ ++count; /* Save pointer to first usable entry */ if (!start) start = name; next_name1: name += (len + 1); } /* No entries found? */ if (count == 0) { /* We couldn't encode the first entry */ if (bytes > maxbytes) return fsalstat(ERR_FSAL_TOOSMALL, 0); /* Bogus cookie from client? */ if (cookie < *lxa_cookie) return fsalstat(ERR_FSAL_BADCOOKIE, 0); /* Otherwise, there just weren't any */ goto out; } names = gsh_calloc(count, sizeof(*names)); assert(start); name = start; i = 0; while (name < end && i < count) { size_t len; len = strnlen(name, end - name); /* Make sure it's the min length */ if (len < XATTR_USER_PREFIX_LEN + 1) goto next_name2; if (strncmp(name, XATTR_USER_PREFIX, XATTR_USER_PREFIX_LEN)) goto next_name2; /* advance past "user." prefix */ name += XATTR_USER_PREFIX_LEN; len -= XATTR_USER_PREFIX_LEN; names[i].utf8string_val = gsh_memdup(name, len); names[i].utf8string_len = len; ++i; next_name2: name += (len + 1); } /* Did we get everything? */ if (i != count) { LogWarn(COMPONENT_FSAL, "LISTXATTRS encoding error!"); status = fsalstat(ERR_FSAL_SERVERFAULT, 0); goto out_error; } out: *lxa_cookie = cookie; *lxr_eof = (bytes <= maxbytes); lxr_names->xl4_count = count; lxr_names->xl4_entries = names; return fsalstat(ERR_FSAL_NO_ERROR, 0); out_error: for (i = 0; i < count; ++i) gsh_free(names[i].utf8string_val); gsh_free(names); return status; } fsal_status_t fsal_close2(struct fsal_obj_handle *obj) { fsal_status_t status = { ERR_FSAL_NO_ERROR, 0 }; if (close_fast) { status = fsal_close(obj); if (FSAL_IS_ERROR(status)) { LogDebug(COMPONENT_FSAL, "%s failed with %s", __func__, fsal_err_txt(status)); } } return status; } /** @} */ nfs-ganesha-6.5/src/FSAL/fsal_manager.c000066400000000000000000000425541473756622300177060ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @addtogroup FSAL * @{ */ /** * @file fsal_manager.c * @author Jim Lieb * @brief FSAL module manager */ #include "config.h" #include #include #include #include #include #include #include #include #include #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "config_parsing.h" #include "pnfs_utils.h" #include "fsal_private.h" #include "FSAL/fsal_localfs.h" /** * @brief List of loaded fsal modules * * Static to be private to the functions in this module * fsal_lock is taken whenever the list is walked. */ pthread_mutex_t fsal_lock; GLIST_HEAD(fsal_list); void initialize_fsal_lock(void) { PTHREAD_MUTEX_init(&fsal_lock, NULL); #if GSH_CAN_HOST_LOCAL_FS PTHREAD_RWLOCK_init(&fs_lock, NULL); #endif } void destroy_fsal_lock(void) { PTHREAD_MUTEX_destroy(&fsal_lock); #if GSH_CAN_HOST_LOCAL_FS PTHREAD_RWLOCK_destroy(&fs_lock); #endif } /** * @{ * * Variables for passing status/errors between shared object * and this module. They must be accessed under lock. */ static char *dl_error; static int so_error; static struct fsal_module *new_fsal; /** * @} */ /** * @brief FSAL load state */ static enum load_state { init, /*< In server start state. .init sections can run */ idle, /*< Switch from init->idle early in main() */ loading, /*< In dlopen(). set by load_fsal() just prior */ registered, /*< signal by registration that all is well */ error /*< signal by registration that all is not well */ } load_state = init; /** * @brief Start a static FSAL * * Start a FSAL that's statically linked in. * * @param[in] name FSAL name * @param[in] init Initialization function for FSAL */ static void load_fsal_static(const char *name, void (*init)(void)) { char *dl_path = gsh_concat("Builtin-", name); struct fsal_module *fsal; PTHREAD_MUTEX_lock(&fsal_lock); if (load_state != idle) LogFatal(COMPONENT_INIT, "Couldn't Register FSAL_%s", name); if (dl_error) { gsh_free(dl_error); dl_error = NULL; } load_state = loading; PTHREAD_MUTEX_unlock(&fsal_lock); /* now it is the module's turn to register itself */ init(); PTHREAD_MUTEX_lock(&fsal_lock); if (load_state != registered) LogFatal(COMPONENT_INIT, "Couldn't Register FSAL_%s", name); /* we now finish things up, doing things the module can't see */ fsal = new_fsal; /* recover handle from .ctor and poison again */ new_fsal = NULL; fsal->path = dl_path; fsal->dl_handle = NULL; so_error = 0; load_state = idle; PTHREAD_MUTEX_unlock(&fsal_lock); } struct dummy_fsal { void *dummy; }; struct dummy_fsal dummy, dummy2; static void *fsal_dummy_init(void *link_mem, void *self_struct) { if (link_mem == NULL) { return self_struct; } else if (self_struct == NULL) { return &dummy2; } else { /* free resources case */ return NULL; } } static struct config_item fsal_dummy_params[] = { CONFIG_EOL }; struct config_block fsal_dummy_block = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal", .blk_desc.name = "FSAL_ITEM", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_RELAX, .blk_desc.u.blk.init = fsal_dummy_init, .blk_desc.u.blk.params = fsal_dummy_params, .blk_desc.u.blk.commit = noop_conf_commit }; static int fsal_name_adder(const char *token, enum term_type type_hint, struct config_item *item, void *param_addr, void *cnode, struct config_error_type *err_type) { int rc; config_file_t myconfig = get_parse_root(cnode); LogMidDebug(COMPONENT_EXPORT, "Adding FSAL %s", token); fsal_dummy_block.blk_desc.name = (char *)token; rc = load_config_from_parse(myconfig, &fsal_dummy_block, &dummy, false, err_type); return rc < 0 ? rc : 0; } static struct config_item fsal_params[] = { CONF_ITEM_PROC_MULT("Name", noop_conf_init, fsal_name_adder, dummy_fsal, dummy), CONFIG_EOL }; struct config_block fsal_block = { .dbus_interface_name = "org.ganesha.nfsd.config.fsal", .blk_desc.name = "FSAL_LIST", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = fsal_params, .blk_desc.u.blk.commit = noop_conf_commit }; /** * @brief Start_fsals * * Called early server initialization. Set load_state to idle * at this point as a check on dynamic loading not starting too early. */ int start_fsals(config_file_t in_config, struct config_error_type *err_type) { int rc; initialize_fsal_lock(); init_ctx_refstr(); rc = load_config_from_parse(in_config, &fsal_block, &dummy, false, err_type); if (rc < 0) { LogCrit(COMPONENT_CONFIG, "FSAL block error"); return -1; } /* .init was a long time ago... */ load_state = idle; /* Load FSAL_MDCACHE */ load_fsal_static("MDCACHE", mdcache_fsal_init); /* Load FSAL_PSEUDO */ load_fsal_static("PSEUDO", pseudo_fsal_init); return 0; } /** * Enforced filename for FSAL library objects. */ static const char *pathfmt = "%s/libfsal%s.so"; /** * @brief Load the fsal's shared object. * * The dlopen() will trigger a .init constructor which will do the * actual registration. after a successful load, the returned handle * needs to be "put" back after any other initialization is done. * * @param[in] name Name of the FSAL to load * @param[out] fsal_hdl_p Newly allocated FSAL handle * * @retval 0 Success, when finished, put_fsal_handle() to free * @retval EBUSY the loader is busy (should not happen) * @retval EEXIST the module is already loaded * @retval ENOLCK register_fsal without load_fsal holding the lock. * @retval EINVAL wrong loading state for registration * @retval ENOMEM out of memory * @retval ENOENT could not find "module_init" function * @retval EFAULT module_init has a bad address * @retval other general dlopen errors are possible, all of them bad */ int load_fsal(const char *name, struct fsal_module **fsal_hdl_p) { void *dl = NULL; int retval = EBUSY; /* already loaded */ char *dl_path; struct fsal_module *fsal; char *bp; struct stat statbuf; size_t size = strlen(nfs_param.core_param.ganesha_modules_loc) + strlen(name) + strlen(pathfmt) + 1; char *path = alloca(size); (void)snprintf(path, size, pathfmt, nfs_param.core_param.ganesha_modules_loc, name); bp = rindex(path, '/'); bp++; /* now it is the basename, lcase it */ while (*bp != '\0') { if (isupper(*bp)) *bp = tolower(*bp); bp++; } dl_path = gsh_strdup(path); PTHREAD_MUTEX_lock(&fsal_lock); /* check filepath */ if (stat(path, &statbuf) < 0) { retval = errno; LogCrit(COMPONENT_INIT, "stat returned %s (%d) while loading FSAL path %s", strerror(retval), retval, path); goto errout; } if (load_state != idle) goto errout; if (dl_error) { gsh_free(dl_error); dl_error = NULL; } load_state = loading; PTHREAD_MUTEX_unlock(&fsal_lock); LogDebug(COMPONENT_INIT, "Loading FSAL %s with %s", name, path); #if defined(LINUX) && !defined(SANITIZE_ADDRESS) dl = dlopen(path, RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND); #elif defined(BSDBASED) || defined(SANITIZE_ADDRESS) dl = dlopen(path, RTLD_NOW | RTLD_LOCAL); #endif PTHREAD_MUTEX_lock(&fsal_lock); if (dl == NULL) { dl_error = dlerror(); LogFatal( COMPONENT_INIT, "Could not dlopen module: %s Error: %s. You might want to install the nfs-ganesha-%s package", path, dl_error, name); } dlerror(); /* clear it */ /* now it is the module's turn to register itself */ if (load_state == loading) { /* constructor didn't fire */ void (*module_init)(void); char *sym_error; module_init = dlsym(dl, "fsal_init"); sym_error = (char *)dlerror(); if (sym_error != NULL) { dl_error = gsh_strdup(sym_error); so_error = ENOENT; LogCrit(COMPONENT_INIT, "Could not execute symbol fsal_init from module:%s Error:%s", path, dl_error); goto dlerr; } if ((void *)module_init == NULL) { so_error = EFAULT; LogCrit(COMPONENT_INIT, "Could not execute symbol fsal_init from module:%s Error:%s", path, dl_error); goto dlerr; } PTHREAD_MUTEX_unlock(&fsal_lock); (*module_init)(); /* try registering by hand this time */ PTHREAD_MUTEX_lock(&fsal_lock); } if (load_state == error) { /* we are in registration hell */ retval = so_error; /* this is the registration error */ LogCrit(COMPONENT_INIT, "Could not execute symbol fsal_init from module:%s Error:%s", path, dl_error); goto dlerr; } if (load_state != registered) { retval = EPERM; LogCrit(COMPONENT_INIT, "Could not execute symbol fsal_init from module:%s Error:%s", path, dl_error); goto dlerr; } /* we now finish things up, doing things the module can't see */ fsal = new_fsal; /* recover handle from .ctor and poison again */ new_fsal = NULL; /* take initial ref so we can pass it back... */ fsal_get(fsal); LogFullDebug(COMPONENT_FSAL, "FSAL %s fsal_refcount %" PRIu32, name, atomic_fetch_int32_t(&fsal->refcount)); fsal->path = dl_path; fsal->dl_handle = dl; so_error = 0; *fsal_hdl_p = fsal; load_state = idle; PTHREAD_MUTEX_unlock(&fsal_lock); return 0; dlerr: dlclose(dl); errout: load_state = idle; PTHREAD_MUTEX_unlock(&fsal_lock); LogMajor(COMPONENT_INIT, "Failed to load module (%s) because: %s", path, strerror(retval)); gsh_free(dl_path); return retval; } /** * @brief Look up an FSAL * * Acquire a handle to the named FSAL and take a reference to it. This * must be done before using any methods. Once done, release it with * @c put_fsal. * * @param[in] name The name to look up * * @return Module pointer or NULL if not found. */ struct fsal_module *lookup_fsal(const char *name) { struct fsal_module *fsal; struct glist_head *entry; PTHREAD_MUTEX_lock(&fsal_lock); glist_for_each(entry, &fsal_list) { fsal = glist_entry(entry, struct fsal_module, fsals); if (strcasecmp(name, fsal->name) == 0) { fsal_get(fsal); PTHREAD_MUTEX_unlock(&fsal_lock); op_ctx->fsal_module = fsal; LogFullDebug(COMPONENT_FSAL, "FSAL %s fsal_refcount %" PRIu32, name, atomic_fetch_int32_t(&fsal->refcount)); return fsal; } } PTHREAD_MUTEX_unlock(&fsal_lock); return NULL; } /* functions only called by modules at ctor/dtor time */ /** * @brief Register the fsal in the system * * This can be called from three places: * * + the server program's .init section if the fsal was statically linked * + the shared object's .init section when load_fsal() dynamically loads it. * + from the shared object's 'fsal_init' function if dlopen does not support * .init/.fini sections. * * Any other case is an error. * Change load_state only for dynamically loaded modules. * * @param[in] fsal_hdl FSAL module handle * @param[in] name FSAL name * @param[in] major_version Major version * @param[in] minor_version Minor version * * @return 0 on success, otherwise POSIX errors. */ /** @todo implement api versioning and pass the major,minor here */ int register_fsal(struct fsal_module *fsal_hdl, const char *name, uint32_t major_version, uint32_t minor_version, uint8_t fsal_id) { PTHREAD_MUTEX_lock(&fsal_lock); if ((major_version != FSAL_MAJOR_VERSION) || (minor_version > FSAL_MINOR_VERSION)) { so_error = EINVAL; LogCrit(COMPONENT_INIT, "FSAL \"%s\" failed to register because of version mismatch core = %d.%d, fsal = %d.%d", name, FSAL_MAJOR_VERSION, FSAL_MINOR_VERSION, major_version, minor_version); load_state = error; goto errout; } so_error = 0; if (!(load_state == loading || load_state == init)) { so_error = EACCES; goto errout; } new_fsal = fsal_hdl; if (name != NULL) new_fsal->name = gsh_strdup(name); /* init ops vector to system wide defaults * from FSAL/default_methods.c */ memcpy(&fsal_hdl->m_ops, &def_fsal_ops, sizeof(struct fsal_ops)); PTHREAD_RWLOCK_init(&fsal_hdl->fsm_lock, NULL); glist_init(&fsal_hdl->servers); glist_init(&fsal_hdl->handles); glist_init(&fsal_hdl->exports); fsal_hdl->is_configured = false; glist_add_tail(&fsal_list, &fsal_hdl->fsals); if (load_state == loading) load_state = registered; if (fsal_id != FSAL_ID_NO_PNFS && fsal_id < FSAL_ID_COUNT) pnfs_fsal[fsal_id] = fsal_hdl; PTHREAD_MUTEX_unlock(&fsal_lock); return 0; errout: gsh_free(fsal_hdl->path); gsh_free(fsal_hdl->name); load_state = error; PTHREAD_MUTEX_unlock(&fsal_lock); LogCrit(COMPONENT_INIT, "FSAL \"%s\" failed to register because: %s", name, strerror(so_error)); return so_error; } /** * @brief Unregisters an FSAL * * Verify that the fsal is not busy and release all its resources * owned at this level. RW Lock is already freed. Called from the * module's MODULE_FINI * * @param[in] fsal_hdl FSAL handle * * @retval 0 on success. * @retval EBUSY if FSAL is in use. */ int unregister_fsal(struct fsal_module *fsal_hdl) { int32_t refcount = atomic_fetch_int32_t(&fsal_hdl->refcount); if (refcount != 0) { /* this would be very bad */ LogCrit(COMPONENT_FSAL, "Unregister FSAL %s with non-zero fsal_refcount=%d", fsal_hdl->name, refcount); return EBUSY; } gsh_free(fsal_hdl->path); gsh_free(fsal_hdl->name); return 0; } /** * @brief Init and commit for FSAL sub-block */ /** * @brief Initialize space for an FSAL sub-block. * * We allocate space to hold the name parameter so that * is available in the commit phase. */ void *fsal_init(void *link_mem, void *self_struct) { struct fsal_args *fp; assert(link_mem != NULL || self_struct != NULL); if (link_mem == NULL) { return self_struct; /* NOP */ } else if (self_struct == NULL) { void *args = gsh_calloc(1, sizeof(struct fsal_args)); LogFullDebug(COMPONENT_CONFIG, "Allocating args %p/%p", link_mem, args); return args; } else { fp = self_struct; gsh_free(fp->name); gsh_free(fp); return NULL; } } /** * @brief Load and initialize FSAL module * * Use the name parameter to lookup the fsal. If the fsal is not * loaded (yet), load it and call its init. This will trigger the * processing of a top level block of the same name as the fsal, i.e. * the VFS fsal will look for a VFS block and process it (if found). * * @param[in] node parse node of FSAL block * @param[in] name name of the FSAL to load and initialize (if * not already loaded) * @param[out] fsal_hdl Pointer to FSAL module or NULL if not found * @param[out] err_type pointer to error type * * @retval 0 on success, error count on errors */ int fsal_load_init(void *node, const char *name, struct fsal_module **fsal_hdl, struct config_error_type *err_type) { fsal_status_t status; if (name == NULL || strlen(name) == 0) { config_proc_error(node, err_type, "Name of FSAL is missing"); err_type->missing = true; return 1; } *fsal_hdl = lookup_fsal(name); if (*fsal_hdl == NULL) { int retval; config_file_t myconfig; retval = load_fsal(name, fsal_hdl); if (retval != 0) { config_proc_error( node, err_type, "Failed to load FSAL (%s) because: %s", name, strerror(retval)); err_type->fsal = true; return 1; } op_ctx->fsal_module = *fsal_hdl; myconfig = get_parse_root(node); status = (*fsal_hdl)->m_ops.init_config(*fsal_hdl, myconfig, err_type); (*fsal_hdl)->is_configured = true; if (FSAL_IS_ERROR(status)) { config_proc_error(node, err_type, "Failed to initialize FSAL (%s)", name); fsal_put(*fsal_hdl); err_type->fsal = true; LogFullDebug( COMPONENT_FSAL, "FSAL %s fsal_refcount %" PRIu32, name, atomic_fetch_int32_t(&(*fsal_hdl)->refcount)); return 1; } } else { config_file_t myconfig; myconfig = get_parse_root(node); if ((*fsal_hdl)->is_configured) { status = (*fsal_hdl)->m_ops.update_config( *fsal_hdl, myconfig, err_type); } else { /* Static fsals might not be configured */ (*fsal_hdl)->is_configured = true; status = (*fsal_hdl)->m_ops.init_config( *fsal_hdl, myconfig, err_type); } if (FSAL_IS_ERROR(status)) { config_proc_error(node, err_type, "Failed to update FSAL (%s)", name); return 0; } } return 0; } /** * @brief Load and initialize sub-FSAL module * * @retval 0 on success, error count on errors */ int subfsal_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { struct fsal_module *fsal_next; struct subfsal_args *subfsal = (struct subfsal_args *)self_struct; int errcnt = fsal_load_init(node, subfsal->name, &fsal_next, err_type); if (errcnt == 0) subfsal->fsal_node = node; return errcnt; } /** @} */ nfs-ganesha-6.5/src/FSAL/fsal_private.h000066400000000000000000000032231473756622300177410ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ #ifndef FSAL_PRIVATE_H #define FSAL_PRIVATE_H /* Define some externals for FSAL */ extern struct fsal_ops def_fsal_ops; extern struct export_ops def_export_ops; /* freebsd gcc workaround */ extern struct fsal_obj_ops def_handle_ops; extern struct fsal_pnfs_ds_ops def_pnfs_ds_ops; /* Global lock for fsal list. * kept in fsal_manager.c * Special checkpatch case here. This is private between the two files. */ extern pthread_mutex_t fsal_lock; void initialize_fsal_lock(void); void destroy_fsal_lock(void); extern struct glist_head fsal_list; /* Definitions for static FSALs */ void pseudo_fsal_init(void); void mdcache_fsal_init(void); #endif /* FSAL_PRIVATE_H */ nfs-ganesha-6.5/src/FSAL/localfs.c000066400000000000000000001456141473756622300167130ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #include "config.h" #include "FSAL/fsal_localfs.h" #ifdef LINUX #include /* for major(3), minor(3) */ #endif #include #if __FreeBSD__ #include #else #include #endif #ifdef HAVE_MNTENT_H #include #endif #include "gsh_config.h" #ifdef USE_BLKID #include #include #endif #include "FSAL/fsal_commonlib.h" #include "fsal_convert.h" #ifdef USE_DBUS #include "gsh_dbus.h" #endif #include "server_stats_private.h" #ifdef USE_BTRFSUTIL #include "btrfsutil.h" #endif #ifdef USE_BLKID static struct blkid_struct_cache *cache; #endif int open_dir_by_path_walk(int first_fd, const char *path, struct stat *stat) { char *name, *rest, *p; int fd, len, rc, err; /* Get length of the path */ len = strlen(path); /* Strip terminating '/' by shrinking length */ while (path[len - 1] == '/' && len > 1) len--; /* Allocate space for duplicate */ name = alloca(len + 1); /* Copy the string */ memcpy(name, path, len); /* Terminate it */ name[len] = '\0'; /* Determine if this is a relative path off some directory * or an absolute path. If absolute path, open root dir. */ if (first_fd == -1) { if (name[0] != '/') { LogInfo(COMPONENT_FSAL, "Absolute path %s must start with '/'", path); return -EINVAL; } rest = name + 1; fd = open("/", O_RDONLY | O_NOFOLLOW); } else { rest = name; fd = dup(first_fd); } if (fd == -1) { err = errno; LogCrit(COMPONENT_FSAL, "Failed initial directory open for path %s with %s", path, strerror(err)); return -err; } while (rest[0] != '\0') { /* Find the end of this path element */ p = index(rest, '/'); /* NUL terminate element (if not at end of string */ if (p != NULL) *p = '\0'; /* Skip extra '/' */ if (rest[0] == '\0') { rest++; continue; } /* Disallow .. elements... */ if (strcmp(rest, "..") == 0) { close(fd); LogInfo(COMPONENT_FSAL, "Failed due to '..' element in path %s", path); return -EACCES; } /* Open the next directory in the path */ rc = openat(fd, rest, O_RDONLY | O_NOFOLLOW); err = errno; close(fd); if (rc == -1) { LogDebug(COMPONENT_FSAL, "openat(%s) in path %s failed with %s", rest, path, strerror(err)); return -err; } fd = rc; /* Done, break out */ if (p == NULL) break; /* Skip the '/' */ rest = p + 1; } rc = fstat(fd, stat); err = errno; if (rc == -1) { close(fd); LogDebug(COMPONENT_FSAL, "fstat %s failed with %s", path, strerror(err)); return -err; } if (!S_ISDIR(stat->st_mode)) { close(fd); LogInfo(COMPONENT_FSAL, "Path %s is not a directory", path); return -ENOTDIR; } return fd; } pthread_rwlock_t fs_lock; static struct glist_head posix_file_systems = { &posix_file_systems, &posix_file_systems }; static bool fs_initialized; static struct avltree avl_fsid; static struct avltree avl_dev; static inline int fsal_fs_cmpf_fsid(const struct avltree_node *lhs, const struct avltree_node *rhs) { struct fsal_filesystem *lk, *rk; lk = avltree_container_of(lhs, struct fsal_filesystem, avl_fsid); rk = avltree_container_of(rhs, struct fsal_filesystem, avl_fsid); return fsal_fs_compare_fsid(lk->fsid_type, &lk->fsid, rk->fsid_type, &rk->fsid); } static inline struct fsal_filesystem * avltree_inline_fsid_lookup(const struct avltree_node *key) { struct avltree_node *node = avltree_inline_lookup(key, &avl_fsid, fsal_fs_cmpf_fsid); if (node != NULL) return avltree_container_of(node, struct fsal_filesystem, avl_fsid); else return NULL; } static inline int fsal_fs_cmpf_dev(const struct avltree_node *lhs, const struct avltree_node *rhs) { struct fsal_filesystem *lk, *rk; lk = avltree_container_of(lhs, struct fsal_filesystem, avl_dev); rk = avltree_container_of(rhs, struct fsal_filesystem, avl_dev); if (lk->dev.major < rk->dev.major) return -1; if (lk->dev.major > rk->dev.major) return 1; if (lk->dev.minor < rk->dev.minor) return -1; if (lk->dev.minor > rk->dev.minor) return 1; return 0; } static inline struct fsal_filesystem * avltree_inline_dev_lookup(const struct avltree_node *key) { struct avltree_node *node = avltree_inline_lookup(key, &avl_dev, fsal_fs_cmpf_dev); if (node != NULL) return avltree_container_of(node, struct fsal_filesystem, avl_dev); else return NULL; } static void remove_fs(struct fsal_filesystem *fs) { if (fs->in_fsid_avl) avltree_remove(&fs->avl_fsid, &avl_fsid); if (fs->in_dev_avl) avltree_remove(&fs->avl_dev, &avl_dev); glist_del(&fs->siblings); glist_del(&fs->filesystems); } static void free_fs(struct fsal_filesystem *fs) { gsh_free(fs->path); gsh_free(fs->device); gsh_free(fs->type); gsh_free(fs); } int re_index_fs_fsid(struct fsal_filesystem *fs, enum fsid_type fsid_type, struct fsal_fsid__ *fsid) { struct avltree_node *node; struct fsal_fsid__ old_fsid = fs->fsid; enum fsid_type old_fsid_type = fs->fsid_type; LogDebug(COMPONENT_FSAL, "Reindex %s from 0x%016" PRIx64 ".0x%016" PRIx64 " to 0x%016" PRIx64 ".0x%016" PRIx64, fs->path, fs->fsid.major, fs->fsid.minor, fsid->major, fsid->minor); /* It is not valid to use this routine to * remove fs from index. */ if (fsid_type == FSID_NO_TYPE) return -EINVAL; if (fs->in_fsid_avl) avltree_remove(&fs->avl_fsid, &avl_fsid); fs->fsid.major = fsid->major; fs->fsid.minor = fsid->minor; fs->fsid_type = fsid_type; node = avltree_insert(&fs->avl_fsid, &avl_fsid); if (node != NULL) { /* This is a duplicate file system. */ fs->fsid = old_fsid; fs->fsid_type = old_fsid_type; if (fs->in_fsid_avl) { /* Put it back where it was */ node = avltree_insert(&fs->avl_fsid, &avl_fsid); if (node != NULL) { LogFatal(COMPONENT_FSAL, "Could not re-insert filesystem %s", fs->path); } } return -EEXIST; } fs->in_fsid_avl = true; return 0; } int re_index_fs_dev(struct fsal_filesystem *fs, struct fsal_dev__ *dev) { struct avltree_node *node; struct fsal_dev__ old_dev = fs->dev; /* It is not valid to use this routine to * remove fs from index. */ if (dev == NULL) return -EINVAL; if (fs->in_dev_avl) avltree_remove(&fs->avl_dev, &avl_dev); fs->dev = *dev; node = avltree_insert(&fs->avl_dev, &avl_dev); if (node != NULL) { /* This is a duplicate file system. */ fs->dev = old_dev; if (fs->in_dev_avl) { /* Put it back where it was */ node = avltree_insert(&fs->avl_dev, &avl_dev); if (node != NULL) { LogFatal(COMPONENT_FSAL, "Could not re-insert filesystem %s", fs->path); } } return -EEXIST; } fs->in_dev_avl = true; return 0; } #define MASK_32 ((uint64_t)UINT32_MAX) int change_fsid_type(struct fsal_filesystem *fs, enum fsid_type fsid_type) { struct fsal_fsid__ fsid = { 0 }; bool valid = false; if (fs->fsid_type == fsid_type) return 0; switch (fsid_type) { case FSID_ONE_UINT64: if (fs->fsid_type == FSID_TWO_UINT64) { /* Use the same compression we use for NFS v3 fsid */ fsid.major = squash_fsid(&fs->fsid); valid = true; } else if (fs->fsid_type == FSID_TWO_UINT32) { /* Put major in the high order 32 bits and minor * in the low order 32 bits. */ fsid.major = fs->fsid.major << 32 | fs->fsid.minor; valid = true; } fsid.minor = 0; break; case FSID_MAJOR_64: /* Nothing to do, will ignore fsid.minor in index */ valid = true; fsid.major = fs->fsid.major; fsid.minor = fs->fsid.minor; break; case FSID_TWO_UINT64: if (fs->fsid_type == FSID_MAJOR_64) { /* Must re-index since minor was not indexed * previously. */ fsid.major = fs->fsid.major; fsid.minor = fs->fsid.minor; valid = true; } else { /* Nothing to do, FSID_TWO_UINT32 will just have high * order zero bits while FSID_ONE_UINT64 will have * minor = 0, without changing the actual value. */ fs->fsid_type = fsid_type; return 0; } break; case FSID_DEVICE: fsid.major = fs->dev.major; fsid.minor = fs->dev.minor; valid = true; /* fallthrough */ case FSID_TWO_UINT32: if (fs->fsid_type == FSID_TWO_UINT64) { /* Shrink each 64 bit quantity to 32 bits by xoring the * two halves. */ fsid.major = (fs->fsid.major & MASK_32) ^ (fs->fsid.major >> 32); fsid.minor = (fs->fsid.minor & MASK_32) ^ (fs->fsid.minor >> 32); valid = true; } else if (fs->fsid_type == FSID_ONE_UINT64) { /* Split 64 bit that is in major into two 32 bit using * the high order 32 bits as major. */ fsid.major = fs->fsid.major >> 32; fsid.minor = fs->fsid.major & MASK_32; valid = true; } break; case FSID_NO_TYPE: /* It is not valid to use this routine to remove an fs */ break; } if (!valid) return -EINVAL; return re_index_fs_fsid(fs, fsid_type, &fsid); } static bool posix_get_fsid(struct fsal_filesystem *fs, struct stat *mnt_stat) { struct statfs stat_fs; #ifdef USE_BLKID char *dev_name; char *uuid_str; #endif LogFullDebug(COMPONENT_FSAL, "statfs of %s pathlen %d", fs->path, fs->pathlen); if (statfs(fs->path, &stat_fs) != 0) LogCrit(COMPONENT_FSAL, "stat_fs of %s resulted in error %s(%d)", fs->path, strerror(errno), errno); #if __FreeBSD__ fs->namelen = stat_fs.f_namemax; #else fs->namelen = stat_fs.f_namelen; #endif fs->dev = posix2fsal_devt(mnt_stat->st_dev); if (nfs_param.core_param.fsid_device) { fs->fsid_type = FSID_DEVICE; fs->fsid.major = fs->dev.major; fs->fsid.minor = fs->dev.minor; return true; } #ifdef USE_BLKID if (cache == NULL) goto out; dev_name = blkid_devno_to_devname(mnt_stat->st_dev); if (dev_name == NULL) { LogDebug(COMPONENT_FSAL, "blkid_devno_to_devname of %s failed for dev %d.%d", fs->path, major(mnt_stat->st_dev), minor(mnt_stat->st_dev)); goto out; } if (blkid_get_dev(cache, dev_name, BLKID_DEV_NORMAL) == NULL) { LogInfo(COMPONENT_FSAL, "blkid_get_dev of %s failed for devname %s", fs->path, dev_name); free(dev_name); goto out; } uuid_str = blkid_get_tag_value(cache, "UUID", dev_name); free(dev_name); if (uuid_str == NULL) { LogInfo(COMPONENT_FSAL, "blkid_get_tag_value of %s failed", fs->path); goto out; } if (uuid_parse(uuid_str, (char *)&fs->fsid) == -1) { LogInfo(COMPONENT_FSAL, "uuid_parse of %s failed for uuid %s", fs->path, uuid_str); free(uuid_str); goto out; } free(uuid_str); fs->fsid_type = FSID_TWO_UINT64; return true; out: #endif fs->fsid_type = FSID_TWO_UINT32; #if __FreeBSD__ fs->fsid.major = (unsigned int)stat_fs.f_fsid.val[0]; fs->fsid.minor = (unsigned int)stat_fs.f_fsid.val[1]; #else fs->fsid.major = (unsigned int)stat_fs.f_fsid.__val[0]; fs->fsid.minor = (unsigned int)stat_fs.f_fsid.__val[1]; #endif if ((fs->fsid.major == 0) && (fs->fsid.minor == 0)) { fs->fsid.major = fs->dev.major; fs->fsid.minor = fs->dev.minor; } return true; } static void posix_create_file_system(struct mntent *mnt, struct stat *mnt_stat); static void posix_create_fs_btrfs_subvols(struct fsal_filesystem *fs) { #ifdef USE_BTRFSUTIL struct mntent mnt; struct stat st; struct btrfs_util_subvolume_iterator *iter; enum btrfs_util_error err = BTRFS_UTIL_OK; uint64_t id, idsv; size_t lenp = strlen(fs->path), lens; char *path; LogFullDebug(COMPONENT_FSAL, "Attempting to add subvols for btrfs filesystem %s", fs->path); /* Setup common fields for fake mntent, many are NULL or 0. * type is set to fake btrfs_sv otherwise we would recurse into here. * fsname (device) is set to the same as the parent (fs->device only * gets used for quota manipulation. */ memset(&mnt, 0, sizeof(mnt)); mnt.mnt_fsname = fs->device; mnt.mnt_type = "btrfs_sv"; err = btrfs_util_subvolume_id(fs->path, &id); if (err != 0) { LogCrit(COMPONENT_FSAL, "btrfs_util_subvolume_id err %s", btrfs_util_strerror(err)); return; } err = btrfs_util_create_subvolume_iterator(fs->path, id, 0, &iter); if (err != 0) { LogCrit(COMPONENT_FSAL, "btrfs_util_create_subvolume_iterator err %s", btrfs_util_strerror(err)); return; } err = btrfs_util_sync_fd(btrfs_util_subvolume_iterator_fd(iter)); if (err != 0) { LogCrit(COMPONENT_FSAL, "btrfs_util_sync_fd err %s", btrfs_util_strerror(err)); goto out; } while (err == BTRFS_UTIL_OK) { err = btrfs_util_subvolume_iterator_next(iter, &path, &idsv); if (err == BTRFS_UTIL_OK) { /* Construct fully qualified path */ lens = strlen(path); mnt.mnt_dir = gsh_malloc(lenp + lens + 2); memcpy(mnt.mnt_dir, fs->path, lenp); mnt.mnt_dir[lenp] = '/'; memcpy(mnt.mnt_dir + lenp + 1, path, lens + 1); if (stat(mnt.mnt_dir, &st) >= 0) { LogInfo(COMPONENT_FSAL, "Adding btrfs subvol %s", mnt.mnt_dir); posix_create_file_system(&mnt, &st); } else { int err = errno; LogCrit(COMPONENT_FSAL, "Could not stat btrfs subvol %s err = %s", mnt.mnt_dir, strerror(err)); } /* Free the path from the iterator and the fully * qualified path. */ free(path); gsh_free(mnt.mnt_dir); } else if (err != BTRFS_UTIL_ERROR_STOP_ITERATION) { LogCrit(COMPONENT_FSAL, "btrfs_util_subvolume_iterator_next err %s", btrfs_util_strerror(err)); } } out: btrfs_util_destroy_subvolume_iterator(iter); #else LogWarn(COMPONENT_FSAL, "btrfs filesystem %s may have unsupported subvols", fs->path); #endif } static void posix_create_file_system(struct mntent *mnt, struct stat *mnt_stat) { struct fsal_filesystem *fs; struct avltree_node *node; fs = gsh_calloc(1, sizeof(*fs)); fs->path = gsh_strdup(mnt->mnt_dir); fs->device = gsh_strdup(mnt->mnt_fsname); fs->type = gsh_strdup(mnt->mnt_type); glist_init(&fs->exports); if (!posix_get_fsid(fs, mnt_stat)) { free_fs(fs); return; } fs->pathlen = strlen(mnt->mnt_dir); node = avltree_insert(&fs->avl_fsid, &avl_fsid); if (node != NULL) { /* This is a duplicate file system. */ struct fsal_filesystem *fs1; fs1 = avltree_container_of(node, struct fsal_filesystem, avl_fsid); LogDebug(COMPONENT_FSAL, "Skipped duplicate %s namelen=%d fsid=0x%016" PRIx64 ".0x%016" PRIx64 " %" PRIu64 ".%" PRIu64 " type=%s", fs->path, (int)fs->namelen, fs->fsid.major, fs->fsid.minor, fs->fsid.major, fs->fsid.minor, fs->type); if (fs1->device[0] != '/' && fs->device[0] == '/') { LogDebug( COMPONENT_FSAL, "Switching device for %s from %s to %s type from %s to %s", fs->path, fs1->device, fs->device, fs1->type, fs->type); gsh_free(fs1->device); fs1->device = fs->device; fs->device = NULL; gsh_free(fs1->type); fs1->type = fs->type; fs->type = NULL; } free_fs(fs); return; } fs->in_fsid_avl = true; node = avltree_insert(&fs->avl_dev, &avl_dev); if (node != NULL) { /* This is a duplicate file system. */ struct fsal_filesystem *fs1; fs1 = avltree_container_of(node, struct fsal_filesystem, avl_dev); LogDebug(COMPONENT_FSAL, "Skipped duplicate %s namelen=%d dev=%" PRIu64 ".%" PRIu64 " type=%s", fs->path, (int)fs->namelen, fs->dev.major, fs->dev.minor, fs->type); if (fs1->device[0] != '/' && fs->device[0] == '/') { LogDebug( COMPONENT_FSAL, "Switching device for %s from %s to %s type from %s to %s", fs->path, fs1->device, fs->device, fs1->type, fs->type); gsh_free(fs1->device); fs1->device = fs->device; fs->device = NULL; gsh_free(fs1->type); fs1->type = fs->type; fs->type = NULL; } remove_fs(fs); free_fs(fs); return; } fs->in_dev_avl = true; glist_add_tail(&posix_file_systems, &fs->filesystems); glist_init(&fs->children); LogInfo(COMPONENT_FSAL, "Added filesystem %p %s namelen=%d dev=%" PRIu64 ".%" PRIu64 " fsid=0x%016" PRIx64 ".0x%016" PRIx64 " %" PRIu64 ".%" PRIu64 " type=%s", fs, fs->path, (int)fs->namelen, fs->dev.major, fs->dev.minor, fs->fsid.major, fs->fsid.minor, fs->fsid.major, fs->fsid.minor, fs->type); if (strcasecmp(mnt->mnt_type, "btrfs") == 0) posix_create_fs_btrfs_subvols(fs); } static void posix_find_parent(struct fsal_filesystem *this) { struct glist_head *glist; struct fsal_filesystem *fs; int plen = 0; /* Check if it already has parent */ if (this->parent != NULL) return; /* Check for root fs, it has no parent */ if (this->pathlen == 1 && this->path[0] == '/') return; glist_for_each(glist, &posix_file_systems) { fs = glist_entry(glist, struct fsal_filesystem, filesystems); /* If this path is longer than this path, then it * can't be a parent, or if it's shorter than the * current match; */ if (fs->pathlen >= this->pathlen || fs->pathlen < plen) continue; /* Check for sub-string match */ if (strncmp(fs->path, this->path, fs->pathlen) != 0) continue; /* Differentiate between /fs1 and /fs10 for parent of * /fs10/fs2, however, if fs->path is "/", we need to * special case. */ if (fs->pathlen != 1 && this->path[fs->pathlen] != '/') continue; this->parent = fs; plen = fs->pathlen; } if (this->parent == NULL) { LogInfo(COMPONENT_FSAL, "Unattached file system %s", this->path); return; } /* Add to parent's list of children */ glist_add_tail(&this->parent->children, &this->siblings); LogInfo(COMPONENT_FSAL, "File system %s is a child of %s", this->path, this->parent->path); } static bool path_is_subset(const char *path, const char *of) { int len_path = strlen(path); int len_of = strlen(of); int len_cmp = MIN(len_path, len_of); /* We can handle special case of "/" trivially, so check for it */ if ((len_path == 1 && path[0] == '/') || (len_of == 1 && of[0] == '/')) { /* One of the paths is "/" so subset MUST be true */ return true; } if (len_path != len_of && ((len_cmp != len_path && path[len_cmp] != '/') || (len_cmp != len_of && of[len_cmp] != '/'))) { /* The character in the longer path just past the length of the * shorter path must be '/' in order to be a valid subset path. */ return false; } /* Compare the two strings to the length of the shorter one */ if (strncmp(path, of, len_cmp) != 0) { /* Since the shortest doesn't match to the start of the longer * neither path is a subset of the other. */ return false; } return true; } int populate_posix_file_systems(const char *path) { FILE *fp; struct mntent *mnt; struct stat st; int retval = 0; struct glist_head *glist, *glistn; struct fsal_filesystem *fs, *fsn; PTHREAD_RWLOCK_wrlock(&fs_lock); if (!fs_initialized) { LogDebug(COMPONENT_FSAL, "Initializing posix file systems"); avltree_init(&avl_fsid, fsal_fs_cmpf_fsid, 0); avltree_init(&avl_dev, fsal_fs_cmpf_dev, 0); fs_initialized = true; } /* We are about to rescan mtab, remove unclaimed file systems. * release_posix_file_system will actually do the depth first * search for unclaimed file systems. */ glist = posix_file_systems.next; /* Scan the list for the first top level file system that is not * claimed. * * NOTE: the following two loops are similar to glist_for_each_safe but * instead of just taking the next list entry, we take the next * list entry that is a top level file system. This way even if * the glist->next was a descendant file system that was going to * be released, before we release a file system or it's * descendants, we look for the next top level file system, since * it's a top level file system it WILL NOT be released when we * release fs or it's descendants. */ while (glist != &posix_file_systems) { fs = glist_entry(glist, struct fsal_filesystem, filesystems); if (fs->parent == NULL) { /* Top level file system done */ break; } glist = glist->next; } /* Now, while we still have a top level file system to process */ while (glist != &posix_file_systems) { /* First, find the NEXT top level file system. */ glistn = glist->next; while (glistn != &posix_file_systems) { fsn = glist_entry(glistn, struct fsal_filesystem, filesystems); if (fsn->parent == NULL) { /* Top level file system done */ break; } glistn = glistn->next; } /* Now glistn/fsn is either the next top level file * system or glistn is &posix_file_systems. * * fs is the file system to try releasing on, try to * release this file system or it's descendants. */ (void)release_posix_file_system(fs, UNCLAIM_SKIP); /* Now ready to start processing the next top level * file system that is not claimed. */ glist = glistn; fs = fsn; } /* start looking for the mount point */ fp = setmntent(MOUNTED, "r"); if (fp == NULL) { retval = errno; LogCrit(COMPONENT_FSAL, "Error %d in setmntent(%s): %s", retval, MOUNTED, strerror(retval)); goto out; } #ifdef USE_BLKID if (blkid_get_cache(&cache, NULL) != 0) LogInfo(COMPONENT_FSAL, "blkid_get_cache failed"); #endif while ((mnt = getmntent(fp)) != NULL) { if (mnt->mnt_dir == NULL) continue; if (!path_is_subset(path, mnt->mnt_dir)) { LogDebug( COMPONENT_FSAL, "Ignoring %s because it is not a subset or superset of path %s", mnt->mnt_dir, path); continue; } /* stat() on NFS mount points is prone to get stuck in * kernel due to unavailable NFS servers. Since we don't * support them anyway, check this early and avoid * hangs! * * Also skip some other filesystem types that we would never * export. */ if (strncasecmp(mnt->mnt_type, "nfs", 3) == 0 || strcasecmp(mnt->mnt_type, "autofs") == 0 || strcasecmp(mnt->mnt_type, "sysfs") == 0 || strcasecmp(mnt->mnt_type, "proc") == 0 || strcasecmp(mnt->mnt_type, "devtmpfs") == 0 || strcasecmp(mnt->mnt_type, "securityfs") == 0 || strcasecmp(mnt->mnt_type, "cgroup") == 0 || strcasecmp(mnt->mnt_type, "selinuxfs") == 0 || strcasecmp(mnt->mnt_type, "debugfs") == 0 || strcasecmp(mnt->mnt_type, "hugetlbfs") == 0 || strcasecmp(mnt->mnt_type, "mqueue") == 0 || strcasecmp(mnt->mnt_type, "pstore") == 0 || strcasecmp(mnt->mnt_type, "devpts") == 0 || strcasecmp(mnt->mnt_type, "configfs") == 0 || strcasecmp(mnt->mnt_type, "binfmt_misc") == 0 || strcasecmp(mnt->mnt_type, "rpc_pipefs") == 0 || strcasecmp(mnt->mnt_type, "vboxsf") == 0 || strcasecmp(mnt->mnt_type, "tmpfs") == 0) { LogDebug(COMPONENT_FSAL, "Ignoring %s because type %s", mnt->mnt_dir, mnt->mnt_type); continue; } if (stat(mnt->mnt_dir, &st) < 0 || !S_ISDIR(st.st_mode)) { continue; } posix_create_file_system(mnt, &st); } #ifdef USE_BLKID if (cache) { blkid_put_cache(cache); cache = NULL; } #endif endmntent(fp); /* build tree of POSIX file systems */ glist_for_each(glist, &posix_file_systems) posix_find_parent( glist_entry(glist, struct fsal_filesystem, filesystems)); out: PTHREAD_RWLOCK_unlock(&fs_lock); return retval; } int resolve_posix_filesystem(const char *path, struct fsal_module *fsal, struct fsal_export *exp, claim_filesystem_cb claimfs, unclaim_filesystem_cb unclaim, struct fsal_filesystem **root_fs) { int retval = EAGAIN; struct stat statbuf; uint32_t retries_left = nfs_param.core_param.resolve_fs_retries; while (retval == EAGAIN && retries_left > 0) { struct timespec how_long = { /* 1M micros per second */ .tv_sec = nfs_param.core_param.resolve_fs_delay / 1000, /* Remainder => nanoseconds */ .tv_nsec = (nfs_param.core_param.resolve_fs_delay % 1000) * 1000000 }; /* Need to retry stat on path until we don't get EAGAIN in case * autofs needed to mount the file system. */ retval = stat(path, &statbuf); if (retval == 0) break; retval = errno; LogDebug( COMPONENT_FSAL, "stat returned %s (%d) while resolving export path %s %s", strerror(retval), retval, path, retval == EAGAIN ? "(may retry)" : "(failed)"); retries_left--; if (retries_left == 0) { LogCrit(COMPONENT_FSAL, "Stat on filesystem %s failed, no retries remaining.", path); break; } if (nanosleep(&how_long, NULL) != 0) { /* * Let interrupts wake us up and not care. Anything else * should be fatal. */ if (errno != EINTR) { retval = errno; LogCrit(COMPONENT_FSAL, "nanosleep failed. Asked for %" PRIu32 " msec. Errno %d (%s)", nfs_param.core_param.resolve_fs_delay, retval, strerror(retval)); return retval; } } } if (retval != 0) { /* Since we failed a stat on the path, we might as well bail * now... */ LogCrit(COMPONENT_FSAL, "stat returned %s (%d) while resolving export path %s", strerror(retval), retval, path); return retval; } retval = populate_posix_file_systems(path); if (retval != 0) { LogCrit(COMPONENT_FSAL, "populate_posix_file_systems returned %s (%d)", strerror(retval), retval); return retval; } retval = claim_posix_filesystems(path, fsal, exp, claimfs, unclaim, root_fs, &statbuf); return retval; } /** * @brief release a POSIX file system and all it's descendants * * @param[in] fs the file system to release * @param[in] release_claims what to do about claimed file systems * * @returns true if a descendant was not released because it was a claimed * file system or a descendant underneath was a claimed file system. * */ bool release_posix_file_system(struct fsal_filesystem *fs, enum release_claims release_claims) { bool claimed = false; /* Assume no claimed children. */ struct glist_head *glist, *glistn; LogFilesystem("TRY RELEASE", "", fs); /* Note: Check this file system AFTER we check the descendants, we will * thus release any descendants that are not claimed. */ glist_for_each_safe(glist, glistn, &fs->children) { struct fsal_filesystem *child_fs; child_fs = glist_entry(glist, struct fsal_filesystem, siblings); /* If a child or child underneath was not released because it * was claimed, propagate that up. */ claimed |= release_posix_file_system(child_fs, release_claims); } if (fs->unclaim != NULL) { if (release_claims == UNCLAIM_WARN) LogWarn(COMPONENT_FSAL, "Filesystem %s is still claimed", fs->path); else LogDebug(COMPONENT_FSAL, "Filesystem %s is still claimed", fs->path); return true; } if (claimed) { if (release_claims == UNCLAIM_WARN) LogWarn(COMPONENT_FSAL, "Filesystem %s had at least one child still claimed", fs->path); else LogDebug( COMPONENT_FSAL, "Filesystem %s had at least one child still claimed", fs->path); return true; } LogFilesystem("REMOVE", "", fs); LogInfo(COMPONENT_FSAL, "Removed filesystem %p %s namelen=%d dev=%" PRIu64 ".%" PRIu64 " fsid=0x%016" PRIx64 ".0x%016" PRIx64 " %" PRIu64 ".%" PRIu64 " type=%s", fs, fs->path, (int)fs->namelen, fs->dev.major, fs->dev.minor, fs->fsid.major, fs->fsid.minor, fs->fsid.major, fs->fsid.minor, fs->type); remove_fs(fs); free_fs(fs); return false; } void release_posix_file_systems(void) { struct fsal_filesystem *fs; PTHREAD_RWLOCK_wrlock(&fs_lock); while ((fs = glist_first_entry(&posix_file_systems, struct fsal_filesystem, filesystems))) { (void)release_posix_file_system(fs, UNCLAIM_WARN); } PTHREAD_RWLOCK_unlock(&fs_lock); } struct fsal_filesystem *lookup_fsid_locked(struct fsal_fsid__ *fsid, enum fsid_type fsid_type) { struct fsal_filesystem key; key.fsid = *fsid; key.fsid_type = fsid_type; return avltree_inline_fsid_lookup(&key.avl_fsid); } struct fsal_filesystem *lookup_dev_locked(struct fsal_dev__ *dev) { struct fsal_filesystem key; key.dev = *dev; return avltree_inline_dev_lookup(&key.avl_dev); } struct fsal_filesystem *lookup_fsid(struct fsal_fsid__ *fsid, enum fsid_type fsid_type) { struct fsal_filesystem *fs; PTHREAD_RWLOCK_rdlock(&fs_lock); fs = lookup_fsid_locked(fsid, fsid_type); PTHREAD_RWLOCK_unlock(&fs_lock); return fs; } struct fsal_filesystem *lookup_dev(struct fsal_dev__ *dev) { struct fsal_filesystem *fs; PTHREAD_RWLOCK_rdlock(&fs_lock); fs = lookup_dev_locked(dev); PTHREAD_RWLOCK_unlock(&fs_lock); return fs; } const char *str_claim_type(enum claim_type claim_type) { switch (claim_type) { case CLAIM_ALL: return "CLAIM_ALL"; case CLAIM_ROOT: return "CLAIM_ROOT"; case CLAIM_SUBTREE: return "CLAIM_SUBTREE"; case CLAIM_CHILD: return "CLAIM_CHILD"; case CLAIM_TEMP: return "CLAIM_TEMP"; case CLAIM_NUM: return "CLAIM_NUM"; } return "unknown claim type"; } void unclaim_child_map(struct fsal_filesystem_export_map *this) { LogFilesystem("UNCLAIM ", "(BEFORE)", this->fs); /* Unclaim any child maps */ while (!glist_empty(&this->child_maps)) { struct fsal_filesystem_export_map *map; map = glist_first_entry(&this->child_maps, struct fsal_filesystem_export_map, on_parent); unclaim_child_map(map); } LogFilesystem("Unclaim Child Map for Claim Type ", str_claim_type(this->claim_type), this->fs); /* Remove this file system from mapping */ glist_del(&this->on_filesystems); glist_del(&this->on_exports); glist_del(&this->on_parent); /* Reduce the claims on the filesystem */ --this->fs->claims[this->claim_type]; --this->fs->claims[CLAIM_ALL]; /* Don't actually unclaim from the FSAL if there are claims remaining or * a temporary claim. */ if (this->fs->claims[CLAIM_ALL] == 0 && this->fs->claims[CLAIM_TEMP] == 0) { /* This was the last claim on the filesystem */ assert(this->fs->claims[CLAIM_ROOT] == 0); assert(this->fs->claims[CLAIM_SUBTREE] == 0); assert(this->fs->claims[CLAIM_CHILD] == 0); if (this->fs->unclaim != NULL) { LogDebug(COMPONENT_FSAL, "Have FSAL %s unclaim filesystem %s", this->fs->fsal->name, this->fs->path); this->fs->unclaim(this->fs); } this->fs->fsal = NULL; this->fs->unclaim = NULL; this->fs->private_data = NULL; } LogFilesystem("UNCLAIM ", "(AFTER)", this->fs); /* And free this map */ gsh_free(this); } void unclaim_all_filesystem_maps(struct fsal_filesystem *this) { while (!glist_empty(&this->exports)) { struct fsal_filesystem_export_map *map; map = glist_first_entry(&this->exports, struct fsal_filesystem_export_map, on_exports); unclaim_child_map(map); } } void get_fs_first_export_ref(struct fsal_filesystem *this, struct gsh_export **gsh_export, struct fsal_export **fsal_export) { struct fsal_filesystem_export_map *map; PTHREAD_RWLOCK_wrlock(&fs_lock); map = glist_first_entry(&this->exports, struct fsal_filesystem_export_map, on_exports); if (map != NULL) { *fsal_export = map->exp; *gsh_export = (*fsal_export)->owning_export; get_gsh_export_ref(*gsh_export); } else { *gsh_export = NULL; *fsal_export = NULL; } PTHREAD_RWLOCK_unlock(&fs_lock); } void unclaim_all_export_maps(struct fsal_export *exp) { PTHREAD_RWLOCK_wrlock(&fs_lock); while (!glist_empty(&exp->filesystems)) { struct fsal_filesystem_export_map *map; map = glist_first_entry(&exp->filesystems, struct fsal_filesystem_export_map, on_filesystems); unclaim_child_map(map); } if (exp->root_fs != NULL) { LogFilesystem("ROOT FS", "", exp->root_fs); /* Now that we've unclaimed all fsal_fileststem objects, see if * we can release any. Once we're done with this, any unclaimed * file systems should be able to be unmounted by the sysadmin * (though note that if they are sub-mounted in another VFS * export, they could become claimed by navigation into them). * If there are any nested exports, the file systems they export * will still be claimed. The nested exports will at least still * be mountable via NFS v3. */ (void)release_posix_file_system(exp->root_fs, UNCLAIM_SKIP); } PTHREAD_RWLOCK_unlock(&fs_lock); } #define HAS_CHILD_CLAIMS(this) (this->claims[CLAIM_CHILD]) #define HAS_NON_CHILD_CLAIMS(this) \ (this->claims[CLAIM_ROOT] != 0 || this->claims[CLAIM_SUBTREE] != 0) static inline bool is_path_child(const char *possible_path, int possible_pathlen, const char *compare_path, int compare_pathlen) { /* For a possible_path to represent a child of compare_path: * possible_pathlen MUST be longer (otherwise it can't be a child * the portion of possible_path up to compare_pathlen must be the same * AND the portion of possible_path that compares MUST end with a '/' * * Thus /short is NOT a child of /short/longer * and /some/path is NOT a child of /some/other/path * and /some/path2 is NOT a child of /some/path * * Since the '/' check is simple, check it before comparing strings */ return possible_pathlen > compare_pathlen && possible_path[compare_pathlen] == '/' && strncmp(possible_path, compare_path, compare_pathlen) == 0; } static inline bool is_filesystem_child(struct fsal_filesystem *fs, const char *path, int pathlen) { return is_path_child(fs->path, fs->pathlen, path, pathlen); } /** * @brief Validate that fs is exported by exp * * @note Must hold fs_lock */ bool is_filesystem_exported(struct fsal_filesystem *fs, struct fsal_export *exp) { struct glist_head *glist, *glistn; struct fsal_filesystem_export_map *map; LogFullDebug(COMPONENT_FSAL, "Checking if FileSystem %s belongs to export %" PRIu16, fs->path, exp->export_id); glist_for_each_safe(glist, glistn, &fs->exports) { map = glist_entry(glist, struct fsal_filesystem_export_map, on_exports); if (map->exp == exp) { /* We found a match! */ return true; } } LogInfo(COMPONENT_FSAL, "FileSystem %s does not belong to export %" PRIu16, fs->path, exp->export_id); return false; } static int process_claim(const char *path, int pathlen, struct fsal_filesystem_export_map *parent_map, struct fsal_filesystem *this, struct fsal_module *fsal, struct fsal_export *exp, claim_filesystem_cb claimfs, unclaim_filesystem_cb unclaim) { struct glist_head *export_glist, *child_glist; struct fsal_filesystem_export_map *map; int retval = 0; bool already_claimed = this->fsal == fsal; enum claim_type claim_type; void *private_data; LogFilesystem("PROCESS CLAIM", "", this); if (path == NULL) claim_type = CLAIM_CHILD; else if (strcmp(path, this->path) == 0) claim_type = CLAIM_ROOT; else claim_type = CLAIM_SUBTREE; /* Either this filesystem must be claimed by a FSAL OR it must not have * any claims at all. */ assert(this->fsal != NULL || this->claims[CLAIM_ALL] == 0); /* Check if the filesystem is already directly exported by some other * FSAL - note we can only get here is this is the root filesystem for * the export, once we start processing nested filesystems, we skip * any that are directly exported. */ if (this->fsal != fsal && HAS_NON_CHILD_CLAIMS(this)) { LogCrit(COMPONENT_FSAL, "Filesystem %s already exported by FSAL %s for export path %s", this->path, this->fsal->name, path); return EINVAL; } if (already_claimed) { /* Since this fs is already claimed by the FSAL, we can share * any private_data already set. */ private_data = this->private_data; } else { /* This fs may be claimed by another FSAL, if so, we MUST pass * NULL for private_data since any private_data belongs to a * different FSAL. */ private_data = NULL; } /* Now claim the file system (we may call claim multiple times) */ retval = claimfs(this, exp, &private_data); if (retval == ENXIO) { if (claim_type != CLAIM_CHILD) { LogCrit(COMPONENT_FSAL, "FSAL %s could not to claim root file system %s for export %s", fsal->name, this->path, path); return EINVAL; } else { LogInfo(COMPONENT_FSAL, "FSAL %s could not to claim file system %s", fsal->name, this->path); return 0; } } if (retval != 0) { LogCrit(COMPONENT_FSAL, "FSAL %s failed to claim file system %s error %s", fsal->name, this->path, strerror(retval)); return retval; } /* Take a temporary claim to prevent call to unclaim */ this->claims[CLAIM_TEMP]++; if (already_claimed) { LogDebug(COMPONENT_FSAL, "FSAL %s Repeat Claiming %p %s", fsal->name, this, this->path); } else { LogInfo(COMPONENT_FSAL, "FSAL %s Claiming %p %s", fsal->name, this, this->path); } LogFullDebug(COMPONENT_FSAL, "Attempting claim type %s by FSAL %s on filesystem %s", str_claim_type(claim_type), fsal->name, this->path); /* Check for another FSAL holding child claims on this filesystem or * any child claims held by this FSAL when the new claim is a root claim */ if (HAS_CHILD_CLAIMS(this) && (this->fsal != fsal || claim_type == CLAIM_ROOT)) { LogFullDebug( COMPONENT_FSAL, "FSAL %s trying to claim filesystem %s from FSAL %s", fsal->name, this->path, this->fsal->name); if (claim_type == CLAIM_SUBTREE) { /* Warn about situation where the claim directory * structure would suggest the other FSAL's child * claim should co-exist with a subtree claim. * * NOTE: this warning could be spurious depending on * order of exports. For example, assume two * filesystems: * /export/fs1 * /export/fs1/some/path/fs2 * And the following exports: * FSAL_XFS /export/fs1 * FSAL_VFS /export/fs1/some/path/fs2/another * FSAL_VFS /export/fs1/some/path/fs2 * * Initially /export/fs1/some/path/fs2 will be * claimed for FSAL_XFS as part of the /export/fs1 * export, but then the FSAL_VFS export * /export/fs1/some/path/fs2/another will take it * away, but at that point, * /export/fs1/some/path/fs2 would still appear to * be part of the XFS export, so the LogWarn fires * but in fact, the final VFS export of * /export/fs1/some/path/fs2 makes it all right. * If this bothers someone, put the exports in * order of longest/deepest path first... */ LogWarn(COMPONENT_FSAL, "FSAL %s export path %s includes filesystem %s which had a subtree export from FSAL %s - unclaiming filesystem from FSAL %s", fsal->name, path, this->path, this->fsal->name, this->fsal->name); } /* Walk the child maps and unclaim them all */ unclaim_all_filesystem_maps(this); assert(!HAS_CHILD_CLAIMS(this)); } /* If this is a root claim and there are any child claims (which must * belong to this FSAL since we already disposed of child claims by * another FSAL above), unclaim all of them. */ if (claim_type == CLAIM_ROOT && HAS_CHILD_CLAIMS(this)) { /* Walk the child maps and unclaim them all */ unclaim_all_filesystem_maps(this); } /* At this point, the claims that remain on this filesystem belong * to this FSAL, the following claims are allowed based on this claim: * * root claim: subtree and root claims are allowed * subtree claim: root, subtree, and child claims are allowed * child claim: subtree and child claims are allowed * * Note that two (or more) root claims ARE allowed as long as the * exports are differentiated. Multiple subtree claims ARE allowed * whether they are distinct or are the same directory (in which case * the exports MUST be differentiated). Multiple parent claims may * result in multiple child claims however the parent claims MUST be * the same directory (root or subtree) otherwise the parent claim with * the shorter path would have the parent claim with the longer path as * a sub-export, and only the sub-export would export the child * filesystems. * * Because of this, we need to check for overlapping subtree claims * below and either unclaim the child claims from the shorter path * or not make child claims when there are child claims from a longer * path already in existence. */ /* Complete the claim */ this->fsal = fsal; this->unclaim = unclaim; this->private_data = private_data; map = gsh_calloc(1, sizeof(*map)); map->exp = exp; map->fs = this; map->claim_type = claim_type; glist_init(&map->child_maps); if (claim_type == CLAIM_ROOT) exp->root_fs = this; /* If this has no children, done */ if (glist_empty(&this->children)) goto account; /* Now we may need to clean out some child claims on this filesystems * children. This happens when this export claim is a subtree with a * longer path that an already existing export claim which could be a * root or subtree claim. */ if (claim_type == CLAIM_SUBTREE) { /* Look for other claims on this filesystem where this claim is * a subtree of the other claim. On such claims, look for any * child filesystems that are mapped by the other claim that * are children of our subtree. Such child claims must be * removed (they will become child claims of this claim). */ glist_for_each(export_glist, &this->exports) { struct glist_head *glist, *glistn; struct fsal_filesystem_export_map *other_map; struct gsh_refstr *map_fullpath; size_t map_pathlen; bool child; other_map = glist_entry( export_glist, struct fsal_filesystem_export_map, on_exports); if (glist_empty(&other_map->child_maps)) { /* This map has no child claims under it, so * skip it. */ continue; } map_fullpath = gsh_refstr_get(rcu_dereference( other_map->exp->owning_export->fullpath)); map_pathlen = strlen(map_fullpath->gr_val); /* We're interested in when this claim is a subtree of * an existing claim, in which case we will take any * child claims that are without our subtree. * * NOTE the order of paths passed to is_path_child is * reversed from other uses because we are checking * if this claim is a subtree of the map claim. */ child = is_path_child(path, pathlen, map_fullpath->gr_val, map_pathlen); /* And we're done with the refstr. */ gsh_refstr_put(map_fullpath); if (!child) { /* Since the map claim is not a subtree of this * claim, we don't care about it's children. Its * mapping some other portion of this filesystem */ continue; } glist_for_each_safe(glist, glistn, &other_map->child_maps) { struct fsal_filesystem_export_map *child_map; child_map = glist_entry( glist, struct fsal_filesystem_export_map, on_parent); if (is_path_child(child_map->fs->path, child_map->fs->pathlen, path, pathlen)) { /* filesystem is a child of our * subtree, now we need to remove this * map's child_maps on the filesystem. */ unclaim_child_map(child_map); } } } } /* Claim the children now */ glist_for_each(child_glist, &this->children) { struct fsal_filesystem *child_fs; child_fs = glist_entry(child_glist, struct fsal_filesystem, siblings); /* Any child filesystem can not have child claims from another * FSAL since that FSAL would have to have a claim on this * filesystem which would conflict with our claim. * * Child claims from our FSAL are fine. */ assert(!HAS_NON_CHILD_CLAIMS(child_fs) || child_fs->fsal == fsal); /* For subtree claims, only consider children that are * children of the provided directory. This handles the * case of an export of something other than the root * of a file system. */ if (claim_type == CLAIM_SUBTREE && !is_filesystem_child(child_fs, path, pathlen)) continue; /* Test if the root of this fs is exported, if so, skip it. It * doesn't matter if the claim is for our FSAL or not. */ if (child_fs->claims[CLAIM_ROOT]) continue; /* Test if there are subtree claims from a different FSAL */ if (child_fs->claims[CLAIM_SUBTREE] && child_fs->fsal != fsal) { LogWarn(COMPONENT_FSAL, "FSAL %s export path %s includes filesystem %s which has subtree exports from FSAL %s - not exporting it as a child filesystem", fsal->name, path, child_fs->path, child_fs->fsal->name); continue; } /* Now we need to check if there is a claim deeper into this * filesystem that has a child claim on the child filesystem. * If so, the filesystem isn't a candidate. */ if (child_fs->claims[CLAIM_CHILD] != 0) { bool skip = false; /* Examine the child claims of the child filesystem to * determinate if they are held by an export that has * the same path as ours (in which case we can also * make child claims) or not (in which case we know * that export is a subtree of this export and thus * that export gets the child claims). */ glist_for_each(export_glist, &child_fs->exports) { struct fsal_filesystem_export_map *other_map; struct gsh_refstr *map_fullpath; size_t map_pathlen; other_map = glist_entry( export_glist, struct fsal_filesystem_export_map, on_exports); if (other_map->claim_type == CLAIM_SUBTREE) { /* A subtree claim doesn't block us * from taking a child claim. */ continue; } /* We already skipped the child filesystem if it * had any root claims on it, so we better not * find any in the map now... And therefore * the claim we are now looking at MUST be a * child claim. */ assert(other_map->claim_type == CLAIM_CHILD); map_fullpath = gsh_refstr_get(rcu_dereference( other_map->exp->owning_export ->fullpath)); map_pathlen = strlen(map_fullpath->gr_val); /* Check if the child claim is from an export * with a matching path or not. If from a path * that doesn't match, it MUST be a subtree of * this export's path and therefore we can not * take child claims. Otherwise since it matches * child claims will be ok. */ if (map_pathlen != pathlen || strcmp(map_fullpath->gr_val, path) != 0) { /* Child claim is from an export that is * a subtree of this export so indicate * that we must skip the filesystem. */ skip = true; } /* And we're done with the refstr. */ gsh_refstr_put(map_fullpath); /* Since we've hit a child claim and all child * claims must be from exports with the same * path, we know if the path was the same as * ours (in which case we can take a claim) or * not. */ break; } /* end of glist_for_each */ if (skip) { /* There is one or more exports with a path * that is a subtree of our path that have * child claims on the filesystem under * consideration, so we can't claim it. */ continue; } } /* end of if (child_fs->claims[CLAIM_CHILD] != 0) */ /* Try to claim this child, we don't care about the return * it might be a child filesystem this FSAL can't export or * there might be some other problem, but it shouldn't cause * failure of the export as a whole. */ (void)process_claim(NULL, 0, map, child_fs, fsal, exp, claimfs, unclaim); } account: /* Account for the claim */ this->claims[claim_type]++; this->claims[CLAIM_ALL]++; /* Release the temporary claim */ this->claims[CLAIM_TEMP]--; LogFullDebug(COMPONENT_FSAL, "Completing claim type %s by FSAL %s on filesystem %s", str_claim_type(claim_type), fsal->name, this->path); /* Now that we are done with all that, add this map into this * filesystem and export (doing this late like this saves looking at it * in loops above). */ glist_add_tail(&this->exports, &map->on_exports); glist_add_tail(&exp->filesystems, &map->on_filesystems); if (parent_map != NULL) glist_add_tail(&parent_map->child_maps, &map->on_parent); LogFilesystem("PROCESS CLAIM FINISHED", "", this); return retval; } int claim_posix_filesystems(const char *path, struct fsal_module *fsal, struct fsal_export *exp, claim_filesystem_cb claimfs, unclaim_filesystem_cb unclaim, struct fsal_filesystem **root_fs, struct stat *statbuf) { int retval = 0; struct fsal_filesystem *fs, *root = NULL; struct glist_head *glist; struct fsal_dev__ dev; PTHREAD_RWLOCK_wrlock(&fs_lock); dev = posix2fsal_devt(statbuf->st_dev); /* Scan POSIX file systems to find export root fs */ glist_for_each(glist, &posix_file_systems) { fs = glist_entry(glist, struct fsal_filesystem, filesystems); if (fs->dev.major == dev.major && fs->dev.minor == dev.minor) { root = fs; break; } } /* Check if we found a filesystem */ if (root == NULL) { retval = ENOENT; goto out; } /* Claim this file system and it's children */ retval = process_claim(path, strlen(path), NULL, root, fsal, exp, claimfs, unclaim); if (retval == 0) { LogInfo(COMPONENT_FSAL, "Root fs for export %s is %s", path, root->path); *root_fs = root; } out: PTHREAD_RWLOCK_unlock(&fs_lock); return retval; } #ifdef USE_DBUS /** *@brief Dbus method for showing dev ids of mounted POSIX filesystems * *@param[in] args *@param[out] reply **/ static bool posix_showfs(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct fsal_filesystem *fs; struct glist_head *glist; DBusMessageIter iter, sub_iter, fs_iter; struct timespec timestamp; uint64_t val; char *path; dbus_message_iter_init_append(reply, &iter); now(×tamp); gsh_dbus_append_timestamp(&iter, ×tamp); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(stt)", &sub_iter); PTHREAD_RWLOCK_rdlock(&fs_lock); /* Traverse POSIX file systems to display dev ids */ glist_for_each(glist, &posix_file_systems) { fs = glist_entry(glist, struct fsal_filesystem, filesystems); dbus_message_iter_open_container(&sub_iter, DBUS_TYPE_STRUCT, NULL, &fs_iter); path = (fs->path != NULL) ? fs->path : ""; dbus_message_iter_append_basic(&fs_iter, DBUS_TYPE_STRING, &path); val = fs->dev.major; dbus_message_iter_append_basic(&fs_iter, DBUS_TYPE_UINT64, &val); val = fs->dev.minor; dbus_message_iter_append_basic(&fs_iter, DBUS_TYPE_UINT64, &val); dbus_message_iter_close_container(&sub_iter, &fs_iter); } PTHREAD_RWLOCK_unlock(&fs_lock); dbus_message_iter_close_container(&iter, &sub_iter); return true; } static struct gsh_dbus_method cachemgr_show_fs = { .name = "showfs", .method = posix_showfs, .args = { TIMESTAMP_REPLY, { .name = "fss", .type = "a(stt)", .direction = "out" }, END_ARG_LIST } }; static struct gsh_dbus_method *cachemgr_methods[] = { &cachemgr_show_fs, &cachemgr_show_idmapper, NULL }; static struct gsh_dbus_interface cachemgr_table = { .name = "org.ganesha.nfsd.cachemgr", .props = NULL, .methods = cachemgr_methods, .signals = NULL }; /* DBUS list of interfaces on /org/ganesha/nfsd/CacheMgr * Intended for showing different caches */ static struct gsh_dbus_interface *cachemgr_interfaces[] = { &cachemgr_table, NULL }; void dbus_cache_init(void) { gsh_dbus_register_path("CacheMgr", cachemgr_interfaces); } #endif /* USE_DBUS */ nfs-ganesha-6.5/src/FSAL/posix_acls.c000066400000000000000000000632111473756622300174240ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * posix_acls.c * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Red Hat Inc., 2015 * Author: Niels de Vos * Jiffin Tony Thottan * * This program 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 3 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 * Lesser General Public License for more details. * * Conversion routines for fsal_acl <-> POSIX ACl * * Routines based on the description from an Internet Draft that has also been * used for the implementation of the conversion in the Linux kernel * NFS-server. * * Title: Mapping Between NFSv4 and Posix Draft ACLs * Authors: Marius Aamodt Eriksen & J. Bruce Fields * URL: http://tools.ietf.org/html/draft-ietf-nfsv4-acl-mapping-05 */ #include "posix_acls.h" /* Checks whether ACE belongs to effective acl (ACCESS TYPE) */ bool is_ace_valid_for_effective_acl_entry(fsal_ace_t *ace) { bool ret; if (IS_FSAL_ACE_HAS_INHERITANCE_FLAGS(*ace)) { if (IS_FSAL_ACE_APPLICABLE_FOR_BOTH_ACL(*ace)) ret = true; else ret = false; } else ret = true; return ret; } /* Checks whether ACE belongs to inherited acl (DEFAULT TYPE) */ bool is_ace_valid_for_inherited_acl_entry(fsal_ace_t *ace) { if (IS_FSAL_ACE_APPLICABLE_FOR_BOTH_ACL(*ace) || IS_FSAL_ACE_APPLICABLE_ONLY_FOR_INHERITED_ACL(*ace)) return true; else return false; } /* * Check whether given perm(ACL_READ, ACL_WRITE or ACL_EXECUTE) is allowed for * a permset, it depends on given ace and permset of @EVERYONE entry. */ bool isallow(fsal_ace_t *ace, acl_permset_t everyone, acl_perm_t perm) { bool ret = acl_get_perm(everyone, perm); switch (perm) { case ACL_READ: ret |= IS_FSAL_ACE_READ_DATA(*ace); break; case ACL_WRITE: ret |= IS_FSAL_ACE_WRITE_DATA(*ace); break; case ACL_EXECUTE: ret |= IS_FSAL_ACE_EXECUTE(*ace); break; } return ret; } /* * Check whether given perm(ACL_READ, ACL_WRITE or ACL_EXECUTE) is denied for a * permset, it depends on permsets of deny entry of the acl and @EVERYONE entry. */ bool isdeny(acl_permset_t deny, acl_permset_t everyone, acl_perm_t perm) { return acl_get_perm(deny, perm) || acl_get_perm(everyone, perm); } /* Returns no of possible fsal_ace entries from a given posix_acl */ int ace_count(acl_t acl) { int ret; ret = acl_entries(acl); if (ret < 0) return 0; return ret; } /* * It traverse entire list of entries for a posix acl and finds ACL entry which * corresponds to a given tag and id * * On success , it returns acl entry otherwise it returns NULL */ acl_entry_t find_entry(acl_t acl, acl_tag_t tag, unsigned int id) { acl_entry_t entry; acl_tag_t entryTag; int ent, ret; if (!acl) return NULL; for (ent = ACL_FIRST_ENTRY;; ent = ACL_NEXT_ENTRY) { ret = acl_get_entry(acl, ent, &entry); if (ret == -1) { LogWarn(COMPONENT_FSAL, "acl_get entry failed errno %d", errno); } if (ret == 0 || ret == -1) return NULL; if (acl_get_tag_type(entry, &entryTag) == -1) { LogWarn(COMPONENT_FSAL, "No entry tag for ACL Entry"); continue; } if (tag == entryTag) { if (tag == ACL_USER || tag == ACL_GROUP) if (id != posix_acl_get_uid(entry)) continue; break; } } return entry; } /* * It tries to find out whether an entry is present in posix acl for the given * (tag, id) tuple and returns it. If not , it will create a new entry for * given (tag, id). * * On success , it returns acl entry otherwise it returns NULL */ acl_entry_t get_entry(acl_t acl, acl_tag_t tag, unsigned int id) { acl_entry_t entry; int ret; if (!acl) return NULL; entry = find_entry(acl, tag, id); if (!entry) { ret = acl_create_entry(&acl, &entry); if (ret) { LogMajor(COMPONENT_FSAL, "Cannot create entry"); return NULL; } ret = acl_set_tag_type(entry, tag); if (ret) LogWarn(COMPONENT_FSAL, "Cannot set tag for Entry"); if (tag == ACL_USER || tag == ACL_GROUP) { ret = acl_set_qualifier(entry, &id); if (ret) { LogWarn(COMPONENT_FSAL, "Failed to set id"); return NULL; } } } return entry; } /* * @brief convert POSIX ACL into an equivalent FSAL ACL * * @param[in] p_posixacl POSIX ACL * @param[in] is_dir Represents file/directory * @param[in] is_inherit Represents type of ace entry * @param[in] ace Stores the starting of fsal_acl_t * @param[out] ace Stores last ace entry in fsal_acl_t * * @returns no of entries on success and -1 on failure */ int posix_acl_2_fsal_acl(acl_t p_posixacl, bool is_dir, bool is_inherit, bool for_v4, fsal_ace_t **ace) { int ret = 0, ent, d_ent, total = 0; fsal_ace_t *pace_deny = NULL, *pace_allow = NULL; acl_t dup_acl; acl_entry_t entry, mask, other, d_entry, dup_mask; acl_tag_t tag; acl_permset_t p_permset; bool readmask = true, readother = false, readcurrent = true; bool writemask = true, writeother = false, writecurrent = true; bool executemask = true, executeother = false, executecurrent = true; if (!p_posixacl) return -1; pace_deny = *ace; pace_allow = (pace_deny + 1); /* Store the mask entry values */ mask = find_entry(p_posixacl, ACL_MASK, 0); if (mask) { ret = acl_get_permset(mask, &p_permset); if (ret) LogWarn(COMPONENT_FSAL, "Cannot retrieve permission set for the Mask Entry"); if (acl_get_perm(p_permset, ACL_READ) == 0) readmask = false; if (acl_get_perm(p_permset, ACL_WRITE) == 0) writemask = false; if (acl_get_perm(p_permset, ACL_EXECUTE) == 0) executemask = false; } other = find_entry(p_posixacl, ACL_OTHER, 0); if (other) { ret = acl_get_permset(other, &p_permset); if (ret) LogWarn(COMPONENT_FSAL, "Cannot retrieve permission set for the Mask Entry"); if (acl_get_perm(p_permset, ACL_READ) == 1) readother = true; if (acl_get_perm(p_permset, ACL_WRITE) == 1) writeother = true; if (acl_get_perm(p_permset, ACL_EXECUTE) == 1) executeother = true; } /* * * Converts each entry in posix acl into fsal_ace by filling type, flag, * perm, iflag, flag and who(uid, gid) appropriately * * Corresponding to each posix acl entry, there is a possibility of two * fsal_aces, it can either be ALLOW or DENY. The DENY added to list * depending on the permission set of other entries. * * Here both entries are created for a posix acl entry and filled up * correspondingly. Then at the end unnecessary DENY entries are removed * from the list. */ for (ent = ACL_FIRST_ENTRY;; ent = ACL_NEXT_ENTRY) { ret = acl_get_entry(p_posixacl, ent, &entry); if (ret == 0 || ret == -1) { LogDebug(COMPONENT_FSAL, "No more ACL entries remaining"); break; } if (acl_get_tag_type(entry, &tag) == -1) { LogWarn(COMPONENT_FSAL, "No entry tag for ACL Entry"); continue; } /* For NFSv4, the mask is not converted to a fsal_acl entry, * so skip it. The mask is retrieved above and used to set * variables readmask, writemask and executemask. These are * then used below to modify other entries. */ if ((tag == ACL_MASK) && for_v4) continue; pace_deny->type = FSAL_ACE_TYPE_DENY; pace_allow->type = FSAL_ACE_TYPE_ALLOW; if (is_inherit) pace_allow->flag = pace_deny->flag = FSAL_ACE_FLAG_INHERIT; else pace_allow->flag = pace_deny->flag = 0; /* Finding uid for the fsal_acl entry */ switch (tag) { case ACL_USER_OBJ: pace_allow->who.uid = pace_deny->who.uid = FSAL_ACE_SPECIAL_OWNER; pace_allow->iflag = pace_deny->iflag = FSAL_ACE_IFLAG_SPECIAL_ID; break; case ACL_GROUP_OBJ: pace_allow->who.uid = pace_deny->who.uid = FSAL_ACE_SPECIAL_GROUP; pace_allow->iflag = pace_deny->iflag = FSAL_ACE_IFLAG_SPECIAL_ID; break; case ACL_OTHER: pace_allow->who.uid = pace_deny->who.uid = FSAL_ACE_SPECIAL_EVERYONE; pace_allow->iflag = pace_deny->iflag = FSAL_ACE_IFLAG_SPECIAL_ID; break; case ACL_USER: pace_allow->who.uid = pace_deny->who.uid = posix_acl_get_uid(entry); break; case ACL_GROUP: pace_allow->who.gid = pace_deny->who.gid = posix_acl_get_gid(entry); pace_allow->flag = pace_deny->flag |= FSAL_ACE_FLAG_GROUP_ID; break; case ACL_MASK: pace_allow->who.uid = pace_deny->who.uid = FSAL_ACE_SPECIAL_MASK; pace_allow->iflag = pace_deny->iflag = FSAL_ACE_IFLAG_SPECIAL_ID; break; default: LogWarn(COMPONENT_FSAL, "Invalid tag for the acl"); } /* * * Finding permission set for the fsal_acl ALLOW entry. * Conversion purely is based on * http://tools.ietf.org/html/draft-ietf-nfsv4-acl-mapping-05 * */ /* * * Unconditionally all ALLOW ACL Entry should have these * permissions * */ pace_allow->perm = FSAL_ACE_PERM_SET_DEFAULT; pace_deny->perm = 0; ret = acl_get_permset(entry, &p_permset); if (ret) { LogWarn(COMPONENT_FSAL, "Cannot retrieve permission set for the ACL Entry"); continue; } /* * * Consider Mask bits only for ACL_USER, ACL_GROUP, * ACL_GROUP_OBJ entries * */ if (acl_get_perm(p_permset, ACL_READ)) { if (tag == ACL_USER_OBJ || tag == ACL_OTHER || readmask) pace_allow->perm |= FSAL_ACE_PERM_READ_DATA; if ((tag == ACL_USER || tag == ACL_GROUP || tag == ACL_GROUP_OBJ) && (!readmask)) pace_allow->iflag |= FSAL_ACE_FLAG_MASK_READ_DENY; } else readcurrent = false; if (acl_get_perm(p_permset, ACL_WRITE)) { if (tag == ACL_USER_OBJ || tag == ACL_OTHER || writemask) pace_allow->perm |= FSAL_ACE_PERM_SET_DEFAULT_WRITE; if (tag == ACL_USER_OBJ) pace_allow->perm |= FSAL_ACE_PERM_SET_OWNER_WRITE; if (is_dir) pace_allow->perm |= FSAL_ACE_PERM_DELETE_CHILD; if ((tag == ACL_USER || tag == ACL_GROUP || tag == ACL_GROUP_OBJ) && (!writemask)) pace_allow->iflag |= FSAL_ACE_FLAG_MASK_WRITE_DENY; } else writecurrent = false; if (acl_get_perm(p_permset, ACL_EXECUTE)) { if (tag == ACL_USER_OBJ || tag == ACL_OTHER || executemask) pace_allow->perm |= FSAL_ACE_PERM_EXECUTE; if ((tag == ACL_USER || tag == ACL_GROUP || tag == ACL_GROUP_OBJ) && (!executemask)) pace_allow->iflag |= FSAL_ACE_FLAG_MASK_EXECUTE_DENY; } else executecurrent = false; /* * * Filling up permission set for DENY entries based on ALLOW * entries , if it is applicable. * If the tag is ACL_USER_OBJ or ACL_USER then all later posix * acl entries should be considered. * If the tag is either ACL_GROUP_OBJ or ACL_GROUP then consider * only ACL_OTHER. */ if (tag == ACL_USER_OBJ || tag == ACL_USER) { dup_acl = acl_dup(p_posixacl); /* * Do not consider ACL_MASK entry in the following loop */ if (mask) { dup_mask = find_entry(dup_acl, ACL_MASK, 0); if (dup_mask) acl_delete_entry(dup_acl, dup_mask); } if (tag == ACL_USER_OBJ) { d_entry = find_entry(dup_acl, ACL_USER_OBJ, 0); ret = acl_get_entry(dup_acl, ACL_NEXT_ENTRY, &d_entry); if (ret == 0 || ret == -1) { LogDebug( COMPONENT_FSAL, "No more ACL entries remaining"); acl_free(dup_acl); break; } } else d_entry = find_entry(dup_acl, ACL_GROUP_OBJ, 0); for (d_ent = ACL_NEXT_ENTRY;; d_ent = ACL_NEXT_ENTRY) { ret = acl_get_permset(d_entry, &p_permset); if (ret) { LogWarn(COMPONENT_FSAL, "Cannot retrieve permission set"); continue; } if (!readcurrent && acl_get_perm(p_permset, ACL_READ)) pace_deny->perm |= FSAL_ACE_PERM_READ_DATA; if (!writecurrent && acl_get_perm(p_permset, ACL_WRITE)) { pace_deny->perm |= FSAL_ACE_PERM_SET_DEFAULT_WRITE; if (tag == ACL_USER_OBJ) pace_deny->perm |= FSAL_ACE_PERM_SET_OWNER_WRITE; if (is_dir) pace_deny->perm |= FSAL_ACE_PERM_DELETE_CHILD; } if (!executecurrent && acl_get_perm(p_permset, ACL_EXECUTE)) pace_deny->perm |= FSAL_ACE_PERM_EXECUTE; ret = acl_get_entry(dup_acl, d_ent, &d_entry); if (ret == 0 || ret == -1) { LogDebug( COMPONENT_FSAL, "No more ACL entries remaining"); break; } } acl_free(dup_acl); } else if (tag == ACL_GROUP_OBJ || tag == ACL_GROUP) { if (!readcurrent && readother) pace_deny->perm |= FSAL_ACE_PERM_READ_DATA; if (!writecurrent && writeother) { pace_deny->perm |= FSAL_ACE_PERM_SET_DEFAULT_WRITE; if (is_dir) pace_deny->perm |= FSAL_ACE_PERM_DELETE_CHILD; } if (!executecurrent && executeother) pace_deny->perm |= FSAL_ACE_PERM_EXECUTE; } readcurrent = writecurrent = executecurrent = true; /* Removing DENY entries if it is not present */ if (pace_deny->perm == 0) { *pace_deny = *pace_allow; memset(pace_allow, 0, sizeof(fsal_ace_t)); total += 1; pace_deny += 1; pace_allow += 1; } else { total += 2; pace_deny += 2; pace_allow += 2; } } *ace = pace_allow - 1; /* Returns last entry in the list */ return total; /* Returning no of entries in the list */ } /* * @brief convert FSAL ACL into an equivalent POSIX ACL * * @param[in] p_fsalacl FSAL ACL * @param[in] type Represents type of posix acl( ACCESS/DEFAULT ) * * @return acl_t structure */ acl_t fsal_acl_2_posix_acl(fsal_acl_t *p_fsalacl, acl_type_t type) { int ret = 0, i; fsal_ace_t *f_ace; acl_t allow_acl, deny_acl; acl_entry_t a_entry, d_entry; acl_permset_t a_permset, e_a_permset, d_permset, e_d_permset; acl_tag_t tag = -1; unsigned int id; bool mask = false, mask_set = false; bool deny_e_r = false, deny_e_w = false, deny_e_x = false; if (p_fsalacl == NULL) return NULL; /* * * Check whether ace list contains any inherited entries, if not then * returns NULL. * */ if (type == ACL_TYPE_DEFAULT) { for (f_ace = p_fsalacl->aces; f_ace < p_fsalacl->aces + p_fsalacl->naces; f_ace++) { if (is_ace_valid_for_inherited_acl_entry(f_ace)) ret++; } if (ret == 0) return NULL; } /* * FIXME: Always allocating with maximum possible value of acl entries, * there is a possibility of memory leak */ allow_acl = acl_init(p_fsalacl->naces + 1); deny_acl = acl_init(p_fsalacl->naces + 1); /* first convert ACE EVERYONE@ to ACL_OTHER */ ret = acl_create_entry(&allow_acl, &a_entry); if (ret) { LogMajor(COMPONENT_FSAL, "Cannot create entry for other"); return NULL; } ret = acl_set_tag_type(a_entry, ACL_OTHER); if (ret) LogWarn(COMPONENT_FSAL, "Cannot set tag for ACL Entry"); ret = acl_get_permset(a_entry, &e_a_permset); if (ret) { LogWarn(COMPONENT_FSAL, "Cannot retrieve permission set"); } /* * Deny entry for @EVERYONE created only because it will ease the * manipulation of other acl entries. It will be updated only when deny * entry for @EVERYONE is encountered */ ret = acl_create_entry(&deny_acl, &d_entry); if (ret) LogMajor(COMPONENT_FSAL, "Cannot create entry for other"); ret = acl_set_tag_type(d_entry, ACL_OTHER); if (ret) LogWarn(COMPONENT_FSAL, "Cannot set tag for ACL Entry"); ret = acl_get_permset(d_entry, &e_d_permset); if (ret) { LogWarn(COMPONENT_FSAL, "Cannot retrieve permission set"); } for (f_ace = p_fsalacl->aces; f_ace < p_fsalacl->aces + p_fsalacl->naces; f_ace++) { if (IS_FSAL_ACE_SPECIAL_EVERYONE(*f_ace)) { if ((type == ACL_TYPE_ACCESS && !is_ace_valid_for_effective_acl_entry(f_ace)) || (type == ACL_TYPE_DEFAULT && !is_ace_valid_for_inherited_acl_entry(f_ace))) continue; if (IS_FSAL_ACE_DENY(*f_ace)) { if (IS_FSAL_ACE_READ_DATA(*f_ace)) deny_e_r = true; if (IS_FSAL_ACE_WRITE_DATA(*f_ace)) deny_e_w = true; if (IS_FSAL_ACE_EXECUTE(*f_ace)) deny_e_x = true; } else if (IS_FSAL_ACE_ALLOW(*f_ace)) { if (IS_FSAL_ACE_READ_DATA(*f_ace) && !deny_e_r) acl_add_perm(e_a_permset, ACL_READ); if (IS_FSAL_ACE_WRITE_DATA(*f_ace) && !deny_e_w) acl_add_perm(e_a_permset, ACL_WRITE); if (IS_FSAL_ACE_EXECUTE(*f_ace) && !deny_e_x) acl_add_perm(e_a_permset, ACL_EXECUTE); } } } /* * It is mandatory to have acl entries for ACL_USER_OBJ and * ACL_GROUP_OBJ */ ret = acl_create_entry(&allow_acl, &a_entry); if (ret) { LogMajor(COMPONENT_FSAL, "Cannot create entry for other"); return NULL; } ret = acl_set_tag_type(a_entry, ACL_USER_OBJ); if (ret) LogWarn(COMPONENT_FSAL, "Cannot set tag for ACL Entry"); ret = acl_create_entry(&allow_acl, &a_entry); if (ret) { LogMajor(COMPONENT_FSAL, "Cannot create entry for other"); return NULL; } ret = acl_set_tag_type(a_entry, ACL_GROUP_OBJ); if (ret) LogWarn(COMPONENT_FSAL, "Cannot set tag for ACL Entry"); /** @todo: Anonymous users/groups (id = -1) should handle properly */ /* * It uses two posix acl - allow_acl and deny_acl which represents ALLOW * and DENY aces respectively. They are filled according to the order in * fsal_acl list. The allow acl is build based on ALLOW ace, @EVERYONE * ace and deny acl. The permset for allow acl entry is constructed in * such a way that it will contain all permissions(READ,WRITE,EXECUTE) * of ALLOW aces plus EVERYONE which is not denied by the corresponding * deny acl entry * * At last allow_acl is returned and deny_acl is ignored. */ for (f_ace = p_fsalacl->aces; f_ace < p_fsalacl->aces + p_fsalacl->naces; f_ace++) { if ((type == ACL_TYPE_ACCESS && !is_ace_valid_for_effective_acl_entry(f_ace)) || (type == ACL_TYPE_DEFAULT && !is_ace_valid_for_inherited_acl_entry(f_ace))) continue; if (IS_FSAL_ACE_SPECIAL_ID(*f_ace)) { id = 0; if (IS_FSAL_ACE_SPECIAL_OWNER(*f_ace)) tag = ACL_USER_OBJ; if (IS_FSAL_ACE_SPECIAL_GROUP(*f_ace)) tag = ACL_GROUP_OBJ; if (IS_FSAL_ACE_SPECIAL_MASK(*f_ace)) tag = ACL_MASK; } else { id = GET_FSAL_ACE_WHO(*f_ace); if (IS_FSAL_ACE_GROUP_ID(*f_ace)) tag = ACL_GROUP; else tag = ACL_USER; /* * Mask entry will be created only if it * contains user or group entry */ mask = true; } if (IS_FSAL_ACE_SPECIAL_EVERYONE(*f_ace)) { if (IS_FSAL_ACE_DENY(*f_ace)) { if (deny_e_r) acl_add_perm(e_d_permset, ACL_READ); if (deny_e_w) acl_add_perm(e_d_permset, ACL_WRITE); if (deny_e_x) acl_add_perm(e_d_permset, ACL_EXECUTE); } continue; } a_entry = get_entry(allow_acl, tag, id); d_entry = get_entry(deny_acl, tag, id); ret = acl_get_permset(d_entry, &d_permset); if (ret) { LogWarn(COMPONENT_FSAL, "Cannot retrieve permission set"); } if (IS_FSAL_ACE_DENY(*f_ace)) { if (IS_FSAL_ACE_READ_DATA(*f_ace)) acl_add_perm(d_permset, ACL_READ); if (IS_FSAL_ACE_WRITE_DATA(*f_ace)) acl_add_perm(d_permset, ACL_WRITE); if (IS_FSAL_ACE_EXECUTE(*f_ace)) acl_add_perm(d_permset, ACL_EXECUTE); } ret = acl_get_permset(a_entry, &a_permset); if (ret) { LogWarn(COMPONENT_FSAL, "Cannot retrieve permission set"); } if (IS_FSAL_ACE_SPECIAL_MASK(*f_ace)) { if (IS_FSAL_ACE_ALLOW(*f_ace)) { if IS_FSAL_ACE_READ_DATA (*f_ace) acl_add_perm(a_permset, ACL_READ); if IS_FSAL_ACE_WRITE_DATA (*f_ace) acl_add_perm(a_permset, ACL_WRITE); if IS_FSAL_ACE_EXECUTE (*f_ace) acl_add_perm(a_permset, ACL_EXECUTE); } mask_set = true; continue; } if ((isallow(f_ace, e_a_permset, ACL_READ) && !isdeny(d_permset, e_d_permset, ACL_READ)) || IS_FSAL_ACE_IFLAG(*f_ace, FSAL_ACE_FLAG_MASK_READ_DENY)) acl_add_perm(a_permset, ACL_READ); if ((isallow(f_ace, e_a_permset, ACL_WRITE) && !isdeny(d_permset, e_d_permset, ACL_WRITE)) || IS_FSAL_ACE_IFLAG(*f_ace, FSAL_ACE_FLAG_MASK_WRITE_DENY)) acl_add_perm(a_permset, ACL_WRITE); if ((isallow(f_ace, e_a_permset, ACL_EXECUTE) && !isdeny(d_permset, e_d_permset, ACL_EXECUTE)) || IS_FSAL_ACE_IFLAG(*f_ace, FSAL_ACE_FLAG_MASK_EXECUTE_DENY)) acl_add_perm(a_permset, ACL_EXECUTE); } if (!mask_set && mask) { ret = acl_calc_mask(&allow_acl); if (ret) LogWarn(COMPONENT_FSAL, "Cannot calculate mask for posix"); } /* A valid acl_t should have only one entry for ACL_USER_OBJ, * ACL_GROUP_OBJ, ACL_OTHER and ACL_MASK is required only if * ACL_USER or ACL_GROUP exists */ ret = acl_check(allow_acl, &i); if (ret) { if (ret > 0) { LogWarn(COMPONENT_FSAL, "Error converting ACL: %s at entry no %d", acl_error(ret), i); } } if (isDebug(COMPONENT_FSAL)) { char *acl_str; acl_str = acl_to_any_text(allow_acl, NULL, ',', TEXT_ABBREVIATE | TEXT_NUMERIC_IDS); LogDebug(COMPONENT_FSAL, "posix acl = %s ", acl_str); acl_free(acl_str); } if (deny_acl) acl_free(deny_acl); return allow_acl; } /* * @brief Calculate the ACL xattr size by quantity of ACL entries * * @param[in] count Quantity of ACL entries * * @return ACL xattr size */ size_t posix_acl_xattr_size(int count) { return sizeof(struct acl_ea_header) + count * sizeof(struct acl_ea_entry); } /* * @brief Calculate the quantity of ACL entries by xattr size * * @param[in] size Length of extended attribute * * @return Quantity of ACL entries on success, -1 on failure. */ int posix_acl_entries_count(size_t size) { if (size < sizeof(struct acl_ea_header)) return -1; size -= sizeof(struct acl_ea_header); if (size % sizeof(struct acl_ea_entry)) return -1; return size / sizeof(struct acl_ea_entry); } /* * @brief Convert extended attribute to POSIX ACL * * @param[in] value Extended attribute * @param[in] size Size of the extended attribute * * @return Posix ACL on success, NULL on failure. */ acl_t xattr_2_posix_acl(const struct acl_ea_header *ea_header, size_t size) { const struct acl_ea_entry *ea_entry = &ea_header->a_entries[0], *end; int count; int ret = 0; acl_t acl = NULL; acl_entry_t acl_entry; acl_tag_t tag; acl_permset_t permset; uid_t uid; gid_t gid; count = posix_acl_entries_count(size); if (count < 0) { LogMajor(COMPONENT_FSAL, "Invalid parameter: size = %d", (int)size); return NULL; } if (count == 0) return NULL; if (ea_header->a_version != htole32(ACL_EA_VERSION)) { LogMajor(COMPONENT_FSAL, "ACL ea version is inconsistent"); return NULL; } acl = acl_init(count); if (!acl) { LogMajor(COMPONENT_FSAL, "Failed to ACL INIT: count = %d", count); return NULL; } for (end = ea_entry + count; ea_entry != end; ea_entry++) { ret = acl_create_entry(&acl, &acl_entry); if (ret) { LogMajor(COMPONENT_FSAL, "Failed to create acl entry"); goto out; } tag = le16toh(ea_entry->e_tag); ret = acl_set_tag_type(acl_entry, tag); if (ret) { LogMajor(COMPONENT_FSAL, "Failed to set acl tag type"); goto out; } ret = acl_get_permset(acl_entry, &permset); if (ret) { LogWarn(COMPONENT_FSAL, "Failed to get acl permset"); goto out; } ret = acl_add_perm(permset, le16toh(ea_entry->e_perm)); if (ret) { LogWarn(COMPONENT_FSAL, "Failed to add acl permission"); goto out; } switch (tag) { case ACL_USER_OBJ: case ACL_GROUP_OBJ: case ACL_MASK: case ACL_OTHER: break; case ACL_USER: uid = le32toh(ea_entry->e_id); ret = acl_set_qualifier(acl_entry, &uid); if (ret) { LogMajor(COMPONENT_FSAL, "Failed to set uid"); goto out; } break; case ACL_GROUP: gid = le32toh(ea_entry->e_id); ret = acl_set_qualifier(acl_entry, &gid); if (ret) { LogMajor(COMPONENT_FSAL, "Failed to set gid"); goto out; } break; default: goto out; } } if (isDebug(COMPONENT_FSAL)) { char *acl_str; acl_str = acl_to_any_text(acl, NULL, ',', TEXT_ABBREVIATE | TEXT_NUMERIC_IDS); LogDebug(COMPONENT_FSAL, "posix acl = %s ", acl_str); acl_free(acl_str); } return acl; out: if (acl) { acl_free((void *)acl); } return NULL; } /* * @brief Convert POSIX ACL to extended attribute * * @param[in] acl Posix ACL * @param[in] size Size of the extended attribute buffer * @param[out] buf Buffer of the extended attribute * * @return Real size of extended attribute on success, -1 on failure. */ int posix_acl_2_xattr(acl_t acl, void *buf, size_t size) { struct acl_ea_header *ea_header = buf; struct acl_ea_entry *ea_entry; acl_entry_t acl_entry; acl_tag_t tag; acl_permset_t permset; int real_size, count, entry_id; int ret = 0; if (isDebug(COMPONENT_FSAL)) { char *acl_str; acl_str = acl_to_any_text(acl, NULL, ',', TEXT_ABBREVIATE | TEXT_NUMERIC_IDS); LogDebug(COMPONENT_FSAL, "posix acl = %s ", acl_str); acl_free(acl_str); } count = acl_entries(acl); real_size = sizeof(*ea_header) + count * sizeof(*ea_entry); if (!buf) return real_size; if (real_size > size) return -1; ea_entry = (void *)(ea_header + 1); ea_header->a_version = htole32(ACL_EA_VERSION); for (entry_id = ACL_FIRST_ENTRY;; entry_id = ACL_NEXT_ENTRY, ea_entry++) { ret = acl_get_entry(acl, entry_id, &acl_entry); if (ret == 0 || ret == -1) { LogDebug(COMPONENT_FSAL, "No more ACL entries remaining"); break; } if (acl_get_tag_type(acl_entry, &tag) == -1) { LogWarn(COMPONENT_FSAL, "No entry tag for ACL Entry"); continue; } ret = acl_get_permset(acl_entry, &permset); if (ret) { LogWarn(COMPONENT_FSAL, "Cannot retrieve permission set for the ACL Entry"); continue; } ea_entry->e_tag = htole16(tag); ea_entry->e_perm = 0; if (acl_get_perm(permset, ACL_READ)) ea_entry->e_perm |= htole16(ACL_READ); if (acl_get_perm(permset, ACL_WRITE)) ea_entry->e_perm |= htole16(ACL_WRITE); if (acl_get_perm(permset, ACL_EXECUTE)) ea_entry->e_perm |= htole16(ACL_EXECUTE); switch (tag) { case ACL_USER: ea_entry->e_id = htole32(posix_acl_get_uid(acl_entry)); break; case ACL_GROUP: ea_entry->e_id = htole32(posix_acl_get_gid(acl_entry)); break; default: ea_entry->e_id = htole32(ACL_UNDEFINED_ID); break; } } return real_size; } nfs-ganesha-6.5/src/FSAL_UP/000077500000000000000000000000001473756622300155155ustar00rootroot00000000000000nfs-ganesha-6.5/src/FSAL_UP/fsal_up_async.c000066400000000000000000000310071473756622300205100ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @addtogroup fsal_up * @{ */ /** * @file fsal_up_async.c * @author Adam C. Emerson * @brief Asynchrony wrappers for FSAL Upcall system * * This is not the most elegant design in history, but should be * reasonably efficient. At present, we have to copy the key supplied * rather than saving a pointer. Once version 2.1 comes out and we * can from the FSAL object to the cache entry with container of, * we'll be able to jump up, grab a ref on the cache entry, and just * store the pointer. * * Every async call requires one allocation and one queue into the * thread fridge. We make the thread fridge a parameter, so an FSAL * that's expecting to shoot out lots and lots of upcalls can make one * holding several threads wide. * * Every async call takes a callback function and an argument, to * allow it to receive errors. The callback function may be NULL if * the caller doesn't care. This doesn't affect methods that may be * called asynchronously by upcall handlers like @c layoutreturn. * * Every async call takes a reference on the export, the queued action * returns it after execution. * * Every async call returns 0 on success and a POSIX error code on error. */ #include "config.h" #include #include #include #include "nfs_core.h" #include "log.h" #include "fsal.h" #include "fsal_up.h" #include "fsal_convert.h" #include "sal_functions.h" #include "pnfs_utils.h" /* Invalidate */ struct invalidate_args { const struct fsal_up_vector *vec; struct gsh_buffdesc obj; uint32_t flags; void (*cb)(void *, fsal_status_t); void *cb_arg; char key[]; }; static void queue_invalidate(struct fridgethr_context *ctx) { struct invalidate_args *args = ctx->arg; fsal_status_t status; status = args->vec->up_fsal_export->up_ops->invalidate( args->vec, &args->obj, args->flags); if (args->cb) args->cb(args->cb_arg, status); gsh_free(args); } fsal_status_t up_async_invalidate(struct fridgethr *fr, const struct fsal_up_vector *vec, struct gsh_buffdesc *obj, uint32_t flags, void (*cb)(void *, fsal_status_t), void *cb_arg) { struct invalidate_args *args = NULL; int rc = 0; args = gsh_malloc(sizeof(struct invalidate_args) + obj->len); args->vec = vec; args->flags = flags; args->cb = cb; args->cb_arg = cb_arg; memcpy(args->key, obj->addr, obj->len); args->obj.addr = args->key; args->obj.len = obj->len; rc = fridgethr_submit(fr, queue_invalidate, args); if (rc != 0) gsh_free(args); return fsalstat(posix2fsal_error(rc), rc); } /* Update */ struct update_args { const struct fsal_up_vector *vec; struct gsh_buffdesc obj; struct fsal_attrlist attr; uint32_t flags; void (*cb)(void *, fsal_status_t); void *cb_arg; char key[]; }; static void queue_update(struct fridgethr_context *ctx) { struct update_args *args = ctx->arg; fsal_status_t status; status = args->vec->up_fsal_export->up_ops->update( args->vec, &args->obj, &args->attr, args->flags); if (args->cb) args->cb(args->cb_arg, status); gsh_free(args); } fsal_status_t up_async_update(struct fridgethr *fr, const struct fsal_up_vector *vec, struct gsh_buffdesc *obj, struct fsal_attrlist *attr, uint32_t flags, void (*cb)(void *, fsal_status_t), void *cb_arg) { struct update_args *args = NULL; int rc = 0; args = gsh_malloc(sizeof(struct update_args) + obj->len); args->vec = vec; args->attr = *attr; args->flags = flags; args->cb = cb; args->cb_arg = cb_arg; memcpy(args->key, obj->addr, obj->len); args->obj.addr = args->key; args->obj.len = obj->len; rc = fridgethr_submit(fr, queue_update, args); if (rc != 0) gsh_free(args); return fsalstat(posix2fsal_error(rc), rc); } /* Lock grant */ struct lock_grant_args { const struct fsal_up_vector *vec; struct gsh_buffdesc file; void *owner; fsal_lock_param_t lock_param; void (*cb)(void *, state_status_t); void *cb_arg; char key[]; }; static void queue_lock_grant(struct fridgethr_context *ctx) { struct lock_grant_args *args = ctx->arg; state_status_t status; status = args->vec->up_fsal_export->up_ops->lock_grant( args->vec, &args->file, args->owner, &args->lock_param); if (args->cb) args->cb(args->cb_arg, status); gsh_free(args); } fsal_status_t up_async_lock_grant(struct fridgethr *fr, const struct fsal_up_vector *vec, struct gsh_buffdesc *file, void *owner, fsal_lock_param_t *lock_param, void (*cb)(void *, state_status_t), void *cb_arg) { struct lock_grant_args *args = NULL; int rc = 0; args = gsh_malloc(sizeof(struct lock_grant_args) + file->len); args->vec = vec; args->owner = owner; args->lock_param = *lock_param; args->cb = cb; args->cb_arg = cb_arg; memcpy(args->key, file->addr, file->len); args->file.addr = args->key; args->file.len = file->len; rc = fridgethr_submit(fr, queue_lock_grant, args); if (rc != 0) gsh_free(args); return fsalstat(posix2fsal_error(rc), rc); } /* Lock avail */ struct lock_avail_args { const struct fsal_up_vector *vec; struct gsh_buffdesc file; void *owner; fsal_lock_param_t lock_param; void (*cb)(void *, state_status_t); void *cb_arg; char key[]; }; static void queue_lock_avail(struct fridgethr_context *ctx) { struct lock_avail_args *args = ctx->arg; state_status_t status; status = args->vec->up_fsal_export->up_ops->lock_avail( args->vec, &args->file, args->owner, &args->lock_param); if (args->cb) args->cb(args->cb_arg, status); gsh_free(args); } fsal_status_t up_async_lock_avail(struct fridgethr *fr, const struct fsal_up_vector *vec, struct gsh_buffdesc *file, void *owner, fsal_lock_param_t *lock_param, void (*cb)(void *, state_status_t), void *cb_arg) { struct lock_avail_args *args = NULL; int rc = 0; args = gsh_malloc(sizeof(struct lock_avail_args) + file->len); args->vec = vec; args->owner = owner; args->lock_param = *lock_param; args->cb = cb; args->cb_arg = cb_arg; memcpy(args->key, file->addr, file->len); args->file.addr = args->key; args->file.len = file->len; rc = fridgethr_submit(fr, queue_lock_avail, args); if (rc != 0) gsh_free(args); return fsalstat(posix2fsal_error(rc), rc); } /* Layoutrecall */ struct layoutrecall_args { const struct fsal_up_vector *vec; struct gsh_buffdesc handle; layouttype4 layout_type; bool changed; struct pnfs_segment segment; void *cookie; struct layoutrecall_spec spec; void (*cb)(void *, state_status_t); void *cb_arg; char data[]; }; static void queue_layoutrecall(struct fridgethr_context *ctx) { struct layoutrecall_args *args = ctx->arg; state_status_t status; status = args->vec->up_fsal_export->up_ops->layoutrecall( args->vec, &args->handle, args->layout_type, args->changed, &args->segment, args->cookie, args->spec.how == layoutrecall_not_specced ? NULL : &args->spec); if (args->cb) args->cb(args->cb_arg, status); gsh_free(args); } fsal_status_t up_async_layoutrecall(struct fridgethr *fr, const struct fsal_up_vector *vec, struct gsh_buffdesc *handle, layouttype4 layout_type, bool changed, const struct pnfs_segment *segment, void *cookie, struct layoutrecall_spec *spec, void (*cb)(void *, state_status_t), void *cb_arg) { struct layoutrecall_args *args = NULL; int rc = 0; args = gsh_malloc(sizeof(struct layoutrecall_args) + handle->len); args->vec = vec; args->cb = cb; args->cb_arg = cb_arg; memcpy(args->data, handle->addr, handle->len); args->handle.addr = args->data; args->handle.len = handle->len; args->layout_type = layout_type; args->changed = changed; args->segment = *segment; args->cookie = cookie; if (spec) args->spec = *spec; else args->spec.how = layoutrecall_not_specced; rc = fridgethr_submit(fr, queue_layoutrecall, args); if (rc != 0) gsh_free(args); return fsalstat(posix2fsal_error(rc), rc); } /* Notify Device */ struct notify_device_args { const struct fsal_up_vector *vec; notify_deviceid_type4 notify_type; layouttype4 layout_type; struct pnfs_deviceid devid; bool immediate; void (*cb)(void *, state_status_t); void *cb_arg; char data[]; }; static void queue_notify_device(struct fridgethr_context *ctx) { struct notify_device_args *args = ctx->arg; state_status_t status; status = args->vec->up_fsal_export->up_ops->notify_device( args->notify_type, args->layout_type, args->devid, args->immediate); if (args->cb) args->cb(args->cb_arg, status); gsh_free(args); } fsal_status_t up_async_notify_device(struct fridgethr *fr, const struct fsal_up_vector *vec, notify_deviceid_type4 notify_type, layouttype4 layout_type, struct pnfs_deviceid *devid, bool immediate, void (*cb)(void *, state_status_t), void *cb_arg) { struct notify_device_args *args = NULL; int rc = 0; args = gsh_malloc(sizeof(struct notify_device_args)); args->vec = vec; args->cb = cb; args->cb_arg = cb_arg; args->notify_type = notify_type; args->layout_type = layout_type; args->devid = *devid; args->immediate = immediate; rc = fridgethr_submit(fr, queue_notify_device, args); if (rc != 0) gsh_free(args); return fsalstat(posix2fsal_error(rc), rc); } /* CB_GETATTR */ struct cbgetattr_args { struct fsal_obj_handle *obj; nfs_client_id_t *clid; struct gsh_export *ctx_export; }; static void queue_cbgetattr(struct fridgethr_context *ctx) { struct cbgetattr_args *args = ctx->arg; (void)cbgetattr_impl(args->obj, args->clid, args->ctx_export); args->obj->obj_ops->put_ref(args->obj); dec_client_id_ref(args->clid); put_gsh_export(args->ctx_export); gsh_free(args); } int async_cbgetattr(struct fridgethr *fr, struct fsal_obj_handle *obj, nfs_client_id_t *client) { int rc = 0; struct cbgetattr_args *args = NULL; args = gsh_malloc(sizeof(struct cbgetattr_args)); /* get a ref to prevent races when callback is called too late */ obj->obj_ops->get_ref(obj); inc_client_id_ref(client); args->obj = obj; args->clid = client; args->ctx_export = op_ctx->ctx_export; get_gsh_export_ref(args->ctx_export); rc = fridgethr_submit(fr, queue_cbgetattr, args); if (rc != 0) { obj->obj_ops->put_ref(obj); dec_client_id_ref(client); put_gsh_export(args->ctx_export); gsh_free(args); } return rc; } /* Delegrecall */ struct delegrecall_args { const struct fsal_up_vector *vec; struct gsh_buffdesc handle; void (*cb)(void *, state_status_t); void *cb_arg; char key[]; }; static void queue_delegrecall(struct fridgethr_context *ctx) { struct fsal_obj_handle *obj = ctx->arg; (void)delegrecall_impl(obj); obj->obj_ops->put_ref(obj); } int async_delegrecall(struct fridgethr *fr, struct fsal_obj_handle *obj) { int rc; /* get a ref to prevent races when delegrecall is called too late */ obj->obj_ops->get_ref(obj); rc = fridgethr_submit(fr, queue_delegrecall, obj); if (rc != 0) obj->obj_ops->put_ref(obj); return rc; } static void up_queue_delegrecall(struct fridgethr_context *ctx) { struct delegrecall_args *args = ctx->arg; state_status_t status; status = args->vec->up_fsal_export->up_ops->delegrecall(args->vec, &args->handle); if (args->cb) args->cb(args->cb_arg, status); gsh_free(args); } /* XXX dang refcount exports in these? */ fsal_status_t up_async_delegrecall(struct fridgethr *fr, const struct fsal_up_vector *vec, struct gsh_buffdesc *handle, void (*cb)(void *, state_status_t), void *cb_arg) { struct delegrecall_args *args = NULL; int rc = 0; args = gsh_malloc(sizeof(struct delegrecall_args) + handle->len); args->vec = vec; args->cb = cb; args->cb_arg = cb_arg; memcpy(args->key, handle->addr, handle->len); args->handle.addr = args->key; args->handle.len = handle->len; rc = fridgethr_submit(fr, up_queue_delegrecall, args); if (rc != 0) gsh_free(args); return fsalstat(posix2fsal_error(rc), rc); } /** @} */ nfs-ganesha-6.5/src/FSAL_UP/fsal_up_top.c000066400000000000000000001540601473756622300202020ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @addtogroup fsal_up * @{ */ /** * @file fsal_up_top.c * @author Adam C. Emerson * @brief Top level FSAL Upcall handlers */ #include "config.h" #include #include #include #include "nfs_core.h" #include "log.h" #include "fsal.h" #include "hashtable.h" #include "fsal_up.h" #include "sal_functions.h" #include "pnfs_utils.h" #include "nfs_rpc_callback.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include "delayed_exec.h" #include "export_mgr.h" #include "server_stats.h" #include "sal_data.h" struct delegrecall_context { /* Reserve lease during delegation recall */ nfs_client_id_t *drc_clid; /* Preserve the stateid we are recalling */ stateid4 drc_stateid; /* Hold a reference to the export during delegation recall */ struct gsh_export *drc_exp; }; enum recall_resp_action { DELEG_RECALL_SCHED, DELEG_RET_WAIT, REVOKE }; static int schedule_delegrevoke_check(struct delegrecall_context *ctx, uint32_t delay); static int schedule_delegrecall_task(struct delegrecall_context *ctx, uint32_t delay); /** Invalidate some or all of a cache entry and close if open * * This version should NOT be used if an FSAL supports extended * operations, instead, the FSAL may directly close the file as * necessary. * * @param[in] vec Up ops vector * @param[in] handle The handle being invalidated * @param[in] flags Flags governing invalidation * * @return FSAL status * */ static fsal_status_t invalidate_close(const struct fsal_up_vector *vec, struct gsh_buffdesc *handle, uint32_t flags) { return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** Invalidate some or all of a cache entry * * @param[in] vec Up ops vector * @param[in] handle The handle being invalidated * @param[in] flags Flags governing invalidation * * @return FSAL status * */ fsal_status_t invalidate(const struct fsal_up_vector *vec, struct gsh_buffdesc *handle, uint32_t flags) { /* No need to invalidate with no cache */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Update cached attributes * * @param[in] vec Up ops vector * @param[in] obj Key to specify object * @param[in] attr New attributes * @param[in] flags Flags to govern update * * @return FSAL status */ static fsal_status_t update(const struct fsal_up_vector *vec, struct gsh_buffdesc *obj, struct fsal_attrlist *attr, uint32_t flags) { /* No need to update with no cache */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Initiate a lock grant * * @param[in] file The file in question * @param[in] owner The lock owner * @param[in] lock_param description of the lock * * @return STATE_SUCCESS or errors. */ static state_status_t lock_grant(const struct fsal_up_vector *vec, struct gsh_buffdesc *file, void *owner, fsal_lock_param_t *lock_param) { struct fsal_obj_handle *obj; fsal_status_t status; struct fsal_export *export = vec->up_fsal_export; status = export->exp_ops.create_handle(export, file, &obj, NULL); if (FSAL_IS_ERROR(status)) return STATE_NOT_FOUND; grant_blocked_lock_upcall(obj, owner, lock_param); obj->obj_ops->put_ref(obj); return STATE_SUCCESS; } /** * @brief Signal lock availability * * @param[in] file The file in question * @param[in] owner The lock owner * @param[in] lock_param description of the lock * * @return STATE_SUCCESS or errors. */ static state_status_t lock_avail(const struct fsal_up_vector *vec, struct gsh_buffdesc *file, void *owner, fsal_lock_param_t *lock_param) { struct fsal_obj_handle *obj; fsal_status_t status; struct fsal_export *export = vec->up_fsal_export; status = export->exp_ops.create_handle(export, file, &obj, NULL); if (FSAL_IS_ERROR(status)) return STATE_NOT_FOUND; available_blocked_lock_upcall(obj, owner, lock_param); obj->obj_ops->put_ref(obj); return STATE_SUCCESS; } /* @note The st_lock MUST be held */ static void destroy_recall(struct state_layout_recall_file *recall) { if (recall == NULL) return; while (!glist_empty(&recall->state_list)) { struct recall_state_list *list_entry; /* The first entry in the queue */ list_entry = glist_first_entry(&recall->state_list, struct recall_state_list, link); dec_state_t_ref(list_entry->state); glist_del(&list_entry->link); gsh_free(list_entry); } /* Remove from entry->layoutrecall_list */ glist_del(&recall->entry_link); gsh_free(recall); } /** * @brief Create layout recall state * * This function creates the layout recall state and work list for a * LAYOUTRECALL operation on a file. * * @note the st_lock MUST be held * * @param[in,out] obj The file on which to send the recall * @param[in] type The layout type * @param[in] offset The offset of the interval to recall * @param[in] length The length of the interval to recall * @param[in] cookie The recall cookie (to be returned to the FSAL * on the final return satisfying this recall.) * @param[in] spec Lets us be fussy about what clients we send * to. May be NULL. * @param[out] recout The recall object * * @retval STATE_SUCCESS if successfully queued. * @retval STATE_INVALID_ARGUMENT if the range is zero or overflows. * @retval STATE_NOT_FOUND if no layouts satisfying the range exist. */ static state_status_t create_file_recall(struct fsal_obj_handle *obj, layouttype4 type, const struct pnfs_segment *segment, void *cookie, struct layoutrecall_spec *spec, struct state_layout_recall_file **recout) { /* True if a layout matching the request has been found */ bool found = false; /* Iterator over all states on the cache entry */ struct glist_head *state_iter = NULL; /* Error return code */ state_status_t rc = STATE_SUCCESS; /* The recall object referenced by future returns */ struct state_layout_recall_file *recall = gsh_malloc(sizeof(struct state_layout_recall_file)); glist_init(&recall->state_list); recall->entry_link.next = NULL; recall->entry_link.prev = NULL; recall->obj = obj; recall->type = type; recall->segment = *segment; recall->recall_cookie = cookie; if ((segment->length == 0) || ((segment->length != UINT64_MAX) && (segment->offset <= UINT64_MAX - segment->length))) { rc = STATE_INVALID_ARGUMENT; goto out; } glist_for_each(state_iter, &obj->state_hdl->file.list_of_states) { /* Entry in the state list */ struct recall_state_list *list_entry = NULL; /* Iterator over segments on this state */ struct glist_head *seg_iter = NULL; /* The state under examination */ state_t *s = glist_entry(state_iter, state_t, state_list); /* Does this state have a matching segment? */ bool match = false; /* referenced owner */ state_owner_t *owner = get_state_owner_ref(s); if (owner == NULL) { /* This state is going stale, skip */ continue; } if ((s->state_type != STATE_TYPE_LAYOUT) || (s->state_data.layout.state_layout_type != type)) { dec_state_owner_ref(owner); continue; } if (spec) { switch (spec->how) { case layoutrecall_howspec_exactly: if (spec->u.client != owner->so_owner.so_nfs4_owner.so_clientid) { dec_state_owner_ref(owner); continue; } break; case layoutrecall_howspec_complement: if (spec->u.client == owner->so_owner.so_nfs4_owner.so_clientid) { dec_state_owner_ref(owner); continue; } break; case layoutrecall_not_specced: break; } } dec_state_owner_ref(owner); glist_for_each(seg_iter, &s->state_data.layout.state_segments) { state_layout_segment_t *g = glist_entry(seg_iter, state_layout_segment_t, sls_state_segments); if (pnfs_segments_overlap(segment, &g->sls_segment)) match = true; } if (match) { /** * @todo This is where you would record that a * recall was initiated. The range recalled * is specified in @c segment. The clientid * is in * s->state_owner->so_owner.so_nfs4_owner.so_clientid * But you may want to ignore this location entirely. */ list_entry = gsh_malloc(sizeof(struct recall_state_list)); list_entry->state = s; glist_add_tail(&recall->state_list, &list_entry->link); /* @todo - this doesn't look right, looks like * references could be leaked */ inc_state_t_ref(s); found = true; } } if (!found) rc = STATE_NOT_FOUND; out: if (rc == STATE_SUCCESS) { glist_add_tail(&obj->state_hdl->file.layoutrecall_list, &recall->entry_link); *recout = recall; } else { /* Destroy the recall list constructed so far. */ destroy_recall(recall); } return rc; } static void layoutrecall_one_call(void *arg); /** * @brief Data used to handle the response to CB_LAYOUTRECALL */ struct layoutrecall_cb_data { char stateid_other[OTHERSIZE]; /*< "Other" part of state id */ struct pnfs_segment segment; /*< Segment to recall */ nfs_cb_argop4 arg; /*< So we don't free */ nfs_client_id_t *client; /*< The client we're calling. */ struct timespec first_recall; /*< Time of first recall */ uint32_t attempts; /*< Number of times we've recalled */ }; /** * @brief Initiate layout recall * * This function validates the recall, creates the recall object, and * sends out CB_LAYOUTRECALL messages. * * @param[in] handle Handle on which the layout is held * @param[in] layout_type The type of layout to recall * @param[in] changed Whether the layout has changed and the * client ought to finish writes through MDS * @param[in] segment Segment to recall * @param[in] cookie A cookie returned with the return that * completely satisfies a recall * @param[in] spec Lets us be fussy about what clients we send * to. May be NULL. * * @retval STATE_SUCCESS if scheduled. * @retval STATE_NOT_FOUND if no matching layouts exist. * @retval STATE_INVALID_ARGUMENT if a nonsensical layout recall has * been specified. * @retval STATE_MALLOC_ERROR if there was insufficient memory to construct the * recall state. */ state_status_t layoutrecall(const struct fsal_up_vector *vec, struct gsh_buffdesc *handle, layouttype4 layout_type, bool changed, const struct pnfs_segment *segment, void *cookie, struct layoutrecall_spec *spec) { /* Return code */ state_status_t rc = STATE_SUCCESS; /* file on which to operate */ struct fsal_obj_handle *obj = NULL; /* The recall object */ struct state_layout_recall_file *recall = NULL; /* Iterator over the work list */ struct glist_head *wi = NULL; struct gsh_export *exp = NULL; state_owner_t *owner = NULL; struct fsal_export *export = vec->up_fsal_export; rc = state_error_convert( export->exp_ops.create_handle(export, handle, &obj, NULL)); if (rc != STATE_SUCCESS) return rc; STATELOCK_lock(obj); /* We build up the list before consuming it so that we have every state on the list before we start executing returns. */ rc = create_file_recall(obj, layout_type, segment, cookie, spec, &recall); STATELOCK_unlock(obj); if (rc != STATE_SUCCESS) goto out; /** * @todo This leaves us open to a race if a return comes in * while we're traversing the work list. However, the race may now * be harmless since everything is refcounted. */ glist_for_each(wi, &recall->state_list) { /* The current entry in the queue */ struct recall_state_list *g = glist_entry(wi, struct recall_state_list, link); struct state_t *s = g->state; struct layoutrecall_cb_data *cb_data; nfs_cb_argop4 *arg; CB_LAYOUTRECALL4args *cb_layoutrec; layoutrecall_file4 *layout; cb_data = gsh_malloc(sizeof(struct layoutrecall_cb_data)); arg = &cb_data->arg; arg->argop = NFS4_OP_CB_LAYOUTRECALL; cb_layoutrec = &arg->nfs_cb_argop4_u.opcblayoutrecall; layout = &cb_layoutrec->clora_recall.layoutrecall4_u.lor_layout; cb_layoutrec->clora_type = layout_type; cb_layoutrec->clora_iomode = segment->io_mode; cb_layoutrec->clora_changed = changed; cb_layoutrec->clora_recall.lor_recalltype = LAYOUTRECALL4_FILE; layout->lor_offset = segment->offset; layout->lor_length = segment->length; if (!get_state_obj_export_owner_refs(s, NULL, &exp, &owner)) { /* The export, owner, or state_t has gone stale, * skip this entry */ gsh_free(cb_data); continue; } if (!nfs4_FSALToFhandle(true, &layout->lor_fh, obj, exp)) { gsh_free(cb_data); put_gsh_export(exp); dec_state_owner_ref(owner); rc = STATE_MALLOC_ERROR; goto out; } put_gsh_export(exp); update_stateid(s, &layout->lor_stateid, NULL, "LAYOUTRECALL"); memcpy(cb_data->stateid_other, s->stateid_other, OTHERSIZE); cb_data->segment = *segment; cb_data->client = owner->so_owner.so_nfs4_owner.so_clientrec; cb_data->attempts = 0; dec_state_owner_ref(owner); layoutrecall_one_call(cb_data); } out: /* Free the recall list resources */ destroy_recall(recall); obj->obj_ops->put_ref(obj); return rc; } /** * @brief Free a CB_LAYOUTRECALL * * @param[in] op Operation to free */ static void free_layoutrec(nfs_cb_argop4 *op) { layoutrecall4 *clora_recall = &op->nfs_cb_argop4_u.opcblayoutrecall.clora_recall; gsh_free(clora_recall->layoutrecall4_u.lor_layout.lor_fh.nfs_fh4_val); } /** * @brief Complete a CB_LAYOUTRECALL * * This function handles the client response to a layoutrecall. In * the event of success it does nothing. In the case of most errors, * it revokes the layout. * * For NOMATCHINGLAYOUT, under the agreed-upon interpretation of the * forgetful model, it acts as if the client had returned a layout * exactly matching the recall. * * For DELAY, it backs off in plateaus, then revokes the layout if the * period of delay has surpassed the lease period. * * @param[in] call The RPC call being completed */ static void layoutrec_completion(rpc_call_t *call) { struct layoutrecall_cb_data *cb_data = call->call_arg; bool deleted = false; state_t *state = NULL; struct req_op_context op_context; struct fsal_obj_handle *obj = NULL; struct gsh_export *export = NULL; state_owner_t *owner = NULL; bool ok = false; /* Initialize op_context */ init_op_context_simple(&op_context, NULL, NULL); LogFullDebug(COMPONENT_NFS_CB, "status %d cb_data %p", call->cbt.v_u.v4.res.status, cb_data); /* Get this out of the way up front */ if (call->states & NFS_CB_CALL_ABORTED) goto revoke; if (call->cbt.v_u.v4.res.status == NFS4_OK) { /** * @todo This is where you would record that a * recall was acknowledged and that a layoutreturn * will be sent later. * The number of times we retried the call is * specified in cb_data->attempts and the time we * specified the first call is in * cb_data->first_recall. * We don't have the clientid here. If you want it, * we could either move the stateid look up to be * above this point in the function, or we could stash * the clientid in cb_data. */ free_layoutrec(&call->cbt.v_u.v4.args.argarray.argarray_val[1]); nfs41_release_single(call); gsh_free(cb_data); goto out; } else if (call->cbt.v_u.v4.res.status == NFS4ERR_DELAY) { struct timespec current; nsecs_elapsed_t delay; now(¤t); if (timespec_diff(&cb_data->first_recall, ¤t) > (nfs_param.nfsv4_param.lease_lifetime * NS_PER_SEC)) { goto revoke; } if (cb_data->attempts < 5) delay = 0; else if (cb_data->attempts < 10) delay = 1 * NS_PER_MSEC; else if (cb_data->attempts < 20) delay = 10 * NS_PER_MSEC; else if (cb_data->attempts < 30) delay = 100 * NS_PER_MSEC; else delay = 1 * NS_PER_SEC; /* We don't free the argument here, because we'll be re-using that to make the queued call. */ nfs41_release_single(call); delayed_submit(layoutrecall_one_call, cb_data, delay); goto out; } /** * @todo Better error handling later when we have more * session/revocation infrastructure. */ revoke: /* If we don't find the state, there's nothing to return. */ state = nfs4_State_Get_Pointer(cb_data->stateid_other); ok = get_state_obj_export_owner_refs(state, &obj, &export, &owner); if (ok) { enum fsal_layoutreturn_circumstance circumstance; if (!(call->states & NFS_CB_CALL_ABORTED) && call->cbt.v_u.v4.res.status == NFS4ERR_NOMATCHING_LAYOUT) circumstance = circumstance_client; else circumstance = circumstance_revoke; /** * @todo This is where you would record that a * recall was completed, one way or the other. * The clientid is specified in * owner->so_owner.so_nfs4_owner.so_clientid * The number of times we retried the call is * specified in cb_data->attempts and the time we * specified the first call is in * cb_data->first_recall. If * call->cbt.v_u.v4.res.status is * NFS4ERR_NOMATCHING_LAYOUT it was a successful * return, otherwise we count it as an error. */ STATELOCK_lock(obj); op_ctx->clientid = &owner->so_owner.so_nfs4_owner.so_clientid; set_op_context_export(export); nfs4_return_one_state(obj, LAYOUTRETURN4_FILE, circumstance, state, cb_data->segment, 0, NULL, &deleted); STATELOCK_unlock(obj); } if (state != NULL) { /* Release the reference taken above */ dec_state_t_ref(state); } free_layoutrec(&call->cbt.v_u.v4.args.argarray.argarray_val[1]); nfs41_release_single(call); gsh_free(cb_data); out: if (ok) { /* Release object ref */ obj->obj_ops->put_ref(obj); /* Release the owner */ dec_state_owner_ref(owner); } release_op_context(); } /** * @brief Return one layout on error * * This is only invoked in the case of a send error on the first * attempt to issue a CB_LAYOUTRECALL, so that we don't call into the * FSAL's layoutreturn function while its layoutrecall function may be * holding locks. * * @param[in] arg Structure holding all arguments, so we can queue * this function in delayed_exec. */ static void return_one_async(void *arg) { struct layoutrecall_cb_data *cb_data = arg; state_t *state; bool deleted = false; struct req_op_context op_context; struct fsal_obj_handle *obj = NULL; struct gsh_export *export = NULL; state_owner_t *owner = NULL; bool ok = false; state = nfs4_State_Get_Pointer(cb_data->stateid_other); ok = get_state_obj_export_owner_refs(state, &obj, &export, &owner); if (ok) { /* Initialize op_context */ init_op_context_simple(&op_context, export, export->fsal_export); STATELOCK_lock(obj); op_ctx->clientid = &owner->so_owner.so_nfs4_owner.so_clientid; nfs4_return_one_state(obj, LAYOUTRETURN4_FILE, circumstance_revoke, state, cb_data->segment, 0, NULL, &deleted); STATELOCK_unlock(obj); } gsh_free(cb_data); if (state != NULL) { /* Release the reference taken above */ dec_state_t_ref(state); } if (ok) { /* Release object ref */ obj->obj_ops->put_ref(obj); /* Release the owner */ dec_state_owner_ref(owner); release_op_context(); } } /** * @brief Send one layoutrecall to one client * * @param[in] arg Structure holding all arguments, so we can queue * this function in delayed_exec for retry on NFS4ERR_DELAY. */ static void layoutrecall_one_call(void *arg) { struct layoutrecall_cb_data *cb_data = arg; state_t *state; int code; struct req_op_context op_context; struct fsal_obj_handle *obj = NULL; struct gsh_export *export = NULL; state_owner_t *owner = NULL; bool ok = false; if (cb_data->attempts == 0) now(&cb_data->first_recall); state = nfs4_State_Get_Pointer(cb_data->stateid_other); ok = get_state_obj_export_owner_refs(state, &obj, &export, &owner); if (ok) { /* Initialize op_context */ init_op_context_simple(&op_context, export, export->fsal_export); STATELOCK_lock(obj); op_ctx->clientid = &owner->so_owner.so_nfs4_owner.so_clientid; code = nfs_rpc_cb_single(cb_data->client, &cb_data->arg, &state->state_refer, layoutrec_completion, cb_data); if (code != 0) { /** * @todo On failure to submit a callback, we * ought to give the client at least one lease * period to establish a back channel before * we start revoking state. We don't have the * infrastructure to properly handle layout * revocation, however. Once we get the * capability to revoke layouts we should * queue requests on the clientid, obey the * retransmission rule, and provide a callback * to dispose of a call and revoke state after * some number of lease periods. * * At present we just assume the client has * gone completely out to lunch and fake a * return. */ /** * @todo This is where you would record that a * recall failed. (It indicates a transport error.) * The clientid is specified in * s->state_owner->so_owner.so_nfs4_owner.so_clientid * The number of times we retried the call is * specified in cb_data->attempts and the time * we specified the first call is in * cb_data->first_recall. */ if (cb_data->attempts == 0) { delayed_submit(return_one_async, cb_data, 0); } else { bool deleted = false; nfs4_return_one_state(obj, LAYOUTRETURN4_FILE, circumstance_revoke, state, cb_data->segment, 0, NULL, &deleted); free_layoutrec(&cb_data->arg); gsh_free(cb_data); } } else { ++cb_data->attempts; } STATELOCK_unlock(obj); } else { gsh_free(cb_data); } if (state != NULL) { /* Release the reference taken above */ dec_state_t_ref(state); } if (ok) { /* Release object ref */ obj->obj_ops->put_ref(obj); /* Release the owner */ dec_state_owner_ref(owner); release_op_context(); } } /** * @brief Data for CB_NOTIFY and CB_NOTIFY_DEVICEID response handler */ struct cb_notify { nfs_cb_argop4 arg; /*< Arguments (so we can free them) */ struct notify4 notify; /*< For notify response */ struct notify_deviceid_delete4 notify_del; /*< For notify_deviceid response. */ }; /** * @brief Handle CB_NOTIFY_DEVICE response * * @param[in] call The RPC call being completed */ static void notifydev_completion(rpc_call_t *call) { LogFullDebug(COMPONENT_NFS_CB, "status %d arg %p", call->cbt.v_u.v4.res.status, call->call_arg); gsh_free(call->call_arg); } /** * The arguments for devnotify_client_callback packed up in a struct */ struct devnotify_cb_data { notify_deviceid_type4 notify_type; layouttype4 layout_type; struct pnfs_deviceid devid; }; /** * @brief Send a single notifydev to a single client * * @param[in] clientid The client record * @param[in] devnotify The device notify args * * @return True on success, false on error. */ static bool devnotify_client_callback(nfs_client_id_t *clientid, void *devnotify) { int code = 0; CB_NOTIFY_DEVICEID4args *cb_notify_dev; struct cb_notify *arg; struct devnotify_cb_data *devicenotify = devnotify; if (clientid) { LogFullDebug(COMPONENT_NFS_CB, "CliP %p ClientID=%" PRIx64 " ver %d", clientid, clientid->cid_clientid, clientid->cid_minorversion); } else { return false; } /* free in notifydev_completion */ arg = gsh_malloc(sizeof(struct cb_notify)); cb_notify_dev = &arg->arg.nfs_cb_argop4_u.opcbnotify_deviceid; arg->arg.argop = NFS4_OP_CB_NOTIFY_DEVICEID; cb_notify_dev->cnda_changes.cnda_changes_len = 1; cb_notify_dev->cnda_changes.cnda_changes_val = &arg->notify; arg->notify.notify_mask.bitmap4_len = 1; arg->notify.notify_mask.map[0] = devicenotify->notify_type; arg->notify.notify_vals.notifylist4_len = sizeof(struct notify_deviceid_delete4); arg->notify.notify_vals.notifylist4_val = (char *)&arg->notify_del; arg->notify_del.ndd_layouttype = devicenotify->layout_type; memcpy(arg->notify_del.ndd_deviceid, &devicenotify->devid, sizeof(arg->notify_del.ndd_deviceid)); code = nfs_rpc_cb_single(clientid, &arg->arg, NULL, notifydev_completion, &arg->arg); if (code != 0) gsh_free(arg); return true; } /** * @brief Remove or change a deviceid * * @param[in] dev_exportid Export responsible for the device ID * @param[in] notify_type Change or remove * @param[in] layout_type The layout type affected * @param[in] devid The lower quad of the device id, unique * within this export * @param[in] immediate Whether the change is immediate (in the case * of a change.) * * @return STATE_SUCCESS or errors. */ state_status_t notify_device(notify_deviceid_type4 notify_type, layouttype4 layout_type, struct pnfs_deviceid devid, bool immediate) { struct devnotify_cb_data *cb_data; cb_data = gsh_malloc(sizeof(struct devnotify_cb_data)); cb_data->notify_type = notify_type; cb_data->layout_type = layout_type; cb_data->devid = devid; nfs41_foreach_client_callback(devnotify_client_callback, cb_data); return STATE_SUCCESS; } /** * @brief Check if the delegation needs to be revoked. * * @param[in] deleg_entry SLE entry for the delegation * * @return true, if the delegation need to be revoked. * @return false, if the delegation should not be revoked. */ bool eval_deleg_revoke(struct state_t *deleg_state) { struct cf_deleg_stats *clfl_stats; time_t curr_time; time_t recall_success_time, first_recall_time; uint32_t lease_lifetime = nfs_param.nfsv4_param.lease_lifetime; clfl_stats = &deleg_state->state_data.deleg.sd_clfile_stats; curr_time = time(NULL); recall_success_time = clfl_stats->cfd_rs_time; first_recall_time = clfl_stats->cfd_r_time; if ((recall_success_time > 0) && (curr_time - recall_success_time) > lease_lifetime) { LogInfo(COMPONENT_STATE, "More than one lease time has passed since recall was successfully sent"); return true; } if ((first_recall_time > 0) && (curr_time - first_recall_time) > (2 * lease_lifetime)) { LogInfo(COMPONENT_STATE, "More than two lease times have passed since recall was attempted"); return true; } return false; } /** * @brief Handle recall response * * @param[in] call The RPC call being completed * @param[in] clfl_stats client-file deleg heuristics * @param[in] p_cargs deleg recall context * */ static enum recall_resp_action handle_recall_response(struct delegrecall_context *p_cargs, struct state_t *state, rpc_call_t *call) { enum recall_resp_action resp_action; char str[DISPLAY_STATEID_OTHER_SIZE] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; if (isDebug(COMPONENT_NFS_CB)) { display_stateid_other(&dspbuf, p_cargs->drc_stateid.other); str_valid = true; } struct cf_deleg_stats *clfl_stats = &state->state_data.deleg.sd_clfile_stats; switch (call->cbt.v_u.v4.res.status) { case NFS4_OK: if (str_valid) LogDebug(COMPONENT_NFS_CB, "Delegation %s successfully recalled", str); resp_action = DELEG_RET_WAIT; clfl_stats->cfd_rs_time = time(NULL); break; case NFS4ERR_BADHANDLE: if (str_valid) LogDebug( COMPONENT_NFS_CB, "Client sent NFS4ERR_BADHANDLE response, retrying recall for Delegation %s", str); resp_action = DELEG_RECALL_SCHED; break; case NFS4ERR_DELAY: if (str_valid) LogDebug( COMPONENT_NFS_CB, "Client sent NFS4ERR_DELAY response, retrying recall for Delegation %s", str); resp_action = DELEG_RECALL_SCHED; break; case NFS4ERR_BAD_STATEID: if (str_valid) LogDebug( COMPONENT_NFS_CB, "Client sent NFS4ERR_BAD_STATEID response, retrying recall for Delegation %s", str); resp_action = DELEG_RECALL_SCHED; break; default: /* some other NFS error, consider the recall failed */ if (str_valid) LogDebug( COMPONENT_NFS_CB, "Client sent %d response, retrying recall for Delegation %s", call->cbt.v_u.v4.res.status, str); resp_action = DELEG_RECALL_SCHED; break; } return resp_action; } static inline void free_delegrecall_context(struct delegrecall_context *deleg_ctx) { update_lease_simple(deleg_ctx->drc_clid); put_gsh_export(deleg_ctx->drc_exp); dec_client_id_ref(deleg_ctx->drc_clid); gsh_free(deleg_ctx); } /** * @brief Handle the reply to a CB_RECALL * * @param[in] call The RPC call being completed * * @return 0, constantly. */ static void delegrecall_completion_func(rpc_call_t *call) { enum recall_resp_action resp_act; nfsstat4 rc = NFS4_OK; struct delegrecall_context *deleg_ctx = call->call_arg; uint32_t minorversion = deleg_ctx->drc_clid->cid_minorversion; struct state_t *state; struct fsal_obj_handle *obj = NULL; char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; CB_RECALL4args *opcbrecall; struct req_op_context op_context; struct gsh_export *export = NULL; bool ret = false; bool used_ctx = false; LogDebug(COMPONENT_NFS_CB, "%p %s", call, !(call->states & NFS_CB_CALL_ABORTED) ? "Success" : "Failed"); state = nfs4_State_Get_Pointer(deleg_ctx->drc_stateid.other); if (state == NULL) { LogDebug(COMPONENT_NFS_CB, "Delegation is already returned"); goto out_free_drc; } ret = get_state_obj_export_owner_refs(state, &obj, &export, NULL); if (!ret || obj == NULL) { LogDebug(COMPONENT_NFS_CB, "Stale file"); goto out_free_drc; } init_op_context_simple(&op_context, export, export->fsal_export); used_ctx = true; if (isDebug(COMPONENT_NFS_CB)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_stateid(&dspbuf, state); LogDebug(COMPONENT_NFS_CB, "deleg_entry %s", str); } if (!(call->states & NFS_CB_CALL_ABORTED)) { LogMidDebug(COMPONENT_NFS_CB, "call result: %d", call->call_req.cc_error.re_status); if (call->call_req.cc_error.re_status != RPC_SUCCESS) { LogEvent(COMPONENT_NFS_CB, "call result: %d, marking CB channel down", call->call_req.cc_error.re_status); set_cb_chan_down(deleg_ctx->drc_clid, true); resp_act = DELEG_RECALL_SCHED; } else resp_act = handle_recall_response(deleg_ctx, state, call); } else { LogEvent(COMPONENT_NFS_CB, "Aborted: %d, marking CB channel down", call->call_req.cc_error.re_status); set_cb_chan_down(deleg_ctx->drc_clid, true); /* Mark the recall as failed */ resp_act = DELEG_RECALL_SCHED; } switch (resp_act) { case DELEG_RECALL_SCHED: if (eval_deleg_revoke(state)) goto out_revoke; else { if (schedule_delegrecall_task(deleg_ctx, 1)) goto out_revoke; goto out_free; } break; case DELEG_RET_WAIT: if (schedule_delegrevoke_check(deleg_ctx, 1)) goto out_revoke; goto out_free; case REVOKE: goto out_revoke; } out_revoke: display_stateid(&dspbuf, state); LogCrit(COMPONENT_NFS_V4, "Revoking delegation for %s", str); deleg_ctx->drc_clid->num_revokes++; inc_revokes(deleg_ctx->drc_clid->gsh_client); /* state_del_locked is called from deleg_revoke, take the lock. */ STATELOCK_lock(obj); rc = deleg_revoke(obj, state); STATELOCK_unlock(obj); if (rc != NFS4_OK) { LogCrit(COMPONENT_NFS_V4, "Delegation could not be revoked for %s", str); } else { LogDebug(COMPONENT_NFS_V4, "Delegation revoked for %s", str); } out_free_drc: free_delegrecall_context(deleg_ctx); out_free: if (minorversion == 0) { opcbrecall = &call->cbt.v_u.v4.args.argarray.argarray_val[0] .nfs_cb_argop4_u.opcbrecall; nfs4_freeFH(&opcbrecall->fh); } else { opcbrecall = &call->cbt.v_u.v4.args.argarray.argarray_val[1] .nfs_cb_argop4_u.opcbrecall; nfs4_freeFH(&opcbrecall->fh); nfs41_release_single(call); } if (state != NULL) dec_state_t_ref(state); /* Release the obj ref and export ref. */ if (obj != NULL) obj->obj_ops->put_ref(obj); if (used_ctx) release_op_context(); } /** * @brief Send one delegation recall to one client. * * This function sends a cb_recall for one delegation, the caller has to lock * cache_entry->st_lock before calling this function. * * @param[in] obj The file being delegated * @param[in] deleg_entry Lock entry covering the delegation * @param[in] delegrecall_context */ void delegrecall_one(struct fsal_obj_handle *obj, struct state_t *state, struct delegrecall_context *p_cargs) { int ret; nfs_cb_argop4 argop; struct cf_deleg_stats *clfl_stats; char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; clfl_stats = &state->state_data.deleg.sd_clfile_stats; if (isDebug(COMPONENT_FSAL_UP)) { display_stateid(&dspbuf, state); str_valid = true; } /* record the first attempt to recall this delegation */ if (clfl_stats->cfd_r_time == 0) clfl_stats->cfd_r_time = time(NULL); if (str_valid) LogFullDebug(COMPONENT_FSAL_UP, "Recalling delegation %s", str); inc_recalls(p_cargs->drc_clid->gsh_client); argop.argop = NFS4_OP_CB_RECALL; COPY_STATEID(&argop.nfs_cb_argop4_u.opcbrecall.stateid, state); argop.nfs_cb_argop4_u.opcbrecall.truncate = false; /* Convert it to a file handle */ if (!nfs4_FSALToFhandle(true, &argop.nfs_cb_argop4_u.opcbrecall.fh, obj, p_cargs->drc_exp)) { LogCrit(COMPONENT_FSAL_UP, "nfs4_FSALToFhandle failed, can not process recall"); goto out; } ret = nfs_rpc_cb_single(p_cargs->drc_clid, &argop, &state->state_refer, delegrecall_completion_func, p_cargs); if (ret == 0) return; LogDebug(COMPONENT_FSAL_UP, "nfs_rpc_cb_single returned %d", ret); out: inc_failed_recalls(p_cargs->drc_clid->gsh_client); nfs4_freeFH(&argop.nfs_cb_argop4_u.opcbrecall.fh); if (!eval_deleg_revoke(state) && !schedule_delegrecall_task(p_cargs, 1)) { /* Keep the delegation in p_cargs */ if (str_valid) LogDebug(COMPONENT_FSAL_UP, "Retry delegation for %s", str); return; } if (!str_valid) display_stateid(&dspbuf, state); LogCrit(COMPONENT_STATE, "Delegation will be revoked for %s", str); p_cargs->drc_clid->num_revokes++; inc_revokes(p_cargs->drc_clid->gsh_client); if (deleg_revoke(obj, state) != NFS4_OK) { LogDebug(COMPONENT_FSAL_UP, "Failed to revoke delegation %s.", str); } else { LogDebug(COMPONENT_FSAL_UP, "Delegation revoked %s", str); } free_delegrecall_context(p_cargs); } /** * @brief Check if the delegation needs to be revoked. * * @param[in] ctx Delegation recall context describing the delegation */ static void delegrevoke_check(void *ctx) { nfsstat4 rc = NFS4_OK; struct delegrecall_context *deleg_ctx = ctx; struct fsal_obj_handle *obj = NULL; struct state_t *state = NULL; bool free_drc = true; char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; struct req_op_context op_context; struct gsh_export *export = NULL; bool ret = false; bool used_ctx = false; state = nfs4_State_Get_Pointer(deleg_ctx->drc_stateid.other); if (state == NULL) { LogDebug(COMPONENT_NFS_CB, "Delegation is already returned"); goto out; } if (isDebug(COMPONENT_NFS_CB)) { display_stateid(&dspbuf, state); str_valid = true; } ret = get_state_obj_export_owner_refs(state, &obj, &export, NULL); if (!ret || obj == NULL) { LogDebug(COMPONENT_NFS_CB, "Stale file"); goto out; } init_op_context_simple(&op_context, export, export->fsal_export); used_ctx = true; if (eval_deleg_revoke(state)) { if (str_valid) LogDebug(COMPONENT_STATE, "Revoking delegation for %s", str); /* state_del_locked is called from deleg_revoke, * take the lock. */ STATELOCK_lock(obj); rc = deleg_revoke(obj, state); STATELOCK_unlock(obj); if (rc != NFS4_OK) { if (!str_valid) display_stateid(&dspbuf, state); LogCrit(COMPONENT_NFS_V4, "Delegation could not be revoked for %s", str); } else { if (str_valid) LogDebug(COMPONENT_NFS_V4, "Delegation revoked for %s", str); } } else { if (str_valid) LogFullDebug(COMPONENT_STATE, "Not yet revoking the delegation for %s", str); schedule_delegrevoke_check(deleg_ctx, 1); free_drc = false; } out: if (free_drc) free_delegrecall_context(deleg_ctx); if (state != NULL) dec_state_t_ref(state); /* Release the obj ref and export ref. */ if (obj != NULL) obj->obj_ops->put_ref(obj); if (used_ctx) release_op_context(); } static void delegrecall_task(void *ctx) { struct delegrecall_context *deleg_ctx = ctx; struct state_t *state; struct fsal_obj_handle *obj; bool ret = false; struct req_op_context op_context; struct gsh_export *export = NULL; state = nfs4_State_Get_Pointer(deleg_ctx->drc_stateid.other); if (state == NULL) { LogDebug(COMPONENT_NFS_CB, "Delegation is already returned"); free_delegrecall_context(deleg_ctx); return; } ret = get_state_obj_export_owner_refs(state, &obj, &export, NULL); if (!ret || obj == NULL) { LogDebug(COMPONENT_NFS_CB, "Delegation recall skipped due to stale file"); goto out; } init_op_context_simple(&op_context, export, export->fsal_export); /* state_del_locked is called from deleg_revoke, take the lock. */ STATELOCK_lock(obj); delegrecall_one(obj, state, deleg_ctx); STATELOCK_unlock(obj); /* Release the obj ref and export ref. */ obj->obj_ops->put_ref(obj); release_op_context(); out: dec_state_t_ref(state); } static int schedule_delegrecall_task(struct delegrecall_context *ctx, uint32_t delay) { int rc = 0; assert(ctx); rc = delayed_submit(delegrecall_task, ctx, delay * NS_PER_SEC); if (rc) LogDebug(COMPONENT_THREAD, "delayed_submit failed with rc = %d", rc); return rc; } static int schedule_delegrevoke_check(struct delegrecall_context *ctx, uint32_t delay) { int rc = 0; assert(ctx); rc = delayed_submit(delegrevoke_check, ctx, delay * NS_PER_SEC); if (rc) LogDebug(COMPONENT_THREAD, "delayed_submit failed with rc = %d", rc); return rc; } state_status_t delegrecall_impl(struct fsal_obj_handle *obj) { struct glist_head *glist, *glist_n; state_status_t rc = 0; uint32_t *deleg_state = NULL; struct state_t *state; state_owner_t *owner; struct delegrecall_context *drc_ctx; struct req_op_context op_context; nfs_client_id_t *client_id = NULL; LogDebug(COMPONENT_FSAL_UP, "FSAL_UP_DELEG: obj %p type %u", obj, obj->type); STATELOCK_lock(obj); glist_for_each_safe(glist, glist_n, &obj->state_hdl->file.list_of_states) { state = glist_entry(glist, struct state_t, state_list); if (state->state_type != STATE_TYPE_DELEG) continue; if (isDebug(COMPONENT_NFS_CB)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_stateid(&dspbuf, state); LogDebug(COMPONENT_NFS_CB, "Delegation for %s", str); } deleg_state = &state->state_data.deleg.sd_state; if (*deleg_state != DELEG_GRANTED) { LogDebug(COMPONENT_FSAL_UP, "Delegation already being recalled, NOOP"); continue; } *deleg_state = DELEG_RECALL_WIP; drc_ctx = gsh_malloc(sizeof(struct delegrecall_context)); /* Get references on the owner and the export. The * export reference we will hold while we perform the recall. * The owner reference will be used to get access to the * clientid and reserve the lease. */ if (!get_state_obj_export_owner_refs( state, NULL, &drc_ctx->drc_exp, &owner)) { LogDebug( COMPONENT_FSAL_UP, "Something is going stale, no need to recall delegation"); gsh_free(drc_ctx); *deleg_state = DELEG_GRANTED; continue; } client_id = owner->so_owner.so_nfs4_owner.so_clientrec; if (!client_id || !atomic_fetch_int32_t(&client_id->cid_refcount)) { /* Let's release the export and owner ref */ put_gsh_export(drc_ctx->drc_exp); dec_state_owner_ref(owner); LogDebug( COMPONENT_FSAL_UP, "Client id within owner has gone stale, no need to recall delegation"); gsh_free(drc_ctx); *deleg_state = DELEG_GRANTED; continue; } inc_client_id_ref(client_id); dec_state_owner_ref(owner); /* Being an area in which there are/could be a lot of races, * updating the logic so that multiple ASync recalls as part of * conflciting IOs on same or different objects could happen * concurrently and in parallel the reaper wiping off the * expired clients with its states. * Steps: * 1) For an expired client, proceed directly to recall off the * conflciting state as part of delegrecall_one() below. * 2) For a non-expired client - * a) Prevent client's lease expiring until we complete this * recall/revoke operation, be renewing/reserving lease. * b) If reserve lease fails, then we cleanup and set the * deleg state back to GRANTED, so that reaper could * clean this up proper later, else below path ignores * the deleg state to be revoked. * nfs_client_id_expire -> revoke_owner_delegs -> * state_deleg_revoke -> state_deleg_revoke * 3) Once deleg state is marked properly, then further ops on * this state would return NFS4ERR_REVOKED or NFS4ERR_EXPIRED * 4) Continue to check for other conflicting delegation holders * holding the state lock */ PTHREAD_MUTEX_lock(&client_id->cid_mutex); if (client_id->cid_confirmed != EXPIRED_CLIENT_ID && !reserve_lease(client_id)) { PTHREAD_MUTEX_unlock(&client_id->cid_mutex); /* Let's release the client and export ref */ dec_client_id_ref(client_id); put_gsh_export(drc_ctx->drc_exp); LogDebug(COMPONENT_FSAL_UP, "Failed to reserve client's lease."); gsh_free(drc_ctx); /* Reset the state for reaper to clean */ *deleg_state = DELEG_GRANTED; continue; } PTHREAD_MUTEX_unlock(&client_id->cid_mutex); /* Get a ref to the drc_ctx->drc_exp and initialize op_context * in case state_del_locks and others need it. */ get_gsh_export_ref(drc_ctx->drc_exp); init_op_context_simple(&op_context, drc_ctx->drc_exp, drc_ctx->drc_exp->fsal_export); drc_ctx->drc_clid = client_id; COPY_STATEID(&drc_ctx->drc_stateid, state); obj->state_hdl->file.fdeleg_stats.fds_last_recall = time(NULL); delegrecall_one(obj, state, drc_ctx); release_op_context(); } STATELOCK_unlock(obj); return rc; } /** * @brief Recall a delegation * * @param[in] handle The handle on which the delegation is held * * @return STATE_SUCCESS or errors. */ state_status_t delegrecall(const struct fsal_up_vector *vec, struct gsh_buffdesc *handle) { struct fsal_export *export = vec->up_fsal_export; struct fsal_obj_handle *obj = NULL; state_status_t rc = 0; if (!nfs_param.nfsv4_param.allow_delegations) { LogCrit(COMPONENT_FSAL_UP, "BUG: Got BREAK_DELEGATION: upcall when delegations are disabled, ignoring"); return STATE_SUCCESS; } rc = state_error_convert( export->exp_ops.create_handle(export, handle, &obj, NULL)); if (rc != STATE_SUCCESS) { LogDebug(COMPONENT_FSAL_UP, "FSAL_UP_DELEG: create_handle failed, rc %d", rc); /* Not an error. Expecting some nodes will not have it * in cache in a cluster. */ return rc; } rc = delegrecall_impl(obj); obj->obj_ops->put_ref(obj); return rc; } struct cbgetattr_context { struct fsal_obj_handle *obj; nfs_client_id_t *clid; /* client holding deleg */ struct gsh_export *ctx_export; /*< current export */ }; /* * release cbgetattr_context args reference and * free the memory allocated. */ static inline void free_cbgetattr_context(struct cbgetattr_context *cbgetattr_ctx) { update_lease_simple(cbgetattr_ctx->clid); put_gsh_export(cbgetattr_ctx->ctx_export); dec_client_id_ref(cbgetattr_ctx->clid); cbgetattr_ctx->obj->obj_ops->put_ref(cbgetattr_ctx->obj); gsh_free(cbgetattr_ctx); } /** * @brief act on response received to CB_GETATTR request * * On successful rpc callback request, read and store * the attributes sent by client and mark cbgetattr_state * as CB_GETATTR_RESP_OK * * @param[in] cbgetattr_context * @param[in] rpc_call * * @return CB_GETATTR_RESP_OK on success * CB_GETATTR_FAILED on failure */ enum cbgetattr_state handle_getattr_response(struct cbgetattr_context *cbg_ctx, rpc_call_t *call) { fattr4 attr; struct fsal_attrlist rsp_attr; int rc = 0; enum cbgetattr_state cb_state; struct fsal_obj_handle *obj = cbg_ctx->obj; nfs_client_id_t *clid = cbg_ctx->clid; nfs_cb_resop4 *cbr = NULL; CB_GETATTR4res *res = NULL; const struct fsal_up_vector *event_func; struct fsal_attrlist up_attr = { 0, }; uint32_t upflags = 0; struct gsh_buffdesc key; fsal_status_t fsal_status = { 0, }; struct req_op_context op_context; struct timespec timeo = { .tv_sec = time(NULL) + 5, .tv_nsec = 0 }; if (clid->cid_minorversion == 0) cbr = &call->cbt.v_u.v4.res.resarray.resarray_val[0]; else cbr = &call->cbt.v_u.v4.res.resarray.resarray_val[1]; res = &cbr->nfs_cb_resop4_u.opcbgetattr; attr = res->CB_GETATTR4res_u.resok4.obj_attributes; rc = nfs4_Fattr_To_FSAL_attr(&rsp_attr, &attr, NULL); if (rc) { cb_state = CB_GETATTR_FAILED; goto out; } cb_state = CB_GETATTR_RSP_OK; /* Determine if the file is modified the very first time */ if (!obj->state_hdl->file.cbgetattr.modified && ((obj->state_hdl->file.cbgetattr.change == rsp_attr.change) && (obj->state_hdl->file.cbgetattr.filesize == rsp_attr.filesize))) { goto out; } else { /* One/more attributes changed */ obj->state_hdl->file.cbgetattr.modified = true; } /* every time we need to update change number to let * other clients know that file contents could have * been modified even if the filesize remains same */ obj->state_hdl->file.cbgetattr.change++; obj->state_hdl->file.cbgetattr.filesize = rsp_attr.filesize; event_func = (cbg_ctx->ctx_export->fsal_export->up_ops); /* Get a ref to the cbg_ctx->ctx_export and initialize op_context */ get_gsh_export_ref(cbg_ctx->ctx_export); init_op_context_simple(&op_context, cbg_ctx->ctx_export, cbg_ctx->ctx_export->fsal_export); /* @todo : log message if event_func is NULL */ obj->obj_ops->handle_to_key(obj, &key); up_attr.valid_mask |= (ATTR_CHANGE | ATTR_SIZE | ATTR_MTIME | ATTR_CTIME); up_attr.request_mask = up_attr.valid_mask; up_attr.filesize = obj->state_hdl->file.cbgetattr.filesize; up_attr.change = obj->state_hdl->file.cbgetattr.change; up_attr.mtime = timeo; up_attr.ctime = timeo; fsal_status = event_func->update(event_func, &key, &up_attr, upflags); release_op_context(); if (FSAL_IS_ERROR(fsal_status)) { cb_state = CB_GETATTR_FAILED; /* log message */ return CB_GETATTR_FAILED; } out: return cb_state; } /** * @brief Handle the reply to a CB_GETATTR * * @param[in] call The RPC call being completed * */ static void cbgetattr_completion_func(rpc_call_t *call) { struct cbgetattr_context *cbg_ctx = call->call_arg; enum cbgetattr_state *cbgetattr_state; CB_GETATTR4args *opcbgetattr; LogDebug(COMPONENT_NFS_CB, "%p %s", call, !(call->states & NFS_CB_CALL_ABORTED) ? "Success" : "Failed"); STATELOCK_lock(cbg_ctx->obj); cbgetattr_state = &cbg_ctx->obj->state_hdl->file.cbgetattr.state; if (!(call->states & NFS_CB_CALL_ABORTED)) { LogMidDebug(COMPONENT_NFS_CB, "call result: %d", call->call_req.cc_error.re_status); if (call->call_req.cc_error.re_status != RPC_SUCCESS) { LogEvent( COMPONENT_NFS_CB, "CB_GETATTR call result: %d, marking CB channel down", call->call_req.cc_error.re_status); set_cb_chan_down(cbg_ctx->clid, true); *cbgetattr_state = CB_GETATTR_FAILED; goto out; } } else { LogEvent(COMPONENT_NFS_CB, "CB_GETATTR Aborted: %d, marking CB channel down", call->call_req.cc_error.re_status); set_cb_chan_down(cbg_ctx->clid, true); /* Mark the getattr as failed */ *cbgetattr_state = CB_GETATTR_FAILED; goto out; } switch (call->cbt.v_u.v4.res.status) { case NFS4_OK: LogDebug(COMPONENT_NFS_CB, "CB_GEATTR succeeded for client(%s)", cbg_ctx->clid->gsh_client->hostaddr_str); *cbgetattr_state = handle_getattr_response(cbg_ctx, call); break; default: *cbgetattr_state = CB_GETATTR_FAILED; } out: STATELOCK_unlock(cbg_ctx->obj); if (cbg_ctx->clid->cid_minorversion == 0) { opcbgetattr = &call->cbt.v_u.v4.args.argarray.argarray_val[0] .nfs_cb_argop4_u.opcbgetattr; nfs4_freeFH(&opcbgetattr->fh); } else { opcbgetattr = &call->cbt.v_u.v4.args.argarray.argarray_val[1] .nfs_cb_argop4_u.opcbgetattr; nfs4_freeFH(&opcbgetattr->fh); nfs41_release_single(call); } free_cbgetattr_context(cbg_ctx); } /** * @brief Send CB_GETATTR to write_delegated client. * * This function sends a CB_GETATTR, the caller has to lock * cache_entry->st_lock before calling this function. * * @param[in] obj The file holding delegation * @param[in] cbgetattr_context * * @return 0, success * otherwise, failure */ int send_cbgetattr(struct fsal_obj_handle *obj, struct cbgetattr_context *p_cargs) { int ret = 0; nfs_cb_argop4 argop; bitmap4 *cb_attr_req; struct req_op_context op_context; /* Get a ref to the p_cargs->ctx_export and initialize op_context */ get_gsh_export_ref(p_cargs->ctx_export); init_op_context_simple(&op_context, p_cargs->ctx_export, p_cargs->ctx_export->fsal_export); LogDebug(COMPONENT_FSAL_UP, "Sending CB_GETATTR to client %s", p_cargs->clid->gsh_client->hostaddr_str); argop.argop = NFS4_OP_CB_GETATTR; /* Convert it to a file handle */ if (!nfs4_FSALToFhandle(true, &argop.nfs_cb_argop4_u.opcbgetattr.fh, obj, p_cargs->ctx_export)) { LogCrit(COMPONENT_FSAL_UP, "nfs4_FSALToFhandle failed, can not process recall"); goto out; } cb_attr_req = &argop.nfs_cb_argop4_u.opcbgetattr.attr_request; memset(cb_attr_req, 0, sizeof(*cb_attr_req)); /* * If we already know that client has modified data, no need to * query for change attribute */ if (!obj->state_hdl->file.cbgetattr.modified) set_attribute_in_bitmap(cb_attr_req, FATTR4_CHANGE); set_attribute_in_bitmap(cb_attr_req, FATTR4_SIZE); ret = nfs_rpc_cb_single(p_cargs->clid, &argop, NULL, cbgetattr_completion_func, p_cargs); LogDebug(COMPONENT_FSAL_UP, "CB_GETATTR nfs_rpc_cb_single returned %d", ret); if (ret == 0) goto out_exp; out: nfs4_freeFH(&argop.nfs_cb_argop4_u.opcbgetattr.fh); LogCrit(COMPONENT_STATE, "CB_GETATTR failed for %s", p_cargs->clid->gsh_client->hostaddr_str); free_cbgetattr_context(p_cargs); out_exp: release_op_context(); return ret; } /** * @brief Implement CB_GETATTR op * * By making use of cbgetattr_context, prepare and * send CB_GETATTR request to the client. On success, * set cbgetattr_state to CB_GETATTR_WIP state, * otherwise mark it as CB_GETATTR_FAILED. * * @param[in] obj file being operated on * @param[in] client The client holding delegation * @param[in] ctx_exp current export reference * * @return: 0, success * otherwise, failure */ int cbgetattr_impl(struct fsal_obj_handle *obj, nfs_client_id_t *client, struct gsh_export *ctx_exp) { int rc = 0; enum cbgetattr_state *cb_state = NULL; struct cbgetattr_context *cbg_ctx; LogDebug(COMPONENT_FSAL_UP, "CB_GETATTR: obj %p type %u", obj, obj->type); STATELOCK_lock(obj); cb_state = &obj->state_hdl->file.cbgetattr.state; if (*cb_state != CB_GETATTR_NONE) { /* Either its already WIP or got error */ goto out; } *cb_state = CB_GETATTR_WIP; cbg_ctx = gsh_malloc(sizeof(struct cbgetattr_context)); obj->obj_ops->get_ref(obj); cbg_ctx->obj = obj; inc_client_id_ref(client); cbg_ctx->clid = client; /* Prevent client's lease expiring until we complete * this cb_getattr operation. If the client's lease * has already expired, let the reaper thread handling * expired clients revoke this delegation, and we just * skip it here. */ PTHREAD_MUTEX_lock(&cbg_ctx->clid->cid_mutex); if (!reserve_lease(cbg_ctx->clid)) { PTHREAD_MUTEX_unlock(&cbg_ctx->clid->cid_mutex); gsh_free(cbg_ctx); *cb_state = CB_GETATTR_FAILED; goto out; } PTHREAD_MUTEX_unlock(&cbg_ctx->clid->cid_mutex); get_gsh_export_ref(ctx_exp); cbg_ctx->ctx_export = ctx_exp; rc = send_cbgetattr(obj, cbg_ctx); out: if (rc) *cb_state = CB_GETATTR_FAILED; STATELOCK_unlock(obj); return rc; } void up_ready_init(struct fsal_up_vector *up_ops) { up_ops->up_ready = false; up_ops->up_cancel = false; PTHREAD_MUTEX_init(&up_ops->up_mutex, NULL); PTHREAD_COND_init(&up_ops->up_cond, NULL); } void up_ready_destroy(struct fsal_up_vector *up_ops) { PTHREAD_MUTEX_destroy(&up_ops->up_mutex); PTHREAD_COND_destroy(&up_ops->up_cond); } /* This should be called when a module is ready to take upcalls */ void up_ready_set(struct fsal_up_vector *up_ops) { PTHREAD_MUTEX_lock(&up_ops->up_mutex); up_ops->up_ready = true; pthread_cond_broadcast(&up_ops->up_cond); PTHREAD_MUTEX_unlock(&up_ops->up_mutex); } /* This is to cancel waiting and resume Upcall threads */ void up_ready_cancel(struct fsal_up_vector *up_ops) { PTHREAD_MUTEX_lock(&up_ops->up_mutex); up_ops->up_cancel = true; pthread_cond_broadcast(&up_ops->up_cond); PTHREAD_MUTEX_unlock(&up_ops->up_mutex); } /* Upcall threads should call this to wait for upcall readiness */ void up_ready_wait(struct fsal_up_vector *up_ops) { PTHREAD_MUTEX_lock(&up_ops->up_mutex); while (!up_ops->up_ready && !up_ops->up_cancel) pthread_cond_wait(&up_ops->up_cond, &up_ops->up_mutex); PTHREAD_MUTEX_unlock(&up_ops->up_mutex); } /** Release a mdcache entry if otherwise unused * * @param[in] vec Up ops vector * @param[in] handle The handle being conditionally released * @param[in] flags Unused, for future expansion */ static fsal_status_t try_release(const struct fsal_up_vector *vec, struct gsh_buffdesc *handle, uint32_t flags) { return fsalstat(ERR_FSAL_NOTSUPP, 0); } /** * @brief The top level vector of operations * * This is the basis for UP calls. It should not be used directly, but copied * into a per-export structure, overridden if necessary, and fsal_export set. * FSAL_MDCACHE does this, so any cached FSALs don't need to worry. */ struct fsal_up_vector fsal_up_top = { .up_gsh_export = NULL, .up_fsal_export = NULL, .lock_grant = lock_grant, .lock_avail = lock_avail, .invalidate = invalidate, .update = update, .layoutrecall = layoutrecall, .notify_device = notify_device, .delegrecall = delegrecall, .invalidate_close = invalidate_close, .try_release = try_release, }; /** @} */ nfs-ganesha-6.5/src/LICENSE.txt000066400000000000000000000167441473756622300162230ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. 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 that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU 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 as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. nfs-ganesha-6.5/src/MainNFSD/000077500000000000000000000000001473756622300157235ustar00rootroot00000000000000nfs-ganesha-6.5/src/MainNFSD/.gitignore000066400000000000000000000000161473756622300177100ustar00rootroot00000000000000*ganesha.nfsd nfs-ganesha-6.5/src/MainNFSD/9p_dispatcher.c000066400000000000000000000636571473756622300206460ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_dispatcher.c * \date $Date: 2006/02/23 12:33:05 $ * \brief 9P protocol dispatch thread. * * The file that contains the '_9p_dispatcher_thread' routine for ganesha * (and all the related stuff). * */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include #include #include #include #include #include #include /* For inet_ntop() */ #include "hashtable.h" #include "log.h" #include "abstract_mem.h" #include "abstract_atomic.h" #include "nfs_init.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_file_handle.h" #include "9p_req_queue.h" #include "client_mgr.h" #include "server_stats.h" #include "9p.h" #include #include #define P_FAMILY AF_INET6 static struct fridgethr *_9p_worker_fridge; static struct _9p_req_st _9p_req_st; /*< 9P request queues */ static const char *req_q_s[N_REQ_QUEUES] = { "REQ_Q_LOW_LATENCY", }; /* static */ uint32_t _9p_outstanding_reqs_est(void) { static uint32_t ctr; static uint32_t nreqs; struct req_q_pair *qpair; uint32_t treqs; int ix; if ((atomic_inc_uint32_t(&ctr) % 10) != 0) return atomic_fetch_uint32_t(&nreqs); treqs = 0; for (ix = 0; ix < N_REQ_QUEUES; ++ix) { qpair = &(_9p_req_st.reqs._9p_request_q.qset[ix]); treqs += atomic_fetch_uint32_t(&qpair->producer.size); treqs += atomic_fetch_uint32_t(&qpair->consumer.size); } atomic_store_uint32_t(&nreqs, treqs); return treqs; } static inline struct _9p_request_data *_9p_consume_req(struct req_q_pair *qpair) { struct _9p_request_data *reqdata = NULL; PTHREAD_SPIN_lock(&qpair->consumer._9p_rq_spinlock); if (qpair->consumer.size > 0) { reqdata = glist_first_entry(&qpair->consumer.q, struct _9p_request_data, req_q); glist_del(&reqdata->req_q); --(qpair->consumer.size); PTHREAD_SPIN_unlock(&qpair->consumer._9p_rq_spinlock); goto out; } else { char *s = NULL; uint32_t csize = ~0U; uint32_t psize = ~0U; PTHREAD_SPIN_lock(&qpair->producer._9p_rq_spinlock); if (isFullDebug(COMPONENT_DISPATCH)) { s = (char *)qpair->s; csize = qpair->consumer.size; psize = qpair->producer.size; } if (qpair->producer.size > 0) { /* splice */ glist_splice_tail(&qpair->consumer.q, &qpair->producer.q); qpair->consumer.size = qpair->producer.size; qpair->producer.size = 0; /* consumer.size > 0 */ PTHREAD_SPIN_unlock(&qpair->producer._9p_rq_spinlock); reqdata = glist_first_entry(&qpair->consumer.q, struct _9p_request_data, req_q); glist_del(&reqdata->req_q); --(qpair->consumer.size); PTHREAD_SPIN_unlock(&qpair->consumer._9p_rq_spinlock); if (s) LogFullDebug( COMPONENT_DISPATCH, "try splice, qpair %s consumer qsize=%u producer qsize=%u", s, csize, psize); goto out; } PTHREAD_SPIN_unlock(&qpair->producer._9p_rq_spinlock); PTHREAD_SPIN_unlock(&qpair->consumer._9p_rq_spinlock); if (s) LogFullDebug( COMPONENT_DISPATCH, "try splice, qpair %s consumer qsize=%u producer qsize=%u", s, csize, psize); } out: return reqdata; } static struct _9p_request_data *_9p_dequeue_req(struct _9p_worker_data *worker) { struct _9p_request_data *reqdata = NULL; struct req_q_set *_9p_request_q = &_9p_req_st.reqs._9p_request_q; struct req_q_pair *qpair; uint32_t ix, slot; struct timespec timeout; /* XXX: the following stands in for a more robust/flexible * weighting function */ retry_deq: slot = atomic_inc_uint32_t(&_9p_req_st.reqs.ctr) % N_REQ_QUEUES; for (ix = 0; ix < N_REQ_QUEUES; ++ix) { qpair = &(_9p_request_q->qset[slot]); LogFullDebug(COMPONENT_DISPATCH, "dequeue_req try qpair %s %p:%p", qpair->s, &qpair->producer, &qpair->consumer); /* anything? */ reqdata = _9p_consume_req(qpair); if (reqdata) { break; } ++slot; slot = slot % N_REQ_QUEUES; } /* for */ /* wait */ if (!reqdata) { struct fridgethr_context *ctx = container_of(worker, struct fridgethr_context, wd); wait_q_entry_t *wqe = &worker->wqe; assert(wqe->waiters == 0); /* wqe is not on any wait queue */ PTHREAD_MUTEX_lock(&wqe->lwe.wq_mtx); wqe->flags = Wqe_LFlag_WaitSync; wqe->waiters = 1; /* XXX functionalize */ PTHREAD_SPIN_lock(&_9p_req_st.reqs._9p_rq_st_spinlock); glist_add_tail(&_9p_req_st.reqs.wait_list, &wqe->waitq); ++(_9p_req_st.reqs.waiters); PTHREAD_SPIN_unlock(&_9p_req_st.reqs._9p_rq_st_spinlock); while (!(wqe->flags & Wqe_LFlag_SyncDone)) { timeout.tv_sec = time(NULL) + 5; timeout.tv_nsec = 0; pthread_cond_timedwait(&wqe->lwe.wq_cv, &wqe->lwe.wq_mtx, &timeout); if (fridgethr_you_should_break(ctx)) { /* We are returning; * so take us out of the waitq */ PTHREAD_SPIN_lock( &_9p_req_st.reqs._9p_rq_st_spinlock); if (wqe->waitq.next != NULL || wqe->waitq.prev != NULL) { /* Element is still in wqitq, * remove it */ glist_del(&wqe->waitq); --(_9p_req_st.reqs.waiters); --(wqe->waiters); wqe->flags &= ~(Wqe_LFlag_WaitSync | Wqe_LFlag_SyncDone); } PTHREAD_SPIN_unlock( &_9p_req_st.reqs._9p_rq_st_spinlock); PTHREAD_MUTEX_unlock(&wqe->lwe.wq_mtx); return NULL; } } /* XXX wqe was removed from _9p_req_st.waitq * (by signalling thread) */ wqe->flags &= ~(Wqe_LFlag_WaitSync | Wqe_LFlag_SyncDone); PTHREAD_MUTEX_unlock(&wqe->lwe.wq_mtx); LogFullDebug(COMPONENT_DISPATCH, "wqe wakeup %p", wqe); goto retry_deq; } /* !reqdata */ return reqdata; } static void _9p_enqueue_req(struct _9p_request_data *reqdata) { struct req_q_set *_9p_request_q; struct req_q_pair *qpair; struct req_q *q; _9p_request_q = &_9p_req_st.reqs._9p_request_q; qpair = &(_9p_request_q->qset[REQ_Q_LOW_LATENCY]); /* always append to producer queue */ q = &qpair->producer; PTHREAD_SPIN_lock(&q->_9p_rq_spinlock); glist_add_tail(&q->q, &reqdata->req_q); ++(q->size); PTHREAD_SPIN_unlock(&q->_9p_rq_spinlock); LogDebug(COMPONENT_DISPATCH, "enqueued req, q %p (%s %p:%p) size is %d (enq %" PRIu64 " deq %" PRIu64 ")", q, qpair->s, &qpair->producer, &qpair->consumer, q->size, nfs_health_.enqueued_reqs, nfs_health_.dequeued_reqs); /* potentially wakeup some thread */ /* global waitq */ { wait_q_entry_t *wqe; /* SPIN LOCKED */ PTHREAD_SPIN_lock(&_9p_req_st.reqs._9p_rq_st_spinlock); if (_9p_req_st.reqs.waiters) { wqe = glist_first_entry(&_9p_req_st.reqs.wait_list, wait_q_entry_t, waitq); LogFullDebug( COMPONENT_DISPATCH, "_9p_req_st.reqs.waiters %u signal wqe %p (for q %p)", _9p_req_st.reqs.waiters, wqe, q); /* release 1 waiter */ glist_del(&wqe->waitq); --(_9p_req_st.reqs.waiters); --(wqe->waiters); /* ! SPIN LOCKED */ PTHREAD_SPIN_unlock( &_9p_req_st.reqs._9p_rq_st_spinlock); PTHREAD_MUTEX_lock(&wqe->lwe.wq_mtx); /* XXX reliable handoff */ wqe->flags |= Wqe_LFlag_SyncDone; if (wqe->flags & Wqe_LFlag_WaitSync) pthread_cond_signal(&wqe->lwe.wq_cv); PTHREAD_MUTEX_unlock(&wqe->lwe.wq_mtx); } else /* ! SPIN LOCKED */ PTHREAD_SPIN_unlock( &_9p_req_st.reqs._9p_rq_st_spinlock); } } /** * @brief Execute a 9p request * * @param[in,out] req9p 9p request */ static void _9p_execute(struct _9p_request_data *req9p) { struct req_op_context op_context; init_op_context(&op_context, NULL, NULL, NULL, (sockaddr_t *)&req9p->pconn->addrpeer, 0, 0, _9P_REQUEST); if (req9p->pconn->trans_type == _9P_TCP) _9p_tcp_process_request(req9p); #ifdef _USE_9P_RDMA else if (req9p->pconn->trans_type == _9P_RDMA) _9p_rdma_process_request(req9p); #endif release_op_context(); } /* _9p_execute */ /** * @brief Free resources allocated for a 9p request * * This does not free the request itself. * * @param[in] req9p 9p request */ static void _9p_free_reqdata(struct _9p_request_data *req9p) { if (req9p->pconn->trans_type == _9P_TCP) gsh_free(req9p->_9pmsg); /* decrease connection refcount */ (void)atomic_dec_uint32_t(&req9p->pconn->refcount); } static uint32_t worker_indexer; /** * @brief Initialize a worker thread * * @param[in] ctx Thread fridge context */ static void worker_thread_initializer(struct fridgethr_context *ctx) { struct _9p_worker_data *wd = &ctx->wd; char thr_name[32]; wd->worker_index = atomic_inc_uint32_t(&worker_indexer); /* We don't care about too long string, truncated is fine and we don't * expect EOVERRUN or EINVAL. */ (void)snprintf(thr_name, sizeof(thr_name), "work-%u", wd->worker_index); SetNameFunction(thr_name); /* Initialize thr waitq */ init_wait_q_entry(&wd->wqe); } /** * @brief Finalize a worker thread * * @param[in] ctx Thread fridge context */ static void worker_thread_finalizer(struct fridgethr_context *ctx) { struct _9p_worker_data *wd = &ctx->wd; /* Destroy thr waitq */ destroy_wait_q_entry(&wd->wqe); ctx->thread_info = NULL; } /** * @brief The main function for a worker thread * * This is the body of the worker thread. Its starting arguments are * located in global array worker_data. The argument is no pointer but * the worker's index. It then uses this index to address its own * worker data in the array. * * @param[in] ctx Fridge thread context */ static void _9p_worker_run(struct fridgethr_context *ctx) { struct _9p_worker_data *worker_data = &ctx->wd; struct _9p_request_data *reqdata; pthread_mutex_t _9pw_mutex; pthread_cond_t _9pw_cond; PTHREAD_MUTEX_init(&_9pw_mutex, NULL); PTHREAD_COND_init(&_9pw_cond, NULL); /* Worker's loop */ while (!fridgethr_you_should_break(ctx)) { reqdata = _9p_dequeue_req(worker_data); if (!reqdata) continue; reqdata->_9prq_mutex = &_9pw_mutex; reqdata->_9prq_mutex = &_9pw_mutex; _9p_execute(reqdata); _9p_free_reqdata(reqdata); /* Free the req by releasing the entry */ LogFullDebug(COMPONENT_DISPATCH, "Invalidating processed entry"); gsh_free(reqdata); (void)atomic_inc_uint64_t(&nfs_health_.dequeued_reqs); } PTHREAD_MUTEX_destroy(&_9pw_mutex); PTHREAD_COND_destroy(&_9pw_cond); } int _9p_worker_init(void) { struct fridgethr_params frp; struct req_q_pair *qpair; int ix; int rc = 0; /* Init request queue before workers */ PTHREAD_SPIN_init(&_9p_req_st.reqs._9p_rq_st_spinlock, PTHREAD_PROCESS_PRIVATE); _9p_req_st.reqs.size = 0; for (ix = 0; ix < N_REQ_QUEUES; ++ix) { qpair = &(_9p_req_st.reqs._9p_request_q.qset[ix]); qpair->s = req_q_s[ix]; _9p_rpc_q_init(&qpair->producer); _9p_rpc_q_init(&qpair->consumer); } /* waitq */ glist_init(&_9p_req_st.reqs.wait_list); _9p_req_st.reqs.waiters = 0; memset(&frp, 0, sizeof(struct fridgethr_params)); frp.thr_max = _9p_param.nb_worker; frp.thr_min = _9p_param.nb_worker; frp.flavor = fridgethr_flavor_looper; frp.thread_initialize = worker_thread_initializer; frp.thread_finalize = worker_thread_finalizer; frp.wake_threads = _9p_queue_awaken; frp.wake_threads_arg = &_9p_req_st; rc = fridgethr_init(&_9p_worker_fridge, "9P", &frp); if (rc != 0) { LogMajor(COMPONENT_DISPATCH, "Unable to initialize worker fridge: %d", rc); return rc; } rc = fridgethr_populate(_9p_worker_fridge, _9p_worker_run, NULL); if (rc != 0) { LogMajor(COMPONENT_DISPATCH, "Unable to populate worker fridge: %d", rc); } return rc; } int _9p_worker_shutdown(void) { int rc, ix; if (!_9p_worker_fridge) return 0; rc = fridgethr_sync_command(_9p_worker_fridge, fridgethr_comm_stop, 120); if (rc == ETIMEDOUT) { LogMajor(COMPONENT_DISPATCH, "Shutdown timed out, cancelling threads."); fridgethr_cancel(_9p_worker_fridge); } else if (rc != 0) { LogMajor(COMPONENT_DISPATCH, "Failed shutting down worker threads: %d", rc); } for (ix = 0; ix < N_REQ_QUEUES; ++ix) { struct req_q_pair *qpair; qpair = &(_9p_req_st.reqs._9p_request_q.qset[ix]); _9p_rpc_q_destroy(&qpair->producer); _9p_rpc_q_destroy(&qpair->consumer); } PTHREAD_SPIN_destroy(&_9p_req_st.reqs._9p_rq_st_spinlock); return rc; } void DispatchWork9P(struct _9p_request_data *req) { switch (req->pconn->trans_type) { case _9P_TCP: LogDebug(COMPONENT_DISPATCH, "Dispatching 9P/TCP request %p, tcpsock=%lu", req, req->pconn->trans_data.sockfd); break; case _9P_RDMA: LogDebug(COMPONENT_DISPATCH, "Dispatching 9P/RDMA request %p", req); break; default: LogCrit(COMPONENT_DISPATCH, "/!\\ Implementation error, bad 9P transport type"); return; } /* increase connection refcount */ (void)atomic_inc_uint32_t(&req->pconn->refcount); /* new-style dispatch */ _9p_enqueue_req(req); } /** * _9p_socket_thread: 9p socket manager. * * This function is the main loop for the 9p socket manager. * One such thread exists per connection. * * @param Arg the socket number cast as a void * in pthread_create * * @return NULL * */ void *_9p_socket_thread(void *Arg) { long tcp_sock = (long)Arg; int rc = -1; struct pollfd fds[1]; int fdcount = 1; static char my_name[32]; char strcaller[SOCK_NAME_MAX]; struct display_buffer dspbuf = { sizeof(strcaller), strcaller, strcaller }; struct _9p_request_data *req = NULL; int tag; unsigned long sequence = 0; unsigned int i = 0; char *_9pmsg = NULL; uint32_t msglen; struct _9p_conn _9p_conn; socklen_t addrpeerlen; int readlen = 0; int total_readlen = 0; /* We don't care about too long string, truncated is fine and we don't * expect EOVERRUN or EINVAL. */ (void)snprintf(my_name, sizeof(my_name), "9p_sock_mgr#fd=%ld", tcp_sock); SetNameFunction(my_name); rcu_register_thread(); /* Init the struct _9p_conn structure */ memset(&_9p_conn, 0, sizeof(_9p_conn)); PTHREAD_MUTEX_init(&_9p_conn.sock_lock, NULL); _9p_conn.trans_type = _9P_TCP; _9p_conn.trans_data.sockfd = tcp_sock; for (i = 0; i < FLUSH_BUCKETS; i++) { PTHREAD_MUTEX_init(&_9p_conn.flush_buckets[i].flb_lock, NULL); glist_init(&_9p_conn.flush_buckets[i].list); } atomic_store_uint32_t(&_9p_conn.refcount, 0); /* Init the fids pointers array */ memset(&_9p_conn.fids, 0, _9P_FID_PER_CONN * sizeof(struct _9p_fid *)); /* Set initial msize. * Client may request a lower value during TVERSION */ _9p_conn.msize = _9p_param._9p_tcp_msize; if (gettimeofday(&_9p_conn.birth, NULL) == -1) LogFatal(COMPONENT_9P, "Cannot get connection's time of birth"); addrpeerlen = sizeof(_9p_conn.addrpeer); rc = getpeername(tcp_sock, (struct sockaddr *)&_9p_conn.addrpeer, &addrpeerlen); if (rc == -1) { LogMajor( COMPONENT_9P, "Cannot get peername to tcp socket for 9p, error %d (%s)", errno, strerror(errno)); /* XXX */ display_cat(&dspbuf, "(unresolved)"); goto end; } else { display_sockaddr(&dspbuf, &_9p_conn.addrpeer); LogEvent(COMPONENT_9P, "9p socket #%ld is connected to %s", tcp_sock, strcaller); } _9p_conn.client = get_gsh_client(&_9p_conn.addrpeer, false); /* Set up the structure used by poll */ memset((char *)fds, 0, sizeof(struct pollfd)); fds[0].fd = tcp_sock; fds[0].events = POLLIN | POLLPRI | POLLRDBAND | POLLRDNORM | POLLRDHUP | POLLHUP | POLLERR | POLLNVAL; for (;;) { total_readlen = 0; /* new message */ rc = poll(fds, fdcount, -1); if (rc == -1) { /* timeout = -1 => Wait indefinitely for events */ /* Interruption if not an issue */ if (errno == EINTR) continue; LogCrit(COMPONENT_9P, "Got error %u (%s) on fd %ld connect to %s while polling on socket", errno, strerror(errno), tcp_sock, strcaller); } if (fds[0].revents & POLLNVAL) { LogEvent(COMPONENT_9P, "Client %s on socket %lu produced POLLNVAL", strcaller, tcp_sock); goto end; } if (fds[0].revents & (POLLERR | POLLHUP | POLLRDHUP)) { LogEvent( COMPONENT_9P, "Client %s on socket %lu has shut down and closed", strcaller, tcp_sock); goto end; } if (!(fds[0].revents & (POLLIN | POLLRDNORM))) continue; /* Prepare to read the message */ _9pmsg = gsh_malloc(_9p_conn.msize); /* An incoming 9P request: the msg has a 4 bytes header showing the size of the msg including the header */ readlen = recv(fds[0].fd, _9pmsg, _9P_HDR_SIZE, MSG_WAITALL); if (readlen != _9P_HDR_SIZE) goto badmsg; msglen = *(uint32_t *)_9pmsg; if (msglen > _9p_conn.msize) { LogCrit(COMPONENT_9P, "Message size too big! got %u, max = %u", msglen, _9p_conn.msize); goto end; } LogFullDebug( COMPONENT_9P, "Received 9P/TCP message of size %u from client %s on socket %lu", msglen, strcaller, tcp_sock); total_readlen += readlen; while (total_readlen < msglen) { readlen = recv(fds[0].fd, _9pmsg + total_readlen, msglen - total_readlen, 0); if (readlen > 0) { total_readlen += readlen; continue; } if (readlen == 0 || (readlen < 0 && errno != EINTR)) goto badmsg; } /* while */ server_stats_transport_done(_9p_conn.client, total_readlen, 1, 0, 0, 0, 0); /* Message is good. */ (void)atomic_inc_uint64_t(&nfs_health_.enqueued_reqs); req = gsh_calloc(1, sizeof(struct _9p_request_data)); req->_9pmsg = _9pmsg; req->pconn = &_9p_conn; /* Add this request to the request list, * should it be flushed later. */ tag = *(u16 *)(_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE); _9p_AddFlushHook(req, tag, sequence++); LogFullDebug(COMPONENT_9P, "Request tag is %d", tag); /* Message was OK push it */ DispatchWork9P(req); /* Not our buffer anymore */ _9pmsg = NULL; continue; badmsg: if (readlen == 0) LogEvent( COMPONENT_9P, "Premature end for Client %s on socket %lu, total read = %u", strcaller, tcp_sock, total_readlen); else if (readlen < 0) { LogEvent( COMPONENT_9P, "Read error client %s on socket %lu errno=%d, total read = %u", strcaller, tcp_sock, errno, total_readlen); } else LogEvent( COMPONENT_9P, "Header too small! for client %s on socket %lu: readlen=%u expected=%u", strcaller, tcp_sock, readlen, _9P_HDR_SIZE); /* Either way, we close the connection. * It is not possible to survive * once we get out of sync in the TCP stream * with the client */ break; /* bail out */ } /* for( ;; ) */ end: LogEvent(COMPONENT_9P, "Closing connection on socket %lu", tcp_sock); close(tcp_sock); /* Free buffer if we encountered an error * before we could give it to a worker */ if (_9pmsg) gsh_free(_9pmsg); while (atomic_fetch_uint32_t(&_9p_conn.refcount)) { LogEvent(COMPONENT_9P, "Waiting for workers to release pconn"); sleep(1); } _9p_cleanup_fids(&_9p_conn); if (_9p_conn.client != NULL) put_gsh_client(_9p_conn.client); rcu_unregister_thread(); PTHREAD_MUTEX_destroy(&_9p_conn.sock_lock); for (i = 0; i < FLUSH_BUCKETS; i++) PTHREAD_MUTEX_destroy(&_9p_conn.flush_buckets[i].flb_lock); pthread_exit(NULL); } /* _9p_socket_thread */ /** * _9p_create_socket_V4 : create the socket and bind for 9P using * the available V4 interfaces on the host. This is not the default * for ganesha. We expect _9p_create_socket_V6 to be called first * and succeed. Only when the V6 function returns failure is this * function expected to be called. See _9p_create_socket(). * * @return socket fd or -1 in case of failure * */ static int _9p_create_socket_V4(void) { int sock = -1; int one = 1; int centvingt = 120; int neuf = 9; struct netbuf netbuf_tcp; struct t_bind bindaddr_tcp; struct __rpc_sockinfo si_tcp; struct sockaddr_in sinaddr_tcp; sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock == -1) { LogWarn(COMPONENT_9P_DISPATCH, "Error creating 9p V4 socket, error %d(%s)", errno, strerror(errno)); return -1; } if ((setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) || (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) == -1) || (setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, ¢vingt, sizeof(centvingt)) == -1) || (setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, ¢vingt, sizeof(centvingt)) == -1) || (setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &neuf, sizeof(neuf)) == -1)) { LogWarn(COMPONENT_9P_DISPATCH, "Error setting 9p V4 socket option, error %d(%s)", errno, strerror(errno)); goto err; } memset(&sinaddr_tcp, 0, sizeof(sinaddr_tcp)); sinaddr_tcp.sin_family = AF_INET; /* All the interfaces on the machine are used */ sinaddr_tcp.sin_addr.s_addr = htonl(INADDR_ANY); sinaddr_tcp.sin_port = htons(_9p_param._9p_tcp_port); netbuf_tcp.maxlen = sizeof(sinaddr_tcp); netbuf_tcp.len = sizeof(sinaddr_tcp); netbuf_tcp.buf = &sinaddr_tcp; bindaddr_tcp.qlen = SOMAXCONN; bindaddr_tcp.addr = netbuf_tcp; if (!__rpc_fd2sockinfo(sock, &si_tcp)) { LogWarn(COMPONENT_9P_DISPATCH, "Cannot get 9p socket info for tcp V4 socket error %d(%s)", errno, strerror(errno)); goto err; } if (bind(sock, (struct sockaddr *)bindaddr_tcp.addr.buf, (socklen_t)si_tcp.si_alen) == -1) { LogWarn(COMPONENT_9P_DISPATCH, "Cannot bind 9p tcp V4 socket, error %d(%s)", errno, strerror(errno)); goto err; } if (listen(sock, 20) == -1) { LogWarn(COMPONENT_9P_DISPATCH, "Cannot bind 9p tcp V4 socket, error %d(%s)", errno, strerror(errno)); goto err; } return sock; err: close(sock); return -1; } /** * _9p_create_socket_V6 : create the socket and bind for 9P using * the available V6 interfaces on the host * * @return socket fd or -1 in case of failure * */ static int _9p_create_socket_V6(void) { int sock = -1; int one = 1; int centvingt = 120; int neuf = 9; struct sockaddr_in6 sinaddr_tcp6; struct netbuf netbuf_tcp6; struct t_bind bindaddr_tcp6; struct __rpc_sockinfo si_tcp6; sock = socket(P_FAMILY, SOCK_STREAM, IPPROTO_TCP); if (sock == -1) { if (errno == EAFNOSUPPORT) { LogWarn(COMPONENT_9P_DISPATCH, "Error creating socket, V6 intfs disabled? error %d(%s)", errno, strerror(errno)); return _9p_create_socket_V4(); } return -1; } if ((setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) || (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) == -1) || (setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, ¢vingt, sizeof(centvingt)) == -1) || (setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, ¢vingt, sizeof(centvingt)) == -1) || (setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &neuf, sizeof(neuf)) == -1)) { LogWarn(COMPONENT_9P_DISPATCH, "Error setting V6 socket option, error %d(%s)", errno, strerror(errno)); goto err; } memset(&sinaddr_tcp6, 0, sizeof(sinaddr_tcp6)); sinaddr_tcp6.sin6_family = AF_INET6; /* All the interfaces on the machine are used */ sinaddr_tcp6.sin6_addr = in6addr_any; sinaddr_tcp6.sin6_port = htons(_9p_param._9p_tcp_port); netbuf_tcp6.maxlen = sizeof(sinaddr_tcp6); netbuf_tcp6.len = sizeof(sinaddr_tcp6); netbuf_tcp6.buf = &sinaddr_tcp6; bindaddr_tcp6.qlen = SOMAXCONN; bindaddr_tcp6.addr = netbuf_tcp6; if (!__rpc_fd2sockinfo(sock, &si_tcp6)) { LogWarn(COMPONENT_9P_DISPATCH, "Cannot get 9p socket info for tcp6 socket error %d(%s)", errno, strerror(errno)); goto err; } if (bind(sock, (struct sockaddr *)bindaddr_tcp6.addr.buf, (socklen_t)si_tcp6.si_alen) == -1) { LogWarn(COMPONENT_9P_DISPATCH, "Cannot bind 9p tcp6 socket, error %d (%s)", errno, strerror(errno)); goto err; } if (listen(sock, 20) == -1) { LogWarn(COMPONENT_9P_DISPATCH, "Cannot bind 9p tcp6 socket, error %d (%s)", errno, strerror(errno)); goto err; } return sock; err: close(sock); return -1; } /** * _9p_dispatcher_thread: thread used for RPC dispatching. * * This function is the main loop for the 9p dispatcher. * It never returns because it is an infinite loop. * * @param Arg (unused) * * @return Pointer to the result (but this function will mostly loop forever). * */ void *_9p_dispatcher_thread(void *Arg) { int _9p_socket; int rc = 0; long newsock = -1; pthread_attr_t attr_thr; pthread_t tcp_thrid; SetNameFunction("_9p_disp"); rcu_register_thread(); /* Calling dispatcher main loop */ LogInfo(COMPONENT_9P_DISPATCH, "Entering 9P dispatcher"); LogDebug(COMPONENT_9P_DISPATCH, "My pthread id is %p", (void *)pthread_self()); /* Set up the _9p_socket (trying V6 first, will fall back to V4 * if V6 fails). */ _9p_socket = _9p_create_socket_V6(); if (_9p_socket == -1) { LogFatal(COMPONENT_9P_DISPATCH, "Can't get socket for 9p dispatcher"); } /* Init for thread parameter (mostly for scheduling) */ PTHREAD_ATTR_init(&attr_thr); PTHREAD_ATTR_setscope(&attr_thr, PTHREAD_SCOPE_SYSTEM); PTHREAD_ATTR_setdetachstate(&attr_thr, PTHREAD_CREATE_DETACHED); LogEvent(COMPONENT_9P_DISPATCH, "9P dispatcher started"); while (true) { newsock = accept(_9p_socket, NULL, NULL); if (newsock < 0) { LogCrit(COMPONENT_9P_DISPATCH, "accept failed: %d", errno); continue; } /* Starting the thread dedicated to signal handling */ rc = pthread_create(&tcp_thrid, &attr_thr, _9p_socket_thread, (void *)newsock); if (rc != 0) { LogFatal( COMPONENT_THREAD, "Could not create 9p socket manager thread, error = %d (%s)", errno, strerror(errno)); } } /* while */ close(_9p_socket); PTHREAD_ATTR_destroy(&attr_thr); rcu_unregister_thread(); return NULL; } /* _9p_dispatcher_thread */ nfs-ganesha-6.5/src/MainNFSD/9p_rdma_callbacks.c000066400000000000000000000140531473756622300214240ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2012) * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_rdma_callbacks.c * \brief This file contains the callbacks used for 9P/RDMA. * * 9p_rdma_callbacks.c: This file contains the callbacks used for 9P/RDMA. * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "abstract_mem.h" #include "abstract_atomic.h" #include "nfs_init.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_file_handle.h" #include "server_stats.h" #include "9p.h" #include void _9p_rdma_callback_send(msk_trans_t *trans, msk_data_t *data, void *arg) { struct _9p_rdma_priv *priv = _9p_rdma_priv_of(trans); PTHREAD_MUTEX_lock(&priv->outqueue->oq_lock); data->next = priv->outqueue->data; priv->outqueue->data = data; pthread_cond_signal(&priv->outqueue->oq_cond); PTHREAD_MUTEX_unlock(&priv->outqueue->oq_lock); server_stats_transport_done(priv->pconn->client, 0, 0, 0, data->size, 1, 0); } void _9p_rdma_callback_send_err(msk_trans_t *trans, msk_data_t *data, void *arg) { struct _9p_rdma_priv *priv = _9p_rdma_priv_of(trans); /** * @todo: This should probably try to send again a few times * before unlocking */ if (priv && priv->outqueue) { PTHREAD_MUTEX_lock(&priv->outqueue->oq_lock); data->next = priv->outqueue->data; priv->outqueue->data = data; pthread_cond_signal(&priv->outqueue->oq_cond); PTHREAD_MUTEX_unlock(&priv->outqueue->oq_lock); } if (priv && priv->pconn && priv->pconn->client) server_stats_transport_done(priv->pconn->client, 0, 0, 0, 0, 0, 1); } void _9p_rdma_callback_recv_err(msk_trans_t *trans, msk_data_t *data, void *arg) { struct _9p_rdma_priv *priv = _9p_rdma_priv_of(trans); if (trans->state == MSK_CONNECTED) { msk_post_recv(trans, data, _9p_rdma_callback_recv, _9p_rdma_callback_recv_err, arg); if (priv && priv->pconn && priv->pconn->client) server_stats_transport_done(priv->pconn->client, 0, 0, 1, 0, 0, 0); } } void _9p_rdma_callback_disconnect(msk_trans_t *trans) { if (!trans || !trans->private_data) return; _9p_rdma_cleanup_conn(trans); } void _9p_rdma_process_request(struct _9p_request_data *req9p) { uint32_t msglen; int rc = 0; msk_trans_t *trans = req9p->pconn->trans_data.rdma_trans; struct _9p_rdma_priv *priv = _9p_rdma_priv_of(trans); msk_data_t *dataout; /* get output buffer and move forward in queue */ PTHREAD_MUTEX_lock(&priv->outqueue->oq_lock); while (priv->outqueue->data == NULL) { LogDebug(COMPONENT_9P, "Waiting for outqueue buffer on trans %p\n", trans); pthread_cond_wait(&priv->outqueue->oq_cond, &priv->outqueue->oq_lock); } dataout = priv->outqueue->data; priv->outqueue->data = dataout->next; dataout->next = NULL; PTHREAD_MUTEX_unlock(&priv->outqueue->oq_lock); dataout->size = 0; dataout->mr = priv->pernic->outmr; /* Use buffer received via RDMA as a 9P message */ req9p->_9pmsg = req9p->data->data; msglen = *(uint32_t *)req9p->_9pmsg; if (req9p->data->size < _9P_HDR_SIZE || msglen != req9p->data->size) { LogMajor(COMPONENT_9P, "Malformed 9P/RDMA packet, bad header size"); /* send a rerror ? */ msk_post_recv(trans, req9p->data, _9p_rdma_callback_recv, _9p_rdma_callback_recv_err, NULL); } else { LogFullDebug(COMPONENT_9P, "Received 9P/RDMA message of size %u", msglen); rc = _9p_process_buffer(req9p, dataout->data, &dataout->size); if (rc != 1) { LogMajor(COMPONENT_9P, "Could not process 9P buffer on trans %p", req9p->pconn->trans_data.rdma_trans); } msk_post_recv(trans, req9p->data, _9p_rdma_callback_recv, _9p_rdma_callback_recv_err, NULL); /* If earlier processing succeeded, post it */ if (rc == 1) { if (0 != msk_post_send( trans, dataout, _9p_rdma_callback_send, _9p_rdma_callback_send_err, NULL)) rc = -1; } if (rc != 1) { LogMajor(COMPONENT_9P, "Could not send buffer on trans %p", req9p->pconn->trans_data.rdma_trans); /* Give the buffer back right away * since no buffer is being sent */ PTHREAD_MUTEX_lock(&priv->outqueue->oq_lock); dataout->next = priv->outqueue->data; priv->outqueue->data = dataout; pthread_cond_signal(&priv->outqueue->oq_cond); PTHREAD_MUTEX_unlock(&priv->outqueue->oq_lock); } } _9p_DiscardFlushHook(req9p); } void _9p_rdma_callback_recv(msk_trans_t *trans, msk_data_t *data, void *arg) { struct _9p_request_data *req = NULL; u16 tag = 0; char *_9pmsg = NULL; (void)atomic_inc_uint64_t(&nfs_health_.enqueued_reqs); req = gsh_calloc(1, sizeof(struct _9p_request_data)); req->_9pmsg = _9pmsg; req->pconn = _9p_rdma_priv_of(trans)->pconn; req->data = data; /* Add this request to the request list, should it be flushed later. */ _9pmsg = data->data; tag = *(u16 *)(_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE); _9p_AddFlushHook(req, tag, req->pconn->sequence++); DispatchWork9P(req); server_stats_transport_done(_9p_rdma_priv_of(trans)->pconn->client, data->size, 1, 0, 0, 0, 0); } /* _9p_rdma_callback_recv */ nfs-ganesha-6.5/src/MainNFSD/9p_rdma_dispatcher.c000066400000000000000000000267341473756622300216440ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_rdma_dispatcher.c * \date $Date: 2006/02/23 12:33:05 $ * \brief The file that contains the '_9p_rdma_dispatcher_thread' function. * * */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include #include #include #include #include #include #include #include "hashtable.h" #include "log.h" #include "abstract_mem.h" #include "abstract_atomic.h" #include "nfs_init.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_dupreq.h" #include "nfs_file_handle.h" #include "client_mgr.h" #include "9p.h" #include #include static void *_9p_rdma_cleanup_conn_thread(void *arg) { msk_trans_t *trans = arg; struct _9p_rdma_priv *priv = _9p_rdma_priv_of(trans); rcu_register_thread(); if (priv) { if (priv->pconn) { LogDebug( COMPONENT_9P, "9P/RDMA: waiting till we're done with all requests on trans [%p]", trans); while (atomic_fetch_uint32_t(&priv->pconn->refcount) != 0) { sleep(1); } } LogDebug(COMPONENT_9P, "9P/RDMA: Freeing data associated with trans [%p]", trans); if (priv->pconn) { if (priv->pconn->client != NULL) put_gsh_client(priv->pconn->client); _9p_cleanup_fids(priv->pconn); } if (priv->pconn) gsh_free(priv->pconn); gsh_free(priv); } msk_destroy_trans(&trans); rcu_unregister_thread(); pthread_exit(NULL); } void _9p_rdma_cleanup_conn(msk_trans_t *trans) { pthread_attr_t attr_thr; pthread_t thr_id; /* Set the pthread attributes */ memset((char *)&attr_thr, 0, sizeof(attr_thr)); PTHREAD_ATTR_init(&attr_thr); PTHREAD_ATTR_setscope(&attr_thr, PTHREAD_SCOPE_SYSTEM); PTHREAD_ATTR_setdetachstate(&attr_thr, PTHREAD_CREATE_DETACHED); return; if (pthread_create(&thr_id, &attr_thr, _9p_rdma_cleanup_conn_thread, trans)) LogMajor( COMPONENT_9P, "9P/RDMA : dispatcher cleanup could not spawn a related thread"); else LogDebug(COMPONENT_9P, "9P/RDMA: thread #%x spawned to cleanup trans [%p]", (unsigned int)thr_id, trans); PTHREAD_ATTR_destroy(&attr_thr); } /** * _9p_rdma_handle_trans_thr: 9P/RDMA listener * * @param Arg : contains the child trans to be managed * * @return NULL * */ /* Equivalent du _9p_socket_thread */ void *_9p_rdma_thread(void *Arg) { msk_trans_t *trans = Arg; struct _9p_rdma_priv *priv = NULL; struct _9p_conn *p_9p_conn = NULL; unsigned int i = 0; int rc = 0; struct _9p_outqueue *outqueue = trans->private_data; struct sockaddr *addrpeer; rcu_register_thread(); priv = gsh_calloc(1, sizeof(*priv)); trans->private_data = priv; priv->pernic = msk_getpd(trans)->private; priv->outqueue = outqueue; p_9p_conn = gsh_calloc(1, sizeof(*p_9p_conn)); priv->pconn = p_9p_conn; for (i = 0; i < FLUSH_BUCKETS; i++) { PTHREAD_MUTEX_init(&p_9p_conn->flush_buckets[i].flb_lock, NULL); glist_init(&p_9p_conn->flush_buckets[i].list); } p_9p_conn->sequence = 0; atomic_store_uint32_t(&p_9p_conn->refcount, 0); p_9p_conn->trans_type = _9P_RDMA; p_9p_conn->trans_data.rdma_trans = trans; addrpeer = msk_get_dst_addr(trans); if (addrpeer == NULL) { LogCrit(COMPONENT_9P, "Cannot get peer address"); goto error; } memcpy(&p_9p_conn->addrpeer, addrpeer, MIN(sizeof(*addrpeer), sizeof(p_9p_conn->addrpeer))); p_9p_conn->client = get_gsh_client(&p_9p_conn->addrpeer, false); /* Init the fids pointers array */ memset(&p_9p_conn->fids, 0, _9P_FID_PER_CONN * sizeof(struct _9p_fid *)); /* Set initial msize. * Client may request a lower value during TVERSION */ p_9p_conn->msize = _9p_param._9p_rdma_msize; if (gettimeofday(&p_9p_conn->birth, NULL) == -1) LogMajor(COMPONENT_9P, "Cannot get connection's time of birth"); /* Finalize accept */ rc = msk_finalize_accept(trans); if (rc != 0) { LogMajor( COMPONENT_9P, "9P/RDMA: trans handler could not finalize accept, rc=%u", rc); goto error; } out: rcu_unregister_thread(); for (i = 0; i < FLUSH_BUCKETS; i++) PTHREAD_MUTEX_destroy(&p_9p_conn->flush_buckets[i].flb_lock); pthread_exit(NULL); error: _9p_rdma_cleanup_conn_thread(trans); goto out; } /* _9p_rdma_handle_trans */ static void _9p_rdma_setup_pernic(msk_trans_t *trans, uint8_t *outrdmabuf) { struct _9p_rdma_priv_pernic *pernic; int rc, i; /* Do nothing if we already have stuff setup */ if (msk_getpd(trans)->private) return; pernic = gsh_calloc(1, sizeof(*pernic)); /* register output buffers */ pernic->outmr = msk_reg_mr(trans, outrdmabuf, _9p_param._9p_rdma_outpool_size * _9p_param._9p_rdma_msize, IBV_ACCESS_LOCAL_WRITE); if (pernic->outmr == NULL) { rc = errno; LogFatal( COMPONENT_9P, "9P/RDMA: pernic setup could not register outrdmabuf, errno: %s (%d)", strerror(rc), rc); } /* register input buffers */ /* Alloc rdmabuf */ pernic->rdmabuf = gsh_malloc(_9p_param._9p_rdma_inpool_size * _9p_param._9p_rdma_msize); /* Register rdmabuf */ pernic->inmr = msk_reg_mr(trans, pernic->rdmabuf, _9p_param._9p_rdma_inpool_size * _9p_param._9p_rdma_msize, IBV_ACCESS_LOCAL_WRITE); if (pernic->inmr == NULL) { rc = errno; LogFatal( COMPONENT_9P, "9P/RDMA: trans handler could not register rdmabuf, errno: %s (%d)", strerror(rc), rc); } /* Get prepared to recv data */ pernic->rdata = gsh_calloc(_9p_param._9p_rdma_inpool_size, sizeof(*pernic->rdata)); for (i = 0; i < _9p_param._9p_rdma_inpool_size; i++) { pernic->rdata[i].data = pernic->rdmabuf + i * _9p_param._9p_rdma_msize; pernic->rdata[i].max_size = _9p_param._9p_rdma_msize; pernic->rdata[i].mr = pernic->inmr; rc = msk_post_recv(trans, pernic->rdata + i, _9p_rdma_callback_recv, _9p_rdma_callback_recv_err, NULL); if (rc != 0) { LogEvent( COMPONENT_9P, "9P/RDMA: trans handler could post_recv first byte of data[%u], rc=%u", i, rc); msk_dereg_mr(pernic->inmr); msk_dereg_mr(pernic->outmr); gsh_free(pernic->rdmabuf); gsh_free(pernic->rdata); gsh_free(pernic); return; } } msk_getpd(trans)->private = pernic; } static void _9p_rdma_setup_global(uint8_t **poutrdmabuf, msk_data_t **pwdata, struct _9p_outqueue **poutqueue) { uint8_t *outrdmabuf; int i; msk_data_t *wdata; struct _9p_outqueue *outqueue; outrdmabuf = gsh_malloc(_9p_param._9p_rdma_outpool_size * _9p_param._9p_rdma_msize); *poutrdmabuf = outrdmabuf; wdata = gsh_malloc(_9p_param._9p_rdma_outpool_size * sizeof(*wdata)); for (i = 0; i < _9p_param._9p_rdma_outpool_size; i++) { wdata[i].data = outrdmabuf + i * _9p_param._9p_rdma_msize; wdata[i].max_size = _9p_param._9p_rdma_msize; if (i != _9p_param._9p_rdma_outpool_size - 1) wdata[i].next = &wdata[i + 1]; else wdata[i].next = NULL; } *pwdata = wdata; outqueue = gsh_malloc(sizeof(*outqueue)); PTHREAD_MUTEX_init(&outqueue->oq_lock, NULL); PTHREAD_COND_init(&outqueue->oq_cond, NULL); outqueue->data = wdata; *poutqueue = outqueue; } static void _9p_rdma_cleanup_global(uint8_t *outrdmabuf, msk_data_t *wdata, struct _9p_outqueue *outqueue) { PTHREAD_MUTEX_destroy(&outqueue->oq_lock); PTHREAD_COND_destroy(&outqueue->oq_cond); gsh_free(outrdmabuf); gsh_free(wdata); gsh_free(outqueue); } /** * _9p_rdma_dispatcher_thread: 9P/RDMA dispatcher * * @param Arg the socket number cast as a void * in pthread_create * * @return NULL * */ void *_9p_rdma_dispatcher_thread(void *Arg) { msk_trans_t *trans; msk_trans_t *child_trans; msk_trans_attr_t trans_attr; pthread_attr_t attr_thr; pthread_t thrid_handle_trans; uint8_t *outrdmabuf = NULL; msk_data_t *wdata; struct _9p_outqueue *outqueue; /* Port is a uint16_t so prints out to at most 5 characters */ char port[6]; rcu_register_thread(); memset(&trans_attr, 0, sizeof(msk_trans_attr_t)); trans_attr.server = _9p_param._9p_rdma_backlog; trans_attr.rq_depth = _9p_param._9p_rdma_inpool_size + 1; trans_attr.sq_depth = _9p_param._9p_rdma_outpool_size + 1; /* Can't overrun and shouldn't return EOVERFLOW or EINVAL */ (void)snprintf(port, sizeof(port), "%d", _9p_param._9p_rdma_port); trans_attr.port = port; trans_attr.node = "::"; trans_attr.use_srq = 1; trans_attr.disconnect_callback = _9p_rdma_callback_disconnect; trans_attr.worker_count = -1; /* if worker_count isn't -1: trans_attr.worker_queue_size = 256; */ trans_attr.debug = MSK_DEBUG_EVENT; /* mooshika stats: * trans_attr.stats_prefix + trans_attr.debug |= MSK_DEBUG_SPEED */ SetNameFunction("_9p_rdma_disp"); /* Calling dispatcher main loop */ LogInfo(COMPONENT_9P_DISPATCH, "Entering 9P/RDMA dispatcher"); LogDebug(COMPONENT_9P_DISPATCH, "My pthread id is %p", (void *)pthread_self()); /* Prepare attr_thr for dispatch */ memset((char *)&attr_thr, 0, sizeof(attr_thr)); /* Set the pthread attributes */ PTHREAD_ATTR_init(&attr_thr); PTHREAD_ATTR_setscope(&attr_thr, PTHREAD_SCOPE_SYSTEM); iPTHREAD_ATTR_setdetachstate(&attr_thr, PTHREAD_CREATE_DETACHED); /* Init RDMA via mooshika */ if (msk_init(&trans, &trans_attr)) LogFatal(COMPONENT_9P, "9P/RDMA dispatcher could not start mooshika engine"); else LogEvent(COMPONENT_9P, "Mooshika engine is started"); /* Bind Mooshika */ if (msk_bind_server(trans)) LogFatal(COMPONENT_9P, "9P/RDMA dispatcher could not bind mooshika engine"); else LogEvent(COMPONENT_9P, "Mooshika engine is bound"); /* Start infinite loop here */ while (1) { child_trans = msk_accept_one(trans); if (child_trans == NULL) LogMajor( COMPONENT_9P, "9P/RDMA : dispatcher failed to accept a new client"); else { /* Create output buffers on first connection. * need it here because we don't want multiple * children to do this job. */ if (!outrdmabuf) { _9p_rdma_setup_global(&outrdmabuf, &wdata, &outqueue); } _9p_rdma_setup_pernic(child_trans, outrdmabuf); child_trans->private_data = outqueue; if (pthread_create(&thrid_handle_trans, &attr_thr, _9p_rdma_thread, child_trans)) LogMajor( COMPONENT_9P, "9P/RDMA : dispatcher accepted a new client but could not spawn a related thread"); else LogEvent( COMPONENT_9P, "9P/RDMA: thread #%x spawned to managed new child_trans [%p]", (unsigned int)thrid_handle_trans, child_trans); } } /* for( ;; ) */ PTHREAD_ATTR_destroy(&attr_thr); _9p_rdma_cleanup_global(outrdmabuf, wdata, outqueue); rcu_unregister_thread(); pthread_exit(NULL); } /* _9p_rdma_dispatcher_thread */ nfs-ganesha-6.5/src/MainNFSD/CMakeLists.txt000066400000000000000000000132121473756622300204620ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- add_definitions( -D__USE_GNU ) if(USE_DBUS) include_directories( ${DBUS_INCLUDE_DIRS} ) endif(USE_DBUS) if(RADOS_URLS) include_directories( ${RADOS_INCLUDE_DIR} ) endif(RADOS_URLS) ########### next target ############### SET(MainServices_STAT_SRCS nfs_admin_thread.c nfs_rpc_callback.c nfs_worker_thread.c nfs_rpc_dispatcher_thread.c nfs_rpc_tcp_socket_manager_thread.c nfs_init.c nfs_lib.c nfs_metrics.c nfs_reaper_thread.c ../support/client_mgr.c ) if(USE_9P) SET(MainServices_STAT_SRCS ${MainServices_STAT_SRCS} 9p_dispatcher.c) endif(USE_9P) if(USE_9P AND USE_9P_RDMA) SET(MainServices_STAT_SRCS ${MainServices_STAT_SRCS} 9p_rdma_dispatcher.c 9p_rdma_callbacks.c) endif(USE_9P AND USE_9P_RDMA) if(USE_NFS_RDMA) add_definitions(-D_USE_NFS_RDMA) endif(USE_NFS_RDMA) if(USE_CB_SIMULATOR) SET(MainServices_STAT_SRCS ${MainServices_STAT_SRCS} nfs_rpc_callback_simulator.c) endif(USE_CB_SIMULATOR) if(USE_UPCALL_SIMULATOR) SET(MainServices_STAT_SRCS ${MainServices_STAT_SRCS} fsal_upcall_simulator_thread.c ) endif(USE_UPCALL_SIMULATOR) if(USE_CB_SIMULATOR) SET(MainServices_STAT_SRCS ${MainServices_STAT_SRCS} nfs_rpc_callback_simulator.c ) endif(USE_CB_SIMULATOR) add_library(MainServices OBJECT ${MainServices_STAT_SRCS}) add_sanitizers(MainServices) set_target_properties(MainServices PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(MainServices gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) # FSAL core sources # fsal_manager and fsal_destroyer are the only objects referenced by the # core server all the rest are for the common support of fsal plugins. set(fsal_CORE_SRCS ../FSAL/fsal_convert.c ../FSAL/commonlib.c ../FSAL/fsal_manager.c ../FSAL/access_check.c ../FSAL/fsal_config.c ../FSAL/default_methods.c ../FSAL/common_pnfs.c ../FSAL/fsal_destroyer.c ../FSAL/fsal_helper.c ../FSAL_UP/fsal_up_top.c ../FSAL_UP/fsal_up_async.c ) if (GSH_CAN_HOST_LOCAL_FS) set(fsal_CORE_SRCS ${fsal_CORE_SRCS} ../FSAL/localfs.c ) endif(GSH_CAN_HOST_LOCAL_FS) if (USE_ACL_MAPPING) set(fsal_CORE_SRCS ${fsal_CORE_SRCS} ../FSAL/posix_acls.c ) endif(USE_ACL_MAPPING) set(ganesha_nfsd_OBJS $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ ) if(USE_DBUS) set(ganesha_nfsd_OBJS ${ganesha_nfsd_OBJS} $ ) endif(USE_DBUS) if(USE_NLM) set(ganesha_nfsd_OBJS ${ganesha_nfsd_OBJS} $ ) endif(USE_NLM) if(USE_RQUOTA) set(ganesha_nfsd_OBJS ${ganesha_nfsd_OBJS} $ ) endif(USE_RQUOTA) if(USE_NFSACL3) set(ganesha_nfsd_OBJS ${ganesha_nfsd_OBJS} $ ) endif(USE_NFSACL3) if(USE_9P) set(ganesha_nfsd_OBJS ${ganesha_nfsd_OBJS} $ ) endif(USE_9P) add_library(ganesha_nfsd SHARED ${ganesha_nfsd_OBJS} ${fsal_CORE_SRCS} ) add_sanitizers(ganesha_nfsd) target_link_libraries(ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${SYSTEM_LIBRARIES} ${LTTNG_LIBRARIES} ${MOOSHIKA_LIBRARIES} ${MONITORING_LIBRARIES} ) if (USE_LTTNG) target_link_libraries(ganesha_nfsd ganesha_trace_symbols) endif (USE_LTTNG) if(SANITIZE_ADDRESS) set(undef_flags ${LDFLAG_ALLOW_UNDEF}) else(SANITIZE_ADDRESS) set(undef_flags ${LDFLAG_DISALLOW_UNDEF}) endif(SANITIZE_ADDRESS) if(LINUX OR FREEBSD) # Apple's /bin/ld does not support the --version-script flag. set(ver_script_flags "-Wl,--version-script=${GANESHA_TOP_CMAKE_DIR}/MainNFSD/libganesha_nfsd.ver") endif(LINUX OR FREEBSD) set_target_properties(ganesha_nfsd PROPERTIES LINK_FLAGS "${undef_flags} ${ver_script_flags}") set_target_properties(ganesha_nfsd PROPERTIES VERSION "${GANESHA_MAJOR_VERSION}${GANESHA_MINOR_VERSION}" SOVERSION "${GANESHA_MAJOR_VERSION}${GANESHA_MINOR_VERSION}" ) install(TARGETS ganesha_nfsd LIBRARY DESTINATION ${LIB_INSTALL_DIR}) #install(TARGETS ganesha.nfsd COMPONENT daemon DESTINATION bin) ########### install files ############### # We are still missing the install of docs and stuff nfs-ganesha-6.5/src/MainNFSD/GNUmakefile.VERSION.include000066400000000000000000000005151473756622300225440ustar00rootroot00000000000000# This file is to be included in the GNUmakefile for versioning information VERSION = "0.99-11-snmp-64bits-tcp--rpcsecgss" VERSION_COMMENT = "GANESHA 64 bits compliant. SNMP exported stats. New TCP connection management. Branch with RPCSEC_GSS support" VERSION_FLAGS= -DVERSION='$(VERSION)' -DVERSION_COMMENT='$(VERSION_COMMENT)' nfs-ganesha-6.5/src/MainNFSD/libganesha_nfsd.ver000066400000000000000000000160741473756622300215600ustar00rootroot00000000000000{ global: ace_count; admin_halt; admin_shutdown; avltree_do_insert; avltree_init; avltree_next; avltree_remove; avltree_sup; base64url_encode; change_fsid_type; check_share_conflict; check_verifier_attrlist; check_verifier_stat; CityHash64; clear_op_context_export; close_fsal_fd; compound_data_Free; component_log_level; config_error_no_error; config_errs_to_log; config_Free; config_GetBlockNode; config_ParseFile; config_url_init; connection_manager__callback_set; connection_manager__callback_clear; connection_manager__drain_and_disconnect_local; convert_ipv6_to_ipv4; create_log_facility; decode_fsid; def_pnfs_ds_ops; default_mutex_attr; default_rwlock_attr; deleg_types; disable_log_facility; display_fsinfo; display_len_cat; display_opaque_value_max; display_opaque_bytes_flags; display_sockaddr; display_sockaddr_port; display_start; display_vprintf; DisplayLogComponentLevel; enable_log_facility; encode_fsid; err_type; err_type_str; export_admin_counter; fd_lru_pkginit; fd_lru_pkgshutdown; fgetxattr; find_unused_blocks; flistxattr; fhlink; fhreadlink; free_export_ops; fremovexattr; fridgethr_cancel; fridgethr_destroy; fridgethr_init; fridgethr_submit; fridgethr_sync_command; fs_lock; fsal_acl_2_posix_acl; fsal_acl_support; fsal_attach_export; fsal_acl_to_mode; fsal_common_is_referral; fsal_complete_fd_work; fsal_complete_io; insert_fd_lru; bump_fd_lru; remove_fd_lru; fsal_create; fsal_default_obj_ops_init; fsal_detach_export; fsal_export_init; fsal_export_stack; fsal_inherit_acls; fsal_init; fsal_link; fsal_listxattr_helper; fsal_lookup; fsal_can_reuse_mode_to_acl; fsal_mode_to_acl; fsal_obj_handle_init; fsal_obj_handle_fini; fsal_open2; fsal_print_acl_int; open_fd_count; fsal_pnfs_ds_fini; fsal_read; fsal_read2; fsal_readdir; fsal_readlink; fsal_remove; fsal_rename; fsal_reopen2; fsal_restore_ganesha_credentials; fsal_setattr; fsal_set_credentials; fsal_set_credentials_only_one_user; fsal_start_fd_work; fsal_start_global_io; fsal_start_io; fsal_supported_attrs; fsal_test_access; fsal_write; fsal2posix_openflags; fsal2unix_mode; FSAL_encode_file_layout; FSAL_encode_v4_multipath; fsetxattr; getfhat; get_fs_first_export_ref; get_gsh_export; get_optional_attrs; general_fridge; gsh_dbus_append_timestamp; gsh_dbus_register_path; gsh_dbus_broadcast; gsh_dbus_status_reply; gsh_refstr_alloc; gsh_refstr_put; gsh_refstr_release; g_max_files_delegatable; g_nodeid; hashtable_deletelatched; hashtable_for_each; hashtable_getlatch; hashtable_init; hashtable_destroy; hashtable_releaselatched; hashtable_setlatched; hashtable_test_and_set; ht_confirmed_client_id; init_error_type; init_op_context; init_op_context_simple; init_server_pkgs; insert_fd_lru; is_filesystem_exported; load_config_from_node; load_config_from_parse; log_attrlist; lookup_dev; lookup_fsal; lookup_fsid; lookup_fsid_locked; LogMallocFailure; mdcache_lru_release_entries; mdcache_param; merge_share; monitoring_worker_thread_max_increment; monitoring_worker_thread_min_increment; msg_fsal_err; nfs_notify_grace_waiters; nfs4_ace_alloc; nfs4_ace_free; nfs4_acl_entry_inc_ref; nfs4_acl_new_entry; nfs4_Compound_FreeOne; nfs4_export_check_access; nfs4_fs_locations_get_ref; nfs4_fs_locations_new; nfs4_Fattr_To_FSAL_attr; nfs4_Fattr_To_fsinfo; nfs4_Fattr_Free; nfs4_FSALattr_To_Fattr; nfs4_FSALToFhandle; nfs4_recovery_init; nfs4_acl_release_entry; nfs4_fs_locations_release; nfs4_op_link; nfs4_op_lookup; nfs4_op_putfh; nfs4_op_rename; nfs_config_path; nfs_export_get_root_entry; nfs_grace_is_member; nfs_init_init; nfs_init_wait; nfs_init_wait_timeout; nfs_libmain; nfs_param; nfs_pidfile_path; nfs_prereq_destroy; nfs_prereq_init; nfs_recovery_get_nodeid; nfs_ServerBootTime; nfs_ServerEpoch; nfs_set_param_from_conf; nfs_start; nfs_start_grace; nfs_wait_for_grace_enforcement; nfsop4_to_str; nfsproc3_to_str; nfsstat3_to_str; nfsstat4_to_str; nfs_get_evchannel_id; noop_conf_commit; noop_conf_init; object_file_type_to_str; op_ctx; open_dir_by_path_walk; pnfs_ds_insert; pnfs_ds_put; pnfs_ds_remove; posix2fsal_attributes; posix2fsal_attributes_all; posix2fsal_devt; posix2fsal_error; posix2fsal_fsid; posix2fsal_type; posix2nfs4_error; posix_acl_2_fsal_acl; posix_acl_2_xattr; posix_acl_entries_count; posix_acl_xattr_size; ReadDataServers; ReadExports; reaper_wake; RegisterCleanup; register_fsal; register_url_provider; release_log_facility; release_op_context; release_posix_file_system; read_log_config; report_config_errors; resume_op_context; claim_posix_filesystems; resolve_posix_filesystem; populate_posix_file_systems; ReturnLevelAscii; re_index_fs_fsid; root_op_export_set; root_op_export_options; set_common_verifier; set_const_log_str; set_op_context_export_fsal; set_saved_entry; SetNameFunction; sprint_sockip; start_fsals; state_err_str; strlcpy; subfsal_commit; suspend_op_context; to_vfs_dirent; unclaim_all_export_maps; unclaim_fs; unix2fsal_mode; unregister_fsal; up_async_layoutrecall; up_async_lock_avail; up_async_lock_grant; up_async_notify_device; up_async_update; update_export; update_share_counters; up_async_delegrecall; up_ready_cancel; up_ready_wait; vfs_readents; vfs_utimes; vfs_utimesat; xattr_2_posix_acl; xdr_io_data; xdr_notify; xdr_READ4res_uio_setup; _get_gsh_export_ref; _put_gsh_export; __tracepoint_fsalmem___mem_free; __tracepoint_fsalmem___mem_alloc_state; __tracepoint_fsalmem___mem_inuse; __tracepoint_fsalmem___mem_get_ref; __tracepoint_fsalmem___mem_put_ref; __tracepoint_fsalmem___mem_alloc; __tracepoint_fsalmem___mem_lookup; __tracepoint_fsalmem___mem_readdir; __tracepoint_fsalmem___mem_mkdir; __tracepoint_fsalmem___mem_getattrs; __tracepoint_fsalmem___mem_setattrs; __tracepoint_fsalmem___mem_link; __tracepoint_fsalmem___mem_unlink; __tracepoint_fsalmem___mem_rename; __tracepoint_fsalmem___mem_open; __tracepoint_fsalmem___mem_read; __tracepoint_fsalmem___mem_write; __tracepoint_fsalmem___mem_close; __tracepoint_fsalmem___mem_create_handle; __tracepoint_fsalgl___open_fd; __tracepoint_fsalgl___close_fd; __tracepoint_fsalgl___gl_handle; __tracepoint_fsalceph___ceph_create_handle; __tracepoint_fsalceph___ceph_close; __tracepoint_fsalceph___ceph_commit; __tracepoint_fsalceph___ceph_falloc; __tracepoint_fsalceph___ceph_getattrs; __tracepoint_fsalceph___ceph_lease; __tracepoint_fsalceph___ceph_lock; __tracepoint_fsalceph___ceph_lookup; __tracepoint_fsalceph___ceph_mkdir; __tracepoint_fsalceph___ceph_mknod; __tracepoint_fsalceph___ceph_open; __tracepoint_fsalceph___ceph_read; __tracepoint_fsalceph___ceph_readdir; __tracepoint_fsalceph___ceph_setattrs; __tracepoint_fsalceph___ceph_unlink; __tracepoint_fsalceph___ceph_write; local: *; }; nfs-ganesha-6.5/src/MainNFSD/maketest.conf000066400000000000000000000027711473756622300204160ustar00rootroot00000000000000# # Fichier de configuration de test # # $Header: /cea/home/cvs/cvs/SHERPA/BaseCvs/GANESHA/src/MainNFSD/maketest.conf,v 1.2 2004/10/22 09:24:32 deniel Exp $ # # $Log: maketest.conf,v $ # Revision 1.2 2004/10/22 09:24:32 deniel # No more dynamic libraries compiled # # Revision 1.1 2004/10/04 14:05:19 deniel # Dispatcher thread presque ok # # Revision 1.1 2004/09/30 14:08:42 deniel # Ajout des log en anglais (a partir des logs aglae) # # # Test Test_libloghash_Static { Product = Static version of the log library Command = ksh ../scripts/run_test_liblog.ksh Comment = Check for correctness of the log Success TestOk { STDOUT =~ /Test reussi : tous les tests sont passes avec succes/m AND STATUS == 0 } Failure FoundBadValue { STATUS != 0 AND STDOUT =~ /Test ECHOUE : la valeur lue est incorrecte/m } Failure BadHashTableInit { STATUS != 0 AND STDOUT =~ /Test ECHOUE : Mauvaise init/m } Failure BadHashDelete { STATUS != 0 AND STDOUT =~ /Test ECHOUE : effacement incorrect/m } Failure BadStatistique { STATUS != 0 AND STDOUT =~ /Test ECHOUE : statistiques incorrectes/m } Failure KeyIncoherence { STATUS != 0 AND STDOUT =~ /Test ECHOUE : La clef n'est pas trouvee/m } Failure RedondantKey { STATUS != 0 AND STDOUT =~ /Test ECHOUE : Clef redondante/m } } nfs-ganesha-6.5/src/MainNFSD/nfs_admin_thread.c000066400000000000000000000507641473756622300213700ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_admin_thread.c * @brief The admin_thread and support code. */ #include "config.h" #include #include #include #include #ifdef LINUX #include /* For mtrace/muntrace */ #endif #ifndef __APPLE__ #include #endif #include "nfs_core.h" #include "log.h" #include "sal_functions.h" #include "sal_data.h" #include "idmapper.h" #include "delayed_exec.h" #include "export_mgr.h" #include "pnfs_utils.h" #include "fsal.h" #include "netgroup_cache.h" #ifdef USE_DBUS #include "gsh_dbus.h" #include "mdcache.h" #include "nfs_lib.h" #endif #include "conf_url.h" #include "nfs_rpc_callback.h" /** * @brief Mutex protecting shutdown flag. */ static pthread_mutex_t admin_control_mtx; /** * @brief Condition variable to signal change in shutdown flag. */ static pthread_cond_t admin_control_cv; /** * @brief Flag to indicate shutdown Ganesha. * * Protected by admin_control_mtx and signaled by admin_control_cv. */ bool admin_shutdown; #ifdef USE_DBUS /** * @brief Dbus method get grace period status * * @param[in] args dbus args * @param[out] reply dbus reply message with grace period status */ static bool admin_dbus_get_grace(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "get grace success"; bool success = true; DBusMessageIter iter; dbus_bool_t ingrace; dbus_message_iter_init_append(reply, &iter); if (args != NULL) { errormsg = "Get grace takes no arguments."; success = false; LogWarn(COMPONENT_DBUS, "%s", errormsg); goto out; } ingrace = nfs_in_grace(); dbus_message_iter_append_basic(&iter, DBUS_TYPE_BOOLEAN, &ingrace); out: gsh_dbus_status_reply(&iter, success, errormsg); return success; } static struct gsh_dbus_method method_get_grace = { .name = "get_grace", .method = admin_dbus_get_grace, .args = { { .name = "isgrace", .type = "b", .direction = "out", }, STATUS_REPLY, END_ARG_LIST } }; /** * @brief Dbus method start grace period * * @param[in] args Unused * @param[out] reply Unused */ static bool admin_dbus_grace(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "Started grace period"; bool success = true; DBusMessageIter iter; nfs_grace_start_t gsp; char *input = NULL; char *ip; int ret; dbus_message_iter_init_append(reply, &iter); if (args == NULL) { errormsg = "Grace period take 1 arguments: event:IP-address."; LogWarn(COMPONENT_DBUS, "%s", errormsg); success = false; goto out; } if (dbus_message_iter_get_arg_type(args) != DBUS_TYPE_STRING) { errormsg = "Grace period arg 1 not a string."; success = false; LogWarn(COMPONENT_DBUS, "%s", errormsg); goto out; } dbus_message_iter_get_basic(args, &input); gsp.nodeid = -1; gsp.event = EVENT_TAKE_IP; ip = index(input, ':'); if (ip == NULL) gsp.ipaddr = input; /* no event specified */ else { int size = strlen(input) + 1; char *buf = alloca(size); gsp.ipaddr = ip + 1; /* point at the ip passed the : */ memcpy(buf, input, size); ip = strstr(buf, ":"); if (ip != NULL) { *ip = '\0'; /* replace ":" with null */ gsp.event = atoi(buf); } if (gsp.event == EVENT_TAKE_NODEID) gsp.nodeid = atoi(gsp.ipaddr); } do { ret = nfs_start_grace(&gsp); /* * grace could fail if there are refs taken. * wait for no refs and retry. */ if (ret == -EAGAIN) { LogEvent(COMPONENT_DBUS, "Retry grace"); nfs_wait_for_grace_norefs(); } else if (ret) { LogCrit(COMPONENT_DBUS, "Start grace failed %d", ret); success = false; errormsg = "Unable to start grace"; break; } } while (ret); out: gsh_dbus_status_reply(&iter, success, errormsg); return success; } static struct gsh_dbus_method method_grace_period = { .name = "grace", .method = admin_dbus_grace, .args = { IPADDR_ARG, STATUS_REPLY, END_ARG_LIST } }; /** * @brief Dbus method for shutting down Ganesha * * @param[in] args Unused * @param[out] reply Unused */ static bool admin_dbus_shutdown(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "Server shut down"; bool success = true; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (args != NULL) { errormsg = "Shutdown takes no arguments."; success = false; LogWarn(COMPONENT_DBUS, "%s", errormsg); goto out; } admin_halt(); out: gsh_dbus_status_reply(&iter, success, errormsg); return success; } static struct gsh_dbus_method method_shutdown = { .name = "shutdown", .method = admin_dbus_shutdown, .args = { STATUS_REPLY, END_ARG_LIST } }; /** * @brief Dbus method for flushing manage gids cache * * @param[in] args * @param[out] reply */ static bool admin_dbus_purge_gids(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "Purge gids cache"; bool success = true; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (args != NULL) { errormsg = "Purge gids takes no arguments."; success = false; LogWarn(COMPONENT_DBUS, "%s", errormsg); goto out; } uid2grp_clear_cache(); out: gsh_dbus_status_reply(&iter, success, errormsg); return success; } static struct gsh_dbus_method method_purge_gids = { .name = "purge_gids", .method = admin_dbus_purge_gids, .args = { STATUS_REPLY, END_ARG_LIST } }; /** * @brief Dbus method for flushing netgroup cache * * @param[in] args * @param[out] reply */ static bool admin_dbus_purge_netgroups(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "Purge netgroup cache"; bool success = true; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (args != NULL) { errormsg = "Purge netgroup takes no arguments."; success = false; LogWarn(COMPONENT_DBUS, "%s", errormsg); goto out; } ng_clear_cache(); out: gsh_dbus_status_reply(&iter, success, errormsg); return success; } static struct gsh_dbus_method method_purge_netgroups = { .name = "purge_netgroups", .method = admin_dbus_purge_netgroups, .args = { STATUS_REPLY, END_ARG_LIST } }; /** * @brief Dbus method for flushing idmapper cache * * @param[in] args * @param[out] reply */ static bool admin_dbus_purge_idmapper_cache(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "Purge idmapper cache"; bool success = true; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (args != NULL) { errormsg = "Purge idmapper takes no arguments."; success = false; LogWarn(COMPONENT_DBUS, "%s", errormsg); goto out; } idmapper_clear_cache(); out: gsh_dbus_status_reply(&iter, success, errormsg); return success; } static struct gsh_dbus_method method_purge_idmapper_cache = { .name = "purge_idmapper_cache", .method = admin_dbus_purge_idmapper_cache, .args = { STATUS_REPLY, END_ARG_LIST } }; /** * @brief Dbus method for updating open fd limit * * @param[in] args * @param[out] reply */ static bool admin_dbus_init_fds_limit(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "Init fds limit"; bool success = true; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (args != NULL) { errormsg = "Init fds limit takes no arguments."; success = false; LogWarn(COMPONENT_DBUS, "%s", errormsg); goto out; } init_fds_limit(); out: gsh_dbus_status_reply(&iter, success, errormsg); return success; } static struct gsh_dbus_method method_init_fds_limit = { .name = "init_fds_limit", .method = admin_dbus_init_fds_limit, .args = { STATUS_REPLY, END_ARG_LIST } }; /** * @brief Dbus method for enabling malloc trace * * @param[in] args * @param[out] reply */ static bool admin_dbus_malloc_trace(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "malloc trace"; bool success = true; DBusMessageIter iter; char *filename; dbus_message_iter_init_append(reply, &iter); if (args == NULL) { errormsg = "malloc trace needs trace filename."; success = false; goto out; } if (dbus_message_iter_get_arg_type(args) != DBUS_TYPE_STRING) { errormsg = "malloc trace needs trace filename."; success = false; goto out; } dbus_message_iter_get_basic(args, &filename); #ifdef LINUX LogEvent(COMPONENT_DBUS, "enabling malloc trace to %s.", filename); setenv("MALLOC_TRACE", filename, 1); mtrace(); #else errormsg = "malloc trace is not supported"; success = false; #endif out: gsh_dbus_status_reply(&iter, success, errormsg); return success; } /** * @brief Dbus method for disabling malloc trace * * @param[in] args * @param[out] reply */ static bool admin_dbus_malloc_untrace(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "malloc untrace"; bool success = true; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (args != NULL) { errormsg = "malloc untrace takes no arguments."; success = false; goto out; } #ifdef LINUX LogEvent(COMPONENT_DBUS, "disabling malloc trace."); muntrace(); #else errormsg = "malloc untrace is not supported"; success = false; #endif out: gsh_dbus_status_reply(&iter, success, errormsg); return success; } static struct gsh_dbus_method method_malloc_trace = { .name = "malloc_trace", .method = admin_dbus_malloc_trace, .args = { { .name = "tracefile", .type = "s", .direction = "in" }, STATUS_REPLY, END_ARG_LIST } }; static struct gsh_dbus_method method_malloc_untrace = { .name = "malloc_untrace", .method = admin_dbus_malloc_untrace, .args = { STATUS_REPLY, END_ARG_LIST } }; /** * @brief Dbus method for enabling malloc trim * * @param[in] args * @param[out] reply */ static bool admin_dbus_trim_enable(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "Malloc trim enabled"; bool success = true; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); LogEvent(COMPONENT_MEMLEAKS, "enabling malloc_trim"); nfs_param.core_param.malloc_trim = true; gsh_dbus_status_reply(&iter, success, errormsg); return success; } static struct gsh_dbus_method method_trim_enable = { .name = "trim_enable", .method = admin_dbus_trim_enable, .args = { STATUS_REPLY, END_ARG_LIST } }; /** * @brief Dbus method for disabling malloc trim * * @param[in] args * @param[out] reply */ static bool admin_dbus_trim_disable(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "Malloc trim disabled"; bool success = true; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); LogEvent(COMPONENT_MEMLEAKS, "disabling malloc_trim"); nfs_param.core_param.malloc_trim = false; gsh_dbus_status_reply(&iter, success, errormsg); return success; } static struct gsh_dbus_method method_trim_disable = { .name = "trim_disable", .method = admin_dbus_trim_disable, .args = { STATUS_REPLY, END_ARG_LIST } }; /** * @brief Dbus method for calling malloc_trim() * * @param[in] args * @param[out] reply */ static bool admin_dbus_trim_call(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "malloc_trim() called"; bool success = true; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); LogEvent(COMPONENT_MEMLEAKS, "Calling malloc_trim"); malloc_trim(0); gsh_dbus_status_reply(&iter, success, errormsg); return success; } static struct gsh_dbus_method method_trim_call = { .name = "trim_call", .method = admin_dbus_trim_call, .args = { STATUS_REPLY, END_ARG_LIST } }; /** * @brief Dbus method for getting trim status * * @param[in] args * @param[out] reply */ static bool admin_dbus_trim_status(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "Malloc trim status: enabled"; bool success = true; DBusMessageIter iter; char hostname[64 + 1] = { 0 }; char name[100]; FILE *fp; /* log malloc_info() as a side effect! */ (void)gethostname(hostname, sizeof(hostname)); snprintf(name, sizeof(name), "/tmp/mallinfo-%s.%d.txt", hostname, getpid()); fp = fopen(name, "w"); if (fp != NULL) { malloc_info(0, fp); fclose(fp); } dbus_message_iter_init_append(reply, &iter); if (!nfs_param.core_param.malloc_trim) errormsg = "Malloc trim status: disabled"; gsh_dbus_status_reply(&iter, success, errormsg); return success; } static struct gsh_dbus_method method_trim_status = { .name = "trim_status", .method = admin_dbus_trim_status, .args = { STATUS_REPLY, END_ARG_LIST } }; /** * @brief Dbus method for getting trim status * * @param[in] args * @param[out] reply */ static bool admin_reread_config(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); bool success = reread_config(); if (!success) errormsg = "Failed to reread config"; gsh_dbus_status_reply(&iter, success, errormsg); return success; } static struct gsh_dbus_method method_reread_config = { .name = "reread_config", .method = admin_reread_config, .args = { STATUS_REPLY, END_ARG_LIST } }; static struct gsh_dbus_method *admin_methods[] = { &method_shutdown, &method_grace_period, &method_get_grace, &method_purge_gids, &method_purge_netgroups, &method_init_fds_limit, &method_purge_idmapper_cache, &method_malloc_trace, &method_malloc_untrace, &method_trim_enable, &method_trim_disable, &method_trim_call, &method_trim_status, &method_reread_config, NULL }; #define HANDLE_VERSION_PROP(prop_name, prop_string) \ static bool dbus_prop_get_VERSION_##prop_name(DBusMessageIter *reply) \ { \ const char *version_string = prop_string; \ if (!dbus_message_iter_append_basic(reply, DBUS_TYPE_STRING, \ &version_string)) \ return false; \ return true; \ } \ \ static struct gsh_dbus_prop VERSION_##prop_name##_prop = { \ .name = "VERSION_" #prop_name, \ .access = DBUS_PROP_READ, \ .type = "s", \ .get = dbus_prop_get_VERSION_##prop_name, \ .set = NULL \ } #define VERSION_PROPERTY_ITEM(name) (&VERSION_##name##_prop) HANDLE_VERSION_PROP(RELEASE, GANESHA_VERSION); #if !GANESHA_BUILD_RELEASE HANDLE_VERSION_PROP(COMPILE_DATE, __DATE__); HANDLE_VERSION_PROP(COMPILE_TIME, __TIME__); HANDLE_VERSION_PROP(COMMENT, VERSION_COMMENT); HANDLE_VERSION_PROP(GIT_HEAD, _GIT_HEAD_COMMIT); HANDLE_VERSION_PROP(GIT_DESCRIBE, _GIT_DESCRIBE); #endif static struct gsh_dbus_prop *admin_props[] = { VERSION_PROPERTY_ITEM(RELEASE), #if !GANESHA_BUILD_RELEASE VERSION_PROPERTY_ITEM(COMPILE_DATE), VERSION_PROPERTY_ITEM(COMPILE_TIME), VERSION_PROPERTY_ITEM(COMMENT), VERSION_PROPERTY_ITEM(GIT_HEAD), VERSION_PROPERTY_ITEM(GIT_DESCRIBE), #endif NULL }; static struct gsh_dbus_signal heartbeat_signal = { .name = HEARTBEAT_NAME, .signal = NULL, .args = { HEARTBEAT_ARG, END_ARG_LIST } }; static struct gsh_dbus_signal *admin_signals[] = { &heartbeat_signal, NULL }; static struct gsh_dbus_interface admin_interface = { .name = DBUS_ADMIN_IFACE, .props = admin_props, .methods = admin_methods, .signals = admin_signals }; static struct gsh_dbus_interface *admin_interfaces[] = { &admin_interface, &log_interface, NULL }; #endif /* USE_DBUS */ /** * @brief Initialize admin thread control state and DBUS methods. */ void nfs_Init_admin_thread(void) { PTHREAD_MUTEX_init(&admin_control_mtx, NULL); PTHREAD_COND_init(&admin_control_cv, NULL); #ifdef USE_DBUS gsh_dbus_register_path("admin", admin_interfaces); #endif /* USE_DBUS */ LogEvent(COMPONENT_NFS_CB, "Admin thread initialized"); } /** * @brief Signal the admin thread to shut down the system */ void admin_halt(void) { PTHREAD_MUTEX_lock(&admin_control_mtx); if (!admin_shutdown) { admin_shutdown = true; pthread_cond_broadcast(&admin_control_cv); } PTHREAD_MUTEX_unlock(&admin_control_mtx); LogEvent(COMPONENT_MAIN, "NFS EXIT: %s done", __func__); } static void do_shutdown(void) { int rc = 0; bool disorderly = false; LogEvent(COMPONENT_MAIN, "NFS EXIT: stopping NFS service"); gsh_rados_url_shutdown_watch(); config_url_shutdown(); #ifdef USE_DBUS /* DBUS shutdown */ gsh_dbus_pkgshutdown(); #endif LogEvent(COMPONENT_MAIN, "Stopping delayed executor."); delayed_shutdown(); LogEvent(COMPONENT_MAIN, "Delayed executor stopped."); LogEvent(COMPONENT_MAIN, "Stopping state asynchronous request thread"); rc = state_async_shutdown(); if (rc != 0) { LogMajor( COMPONENT_THREAD, "Error shutting down state asynchronous request system: %d", rc); disorderly = true; } else { LogEvent(COMPONENT_THREAD, "State asynchronous request system shut down."); } LogEvent(COMPONENT_MAIN, "Unregistering ports used by NFS service"); /* finalize RPC package */ Clean_RPC(); LogEvent(COMPONENT_MAIN, "Shutting down RPC services"); (void)svc_shutdown(SVC_SHUTDOWN_FLAG_NONE); LogEvent(COMPONENT_MAIN, "Stopping reaper threads"); rc = reaper_shutdown(); if (rc != 0) { LogMajor(COMPONENT_THREAD, "Error shutting down reaper thread: %d", rc); disorderly = true; } else { LogEvent(COMPONENT_THREAD, "Reaper thread shut down."); } LogEvent(COMPONENT_MAIN, "Stopping worker threads"); #ifdef _USE_9P if (nfs_param.core_param.core_options & CORE_OPTION_9P) { rc = _9p_worker_shutdown(); if (rc != 0) { LogMajor(COMPONENT_THREAD, "Unable to shut down worker threads: %d", rc); disorderly = true; } else { LogEvent(COMPONENT_THREAD, "Worker threads successfully shut down."); } } #endif rc = general_fridge_shutdown(); if (rc != 0) { LogMajor(COMPONENT_THREAD, "Error shutting down general fridge: %d", rc); disorderly = true; } else { LogEvent(COMPONENT_THREAD, "General fridge shut down."); } /* We have to remove DS before exports, DS refer to exports but * exports do not refer to DS. This SHOULD remove every single DS. */ LogEvent(COMPONENT_MAIN, "Removing all DSs."); remove_all_dss(); LogEvent(COMPONENT_MAIN, "Removing all exports."); remove_all_exports(); nfs4_recovery_shutdown(); nfs_rpc_cb_pkgshutdown(); if (disorderly) { LogMajor(COMPONENT_MAIN, "Error in shutdown, taking emergency cleanup."); /* We don't attempt to free state, clean the cache, or unload the FSALs more cleanly, since doing anything more than this risks hanging up on potentially invalid locks. */ emergency_cleanup_fsals(); } else { LogEvent(COMPONENT_MAIN, "Destroying the FSAL system."); destroy_fsals(); LogEvent(COMPONENT_MAIN, "FSAL system destroyed."); } unlink(nfs_pidfile_path); PTHREAD_MUTEX_destroy(&admin_control_mtx); PTHREAD_COND_destroy(&admin_control_cv); LogEvent(COMPONENT_MAIN, "NFS EXIT: %s done", __func__); } void *admin_thread(void *UnusedArg) { SetNameFunction("Admin"); rcu_register_thread(); PTHREAD_MUTEX_lock(&admin_control_mtx); while (!admin_shutdown) { /* Wait for shutdown indication. */ pthread_cond_wait(&admin_control_cv, &admin_control_mtx); } PTHREAD_MUTEX_unlock(&admin_control_mtx); do_shutdown(); rcu_unregister_thread(); return NULL; } nfs-ganesha-6.5/src/MainNFSD/nfs_init.c000066400000000000000000001150461473756622300177070ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_init.c * @brief Most of the init routines */ #include "config.h" #include "nfs_init.h" #include "log.h" #include "fsal.h" #include "rquota.h" #include "nfs_core.h" #include "nfs_file_handle.h" #include "nfs_exports.h" #include "nfs_ip_stats.h" #include "nfs_proto_functions.h" #include "nfs_dupreq.h" #include "config_parsing.h" #include "nfs4_acls.h" #include "nfs_rpc_callback.h" #ifdef USE_DBUS #include "gsh_dbus.h" #endif #include "FSAL/fsal_commonlib.h" #ifdef _USE_CB_SIMULATOR #include "nfs_rpc_callback_simulator.h" #endif #include #include #include #include #include #include #ifndef __APPLE__ #include #endif #ifdef _USE_NLM #include "nlm_util.h" #endif /* _USE_NLM */ #include "nsm.h" #include "sal_functions.h" #include "fridgethr.h" #include "idmapper.h" #include "delayed_exec.h" #include "client_mgr.h" #include "export_mgr.h" #ifdef USE_CAPS #include /* For capget/capset */ #endif #include "uid2grp.h" #include "netgroup_cache.h" #include "pnfs_utils.h" #include "mdcache.h" #include "common_utils.h" #include "nfs_init.h" #include #include "conf_url.h" #include "FSAL/fsal_localfs.h" #ifdef USE_MONITORING #include "nfs_metrics.h" #include "sal_metrics.h" #endif pthread_mutexattr_t default_mutex_attr; pthread_rwlockattr_t default_rwlock_attr; unsigned long PTHREAD_stack_size; /** * @brief init_complete used to indicate if ganesha is during * startup or not */ struct nfs_init nfs_init; /* global information exported to all layers (as extern vars) */ nfs_parameter_t nfs_param; struct _nfs_health nfs_health_; static struct _nfs_health healthstats; /* ServerEpoch is ServerBootTime unless overridden by -E command line option */ struct timespec nfs_ServerBootTime; time_t nfs_ServerEpoch; verifier4 NFS4_write_verifier; /* NFS V4 write verifier */ writeverf3 NFS3_write_verifier; /* NFS V3 write verifier */ /* node ID used to identify an individual node in a cluster */ int g_nodeid; nfs_start_info_t nfs_start_info; pthread_t admin_thrid; pthread_t sigmgr_thrid; tirpc_pkg_params ntirpc_pp = { TIRPC_DEBUG_FLAG_DEFAULT, 0, SetNameFunction, (mem_format_t)rpc_warnx, gsh_free_size, gsh_malloc__, gsh_malloc_aligned__, gsh_calloc__, gsh_realloc__, }; #ifdef _USE_9P pthread_t _9p_dispatcher_thrid; #endif #ifdef _USE_9P_RDMA pthread_t _9p_rdma_dispatcher_thrid; #endif #ifdef _USE_NFS_RDMA pthread_t nfs_rdma_dispatcher_thrid; #endif char *nfs_config_path = GANESHA_CONFIG_PATH; char *nfs_pidfile_path = GANESHA_PIDFILE_PATH; char cid_server_owner[MAXNAMLEN + 1]; /* max hostname length */ char *cid_server_scope; /** * @brief Reread the configuration file to accomplish update of options. * * The following option blocks are currently supported for update: * * LOG {} * LOG { COMPONENTS {} } * LOG { FACILITY {} } * LOG { FORMAT {} } * EXPORT {} * EXPORT { CLIENT {} } * */ struct config_error_type err_type; #ifdef _HAVE_GSSAPI /** * @brief Set up the server-principal and creds to be used for GSS forechannel */ static void gss_principal_init(enum log_components log_component) { gss_buffer_desc gss_service_buf; OM_uint32 maj_stat, min_stat; char gssError[MAXNAMLEN + 1]; gss_service_buf.value = nfs_param.krb5_param.svc.principal; gss_service_buf.length = strlen(nfs_param.krb5_param.svc.principal) + 1; /* The '+1' is not to be forgotten, for the '\0' at the end */ maj_stat = gss_import_name(&min_stat, &gss_service_buf, (gss_OID)GSS_C_NT_HOSTBASED_SERVICE, &nfs_param.krb5_param.svc.gss_name); if (maj_stat != GSS_S_COMPLETE) { log_sperror_gss(gssError, maj_stat, min_stat); LogFatal(log_component, "Error importing gss principal %s is %s", nfs_param.krb5_param.svc.principal, gssError); } if (nfs_param.krb5_param.svc.gss_name == GSS_C_NO_NAME) { LogInfo(log_component, "Regression: svc.gss_name == GSS_C_NO_NAME"); } LogInfo(log_component, "gss principal \"%s\" successfully set", nfs_param.krb5_param.svc.principal); /* Set the principal to GSSRPC */ if (!svcauth_gss_set_svc_name(nfs_param.krb5_param.svc.gss_name)) { LogFatal(log_component, "Impossible to set gss principal to GSSRPC"); } /* Don't release name until shutdown, it will be used by the * backchannel. */ /* Trying to acquire credentials, while checking name's validity */ if (!svcauth_gss_acquire_cred()) { LogCrit(log_component, "Cannot acquire credentials for principal %s", nfs_param.krb5_param.svc.principal); } else { LogInfo(log_component, "Principal %s is suitable for acquiring credentials", nfs_param.krb5_param.svc.principal); } } /** * @brief Enable nfs_krb5 functionality * * This functionality includes gss based authentication for forechannel * and gss based callbacks. */ static void enable_nfs_krb5(nfs_krb5_parameter_t krb5_param, enum log_components log_component) { char gssError[MAXNAMLEN + 1]; /* Update global nfs_param.krb5_param */ nfs_param.krb5_param = krb5_param; /* Enable gss based callbacks */ nfs_rpc_cb_set_gss_status(true); /* Setup keytab */ #ifdef HAVE_KRB5 OM_uint32 gss_status = krb5_gss_register_acceptor_identity(krb5_param.keytab); if (gss_status != GSS_S_COMPLETE) { log_sperror_gss(gssError, gss_status, 0); LogFatal(log_component, "Error setting krb5 keytab to value %s is %s", krb5_param.keytab, gssError); } LogInfo(log_component, "krb5 keytab path successfully set to %s", krb5_param.keytab); #endif /* HAVE_KRB5 */ /* Set up gss principal to use with GSSAPI */ gss_principal_init(log_component); /* Set rpcsec_gss fore-channel authentication status as ON */ svcauth_gss_set_status(true); LogInfo(log_component, "nfs_krb5 functionality is now enabled"); } /** * @brief Disable nfs_krb5 functionality * * This functionality includes gss based authentication for forechannel * and gss based callbacks. */ static void disable_nfs_krb5(nfs_krb5_parameter_t krb5_param, log_components_t log_component) { char gss_error[MAXNAMLEN + 1]; OM_uint32 maj_stat, min_stat; nfs_param.krb5_param.active_krb5 = false; #ifdef HAVE_KRB5 /* Clear keytab used by gss/krb5 lib */ OM_uint32 gss_status = krb5_gss_register_acceptor_identity(NULL); if (gss_status != GSS_S_COMPLETE) { log_sperror_gss(gss_error, gss_status, 0); LogCrit(log_component, "Error clearing krb5 keytab: %s", gss_error); } else { LogInfo(log_component, "krb5 keytab path successfully cleared"); } #endif /* HAVE_KRB5 */ /* Clear gss_name */ if (nfs_param.krb5_param.svc.gss_name != GSS_C_NO_NAME) { maj_stat = gss_release_name(&min_stat, &nfs_param.krb5_param.svc.gss_name); if (maj_stat != GSS_S_COMPLETE) { LogCrit(log_component, "Error freeing svc.gss_name major=%u minor=%u", maj_stat, min_stat); } nfs_param.krb5_param.svc.gss_name = NULL; } /* Set rpcsec_gss fore-channel authentication status as OFF */ svcauth_gss_set_status(false); LogInfo(log_component, "svcauth_gss is now disabled"); /* Disable gss based callbacks */ nfs_rpc_cb_set_gss_status(false); LogInfo(log_component, "nfs_krb5 functionality is now disabled"); } /** * @brief Handle nfs_krb5 configuration update * * This function detects changes against the existing nfs_krb5 configuration, * and makes the required Ganesha-wide changes. * * @note We only support dynamic toggling of the krb5 functionality from the * states -- OFF to OFF, OFF to ON and ON to OFF. ON to ON with an updated * configuration is not supported. */ static void handle_krb5_config_update(nfs_krb5_parameter_t new_krb5_param) { if (!nfs_param.krb5_param.active_krb5) { if (!new_krb5_param.active_krb5) { LogInfo(COMPONENT_CONFIG, "NFSv4-KRB5 state: OFF --> OFF. No action required"); return; } LogInfo(COMPONENT_CONFIG, "NFSv4-KRB5 state: OFF --> ON. Enabling KRB5"); enable_nfs_krb5(new_krb5_param, COMPONENT_CONFIG); return; } /* At this point, existing nfs_param.krb5_param.active_krb5 is true */ if (new_krb5_param.active_krb5) { /* No action needed if new and old configs are the same */ if (!strcmp(nfs_param.krb5_param.keytab, new_krb5_param.keytab) && !strcmp(nfs_param.krb5_param.ccache_dir, new_krb5_param.ccache_dir) && !strcmp(nfs_param.krb5_param.svc.principal, new_krb5_param.svc.principal)) { LogInfo(COMPONENT_CONFIG, "NFSv4-KRB5 state: ON --> ON (same config). No action required"); return; } LogCrit(COMPONENT_CONFIG, "NFSv4-KRB5 state: ON --> ON (updated config). Reload does not support it!"); /* For backward compatibility, we release gss-creds on reload */ svcauth_gss_release_cred(); return; } LogInfo(COMPONENT_CONFIG, "NFSv4-KRB5 state: ON --> OFF. Disabling KRB5"); disable_nfs_krb5(new_krb5_param, COMPONENT_CONFIG); } #endif /* _HAVE_GSSAPI */ bool reread_config(void) { int status = 0; config_file_t config_struct; #ifdef _HAVE_GSSAPI nfs_krb5_parameter_t new_krb5_param; #endif /* If no configuration file is given, then the caller must want to * reparse the configuration file from startup. */ if (nfs_config_path[0] == '\0') { LogCrit(COMPONENT_CONFIG, "No configuration file was specified for reloading log config."); return false; } /* Create a memstream for parser+processing error messages */ if (!init_error_type(&err_type)) return false; /* Attempt to parse the new configuration file */ config_struct = config_ParseFile(nfs_config_path, &err_type); if (!config_error_no_error(&err_type)) { config_Free(config_struct); LogCrit(COMPONENT_CONFIG, "Error while parsing new configuration file %s", nfs_config_path); (void)report_config_errors(&err_type, NULL, config_errs_to_log); return false; } /* Update the logging configuration */ status = read_log_config(config_struct, &err_type); if (status < 0) { LogCrit(COMPONENT_CONFIG, "Error while parsing LOG entries"); return false; } /* Update the export configuration */ status = reread_exports(config_struct, &err_type); if (status < 0 || !config_error_is_harmless(&err_type)) { LogCrit(COMPONENT_CONFIG, "Error while parsing EXPORT entries"); return false; } /* Reread directory_services configuration */ (void)load_config_from_parse(config_struct, &directory_services_param, &nfs_param.directory_services_param, true, &err_type); if (!config_error_is_harmless(&err_type)) { LogCrit(COMPONENT_CONFIG, "Error while parsing DIRECTORY_SERVICES configuration"); return false; } /* Set idmapping status based on directory_services configuration */ status = set_idmapping_status( nfs_param.directory_services_param.idmapping_active); if (!status) LogFatal(COMPONENT_CONFIG, "Failed to set idmapping status"); #ifdef _HAVE_GSSAPI /* Reread NFS kerberos5 configuration */ (void)load_config_from_parse(config_struct, &krb5_param, &new_krb5_param, true, &err_type); if (!config_error_is_harmless(&err_type)) { LogCrit(COMPONENT_CONFIG, "Error while parsing NFSv4-KRB5 configuration section"); return false; } handle_krb5_config_update(new_krb5_param); #endif /* _HAVE_GSSAPI */ (void)report_config_errors(&err_type, NULL, config_errs_to_log); config_Free(config_struct); LogDebug(COMPONENT_CONFIG, "Config reread successfully"); return true; } /** * @brief This thread is in charge of signal management * * @param[in] UnusedArg Unused * * @return NULL. */ static void *sigmgr_thread(void *UnusedArg) { int signal_caught = 0; SetNameFunction("sigmgr"); rcu_register_thread(); /* Loop until we catch SIGTERM */ while (signal_caught != SIGTERM) { sigset_t signals_to_catch; sigemptyset(&signals_to_catch); sigaddset(&signals_to_catch, SIGTERM); sigaddset(&signals_to_catch, SIGHUP); if (sigwait(&signals_to_catch, &signal_caught) != 0) { LogFullDebug(COMPONENT_THREAD, "sigwait exited with error"); continue; } if (signal_caught == SIGHUP) { LogEvent( COMPONENT_MAIN, "SIGHUP_HANDLER: Received SIGHUP.... initiating export list reload"); reread_config(); } } LogDebug(COMPONENT_THREAD, "sigmgr thread exiting"); admin_halt(); /* Might as well exit - no need for this thread any more */ rcu_unregister_thread(); return NULL; } static void crash_handler(int signo, siginfo_t *info, void *ctx) { /* Already in the process of shutting down, skip backtraces & cores */ if (admin_shutdown) return; #ifdef USE_UNWIND gsh_libunwind(); #else gsh_backtrace(); #endif /* re-raise the signal for the default signal handler to dump core */ raise(signo); } static void install_sighandler(int signo, void (*handler)(int, siginfo_t *, void *)) { struct sigaction sa = {}; int ret; sa.sa_sigaction = handler; /* set SA_RESETHAND to restore default handler */ sa.sa_flags = SA_SIGINFO | SA_RESETHAND | SA_NODEFER; sigemptyset(&sa.sa_mask); ret = sigaction(signo, &sa, NULL); if (ret) { LogWarn(COMPONENT_INIT, "Install handler for signal (%s) failed", strsignal(signo)); } } static void init_crash_handlers(void) { install_sighandler(SIGSEGV, crash_handler); install_sighandler(SIGABRT, crash_handler); install_sighandler(SIGBUS, crash_handler); install_sighandler(SIGILL, crash_handler); install_sighandler(SIGFPE, crash_handler); install_sighandler(SIGQUIT, crash_handler); } void nfs_prereq_init_mutexes(void) { PTHREAD_MUTEXATTR_init(&default_mutex_attr); #if defined(__linux__) PTHREAD_MUTEXATTR_settype(&default_mutex_attr, PTHREAD_MUTEX_ADAPTIVE_NP); #endif PTHREAD_RWLOCKATTR_init(&default_rwlock_attr); #if defined(__linux__) PTHREAD_RWLOCKATTR_setkind_np( &default_rwlock_attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); #endif } /** * @brief Initialize NFSd prerequisites * * @param[in] program_name Name of the program * @param[in] host_name Server host name * @param[in] debug_level Debug level * @param[in] log_path Log path * @param[in] dump_trace Dump trace when segfault */ void nfs_prereq_init(const char *program_name, const char *host_name, int debug_level, const char *log_path, bool dump_trace, unsigned long stack_size) { nfs_prereq_init_mutexes(); PTHREAD_stack_size = stack_size; healthstats.enqueued_reqs = nfs_health_.enqueued_reqs = 0; healthstats.dequeued_reqs = nfs_health_.dequeued_reqs = 0; /* Initialize logging */ SetNamePgm(program_name); SetNameFunction("main"); SetNameHost(host_name); init_logging(log_path, debug_level); if (dump_trace) { init_crash_handlers(); } /* Redirect TI-RPC allocators, log channel */ if (!tirpc_control(TIRPC_PUT_PARAMETERS, &ntirpc_pp)) { LogFatal(COMPONENT_INIT, "Setting nTI-RPC parameters failed"); } } void nfs_prereq_destroy(void) { PTHREAD_MUTEXATTR_destroy(&default_mutex_attr); PTHREAD_RWLOCKATTR_destroy(&default_rwlock_attr); } /** * @brief Print the nfs_parameter_structure */ void nfs_print_param_config(void) { printf("NFS_Core_Param\n{\n"); printf("\tNFS_Port = %u ;\n", nfs_param.core_param.port[P_NFS]); #ifdef _USE_NFS3 printf("\tMNT_Port = %u ;\n", nfs_param.core_param.port[P_MNT]); #endif printf("\tNFS_Program = %u ;\n", nfs_param.core_param.program[P_NFS]); printf("\tMNT_Program = %u ;\n", nfs_param.core_param.program[P_NFS]); printf("\tDRC_TCP_Npart = %u ;\n", nfs_param.core_param.drc.tcp.npart); printf("\tDRC_TCP_Size = %u ;\n", nfs_param.core_param.drc.tcp.size); printf("\tDRC_TCP_Cachesz = %u ;\n", nfs_param.core_param.drc.tcp.cachesz); printf("\tDRC_TCP_Hiwat = %u ;\n", nfs_param.core_param.drc.tcp.hiwat); printf("\tDRC_TCP_Recycle_Npart = %u ;\n", nfs_param.core_param.drc.tcp.recycle_npart); printf("\tDRC_TCP_Recycle_Expire_S = %u ;\n", nfs_param.core_param.drc.tcp.recycle_expire_s); printf("\tDRC_TCP_Checksum = %u ;\n", nfs_param.core_param.drc.tcp.checksum); printf("\tDRC_UDP_Npart = %u ;\n", nfs_param.core_param.drc.udp.npart); printf("\tDRC_UDP_Size = %u ;\n", nfs_param.core_param.drc.udp.size); printf("\tDRC_UDP_Cachesz = %u ;\n", nfs_param.core_param.drc.udp.cachesz); printf("\tDRC_UDP_Hiwat = %u ;\n", nfs_param.core_param.drc.udp.hiwat); printf("\tDRC_UDP_Checksum = %u ;\n", nfs_param.core_param.drc.udp.checksum); printf("\tBlocked_Lock_Poller_Interval = %" PRIu64 " ;\n", (uint64_t)nfs_param.core_param.blocked_lock_poller_interval); printf("\tManage_Gids_Expiration = %" PRIu64 " ;\n", (uint64_t)nfs_param.core_param.manage_gids_expiration); printf("\tDrop_IO_Errors = %s ;\n", nfs_param.core_param.drop_io_errors ? "true" : "false"); printf("\tDrop_Inval_Errors = %s ;\n", nfs_param.core_param.drop_inval_errors ? "true" : "false"); printf("\tDrop_Delay_Errors = %s ;\n", nfs_param.core_param.drop_delay_errors ? "true" : "false"); printf("\tEnable UDP = %u ;\n", nfs_param.core_param.enable_UDP); printf("}\n\n"); } static inline void core_pkginit(void) { glist_init(&nfs_param.core_param.haproxy_hosts); } /** * @brief Load parameters from config file * * @param[in] config_struct Parsed config file * @param[out] p_start_info Startup parameters * @param[out] err_type error reporting state * * @return -1 on failure. */ int nfs_set_param_from_conf(config_file_t parse_tree, nfs_start_info_t *p_start_info, struct config_error_type *err_type) { const time_t unset_time_validity = -1; directory_services_param_t *ds_param = &nfs_param.directory_services_param; /* * Initialize exports and clients so config parsing can use them * early. */ core_pkginit(); client_pkginit(); export_pkginit(); server_pkginit(); /* Core parameters */ (void)load_config_from_parse(parse_tree, &nfs_core, &nfs_param.core_param, true, err_type); if (!config_error_is_harmless(err_type)) { LogCrit(COMPONENT_INIT, "Error while parsing core configuration"); return -1; } /* Worker parameters: ip/name hash table and expiration * for each entry */ (void)load_config_from_parse(parse_tree, &nfs_ip_name, NULL, true, err_type); if (!config_error_is_harmless(err_type)) { LogCrit(COMPONENT_INIT, "Error while parsing IP/name configuration"); return -1; } #ifdef _HAVE_GSSAPI /* NFS kerberos5 configuration */ (void)load_config_from_parse(parse_tree, &krb5_param, &nfs_param.krb5_param, true, err_type); if (!config_error_is_harmless(err_type)) { LogCrit(COMPONENT_INIT, "Error while parsing NFS/KRB5 configuration for RPCSEC_GSS"); return -1; } #endif /* Directory Services specific configuration */ (void)load_config_from_parse(parse_tree, &directory_services_param, ds_param, true, err_type); if (!config_error_is_harmless(err_type)) { LogCrit(COMPONENT_INIT, "Error while parsing DIRECTORY_SERVICES configuration"); return -1; } /* NFSv4 specific configuration */ (void)load_config_from_parse(parse_tree, &version4_param, &nfs_param.nfsv4_param, true, err_type); if (!config_error_is_harmless(err_type)) { LogCrit(COMPONENT_INIT, "Error while parsing NFSv4 specific configuration"); return -1; } /* Use `domainname` from `nfsv4` config section, if it is not set under * `directory_services` section. Otherwise, ignore the `nfsv4` value. */ if (ds_param->domainname == NULL) { LogWarn(COMPONENT_INIT, "domainname in NFSv4 config section will soon be deprecated, define it under DIRECTORY_SERVICES section"); ds_param->domainname = nfs_param.nfsv4_param.domainname; } else { /* TODO: Remove below log when NFSv4/domainname is removed */ LogWarn(COMPONENT_INIT, "Using domainname from DIRECTORY_SERVICES config section, instead of NFSv4"); } /* For backward compatibility, use `manage_gids_expiration` from * `nfs_core_param` config section, if `idmapped_user_time_validity` or * `idmapped_group_time_validity` is not set under `directory_services` * section. Otherwise, ignore the `manage_gids_expiration` value. */ if (ds_param->idmapped_user_time_validity == unset_time_validity) { LogWarn(COMPONENT_INIT, "Use idmapped_user_time_validity under DIRECTORY_SERVICES section to configure time validity of idmapped users"); ds_param->idmapped_user_time_validity = nfs_param.core_param.manage_gids_expiration; } else { /* TODO: Remove below log when NFS_CORE_PARAM/ * manage_gids_expiration is deprecated for user validity. */ LogWarn(COMPONENT_INIT, "Using idmapped_user_time_validity from DIRECTORY_SERVICES config section, instead of manage_gids_expiration from NFS_CORE_PARAM"); } if (ds_param->idmapped_group_time_validity == unset_time_validity) { LogWarn(COMPONENT_INIT, "Use idmapped_group_time_validity under DIRECTORY_SERVICES section to configure time validity of idmapped groups"); ds_param->idmapped_group_time_validity = nfs_param.core_param.manage_gids_expiration; } else { /* TODO: Remove below log when NFS_CORE_PARAM/ * manage_gids_expiration is deprecated for group validity. */ LogWarn(COMPONENT_INIT, "Using idmapped_group_time_validity from DIRECTORY_SERVICES config section, instead of manage_gids_expiration from NFS_CORE_PARAM"); } #ifdef _USE_9P (void)load_config_from_parse(parse_tree, &_9p_param_blk, NULL, true, err_type); if (!config_error_is_harmless(err_type)) { LogCrit(COMPONENT_INIT, "Error while parsing 9P specific configuration"); return -1; } #endif if (mdcache_set_param_from_conf(parse_tree, err_type) < 0) return -1; if (load_recovery_param_from_conf(parse_tree, err_type) < 0) return -1; if (gsh_rados_url_setup_watch() != 0) { LogEvent(COMPONENT_INIT, "Couldn't setup rados_urls"); return -1; } LogEvent(COMPONENT_INIT, "Configuration file successfully parsed"); return 0; } int init_server_pkgs(void) { fsal_status_t fsal_status; state_status_t state_status; /* init uid2grp cache */ uid2grp_cache_init(); ng_cache_init(); /* netgroup cache */ /* MDCACHE Initialisation */ fsal_status = mdcache_pkginit(); if (FSAL_IS_ERROR(fsal_status)) { LogCrit(COMPONENT_INIT, "MDCACHE FSAL could not be initialized, status=%s", fsal_err_txt(fsal_status)); return -1; } state_status = state_lock_init(); if (state_status != STATE_SUCCESS) { LogCrit(COMPONENT_INIT, "State Lock Layer could not be initialized, status=%s", state_err_str(state_status)); return -1; } LogInfo(COMPONENT_INIT, "State lock layer successfully initialized"); /* Init the IP/name cache */ LogDebug(COMPONENT_INIT, "Now building IP/name cache"); if (nfs_Init_ip_name() != IP_NAME_SUCCESS) { LogCrit(COMPONENT_INIT, "Error while initializing IP/name cache"); return -1; } LogInfo(COMPONENT_INIT, "IP/name cache successfully initialized"); LogEvent(COMPONENT_INIT, "Initializing ID Mapper."); if (!idmapper_init()) { LogCrit(COMPONENT_INIT, "Failed initializing ID Mapper."); return -1; } /* Set idmapping status based on directory_services configuration */ if (!set_idmapping_status( nfs_param.directory_services_param.idmapping_active)) { LogCrit(COMPONENT_INIT, "Failed to set idmapping status"); return -1; } LogEvent(COMPONENT_INIT, "ID Mapper successfully initialized."); connection_manager__init(); LogEvent(COMPONENT_INIT, "Connection Manager initialized."); return 0; } static void nfs_Start_threads(void) { int rc = 0; pthread_attr_t attr_thr; LogDebug(COMPONENT_THREAD, "Starting threads"); /* Init for thread parameter (mostly for scheduling) */ PTHREAD_ATTR_init(&attr_thr); PTHREAD_ATTR_setscope(&attr_thr, PTHREAD_SCOPE_SYSTEM); PTHREAD_ATTR_setdetachstate(&attr_thr, PTHREAD_CREATE_JOINABLE); LogEvent(COMPONENT_THREAD, "Starting delayed executor."); delayed_start(); /* Starting the thread dedicated to signal handling */ rc = PTHREAD_create(&sigmgr_thrid, &attr_thr, sigmgr_thread, NULL); if (rc != 0) { LogFatal(COMPONENT_THREAD, "Could not create sigmgr_thread, error = %d (%s)", errno, strerror(errno)); } LogDebug(COMPONENT_THREAD, "sigmgr thread started"); #ifdef _USE_9P if (nfs_param.core_param.core_options & CORE_OPTION_9P) { /* Start 9P worker threads */ rc = _9p_worker_init(); if (rc != 0) { LogFatal(COMPONENT_THREAD, "Could not start worker threads: %d", errno); } /* Starting the 9P/TCP dispatcher thread */ rc = PTHREAD_create(&_9p_dispatcher_thrid, &attr_thr, _9p_dispatcher_thread, NULL); if (rc != 0) { LogFatal( COMPONENT_THREAD, "Could not create 9P/TCP dispatcher, error = %d (%s)", errno, strerror(errno)); } LogEvent(COMPONENT_THREAD, "9P/TCP dispatcher thread was started successfully"); } #endif #ifdef _USE_9P_RDMA /* Starting the 9P/RDMA dispatcher thread */ if (nfs_param.core_param.core_options & CORE_OPTION_9P) { /** @todo - this thread is never cancelled or cleaned up... */ rc = PTHREAD_create(&_9p_rdma_dispatcher_thrid, &attr_thr, _9p_rdma_dispatcher_thread, NULL); if (rc != 0) { LogFatal( COMPONENT_THREAD, "Could not create 9P/RDMA dispatcher, error = %d (%s)", errno, strerror(errno)); } LogEvent(COMPONENT_THREAD, "9P/RDMA dispatcher thread was started successfully"); } #endif #ifdef USE_DBUS /* DBUS event thread */ rc = PTHREAD_create(&gsh_dbus_thrid, &attr_thr, gsh_dbus_thread, NULL); if (rc != 0) { LogFatal(COMPONENT_THREAD, "Could not create gsh_dbus_thread, error = %d (%s)", errno, strerror(errno)); } LogEvent(COMPONENT_THREAD, "gsh_dbusthread was started successfully"); #endif /* Starting the admin thread */ rc = PTHREAD_create(&admin_thrid, &attr_thr, admin_thread, NULL); if (rc != 0) { LogFatal(COMPONENT_THREAD, "Could not create admin_thread, error = %d (%s)", errno, strerror(errno)); } LogEvent(COMPONENT_THREAD, "admin thread was started successfully"); /* Starting the reaper thread */ rc = reaper_init(); if (rc != 0) { LogFatal(COMPONENT_THREAD, "Could not create reaper_thread, error = %d (%s)", errno, strerror(errno)); } LogEvent(COMPONENT_THREAD, "reaper thread was started successfully"); /* Starting the general fridge */ rc = general_fridge_init(); if (rc != 0) { LogFatal(COMPONENT_THREAD, "Could not create general fridge, error = %d (%s)", errno, strerror(errno)); } LogEvent(COMPONENT_THREAD, "General fridge was started successfully"); PTHREAD_ATTR_destroy(&attr_thr); } /** * @brief Initialise NFSv4 specific parameters. * * @retval 0 if successful. * @retval -1 otherwise * */ int nfsv4_init_params(void) { int __attribute__((unused)) rc; int owner_len, scope_len, ss_suffix_len; char *cid_server_scope_suffix = "_NFS-Ganesha"; /* Set up the server owner string */ if (nfs_param.nfsv4_param.server_owner == NULL) { /* If the server owner param is NULL, set it to hostname */ if (gsh_gethostname(cid_server_owner, sizeof(cid_server_owner), nfs_param.core_param.enable_AUTHSTATS) == -1) { LogCrit(COMPONENT_NFS_V4, "gsh_gethostname failed"); return -1; } } else { rc = snprintf(cid_server_owner, sizeof(cid_server_owner), "%s", nfs_param.nfsv4_param.server_owner); /* Assert that server owner conf param is not too long. * this should never happen since it is validated during * conf parsing */ assert(rc >= 0 && rc < sizeof(cid_server_owner)); } /* use server_owner as server_scope if server_scope not * mentioned in main config file */ if (nfs_param.nfsv4_param.server_scope == NULL) { owner_len = strlen(cid_server_owner); ss_suffix_len = strlen(cid_server_scope_suffix); scope_len = owner_len + ss_suffix_len; cid_server_scope = gsh_malloc(scope_len + 1); memcpy(cid_server_scope, cid_server_owner, owner_len); memcpy(cid_server_scope + owner_len, cid_server_scope_suffix, ss_suffix_len + 1); } else { cid_server_scope = nfs_param.nfsv4_param.server_scope; } return 0; } /** * @brief Init the nfs daemon * * @param[in] p_start_info Unused */ static void nfs_Init(const nfs_start_info_t *p_start_info) { #ifdef USE_DBUS /* DBUS init */ gsh_dbus_pkginit(); dbus_export_init(); dbus_client_init(); dbus_cache_init(); #endif #ifdef USE_MONITORING /* initializing nfs ganesha metrics */ nfs_metrics__init(); sal_metrics__init(); #endif /* acls cache may be needed by exports_pkginit */ LogDebug(COMPONENT_INIT, "Now building NFSv4 ACL cache"); if (nfs4_acls_init() != 0) LogFatal(COMPONENT_INIT, "Error while initializing NFSv4 ACLs"); LogInfo(COMPONENT_INIT, "NFSv4 ACL cache successfully initialized"); /* finish the job with exports by caching the root entries */ exports_pkginit(); nfs41_session_pool = pool_basic_init("NFSv4.1 session pool", sizeof(nfs41_session_t)); /* Init the NFSv4 Clientid cache */ LogDebug(COMPONENT_INIT, "Now building NFSv4 clientid cache"); if (nfs_Init_client_id() != CLIENT_ID_SUCCESS) { LogFatal(COMPONENT_INIT, "Error while initializing NFSv4 clientid cache"); } LogInfo(COMPONENT_INIT, "NFSv4 clientid cache successfully initialized"); /* Init duplicate request cache */ dupreq2_pkginit(); LogInfo(COMPONENT_INIT, "duplicate request hash table cache successfully initialized"); /* Init The NFSv4 State id cache */ LogDebug(COMPONENT_INIT, "Now building NFSv4 State Id cache"); if (nfs4_Init_state_id() != 0) { LogFatal(COMPONENT_INIT, "Error while initializing NFSv4 State Id cache"); } LogInfo(COMPONENT_INIT, "NFSv4 State Id cache successfully initialized"); /* Init The NFSv4 Open Owner cache */ LogDebug(COMPONENT_INIT, "Now building NFSv4 Owner cache"); if (Init_nfs4_owner() != 0) { LogFatal(COMPONENT_INIT, "Error while initializing NFSv4 Owner cache"); } LogInfo(COMPONENT_INIT, "NFSv4 Open Owner cache successfully initialized"); #ifdef _USE_NLM if (nfs_param.core_param.enable_NLM) { /* Init The NLM Owner cache */ LogDebug(COMPONENT_INIT, "Now building NLM Owner cache"); if (Init_nlm_hash() != 0) { LogFatal(COMPONENT_INIT, "Error while initializing NLM Owner cache"); } LogInfo(COMPONENT_INIT, "NLM Owner cache successfully initialized"); /* Init The NLM Owner cache */ LogDebug(COMPONENT_INIT, "Now building NLM State cache"); if (Init_nlm_state_hash() != 0) { LogFatal(COMPONENT_INIT, "Error while initializing NLM State cache"); } LogInfo(COMPONENT_INIT, "NLM State cache successfully initialized"); nlm_init(); } #endif /* _USE_NLM */ #ifdef _USE_9P /* Init the 9P lock owner cache */ LogDebug(COMPONENT_INIT, "Now building 9P Owner cache"); if (Init_9p_hash() != 0) { LogFatal(COMPONENT_INIT, "Error while initializing 9P Owner cache"); } LogInfo(COMPONENT_INIT, "9P Owner cache successfully initialized"); #endif LogDebug(COMPONENT_INIT, "Now building NFSv4 Session Id cache"); if (nfs41_Init_session_id() != 0) { LogFatal(COMPONENT_INIT, "Error while initializing NFSv4 Session Id cache"); } LogInfo(COMPONENT_INIT, "NFSv4 Session Id cache successfully initialized"); if (nfs_param.core_param.core_options & CORE_OPTION_NFSV4) { /* Initialise NFSv4 specific parameters */ if (nfsv4_init_params() != 0) { LogFatal( COMPONENT_INIT, "Error while initializing NFSv4 specific parameter"); } LogInfo(COMPONENT_INIT, "NFSv4 specific parameter initialized"); } #ifdef _USE_9P LogDebug(COMPONENT_INIT, "Now building 9P resources"); if (_9p_init()) { LogFatal(COMPONENT_INIT, "Error while initializing 9P Resources"); } LogInfo(COMPONENT_INIT, "9P resources successfully initialized"); #endif /* _USE_9P */ /* Creates the pseudo fs */ LogDebug(COMPONENT_INIT, "Now building pseudo fs"); create_pseudofs(); LogInfo(COMPONENT_INIT, "NFSv4 pseudo file system successfully initialized"); /* Save Ganesha thread credentials with Frank's routine for later use */ fsal_save_ganesha_credentials(); /* RPC Initialisation - exits on failure */ nfs_Init_svc(); LogInfo(COMPONENT_INIT, "RPC resources successfully initialized"); /* Admin initialisation */ nfs_Init_admin_thread(); /* callback dispatch */ nfs_rpc_cb_pkginit(); /* If rpcsec_gss is used, setup nfs-krb5 */ #ifdef _HAVE_GSSAPI if (nfs_param.krb5_param.active_krb5) enable_nfs_krb5(nfs_param.krb5_param, COMPONENT_INIT); else disable_nfs_krb5(nfs_param.krb5_param, COMPONENT_INIT); #endif #ifdef _USE_CB_SIMULATOR nfs_rpc_cbsim_pkginit(); #endif /* _USE_CB_SIMULATOR */ } /* nfs_Init */ #ifdef USE_CAPS /** * @brief Lower my capabilities (privs) so quotas work right * * This will/should be moved to set_credentials where it belongs * Deal with capabilities in order to remove CAP_SYS_RESOURCE (needed * for proper management of data quotas) */ static void lower_my_caps(void) { cap_value_t cap_values[] = { CAP_SYS_RESOURCE }; cap_t cap; char *cap_text; ssize_t capstrlen = 0; int ret; if (!nfs_start_info.drop_caps) { /* Skip dropping caps by request */ return; } cap = cap_get_proc(); if (cap == NULL) { LogFatal(COMPONENT_INIT, "cap_get_proc() failed, %s", strerror(errno)); } ret = cap_set_flag(cap, CAP_EFFECTIVE, sizeof(cap_values) / sizeof(cap_values[0]), cap_values, CAP_CLEAR); if (ret != 0) { LogFatal(COMPONENT_INIT, "cap_set_flag() failed, %s", strerror(errno)); } ret = cap_set_flag(cap, CAP_PERMITTED, sizeof(cap_values) / sizeof(cap_values[0]), cap_values, CAP_CLEAR); if (ret != 0) { LogFatal(COMPONENT_INIT, "cap_set_flag() failed, %s", strerror(errno)); } ret = cap_set_flag(cap, CAP_INHERITABLE, sizeof(cap_values) / sizeof(cap_values[0]), cap_values, CAP_CLEAR); if (ret != 0) { LogFatal(COMPONENT_INIT, "cap_set_flag() failed, %s", strerror(errno)); } ret = cap_set_proc(cap); if (ret != 0) { LogFatal(COMPONENT_INIT, "Failed to set capabilities for process, %s", strerror(errno)); } LogEvent( COMPONENT_INIT, "CAP_SYS_RESOURCE was successfully removed for proper quota management in FSAL"); /* Print newly set capabilities (same as what CLI "getpcaps" displays */ cap_text = cap_to_text(cap, &capstrlen); LogEvent(COMPONENT_INIT, "currently set capabilities are: %s", cap_text); cap_free(cap_text); cap_free(cap); } #endif #if defined(M_TRIM_THRESHOLD) #define THIRTY_MIN 1800000000000UL static void do_malloc_trim(void *param) { LogDebug(COMPONENT_MAIN, malloc_trim(0) ? "malloc_trim() released some memory" : "malloc_trim() was not able to release memory"); (void)delayed_submit(do_malloc_trim, 0, THIRTY_MIN); } #endif /** * @brief Start NFS service * * @param[in] p_start_info Startup parameters */ void nfs_start(nfs_start_info_t *p_start_info) { /* store the start info so it is available for all layers */ nfs_start_info = *p_start_info; if (p_start_info->dump_default_config == true) { nfs_print_param_config(); exit(0); } /* Make sure Ganesha runs with a 0000 umask. */ umask(0000); { /* Set the write verifiers */ union { verifier4 NFS4_write_verifier; writeverf3 NFS3_write_verifier; uint64_t epoch; } build_verifier; build_verifier.epoch = get_unique_server_id(); memcpy(NFS3_write_verifier, build_verifier.NFS3_write_verifier, sizeof(NFS3_write_verifier)); memcpy(NFS4_write_verifier, build_verifier.NFS4_write_verifier, sizeof(NFS4_write_verifier)); } #ifdef USE_CAPS lower_my_caps(); #endif /* Initialize all layers and service threads */ nfs_Init(p_start_info); nfs_Start_threads(); /* Spawns service threads */ #if defined(M_TRIM_THRESHOLD) (void)delayed_submit(do_malloc_trim, 0, THIRTY_MIN); #endif nfs_init_complete(); #ifdef _USE_NLM if (nfs_param.core_param.enable_NLM) { /* NSM Unmonitor all */ nsm_unmonitor_all(); } #endif /* _USE_NLM */ LogEvent(COMPONENT_INIT, "-------------------------------------------------"); LogEvent(COMPONENT_INIT, " NFS SERVER INITIALIZED"); LogEvent(COMPONENT_INIT, "-------------------------------------------------"); /* Set the time of NFS stat counting */ nfs_init_stats_time(); /* Wait for dispatcher to exit */ LogDebug(COMPONENT_THREAD, "Wait for admin thread to exit"); pthread_join(admin_thrid, NULL); /* Regular exit */ LogEvent(COMPONENT_MAIN, "NFS EXIT: regular exit"); nfs_init_cleanup(); Cleanup(); /* let main return 0 to exit */ } void nfs_init_init(void) { PTHREAD_MUTEX_init(&nfs_init.init_mutex, NULL); PTHREAD_COND_init(&nfs_init.init_cond, NULL); nfs_init.init_complete = false; } void nfs_init_cleanup(void) { PTHREAD_MUTEX_destroy(&nfs_init.init_mutex); PTHREAD_COND_destroy(&nfs_init.init_cond); } void nfs_init_complete(void) { PTHREAD_MUTEX_lock(&nfs_init.init_mutex); nfs_init.init_complete = true; pthread_cond_broadcast(&nfs_init.init_cond); PTHREAD_MUTEX_unlock(&nfs_init.init_mutex); } void nfs_init_wait(void) { PTHREAD_MUTEX_lock(&nfs_init.init_mutex); while (!nfs_init.init_complete) { pthread_cond_wait(&nfs_init.init_cond, &nfs_init.init_mutex); } PTHREAD_MUTEX_unlock(&nfs_init.init_mutex); } int nfs_init_wait_timeout(int timeout) { int rc = 0; PTHREAD_MUTEX_lock(&nfs_init.init_mutex); if (!nfs_init.init_complete) { struct timespec ts; ts.tv_sec = time(NULL) + timeout; ts.tv_nsec = 0; rc = pthread_cond_timedwait(&nfs_init.init_cond, &nfs_init.init_mutex, &ts); } PTHREAD_MUTEX_unlock(&nfs_init.init_mutex); return rc; } bool nfs_health(void) { uint64_t newenq, newdeq; uint64_t dequeue_diff, enqueue_diff; bool healthy; newenq = nfs_health_.enqueued_reqs; newdeq = nfs_health_.dequeued_reqs; enqueue_diff = newenq - healthstats.enqueued_reqs; dequeue_diff = newdeq - healthstats.dequeued_reqs; /* Consider healthy and making progress if we have dequeued some * requests or there is one or less to dequeue. Don't check * enqueue_diff == 0 here, as there will be spurious warnings during * times of low traffic, when an enqueue happens to coincide with the * heartbeat firing. */ healthy = dequeue_diff > 0 || enqueue_diff <= 1; if (!healthy) { LogWarn(COMPONENT_DBUS, "Health status is unhealthy. " "enq new: %" PRIu64 ", old: %" PRIu64 "; " "deq new: %" PRIu64 ", old: %" PRIu64, newenq, healthstats.enqueued_reqs, newdeq, healthstats.dequeued_reqs); } healthstats.enqueued_reqs = newenq; healthstats.dequeued_reqs = newdeq; return healthy; } nfs-ganesha-6.5/src/MainNFSD/nfs_lib.c000066400000000000000000000157701473756622300175150ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_lib.c * @brief The file that contain the 'main' routine for the nfsd. * */ #include "config.h" #include #include #include #include #include #include #include /* for sigaction */ #include #include "fsal.h" #include "log.h" #include "nfs_init.h" #include "nfs_exports.h" #include "pnfs_utils.h" #include "conf_url.h" #include "sal_functions.h" /* parameters for NFSd startup and default values */ nfs_start_info_t my_nfs_start_info = { .dump_default_config = false, .lw_mark_trigger = false, .drop_caps = false }; config_file_t nfs_config_struct; char *nfs_host_name = "localhost"; /* Cleanup on shutdown */ void export_cleanup(void) { PTHREAD_RWLOCK_destroy(&export_opt_lock); } struct cleanup_list_element export_cleanup_element = { .clean = export_cleanup, }; /** * nfs_libmain: library initializer * * @return status to calling program by calling the exit(3C) function. * */ int nfs_libmain(const char *ganesha_conf, const char *lpath, const int debug_level) { char localmachine[MAXHOSTNAMELEN + 1]; int dsc; int rc; char *log_path = NULL; const char *exec_name = "nfs-ganesha"; sigset_t signals_to_block; struct config_error_type err_type; /* Set the server's boot time and epoch */ now(&nfs_ServerBootTime); nfs_ServerEpoch = (time_t)nfs_ServerBootTime.tv_sec; if (ganesha_conf) nfs_config_path = gsh_strdup(ganesha_conf); if (lpath) log_path = gsh_strdup(lpath); /* get host name */ if (gethostname(localmachine, sizeof(localmachine)) != 0) { fprintf(stderr, "Could not get local host name, exiting...\n"); exit(1); } else { nfs_host_name = gsh_strdup(localmachine); if (!nfs_host_name) { fprintf(stderr, "Unable to allocate memory for hostname, exiting...\n"); exit(1); } } /* initialize memory and logging */ nfs_prereq_init(exec_name, nfs_host_name, debug_level, log_path, false, 8192 * 1024); #if GANESHA_BUILD_RELEASE LogEvent(COMPONENT_MAIN, "%s Starting: Ganesha Version %s", exec_name, GANESHA_VERSION); #else LogEvent(COMPONENT_MAIN, "%s Starting: %s", exec_name, "Ganesha Version " _GIT_DESCRIBE ", built at " __DATE__ " " __TIME__ " on " BUILD_HOST); #endif /* initialize nfs_init */ nfs_init_init(); nfs_check_malloc(); /* Make sure Linux file i/o will return with error * if file size is exceeded. */ #ifdef linux signal(SIGXFSZ, SIG_IGN); #endif /* Set up for the signal handler. * Blocks the signals the signal handler will handle. */ sigemptyset(&signals_to_block); sigaddset(&signals_to_block, SIGPIPE); /* XXX */ if (pthread_sigmask(SIG_BLOCK, &signals_to_block, NULL) != 0) LogFatal(COMPONENT_MAIN, "pthread_sigmask failed"); /* init URL package */ config_url_init(); /* Create a memstream for parser+processing error messages */ if (!init_error_type(&err_type)) goto fatal_die; if (nfs_config_path == NULL || nfs_config_path[0] == '\0') { LogWarn(COMPONENT_INIT, "No configuration file named."); nfs_config_struct = NULL; } else nfs_config_struct = config_ParseFile(nfs_config_path, &err_type); if (!config_error_no_error(&err_type)) { char *errstr = err_type_str(&err_type); if (!config_error_is_harmless(&err_type)) { LogCrit(COMPONENT_INIT, "Error %s while parsing (%s)", errstr != NULL ? errstr : "unknown", nfs_config_path); if (errstr != NULL) gsh_free(errstr); goto fatal_die; } else LogWarn(COMPONENT_INIT, "Error %s while parsing (%s)", errstr != NULL ? errstr : "unknown", nfs_config_path); if (errstr != NULL) gsh_free(errstr); } if (read_log_config(nfs_config_struct, &err_type) < 0) { LogCrit(COMPONENT_INIT, "Error while parsing log configuration"); goto fatal_die; } /* We need all the fsal modules loaded so we can have * the list available at exports parsing time. */ if (start_fsals(nfs_config_struct, &err_type) < 0) { LogCrit(COMPONENT_INIT, "Error starting FSALs."); goto fatal_die; } /* parse configuration file */ if (nfs_set_param_from_conf(nfs_config_struct, &my_nfs_start_info, &err_type)) { LogCrit(COMPONENT_INIT, "Error setting parameters from configuration file."); goto fatal_die; } /* initialize core subsystems and data structures */ if (init_server_pkgs() != 0) { LogCrit(COMPONENT_INIT, "Failed to initialize server packages"); goto fatal_die; } /* Load Data Server entries from parsed file * returns the number of DS entries. */ dsc = ReadDataServers(nfs_config_struct, &err_type); if (dsc < 0) { LogCrit(COMPONENT_INIT, "Error while parsing DS entries"); goto fatal_die; } /* Create stable storage directory, this needs to be done before * starting the recovery thread. */ rc = nfs4_recovery_init(); if (rc) { LogCrit(COMPONENT_INIT, "Recovery backend initialization failed!"); goto fatal_die; } /* Start grace period */ nfs_start_grace(NULL); /* Wait for enforcement to begin */ nfs_wait_for_grace_enforcement(); PTHREAD_RWLOCK_init(&export_opt_lock, NULL); RegisterCleanup(&export_cleanup_element); /* Load export entries from parsed file * returns the number of export entries. */ rc = ReadExports(nfs_config_struct, &err_type); if (rc < 0) { LogCrit(COMPONENT_INIT, "Error while parsing export entries"); goto fatal_die; } if (rc == 0 && dsc == 0) LogWarn(COMPONENT_INIT, "No export entries found in configuration file !!!"); (void)report_config_errors(&err_type, NULL, config_errs_to_log); /* freeing syntax tree : */ config_Free(nfs_config_struct); /* Everything seems to be OK! We can now start service threads */ nfs_start(&my_nfs_start_info); nfs_prereq_destroy(); /* nfs_config_path is allocated only if ganesha_conf is not null. */ if (ganesha_conf) gsh_free(nfs_config_path); if (log_path) gsh_free(log_path); gsh_free(nfs_host_name); return 0; fatal_die: (void)report_config_errors(&err_type, NULL, config_errs_to_log); LogFatal(COMPONENT_INIT, "Fatal errors. Server exiting..."); /* NOT REACHED */ return 2; } nfs-ganesha-6.5/src/MainNFSD/nfs_main.c000066400000000000000000000426711473756622300176730ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_main.c * @brief The file that contain the 'main' routine for the nfsd. * */ #include "config.h" #include #include #include #include #include #include #include /* for sigaction */ #include #include #include "fsal.h" #include "log.h" #include "gsh_rpc.h" #include "nfs_init.h" #include "nfs_exports.h" #include "pnfs_utils.h" #include "config_parsing.h" #include "conf_url.h" #include "sal_functions.h" #ifdef USE_MONITORING #include "monitoring.h" #endif /* USE_MONITORING */ #ifdef LINUX #include #ifndef PR_SET_IO_FLUSHER #define PR_SET_IO_FLUSHER 57 #endif #endif /* parameters for NFSd startup and default values */ static nfs_start_info_t my_nfs_start_info = { .dump_default_config = false, .lw_mark_trigger = false, .drop_caps = true }; config_file_t nfs_config_struct; char *nfs_host_name = "localhost"; bool config_errors_fatal; /* command line syntax */ #ifdef USE_LTTNG #define LTTNG_OPTION "G" #else #define LTTNG_OPTION #endif /* USE_LTTNG */ static const char options[] = "v@L:N:S:f:p:FRTE:ChI:x" LTTNG_OPTION; static const char usage[] = "Usage: %s [-hd][-L ][-N ][-f ]\n" "\t[-v] display version information\n" "\t[-L ] set the default logfile for the daemon\n" "\t[-N ] set the verbosity level\n" "\t[-f ] set the config file to be used\n" "\t[-p ] set the pid file\n" "\t[-F] the program stays in foreground\n" "\t[-R] daemon will manage RPCSEC_GSS (default is no RPCSEC_GSS)\n" "\t[-S ] set the default thread stack size (in K) to be used\n" "\t[-T] dump the default configuration on stdout\n" "\t[-E ] overrides ServerBootTime for ServerEpoch\n" "\t[-I ] cluster nodeid\n" "\t[-C] dump trace when segfault\n" "\t[-x] fatal exit if there are config errors on startup\n" "\t[-h] display this help\n" #ifdef USE_LTTNG "\t[-G] Load LTTNG traces\n" #endif /* USE_LTTNG */ "----------------- Signals ----------------\n" "SIGHUP : Reload LOG and EXPORT config\n" "SIGTERM : Cleanly terminate the program\n" "------------- Default Values -------------\n" "LogFile : SYSLOG\n" "PidFile : " GANESHA_PIDFILE_PATH "\n" "DebugLevel : NIV_EVENT\n" "ConfigFile : " GANESHA_CONFIG_PATH "\n"; static inline char *main_strdup(const char *var, const char *str) { char *s = strdup(str); if (s == NULL) { fprintf(stderr, "strdup failed for %s value %s\n", var, str); abort(); } return s; } static int valid_stack_size(unsigned long stack_size) { static unsigned long valid_sizes[] = { 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192 }; for (unsigned int i = 0; i < sizeof(valid_sizes) / sizeof(valid_sizes[0]); i++) if (valid_sizes[i] == stack_size) return 1; return 0; } #ifdef USE_LTTNG static void load_lttng(void) { void *dl = NULL; #if defined(LINUX) && !defined(SANITIZE_ADDRESS) dl = dlopen("libganesha_trace.so", RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND); #elif defined(BSDBASED) || defined(SANITIZE_ADDRESS) dl = dlopen("libganesha_trace.so", RTLD_NOW | RTLD_LOCAL); #endif if (dl == NULL) { fprintf(stderr, "Failed to load libganesha_trace.so\n"); exit(1); } #if defined(LINUX) && !defined(SANITIZE_ADDRESS) dl = dlopen("libntirpc_tracepoints.so", RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND); #elif defined(BSDBASED) || defined(SANITIZE_ADDRESS) dl = dlopen("libntirpc_tracepoints.so", RTLD_NOW | RTLD_LOCAL); #endif if (dl == NULL) { fprintf(stderr, "Failed to load libntirpc_tracepoints.so\n"); exit(1); } } #endif /* USE_LTTNG */ /** * main: simply the main function. * * The 'main' function as in every C program. * * @param argc number of arguments * @param argv array of arguments * * @return status to calling program by calling the exit(3C) function. * */ int main(int argc, char *argv[]) { char *tempo_exec_name = NULL; char localmachine[MAXHOSTNAMELEN + 1]; int c; int dsc; int rc; int pidfile = -1; /* fd for file to store pid */ unsigned long stack_size = 8388608; /* 8M, glibc's default */ char *log_path = NULL; char *exec_name = "nfs-ganesha"; int debug_level = -1; int detach_flag = true; bool dump_trace = false; #ifndef HAVE_DAEMON int dev_null_fd = 0; pid_t son_pid; #endif sigset_t signals_to_block; struct config_error_type err_type; /* Set the server's boot time and epoch */ now(&nfs_ServerBootTime); nfs_ServerEpoch = (time_t)nfs_ServerBootTime.tv_sec; srand(nfs_ServerEpoch); tempo_exec_name = strrchr(argv[0], '/'); if (tempo_exec_name != NULL) exec_name = main_strdup("exec_name", tempo_exec_name + 1); if (*exec_name == '\0') exec_name = argv[0]; /* get host name */ if (gethostname(localmachine, sizeof(localmachine)) != 0) { fprintf(stderr, "Could not get local host name, exiting...\n"); exit(1); } else { nfs_host_name = main_strdup("host_name", localmachine); } /* now parsing options with getopt */ while ((c = getopt(argc, argv, options)) != EOF) { switch (c) { case 'v': case '@': printf("NFS-Ganesha Release = V%s\n", GANESHA_VERSION); #if !GANESHA_BUILD_RELEASE /* A little backdoor to keep track of binary versions */ printf("%s compiled on %s at %s\n", exec_name, __DATE__, __TIME__); printf("Release comment = %s\n", VERSION_COMMENT); printf("Git HEAD = %s\n", _GIT_HEAD_COMMIT); printf("Git Describe = %s\n", _GIT_DESCRIBE); #endif exit(0); break; case 'L': /* Default Log */ log_path = main_strdup("log_path", optarg); break; #ifdef USE_LTTNG case 'G': load_lttng(); break; #endif /* USE_LTTNG */ case 'N': /* debug level */ debug_level = ReturnLevelAscii(optarg); if (debug_level == -1) { fprintf(stderr, "Invalid value for option 'N': NIV_NULL, NIV_MAJ, NIV_CRIT, NIV_EVENT, NIV_DEBUG, NIV_MID_DEBUG or NIV_FULL_DEBUG expected.\n"); exit(1); } break; case 'S': /* default thread stack size */ stack_size = strtoul(optarg, NULL, 10); if (!valid_stack_size(stack_size)) { fprintf(stderr, "Invalid value for option 'S': valid choices are 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192\n"); exit(1); } stack_size *= 1024; break; case 'f': /* config file */ nfs_config_path = main_strdup("config_path", optarg); break; case 'p': /* PID file */ nfs_pidfile_path = main_strdup("pidfile_path", optarg); break; case 'F': /* Don't detach, foreground mode */ detach_flag = false; break; case 'R': /* Shall we manage RPCSEC_GSS ? */ fprintf(stderr, "\n\nThe -R flag is deprecated, use this syntax in the configuration file instead:\n\n"); fprintf(stderr, "NFS_KRB5\n"); fprintf(stderr, "{\n"); fprintf(stderr, "\tPrincipalName = nfs@ ;\n"); fprintf(stderr, "\tKeytabPath = /etc/krb5.keytab ;\n"); fprintf(stderr, "\tActive_krb5 = true ;\n"); fprintf(stderr, "}\n\n\n"); exit(1); break; case 'T': /* Dump the default configuration on stdout */ my_nfs_start_info.dump_default_config = true; break; case 'C': dump_trace = true; break; case 'E': nfs_ServerEpoch = (time_t)atoll(optarg); break; case 'I': g_nodeid = atoi(optarg); break; case 'x': config_errors_fatal = true; break; case 'h': fprintf(stderr, usage, exec_name); exit(0); default: /* '?' */ fprintf(stderr, "Try '%s -h' for usage\n", exec_name); exit(1); } } /* initialize memory and logging */ nfs_prereq_init(exec_name, nfs_host_name, debug_level, log_path, dump_trace, stack_size); #if GANESHA_BUILD_RELEASE LogEvent(COMPONENT_MAIN, "%s Starting: Ganesha Version %s", exec_name, GANESHA_VERSION); #else LogEvent(COMPONENT_MAIN, "%s Starting: %s", exec_name, "Ganesha Version " _GIT_DESCRIBE ", built at " __DATE__ " " __TIME__ " on " BUILD_HOST); #endif /* initialize nfs_init */ nfs_init_init(); nfs_check_malloc(); /* Start in background, if wanted */ if (detach_flag) { #ifdef HAVE_DAEMON /* daemonize the process (fork, close xterm fds, * detach from parent process) */ if (daemon(0, 0)) LogFatal(COMPONENT_MAIN, "Error detaching process from parent: %s", strerror(errno)); /* In the child process, change the log header * if not, the header will contain the parent's pid */ set_const_log_str(); #else /* Step 1: forking a service process */ switch (son_pid = fork()) { case -1: /* Fork failed */ LogFatal( COMPONENT_MAIN, "Could not start nfs daemon (fork error %d (%s)", errno, strerror(errno)); break; case 0: /* This code is within the son (that will actually work) * Let's make it the leader of its group of process */ if (setsid() == -1) { LogFatal( COMPONENT_MAIN, "Could not start nfs daemon (setsid error %d (%s)", errno, strerror(errno)); } /* stdin, stdout and stderr should not refer to a tty * I close 0, 1 & 2 and redirect them to /dev/null */ dev_null_fd = open("/dev/null", O_RDWR); if (dev_null_fd < 0) LogFatal(COMPONENT_MAIN, "Could not open /dev/null: %d (%s)", errno, strerror(errno)); if (dup2(dev_null_fd, STDIN_FILENO) == -1) { LogFatal(COMPONENT_MAIN, "dup2 for stdin failed: %d (%s)", errno, strerror(errno)); } if (dup2(dev_null_fd, STDOUT_FILENO) == -1) { LogFatal(COMPONENT_MAIN, "dup2 for stdout failed: %d (%s)", errno, strerror(errno)); } if (dup2(dev_null_fd, STDERR_FILENO) == -1) { LogFatal(COMPONENT_MAIN, "dup2 for stderr failed: %d (%s)", errno, strerror(errno)); } if (close(dev_null_fd) == -1) LogFatal( COMPONENT_MAIN, "Could not close tmp fd to /dev/null: %d (%s)", errno, strerror(errno)); /* In the child process, change the log header * if not, the header will contain the parent's pid */ set_const_log_str(); break; default: /* This code is within the parent process, * it is useless, it must die */ LogFullDebug(COMPONENT_MAIN, "Starting a child of pid %d", son_pid); exit(0); break; } #endif } /* Make sure Linux file i/o will return with error * if file size is exceeded. */ #ifdef linux signal(SIGXFSZ, SIG_IGN); #endif /* Echo our PID into pidfile: this serves as a lock to prevent */ /* multiple instances from starting, so any failure creating */ /* this file is a fatal error. */ pidfile = open(nfs_pidfile_path, O_CREAT | O_RDWR, 0644); if (pidfile == -1) { LogFatal( COMPONENT_MAIN, "open(%s, O_CREAT | O_RDWR, 0644) failed for pid file, errno was: %s (%d)", nfs_pidfile_path, strerror(errno), errno); goto fatal_die; } else { struct flock lk; /* Try to obtain a lock on the file: if we cannot lock it, */ /* Ganesha may already be running. */ lk.l_type = F_WRLCK; lk.l_whence = SEEK_SET; lk.l_start = (off_t)0; lk.l_len = (off_t)0; if (fcntl(pidfile, F_SETLK, &lk) == -1) { LogFatal(COMPONENT_MAIN, "fcntl(%d) failed, Ganesha already started", pidfile); goto fatal_die; } if (ftruncate(pidfile, 0) == -1) { LogFatal(COMPONENT_MAIN, "ftruncate(%d) failed, errno was: %s (%d)", pidfile, strerror(errno), errno); goto fatal_die; } /* Put our pid into the file, then explicitly sync it */ /* to ensure it winds up on the disk. */ if (dprintf(pidfile, "%u\n", getpid()) < 0 || fsync(pidfile) < 0) { LogFatal( COMPONENT_MAIN, "dprintf() or fsync() failed trying to write pid to file %s errno was: %s (%d)", nfs_pidfile_path, strerror(errno), errno); goto fatal_die; } } /* Set up for the signal handler. * Blocks the signals the signal handler will handle. */ sigemptyset(&signals_to_block); sigaddset(&signals_to_block, SIGTERM); sigaddset(&signals_to_block, SIGHUP); sigaddset(&signals_to_block, SIGPIPE); if (pthread_sigmask(SIG_BLOCK, &signals_to_block, NULL) != 0) { LogFatal(COMPONENT_MAIN, "Could not start nfs daemon, pthread_sigmask failed"); goto fatal_die; } /* init URL package */ config_url_init(); /* Create a memstream for parser+processing error messages */ if (!init_error_type(&err_type)) goto fatal_die; /* Parse the configuration file so we all know what is going on. */ if (nfs_config_path == NULL || nfs_config_path[0] == '\0') { LogWarn(COMPONENT_INIT, "No configuration file named."); nfs_config_struct = NULL; } else nfs_config_struct = config_ParseFile(nfs_config_path, &err_type); if (!config_error_no_error(&err_type)) { char *errstr = err_type_str(&err_type); if (!config_error_is_harmless(&err_type)) { LogCrit(COMPONENT_INIT, "Error %s while parsing (%s)", errstr != NULL ? errstr : "unknown", nfs_config_path); if (errstr != NULL) gsh_free(errstr); goto fatal_die; } else LogWarn(COMPONENT_INIT, "Error %s while parsing (%s)", errstr != NULL ? errstr : "unknown", nfs_config_path); if (errstr != NULL) gsh_free(errstr); } if (read_log_config(nfs_config_struct, &err_type) < 0) { LogCrit(COMPONENT_INIT, "Error while parsing log configuration"); goto fatal_die; } /* We need all the fsal modules loaded so we can have * the list available at exports parsing time. */ if (start_fsals(nfs_config_struct, &err_type) < 0) { LogCrit(COMPONENT_INIT, "Error starting FSALs."); goto fatal_die; } /* parse configuration file */ if (nfs_set_param_from_conf(nfs_config_struct, &my_nfs_start_info, &err_type)) { LogCrit(COMPONENT_INIT, "Error setting parameters from configuration file."); goto fatal_die; } #ifdef LINUX /* Set thread I/O flusher, see * https://git.kernel.org/torvalds/p/8d19f1c8e1937baf74e1962aae9f90fa3aeab463 */ if (prctl(PR_SET_IO_FLUSHER, 1, 0, 0, 0) == -1) { if (errno == EPERM) { if (nfs_param.core_param.allow_set_io_flusher_fail) LogWarn(COMPONENT_MAIN, "Failed to set PR_SET_IO_FLUSHER due to EPERM, ignoring..."); else { LogFatal( COMPONENT_MAIN, "Failed to PR_SET_IO_FLUSHER with EPERM. Take a look at config option allow_set_io_flusher_fail to see if you should allow it"); goto fatal_die; } } else if (errno != EINVAL) { LogFatal( COMPONENT_MAIN, "Error setting prctl PR_SET_IO_FLUSHER flag: %s", strerror(errno)); goto fatal_die; } } #endif #ifdef USE_MONITORING monitoring__init(nfs_param.core_param.monitoring_port, nfs_param.core_param.enable_dynamic_metrics); #endif /* USE_MONITORING */ /* initialize core subsystems and data structures */ if (init_server_pkgs() != 0) { LogCrit(COMPONENT_INIT, "Failed to initialize server packages"); goto fatal_die; } /* Load Data Server entries from parsed file * returns the number of DS entries. */ dsc = ReadDataServers(nfs_config_struct, &err_type); if (dsc < 0) { LogCrit(COMPONENT_INIT, "Error while parsing DS entries"); goto fatal_die; } /* Create stable storage directory, this needs to be done before * starting the recovery thread. */ rc = nfs4_recovery_init(); if (rc) { LogCrit(COMPONENT_INIT, "Recovery backend initialization failed!"); goto fatal_die; } /* Start grace period */ nfs_start_grace(NULL); /* Wait for enforcement to begin */ nfs_wait_for_grace_enforcement(); /* Load export entries from parsed file * returns the number of export entries. */ rc = ReadExports(nfs_config_struct, &err_type); if (rc < 0) { LogCrit(COMPONENT_INIT, "Error while parsing export entries"); goto fatal_die; } if (rc == 0 && dsc == 0) LogWarn(COMPONENT_INIT, "No export entries found in configuration file !!!"); find_unused_blocks(nfs_config_struct, &err_type); rc = report_config_errors(&err_type, NULL, config_errs_to_log); if (config_errors_fatal && rc > 0) goto fatal_die; /* freeing syntax tree : */ config_Free(nfs_config_struct); /* Everything seems to be OK! We can now start service threads */ nfs_start(&my_nfs_start_info); if (tempo_exec_name) free(exec_name); if (log_path) free(log_path); if (pidfile != -1) close(pidfile); nfs_prereq_destroy(); return 0; fatal_die: (void)report_config_errors(&err_type, NULL, config_errs_to_log); if (tempo_exec_name) free(exec_name); if (log_path) free(log_path); if (pidfile != -1) close(pidfile); /* systemd journal won't display our errors without this */ sleep(1); LogFatal(COMPONENT_INIT, "Fatal errors. Server exiting..."); /* NOT REACHED */ return 2; } nfs-ganesha-6.5/src/MainNFSD/nfs_metrics.c000066400000000000000000000271551473756622300204150ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2024 Google LLC * Contributor : Yoni Couriel yonic@google.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_metrics.c * @brief NFS metrics functions */ #include "nfs_metrics.h" #include "common_utils.h" #include "nfs_convert.h" #define FOREACH_NFS_STAT4(X) \ X(NFS4_OK) \ X(NFS4ERR_PERM) \ X(NFS4ERR_NOENT) \ X(NFS4ERR_IO) \ X(NFS4ERR_NXIO) \ X(NFS4ERR_ACCESS) \ X(NFS4ERR_EXIST) \ X(NFS4ERR_XDEV) \ X(NFS4ERR_NOTDIR) \ X(NFS4ERR_ISDIR) \ X(NFS4ERR_INVAL) \ X(NFS4ERR_FBIG) \ X(NFS4ERR_NOSPC) \ X(NFS4ERR_ROFS) \ X(NFS4ERR_MLINK) \ X(NFS4ERR_NAMETOOLONG) \ X(NFS4ERR_NOTEMPTY) \ X(NFS4ERR_DQUOT) \ X(NFS4ERR_STALE) \ X(NFS4ERR_BADHANDLE) \ X(NFS4ERR_BAD_COOKIE) \ X(NFS4ERR_NOTSUPP) \ X(NFS4ERR_TOOSMALL) \ X(NFS4ERR_SERVERFAULT) \ X(NFS4ERR_BADTYPE) \ X(NFS4ERR_DELAY) \ X(NFS4ERR_SAME) \ X(NFS4ERR_DENIED) \ X(NFS4ERR_EXPIRED) \ X(NFS4ERR_LOCKED) \ X(NFS4ERR_GRACE) \ X(NFS4ERR_FHEXPIRED) \ X(NFS4ERR_SHARE_DENIED) \ X(NFS4ERR_WRONGSEC) \ X(NFS4ERR_CLID_INUSE) \ X(NFS4ERR_RESOURCE) \ X(NFS4ERR_MOVED) \ X(NFS4ERR_NOFILEHANDLE) \ X(NFS4ERR_MINOR_VERS_MISMATCH) \ X(NFS4ERR_STALE_CLIENTID) \ X(NFS4ERR_STALE_STATEID) \ X(NFS4ERR_OLD_STATEID) \ X(NFS4ERR_BAD_STATEID) \ X(NFS4ERR_BAD_SEQID) \ X(NFS4ERR_NOT_SAME) \ X(NFS4ERR_LOCK_RANGE) \ X(NFS4ERR_SYMLINK) \ X(NFS4ERR_RESTOREFH) \ X(NFS4ERR_LEASE_MOVED) \ X(NFS4ERR_ATTRNOTSUPP) \ X(NFS4ERR_NO_GRACE) \ X(NFS4ERR_RECLAIM_BAD) \ X(NFS4ERR_RECLAIM_CONFLICT) \ X(NFS4ERR_BADXDR) \ X(NFS4ERR_LOCKS_HELD) \ X(NFS4ERR_OPENMODE) \ X(NFS4ERR_BADOWNER) \ X(NFS4ERR_BADCHAR) \ X(NFS4ERR_BADNAME) \ X(NFS4ERR_BAD_RANGE) \ X(NFS4ERR_LOCK_NOTSUPP) \ X(NFS4ERR_OP_ILLEGAL) \ X(NFS4ERR_DEADLOCK) \ X(NFS4ERR_FILE_OPEN) \ X(NFS4ERR_ADMIN_REVOKED) \ X(NFS4ERR_CB_PATH_DOWN) \ X(NFS4ERR_BADIOMODE) \ X(NFS4ERR_BADLAYOUT) \ X(NFS4ERR_BAD_SESSION_DIGEST) \ X(NFS4ERR_BADSESSION) \ X(NFS4ERR_BADSLOT) \ X(NFS4ERR_COMPLETE_ALREADY) \ X(NFS4ERR_CONN_NOT_BOUND_TO_SESSION) \ X(NFS4ERR_DELEG_ALREADY_WANTED) \ X(NFS4ERR_BACK_CHAN_BUSY) \ X(NFS4ERR_LAYOUTTRYLATER) \ X(NFS4ERR_LAYOUTUNAVAILABLE) \ X(NFS4ERR_NOMATCHING_LAYOUT) \ X(NFS4ERR_RECALLCONFLICT) \ X(NFS4ERR_UNKNOWN_LAYOUTTYPE) \ X(NFS4ERR_SEQ_MISORDERED) \ X(NFS4ERR_SEQUENCE_POS) \ X(NFS4ERR_REQ_TOO_BIG) \ X(NFS4ERR_REP_TOO_BIG) \ X(NFS4ERR_REP_TOO_BIG_TO_CACHE) \ X(NFS4ERR_RETRY_UNCACHED_REP) \ X(NFS4ERR_UNSAFE_COMPOUND) \ X(NFS4ERR_TOO_MANY_OPS) \ X(NFS4ERR_OP_NOT_IN_SESSION) \ X(NFS4ERR_HASH_ALG_UNSUPP) \ X(NFS4ERR_CLIENTID_BUSY) \ X(NFS4ERR_PNFS_IO_HOLE) \ X(NFS4ERR_SEQ_FALSE_RETRY) \ X(NFS4ERR_BAD_HIGH_SLOT) \ X(NFS4ERR_DEADSESSION) \ X(NFS4ERR_ENCR_ALG_UNSUPP) \ X(NFS4ERR_PNFS_NO_LAYOUT) \ X(NFS4ERR_NOT_ONLY_OP) \ X(NFS4ERR_WRONG_CRED) \ X(NFS4ERR_WRONG_TYPE) \ X(NFS4ERR_DIRDELEG_UNAVAIL) \ X(NFS4ERR_REJECT_DELEG) \ X(NFS4ERR_RETURNCONFLICT) \ X(NFS4ERR_DELEG_REVOKED) \ X(NFS4ERR_PARTNER_NOTSUPP) \ X(NFS4ERR_PARTNER_NO_AUTH) \ X(NFS4ERR_UNION_NOTSUPP) \ X(NFS4ERR_OFFLOAD_DENIED) \ X(NFS4ERR_WRONG_LFS) \ X(NFS4ERR_BADLABEL) \ X(NFS4ERR_OFFLOAD_NO_REQS) \ X(NFS4ERR_NOXATTR) \ X(NFS4ERR_XATTR2BIG) \ X(NFS4ERR_REPLAY) enum nfsstat4_index { NFSSTAT4_INDEX_UNKNOWN_STATUS = 0, #define DEFINE_INDEX(name) CONCAT(name, __INDEX), FOREACH_NFS_STAT4(DEFINE_INDEX) NFSSTAT4_INDEX_LAST, }; static counter_metric_handle_t rpcs_received_total; static counter_metric_handle_t rpcs_completed_total; static gauge_metric_handle_t rpcs_inflight; /* NFSv4 Operation Metrics */ static histogram_metric_handle_t nfsv4_op_latency[NFS4_OP_LAST_ONE] [NFSSTAT4_INDEX_LAST]; static counter_metric_handle_t nfsv4_op_count[NFS4_OP_LAST_ONE] [NFSSTAT4_INDEX_LAST]; /* Compound procedure latency metric */ static histogram_metric_handle_t compound_latency_metric[NFSSTAT4_INDEX_LAST]; /* NFS operations per Compound procedure metric */ static histogram_metric_handle_t compound_ops_count_metric; static counter_metric_handle_t dropped_gss_requests_count; static enum nfsstat4_index nfsstat4_to_index(nfsstat4 stat) { switch (stat) { #define DEFINE_CASE(name) \ case name: \ return CONCAT(name, __INDEX); FOREACH_NFS_STAT4(DEFINE_CASE) default: return NFSSTAT4_INDEX_UNKNOWN_STATUS; } } static const nfsstat4 index_to_nfsstat4[] = { [NFSSTAT4_INDEX_UNKNOWN_STATUS] = (nfsstat4)(-1), #define DEFINE_INDEX_TO_STAT(name) [CONCAT(name, __INDEX)] = name, FOREACH_NFS_STAT4(DEFINE_INDEX_TO_STAT) }; static void register_nfsv4_operation_metrics(nfs_opnum4 opcode, enum nfsstat4_index statcode_index) { const metric_label_t labels[] = { METRIC_LABEL("op", nfsop4_to_str(opcode)), METRIC_LABEL("status", nfsstat4_to_str(index_to_nfsstat4[statcode_index])) }; nfsv4_op_latency[opcode][statcode_index] = monitoring__register_histogram( "nfsv4__op_latency", METRIC_METADATA("NFSv4 Operations Latency", METRIC_UNIT_MILLISECOND), labels, ARRAY_SIZE(labels), monitoring__buckets_exp2()); nfsv4_op_count[opcode][statcode_index] = monitoring__register_counter( "nfsv4__op_count", METRIC_METADATA("NFSv4 Operations Counter", METRIC_UNIT_NONE), labels, ARRAY_SIZE(labels)); } static void register_nfsv4_operations_metrics(void) { for (nfs_opnum4 opcode = 0; opcode < NFS4_OP_LAST_ONE; opcode++) { for (enum nfsstat4_index statcode_index = 0; statcode_index < NFSSTAT4_INDEX_LAST; statcode_index++) { register_nfsv4_operation_metrics(opcode, statcode_index); } } } static void register_dropped_gss_requests_count_metric(void) { const metric_label_t labels[] = {}; dropped_gss_requests_count = monitoring__register_counter( "nfsv4__dropped_gss_requests_count", METRIC_METADATA("Number of dropped rpcsec_gss requests", METRIC_UNIT_NONE), labels, ARRAY_SIZE(labels)); } static void register_compound_operation_metrics(void) { const metric_label_t empty_labels[] = {}; compound_ops_count_metric = monitoring__register_histogram( "compound__ops_count", METRIC_METADATA("Number of Operations in a Compound", METRIC_UNIT_NONE), empty_labels, ARRAY_SIZE(empty_labels), monitoring__buckets_exp2()); for (enum nfsstat4_index statcode_index = 0; statcode_index < NFSSTAT4_INDEX_LAST; statcode_index++) { const metric_label_t labels[] = { METRIC_LABEL( "status", nfsstat4_to_str(index_to_nfsstat4[statcode_index])) }; compound_latency_metric[statcode_index] = monitoring__register_histogram( "compound__latency", METRIC_METADATA("Compound Latency Histogram", METRIC_UNIT_MILLISECOND), labels, ARRAY_SIZE(labels), monitoring__buckets_exp2()); } } void nfs_metrics__nfs4_op_completed(nfs_opnum4 opcode, nfsstat4 statcode, nsecs_elapsed_t latency) { monitoring__histogram_observe( nfsv4_op_latency[opcode][nfsstat4_to_index(statcode)], latency / NS_PER_MSEC); monitoring__counter_inc( nfsv4_op_count[opcode][nfsstat4_to_index(statcode)], 1); } void nfs_metrics__gss_request_dropped(void) { monitoring__counter_inc(dropped_gss_requests_count, 1); } void nfs_metrics__nfs4_compound_completed(nfsstat4 statcode, nsecs_elapsed_t latency, int num_ops) { monitoring__histogram_observe( compound_latency_metric[nfsstat4_to_index(statcode)], latency / NS_PER_MSEC); monitoring__histogram_observe(compound_ops_count_metric, num_ops); } static void register_rpcs_metrics(void) { const metric_label_t labels[] = {}; rpcs_received_total = monitoring__register_counter( "rpcs_received_total", METRIC_METADATA("Number of NFS requests received", METRIC_UNIT_NONE), labels, ARRAY_SIZE(labels)); rpcs_completed_total = monitoring__register_counter( "rpcs_completed_total", METRIC_METADATA("Number of NFS requests completed", METRIC_UNIT_NONE), labels, ARRAY_SIZE(labels)); rpcs_inflight = monitoring__register_gauge( "rpcs_in_flight", METRIC_METADATA("Number of NFS requests received or in flight.", METRIC_UNIT_NONE), labels, ARRAY_SIZE(labels)); } void nfs_metrics__rpc_received(void) { monitoring__counter_inc(rpcs_received_total, 1); } void nfs_metrics__rpc_completed(void) { monitoring__counter_inc(rpcs_completed_total, 1); } void nfs_metrics__rpcs_in_flight(int64_t value) { monitoring__gauge_set(rpcs_inflight, value); } #ifdef _USE_NFS3 void nfs_metrics__nfs3_request(uint32_t proc, nsecs_elapsed_t request_time, nfsstat3 nfs_status, export_id_t export_id, const char *client_ip) { const char *const version = "nfs3"; const char *const operation = nfsproc3_to_str(proc); const char *const status_label = nfsstat3_to_str(nfs_status); monitoring__dynamic_observe_nfs_request(operation, request_time, version, status_label, export_id, client_ip); } #endif void nfs_metrics__nfs4_request(uint32_t op, nsecs_elapsed_t request_time, nfsstat4 status, export_id_t export_id, const char *client_ip) { const char *const version = "nfs4"; const char *const operation = nfsop4_to_str(op); const char *const status_label = nfsstat4_to_str(status); monitoring__dynamic_observe_nfs_request(operation, request_time, version, status_label, export_id, client_ip); } void nfs_metrics__init(void) { register_rpcs_metrics(); register_nfsv4_operations_metrics(); register_dropped_gss_requests_count_metric(); register_compound_operation_metrics(); } nfs-ganesha-6.5/src/MainNFSD/nfs_reaper_thread.c000066400000000000000000000226211473756622300215450ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_reaper_thread.c * @brief check for expired clients and whack them. */ #include "config.h" #include #include #include #include #include #ifndef __APPLE__ #include #endif #include "log.h" #include "nfs4.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_core.h" #include "log.h" #include "fridgethr.h" #define REAPER_DELAY 10 unsigned int reaper_delay = REAPER_DELAY; static struct fridgethr *reaper_fridge; static int reap_hash_table(hash_table_t *ht_reap) { struct rbt_head *head_rbt; struct hash_data *addr = NULL; uint32_t i; struct rbt_node *pn; nfs_client_id_t *client_id; nfs_client_record_t *client_rec; int count = 0; /* For each bucket of the requested hashtable */ for (i = 0; i < ht_reap->parameter.index_size; i++) { /* Before starting with traversal of RBT in the bucket, * if the expired client list gets filled, need reap it first */ count += reap_expired_client_list(NULL); head_rbt = &ht_reap->partitions[i].rbt; restart: /* acquire mutex */ PTHREAD_RWLOCK_wrlock(&ht_reap->partitions[i].ht_lock); /* go through all entries in the red-black-tree */ RBT_LOOP(head_rbt, pn) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; addr = RBT_OPAQ(pn); client_id = addr->val.addr; count++; PTHREAD_MUTEX_lock(&client_id->cid_mutex); if (client_id->marked_for_delayed_cleanup || valid_lease(client_id, false)) { PTHREAD_MUTEX_unlock(&client_id->cid_mutex); RBT_INCREMENT(pn); continue; } if (isDebug(COMPONENT_CLIENTID)) { display_client_id_rec(&dspbuf, client_id); LogFullDebug(COMPONENT_CLIENTID, "Expired index %d %s", i, str); str_valid = true; } /* Get the client record. It will not be NULL. */ client_rec = client_id->cid_client_record; /* get a ref to client_id as we might drop the * last reference while expiring. This also protects * the reference to the client_record. */ inc_client_id_ref(client_id); PTHREAD_MUTEX_unlock(&client_id->cid_mutex); PTHREAD_RWLOCK_unlock(&ht_reap->partitions[i].ht_lock); /* Before expiring the current client in RBT node, * if the expired client list gets filled, need reap it */ count += reap_expired_client_list(NULL); PTHREAD_MUTEX_lock(&client_rec->cr_mutex); nfs_client_id_expire(client_id, false, false); PTHREAD_MUTEX_unlock(&client_rec->cr_mutex); if (isFullDebug(COMPONENT_CLIENTID)) { if (!str_valid) display_printf(&dspbuf, "clientid %p", client_id); if (client_id->marked_for_delayed_cleanup) { LogFullDebug( COMPONENT_CLIENTID, "Reaper, Parked for later cleanup {%s}", str); } else { LogFullDebug( COMPONENT_CLIENTID, "Reaper done, expired {%s}", str); } } /* drop our reference to the client_id */ dec_client_id_ref(client_id); goto restart; } PTHREAD_RWLOCK_unlock(&ht_reap->partitions[i].ht_lock); } return count; } static int reap_expired_open_owners(void) { int count = 0; time_t tnow = time(NULL); time_t texpire; state_owner_t *owner; struct state_nfs4_owner_t *nfs4_owner; PTHREAD_MUTEX_lock(&cached_open_owners_lock); /* Walk the list of cached NFS 4 open owners. Because we hold * the mutex while walking this list, it is impossible for another * thread to get a primary reference to these owners while we * process, and thus prevent them from expiring. */ while (true) { owner = glist_first_entry( &cached_open_owners, state_owner_t, so_owner.so_nfs4_owner.so_cache_entry); if (owner == NULL) break; nfs4_owner = &owner->so_owner.so_nfs4_owner; texpire = atomic_fetch_time_t(&nfs4_owner->so_cache_expire); if (texpire > tnow) { /* This owner has not yet expired. */ if (isFullDebug(COMPONENT_STATE)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_owner(&dspbuf, owner); LogFullDebug( COMPONENT_STATE, "Did not release CLOSE_PENDING %d seconds left for {%s}", (int)(texpire - tnow), str); } /* Because entries are not moved on this list, and * they are added when they first become eligible, * the entries are in order of expiration time, and * thus once we hit one that is not expired yet, the * rest are also not expired. */ break; } /* This cached owner has expired, uncache it. */ uncache_nfs4_owner(nfs4_owner); count++; } PTHREAD_MUTEX_unlock(&cached_open_owners_lock); return count; } #ifndef __APPLE__ /* Return resident memory of the process in MB */ static size_t get_current_rss(void) { static long page_size; int rc, fd; char buf[1024]; unsigned long vsize = 0, rss = 0; if (page_size == 0) { page_size = sysconf(_SC_PAGESIZE); if (page_size <= 0) { LogEvent(COMPONENT_MEMLEAKS, "_SC_PAGESIZE failed, %d", errno); return 0; } } fd = open("/proc/self/statm", O_RDONLY); if (fd < 0) return 0; rc = read(fd, buf, sizeof(buf) - 1); if (rc < 0) goto out; buf[rc] = '\0'; rc = sscanf(buf, "%lu %lu", &vsize, &rss); if (rc != 2) { LogEvent(COMPONENT_MEMLEAKS, "Failed to read data from /proc/self/statm"); } out: close(fd); return (rss * page_size) / (1024 * 1024); } static void reap_malloc_frag(void) { static size_t trim_threshold; size_t min_threshold = nfs_param.core_param.malloc_trim_minthreshold; size_t rss; if (trim_threshold == 0) trim_threshold = min_threshold; rss = get_current_rss(); LogDebug(COMPONENT_MEMLEAKS, "current rss: %zu MB, threshold: %zu MB", rss, trim_threshold); if (rss < trim_threshold) { /* If the threshold is too big, drop it in relation to * the current RSS */ if (trim_threshold > rss + rss / 2) trim_threshold = MAX(rss + rss / 2, min_threshold); return; } LogEvent(COMPONENT_MEMLEAKS, "calling malloc_trim, current rss: %zu MB, threshold: %zu MB", rss, trim_threshold); malloc_trim(0); rss = get_current_rss(); /* Set trim threshold to one and one half times of the current RSS */ trim_threshold = MAX(rss + rss / 2, min_threshold); LogEvent(COMPONENT_MEMLEAKS, "called malloc_trim, current rss: %zu MB, threshold: %zu MB", rss, trim_threshold); } #endif struct reaper_state { size_t count; bool logged; }; static struct reaper_state reaper_state; static void reaper_run(struct fridgethr_context *ctx) { struct reaper_state *rst = ctx->arg; SetNameFunction("reaper"); /* see if we need to start a grace period */ nfs_maybe_start_grace(); /* * Try to lift the grace period, unless we're shutting down. * Ordinarily, we'd take the mutex to check this, but this is just a * best-effort sort of thing. */ if (!admin_shutdown) nfs_try_lift_grace(); if (isDebug(COMPONENT_CLIENTID) && ((rst->count > 0) || !rst->logged)) { LogDebug(COMPONENT_CLIENTID, "Now checking NFS4 clients for expiration"); rst->logged = (rst->count == 0); #ifdef DEBUG_SAL if (rst->count == 0) { dump_all_states(); dump_all_owners(); } #endif } rst->count = reap_expired_client_list(NULL); rst->count += (reap_hash_table(ht_confirmed_client_id) + reap_hash_table(ht_unconfirmed_client_id)); rst->count += reap_expired_open_owners(); #ifndef __APPLE__ if (nfs_param.core_param.malloc_trim) reap_malloc_frag(); #endif } int reaper_init(void) { struct fridgethr_params frp; int rc = 0; if (nfs_param.nfsv4_param.lease_lifetime < (2 * REAPER_DELAY)) reaper_delay = nfs_param.nfsv4_param.lease_lifetime / 2; memset(&frp, 0, sizeof(struct fridgethr_params)); frp.thr_max = 1; frp.thr_min = 1; frp.thread_delay = reaper_delay; frp.flavor = fridgethr_flavor_looper; rc = fridgethr_init(&reaper_fridge, "reaper", &frp); if (rc != 0) { LogMajor(COMPONENT_CLIENTID, "Unable to initialize reaper fridge, error code %d.", rc); return rc; } rc = fridgethr_submit(reaper_fridge, reaper_run, &reaper_state); if (rc != 0) { LogMajor(COMPONENT_CLIENTID, "Unable to start reaper thread, error code %d.", rc); return rc; } return 0; } void reaper_wake(void) { struct fridgethr *frt = reaper_fridge; if (frt) fridgethr_wake(frt); } int reaper_shutdown(void) { int rc = fridgethr_sync_command(reaper_fridge, fridgethr_comm_stop, 120); if (rc == ETIMEDOUT) { LogMajor(COMPONENT_CLIENTID, "Shutdown timed out, cancelling threads."); fridgethr_cancel(reaper_fridge); } else if (rc != 0) { LogMajor(COMPONENT_CLIENTID, "Failed shutting down reaper thread: %d", rc); } return rc; } nfs-ganesha-6.5/src/MainNFSD/nfs_rpc_callback.c000066400000000000000000001110371473756622300213400ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright (C) 2012, The Linux Box Corporation * Copyright (c) 2012-2018 Red Hat, Inc. and/or its affiliates. * Contributor : Matt Benjamin * William Allen Simpson * * Some portions Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @file nfs_rpc_callback.c * @author Matt Benjamin * @author Lee Dobryden * @author Adam C. Emerson * @brief RPC callback dispatch package * * This module implements APIs for submission, and dispatch of NFSv4.0 * and NFSv4.1 callbacks. * */ #include "config.h" #include #include #include #include #include #include #include #include #include "fsal.h" #include "nfs_core.h" #include "log.h" #include "nfs_rpc_callback.h" #include "nfs4.h" #ifdef _HAVE_GSSAPI #include "gss_credcache.h" #endif /* _HAVE_GSSAPI */ #include "sal_data.h" #include "sal_functions.h" #include const struct __netid_nc_table netid_nc_table[9] = { { "-", _NC_ERR, 0 }, { "tcp", _NC_TCP, AF_INET }, { "tcp6", _NC_TCP6, AF_INET6 }, { "rdma", _NC_RDMA, AF_INET }, { "rdma6", _NC_RDMA6, AF_INET6 }, { "sctp", _NC_SCTP, AF_INET }, { "sctp6", _NC_SCTP6, AF_INET6 }, { "udp", _NC_UDP, AF_INET }, { "udp6", _NC_UDP6, AF_INET6 }, }; /* retry timeout default to the moon and back */ static const struct timespec tout = { 3, 0 }; #ifdef _HAVE_GSSAPI struct gss_callback_status_holder { /* Switch to enable or disable gss callback */ bool enabled; /* Lock to protect reads and writes to gss callback status */ pthread_rwlock_t lock; }; static struct gss_callback_status_holder gss_callback_status = { .enabled = true }; /** * @brief Initialize the callback credential cache * * @param[in] ccache Location of credential cache */ static inline void nfs_rpc_cb_init_ccache(const char *ccache) { int code; if (mkdir(ccache, 0700) < 0) { if (errno == EEXIST) LogEvent(COMPONENT_INIT, "Callback creds directory (%s) already exists", ccache); else LogWarn(COMPONENT_INIT, "Could not create credential cache directory: %s (%s)", ccache, strerror(errno)); } ccachesearch[0] = nfs_param.krb5_param.ccache_dir; code = gssd_refresh_krb5_machine_credential( nfs_host_name, NULL, nfs_param.krb5_param.svc.principal); if (code) LogWarn(COMPONENT_INIT, "gssd_refresh_krb5_machine_credential failed (%d:%d)", code, errno); } #endif /* _HAVE_GSSAPI */ /** * @brief Initialize callback subsystem * * @note This should be called once in the process lifetime, during startup. */ void nfs_rpc_cb_pkginit(void) { #ifdef _HAVE_GSSAPI gssd_init_cred_cache(); PTHREAD_RWLOCK_init(&gss_callback_status.lock, NULL); /* sanity check GSSAPI */ if (gssd_check_mechs() != 0) LogCrit(COMPONENT_INIT, "sanity check: gssd_check_mechs() failed"); #endif /* _HAVE_GSSAPI */ } /** * @brief Shutdown callback subsystem * * @note This should be called once in the process lifetime, during shutdown. */ void nfs_rpc_cb_pkgshutdown(void) { #ifdef _HAVE_GSSAPI gssd_clear_cred_cache(); PTHREAD_RWLOCK_destroy(&gss_callback_status.lock); gssd_shutdown_cred_cache(); #endif } #ifdef _HAVE_GSSAPI /** * @brief Set gss status for callback * * The status can be set to ON or OFF. * If the status is set to OFF, existing gss-creds will be cleared. * If the status is set to ON, machine gss creds will be initialised. * * @note This function can be used if during the process lifetime, we want to * enable or disable gss for callback channel */ void nfs_rpc_cb_set_gss_status(bool gss_enabled) { PTHREAD_RWLOCK_wrlock(&gss_callback_status.lock); if (gss_callback_status.enabled == gss_enabled) { PTHREAD_RWLOCK_unlock(&gss_callback_status.lock); LogInfo(COMPONENT_NFS_CB, "Callback channel's gss status is already set to %d", gss_enabled); return; } if (gss_enabled) { nfs_rpc_cb_init_ccache(nfs_param.krb5_param.ccache_dir); gss_callback_status.enabled = true; PTHREAD_RWLOCK_unlock(&gss_callback_status.lock); LogInfo(COMPONENT_NFS_CB, "Gss callbacks are now enabled"); return; } gssd_clear_cred_cache(); gss_callback_status.enabled = false; PTHREAD_RWLOCK_unlock(&gss_callback_status.lock); LogInfo(COMPONENT_NFS_CB, "Gss callbacks are now disabled"); } #endif /** * @brief Convert a netid label * * @todo This is automatically redundant, but in fact upstream TI-RPC is * not up-to-date with RFC 5665, will fix (Matt) * * @param[in] netid The netid label dictating the protocol * * @return The numerical protocol identifier. */ nc_type nfs_netid_to_nc(const char *netid) { if (!strcmp(netid, netid_nc_table[_NC_TCP6].netid)) return _NC_TCP6; if (!strcmp(netid, netid_nc_table[_NC_TCP].netid)) return _NC_TCP; if (!strcmp(netid, netid_nc_table[_NC_UDP6].netid)) return _NC_UDP6; if (!strcmp(netid, netid_nc_table[_NC_UDP].netid)) return _NC_UDP; if (!strcmp(netid, netid_nc_table[_NC_RDMA6].netid)) return _NC_RDMA6; if (!strcmp(netid, netid_nc_table[_NC_RDMA].netid)) return _NC_RDMA; if (!strcmp(netid, netid_nc_table[_NC_SCTP6].netid)) return _NC_SCTP6; if (!strcmp(netid, netid_nc_table[_NC_SCTP].netid)) return _NC_SCTP; return _NC_ERR; } /** * @brief Convert string format address to sockaddr * * This function takes the host.port format used in the NFSv4.0 * clientaddr4 and converts it to a POSIX sockaddr structure stored in * the callback information of the clientid. * * @param[in,out] clientid The clientid in which to store the sockaddr * @param[in] uaddr na_r_addr from the clientaddr4 */ static inline void setup_client_saddr(nfs_client_id_t *clientid, const char *uaddr) { int code; char *uaddr2 = gsh_strdup(uaddr); char *dot, *p1, *p2; uint16_t port; assert(clientid->cid_minorversion == 0); /* find the two port bytes and terminate the string */ dot = strrchr(uaddr2, '.'); if (dot == NULL) goto out; p2 = dot + 1; *dot = '\0'; dot = strrchr(uaddr2, '.'); if (dot == NULL) goto out; p1 = dot + 1; *dot = '\0'; port = htons((atoi(p1) << 8) | atoi(p2)); /* At this point, the port has been extracted and uaddr2 is now * terminated without the port portion and is thus suitable for * passing to inet_pton as is and will support a variety of formats. */ memset(&clientid->cid_cb.v40.cb_addr.ss, 0, sizeof(sockaddr_t)); switch (clientid->cid_cb.v40.cb_addr.nc) { case _NC_TCP: case _NC_RDMA: case _NC_SCTP: case _NC_UDP: { /* IPv4 (ws inspired) */ struct sockaddr_in *sin = ((struct sockaddr_in *)&clientid->cid_cb.v40.cb_addr.ss); sin->sin_family = AF_INET; sin->sin_port = port; code = inet_pton(AF_INET, uaddr2, &sin->sin_addr); if (code != 1) LogWarn(COMPONENT_NFS_CB, "inet_pton failed (%d %s)", code, uaddr); else LogDebug(COMPONENT_NFS_CB, "client callback addr:port %s:%d", uaddr2, ntohs(port)); break; } case _NC_TCP6: case _NC_RDMA6: case _NC_SCTP6: case _NC_UDP6: { /* IPv6 (ws inspired) */ struct sockaddr_in6 *sin6 = ((struct sockaddr_in6 *)&clientid ->cid_cb.v40.cb_addr.ss); sin6->sin6_family = AF_INET6; sin6->sin6_port = port; code = inet_pton(AF_INET6, uaddr2, &sin6->sin6_addr); if (code != 1) LogWarn(COMPONENT_NFS_CB, "inet_pton failed (%d %s)", code, uaddr); else LogDebug(COMPONENT_NFS_CB, "client callback addr:port %s:%d", uaddr2, ntohs(port)); break; } default: /* unknown netid */ break; }; out: gsh_free(uaddr2); } /** * @brief Set the callback location for an NFSv4.0 clientid * * @param[in,out] clientid The clientid in which to set the location * @param[in] addr4 The client's supplied callback address */ void nfs_set_client_location(nfs_client_id_t *clientid, const clientaddr4 *addr4) { assert(clientid->cid_minorversion == 0); clientid->cid_cb.v40.cb_addr.nc = nfs_netid_to_nc(addr4->r_netid); if (strlcpy(clientid->cid_cb.v40.cb_client_r_addr, addr4->r_addr, sizeof(clientid->cid_cb.v40.cb_client_r_addr)) >= sizeof(clientid->cid_cb.v40.cb_client_r_addr)) { LogCrit(COMPONENT_CLIENTID, "Callback r_addr %s too long", addr4->r_addr); } setup_client_saddr(clientid, clientid->cid_cb.v40.cb_client_r_addr); } /** * @brief Get the fd of an NFSv4.0 callback connection * * @param[in] clientid The clientid to query * @param[out] fd The file descriptor * @param[out] proto The protocol used on this connection * * @return 0 or values of errno. */ static inline int32_t nfs_clid_connected_socket(nfs_client_id_t *clientid, int *fd, int *proto) { int domain, sock_type, protocol, sock_size; int nfd; int code; assert(clientid->cid_minorversion == 0); *fd = -1; *proto = -1; switch (clientid->cid_cb.v40.cb_addr.nc) { case _NC_TCP: case _NC_TCP6: sock_type = SOCK_STREAM; protocol = IPPROTO_TCP; break; case _NC_UDP6: case _NC_UDP: sock_type = SOCK_DGRAM; protocol = IPPROTO_UDP; break; default: return EINVAL; } switch (clientid->cid_cb.v40.cb_addr.ss.ss_family) { case AF_INET: domain = PF_INET; sock_size = sizeof(struct sockaddr_in); break; case AF_INET6: domain = PF_INET6; sock_size = sizeof(struct sockaddr_in6); break; default: return EINVAL; } nfd = socket(domain, sock_type, protocol); if (nfd < 0) { code = errno; LogWarn(COMPONENT_NFS_CB, "socket failed %d (%s)", code, strerror(code)); return code; } code = connect(nfd, (struct sockaddr *)&clientid->cid_cb.v40.cb_addr.ss, sock_size); if (code < 0) { code = errno; LogWarn(COMPONENT_NFS_CB, "connect fail errno %d (%s)", code, strerror(code)); close(nfd); return code; } *proto = protocol; *fd = nfd; return code; } /* end refactorable RPC code */ /** * @brief Check if an authentication flavor is supported * * @param[in] flavor RPC authentication flavor * * @retval true if supported. * @retval false if not. */ static inline bool supported_auth_flavor(int flavor) { switch (flavor) { case RPCSEC_GSS: case AUTH_SYS: case AUTH_NONE: return true; default: return false; }; } /** * @brief Kerberos OID * * This value comes from kerberos source, gssapi_krb5.c (Umich). */ #ifdef _HAVE_GSSAPI gss_OID_desc krb5oid = { 9, "\052\206\110\206\367\022\001\002\002" }; /** * @brief Format a principal name for an RPC call channel * * @param[in] chan Call channel * @param[out] buf Buffer to hold formatted name * @param[in] len Size of buffer * * @return The principle or NULL. */ static inline char *format_host_principal(rpc_call_channel_t *chan, char *buf, size_t len) { const char *qualifier = "nfs@"; int qualifier_len = strlen(qualifier); void *sin; if (len < SOCK_NAME_MAX) return NULL; switch (chan->type) { case RPC_CHAN_V40: sin = &chan->source.clientid->cid_cb.v40.cb_addr.ss; break; default: return NULL; } memcpy(buf, qualifier, qualifier_len + 1); if (sprint_sockip(sin, buf + qualifier_len, len - qualifier_len)) return buf; return NULL; } #endif /* _HAVE_GSSAPI */ /** * @brief Set up GSS on a callback channel * * @param[in,out] chan Channel on which to set up GSS * @param[in] cred GSS Credential * * @return auth->ah_error; check AUTH_FAILURE or AUTH_SUCCESS. * @note this function only works for NFS v4.0 as of now */ #ifdef _HAVE_GSSAPI static inline AUTH *nfs_rpc_callback_setup_gss(rpc_call_channel_t *chan, nfs_client_cred_t *cred) { AUTH *result; char hprinc[MAXPATHLEN + 1]; char *principal = nfs_param.krb5_param.svc.principal; int32_t code; assert(cred->flavor == RPCSEC_GSS); /* MUST RFC 3530bis, section 3.3.3 */ chan->gss_sec.svc = cred->auth_union.auth_gss.svc; chan->gss_sec.qop = cred->auth_union.auth_gss.qop; PTHREAD_RWLOCK_rdlock(&gss_callback_status.lock); if (!gss_callback_status.enabled) { PTHREAD_RWLOCK_unlock(&gss_callback_status.lock); LogWarn(COMPONENT_NFS_CB, "gss callback is not enabled. Skipping gss setup for callback"); code = EINVAL; goto out_err; } /* the GSSAPI k5 mech needs to find an unexpired credential * for nfs/hostname in an accessible k5ccache */ code = gssd_refresh_krb5_machine_credential(nfs_host_name, NULL, principal); PTHREAD_RWLOCK_unlock(&gss_callback_status.lock); if (code) { LogWarn(COMPONENT_NFS_CB, "gssd_refresh_krb5_machine_credential failed (%d:%d)", code, errno); goto out_err; } if (!format_host_principal(chan, hprinc, sizeof(hprinc))) { code = errno; LogCrit(COMPONENT_NFS_CB, "format_host_principal failed"); goto out_err; } chan->gss_sec.cred = GSS_C_NO_CREDENTIAL; chan->gss_sec.req_flags = 0; if (chan->gss_sec.svc != RPCSEC_GSS_SVC_NONE) { /* no more lipkey, spkm3 */ chan->gss_sec.mech = (gss_OID)&krb5oid; chan->gss_sec.req_flags = GSS_C_MUTUAL_FLAG; /* XXX */ result = authgss_ncreate_default(chan->clnt, hprinc, &chan->gss_sec); } else { result = authnone_ncreate(); } return result; out_err: result = authnone_ncreate_dummy(); result->ah_error.re_status = RPC_SYSTEMERROR; result->ah_error.re_errno = code; return result; } #endif /* _HAVE_GSSAPI */ /** * @brief Create a channel for an NFSv4.0 client * * @param[in] clientid Client record * @param[in] flags Currently unused * * @return Status code. */ int nfs_rpc_create_chan_v40(nfs_client_id_t *clientid, uint32_t flags) { rpc_call_channel_t *chan = &clientid->cid_cb.v40.cb_chan; char *err; struct netbuf raddr; int fd; int proto; int code; assert(!chan->clnt); assert(clientid->cid_minorversion == 0); /* XXX we MUST error RFC 3530bis, sec. 3.3.3 */ if (!supported_auth_flavor(clientid->cid_credential.flavor)) return EINVAL; chan->type = RPC_CHAN_V40; chan->source.clientid = clientid; code = nfs_clid_connected_socket(clientid, &fd, &proto); if (code) { LogWarn(COMPONENT_NFS_CB, "Failed creating socket"); return code; } raddr.buf = &clientid->cid_cb.v40.cb_addr.ss; switch (proto) { case IPPROTO_TCP: raddr.maxlen = raddr.len = sizeof(struct sockaddr_in); chan->clnt = clnt_vc_ncreatef( fd, &raddr, clientid->cid_cb.v40.cb_program, NFS_CB /* Errata ID: 2291 */, 0, 0, CLNT_CREATE_FLAG_CLOSE | CLNT_CREATE_FLAG_CONNECT); break; case IPPROTO_UDP: raddr.maxlen = raddr.len = sizeof(struct sockaddr_in6); chan->clnt = clnt_dg_ncreatef(fd, &raddr, clientid->cid_cb.v40.cb_program, NFS_CB /* Errata ID: 2291 */, 0, 0, CLNT_CREATE_FLAG_CLOSE); break; default: break; } if (CLNT_FAILURE(chan->clnt)) { err = rpc_sperror(&chan->clnt->cl_error, "failed"); LogDebug(COMPONENT_NFS_CB, "%s", err); gsh_free(err); CLNT_DESTROY(chan->clnt); chan->clnt = NULL; close(fd); return EINVAL; } /* channel protection */ switch (clientid->cid_credential.flavor) { #ifdef _HAVE_GSSAPI case RPCSEC_GSS: chan->auth = nfs_rpc_callback_setup_gss( chan, &clientid->cid_credential); break; #endif /* _HAVE_GSSAPI */ case AUTH_SYS: chan->auth = authunix_ncreate_default(); break; case AUTH_NONE: chan->auth = authnone_ncreate(); break; default: return EINVAL; } if (AUTH_FAILURE(chan->auth)) { err = rpc_sperror(&chan->auth->ah_error, "failed"); LogDebug(COMPONENT_NFS_CB, "%s", err); gsh_free(err); AUTH_DESTROY(chan->auth); chan->auth = NULL; CLNT_DESTROY(chan->clnt); chan->clnt = NULL; return EINVAL; } return 0; } /** * @brief Dispose of a channel without locking * * The caller should hold the channel mutex. * * @param[in] chan The channel to dispose of */ void nfs_rpc_destroy_chan_no_lock(rpc_call_channel_t *chan) { assert(chan); /* clean up auth, if any */ if (chan->auth) { AUTH_DESTROY(chan->auth); chan->auth = NULL; } /* channel has a dedicated RPC client */ if (chan->clnt) { /* destroy it */ CLNT_DESTROY(chan->clnt); chan->clnt = NULL; } chan->last_called = 0; } /** * Call the NFSv4 client's CB_NULL procedure. * * @param[in] chan Channel on which to call * @param[in] timeout The timeout for client call * @param[in] locked True if the channel is already locked * * @return Client status. */ static enum clnt_stat rpc_cb_null(rpc_call_channel_t *chan, bool locked) { struct clnt_req *cc; enum clnt_stat stat; /* XXX TI-RPC does the signal masking */ if (!locked) PTHREAD_MUTEX_lock(&chan->chan_mtx); if (!chan->clnt) { stat = RPC_INTR; goto unlock; } cc = gsh_malloc(sizeof(*cc)); clnt_req_fill(cc, chan->clnt, chan->auth, CB_NULL, (xdrproc_t)xdr_void, NULL, (xdrproc_t)xdr_void, NULL); stat = clnt_req_setup(cc, tout); if (stat == RPC_SUCCESS) { cc->cc_refreshes = 1; stat = CLNT_CALL_WAIT(cc); } clnt_req_release(cc); /* If a call fails, we have to assume path down, or equally fatal * error. We may need back-off. */ if (stat != RPC_SUCCESS) nfs_rpc_destroy_chan_no_lock(chan); unlock: if (!locked) PTHREAD_MUTEX_unlock(&chan->chan_mtx); return stat; } /** * @brief Create a channel for an NFSv4.1 session * * This function creates a channel on an NFSv4.1 session, using the * given security parameters. If a channel already exists, it is * removed and replaced. * * @param[in,out] session The session on which to create the * back channel * @param[in] num_sec_parms Length of sec_parms list * @param[in] sec_parms Allowable security parameters * * @return 0 or POSIX error code. */ int nfs_rpc_create_chan_v41(SVCXPRT *xprt, nfs41_session_t *session, int num_sec_parms, callback_sec_parms4 *sec_parms) { rpc_call_channel_t *chan = &session->cb_chan; char *err; int i; int code = 0; bool authed = false; PTHREAD_MUTEX_lock(&chan->chan_mtx); if (chan->clnt) { SVCXPRT *const existing_xprt = clnt_vc_get_client_xprt(chan->clnt); if (existing_xprt == xprt) { LogInfo(COMPONENT_NFS_CB, "Xprt FD: %d is already associated with the channel. Skip creation", xprt->xp_fd); goto out; } if (existing_xprt == NULL) { LogInfo(COMPONENT_NFS_CB, "The channel exists with NULL xprt, destroy and recreate with xprt FD: %d", xprt->xp_fd); } else { LogInfo(COMPONENT_NFS_CB, "The channel exists with xprt FD: %d, destroy and recreate with xprt FD: %d", existing_xprt->xp_fd, xprt->xp_fd); } nfs_rpc_destroy_chan_no_lock(chan); } chan->type = RPC_CHAN_V41; chan->source.session = session; assert(xprt); if (svc_get_xprt_type(xprt) == XPRT_RDMA) { LogWarn(COMPONENT_NFS_CB, "refusing to create back channel over RDMA for now"); code = EINVAL; goto out; } /* connect an RPC client * Use version 1 per errata ID 2291 for RFC 5661 */ chan->clnt = clnt_vc_ncreate_svc(xprt, session->cb_program, NFS_CB /* Errata ID: 2291 */, CLNT_CREATE_FLAG_NONE); if (CLNT_FAILURE(chan->clnt)) { err = rpc_sperror(&chan->clnt->cl_error, "failed"); LogDebug(COMPONENT_NFS_CB, "%s", err); gsh_free(err); CLNT_DESTROY(chan->clnt); chan->clnt = NULL; code = EINVAL; goto out; } for (i = 0; i < num_sec_parms; ++i) { if (sec_parms[i].cb_secflavor == AUTH_NONE) { chan->auth = authnone_ncreate(); authed = true; break; } else if (sec_parms[i].cb_secflavor == AUTH_SYS) { struct authunix_parms *sys_parms = &sec_parms[i] .callback_sec_parms4_u.cbsp_sys_cred; chan->auth = authunix_ncreate(sys_parms->aup_machname, sys_parms->aup_uid, sys_parms->aup_gid, sys_parms->aup_len, sys_parms->aup_gids); if (AUTH_SUCCESS(chan->auth)) { authed = true; break; } } else if (sec_parms[i].cb_secflavor == RPCSEC_GSS) { /** * @todo ACE: Come back later and implement * GSS. */ continue; } else { LogMajor(COMPONENT_NFS_CB, "Client sent unknown auth type."); continue; } err = rpc_sperror(&chan->auth->ah_error, "failed"); LogDebug(COMPONENT_NFS_CB, "%s", err); gsh_free(err); AUTH_DESTROY(chan->auth); chan->auth = NULL; } if (!authed) { code = EPERM; LogMajor(COMPONENT_NFS_CB, "No working auth in sec_params."); goto out; } atomic_set_uint32_t_bits(&session->flags, session_bc_up); out: if (code != 0) { LogWarn(COMPONENT_NFS_CB, "can not create back channel, code %d", code); if (chan->clnt) nfs_rpc_destroy_chan_no_lock(chan); } PTHREAD_MUTEX_unlock(&chan->chan_mtx); return code; } /** * @brief Get a backchannel for a clientid * * This function works for both NFSv4.0 and NFSv4.1. For NFSv4.0, if * the channel isn't up, it tries to create it. * * @param[in,out] clientid The clientid to use * @param[out] flags Unused * * @return The back channel or NULL if none existed or could be * established. */ rpc_call_channel_t *nfs_rpc_get_chan(nfs_client_id_t *clientid, uint32_t flags) { rpc_call_channel_t *chan; struct glist_head *glist; nfs41_session_t *session; if (clientid->cid_minorversion == 0) { chan = &clientid->cid_cb.v40.cb_chan; if (!chan->clnt) { if (nfs_rpc_create_chan_v40(clientid, flags)) { chan = NULL; } } return chan; } /* Get the first working back channel we have */ chan = NULL; pthread_mutex_lock(&clientid->cid_mutex); glist_for_each(glist, &clientid->cid_cb.v41.cb_session_list) { session = glist_entry(glist, nfs41_session_t, session_link); if (atomic_fetch_uint32_t(&session->flags) & session_bc_up) { chan = &session->cb_chan; break; } } pthread_mutex_unlock(&clientid->cid_mutex); return chan; } /** * @brief Dispose of a channel * * @param[in] chan The channel to dispose of */ void nfs_rpc_destroy_chan(rpc_call_channel_t *chan) { assert(chan); PTHREAD_MUTEX_lock(&chan->chan_mtx); nfs_rpc_destroy_chan_no_lock(chan); PTHREAD_MUTEX_unlock(&chan->chan_mtx); } /** * @brief Free callback arguments * * @param[in] op The argop to free */ static inline void free_argop(nfs_cb_argop4 *op) { gsh_free(op); } /** * @brief Free callback result * * @param[in] op The resop to free */ static inline void free_resop(nfs_cb_resop4 *op) { gsh_free(op); } /** * @brief Allocate an RPC call * * @return The newly allocated call or NULL. */ struct _rpc_call *alloc_rpc_call(void) { struct _rpc_call *call = gsh_calloc(1, sizeof(struct _rpc_call)); (void)atomic_inc_uint64_t(&nfs_health_.enqueued_reqs); return call; } /** * @brief Free an RPC call * * @param[in] call The call to free */ void free_rpc_call(rpc_call_t *call) { free_argop(call->cbt.v_u.v4.args.argarray.argarray_val); free_resop(call->cbt.v_u.v4.res.resarray.resarray_val); clnt_req_release(&call->call_req); } /** * @brief Free the RPC call context * * @param[in] cc The call context to free */ static void nfs_rpc_call_free(struct clnt_req *cc, size_t unused) { rpc_call_t *call = container_of(cc, struct _rpc_call, call_req); gsh_free(call); (void)atomic_inc_uint64_t(&nfs_health_.dequeued_reqs); } /** * @brief Call response processing * * @param[in] cc The RPC call request context */ static void nfs_rpc_call_process(struct clnt_req *cc) { rpc_call_t *call = container_of(cc, rpc_call_t, call_req); /* always TCP for retries, cc_refreshes only for AUTH_REFRESH() */ if (cc->cc_error.re_status == RPC_AUTHERROR && cc->cc_refreshes-- > 0 && AUTH_REFRESH(cc->cc_auth, NULL)) { if (clnt_req_refresh(cc) == RPC_SUCCESS) { cc->cc_error.re_status = CLNT_CALL_BACK(cc); return; } } call->states |= NFS_CB_CALL_FINISHED; if (call->call_hook) call->call_hook(call); LogDebug(COMPONENT_NFS_CB, "(rpc_call_t *)call = %p", call); free_rpc_call(call); } /** * @brief Dispatch a call * * @param[in,out] call The call to dispatch * @param[in] flags The flags governing call * * @return enum clnt_stat. */ enum clnt_stat nfs_rpc_call(rpc_call_t *call, uint32_t flags) { struct clnt_req *cc = &call->call_req; rpc_call_channel_t *chan = call->chan; enum clnt_stat re_status; call->states = NFS_CB_CALL_DISPATCH; /* XXX TI-RPC does the signal masking */ PTHREAD_MUTEX_lock(&chan->chan_mtx); clnt_req_fill(cc, call->chan->clnt, call->chan->auth, CB_COMPOUND, (xdrproc_t)xdr_CB_COMPOUND4args, &call->cbt.v_u.v4.args, (xdrproc_t)xdr_CB_COMPOUND4res, &call->cbt.v_u.v4.res); cc->cc_size = sizeof(nfs_request_t); cc->cc_free_cb = nfs_rpc_call_free; if (!call->chan->clnt) { cc->cc_error.re_status = RPC_INTR; re_status = RPC_INTR; goto unlock; } re_status = clnt_req_setup(cc, tout); if (re_status == RPC_SUCCESS) { cc->cc_process_cb = nfs_rpc_call_process; re_status = CLNT_CALL_BACK(cc); } /* If a call fails, we have to assume path down, or equally fatal * error. We may need back-off. */ if (re_status != RPC_SUCCESS) { cc->cc_error.re_status = re_status; nfs_rpc_destroy_chan_no_lock(call->chan); call->states |= NFS_CB_CALL_ABORTED; } unlock: LogDebug(COMPONENT_NFS_CB, "(rpc_call_t *)call = %p", call); PTHREAD_MUTEX_unlock(&chan->chan_mtx); /* any broadcast or signalling done in completion function */ return re_status; } /** * @brief Abort a call * * @param[in] call The call to abort * * @todo function doesn't seem to do anything. * * @return But it does it successfully. */ int32_t nfs_rpc_abort_call(rpc_call_t *call) { return 0; } /** * @brief Construct a CB_COMPOUND for v41 * * This function constructs a compound with a CB_SEQUENCE and one * other operation. * * @param[in] session The session on whose back channel we make the call * @param[in] op The operation to add * @param[in] refer Referral data, NULL if none * @param[in] slot Slot number to use * * @return The constructed call or NULL. */ static rpc_call_t *construct_v41(nfs41_session_t *session, nfs_cb_argop4 *op, struct state_refer *refer, slotid4 slot, slotid4 highest_slot) { rpc_call_t *call = alloc_rpc_call(); nfs_cb_argop4 sequenceop; CB_SEQUENCE4args *sequence = &sequenceop.nfs_cb_argop4_u.opcbsequence; const uint32_t minor = session->clientid_record->cid_minorversion; call->chan = &session->cb_chan; cb_compound_init_v4(&call->cbt, 2, minor, 0, NULL, 0); memset(sequence, 0, sizeof(CB_SEQUENCE4args)); sequenceop.argop = NFS4_OP_CB_SEQUENCE; memcpy(sequence->csa_sessionid, session->session_id, NFS4_SESSIONID_SIZE); sequence->csa_sequenceid = session->bc_slots[slot].sequence; sequence->csa_slotid = slot; sequence->csa_highest_slotid = highest_slot; sequence->csa_cachethis = false; if (refer) { referring_call_list4 *list; referring_call4 *ref_call = NULL; list = gsh_calloc(1, sizeof(referring_call_list4)); ref_call = gsh_malloc(sizeof(referring_call4)); sequence->csa_referring_call_lists.csarcl_len = 1; sequence->csa_referring_call_lists.csarcl_val = list; memcpy(list->rcl_sessionid, refer->session, sizeof(NFS4_SESSIONID_SIZE)); list->rcl_referring_calls.rcl_referring_calls_len = 1; list->rcl_referring_calls.rcl_referring_calls_val = ref_call; ref_call->rc_sequenceid = refer->sequence; ref_call->rc_slotid = refer->slot; } else { sequence->csa_referring_call_lists.csarcl_len = 0; sequence->csa_referring_call_lists.csarcl_val = NULL; } cb_compound_add_op(&call->cbt, &sequenceop); cb_compound_add_op(&call->cbt, op); return call; } /** * @brief Free a CB sequence for v41 * * @param[in] call The call to free */ static void release_v41(rpc_call_t *call) { nfs_cb_argop4 *argarray_val = call->cbt.v_u.v4.args.argarray.argarray_val; CB_SEQUENCE4args *sequence = &argarray_val[0].nfs_cb_argop4_u.opcbsequence; referring_call_list4 *call_lists = sequence->csa_referring_call_lists.csarcl_val; if (call_lists == NULL) return; gsh_free(call_lists->rcl_referring_calls.rcl_referring_calls_val); gsh_free(call_lists); } /** * @brief Find a callback slot * * Find and reserve a slot, if we can. If @c wait is set to true, we * wait on the condition variable for a limited time. * * @param[in,out] session Session on which to operate * @param[in] wait Whether to wait on the condition variable if * no slot can be found * @param[out] slot Slot to use * @param[out] highest_slot Highest slot in use * * @retval false if a slot was not found. * @retval true if a slot was found. */ static bool find_cb_slot(nfs41_session_t *session, bool wait, slotid4 *slot, slotid4 *highest_slot) { slotid4 cur = 0; bool found = false; PTHREAD_MUTEX_lock(&session->cb_mutex); retry: for (cur = 0; cur < MIN(session->back_channel_attrs.ca_maxrequests, session->nb_slots); ++cur) { if (!(session->bc_slots[cur].in_use) && (!found)) { found = true; *slot = cur; *highest_slot = cur; } if (session->bc_slots[cur].in_use) *highest_slot = cur; } if (!found && wait) { struct timespec ts; bool woke = false; clock_gettime(CLOCK_REALTIME, &ts); timespec_addms(&ts, 100); woke = (pthread_cond_timedwait(&session->cb_cond, &session->cb_mutex, &ts) != ETIMEDOUT); if (woke) { wait = false; goto retry; } } if (found) { session->bc_slots[*slot].in_use = true; ++session->bc_slots[*slot].sequence; assert(*slot < session->back_channel_attrs.ca_maxrequests); } PTHREAD_MUTEX_unlock(&session->cb_mutex); return found; } /** * @brief Release a reserved callback slot and wake waiters * * @param[in,out] session The session holding slot to release * @param[in] slot Slot to release * @param[in] bool Whether the operation was ever sent */ static void release_cb_slot(nfs41_session_t *session, slotid4 slot, bool sent) { PTHREAD_MUTEX_lock(&session->cb_mutex); session->bc_slots[slot].in_use = false; if (!sent) --session->bc_slots[slot].sequence; pthread_cond_broadcast(&session->cb_cond); PTHREAD_MUTEX_unlock(&session->cb_mutex); } static int nfs_rpc_v41_single(nfs_client_id_t *clientid, nfs_cb_argop4 *op, struct state_refer *refer, void (*completion)(rpc_call_t *), void *completion_arg) { struct glist_head *glist; int ret = ENOTCONN; bool wait = false; if (!completion) { LogFatal( COMPONENT_NFS_CB, "completion function must be set or else nfs41_release_single can't be called from cb which will cause a leak"); } restart: pthread_mutex_lock(&clientid->cid_mutex); glist_for_each(glist, &clientid->cid_cb.v41.cb_session_list) { nfs41_session_t *scur, *session; slotid4 slot = 0; slotid4 highest_slot = 0; rpc_call_t *call = NULL; scur = glist_entry(glist, nfs41_session_t, session_link); /* * This is part of the infinite loop avoidance. When we * attempt to use a session and that fails, we clear the * session_bc_up flag. Then, we can avoid that session until * the backchannel has been reestablished. */ if (!(atomic_fetch_uint32_t(&scur->flags) & session_bc_up)) { LogDebug(COMPONENT_NFS_CB, "bc is down"); continue; } /* * We get a slot before we try to get a reference to the * session, which is odd, but necessary, as we can't hold * the cid_mutex when we go to put the session reference. */ if (!(find_cb_slot(scur, wait, &slot, &highest_slot))) { LogDebug(COMPONENT_NFS_CB, "can't get slot"); continue; } /* * Get a reference to the session. * * @todo: We don't really need to do the hashtable lookup * here since we have a pointer, but it's currently the only * safe way to get a reference. */ if (!nfs41_Session_Get_Pointer(scur->session_id, &session)) { release_cb_slot(scur, slot, false); continue; } assert(session == scur); /* Drop mutex since we have a session ref */ pthread_mutex_unlock(&clientid->cid_mutex); call = construct_v41(session, op, refer, slot, highest_slot); call->call_hook = completion; call->call_arg = completion_arg; ret = nfs_rpc_call(call, NFS_RPC_CALL_NONE); if (ret == 0) return 0; /* * Tear down channel since there is likely something * wrong with it. */ LogDebug(COMPONENT_NFS_CB, "nfs_rpc_call failed: %d", ret); atomic_clear_uint32_t_bits(&session->flags, session_bc_up); release_v41(call); free_rpc_call(call); release_cb_slot(session, slot, false); dec_session_ref(session); goto restart; } pthread_mutex_unlock(&clientid->cid_mutex); /* If it didn't work, then try again and wait on a slot */ if (ret && !wait) { wait = true; goto restart; } return ret; } /** * @brief Free information associated with any 'single' call */ void nfs41_release_single(rpc_call_t *call) { release_cb_slot(call->chan->source.session, call->cbt.v_u.v4.args.argarray.argarray_val[0] .nfs_cb_argop4_u.opcbsequence.csa_slotid, true); dec_session_ref(call->chan->source.session); release_v41(call); } /** * @brief test the state of callback channel for a clientid using NULL. * @return enum clnt_stat */ enum clnt_stat nfs_test_cb_chan(nfs_client_id_t *clientid) { rpc_call_channel_t *chan; enum clnt_stat stat; int retries = 1; /* create (fix?) channel */ do { chan = nfs_rpc_get_chan(clientid, NFS_RPC_FLAG_NONE); if (!chan) { LogCrit(COMPONENT_NFS_CB, "nfs_rpc_get_chan failed"); return RPC_SYSTEMERROR; } if (!chan->clnt) { LogCrit(COMPONENT_NFS_CB, "nfs_rpc_get_chan failed (no clnt)"); return RPC_SYSTEMERROR; } if (!chan->auth) { LogCrit(COMPONENT_NFS_CB, "nfs_rpc_get_chan failed (no auth)"); return RPC_SYSTEMERROR; } /* try the CB_NULL proc -- inline here, should be ok-ish */ stat = rpc_cb_null(chan, false); LogDebug(COMPONENT_NFS_CB, "rpc_cb_null on client %p returns %d", clientid, stat); /* RPC_INTR indicates that we should refresh the * channel and retry */ } while (stat == RPC_INTR && retries-- > 0); return stat; } static int nfs_rpc_v40_single(nfs_client_id_t *clientid, nfs_cb_argop4 *op, void (*completion)(rpc_call_t *), void *completion_arg) { rpc_call_channel_t *chan; rpc_call_t *call; int rc; /* Attempt a recall only if channel state is UP */ if (get_cb_chan_down(clientid)) { LogCrit(COMPONENT_NFS_CB, "Call back channel down, not issuing a recall"); return ENOTCONN; } chan = nfs_rpc_get_chan(clientid, NFS_RPC_FLAG_NONE); if (!chan) { LogCrit(COMPONENT_NFS_CB, "nfs_rpc_get_chan failed"); /* TODO: move this to nfs_rpc_get_chan ? */ set_cb_chan_down(clientid, true); return ENOTCONN; } if (!chan->clnt) { LogCrit(COMPONENT_NFS_CB, "nfs_rpc_get_chan failed (no clnt)"); set_cb_chan_down(clientid, true); return ENOTCONN; } if (!chan->auth) { LogCrit(COMPONENT_NFS_CB, "nfs_rpc_get_chan failed (no auth)"); set_cb_chan_down(clientid, true); return ENOTCONN; } call = alloc_rpc_call(); call->chan = chan; cb_compound_init_v4(&call->cbt, 1, 0, clientid->cid_cb.v40.cb_callback_ident, NULL, 0); cb_compound_add_op(&call->cbt, op); call->call_hook = completion; call->call_arg = completion_arg; rc = nfs_rpc_call(call, NFS_RPC_CALL_NONE); if (rc) free_rpc_call(call); return rc; } /** * @brief Send CB_COMPOUND with a single operation * * In the case of v4.1+, this actually sends two operations, a CB_SEQUENCE * and the supplied operation. It works as a convenience function to handle * the details of callback management, finding a connection with a working * back channel, and so forth. * * @note This should work for most practical purposes, but is not * ideal. What we ought to have is a per-clientid queue that * operations can be submitted to that will be sent when a * back-channel is re-established, with a per-session queue for * operations that were sent but had the back-channel fail before the * response was received. * * @param[in] clientid Client record * @param[in] op The operation to perform * @param[in] refer Referral tracking info (or NULL) * @param[in] completion Completion function for this operation * @param[in] c_arg Argument provided to completion hook * * @return POSIX error codes. */ int nfs_rpc_cb_single(nfs_client_id_t *clientid, nfs_cb_argop4 *op, struct state_refer *refer, void (*completion)(rpc_call_t *), void *c_arg) { if (clientid->cid_minorversion == 0) return nfs_rpc_v40_single(clientid, op, completion, c_arg); return nfs_rpc_v41_single(clientid, op, refer, completion, c_arg); } nfs-ganesha-6.5/src/MainNFSD/nfs_rpc_callback_simulator.c000066400000000000000000000255241473756622300234440ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2010, The Linux Box Corporation * Copyright (c) 2010-2017 Red Hat, Inc. and/or its affiliates. * Contributor : Matt Benjamin * * Some portions Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #include "config.h" #include #include #include #include #include #include #include "gsh_list.h" #include "abstract_mem.h" #include "fsal.h" #include "nfs_core.h" #include "log.h" #include "nfs_rpc_callback.h" #include "nfs_rpc_callback_simulator.h" #include "sal_functions.h" #include "gsh_dbus.h" /** * @file nfs_rpc_callback_simulator.c * @author Matt Benjamin * @author Lee Dobryden * @brief RPC callback dispatch package * * This module implements a stochastic dispatcher for callbacks, which * works by traversing the list of connected clients and, dispatching * a callback at random in consideration of state. * * This concept is inspired by the upcall simulator, though * necessarily less fully satisfactory until delegation and layout * state are available. */ /** * @brief Return a timestamped list of NFSv4 client ids. * * For all NFSv4 clients, a clientid reliably indicates a callback * channel * * @param args (not used) * @param reply the message reply */ static bool nfs_rpc_cbsim_get_v40_client_ids(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { hash_table_t *ht = ht_confirmed_client_id; struct rbt_head *head_rbt; struct hash_data *pdata = NULL; struct rbt_node *pn; nfs_client_id_t *pclientid; uint64_t clientid; DBusMessageIter iter, sub_iter; struct timespec ts; uint32_t i; /* create a reply from the message */ now(&ts); dbus_message_iter_init_append(reply, &iter); gsh_dbus_append_timestamp(&iter, &ts); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_UINT64_AS_STRING, &sub_iter); /* For each bucket of the hashtable */ for (i = 0; i < ht->parameter.index_size; i++) { head_rbt = &(ht->partitions[i].rbt); /* acquire mutex */ PTHREAD_RWLOCK_wrlock(&(ht->partitions[i].ht_lock)); /* go through all entries in the red-black-tree */ RBT_LOOP(head_rbt, pn) { pdata = RBT_OPAQ(pn); pclientid = pdata->val.addr; clientid = pclientid->cid_clientid; dbus_message_iter_append_basic( &sub_iter, DBUS_TYPE_UINT64, &clientid); RBT_INCREMENT(pn); } PTHREAD_RWLOCK_unlock(&(ht->partitions[i].ht_lock)); } dbus_message_iter_close_container(&iter, &sub_iter); return true; } /* DBUS get_client_ids method descriptor */ static struct gsh_dbus_method cbsim_get_client_ids = { .name = "get_client_ids", .method = nfs_rpc_cbsim_get_v40_client_ids, .args = { { .name = "time", .type = "(tt)", .direction = "out" }, { .name = "clientids", .type = "at", .direction = "out" }, { NULL, NULL, NULL } } }; /** * @brief Return a timestamped list of session ids. * * @param args (not used) * @param reply the message reply */ static bool nfs_rpc_cbsim_get_session_ids(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { uint32_t i; hash_table_t *ht = ht_session_id; struct rbt_head *head_rbt; struct hash_data *pdata = NULL; struct rbt_node *pn; /* guaranteed to fit */ char *session_id = alloca(2 * NFS4_SESSIONID_SIZE); nfs41_session_t *session_data; DBusMessageIter iter, sub_iter; struct timespec ts; /* create a reply from the message */ now(&ts); dbus_message_iter_init_append(reply, &iter); gsh_dbus_append_timestamp(&iter, &ts); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_UINT64_AS_STRING, &sub_iter); /* For each bucket of the hashtable */ for (i = 0; i < ht->parameter.index_size; i++) { head_rbt = &(ht->partitions[i].rbt); /* acquire mutex */ PTHREAD_RWLOCK_wrlock(&(ht->partitions[i].ht_lock)); /* go through all entries in the red-black-tree */ RBT_LOOP(head_rbt, pn) { pdata = RBT_OPAQ(pn); session_data = pdata->val.addr; /* format */ b64_ntop((unsigned char *)session_data->session_id, NFS4_SESSIONID_SIZE, session_id, (2 * NFS4_SESSIONID_SIZE)); dbus_message_iter_append_basic( &sub_iter, DBUS_TYPE_STRING, &session_id); RBT_INCREMENT(pn); } PTHREAD_RWLOCK_unlock(&(ht->partitions[i].ht_lock)); } dbus_message_iter_close_container(&iter, &sub_iter); return true; } /* DBUS get_session_ids method descriptor */ static struct gsh_dbus_method cbsim_get_session_ids = { .name = "get_session_ids", .method = nfs_rpc_cbsim_get_session_ids, .args = { { .name = "time", .type = "(tt)", .direction = "out" }, { .name = "sessionids", .type = "at", .direction = "out" }, { NULL, NULL, NULL } } }; static int cbsim_test_bchan(clientid4 clientid) { nfs_client_id_t *pclientid = NULL; int code; code = nfs_client_id_get_confirmed(clientid, &pclientid); if (code != CLIENT_ID_SUCCESS) { LogCrit(COMPONENT_NFS_CB, "No clid record for %" PRIx64 " (%d) code %d", clientid, (int32_t)clientid, code); return EINVAL; } nfs_test_cb_chan(pclientid); return code; } /** * Demonstration callback invocation. */ static void cbsim_free_compound(nfs4_compound_t *cbt) __attribute__((unused)); static void cbsim_free_compound(nfs4_compound_t *cbt) { int ix; nfs_cb_argop4 *argop = NULL; for (ix = 0; ix < cbt->v_u.v4.args.argarray.argarray_len; ++ix) { argop = cbt->v_u.v4.args.argarray.argarray_val + ix; if (argop) { CB_RECALL4args *opcbrecall = &argop->nfs_cb_argop4_u.opcbrecall; switch (argop->argop) { case NFS4_OP_CB_RECALL: gsh_free(opcbrecall->fh.nfs_fh4_val); break; default: /* TODO: ahem */ break; } } } /* XXX general free (move ?) */ cb_compound_free(cbt); } static void cbsim_completion_func(rpc_call_t *call) { LogDebug(COMPONENT_NFS_CB, "%p %s", call, !(call->states & NFS_CB_CALL_ABORTED) ? "Success" : "Failed"); if (!(call->states & NFS_CB_CALL_ABORTED)) { /* potentially, do something more interesting here */ LogMidDebug(COMPONENT_NFS_CB, "call result: %d", call->call_req.cc_error.re_status); } else { LogDebug(COMPONENT_NFS_CB, "Aborted: %d", call->call_req.cc_error.re_status); } } static int cbsim_fake_cbrecall(clientid4 clientid) { nfs_client_id_t *pclientid = NULL; rpc_call_channel_t *chan = NULL; nfs_cb_argop4 argop[1]; rpc_call_t *call; int code; LogDebug(COMPONENT_NFS_CB, "called with clientid %" PRIx64, clientid); code = nfs_client_id_get_confirmed(clientid, &pclientid); if (code != CLIENT_ID_SUCCESS) { LogCrit(COMPONENT_NFS_CB, "No clid record for %" PRIx64 " (%d) code %d", clientid, (int32_t)clientid, code); code = EINVAL; goto out; } assert(pclientid); chan = nfs_rpc_get_chan(pclientid, NFS_RPC_FLAG_NONE); if (!chan) { LogCrit(COMPONENT_NFS_CB, "nfs_rpc_get_chan failed"); goto out; } if (!chan->clnt) { LogCrit(COMPONENT_NFS_CB, "nfs_rpc_get_chan failed (no clnt)"); goto out; } if (!chan->auth) { LogCrit(COMPONENT_NFS_CB, "nfs_rpc_get_chan failed (no auth)"); goto out; } /* allocate a new call--freed in completion hook */ call = alloc_rpc_call(); call->chan = chan; /* setup a compound */ cb_compound_init_v4(&call->cbt, 6, 0, pclientid->cid_cb.v40.cb_callback_ident, "brrring!!!", 10); /* TODO: api-ify */ memset(argop, 0, sizeof(nfs_cb_argop4)); argop->argop = NFS4_OP_CB_RECALL; argop->nfs_cb_argop4_u.opcbrecall.stateid.seqid = 0xdeadbeef; memcpy(argop->nfs_cb_argop4_u.opcbrecall.stateid.other, "\0xde\0xad\0xbe\0xef\0xde\0xad\0xbe\0xef\0xde\0xad\0xbe\0xef", sizeof(argop->nfs_cb_argop4_u.opcbrecall.stateid.other)); argop->nfs_cb_argop4_u.opcbrecall.truncate = TRUE; argop->nfs_cb_argop4_u.opcbrecall.fh.nfs_fh4_len = 11; /* leaks, sorry */ argop->nfs_cb_argop4_u.opcbrecall.fh.nfs_fh4_val = gsh_strdup("0xabadcafe"); /* add ops, till finished (dont exceed count) */ cb_compound_add_op(&call->cbt, argop); /* set completion hook */ call->call_hook = cbsim_completion_func; call->call_arg = NULL; /* call it (here, in current thread context) */ code = nfs_rpc_call(call, NFS_RPC_CALL_NONE); if (code) free_rpc_call(call); out: return code; } /** * @brief Fake/force a recall of a client id. * * For all NFSv4 clients, a clientid reliably indicates a callback * channel * * @param args the client id to be recalled * @param reply the message reply (empty) */ static bool nfs_rpc_cbsim_fake_recall(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { clientid4 clientid = 9315; /* XXX ew! */ LogDebug(COMPONENT_NFS_CB, "called!"); /* read the arguments */ if (args == NULL) { LogDebug(COMPONENT_DBUS, "message has no arguments"); } else if (dbus_message_iter_get_arg_type(args) != DBUS_TYPE_UINT64) { LogDebug(COMPONENT_DBUS, "arg not uint64"); } else { dbus_message_iter_get_basic(args, &clientid); LogDebug(COMPONENT_DBUS, "param: %" PRIx64, clientid); } cbsim_test_bchan(clientid); cbsim_fake_cbrecall(clientid); return true; } /* DBUS fake_recall method descriptor */ static struct gsh_dbus_method cbsim_fake_recall = { .name = "fake_recall", .method = nfs_rpc_cbsim_fake_recall, .args = { { .name = "clientid", .type = "t", .direction = "in" }, { NULL, NULL, NULL } } }; /* DBUS org.ganesha.nfsd.cbsim methods list */ static struct gsh_dbus_method *cbsim_methods[] = { &cbsim_get_client_ids, &cbsim_get_session_ids, &cbsim_fake_recall, NULL }; static struct gsh_dbus_interface cbsim_interface = { .name = "org.ganesha.nfsd.cbsim", .props = NULL, .methods = cbsim_methods, .signals = NULL }; /* DBUS list of interfaces on /org/ganesha/nfsd/CBSIM */ static struct gsh_dbus_interface *cbsim_interfaces[] = { &cbsim_interface, NULL }; /** * @brief Initialize subsystem */ void nfs_rpc_cbsim_pkginit(void) { gsh_dbus_register_path("CBSIM", cbsim_interfaces); LogEvent(COMPONENT_NFS_CB, "Callback Simulator Initialized"); } /** * @brief Shutdown subsystem */ void nfs_rpc_cbsim_pkgshutdown(void) { /* return */ } nfs-ganesha-6.5/src/MainNFSD/nfs_rpc_dispatcher_thread.c000066400000000000000000001241001473756622300232540ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * William Allen Simpson * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_rpc_dispatcher_thread.c * @brief Contains the @c rpc_dispatcher_thread routine and support code */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include #include #ifdef RPC_VSOCK #include #include #include #endif #include #include #include #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "abstract_atomic.h" #include "nfs23.h" #include "nfs4.h" #include "mount.h" #include "nlm4.h" #include "rquota.h" #include "nfsacl.h" #include "nfs_init.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_dupreq.h" #include "nfs_file_handle.h" #include "xprt_handler.h" #include "connection_manager.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs_rpc.h" #endif #include "gsh_xprt_tracepoint.h" #ifdef __APPLE__ #include #include #define TCP_KEEPIDLE TCPCTL_KEEPIDLE #endif #ifdef USE_MONITORING #include "monitoring.h" #include "nfs_metrics.h" #endif /* USE_MONITORING */ #define NFS_options nfs_param.core_param.core_options #define NFS_program nfs_param.core_param.program /** * TI-RPC event channels. Each channel is a thread servicing an event * demultiplexer. */ struct rpc_evchan { uint32_t chan_id; /*< Channel ID */ }; #define N_TCP_EVENT_CHAN \ 3 /*< We don't really want to have too many, relative to the number of available cores. */ #define N_EVENT_CHAN (N_TCP_EVENT_CHAN + EVCHAN_SIZE) static struct rpc_evchan rpc_evchan[EVCHAN_SIZE]; static enum xprt_stat nfs_rpc_tcp_user_data(SVCXPRT *); static void nfs_rpc_unref_user_data(SVCXPRT *); static void nfs_rpc_alloc_user_data(SVCXPRT *); static enum xprt_stat nfs_rpc_free_user_data(SVCXPRT *); static struct svc_req *alloc_nfs_request(SVCXPRT *xprt, XDR *xdrs); static void free_nfs_request(struct svc_req *req, enum xprt_stat stat); const char *xprt_stat_s[XPRT_DESTROYED + 1] = { "XPRT_IDLE", "XPRT_DISPATCH", "XPRT_DIED", "XPRT_DESTROYED" }; /** * @brief Function never called, but the symbol is needed for svc_register. * * @param[in] ptr_req Unused * @param[in] ptr_svc Unused */ void nfs_rpc_dispatch_dummy(struct svc_req *req) { LogMajor(COMPONENT_DISPATCH, "Possible error, function %s should never be called", __func__); } const char *tags[P_COUNT] = { "NFS", #ifdef _USE_NFS3 "MNT", #endif #ifdef _USE_NLM "NLM", #ifdef _USE_RQUOTA "RQUOTA", #endif #endif #ifdef USE_NFSACL3 "NFSACL", #endif #ifdef RPC_VSOCK "NFS_VSOCK", #endif #ifdef _USE_NFS_RDMA "NFS_RDMA", #endif }; typedef struct proto_data { struct sockaddr_in sinaddr_udp; struct sockaddr_in sinaddr_tcp; struct sockaddr_in6 sinaddr_udp6; struct sockaddr_in6 sinaddr_tcp6; struct netbuf netbuf_udp6; struct netbuf netbuf_tcp6; struct t_bind bindaddr_udp6; struct t_bind bindaddr_tcp6; struct __rpc_sockinfo si_udp6; struct __rpc_sockinfo si_tcp6; } proto_data; proto_data pdata[P_COUNT]; struct netconfig *netconfig_udpv4; struct netconfig *netconfig_tcpv4; struct netconfig *netconfig_udpv6; struct netconfig *netconfig_tcpv6; /* RPC Service Sockets and Transports */ int udp_socket[P_COUNT]; int tcp_socket[P_COUNT]; SVCXPRT *udp_xprt[P_COUNT]; SVCXPRT *tcp_xprt[P_COUNT]; /* Flag to indicate if V6 interfaces on the host are enabled */ bool v6disabled; bool vsock; bool rdma; /** * @brief Unregister an RPC program. * * @param[in] prog Program to unregister * @param[in] vers1 Lowest version * @param[in] vers2 Highest version */ static void unregister(const rpcprog_t prog, const rpcvers_t vers1, const rpcvers_t vers2) { rpcvers_t vers; for (vers = vers1; vers <= vers2; vers++) { rpcb_unset(prog, vers, netconfig_udpv4); rpcb_unset(prog, vers, netconfig_tcpv4); if (!v6disabled && netconfig_udpv6) rpcb_unset(prog, vers, netconfig_udpv6); if (!v6disabled && netconfig_tcpv6) rpcb_unset(prog, vers, netconfig_tcpv6); } } static void unregister_rpc(void) { if ((NFS_options & CORE_OPTION_NFSV3) != 0) { #ifdef _USE_NFS3 unregister(NFS_program[P_NFS], NFS_V3, NFS_V4); unregister(NFS_program[P_MNT], MOUNT_V1, MOUNT_V3); #endif } else { unregister(NFS_program[P_NFS], NFS_V4, NFS_V4); } #ifdef _USE_NLM if (nfs_param.core_param.enable_NLM) unregister(NFS_program[P_NLM], 1, NLM4_VERS); #endif /* _USE_NLM */ #ifdef _USE_RQUOTA if (nfs_param.core_param.enable_RQUOTA) { unregister(NFS_program[P_RQUOTA], RQUOTAVERS, EXT_RQUOTAVERS); } #endif #ifdef USE_NFSACL3 if (nfs_param.core_param.enable_NFSACL) { unregister(NFS_program[P_NFSACL], NFSACL_V3, NFSACL_V3); } #endif } static inline bool nfs_protocol_enabled(protos p) { #ifdef _USE_NFS3 bool nfsv3 = NFS_options & CORE_OPTION_NFSV3; #endif switch (p) { case P_NFS: return true; #ifdef _USE_NFS3 case P_MNT: /* valid only for NFSv3 environments */ if (nfsv3) return true; break; #endif #ifdef _USE_NLM case P_NLM: /* valid only for NFSv3 environments */ if (nfsv3 && nfs_param.core_param.enable_NLM) return true; break; #endif #ifdef _USE_RQUOTA case P_RQUOTA: if (nfs_param.core_param.enable_RQUOTA) return true; break; #endif #ifdef USE_NFSACL3 case P_NFSACL: /* valid only for NFSv3 environments */ if (nfsv3 && nfs_param.core_param.enable_NFSACL) { return true; } break; #endif default: break; } return false; } /** * @brief Close transports and file descriptors used for RPC services. * * So that restarting the NFS server won't encounter issues of "Address * Already In Use" - this has occurred even though we set the * SO_REUSEADDR option when restarting the server with a single export * (i.e.: a small config) & no logging at all, making the restart very * fast. when closing a listening socket it will be closed * immediately if no connection is pending on it, hence drastically * reducing the probability for trouble. */ static void close_rpc_fd(void) { protos p; for (p = P_NFS; p < P_COUNT; p++) { if (udp_socket[p] != -1) close(udp_socket[p]); if (udp_xprt[p]) SVC_DESTROY(udp_xprt[p]); if (tcp_socket[p] != -1) close(tcp_socket[p]); if (tcp_xprt[p]) SVC_DESTROY(tcp_xprt[p]); } /* no need for special tcp_xprt[P_NFS_VSOCK] treatment */ } /** * @brief Get evchannel id * * Given an evchannel, return it's channel id * */ uint32_t nfs_get_evchannel_id(enum evchan channel) { return rpc_evchan[channel].chan_id; } /** * @brief Dispatch after rendezvous * * Record activity on a rendezvous transport handle. * * @note * Cases are distinguished by separate callbacks for each fd. * UDP connections are bound to socket NFS_UDPSocket * TCP initial connections are bound to socket NFS_TCPSocket * all the other cases are requests from already connected TCP Clients */ static enum xprt_stat nfs_rpc_dispatch_udp_NFS(SVCXPRT *xprt) { LogFullDebug(COMPONENT_DISPATCH, "NFS UDP request for SVCXPRT %p fd %d", xprt, xprt->xp_fd); GSH_XPRT_UNIQUE_AUTO_TRACEPOINT(nfs_rpc, before_recv, TRACE_INFO, xprt, "nfs udp dispatch"); xprt->xp_dispatch.process_cb = nfs_rpc_valid_NFS; return SVC_RECV(xprt); } #ifdef _USE_NFS3 static enum xprt_stat nfs_rpc_dispatch_udp_MNT(SVCXPRT *xprt) { LogFullDebug(COMPONENT_DISPATCH, "MOUNT UDP request for SVCXPRT %p fd %d", xprt, xprt->xp_fd); GSH_XPRT_UNIQUE_AUTO_TRACEPOINT(nfs_rpc, before_recv, TRACE_INFO, xprt, "mnt udp dispatch"); xprt->xp_dispatch.process_cb = nfs_rpc_valid_MNT; return SVC_RECV(xprt); } #endif #ifdef _USE_NLM static enum xprt_stat nfs_rpc_dispatch_udp_NLM(SVCXPRT *xprt) { LogFullDebug(COMPONENT_DISPATCH, "NLM UDP request for SVCXPRT %p fd %d", xprt, xprt->xp_fd); GSH_XPRT_UNIQUE_AUTO_TRACEPOINT(nfs_rpc, before_recv, TRACE_INFO, xprt, "nlm udp dispatch"); xprt->xp_dispatch.process_cb = nfs_rpc_valid_NLM; return SVC_RECV(xprt); } #endif #ifdef _USE_RQUOTA static enum xprt_stat nfs_rpc_dispatch_udp_RQUOTA(SVCXPRT *xprt) { LogFullDebug(COMPONENT_DISPATCH, "RQUOTA UDP request for SVCXPRT %p fd %d", xprt, xprt->xp_fd); GSH_XPRT_UNIQUE_AUTO_TRACEPOINT(nfs_rpc, before_recv, TRACE_INFO, xprt, "rquota udp dispatch"); xprt->xp_dispatch.process_cb = nfs_rpc_valid_RQUOTA; return SVC_RECV(xprt); } #endif #ifdef USE_NFSACL3 static enum xprt_stat nfs_rpc_dispatch_udp_NFSACL(SVCXPRT *xprt) { LogFullDebug(COMPONENT_DISPATCH, "NFSACL UDP request for SVCXPRT %p fd %d", xprt, xprt->xp_fd); xprt->xp_dispatch.process_cb = nfs_rpc_valid_NFSACL; return SVC_RECV(xprt); } #endif const svc_xprt_fun_t udp_dispatch[] = { nfs_rpc_dispatch_udp_NFS, #ifdef _USE_NFS3 nfs_rpc_dispatch_udp_MNT, #endif #ifdef _USE_NLM nfs_rpc_dispatch_udp_NLM, #endif #ifdef _USE_RQUOTA nfs_rpc_dispatch_udp_RQUOTA, #endif #ifdef USE_NFSACL3 nfs_rpc_dispatch_udp_NFSACL, #endif #ifdef RPC_VSOCK NULL, #endif #ifdef _USE_NFS_RDMA NULL, #endif }; static enum xprt_stat nfs_rpc_dispatch_remote_addr_set_tcp(SVCXPRT *xprt) { char remote_addr_str[SOCK_NAME_MAX] = "\0"; struct display_buffer remote_addr_dbuf = { sizeof(remote_addr_str), remote_addr_str, remote_addr_str }; display_sockaddr(&remote_addr_dbuf, &xprt->xp_remote.ss); char proxy_addr_str[SOCK_NAME_MAX] = "\0"; struct display_buffer proxy_addr_dbuf_p = { sizeof(proxy_addr_str), proxy_addr_str, proxy_addr_str }; display_sockaddr(&proxy_addr_dbuf_p, &xprt->xp_proxy.ss); LogInfo(COMPONENT_DISPATCH, "fd %d: remote_address_is_set received for xprt %p. Remote address %s, HaProxy address %s", xprt->xp_fd, xprt, remote_addr_str, proxy_addr_str); if (connection_manager__connection_started(xprt) != CONNECTION_MANAGER__CONNECTION_STARTED__ALLOW) { LogWarn(COMPONENT_DISPATCH, "fd - %d: xprt %p failed on connection_started.", xprt->xp_fd, xprt); return XPRT_DIED; } return XPRT_IDLE; } static enum xprt_stat nfs_rpc_dispatch_tcp_NFS(SVCXPRT *xprt) { LogFullDebug(COMPONENT_DISPATCH, "NFS TCP dispatch setup for SVCXPRT %p fd %d", xprt, xprt->xp_fd); /* Allocate user-data for the xprt. * * Note: Currently, we only associate user-data related to NFS * operations. This allocation can be later generalized as an alloc * callback on the xprt, if non-NFS operations require user-data. */ nfs_rpc_alloc_user_data(xprt); /* Add callback to un-reference xprt user data */ (void)SVC_CONTROL(xprt, SVCSET_XP_UNREF_USER_DATA, nfs_rpc_unref_user_data); connection_manager__connection_init(xprt); xprt->xp_dispatch.process_cb = nfs_rpc_valid_NFS; xprt->xp_dispatch.remote_addr_set_cb = nfs_rpc_dispatch_remote_addr_set_tcp; return nfs_rpc_tcp_user_data(xprt); } #ifdef _USE_NFS3 static enum xprt_stat nfs_rpc_dispatch_tcp_MNT(SVCXPRT *xprt) { LogFullDebug(COMPONENT_DISPATCH, "MOUNT TCP request on SVCXPRT %p fd %d", xprt, xprt->xp_fd); xprt->xp_dispatch.process_cb = nfs_rpc_valid_MNT; return nfs_rpc_tcp_user_data(xprt); } #endif #ifdef _USE_NLM static enum xprt_stat nfs_rpc_dispatch_tcp_NLM(SVCXPRT *xprt) { LogFullDebug(COMPONENT_DISPATCH, "NLM TCP request on SVCXPRT %p fd %d", xprt, xprt->xp_fd); xprt->xp_dispatch.process_cb = nfs_rpc_valid_NLM; return nfs_rpc_tcp_user_data(xprt); } #endif #ifdef _USE_RQUOTA static enum xprt_stat nfs_rpc_dispatch_tcp_RQUOTA(SVCXPRT *xprt) { LogFullDebug(COMPONENT_DISPATCH, "RQUOTA TCP request on SVCXPRT %p fd %d", xprt, xprt->xp_fd); xprt->xp_dispatch.process_cb = nfs_rpc_valid_RQUOTA; return nfs_rpc_tcp_user_data(xprt); } #endif #ifdef USE_NFSACL3 static enum xprt_stat nfs_rpc_dispatch_tcp_NFSACL(SVCXPRT *xprt) { LogFullDebug(COMPONENT_DISPATCH, "NFSACL TCP request on SVCXPRT %p fd %d", xprt, xprt->xp_fd); xprt->xp_dispatch.process_cb = nfs_rpc_valid_NFSACL; return nfs_rpc_tcp_user_data(xprt); } #endif #ifdef RPC_VSOCK static enum xprt_stat nfs_rpc_dispatch_tcp_VSOCK(SVCXPRT *xprt) { LogFullDebug(COMPONENT_DISPATCH, "VSOCK TCP request on SVCXPRT %p fd %d", xprt, xprt->xp_fd); xprt->xp_dispatch.process_cb = nfs_rpc_valid_NFS; return nfs_rpc_tcp_user_data(xprt); } #endif #ifdef _USE_NFS_RDMA static enum xprt_stat nfs_rpc_dispatch_RDMA(SVCXPRT *xprt) { LogFullDebug(COMPONENT_DISPATCH, "RDMA request on SVCXPRT %p fd %d stat %d", xprt, xprt->xp_fd, SVC_STAT(xprt->xp_parent)); xprt->xp_dispatch.process_cb = nfs_rpc_valid_NFS_RDMA; return SVC_STAT(xprt->xp_parent); } #endif const svc_xprt_fun_t tcp_dispatch[P_COUNT] = { nfs_rpc_dispatch_tcp_NFS, #ifdef _USE_NFS3 nfs_rpc_dispatch_tcp_MNT, #endif #ifdef _USE_NLM nfs_rpc_dispatch_tcp_NLM, #endif #ifdef _USE_RQUOTA nfs_rpc_dispatch_tcp_RQUOTA, #endif #ifdef USE_NFSACL3 nfs_rpc_dispatch_tcp_NFSACL, #endif #ifdef RPC_VSOCK nfs_rpc_dispatch_tcp_VSOCK, #endif #ifdef _USE_NFS_RDMA nfs_rpc_dispatch_RDMA, #endif }; void Create_udp(protos prot) { if (udp_socket[prot] != -1) { udp_xprt[prot] = svc_dg_create( udp_socket[prot], nfs_param.core_param.rpc.max_send_buffer_size, nfs_param.core_param.rpc.max_recv_buffer_size); if (udp_xprt[prot] == NULL) LogFatal(COMPONENT_DISPATCH, "Cannot allocate %s/UDP SVCXPRT", tags[prot]); udp_xprt[prot]->xp_dispatch.rendezvous_cb = udp_dispatch[prot]; /* Hook xp_free_user_data (finalize/free private data) */ (void)SVC_CONTROL(udp_xprt[prot], SVCSET_XP_FREE_USER_DATA, nfs_rpc_free_user_data); (void)svc_rqst_evchan_reg(rpc_evchan[UDP_UREG_CHAN].chan_id, udp_xprt[prot], SVC_RQST_FLAG_XPRT_UREG); } } void Create_tcp(protos prot) { tcp_xprt[prot] = svc_vc_ncreatef( tcp_socket[prot], nfs_param.core_param.rpc.max_send_buffer_size, nfs_param.core_param.rpc.max_recv_buffer_size, SVC_CREATE_FLAG_CLOSE | SVC_CREATE_FLAG_LISTEN); if (tcp_xprt[prot] == NULL) LogFatal(COMPONENT_DISPATCH, "Cannot allocate %s/TCP SVCXPRT", tags[prot]); tcp_xprt[prot]->xp_dispatch.rendezvous_cb = tcp_dispatch[prot]; /* Hook xp_free_user_data (finalize/free private data) */ (void)SVC_CONTROL(tcp_xprt[prot], SVCSET_XP_FREE_USER_DATA, nfs_rpc_free_user_data); (void)svc_rqst_evchan_reg(rpc_evchan[TCP_UREG_CHAN].chan_id, tcp_xprt[prot], SVC_RQST_FLAG_XPRT_UREG); } #ifdef _USE_NFS_RDMA struct rpc_rdma_attr rpc_rdma_xa = { .statistics_prefix = NULL, .node = "::", .port = "20049", /* default port 20049 */ .sq_depth = 32, /* default was 50 */ .max_send_sge = 32, /* minimum 2 */ .rq_depth = 32, /* default was 50 */ .max_recv_sge = 31, /* minimum 1 */ .backlog = 10, /* minimum 2 */ .credits = 30, /* default 10 */ .destroy_on_disconnect = true, .use_srq = false, }; void Create_RDMA(protos prot) { /* Override the RDMA service port as per the port configured */ char str[20]; snprintf(str, 20, "%d", nfs_param.core_param.port[P_NFS_RDMA]); rpc_rdma_xa.port = strdup(str); rpc_rdma_xa.credits = nfs_param.core_param.rpc.rdma_credits; /* This has elements of both UDP and TCP setup */ tcp_xprt[prot] = svc_rdma_create( &rpc_rdma_xa, nfs_param.core_param.rpc.max_send_buffer_size, nfs_param.core_param.rpc.max_recv_buffer_size); if (tcp_xprt[prot] == NULL) LogFatal(COMPONENT_DISPATCH, "Cannot allocate RPC/%s SVCXPRT", tags[prot]); tcp_xprt[prot]->xp_dispatch.rendezvous_cb = tcp_dispatch[prot]; /* Hook xp_free_user_data (finalize/free private data) */ (void)SVC_CONTROL(tcp_xprt[prot], SVCSET_XP_FREE_USER_DATA, nfs_rpc_free_user_data); } #endif /** * @brief Create the SVCXPRT for each protocol in use */ void Create_SVCXPRTs(void) { protos p; LogFullDebug(COMPONENT_DISPATCH, "Allocation of the SVCXPRT"); for (p = P_NFS; p < P_COUNT; p++) if (nfs_protocol_enabled(p)) { Create_udp(p); Create_tcp(p); } #ifdef RPC_VSOCK if (vsock) Create_tcp(P_NFS_VSOCK); #endif /* RPC_VSOCK */ #ifdef _USE_NFS_RDMA if (rdma) Create_RDMA(P_NFS_RDMA); #endif /* _USE_NFS_RDMA */ } /** * @brief Bind the udp and tcp sockets for V6 Interfaces */ static int Bind_sockets_V6(void) { protos p; int rc = 0; if (isInfo(COMPONENT_DISPATCH)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dbuf = { sizeof(str), str, str }; display_sockaddr(&dbuf, &nfs_param.core_param.bind_addr); LogInfo(COMPONENT_DISPATCH, "Binding to address %s", str); } for (p = P_NFS; p < P_COUNT; p++) { if (nfs_protocol_enabled(p)) { proto_data *pdatap = &pdata[p]; if (udp_socket[p] != -1) { memset(&pdatap->sinaddr_udp6, 0, sizeof(pdatap->sinaddr_udp6)); pdatap->sinaddr_udp6.sin6_family = AF_INET6; /* all interfaces */ pdatap->sinaddr_udp6.sin6_addr = ((struct sockaddr_in6 *)&nfs_param .core_param.bind_addr) ->sin6_addr; pdatap->sinaddr_udp6.sin6_port = htons(nfs_param.core_param.port[p]); pdatap->netbuf_udp6.maxlen = sizeof(pdatap->sinaddr_udp6); pdatap->netbuf_udp6.len = sizeof(pdatap->sinaddr_udp6); pdatap->netbuf_udp6.buf = &pdatap->sinaddr_udp6; pdatap->bindaddr_udp6.qlen = SOMAXCONN; pdatap->bindaddr_udp6.addr = pdatap->netbuf_udp6; if (!__rpc_fd2sockinfo(udp_socket[p], &pdatap->si_udp6)) { LogWarn(COMPONENT_DISPATCH, "Cannot get %s socket info for udp6 socket errno=%d (%s)", tags[p], errno, strerror(errno)); return -1; } if (isInfo(COMPONENT_DISPATCH)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dbuf = { sizeof(str), str, str }; display_sockaddr(&dbuf, (sockaddr_t *)pdatap ->bindaddr_udp6 .addr.buf); LogInfo(COMPONENT_DISPATCH, "Binding UDP socket to address %s for %s", str, tags[p]); } rc = bind(udp_socket[p], (struct sockaddr *)pdatap ->bindaddr_udp6.addr.buf, (socklen_t)pdatap->si_udp6.si_alen); if (rc == -1) { LogWarn(COMPONENT_DISPATCH, "Cannot bind %s udp6 socket, error %d (%s)", tags[p], errno, strerror(errno)); goto exit; } } memset(&pdatap->sinaddr_tcp6, 0, sizeof(pdatap->sinaddr_tcp6)); pdatap->sinaddr_tcp6.sin6_family = AF_INET6; /* all interfaces */ pdatap->sinaddr_tcp6.sin6_addr = ((struct sockaddr_in6 *)&nfs_param.core_param .bind_addr) ->sin6_addr; pdatap->sinaddr_tcp6.sin6_port = htons(nfs_param.core_param.port[p]); pdatap->netbuf_tcp6.maxlen = sizeof(pdatap->sinaddr_tcp6); pdatap->netbuf_tcp6.len = sizeof(pdatap->sinaddr_tcp6); pdatap->netbuf_tcp6.buf = &pdatap->sinaddr_tcp6; pdatap->bindaddr_tcp6.qlen = SOMAXCONN; pdatap->bindaddr_tcp6.addr = pdatap->netbuf_tcp6; if (!__rpc_fd2sockinfo(tcp_socket[p], &pdatap->si_tcp6)) { LogWarn(COMPONENT_DISPATCH, "Cannot get %s socket info for tcp6 socket errno=%d (%s)", tags[p], errno, strerror(errno)); return -1; } if (isInfo(COMPONENT_DISPATCH)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dbuf = { sizeof(str), str, str }; display_sockaddr( &dbuf, (sockaddr_t *) pdatap->bindaddr_tcp6.addr.buf); LogInfo(COMPONENT_DISPATCH, "Binding TCP socket to address %s for %s", str, tags[p]); } rc = bind(tcp_socket[p], (struct sockaddr *) pdatap->bindaddr_tcp6.addr.buf, (socklen_t)pdatap->si_tcp6.si_alen); if (rc == -1) { LogWarn(COMPONENT_DISPATCH, "Cannot bind %s tcp6 socket, error %d (%s)", tags[p], errno, strerror(errno)); goto exit; } } } exit: return rc; } /** * @brief Bind the udp and tcp sockets for V4 Interfaces */ static int Bind_sockets_V4(void) { protos p; int rc = 0; if (isInfo(COMPONENT_DISPATCH)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dbuf = { sizeof(str), str, str }; display_sockaddr(&dbuf, &nfs_param.core_param.bind_addr); LogInfo(COMPONENT_DISPATCH, "Binding to address %s", str); } for (p = P_NFS; p < P_COUNT; p++) { if (nfs_protocol_enabled(p)) { proto_data *pdatap = &pdata[p]; if (udp_socket[p] != -1) { memset(&pdatap->sinaddr_udp, 0, sizeof(pdatap->sinaddr_udp)); pdatap->sinaddr_udp.sin_family = AF_INET; /* all interfaces */ pdatap->sinaddr_udp.sin_addr.s_addr = ((struct sockaddr_in *)&nfs_param .core_param.bind_addr) ->sin_addr.s_addr; pdatap->sinaddr_udp.sin_port = htons(nfs_param.core_param.port[p]); pdatap->netbuf_udp6.maxlen = sizeof(pdatap->sinaddr_udp); pdatap->netbuf_udp6.len = sizeof(pdatap->sinaddr_udp); pdatap->netbuf_udp6.buf = &pdatap->sinaddr_udp; pdatap->bindaddr_udp6.qlen = SOMAXCONN; pdatap->bindaddr_udp6.addr = pdatap->netbuf_udp6; if (!__rpc_fd2sockinfo(udp_socket[p], &pdatap->si_udp6)) { LogWarn(COMPONENT_DISPATCH, "Cannot get %s socket info for udp6 socket errno=%d (%s)", tags[p], errno, strerror(errno)); return -1; } if (isInfo(COMPONENT_DISPATCH)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dbuf = { sizeof(str), str, str }; display_sockaddr(&dbuf, (sockaddr_t *)pdatap ->bindaddr_udp6 .addr.buf); LogInfo(COMPONENT_DISPATCH, "Binding UDP socket to address %s for %s", str, tags[p]); } rc = bind(udp_socket[p], (struct sockaddr *)pdatap ->bindaddr_udp6.addr.buf, (socklen_t)pdatap->si_udp6.si_alen); if (rc == -1) { LogWarn(COMPONENT_DISPATCH, "Cannot bind %s udp6 socket, error %d (%s)", tags[p], errno, strerror(errno)); return -1; } } memset(&pdatap->sinaddr_tcp, 0, sizeof(pdatap->sinaddr_tcp)); pdatap->sinaddr_tcp.sin_family = AF_INET; /* all interfaces */ pdatap->sinaddr_tcp.sin_addr.s_addr = ((struct sockaddr_in *)&nfs_param.core_param .bind_addr) ->sin_addr.s_addr; pdatap->sinaddr_tcp.sin_port = htons(nfs_param.core_param.port[p]); pdatap->netbuf_tcp6.maxlen = sizeof(pdatap->sinaddr_tcp); pdatap->netbuf_tcp6.len = sizeof(pdatap->sinaddr_tcp); pdatap->netbuf_tcp6.buf = &pdatap->sinaddr_tcp; pdatap->bindaddr_tcp6.qlen = SOMAXCONN; pdatap->bindaddr_tcp6.addr = pdatap->netbuf_tcp6; if (!__rpc_fd2sockinfo(tcp_socket[p], &pdatap->si_tcp6)) { LogWarn(COMPONENT_DISPATCH, "V4 : Cannot get %s socket info for tcp socket error %d(%s)", tags[p], errno, strerror(errno)); return -1; } if (isInfo(COMPONENT_DISPATCH)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dbuf = { sizeof(str), str, str }; display_sockaddr( &dbuf, (sockaddr_t *) pdatap->bindaddr_tcp6.addr.buf); LogInfo(COMPONENT_DISPATCH, "Binding TCP socket to address %s for %s", str, tags[p]); } rc = bind(tcp_socket[p], (struct sockaddr *) pdatap->bindaddr_tcp6.addr.buf, (socklen_t)pdatap->si_tcp6.si_alen); if (rc == -1) { LogWarn(COMPONENT_DISPATCH, "Cannot bind %s tcp socket, error %d(%s)", tags[p], errno, strerror(errno)); return -1; } } } return rc; } #ifdef RPC_VSOCK int bind_sockets_vsock(void) { int rc = 0; struct sockaddr_vm sa_listen = { .svm_family = AF_VSOCK, .svm_cid = VMADDR_CID_ANY, .svm_port = nfs_param.core_param.port[P_NFS], }; rc = bind(tcp_socket[P_NFS_VSOCK], (struct sockaddr *)(struct sockaddr *)&sa_listen, sizeof(sa_listen)); if (rc == -1) { LogWarn(COMPONENT_DISPATCH, "cannot bind %s stream socket, error %d(%s)", tags[P_NFS_VSOCK], errno, strerror(errno)); } return rc; } #endif /* RPC_VSOCK */ void Bind_sockets(void) { int rc = 0; /* * See Allocate_sockets(), which should already * have set the global v6disabled accordingly */ if (v6disabled) { rc = Bind_sockets_V4(); if (rc) LogFatal( COMPONENT_DISPATCH, "Error binding to V4 interface. Cannot continue."); } else { rc = Bind_sockets_V6(); if (rc) LogFatal( COMPONENT_DISPATCH, "Error binding to V6 interface. Cannot continue."); } #ifdef RPC_VSOCK if (vsock) { rc = bind_sockets_vsock(); if (rc) LogMajor(COMPONENT_DISPATCH, "AF_VSOCK bind failed (continuing startup)"); } #endif /* RPC_VSOCK */ LogInfo(COMPONENT_DISPATCH, "Bind sockets successful, v6disabled = %d, vsock = %d, rdma = %d", v6disabled, vsock, rdma); } /** * @brief Function to set the socket options on the allocated * udp and tcp sockets * */ static int alloc_socket_setopts(int p) { int one = 1; const struct nfs_core_param *nfs_cp = &nfs_param.core_param; /* Use SO_REUSEADDR in order to avoid wait * the 2MSL timeout */ if (udp_socket[p] != -1) { if (setsockopt(udp_socket[p], SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) { LogWarn(COMPONENT_DISPATCH, "Bad udp socket options for %s, error %d(%s)", tags[p], errno, strerror(errno)); return -1; } } if (setsockopt(tcp_socket[p], SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) { LogWarn(COMPONENT_DISPATCH, "Bad tcp socket option reuseaddr for %s, error %d(%s)", tags[p], errno, strerror(errno)); return -1; } if (nfs_cp->enable_tcp_keepalive) { if (setsockopt(tcp_socket[p], SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one))) { LogWarn(COMPONENT_DISPATCH, "Bad tcp socket option keepalive for %s, error %d(%s)", tags[p], errno, strerror(errno)); return -1; } if (nfs_cp->tcp_keepcnt) { if (setsockopt(tcp_socket[p], IPPROTO_TCP, TCP_KEEPCNT, &nfs_cp->tcp_keepcnt, sizeof(nfs_cp->tcp_keepcnt))) { LogWarn(COMPONENT_DISPATCH, "Bad tcp socket option TCP_KEEPCNT for %s, error %d(%s)", tags[p], errno, strerror(errno)); return -1; } } if (nfs_cp->tcp_keepidle) { if (setsockopt(tcp_socket[p], IPPROTO_TCP, TCP_KEEPIDLE, &nfs_cp->tcp_keepidle, sizeof(nfs_cp->tcp_keepidle))) { LogWarn(COMPONENT_DISPATCH, "Bad tcp socket option TCP_KEEPIDLE for %s, error %d(%s)", tags[p], errno, strerror(errno)); return -1; } } if (nfs_cp->tcp_keepintvl) { if (setsockopt(tcp_socket[p], IPPROTO_TCP, TCP_KEEPINTVL, &nfs_cp->tcp_keepintvl, sizeof(nfs_cp->tcp_keepintvl))) { LogWarn(COMPONENT_DISPATCH, "Bad tcp socket option TCP_KEEPINTVL for %s, error %d(%s)", tags[p], errno, strerror(errno)); return -1; } } } if (udp_socket[p] != -1) { /* We prefer using non-blocking socket * in the specific case */ if (fcntl(udp_socket[p], F_SETFL, FNDELAY) == -1) { LogWarn(COMPONENT_DISPATCH, "Cannot set udp socket for %s as non blocking, error %d(%s)", tags[p], errno, strerror(errno)); return -1; } } return 0; } static bool enable_udp_listener(protos prot) { if (nfs_param.core_param.enable_UDP & UDP_LISTENER_ALL) return true; #ifdef _USE_NFS3 if (prot == P_MNT && (nfs_param.core_param.enable_UDP & UDP_LISTENER_MOUNT)) return true; #endif return false; } /** * @brief Allocate the tcp and udp sockets for the nfs daemon * using V4 interfaces */ static int Allocate_sockets_V4(int p) { udp_socket[p] = tcp_socket[p] = -1; if (enable_udp_listener(p)) { udp_socket[p] = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (udp_socket[p] == -1) { if (errno == EAFNOSUPPORT) { LogInfo(COMPONENT_DISPATCH, "No V6 and V4 intfs configured?!"); } LogWarn(COMPONENT_DISPATCH, "Cannot allocate a udp socket for %s, error %d(%s)", tags[p], errno, strerror(errno)); return -1; } } tcp_socket[p] = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (tcp_socket[p] == -1) { LogWarn(COMPONENT_DISPATCH, "Cannot allocate a tcp socket for %s, error %d(%s)", tags[p], errno, strerror(errno)); return -1; } return 0; } #ifdef RPC_VSOCK /** * @brief Create vmci stream socket */ static int allocate_socket_vsock(void) { int one = 1; tcp_socket[P_NFS_VSOCK] = socket(AF_VSOCK, SOCK_STREAM, 0); if (tcp_socket[P_NFS_VSOCK] == -1) { LogWarn(COMPONENT_DISPATCH, "socket create failed for %s, error %d(%s)", tags[P_NFS_VSOCK], errno, strerror(errno)); return -1; } if (setsockopt(tcp_socket[P_NFS_VSOCK], SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) { LogWarn(COMPONENT_DISPATCH, "bad tcp socket options for %s, error %d(%s)", tags[P_NFS_VSOCK], errno, strerror(errno)); return -1; } LogDebug(COMPONENT_DISPATCH, "Socket numbers are: %s tcp=%d", tags[P_NFS_VSOCK], tcp_socket[P_NFS_VSOCK]); return 0; } #endif /* RPC_VSOCK */ /** * @brief Allocate the tcp and udp sockets for the nfs daemon */ static void Allocate_sockets(void) { protos p; int rc = 0; LogFullDebug(COMPONENT_DISPATCH, "Allocation of the sockets"); for (p = P_NFS; p < P_COUNT; p++) { /* Initialize all the sockets to -1 because * it makes some code later easier */ udp_socket[p] = tcp_socket[p] = -1; if (nfs_protocol_enabled(p)) { if (v6disabled) goto try_V4; if (enable_udp_listener(p)) { udp_socket[p] = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (udp_socket[p] == -1) { /* * We assume that EAFNOSUPPORT points * to the likely case when the host has * V6 interfaces disabled. So we will * try to use the existing V4 interfaces * instead */ if (errno == EAFNOSUPPORT) { v6disabled = true; LogWarn(COMPONENT_DISPATCH, "System may not have V6 intfs configured error %d(%s)", errno, strerror(errno)); goto try_V4; } LogFatal( COMPONENT_DISPATCH, "Cannot allocate a udp socket for %s, error %d(%s)", tags[p], errno, strerror(errno)); } } tcp_socket[p] = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); if (tcp_socket[p] == -1) { /* We fail with LogFatal here on error because * it shouldn't be that we have managed to * create a V6 based udp socket and have failed * for the tcp sock. If it were a case of V6 * being disabled, then we would have * encountered that case with the first udp * sock create and would have moved on to * create the V4 sockets. */ if (enable_udp_listener(p) || errno != EAFNOSUPPORT) { LogFatal( COMPONENT_DISPATCH, "Cannot allocate a tcp socket for %s, error %d(%s)", tags[p], errno, strerror(errno)); } v6disabled = true; LogWarn(COMPONENT_DISPATCH, "System may not have V6 intfs configured error %d(%s)", errno, strerror(errno)); goto try_V4; } try_V4: if (v6disabled) { rc = Allocate_sockets_V4(p); if (rc) { LogFatal( COMPONENT_DISPATCH, "Error allocating V4 socket for proto %d, %s", p, tags[p]); } } rc = alloc_socket_setopts(p); if (rc) { LogFatal( COMPONENT_DISPATCH, "Error setting socket option for proto %d, %s", p, tags[p]); } LogDebug(COMPONENT_DISPATCH, "Socket numbers are: %s tcp=%d udp=%d", tags[p], tcp_socket[p], udp_socket[p]); } } #ifdef RPC_VSOCK if (vsock) allocate_socket_vsock(); #endif /* RPC_VSOCK */ } /* The following routine must ONLY be called from the shutdown * thread */ void Clean_RPC(void) { /** * @todo Consider the need to call Svc_dg_destroy for UDP & ?? for * TCP based services */ unregister_rpc(); close_rpc_fd(); freenetconfigent(netconfig_udpv4); freenetconfigent(netconfig_tcpv4); freenetconfigent(netconfig_udpv6); freenetconfigent(netconfig_tcpv6); } #define UDP_REGISTER(prot, vers, netconfig) \ svc_reg(udp_xprt[prot], NFS_program[prot], (u_long)vers, \ nfs_rpc_dispatch_dummy, netconfig) #define TCP_REGISTER(prot, vers, netconfig) \ svc_reg(tcp_xprt[prot], NFS_program[prot], (u_long)vers, \ nfs_rpc_dispatch_dummy, netconfig) #ifdef RPCBIND static bool __Register_program(protos prot, int vers) { if (enable_udp_listener(prot)) { LogInfo(COMPONENT_DISPATCH, "Registering %s V%d/UDP", tags[prot], (int)vers); /* XXXX fix svc_register! */ if (!UDP_REGISTER(prot, vers, netconfig_udpv4)) { LogMajor(COMPONENT_DISPATCH, "Cannot register %s V%d on UDP", tags[prot], (int)vers); return false; } if (!v6disabled && netconfig_udpv6) { LogInfo(COMPONENT_DISPATCH, "Registering %s V%d/UDPv6", tags[prot], (int)vers); if (!UDP_REGISTER(prot, vers, netconfig_udpv6)) { LogMajor(COMPONENT_DISPATCH, "Cannot register %s V%d on UDPv6", tags[prot], (int)vers); return false; } } } #ifndef _NO_TCP_REGISTER LogInfo(COMPONENT_DISPATCH, "Registering %s V%d/TCP", tags[prot], (int)vers); if (!TCP_REGISTER(prot, vers, netconfig_tcpv4)) { LogMajor(COMPONENT_DISPATCH, "Cannot register %s V%d on TCP", tags[prot], (int)vers); return false; } if (!v6disabled && netconfig_tcpv6) { LogInfo(COMPONENT_DISPATCH, "Registering %s V%d/TCPv6", tags[prot], (int)vers); if (!TCP_REGISTER(prot, vers, netconfig_tcpv6)) { LogMajor(COMPONENT_DISPATCH, "Cannot register %s V%d on TCPv6", tags[prot], (int)vers); return false; } } #endif /* _NO_TCP_REGISTER */ return true; } static inline void Register_program(protos prot, int vers) { if (!__Register_program(prot, vers)) Fatal(); } #endif /* RPCBIND */ /** * @brief Init the svc descriptors for the nfs daemon * * Perform all the required initialization for the RPC subsystem and event * channels. */ void nfs_Init_svc(void) { svc_init_params svc_params; int ix; int code; LogDebug(COMPONENT_DISPATCH, "NFS INIT: Core options = %d", NFS_options); LogInfo(COMPONENT_DISPATCH, "NFS INIT: using TIRPC"); memset(&svc_params, 0, sizeof(svc_params)); #ifdef __FreeBSD__ v6disabled = true; #else v6disabled = false; #endif #ifdef RPC_VSOCK vsock = NFS_options & CORE_OPTION_NFS_VSOCK; #endif #ifdef _USE_NFS_RDMA rdma = NFS_options & CORE_OPTION_NFS_RDMA; #endif /* New TI-RPC package init function */ svc_params.disconnect_cb = NULL; svc_params.alloc_cb = alloc_nfs_request; svc_params.free_cb = free_nfs_request; svc_params.flags = SVC_INIT_EPOLL; /* use EPOLL event mgmt */ svc_params.flags |= SVC_INIT_NOREG_XPRTS; /* don't call xprt_register */ svc_params.max_connections = nfs_param.core_param.rpc.max_connections; svc_params.max_events = 1024; /* length of epoll event queue */ svc_params.ioq_send_max = nfs_param.core_param.rpc.max_send_buffer_size; svc_params.channels = N_EVENT_CHAN; svc_params.idle_timeout = nfs_param.core_param.rpc.idle_timeout_s; svc_params.ioq_thrd_min = nfs_param.core_param.rpc.ioq_thrd_min; svc_params.ioq_thrd_max = nfs_param.core_param.rpc.ioq_thrd_max; /* GSS ctx cache tuning, expiration */ svc_params.gss_ctx_hash_partitions = nfs_param.core_param.rpc.gss.ctx_hash_partitions; svc_params.gss_max_ctx = nfs_param.core_param.rpc.gss.max_ctx; svc_params.gss_max_gc = nfs_param.core_param.rpc.gss.max_gc; #ifdef SVC_PARAM_HAS_THR_STACK_SIZE svc_params.thr_stack_size = PTHREAD_stack_size; #endif #ifdef _USE_NFS_RDMA svc_params.nfs_rdma_port = nfs_param.core_param.port[P_NFS_RDMA]; svc_params.max_rdma_connections = nfs_param.core_param.rpc.max_rdma_connections; #endif /* Only after TI-RPC allocators, log channel are setup */ if (!svc_init(&svc_params)) LogFatal(COMPONENT_INIT, "SVC initialization failed"); for (ix = 0; ix < EVCHAN_SIZE; ++ix) { rpc_evchan[ix].chan_id = 0; code = svc_rqst_new_evchan(&rpc_evchan[ix].chan_id, NULL /* u_data */, SVC_RQST_FLAG_NONE); if (code) LogFatal(COMPONENT_DISPATCH, "Cannot create TI-RPC event channel (%d, %d)", ix, code); /* XXX bail?? */ } /* Get the netconfig entries from /etc/netconfig */ netconfig_udpv4 = (struct netconfig *)getnetconfigent("udp"); if (netconfig_udpv4 == NULL) LogFatal( COMPONENT_DISPATCH, "Cannot get udp netconfig, cannot get an entry for udp in netconfig file. Check file /etc/netconfig..."); /* Get the netconfig entries from /etc/netconfig */ netconfig_tcpv4 = (struct netconfig *)getnetconfigent("tcp"); if (netconfig_tcpv4 == NULL) LogFatal( COMPONENT_DISPATCH, "Cannot get tcp netconfig, cannot get an entry for tcp in netconfig file. Check file /etc/netconfig..."); /* A short message to show that /etc/netconfig parsing was a success */ LogFullDebug(COMPONENT_DISPATCH, "netconfig found for UDPv4 and TCPv4"); LogInfo(COMPONENT_DISPATCH, "NFS INIT: Using IPv6"); /* Get the netconfig entries from /etc/netconfig */ netconfig_udpv6 = (struct netconfig *)getnetconfigent("udp6"); if (netconfig_udpv6 == NULL) LogInfo(COMPONENT_DISPATCH, "Cannot get udp6 netconfig, cannot get an entry for udp6 in netconfig file. Check file /etc/netconfig..."); /* Get the netconfig entries from /etc/netconfig */ netconfig_tcpv6 = (struct netconfig *)getnetconfigent("tcp6"); if (netconfig_tcpv6 == NULL) LogInfo(COMPONENT_DISPATCH, "Cannot get tcp6 netconfig, cannot get an entry for tcp in netconfig file. Check file /etc/netconfig..."); /* A short message to show that /etc/netconfig parsing was a success * for ipv6 */ if (netconfig_udpv6 && netconfig_tcpv6) LogFullDebug(COMPONENT_DISPATCH, "netconfig found for UDPv6 and TCPv6"); /* Allocate the UDP and TCP sockets for the RPC */ Allocate_sockets(); if ((NFS_options & CORE_OPTION_ALL_NFS_VERS) != 0) { /* Bind the tcp and udp sockets */ Bind_sockets(); /* Unregister from portmapper/rpcbind */ unregister_rpc(); /* Set up well-known xprt handles */ Create_SVCXPRTs(); } #ifdef RPCBIND /* * Perform all the RPC registration, for UDP and TCP, on both NFS_V3 * and NFS_V4. Note that v4 servers are not required to register with * rpcbind, so we don't fail to start if only that fails. */ #ifdef _USE_NFS3 if (NFS_options & CORE_OPTION_NFSV3) { Register_program(P_NFS, NFS_V3); Register_program(P_MNT, MOUNT_V1); Register_program(P_MNT, MOUNT_V3); #ifdef _USE_NLM if (nfs_param.core_param.enable_NLM) Register_program(P_NLM, NLM4_VERS); #endif /* _USE_NLM */ #ifdef USE_NFSACL3 if (nfs_param.core_param.enable_NFSACL) Register_program(P_NFSACL, NFSACL_V3); #endif } #endif /* _USE_NFS3 */ /* v4 registration is optional */ if (NFS_options & CORE_OPTION_NFSV4) __Register_program(P_NFS, NFS_V4); #ifdef _USE_RQUOTA if (nfs_param.core_param.enable_RQUOTA && (NFS_options & CORE_OPTION_ALL_NFS_VERS)) { Register_program(P_RQUOTA, RQUOTAVERS); Register_program(P_RQUOTA, EXT_RQUOTAVERS); } #endif #endif /* RPCBIND */ } /** * @brief Rendezvous callout. This routine will be called by TI-RPC * after newxprt has been accepted. * * Register newxprt on a TCP event channel. Balancing events/channels * could become involved. To start with, just cycle through them as * new connections are accepted. * * @param[in] newxprt Newly created transport * * @return status of parent. */ static enum xprt_stat nfs_rpc_tcp_user_data(SVCXPRT *newxprt) { return SVC_STAT(newxprt->xp_parent); } /** * @brief xprt user-data allocator function * * @param[in] xprt Transport to use for allocation */ static void nfs_rpc_alloc_user_data(SVCXPRT *xprt) { init_custom_data_for_xprt(xprt); } /** * @brief xprt destructor callout * * @param[in] xprt Transport to destroy */ static enum xprt_stat nfs_rpc_free_user_data(SVCXPRT *xprt) { if (xprt->xp_u2) { nfs_dupreq_put_drc(xprt->xp_u2); xprt->xp_u2 = NULL; } connection_manager__connection_finished(xprt); destroy_custom_data_for_destroyed_xprt(xprt); return XPRT_DESTROYED; } /** * @brief xprt user-data un-referencing function * * @param[in] xprt Transport to use for un-referencing user-data */ static void nfs_rpc_unref_user_data(SVCXPRT *xprt) { dissociate_custom_data_from_xprt(xprt); } /** * @brief Allocate a new request * * @param[in] xprt Transport to use * @param[in] xdrs XDR to use * * @return New svc request */ static struct svc_req *alloc_nfs_request(SVCXPRT *xprt, XDR *xdrs) { nfs_request_t *reqdata = gsh_calloc(1, sizeof(nfs_request_t)); if (!xprt) { LogFatal(COMPONENT_DISPATCH, "missing xprt!"); } if (!xdrs) { LogFatal(COMPONENT_DISPATCH, "missing xdrs!"); } LogDebug(COMPONENT_DISPATCH, "%p fd %d context %p", xprt, xprt->xp_fd, xdrs); (void)atomic_inc_uint64_t(&nfs_health_.enqueued_reqs); #ifdef USE_MONITORING nfs_metrics__rpc_received(); nfs_metrics__rpcs_in_flight(nfs_health_.enqueued_reqs - nfs_health_.dequeued_reqs); #endif /* USE_MONITORING*/ /* set up req */ SVC_REF(xprt, SVC_REF_FLAG_NONE); reqdata->svc.rq_xprt = xprt; reqdata->svc.rq_xdrs = xdrs; reqdata->svc.rq_refcnt = 1; TAILQ_INIT_ENTRY(reqdata, dupes); return &reqdata->svc; } static void free_nfs_request(struct svc_req *req, enum xprt_stat stat) { nfs_request_t *reqdata = container_of(req, nfs_request_t, svc); SVCXPRT *xprt = reqdata->svc.rq_xprt; if (unlikely(stat > XPRT_DESTROYED)) { LogInfo(COMPONENT_DISPATCH, "SVC_DECODE on %p fd %d returned unknown %u", xprt, xprt->xp_fd, stat); } else if (isDebug(COMPONENT_DISPATCH)) { sockaddr_t addr; char addrbuf[SOCK_NAME_MAX]; struct display_buffer dspbuf = { sizeof(addrbuf), addrbuf, addrbuf }; copy_xprt_addr(&addr, xprt); display_sockaddr(&dspbuf, &addr); LogDebug(COMPONENT_DISPATCH, "SVC_DECODE on %p fd %d (%s) xid=%" PRIu32 " returned %s", xprt, xprt->xp_fd, addrbuf, reqdata->svc.rq_msg.rm_xid, xprt_stat_s[stat]); } LogFullDebug(COMPONENT_DISPATCH, "%s: %p fd %d xp_refcnt %" PRIu32, __func__, xprt, xprt->xp_fd, xprt->xp_refcnt); gsh_free(reqdata); SVC_RELEASE(xprt, SVC_REF_FLAG_NONE); (void)atomic_inc_uint64_t(&nfs_health_.dequeued_reqs); #ifdef USE_MONITORING nfs_metrics__rpc_completed(); #endif /* USE_MONITORING*/ } nfs-ganesha-6.5/src/MainNFSD/nfs_rpc_tcp_socket_manager_thread.c000066400000000000000000000035621473756622300247660ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_rpc_tcp_socket_manager_thread.c * @brief The file that contain the 'rpc_tcp_socket_manager_thread' routine * for the nfsd. * * nfs_rpc_dispatcher.c : The file that contain the * 'rpc_tcp_socket_manager_thread.c' routine for the nfsd (and all * the related stuff). * */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs23.h" #include "nfs4.h" #include "mount.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_file_handle.h" #if 0 /* XXX This routine was used only in prior revision of the rendezvous_request * which spawned a dedicated thread for each client connection. */ #endif nfs-ganesha-6.5/src/MainNFSD/nfs_worker_thread.c000066400000000000000000001677541473756622300216210ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * William Allen Simpson * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_worker_thread.c * @brief The file that contain the 'worker_thread' routine for the nfsd. */ #include "config.h" #ifdef FREEBSD #include #endif #include #include #include #include #include #include #include /* for having FNDELAY */ #include #include #include "hashtable.h" #include "abstract_atomic.h" #include "log.h" #include "fsal.h" #include "rquota.h" #include "nfs_init.h" #include "nfs_convert.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_creds.h" #include "nfs_proto_functions.h" #include "nfs_dupreq.h" #include "nfs_file_handle.h" #include "client_mgr.h" #include "export_mgr.h" #include "server_stats.h" #include "uid2grp.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs_rpc.h" #endif #include "gsh_xprt_tracepoint.h" #include "nfs_metrics.h" #define NFS_pcp nfs_param.core_param #define NFS_options NFS_pcp.core_options #define NFS_program NFS_pcp.program const nfs_function_desc_t invalid_funcdesc = { .service_function = nfs_null, .free_function = nfs_null_free, .xdr_decode_func = (xdrproc_t)xdr_void, .xdr_encode_func = (xdrproc_t)xdr_void, .funcname = "invalid_function", .dispatch_behaviour = NOTHING_SPECIAL }; #ifdef _USE_NFS3 const nfs_function_desc_t nfs3_func_desc[] = { { .service_function = nfs_null, .free_function = nfs_null_free, .xdr_decode_func = (xdrproc_t)xdr_void, .xdr_encode_func = (xdrproc_t)xdr_void, .funcname = "NFS3_NULL", .dispatch_behaviour = NOTHING_SPECIAL }, { .service_function = nfs3_getattr, .free_function = nfs3_getattr_free, .xdr_decode_func = (xdrproc_t)xdr_GETATTR3args, .xdr_encode_func = (xdrproc_t)xdr_GETATTR3res, .funcname = "NFS3_GETATTR", .dispatch_behaviour = NEEDS_CRED | SUPPORTS_GSS }, { .service_function = nfs3_setattr, .free_function = nfs3_setattr_free, .xdr_decode_func = (xdrproc_t)xdr_SETATTR3args, .xdr_encode_func = (xdrproc_t)xdr_SETATTR3res, .funcname = "NFS3_SETATTR", .dispatch_behaviour = (MAKES_WRITE | NEEDS_CRED | CAN_BE_DUP | SUPPORTS_GSS) }, { .service_function = nfs3_lookup, .free_function = nfs3_lookup_free, .xdr_decode_func = (xdrproc_t)xdr_LOOKUP3args, .xdr_encode_func = (xdrproc_t)xdr_LOOKUP3res, .funcname = "NFS3_LOOKUP", .dispatch_behaviour = NEEDS_CRED | SUPPORTS_GSS }, { .service_function = nfs3_access, .free_function = nfs3_access_free, .xdr_decode_func = (xdrproc_t)xdr_ACCESS3args, .xdr_encode_func = (xdrproc_t)xdr_ACCESS3res, .funcname = "NFS3_ACCESS", .dispatch_behaviour = NEEDS_CRED | SUPPORTS_GSS }, { .service_function = nfs3_readlink, .free_function = nfs3_readlink_free, .xdr_decode_func = (xdrproc_t)xdr_READLINK3args, .xdr_encode_func = (xdrproc_t)xdr_READLINK3res, .funcname = "NFS3_READLINK", .dispatch_behaviour = NEEDS_CRED | SUPPORTS_GSS }, { .service_function = nfs3_read, .free_function = nfs3_read_free, .xdr_decode_func = (xdrproc_t)xdr_READ3args, .xdr_encode_func = (xdrproc_t)xdr_READ3res, .funcname = "NFS3_READ", .dispatch_behaviour = NEEDS_CRED | SUPPORTS_GSS | MAKES_IO }, { .service_function = nfs3_write, .free_function = nfs3_write_free, .xdr_decode_func = (xdrproc_t)xdr_WRITE3args, .xdr_encode_func = (xdrproc_t)xdr_WRITE3res, .funcname = "NFS3_WRITE", .dispatch_behaviour = (MAKES_WRITE | NEEDS_CRED | CAN_BE_DUP | SUPPORTS_GSS | MAKES_IO) }, { .service_function = nfs3_create, .free_function = nfs3_create_free, .xdr_decode_func = (xdrproc_t)xdr_CREATE3args, .xdr_encode_func = (xdrproc_t)xdr_CREATE3res, .funcname = "NFS3_CREATE", .dispatch_behaviour = (MAKES_WRITE | NEEDS_CRED | CAN_BE_DUP | SUPPORTS_GSS) }, { .service_function = nfs3_mkdir, .free_function = nfs3_mkdir_free, .xdr_decode_func = (xdrproc_t)xdr_MKDIR3args, .xdr_encode_func = (xdrproc_t)xdr_MKDIR3res, .funcname = "NFS3_MKDIR", .dispatch_behaviour = (MAKES_WRITE | NEEDS_CRED | CAN_BE_DUP | SUPPORTS_GSS) }, { .service_function = nfs3_symlink, .free_function = nfs3_symlink_free, .xdr_decode_func = (xdrproc_t)xdr_SYMLINK3args, .xdr_encode_func = (xdrproc_t)xdr_SYMLINK3res, .funcname = "NFS3_SYMLINK", .dispatch_behaviour = (MAKES_WRITE | NEEDS_CRED | CAN_BE_DUP | SUPPORTS_GSS) }, { .service_function = nfs3_mknod, .free_function = nfs3_mknod_free, .xdr_decode_func = (xdrproc_t)xdr_MKNOD3args, .xdr_encode_func = (xdrproc_t)xdr_MKNOD3res, .funcname = "NFS3_MKNOD", .dispatch_behaviour = (MAKES_WRITE | NEEDS_CRED | CAN_BE_DUP | SUPPORTS_GSS) }, { .service_function = nfs3_remove, .free_function = nfs3_remove_free, .xdr_decode_func = (xdrproc_t)xdr_REMOVE3args, .xdr_encode_func = (xdrproc_t)xdr_REMOVE3res, .funcname = "NFS3_REMOVE", .dispatch_behaviour = (MAKES_WRITE | NEEDS_CRED | CAN_BE_DUP | SUPPORTS_GSS) }, { .service_function = nfs3_rmdir, .free_function = nfs3_rmdir_free, .xdr_decode_func = (xdrproc_t)xdr_RMDIR3args, .xdr_encode_func = (xdrproc_t)xdr_RMDIR3res, .funcname = "NFS3_RMDIR", .dispatch_behaviour = (MAKES_WRITE | NEEDS_CRED | CAN_BE_DUP | SUPPORTS_GSS) }, { .service_function = nfs3_rename, .free_function = nfs3_rename_free, .xdr_decode_func = (xdrproc_t)xdr_RENAME3args, .xdr_encode_func = (xdrproc_t)xdr_RENAME3res, .funcname = "NFS3_RENAME", .dispatch_behaviour = (MAKES_WRITE | NEEDS_CRED | CAN_BE_DUP | SUPPORTS_GSS) }, { .service_function = nfs3_link, .free_function = nfs3_link_free, .xdr_decode_func = (xdrproc_t)xdr_LINK3args, .xdr_encode_func = (xdrproc_t)xdr_LINK3res, .funcname = "NFS3_LINK", .dispatch_behaviour = (MAKES_WRITE | NEEDS_CRED | CAN_BE_DUP | SUPPORTS_GSS) }, { .service_function = nfs3_readdir, .free_function = nfs3_readdir_free, .xdr_decode_func = (xdrproc_t)xdr_READDIR3args, .xdr_encode_func = (xdrproc_t)xdr_READDIR3res, .funcname = "NFS3_READDIR", .dispatch_behaviour = (NEEDS_CRED | SUPPORTS_GSS) }, { .service_function = nfs3_readdirplus, .free_function = nfs3_readdirplus_free, .xdr_decode_func = (xdrproc_t)xdr_READDIRPLUS3args, .xdr_encode_func = (xdrproc_t)xdr_READDIRPLUS3res, .funcname = "NFS3_READDIRPLUS", .dispatch_behaviour = (NEEDS_CRED | SUPPORTS_GSS) }, { .service_function = nfs3_fsstat, .free_function = nfs3_fsstat_free, .xdr_decode_func = (xdrproc_t)xdr_FSSTAT3args, .xdr_encode_func = (xdrproc_t)xdr_FSSTAT3res, .funcname = "NFS3_FSSTAT", .dispatch_behaviour = (NEEDS_CRED | SUPPORTS_GSS) }, { .service_function = nfs3_fsinfo, .free_function = nfs3_fsinfo_free, .xdr_decode_func = (xdrproc_t)xdr_FSINFO3args, .xdr_encode_func = (xdrproc_t)xdr_FSINFO3res, .funcname = "NFS3_FSINFO", .dispatch_behaviour = (NEEDS_CRED) }, { .service_function = nfs3_pathconf, .free_function = nfs3_pathconf_free, .xdr_decode_func = (xdrproc_t)xdr_PATHCONF3args, .xdr_encode_func = (xdrproc_t)xdr_PATHCONF3res, .funcname = "NFS3_PATHCONF", .dispatch_behaviour = (NEEDS_CRED | SUPPORTS_GSS) }, { .service_function = nfs3_commit, .free_function = nfs3_commit_free, .xdr_decode_func = (xdrproc_t)xdr_COMMIT3args, .xdr_encode_func = (xdrproc_t)xdr_COMMIT3res, .funcname = "NFS3_COMMIT", .dispatch_behaviour = (MAKES_WRITE | NEEDS_CRED | SUPPORTS_GSS) } }; #endif /* _USE_NFS3 */ /* Remember that NFSv4 manages authentication though junction crossing, and * so does it for RO FS management (for each operation) */ const nfs_function_desc_t nfs4_func_desc[] = { { .service_function = nfs_null, .free_function = nfs_null_free, .xdr_decode_func = (xdrproc_t)xdr_void, .xdr_encode_func = (xdrproc_t)xdr_void, .funcname = "NFS_NULL", .dispatch_behaviour = NOTHING_SPECIAL }, { .service_function = nfs4_Compound, .free_function = nfs4_Compound_Free, .xdr_decode_func = (xdrproc_t)xdr_COMPOUND4args, .xdr_encode_func = (xdrproc_t)xdr_COMPOUND4res_extended, .funcname = "NFS4_COMP", .dispatch_behaviour = CAN_BE_DUP } }; #ifdef _USE_NFS3 const nfs_function_desc_t mnt1_func_desc[] = { { .service_function = mnt_Null, .free_function = mnt_Null_Free, .xdr_decode_func = (xdrproc_t)xdr_void, .xdr_encode_func = (xdrproc_t)xdr_void, .funcname = "MNT_NULL", .dispatch_behaviour = NOTHING_SPECIAL }, { .service_function = mnt_Mnt, .free_function = mnt1_Mnt_Free, .xdr_decode_func = (xdrproc_t)xdr_dirpath, .xdr_encode_func = (xdrproc_t)xdr_fhstatus2, .funcname = "MNT_MNT", .dispatch_behaviour = NEEDS_CRED }, { .service_function = mnt_Dump, .free_function = mnt_Dump_Free, .xdr_decode_func = (xdrproc_t)xdr_void, .xdr_encode_func = (xdrproc_t)xdr_mountlist, .funcname = "MNT_DUMP", .dispatch_behaviour = NOTHING_SPECIAL }, { .service_function = mnt_Umnt, .free_function = mnt_Umnt_Free, .xdr_decode_func = (xdrproc_t)xdr_dirpath, .xdr_encode_func = (xdrproc_t)xdr_void, .funcname = "MNT_UMNT", .dispatch_behaviour = NOTHING_SPECIAL }, { .service_function = mnt_UmntAll, .free_function = mnt_UmntAll_Free, .xdr_decode_func = (xdrproc_t)xdr_void, .xdr_encode_func = (xdrproc_t)xdr_void, .funcname = "MNT_UMNTALL", .dispatch_behaviour = NOTHING_SPECIAL }, { .service_function = mnt_Export, .free_function = mnt_Export_Free, .xdr_decode_func = (xdrproc_t)xdr_void, .xdr_encode_func = (xdrproc_t)xdr_exports, .funcname = "MNT_EXPORT", .dispatch_behaviour = NOTHING_SPECIAL } }; const nfs_function_desc_t mnt3_func_desc[] = { { .service_function = mnt_Null, .free_function = mnt_Null_Free, .xdr_decode_func = (xdrproc_t)xdr_void, .xdr_encode_func = (xdrproc_t)xdr_void, .funcname = "MNT_NULL", .dispatch_behaviour = NOTHING_SPECIAL }, { .service_function = mnt_Mnt, .free_function = mnt3_Mnt_Free, .xdr_decode_func = (xdrproc_t)xdr_dirpath, .xdr_encode_func = (xdrproc_t)xdr_mountres3, .funcname = "MNT_MNT", .dispatch_behaviour = NOTHING_SPECIAL }, { .service_function = mnt_Dump, .free_function = mnt_Dump_Free, .xdr_decode_func = (xdrproc_t)xdr_void, .xdr_encode_func = (xdrproc_t)xdr_mountlist, .funcname = "MNT_DUMP", .dispatch_behaviour = NOTHING_SPECIAL }, { .service_function = mnt_Umnt, .free_function = mnt_Umnt_Free, .xdr_decode_func = (xdrproc_t)xdr_dirpath, .xdr_encode_func = (xdrproc_t)xdr_void, .funcname = "MNT_UMNT", .dispatch_behaviour = NOTHING_SPECIAL }, { .service_function = mnt_UmntAll, .free_function = mnt_UmntAll_Free, .xdr_decode_func = (xdrproc_t)xdr_void, .xdr_encode_func = (xdrproc_t)xdr_void, .funcname = "MNT_UMNTALL", .dispatch_behaviour = NOTHING_SPECIAL }, { .service_function = mnt_Export, .free_function = mnt_Export_Free, .xdr_decode_func = (xdrproc_t)xdr_void, .xdr_encode_func = (xdrproc_t)xdr_exports, .funcname = "MNT_EXPORT", .dispatch_behaviour = NOTHING_SPECIAL } }; #endif #ifdef _USE_NLM #define nlm4_Unsupported nlm_Null #define nlm4_Unsupported_Free nlm_Null_Free const nfs_function_desc_t nlm4_func_desc[] = { [NLMPROC4_NULL] = { .service_function = nlm_Null, .free_function = nlm_Null_Free, .xdr_decode_func = (xdrproc_t) xdr_void, .xdr_encode_func = (xdrproc_t) xdr_void, .funcname = "NLM_NULL", .dispatch_behaviour = NOTHING_SPECIAL}, [NLMPROC4_TEST] = { .service_function = nlm4_Test, .free_function = nlm4_Test_Free, .xdr_decode_func = (xdrproc_t) xdr_nlm4_testargs, .xdr_encode_func = (xdrproc_t) xdr_nlm4_testres, .funcname = "NLM4_TEST", .dispatch_behaviour = NEEDS_CRED}, [NLMPROC4_LOCK] = { .service_function = nlm4_Lock, .free_function = nlm4_Lock_Free, .xdr_decode_func = (xdrproc_t) xdr_nlm4_lockargs, .xdr_encode_func = (xdrproc_t) xdr_nlm4_res, .funcname = "NLM4_LOCK", .dispatch_behaviour = NEEDS_CRED}, [NLMPROC4_CANCEL] = { .service_function = nlm4_Cancel, .free_function = nlm4_Cancel_Free, .xdr_decode_func = (xdrproc_t) xdr_nlm4_cancargs, .xdr_encode_func = (xdrproc_t) xdr_nlm4_res, .funcname = "NLM4_CANCEL", .dispatch_behaviour = NEEDS_CRED}, [NLMPROC4_UNLOCK] = { .service_function = nlm4_Unlock, .free_function = nlm4_Unlock_Free, .xdr_decode_func = (xdrproc_t) xdr_nlm4_unlockargs, .xdr_encode_func = (xdrproc_t) xdr_nlm4_res, .funcname = "NLM4_UNLOCK", .dispatch_behaviour = NEEDS_CRED}, [NLMPROC4_GRANTED] = { .service_function = nlm4_Unsupported, .free_function = nlm4_Unsupported_Free, .xdr_decode_func = (xdrproc_t) xdr_void, .xdr_encode_func = (xdrproc_t) xdr_void, .funcname = "NLM4_GRANTED", .dispatch_behaviour = NOTHING_SPECIAL}, [NLMPROC4_TEST_MSG] = { .service_function = nlm4_Test_Message, .free_function = nlm4_Test_Free, .xdr_decode_func = (xdrproc_t) xdr_nlm4_testargs, .xdr_encode_func = (xdrproc_t) xdr_void, .funcname = "NLM4_TEST_MSG", .dispatch_behaviour = NEEDS_CRED}, [NLMPROC4_LOCK_MSG] = { .service_function = nlm4_Lock_Message, .free_function = nlm4_Lock_Free, .xdr_decode_func = (xdrproc_t) xdr_nlm4_lockargs, .xdr_encode_func = (xdrproc_t) xdr_void, .funcname = "NLM4_LOCK_MSG", .dispatch_behaviour = NEEDS_CRED}, [NLMPROC4_CANCEL_MSG] = { .service_function = nlm4_Cancel_Message, .free_function = nlm4_Cancel_Free, .xdr_decode_func = (xdrproc_t) xdr_nlm4_cancargs, .xdr_encode_func = (xdrproc_t) xdr_void, .funcname = "NLM4_CANCEL_MSG", .dispatch_behaviour = NEEDS_CRED}, [NLMPROC4_UNLOCK_MSG] = { .service_function = nlm4_Unlock_Message, .free_function = nlm4_Unlock_Free, .xdr_decode_func = (xdrproc_t) xdr_nlm4_unlockargs, .xdr_encode_func = (xdrproc_t) xdr_void, .funcname = "NLM4_UNLOCK_MSG", .dispatch_behaviour = NEEDS_CRED}, [NLMPROC4_GRANTED_MSG] = { .service_function = nlm4_Unsupported, .free_function = nlm4_Unsupported_Free, .xdr_decode_func = (xdrproc_t) xdr_void, .xdr_encode_func = (xdrproc_t) xdr_void, .funcname = "NLM4_GRANTED_MSG", .dispatch_behaviour = NOTHING_SPECIAL}, [NLMPROC4_TEST_RES] = { .service_function = nlm4_Unsupported, .free_function = nlm4_Unsupported_Free, .xdr_decode_func = (xdrproc_t) xdr_void, .xdr_encode_func = (xdrproc_t) xdr_void, .funcname = "NLM4_TEST_RES", .dispatch_behaviour = NOTHING_SPECIAL}, [NLMPROC4_LOCK_RES] = { .service_function = nlm4_Unsupported, .free_function = nlm4_Unsupported_Free, .xdr_decode_func = (xdrproc_t) xdr_void, .xdr_encode_func = (xdrproc_t) xdr_void, .funcname = "NLM4_LOCK_RES", .dispatch_behaviour = NOTHING_SPECIAL}, [NLMPROC4_CANCEL_RES] = { .service_function = nlm4_Unsupported, .free_function = nlm4_Unsupported_Free, .xdr_decode_func = (xdrproc_t) xdr_void, .xdr_encode_func = (xdrproc_t) xdr_void, .funcname = "NLM4_CANCEL_RES", .dispatch_behaviour = NOTHING_SPECIAL}, [NLMPROC4_UNLOCK_RES] = { .service_function = nlm4_Unsupported, .free_function = nlm4_Unsupported_Free, .xdr_decode_func = (xdrproc_t) xdr_void, .xdr_encode_func = (xdrproc_t) xdr_void, .funcname = "NLM4_UNLOCK_RES", .dispatch_behaviour = NOTHING_SPECIAL}, [NLMPROC4_GRANTED_RES] = { .service_function = nlm4_Granted_Res, .free_function = nlm4_Granted_Res_Free, .xdr_decode_func = (xdrproc_t) xdr_nlm4_res, .xdr_encode_func = (xdrproc_t) xdr_void, .funcname = "NLM4_GRANTED_RES", .dispatch_behaviour = NOTHING_SPECIAL}, [NLMPROC4_SM_NOTIFY] = { .service_function = nlm4_Sm_Notify, .free_function = nlm4_Sm_Notify_Free, .xdr_decode_func = (xdrproc_t) xdr_nlm4_sm_notifyargs, .xdr_encode_func = (xdrproc_t) xdr_void, .funcname = "NLM4_SM_NOTIFY", .dispatch_behaviour = NOTHING_SPECIAL}, [17] = { .service_function = nlm4_Unsupported, .free_function = nlm4_Unsupported_Free, .xdr_decode_func = (xdrproc_t) xdr_void, .xdr_encode_func = (xdrproc_t) xdr_void, .funcname = "NLM4_GRANTED_RES", .dispatch_behaviour = NOTHING_SPECIAL}, [18] = { .service_function = nlm4_Unsupported, .free_function = nlm4_Unsupported_Free, .xdr_decode_func = (xdrproc_t) xdr_void, .xdr_encode_func = (xdrproc_t) xdr_void, .funcname = "NLM4_GRANTED_RES", .dispatch_behaviour = NOTHING_SPECIAL}, [19] = { .service_function = nlm4_Unsupported, .free_function = nlm4_Unsupported_Free, .xdr_decode_func = (xdrproc_t) xdr_void, .xdr_encode_func = (xdrproc_t) xdr_void, .funcname = "NLM4_GRANTED_RES", .dispatch_behaviour = NOTHING_SPECIAL}, [NLMPROC4_SHARE] = { .service_function = nlm4_Share, .free_function = nlm4_Share_Free, .xdr_decode_func = (xdrproc_t) xdr_nlm4_shareargs, .xdr_encode_func = (xdrproc_t) xdr_nlm4_shareres, .funcname = "NLM4_SHARE", .dispatch_behaviour = NEEDS_CRED}, [NLMPROC4_UNSHARE] = { .service_function = nlm4_Unshare, .free_function = nlm4_Unshare_Free, .xdr_decode_func = (xdrproc_t) xdr_nlm4_shareargs, .xdr_encode_func = (xdrproc_t) xdr_nlm4_shareres, .funcname = "NLM4_UNSHARE", .dispatch_behaviour = NEEDS_CRED}, [NLMPROC4_NM_LOCK] = { /* NLM_NM_LOCK uses the same handling as NLM_LOCK * except for monitoring, nlm4_Lock will make * that determination. */ .service_function = nlm4_Lock, .free_function = nlm4_Lock_Free, .xdr_decode_func = (xdrproc_t) xdr_nlm4_lockargs, .xdr_encode_func = (xdrproc_t) xdr_nlm4_res, .funcname = "NLM4_NM_LOCK", .dispatch_behaviour = NEEDS_CRED}, [NLMPROC4_FREE_ALL] = { .service_function = nlm4_Free_All, .free_function = nlm4_Free_All_Free, .xdr_decode_func = (xdrproc_t) xdr_nlm4_free_allargs, .xdr_encode_func = (xdrproc_t) xdr_void, .funcname = "NLM4_FREE_ALL", .dispatch_behaviour = NOTHING_SPECIAL}, }; #endif /* _USE_NLM */ #ifdef _USE_RQUOTA const nfs_function_desc_t rquota1_func_desc[] = { [0] = { .service_function = rquota_Null, .free_function = rquota_Null_Free, .xdr_decode_func = (xdrproc_t)xdr_void, .xdr_encode_func = (xdrproc_t)xdr_void, .funcname = "RQUOTA_NULL", .dispatch_behaviour = NOTHING_SPECIAL }, [RQUOTAPROC_GETQUOTA] = { .service_function = rquota_getquota, .free_function = rquota_getquota_Free, .xdr_decode_func = (xdrproc_t)xdr_getquota_args, .xdr_encode_func = (xdrproc_t)xdr_getquota_rslt, .funcname = "RQUOTA_GETQUOTA", .dispatch_behaviour = NEEDS_CRED }, [RQUOTAPROC_GETACTIVEQUOTA] = { .service_function = rquota_getactivequota, .free_function = rquota_getactivequota_Free, .xdr_decode_func = (xdrproc_t)xdr_getquota_args, .xdr_encode_func = (xdrproc_t)xdr_getquota_rslt, .funcname = "RQUOTA_GETACTIVEQUOTA", .dispatch_behaviour = NEEDS_CRED }, [RQUOTAPROC_SETQUOTA] = { .service_function = rquota_setquota, .free_function = rquota_setquota_Free, .xdr_decode_func = (xdrproc_t)xdr_setquota_args, .xdr_encode_func = (xdrproc_t)xdr_setquota_rslt, .funcname = "RQUOTA_SETACTIVEQUOTA", .dispatch_behaviour = NEEDS_CRED }, [RQUOTAPROC_SETACTIVEQUOTA] = { .service_function = rquota_setactivequota, .free_function = rquota_setactivequota_Free, .xdr_decode_func = (xdrproc_t)xdr_setquota_args, .xdr_encode_func = (xdrproc_t)xdr_setquota_rslt, .funcname = "RQUOTA_GETACTIVEQUOTA", .dispatch_behaviour = NEEDS_CRED } }; const nfs_function_desc_t rquota2_func_desc[] = { [0] = { .service_function = rquota_Null, .free_function = rquota_Null_Free, .xdr_decode_func = (xdrproc_t)xdr_void, .xdr_encode_func = (xdrproc_t)xdr_void, .funcname = "RQUOTA_NULL", .dispatch_behaviour = NOTHING_SPECIAL }, [RQUOTAPROC_GETQUOTA] = { .service_function = rquota_getquota, .free_function = rquota_getquota_Free, .xdr_decode_func = (xdrproc_t)xdr_ext_getquota_args, .xdr_encode_func = (xdrproc_t)xdr_getquota_rslt, .funcname = "RQUOTA_EXT_GETQUOTA", .dispatch_behaviour = NEEDS_CRED }, [RQUOTAPROC_GETACTIVEQUOTA] = { .service_function = rquota_getactivequota, .free_function = rquota_getactivequota_Free, .xdr_decode_func = (xdrproc_t) xdr_ext_getquota_args, .xdr_encode_func = (xdrproc_t)xdr_getquota_rslt, .funcname = "RQUOTA_EXT_GETACTIVEQUOTA", .dispatch_behaviour = NEEDS_CRED }, [RQUOTAPROC_SETQUOTA] = { .service_function = rquota_setquota, .free_function = rquota_setquota_Free, .xdr_decode_func = (xdrproc_t)xdr_ext_setquota_args, .xdr_encode_func = (xdrproc_t)xdr_setquota_rslt, .funcname = "RQUOTA_EXT_SETACTIVEQUOTA", .dispatch_behaviour = NEEDS_CRED }, [RQUOTAPROC_SETACTIVEQUOTA] = { .service_function = rquota_setactivequota, .free_function = rquota_setactivequota_Free, .xdr_decode_func = (xdrproc_t) xdr_ext_setquota_args, .xdr_encode_func = (xdrproc_t)xdr_setquota_rslt, .funcname = "RQUOTA_EXT_GETACTIVEQUOTA", .dispatch_behaviour = NEEDS_CRED } }; #endif #ifdef USE_NFSACL3 const nfs_function_desc_t nfsacl_func_desc[] = { [0] = { .service_function = nfsacl_Null, .free_function = nfsacl_Null_Free, .xdr_decode_func = (xdrproc_t)xdr_void, .xdr_encode_func = (xdrproc_t)xdr_void, .funcname = "NFSACL_NULL", .dispatch_behaviour = NOTHING_SPECIAL }, [NFSACLPROC_GETACL] = { .service_function = nfsacl_getacl, .free_function = nfsacl_getacl_Free, .xdr_decode_func = (xdrproc_t)xdr_getaclargs, .xdr_encode_func = (xdrproc_t)xdr_getaclres, .funcname = "NFSACL_GETACL", .dispatch_behaviour = NEEDS_CRED }, [NFSACLPROC_SETACL] = { .service_function = nfsacl_setacl, .free_function = nfsacl_setacl_Free, .xdr_decode_func = (xdrproc_t)xdr_setaclargs, .xdr_encode_func = (xdrproc_t)xdr_setaclres, .funcname = "NFSACL_SETACL", .dispatch_behaviour = NEEDS_CRED } }; #endif void auth_failure(nfs_request_t *reqdata, enum auth_stat auth_rc) { svcerr_auth(&reqdata->svc, auth_rc); /* nb, a no-op when req is uncacheable */ nfs_dupreq_delete(reqdata, NFS_REQ_AUTH_ERR); } void free_args(nfs_request_t *reqdata) { const nfs_function_desc_t *reqdesc = reqdata->funcdesc; /* Free the allocated resources once the work is done */ /* Free the arguments */ if ((reqdata->svc.rq_msg.cb_vers == 2) || (reqdata->svc.rq_msg.cb_vers == 3) || (reqdata->svc.rq_msg.cb_vers == 4)) { if (!xdr_free(reqdesc->xdr_decode_func, &reqdata->arg_nfs)) { LogCrit(COMPONENT_DISPATCH, "%s FAILURE: Bad xdr_free for %s", __func__, reqdesc->funcname); } } /* Finalize the request. */ nfs_dupreq_rele(reqdata); SetClientIP(NULL); /* Clean up the op_ctx */ if (op_ctx->client != NULL) { put_gsh_client(op_ctx->client); op_ctx->client = NULL; } clean_credentials(); release_op_context(); GSH_AUTO_TRACEPOINT(nfs_rpc, end, TRACE_INFO, "Rpc end. request: {}", reqdata); } enum nfs_req_result complete_request(nfs_request_t *reqdata, enum nfs_req_result rc) { SVCXPRT *xprt = reqdata->svc.rq_xprt; const nfs_function_desc_t *reqdesc = reqdata->funcdesc; /* NFSv4 stats are handled in nfs4_compound() */ if (reqdata->svc.rq_msg.cb_prog != NFS_program[P_NFS] || reqdata->svc.rq_msg.cb_vers != NFS_V4) server_stats_nfs_done(reqdata, rc, false); /* If request is dropped, no return to the client */ if (rc == NFS_REQ_DROP) { /* The request was dropped */ LogDebug(COMPONENT_DISPATCH, "Drop request rpc_xid=%" PRIu32 ", program %" PRIu32 ", version %" PRIu32 ", function %" PRIu32, reqdata->svc.rq_msg.rm_xid, reqdata->svc.rq_msg.cb_prog, reqdata->svc.rq_msg.cb_vers, reqdata->svc.rq_msg.cb_proc); /* If the request is not normally cached, then the entry * will be removed later. We only remove a reply that is * normally cached that has been dropped. */ nfs_dupreq_delete(reqdata, rc); return rc; } LogFullDebug(COMPONENT_DISPATCH, "Before svc_sendreply on socket %d", xprt->xp_fd); reqdata->svc.rq_msg.RPCM_ack.ar_results.where = reqdata->res_nfs; reqdata->svc.rq_msg.RPCM_ack.ar_results.proc = reqdesc->xdr_encode_func; GSH_XPRT_AUTO_TRACEPOINT(nfs_rpc, before_reply, TRACE_INFO, xprt, "Before reply"); if (svc_sendreply(&reqdata->svc) >= XPRT_DIED) { LogDebug( COMPONENT_DISPATCH, "NFS DISPATCHER: FAILURE: Error while calling svc_sendreply on a new request. rpcxid=%" PRIu32 " socket=%d function:%s client:%s program:%" PRIu32 " nfs version:%" PRIu32 " proc:%" PRIu32 " errno: %d", reqdata->svc.rq_msg.rm_xid, xprt->xp_fd, reqdesc->funcname, op_ctx->client->hostaddr_str, reqdata->svc.rq_msg.cb_prog, reqdata->svc.rq_msg.cb_vers, reqdata->svc.rq_msg.cb_proc, errno); SVC_DESTROY(xprt); rc = NFS_REQ_XPRT_DIED; } LogFullDebug(COMPONENT_DISPATCH, "After svc_sendreply on socket %d", xprt->xp_fd); /* Finish any request not already deleted */ nfs_dupreq_finish(reqdata, rc); return rc; } void complete_request_instrumentation(nfs_request_t *reqdata) { GSH_AUTO_TRACEPOINT(nfs_rpc, op_end, TRACE_INFO, "Op end. request: {}", reqdata); } /** @brief Completion of async RPC dispatch * * This function is called by an RPC op handler that has previously returned * NFS_REQ_ASYNC to complete the operation and send the reply and clean up the * RPC transport. * * @param[in] reqdata The request data for the operation * @param[in] rc NFS_REQ_OK or NFS_REQ_DROP */ void nfs_rpc_complete_async_request(nfs_request_t *reqdata, enum nfs_req_result rc) { complete_request_instrumentation(reqdata); rc = complete_request(reqdata, rc); free_args(reqdata); } enum nfs_req_result process_dupreq(nfs_request_t *reqdata, const char *client_ip) { enum xprt_stat xprt_rc; /* Found the request in the dupreq cache. * Send cached reply. */ LogFullDebug( COMPONENT_DISPATCH, "DUP: DupReq Cache Hit: using previous reply, rpcxid=%" PRIu32, reqdata->svc.rq_msg.rm_xid); LogFullDebug(COMPONENT_DISPATCH, "Before svc_sendreply on socket %d (dup req)", reqdata->svc.rq_xprt->xp_fd); reqdata->svc.rq_msg.RPCM_ack.ar_results.where = reqdata->res_nfs; reqdata->svc.rq_msg.RPCM_ack.ar_results.proc = reqdata->funcdesc->xdr_encode_func; GSH_XPRT_UNIQUE_AUTO_TRACEPOINT(nfs_rpc, before_reply, TRACE_INFO, reqdata->svc.rq_xprt, "Before reply"); xprt_rc = svc_sendreply(&reqdata->svc); if (xprt_rc >= XPRT_DIED) { LogDebug( COMPONENT_DISPATCH, "NFS DISPATCHER: FAILURE: Error while calling svc_sendreply on a duplicate request. rpcxid=%" PRIu32 " socket=%d function:%s client:%s program:%" PRIu32 " nfs version:%" PRIu32 " proc:%" PRIu32 " errno: %d", reqdata->svc.rq_msg.rm_xid, reqdata->svc.rq_xprt->xp_fd, reqdata->funcdesc->funcname, client_ip, reqdata->svc.rq_msg.cb_prog, reqdata->svc.rq_msg.cb_vers, reqdata->svc.rq_msg.cb_proc, errno); svcerr_systemerr(&reqdata->svc); return NFS_REQ_XPRT_DIED; } return NFS_REQ_OK; } /** * @brief Main RPC dispatcher routine * * @param[in,out] reqdata NFS request * @param[in] retry True if a dupe request is coming back in * */ static enum xprt_stat nfs_rpc_process_request(nfs_request_t *reqdata, bool retry) { const char *client_ip = ""; const char *progname = "unknown"; const nfs_function_desc_t *reqdesc = reqdata->funcdesc; nfs_arg_t *arg_nfs = &reqdata->arg_nfs; SVCXPRT *xprt = reqdata->svc.rq_xprt; XDR *xdrs = reqdata->svc.rq_xdrs; dupreq_status_t dpq_status; enum auth_stat auth_rc = AUTH_OK; int port; int rc = NFS_REQ_OK; #ifdef _USE_NFS3 int exportid = -1; #endif /* _USE_NFS3 */ bool no_dispatch = false; if (retry) goto retry_after_drc_suspend; GSH_AUTO_TRACEPOINT(nfs_rpc, start, TRACE_INFO, "Rpc start. request: {}", reqdata); if ((xprt->xp_proxy.ss.ss_family == AF_INET || xprt->xp_proxy.ss.ss_family == AF_INET6) && !haproxy_match(xprt)) { /* We appear to have a connection via HAProxy from a proxy * that is not authorized. */ char addr[SOCK_NAME_MAX]; struct display_buffer dspbuf = { sizeof(addr), addr, addr }; (void)display_sockaddr(&dspbuf, &xprt->xp_proxy.ss); LogWarn(COMPONENT_DISPATCH, "HAProxy connection from %s rejected", addr); return svcerr_auth(&reqdata->svc, AUTH_FAILED); } LogFullDebug(COMPONENT_DISPATCH, "About to authenticate Prog=%" PRIu32 ", vers=%" PRIu32 ", proc=%" PRIu32 ", xid=%" PRIu32 ", SVCXPRT=%p, fd=%d", reqdata->svc.rq_msg.cb_prog, reqdata->svc.rq_msg.cb_vers, reqdata->svc.rq_msg.cb_proc, reqdata->svc.rq_msg.rm_xid, xprt, xprt->xp_fd); /* If authentication is AUTH_NONE or AUTH_UNIX, then the value of * no_dispatch remains false and the request proceeds normally. * * If authentication is RPCSEC_GSS, no_dispatch may have value true, * this means that gc->gc_proc != RPCSEC_GSS_DATA and that the message * is in fact an internal negotiation message from RPCSEC_GSS using * GSSAPI. It should not be processed by the worker and SVC_STAT * should be returned to the dispatcher. */ auth_rc = svc_auth_authenticate(&reqdata->svc, &no_dispatch); if (auth_rc != AUTH_OK) { LogInfo(COMPONENT_DISPATCH, "Could not authenticate request... rejecting with AUTH_STAT=%s", auth_stat2str(auth_rc)); return svcerr_auth(&reqdata->svc, auth_rc); #ifdef _HAVE_GSSAPI } else if (reqdata->svc.rq_msg.RPCM_ack.ar_verf.oa_flavor == RPCSEC_GSS) { struct rpc_gss_cred *gc = (struct rpc_gss_cred *)reqdata->svc.rq_msg.rq_cred_body; LogFullDebug(COMPONENT_DISPATCH, "RPCSEC_GSS no_dispatch=%d gc->gc_proc=(%u) %s %s", no_dispatch, gc->gc_proc, str_gc_proc(gc->gc_proc), gc->gc_svc == RPCSEC_GSS_SVC_NONE ? "krb5" : gc->gc_svc == RPCSEC_GSS_SVC_INTEGRITY ? "krb5i" : gc->gc_svc == RPCSEC_GSS_SVC_PRIVACY ? "krb5p" : "unknown"); #ifdef _USE_NFS_RDMA if (get_port(svc_getrpclocal(xprt)) == nfs_param.core_param.port[P_NFS_RDMA] && gc->gc_svc == RPCSEC_GSS_SVC_PRIVACY) { LogCrit(COMPONENT_DISPATCH, "NFS over RDMA transport is not supported for GSS flavour krb5p, failing the request !!!"); /* mount.nfs gets Protocol not * supported errors */ return svcerr_progvers(&reqdata->svc, NFS_V4, NFS_V4); } #endif if (no_dispatch) { /* In case of `RPCSEC_GSS_DATA` procedure, if we * received an AUTH_OK response with `no_dispatch` set * to true, the authentication did not actually complete * successfully. The GSS server chose to silently * discard such a request without sending any response * to the client. If we (the NFS server) also don't send * any response, the client will keep waiting * indefinitely for the response. * * RFC 2203 (section 5.3.3.1) mentions that GSS * implementation server can silently discard the * requests in certain conditions (such as this code * block), while expecting the client to time out and * eventually retry. The ntirpc codebase, that * implements the GSS protocol, honors this contract, * and sets `no_dispatch` to true, without explicitly * failing. * * However, RFC 8881 (section 2.10.6.2) mentions that an * NFS client, when working over a reliable network, * expects the NFS server to always send a response, * unless the transport connection breaks. With Ganesha * being the NFS server, it must honor the NFS contract. * Therefore, we hereby choose to explicitly disconnect * the transport connection in this scenario. * * Additionally, we add the below added metric to * measure the frequency of occurrence this scenario. */ if (gc->gc_proc == RPCSEC_GSS_DATA) { nfs_metrics__gss_request_dropped(); LogInfo(COMPONENT_DISPATCH, "Received AUTH_OK for RPCSEC_GSS_DATA procedure with no-dispatch set to true. Destroying connection."); SVC_DESTROY(reqdata->svc.rq_xprt); return XPRT_DESTROYED; } /* We already sent the GSS auth response to client * through ntirpc. */ LogFullDebug( COMPONENT_DISPATCH, "Received AUTH_OK for RPCSEC_GSS (non-DATA procedure) with no-dispatch set to true. We silently return from the function"); return SVC_STAT(xprt); } } else if (no_dispatch) { LogFullDebug(COMPONENT_DISPATCH, "RPCSEC_GSS no_dispatch=%d", no_dispatch); if (reqdata->svc.rq_msg.cb_cred.oa_flavor == RPCSEC_GSS) { struct rpc_gss_cred *gc = (struct rpc_gss_cred *) reqdata->svc.rq_msg.rq_cred_body; if (gc->gc_proc == RPCSEC_GSS_DATA) return svcerr_auth(&reqdata->svc, RPCSEC_GSS_CREDPROBLEM); } return SVC_STAT(xprt); #endif } /* * Extract RPC argument. */ LogFullDebug(COMPONENT_DISPATCH, "Before SVCAUTH_CHECKSUM on SVCXPRT %p fd %d", xprt, xprt->xp_fd); memset(arg_nfs, 0, sizeof(nfs_arg_t)); reqdata->svc.rq_msg.rm_xdr.where = arg_nfs; reqdata->svc.rq_msg.rm_xdr.proc = reqdesc->xdr_decode_func; xdrs->x_public = &reqdata->lookahead; if (!SVCAUTH_CHECKSUM(&reqdata->svc)) { LogInfo(COMPONENT_DISPATCH, "SVCAUTH_CHECKSUM failed for Program %" PRIu32 ", Version %" PRIu32 ", Function %" PRIu32 ", xid=%" PRIu32 ", SVCXPRT=%p, fd=%d", reqdata->svc.rq_msg.cb_prog, reqdata->svc.rq_msg.cb_vers, reqdata->svc.rq_msg.cb_proc, reqdata->svc.rq_msg.rm_xid, xprt, xprt->xp_fd); if (!xdr_free(reqdesc->xdr_decode_func, arg_nfs)) { LogCrit(COMPONENT_DISPATCH, "%s FAILURE: Bad xdr_free for %s", __func__, reqdesc->funcname); } return svcerr_decode(&reqdata->svc); } /* set up the request context */ init_op_context(&reqdata->op_context, NULL, NULL, reqdata, (sockaddr_t *)svc_getrpccaller(xprt), reqdata->svc.rq_msg.cb_vers, 0, NFS_REQUEST); /* Set up initial export permissions that don't allow anything. */ export_check_access(); /* start the processing clock * we measure all time stats as intervals (elapsed nsecs) from * server boot time. This gets high precision with simple 64 bit math. */ now(&op_ctx->start_time); /* Initialized user_credentials */ init_credentials(); /* XXX must hold lock when calling any TI-RPC channel function, * including svc_sendreply2 and the svcerr_* calls */ /* XXX also, need to check UDP correctness, this may need some more * TI-RPC work (for UDP, if we -really needed it-, we needed to * capture hostaddr at SVC_RECV). For TCP, if we intend to use * this, we should sprint a buffer once, in when we're setting up * xprt private data. */ op_ctx->client = get_gsh_client(op_ctx->caller_addr, false); if (op_ctx->client == NULL) { LogDebug(COMPONENT_DISPATCH, "Cannot get client block for Program %" PRIu32 ", Version %" PRIu32 ", Function %" PRIu32, reqdata->svc.rq_msg.cb_prog, reqdata->svc.rq_msg.cb_vers, reqdata->svc.rq_msg.cb_proc); } else { /* Set the Client IP for this thread */ SetClientIP(op_ctx->client->hostaddr_str); client_ip = op_ctx->client->hostaddr_str; LogDebug(COMPONENT_DISPATCH, "Request from %s for Program %" PRIu32 ", Version %" PRIu32 ", Function %" PRIu32 " has xid=%" PRIu32, client_ip, reqdata->svc.rq_msg.cb_prog, reqdata->svc.rq_msg.cb_vers, reqdata->svc.rq_msg.cb_proc, reqdata->svc.rq_msg.rm_xid); } /* If req is uncacheable, or if req is v41+, nfs_dupreq_start will do * nothing but allocate a result object and mark the request (ie, the * path is short, lockless, and does no hash/search). */ dpq_status = nfs_dupreq_start(reqdata); if (dpq_status == DUPREQ_SUCCESS) { /* A new request, continue processing it. */ LogFullDebug( COMPONENT_DISPATCH, "Current request is not duplicate or not cacheable."); } else { switch (dpq_status) { case DUPREQ_EXISTS: (void)process_dupreq(reqdata, client_ip); break; /* Another thread owns the request */ case DUPREQ_BEING_PROCESSED: LogFullDebug( COMPONENT_DISPATCH, "Suspending DUP: Request xid=%" PRIu32 " is already being processed; the active thread will reply", reqdata->svc.rq_msg.rm_xid); /* The request is suspended, don't touch the request in * any way because the resume may already be scheduled * and running on nother thread. The xp_resume_cb has * already been set up before we started processing * ops on this request at all. */ suspend_op_context(); return XPRT_SUSPEND; case DUPREQ_DROP: LogFullDebug( COMPONENT_DISPATCH, "DUP: Request xid=%" PRIu32 " is already being processed and has too many dupes queued", reqdata->svc.rq_msg.rm_xid); /* Free the arguments */ /* Ignore the request, send no error */ break; default: LogCrit(COMPONENT_DISPATCH, "DUP: Unknown duplicate request cache status. This should never be reached!"); svcerr_systemerr(&reqdata->svc); break; } server_stats_nfs_done(reqdata, rc, true); goto freeargs; } retry_after_drc_suspend: /* If we come here on a retry after drc suspend, then we already did * the stuff above. */ /* We need the port below. */ port = get_port(op_ctx->caller_addr); /* Don't waste time for null or invalid ops * null op code in all valid protos == 0 * and invalid protos all point to invalid_funcdesc * NFS v2 is set to invalid_funcdesc in nfs_rpc_get_funcdesc() */ if (reqdesc == &invalid_funcdesc || reqdata->svc.rq_msg.cb_proc == NFSPROC_NULL) goto null_op; /* Get the export entry */ if (reqdata->svc.rq_msg.cb_prog == NFS_program[P_NFS] #ifdef USE_NFSACL3 || reqdata->svc.rq_msg.cb_prog == NFS_program[P_NFSACL] #endif ) { /* The NFSv3 functions' arguments always begin with the file * handle (but not the NULL function). This hook is used to * get the fhandle with the arguments and so determine the * export entry to be used. In NFSv4, junction traversal * is managed by the protocol. */ progname = "NFS"; #ifdef _USE_NFS3 if (reqdata->svc.rq_msg.cb_vers == NFS_V3) { exportid = nfs3_FhandleToExportId((nfs_fh3 *)arg_nfs); if (exportid < 0) { LogInfo(COMPONENT_DISPATCH, "NFS3 Request from client %s has badly formed handle", client_ip); /* Bad handle, report to client */ reqdata->res_nfs->res_getattr3.status = NFS3ERR_BADHANDLE; rc = NFS_REQ_OK; goto req_error; } set_op_context_export(get_gsh_export(exportid)); if (op_ctx->ctx_export == NULL) { LogInfoAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "NFS3 Request from client %s has invalid export %d", client_ip, exportid); /* Bad export, report to client */ reqdata->res_nfs->res_getattr3.status = NFS3ERR_STALE; rc = NFS_REQ_OK; goto req_error; } LogMidDebugAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "Found export entry for path=%s as exportid=%d", op_ctx_export_path(op_ctx), op_ctx->ctx_export->export_id); } #endif /* _USE_NFS3 */ /* NFS V4 gets its own export id from the ops * in the compound */ #ifdef _USE_NLM } else if (reqdata->svc.rq_msg.cb_prog == NFS_program[P_NLM]) { netobj *pfh3 = NULL; progname = "NLM"; switch (reqdata->svc.rq_msg.cb_proc) { case NLMPROC4_NULL: /* caught above and short circuited */ case NLMPROC4_TEST_RES: case NLMPROC4_LOCK_RES: case NLMPROC4_CANCEL_RES: case NLMPROC4_UNLOCK_RES: case NLMPROC4_GRANTED_RES: case NLMPROC4_SM_NOTIFY: case NLMPROC4_FREE_ALL: break; case NLMPROC4_TEST: case NLMPROC4_TEST_MSG: case NLMPROC4_GRANTED: case NLMPROC4_GRANTED_MSG: pfh3 = &arg_nfs->arg_nlm4_test.alock.fh; break; case NLMPROC4_LOCK: case NLMPROC4_LOCK_MSG: case NLMPROC4_NM_LOCK: pfh3 = &arg_nfs->arg_nlm4_lock.alock.fh; break; case NLMPROC4_CANCEL: case NLMPROC4_CANCEL_MSG: pfh3 = &arg_nfs->arg_nlm4_cancel.alock.fh; break; case NLMPROC4_UNLOCK: case NLMPROC4_UNLOCK_MSG: pfh3 = &arg_nfs->arg_nlm4_unlock.alock.fh; break; case NLMPROC4_SHARE: case NLMPROC4_UNSHARE: pfh3 = &arg_nfs->arg_nlm4_share.share.fh; break; } if (pfh3 != NULL) { exportid = nlm4_FhandleToExportId(pfh3); if (exportid < 0) { /* We need to send a NLM4_STALE_FH response * (NLM doesn't have an error code for * BADHANDLE), but we don't know how to do that * here, we will send a NULL pexport to NLM * routine to let it know what to do since it * can respond to ASYNC calls. */ LogInfo(COMPONENT_DISPATCH, "NLM4 Request from client %s has badly formed handle", client_ip); } else { set_op_context_export(get_gsh_export(exportid)); if (op_ctx->ctx_export == NULL) { LogInfoAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "NLM4 Request from client %s has invalid export %d", client_ip, exportid); /* We need to send a NLM4_STALE_FH * response (NLM doesn't have an error * code for BADHANDLE), but we don't * know how to do that here, we will * send a NULL pexport to NLM routine * to let it know what to do since it * can respond to ASYNC calls. */ } else { LogMidDebugAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "Found export entry for dirname=%s as exportid=%d", ctx_export_path(op_ctx), op_ctx->ctx_export->export_id); } } } #endif /* _USE_NLM */ #ifdef _USE_NFS3 } else if (reqdata->svc.rq_msg.cb_prog == NFS_program[P_MNT]) { progname = "MNT"; #endif /* _USE_NFS3 */ } /* Only do access check if we have an export. */ if (op_ctx->ctx_export != NULL) { /* We ONLY get here for NFS v3 or NLM requests with a handle */ xprt_type_t xprt_type = svc_get_xprt_type(xprt); LogMidDebugAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "%s about to call nfs_export_check_access for client %s", __func__, client_ip); export_check_access(); if ((op_ctx->export_perms.options & EXPORT_OPTION_ACCESS_MASK) == 0) { LogInfoAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "Client %s is not allowed to access Export_Id %d %s, vers=%" PRIu32 ", proc=%" PRIu32, client_ip, op_ctx->ctx_export->export_id, op_ctx_export_path(op_ctx), reqdata->svc.rq_msg.cb_vers, reqdata->svc.rq_msg.cb_proc); auth_failure(reqdata, AUTH_TOOWEAK); goto freeargs; } if ((op_ctx->export_perms.options & EXPORT_OPTION_NFSV3) == 0) { LogInfoAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "%s Version %" PRIu32 " not allowed on Export_Id %d %s for client %s", progname, reqdata->svc.rq_msg.cb_vers, op_ctx->ctx_export->export_id, op_ctx_export_path(op_ctx), client_ip); auth_failure(reqdata, AUTH_FAILED); goto freeargs; } /* Check transport type */ if (((xprt_type == XPRT_UDP) && ((op_ctx->export_perms.options & EXPORT_OPTION_UDP) == 0)) || ((xprt_type == XPRT_TCP) && ((op_ctx->export_perms.options & EXPORT_OPTION_TCP) == 0))) { LogInfoAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "%s Version %" PRIu32 " over %s not allowed on Export_Id %d %s for client %s", progname, reqdata->svc.rq_msg.cb_vers, xprt_type_to_str(xprt_type), op_ctx->ctx_export->export_id, op_ctx_export_path(op_ctx), client_ip); auth_failure(reqdata, AUTH_FAILED); goto freeargs; } /* Test if export allows the authentication provided */ if ((reqdesc->dispatch_behaviour & SUPPORTS_GSS) && !export_check_security(&reqdata->svc)) { LogInfoAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "%s Version %" PRIu32 " auth not allowed on Export_Id %d %s for client %s", progname, reqdata->svc.rq_msg.cb_vers, op_ctx->ctx_export->export_id, op_ctx_export_path(op_ctx), client_ip); auth_failure(reqdata, AUTH_TOOWEAK); goto freeargs; } /* Check if client is using a privileged port, * but only for NFS protocol */ if ((reqdata->svc.rq_msg.cb_prog == NFS_program[P_NFS]) && (op_ctx->export_perms.options & EXPORT_OPTION_PRIVILEGED_PORT) && (port >= IPPORT_RESERVED)) { LogInfoAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "Non-reserved Port %d is not allowed on Export_Id %d %s for client %s", port, op_ctx->ctx_export->export_id, op_ctx_export_path(op_ctx), client_ip); auth_failure(reqdata, AUTH_TOOWEAK); goto freeargs; } } /* * It is now time for checking if export list allows the machine * to perform the request */ if (op_ctx->ctx_export != NULL && (reqdesc->dispatch_behaviour & MAKES_IO) && !(op_ctx->export_perms.options & EXPORT_OPTION_RW_ACCESS)) { /* Request of type MDONLY_RO were rejected at the * nfs_rpc_dispatcher level. * This is done by replying EDQUOT * (this error is known for not disturbing * the client's requests cache) */ if (reqdata->svc.rq_msg.cb_prog == NFS_program[P_NFS]) switch (reqdata->svc.rq_msg.cb_vers) { #ifdef _USE_NFS3 case NFS_V3: LogDebugAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "Returning NFS3ERR_DQUOT because request is on an MD Only export"); reqdata->res_nfs->res_getattr3.status = NFS3ERR_DQUOT; rc = NFS_REQ_OK; break; #endif /* _USE_NFS3 */ default: LogDebugAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "Dropping IO request on an MD Only export"); rc = NFS_REQ_DROP; break; } else { LogDebugAlt(COMPONENT_DISPATCH, COMPONENT_EXPORT, "Dropping IO request on an MD Only export"); rc = NFS_REQ_DROP; } } else if (op_ctx->ctx_export != NULL && (reqdesc->dispatch_behaviour & MAKES_WRITE) && (op_ctx->export_perms.options & (EXPORT_OPTION_WRITE_ACCESS | EXPORT_OPTION_MD_WRITE_ACCESS)) == 0) { if (reqdata->svc.rq_msg.cb_prog == NFS_program[P_NFS]) switch (reqdata->svc.rq_msg.cb_vers) { #ifdef _USE_NFS3 case NFS_V3: LogDebugAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "Returning NFS3ERR_ROFS because request is on a Read Only export"); reqdata->res_nfs->res_getattr3.status = NFS3ERR_ROFS; rc = NFS_REQ_OK; break; #endif /* _USE_NFS3 */ default: LogDebugAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "Dropping request on a Read Only export"); rc = NFS_REQ_DROP; break; } else { LogDebugAlt(COMPONENT_DISPATCH, COMPONENT_EXPORT, "Dropping request on a Read Only export"); rc = NFS_REQ_DROP; } } else if (op_ctx->ctx_export != NULL && (op_ctx->export_perms.options & (EXPORT_OPTION_READ_ACCESS | EXPORT_OPTION_MD_READ_ACCESS)) == 0) { LogInfoAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "Client %s is not allowed to access Export_Id %d %s, vers=%" PRIu32 ", proc=%" PRIu32, client_ip, op_ctx->ctx_export->export_id, op_ctx_export_path(op_ctx), reqdata->svc.rq_msg.cb_vers, reqdata->svc.rq_msg.cb_proc); auth_failure(reqdata, AUTH_TOOWEAK); goto freeargs; } else { /* Get user credentials */ if (reqdesc->dispatch_behaviour & NEEDS_CRED) { /* If we don't have an export, don't squash */ if (op_ctx->fsal_export == NULL) { op_ctx->export_perms.options &= ~EXPORT_OPTION_SQUASH_TYPES; } else if (nfs_req_creds(&reqdata->svc) != NFS4_OK) { LogInfoAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "could not get uid and gid, rejecting client %s", client_ip); auth_failure(reqdata, AUTH_TOOWEAK); goto freeargs; } } /* processing * At this point, op_ctx->ctx_export has one of the following * conditions: * non-NULL - valid handle for NFS v3 or NLM functions * that take handles * NULL - For NULL RPC calls * NULL - for RQUOTAD calls * NULL - for NFS v4 COMPOUND call * NULL - for MOUNT calls * NULL - for NLM calls where handle is bad, NLM must handle * response in the case of async "MSG" calls, so we * just defer to NLM routines to respond with * NLM4_STALE_FH (NLM doesn't have a BADHANDLE code) */ #ifdef _ERROR_INJECTION if (worker_delay_time != 0) sleep(worker_delay_time); else if (next_worker_delay_time != 0) { sleep(next_worker_delay_time); next_worker_delay_time = 0; } #endif null_op: GSH_AUTO_TRACEPOINT( nfs_rpc, op_start, TRACE_INFO, "Op start. request: {}, func: {}, export_id: {}", reqdata, TP_STR(reqdesc->funcname), (op_ctx->ctx_export != NULL ? op_ctx->ctx_export->export_id : -1)); rc = reqdesc->service_function(arg_nfs, &reqdata->svc, reqdata->res_nfs); if (rc == NFS_REQ_ASYNC_WAIT) { /* The request is suspended, don't touch the request in * any way because the resume may already be scheduled * and running on nother thread. The xp_resume_cb has * already been set up before we started processing * ops on this request at all. */ suspend_op_context(); return XPRT_SUSPEND; } complete_request_instrumentation(reqdata); } #ifdef _USE_NFS3 req_error: #endif /* _USE_NFS3 */ rc = complete_request(reqdata, rc); freeargs: free_args(reqdata); /* Make sure no-one called init_op_context() without calling * release_op_context(). This must be true in part to assure that * suspend_op_context() and resume_op_context() work properly, but * also so that when ntirpc makes another call on the same thread, * op_ctx doesn't point to memory that has likely been freed or even * re-allocated. */ assert(op_ctx == NULL); /* Make sure we return to ntirpc without op_ctx set, or saved_op_ctx can * point to freed memory */ op_ctx = NULL; return SVC_STAT(xprt); } /** @brief Resume a duplicate request * * When a duplicate request comes in while still processing the original request * we suspend it, and queue it in the dupreq_entry_t. Now the original request * has been finished and we are ready to respond to the duplicate request. * */ enum xprt_stat drc_resume(struct svc_req *req) { nfs_request_t *reqdata = container_of(req, nfs_request_t, svc); int rc = NFS_REQ_OK; /* Restore the op_ctx */ resume_op_context(&reqdata->op_context); /* Get the result of sending the response. Note that we are ONLY here * if this request came in as a dupe while we were processing the * original reqyest and it has now completed. So either we just sent a * response and there is no need to respond to this dupe OR something * went wrong that warrants further attention. */ rc = nfs_dupreq_reply_rc(reqdata); server_stats_nfs_done(reqdata, rc, true); switch (rc) { case NFS_REQ_OK: case NFS_REQ_ERROR: /* In these cases, we don't need to respond */ LogFullDebug(COMPONENT_DISPATCH, "Suspended DUP: Request xid=%" PRIu32 " was processed and replied to", reqdata->svc.rq_msg.rm_xid); break; case NFS_REQ_DROP: /* The original request was dropped for some reason, we actually * need to process now... Resubmit this request. */ return nfs_rpc_process_request(reqdata, true); case NFS_REQ_REPLAY: case NFS_REQ_ASYNC_WAIT: /* These cases can never happen... */ break; case NFS_REQ_XPRT_DIED: /* The attempt to send a response failed because the XPRT died * so now we need to actually resend the response here. */ rc = process_dupreq(reqdata, op_ctx->client != NULL ? op_ctx->client->hostaddr_str : ""); /* And now we need to try and finish this request. If we had * another XPRT_DIED and there's yet another duplicate request * queued, we will be back around with that request shortly... */ nfs_dupreq_finish(reqdata, rc); break; case NFS_REQ_AUTH_ERR: /* The original request failed due to an auth error. This retry * will probably also similarly fail, but we will re-submit * anyway. */ return nfs_rpc_process_request(reqdata, true); } free_args(reqdata); /* Make sure no-one called init_op_context() without calling * release_op_context() */ assert(op_ctx == NULL); /* Make sure we return to ntirpc without op_ctx set, or saved_op_ctx can * point to freed memory */ op_ctx = NULL; return SVC_STAT(reqdata->svc.rq_xprt); } /** * @brief Report Invalid Program number * * @param[in] reqnfs NFS request * */ static enum xprt_stat nfs_rpc_noprog(nfs_request_t *reqdata) { LogFullDebug(COMPONENT_DISPATCH, "Invalid Program number %" PRIu32, reqdata->svc.rq_msg.cb_prog); return svcerr_noprog(&reqdata->svc); } /** * @brief Report Invalid protocol Version * * @param[in] reqnfs NFS request * */ static enum xprt_stat nfs_rpc_novers(nfs_request_t *reqdata, int lo_vers, int hi_vers) { LogFullDebug(COMPONENT_DISPATCH, "Invalid protocol Version %" PRIu32 " for Program number %" PRIu32, reqdata->svc.rq_msg.cb_vers, reqdata->svc.rq_msg.cb_prog); return svcerr_progvers(&reqdata->svc, lo_vers, hi_vers); } /** * @brief Report Invalid Procedure * * @param[in] reqnfs NFS request * */ static enum xprt_stat nfs_rpc_noproc(nfs_request_t *reqdata) { LogFullDebug(COMPONENT_DISPATCH, "Invalid Procedure %" PRIu32 " in protocol Version %" PRIu32 " for Program number %" PRIu32, reqdata->svc.rq_msg.cb_proc, reqdata->svc.rq_msg.cb_vers, reqdata->svc.rq_msg.cb_prog); return svcerr_noproc(&reqdata->svc); } /** * @brief Validate rpc calls, extract nfs function descriptor. * * Validate the rpc call program, version, and procedure within range. * Send svcerr_* reply on errors. * * Choose the function descriptor, either a valid one or the default * invalid handler. * * @param[in,out] req service request * * @return whether the request is valid. */ enum xprt_stat nfs_rpc_valid_NFS(struct svc_req *req) { nfs_request_t *reqdata = container_of(req, struct nfs_request, svc); int lo_vers; int hi_vers; GSH_AUTO_TRACEPOINT( nfs_rpc, valid, TRACE_INFO, "Valid nfs req. xprt_ptr: {}, cb_prog: {}, cb_vers: {} cb_proc: {}", reqdata->svc.rq_xprt, req->rq_msg.cb_prog, req->rq_msg.cb_vers, reqdata->svc.rq_msg.cb_proc); reqdata->funcdesc = &invalid_funcdesc; #ifdef USE_NFSACL3 if (req->rq_msg.cb_prog == NFS_program[P_NFSACL]) { if (req->rq_msg.cb_vers == NFSACL_V3 && CORE_OPTION_NFSV3) { if (req->rq_msg.cb_proc <= NFSACLPROC_SETACL) { reqdata->funcdesc = &nfsacl_func_desc[req->rq_msg.cb_proc]; return nfs_rpc_process_request(reqdata, false); } } } #endif /* USE_NFSACL3 */ if (req->rq_msg.cb_prog != NFS_program[P_NFS]) { return nfs_rpc_noprog(reqdata); } if (req->rq_msg.cb_vers == NFS_V4 && NFS_options & CORE_OPTION_NFSV4) { if (req->rq_msg.cb_proc <= NFSPROC4_COMPOUND) { reqdata->funcdesc = &nfs4_func_desc[req->rq_msg.cb_proc]; return nfs_rpc_process_request(reqdata, false); } return nfs_rpc_noproc(reqdata); } #ifdef _USE_NFS3 if (req->rq_msg.cb_vers == NFS_V3 && NFS_options & CORE_OPTION_NFSV3) { if (req->rq_msg.cb_proc <= NFSPROC3_COMMIT) { reqdata->funcdesc = &nfs3_func_desc[req->rq_msg.cb_proc]; return nfs_rpc_process_request(reqdata, false); } return nfs_rpc_noproc(reqdata); } #endif /* _USE_NFS3 */ /* Unsupported version! Set low and high versions correctly */ if (NFS_options & CORE_OPTION_NFSV4) hi_vers = NFS_V4; else hi_vers = NFS_V3; #ifdef _USE_NFS3 if (NFS_options & CORE_OPTION_NFSV3) lo_vers = NFS_V3; else lo_vers = NFS_V4; #else lo_vers = NFS_V4; #endif return nfs_rpc_novers(reqdata, lo_vers, hi_vers); } #ifdef _USE_NLM enum xprt_stat nfs_rpc_valid_NLM(struct svc_req *req) { nfs_request_t *reqdata = container_of(req, struct nfs_request, svc); reqdata->funcdesc = &invalid_funcdesc; if (req->rq_msg.cb_prog == NFS_program[P_NLM] && (NFS_options & CORE_OPTION_NFSV3)) { if (req->rq_msg.cb_vers == NLM4_VERS) { if (req->rq_msg.cb_proc <= NLMPROC4_FREE_ALL) { reqdata->funcdesc = &nlm4_func_desc[req->rq_msg.cb_proc]; return nfs_rpc_process_request(reqdata, false); } return nfs_rpc_noproc(reqdata); } return nfs_rpc_novers(reqdata, NLM4_VERS, NLM4_VERS); } return nfs_rpc_noprog(reqdata); } #endif /* _USE_NLM */ #ifdef _USE_NFS3 enum xprt_stat nfs_rpc_valid_MNT(struct svc_req *req) { nfs_request_t *reqdata = container_of(req, struct nfs_request, svc); reqdata->funcdesc = &invalid_funcdesc; if (req->rq_msg.cb_prog == NFS_program[P_MNT] && (NFS_options & CORE_OPTION_NFSV3)) { reqdata->lookahead.flags |= NFS_LOOKAHEAD_MOUNT; /* Some clients may use the wrong mount version to * umount, so always allow umount. Otherwise, only allow * request if the appropriate mount version is enabled. * Also need to allow dump and export, so just disallow * mount if version not supported. */ if (req->rq_msg.cb_vers == MOUNT_V3) { if (req->rq_msg.cb_proc <= MOUNTPROC3_EXPORT) { reqdata->funcdesc = &mnt3_func_desc[req->rq_msg.cb_proc]; return nfs_rpc_process_request(reqdata, false); } return nfs_rpc_noproc(reqdata); } if (req->rq_msg.cb_vers == MOUNT_V1) { if (req->rq_msg.cb_proc <= MOUNTPROC2_EXPORT && req->rq_msg.cb_proc != MOUNTPROC2_MNT) { reqdata->funcdesc = &mnt1_func_desc[req->rq_msg.cb_proc]; return nfs_rpc_process_request(reqdata, false); } return nfs_rpc_noproc(reqdata); } return nfs_rpc_novers(reqdata, MOUNT_V1, MOUNT_V3); } return nfs_rpc_noprog(reqdata); } #endif #ifdef _USE_RQUOTA enum xprt_stat nfs_rpc_valid_RQUOTA(struct svc_req *req) { nfs_request_t *reqdata = container_of(req, struct nfs_request, svc); reqdata->funcdesc = &invalid_funcdesc; if (req->rq_msg.cb_prog == NFS_program[P_RQUOTA]) { if (req->rq_msg.cb_vers == EXT_RQUOTAVERS) { if (req->rq_msg.cb_proc <= RQUOTAPROC_SETACTIVEQUOTA) { reqdata->funcdesc = &rquota2_func_desc[req->rq_msg.cb_proc]; return nfs_rpc_process_request(reqdata, false); } return nfs_rpc_noproc(reqdata); } if (req->rq_msg.cb_vers == RQUOTAVERS) { if (req->rq_msg.cb_proc <= RQUOTAPROC_SETACTIVEQUOTA) { reqdata->funcdesc = &rquota1_func_desc[req->rq_msg.cb_proc]; return nfs_rpc_process_request(reqdata, false); } return nfs_rpc_noproc(reqdata); } return nfs_rpc_novers(reqdata, RQUOTAVERS, EXT_RQUOTAVERS); } return nfs_rpc_noprog(reqdata); } #endif #ifdef USE_NFSACL3 enum xprt_stat nfs_rpc_valid_NFSACL(struct svc_req *req) { nfs_request_t *reqdata = container_of(req, struct nfs_request, svc); reqdata->funcdesc = &invalid_funcdesc; if (req->rq_msg.cb_prog == NFS_program[P_NFSACL]) { if (req->rq_msg.cb_vers == NFSACL_V3) { if (req->rq_msg.cb_proc <= NFSACLPROC_SETACL) { reqdata->funcdesc = &nfsacl_func_desc[req->rq_msg.cb_proc]; return nfs_rpc_process_request(reqdata, false); } return nfs_rpc_noproc(reqdata); } return nfs_rpc_novers(reqdata, NFSACL_V3, NFSACL_V3); } return nfs_rpc_noprog(reqdata); } #endif #ifdef _USE_NFS_RDMA enum xprt_stat nfs_rpc_valid_NFS_RDMA(struct svc_req *req) { nfs_request_t *reqdata = container_of(req, struct nfs_request, svc); SVCXPRT *xprt = reqdata->svc.rq_xprt; /* NFS over RDMA transport is supported by default for NFSv4.0 Only, * fail NFSv3 unless configured in NFS_RDMA_Protocol_Versions. * For NFSv4+ we could validate this once we decode the compound, hence * logic done inside nfs4_Compound() */ if (get_port(svc_getrpclocal(xprt)) == nfs_param.core_param.port[P_NFS_RDMA] && reqdata->svc.rq_msg.cb_vers == NFS_V3 && !(nfs_param.core_param.nfs_rdma_supported_protocol_versions & NFS_RDMA_ENABLE_FOR_NFSV3)) { LogCrit(COMPONENT_DISPATCH, "NFS over RDMA transport is not supported for NFSv3, failing the request !!!"); /* mount.nfs gets Protocol not supported errors */ return svcerr_progvers(&reqdata->svc, NFS_V4, NFS_V4); } return nfs_rpc_valid_NFS(req); } #endif nfs-ganesha-6.5/src/Protocols/000077500000000000000000000000001473756622300163505ustar00rootroot00000000000000nfs-ganesha-6.5/src/Protocols/.gitignore000066400000000000000000000000171473756622300203360ustar00rootroot00000000000000test_mnt_proto nfs-ganesha-6.5/src/Protocols/9P/000077500000000000000000000000001473756622300166405ustar00rootroot00000000000000nfs-ganesha-6.5/src/Protocols/9P/9P_Structures.txt000066400000000000000000000102111473756622300221270ustar00rootroot00000000000000 /* Structures for Protocol Operations */ struct _9p_rlerror { u32 ecode; }; struct _9p_tstatfs { u32 fid; }; struct _9p_rstatfs { u32 type; u32 bsize; u64 blocks; u64 bfree; u64 bavail; u64 files; u64 ffree; u64 fsid; u32 namelen; }; struct _9p_tlopen { u32 fid; u32 flags; }; struct _9p_rlopen { struct _9p_qid qid; u32 iounit; }; struct _9p_tlcreate { u32 fid; struct _9p_str name; u32 flags; u32 mode; u32 gid; }; struct _9p_rlcreate { struct _9p_qid qid; u32 iounit; }; struct _9p_tsymlink { u32 fid; struct _9p_str name; struct _9p_str symtgt; u32 gid; }; struct _9p_rsymlink { struct _9p_qid qid; }; struct _9p_tmknod { u32 fid; struct _9p_str name; u32 mode; u32 major; u32 minor; u32 gid; }; struct _9p_rmknod { struct _9p_qid qid; }; struct _9p_trename { u32 fid; u32 dfid; struct _9p_str name; }; struct _9p_rrename { }; struct _9p_treadlink { u32 fid; }; struct _9p_rreadlink { struct _9p_str target; }; struct _9p_tgetattr { u32 fid; u64 request_mask; }; struct _9p_rgetattr { u64 valid; struct _9p_qid qid; u32 mode; u32 uid; u32 gid; u64 nlink; u64 rdev; u64 size; u64 blksize; u64 blocks; u64 atime_sec; u64 atime_nsec; u64 mtime_sec; u64 mtime_nsec; u64 ctime_sec; u64 ctime_nsec; u64 btime_sec; u64 btime_nsec; u64 gen; u64 data_version; }; struct _9p_tsetattr { u32 fid; u32 valid; u32 mode; u32 uid; u32 gid; u64 size; u64 atime_sec; u64 atime_nsec; u64 mtime_sec; u64 mtime_nsec; }; struct _9p_rsetattr { }; struct _9p_txattrwalk { u32 fid; u32 attrfid; struct _9p_str name; }; struct _9p_rxattrwalk { u64 size; }; struct _9p_txattrcreate { u32 fid; struct _9p_str name; u64 size; u32 flag; }; struct _9p_rxattrcreate { }; struct _9p_treaddir { u32 fid; u64 offset; u32 count; }; struct _9p_rreaddir { u32 count; u8 *data; }; struct _9p_tfsync { u32 fid; }; struct _9p_rfsync { }; struct _9p_tlock { u32 fid; u8 type; u32 flags; u64 start; u64 length; u32 proc_id; struct _9p_str client_id; }; struct _9p_rlock { u8 status; }; struct _9p_tgetlock { u32 fid; u8 type; u64 start; u64 length; u32 proc_id; struct _9p_str client_id; }; struct _9p_rgetlock { u8 type; u64 start; u64 length; u32 proc_id; struct _9p_str client_id; }; struct _9p_tlink { u32 dfid; u32 fid; struct _9p_str name; }; struct _9p_rlink { }; struct _9p_tmkdir { u32 fid; struct _9p_str name; u32 mode; u32 gid; }; struct _9p_rmkdir { struct _9p_qid qid; }; struct _9p_trenameat { u32 olddirfid; struct _9p_str oldname; u32 newdirfid; struct _9p_str newname; }; struct _9p_rrenameat { }; struct _9p_tunlinkat { u32 dirfid; struct _9p_str name; u32 flags; }; struct _9p_runlinkat { }; struct _9p_tawrite { u32 fid; u8 datacheck; u64 offset; u32 count; u32 rsize; u8 *data; u32 check; }; struct _9p_rawrite { u32 count; }; struct _9p_tversion { u32 msize ; struct _9p_str version ; }; struct _9p_rversion { u32 msize; struct _9p_str version; }; struct _9p_tauth { u32 afid; struct _9p_str uname; struct _9p_str aname; u32 n_uname; /* 9P2000.u extensions */ }; struct _9p_rauth { struct _9p_qid qid; }; struct _9p_rerror { struct _9p_str error; u32 errnum; /* 9p2000.u extension */ }; struct _9p_tflush { u16 oldtag; }; struct _9p_rflush { }; struct _9p_tattach { u32 fid; u32 afid; struct _9p_str uname; struct _9p_str aname; u32 n_uname; /* 9P2000.u extensions */ }; struct _9p_rattach { struct _9p_qid qid; }; struct _9p_twalk { u32 fid; u32 newfid; u16 nwname; struct _9p_str wnames[_9P_MAXWELEM]; }; struct _9p_rwalk { u16 nwqid; struct _9p_qid wqids[_9P_MAXWELEM]; }; struct _9p_topen { u32 fid; u8 mode; }; struct _9p_ropen { struct _9p_qid qid; u32 iounit; }; struct _9p_tcreate { u32 fid; struct _9p_str name; u32 perm; u8 mode; struct _9p_str extension; }; struct _9p_rcreate { struct _9p_qid qid; u32 iounit; }; struct _9p_tread { u32 fid; u64 offset; u32 count; }; struct _9p_rread { u32 count; u8 *data; }; struct _9p_twrite { u32 fid; u64 offset; u32 count; u8 *data; }; struct _9p_rwrite { u32 count; }; struct _9p_tclunk { u32 fid; }; struct _9p_rclunk { }; struct _9p_tremove { u32 fid; }; struct _9p_rremove { }; union _9p_tmsg { } ; nfs-ganesha-6.5/src/Protocols/9P/9p_attach.c000066400000000000000000000142371473756622300206670ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_attach.c * \brief 9P version * * 9p_attach.c : _9P_interpretor, request ATTACH * * */ #include "config.h" #include #include #include #include "nfs_core.h" #include "export_mgr.h" #include "log.h" #include "fsal.h" #include "nfs_exports.h" #include "9p.h" int _9p_attach(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *fid = NULL; u32 *afid = NULL; u16 *uname_len = NULL; char *uname_str = NULL; u16 *aname_len = NULL; char *aname_str = NULL; u32 *n_uname = NULL; u32 err = 0; struct _9p_fid *pfid = NULL; fsal_status_t fsal_status; char exppath[MAXPATHLEN + 1]; int port; struct gsh_export *export; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); _9p_getptr(cursor, afid, u32); _9p_getstr(cursor, uname_len, uname_str); _9p_getstr(cursor, aname_len, aname_str); _9p_getptr(cursor, n_uname, u32); LogDebug( COMPONENT_9P, "TATTACH: tag=%u fid=%u afid=%d uname='%.*s' aname='%.*s' n_uname=%d", (u32)*msgtag, *fid, *afid, (int)*uname_len, uname_str, (int)*aname_len, aname_str, *n_uname); if (*fid >= _9P_FID_PER_CONN) { err = ERANGE; goto errout; } /* * Find the export for the aname (using as well Path or Tag) * * Keep it in the op_ctx. */ if (*aname_len >= sizeof(exppath)) { err = ENAMETOOLONG; goto errout; } _9p_get_fname(exppath, *aname_len, aname_str); /* Find the export for the dirname (using as well Path, Pseudo, or Tag) */ if (exppath[0] != '/') { LogFullDebug(COMPONENT_9P, "Searching for export by tag for %s", exppath); export = get_gsh_export_by_tag(exppath); } else if (nfs_param.core_param.mount_path_pseudo) { LogFullDebug(COMPONENT_9P, "Searching for export by pseudo for %s", exppath); export = get_gsh_export_by_pseudo(exppath, false); } else { LogFullDebug(COMPONENT_9P, "Searching for export by path for %s", exppath); export = get_gsh_export_by_path(exppath, false); } /* Did we find something ? */ if (export == NULL) { err = ENOENT; goto errout; } /* Fill in more of the op_ctx */ set_op_context_export(export); op_ctx->caller_addr = &req9p->pconn->addrpeer; /* check export_perms. */ export_check_access(); if ((op_ctx->export_perms.options & EXPORT_OPTION_9P) == 0) { LogInfo(COMPONENT_9P, "9P is not allowed for this export entry, rejecting client"); err = EACCES; goto errout; } port = get_port(&req9p->pconn->addrpeer); if (op_ctx->export_perms.options & EXPORT_OPTION_PRIVILEGED_PORT && port >= IPPORT_RESERVED) { LogInfo(COMPONENT_9P, "Port %d is too high for this export entry, rejecting client", port); err = EACCES; goto errout; } /* Set export and fid id in fid */ pfid = gsh_calloc(1, sizeof(struct _9p_fid)); /* Copy the export into the pfid with reference. */ pfid->fid_export = op_ctx->ctx_export; get_gsh_export_ref(pfid->fid_export); pfid->fid = *fid; req9p->pconn->fids[*fid] = pfid; /* Is user name provided as a string or as an uid ? */ if (*n_uname != _9P_NONUNAME) { /* Build the fid creds */ err = _9p_tools_get_req_context_by_uid(*n_uname, pfid); if (err != 0) { err = -err; goto errout; } } else if (*uname_len != 0) { /* Build the fid creds */ err = _9p_tools_get_req_context_by_name(*uname_len, uname_str, pfid); if (err != 0) { err = -err; goto errout; } } else { /* No n_uname nor uname */ err = EINVAL; goto errout; } if (exppath[0] != '/') { /* The client used the Tag. Use the export root object is * correctly set, fetch it, and take an LRU reference. */ fsal_status = nfs_export_get_root_entry(op_ctx->ctx_export, &pfid->pentry); } else { /* Note that we call this even if exppath is just the path to * the export. It resolves that efficiently. */ fsal_status = fsal_lookup_path(exppath, &pfid->pentry); } if (FSAL_IS_ERROR(fsal_status)) { err = _9p_tools_errno(fsal_status); goto errout; } /* Initialize state_t embedded in fid. The refcount is initialized * to one to represent the state_t being embedded in the fid. This * prevents it from ever being reduced to zero by dec_state_t_ref. */ pfid->state = op_ctx->fsal_export->exp_ops.alloc_state( op_ctx->fsal_export, STATE_TYPE_9P_FID, NULL); glist_init(&pfid->state->state_data.fid.state_locklist); pfid->state->state_refcount = 1; /* Compute the qid */ pfid->qid.type = _9P_QTDIR; pfid->qid.version = 0; /* No cache, we want the client * to stay synchronous with the server */ pfid->qid.path = pfid->pentry->fileid; pfid->xattr = NULL; /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RATTACH); _9p_setptr(cursor, msgtag, u16); _9p_setqid(cursor, pfid->qid); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug(COMPONENT_9P, "RATTACH: tag=%u fid=%u qid=(type=%u,version=%u,path=%llu)", *msgtag, *fid, (u32)pfid->qid.type, pfid->qid.version, (unsigned long long)pfid->qid.path); return 1; errout: _9p_release_opctx(); if (pfid != NULL) free_fid(pfid); return _9p_rerror(req9p, msgtag, err, plenout, preply); } nfs-ganesha-6.5/src/Protocols/9P/9p_auth.c000066400000000000000000000043041473756622300203560ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_attach.c * \brief 9P version * * 9p_attach.c : _9P_interpretor, request ATTACH * * */ #include "config.h" #include #include #include #include "nfs_core.h" #include "export_mgr.h" #include "log.h" #include "fsal.h" #include "9p.h" int _9p_auth(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *afid = NULL; u16 *uname_len = NULL; char *uname_str = NULL; u16 *aname_len = NULL; char *aname_str = NULL; u32 *n_aname = NULL; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, afid, u32); _9p_getstr(cursor, uname_len, uname_str); _9p_getstr(cursor, aname_len, aname_str); _9p_getptr(cursor, n_aname, u32); LogDebug(COMPONENT_9P, "TAUTH: tag=%u afid=%d uname='%.*s' aname='%.*s' n_uname=%d", (u32)*msgtag, *afid, (int)*uname_len, uname_str, (int)*aname_len, aname_str, *n_aname); if (*afid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); /* This message is not implemented yet, return ENOTSUPP */ return _9p_rerror(req9p, msgtag, EOPNOTSUPP, plenout, preply); } nfs-ganesha-6.5/src/Protocols/9P/9p_clunk.c000066400000000000000000000045761473756622300205440ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_clunk.c * \brief 9P version * * 9p_clunk.c : _9P_interpretor, request ATTACH * * */ #include "config.h" #include #include #include #include "nfs_core.h" #include "log.h" #include "9p.h" int _9p_clunk(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *fid = NULL; int rc; struct _9p_fid *pfid = NULL; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); LogDebug(COMPONENT_9P, "TCLUNK: tag=%u fid=%u", (u32)*msgtag, *fid); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Check that it is a valid fid */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "clunk request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(pfid, req9p); rc = _9p_tools_clunk(pfid); req9p->pconn->fids[*fid] = NULL; if (rc) { return _9p_rerror(req9p, msgtag, rc, plenout, preply); } /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RCLUNK); _9p_setptr(cursor, msgtag, u16); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug(COMPONENT_9P, "RCLUNK: tag=%u fid=%u", (u32)*msgtag, *fid); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_flush.c000066400000000000000000000037471473756622300205500ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_flush.c * \brief 9P version * * 9p_flush.c : _9P_interpretor, request ATTACH * * */ #include "config.h" #include #include #include #include "nfs_core.h" #include "log.h" #include "fsal.h" #include "9p.h" int _9p_flush(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u16 *oldtag = NULL; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, oldtag, u16); LogDebug(COMPONENT_9P, "TFLUSH: tag=%u oldtag=%u", (u32)*msgtag, (u32)*oldtag); _9p_FlushFlushHook(req9p->pconn, (int)*oldtag, req9p->flush_hook.sequence); /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RFLUSH); _9p_setptr(cursor, msgtag, u16); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug(COMPONENT_9P, "RFLUSH: tag=%u oldtag=%u", (u32)*msgtag, (u32)*oldtag); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_flush_hook.c000066400000000000000000000100111473756622300215460ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_flush_hook.c * \date $Date: 2006/02/23 12:33:05 $ * \brief The file that contain the routines dedicated to TFLUSH management * * 9p_flush_hook.c.c : routines for TFLUSH management. * */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include #include #include #include #include #include #include "hashtable.h" #include "log.h" #include "abstract_mem.h" #include "abstract_atomic.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_dupreq.h" #include "nfs_file_handle.h" #include "9p.h" struct flush_condition { pthread_cond_t flc_condition; int reply_sent; }; void _9p_AddFlushHook(struct _9p_request_data *req, int tag, unsigned long sequence) { int bucket = tag % FLUSH_BUCKETS; struct _9p_flush_hook *hook = &req->flush_hook; struct _9p_conn *conn = req->pconn; hook->tag = tag; hook->condition = NULL; hook->sequence = sequence; PTHREAD_MUTEX_lock(&conn->flush_buckets[bucket].flb_lock); glist_add(&conn->flush_buckets[bucket].list, &hook->list); PTHREAD_MUTEX_unlock(&conn->flush_buckets[bucket].flb_lock); } void _9p_FlushFlushHook(struct _9p_conn *conn, int tag, unsigned long sequence) { int bucket = tag % FLUSH_BUCKETS; struct glist_head *node; struct _9p_flush_hook *hook = NULL; struct flush_condition fc; PTHREAD_MUTEX_lock(&conn->flush_buckets[bucket].flb_lock); glist_for_each(node, &conn->flush_buckets[bucket].list) { hook = glist_entry(node, struct _9p_flush_hook, list); /* Cancel a request that has the right tag * --AND-- is older than the flush request. **/ if ((hook->tag == tag) && (hook->sequence < sequence)) { PTHREAD_COND_init(&fc.flc_condition, NULL); fc.reply_sent = 0; hook->condition = &fc; glist_del(&hook->list); LogFullDebug(COMPONENT_9P, "Found tag to flush %d\n", tag); /* * Now, wait until the request is complete * so we can send the RFLUSH. * warning: this will unlock the bucket lock */ while (!fc.reply_sent) PTHREAD_COND_wait( &fc.flc_condition, &conn->flush_buckets[bucket].flb_lock); hook->condition = NULL; PTHREAD_COND_destroy(&fc.flc_condition); break; } } PTHREAD_MUTEX_unlock(&conn->flush_buckets[bucket].flb_lock); } void _9p_DiscardFlushHook(struct _9p_request_data *req) { struct _9p_flush_hook *hook = &req->flush_hook; struct _9p_conn *conn = req->pconn; int bucket = hook->tag % FLUSH_BUCKETS; PTHREAD_MUTEX_lock(&conn->flush_buckets[bucket].flb_lock); /* If no flush request arrived, we have to * remove the hook from the list. * If a flush request arrived, signal the thread that is waiting */ if (hook->condition == NULL) glist_del(&hook->list); else { hook->condition->reply_sent = 1; PTHREAD_COND_signal(&hook->condition->flc_condition); } PTHREAD_MUTEX_unlock(&conn->flush_buckets[bucket].flb_lock); } nfs-ganesha-6.5/src/Protocols/9P/9p_fsync.c000066400000000000000000000051021473756622300205340ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_fsync.c * * 9p_fsync.c : _9P_interpretor, request FSYNC * * */ #include "config.h" #include #include #include #include #include "nfs_core.h" #include "log.h" #include "fsal.h" #include "9p.h" int _9p_fsync(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *fid = NULL; struct _9p_fid *pfid = NULL; fsal_status_t fsal_status; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); LogDebug(COMPONENT_9P, "TFSYNC: tag=%u fid=%u", (u32)*msgtag, *fid); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Check that it is a valid open file */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(pfid, req9p); fsal_status = fsal_commit(pfid->pentry, 0LL, /* start at beginning of file */ 0LL); /* Mimic sync_file_range's behavior: */ /* count=0 means "whole file" */ if (FSAL_IS_ERROR(fsal_status)) return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RFSYNC); _9p_setptr(cursor, msgtag, u16); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug(COMPONENT_9P, "RFSYNC: tag=%u fid=%u", (u32)*msgtag, *fid); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_getattr.c000066400000000000000000000151611473756622300210720ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_getattr.c * \brief 9P version * * 9p_getattr.c : _9P_interpretor, request ATTACH * * */ #include "config.h" #include #include #include #include #include "nfs_core.h" #include "log.h" #include "export_mgr.h" #include "fsal.h" #include "9p.h" int _9p_getattr(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *fid = NULL; u64 *request_mask = NULL; fsal_status_t fsal_status; struct fsal_attrlist attrs; struct _9p_fid *pfid = NULL; u64 valid = 0LL; /* Not a pointer */ u32 mode = 0; /* Not a pointer */ u32 uid = 0; u32 gid = 0; u64 nlink = 0LL; u64 rdev = 0LL; u64 size = 0LL; u64 blksize = 0LL; /* Be careful, this one is no pointer */ u64 blocks = 0LL; /* And so does this one... */ u64 atime_sec = 0LL; u64 atime_nsec = 0LL; u64 mtime_sec = 0LL; u64 mtime_nsec = 0LL; u64 ctime_sec = 0LL; u64 ctime_nsec = 0LL; u64 btime_sec = 0LL; u64 btime_nsec = 0LL; u64 gen = 0LL; u64 data_version = 0LL; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); _9p_getptr(cursor, request_mask, u64); LogDebug(COMPONENT_9P, "TGETATTR: tag=%u fid=%u request_mask=0x%llx", (u32)*msgtag, *fid, (unsigned long long)*request_mask); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Check that it is a valid fid */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(pfid, req9p); fsal_prepare_attrs(&attrs, ATTRS_NFS3); fsal_status = pfid->pentry->obj_ops->getattrs(pfid->pentry, &attrs); if (FSAL_IS_ERROR(fsal_status)) { LogDebug(COMPONENT_9P, "fsal_refresh_attrs failed %s", fsal_err_txt(fsal_status)); /* Done with the attrs */ fsal_release_attrs(&attrs); return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); } /* Attach point is found, build the requested attributes */ valid = _9P_GETATTR_BASIC; /* FSAL covers all basic attributes */ if (*request_mask & _9P_GETATTR_RDEV) { mode = (u32)attrs.mode; if (attrs.type == DIRECTORY) mode |= __S_IFDIR; if (attrs.type == REGULAR_FILE) mode |= __S_IFREG; if (attrs.type == SYMBOLIC_LINK) mode |= __S_IFLNK; if (attrs.type == SOCKET_FILE) mode |= __S_IFSOCK; if (attrs.type == BLOCK_FILE) mode |= __S_IFBLK; if (attrs.type == CHARACTER_FILE) mode |= __S_IFCHR; if (attrs.type == FIFO_FILE) mode |= __S_IFIFO; } else mode = 0; uid = (*request_mask & _9P_GETATTR_UID) ? (u32)attrs.owner : 0; gid = (*request_mask & _9P_GETATTR_GID) ? (u32)attrs.group : 0; nlink = (*request_mask & _9P_GETATTR_NLINK) ? (u64)attrs.numlinks : 0LL; /* rdev = (*request_mask & _9P_GETATTR_RDEV) ? * (u64) attrs.rawdev.major : * 0LL; */ rdev = (*request_mask & _9P_GETATTR_RDEV) ? (u64)pfid->fid_export->filesystem_id.major : 0LL; size = (*request_mask & _9P_GETATTR_SIZE) ? (u64)attrs.filesize : 0LL; blksize = (*request_mask & _9P_GETATTR_BLOCKS) ? (u64)_9P_BLK_SIZE : 0LL; blocks = (*request_mask & _9P_GETATTR_BLOCKS) ? (u64)(attrs.filesize / DEV_BSIZE) : 0LL; atime_sec = (*request_mask & _9P_GETATTR_ATIME) ? (u64)attrs.atime.tv_sec : 0LL; atime_nsec = (*request_mask & _9P_GETATTR_ATIME) ? (u64)attrs.atime.tv_nsec : 0LL; mtime_sec = (*request_mask & _9P_GETATTR_MTIME) ? (u64)attrs.mtime.tv_sec : 0LL; mtime_nsec = (*request_mask & _9P_GETATTR_MTIME) ? (u64)attrs.mtime.tv_nsec : 0LL; ctime_sec = (*request_mask & _9P_GETATTR_CTIME) ? (u64)attrs.ctime.tv_sec : 0LL; ctime_nsec = (*request_mask & _9P_GETATTR_CTIME) ? (u64)attrs.ctime.tv_nsec : 0LL; /* Not yet supported attributes */ btime_sec = 0LL; btime_nsec = 0LL; gen = 0LL; data_version = 0LL; /* Done with the attrs */ fsal_release_attrs(&attrs); /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RGETATTR); _9p_setptr(cursor, msgtag, u16); _9p_setvalue(cursor, valid, u64); _9p_setqid(cursor, pfid->qid); _9p_setvalue(cursor, mode, u32); _9p_setvalue(cursor, uid, u32); _9p_setvalue(cursor, gid, u32); _9p_setvalue(cursor, nlink, u64); _9p_setvalue(cursor, rdev, u64); _9p_setvalue(cursor, size, u64); _9p_setvalue(cursor, blksize, u64); _9p_setvalue(cursor, blocks, u64); _9p_setvalue(cursor, atime_sec, u64); _9p_setvalue(cursor, atime_nsec, u64); _9p_setvalue(cursor, mtime_sec, u64); _9p_setvalue(cursor, mtime_nsec, u64); _9p_setvalue(cursor, ctime_sec, u64); _9p_setvalue(cursor, ctime_nsec, u64); _9p_setvalue(cursor, btime_sec, u64); _9p_setvalue(cursor, btime_nsec, u64); _9p_setvalue(cursor, gen, u64); _9p_setvalue(cursor, data_version, u64); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug(COMPONENT_9P, "RGETATTR: tag=%u valid=0x%" PRIx64 "qid=(type=%u,version=%u, path=%" PRIu64 ") mode=0%o uid=%u gid=%u nlink=%" PRIu64 " rdev=%" PRIu64 " size=%" PRIu64 " blksize=%" PRIu64 " blocks=%" PRIu64 " atime=(%" PRIu64 ",%" PRIu64 ") mtime=(%" PRIu64 ",%" PRIu64 ") ctime=(%" PRIu64 ",%" PRIu64 ") btime=(%" PRIu64 ",%" PRIu64 ") gen=%" PRIu64 ", data_version=%" PRIu64, *msgtag, valid, pfid->qid.type, pfid->qid.version, pfid->qid.path, mode, uid, gid, nlink, rdev, size, blksize, blocks, atime_sec, atime_nsec, mtime_sec, mtime_nsec, ctime_sec, ctime_nsec, btime_sec, btime_nsec, gen, data_version); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_getlock.c000066400000000000000000000060601473756622300210460ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_getlock.c * \brief 9P version * * 9p_getlock.c : _9P_interpretor, request GETLOCK * * */ #include "config.h" #include #include #include #include #include "nfs_core.h" #include "abstract_mem.h" #include "log.h" #include "sal_functions.h" #include "fsal.h" #include "9p.h" int _9p_getlock(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *fid = NULL; u8 *type = NULL; u64 *start = NULL; u64 *length = NULL; u32 *proc_id = NULL; u16 *client_id_len = NULL; char *client_id_str = NULL; /* struct _9p_fid * pfid = NULL ; */ /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); _9p_getptr(cursor, type, u8); _9p_getptr(cursor, start, u64); _9p_getptr(cursor, length, u64); _9p_getptr(cursor, proc_id, u32); _9p_getstr(cursor, client_id_len, client_id_str); LogDebug( COMPONENT_9P, "TGETLOCK: tag=%u fid=%u type=%u start=%llu length=%llu proc_id=%u client=%.*s", (u32)*msgtag, *fid, *type, (unsigned long long)*start, (unsigned long long)*length, *proc_id, *client_id_len, client_id_str); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); /* pfid = req9p->pconn->fids[*fid] ; */ /** @todo This function does nothing for the moment. * Make it compliant with fcntl( F_GETLCK, ... */ /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RGETLOCK); _9p_setptr(cursor, msgtag, u16); _9p_setptr(cursor, type, u8); _9p_setptr(cursor, start, u64); _9p_setptr(cursor, length, u64); _9p_setptr(cursor, proc_id, u32); _9p_setstr(cursor, *client_id_len, client_id_str); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug( COMPONENT_9P, "RGETLOCK: tag=%u fid=%u type=%u start=%llu length=%llu proc_id=%u client=%.*s", (u32)*msgtag, *fid, *type, (unsigned long long)*start, (unsigned long long)*length, *proc_id, *client_id_len, client_id_str); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_interpreter.c000066400000000000000000000141751473756622300217670ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file 9p_interpreter.c * @brief 9P interpreter */ #include "config.h" #include #include #include #include "nfs_core.h" #include "9p.h" #include "fsal.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_dupreq.h" #include "nfs_file_handle.h" #include "server_stats.h" /* opcode to function array */ const struct _9p_function_desc _9pfuncdesc[] = { [0] = { _9p_not_2000L, "no function" }, /* out of bounds */ [_9P_TSTATFS] = { _9p_statfs, "_9P_TSTATFS" }, [_9P_TLOPEN] = { _9p_lopen, "_9P_TLOPEN" }, [_9P_TLCREATE] = { _9p_lcreate, "_9P_TLCREATE" }, [_9P_TSYMLINK] = { _9p_symlink, "_9P_TSYMLINK" }, [_9P_TMKNOD] = { _9p_mknod, "_9P_TMKNOD" }, [_9P_TRENAME] = { _9p_rename, "_9P_TRENAME" }, [_9P_TREADLINK] = { _9p_readlink, "_9P_TREADLINK" }, [_9P_TGETATTR] = { _9p_getattr, "_9P_TGETATTR" }, [_9P_TSETATTR] = { _9p_setattr, "_9P_TSETATTR" }, [_9P_TXATTRWALK] = { _9p_xattrwalk, "_9P_TXATTRWALK" }, [_9P_TXATTRCREATE] = { _9p_xattrcreate, "_9P_TXATTRCREATE" }, [_9P_TREADDIR] = { _9p_readdir, "_9P_TREADDIR" }, [_9P_TFSYNC] = { _9p_fsync, "_9P_TFSYNC" }, [_9P_TLOCK] = { _9p_lock, "_9P_TLOCK" }, [_9P_TGETLOCK] = { _9p_getlock, "_9P_TGETLOCK" }, [_9P_TLINK] = { _9p_link, "_9P_TLINK" }, [_9P_TMKDIR] = { _9p_mkdir, "_9P_TMKDIR" }, [_9P_TRENAMEAT] = { _9p_renameat, "_9P_TRENAMEAT" }, [_9P_TUNLINKAT] = { _9p_unlinkat, "_9P_TUNLINKAT" }, [_9P_TVERSION] = { _9p_version, "_9P_TVERSION" }, [_9P_TAUTH] = { _9p_auth, "_9P_TAUTH" }, [_9P_TATTACH] = { _9p_attach, "_9P_TATTACH" }, [_9P_TFLUSH] = { _9p_flush, "_9P_TFLUSH" }, [_9P_TWALK] = { _9p_walk, "_9P_TWALK" }, [_9P_TOPEN] = { _9p_not_2000L, "_9P_TOPEN" }, [_9P_TCREATE] = { _9p_not_2000L, "_9P_TCREATE" }, [_9P_TREAD] = { _9p_read, "_9P_TREAD" }, [_9P_TWRITE] = { _9p_write, "_9P_TWRITE" }, [_9P_TCLUNK] = { _9p_clunk, "_9P_TCLUNK" }, [_9P_TREMOVE] = { _9p_remove, "_9P_TREMOVE" }, [_9P_TSTAT] = { _9p_not_2000L, "_9P_TSTAT" }, [_9P_TWSTAT] = { _9p_not_2000L, "_9P_TWSTAT" } }; int _9p_not_2000L(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *msgdata = req9p->_9pmsg + _9P_HDR_SIZE; u8 msgtype = 0; u16 msgtag = 0; char *funcname = "inval"; /* Get message's type */ msgtype = *(u8 *)msgdata; /* TWSTAT is the last element in func_desc array, make sure * we don't lookup past it */ if (msgtype <= _9P_TWSTAT) funcname = _9pfuncdesc[msgtype].funcname; LogEvent(COMPONENT_9P, "(%u|%s) is not a 9P2000.L message, returning ENOTSUP", msgtype, funcname); _9p_rerror(req9p, &msgtag, ENOTSUP, plenout, preply); return -1; } /* _9p_not_2000L */ static ssize_t tcp_conn_send(struct _9p_conn *conn, const void *buf, size_t len, int flags) { ssize_t ret; PTHREAD_MUTEX_lock(&conn->sock_lock); ret = send(conn->trans_data.sockfd, buf, len, flags); PTHREAD_MUTEX_unlock(&conn->sock_lock); if (ret < 0) server_stats_transport_done(conn->client, 0, 0, 0, 0, 0, 1); else server_stats_transport_done(conn->client, 0, 0, 0, ret, 1, 0); return ret; } void _9p_tcp_process_request(struct _9p_request_data *req9p) { u32 outdatalen = 0; int rc = 0; char replydata[_9P_MSG_SIZE]; rc = _9p_process_buffer(req9p, replydata, &outdatalen); if (rc != 1) { LogMajor(COMPONENT_9P, "Could not process 9P buffer on socket #%lu", req9p->pconn->trans_data.sockfd); } else { if (tcp_conn_send(req9p->pconn, replydata, outdatalen, 0) != outdatalen) LogMajor( COMPONENT_9P, "Could not send 9P/TCP reply correctly on socket #%lu", req9p->pconn->trans_data.sockfd); } _9p_DiscardFlushHook(req9p); } /* _9p_process_request */ int _9p_process_buffer(struct _9p_request_data *req9p, char *replydata, u32 *poutlen) { char *msgdata; u32 msglen; u8 msgtype; int rc = 0; msgdata = req9p->_9pmsg; /* Get message's length */ msglen = *(u32 *)msgdata; msgdata += _9P_HDR_SIZE; /* Get message's type */ msgtype = *(u8 *)msgdata; msgdata += _9P_TYPE_SIZE; /* Check boundaries. 0 is no_function fallback */ if (msgtype < _9P_TSTATFS || msgtype > _9P_TWSTAT || _9pfuncdesc[msgtype].service_function == NULL) msgtype = 0; LogFullDebug(COMPONENT_9P, "9P msg: length=%u type (%u|%s)", msglen, (u32)msgtype, _9pfuncdesc[msgtype].funcname); /* Temporarily set outlen to maximum message size. This value will be * used inside the protocol functions for additional bound checking, * and then replaced by the actual message size, (see _9p_checkbound()) */ *poutlen = req9p->pconn->msize; /* Call the 9P service function */ rc = _9pfuncdesc[msgtype].service_function(req9p, poutlen, replydata); /* Record 9P statistics */ server_stats_9p_done(msgtype, req9p); _9p_release_opctx(); if (rc < 0) LogDebug(COMPONENT_9P, "%s: Error", _9pfuncdesc[msgtype].funcname); /** * @todo ops stats accounting goes here. * service function return codes need to be reworked to return error code * properly so that internal error code (currently -1) is distinguished * from protocol op error, currently partially handled in rerror, and * success return here so we can count errors and totals properly. * I/O stats handled in read and write as in nfs. */ return rc; } nfs-ganesha-6.5/src/Protocols/9P/9p_lcreate.c000066400000000000000000000117411473756622300210370ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_lcreate.c * \brief 9P version * * 9p_lcreate.c : _9P_interpretor, request LCREATE * * */ #include "config.h" #include #include #include #include #include "nfs_core.h" #include "nfs_exports.h" #include "log.h" #include "fsal.h" #include "9p.h" int _9p_lcreate(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *fid = NULL; u32 *flags = NULL; u32 *mode = NULL; u32 *gid = NULL; u16 *name_len = NULL; char *name_str = NULL; struct _9p_fid *pfid = NULL; struct _9p_qid qid_newfile; u32 iounit = _9P_IOUNIT; struct fsal_obj_handle *pentry_newfile = NULL; char file_name[MAXNAMLEN + 1]; fsal_status_t fsal_status; fsal_openflags_t openflags = 0; struct fsal_attrlist sattr; fsal_verifier_t verifier; enum fsal_create_mode createmode = FSAL_UNCHECKED; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); _9p_getstr(cursor, name_len, name_str); _9p_getptr(cursor, flags, u32); _9p_getptr(cursor, mode, u32); _9p_getptr(cursor, gid, u32); LogDebug(COMPONENT_9P, "TLCREATE: tag=%u fid=%u name=%.*s flags=0%o mode=0%o gid=%u", (u32)*msgtag, *fid, *name_len, name_str, *flags, *mode, *gid); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Check that it is a valid fid */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(pfid, req9p); if ((op_ctx->export_perms.options & EXPORT_OPTION_WRITE_ACCESS) == 0) return _9p_rerror(req9p, msgtag, EROFS, plenout, preply); if (*name_len >= sizeof(file_name)) { LogDebug(COMPONENT_9P, "request with name too long (%u)", *name_len); return _9p_rerror(req9p, msgtag, ENAMETOOLONG, plenout, preply); } _9p_get_fname(file_name, *name_len, name_str); _9p_openflags2FSAL(flags, &openflags); pfid->state->state_data.fid.share_access = _9p_openflags_to_share_access(flags); memset(&verifier, 0, sizeof(verifier)); memset(&sattr, 0, sizeof(sattr)); sattr.valid_mask = ATTR_MODE | ATTR_GROUP; sattr.mode = *mode; sattr.group = *gid; if (*flags & 0x10) { /* Filesize is already 0. */ sattr.valid_mask |= ATTR_SIZE; } if (*flags & 0x1000) { /* If OEXCL, use FSAL_EXCLUSIVE_9P create mode * so that we can pass the attributes specified * above. Verifier is ignored for this create mode * because we don't have to deal with retry. */ createmode = FSAL_EXCLUSIVE_9P; } fsal_status = fsal_open2(pfid->pentry, pfid->state, openflags, createmode, file_name, &sattr, verifier, &pentry_newfile, NULL, NULL, NULL); /* Release the attributes (may release an inherited ACL) */ fsal_release_attrs(&sattr); if (FSAL_IS_ERROR(fsal_status)) return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); /* put parent directory entry */ pfid->pentry->obj_ops->put_ref(pfid->pentry); /* Build the qid */ qid_newfile.type = _9P_QTFILE; qid_newfile.version = 0; qid_newfile.path = pentry_newfile->fileid; /* The fid will represent the new file now - we can't fail anymore */ pfid->pentry = pentry_newfile; pfid->qid = qid_newfile; pfid->xattr = NULL; pfid->opens = 1; /* Get a active reference */ pfid->ppentry->obj_ops->get_ref(pfid->ppentry); /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RLCREATE); _9p_setptr(cursor, msgtag, u16); _9p_setqid(cursor, qid_newfile); _9p_setvalue(cursor, iounit, u32); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug( COMPONENT_9P, "RLCREATE: tag=%u fid=%u name=%.*s qid=(type=%u,version=%u,path=%llu) iounit=%u pentry=%p", (u32)*msgtag, *fid, *name_len, name_str, qid_newfile.type, qid_newfile.version, (unsigned long long)qid_newfile.path, iounit, pfid->pentry); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_link.c000066400000000000000000000077411473756622300203620ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_link.c * \brief 9P version * * 9p_link.c : _9P_interpretor, request LINK * * */ #include "config.h" #include #include #include #include #include "nfs_core.h" #include "nfs_exports.h" #include "log.h" #include "fsal.h" #include "9p.h" int _9p_link(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *dfid = NULL; u32 *targetfid = NULL; u16 *name_len = NULL; char *name_str = NULL; struct _9p_fid *pdfid = NULL; struct _9p_fid *ptargetfid = NULL; fsal_status_t fsal_status; char link_name[MAXNAMLEN + 1]; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, dfid, u32); _9p_getptr(cursor, targetfid, u32); _9p_getstr(cursor, name_len, name_str); LogDebug(COMPONENT_9P, "TLINK: tag=%u dfid=%u targetfid=%u name=%.*s", (u32)*msgtag, *dfid, *targetfid, *name_len, name_str); if (*dfid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pdfid = req9p->pconn->fids[*dfid]; /* Check that it is a valid fid */ if (pdfid == NULL || pdfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid dfid=%u", *dfid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(pdfid, req9p); if ((op_ctx->export_perms.options & EXPORT_OPTION_WRITE_ACCESS) == 0) return _9p_rerror(req9p, msgtag, EROFS, plenout, preply); if (*targetfid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); ptargetfid = req9p->pconn->fids[*targetfid]; /* Check that it is a valid fid */ if (ptargetfid == NULL || ptargetfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid targetfid=%u", *targetfid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } /* Check that pfid and pdfid are in the same export. */ if (ptargetfid->fid_export != NULL && pdfid->fid_export != NULL && ptargetfid->fid_export->export_id != pdfid->fid_export->export_id) { LogDebug(COMPONENT_9P, "request on targetfid=%u and dfid=%u crosses exports", *targetfid, *dfid); return _9p_rerror(req9p, msgtag, EXDEV, plenout, preply); } /* Let's do the job */ if (*name_len >= sizeof(link_name)) { LogDebug(COMPONENT_9P, "request with name too long (%u)", *name_len); return _9p_rerror(req9p, msgtag, ENAMETOOLONG, plenout, preply); } _9p_get_fname(link_name, *name_len, name_str); fsal_status = fsal_link(ptargetfid->pentry, pdfid->pentry, link_name, NULL, NULL); if (FSAL_IS_ERROR(fsal_status)) return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RLINK); _9p_setptr(cursor, msgtag, u16); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug(COMPONENT_9P, "TLINK: tag=%u dfid=%u targetfid=%u name=%.*s", (u32)*msgtag, *dfid, *targetfid, *name_len, name_str); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_lock.c000066400000000000000000000143501473756622300203470ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_lock.c * \brief 9P version * * 9p_lock.c : _9P_interpretor, request LOCK * * */ #include "config.h" #include #include #include #include #include #include "nfs_core.h" #include "log.h" #include "export_mgr.h" #include "sal_functions.h" #include "fsal.h" #include "9p.h" /* * Reminder: * LOCK_TYPE_RDLCK = 0 * LOCK_TYPE_WRLCK = 1 * LOCK_TYPE_UNLCK = 2 */ char *strtype[] = { "RDLOCK", "WRLOCK", "UNLOCK" }; /* * Reminder: * LOCK_SUCCESS = 0 * LOCK_BLOCKED = 1 * LOCK_ERROR = 2 * LOCK_GRACE = 3 */ char *strstatus[] = { "SUCCESS", "BLOCKED", "ERROR", "GRACE" }; int _9p_lock(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *fid = NULL; u8 *type = NULL; u32 *flags = NULL; u64 *start = NULL; u64 *length = NULL; u32 *proc_id = NULL; u16 *client_id_len = NULL; char *client_id_str = NULL; fsal_openflags_t openflags; u8 status = _9P_LOCK_SUCCESS; state_status_t state_status = STATE_SUCCESS; state_owner_t *powner; fsal_lock_param_t lock; memset(&lock, 0, sizeof(lock)); char name[MAXNAMLEN + 1]; struct addrinfo hints, *result; sockaddr_t client_addr; struct _9p_fid *pfid = NULL; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); _9p_getptr(cursor, type, u8); _9p_getptr(cursor, flags, u32); _9p_getptr(cursor, start, u64); _9p_getptr(cursor, length, u64); _9p_getptr(cursor, proc_id, u32); _9p_getstr(cursor, client_id_len, client_id_str); LogDebug( COMPONENT_9P, "TLOCK: tag=%u fid=%u type=%u|%s flags=0x%x start=%llu length=%llu proc_id=%u client=%.*s", (u32)*msgtag, *fid, *type, strtype[*type], *flags, (unsigned long long)*start, (unsigned long long)*length, *proc_id, *client_id_len, client_id_str); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Check that it is a valid fid */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(pfid, req9p); /* Tmp hook to avoid lock issue when compiling kernels. * This should not impact ONE client only * get the client's ip addr */ if (*client_id_len >= sizeof(name)) { LogDebug(COMPONENT_9P, "request with name too long (%u)", *client_id_len); return _9p_rerror(req9p, msgtag, ENAMETOOLONG, plenout, preply); } _9p_get_fname(name, *client_id_len, client_id_str); memset((char *)&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_INET; if (getaddrinfo(name, NULL, &hints, &result)) return _9p_rerror(req9p, msgtag, EINVAL, plenout, preply); memcpy((char *)&client_addr, (char *)result->ai_addr, result->ai_addrlen); /* variable result is not needed anymore, let's free it */ freeaddrinfo(result); powner = get_9p_owner(&client_addr, *proc_id); if (powner == NULL) return _9p_rerror(req9p, msgtag, EINVAL, plenout, preply); /* Do the job */ switch (*type) { case _9P_LOCK_TYPE_RDLCK: case _9P_LOCK_TYPE_WRLCK: /* Fill in plock */ lock.lock_type = (*type == _9P_LOCK_TYPE_WRLCK) ? FSAL_LOCK_W : FSAL_LOCK_R; lock.lock_start = *start; lock.lock_length = *length; if (!nfs_get_grace_status(false)) { status = _9P_LOCK_GRACE; break; } STATELOCK_lock(pfid->pentry); /* Test that the file is open in the right mode for the lock */ openflags = pfid->pentry->obj_ops->status2(pfid->pentry, pfid->state); if ((*type == _9P_LOCK_TYPE_RDLCK && ((openflags & FSAL_O_READ) == 0)) || (*type == _9P_LOCK_TYPE_WRLCK && ((openflags & FSAL_O_WRITE) == 0))) { /* Open in wrong mode - return error */ STATELOCK_unlock(pfid->pentry); nfs_put_grace_status(); status = _9P_LOCK_ERROR; break; } state_status = state_lock(pfid->pentry, powner, pfid->state, STATE_NON_BLOCKING, LOCK_9P, NULL, &lock, NULL, NULL); STATELOCK_unlock(pfid->pentry); nfs_put_grace_status(); if (state_status == STATE_SUCCESS) status = _9P_LOCK_SUCCESS; else if (state_status == STATE_LOCK_BLOCKED || state_status == STATE_LOCK_CONFLICT) /** * Should handle _9P_LOCK_FLAGS_BLOCK in *flags, * but linux client replays blocking requests */ status = _9P_LOCK_BLOCKED; else status = _9P_LOCK_ERROR; break; case _9P_LOCK_TYPE_UNLCK: if (state_unlock(pfid->pentry, pfid->state, powner, false, 0, &lock) != STATE_SUCCESS) status = _9P_LOCK_ERROR; else status = _9P_LOCK_SUCCESS; break; default: return _9p_rerror(req9p, msgtag, EINVAL, plenout, preply); } /* switch( *type ) */ /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RLOCK); _9p_setptr(cursor, msgtag, u16); _9p_setvalue(cursor, status, u8); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug( COMPONENT_9P, "RLOCK: tag=%u fid=%u type=%u|%s flags=0x%x start=%llu length=%llu proc_id=%u client=%.*s status=%u|%s", (u32)*msgtag, *fid, *type, strtype[*type], *flags, (unsigned long long)*start, (unsigned long long)*length, *proc_id, *client_id_len, client_id_str, status, strstatus[status]); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_lopen.c000066400000000000000000000063731473756622300205420ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_lopen.c * \brief 9P version * * 9p_lopen.c : _9P_interpretor, request LOPEN * * */ #include "config.h" #include #include #include #include #include "nfs_core.h" #include "log.h" #include "fsal.h" #include "9p.h" int _9p_lopen(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *fid = NULL; u32 *flags = NULL; fsal_status_t fsal_status; fsal_openflags_t openflags = 0; struct _9p_fid *pfid = NULL; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); _9p_getptr(cursor, flags, u32); LogDebug(COMPONENT_9P, "TLOPEN: tag=%u fid=%u flags=0x%x", (u32)*msgtag, *fid, *flags); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Check that it is a valid fid */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_openflags2FSAL(flags, &openflags); pfid->state->state_data.fid.share_access = _9p_openflags_to_share_access(flags); _9p_init_opctx(pfid, req9p); if (pfid->pentry->type == REGULAR_FILE) { /** @todo: Maybe other types (FIFO, SOCKET,...) require * to be opened too */ if (*flags & 0x10) openflags |= FSAL_O_TRUNC; fsal_status = fsal_reopen2(pfid->pentry, pfid->state, openflags, true); if (FSAL_IS_ERROR(fsal_status)) return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); atomic_inc_uint32_t(&pfid->opens); /* Get an active reference for every open */ pfid->ppentry->obj_ops->get_ref(pfid->ppentry); } /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RLOPEN); _9p_setptr(cursor, msgtag, u16); _9p_setqid(cursor, pfid->qid); _9p_setvalue(cursor, _9P_IOUNIT, u32); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug( COMPONENT_9P, "RLOPEN: tag=%u fid=%u qid=(type=%u,version=%u,path=%llu) iounit=%u", *msgtag, *fid, (u32)pfid->qid.type, pfid->qid.version, (unsigned long long)pfid->qid.path, _9P_IOUNIT); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_mkdir.c000066400000000000000000000076271473756622300205360ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_mkdir.c * \brief 9P version * * 9p_mkdir.c : _9P_interpretor, request MKDIR * * */ #include "config.h" #include #include #include #include #include "nfs_core.h" #include "nfs_exports.h" #include "log.h" #include "fsal.h" #include "9p.h" int _9p_mkdir(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *fid = NULL; u32 *mode = NULL; u32 *gid = NULL; u16 *name_len = NULL; char *name_str = NULL; struct _9p_fid *pfid = NULL; struct _9p_qid qid_newdir; struct fsal_obj_handle *pentry_newdir = NULL; char dir_name[MAXNAMLEN + 1]; fsal_status_t fsal_status; struct fsal_attrlist sattr; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); _9p_getstr(cursor, name_len, name_str); _9p_getptr(cursor, mode, u32); _9p_getptr(cursor, gid, u32); LogDebug(COMPONENT_9P, "TMKDIR: tag=%u fid=%u name=%.*s mode=0%o gid=%u", (u32)*msgtag, *fid, *name_len, name_str, *mode, *gid); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Check that it is a valid fid */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(pfid, req9p); if ((op_ctx->export_perms.options & EXPORT_OPTION_WRITE_ACCESS) == 0) return _9p_rerror(req9p, msgtag, EROFS, plenout, preply); if (*name_len >= sizeof(dir_name)) { LogDebug(COMPONENT_9P, "request with name too long (%u)", *name_len); return _9p_rerror(req9p, msgtag, ENAMETOOLONG, plenout, preply); } _9p_get_fname(dir_name, *name_len, name_str); fsal_prepare_attrs(&sattr, ATTR_MODE); sattr.mode = *mode; sattr.valid_mask = ATTR_MODE; /* Create the directory */ /* BUGAZOMEU: @todo : the gid parameter is not used yet */ fsal_status = fsal_create(pfid->pentry, dir_name, DIRECTORY, &sattr, NULL, &pentry_newdir, NULL, NULL, NULL); /* Release the attributes (may release an inherited ACL) */ fsal_release_attrs(&sattr); if (FSAL_IS_ERROR(fsal_status)) return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); pentry_newdir->obj_ops->put_ref(pentry_newdir); /* Build the qid */ qid_newdir.type = _9P_QTDIR; qid_newdir.version = 0; qid_newdir.path = pentry_newdir->fileid; /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RMKDIR); _9p_setptr(cursor, msgtag, u16); _9p_setqid(cursor, qid_newdir); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug( COMPONENT_9P, "RMKDIR: tag=%u fid=%u name=%.*s qid=(type=%u,version=%u,path=%llu)", (u32)*msgtag, *fid, *name_len, name_str, qid_newdir.type, qid_newdir.version, (unsigned long long)qid_newdir.path); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_mknod.c000066400000000000000000000113011473756622300205200ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_mknod.c * \brief 9P version * * 9p_mknod.c : _9P_interpretor, request MKNOD * * */ #include "config.h" #include #include #include #include #include "nfs_core.h" #include "nfs_exports.h" #include "log.h" #include "fsal.h" #include "9p.h" int _9p_mknod(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *fid = NULL; u32 *mode = NULL; u32 *gid = NULL; u32 *major = NULL; u32 *minor = NULL; u16 *name_len = NULL; char *name_str = NULL; struct _9p_fid *pfid = NULL; struct _9p_qid qid_newobj; struct fsal_obj_handle *pentry_newobj = NULL; char obj_name[MAXNAMLEN + 1]; uint64_t fileid = 0LL; fsal_status_t fsal_status; object_file_type_t nodetype; struct fsal_attrlist object_attributes; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); _9p_getstr(cursor, name_len, name_str); _9p_getptr(cursor, mode, u32); _9p_getptr(cursor, major, u32); _9p_getptr(cursor, minor, u32); _9p_getptr(cursor, gid, u32); LogDebug( COMPONENT_9P, "TMKNOD: tag=%u fid=%u name=%.*s mode=0%o major=%u minor=%u gid=%u", (u32)*msgtag, *fid, *name_len, name_str, *mode, *major, *minor, *gid); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Check that it is a valid fid */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(pfid, req9p); if ((op_ctx->export_perms.options & EXPORT_OPTION_WRITE_ACCESS) == 0) return _9p_rerror(req9p, msgtag, EROFS, plenout, preply); if (*name_len >= sizeof(obj_name)) { LogDebug(COMPONENT_9P, "request with name too long (%u)", *name_len); return _9p_rerror(req9p, msgtag, ENAMETOOLONG, plenout, preply); } _9p_get_fname(obj_name, *name_len, name_str); /* Set the nodetype */ if (S_ISDIR(*mode)) nodetype = CHARACTER_FILE; else if (S_ISBLK(*mode)) nodetype = BLOCK_FILE; else if (S_ISFIFO(*mode)) nodetype = FIFO_FILE; else if (S_ISSOCK(*mode)) nodetype = SOCKET_FILE; else /* bad type */ return _9p_rerror(req9p, msgtag, EINVAL, plenout, preply); fsal_prepare_attrs(&object_attributes, ATTR_RAWDEV | ATTR_MODE); object_attributes.rawdev.major = *major; object_attributes.rawdev.minor = *minor; object_attributes.valid_mask |= ATTR_RAWDEV; object_attributes.mode = *mode; /* Create the directory */ /** @todo BUGAZOMEU the gid parameter is not used yet */ fsal_status = fsal_create(pfid->pentry, obj_name, nodetype, &object_attributes, NULL, &pentry_newobj, NULL, NULL, NULL); /* Release the attributes (may release an inherited ACL) */ fsal_release_attrs(&object_attributes); if (FSAL_IS_ERROR(fsal_status)) return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); /* we don't keep a reference to the entry */ pentry_newobj->obj_ops->put_ref(pentry_newobj); /* Build the qid */ qid_newobj.type = _9P_QTTMP; /** @todo BUGAZOMEU For wanting of something better */ qid_newobj.version = 0; qid_newobj.path = fileid; /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RMKNOD); _9p_setptr(cursor, msgtag, u16); _9p_setqid(cursor, qid_newobj); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug( COMPONENT_9P, "TMKNOD: tag=%u fid=%u name=%.*s major=%u minor=%u qid=(type=%u,version=%u,path=%llu)", (u32)*msgtag, *fid, *name_len, name_str, *major, *minor, qid_newobj.type, qid_newobj.version, (unsigned long long)qid_newobj.path); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_proto_tools.c000066400000000000000000000240051473756622300220000ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_proto_tools.c * \brief 9P version * * 9p_proto_tools.c : _9P_interpretor, protocol's service functions * */ #include "config.h" #include #include #include #include #include #include #include #include "nfs_core.h" #include "log.h" #include "fsal.h" #include "9p.h" #include "idmapper.h" #include "uid2grp.h" #include "export_mgr.h" #include "fsal_convert.h" /** * @brief Allocate a new struct _9p_user_cred, with refcounter set to 1. * * @return NULL if the allocation failed, else the new structure. */ static struct _9p_user_cred *new_9p_user_creds(void) { struct _9p_user_cred *result = gsh_calloc(1, sizeof(struct _9p_user_cred)); result->refcount = 1; return result; } /** * @brief Get a new reference of user credential into the op_ctx. * * This function increments the refcount of the argument. Any reference returned * by this function must be released. * * @param[in,out] pfid The _9p_fid containing the wanted credentials. */ static void set_op_ctx_creds_from_fid(struct _9p_fid *pfid) { (void)atomic_inc_int64_t(&pfid->ucred->refcount); op_ctx->proto_private = pfid->ucred; op_ctx->creds = pfid->ucred->creds; } void get_9p_user_cred_ref(struct _9p_user_cred *creds) { (void)atomic_inc_int64_t(&creds->refcount); } void release_9p_user_cred_ref(struct _9p_user_cred *creds) { int64_t refcount = atomic_dec_int64_t(&creds->refcount); if (refcount != 0) { assert(refcount > 0); return; } gsh_free(creds); } /** * @brief Release a reference obtained with set_op_ctx_creds_from_fid. * * This function decrements the refcounter of the containing _9p_user_cred * structure. If this counter reaches 0, the structure is freed. */ static void release_op_ctx_creds_ref_to_fid_creds(void) { struct _9p_user_cred *cred_9p = op_ctx->proto_private; if (cred_9p == NULL) return; memset(&op_ctx->creds, 0, sizeof(op_ctx->creds)); op_ctx->proto_private = NULL; release_9p_user_cred_ref(cred_9p); } int _9p_init(void) { return 0; } /* _9p_init */ void _9p_init_opctx(struct _9p_fid *pfid, struct _9p_request_data *req9p) { if (pfid->fid_export != NULL) { /* export affectation (require refcount handling). */ if (op_ctx->ctx_export != pfid->fid_export) { if (op_ctx->ctx_export != NULL) { LogCrit(COMPONENT_9P, "Op_ctx was already initialized, or was not allocated/cleaned up properly."); /* This tells there's an error in the code. * Use an assert because : * - if compiled in debug mode, the program will * crash and tell the developer he has something * to fix here. * - if compiled for production, we'll try to * recover. */ assert(false); } get_gsh_export_ref(pfid->fid_export); set_op_context_export(pfid->fid_export); } } set_op_ctx_creds_from_fid(pfid); } void _9p_release_opctx(void) { if (op_ctx->ctx_export != NULL) { clear_op_context_export(); } release_op_ctx_creds_ref_to_fid_creds(); } int _9p_tools_get_req_context_by_uid(u32 uid, struct _9p_fid *pfid) { struct group_data *grpdata; if (!uid2grp(uid, &grpdata)) return -ENOENT; pfid->ucred = new_9p_user_creds(); pfid->gdata = grpdata; pfid->ucred->creds.caller_uid = grpdata->uid; pfid->ucred->creds.caller_gid = grpdata->gid; pfid->ucred->creds.caller_glen = grpdata->nbgroups; pfid->ucred->creds.caller_garray = grpdata->groups; release_op_ctx_creds_ref_to_fid_creds(); set_op_ctx_creds_from_fid(pfid); op_ctx->req_type = _9P_REQUEST; return 0; } /* _9p_tools_get_fsal_cred */ int _9p_tools_get_req_context_by_name(int uname_len, char *uname_str, struct _9p_fid *pfid) { struct gsh_buffdesc name = { .addr = uname_str, .len = uname_len }; struct group_data *grpdata; if (!name2grp(&name, &grpdata)) return -ENOENT; pfid->ucred = new_9p_user_creds(); pfid->gdata = grpdata; pfid->ucred->creds.caller_uid = grpdata->uid; pfid->ucred->creds.caller_gid = grpdata->gid; pfid->ucred->creds.caller_glen = grpdata->nbgroups; pfid->ucred->creds.caller_garray = grpdata->groups; release_op_ctx_creds_ref_to_fid_creds(); set_op_ctx_creds_from_fid(pfid); op_ctx->req_type = _9P_REQUEST; return 0; } /* _9p_tools_get_fsal_cred */ int _9p_tools_errno(fsal_status_t fsal_status) { int rc = 0; switch (fsal_status.major) { case ERR_FSAL_NO_ERROR: rc = 0; break; case ERR_FSAL_NOMEM: rc = ENOMEM; break; case ERR_FSAL_NOTDIR: rc = ENOTDIR; break; case ERR_FSAL_EXIST: rc = EEXIST; break; case ERR_FSAL_NOTEMPTY: rc = ENOTEMPTY; break; case ERR_FSAL_NOENT: rc = ENOENT; break; case ERR_FSAL_ISDIR: rc = EISDIR; break; case ERR_FSAL_PERM: case ERR_FSAL_SEC: rc = EPERM; break; case ERR_FSAL_INVAL: case ERR_FSAL_NAMETOOLONG: case ERR_FSAL_NOT_OPENED: case ERR_FSAL_BADTYPE: case ERR_FSAL_SYMLINK: rc = EINVAL; break; case ERR_FSAL_NOSPC: rc = ENOSPC; break; case ERR_FSAL_ROFS: rc = EROFS; break; case ERR_FSAL_STALE: case ERR_FSAL_FHEXPIRED: rc = ESTALE; break; case ERR_FSAL_DQUOT: case ERR_FSAL_NO_QUOTA: rc = EDQUOT; break; case ERR_FSAL_IO: case ERR_FSAL_NXIO: rc = EIO; break; case ERR_FSAL_NOTSUPP: case ERR_FSAL_ATTRNOTSUPP: rc = ENOTSUP; break; case ERR_FSAL_ACCESS: rc = EACCES; break; case ERR_FSAL_DELAY: rc = EAGAIN; break; case ERR_FSAL_NO_DATA: rc = ENODATA; break; default: rc = EIO; break; } return rc; } /* _9p_tools_errno */ void _9p_openflags2FSAL(u32 *inflags, fsal_openflags_t *outflags) { if (inflags == NULL || outflags == NULL) return; if (*inflags & O_WRONLY) *outflags |= FSAL_O_WRITE; if (*inflags & O_RDWR) *outflags |= FSAL_O_RDWR; /* Exception: O_RDONLY is 0, it can't be tested with a logical and */ /* We consider that non( has O_WRONLY or has O_RDWR ) is RD_ONLY */ if (!(*inflags & (O_WRONLY | O_RDWR))) *outflags = FSAL_O_READ; } /* _9p_openflags2FSAL */ void free_fid(struct _9p_fid *pfid) { if (pfid->state != NULL) { if ((pfid->pentry->type == REGULAR_FILE) && pfid->opens) { /* We need to close the state before freeing the state. */ (void)pfid->pentry->obj_ops->close2(pfid->pentry, pfid->state); /* Now release the active references, we do this * after the close to make sure the object lifetime is * preserved. */ while (pfid->opens > 0) { /* Release the active reference for each open */ pfid->ppentry->obj_ops->put_ref(pfid->ppentry); pfid->opens--; } } free_state(pfid->state); } if (pfid->pentry != NULL) pfid->pentry->obj_ops->put_ref(pfid->pentry); if (pfid->ppentry != NULL) pfid->ppentry->obj_ops->put_ref(pfid->ppentry); if (pfid->fid_export != NULL) put_gsh_export(pfid->fid_export); if (pfid->ucred != NULL) release_9p_user_cred_ref(pfid->ucred); gsh_free(pfid->xattr); gsh_free(pfid); } int _9p_tools_clunk(struct _9p_fid *pfid) { fsal_status_t fsal_status; /* pentry may be null in the case of an aborted TATTACH * this would happens when trying to mount a non-existing * or non-authorized directory */ if (pfid->pentry == NULL) { LogEvent(COMPONENT_9P, "Trying to clunk a fid with NULL pentry. Bad mount ?"); return 0; } /* unref the related group list */ uid2grp_unref(pfid->gdata); /* If the fid is related to a xattr, free the related memory */ if (pfid->xattr != NULL && pfid->xattr->xattr_write == _9P_XATTR_DID_WRITE) { /* Check size give at TXATTRCREATE with * the one resulting from the writes */ if (pfid->xattr->xattr_size != pfid->xattr->xattr_offset) { free_fid(pfid); return EINVAL; } fsal_status = pfid->pentry->obj_ops->setextattr_value( pfid->pentry, pfid->xattr->xattr_name, pfid->xattr->xattr_content, pfid->xattr->xattr_size, false); if (FSAL_IS_ERROR(fsal_status)) { free_fid(pfid); return _9p_tools_errno(fsal_status); } } /* If object is an opened file, close it */ if ((pfid->pentry->type == REGULAR_FILE) && pfid->opens) { LogDebug(COMPONENT_9P, "Calling close on %s entry %p", object_file_type_to_str(pfid->pentry->type), pfid->pentry); fsal_status = pfid->pentry->obj_ops->close2(pfid->pentry, pfid->state); /* Now release the active references, we do this after the * close to make sure the object lifetime is preserved. */ while (pfid->opens > 0) { /* Release the active reference for each open */ pfid->ppentry->obj_ops->put_ref(pfid->ppentry); pfid->opens--; } if (FSAL_IS_ERROR(fsal_status)) { free_fid(pfid); return _9p_tools_errno(fsal_status); } } free_fid(pfid); return 0; } void _9p_cleanup_fids(struct _9p_conn *conn) { int i; struct req_op_context op_context; /* Initialize op context. * Note we only need it if there is a non-null fid, * might be worth optimizing for huge clusters */ init_op_context(&op_context, NULL, NULL, NULL, NULL, 0, 0, _9P_REQUEST); for (i = 0; i < _9P_FID_PER_CONN; i++) { if (conn->fids[i]) { _9p_init_opctx(conn->fids[i], NULL); _9p_tools_clunk(conn->fids[i]); _9p_release_opctx(); conn->fids[i] = NULL; /* poison the entry */ } } release_op_context(); } nfs-ganesha-6.5/src/Protocols/9P/9p_read.c000066400000000000000000000110731473756622300203310ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_read.c * \brief 9P version * * 9p_read.c : _9P_interpretor, request READ * * */ #include "config.h" #include #include #include #include #include "nfs_core.h" #include "log.h" #include "fsal.h" #include "9p.h" #include "server_stats.h" #include "client_mgr.h" int _9p_read(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; char *databuffer; u16 *msgtag = NULL; u32 *fid = NULL; u64 *offset = NULL; u32 *count = NULL; u32 outcount = 0; struct _9p_fid *pfid = NULL; size_t read_size = 0; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); _9p_getptr(cursor, offset, u64); _9p_getptr(cursor, count, u32); LogDebug(COMPONENT_9P, "TREAD: tag=%u fid=%u offset=%llu count=%u", (u32)*msgtag, *fid, (unsigned long long)*offset, *count); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Make sure the requested amount of data respects negotiated msize */ if (*count + _9P_ROOM_RREAD > req9p->pconn->msize) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); /* Check that it is a valid fid */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(pfid, req9p); /* Start building the reply already * So we don't need to use an intermediate data buffer */ _9p_setinitptr(cursor, preply, _9P_RREAD); _9p_setptr(cursor, msgtag, u16); databuffer = _9p_getbuffertofill(cursor); /* Do the job */ if (pfid->xattr != NULL) { /* Copy the value cached during xattrwalk */ if (*offset > pfid->xattr->xattr_size) return _9p_rerror(req9p, msgtag, EINVAL, plenout, preply); if (pfid->xattr->xattr_write != _9P_XATTR_READ_ONLY) return _9p_rerror(req9p, msgtag, EINVAL, plenout, preply); read_size = MIN(*count, pfid->xattr->xattr_size - *offset); memcpy(databuffer, pfid->xattr->xattr_content + *offset, read_size); outcount = read_size; } else { struct async_process_data read_data; struct fsal_io_arg *read_arg = alloca(sizeof(*read_arg) + sizeof(struct iovec)); read_arg->info = NULL; read_arg->state = pfid->state; read_arg->offset = *offset; read_arg->iov_count = 1; read_arg->iov = (struct iovec *)(read_arg + 1); read_arg->iov[0].iov_len = *count; read_arg->iov[0].iov_base = databuffer; read_arg->io_amount = 0; read_arg->end_of_file = false; read_data.ret.major = 0; read_data.ret.minor = 0; read_data.done = false; read_data.fsa_cond = req9p->_9prq_cond; read_data.fsa_mutex = req9p->_9prq_mutex; /* Do the actual read */ fsal_read(pfid->pentry, true, read_arg, &read_data); if (req9p->pconn->client) { op_ctx->client = req9p->pconn->client; server_stats_io_done(read_arg->iov[0].iov_len, read_arg->io_amount, FSAL_IS_ERROR(read_data.ret), false); } if (FSAL_IS_ERROR(read_data.ret)) return _9p_rerror(req9p, msgtag, _9p_tools_errno(read_data.ret), plenout, preply); outcount = (u32)read_arg->io_amount; } _9p_setfilledbuffer(cursor, outcount); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug(COMPONENT_9P, "RREAD: tag=%u fid=%u offset=%llu count=%u", (u32)*msgtag, *fid, (unsigned long long)*offset, *count); /** * @todo read statistics accounting goes here * modeled on nfs I/O statistics */ return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_read_conf.c000066400000000000000000000051251473756622300213370ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @brief 9P protocol parameter tables * */ #include "config.h" #include "9p.h" #include "config_parsing.h" #include "gsh_config.h" /* 9P parameters, settable in the 9P stanza. */ struct _9p_param _9p_param; static struct config_item _9p_params[] = { CONF_ITEM_UI32("Nb_Worker", 1, 1024 * 128, NB_WORKER_THREAD_DEFAULT, _9p_param, nb_worker), CONF_ITEM_UI16("_9P_TCP_Port", 1, UINT16_MAX, _9P_TCP_PORT, _9p_param, _9p_tcp_port), CONF_ITEM_UI16("_9P_RDMA_Port", 1, UINT16_MAX, _9P_RDMA_PORT, _9p_param, _9p_rdma_port), CONF_ITEM_UI32("_9P_TCP_Msize", 1024, UINT32_MAX, _9P_TCP_MSIZE, _9p_param, _9p_tcp_msize), CONF_ITEM_UI32("_9P_RDMA_Msize", 1024, UINT32_MAX, _9P_RDMA_MSIZE, _9p_param, _9p_rdma_msize), CONF_ITEM_UI16("_9P_RDMA_Backlog", 1, UINT16_MAX, _9P_RDMA_BACKLOG, _9p_param, _9p_rdma_backlog), CONF_ITEM_UI16("_9P_RDMA_Inpool_size", 1, UINT16_MAX, _9P_RDMA_INPOOL_SIZE, _9p_param, _9p_rdma_inpool_size), CONF_ITEM_UI16("_9P_RDMA_Outpool_Size", 1, UINT16_MAX, _9P_RDMA_OUTPOOL_SIZE, _9p_param, _9p_rdma_outpool_size), CONFIG_EOL }; static void *_9p_param_init(void *link_mem, void *self_struct) { if (self_struct == NULL) return &_9p_param; else return NULL; } struct config_block _9p_param_blk = { .dbus_interface_name = "org.ganesha.nfsd.config.9p", .blk_desc.name = "_9P", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = _9p_param_init, .blk_desc.u.blk.params = _9p_params, .blk_desc.u.blk.commit = noop_conf_commit }; nfs-ganesha-6.5/src/Protocols/9P/9p_readdir.c000066400000000000000000000172041473756622300210320ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_readdir.c * \brief 9P version * * 9p_readdir.c : _9P_interpretor, request READDIR * * */ #include "config.h" #include #include #include #include #include "nfs_core.h" #include "log.h" #include "fsal.h" #include "9p.h" #include "abstract_mem.h" char pathdot[] = "."; char pathdotdot[] = ".."; struct _9p_cb_entry { u64 qid_path; u8 *qid_type; char d_type; /* Attention, this is a VFS d_type, not a 9P type */ const char *name_str; u16 name_len; uint64_t cookie; }; struct _9p_cb_data { u8 *cursor; unsigned int count; unsigned int max; }; static inline u8 *fill_entry(u8 *cursor, u8 qid_type, u64 qid_path, u64 cookie, u8 d_type, u16 name_len, const char *name_str) { /* qid in 3 parts */ _9p_setvalue(cursor, qid_type, u8); /* 9P entry type */ /* qid_version set to 0 to prevent the client from caching */ _9p_setvalue(cursor, 0, u32); _9p_setvalue(cursor, qid_path, u64); /* offset */ _9p_setvalue(cursor, cookie, u64); /* Type (this time outside the qid) - VFS d_type (like in getdents) */ _9p_setvalue(cursor, d_type, u8); /* name */ _9p_setstr(cursor, name_len, name_str); return cursor; } static fsal_errors_t _9p_readdir_callback(void *opaque, struct fsal_obj_handle *obj, const struct fsal_attrlist *attr, uint64_t mounted_on_fileid, uint64_t cookie, enum cb_state cb_state) { struct fsal_readdir_cb_parms *cb_parms = opaque; struct _9p_cb_data *tracker = cb_parms->opaque; int name_len = strlen(cb_parms->name); u8 qid_type, d_type; if (tracker == NULL) { cb_parms->in_result = false; return ERR_FSAL_NO_ERROR; } if (tracker->count + 24 + name_len > tracker->max) { cb_parms->in_result = false; return ERR_FSAL_NO_ERROR; } switch (obj->type) { case FIFO_FILE: qid_type = _9P_QTFILE; d_type = DT_FIFO; break; case CHARACTER_FILE: qid_type = _9P_QTFILE; d_type = DT_CHR; break; case BLOCK_FILE: qid_type = _9P_QTFILE; d_type = DT_BLK; break; case REGULAR_FILE: qid_type = _9P_QTFILE; d_type = DT_REG; break; case SOCKET_FILE: qid_type = _9P_QTFILE; d_type = DT_SOCK; break; case DIRECTORY: qid_type = _9P_QTDIR; d_type = DT_DIR; break; case SYMBOLIC_LINK: qid_type = _9P_QTSYMLINK; d_type = DT_LNK; break; default: cb_parms->in_result = false; return ERR_FSAL_NO_ERROR; } /* Add 13 bytes in recsize for qid + 8 bytes for offset + 1 for type * + 2 for strlen = 24 bytes */ tracker->count += 24 + name_len; tracker->cursor = fill_entry(tracker->cursor, qid_type, obj->fileid, cookie, d_type, name_len, cb_parms->name); cb_parms->in_result = true; return ERR_FSAL_NO_ERROR; } int _9p_readdir(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; struct _9p_cb_data tracker; u16 *msgtag = NULL; u32 *fid = NULL; u64 *offset = NULL; u32 *count = NULL; u32 dcount = 0; char *dcount_pos = NULL; fsal_status_t fsal_status; bool eod_met; struct fsal_obj_handle *pentry_dot_dot = NULL; uint64_t cookie = 0LL; unsigned int num_entries = 0; struct _9p_fid *pfid = NULL; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); _9p_getptr(cursor, offset, u64); _9p_getptr(cursor, count, u32); LogDebug(COMPONENT_9P, "TREADDIR: tag=%u fid=%u offset=%llu count=%u", (u32)*msgtag, *fid, (unsigned long long)*offset, *count); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Make sure the requested amount of data respects negotiated msize */ if (*count + _9P_ROOM_RREADDIR > req9p->pconn->msize) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); /* Check that it is a valid fid */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(pfid, req9p); /* For each entry, returns: * qid = 13 bytes * offset = 8 bytes * type = 1 byte * namelen = 2 bytes * namestr = ~16 bytes (average size) * ------------------- * total = ~40 bytes (average size) per dentry */ if (*count < 52) /* require room for . and .. */ return _9p_rerror(req9p, msgtag, EIO, plenout, preply); /* Build the reply - it'll just be overwritten if error */ _9p_setinitptr(cursor, preply, _9P_RREADDIR); _9p_setptr(cursor, msgtag, u16); /* Remember dcount position for later use */ _9p_savepos(cursor, dcount_pos, u32); /* Is this the first request ? */ if (*offset == 0LL) { /* compute the parent entry */ fsal_status = fsal_lookupp(pfid->pentry, &pentry_dot_dot, NULL); if (FSAL_IS_ERROR(fsal_status)) return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); /* Deal with "." and ".." */ cursor = fill_entry(cursor, _9P_QTDIR, pfid->pentry->fileid, 1LL, DT_DIR, strlen(pathdot), pathdot); dcount += 24 + strlen(pathdot); cursor = fill_entry(cursor, _9P_QTDIR, pentry_dot_dot->fileid, 2LL, DT_DIR, strlen(pathdotdot), pathdotdot); dcount += 24 + strlen(pathdotdot); /* put the parent */ pentry_dot_dot->obj_ops->put_ref(pentry_dot_dot); cookie = 0LL; } else if (*offset == 1LL) { /* compute the parent entry */ fsal_status = fsal_lookupp(pfid->pentry, &pentry_dot_dot, NULL); if (FSAL_IS_ERROR(fsal_status)) return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); cursor = fill_entry(cursor, _9P_QTDIR, pentry_dot_dot->fileid, 2LL, DT_DIR, strlen(pathdotdot), pathdotdot); dcount += 24 + strlen(pathdotdot); /* put the parent */ pentry_dot_dot->obj_ops->put_ref(pentry_dot_dot); cookie = 0LL; } else if (*offset == 2LL) { cookie = 0LL; } else { cookie = (uint64_t)(*offset); } tracker.cursor = cursor; tracker.count = dcount; tracker.max = *count; fsal_status = fsal_readdir(pfid->pentry, cookie, &num_entries, &eod_met, 0, _9p_readdir_callback, &tracker); if (FSAL_IS_ERROR(fsal_status)) { /* The avl lookup will try to get the next entry after 'cookie'. * If none is found ERR_FSAL_NOENT is returned * In the 9P logic, this situation just mean * "end of directory reached" */ return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); } cursor = tracker.cursor; /* Set buffsize in previously saved position */ _9p_setvalue(dcount_pos, tracker.count, u32); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug(COMPONENT_9P, "RREADDIR: tag=%u fid=%u dcount=%u", (u32)*msgtag, *fid, dcount); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_readlink.c000066400000000000000000000053071473756622300212120ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_readlink.c * \brief 9P version * * 9p_readlink.c : _9P_interpretor, request ATTACH * * */ #include "config.h" #include #include #include #include #include "nfs_core.h" #include "log.h" #include "fsal.h" #include "9p.h" int _9p_readlink(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *fid = NULL; struct _9p_fid *pfid = NULL; fsal_status_t fsal_status; struct gsh_buffdesc link_buffer = { .addr = NULL, .len = 0 }; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); LogDebug(COMPONENT_9P, "TREADLINK: tag=%u fid=%u", (u32)*msgtag, *fid); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Check that it is a valid fid */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(pfid, req9p); /* let's do the job */ fsal_status = fsal_readlink(pfid->pentry, &link_buffer); if (FSAL_IS_ERROR(fsal_status)) return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RREADLINK); _9p_setptr(cursor, msgtag, u16); _9p_setstr(cursor, link_buffer.len - 1, link_buffer.addr); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug(COMPONENT_9P, "RREADLINK: tag=%u fid=%u link=%s", *msgtag, (u32)*fid, (char *)link_buffer.addr); gsh_free(link_buffer.addr); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_remove.c000066400000000000000000000072061473756622300207160ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_remove.c * \brief 9P version * * 9p_remove.c : _9P_interpretor, request ATTACH * * */ #include "config.h" #include #include #include #include "nfs_core.h" #include "nfs_exports.h" #include "log.h" #include "fsal.h" #include "9p.h" #define FREE_FID(pfid, fid, req9p) \ do { \ /* mark object no longer reachable */ \ pfid->pentry->obj_ops->put_ref(pfid->pentry); \ pfid->pentry = NULL; \ /* Free the fid */ \ free_fid(pfid); \ req9p->pconn->fids[*fid] = NULL; \ } while (0) int _9p_remove(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *fid = NULL; struct _9p_fid *pfid = NULL; fsal_status_t fsal_status; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); LogDebug(COMPONENT_9P, "TREMOVE: tag=%u fid=%u", (u32)*msgtag, *fid); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Check that it is a valid fid */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(pfid, req9p); if ((op_ctx->export_perms.options & EXPORT_OPTION_WRITE_ACCESS) == 0) return _9p_rerror(req9p, msgtag, EROFS, plenout, preply); fsal_status = fsal_remove(pfid->ppentry, pfid->name, NULL, NULL); if (FSAL_IS_ERROR(fsal_status)) return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); /* If object is an opened file, close it */ if ((pfid->pentry->type == REGULAR_FILE) && pfid->opens) { pfid->opens = 0; /* dead */ /* Release the active reference */ pfid->ppentry->obj_ops->put_ref(pfid->ppentry); fsal_status = pfid->pentry->obj_ops->close2(pfid->pentry, pfid->state); if (FSAL_IS_ERROR(fsal_status)) { FREE_FID(pfid, fid, req9p); return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); } } /* Clean the fid */ FREE_FID(pfid, fid, req9p); /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RREMOVE); _9p_setptr(cursor, msgtag, u16); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug(COMPONENT_9P, "TREMOVE: tag=%u fid=%u", (u32)*msgtag, *fid); /* _9p_stat_update( *pmsgtype, TRUE, &pwkrdata->stats._9p_stat_req); */ return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_rename.c000066400000000000000000000077151473756622300206750ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_rename.c * \brief 9P version * * 9p_rename.c : _9P_interpretor, request ATTACH * * */ #include "config.h" #include #include #include #include "nfs_core.h" #include "nfs_exports.h" #include "log.h" #include "fsal.h" #include "9p.h" int _9p_rename(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *fid = NULL; u32 *dfid = NULL; u16 *name_len = NULL; char *name_str = NULL; /* for unused-but-set-variable */ struct _9p_fid *pfid = NULL; struct _9p_fid *pdfid = NULL; char newname[MAXNAMLEN + 1]; fsal_status_t fsal_status; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); _9p_getptr(cursor, dfid, u32); _9p_getstr(cursor, name_len, name_str); LogDebug(COMPONENT_9P, "TRENAME: tag=%u fid=%u dfid=%u name=%.*s", (u32)*msgtag, *fid, *dfid, *name_len, name_str); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Check that it is a valid fid */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(pfid, req9p); if ((op_ctx->export_perms.options & EXPORT_OPTION_WRITE_ACCESS) == 0) return _9p_rerror(req9p, msgtag, EROFS, plenout, preply); if (*dfid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pdfid = req9p->pconn->fids[*dfid]; /* Check that it is a valid fid */ if (pdfid == NULL || pdfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *dfid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } /* Check that pfid and pdfid are in the same export. */ if (pfid->fid_export != NULL && pdfid->fid_export != NULL && pfid->fid_export->export_id != pdfid->fid_export->export_id) { LogDebug(COMPONENT_9P, "request on fid=%u and dfid=%u crosses exports", *fid, *dfid); return _9p_rerror(req9p, msgtag, EXDEV, plenout, preply); } if (*name_len >= sizeof(newname)) { LogDebug(COMPONENT_9P, "request with name too long (%u)", *name_len); return _9p_rerror(req9p, msgtag, ENAMETOOLONG, plenout, preply); } _9p_get_fname(newname, *name_len, name_str); fsal_status = fsal_rename(pfid->ppentry, pfid->name, pdfid->pentry, newname, NULL, NULL, NULL, NULL); if (FSAL_IS_ERROR(fsal_status)) return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RRENAME); _9p_setptr(cursor, msgtag, u16); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug(COMPONENT_9P, "RRENAMEAT: tag=%u fid=%u dfid=%u newname=%.*s", (u32)*msgtag, *fid, *dfid, *name_len, name_str); /* _9p_stat_update(*pmsgtype, TRUE, &pwkrdata->stats._9p_stat_req); */ return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_renameat.c000066400000000000000000000110061473756622300212060ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_renameat.c * \brief 9P version * * 9p_renameat.c : _9P_interpretor, request ATTACH * * */ #include "config.h" #include #include #include #include "nfs_core.h" #include "nfs_exports.h" #include "log.h" #include "fsal.h" #include "9p.h" int _9p_renameat(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *oldfid = NULL; u16 *oldname_len = NULL; char *oldname_str = NULL; u32 *newfid = NULL; u16 *newname_len = NULL; char *newname_str = NULL; struct _9p_fid *poldfid = NULL; struct _9p_fid *pnewfid = NULL; fsal_status_t fsal_status; char oldname[MAXNAMLEN + 1]; char newname[MAXNAMLEN + 1]; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, oldfid, u32); _9p_getstr(cursor, oldname_len, oldname_str); _9p_getptr(cursor, newfid, u32); _9p_getstr(cursor, newname_len, newname_str); LogDebug( COMPONENT_9P, "TRENAMEAT: tag=%u oldfid=%u oldname=%.*s newfid=%u newname=%.*s", (u32)*msgtag, *oldfid, *oldname_len, oldname_str, *newfid, *newname_len, newname_str); if (*oldfid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); poldfid = req9p->pconn->fids[*oldfid]; /* Check that it is a valid fid */ if (poldfid == NULL || poldfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *oldfid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(poldfid, req9p); if (*newfid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pnewfid = req9p->pconn->fids[*newfid]; /* Check that it is a valid fid */ if (pnewfid == NULL || pnewfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *newfid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } /* Check that poldfid and pnewfid are in the same export. */ if (poldfid->fid_export != NULL && pnewfid->fid_export != NULL && poldfid->fid_export->export_id != pnewfid->fid_export->export_id) { LogDebug(COMPONENT_9P, "request on oldfid=%u and newfid=%u crosses exports", *oldfid, *newfid); return _9p_rerror(req9p, msgtag, EXDEV, plenout, preply); } if ((op_ctx->export_perms.options & EXPORT_OPTION_WRITE_ACCESS) == 0) return _9p_rerror(req9p, msgtag, EROFS, plenout, preply); /* Let's do the job */ if (*oldname_len >= sizeof(oldname)) { LogDebug(COMPONENT_9P, "request with names too long (%u or %u)", *oldname_len, *newname_len); return _9p_rerror(req9p, msgtag, ENAMETOOLONG, plenout, preply); } _9p_get_fname(oldname, *oldname_len, oldname_str); if (*newname_len >= sizeof(newname)) { LogDebug(COMPONENT_9P, "request with names too long (%u or %u)", *oldname_len, *newname_len); return _9p_rerror(req9p, msgtag, ENAMETOOLONG, plenout, preply); } _9p_get_fname(newname, *newname_len, newname_str); fsal_status = fsal_rename(poldfid->pentry, oldname, pnewfid->pentry, newname, NULL, NULL, NULL, NULL); if (FSAL_IS_ERROR(fsal_status)) return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RRENAMEAT); _9p_setptr(cursor, msgtag, u16); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug( COMPONENT_9P, "RRENAMEAT: tag=%u oldfid=%u oldname=%.*s newfid=%u newname=%.*s", (u32)*msgtag, *oldfid, *oldname_len, oldname_str, *newfid, *newname_len, newname_str); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_rerror.c000066400000000000000000000037541473756622300207400ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_rerror.c * \brief 9P version * * 9p_rerror.c : _9P_interpretor, request RERROR * * */ #include "config.h" #include #include #include #include "nfs_core.h" #include "log.h" #include "9p.h" int _9p_rerror(struct _9p_request_data *req9p, u16 *msgtag, u32 err, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u8 msgtype = *(req9p->_9pmsg + _9P_HDR_SIZE); /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RERROR); _9p_setptr(cursor, msgtag, u16); _9p_setvalue(cursor, err, u32); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); /* Check boundaries. 0 is no_function fallback */ if (msgtype < _9P_TSTATFS || msgtype > _9P_TWSTAT || _9pfuncdesc[msgtype].service_function == NULL) msgtype = 0; LogDebug(COMPONENT_9P, "RERROR(%s) tag=%u err=(%u|%s)", _9pfuncdesc[msgtype].funcname, *msgtag, err, strerror(err)); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_setattr.c000066400000000000000000000130611473756622300211030ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_setattr.c * \brief 9P version * * 9p_setattr.c : _9P_interpretor, request SETATTR * * */ #include "config.h" #include #include #include #include #include "nfs_core.h" #include "nfs_exports.h" #include "log.h" #include "fsal.h" #include "9p.h" int _9p_setattr(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *fid = NULL; u32 *valid = NULL; u32 *mode = NULL; u32 *uid = NULL; u32 *gid = NULL; u64 *size = NULL; u64 *atime_sec = NULL; u64 *atime_nsec = NULL; u64 *mtime_sec = NULL; u64 *mtime_nsec = NULL; struct _9p_fid *pfid = NULL; struct fsal_attrlist fsalattr; fsal_status_t fsal_status; struct timeval t; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); _9p_getptr(cursor, valid, u32); _9p_getptr(cursor, mode, u32); _9p_getptr(cursor, uid, u32); _9p_getptr(cursor, gid, u32); _9p_getptr(cursor, size, u64); _9p_getptr(cursor, atime_sec, u64); _9p_getptr(cursor, atime_nsec, u64); _9p_getptr(cursor, mtime_sec, u64); _9p_getptr(cursor, mtime_nsec, u64); LogDebug( COMPONENT_9P, "TSETATTR: tag=%u fid=%u valid=0x%x mode=0%o uid=%u gid=%u size=%" PRIu64 " atime=(%llu|%llu) mtime=(%llu|%llu)", (u32)*msgtag, *fid, *valid, *mode, *uid, *gid, *size, (unsigned long long)*atime_sec, (unsigned long long)*atime_nsec, (unsigned long long)*mtime_sec, (unsigned long long)*mtime_nsec); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Check that it is a valid fid */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(pfid, req9p); if ((op_ctx->export_perms.options & EXPORT_OPTION_WRITE_ACCESS) == 0) return _9p_rerror(req9p, msgtag, EROFS, plenout, preply); /* If a "time" change is required, but not with the "_set" suffix, * use gettimeofday */ if (*valid & (_9P_SETATTR_ATIME | _9P_SETATTR_CTIME | _9P_SETATTR_MTIME)) { if (gettimeofday(&t, NULL) == -1) { LogMajor( COMPONENT_9P, "TSETATTR: tag=%u fid=%u ERROR !! gettimeofday returned -1 with errno=%u", (u32)*msgtag, *fid, errno); return _9p_rerror(req9p, msgtag, errno, plenout, preply); } } /* Let's do the job */ memset((char *)&fsalattr, 0, sizeof(fsalattr)); if (*valid & _9P_SETATTR_MODE) { fsalattr.valid_mask |= ATTR_MODE; fsalattr.mode = *mode; } if (*valid & _9P_SETATTR_UID) { fsalattr.valid_mask |= ATTR_OWNER; fsalattr.owner = *uid; } if (*valid & _9P_SETATTR_GID) { fsalattr.valid_mask |= ATTR_GROUP; fsalattr.group = *gid; } if (*valid & _9P_SETATTR_SIZE) { fsalattr.valid_mask |= ATTR_SIZE; fsalattr.filesize = *size; } if (*valid & _9P_SETATTR_ATIME) { fsalattr.valid_mask |= ATTR_ATIME; fsalattr.atime.tv_sec = t.tv_sec; fsalattr.atime.tv_nsec = t.tv_usec * 1000; } if (*valid & _9P_SETATTR_MTIME) { fsalattr.valid_mask |= ATTR_MTIME; fsalattr.mtime.tv_sec = t.tv_sec; fsalattr.mtime.tv_nsec = t.tv_usec * 1000; } if (*valid & _9P_SETATTR_CTIME) { fsalattr.valid_mask |= ATTR_CTIME; fsalattr.ctime.tv_sec = t.tv_sec; fsalattr.ctime.tv_nsec = t.tv_usec * 1000; } if (*valid & _9P_SETATTR_ATIME_SET) { fsalattr.valid_mask |= ATTR_ATIME; fsalattr.atime.tv_sec = *atime_sec; fsalattr.atime.tv_nsec = *atime_nsec; } if (*valid & _9P_SETATTR_MTIME_SET) { fsalattr.valid_mask |= ATTR_MTIME; fsalattr.mtime.tv_sec = *mtime_sec; fsalattr.mtime.tv_nsec = *mtime_nsec; } /* Now set the attr */ fsal_status = fsal_setattr(pfid->pentry, false, pfid->state, &fsalattr); /* Release the attributes (may release an inherited ACL) */ fsal_release_attrs(&fsalattr); if (FSAL_IS_ERROR(fsal_status)) return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RSETATTR); _9p_setptr(cursor, msgtag, u16); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug( COMPONENT_9P, "RSETATTR: tag=%u fid=%u valid=0x%x mode=0%o uid=%u gid=%u size=%" PRIu64 " atime=(%llu|%llu) mtime=(%llu|%llu)", (u32)*msgtag, *fid, *valid, *mode, *uid, *gid, *size, (unsigned long long)*atime_sec, (unsigned long long)*atime_nsec, (unsigned long long)*mtime_sec, (unsigned long long)*mtime_nsec); /* _9p_stat_update(*pmsgtype, TRUE, &pwkrdata->stats._9p_stat_req); */ return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_statfs.c000066400000000000000000000072661473756622300207330ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_statfs.c * \brief 9P version * * 9p_statfs.c : _9P_interpretor, request ATTACH * * */ #include "config.h" #include #include #include #include #include "nfs_core.h" #include "log.h" #include "fsal.h" #include "9p.h" int _9p_statfs(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *fid = NULL; struct _9p_fid *pfid = NULL; u32 type = 0x01021997; /* V9FS_MAGIC */ u32 bsize = 1; /* fsal_statfs and * FSAL already care for blocksize */ u64 *blocks = NULL; u64 *bfree = NULL; u64 *bavail = NULL; u64 *files = NULL; u64 *ffree = NULL; u64 fsid = 0LL; u32 namelen = MAXNAMLEN; fsal_dynamicfsinfo_t dynamicinfo; fsal_status_t fsal_status; struct fsal_attrlist attrs; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); LogDebug(COMPONENT_9P, "TSTATFS: tag=%u fid=%u", (u32)*msgtag, *fid); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; if (pfid == NULL) return _9p_rerror(req9p, msgtag, EINVAL, plenout, preply); _9p_init_opctx(pfid, req9p); /* Get the obj's attributes */ fsal_prepare_attrs(&attrs, ATTRS_NFS3); fsal_status = pfid->pentry->obj_ops->getattrs(pfid->pentry, &attrs); if (FSAL_IS_ERROR(fsal_status)) { /* Done with the attrs */ fsal_release_attrs(&attrs); return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); } /* Get the FS's stats */ fsal_status = fsal_statfs(pfid->pentry, &dynamicinfo); if (FSAL_IS_ERROR(fsal_status)) { /* Done with the attrs */ fsal_release_attrs(&attrs); return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); } blocks = (u64 *)&dynamicinfo.total_bytes; bfree = (u64 *)&dynamicinfo.free_bytes; bavail = (u64 *)&dynamicinfo.avail_bytes; files = (u64 *)&dynamicinfo.total_files; ffree = (u64 *)&dynamicinfo.free_files; fsid = (u64)attrs.rawdev.major; /* Done with the attrs */ fsal_release_attrs(&attrs); /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RSTATFS); _9p_setptr(cursor, msgtag, u16); _9p_setvalue(cursor, type, u32); _9p_setvalue(cursor, bsize, u32); _9p_setptr(cursor, blocks, u64); _9p_setptr(cursor, bfree, u64); _9p_setptr(cursor, bavail, u64); _9p_setptr(cursor, files, u64); _9p_setptr(cursor, ffree, u64); _9p_setvalue(cursor, fsid, u64); _9p_setvalue(cursor, namelen, u32); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug(COMPONENT_9P, "RSTATFS: tag=%u fid=%u", (u32)*msgtag, *fid); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_symlink.c000066400000000000000000000106041473756622300211030ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_symlink.c * \brief 9P version * * 9p_symlink.c : _9P_interpretor, request SYMLINK * * */ #include "config.h" #include #include #include #include #include "nfs_core.h" #include "nfs_exports.h" #include "log.h" #include "fsal.h" #include "9p.h" int _9p_symlink(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *fid = NULL; u16 *name_len = NULL; char *name_str = NULL; u16 *linkcontent_len = NULL; char *linkcontent_str = NULL; u32 *gid = NULL; struct _9p_fid *pfid = NULL; struct _9p_qid qid_symlink; struct fsal_obj_handle *pentry_symlink = NULL; char symlink_name[MAXNAMLEN + 1]; char *link_content = NULL; fsal_status_t fsal_status; uint32_t mode = 0777; struct fsal_attrlist object_attributes; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); _9p_getstr(cursor, name_len, name_str); _9p_getstr(cursor, linkcontent_len, linkcontent_str); _9p_getptr(cursor, gid, u32); LogDebug(COMPONENT_9P, "TSYMLINK: tag=%u fid=%u name=%.*s linkcontent=%.*s gid=%u", (u32)*msgtag, *fid, *name_len, name_str, *linkcontent_len, linkcontent_str, *gid); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Check that it is a valid fid */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(pfid, req9p); if ((op_ctx->export_perms.options & EXPORT_OPTION_WRITE_ACCESS) == 0) return _9p_rerror(req9p, msgtag, EROFS, plenout, preply); if (*name_len >= sizeof(symlink_name)) { LogDebug(COMPONENT_9P, "request with name too long (%u)", *name_len); return _9p_rerror(req9p, msgtag, ENAMETOOLONG, plenout, preply); } _9p_get_fname(symlink_name, *name_len, name_str); link_content = gsh_malloc(*linkcontent_len + 1); memcpy(link_content, linkcontent_str, *linkcontent_len); link_content[*linkcontent_len] = '\0'; fsal_prepare_attrs(&object_attributes, ATTR_MODE); object_attributes.mode = mode; object_attributes.valid_mask = ATTR_MODE; /* Let's do the job */ /* BUGAZOMEU: @todo : the gid parameter is not used yet, * flags is not yet used */ fsal_status = fsal_create(pfid->pentry, symlink_name, SYMBOLIC_LINK, &object_attributes, link_content, &pentry_symlink, NULL, NULL, NULL); /* Release the attributes (may release an inherited ACL) */ fsal_release_attrs(&object_attributes); gsh_free(link_content); if (pentry_symlink == NULL) { return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); } pentry_symlink->obj_ops->put_ref(pentry_symlink); /* Build the qid */ qid_symlink.type = _9P_QTSYMLINK; qid_symlink.version = 0; qid_symlink.path = pentry_symlink->fileid; /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RSYMLINK); _9p_setptr(cursor, msgtag, u16); _9p_setqid(cursor, qid_symlink); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug( COMPONENT_9P, "RSYMLINK: tag=%u fid=%u name=%.*s qid=(type=%u,version=%u,path=%llu)", (u32)*msgtag, *fid, *name_len, name_str, qid_symlink.type, qid_symlink.version, (unsigned long long)qid_symlink.path); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_unlinkat.c000066400000000000000000000062411473756622300212440ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_unlinkat.c * \brief 9P version * * 9p_unlinkat.c : _9P_interpretor, request ATTACH * * */ #include "config.h" #include #include #include #include "nfs_core.h" #include "nfs_exports.h" #include "log.h" #include "fsal.h" #include "9p.h" int _9p_unlinkat(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *dfid = NULL; u16 *name_len = NULL; char *name_str = NULL; /* flags are not used */ __attribute__((unused)) u32 *flags = NULL; struct _9p_fid *pdfid = NULL; fsal_status_t fsal_status; char name[MAXNAMLEN + 1]; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, dfid, u32); _9p_getstr(cursor, name_len, name_str); _9p_getptr(cursor, flags, u32); LogDebug(COMPONENT_9P, "TUNLINKAT: tag=%u dfid=%u name=%.*s", (u32)*msgtag, *dfid, *name_len, name_str); if (*dfid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pdfid = req9p->pconn->fids[*dfid]; /* Check that it is a valid fid */ if (pdfid == NULL || pdfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *dfid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(pdfid, req9p); if ((op_ctx->export_perms.options & EXPORT_OPTION_WRITE_ACCESS) == 0) return _9p_rerror(req9p, msgtag, EROFS, plenout, preply); /* Let's do the job */ if (*name_len >= sizeof(name)) { LogDebug(COMPONENT_9P, "request with name too long (%u)", *name_len); return _9p_rerror(req9p, msgtag, ENAMETOOLONG, plenout, preply); } _9p_get_fname(name, *name_len, name_str); fsal_status = fsal_remove(pdfid->pentry, name, NULL, NULL); if (FSAL_IS_ERROR(fsal_status)) return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RUNLINKAT); _9p_setptr(cursor, msgtag, u16); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug(COMPONENT_9P, "TUNLINKAT: tag=%u dfid=%u name=%.*s", (u32)*msgtag, *dfid, *name_len, name_str); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_version.c000066400000000000000000000054711473756622300211100ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_version.c * \brief 9P version * * 9p_version.c : _9P_interpretor, request VERSION * * */ #include "config.h" #include #include #include #include "nfs_core.h" #include "log.h" #include "fsal.h" #include "9p.h" static const char version_9p200l[] = "9P2000.L"; int _9p_version(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *msize = NULL; u16 *version_len = NULL; char *version_str = NULL; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, msize, u32); _9p_getstr(cursor, version_len, version_str); LogDebug(COMPONENT_9P, "TVERSION: tag=%u msize=%u version='%.*s'", (u32)*msgtag, *msize, (int)*version_len, version_str); if (*version_len < sizeof(version_9p200l) - 1 || strncmp(version_str, version_9p200l, *version_len)) { LogEvent(COMPONENT_9P, "RVERSION: BAD VERSION"); return _9p_rerror(req9p, msgtag, ENOENT, plenout, preply); } if (req9p->pconn->msize < *msize) *msize = req9p->pconn->msize; else req9p->pconn->msize = *msize; LogDebug(COMPONENT_9P, "Negotiated msize is %u", *msize); /* A too small msize would result in buffer overflows on calls * such as STAT. Make sure it is not ridiculously low. */ if (*msize < 512) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); /* Good version, build the reply */ _9p_setinitptr(cursor, preply, _9P_RVERSION); _9p_setptr(cursor, msgtag, u16); _9p_setptr(cursor, msize, u32); _9p_setstr(cursor, *version_len, version_str); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug(COMPONENT_9P, "RVERSION: msize=%u version='%.*s'", *msize, (int)*version_len, version_str); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_walk.c000066400000000000000000000147421473756622300203620ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_walk.c * \brief 9P version * * 9p_walk.c : _9P_interpretor, request WALK * * */ #include "config.h" #include #include #include #include #include "nfs_core.h" #include "log.h" #include "fsal.h" #include "9p.h" int _9p_walk(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; unsigned int i = 0; u16 *msgtag = NULL; u32 *fid = NULL; u32 *newfid = NULL; u16 *nwname = NULL; u16 *wnames_len; char *wnames_str; fsal_status_t fsal_status; struct fsal_obj_handle *pentry = NULL; char name[MAXNAMLEN + 1]; u16 *nwqid; struct _9p_fid *pfid = NULL; struct _9p_fid *pnewfid = NULL; /* Now Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); _9p_getptr(cursor, newfid, u32); _9p_getptr(cursor, nwname, u16); LogDebug(COMPONENT_9P, "TWALK: tag=%u fid=%u newfid=%u nwname=%u", (u32)*msgtag, *fid, *newfid, *nwname); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); if (*newfid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Check that it is a valid fid */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(pfid, req9p); pnewfid = gsh_calloc(1, sizeof(struct _9p_fid)); /* Is this a lookup or a fid cloning operation ? */ if (*nwname == 0) { /* Cloning operation */ memcpy((char *)pnewfid, (char *)pfid, sizeof(struct _9p_fid)); /* Set the new fid id */ pnewfid->fid = *newfid; /* Increments refcount */ pnewfid->pentry->obj_ops->get_ref(pnewfid->pentry); } else { /* the walk is in fact a lookup */ pentry = pfid->pentry; for (i = 0; i < *nwname; i++) { _9p_getstr(cursor, wnames_len, wnames_str); if (*wnames_len >= sizeof(name)) { gsh_free(pnewfid); return _9p_rerror(req9p, msgtag, ENAMETOOLONG, plenout, preply); } _9p_get_fname(name, *wnames_len, wnames_str); LogDebug( COMPONENT_9P, "TWALK (lookup): tag=%u fid=%u newfid=%u (component %u/%u :%s)", (u32)*msgtag, *fid, *newfid, i + 1, *nwname, name); if (pnewfid->pentry == pentry) pnewfid->pentry = NULL; /* refcount +1 */ fsal_status = fsal_lookup(pentry, name, &pnewfid->pentry, NULL); if (FSAL_IS_ERROR(fsal_status)) { gsh_free(pnewfid); return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); } if (pentry != pfid->pentry) pentry->obj_ops->put_ref(pentry); pentry = pnewfid->pentry; } pnewfid->fid = *newfid; pnewfid->ppentry = pfid->pentry; if (strlcpy(pnewfid->name, name, sizeof(pnewfid->name)) >= sizeof(pnewfid->name)) { pentry->obj_ops->put_ref(pentry); gsh_free(pnewfid); return _9p_rerror(req9p, msgtag, ENAMETOOLONG, plenout, preply); } /* gdata ref is not hold : the pfid, which use same gdata */ /* will be clunked after pnewfid */ /* This clunk release the gdata */ pnewfid->gdata = pfid->gdata; /* Refcounted object (incremented at the end of the function, * if there was no errors). */ pnewfid->fid_export = pfid->fid_export; pnewfid->ucred = pfid->ucred; /* Build the qid */ /* No cache, we want the client to stay synchronous * with the server */ pnewfid->qid.version = 0; pnewfid->qid.path = pnewfid->pentry->fileid; pnewfid->xattr = NULL; switch (pnewfid->pentry->type) { case REGULAR_FILE: case CHARACTER_FILE: case BLOCK_FILE: case SOCKET_FILE: case FIFO_FILE: pnewfid->qid.type = _9P_QTFILE; break; case SYMBOLIC_LINK: pnewfid->qid.type = _9P_QTSYMLINK; break; case DIRECTORY: pnewfid->qid.type = _9P_QTDIR; break; default: LogMajor( COMPONENT_9P, "implementation error, you should not see this message !!!!!!"); pentry->obj_ops->put_ref(pentry); gsh_free(pnewfid); return _9p_rerror(req9p, msgtag, EINVAL, plenout, preply); break; } } /* Initialize state_t embedded in fid. The refcount is initialized * to one to represent the state_t being embedded in the fid. This * prevents it from ever being reduced to zero by dec_state_t_ref. */ pnewfid->state = pnewfid->fid_export->fsal_export->exp_ops.alloc_state( pnewfid->fid_export->fsal_export, STATE_TYPE_9P_FID, NULL); glist_init(&pnewfid->state->state_data.fid.state_locklist); pnewfid->state->state_refcount = 1; /* keep info on new fid */ req9p->pconn->fids[*newfid] = pnewfid; /* As much qid as requested fid */ nwqid = nwname; /* Increment refcounters. */ uid2grp_hold_group_data(pnewfid->gdata); get_9p_user_cred_ref(pnewfid->ucred); get_gsh_export_ref(pnewfid->fid_export); if (pnewfid->ppentry != NULL) { /* Increments refcount for ppentry */ pnewfid->ppentry->obj_ops->get_ref(pnewfid->ppentry); } /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RWALK); _9p_setptr(cursor, msgtag, u16); _9p_setptr(cursor, nwqid, u16); for (i = 0; i < *nwqid; i++) { /** @todo: should be different qids * for each directory walked through */ _9p_setqid(cursor, pnewfid->qid); } _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug( COMPONENT_9P, "RWALK: tag=%u fid=%u newfid=%u nwqid=%u fileid=%llu pentry=%p", (u32)*msgtag, *fid, *newfid, *nwqid, (unsigned long long)pnewfid->qid.path, pnewfid->pentry); return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_write.c000066400000000000000000000122751473756622300205550ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_write.c * \brief 9P version * * 9p_write.c : _9P_interpretor, request WRITE * * */ #include "config.h" #include #include #include #include #include "nfs_core.h" #include "nfs_exports.h" #include "log.h" #include "fsal.h" #include "9p.h" #include "server_stats.h" #include "client_mgr.h" int _9p_write(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *fid = NULL; u64 *offset = NULL; u32 *count = NULL; u32 outcount = 0; struct _9p_fid *pfid = NULL; size_t size; size_t written_size = 0; char *databuffer = NULL; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); _9p_getptr(cursor, offset, u64); _9p_getptr(cursor, count, u32); databuffer = cursor; LogDebug(COMPONENT_9P, "TWRITE: tag=%u fid=%u offset=%llu count=%u", (u32)*msgtag, *fid, (unsigned long long)*offset, *count); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Make sure the requested amount of data respects negotiated msize */ if (*count + _9P_ROOM_TWRITE > req9p->pconn->msize) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); /* Check that it is a valid fid */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } _9p_init_opctx(pfid, req9p); if ((op_ctx->export_perms.options & EXPORT_OPTION_WRITE_ACCESS) == 0) return _9p_rerror(req9p, msgtag, EROFS, plenout, preply); /* Do the job */ size = *count; if (pfid->xattr != NULL) { if (*offset > pfid->xattr->xattr_size) return _9p_rerror(req9p, msgtag, EINVAL, plenout, preply); if (pfid->xattr->xattr_write != _9P_XATTR_CAN_WRITE && pfid->xattr->xattr_write != _9P_XATTR_DID_WRITE) return _9p_rerror(req9p, msgtag, EINVAL, plenout, preply); written_size = MIN(*count, pfid->xattr->xattr_size - *offset); memcpy(pfid->xattr->xattr_content + *offset, databuffer, written_size); pfid->xattr->xattr_offset += size; pfid->xattr->xattr_write = _9P_XATTR_DID_WRITE; /* ADD CODE TO DETECT GAP */ #if 0 fsal_status = pfid->pentry->ops->setextattr_value_by_id( pfid->pentry, &pfid->op_context, pfid->xattr->xattr_id, xattrval, size + 1); if (FSAL_IS_ERROR(fsal_status)) return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); #endif outcount = written_size; } else { struct async_process_data write_data; struct fsal_io_arg *write_arg = alloca(sizeof(*write_arg) + sizeof(struct iovec)); write_arg->info = NULL; write_arg->state = pfid->state; write_arg->offset = *offset; write_arg->io_request = size; write_arg->iov_count = 1; write_arg->iov = (struct iovec *)(write_arg + 1); write_arg->iov[0].iov_len = size; write_arg->iov[0].iov_base = databuffer; write_arg->io_amount = 0; write_arg->fsal_stable = false; write_data.ret.major = 0; write_data.ret.minor = 0; write_data.done = false; write_data.fsa_cond = req9p->_9prq_cond; write_data.fsa_mutex = req9p->_9prq_mutex; /* Do the actual write */ fsal_write(pfid->pentry, true, write_arg, &write_data); if (req9p->pconn->client) { op_ctx->client = req9p->pconn->client; server_stats_io_done(write_arg->iov[0].iov_len, write_arg->io_amount, FSAL_IS_ERROR(write_data.ret), false); } if (FSAL_IS_ERROR(write_data.ret)) return _9p_rerror(req9p, msgtag, _9p_tools_errno(write_data.ret), plenout, preply); outcount = (u32)write_arg->io_amount; } /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RWRITE); _9p_setptr(cursor, msgtag, u16); _9p_setvalue(cursor, outcount, u32); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug( COMPONENT_9P, "RWRITE: tag=%u fid=%u offset=%llu input count=%u output count=%u", (u32)*msgtag, *fid, (unsigned long long)*offset, *count, outcount); /** * @todo write statistics accounting goes here * modeled on nfs I/O stats */ return 1; } nfs-ganesha-6.5/src/Protocols/9P/9p_xattrcreate.c000066400000000000000000000125011473756622300217410ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_xattrcreate.c * \brief 9P version * * 9p_xattrcreate.c : _9P_interpretor, request XATTRCREATE * * */ #include "config.h" #include #include #include #include #include #include "os/xattr.h" #include "nfs_core.h" #include "nfs_exports.h" #include "log.h" #include "fsal.h" #include "9p.h" int _9p_xattrcreate(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; int create; u16 *msgtag = NULL; u32 *fid = NULL; u64 *size; u32 *flag; u16 *name_len; char *name_str; struct _9p_fid *pfid = NULL; fsal_status_t fsal_status = { .major = ERR_FSAL_NO_ERROR, .minor = 0 }; char name[MAXNAMLEN + 1]; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); _9p_getstr(cursor, name_len, name_str); _9p_getptr(cursor, size, u64); _9p_getptr(cursor, flag, u32); LogDebug(COMPONENT_9P, "TXATTRCREATE: tag=%u fid=%u name=%.*s size=%llu flag=%u", (u32)*msgtag, *fid, *name_len, name_str, (unsigned long long)*size, *flag); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); if (*size > _9P_XATTR_MAX_SIZE) return _9p_rerror(req9p, msgtag, ENOSPC, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Check that it is a valid fid */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } /* set op_ctx, it will be useful if FSAL is later called */ _9p_init_opctx(pfid, req9p); if ((op_ctx->export_perms.options & EXPORT_OPTION_WRITE_ACCESS) == 0) return _9p_rerror(req9p, msgtag, EROFS, plenout, preply); if (*name_len >= sizeof(name)) { LogDebug(COMPONENT_9P, "request with name too long (%u)", *name_len); return _9p_rerror(req9p, msgtag, ENAMETOOLONG, plenout, preply); } _9p_get_fname(name, *name_len, name_str); if (*size == 0LL) { /* Size == 0 : this is in fact a call to removexattr */ LogDebug(COMPONENT_9P, "TXATTRCREATE: tag=%u fid=%u : will remove xattr %s", (u32)*msgtag, *fid, name); fsal_status = pfid->pentry->obj_ops->remove_extattr_by_name( pfid->pentry, name); if (FSAL_IS_ERROR(fsal_status)) return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); } else { /* Size != 0 , this is a creation/replacement of xattr */ /* Create the xattr at the FSAL level and cache result */ pfid->xattr = gsh_malloc(sizeof(*pfid->xattr) + *size); pfid->xattr->xattr_size = *size; pfid->xattr->xattr_offset = 0LL; pfid->xattr->xattr_write = _9P_XATTR_CAN_WRITE; if (strlcpy(pfid->xattr->xattr_name, name, sizeof(pfid->xattr->xattr_name)) >= sizeof(pfid->xattr->xattr_name)) goto skip_create; /* /!\ POSIX_ACL RELATED HOOK * Setting a POSIX ACL (using setfacl for example) means * settings a xattr named system.posix_acl_access BUT this * attribute is to be used and should not be created * (it exists already since acl feature is on) */ if (!strcmp(name, "system.posix_acl_access")) goto skip_create; /* try to create if flag doesn't have REPLACE bit */ if ((*flag & XATTR_REPLACE) == 0) create = true; else create = false; fsal_status = pfid->pentry->obj_ops->setextattr_value( pfid->pentry, name, pfid->xattr->xattr_content, *size, create); /* Try again with create = false if flag was set to 0 * and create failed because attribute already exists */ if (FSAL_IS_ERROR(fsal_status) && fsal_status.major == ERR_FSAL_EXIST && (*flag == 0)) { fsal_status = pfid->pentry->obj_ops->setextattr_value( pfid->pentry, name, pfid->xattr->xattr_content, *size, false); } if (FSAL_IS_ERROR(fsal_status)) { gsh_free(pfid->xattr); pfid->xattr = NULL; return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); } } skip_create: /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RXATTRCREATE); _9p_setptr(cursor, msgtag, u16); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug(COMPONENT_9P, "RXATTRCREATE: tag=%u fid=%u name=%.*s size=%llu flag=%u", (u32)*msgtag, *fid, *name_len, name_str, (unsigned long long)*size, *flag); return 1; } /* _9p_xattrcreate */ nfs-ganesha-6.5/src/Protocols/9P/9p_xattrwalk.c000066400000000000000000000170221473756622300214370ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file 9p_xattrwalk.c * \brief 9P version * * 9p_xattrwalk.c : _9P_interpretor, request XATTRWALK * * */ #include "config.h" #include #include #include #include #include #include "os/xattr.h" #include "nfs_core.h" #include "log.h" #include "fsal.h" #include "9p.h" #define XATTRS_ARRAY_LEN 100 int _9p_xattrwalk(struct _9p_request_data *req9p, u32 *plenout, char *preply) { char *cursor = req9p->_9pmsg + _9P_HDR_SIZE + _9P_TYPE_SIZE; u16 *msgtag = NULL; u32 *fid = NULL; u32 *attrfid = NULL; u16 *name_len; char *name_str; size_t attrsize = 0; fsal_status_t fsal_status; char name[MAXNAMLEN + 1]; fsal_xattrent_t xattrs_arr[XATTRS_ARRAY_LEN]; int eod_met = false; unsigned int nb_xattrs_read = 0; unsigned int i = 0; char *xattr_cursor = NULL; unsigned int tmplen = 0; struct _9p_fid *pfid = NULL; struct _9p_fid *pxattrfid = NULL; /* Get data */ _9p_getptr(cursor, msgtag, u16); _9p_getptr(cursor, fid, u32); _9p_getptr(cursor, attrfid, u32); LogDebug(COMPONENT_9P, "TXATTRWALK: tag=%u fid=%u attrfid=%u", (u32)*msgtag, *fid, *attrfid); _9p_getstr(cursor, name_len, name_str); if (*name_len == 0) LogDebug( COMPONENT_9P, "TXATTRWALK (component): tag=%u fid=%u attrfid=%u name=(LIST XATTR)", (u32)*msgtag, *fid, *attrfid); else LogDebug( COMPONENT_9P, "TXATTRWALK (component): tag=%u fid=%u attrfid=%u name=%.*s", (u32)*msgtag, *fid, *attrfid, *name_len, name_str); if (*fid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); if (*attrfid >= _9P_FID_PER_CONN) return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); pfid = req9p->pconn->fids[*fid]; /* Check that it is a valid fid */ if (pfid == NULL || pfid->pentry == NULL) { LogDebug(COMPONENT_9P, "request on invalid fid=%u", *fid); return _9p_rerror(req9p, msgtag, EIO, plenout, preply); } if (*name_len >= sizeof(name)) { LogDebug(COMPONENT_9P, "request with name too long (%u)", *name_len); return _9p_rerror(req9p, msgtag, ENAMETOOLONG, plenout, preply); } pxattrfid = gsh_calloc(1, sizeof(struct _9p_fid)); /* set op_ctx, it will be useful if FSAL is later called */ _9p_init_opctx(pfid, req9p); /* Initiate xattr's fid by copying file's fid in it. * Don't copy the state_t pointer. */ memcpy((char *)pxattrfid, (char *)pfid, sizeof(struct _9p_fid)); pxattrfid->state = NULL; _9p_get_fname(name, *name_len, name_str); pxattrfid->xattr = gsh_malloc(sizeof(*pxattrfid->xattr) + XATTR_BUFFERSIZE); if (*name_len == 0) { /* xattrwalk is used with an empty name, * this is a listxattr request */ fsal_status = pxattrfid->pentry->obj_ops->list_ext_attrs( pxattrfid->pentry, FSAL_XATTR_RW_COOKIE, /* Start with RW cookie, * hiding RO ones */ xattrs_arr, XATTRS_ARRAY_LEN, /** @todo fix static length */ &nb_xattrs_read, &eod_met); if (FSAL_IS_ERROR(fsal_status)) { gsh_free(pxattrfid->xattr); gsh_free(pxattrfid); return _9p_rerror(req9p, msgtag, _9p_tools_errno(fsal_status), plenout, preply); } /* if all xattrent are not read, * returns ERANGE as listxattr does */ if (eod_met != true) { gsh_free(pxattrfid->xattr); gsh_free(pxattrfid); return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); } xattr_cursor = pxattrfid->xattr->xattr_content; attrsize = 0; for (i = 0; i < nb_xattrs_read; i++) { tmplen = strnlen(xattrs_arr[i].xattr_name, MAXNAMLEN); /* Make sure not to go beyond the buffer * @todo realloc here too */ if (attrsize + tmplen + 1 > XATTR_BUFFERSIZE) { gsh_free(pxattrfid->xattr); gsh_free(pxattrfid); return _9p_rerror(req9p, msgtag, ERANGE, plenout, preply); } memcpy(xattr_cursor, xattrs_arr[i].xattr_name, tmplen); xattr_cursor[tmplen] = '\0'; /* +1 for trailing '\0' */ xattr_cursor += tmplen + 1; attrsize += tmplen + 1; } } else { /* xattrwalk has a non-empty name, use regular getxattr */ fsal_status = pxattrfid->pentry->obj_ops->getextattr_value_by_name( pxattrfid->pentry, name, pxattrfid->xattr->xattr_content, XATTR_BUFFERSIZE, &attrsize); if (fsal_status.minor == ERANGE) { /* we need a bigger buffer, do one more request with * 0-size to get length and reallocate/try again */ fsal_status = pxattrfid->pentry->obj_ops ->getextattr_value_by_name( pxattrfid->pentry, name, pxattrfid->xattr->xattr_content, 0, &attrsize); if (FSAL_IS_ERROR(fsal_status)) { gsh_free(pxattrfid->xattr); gsh_free(pxattrfid); /* fsal_status.minor is a valid errno code */ return _9p_rerror(req9p, msgtag, fsal_status.minor, plenout, preply); } /* Check our own limit too before reallocating */ if (attrsize > _9P_XATTR_MAX_SIZE) { gsh_free(pxattrfid->xattr); gsh_free(pxattrfid); return _9p_rerror(req9p, msgtag, E2BIG, plenout, preply); } pxattrfid->xattr = gsh_realloc( pxattrfid->xattr, sizeof(*pxattrfid->xattr) + attrsize); fsal_status = pxattrfid->pentry->obj_ops ->getextattr_value_by_name( pxattrfid->pentry, name, pxattrfid->xattr->xattr_content, attrsize, &attrsize); } if (FSAL_IS_ERROR(fsal_status)) { gsh_free(pxattrfid->xattr); gsh_free(pxattrfid); /* ENOENT for xattr is ENOATTR */ if (fsal_status.major == ERR_FSAL_NOENT) return _9p_rerror(req9p, msgtag, ENOATTR, plenout, preply); /* fsal_status.minor is a valid errno code */ return _9p_rerror(req9p, msgtag, fsal_status.minor, plenout, preply); } } pxattrfid->xattr->xattr_size = attrsize; pxattrfid->xattr->xattr_write = _9P_XATTR_READ_ONLY; req9p->pconn->fids[*attrfid] = pxattrfid; /* Increments refcount as we're manually making a new copy */ pxattrfid->pentry->obj_ops->get_ref(pxattrfid->pentry); /* hold reference on gdata */ uid2grp_hold_group_data(pxattrfid->gdata); get_gsh_export_ref(pxattrfid->fid_export); get_9p_user_cred_ref(pxattrfid->ucred); if (pxattrfid->ppentry != NULL) { /* Increments refcount for ppentry */ pxattrfid->ppentry->obj_ops->get_ref(pxattrfid->ppentry); } /* Build the reply */ _9p_setinitptr(cursor, preply, _9P_RXATTRWALK); _9p_setptr(cursor, msgtag, u16); _9p_setvalue(cursor, attrsize, u64); _9p_setendptr(cursor, preply); _9p_checkbound(cursor, preply, plenout); LogDebug(COMPONENT_9P, "RXATTRWALK: tag=%u fid=%u attrfid=%u name=%.*s size=%llu", (u32)*msgtag, *fid, *attrfid, *name_len, name_str, (unsigned long long)attrsize); return 1; } /* _9p_xattrwalk */ nfs-ganesha-6.5/src/Protocols/9P/CMakeLists.txt000066400000000000000000000035561473756622300214110ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- ########### next target ############### SET(9p_STAT_SRCS 9p_interpreter.c 9p_proto_tools.c 9p_read_conf.c 9p_attach.c 9p_auth.c 9p_clunk.c 9p_flush.c 9p_flush_hook.c 9p_getattr.c 9p_getlock.c 9p_lcreate.c 9p_lopen.c 9p_link.c 9p_lock.c 9p_mkdir.c 9p_mknod.c 9p_read.c 9p_readdir.c 9p_readlink.c 9p_remove.c 9p_rename.c 9p_renameat.c 9p_fsync.c 9p_unlinkat.c 9p_setattr.c 9p_statfs.c 9p_symlink.c 9p_version.c 9p_walk.c 9p_xattrcreate.c 9p_xattrwalk.c 9p_write.c 9p_rerror.c ) add_library(9p OBJECT ${9p_STAT_SRCS}) add_sanitizers(9p) set_target_properties(9p PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(9p gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) ########### install files ############### nfs-ganesha-6.5/src/Protocols/CMakeLists.txt000066400000000000000000000024431473756622300211130ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- add_subdirectory(NFS) add_subdirectory(XDR) if(USE_NLM) add_subdirectory(NLM) endif(USE_NLM) if(USE_RQUOTA) add_subdirectory(RQUOTA) endif(USE_RQUOTA) if(USE_NFSACL3) add_subdirectory(NFSACL) endif(USE_NFSACL3) if(USE_9P) add_subdirectory(9P) endif(USE_9P) ########### install files ############### nfs-ganesha-6.5/src/Protocols/NFS/000077500000000000000000000000001473756622300167765ustar00rootroot00000000000000nfs-ganesha-6.5/src/Protocols/NFS/CMakeLists.txt000066400000000000000000000071631473756622300215450ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- ########### next target ############### SET(nfsproto_STAT_SRCS nfs4_Compound.c nfs4_op_access.c nfs4_op_allocate.c nfs4_op_bind_conn.c nfs4_op_close.c nfs4_op_commit.c nfs4_op_create.c nfs4_op_create_session.c nfs4_op_delegpurge.c nfs4_op_delegreturn.c nfs4_op_destroy_session.c nfs4_op_exchange_id.c nfs4_op_free_stateid.c nfs4_op_getattr.c nfs4_op_getdeviceinfo.c nfs4_op_getdevicelist.c nfs4_op_getfh.c nfs4_op_illegal.c nfs4_op_layoutcommit.c nfs4_op_layoutget.c nfs4_op_layoutreturn.c nfs4_op_link.c nfs4_op_lock.c nfs4_op_lockt.c nfs4_op_locku.c nfs4_op_lookup.c nfs4_op_lookupp.c nfs4_op_nverify.c nfs4_op_open.c nfs4_op_open_confirm.c nfs4_op_open_downgrade.c nfs4_op_openattr.c nfs4_op_putfh.c nfs4_op_putpubfh.c nfs4_op_putrootfh.c nfs4_op_read.c nfs4_op_readdir.c nfs4_op_readlink.c nfs4_op_reclaim_complete.c nfs4_op_release_lockowner.c nfs4_op_remove.c nfs4_op_rename.c nfs4_op_renew.c nfs4_op_restorefh.c nfs4_op_savefh.c nfs4_op_secinfo.c nfs4_op_secinfo_no_name.c nfs4_op_sequence.c nfs4_op_set_ssv.c nfs4_op_setattr.c nfs4_op_xattr.c nfs4_op_setclientid.c nfs4_op_setclientid_confirm.c nfs4_op_destroy_clientid.c nfs4_op_test_stateid.c nfs4_op_verify.c nfs4_op_write.c nfs4_pseudo.c nfs_proto_tools.c nfs_null.c ) if(USE_NFS3) SET(nfsproto_STAT_SRCS ${nfsproto_STAT_SRCS} mnt_Dump.c mnt_Export.c mnt_Mnt.c mnt_Null.c mnt_Umnt.c mnt_UmntAll.c nfs3_access.c nfs3_commit.c nfs3_create.c nfs3_fsinfo.c nfs3_fsstat.c nfs3_getattr.c nfs3_link.c nfs3_lookup.c nfs3_mkdir.c nfs3_mknod.c nfs3_pathconf.c nfs3_read.c nfs3_readdir.c nfs3_readdirplus.c nfs3_readlink.c nfs3_remove.c nfs3_rename.c nfs3_rmdir.c nfs3_setattr.c nfs3_symlink.c nfs3_write.c ) endif(USE_NFS3) add_library(nfsproto OBJECT ${nfsproto_STAT_SRCS}) add_sanitizers(nfsproto) set_target_properties(nfsproto PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(nfsproto gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) set(nfs4callbackSRCS nfs4_cb_Compound.c ) add_library(nfs4callbacks OBJECT ${nfs4callbackSRCS}) add_sanitizers(nfs4callbacks) set_target_properties(nfs4callbacks PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(nfs4callbacks gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) ########### install files ############### nfs-ganesha-6.5/src/Protocols/NFS/maketest.conf000066400000000000000000000015711473756622300214660ustar00rootroot00000000000000 Test mount_protocol { Product = Mount protocol. Command = ../../bin/`archi -M`/test_mntproto Comment = Testing mount protocol. # all tests OK Success TestOk { STDOUT =~ /test_mnt_Null : OK/ AND STDOUT =~ /test_mnt_Export : OK/ } # Command execution problem Failure ERROR_MNTPROC_NULL { NOT STDOUT =~ /test_mnt_Null : OK/ } # Command execution problem Failure ERROR_MNTPROC_EXPORT { NOT STDOUT =~ /test_mnt_Export : OK/ } # other problem ? Failure UNKNOWN_ERROR { STDOUT =~ /ERROR/ } # abnormal termination (should return 0) Failure AbnormalTermination { STATUS != 0 } } nfs-ganesha-6.5/src/Protocols/NFS/mnt_Dump.c000066400000000000000000000037121473756622300207300ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * file mnt_Dump.c * brief MOUNTPROC_Dump for Mount protocol v1 and v3. * */ #include "config.h" #include "log.h" #include "nfs_core.h" #include "nfs_exports.h" #include "mount.h" #include "nfs_proto_functions.h" /** * @brief The Mount proc Dump function, for all versions. * * @param[in] parg Arguments (ignored) * @param[in] preq Ignored * @param[out] pres Pointer to results * */ int mnt_Dump(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { LogDebug(COMPONENT_NFSPROTO, "REQUEST PROCESSING: Calling MNT_DUMP"); /* Ganesha does not support the mount list so this is a NOOP */ res->res_dump = NULL; return NFS_REQ_OK; } /* mnt_Null */ /** * mnt_Dump_Free: Frees the result structure allocated for mnt_Dump. * * Frees the result structure allocated for mnt_Dump. * * @param res [INOUT] Pointer to the result structure. * */ void mnt_Dump_Free(nfs_res_t *res) { /* Nothing to do */ } nfs-ganesha-6.5/src/Protocols/NFS/mnt_Export.c000066400000000000000000000135751473756622300213140ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * file mnt_Export.c * brief MOUNTPROC_EXPORT for Mount protocol v1 and v3. * * mnt_Null.c : MOUNTPROC_EXPORT in V1, V3. * * Exporting client hosts and networks OK. * */ #include "config.h" #include #include "log.h" #include "nfs_core.h" #include "nfs_exports.h" #include "fsal.h" #include "nfs_proto_functions.h" #include "export_mgr.h" struct proc_state { mnt3_exports head; mnt3_exports tail; int retval; }; static bool proc_export(struct gsh_export *export, void *arg) { struct proc_state *state = arg; struct exportnode *new_expnode; struct glist_head *glist_item; struct groupnode *group, *grp_tail = NULL; char *grp_name; bool free_grp_name; struct glist_head *clients = &export->clients; state->retval = 0; /* If client does not have any access to the export, * don't add it to the list */ get_gsh_export_ref(export); set_op_context_export(export); export_check_access(); if (!(op_ctx->export_perms.options & EXPORT_OPTION_ACCESS_MASK)) { LogFullDebug(COMPONENT_NFSPROTO, "Client is not allowed to access Export_Id %d %s", export->export_id, ctx_export_path(op_ctx)); goto out; } if (!(op_ctx->export_perms.options & EXPORT_OPTION_NFSV3)) { LogFullDebug(COMPONENT_NFSPROTO, "Not exported for NFSv3, Export_Id %d %s", export->export_id, ctx_export_path(op_ctx)); goto out; } new_expnode = gsh_calloc(1, sizeof(struct exportnode)); PTHREAD_RWLOCK_rdlock(&op_ctx->ctx_export->exp_lock); PTHREAD_RWLOCK_rdlock(&export_opt_lock); if (glist_empty(clients)) clients = &export_opt.clients; glist_for_each(glist_item, clients) { struct base_client_entry *client = glist_entry( glist_item, struct base_client_entry, cle_list); group = gsh_calloc(1, sizeof(struct groupnode)); if (grp_tail == NULL) new_expnode->ex_groups = group; else grp_tail->gr_next = group; grp_tail = group; free_grp_name = false; switch (client->type) { case NETWORK_CLIENT: grp_name = cidr_to_str(client->client.network.cidr, CIDR_NOFLAGS); if (grp_name == NULL) { state->retval = errno; grp_name = "Invalid Network Address"; } else { free_grp_name = true; } break; case NETGROUP_CLIENT: grp_name = client->client.netgroup.netgroupname; break; case GSSPRINCIPAL_CLIENT: grp_name = client->client.gssprinc.princname; break; case MATCH_ANY_CLIENT: grp_name = "*"; break; case WILDCARDHOST_CLIENT: grp_name = client->client.wildcard.wildcard; break; default: grp_name = ""; } LogFullDebug(COMPONENT_NFSPROTO, "Export %s client %s", ctx_export_path(op_ctx), grp_name); group->gr_name = gsh_strdup(grp_name); if (free_grp_name) gsh_free(grp_name); } PTHREAD_RWLOCK_unlock(&export_opt_lock); PTHREAD_RWLOCK_unlock(&op_ctx->ctx_export->exp_lock); /* Now that we are almost done, get a gsh_refstr to the path for ex_dir. * The op context has a reference but we don't want to play games with * it to keep the code understandable. */ if (nfs_param.core_param.mount_path_pseudo) new_expnode->ex_refdir = gsh_refstr_get(op_ctx->ctx_pseudopath); else new_expnode->ex_refdir = gsh_refstr_get(op_ctx->ctx_fullpath); new_expnode->ex_dir = new_expnode->ex_refdir->gr_val; if (state->head == NULL) state->head = new_expnode; else state->tail->ex_next = new_expnode; state->tail = new_expnode; out: clear_op_context_export(); return true; } /** * @brief The Mount proc EXPORT function, for all versions. * * Return a list of all exports and their allowed clients/groups/networks. * * @param[in] arg Ignored * @param[in] req Ignored * @param[out] res Pointer to the export list * */ int mnt_Export(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { struct proc_state proc_state; /* init everything of interest to good state. */ memset(res, 0, sizeof(nfs_res_t)); memset(&proc_state, 0, sizeof(proc_state)); (void)foreach_gsh_export(proc_export, false, &proc_state); if (proc_state.retval != 0) { LogCrit(COMPONENT_NFSPROTO, "Processing exports failed. error = \"%s\" (%d)", strerror(proc_state.retval), proc_state.retval); } res->res_mntexport = proc_state.head; return NFS_REQ_OK; } /* mnt_Export */ /** * mnt_Export_Free: Frees the result structure allocated for mnt_Export. * * Frees the result structure allocated for mnt_Dump. * * @param res [INOUT] Pointer to the result structure. * */ void mnt_Export_Free(nfs_res_t *res) { struct exportnode *exp, *next_exp; struct groupnode *grp, *next_grp; exp = res->res_mntexport; while (exp != NULL) { next_exp = exp->ex_next; grp = exp->ex_groups; while (grp != NULL) { next_grp = grp->gr_next; if (grp->gr_name != NULL) gsh_free(grp->gr_name); gsh_free(grp); grp = next_grp; } if (exp->ex_refdir != NULL) gsh_refstr_put(exp->ex_refdir); gsh_free(exp); exp = next_exp; } } /* mnt_Export_Free */ nfs-ganesha-6.5/src/Protocols/NFS/mnt_Mnt.c000066400000000000000000000167651473756622300205750ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * file mnt_Mnt.c * brief MOUNTPROC_MNT for Mount protocol v1 and v3. * * mnt_Null.c : MOUNTPROC_EXPORT in V1, V3. * */ #include "config.h" #include "log.h" #include "nfs_core.h" #include "nfs_exports.h" #include "fsal.h" #include "nfs_file_handle.h" #include "nfs_proto_functions.h" #include "client_mgr.h" #include "export_mgr.h" /** * @brief The Mount proc mount function for MOUNT_V3 version * * The MOUNT proc function for MOUNT_V3 version * * @param[in] arg The export path to be mounted * @param[in] req ignored * @param[out] res Result structure. * */ int mnt_Mnt(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { struct gsh_export *export = NULL; int auth_flavor[NB_AUTH_FLAVOR]; int index_auth = 0; int i = 0; int retval = NFS_REQ_OK; nfs_fh3 *fh3 = (nfs_fh3 *)&res->res_mnt3.mountres3_u.mountinfo.fhandle; struct fsal_obj_handle *obj = NULL; mountres3_ok *const RES_MOUNTINFO = &res->res_mnt3.mountres3_u.mountinfo; LogDebug(COMPONENT_NFSPROTO, "REQUEST PROCESSING: Calling MNT_MNT path=%s", arg->arg_mnt); /* Paranoid command to clean the result struct. */ memset(res, 0, sizeof(nfs_res_t)); /* Quick escape if an unsupported MOUNT version */ if (req->rq_msg.cb_vers != MOUNT_V3) { res->res_mnt1.status = NFSERR_ACCES; goto out; } if (arg->arg_mnt == NULL) { LogCrit(COMPONENT_NFSPROTO, "NULL path passed as Mount argument !!!"); retval = NFS_REQ_DROP; goto out; } /* If the path ends with a '/', get rid of it */ /** @todo: should it be a while()?? */ if ((strlen(arg->arg_mnt) > 1) && (arg->arg_mnt[strlen(arg->arg_mnt) - 1] == '/')) arg->arg_mnt[strlen(arg->arg_mnt) - 1] = '\0'; /* Find the export for the dirname (using as well Path, Pseudo, or Tag) */ if (arg->arg_mnt[0] != '/') { LogFullDebug(COMPONENT_NFSPROTO, "Searching for export by tag for %s", arg->arg_mnt); export = get_gsh_export_by_tag(arg->arg_mnt); } else if (nfs_param.core_param.mount_path_pseudo) { LogFullDebug(COMPONENT_NFSPROTO, "Searching for export by pseudo for %s", arg->arg_mnt); export = get_gsh_export_by_pseudo(arg->arg_mnt, false); } else { LogFullDebug(COMPONENT_NFSPROTO, "Searching for export by path for %s", arg->arg_mnt); export = get_gsh_export_by_path(arg->arg_mnt, false); } if (export == NULL) { /* No export found, return ACCESS error. */ LogEvent(COMPONENT_NFSPROTO, "MOUNT: Export entry for %s not found", arg->arg_mnt); /* entry not found. */ /* @todo : not MNT3ERR_NOENT => ok */ res->res_mnt3.fhs_status = MNT3ERR_ACCES; goto out; } /* set the export in the context */ set_op_context_export(export); /* Check access based on client. Don't bother checking TCP/UDP as some * clients use UDP for MOUNT even when they will use TCP for NFS. */ export_check_access(); if ((op_ctx->export_perms.options & EXPORT_OPTION_NFSV3) == 0) { LogInfoAlt( COMPONENT_NFSPROTO, COMPONENT_EXPORT, "MOUNT: Export entry %s does not support NFS v3 for client %s", ctx_export_path(op_ctx), op_ctx->client ? op_ctx->client->hostaddr_str : "unknown client"); res->res_mnt3.fhs_status = MNT3ERR_ACCES; goto out; } if ((op_ctx->export_perms.options & EXPORT_OPTION_ACCESS_MASK) == 0) { LogInfoAlt( COMPONENT_NFSPROTO, COMPONENT_EXPORT, "MOUNT: Export entry %s does not allow access for client %s", ctx_export_path(op_ctx), op_ctx->client ? op_ctx->client->hostaddr_str : "unknown client"); res->res_mnt3.fhs_status = MNT3ERR_ACCES; goto out; } /* retrieve the associated NFS handle */ if (arg->arg_mnt[0] != '/') { /* We found the export by tag. Use the root entry of the * export. */ if (FSAL_IS_ERROR(nfs_export_get_root_entry(export, &obj))) { res->res_mnt3.fhs_status = MNT3ERR_ACCES; goto out; } } else { /* Note that we call this even if arg_mnt is just the path to * the export. It resolves that efficiently. */ if (FSAL_IS_ERROR(fsal_lookup_path(arg->arg_mnt, &obj))) { res->res_mnt3.fhs_status = MNT3ERR_ACCES; goto out; } } /* convert the fsal_handle to a file handle */ /** @todo: * The mountinfo.fhandle definition is an overlay on/of nfs_fh3. * redefine and eliminate one or the other. */ if (!nfs3_FSALToFhandle(true, fh3, obj, export)) res->res_mnt3.fhs_status = MNT3ERR_INVAL; else res->res_mnt3.fhs_status = MNT3_OK; /* Release the fsal_obj_handle created for the path */ LogFullDebug(COMPONENT_FSAL, "Releasing %p", obj); obj->obj_ops->put_ref(obj); /* Return the supported authentication flavor in V3 based * on the client's export permissions. These should be listed * in a preferred order. */ #ifdef _HAVE_GSSAPI if (nfs_param.krb5_param.active_krb5 == true) { if (op_ctx->export_perms.options & EXPORT_OPTION_RPCSEC_GSS_PRIV) auth_flavor[index_auth++] = MNT_RPC_GSS_PRIVACY; if (op_ctx->export_perms.options & EXPORT_OPTION_RPCSEC_GSS_INTG) auth_flavor[index_auth++] = MNT_RPC_GSS_INTEGRITY; if (op_ctx->export_perms.options & EXPORT_OPTION_RPCSEC_GSS_NONE) auth_flavor[index_auth++] = MNT_RPC_GSS_NONE; } #endif if (op_ctx->export_perms.options & EXPORT_OPTION_AUTH_UNIX) auth_flavor[index_auth++] = AUTH_UNIX; if (op_ctx->export_perms.options & EXPORT_OPTION_AUTH_NONE) auth_flavor[index_auth++] = AUTH_NONE; if (isDebug(COMPONENT_NFSPROTO)) { char str[LEN_FH_STR]; struct display_buffer dspbuf = { sizeof(str), str, str }; display_opaque_bytes(&dspbuf, fh3->data.data_val, fh3->data.data_len); LogDebug( COMPONENT_NFSPROTO, "MOUNT: Entry supports %d different flavours handle=%s for client %s", index_auth, str, op_ctx->client ? op_ctx->client->hostaddr_str : "unknown client"); } RES_MOUNTINFO->auth_flavors.auth_flavors_val = gsh_calloc(index_auth, sizeof(int)); RES_MOUNTINFO->auth_flavors.auth_flavors_len = index_auth; for (i = 0; i < index_auth; i++) RES_MOUNTINFO->auth_flavors.auth_flavors_val[i] = auth_flavor[i]; out: if (export != NULL) clear_op_context_export(); return retval; } /* mnt_Mnt */ /** * mnt_Mnt_Free: Frees the result structure allocated for mnt_Mnt. * * Frees the result structure allocated for mnt_Mnt. * * @param res [INOUT] Pointer to the result structure. * */ void mnt1_Mnt_Free(nfs_res_t *res) { /* return */ } void mnt3_Mnt_Free(nfs_res_t *res) { mountres3_ok *resok = &res->res_mnt3.mountres3_u.mountinfo; if (res->res_mnt3.fhs_status == MNT3_OK) { gsh_free(resok->auth_flavors.auth_flavors_val); gsh_free(resok->fhandle.fhandle3_val); } } nfs-ganesha-6.5/src/Protocols/NFS/mnt_Null.c000066400000000000000000000035211473756622300207330ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * file mnt_Null.c * brief MOUNTPROC_NULL for Mount protocol v1 and v3. * * mnt_Null.c : MOUNTPROC_NULL in V1, V3. * */ #include "config.h" #include "log.h" #include "nfs_core.h" #include "mount.h" #include "nfs_proto_functions.h" /** * @brief The Mount proc null function, for all versions. * * The MOUNT proc null function, for all versions. * * @param[in] arg ignored * @param[in] req ignored * @param[out] res ignored * */ int mnt_Null(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { LogDebug(COMPONENT_NFSPROTO, "REQUEST PROCESSING: Calling MNT_NULL"); return MNT3_OK; } /* mnt_Null */ /** * @brief Frees the result structure allocated for mnt_Null * * @param res [INOUT] Pointer to the result structure. * */ void mnt_Null_Free(nfs_res_t *res) { /* return */ } nfs-ganesha-6.5/src/Protocols/NFS/mnt_Umnt.c000066400000000000000000000036231473756622300207470ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * file mnt_Umnt.c * brief MOUNTPROC_UMNT for Mount protocol v1 and v3. * * */ #include "config.h" #include "log.h" #include "nfs_core.h" #include "nfs_exports.h" #include "mount.h" #include "nfs_proto_functions.h" /** * @brief The Mount proc umount function, for all versions. * * @param[in] arg * @param[in] req * @param[out] res * */ int mnt_Umnt(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { LogDebug(COMPONENT_NFSPROTO, "REQUEST PROCESSING: Calling MNT_UMNT path %s", arg->arg_mnt); /* Ganesha does not support the mount list so this is a NOOP */ return NFS_REQ_OK; } /* mnt_Umnt */ /** * mnt_Umnt_Free: Frees the result structure allocated for mnt_Umnt. * * Frees the result structure allocated for mnt_UmntAll. * * @param res [INOUT] Pointer to the result structure. * */ void mnt_Umnt_Free(nfs_res_t *res) { /* Nothing to do */ } nfs-ganesha-6.5/src/Protocols/NFS/mnt_UmntAll.c000066400000000000000000000036261473756622300214030ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * file mnt_UmntAll.c * brief MOUNTPROC_UMNTALL for Mount protocol v1 and v3. * * */ #include "config.h" #include "log.h" #include "nfs_core.h" #include "nfs_exports.h" #include "mount.h" #include "nfs_proto_functions.h" /** * @brief The Mount proc umount_all function, for all versions. * * @param[in] arg * @param[in] req * @param[out] res * */ int mnt_UmntAll(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { LogDebug(COMPONENT_NFSPROTO, "REQUEST PROCESSING: Calling MNT_UMNTALL"); /* Ganesha does not support the mount list so this is a NOOP */ return NFS_REQ_OK; } /* mnt_UmntAll */ /** * mnt_UmntAll_Free: Frees the result structure allocated for mnt_UmntAll. * * Frees the result structure allocated for mnt_UmntAll. * * @param res [INOUT] Pointer to the result structure. * */ void mnt_UmntAll_Free(nfs_res_t *res) { /* Nothing to do */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_access.c000066400000000000000000000071221473756622300213360ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_access.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "nfs23.h" #include "nfs4.h" #include "mount.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_creds.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" /** * Implements NFSPROC3_ACCESS. * * This function implements NFSPROC3_ACCESS. * * @param[in] arg NFS arguments union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable * */ int nfs3_access(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { fsal_status_t status; struct fsal_obj_handle *entry = NULL; int rc = NFS_REQ_OK; LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_access3.object, ""); /* to avoid setting it on each error case */ res->res_access3.ACCESS3res_u.resfail.obj_attributes.attributes_follow = FALSE; /* Convert file handle into a vnode */ entry = nfs3_FhandleToCache(&(arg->arg_access3.object), &(res->res_access3.status), &rc); if (entry == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ goto out; } /* Perform the 'access' call */ status = nfs_access_op(entry, arg->arg_access3.access, &res->res_access3.ACCESS3res_u.resok.access, NULL); if (status.major == ERR_FSAL_NO_ERROR || status.major == ERR_FSAL_ACCESS) { /* Build Post Op Attributes */ nfs_SetPostOpAttr( entry, &res->res_access3.ACCESS3res_u.resok.obj_attributes, NULL); res->res_access3.status = NFS3_OK; rc = NFS_REQ_OK; goto out; } /* If we are here, there was an error */ if (nfs_RetryableError(status.major)) { rc = NFS_REQ_DROP; goto out; } res->res_access3.status = nfs3_Errno_status(status); nfs_SetPostOpAttr(entry, &res->res_access3.ACCESS3res_u.resfail.obj_attributes, NULL); out: if (entry) entry->obj_ops->put_ref(entry); return rc; } /* nfs3_access */ /** * @brief Free the result structure allocated for nfs3_access. * * this function frees the result structure allocated for nfs3_access. * * @param[in,out] res Result structure. * */ void nfs3_access_free(nfs_res_t *res) { /* Nothing to do */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_commit.c000066400000000000000000000067221473756622300213720ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * file nfs3_commit.c * brief Routines used for managing the NFS4 COMPOUND functions. * * nfs3_commit.c : Routines used for managing the NFS4 COMPOUND functions. * */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "nfs23.h" #include "nfs4.h" #include "mount.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" /** * @brief Implements NFSPROC3_COMMIT * * Implements NFSPROC3_COMMIT. * * @param[in] arg NFS arguments union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable * */ int nfs3_commit(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { fsal_status_t fsal_status; struct fsal_obj_handle *obj = NULL; int rc = NFS_REQ_OK; LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_commit3.file, ""); /* To avoid setting it on each error case */ res->res_commit3.COMMIT3res_u.resfail.file_wcc.before.attributes_follow = FALSE; res->res_commit3.COMMIT3res_u.resfail.file_wcc.after.attributes_follow = FALSE; obj = nfs3_FhandleToCache(&arg->arg_commit3.file, &res->res_commit3.status, &rc); if (obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ goto out; } fsal_status = fsal_commit(obj, arg->arg_commit3.offset, arg->arg_commit3.count); if (FSAL_IS_ERROR(fsal_status)) { res->res_commit3.status = nfs3_Errno_status(fsal_status); nfs_SetWccData(NULL, obj, NULL, &res->res_commit3.COMMIT3res_u.resfail.file_wcc); rc = NFS_REQ_OK; goto out; } nfs_SetWccData(NULL, obj, NULL, &(res->res_commit3.COMMIT3res_u.resok.file_wcc)); /* Set the write verifier */ memcpy(res->res_commit3.COMMIT3res_u.resok.verf, NFS3_write_verifier, sizeof(writeverf3)); res->res_commit3.status = NFS3_OK; out: if (obj) obj->obj_ops->put_ref(obj); return rc; } /* nfs3_commit */ /** * @brief Free the result structure allocated for nfs3_commit. * * This function frees the result structure allocated for nfs3_commit. * * @param[in,out] res Result structure * */ void nfs3_commit_free(nfs_res_t *res) { /* Nothing to do here */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_create.c000066400000000000000000000160701473756622300213420ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_create.c * @brief Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_creds.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_file_handle.h" #include "nfs_proto_tools.h" #include "fsal_convert.h" #include "export_mgr.h" /** * * @brief The NFSPROC3_CREATE * * Implements the NFSPROC3_CREATE function. * * @param[in] arg NFS arguments union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable * */ int nfs3_create(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { const char *file_name = arg->arg_create3.where.name; struct fsal_obj_handle *file_obj = NULL; struct fsal_obj_handle *parent_obj = NULL; pre_op_attr pre_parent = { .attributes_follow = false }; struct fsal_attrlist sattr, attrs, parent_pre_attrs, parent_post_attrs; fsal_status_t fsal_status = { 0, 0 }; int rc = NFS_REQ_OK; fsal_verifier_t verifier; enum fsal_create_mode createmode; LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_create3.where.dir, " name: %s", file_name ? file_name : ""); /* We have the option of not sending attributes, so set ATTR_RDATTR_ERR. */ fsal_prepare_attrs(&attrs, ATTRS_NFS3 | ATTR_RDATTR_ERR); fsal_prepare_attrs(&parent_pre_attrs, ATTR_SIZE | ATTR_CTIME | ATTR_MTIME); fsal_prepare_attrs(&parent_post_attrs, ATTRS_NFS3); memset(&sattr, 0, sizeof(struct fsal_attrlist)); /* to avoid setting it on each error case */ res->res_create3.CREATE3res_u.resfail.dir_wcc.before.attributes_follow = FALSE; res->res_create3.CREATE3res_u.resfail.dir_wcc.after.attributes_follow = FALSE; parent_obj = nfs3_FhandleToCache(&arg->arg_create3.where.dir, &res->res_create3.status, &rc); if (parent_obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ goto out; } /* get directory attributes before action (for V3 reply) */ nfs_SetPreOpAttr(parent_obj, &pre_parent); /* Sanity checks: new file name must be non-null; parent must be a directory. */ if (parent_obj->type != DIRECTORY) { res->res_create3.status = NFS3ERR_NOTDIR; rc = NFS_REQ_OK; goto out; } /* if quota support is active, then we should check is the FSAL allows inode creation or not */ fsal_status = op_ctx->fsal_export->exp_ops.check_quota( op_ctx->fsal_export, CTX_FULLPATH(op_ctx), FSAL_QUOTA_INODES); if (FSAL_IS_ERROR(fsal_status)) { res->res_create3.status = NFS3ERR_DQUOT; rc = NFS_REQ_OK; goto out; } if (file_name == NULL || *file_name == '\0') { fsal_status = fsalstat(ERR_FSAL_INVAL, 0); goto out_fail; } /* Check if asked attributes are correct */ if (arg->arg_create3.how.mode == GUARDED || arg->arg_create3.how.mode == UNCHECKED) { if (nfs3_Sattr_To_FSALattr(&sattr, &arg->arg_create3.how.createhow3_u .obj_attributes) == 0) { res->res_create3.status = NFS3ERR_INVAL; rc = NFS_REQ_OK; goto out; } } if (!(sattr.valid_mask & ATTR_MODE)) { /* Make sure mode is set. */ sattr.mode = 0600; sattr.valid_mask |= ATTR_MODE; } /* Set the createmode */ createmode = nfs3_createmode_to_fsal(arg->arg_create3.how.mode); if (createmode == FSAL_EXCLUSIVE) { /* Set the verifier if EXCLUSIVE */ memcpy(verifier, &arg->arg_create3.how.createhow3_u.verf, sizeof(fsal_verifier_t)); } /* If owner or owner_group are set, and the credential was * squashed, then we must squash the set owner and owner_group. */ squash_setattr(&sattr); /* Stateless open, assume Read/Write. */ fsal_status = fsal_open2(parent_obj, NULL, FSAL_O_RDWR, createmode, file_name, &sattr, verifier, &file_obj, &attrs, &parent_pre_attrs, &parent_post_attrs); if (FSAL_IS_ERROR(fsal_status)) goto out_fail; /* Release the attributes (may release an inherited ACL) */ fsal_release_attrs(&sattr); /* Build file handle and set Post Op Fh3 structure */ if (!nfs3_FSALToFhandle(true, &res->res_create3.CREATE3res_u.resok.obj .post_op_fh3_u.handle, file_obj, op_ctx->ctx_export)) { res->res_create3.status = NFS3ERR_BADHANDLE; rc = NFS_REQ_OK; goto out; } /* Set Post Op Fh3 structure */ res->res_create3.CREATE3res_u.resok.obj.handle_follows = TRUE; /* Build entry attributes */ nfs_PreOpAttrFromFsalAttr(&parent_pre_attrs, &pre_parent); nfs_SetPostOpAttr(file_obj, &res->res_create3.CREATE3res_u.resok.obj_attributes, &attrs); nfs_SetWccData(&pre_parent, parent_obj, &parent_post_attrs, &res->res_create3.CREATE3res_u.resok.dir_wcc); res->res_create3.status = NFS3_OK; rc = NFS_REQ_OK; out: fsal_release_attrs(&parent_pre_attrs); fsal_release_attrs(&parent_post_attrs); /* return references */ if (file_obj) { fsal_close2(file_obj); file_obj->obj_ops->put_ref(file_obj); } if (parent_obj) parent_obj->obj_ops->put_ref(parent_obj); return rc; out_fail: /* Release the attributes. */ fsal_release_attrs(&attrs); if (nfs_RetryableError(fsal_status.major)) { rc = NFS_REQ_DROP; } else { res->res_create3.status = nfs3_Errno_status(fsal_status); nfs_PreOpAttrFromFsalAttr(&parent_pre_attrs, &pre_parent); nfs_SetWccData(&pre_parent, parent_obj, &parent_post_attrs, &res->res_create3.CREATE3res_u.resfail.dir_wcc); } goto out; } /* nfs3_create */ /** * @brief Free the result structure allocated for nfs3_create. * * This function frees the result structure allocated for nfs3_create. * * @param[in,out] res Result structure * */ void nfs3_create_free(nfs_res_t *res) { nfs_fh3 *handle = &res->res_create3.CREATE3res_u.resok.obj.post_op_fh3_u.handle; if ((res->res_create3.status == NFS3_OK) && (res->res_create3.CREATE3res_u.resok.obj.handle_follows)) { gsh_free(handle->data.data_val); } } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_fsinfo.c000066400000000000000000000121621473756622300213610ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_fsinfo.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include #include #include #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "export_mgr.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_proto_tools.h" /** * @brief Implement NFSPROC3_FSINFO * * This function Implements NFSPROC3_FSINFO. * * @todo ACE: This function uses a lot of constants instead of * retrieving information from the FSAL as it ought. * * @param[in] arg NFS arguments union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable */ int nfs3_fsinfo(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { struct fsal_obj_handle *obj = NULL; int rc = NFS_REQ_OK; FSINFO3resok *const FSINFO_FIELD = &res->res_fsinfo3.FSINFO3res_u.resok; fsal_dynamicfsinfo_t dynamicinfo; fsal_status_t fsal_status; LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_fsinfo3.fsroot, ""); /* To avoid setting it on each error case */ res->res_fsinfo3.FSINFO3res_u.resfail.obj_attributes.attributes_follow = FALSE; obj = nfs3_FhandleToCache(&arg->arg_fsinfo3.fsroot, &res->res_fsinfo3.status, &rc); if (obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ goto out; } /* Get statistics and convert from FSAL to get time_delta */ fsal_status = fsal_statfs(obj, &dynamicinfo); if (FSAL_IS_ERROR(fsal_status)) { /* At this point we met an error */ LogFullDebug(COMPONENT_NFSPROTO, "failed statfs: fsal_status=%s", fsal_err_txt(fsal_status)); if (nfs_RetryableError(fsal_status.major)) { /* Drop retryable errors. */ rc = NFS_REQ_DROP; } else { res->res_fsstat3.status = nfs3_Errno_status(fsal_status); rc = NFS_REQ_OK; } goto out; } /* New fields were added to nfs_config_t to handle this value. We use them */ FSINFO_FIELD->rtmax = atomic_fetch_uint64_t(&op_ctx->ctx_export->MaxRead); FSINFO_FIELD->rtpref = atomic_fetch_uint64_t(&op_ctx->ctx_export->PrefRead); /* This field is generally unused, it will be removed in V4 */ FSINFO_FIELD->rtmult = DEV_BSIZE; FSINFO_FIELD->wtmax = atomic_fetch_uint64_t(&op_ctx->ctx_export->MaxWrite); FSINFO_FIELD->wtpref = atomic_fetch_uint64_t(&op_ctx->ctx_export->PrefWrite); /* This field is generally unused, it will be removed in V4 */ FSINFO_FIELD->wtmult = DEV_BSIZE; FSINFO_FIELD->dtpref = atomic_fetch_uint64_t(&op_ctx->ctx_export->PrefReaddir); FSINFO_FIELD->maxfilesize = op_ctx->fsal_export->exp_ops.fs_maxfilesize( op_ctx->fsal_export); FSINFO_FIELD->time_delta.tv_sec = dynamicinfo.time_delta.tv_sec; FSINFO_FIELD->time_delta.tv_nsec = dynamicinfo.time_delta.tv_nsec; LogFullDebug(COMPONENT_NFSPROTO, "rtmax = %d | rtpref = %d | trmult = %d", FSINFO_FIELD->rtmax, FSINFO_FIELD->rtpref, FSINFO_FIELD->rtmult); LogFullDebug(COMPONENT_NFSPROTO, "wtmax = %d | wtpref = %d | wrmult = %d", FSINFO_FIELD->wtmax, FSINFO_FIELD->wtpref, FSINFO_FIELD->wtmult); LogFullDebug(COMPONENT_NFSPROTO, "dtpref = %d | maxfilesize = %" PRIu64, FSINFO_FIELD->dtpref, FSINFO_FIELD->maxfilesize); /* Allow all kinds of operations to be performed on the server through NFS v3 */ FSINFO_FIELD->properties = FSF3_LINK | FSF3_SYMLINK | FSF3_HOMOGENEOUS | FSF3_CANSETTIME; nfs_SetPostOpAttr( obj, &res->res_fsinfo3.FSINFO3res_u.resok.obj_attributes, NULL); res->res_fsinfo3.status = NFS3_OK; out: if (obj) obj->obj_ops->put_ref(obj); return rc; } /* nfs3_fsinfo */ /** * @brief Free the result structure allocated for nfs3_fsinfo. * * This function frees the result structure allocated for nfs3_fsinfo. * * @param[in,out] res The result structure * */ void nfs3_fsinfo_free(nfs_res_t *res) { /* Nothing to do here */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_fsstat.c000066400000000000000000000117421473756622300214040ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_fsstat.c * @brief Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs23.h" #include "nfs4.h" #include "mount.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_proto_tools.h" /** * * @brief The NFSPROC3_FSSTAT * * Implements the NFSPROC3_FSSTAT. * * @param[in] arg NFS argument union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable * */ int nfs3_fsstat(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { fsal_dynamicfsinfo_t dynamicinfo; fsal_status_t fsal_status; struct fsal_obj_handle *obj = NULL; int rc = NFS_REQ_OK; LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_fsstat3.fsroot, ""); /* to avoid setting it on each error case */ res->res_fsstat3.FSSTAT3res_u.resfail.obj_attributes.attributes_follow = FALSE; obj = nfs3_FhandleToCache(&arg->arg_fsstat3.fsroot, &res->res_fsstat3.status, &rc); if (obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ return rc; } /* Get statistics and convert from FSAL */ fsal_status = fsal_statfs(obj, &dynamicinfo); if (FSAL_IS_ERROR(fsal_status)) { /* At this point we met an error */ LogFullDebug(COMPONENT_NFSPROTO, "failed statfs: fsal_status=%s", fsal_err_txt(fsal_status)); if (nfs_RetryableError(fsal_status.major)) { /* Drop retryable errors. */ rc = NFS_REQ_DROP; } else { res->res_fsstat3.status = nfs3_Errno_status(fsal_status); rc = NFS_REQ_OK; } goto out; } LogFullDebug(COMPONENT_NFSPROTO, "nfs_Fsstat --> dynamicinfo.total_bytes=%" PRIu64 " dynamicinfo.free_bytes=%" PRIu64 " dynamicinfo.avail_bytes=%" PRIu64, dynamicinfo.total_bytes, dynamicinfo.free_bytes, dynamicinfo.avail_bytes); LogFullDebug(COMPONENT_NFSPROTO, "nfs_Fsstat --> dynamicinfo.total_files=%" PRIu64 " dynamicinfo.free_files=%" PRIu64 " dynamicinfo.avail_files=%" PRIu64, dynamicinfo.total_files, dynamicinfo.free_files, dynamicinfo.avail_files); nfs_SetPostOpAttr( obj, &res->res_fsstat3.FSSTAT3res_u.resok.obj_attributes, NULL); res->res_fsstat3.FSSTAT3res_u.resok.tbytes = dynamicinfo.total_bytes; res->res_fsstat3.FSSTAT3res_u.resok.fbytes = dynamicinfo.free_bytes; res->res_fsstat3.FSSTAT3res_u.resok.abytes = dynamicinfo.avail_bytes; res->res_fsstat3.FSSTAT3res_u.resok.tfiles = dynamicinfo.total_files; res->res_fsstat3.FSSTAT3res_u.resok.ffiles = dynamicinfo.free_files; res->res_fsstat3.FSSTAT3res_u.resok.afiles = dynamicinfo.avail_files; /* volatile FS */ res->res_fsstat3.FSSTAT3res_u.resok.invarsec = 0; res->res_fsstat3.status = NFS3_OK; LogFullDebug(COMPONENT_NFSPROTO, "nfs_Fsstat --> tbytes=%" PRIu64 " fbytes=%" PRIu64 " abytes=%" PRIu64, res->res_fsstat3.FSSTAT3res_u.resok.tbytes, res->res_fsstat3.FSSTAT3res_u.resok.fbytes, res->res_fsstat3.FSSTAT3res_u.resok.abytes); LogFullDebug(COMPONENT_NFSPROTO, "nfs_Fsstat --> tfiles=%" PRIu64 " fffiles=%" PRIu64 " afiles=%" PRIu64, res->res_fsstat3.FSSTAT3res_u.resok.tfiles, res->res_fsstat3.FSSTAT3res_u.resok.ffiles, res->res_fsstat3.FSSTAT3res_u.resok.afiles); rc = NFS_REQ_OK; out: /* return references */ obj->obj_ops->put_ref(obj); return rc; } /* nfs3_fsstat */ /** * @brief Free the result structure allocated for nfs3_fsstat * * This function frees the result structure allocated for nfs3_fsstat. * * @param[in] res Result structure * */ void nfs3_fsstat_free(nfs_res_t *res) { /* Nothing to do here */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_getattr.c000066400000000000000000000064371473756622300215570ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_getattr.c * @brief Implements the NFSv3 GETATTR proc */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs23.h" #include "nfs4.h" #include "mount.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_proto_tools.h" /** * * @brief Get attributes for a file * * Get attributes for a file. Implements NFSPROC3_GETATTR. * * @param[in] arg NFS arguments union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable */ int nfs3_getattr(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { struct fsal_obj_handle *obj = NULL; int rc = NFS_REQ_OK; struct fsal_attrlist *attrs; fsal_status_t status; attrs = &res->res_getattr3.GETATTR3res_u.resok.obj_attributes; LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_getattr3.object, ""); fsal_prepare_attrs(attrs, ATTRS_NFS3); obj = nfs3_FhandleToCache(&arg->arg_getattr3.object, &res->res_getattr3.status, &rc); if (obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ LogFullDebug(COMPONENT_NFSPROTO, "nfs_Getattr returning %d", rc); goto out; } status = obj->obj_ops->getattrs(obj, attrs); if (FSAL_IS_ERROR(status)) { res->res_getattr3.status = nfs3_Errno_status(status); LogFullDebug(COMPONENT_NFSPROTO, "nfs_Getattr set failed status v3"); rc = NFS_REQ_OK; goto out; } nfs3_Fixup_FSALattr(obj, attrs); res->res_getattr3.status = NFS3_OK; LogFullDebug(COMPONENT_NFSPROTO, "nfs_Getattr succeeded"); rc = NFS_REQ_OK; out: /* Done with the attrs (NFSv3 doesn't use ANY of the reffed attributes) */ fsal_release_attrs(attrs); /* return references */ if (obj) obj->obj_ops->put_ref(obj); return rc; } /** * @brief Free the result structure allocated for nfs3_getattr. * * @param[in,out] resp Result structure * */ void nfs3_getattr_free(nfs_res_t *resp) { /* Nothing to do here */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_link.c000066400000000000000000000133351473756622300210350ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_link.c * @brief everything that is needed to handle NFS PROC3 LINK. */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_proto_tools.h" #include "nfs_file_handle.h" #include "client_mgr.h" /** * @brief Helper to verify validity of export ID's * * @l3_arg NFS argument * @req SVC request * * @return nfsstat3 */ static enum nfsstat3 nfs3_verify_exportid(struct LINK3args *l3_arg, struct svc_req *req) { const int to_exportid = nfs3_FhandleToExportId(&l3_arg->link.dir); const int from_exportid = nfs3_FhandleToExportId(&l3_arg->file); if (to_exportid < 0 || from_exportid < 0) { LogInfo(COMPONENT_DISPATCH, "NFS%d LINK Request from client %s has badly formed handle for link dir", req->rq_msg.cb_vers, op_ctx->client ? op_ctx->client->hostaddr_str : "unknown client"); return NFS3ERR_BADHANDLE; } /* Both objects have to be in the same filesystem */ if (to_exportid != from_exportid) return NFS3ERR_XDEV; return NFS3_OK; } /** * * @brief The NFSPROC3_LINK * * The NFSPROC3_LINK. * * @param[in] arg NFS argument union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable * */ int nfs3_link(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { struct LINK3args *l3_arg = &arg->arg_link3; struct LINK3res *l3_res = &res->res_link3; const char *link_name = l3_arg->link.name; struct fsal_obj_handle *target_obj = NULL; struct fsal_obj_handle *parent_obj = NULL; pre_op_attr pre_parent = { 0 }; fsal_status_t fsal_status = { 0, 0 }; struct fsal_attrlist destdir_pre_attrs, destdir_post_attrs; int rc = NFS_REQ_OK; LogNFS3_Operation2(COMPONENT_NFSPROTO, req, &l3_arg->file, NULL, &l3_arg->link.dir, link_name); fsal_prepare_attrs(&destdir_pre_attrs, ATTR_SIZE | ATTR_CTIME | ATTR_MTIME); fsal_prepare_attrs(&destdir_post_attrs, ATTRS_NFS3); /* to avoid setting it on each error case */ l3_res->LINK3res_u.resfail.file_attributes.attributes_follow = FALSE; l3_res->LINK3res_u.resfail.linkdir_wcc.before.attributes_follow = FALSE; l3_res->LINK3res_u.resfail.linkdir_wcc.after.attributes_follow = FALSE; l3_res->status = nfs3_verify_exportid(l3_arg, req); if (l3_res->status != NFS3_OK) return rc; parent_obj = nfs3_FhandleToCache(&l3_arg->link.dir, &l3_res->status, &rc); if (parent_obj == NULL) return rc; /* Status and rc are set by nfs3_FhandleToCache */ nfs_SetPreOpAttr(parent_obj, &pre_parent); target_obj = nfs3_FhandleToCache(&l3_arg->file, &l3_res->status, &rc); if (target_obj == NULL) { parent_obj->obj_ops->put_ref(parent_obj); return rc; /* Status and rc are set by nfs3_FhandleToCache */ } if (parent_obj->type != DIRECTORY) { l3_res->status = NFS3ERR_NOTDIR; goto out; } if (link_name == NULL || *link_name == '\0') { l3_res->status = NFS3ERR_INVAL; goto out; } fsal_status = fsal_link(target_obj, parent_obj, link_name, &destdir_pre_attrs, &destdir_post_attrs); if (FSAL_IS_ERROR(fsal_status)) { /* If we are here, there was an error */ LogFullDebug(COMPONENT_NFSPROTO, "failed link: fsal_status=%s", fsal_err_txt(fsal_status)); if (nfs_RetryableError(fsal_status.major)) { rc = NFS_REQ_DROP; goto out; } l3_res->status = nfs3_Errno_status(fsal_status); nfs_PreOpAttrFromFsalAttr(&destdir_pre_attrs, &pre_parent); nfs_SetPostOpAttr(target_obj, &l3_res->LINK3res_u.resfail.file_attributes, NULL); nfs_SetWccData(&pre_parent, parent_obj, &destdir_post_attrs, &l3_res->LINK3res_u.resfail.linkdir_wcc); } else { nfs_SetPostOpAttr(target_obj, &l3_res->LINK3res_u.resok.file_attributes, NULL); nfs_PreOpAttrFromFsalAttr(&destdir_pre_attrs, &pre_parent); nfs_SetWccData(&pre_parent, parent_obj, &destdir_post_attrs, &l3_res->LINK3res_u.resok.linkdir_wcc); l3_res->status = NFS3_OK; } out: fsal_release_attrs(&destdir_pre_attrs); fsal_release_attrs(&destdir_post_attrs); /* return references */ target_obj->obj_ops->put_ref(target_obj); parent_obj->obj_ops->put_ref(parent_obj); return rc; } /* nfs3_link */ /** * @brief Free the result structure allocated for nfs3_link * * This function frees the result structure allocated for nfs3_link. * * @param[in,out] resp Result structure * */ void nfs3_link_free(nfs_res_t *resp) { /* Nothing to do here */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_lookup.c000066400000000000000000000101501473756622300214010ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_lookup.c * @brief everything that is needed to handle NFS PROC3 LINK. */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_proto_tools.h" /** * * @brief The NFS3_LOOKUP * * Implements the NFS3_LOOKUP function. * * @param[in] arg NFS arguments union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable * */ int nfs3_lookup(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { struct fsal_obj_handle *obj_dir = NULL; struct fsal_obj_handle *obj_file = NULL; fsal_status_t fsal_status; char *name = arg->arg_lookup3.what.name; int rc = NFS_REQ_OK; struct fsal_attrlist attrs; LOOKUP3resfail *resfail = &res->res_lookup3.LOOKUP3res_u.resfail; LOOKUP3resok *resok = &res->res_lookup3.LOOKUP3res_u.resok; /* We have the option of not sending attributes, so set ATTR_RDATTR_ERR. */ fsal_prepare_attrs(&attrs, ATTRS_NFS3 | ATTR_RDATTR_ERR); LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_lookup3.what.dir, " name: %s", name); /* to avoid setting it on each error case */ resfail->dir_attributes.attributes_follow = FALSE; obj_dir = nfs3_FhandleToCache(&arg->arg_lookup3.what.dir, &res->res_lookup3.status, &rc); if (obj_dir == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ goto out; } fsal_status = fsal_lookup(obj_dir, name, &obj_file, &attrs); if (FSAL_IS_ERROR(fsal_status)) { /* If we are here, there was an error */ if (nfs_RetryableError(fsal_status.major)) { rc = NFS_REQ_DROP; goto out; } res->res_lookup3.status = nfs3_Errno_status(fsal_status); nfs_SetPostOpAttr(obj_dir, &resfail->dir_attributes, NULL); } else { /* Build FH */ if (nfs3_FSALToFhandle(true, &resok->object, obj_file, op_ctx->ctx_export)) { /* Build entry attributes */ nfs_SetPostOpAttr(obj_file, &resok->obj_attributes, &attrs); /* Build directory attributes */ nfs_SetPostOpAttr(obj_dir, &resok->dir_attributes, NULL); res->res_lookup3.status = NFS3_OK; } else { res->res_lookup3.status = NFS3ERR_BADHANDLE; } } rc = NFS_REQ_OK; out: /* Release the attributes. */ fsal_release_attrs(&attrs); /* return references */ if (obj_dir) obj_dir->obj_ops->put_ref(obj_dir); if (obj_file) obj_file->obj_ops->put_ref(obj_file); return rc; } /* nfs3_lookup */ /** * @brief Free the result structure allocated for nfs3_lookup. * * This function frees the result structure allocated for nfs3_lookup. * * @param[in,out] res Result structure * */ void nfs3_lookup_free(nfs_res_t *res) { if (res->res_lookup3.status == NFS3_OK) { gsh_free(res->res_lookup3.LOOKUP3res_u.resok.object.data .data_val); } } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_mkdir.c000066400000000000000000000137111473756622300212040ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_mkdir.c * @brief Evrythinhg you need to handle NFSv3 MKDIR */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_creds.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_file_handle.h" #include "nfs_proto_tools.h" #include "export_mgr.h" /** * * @brief The NFSPROC3_MKDIR * * Implements the NFSPROC3_MKDIR. * * @param[in] arg NFS arguments union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable * */ int nfs3_mkdir(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { const char *dir_name = arg->arg_mkdir3.where.name; struct fsal_obj_handle *dir_obj = NULL; struct fsal_obj_handle *parent_obj = NULL; pre_op_attr pre_parent = { .attributes_follow = false }; fsal_status_t fsal_status = { 0, 0 }; int rc = NFS_REQ_OK; struct fsal_attrlist sattr, attrs, parent_pre_attrs, parent_post_attrs; MKDIR3resfail *resfail = &res->res_mkdir3.MKDIR3res_u.resfail; MKDIR3resok *resok = &res->res_mkdir3.MKDIR3res_u.resok; /* We have the option of not sending attributes, so set ATTR_RDATTR_ERR. */ fsal_prepare_attrs(&attrs, ATTRS_NFS3 | ATTR_RDATTR_ERR); fsal_prepare_attrs(&parent_pre_attrs, ATTR_SIZE | ATTR_CTIME | ATTR_MTIME); fsal_prepare_attrs(&parent_post_attrs, ATTRS_NFS3); memset(&sattr, 0, sizeof(sattr)); LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_mkdir3.where.dir, " name: %s", dir_name); /* to avoid setting it on each error case */ resfail->dir_wcc.before.attributes_follow = FALSE; resfail->dir_wcc.after.attributes_follow = FALSE; parent_obj = nfs3_FhandleToCache(&arg->arg_mkdir3.where.dir, &res->res_mkdir3.status, &rc); if (parent_obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ goto out; } /* Sanity checks */ if (parent_obj->type != DIRECTORY) { res->res_mkdir3.status = NFS3ERR_NOTDIR; rc = NFS_REQ_OK; goto out; } /* if quota support is active, then we should check is the FSAL allows inode creation or not */ fsal_status = op_ctx->fsal_export->exp_ops.check_quota( op_ctx->fsal_export, CTX_FULLPATH(op_ctx), FSAL_QUOTA_INODES); if (FSAL_IS_ERROR(fsal_status)) { res->res_mkdir3.status = NFS3ERR_DQUOT; rc = NFS_REQ_OK; goto out; } if (dir_name == NULL || *dir_name == '\0') { fsal_status = fsalstat(ERR_FSAL_INVAL, 0); goto out_fail; } if (nfs3_Sattr_To_FSALattr(&sattr, &arg->arg_mkdir3.attributes) == 0) { fsal_status = fsalstat(ERR_FSAL_INVAL, 0); goto out_fail; } squash_setattr(&sattr); if (!(sattr.valid_mask & ATTR_MODE)) { /* Make sure mode is set. */ sattr.mode = 0; sattr.valid_mask |= ATTR_MODE; } /* Try to create the directory */ fsal_status = fsal_create(parent_obj, dir_name, DIRECTORY, &sattr, NULL, &dir_obj, &attrs, &parent_pre_attrs, &parent_post_attrs); /* Release the attributes (may release an inherited ACL) */ fsal_release_attrs(&sattr); if (FSAL_IS_ERROR(fsal_status)) goto out_fail; /* Build file handle */ if (!nfs3_FSALToFhandle(true, &resok->obj.post_op_fh3_u.handle, dir_obj, op_ctx->ctx_export)) { res->res_mkdir3.status = NFS3ERR_BADHANDLE; rc = NFS_REQ_OK; goto out; } /* Set Post Op Fh3 structure */ resok->obj.handle_follows = true; /* Build entry attributes */ nfs_PreOpAttrFromFsalAttr(&parent_pre_attrs, &pre_parent); nfs_SetPostOpAttr(dir_obj, &resok->obj_attributes, &attrs); /* Build Weak Cache Coherency data */ nfs_SetWccData(&pre_parent, parent_obj, &parent_post_attrs, &resok->dir_wcc); res->res_mkdir3.status = NFS3_OK; rc = NFS_REQ_OK; goto out; out_fail: res->res_mkdir3.status = nfs3_Errno_status(fsal_status); nfs_PreOpAttrFromFsalAttr(&parent_pre_attrs, &pre_parent); nfs_SetWccData(&pre_parent, parent_obj, &parent_post_attrs, &resfail->dir_wcc); if (nfs_RetryableError(fsal_status.major)) rc = NFS_REQ_DROP; out: /* Release the attributes. */ fsal_release_attrs(&attrs); fsal_release_attrs(&parent_pre_attrs); fsal_release_attrs(&parent_post_attrs); /* return references */ if (dir_obj) dir_obj->obj_ops->put_ref(dir_obj); if (parent_obj) parent_obj->obj_ops->put_ref(parent_obj); return rc; } /** * @brief Free the result structure allocated for nfs3_mkdir. * * This function frees the result structure allocated for nfs3_mkdir. * * @param[in,out] res Result structure * */ void nfs3_mkdir_free(nfs_res_t *res) { nfs_fh3 *handle = &res->res_mkdir3.MKDIR3res_u.resok.obj.post_op_fh3_u.handle; if ((res->res_mkdir3.status == NFS3_OK) && (res->res_mkdir3.MKDIR3res_u.resok.obj.handle_follows)) { gsh_free(handle->data.data_val); } } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_mknod.c000066400000000000000000000161571473756622300212150ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_mknod.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * * */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_creds.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include "export_mgr.h" /** * @brief Implements NFSPROC3_MKNOD * * Implements NFSPROC3_MKNOD. * * @param[in] arg NFS arguments union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable */ int nfs3_mknod(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { struct fsal_obj_handle *parent_obj = NULL; struct fsal_obj_handle *node_obj = NULL; pre_op_attr pre_parent; object_file_type_t nodetype; const char *file_name = arg->arg_mknod3.where.name; int rc = NFS_REQ_OK; fsal_status_t fsal_status; struct fsal_attrlist sattr, attrs, parent_pre_attrs, parent_post_attrs; MKNOD3resfail *resfail = &res->res_mknod3.MKNOD3res_u.resfail; MKNOD3resok *const rok = &res->res_mknod3.MKNOD3res_u.resok; /* We have the option of not sending attributes, so set ATTR_RDATTR_ERR. */ fsal_prepare_attrs(&attrs, ATTRS_NFS3 | ATTR_RDATTR_ERR); fsal_prepare_attrs(&parent_pre_attrs, ATTR_SIZE | ATTR_CTIME | ATTR_MTIME); fsal_prepare_attrs(&parent_post_attrs, ATTRS_NFS3); memset(&sattr, 0, sizeof(sattr)); LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_mknod3.where.dir, " name: %s", file_name); /* to avoid setting them on each error case */ resfail->dir_wcc.before.attributes_follow = FALSE; resfail->dir_wcc.after.attributes_follow = FALSE; /* retrieve parent entry */ parent_obj = nfs3_FhandleToCache(&arg->arg_mknod3.where.dir, &res->res_mknod3.status, &rc); if (parent_obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ goto out; } nfs_SetPreOpAttr(parent_obj, &pre_parent); /* Sanity checks: new node name must be non-null; parent must be a directory. */ if (parent_obj->type != DIRECTORY) { res->res_mknod3.status = NFS3ERR_NOTDIR; rc = NFS_REQ_OK; goto out; } if (file_name == NULL || *file_name == '\0') { res->res_mknod3.status = NFS3ERR_INVAL; rc = NFS_REQ_OK; goto out; } switch (arg->arg_mknod3.what.type) { case NF3CHR: case NF3BLK: if (nfs3_Sattr_To_FSALattr( &sattr, &arg->arg_mknod3.what.mknoddata3_u.device .dev_attributes) == 0) { res->res_mknod3.status = NFS3ERR_INVAL; rc = NFS_REQ_OK; goto out; } sattr.rawdev.major = arg->arg_mknod3.what.mknoddata3_u.device.spec.specdata1; sattr.rawdev.minor = arg->arg_mknod3.what.mknoddata3_u.device.spec.specdata2; sattr.valid_mask |= ATTR_RAWDEV; break; case NF3FIFO: case NF3SOCK: if (nfs3_Sattr_To_FSALattr(&sattr, &arg->arg_mknod3.what.mknoddata3_u .pipe_attributes) == 0) { res->res_mknod3.status = NFS3ERR_INVAL; rc = NFS_REQ_OK; goto out; } break; default: res->res_mknod3.status = NFS3ERR_BADTYPE; rc = NFS_REQ_OK; goto out; } switch (arg->arg_mknod3.what.type) { case NF3CHR: nodetype = CHARACTER_FILE; break; case NF3BLK: nodetype = BLOCK_FILE; break; case NF3FIFO: nodetype = FIFO_FILE; break; case NF3SOCK: nodetype = SOCKET_FILE; break; default: res->res_mknod3.status = NFS3ERR_BADTYPE; rc = NFS_REQ_OK; goto out; } /* if quota support is active, then we should check is the FSAL allows inode creation or not */ fsal_status = op_ctx->fsal_export->exp_ops.check_quota( op_ctx->fsal_export, CTX_FULLPATH(op_ctx), FSAL_QUOTA_INODES); if (FSAL_IS_ERROR(fsal_status)) { res->res_mknod3.status = NFS3ERR_DQUOT; return NFS_REQ_OK; } squash_setattr(&sattr); if (!(sattr.valid_mask & ATTR_MODE)) { /* Make sure mode is set. */ sattr.mode = 0; sattr.valid_mask |= ATTR_MODE; } /* Try to create it */ fsal_status = fsal_create(parent_obj, file_name, nodetype, &sattr, NULL, &node_obj, &attrs, &parent_pre_attrs, &parent_post_attrs); /* Release the attributes (may release an inherited ACL) */ fsal_release_attrs(&sattr); if (FSAL_IS_ERROR(fsal_status)) goto out_fail; /* Build file handle */ if (!nfs3_FSALToFhandle(true, &rok->obj.post_op_fh3_u.handle, node_obj, op_ctx->ctx_export)) { res->res_mknod3.status = NFS3ERR_BADHANDLE; rc = NFS_REQ_OK; goto out; } /* Set Post Op Fh3 structure */ rok->obj.handle_follows = TRUE; /* Build entry attributes */ nfs_PreOpAttrFromFsalAttr(&parent_pre_attrs, &pre_parent); nfs_SetPostOpAttr(node_obj, &rok->obj_attributes, &attrs); /* Build Weak Cache Coherency data */ nfs_SetWccData(&pre_parent, parent_obj, &parent_post_attrs, &rok->dir_wcc); res->res_mknod3.status = NFS3_OK; rc = NFS_REQ_OK; goto out; out_fail: res->res_mknod3.status = nfs3_Errno_status(fsal_status); nfs_PreOpAttrFromFsalAttr(&parent_pre_attrs, &pre_parent); nfs_SetWccData(&pre_parent, parent_obj, &parent_post_attrs, &resfail->dir_wcc); if (nfs_RetryableError(fsal_status.major)) rc = NFS_REQ_DROP; out: /* Release the attributes. */ fsal_release_attrs(&attrs); fsal_release_attrs(&parent_pre_attrs); fsal_release_attrs(&parent_post_attrs); /* return references */ if (parent_obj) parent_obj->obj_ops->put_ref(parent_obj); if (node_obj) node_obj->obj_ops->put_ref(node_obj); return rc; } /* nfs3_mknod */ /** * @brief Free the result structure allocated for nfs3_mknod. * * This function frees the result structure allocated for nfs3_mknod. * * @param[in,out] res The result structure. * */ void nfs3_mknod_free(nfs_res_t *res) { nfs_fh3 *handle = &res->res_mknod3.MKNOD3res_u.resok.obj.post_op_fh3_u.handle; if ((res->res_mknod3.status == NFS3_OK) && (res->res_mknod3.MKNOD3res_u.resok.obj.handle_follows)) { gsh_free(handle->data.data_val); } } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_pathconf.c000066400000000000000000000067461473756622300217120ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_pathconf.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" /** * @brief Implements NFSPROC3_PATHCONF * * Implements NFSPROC3_PATHCONF. * * @param[in] arg NFS arguments union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable */ int nfs3_pathconf(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { struct fsal_obj_handle *obj = NULL; int rc = NFS_REQ_OK; struct fsal_export *exp_hdl = op_ctx->fsal_export; PATHCONF3resfail *resfail = &res->res_pathconf3.PATHCONF3res_u.resfail; PATHCONF3resok *resok = &res->res_pathconf3.PATHCONF3res_u.resok; LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_pathconf3.object, ""); /* to avoid setting it on each error case */ resfail->obj_attributes.attributes_follow = FALSE; /* Convert file handle into a fsal_handle */ obj = nfs3_FhandleToCache(&arg->arg_pathconf3.object, &res->res_pathconf3.status, &rc); if (obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ goto out; } resok->linkmax = exp_hdl->exp_ops.fs_maxlink(exp_hdl); resok->name_max = exp_hdl->exp_ops.fs_maxnamelen(exp_hdl); resok->no_trunc = exp_hdl->exp_ops.fs_supports(exp_hdl, fso_no_trunc); resok->chown_restricted = exp_hdl->exp_ops.fs_supports(exp_hdl, fso_chown_restricted); resok->case_insensitive = exp_hdl->exp_ops.fs_supports(exp_hdl, fso_case_insensitive); resok->case_preserving = exp_hdl->exp_ops.fs_supports(exp_hdl, fso_case_preserving); /* Build post op file attributes */ nfs_SetPostOpAttr(obj, &resok->obj_attributes, NULL); out: if (obj) obj->obj_ops->put_ref(obj); return rc; } /* nfs3_pathconf */ /** * @brief Free the result structure allocated for nfs3_pathconf. * * This function free the result structure allocated for nfs3_pathconf. * * @param[in,out] res Result structure. * */ void nfs3_pathconf_free(nfs_res_t *res) { /* Nothing to do here */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_read.c000066400000000000000000000326531473756622300210170ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_read.c * @brief Everything you need to read. */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include "server_stats.h" #include "export_mgr.h" #include "sal_functions.h" static void nfs_read_ok(READ3resok *resok, struct fsal_io_arg *read_arg, struct fsal_obj_handle *obj) { /* Build Post Op Attributes */ nfs_SetPostOpAttr(obj, &resok->file_attributes, NULL); resok->data.data_len = read_arg->io_amount; resok->count = read_arg->io_amount; resok->eof = read_arg->end_of_file; if (read_arg->io_amount == 0) { /* We won't need the FSAL's iovec and buffers if it used them */ if (read_arg->iov_release != NULL) { read_arg->iov_release(read_arg->release_data); read_arg->iov[0].iov_base = NULL; read_arg->iov_release = NULL; } /* We will use the iov we set up before the call, but set the * length of the buffer to 0. The io_data->release is already * set up. */ resok->data.iov[0].iov_len = 0; return; } if (read_arg->iov != resok->data.iov) { /* FSAL returned a different iovector */ resok->data.iov = read_arg->iov; resok->data.iovcnt = read_arg->iov_count; } if (read_arg->iov_release != resok->data.release) { /* The FSAL replaced the release */ resok->data.release = read_arg->iov_release; resok->data.release_data = read_arg->release_data; } } struct nfs3_read_data { /** Results for read */ nfs_res_t *res; /** RPC Request for this READ */ struct svc_req *req; /** Object being acted on */ struct fsal_obj_handle *obj; /** Return code */ int rc; /** Flags to control synchronization */ uint32_t flags; /** Arguments for read call - must be last */ struct fsal_io_arg read_arg; }; static int nfs3_complete_read(struct nfs3_read_data *data) { struct fsal_io_arg *read_arg = &data->read_arg; READ3resfail *resfail = &data->res->res_read3.READ3res_u.resfail; if (data->rc == NFS_REQ_OK) { if (!op_ctx->fsal_export->exp_ops.fs_supports( op_ctx->fsal_export, fso_compliant_eof_behavior) && nfs_param.core_param.getattrs_in_complete_read && !read_arg->end_of_file) { /* * NFS requires to set the EOF flag for all reads that * reach the EOF, i.e., even the ones returning data. * Most FSALs don't set the flag in this case. The only * client that cares about this is ESXi. Other clients * will just see a short read and continue reading and * then get the EOF flag as 0 bytes are returned. */ struct fsal_attrlist attrs; fsal_status_t status; fsal_prepare_attrs(&attrs, ATTR_SIZE); status = data->obj->obj_ops->getattrs(data->obj, &attrs); if (FSAL_IS_SUCCESS(status)) { read_arg->end_of_file = (read_arg->offset + read_arg->io_amount) >= attrs.filesize; } /* Done with the attrs */ fsal_release_attrs(&attrs); } nfs_read_ok(&data->res->res_read3.READ3res_u.resok, read_arg, data->obj); goto out; } /* Just in case... We won't need the FSAL's iovec and buffers if it used * them */ if (read_arg->iov_release != NULL) read_arg->iov_release(read_arg->release_data); /* If we are here, there was an error */ if (data->rc == NFS_REQ_DROP) { goto out; } nfs_SetPostOpAttr(data->obj, &resfail->file_attributes, NULL); /* Now we convert NFS_REQ_ERROR into NFS_REQ_OK */ data->rc = NFS_REQ_OK; out: /* return references */ if (data->obj) data->obj->obj_ops->put_ref(data->obj); server_stats_io_done(read_arg->io_request, read_arg->io_amount, (data->rc == NFS_REQ_OK) ? true : false, false); return data->rc; } static void nfs3_read_cb(struct fsal_obj_handle *obj, fsal_status_t ret, void *read_data, void *caller_data); static enum xprt_stat nfs3_read_resume(struct svc_req *req) { nfs_request_t *reqdata = container_of(req, nfs_request_t, svc); struct nfs3_read_data *data = reqdata->proc_data; int rc; uint32_t flags; /* Restore the op_ctx */ resume_op_context(&reqdata->op_context); if (data->read_arg.fsal_resume) { /* FSAL is requesting another read2 call on resume */ atomic_postclear_uint32_t_bits( &data->flags, ASYNC_PROC_EXIT | ASYNC_PROC_DONE); data->obj->obj_ops->read2(data->obj, true, nfs3_read_cb, &data->read_arg, data); /* Only atomically set the flags if we actually call read2, * otherwise we will have indicated as having been DONE. */ flags = atomic_postset_uint32_t_bits(&data->flags, ASYNC_PROC_EXIT); if ((flags & ASYNC_PROC_DONE) != ASYNC_PROC_DONE) { /* The read was not finished before we got here. When * the read completes, nfs3_read_cb() will have to * reschedule the request for completion. The resume * will be resolved by nfs3_read_resume() which will * free read_data and return the appropriate return * result. We will NOT go async again for the read op * (but could for a subsequent op in the compound). */ suspend_op_context(); return XPRT_SUSPEND; } } /* Complete the read */ rc = nfs3_complete_read(data); /* Free the read_data. */ gsh_free(data); reqdata->proc_data = NULL; nfs_rpc_complete_async_request(reqdata, rc); return XPRT_IDLE; } /** * @brief Callback for NFS3 read done * * @param[in] obj Object being acted on * @param[in] ret Return status of call * @param[in] read_data Data for read call * @param[in] caller_data Data for caller */ static void nfs3_read_cb(struct fsal_obj_handle *obj, fsal_status_t ret, void *read_data, void *caller_data) { struct nfs3_read_data *data = caller_data; uint32_t flags; if (ret.major == ERR_FSAL_SHARE_DENIED) { /* Fixup FSAL_SHARE_DENIED status */ ret = fsalstat(ERR_FSAL_LOCKED, 0); } if (FSAL_IS_SUCCESS(ret)) { /* No error */ data->rc = NFS_REQ_OK; } else if (nfs_RetryableError(ret.major)) { /* If we are here, there was an error */ data->rc = NFS_REQ_DROP; } else { /* We need to let nfs3_complete_read know there was an error. * This will be converted to NFS_REQ_OK later. */ data->rc = NFS_REQ_ERROR; } data->res->res_read3.status = nfs3_Errno_status(ret); flags = atomic_postset_uint32_t_bits(&data->flags, ASYNC_PROC_DONE); if ((flags & ASYNC_PROC_EXIT) == ASYNC_PROC_EXIT) { /* nfs3_read has already exited, we will need to reschedule * the request for completion. */ data->req->rq_resume_cb = nfs3_read_resume; svc_resume(data->req); } } static void read3_io_data_release(void *release_data) { /* Nothing to do */ } /** * * @brief The NFSPROC3_READ * * Implements the NFSPROC3_READ function. * * @param[in] arg NFS arguments union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable * */ int nfs3_read(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { struct fsal_obj_handle *obj; pre_op_attr pre_attr; fsal_status_t fsal_status = { 0, 0 }; uint64_t offset = arg->arg_read3.offset; size_t size = arg->arg_read3.count; uint64_t MaxRead = atomic_fetch_uint64_t(&op_ctx->ctx_export->MaxRead); uint64_t MaxOffsetRead = atomic_fetch_uint64_t(&op_ctx->ctx_export->MaxOffsetRead); READ3resfail *resfail = &res->res_read3.READ3res_u.resfail; struct nfs3_read_data *read_data = NULL; struct fsal_io_arg *read_arg; nfs_request_t *reqdata = container_of(req, nfs_request_t, svc); int rc = NFS_REQ_OK; uint32_t flags; READ3resok *resok = &res->res_read3.READ3res_u.resok; LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_read3.file, " start: %" PRIx64 " len: %zu", offset, size); /* to avoid setting it on each error case */ resfail->file_attributes.attributes_follow = FALSE; /* initialize for read of size 0 */ memset(&res->res_read3, 0, sizeof(res->res_read3)); obj = nfs3_FhandleToCache(&arg->arg_read3.file, &res->res_read3.status, &rc); if (obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ server_stats_io_done(size, 0, false, false); return rc; } nfs_SetPreOpAttr(obj, &pre_attr); fsal_status = obj->obj_ops->test_access(obj, FSAL_READ_ACCESS, NULL, NULL, true); if (fsal_status.major == ERR_FSAL_ACCESS) { /* Test for execute permission */ fsal_status = fsal_access( obj, FSAL_MODE_MASK_SET(FSAL_X_OK) | FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_EXECUTE)); } if (FSAL_IS_ERROR(fsal_status)) { res->res_read3.status = nfs3_Errno_status(fsal_status); goto return_ok; } /* Sanity check: read only from a regular file */ if (obj->type != REGULAR_FILE) { if (obj->type == DIRECTORY) res->res_read3.status = NFS3ERR_ISDIR; else res->res_read3.status = NFS3ERR_INVAL; goto return_ok; } /* do not exceed maximum READ offset if set */ if (MaxOffsetRead < UINT64_MAX) { LogFullDebug(COMPONENT_NFSPROTO, "Read offset=%" PRIu64 " count=%zd MaxOffSet=%" PRIu64, offset, size, MaxOffsetRead); if ((offset + size) > MaxOffsetRead) { LogEvent( COMPONENT_NFSPROTO, "A client tried to violate max file size %" PRIu64 " for exportid #%hu", MaxOffsetRead, op_ctx->ctx_export->export_id); res->res_read3.status = NFS3ERR_FBIG; nfs_SetPostOpAttr(obj, &resfail->file_attributes, NULL); goto return_ok; } } /* We should not exceed the FSINFO rtmax field for the size */ if (size > MaxRead) { /* The client asked for too much, normally this should not happen because the client is calling nfs_Fsinfo at mount time and so is aware of the server maximum write size */ size = MaxRead; } if (size == 0) { struct fsal_io_arg read_arg; memset(&read_arg, 0, sizeof(read_arg)); nfs_read_ok(&res->res_read3.READ3res_u.resok, &read_arg, obj); goto return_ok; } /* Check for delegation conflict. */ if (state_deleg_conflict(obj, false)) { res->res_read3.status = NFS3ERR_JUKEBOX; goto return_ok; } /* Set up result using internal iovec of length 1 that allows FSAL * layer to allocate the read buffer. */ resok->data.data_len = size; resok->data.iovcnt = 1; resok->data.iov = &resok->iov0; resok->data.iov[0].iov_len = size; resok->data.iov[0].iov_base = NULL; resok->data.last_iov_buf_size = 0; resok->data.release = read3_io_data_release; /* Set up args, allocate from heap */ read_data = gsh_calloc(1, sizeof(*read_data)); read_arg = &read_data->read_arg; read_arg->info = NULL; /** @todo for now pass NULL state */ read_arg->state = NULL; read_arg->offset = offset; read_arg->iov_count = resok->data.iovcnt; read_arg->iov = resok->data.iov; read_arg->last_iov_buf_size = &resok->data.last_iov_buf_size; read_arg->io_amount = 0; read_arg->end_of_file = false; read_data->res = res; read_data->req = req; read_data->obj = obj; reqdata->proc_data = read_data; again: /* Do the actual read */ fsal_read2(obj, true, nfs3_read_cb, read_arg, read_data); /* Only atomically set the flags if we actually call read2, otherwise * we will have indicated as having been DONE. */ flags = atomic_postset_uint32_t_bits(&read_data->flags, ASYNC_PROC_EXIT); if ((flags & ASYNC_PROC_DONE) != ASYNC_PROC_DONE) { /* The read was not finished before we got here. When the * read completes, nfs3_read_cb() will have to reschedule the * request for completion. The resume will be resolved by * nfs3_read_resume() which will free read_data and return * the appropriate return result. */ return NFS_REQ_ASYNC_WAIT; } if (read_arg->fsal_resume) { /* FSAL is requesting another read2 call */ atomic_postclear_uint32_t_bits( &read_data->flags, ASYNC_PROC_EXIT | ASYNC_PROC_DONE); /* Make the call with the same params, though the FSAL will be * signaled by fsal_resume being set. */ goto again; } /* Complete the read */ rc = nfs3_complete_read(read_data); /* Since we're actually done, we can free read_data. */ gsh_free(read_data); reqdata->proc_data = NULL; return rc; return_ok: /* return references */ if (obj) obj->obj_ops->put_ref(obj); server_stats_io_done(size, 0, true, false); return NFS_REQ_OK; } /* nfs3_read */ /** * @brief Free the result structure allocated for nfs3_read. * * This function frees the result structure allocated for nfs3_read. * * @param[in,out] res Result structure */ void nfs3_read_free(nfs_res_t *res) { /* Nothing to clean up. */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_readdir.c000066400000000000000000000335301473756622300215110ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vimf:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_readdir.c * @brief Everything you need for a simple READDIR * */ #include "config.h" #include #include #include #include #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "export_mgr.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_proto_tools.h" #include fsal_errors_t nfs3_readdir_callback(void *opaque, struct fsal_obj_handle *obj, const struct fsal_attrlist *attr, uint64_t mounted_on_fileid, uint64_t cookie, enum cb_state cb_state); /** * @brief Opaque bookkeeping structure for NFSv3 readdir * * This structure keeps track of the process of writing out an NFSv3 * READDIR response between calls to nfs3_readdir_callback. */ struct nfs3_readdir_cb_data { XDR xdr; /*< The xdrmem to serialize the entries */ uint8_t *entries; /*< The array holding serialized entries */ size_t mem_avail; /*< The amount of memory available before we hit maxcount */ int count; /*< Number of entries accumulated so far. */ uint32_t max_count; /*< Maximum number of entries allowed. */ nfsstat3 error; /*< Set to a value other than NFS_OK if the callback function finds a fatal error. */ }; static nfsstat3 nfs_readdir_dot_entry(struct fsal_obj_handle *obj, const char *name, uint64_t cookie, helper_readdir_cb cb, struct nfs3_readdir_cb_data *tracker) { struct fsal_readdir_cb_parms cb_parms; fsal_status_t fsal_status; cb_parms.opaque = tracker; cb_parms.name = name; cb_parms.attr_allowed = true; cb_parms.in_result = true; /* NFS v3 READDIR does not use attributes, so pass NULL */ fsal_status.major = cb(&cb_parms, obj, NULL, 0, cookie, CB_ORIGINAL); fsal_status.minor = 0; if (FSAL_IS_ERROR(fsal_status)) return nfs3_Errno_status(fsal_status); else return tracker->error; } /** * * @brief The NFSPROC3_READDIR * * Implements the NFSPROC3_READDIR function. * * @param[in] arg NFS argument union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable * */ int nfs3_readdir(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { struct fsal_obj_handle *dir_obj = NULL; struct fsal_obj_handle *parent_dir_obj = NULL; uint64_t cookie = 0; uint64_t fsal_cookie = 0; cookieverf3 cookie_verifier; unsigned int num_entries = 0; uint32_t maxcount; uint32_t cfg_readdir_size = nfs_param.core_param.readdir_res_size; uint32_t cfg_readdir_count = nfs_param.core_param.readdir_max_count; uint64_t mem_avail = 0; uint64_t max_mem = 0; object_file_type_t dir_filetype = 0; bool eod_met = false; fsal_status_t fsal_status = { 0, 0 }; fsal_status_t fsal_status_gethandle = { 0, 0 }; int rc = NFS_REQ_OK; struct nfs3_readdir_cb_data tracker; bool use_cookie_verifier = op_ctx_export_has_option(EXPORT_OPTION_USE_COOKIE_VERIFIER); READDIR3resfail *resfail = &res->res_readdir3.READDIR3res_u.resfail; LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_readdir3.dir, ""); READDIR3resok *const RES_READDIR3_OK = &res->res_readdir3.READDIR3res_u.resok; /* to avoid setting it on each error case */ resfail->dir_attributes.attributes_follow = FALSE; memset(&tracker, 0, sizeof(tracker)); /* Look up object for filehandle */ dir_obj = nfs3_FhandleToCache(&(arg->arg_readdir3.dir), &(res->res_readdir3.status), &rc); if (dir_obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ goto out; } /* Extract the filetype */ dir_filetype = dir_obj->type; /* Sanity checks -- must be a directory */ if (dir_filetype != DIRECTORY) { res->res_readdir3.status = NFS3ERR_NOTDIR; rc = NFS_REQ_OK; goto out; } /* Parse out request arguments and decide how many entries we * want. For NFSv3, deal with the cookie verifier. */ cookie = arg->arg_readdir3.cookie; /* Figure out how much memory we have available for the dirlist3. * Subtract: * nfsstat3 status (BYTES_PER_XDR_UNIT) * post_op_attr dir_attributes * bool_t attributes_follow * struct fattr3_wire attributes * cookieverf3 cookieverf (8 or sizeof(cookieverf3)) */ if (cfg_readdir_size < arg->arg_readdir3.count) maxcount = cfg_readdir_size; else maxcount = arg->arg_readdir3.count; mem_avail = maxcount - BYTES_PER_XDR_UNIT - BYTES_PER_XDR_UNIT - sizeof(struct fattr3_wire) - sizeof(cookieverf3); max_mem = atomic_fetch_uint64_t(&op_ctx->ctx_export->MaxRead); tracker.mem_avail = MIN(mem_avail, max_mem); /* Divide count by 8 to get a rough max number of entries per response * or use the configured number if lower. */ if (arg->arg_readdir3.count >> 3 < cfg_readdir_count) tracker.max_count = arg->arg_readdir3.count >> 3; else tracker.max_count = cfg_readdir_count; LogDebug(COMPONENT_NFS_READDIR, "---> NFS3_READDIR: count=%u cookie=%" PRIu64 " mem_avail=%zd max_count =%" PRIu32, arg->arg_readdir3.count, cookie, tracker.mem_avail, tracker.max_count); /* To make or check the cookie verifier */ memset(cookie_verifier, 0, sizeof(cookie_verifier)); /* If cookie verifier is used, then an non-trivial value is * returned to the client This value is the change attribute of the * directory. If verifier is unused (as in many NFS Servers) then * only a set of zeros is returned (trivial value) */ if (use_cookie_verifier) { struct fsal_attrlist attrs; fsal_prepare_attrs(&attrs, ATTR_CTIME); fsal_status = dir_obj->obj_ops->getattrs(dir_obj, &attrs); if (FSAL_IS_ERROR(fsal_status)) { res->res_readdir3.status = nfs3_Errno_status(fsal_status); LogDebug(COMPONENT_NFS_READDIR, "getattrs returned %s", msg_fsal_err(fsal_status.major)); goto out; } memcpy(cookie_verifier, &attrs.change, MIN(sizeof(cookie_verifier), sizeof(attrs.change))); /* Done with the attrs */ fsal_release_attrs(&attrs); } if (cookie != 0 && use_cookie_verifier) { /* Not the first call, so we have to check the cookie * verifier */ if (memcmp(cookie_verifier, arg->arg_readdir3.cookieverf, NFS3_COOKIEVERFSIZE) != 0) { res->res_readdir3.status = NFS3ERR_BAD_COOKIE; rc = NFS_REQ_OK; goto out; } } tracker.entries = get_buffer_for_io_response(tracker.mem_avail, NULL); /* If buffer was not assigned, let's allocate it */ if (tracker.entries == NULL) tracker.entries = gsh_malloc(tracker.mem_avail); xdrmem_create(&tracker.xdr, (char *)tracker.entries, tracker.mem_avail, XDR_ENCODE); tracker.error = NFS3_OK; /* Adjust the cookie we supply to fsal */ if (cookie > 2) { /* it is not the cookie for "." nor ".." */ fsal_cookie = cookie; } else { fsal_cookie = 0; } /* Fills "." */ if (cookie == 0) { res->res_readdir3.status = nfs_readdir_dot_entry( dir_obj, ".", 1, nfs3_readdir_callback, &tracker); if (res->res_readdir3.status != NFS3_OK) { rc = NFS_REQ_OK; goto out_destroy; } } /* Fills ".." */ if ((cookie <= 1)) { /* Get parent pentry */ fsal_status_gethandle = fsal_lookupp(dir_obj, &parent_dir_obj, NULL); if (parent_dir_obj == NULL) { res->res_readdir3.status = nfs3_Errno_status(fsal_status_gethandle); rc = NFS_REQ_OK; goto out_destroy; } res->res_readdir3.status = nfs_readdir_dot_entry(parent_dir_obj, "..", 2, nfs3_readdir_callback, &tracker); if (res->res_readdir3.status != NFS3_OK) { rc = NFS_REQ_OK; goto out_destroy; } parent_dir_obj->obj_ops->put_ref(parent_dir_obj); parent_dir_obj = NULL; } /* Call readdir */ fsal_status = fsal_readdir(dir_obj, fsal_cookie, &num_entries, &eod_met, 0, /* no attr */ nfs3_readdir_callback, &tracker); if (FSAL_IS_ERROR(fsal_status)) { if (nfs_RetryableError(fsal_status.major)) { rc = NFS_REQ_DROP; goto out_destroy; } res->res_readdir3.status = nfs3_Errno_status(fsal_status); nfs_SetPostOpAttr(dir_obj, &resfail->dir_attributes, NULL); goto out_destroy; } if (tracker.error != NFS3_OK) { res->res_readdir3.status = tracker.error; nfs_SetPostOpAttr(dir_obj, &resfail->dir_attributes, NULL); goto out_destroy; } LogDebug(COMPONENT_NFS_READDIR, "-- Readdir -> Call to fsal_readdir(cookie=%" PRIu64 ")", fsal_cookie); if ((num_entries == 0) && (cookie > 1)) { RES_READDIR3_OK->reply.entries = NULL; RES_READDIR3_OK->reply.eof = TRUE; } else { struct xdr_uio *uio; u_int pos_end; bool_t tmp; if (eod_met) { /* If we hit end of directory, then the dirlist3 we have * encoded so far has a complete entry (we MUST have * consumed the last entry and encoded it). Now we need * to encode a FALSE to indicate no next entry. */ tmp = FALSE; if (!xdr_bool(&tracker.xdr, &tmp)) { /* Oops... */ LogCrit(COMPONENT_NFS_READDIR, "Encode of no next entry failed."); res->res_readdir3.status = NFS3ERR_SERVERFAULT; goto out_destroy; } } /* Serialize eod_met into the entries buffer */ tmp = eod_met; if (!xdr_bool(&tracker.xdr, &tmp)) { /* Oops... */ LogCrit(COMPONENT_NFS_READDIR, "Encode of EOD failed."); res->res_readdir3.status = NFS3ERR_SERVERFAULT; goto out_destroy; } pos_end = xdr_getpos(&tracker.xdr); /* Get an xdr_uio and fill it in */ uio = gsh_calloc(1, sizeof(struct xdr_uio) + sizeof(struct xdr_uio)); uio->uio_release = xdr_dirlist3_uio_release; uio->uio_count = 1; uio->uio_vio[0].vio_base = tracker.entries; uio->uio_vio[0].vio_head = tracker.entries; uio->uio_vio[0].vio_tail = tracker.entries + pos_end; uio->uio_vio[0].vio_wrap = tracker.entries + pos_end; uio->uio_vio[0].vio_length = pos_end; uio->uio_vio[0].vio_type = VIO_DATA; /* Take over entries buffer */ tracker.entries = NULL; RES_READDIR3_OK->reply.uio = uio; RES_READDIR3_OK->reply.entries = NULL; } nfs_SetPostOpAttr(dir_obj, &RES_READDIR3_OK->dir_attributes, NULL); memcpy(RES_READDIR3_OK->cookieverf, cookie_verifier, sizeof(cookieverf3)); res->res_readdir3.status = NFS3_OK; rc = NFS_REQ_OK; out_destroy: xdr_destroy(&tracker.xdr); out: /* return references */ if (dir_obj) dir_obj->obj_ops->put_ref(dir_obj); if (parent_dir_obj) parent_dir_obj->obj_ops->put_ref(parent_dir_obj); if (tracker.entries) { if (!op_ctx->is_rdma_buff_used) gsh_free(tracker.entries); tracker.entries = NULL; } return rc; } /* nfs3_readdir */ /** * @brief Free the result structure allocated for nfs3_readdir * * @param[in,out] resp Result structure * */ void nfs3_readdir_free(nfs_res_t *resp) { /* Nothing to do, entries has been handed off or already freed. */ } /** * @brief Populate entry3s when called from fsal_readdir * * This function is a callback passed to fsal_readdir. It * fills in a pre-allocated XDR buffer. * * @param opaque [in] Pointer to a struct nfs3_readdir_cb_data that is * gives the location of the array and other * bookkeeping information * @param name [in] The filename for the current obj * @param handle [in] The current obj's filehandle * @param attrs [in] The current obj's attributes * @param cookie [in] The readdir cookie for the current obj */ fsal_errors_t nfs3_readdir_callback(void *opaque, struct fsal_obj_handle *obj, const struct fsal_attrlist *attr, uint64_t mounted_on_fileid, uint64_t cookie, enum cb_state cb_state) { /* Not-so-opaque pointer to callback data` */ struct fsal_readdir_cb_parms *cb_parms = opaque; struct nfs3_readdir_cb_data *tracker = cb_parms->opaque; entry3 e3; u_int pos_start = xdr_getpos(&tracker->xdr); memset(&e3, 0, sizeof(e3)); e3.fileid = obj->fileid; e3.name = (char *)cb_parms->name; e3.cookie = cookie; /* Encode the entry into the xdrmem buffer and then assure there is * space for at least two booleans (one to be false to terminate the * entry list, the other to encode EOD or not). * * Before doing that though, check if the enty fits based on max_count. */ if (tracker->count >= tracker->max_count || !xdr_encode_entry3(&tracker->xdr, &e3) || (xdr_getpos(&tracker->xdr) + BYTES_PER_XDR_UNIT) >= tracker->mem_avail) { bool_t res_false = false; /* XDR serialization of the entry failed or we ran out of room * to serialize at least a boolean after the result. */ cb_parms->in_result = false; /* Reset to where we started this entry and encode a boolean * false instead (entry_follows is false). */ if (!xdr_setpos(&tracker->xdr, pos_start) || !xdr_bool(&tracker->xdr, &res_false)) { /* Oops, what broke... */ LogCrit(COMPONENT_NFS_READDIR, "Unexpected XDR failure processing readdir result"); tracker->error = NFS3ERR_SERVERFAULT; } } else { /* The entry fit, let the caller know it fit */ cb_parms->in_result = true; tracker->count++; } return ERR_FSAL_NO_ERROR; } /* nfs3_readdir_callback */ nfs-ganesha-6.5/src/Protocols/NFS/nfs3_readdirplus.c000066400000000000000000000373301473756622300224170ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_readdirplus.c * @brief Everything you need for the NFSv3 readdirplus operation */ #include "config.h" #include #include #include #include #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "export_mgr.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_file_handle.h" #include "nfs_proto_tools.h" #include fsal_errors_t nfs3_readdirplus_callback(void *opaque, struct fsal_obj_handle *obj, const struct fsal_attrlist *attr, uint64_t mounted_on_fileid, uint64_t cookie, enum cb_state cb_state); /** * @brief Opaque bookkeeping structure for NFSPROC3_READDIRPLUS * * This structure keeps track of the process of writing out an NFSv3 * READDIRPLUS response between calls to nfs3_readdirplus_callback. */ struct nfs3_readdirplus_cb_data { XDR xdr; /*< The xdrmem to serialize the entries */ uint8_t *entries; /*< The array holding serialized entries */ size_t mem_avail; /*< The amount of memory available before we hit maxcount */ int count; /*< Number of entries accumulated so far. */ uint32_t max_count; /*< Maximum number of entries allowed. */ nfsstat3 error; /*< Set to a value other than NFS_OK if the callback function finds a fatal error. */ }; static nfsstat3 nfs_readdir_dot_entry(struct fsal_obj_handle *obj, const char *name, uint64_t cookie, helper_readdir_cb cb, struct nfs3_readdirplus_cb_data *tracker, struct fsal_attrlist *attrs) { struct fsal_readdir_cb_parms cb_parms; fsal_status_t fsal_status = { ERR_FSAL_NO_ERROR, 0 }; cb_parms.opaque = tracker; cb_parms.name = name; cb_parms.attr_allowed = true; cb_parms.in_result = true; fsal_status.major = cb(&cb_parms, obj, attrs, 0, cookie, CB_ORIGINAL); if (FSAL_IS_ERROR(fsal_status)) return nfs3_Errno_status(fsal_status); else return tracker->error; } /** * @brief The NFSPROC3_READDIRPLUS * * Implements the NFSPROC3_READDIRPLUS function * * @param[in] arg NFS argument union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable */ int nfs3_readdirplus(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { struct fsal_obj_handle *dir_obj = NULL; uint64_t begin_cookie = 0; uint64_t fsal_cookie = 0; uint32_t maxcount; uint32_t cfg_readdir_size = nfs_param.core_param.readdir_res_size; uint32_t cfg_readdir_count = nfs_param.core_param.readdir_max_count; cookieverf3 cookie_verifier; unsigned int num_entries = 0; uint64_t mem_avail = 0; uint64_t max_mem = 0; object_file_type_t dir_filetype = 0; bool eod_met = false; fsal_status_t fsal_status = { 0, 0 }; fsal_status_t fsal_status_gethandle = { 0, 0 }; int rc = NFS_REQ_OK; struct nfs3_readdirplus_cb_data tracker; struct fsal_attrlist attrs_dir, attrs_parent; bool use_cookie_verifier = op_ctx_export_has_option(EXPORT_OPTION_USE_COOKIE_VERIFIER); READDIRPLUS3resfail *resfail = &res->res_readdirplus3.READDIRPLUS3res_u.resfail; READDIRPLUS3resok *resok = &res->res_readdirplus3.READDIRPLUS3res_u.resok; /* We have the option of not sending attributes, so set ATTR_RDATTR_ERR. */ fsal_prepare_attrs(&attrs_dir, ATTRS_NFS3 | ATTR_RDATTR_ERR); fsal_prepare_attrs(&attrs_parent, ATTRS_NFS3 | ATTR_RDATTR_ERR); LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_readdirplus3.dir, ""); /* to avoid setting it on each error case */ resfail->dir_attributes.attributes_follow = FALSE; memset(&tracker, 0, sizeof(tracker)); if (op_ctx_export_has_option(EXPORT_OPTION_NO_READDIR_PLUS)) { res->res_readdirplus3.status = NFS3ERR_NOTSUPP; LogDebug(COMPONENT_NFS_READDIR, "Request not supported"); goto out; } /* Figure out how much memory we have available for the dirlist3. * Subtract: * nfsstat3 status (BYTES_PER_XDR_UNIT) * post_op_attr dir_attributes * bool_t attributes_follow * struct fattr3_wire attributes * cookieverf3 cookieverf (8 or sizeof(cookieverf3)) */ if (cfg_readdir_size < arg->arg_readdirplus3.maxcount) maxcount = cfg_readdir_size; else maxcount = arg->arg_readdirplus3.maxcount; mem_avail = maxcount - BYTES_PER_XDR_UNIT - BYTES_PER_XDR_UNIT - sizeof(struct fattr3_wire) - sizeof(cookieverf3); max_mem = atomic_fetch_uint64_t(&op_ctx->ctx_export->MaxRead); tracker.mem_avail = MIN(mem_avail, max_mem); /* Use the dircount from the request as the max number of entries if * lower than the configured max. */ if (arg->arg_readdirplus3.dircount < cfg_readdir_count) tracker.max_count = arg->arg_readdirplus3.dircount; else tracker.max_count = cfg_readdir_count; begin_cookie = arg->arg_readdirplus3.cookie; LogDebug(COMPONENT_NFS_READDIR, "NFS3_READDIRPLUS: dircount=%u begin_cookie=%" PRIu64 " mem_avail=%zd max_count = %" PRIi32, arg->arg_readdirplus3.dircount, begin_cookie, tracker.mem_avail, tracker.max_count); /* Convert file handle into a vnode */ dir_obj = nfs3_FhandleToCache(&(arg->arg_readdirplus3.dir), &(res->res_readdirplus3.status), &rc); if (dir_obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ goto out; } /* Extract the filetype */ dir_filetype = dir_obj->type; /* Sanity checks -- must be a directory */ if (dir_filetype != DIRECTORY) { res->res_readdirplus3.status = NFS3ERR_NOTDIR; rc = NFS_REQ_OK; goto out; } /* Fetch the attributes for use later. */ fsal_status = dir_obj->obj_ops->getattrs(dir_obj, &attrs_dir); if (FSAL_IS_ERROR(fsal_status)) { res->res_readdirplus3.status = nfs3_Errno_status(fsal_status); LogDebug(COMPONENT_NFS_READDIR, "Error %s fetching attributes", fsal_err_txt(fsal_status)); goto out; } /* To make or check the cookie verifier */ memset(cookie_verifier, 0, sizeof(cookie_verifier)); /* If cookie verifier is used, then an non-trivial value is * returned to the client This value is the change attribute of the * directory. If verifier is unused (as in many NFS Servers) then * only a set of zeros is returned (trivial value) */ if (use_cookie_verifier) { if (attrs_dir.valid_mask == ATTR_RDATTR_ERR) { res->res_readdirplus3.status = NFS3ERR_SERVERFAULT; LogDebug(COMPONENT_NFS_READDIR, "Could not fetch ctime"); goto out_fail; } memcpy(cookie_verifier, &attrs_dir.change, MIN(sizeof(cookie_verifier), sizeof(attrs_dir.change))); } if (use_cookie_verifier && begin_cookie != 0) { /* Not the first call, so we have to check the cookie verifier */ if (memcmp(cookie_verifier, arg->arg_readdirplus3.cookieverf, NFS3_COOKIEVERFSIZE) != 0) { res->res_readdirplus3.status = NFS3ERR_BAD_COOKIE; rc = NFS_REQ_OK; goto out_fail; } } resok->reply.entries = NULL; resok->reply.eof = FALSE; /* Fudge cookie for "." and "..", if necessary */ if (begin_cookie > 2) fsal_cookie = begin_cookie; else fsal_cookie = 0; /* Allocate space for entries */ tracker.entries = get_buffer_for_io_response(tracker.mem_avail, NULL); /* If buffer was not assigned, let's allocate it */ if (tracker.entries == NULL) tracker.entries = gsh_malloc(tracker.mem_avail); xdrmem_create(&tracker.xdr, (char *)tracker.entries, tracker.mem_avail, XDR_ENCODE); if (begin_cookie == 0) { /* Fill in "." */ res->res_readdirplus3.status = nfs_readdir_dot_entry( dir_obj, ".", 1, nfs3_readdirplus_callback, &tracker, &attrs_dir); if (res->res_readdirplus3.status != NFS3_OK) { rc = NFS_REQ_OK; goto out_destroy; } } /* Fill in ".." */ if (begin_cookie <= 1) { struct fsal_obj_handle *parent_dir_obj = NULL; fsal_status_gethandle = fsal_lookupp(dir_obj, &parent_dir_obj, &attrs_parent); if (parent_dir_obj == NULL) { res->res_readdirplus3.status = nfs3_Errno_status(fsal_status_gethandle); rc = NFS_REQ_OK; goto out_destroy; } res->res_readdirplus3.status = nfs_readdir_dot_entry( parent_dir_obj, "..", 2, nfs3_readdirplus_callback, &tracker, &attrs_parent); parent_dir_obj->obj_ops->put_ref(parent_dir_obj); if (res->res_readdirplus3.status != NFS3_OK) { rc = NFS_REQ_OK; goto out_destroy; } } LogDebug(COMPONENT_NFS_READDIR, "Readdirplus3 -> Call to fsal_readdir, cookie=%" PRIu64, fsal_cookie); /* Call readdir */ fsal_status = fsal_readdir(dir_obj, fsal_cookie, &num_entries, &eod_met, ATTRS_NFS3, nfs3_readdirplus_callback, &tracker); if (FSAL_IS_ERROR(fsal_status)) { /* Is this a retryable error */ if (nfs_RetryableError(fsal_status.major)) { rc = NFS_REQ_DROP; goto out_destroy; } res->res_readdirplus3.status = nfs3_Errno_status(fsal_status); goto out_destroy; } if (tracker.error != NFS3_OK) { res->res_readdirplus3.status = tracker.error; goto out_destroy; } if ((num_entries == 0) && (begin_cookie > 1)) { res->res_readdirplus3.status = NFS3_OK; resok->reply.entries = NULL; resok->reply.eof = TRUE; } else { struct xdr_uio *uio; u_int pos_end; bool_t tmp; if (eod_met) { /* If we hit end of directory, then the dirlist3 we have * encoded so far has a complete entry (we MUST have * consumed the last entry and encoded it). Now we need * to encode a FALSE to indicate no next entry. */ tmp = FALSE; if (!xdr_bool(&tracker.xdr, &tmp)) { /* Oops... */ LogCrit(COMPONENT_NFS_READDIR, "Encode of no next entry failed."); res->res_readdirplus3.status = NFS3ERR_SERVERFAULT; goto out_destroy; } } /* Serialize eod_met into the entries buffer */ tmp = eod_met; if (!xdr_bool(&tracker.xdr, &tmp)) { /* Oops... */ LogCrit(COMPONENT_NFS_READDIR, "Encode of EOD failed."); res->res_readdirplus3.status = NFS3ERR_SERVERFAULT; goto out_destroy; } pos_end = xdr_getpos(&tracker.xdr); /* Get an xdr_uio and fill it in */ uio = gsh_calloc(1, sizeof(struct xdr_uio) + sizeof(struct xdr_uio)); uio->uio_release = xdr_dirlistplus3_uio_release; uio->uio_count = 1; uio->uio_vio[0].vio_base = tracker.entries; uio->uio_vio[0].vio_head = tracker.entries; uio->uio_vio[0].vio_tail = tracker.entries + pos_end; uio->uio_vio[0].vio_wrap = tracker.entries + pos_end; uio->uio_vio[0].vio_length = pos_end; uio->uio_vio[0].vio_type = VIO_DATA; /* Take over entries buffer */ tracker.entries = NULL; resok->reply.uio = uio; resok->reply.entries = NULL; } nfs_SetPostOpAttr(dir_obj, &resok->dir_attributes, &attrs_dir); memcpy(resok->cookieverf, cookie_verifier, sizeof(cookieverf3)); res->res_readdirplus3.status = NFS3_OK; rc = NFS_REQ_OK; out_destroy: xdr_destroy(&tracker.xdr); out_fail: if (res->res_readdirplus3.status != NFS3_OK) { /* Set the post op attributes as we have found them. */ nfs_SetPostOpAttr(dir_obj, &resfail->dir_attributes, &attrs_dir); } out: /* Release the attributes. */ fsal_release_attrs(&attrs_dir); fsal_release_attrs(&attrs_parent); if (dir_obj) dir_obj->obj_ops->put_ref(dir_obj); /* If we allocated but didn't consume entries, free it now. */ if (!op_ctx->is_rdma_buff_used) gsh_free(tracker.entries); return rc; } /* nfs3_readdirplus */ /** * @brief Frees the result structure allocated for nfs3_readdirplus. * * Frees the result structure allocated for nfs3_readdirplus. * * @param resp [in,out] Pointer to the result structure * */ void nfs3_readdirplus_free(nfs_res_t *resp) { /* Nothing to do, entries has been handed off or already freed. */ } /** * @brief Populate entryplus3s when called from fsal_readdir * * This function is a callback passed to fsal_readdir. It * fills in a pre-allocated array of entryplys3 structures and allocates * space for the name and attributes. This space must be freed. * * @param opaque [in] Pointer to a struct nfs3_readdirplus_cb_data that is * gives the location of the array and other * bookkeeping information * @param name [in] The filename for the current obj * @param handle [in] The current obj's filehandle * @param attrs [in] The current obj's attributes * @param cookie [in] The readdir cookie for the current obj */ fsal_errors_t nfs3_readdirplus_callback(void *opaque, struct fsal_obj_handle *obj, const struct fsal_attrlist *attr, uint64_t mounted_on_fileid, uint64_t cookie, enum cb_state cb_state) { /* Not-so-opaque pointer to callback data` */ struct fsal_readdir_cb_parms *cb_parms = opaque; struct nfs3_readdirplus_cb_data *tracker = cb_parms->opaque; entryplus3 ep3; u_int pos_start = xdr_getpos(&tracker->xdr); LogDebug(COMPONENT_NFS_READDIR, "Callback for %s cookie %" PRIu64, cb_parms->name, cookie); memset(&ep3, 0, sizeof(ep3)); ep3.fileid = obj->fileid; ep3.name = (char *)cb_parms->name; ep3.cookie = cookie; if (cb_parms->attr_allowed) { ep3.name_handle.handle_follows = TRUE; if (!nfs3_FSALToFhandle(true, &ep3.name_handle.post_op_fh3_u.handle, obj, op_ctx->ctx_export)) { tracker->error = NFS3ERR_SERVERFAULT; cb_parms->in_result = false; return ERR_FSAL_NO_ERROR; } /* Check if attributes follow */ ep3.name_attributes.attributes_follow = nfs3_Fixup_FSALattr(obj, attr); } else { ep3.name_handle.handle_follows = false; ep3.name_attributes.attributes_follow = false; } /* Encode the entry into the xdrmem buffer and then assure there is * space for at least two booleans (one to be false to terminate the * entry list, the other to encode EOD or not). Note we use the special * xdr_encode_entryplus3 that uses a passed in struct fsal_attrlist * rather than name_attributes from entryplus3, though we will use the * boolean attributes_follow from the entryplus3. * * Before doing that though, check if the enty fits based on max_count. */ if (tracker->count >= tracker->max_count || !xdr_encode_entryplus3(&tracker->xdr, &ep3, attr) || (xdr_getpos(&tracker->xdr) + BYTES_PER_XDR_UNIT) >= tracker->mem_avail) { bool_t res_false = false; /* XDR serialization of the entry failed or we ran out of room * to serialize at least a boolean after the result. */ cb_parms->in_result = false; /* Reset to where we started this entry and encode a boolean * false instead (entry_follows is false). */ if (!xdr_setpos(&tracker->xdr, pos_start) || !xdr_bool(&tracker->xdr, &res_false)) { /* Oops, what broke... */ LogCrit(COMPONENT_NFS_READDIR, "Unexpected XDR failure processing readdir result"); tracker->error = NFS3ERR_SERVERFAULT; } } else { /* The entry fit, let the caller know it fit */ cb_parms->in_result = true; tracker->count++; } /* Now we are done with anything allocated */ gsh_free(ep3.name_handle.post_op_fh3_u.handle.data.data_val); return ERR_FSAL_NO_ERROR; } /* nfs3_readdirplus_callback */ nfs-ganesha-6.5/src/Protocols/NFS/nfs3_readlink.c000066400000000000000000000073141473756622300216710ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_readlink.c * @brief Everything you need for NFSv3 READLINK. */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs23.h" #include "nfs4.h" #include "mount.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_proto_tools.h" /** * * @brief The NFSPROC3_READLINK. * * This function implements the NFSPROC3_READLINK function. * * @param[in] arg NFS argument union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @see fsal_readlink * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable */ int nfs3_readlink(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { struct fsal_obj_handle *obj = NULL; fsal_status_t fsal_status; struct gsh_buffdesc link_buffer = { .addr = NULL, .len = 0 }; int rc = NFS_REQ_OK; READLINK3resfail *resfail = &res->res_readlink3.READLINK3res_u.resfail; READLINK3resok *resok = &res->res_readlink3.READLINK3res_u.resok; LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_readlink3.symlink, ""); /* to avoid setting it on each error case */ resfail->symlink_attributes.attributes_follow = false; obj = nfs3_FhandleToCache(&arg->arg_readlink3.symlink, &res->res_readlink3.status, &rc); if (obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ goto out; } /* Sanity Check: the obj must be a link */ if (obj->type != SYMBOLIC_LINK) { res->res_readlink3.status = NFS3ERR_INVAL; rc = NFS_REQ_OK; goto out; } fsal_status = fsal_readlink(obj, &link_buffer); if (FSAL_IS_ERROR(fsal_status)) { res->res_readlink3.status = nfs3_Errno_status(fsal_status); nfs_SetPostOpAttr(obj, &resfail->symlink_attributes, NULL); if (nfs_RetryableError(fsal_status.major)) rc = NFS_REQ_DROP; goto out; } /* Reply to the client */ resok->data = link_buffer.addr; nfs_SetPostOpAttr(obj, &resok->symlink_attributes, NULL); res->res_readlink3.status = NFS3_OK; rc = NFS_REQ_OK; out: /* return references */ if (obj) obj->obj_ops->put_ref(obj); return rc; } /* nfs3_readlink */ /** * @brief Free the result structure allocated for nfs3_readlink. * * This function frees the result structure allocated for * nfs3_readlink. * * @param[in,out] res Result structure * */ void nfs3_readlink_free(nfs_res_t *res) { if (res->res_readlink3.status == NFS3_OK) gsh_free(res->res_readlink3.READLINK3res_u.resok.data); } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_remove.c000066400000000000000000000117141473756622300213740ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_remove.c * @brief Everything you need for NFSv3 REMOVE */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs23.h" #include "nfs4.h" #include "mount.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_proto_tools.h" /** * * @brief The NFSPROC3_REMOVE * * Implements the NFSPROC3_REMOVE function. * * @param[in] arg NFS arguments union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable * */ int nfs3_remove(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { struct fsal_obj_handle *parent_obj = NULL; struct fsal_obj_handle *child_obj = NULL; pre_op_attr pre_parent = { .attributes_follow = false }; fsal_status_t fsal_status; struct fsal_attrlist parent_pre_attrs, parent_post_attrs; const char *name = arg->arg_remove3.object.name; int rc = NFS_REQ_OK; LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_remove3.object.dir, " name: %s", name); /* Convert file handle into a pentry */ /* to avoid setting it on each error case */ res->res_remove3.REMOVE3res_u.resfail.dir_wcc.before.attributes_follow = FALSE; res->res_remove3.REMOVE3res_u.resfail.dir_wcc.after.attributes_follow = FALSE; fsal_prepare_attrs(&parent_pre_attrs, ATTR_SIZE | ATTR_CTIME | ATTR_MTIME); fsal_prepare_attrs(&parent_post_attrs, ATTRS_NFS3); parent_obj = nfs3_FhandleToCache(&arg->arg_remove3.object.dir, &res->res_remove3.status, &rc); if (parent_obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ goto out; } nfs_SetPreOpAttr(parent_obj, &pre_parent); /* Sanity checks: file name must be non-null; parent must be a * directory. */ if (parent_obj->type != DIRECTORY) { res->res_remove3.status = NFS3ERR_NOTDIR; rc = NFS_REQ_OK; goto out; } if (name == NULL || *name == '\0') { fsal_status = fsalstat(ERR_FSAL_INVAL, 0); goto out_fail; } /* Lookup the child entry to verify that it is not a directory */ fsal_status = fsal_lookup(parent_obj, name, &child_obj, NULL); if (!FSAL_IS_ERROR(fsal_status)) { /* Sanity check: make sure we are not removing a * directory */ if (child_obj->type == DIRECTORY) { res->res_remove3.status = NFS3ERR_ISDIR; rc = NFS_REQ_OK; goto out; } } LogFullDebug(COMPONENT_NFSPROTO, "Trying to remove file %s", name); /* Remove the entry. */ fsal_status = fsal_remove(parent_obj, name, &parent_pre_attrs, &parent_post_attrs); if (FSAL_IS_ERROR(fsal_status)) goto out_fail; /* Build Weak Cache Coherency data */ nfs_PreOpAttrFromFsalAttr(&parent_pre_attrs, &pre_parent); nfs_SetWccData(&pre_parent, parent_obj, &parent_post_attrs, &res->res_remove3.REMOVE3res_u.resok.dir_wcc); res->res_remove3.status = NFS3_OK; rc = NFS_REQ_OK; goto out; out_fail: res->res_remove3.status = nfs3_Errno_status(fsal_status); nfs_PreOpAttrFromFsalAttr(&parent_pre_attrs, &pre_parent); nfs_SetWccData(&pre_parent, parent_obj, &parent_post_attrs, &res->res_remove3.REMOVE3res_u.resfail.dir_wcc); if (nfs_RetryableError(fsal_status.major)) rc = NFS_REQ_DROP; out: fsal_release_attrs(&parent_pre_attrs); fsal_release_attrs(&parent_post_attrs); /* return references */ if (child_obj) child_obj->obj_ops->put_ref(child_obj); if (parent_obj) parent_obj->obj_ops->put_ref(parent_obj); return rc; } /* nfs3_remove */ /** * @brief Free the result structure allocated for nfs3_remove. * * This function frees the result structure allocated for nfs3_remove. * * @param[in,out] res Result structure * */ void nfs3_remove_free(nfs_res_t *res) { /* Nothing to do here */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_rename.c000066400000000000000000000146201473756622300213450ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_rename.c * @brief Everything you need for NFSv3 RENAME */ #include "config.h" #include #include #include #include #include #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_proto_tools.h" #include "client_mgr.h" /** * * @brief The NFSPROC3_RENAME * * Implements the NFSPROC3_RENAME function. * * @param[in] arg NFS argument union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable * */ int nfs3_rename(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { const char *entry_name = arg->arg_rename3.from.name; const char *new_entry_name = arg->arg_rename3.to.name; struct fsal_obj_handle *parent_obj = NULL; struct fsal_obj_handle *new_parent_obj = NULL; fsal_status_t fsal_status; struct fsal_attrlist olddir_pre_attrs_out, olddir_post_attrs_out, newdir_pre_attrs_out, newdir_post_attrs_out; int to_exportid = 0; int from_exportid = 0; int rc = NFS_REQ_OK; RENAME3resfail *resfail = &res->res_rename3.RENAME3res_u.resfail; RENAME3resok *resok = &res->res_rename3.RENAME3res_u.resok; pre_op_attr pre_parent = { .attributes_follow = false }; pre_op_attr pre_new_parent = { .attributes_follow = false }; fsal_prepare_attrs(&olddir_pre_attrs_out, ATTR_SIZE | ATTR_CTIME | ATTR_MTIME); fsal_prepare_attrs(&olddir_post_attrs_out, ATTRS_NFS3); fsal_prepare_attrs(&newdir_pre_attrs_out, ATTR_SIZE | ATTR_CTIME | ATTR_MTIME); fsal_prepare_attrs(&newdir_post_attrs_out, ATTRS_NFS3); LogNFS3_Operation2(COMPONENT_NFSPROTO, req, &arg->arg_rename3.from.dir, entry_name, &arg->arg_rename3.to.dir, new_entry_name); /* to avoid setting it on each error case */ resfail->fromdir_wcc.before.attributes_follow = FALSE; resfail->fromdir_wcc.after.attributes_follow = FALSE; resfail->todir_wcc.before.attributes_follow = FALSE; resfail->todir_wcc.after.attributes_follow = FALSE; /* Get the exportids for the two handles. */ to_exportid = nfs3_FhandleToExportId(&(arg->arg_rename3.to.dir)); from_exportid = nfs3_FhandleToExportId(&(arg->arg_rename3.from.dir)); /* Validate the to_exportid */ if (to_exportid < 0 || from_exportid < 0) { LogInfo(COMPONENT_DISPATCH, "NFS%d RENAME Request from client %s has badly formed handle for to dir", req->rq_msg.cb_vers, op_ctx->client ? op_ctx->client->hostaddr_str : "unknown client"); /* Bad handle, report to client */ res->res_rename3.status = NFS3ERR_BADHANDLE; goto out; } /* Both objects have to be in the same filesystem */ if (to_exportid != from_exportid) { res->res_rename3.status = NFS3ERR_XDEV; goto out; } /* Convert fromdir file handle into a FSAL obj */ parent_obj = nfs3_FhandleToCache(&arg->arg_rename3.from.dir, &res->res_rename3.status, &rc); if (parent_obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ goto out; } nfs_SetPreOpAttr(parent_obj, &pre_parent); /* Convert todir file handle into a FSAL obj */ new_parent_obj = nfs3_FhandleToCache(&arg->arg_rename3.to.dir, &res->res_rename3.status, &rc); if (new_parent_obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ goto out; } nfs_SetPreOpAttr(new_parent_obj, &pre_new_parent); if (entry_name == NULL || *entry_name == '\0' || new_entry_name == NULL || *new_entry_name == '\0') { fsal_status = fsalstat(ERR_FSAL_INVAL, 0); goto out_fail; } fsal_status = fsal_rename(parent_obj, entry_name, new_parent_obj, new_entry_name, &olddir_pre_attrs_out, &olddir_post_attrs_out, &newdir_pre_attrs_out, &newdir_post_attrs_out); if (FSAL_IS_ERROR(fsal_status)) goto out_fail; res->res_rename3.status = NFS3_OK; nfs_PreOpAttrFromFsalAttr(&olddir_pre_attrs_out, &pre_parent); nfs_SetWccData(&pre_parent, parent_obj, &olddir_post_attrs_out, &resok->fromdir_wcc); nfs_PreOpAttrFromFsalAttr(&newdir_pre_attrs_out, &pre_parent); nfs_SetWccData(&pre_new_parent, new_parent_obj, &newdir_post_attrs_out, &resok->todir_wcc); rc = NFS_REQ_OK; goto out; out_fail: res->res_rename3.status = nfs3_Errno_status(fsal_status); nfs_PreOpAttrFromFsalAttr(&olddir_pre_attrs_out, &pre_parent); nfs_SetWccData(&pre_parent, parent_obj, &olddir_post_attrs_out, &resfail->fromdir_wcc); nfs_PreOpAttrFromFsalAttr(&newdir_pre_attrs_out, &pre_parent); nfs_SetWccData(&pre_new_parent, new_parent_obj, &newdir_post_attrs_out, &resfail->todir_wcc); /* If we are here, there was an error */ if (nfs_RetryableError(fsal_status.major)) rc = NFS_REQ_DROP; out: fsal_release_attrs(&olddir_pre_attrs_out); fsal_release_attrs(&olddir_post_attrs_out); fsal_release_attrs(&newdir_pre_attrs_out); fsal_release_attrs(&newdir_post_attrs_out); if (parent_obj) parent_obj->obj_ops->put_ref(parent_obj); if (new_parent_obj) new_parent_obj->obj_ops->put_ref(new_parent_obj); return rc; } /** * @brief Free the result structure allocated for nfs3_rename. * * This function frees the result structure allocated for nfs3_rename. * * @param[in,out] res Result structure * */ void nfs3_rename_free(nfs_res_t *res) { /* Nothing to do here */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_rmdir.c000066400000000000000000000115151473756622300212130ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_rmdir.c * @brief Everything you need for NFSv3 RMDIR */ #include "config.h" #include #include #include #include #include #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs23.h" #include "nfs4.h" #include "mount.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_proto_tools.h" /** * * @brief The NFSPROC3_RMDIR * * Implements the NFSPROC3_RMDIR function. * * @param[in] arg NFS arguments union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable * */ int nfs3_rmdir(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { struct fsal_obj_handle *parent_obj = NULL; struct fsal_obj_handle *child_obj = NULL; pre_op_attr pre_parent = { .attributes_follow = false }; fsal_status_t fsal_status; struct fsal_attrlist parent_pre_attrs, parent_post_attrs; const char *name = arg->arg_rmdir3.object.name; int rc = NFS_REQ_OK; LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_rmdir3.object.dir, " name: %s", name); /* Convert file handle into a pentry */ /* to avoid setting it on each error case */ res->res_rmdir3.RMDIR3res_u.resfail.dir_wcc.before.attributes_follow = FALSE; res->res_rmdir3.RMDIR3res_u.resfail.dir_wcc.after.attributes_follow = FALSE; fsal_prepare_attrs(&parent_pre_attrs, ATTR_SIZE | ATTR_CTIME | ATTR_MTIME); fsal_prepare_attrs(&parent_post_attrs, ATTRS_NFS3); parent_obj = nfs3_FhandleToCache(&arg->arg_rmdir3.object.dir, &res->res_rmdir3.status, &rc); if (parent_obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ goto out; } nfs_SetPreOpAttr(parent_obj, &pre_parent); /* Sanity checks: directory name must be non-null; parent * must be a directory. */ if (parent_obj->type != DIRECTORY) { res->res_rmdir3.status = NFS3ERR_NOTDIR; rc = NFS_REQ_OK; goto out; } if ((name == NULL) || (*name == '\0')) { fsal_status = fsalstat(ERR_FSAL_INVAL, 0); goto out_fail; } /* Lookup to the entry to be removed to check that it is a * directory */ fsal_status = fsal_lookup(parent_obj, name, &child_obj, NULL); if (child_obj != NULL) { /* Sanity check: make sure we are about to remove a * directory */ if (child_obj->type != DIRECTORY) { res->res_rmdir3.status = NFS3ERR_NOTDIR; rc = NFS_REQ_OK; goto out; } } fsal_status = fsal_remove(parent_obj, name, &parent_pre_attrs, &parent_post_attrs); if (FSAL_IS_ERROR(fsal_status)) goto out_fail; nfs_PreOpAttrFromFsalAttr(&parent_pre_attrs, &pre_parent); nfs_SetWccData(&pre_parent, parent_obj, &parent_post_attrs, &res->res_rmdir3.RMDIR3res_u.resok.dir_wcc); res->res_rmdir3.status = NFS3_OK; rc = NFS_REQ_OK; goto out; out_fail: res->res_rmdir3.status = nfs3_Errno_status(fsal_status); nfs_PreOpAttrFromFsalAttr(&parent_pre_attrs, &pre_parent); nfs_SetWccData(&pre_parent, parent_obj, &parent_post_attrs, &res->res_rmdir3.RMDIR3res_u.resfail.dir_wcc); /* If we are here, there was an error */ if (nfs_RetryableError(fsal_status.major)) rc = NFS_REQ_DROP; out: fsal_release_attrs(&parent_pre_attrs); fsal_release_attrs(&parent_post_attrs); /* return references */ if (child_obj) child_obj->obj_ops->put_ref(child_obj); if (parent_obj) parent_obj->obj_ops->put_ref(parent_obj); return rc; } /* nfs3_rmdir */ /** * @brief Free the result structure allocated for nfs3_rmdir * * This function frees the result structure allocated for nfs3_rmdir. * * @param[in,out] res Result structure * */ void nfs3_rmdir_free(nfs_res_t *res) { /* Nothing to do here */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_setattr.c000066400000000000000000000135111473756622300215620ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_setattr.c * @brief Everything you need for NFSv3 SETATTR */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs23.h" #include "nfs4.h" #include "mount.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_creds.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_proto_tools.h" #include "sal_functions.h" /** * @brief The NFSPROC3_SETATTR * * Implements the NFSPROC3_SETATTR function. * * @param[in] arg NFS arguments union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable * */ int nfs3_setattr(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { struct fsal_attrlist setattr; struct fsal_obj_handle *obj = NULL; pre_op_attr pre_attr = { .attributes_follow = false }; fsal_status_t fsal_status = { 0, 0 }; int rc = NFS_REQ_OK; SETATTR3resfail *resfail = &res->res_setattr3.SETATTR3res_u.resfail; SETATTR3resok *resok = &res->res_setattr3.SETATTR3res_u.resok; memset(&setattr, 0, sizeof(setattr)); LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_setattr3.object, ""); /* to avoid setting it on each error case */ resfail->obj_wcc.before.attributes_follow = FALSE; resfail->obj_wcc.after.attributes_follow = FALSE; obj = nfs3_FhandleToCache(&arg->arg_setattr3.object, &res->res_setattr3.status, &rc); if (obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ LogFullDebug(COMPONENT_NFSPROTO, "nfs3_FhandleToCache failed"); goto out; } nfs_SetPreOpAttr(obj, &pre_attr); if (arg->arg_setattr3.guard.check) { /* This pack of lines implements the "guard check" setattr. This * feature of nfsv3 is used to avoid several setattr to occur * concurrently on the same object, from different clients */ nfstime3 *obj_ctime = &arg->arg_setattr3.guard.sattrguard3_u.obj_ctime; nfstime3 *pre_ctime = &pre_attr.pre_op_attr_u.attributes.ctime; LogFullDebug(COMPONENT_NFSPROTO, "css=%d acs=%d csn=%d acn=%d", obj_ctime->tv_sec, pre_ctime->tv_sec, obj_ctime->tv_nsec, pre_ctime->tv_nsec); if (obj_ctime->tv_sec != pre_ctime->tv_sec || obj_ctime->tv_nsec != pre_ctime->tv_nsec) { res->res_setattr3.status = NFS3ERR_NOT_SYNC; rc = NFS_REQ_OK; LogFullDebug(COMPONENT_NFSPROTO, "guard check failed"); goto out; } } /* Conversion to FSAL attributes */ if (!nfs3_Sattr_To_FSALattr(&setattr, &arg->arg_setattr3.new_attributes)) { res->res_setattr3.status = NFS3ERR_INVAL; rc = NFS_REQ_OK; LogFullDebug(COMPONENT_NFSPROTO, "nfs3_Sattr_To_FSALattr failed"); goto out; } if (setattr.valid_mask != 0) { /* If owner or owner_group are set, and the credential was * squashed, then we must squash the set owner and owner_group. */ squash_setattr(&setattr); /* Don't allow attribute change while we are in grace period. * Required for delegation reclaims and may be needed for other * reclaimable states as well. No NFS4ERR_GRACE in NFS v3, so * send jukebox error. */ if (!nfs_get_grace_status(false)) { res->res_setattr3.status = NFS3ERR_JUKEBOX; rc = NFS_REQ_OK; LogFullDebug(COMPONENT_NFSPROTO, "nfs_in_grace is true"); goto out; } /* For now we don't look for states, so indicate bypass so * we will get through an NLM_SHARE with deny. */ fsal_status = fsal_setattr(obj, true, NULL, &setattr); nfs_put_grace_status(); if (FSAL_IS_ERROR(fsal_status)) { res->res_setattr3.status = nfs3_Errno_status(fsal_status); LogFullDebug(COMPONENT_NFSPROTO, "fsal_setattr failed"); goto out_fail; } } /* Set the NFS return */ res->res_setattr3.status = NFS3_OK; /* Build Weak Cache Coherency data */ nfs_SetWccData(&pre_attr, obj, NULL, &resok->obj_wcc); rc = NFS_REQ_OK; out: /* Release the attributes (may release an inherited ACL) */ fsal_release_attrs(&setattr); /* return references */ if (obj) obj->obj_ops->put_ref(obj); LogDebug(COMPONENT_NFSPROTO, "Result %s%s", nfsstat3_to_str(res->res_setattr3.status), rc == NFS_REQ_DROP ? " Dropping response" : ""); return rc; out_fail: nfs_SetWccData(&pre_attr, obj, NULL, &resfail->obj_wcc); if (nfs_RetryableError(fsal_status.major)) { /* Drop retryable request. */ rc = NFS_REQ_DROP; } goto out; } /* nfs3_setattr */ /** * @brief Free the result structure allocated for nfs3_setattr. * * This function frees the result structure allocated for nfs3_setattr. * * @param[in,out] res Result structure */ void nfs3_setattr_free(nfs_res_t *res) { /* Nothing to do here */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_symlink.c000066400000000000000000000143041473756622300215630ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_symlink.c * @brief Everything you need for NFSv3 SYMLINK */ #include "config.h" #include #include #include #include #include #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_creds.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_file_handle.h" #include "nfs_proto_tools.h" #include "export_mgr.h" /** * * @brief The NFSPROC3_SYMLINK * * Implements the NFSPROC3_SYMLINK function. * * @param[in] arg NFS argument union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable * */ int nfs3_symlink(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { const char *symlink_name = arg->arg_symlink3.where.name; char *target_path = arg->arg_symlink3.symlink.symlink_data; struct fsal_obj_handle *symlink_obj = NULL; struct fsal_obj_handle *parent_obj; pre_op_attr pre_parent; fsal_status_t fsal_status; int rc = NFS_REQ_OK; struct fsal_attrlist sattr, attrs, parent_pre_attrs, parent_post_attrs; SYMLINK3resfail *resfail = &res->res_symlink3.SYMLINK3res_u.resfail; SYMLINK3resok *resok = &res->res_symlink3.SYMLINK3res_u.resok; /* We have the option of not sending attributes, so set ATTR_RDATTR_ERR. */ fsal_prepare_attrs(&attrs, ATTRS_NFS3 | ATTR_RDATTR_ERR); fsal_prepare_attrs(&parent_pre_attrs, ATTR_SIZE | ATTR_CTIME | ATTR_MTIME); fsal_prepare_attrs(&parent_post_attrs, ATTRS_NFS3); memset(&sattr, 0, sizeof(sattr)); LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_symlink3.where.dir, " name: %s target: %s", symlink_name, target_path); /* to avoid setting it on each error case */ resfail->dir_wcc.before.attributes_follow = false; resfail->dir_wcc.after.attributes_follow = false; parent_obj = nfs3_FhandleToCache(&arg->arg_symlink3.where.dir, &res->res_symlink3.status, &rc); if (parent_obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ goto out; } nfs_SetPreOpAttr(parent_obj, &pre_parent); if (parent_obj->type != DIRECTORY) { res->res_symlink3.status = NFS3ERR_NOTDIR; rc = NFS_REQ_OK; goto out; } /* if quota support is active, then we should check is the * FSAL allows inode creation or not */ fsal_status = op_ctx->fsal_export->exp_ops.check_quota( op_ctx->fsal_export, CTX_FULLPATH(op_ctx), FSAL_QUOTA_INODES); if (FSAL_IS_ERROR(fsal_status)) { res->res_symlink3.status = NFS3ERR_DQUOT; rc = NFS_REQ_OK; goto out; } if (symlink_name == NULL || *symlink_name == '\0' || target_path == NULL || *target_path == '\0') { fsal_status = fsalstat(ERR_FSAL_INVAL, 0); goto out_fail; } /* Some clients (like the Spec NFS benchmark) set * attributes with the NFSPROC3_SYMLINK request */ if (!nfs3_Sattr_To_FSALattr( &sattr, &arg->arg_symlink3.symlink.symlink_attributes)) { res->res_symlink3.status = NFS3ERR_INVAL; rc = NFS_REQ_OK; goto out; } squash_setattr(&sattr); if (!(sattr.valid_mask & ATTR_MODE)) { /* Make sure mode is set. */ sattr.mode = 0777; sattr.valid_mask |= ATTR_MODE; } /* Make the symlink */ fsal_status = fsal_create(parent_obj, symlink_name, SYMBOLIC_LINK, &sattr, target_path, &symlink_obj, &attrs, &parent_pre_attrs, &parent_post_attrs); /* Release the attributes (may release an inherited ACL) */ fsal_release_attrs(&sattr); if (FSAL_IS_ERROR(fsal_status)) goto out_fail; if (!nfs3_FSALToFhandle(true, &resok->obj.post_op_fh3_u.handle, symlink_obj, op_ctx->ctx_export)) { res->res_symlink3.status = NFS3ERR_BADHANDLE; rc = NFS_REQ_OK; goto out; } resok->obj.handle_follows = TRUE; /* Build entry attributes */ nfs_PreOpAttrFromFsalAttr(&parent_pre_attrs, &pre_parent); nfs_SetPostOpAttr(symlink_obj, &resok->obj_attributes, &attrs); /* Build Weak Cache Coherency data */ nfs_SetWccData(&pre_parent, parent_obj, &parent_post_attrs, &resok->dir_wcc); res->res_symlink3.status = NFS3_OK; rc = NFS_REQ_OK; goto out; out_fail: res->res_symlink3.status = nfs3_Errno_status(fsal_status); nfs_PreOpAttrFromFsalAttr(&parent_pre_attrs, &pre_parent); nfs_SetWccData(&pre_parent, parent_obj, &parent_post_attrs, &resfail->dir_wcc); if (nfs_RetryableError(fsal_status.major)) rc = NFS_REQ_DROP; out: /* Release the attributes. */ fsal_release_attrs(&attrs); fsal_release_attrs(&parent_pre_attrs); fsal_release_attrs(&parent_post_attrs); /* return references */ if (parent_obj) parent_obj->obj_ops->put_ref(parent_obj); if (symlink_obj) symlink_obj->obj_ops->put_ref(symlink_obj); return rc; } /* nfs3_symlink */ /** * @brief Free the result structure allocated for nfs3_symlink. * * This function frees the result structure allocated for nfs3_symlink. * * @param[in,out] res Result structure * */ void nfs3_symlink_free(nfs_res_t *res) { SYMLINK3resok *resok = &res->res_symlink3.SYMLINK3res_u.resok; if (res->res_symlink3.status == NFS3_OK && resok->obj.handle_follows) gsh_free(resok->obj.post_op_fh3_u.handle.data.data_val); } nfs-ganesha-6.5/src/Protocols/NFS/nfs3_write.c000066400000000000000000000277161473756622300212420ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs3_write.c * @brief Everything you need for NFSv3 WRITE. */ #include "config.h" #include #include #include #include #include #include #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_proto_tools.h" #include "server_stats.h" #include "export_mgr.h" #include "sal_functions.h" struct nfs3_write_data { /** Results for write */ nfs_res_t *res; /** RPC Request for this WRITE */ struct svc_req *req; /** Object being acted on */ struct fsal_obj_handle *obj; /** Return code */ int rc; /** Flags to control synchronization */ uint32_t flags; /** Arguments for write call - must be last */ struct fsal_io_arg write_arg; }; static int nfs3_complete_write(struct nfs3_write_data *data) { struct fsal_io_arg *write_arg = &data->write_arg; WRITE3resfail *resfail = &data->res->res_write3.WRITE3res_u.resfail; WRITE3resok *resok = &data->res->res_write3.WRITE3res_u.resok; if (data->rc == NFS_REQ_OK) { /* Build Weak Cache Coherency data */ nfs_SetWccData(NULL, data->obj, NULL, &resok->file_wcc); /* Set the written size */ resok->count = write_arg->io_amount; /* How do we commit data ? */ if (write_arg->fsal_stable) resok->committed = FILE_SYNC; else resok->committed = UNSTABLE; /* Set the write verifier */ memcpy(resok->verf, NFS3_write_verifier, sizeof(writeverf3)); } else if (data->rc == NFS_REQ_ERROR) { /* If we are here, there was an error */ nfs_SetWccData(NULL, data->obj, NULL, &resfail->file_wcc); /* Now we convert NFS_REQ_ERROR into NFS_REQ_OK */ data->rc = NFS_REQ_OK; } /* return references */ data->obj->obj_ops->put_ref(data->obj); server_stats_io_done(write_arg->iov[0].iov_len, write_arg->io_amount, (data->rc == NFS_REQ_OK) ? true : false, true); return data->rc; } static void nfs3_write_cb(struct fsal_obj_handle *obj, fsal_status_t ret, void *write_data, void *caller_data); static enum xprt_stat nfs3_write_resume(struct svc_req *req) { nfs_request_t *reqdata = container_of(req, nfs_request_t, svc); struct nfs3_write_data *data = reqdata->proc_data; int rc; uint32_t flags; /* Restore the op_ctx */ resume_op_context(&reqdata->op_context); if (data->write_arg.fsal_resume) { /* FSAL is requesting another write2 call on resume */ atomic_postclear_uint32_t_bits( &data->flags, ASYNC_PROC_EXIT | ASYNC_PROC_DONE); data->obj->obj_ops->write2(data->obj, true, nfs3_write_cb, &data->write_arg, data); /* Only atomically set the flags if we actually call write2, * otherwise we will have indicated as having been DONE. */ flags = atomic_postset_uint32_t_bits(&data->flags, ASYNC_PROC_EXIT); if ((flags & ASYNC_PROC_DONE) != ASYNC_PROC_DONE) { /* The write was not finished before we got here. When * the write completes, nfs3_write_cb() will have to * reschedule the request for completion. The resume * will be resolved by nfs3_write_resume() which will * free write_data and return the appropriate return * result. We will NOT go async again for the write op * (but could for a subsequent op in the compound). */ suspend_op_context(); return XPRT_SUSPEND; } } /* Complete the write */ rc = nfs3_complete_write(data); /* Free the write_data. */ gsh_free(data); reqdata->proc_data = NULL; nfs_rpc_complete_async_request(reqdata, rc); return XPRT_IDLE; } /** * @brief Callback for NFS3 write done * * @param[in] obj Object being acted on * @param[in] ret Return status of call * @param[in] write_data Data for write call * @param[in] caller_data Data for caller */ static void nfs3_write_cb(struct fsal_obj_handle *obj, fsal_status_t ret, void *write_data, void *caller_data) { struct nfs3_write_data *data = caller_data; uint32_t flags; if (ret.major == ERR_FSAL_SHARE_DENIED) { /* Fixup FSAL_SHARE_DENIED status */ ret = fsalstat(ERR_FSAL_LOCKED, 0); } LogFullDebug(COMPONENT_NFSPROTO, "write fsal_status=%s", fsal_err_txt(ret)); if (FSAL_IS_SUCCESS(ret)) { /* No error */ data->rc = NFS_REQ_OK; } else if (nfs_RetryableError(ret.major)) { /* If we are here, there was an error */ data->rc = NFS_REQ_DROP; } else { /* We need to let nfs3_complete_write know there was an error. * This will be converted to NFS_REQ_OK later. */ data->rc = NFS_REQ_ERROR; } data->res->res_write3.status = nfs3_Errno_status(ret); flags = atomic_postset_uint32_t_bits(&data->flags, ASYNC_PROC_DONE); if ((flags & ASYNC_PROC_EXIT) == ASYNC_PROC_EXIT) { /* nfs3_write has already exited, we will need to reschedule * the request for completion. */ data->req->rq_resume_cb = nfs3_write_resume; svc_resume(data->req); } } /** * * @brief The NFSPROC3_WRITE * * Implements the NFSPROC3_WRITE function. * * @param[in] arg NFS argument union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable * */ int nfs3_write(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { struct fsal_obj_handle *obj; pre_op_attr pre_attr = { .attributes_follow = false }; fsal_status_t fsal_status = { 0, 0 }; uint64_t offset = arg->arg_write3.offset; size_t size = arg->arg_write3.count; uint64_t MaxWrite = atomic_fetch_uint64_t(&op_ctx->ctx_export->MaxWrite); uint64_t MaxOffsetWrite = atomic_fetch_uint64_t(&op_ctx->ctx_export->MaxOffsetWrite); bool force_sync = op_ctx->export_perms.options & EXPORT_OPTION_COMMIT; WRITE3resfail *resfail = &res->res_write3.WRITE3res_u.resfail; WRITE3resok *resok = &res->res_write3.WRITE3res_u.resok; struct nfs3_write_data *write_data = NULL; struct fsal_io_arg *write_arg; nfs_request_t *reqdata = container_of(req, nfs_request_t, svc); int rc = NFS_REQ_OK; uint32_t flags; rc = NFS_REQ_OK; LogNFS3_Operation(COMPONENT_NFSPROTO, req, &arg->arg_write3.file, " start: %" PRIx64 " len: %zu %s", offset, size, arg->arg_write3.stable == UNSTABLE ? "UNSTABLE" : arg->arg_write3.stable == UNSTABLE ? "DATA_SYNC" : "FILE_SYNC"); /* to avoid setting it on each error case */ resfail->file_wcc.before.attributes_follow = false; resfail->file_wcc.after.attributes_follow = false; obj = nfs3_FhandleToCache(&arg->arg_write3.file, &res->res_write3.status, &rc); if (obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ return rc; } nfs_SetPreOpAttr(obj, &pre_attr); fsal_status = obj->obj_ops->test_access(obj, FSAL_WRITE_ACCESS, NULL, NULL, true); if (FSAL_IS_ERROR(fsal_status)) { res->res_write3.status = nfs3_Errno_status(fsal_status); goto return_ok; } /* Sanity check: write only a regular file */ if (obj->type != REGULAR_FILE) { if (obj->type == DIRECTORY) res->res_write3.status = NFS3ERR_ISDIR; else res->res_write3.status = NFS3ERR_INVAL; goto return_ok; } /* if quota support is active, then we should check is the FSAL allows inode creation or not */ fsal_status = op_ctx->fsal_export->exp_ops.check_quota( op_ctx->fsal_export, CTX_FULLPATH(op_ctx), FSAL_QUOTA_INODES); if (FSAL_IS_ERROR(fsal_status)) { res->res_write3.status = NFS3ERR_DQUOT; goto return_ok; } if (size > arg->arg_write3.data.data_len) { /* should never happen */ res->res_write3.status = NFS3ERR_INVAL; goto return_ok; } /* Do not exceed maximum WRITE offset if set */ if (MaxOffsetWrite < UINT64_MAX) { LogFullDebug(COMPONENT_NFSPROTO, "Write offset=%" PRIu64 " size=%zu MaxOffSet=%" PRIu64, offset, size, MaxOffsetWrite); if ((offset + size) > MaxOffsetWrite) { LogEvent( COMPONENT_NFSPROTO, "A client tried to violate max file size %" PRIu64 " for exportid #%hu", MaxOffsetWrite, op_ctx->ctx_export->export_id); res->res_write3.status = NFS3ERR_FBIG; nfs_SetWccData(NULL, obj, NULL, &resfail->file_wcc); goto return_ok; } } /* We should take care not to exceed FSINFO wtmax field for the size */ if (size > MaxWrite) { /* The client asked for too much data, we must restrict him */ size = MaxWrite; } if (size == 0) { fsal_status = fsalstat(ERR_FSAL_NO_ERROR, 0); res->res_write3.status = NFS3_OK; nfs_SetWccData(NULL, obj, NULL, &resfail->file_wcc); if ((arg->arg_write3.stable == DATA_SYNC) || (arg->arg_write3.stable == FILE_SYNC)) resok->committed = FILE_SYNC; else resok->committed = UNSTABLE; memcpy(resok->verf, NFS3_write_verifier, sizeof(writeverf3)); goto return_ok; } /* An actual write is to be made, prepare it */ /* Check for delegation conflict. */ if (state_deleg_conflict(obj, true)) { res->res_write3.status = NFS3ERR_JUKEBOX; goto return_ok; } /* Set up args, allocate from heap, iov_count will be 1 */ write_data = gsh_calloc(1, sizeof(*write_data)); write_arg = &write_data->write_arg; write_arg->info = NULL; /** @todo for now pass NULL state */ write_arg->state = NULL; write_arg->offset = offset; write_arg->io_request = arg->arg_write3.data.data_len; write_arg->fsal_stable = arg->arg_write3.stable != UNSTABLE || force_sync; write_arg->iov_count = arg->arg_write3.data.iovcnt; write_arg->iov = arg->arg_write3.data.iov; write_arg->io_amount = 0; write_data->res = res; write_data->req = req; write_data->obj = obj; reqdata->proc_data = write_data; again: obj->obj_ops->write2(obj, true, nfs3_write_cb, write_arg, write_data); /* Only atomically set the flags if we actually call write2, otherwise * we will have indicated as having been DONE. */ flags = atomic_postset_uint32_t_bits(&write_data->flags, ASYNC_PROC_EXIT); if ((flags & ASYNC_PROC_DONE) != ASYNC_PROC_DONE) { /* The write was not finished before we got here. When the * write completes, nfs3_write_cb() will have to reschedule the * request for completion. The resume will be resolved by * nfs3_write_resume() which will free write_data and return * the appropriate return result. */ return NFS_REQ_ASYNC_WAIT; } if (write_arg->fsal_resume) { /* FSAL is requesting another write2 call */ atomic_postclear_uint32_t_bits( &write_data->flags, ASYNC_PROC_EXIT | ASYNC_PROC_DONE); /* Make the call with the same params, though the FSAL will be * signaled by fsal_resume being set. */ goto again; } /* Complete the write */ rc = nfs3_complete_write(write_data); /* Since we're actually done, we can free write_data. */ gsh_free(write_data); reqdata->proc_data = NULL; return rc; return_ok: /* return references */ obj->obj_ops->put_ref(obj); server_stats_io_done(size, 0, true, true); return NFS_REQ_OK; } /* nfs3_write */ /** * @brief Frees the result structure allocated for nfs3_write. * * Frees the result structure allocated for nfs3_write. * * @param[in,out] res Result structure * */ void nfs3_write_free(nfs_res_t *res) { /* Nothing to do here */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_Compound.c000066400000000000000000001462501473756622300216700ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_Compound.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * */ #include "config.h" #include "fsal.h" #include "sal_functions.h" #include "nfs_convert.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "server_stats.h" #include "export_mgr.h" #include "nfs_creds.h" #include "pnfs_utils.h" #include #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs_rpc.h" #endif static enum nfs_req_result nfs4_default_resume(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { return NFS_REQ_OK; } /** * #brief Structure to map out how each compound op is managed. * */ struct nfs4_op_desc { /** Operation name */ char *name; /** Function to process the operation */ nfs4_function_t funct; /** Function to resume a suspended operation */ nfs4_function_t resume; /** Function to free the results of the operation. * * Note this function is called whether the operation succeeds or * fails. It may be called as a result of higher level operation * completion (depending on DRC handling) or it may be called as part * of NFS v4.1 slot cache management. * * Note that entries placed into the NFS v4.1 slot cache are marked so * the higher level operation completion will not release them. A deep * copy is made when the slot cache is replayed. If sa_cachethis * indicates a response will not be cached, the higher level operation * completion will call the free_res, HOWEVER, a shallow copy of the * SEQUENCE op and first operation responses are made. If the first * operation resulted in an error (other than NFS4_DENIED for LOCK and * LOCKT) the shallow copy preserves that error rather than replacing * it with NFS4ERR_RETRY_UNCACHED_REP. For this reason for any response * that includes dynamically allocated data on NFS4_OK MUST check the * response status before freeing any memory since the shallow copy will * mean the cached NFS4ERR_RETRY_UNCACHED_REP response will have copied * those pointers. It should only free data if the status is NFS4_OK * (or NFS4ERR_DENIED in the case of LOCK and LOCKT). Note that * SETCLIENTID also has dunamic data on a non-NFS4_OK status, and the * free_res function for that checks, however, we will never see * SETCLIENTID in NFS v4.1+, or if we do, it will get an error. * * At this time, LOCK and LOCKT are the only NFS v4.1 or v4.2 operations * that have dynamic data on a non-NFS4_OK response. Should any others * be added, checks for that MUST be added to the shallow copy code * below. * */ void (*free_res)(nfs_resop4 *); /** Default response size */ uint32_t resp_size; /** Export permissions required flags */ int exp_perm_flags; }; /** * @brief NFSv4 and 4.1 ops table. * indexed by opcode * name *must* begin with OP_ */ static const struct nfs4_op_desc optabv4[] = { [0] = { /* all out of bounds illegals go here to die */ .name = "OP_ILLEGAL", .funct = nfs4_op_illegal, .resume = nfs4_default_resume, .free_res = nfs4_op_illegal_Free, .resp_size = sizeof(ILLEGAL4res), .exp_perm_flags = 0}, [1] = { .name = "OP_ILLEGAL", .funct = nfs4_op_illegal, .resume = nfs4_default_resume, .free_res = nfs4_op_illegal_Free, .resp_size = sizeof(ILLEGAL4res), .exp_perm_flags = 0}, [2] = { .name = "OP_ILLEGAL", .funct = nfs4_op_illegal, .resume = nfs4_default_resume, .free_res = nfs4_op_illegal_Free, .resp_size = sizeof(ILLEGAL4res), .exp_perm_flags = 0}, [NFS4_OP_ACCESS] = { .name = "OP_ACCESS", .funct = nfs4_op_access, .resume = nfs4_default_resume, .free_res = nfs4_op_access_Free, .resp_size = sizeof(ACCESS4res), .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_CLOSE] = { .name = "OP_CLOSE", .funct = nfs4_op_close, .resume = nfs4_default_resume, .free_res = nfs4_op_close_Free, .resp_size = sizeof(CLOSE4res), .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_COMMIT] = { .name = "OP_COMMIT", .funct = nfs4_op_commit, .resume = nfs4_default_resume, .free_res = nfs4_op_commit_Free, .resp_size = sizeof(COMMIT4res), .exp_perm_flags = EXPORT_OPTION_MD_WRITE_ACCESS}, [NFS4_OP_CREATE] = { .name = "OP_CREATE", .funct = nfs4_op_create, .resume = nfs4_default_resume, .free_res = nfs4_op_create_Free, .resp_size = sizeof(CREATE4res), .exp_perm_flags = EXPORT_OPTION_MD_WRITE_ACCESS}, [NFS4_OP_DELEGPURGE] = { .name = "OP_DELEGPURGE", .funct = nfs4_op_delegpurge, .resume = nfs4_default_resume, .free_res = nfs4_op_delegpurge_Free, .resp_size = sizeof(DELEGPURGE4res), .exp_perm_flags = 0}, [NFS4_OP_DELEGRETURN] = { .name = "OP_DELEGRETURN", .funct = nfs4_op_delegreturn, .resume = nfs4_default_resume, .free_res = nfs4_op_delegreturn_Free, .resp_size = sizeof(DELEGRETURN4res), .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_GETATTR] = { .name = "OP_GETATTR", .funct = nfs4_op_getattr, .resume = nfs4_default_resume, .free_res = nfs4_op_getattr_Free, .resp_size = VARIABLE_RESP_SIZE, .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_GETFH] = { .name = "OP_GETFH", .funct = nfs4_op_getfh, .resume = nfs4_default_resume, .free_res = nfs4_op_getfh_Free, .resp_size = VARIABLE_RESP_SIZE, .exp_perm_flags = 0}, [NFS4_OP_LINK] = { .name = "OP_LINK", .funct = nfs4_op_link, .resume = nfs4_default_resume, .free_res = nfs4_op_link_Free, .resp_size = sizeof(LINK4res), .exp_perm_flags = EXPORT_OPTION_MD_WRITE_ACCESS}, [NFS4_OP_LOCK] = { .name = "OP_LOCK", .funct = nfs4_op_lock, .resume = nfs4_default_resume, .free_res = nfs4_op_lock_Free, .resp_size = VARIABLE_RESP_SIZE, .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_LOCKT] = { .name = "OP_LOCKT", .funct = nfs4_op_lockt, .resume = nfs4_default_resume, .free_res = nfs4_op_lockt_Free, .resp_size = VARIABLE_RESP_SIZE, .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_LOCKU] = { .name = "OP_LOCKU", .funct = nfs4_op_locku, .resume = nfs4_default_resume, .free_res = nfs4_op_locku_Free, .resp_size = sizeof(LOCKU4res), .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_LOOKUP] = { .name = "OP_LOOKUP", .funct = nfs4_op_lookup, .resume = nfs4_default_resume, .free_res = nfs4_op_lookup_Free, .resp_size = sizeof(LOOKUP4res), .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_LOOKUPP] = { .name = "OP_LOOKUPP", .funct = nfs4_op_lookupp, .resume = nfs4_default_resume, .free_res = nfs4_op_lookupp_Free, .resp_size = sizeof(LOOKUPP4res), .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_NVERIFY] = { .name = "OP_NVERIFY", .funct = nfs4_op_nverify, .resume = nfs4_default_resume, .free_res = nfs4_op_nverify_Free, .resp_size = sizeof(NVERIFY4res), .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_OPEN] = { .name = "OP_OPEN", .funct = nfs4_op_open, .resume = nfs4_default_resume, .free_res = nfs4_op_open_Free, .resp_size = sizeof(OPEN4res), .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_OPENATTR] = { .name = "OP_OPENATTR", .funct = nfs4_op_openattr, .resume = nfs4_default_resume, .free_res = nfs4_op_openattr_Free, .resp_size = sizeof(OPENATTR4res), .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_OPEN_CONFIRM] = { .name = "OP_OPEN_CONFIRM", .funct = nfs4_op_open_confirm, .resume = nfs4_default_resume, .free_res = nfs4_op_open_confirm_Free, .resp_size = sizeof(OPEN_CONFIRM4res), .exp_perm_flags = 0}, [NFS4_OP_OPEN_DOWNGRADE] = { .name = "OP_OPEN_DOWNGRADE", .funct = nfs4_op_open_downgrade, .resume = nfs4_default_resume, .free_res = nfs4_op_open_downgrade_Free, .resp_size = sizeof(OPEN_DOWNGRADE4res), .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_PUTFH] = { .name = "OP_PUTFH", .funct = nfs4_op_putfh, .resume = nfs4_default_resume, .free_res = nfs4_op_putfh_Free, .resp_size = sizeof(PUTFH4res), .exp_perm_flags = 0}, [NFS4_OP_PUTPUBFH] = { .name = "OP_PUTPUBFH", .funct = nfs4_op_putpubfh, .resume = nfs4_default_resume, .free_res = nfs4_op_putpubfh_Free, .resp_size = sizeof(PUTPUBFH4res), .exp_perm_flags = 0}, [NFS4_OP_PUTROOTFH] = { .name = "OP_PUTROOTFH", .funct = nfs4_op_putrootfh, .resume = nfs4_default_resume, .free_res = nfs4_op_putrootfh_Free, .resp_size = sizeof(PUTROOTFH4res), .exp_perm_flags = 0}, [NFS4_OP_READ] = { .name = "OP_READ", .funct = nfs4_op_read, .resume = nfs4_op_read_resume, .free_res = nfs4_op_read_Free, .resp_size = VARIABLE_RESP_SIZE, .exp_perm_flags = EXPORT_OPTION_READ_ACCESS}, [NFS4_OP_READDIR] = { .name = "OP_READDIR", .funct = nfs4_op_readdir, .resume = nfs4_default_resume, .free_res = nfs4_op_readdir_Free, .resp_size = VARIABLE_RESP_SIZE, .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_READLINK] = { .name = "OP_READLINK", .funct = nfs4_op_readlink, .resume = nfs4_default_resume, .free_res = nfs4_op_readlink_Free, .resp_size = VARIABLE_RESP_SIZE, .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_REMOVE] = { .name = "OP_REMOVE", .funct = nfs4_op_remove, .resume = nfs4_default_resume, .free_res = nfs4_op_remove_Free, .resp_size = sizeof(REMOVE4res), .exp_perm_flags = EXPORT_OPTION_MD_WRITE_ACCESS}, [NFS4_OP_RENAME] = { .name = "OP_RENAME", .funct = nfs4_op_rename, .resume = nfs4_default_resume, .free_res = nfs4_op_rename_Free, .resp_size = sizeof(RENAME4res), .exp_perm_flags = EXPORT_OPTION_MD_WRITE_ACCESS}, [NFS4_OP_RENEW] = { .name = "OP_RENEW", .funct = nfs4_op_renew, .resume = nfs4_default_resume, .free_res = nfs4_op_renew_Free, .resp_size = sizeof(RENEW4res), .exp_perm_flags = 0}, [NFS4_OP_RESTOREFH] = { .name = "OP_RESTOREFH", .funct = nfs4_op_restorefh, .resume = nfs4_default_resume, .free_res = nfs4_op_restorefh_Free, .resp_size = sizeof(RESTOREFH4res), .exp_perm_flags = 0}, [NFS4_OP_SAVEFH] = { .name = "OP_SAVEFH", .funct = nfs4_op_savefh, .resume = nfs4_default_resume, .free_res = nfs4_op_savefh_Free, .resp_size = sizeof(SAVEFH4res), .exp_perm_flags = 0}, [NFS4_OP_SECINFO] = { .name = "OP_SECINFO", .funct = nfs4_op_secinfo, .resume = nfs4_default_resume, .free_res = nfs4_op_secinfo_Free, .resp_size = VARIABLE_RESP_SIZE, .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_SETATTR] = { .name = "OP_SETATTR", .funct = nfs4_op_setattr, .resume = nfs4_default_resume, .free_res = nfs4_op_setattr_Free, .resp_size = sizeof(SETATTR4res), .exp_perm_flags = EXPORT_OPTION_MD_WRITE_ACCESS}, [NFS4_OP_SETCLIENTID] = { .name = "OP_SETCLIENTID", .funct = nfs4_op_setclientid, .resume = nfs4_default_resume, .free_res = nfs4_op_setclientid_Free, .resp_size = sizeof(SETCLIENTID4res), .exp_perm_flags = 0}, [NFS4_OP_SETCLIENTID_CONFIRM] = { .name = "OP_SETCLIENTID_CONFIRM", .funct = nfs4_op_setclientid_confirm, .resume = nfs4_default_resume, .free_res = nfs4_op_setclientid_confirm_Free, .resp_size = sizeof(SETCLIENTID_CONFIRM4res), .exp_perm_flags = 0}, [NFS4_OP_VERIFY] = { .name = "OP_VERIFY", .funct = nfs4_op_verify, .resume = nfs4_default_resume, .free_res = nfs4_op_verify_Free, .resp_size = sizeof(VERIFY4res), .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_WRITE] = { .name = "OP_WRITE", .funct = nfs4_op_write, .resume = nfs4_op_write_resume, .free_res = nfs4_op_write_Free, .resp_size = sizeof(WRITE4res), .exp_perm_flags = EXPORT_OPTION_WRITE_ACCESS}, [NFS4_OP_RELEASE_LOCKOWNER] = { .name = "OP_RELEASE_LOCKOWNER", .funct = nfs4_op_release_lockowner, .resume = nfs4_default_resume, .free_res = nfs4_op_release_lockowner_Free, .resp_size = sizeof(RELEASE_LOCKOWNER4res), .exp_perm_flags = 0}, [NFS4_OP_BACKCHANNEL_CTL] = { .name = "OP_BACKCHANNEL_CTL", .funct = nfs4_op_illegal, .resume = nfs4_default_resume, .free_res = nfs4_op_illegal_Free, .resp_size = sizeof(BACKCHANNEL_CTL4res), .exp_perm_flags = 0 /* tbd */}, [NFS4_OP_BIND_CONN_TO_SESSION] = { .name = "OP_BIND_CONN_TO_SESSION", .funct = nfs4_op_bind_conn, .resume = nfs4_default_resume, .free_res = nfs4_op_nfs4_op_bind_conn_Free, .resp_size = sizeof(BIND_CONN_TO_SESSION4res), .exp_perm_flags = 0 /* tbd */}, [NFS4_OP_EXCHANGE_ID] = { .name = "OP_EXCHANGE_ID", .funct = nfs4_op_exchange_id, .resume = nfs4_default_resume, .free_res = nfs4_op_exchange_id_Free, .resp_size = VARIABLE_RESP_SIZE, .exp_perm_flags = 0}, [NFS4_OP_CREATE_SESSION] = { .name = "OP_CREATE_SESSION", .funct = nfs4_op_create_session, .resume = nfs4_default_resume, .free_res = nfs4_op_create_session_Free, .resp_size = sizeof(CREATE_SESSION4res), .exp_perm_flags = 0}, [NFS4_OP_DESTROY_SESSION] = { .name = "OP_DESTROY_SESSION", .funct = nfs4_op_destroy_session, .resume = nfs4_default_resume, .free_res = nfs4_op_reclaim_complete_Free, .resp_size = sizeof(DESTROY_SESSION4res), .exp_perm_flags = 0}, [NFS4_OP_FREE_STATEID] = { .name = "OP_FREE_STATEID", .funct = nfs4_op_free_stateid, .resume = nfs4_default_resume, .free_res = nfs4_op_free_stateid_Free, .resp_size = sizeof(FREE_STATEID4res), .exp_perm_flags = 0}, [NFS4_OP_GET_DIR_DELEGATION] = { .name = "OP_GET_DIR_DELEGATION", .funct = nfs4_op_illegal, .resume = nfs4_default_resume, .free_res = nfs4_op_illegal_Free, .resp_size = sizeof(GET_DIR_DELEGATION4res), .exp_perm_flags = 0 /* tbd */}, [NFS4_OP_GETDEVICEINFO] = { .name = "OP_GETDEVICEINFO", .funct = nfs4_op_getdeviceinfo, .resume = nfs4_default_resume, .free_res = nfs4_op_getdeviceinfo_Free, .resp_size = VARIABLE_RESP_SIZE, .exp_perm_flags = 0}, [NFS4_OP_GETDEVICELIST] = { .name = "OP_GETDEVICELIST", .funct = nfs4_op_getdevicelist, .resume = nfs4_default_resume, .free_res = nfs4_op_getdevicelist_Free, .resp_size = VARIABLE_RESP_SIZE, .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_LAYOUTCOMMIT] = { .name = "OP_LAYOUTCOMMIT", .funct = nfs4_op_layoutcommit, .resume = nfs4_default_resume, .free_res = nfs4_op_reclaim_complete_Free, .resp_size = sizeof(LAYOUTCOMMIT4res), .exp_perm_flags = 0}, [NFS4_OP_LAYOUTGET] = { .name = "OP_LAYOUTGET", .funct = nfs4_op_layoutget, .resume = nfs4_default_resume, .free_res = nfs4_op_reclaim_complete_Free, .resp_size = VARIABLE_RESP_SIZE, .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_LAYOUTRETURN] = { .name = "OP_LAYOUTRETURN", .funct = nfs4_op_layoutreturn, .resume = nfs4_default_resume, .free_res = nfs4_op_reclaim_complete_Free, .resp_size = sizeof(LAYOUTRETURN4res), .exp_perm_flags = 0}, [NFS4_OP_SECINFO_NO_NAME] = { .name = "OP_SECINFO_NO_NAME", .funct = nfs4_op_secinfo_no_name, .resume = nfs4_default_resume, .free_res = nfs4_op_secinfo_no_name_Free, .resp_size = VARIABLE_RESP_SIZE, .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS}, [NFS4_OP_SEQUENCE] = { .name = "OP_SEQUENCE", .funct = nfs4_op_sequence, .resume = nfs4_default_resume, .free_res = nfs4_op_sequence_Free, .resp_size = sizeof(SEQUENCE4res), .exp_perm_flags = 0}, [NFS4_OP_SET_SSV] = { .name = "OP_SET_SSV", .funct = nfs4_op_set_ssv, .resume = nfs4_default_resume, .free_res = nfs4_op_set_ssv_Free, .resp_size = sizeof(SET_SSV4res), .exp_perm_flags = 0}, [NFS4_OP_TEST_STATEID] = { .name = "OP_TEST_STATEID", .funct = nfs4_op_test_stateid, .resume = nfs4_default_resume, .free_res = nfs4_op_test_stateid_Free, .resp_size = sizeof(TEST_STATEID4res), .exp_perm_flags = 0}, [NFS4_OP_WANT_DELEGATION] = { .name = "OP_WANT_DELEGATION", .funct = nfs4_op_illegal, .resume = nfs4_default_resume, .free_res = nfs4_op_illegal_Free, .resp_size = sizeof(WANT_DELEGATION4res), .exp_perm_flags = EXPORT_OPTION_MD_READ_ACCESS /* tbd */}, [NFS4_OP_DESTROY_CLIENTID] = { .name = "OP_DESTROY_CLIENTID", .funct = nfs4_op_destroy_clientid, .resume = nfs4_default_resume, .free_res = nfs4_op_destroy_clientid_Free, .resp_size = sizeof(DESTROY_CLIENTID4res), .exp_perm_flags = 0 /* tbd */}, [NFS4_OP_RECLAIM_COMPLETE] = { .name = "OP_RECLAIM_COMPLETE", .funct = nfs4_op_reclaim_complete, .resume = nfs4_default_resume, .free_res = nfs4_op_reclaim_complete_Free, .resp_size = sizeof(RECLAIM_COMPLETE4res), .exp_perm_flags = 0}, /* NFSv4.2 */ [NFS4_OP_ALLOCATE] = { .name = "OP_ALLOCATE", .funct = nfs4_op_allocate, .resume = nfs4_default_resume, .free_res = nfs4_op_write_Free, .resp_size = sizeof(ALLOCATE4res), .exp_perm_flags = 0}, [NFS4_OP_COPY] = { .name = "OP_COPY", .funct = nfs4_op_notsupp, .resume = nfs4_default_resume, .free_res = nfs4_op_notsupp_Free, .resp_size = sizeof(COPY4res), .exp_perm_flags = 0}, [NFS4_OP_COPY_NOTIFY] = { .name = "OP_COPY_NOTIFY", .funct = nfs4_op_notsupp, .resume = nfs4_default_resume, .free_res = nfs4_op_notsupp_Free, .resp_size = sizeof(COPY_NOTIFY4res), .exp_perm_flags = 0}, [NFS4_OP_DEALLOCATE] = { .name = "OP_DEALLOCATE", .funct = nfs4_op_deallocate, .resume = nfs4_default_resume, .free_res = nfs4_op_write_Free, .resp_size = sizeof(DEALLOCATE4res), .exp_perm_flags = 0}, [NFS4_OP_IO_ADVISE] = { .name = "OP_IO_ADVISE", .funct = nfs4_op_io_advise, .resume = nfs4_default_resume, .free_res = nfs4_op_io_advise_Free, .resp_size = sizeof(IO_ADVISE4res), .exp_perm_flags = 0}, [NFS4_OP_LAYOUTERROR] = { .name = "OP_LAYOUTERROR", .funct = nfs4_op_layouterror, .resume = nfs4_default_resume, .free_res = nfs4_op_layouterror_Free, .resp_size = sizeof(LAYOUTERROR4res), .exp_perm_flags = 0}, [NFS4_OP_LAYOUTSTATS] = { .name = "OP_LAYOUTSTATS", .funct = nfs4_op_layoutstats, .resume = nfs4_default_resume, .free_res = nfs4_op_layoutstats_Free, .resp_size = sizeof(LAYOUTSTATS4res), .exp_perm_flags = 0}, [NFS4_OP_OFFLOAD_CANCEL] = { .name = "OP_OFFLOAD_CANCEL", .funct = nfs4_op_notsupp, .resume = nfs4_default_resume, .free_res = nfs4_op_notsupp_Free, .resp_size = sizeof(OFFLOAD_ABORT4res), .exp_perm_flags = 0}, [NFS4_OP_OFFLOAD_STATUS] = { .name = "OP_OFFLOAD_STATUS", .funct = nfs4_op_notsupp, .resume = nfs4_default_resume, .free_res = nfs4_op_notsupp_Free, .resp_size = sizeof(OFFLOAD_STATUS4res), .exp_perm_flags = 0}, [NFS4_OP_READ_PLUS] = { .name = "OP_READ_PLUS", .funct = nfs4_op_read_plus, .resume = nfs4_op_read_plus_resume, .free_res = nfs4_op_read_plus_Free, .resp_size = sizeof(READ_PLUS4res), .exp_perm_flags = 0}, [NFS4_OP_SEEK] = { .name = "OP_SEEK", .funct = nfs4_op_seek, .resume = nfs4_default_resume, .free_res = nfs4_op_write_Free, .resp_size = sizeof(SEEK4res), .exp_perm_flags = 0}, [NFS4_OP_WRITE_SAME] = { .name = "OP_WRITE_SAME", .funct = nfs4_op_write_same, .resume = nfs4_default_resume, .free_res = nfs4_op_write_same_Free, .resp_size = sizeof(WRITE_SAME4res), .exp_perm_flags = 0}, [NFS4_OP_CLONE] = { .name = "OP_CLONE", .funct = nfs4_op_notsupp, .resume = nfs4_default_resume, .free_res = nfs4_op_notsupp_Free, .resp_size = sizeof(ILLEGAL4res), .exp_perm_flags = 0}, /* NFSv4.3 */ [NFS4_OP_GETXATTR] = { .name = "OP_GETXATTR", .funct = nfs4_op_getxattr, .resume = nfs4_default_resume, .free_res = nfs4_op_getxattr_Free, .resp_size = sizeof(GETXATTR4res), .exp_perm_flags = 0}, [NFS4_OP_SETXATTR] = { .name = "OP_SETXATTR", .funct = nfs4_op_setxattr, .resume = nfs4_default_resume, .free_res = nfs4_op_setxattr_Free, .resp_size = sizeof(SETXATTR4res), .exp_perm_flags = 0}, [NFS4_OP_LISTXATTR] = { .name = "OP_LISTXATTR", .funct = nfs4_op_listxattr, .resume = nfs4_default_resume, .free_res = nfs4_op_listxattr_Free, .resp_size = sizeof(LISTXATTR4res), .exp_perm_flags = 0}, [NFS4_OP_REMOVEXATTR] = { .name = "OP_REMOVEXATTR", .funct = nfs4_op_removexattr, .resume = nfs4_default_resume, .free_res = nfs4_op_removexattr_Free, .resp_size = sizeof(REMOVEXATTR4res), .exp_perm_flags = 0}, }; /** Define the last valid NFS v4 op for each minor version. * */ nfs_opnum4 LastOpcode[] = { NFS4_OP_RELEASE_LOCKOWNER, NFS4_OP_RECLAIM_COMPLETE, NFS4_OP_REMOVEXATTR }; static inline void copy_tag(utf8str_cs *dest, utf8str_cs *src) { utf8string_dup(dest, src->utf8string_val, src->utf8string_len); } enum nfs_req_result complete_op(compound_data_t *data, nfsstat4 *status, enum nfs_req_result result) { nfs_resop4 *thisres = &data->resarray[data->oppos]; COMPOUND4res *res_compound4; res_compound4 = &data->res->res_compound4_extended->res_compound4; if (result == NFS_REQ_REPLAY) { /* Replay cache, only true for SEQUENCE. Since will only be set * in those cases, no need to check operation or anything. This * result will be converted to NFS_REQ_OK before we actually * return from the compound. */ *status = data->cached_result_status; LogFullDebug(COMPONENT_SESSIONS, "Use session replay cache result %s", nfsstat4_to_str(*status)); /* Will exit the for loop since result is not NFS_REQ_OK */ goto out; } /* All the operations, like NFS4_OP_ACCESS, have a first replied * field called .status */ *status = thisres->nfs_resop4_u.opaccess.status; GSH_AUTO_TRACEPOINT(nfs_rpc, v4op_end, TRACE_INFO, "v4 op end. op pos: {}, op: {}, res: {}", data->oppos, data->opcode, *status); LogCompoundFH(data); /* Tally the response size */ if (*status != NFS4_OK && (optabv4[data->opcode].resp_size != VARIABLE_RESP_SIZE || data->op_resp_size == VARIABLE_RESP_SIZE)) { /* If the op failed and has a static response size, or * it has a variable size that hasn't been set, use the * sizeof nfsstat4 instead. */ data->op_resp_size = sizeof(nfsstat4); } data->resp_size += sizeof(nfs_opnum4) + data->op_resp_size; LogDebug( COMPONENT_NFS_V4, "Status of %s in position %d = %s, op response size is %" PRIu32 " total response size is %" PRIu32, data->opname, data->oppos, nfsstat4_to_str(*status), data->op_resp_size, data->resp_size); if (result == NFS_REQ_ERROR) { /* An error occurred, we do not manage the other requests * in the COMPOUND, this may be a regular behavior */ res_compound4->resarray.resarray_len = data->oppos + 1; } else { /* Continue for loop (result will be NFS_REQ_OK since * NFS_REQ_ERROR, NFS_REQ_REPLAY, and NFS_REQ_ASYNC_WAIT have * already been handled (we don't even get into this function * with NFS_REQ_ASYNC_WAIT) and NFS_REQ_DROP is not returned by * any nfs4_op. */ } out: server_stats_nfsv4_op_done(data->opcode, &data->op_start_time, *status); return result; } enum nfs_req_result process_one_op(compound_data_t *data, nfsstat4 *status) { const char *bad_op_state_reason = ""; int perm_flags; log_components_t alt_component = COMPONENT_NFS_V4; nfs_argop4 *thisarg = &data->argarray[data->oppos]; nfs_resop4 *thisres = &data->resarray[data->oppos]; enum nfs_req_result result; COMPOUND4res *res_compound4; res_compound4 = &data->res->res_compound4_extended->res_compound4; /* Used to check if OP_SEQUENCE is the first operation */ data->op_resp_size = sizeof(nfsstat4); data->opcode = thisarg->argop; /* Handle opcode overflow */ if (data->opcode > LastOpcode[data->minorversion]) data->opcode = 0; data->opname = optabv4[data->opcode].name; LogDebug(COMPONENT_NFS_V4, "Request %d: opcode %d is %s", data->oppos, data->opcode, data->opname); /* Verify BIND_CONN_TO_SESSION is not used in a compound * with length > 1. This check is NOT redundant with the * checks in nfs4_Compound(). */ if (data->oppos > 0 && data->opcode == NFS4_OP_BIND_CONN_TO_SESSION) { *status = NFS4ERR_NOT_ONLY_OP; bad_op_state_reason = "BIND_CONN_TO_SESSION past position 1"; goto bad_op_state; } /* OP_SEQUENCE is always the first operation of the request */ if (data->oppos > 0 && data->opcode == NFS4_OP_SEQUENCE) { *status = NFS4ERR_SEQUENCE_POS; bad_op_state_reason = "SEQUENCE past position 1"; goto bad_op_state; } /* If a DESTROY_SESSION not the only operation, and it matches * the session specified in the SEQUENCE op (since the compound * has more than one op, we already know it MUST start with * SEQUENCE), then it MUST be the final op in the compound. */ if (data->oppos > 0 && data->opcode == NFS4_OP_DESTROY_SESSION) { bool session_compare; bool bad_pos; session_compare = memcmp(data->argarray[0] .nfs_argop4_u.opsequence.sa_sessionid, thisarg->nfs_argop4_u.opdestroy_session .dsa_sessionid, NFS4_SESSIONID_SIZE) == 0; bad_pos = session_compare && data->oppos != (data->argarray_len - 1); LogAtLevel(COMPONENT_SESSIONS, bad_pos ? NIV_INFO : NIV_DEBUG, "DESTROY_SESSION in position %u out of 0-%" PRIi32 " %s is %s", data->oppos, data->argarray_len - 1, session_compare ? "same session as SEQUENCE" : "different session from SEQUENCE", bad_pos ? "not last op in compound" : "opk"); if (bad_pos) { *status = NFS4ERR_NOT_ONLY_OP; bad_op_state_reason = "DESTROY_SESSION not last op in compound"; goto bad_op_state; } } /* time each op */ now(&data->op_start_time); if (data->minorversion > 0 && data->session != NULL && data->session->fore_channel_attrs.ca_maxoperations == data->oppos) { *status = NFS4ERR_TOO_MANY_OPS; bad_op_state_reason = "Too many operations"; goto bad_op_state; } perm_flags = optabv4[data->opcode].exp_perm_flags & EXPORT_OPTION_ACCESS_MASK; if (perm_flags != 0) { *status = nfs4_Is_Fh_Empty(&data->currentFH); if (*status != NFS4_OK) { bad_op_state_reason = "Empty or NULL handle"; goto bad_op_state; } /* Operation uses a CurrentFH, so we can check export * perms. Perms should even be set reasonably for pseudo * file system. */ LogMidDebugAlt(COMPONENT_NFS_V4, COMPONENT_EXPORT, "Check export perms export = %08x req = %08x", op_ctx->export_perms.options & EXPORT_OPTION_ACCESS_MASK, perm_flags); if ((op_ctx->export_perms.options & perm_flags) != perm_flags) { /* Export doesn't allow requested * access for this client. */ if ((perm_flags & EXPORT_OPTION_MODIFY_ACCESS) != 0) *status = NFS4ERR_ROFS; else *status = NFS4ERR_ACCESS; bad_op_state_reason = "Export permission failure"; alt_component = COMPONENT_EXPORT; goto bad_op_state; } } /* Set up the minimum/default response size and check if there * is room for it. */ data->op_resp_size = optabv4[data->opcode].resp_size; *status = check_resp_room(data, data->op_resp_size); if (*status != NFS4_OK) { bad_op_state_reason = "op response size"; bad_op_state: /* Tally the response size */ data->resp_size += sizeof(nfs_opnum4) + sizeof(nfsstat4); LogDebugAlt( COMPONENT_NFS_V4, alt_component, "Status of %s in position %d due to %s is %s, op response size = %" PRIu32 " total response size = %" PRIu32, data->opname, data->oppos, bad_op_state_reason, nfsstat4_to_str(*status), data->op_resp_size, data->resp_size); /* All the operation, like NFS4_OP_ACCESS, have * a first replied field called .status */ thisres->nfs_resop4_u.opaccess.status = *status; thisres->resop = data->opcode; /* Do not manage the other requests in the COMPOUND. */ res_compound4->resarray.resarray_len = data->oppos + 1; return NFS_REQ_ERROR; } /*************************************************************** * Make the actual op call * **************************************************************/ GSH_AUTO_TRACEPOINT(nfs_rpc, v4op_start, TRACE_INFO, "v4 op start. op pos: {}, op: {}", data->oppos, data->opcode); result = (optabv4[data->opcode].funct)(thisarg, data, thisres); if (result != NFS_REQ_ASYNC_WAIT) { /* Complete the operation, otherwise return without doing * anything else. */ result = complete_op(data, status, result); } return result; } void set_slot_last_req(compound_data_t *data) { struct timespec curr_time; data->slot->last_req.opcode_num = get_nfs4_opcodes( data, data->slot->last_req.opcodes, NFS4_MAX_OPERATIONS); data->slot->last_req.xid = data->req->rq_msg.rm_xid; data->slot->last_req.seq_id = data->sequence; now(&curr_time); data->slot->last_req.finish_time_ms = curr_time.tv_sec * 1000 + curr_time.tv_nsec / 1000000; } void complete_nfs4_compound(compound_data_t *data, int status, enum nfs_req_result result) { COMPOUND4res *res_compound4; res_compound4 = &data->res->res_compound4_extended->res_compound4; server_stats_compound_done(data->argarray_len, status); /* Complete the reply, in particular, tell where you stopped if * unsuccessful COMPOUD */ res_compound4->status = status; /* Manage session's DRC: keep NFS4.1 replay for later use, but don't * save a replayed result again. */ if (data->sa_cachethis) { /* Pointer has been set by nfs4_op_sequence and points to slot * to cache result in. */ LogFullDebug( COMPONENT_SESSIONS, "Save result in session replay cache %p sizeof nfs_res_t=%d", data->slot->cached_result, (int)sizeof(nfs_res_t)); /* Save the result pointer in the slot cache (the correct slot * is pointed to by data->cached_result). */ data->slot->cached_result = data->res->res_compound4_extended; /* record the latest request. */ set_slot_last_req(data); /* Take a reference to indicate that this reply is cached. */ atomic_inc_int32_t(&data->slot->cached_result->res_refcnt); } else if (data->minorversion > 0 && result != NFS_REQ_REPLAY && data->argarray[0].argop == NFS4_OP_SEQUENCE && data->slot != NULL) { /* We need to cache an "uncached" response. The length is * 1 if only one op processed, otherwise 2. */ struct COMPOUND4res *c_res; u_int resarray_len = res_compound4->resarray.resarray_len == 1 ? 1 : 2; struct nfs_resop4 *res0; /* If the slot happened to be in use, release it. */ release_slot(data->slot); /* Allocate (and zero) a new COMPOUND4res_extended */ data->slot->cached_result = gsh_calloc(1, sizeof(*data->slot->cached_result)); /* record the latest request. */ set_slot_last_req(data); /* Take initial reference to response. */ data->slot->cached_result->res_refcnt = 1; c_res = &data->slot->cached_result->res_compound4; c_res->resarray.resarray_len = resarray_len; c_res->resarray.resarray_val = gsh_calloc(resarray_len, sizeof(struct nfs_resop4)); copy_tag(&c_res->tag, &res_compound4->tag); res0 = c_res->resarray.resarray_val; /* Copy the sequence result. */ *res0 = res_compound4->resarray.resarray_val[0]; c_res->status = res0->nfs_resop4_u.opillegal.status; if (resarray_len == 2) { struct nfs_resop4 *res1 = res0 + 1; /* Shallow copy response since we will override any * resok or any negative response that might have * allocated data. */ *res1 = res_compound4->resarray.resarray_val[1]; /* Override NFS4_OK and NFS4ERR_DENIED. We MUST override * NFS4_OK since we aren't caching a full response and * we MUST override NFS4ERR_DENIED because LOCK and * LOCKT allocate data that we did not deep copy. * * If any new operations are added with dynamically * allocated data associated with a non-NFS4_OK * status are added in some future minor version, they * will likely need special handling here also. * * Note that we COULD get fancy and if we had a 2 op * compound that had an NFS4_OK status and no dynamic * data was allocated then go ahead and cache the * full response since it wouldn't take any more * memory. However, that would add a lot more special * handling here. */ if (res1->nfs_resop4_u.opillegal.status == NFS4_OK || res1->nfs_resop4_u.opillegal.status == NFS4ERR_DENIED) { res1->nfs_resop4_u.opillegal.status = NFS4ERR_RETRY_UNCACHED_REP; } c_res->status = res1->nfs_resop4_u.opillegal.status; } /* NOTE: We just built a 2nd "uncached" response and put that * in the slot cache with 1 reference. The actual response is * whatever it is, but is different and has it's OWN 1 refcount. * It can't have more than 1 reference since this is NOT a * replay. */ } /* If we have reserved a lease, update it and release it */ if (data->preserved_clientid != NULL) { /* Update and release lease */ update_lease_simple(data->preserved_clientid); } if (status != NFS4_OK) LogDebug(COMPONENT_NFS_V4, "End status = %s lastindex = %d", nfsstat4_to_str(status), data->oppos); } static enum xprt_stat nfs4_compound_resume(struct svc_req *req) { nfs_request_t *reqdata = container_of(req, nfs_request_t, svc); nfsstat4 status = NFS4_OK; compound_data_t *data = reqdata->proc_data; enum nfs_req_result result; /* Restore the op_ctx */ resume_op_context(&reqdata->op_context); /* Start by resuming the operation that suspended. */ result = (optabv4[data->opcode].resume)(&data->argarray[data->oppos], data, &data->resarray[data->oppos]); if (result != NFS_REQ_ASYNC_WAIT) { /* Complete the operation (will fill in status). */ result = complete_op(data, &status, result); } else { /* The request is suspended, don't touch the request in * any way because the resume may already be scheduled * and running on nother thread. The xp_resume_cb has * already been set up before we started processing * ops on this request at all. */ suspend_op_context(); return XPRT_SUSPEND; } /* Skip the resumed op and continue through the rest of the compound. */ for (data->oppos += 1; result == NFS_REQ_OK && data->oppos < data->argarray_len; data->oppos++) { result = process_one_op(data, &status); if (result == NFS_REQ_ASYNC_WAIT) { /* The request is suspended, don't touch the request in * any way because the resume may already be scheduled * and running on nother thread. The xp_resume_cb has * already been set up before we started processing * ops on this request at all. */ suspend_op_context(); return XPRT_SUSPEND; } } complete_nfs4_compound(data, status, result); compound_data_Free(data); /* release current active export in op_ctx. */ if (op_ctx->ctx_export) clear_op_context_export(); nfs_rpc_complete_async_request(reqdata, NFS_REQ_OK); return XPRT_IDLE; } /** * * @brief get the opcodes of compound * * @param[in] data Compound request's data * @param[out] opcodes all opcodes in Compound * @param[in] opcodes_array_len length of opcodes array * * @retval number of opcode in compound. */ uint32_t get_nfs4_opcodes(compound_data_t *data, nfs_opnum4 *opcodes, uint32_t opcodes_array_len) { uint32_t i = 0; assert(opcodes_array_len >= data->argarray_len); for (i = 0; i < data->argarray_len; i++) opcodes[i] = data->argarray[i].argop; return data->argarray_len; } /** * @brief The NFS PROC4 COMPOUND * * Implements the NFS PROC4 COMPOUND. This routine processes the * content of the nfsv4 operation list and composes the result. On * this aspect it is a little similar to a dispatch routine. * Operation and functions necessary to process them are defined in * the optabv4 array. * * * @param[in] arg Generic nfs arguments * @param[in] req NFSv4 request structure * @param[out] res NFSv4 reply structure * * @see nfs4_op_<*> functions * @see nfs4_GetPseudoFs * * @retval NFS_REQ_OKAY if a result is sent. * @retval NFS_REQ_DROP if we pretend we never saw the request. */ int nfs4_Compound(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { nfsstat4 status = NFS4_OK; compound_data_t *data = NULL; const uint32_t compound4_minor = arg->arg_compound4.minorversion; const uint32_t argarray_len = arg->arg_compound4.argarray.argarray_len; /* Array of op arguments */ nfs_argop4 *const argarray = arg->arg_compound4.argarray.argarray_val; bool drop = false; nfs_request_t *reqdata = container_of(req, nfs_request_t, svc); struct COMPOUND4res *res_compound4; enum nfs_req_result result = NFS_REQ_OK; /* Allocate (and zero) the COMPOUND4res_extended */ res->res_compound4_extended = gsh_calloc(1, sizeof(*res->res_compound4_extended)); res_compound4 = &res->res_compound4_extended->res_compound4; /* Take initial reference to response. */ res->res_compound4_extended->res_refcnt = 1; if (compound4_minor > 2) { LogCrit(COMPONENT_NFS_V4, "Bad Minor Version %d", compound4_minor); res_compound4->status = NFS4ERR_MINOR_VERS_MISMATCH; res_compound4->resarray.resarray_len = 0; goto out; } if ((nfs_param.nfsv4_param.minor_versions & (1 << compound4_minor)) == 0) { LogInfo(COMPONENT_NFS_V4, "Unsupported minor version %d", compound4_minor); res_compound4->status = NFS4ERR_MINOR_VERS_MISMATCH; res_compound4->resarray.resarray_len = 0; goto out; } #ifdef _USE_NFS_RDMA /* NFS over RDMA transport is supported by default for NFSv4.0 Only, * fail other minor versions unless configured in * NFS_RDMA_Protocol_Versions */ if ((get_port(svc_getrpclocal(req->rq_xprt)) == nfs_param.core_param.port[P_NFS_RDMA]) && (!(nfs_param.core_param.nfs_rdma_supported_protocol_versions & (NFS_RDMA_ENABLE_FOR_NFSV40 << compound4_minor)))) { LogCrit(COMPONENT_NFS_V4, "NFS over RDMA transport is not supported for NFSv4.%d, failing the request !!!", compound4_minor); /* mount.nfs gets Protocol not supported errors */ res_compound4->status = NFS4ERR_MINOR_VERS_MISMATCH; res_compound4->resarray.resarray_len = 0; goto out; } #endif /* Initialisation of the compound request internal's data */ data = gsh_calloc(1, sizeof(*data)); data->req = req; data->argarray_len = argarray_len; data->argarray = arg->arg_compound4.argarray.argarray_val; data->res = res; reqdata->proc_data = data; /* Minor version related stuff */ op_ctx->nfs_minorvers = compound4_minor; data->minorversion = compound4_minor; /* Keeping the same tag as in the arguments */ copy_tag(&res_compound4->tag, &arg->arg_compound4.tag); if (res_compound4->tag.utf8string_len > 0) { /* Check if the tag is a valid utf8 string (., .., and / ok) */ if (nfs4_utf8string_scan(&res_compound4->tag, UTF8_SCAN_STRICT) != 0) { char str[LOG_BUFF_LEN]; struct display_buffer dspbuf = { sizeof(str), str, str }; display_opaque_bytes(&dspbuf, res_compound4->tag.utf8string_val, res_compound4->tag.utf8string_len); LogCrit(COMPONENT_NFS_V4, "COMPOUND: bad tag %p len %d bytes %s", res_compound4->tag.utf8string_val, res_compound4->tag.utf8string_len, str); res_compound4->status = NFS4ERR_INVAL; res_compound4->resarray.resarray_len = 0; goto out; } /* Make a copy of the tagname */ data->tagname = gsh_malloc(res_compound4->tag.utf8string_len + 1); memcpy(data->tagname, res_compound4->tag.utf8string_val, res_compound4->tag.utf8string_len + 1); } else { /* No tag */ data->tagname = gsh_strdup("NO TAG"); } /* Managing the operation list */ LogDebug(COMPONENT_NFS_V4, "COMPOUND: There are %d operations, res = %p, tag = %s", argarray_len, res, data->tagname); GSH_AUTO_TRACEPOINT( nfs_rpc, compound_start, TRACE_INFO, "COMPOUND: There are {} operations, minor = {}, tag = {}", argarray_len, compound4_minor, TP_STR(data->tagname)); /* Check for empty COMPOUND request */ if (argarray_len == 0) { LogMajor(COMPONENT_NFS_V4, "An empty COMPOUND (no operation in it) was received"); res_compound4->status = NFS4_OK; res_compound4->resarray.resarray_len = 0; goto out; } /* Check for too long request */ if (argarray_len > NFS4_MAX_OPERATIONS) { LogMajor( COMPONENT_NFS_V4, "A COMPOUND with too many operations (%d) was received", argarray_len); res_compound4->status = NFS4ERR_RESOURCE; res_compound4->resarray.resarray_len = 0; goto out; } /* Initialize response size with size of compound response size. */ data->resp_size = sizeof(COMPOUND4res) - sizeof(nfs_resop4 *); /* Building the client credential field */ if (nfs_rpc_req2client_cred(req, &data->credential) == -1) { /* Malformed credential */ drop = true; goto out; } /* Keeping the same tag as in the arguments */ res_compound4->tag.utf8string_len = arg->arg_compound4.tag.utf8string_len; /* Allocating the reply nfs_resop4 */ data->resarray = gsh_calloc(argarray_len, sizeof(struct nfs_resop4)); res_compound4->resarray.resarray_len = argarray_len; res_compound4->resarray.resarray_val = data->resarray; /* Manage errors NFS4ERR_OP_NOT_IN_SESSION and NFS4ERR_NOT_ONLY_OP. * These checks apply only to 4.1 */ if (compound4_minor > 0) { /* Check for valid operation to start an NFS v4.1 COMPOUND: */ if (argarray[0].argop != NFS4_OP_ILLEGAL && argarray[0].argop != NFS4_OP_SEQUENCE && argarray[0].argop != NFS4_OP_EXCHANGE_ID && argarray[0].argop != NFS4_OP_CREATE_SESSION && argarray[0].argop != NFS4_OP_DESTROY_SESSION && argarray[0].argop != NFS4_OP_BIND_CONN_TO_SESSION && argarray[0].argop != NFS4_OP_DESTROY_CLIENTID) { res_compound4->status = NFS4ERR_OP_NOT_IN_SESSION; res_compound4->resarray.resarray_len = 0; goto out; } if (argarray_len > 1) { /* If not prepended by OP4_SEQUENCE, OP4_EXCHANGE_ID * should be the only request in the compound see * 18.35.3. and test EID8 for details * * If not prepended bu OP4_SEQUENCE, OP4_CREATE_SESSION * should be the only request in the compound see * 18.36.3 and test CSESS23 for details * * If the COMPOUND request does not start with SEQUENCE, * and if DESTROY_SESSION is not the sole operation, * then server MUST return NFS4ERR_NOT_ONLY_OP. See * 18.37.3 and test DSESS9005 for details */ if (argarray[0].argop == NFS4_OP_EXCHANGE_ID || argarray[0].argop == NFS4_OP_CREATE_SESSION || argarray[0].argop == NFS4_OP_DESTROY_CLIENTID || argarray[0].argop == NFS4_OP_DESTROY_SESSION || argarray[0].argop == NFS4_OP_BIND_CONN_TO_SESSION) { res_compound4->status = NFS4ERR_NOT_ONLY_OP; res_compound4->resarray.resarray_len = 0; goto out; } } } if (likely(component_log_level[COMPONENT_NFS_V4] >= NIV_FULL_DEBUG)) { nfs_opnum4 opcodes[NFS4_MAX_OPERATIONS] = { 0 }; uint32_t opcode_num = get_nfs4_opcodes(data, opcodes, NFS4_MAX_OPERATIONS); char operations[NFS4_COMPOUND_OPERATIONS_STR_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(operations), operations, operations }; display_nfs4_operations(&dspbuf, opcodes, opcode_num); LogFullDebug(COMPONENT_NFS_V4, "COMPOUND: There are %d operations %s", argarray_len, operations); } /* Before we start running, we must prepare to be suspended. We must do * this now because after we have been suspended, it's too late, the * request might have already been resumed on another worker thread. */ data->req->rq_resume_cb = nfs4_compound_resume; /********************************************************************** * Now start processing the compound ops. **********************************************************************/ for (data->oppos = 0; result == NFS_REQ_OK && data->oppos < data->argarray_len; data->oppos++) { result = process_one_op(data, &status); if (result == NFS_REQ_ASYNC_WAIT) { /* The request is suspended, don't touch the request in * any way because the resume may already be scheduled * and running on nother thread. The xp_resume_cb has * already been set up before we started processing * ops on this request at all. */ return result; } } complete_nfs4_compound(data, status, result); out: compound_data_Free(data); /* release current active export in op_ctx. */ if (op_ctx->ctx_export) clear_op_context_export(); return drop ? NFS_REQ_DROP : NFS_REQ_OK; } /* nfs4_Compound */ /** * * @brief Free the result for one NFS4_OP * * This function frees any memory allocated for the result of an NFSv4 * operation. * * @param[in,out] res The result to be freed * */ void nfs4_Compound_FreeOne(nfs_resop4 *res) { int opcode; opcode = (res->resop != NFS4_OP_ILLEGAL) ? res->resop : 0; /* opcode 0 for illegals */ optabv4[opcode].free_res(res); } void release_nfs4_res_compound(struct COMPOUND4res_extended *res_compound4_ex) { unsigned int i = 0; int32_t refcnt = atomic_dec_int32_t(&res_compound4_ex->res_refcnt); struct COMPOUND4res *res_compound4 = &res_compound4_ex->res_compound4; assert(refcnt >= 0); if (refcnt > 0) { LogFullDebugAlt( COMPONENT_NFS_V4, COMPONENT_SESSIONS, "Skipping free of NFS4 result %p refcnt %" PRIi32, res_compound4_ex, refcnt); return; } LogFullDebugAlt(COMPONENT_NFS_V4, COMPONENT_SESSIONS, "Compound Free %p (resarraylen=%i)", res_compound4_ex, res_compound4->resarray.resarray_len); for (i = 0; i < res_compound4->resarray.resarray_len; i++) { nfs_resop4 *val = &res_compound4->resarray.resarray_val[i]; if (val) { /* !val is an error case, but it can occur, so avoid * indirect on NULL */ nfs4_Compound_FreeOne(val); } } gsh_free(res_compound4->resarray.resarray_val); res_compound4->resarray.resarray_val = NULL; gsh_free(res_compound4->tag.utf8string_val); res_compound4->tag.utf8string_val = NULL; gsh_free(res_compound4_ex); } /** * * @brief Free the result for NFS4PROC_COMPOUND * * This function frees the result for one NFS4PROC_COMPOUND. * * @param[in] res The result * */ void nfs4_Compound_Free(nfs_res_t *res) { release_nfs4_res_compound(res->res_compound4_extended); } /** * @brief Free a compound data structure * * This function frees one compound data structure. * * @param[in,out] data The compound_data_t to be freed * */ void compound_data_Free(compound_data_t *data) { if (data == NULL) return; /* Release refcounted cache entries. A note on current_ds and saved_ds, * If both are in use and the same, it will be released during * set_saved_entry since set_current_entry will have set current_ds to * NULL. If both are non-NULL and different, current_ds will be * release by set_current_entry and saved_ds will be released by * set_saved_entry. */ set_current_entry(data, NULL); set_saved_entry(data, NULL); gsh_free(data->tagname); if (data->session) { if (data->slotid != UINT32_MAX) { nfs41_session_slot_t *slot; /* Release the slot if in use */ slot = &data->session->fc_slots[data->slotid]; PTHREAD_MUTEX_unlock(&slot->slot_lock); } dec_session_ref(data->session); data->session = NULL; } /* Release SavedFH reference to export. */ if (data->saved_export) { put_gsh_export(data->saved_export); data->saved_export = NULL; } /* If there was a saved_pnfs_ds is present, release reference. */ if (data->saved_pnfs_ds != NULL) { pnfs_ds_put(data->saved_pnfs_ds); data->saved_pnfs_ds = NULL; } if (data->currentFH.nfs_fh4_val != NULL) gsh_free(data->currentFH.nfs_fh4_val); if (data->savedFH.nfs_fh4_val != NULL) gsh_free(data->savedFH.nfs_fh4_val); gsh_free(data); } /* compound_data_Free */ /** * * @brief Copy the result for one NFS4_OP * * This function copies the result structure for a single NFSv4 * operation. * * @param[out] res_dst Buffer to which to copy the result * @param[in] res_src The result to copy * */ void nfs4_Compound_CopyResOne(nfs_resop4 *res_dst, nfs_resop4 *res_src) { /* Copy base data structure */ memcpy(res_dst, res_src, sizeof(*res_dst)); /* Do deep copy where necessary */ switch (res_src->resop) { case NFS4_OP_ACCESS: break; case NFS4_OP_CLOSE: nfs4_op_close_CopyRes(&res_dst->nfs_resop4_u.opclose, &res_src->nfs_resop4_u.opclose); return; case NFS4_OP_COMMIT: case NFS4_OP_CREATE: case NFS4_OP_DELEGPURGE: case NFS4_OP_DELEGRETURN: case NFS4_OP_GETATTR: case NFS4_OP_GETFH: case NFS4_OP_LINK: break; case NFS4_OP_LOCK: nfs4_op_lock_CopyRes(&res_dst->nfs_resop4_u.oplock, &res_src->nfs_resop4_u.oplock); return; case NFS4_OP_LOCKT: break; case NFS4_OP_LOCKU: nfs4_op_locku_CopyRes(&res_dst->nfs_resop4_u.oplocku, &res_src->nfs_resop4_u.oplocku); return; case NFS4_OP_LOOKUP: case NFS4_OP_LOOKUPP: case NFS4_OP_NVERIFY: break; case NFS4_OP_OPEN: nfs4_op_open_CopyRes(&res_dst->nfs_resop4_u.opopen, &res_src->nfs_resop4_u.opopen); return; case NFS4_OP_OPENATTR: break; case NFS4_OP_OPEN_CONFIRM: nfs4_op_open_confirm_CopyRes( &res_dst->nfs_resop4_u.opopen_confirm, &res_src->nfs_resop4_u.opopen_confirm); return; case NFS4_OP_OPEN_DOWNGRADE: nfs4_op_open_downgrade_CopyRes( &res_dst->nfs_resop4_u.opopen_downgrade, &res_src->nfs_resop4_u.opopen_downgrade); return; case NFS4_OP_PUTFH: case NFS4_OP_PUTPUBFH: case NFS4_OP_PUTROOTFH: case NFS4_OP_READ: case NFS4_OP_READDIR: case NFS4_OP_READLINK: case NFS4_OP_REMOVE: case NFS4_OP_RENAME: case NFS4_OP_RENEW: case NFS4_OP_RESTOREFH: case NFS4_OP_SAVEFH: case NFS4_OP_SECINFO: case NFS4_OP_SETATTR: case NFS4_OP_SETCLIENTID: case NFS4_OP_SETCLIENTID_CONFIRM: case NFS4_OP_VERIFY: case NFS4_OP_WRITE: case NFS4_OP_RELEASE_LOCKOWNER: break; case NFS4_OP_EXCHANGE_ID: case NFS4_OP_CREATE_SESSION: case NFS4_OP_SEQUENCE: case NFS4_OP_GETDEVICEINFO: case NFS4_OP_GETDEVICELIST: case NFS4_OP_BACKCHANNEL_CTL: case NFS4_OP_BIND_CONN_TO_SESSION: case NFS4_OP_DESTROY_SESSION: case NFS4_OP_FREE_STATEID: case NFS4_OP_GET_DIR_DELEGATION: case NFS4_OP_LAYOUTCOMMIT: case NFS4_OP_LAYOUTGET: case NFS4_OP_LAYOUTRETURN: case NFS4_OP_SECINFO_NO_NAME: case NFS4_OP_SET_SSV: case NFS4_OP_TEST_STATEID: case NFS4_OP_WANT_DELEGATION: case NFS4_OP_DESTROY_CLIENTID: case NFS4_OP_RECLAIM_COMPLETE: /* NFSv4.2 */ case NFS4_OP_ALLOCATE: case NFS4_OP_COPY: case NFS4_OP_COPY_NOTIFY: case NFS4_OP_DEALLOCATE: case NFS4_OP_IO_ADVISE: case NFS4_OP_LAYOUTERROR: case NFS4_OP_LAYOUTSTATS: case NFS4_OP_OFFLOAD_CANCEL: case NFS4_OP_OFFLOAD_STATUS: case NFS4_OP_READ_PLUS: case NFS4_OP_SEEK: case NFS4_OP_WRITE_SAME: case NFS4_OP_CLONE: /* NFSv4.3 */ case NFS4_OP_GETXATTR: case NFS4_OP_SETXATTR: case NFS4_OP_LISTXATTR: case NFS4_OP_REMOVEXATTR: case NFS4_OP_LAST_ONE: break; case NFS4_OP_ILLEGAL: break; } /* switch */ LogFatal(COMPONENT_NFS_V4, "Copy one result not implemented for %d", res_src->resop); } /** * @brief Handle the xdr encode of the COMPOUND response * * @param(in) xdrs The XDR object * @param(in) objp The response pointer * */ bool xdr_COMPOUND4res_extended(XDR *xdrs, struct COMPOUND4res_extended **objp) { /* Since the response in nfs_res_t is a pointer, we must dereference it * to complete the encode. */ struct COMPOUND4res_extended *res_compound4_extended = *objp; /* And we must pass the actual COMPOUND4res */ return xdr_COMPOUND4res(xdrs, &res_compound4_extended->res_compound4); } /* @} */ nfs-ganesha-6.5/src/Protocols/NFS/nfs4_cb_Compound.c000066400000000000000000000060721473756622300223310ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * Portions Copyright (C) 2012, The Linux Box Corporation * Contributor : Matt Benjamin * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file nfs4_cb_Compound.c * \brief Routines used for managing the NFS4/CB COMPOUND functions. * * Routines used for managing the NFS4/CB COMPOUND functions. */ #include "config.h" #include #include #include #include #include #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_rpc_callback.h" static const nfs4_cb_tag_t cbtagtab4[] = { { NFS4_CB_TAG_DEFAULT, "Ganesha CB Compound", 19 }, }; /* Some CITI-inspired compound helper ideas */ void cb_compound_init_v4(nfs4_compound_t *cbt, uint32_t n_ops, uint32_t minorversion, uint32_t ident, char *tag, uint32_t tag_len) { /* args */ memset(cbt, 0, sizeof(nfs4_compound_t)); /* XDRS */ cbt->v_u.v4.args.minorversion = minorversion; cbt->v_u.v4.args.callback_ident = ident; cbt->v_u.v4.args.argarray.argarray_val = alloc_cb_argop(n_ops); cbt->v_u.v4.args.argarray.argarray_len = 0; /* not n_ops, see below */ if (tag) { /* sender must ensure tag is safe to queue */ cbt->v_u.v4.args.tag.utf8string_val = tag; cbt->v_u.v4.args.tag.utf8string_len = tag_len; } else { cbt->v_u.v4.args.tag.utf8string_val = cbtagtab4[NFS4_CB_TAG_DEFAULT].val; cbt->v_u.v4.args.tag.utf8string_len = cbtagtab4[NFS4_CB_TAG_DEFAULT].len; } cbt->v_u.v4.res.resarray.resarray_val = alloc_cb_resop(n_ops); cbt->v_u.v4.res.resarray.resarray_len = 0; } void cb_compound_add_op(nfs4_compound_t *cbt, nfs_cb_argop4 *src) { /* old value */ uint32_t ix = (cbt->v_u.v4.args.argarray.argarray_len)++; nfs_cb_argop4 *dst = cbt->v_u.v4.args.argarray.argarray_val + ix; *dst = *src; /* nothing to do for (zero) val region */ cbt->v_u.v4.res.resarray.resarray_len++; } void cb_compound_free(nfs4_compound_t *cbt) { free_cb_argop(cbt->v_u.v4.args.argarray.argarray_val); free_cb_resop(cbt->v_u.v4.res.resarray.resarray_val); } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_access.c000066400000000000000000000100541473756622300220330ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_access.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include #include #include #include #include "log.h" #include "gsh_rpc.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_creds.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_file_handle.h" #include "nfs_proto_tools.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief NFS4_OP_ACCESS, checks for file's accessibility. * * This function implements the NFS4_OP_ACCESS operation, which checks * for file's accessibility. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 362 * */ enum nfs_req_result nfs4_op_access(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { ACCESS4args *const arg_ACCESS4 = &op->nfs_argop4_u.opaccess; GSH_AUTO_TRACEPOINT(nfs4, op_access_start, TRACE_INFO, "ACCESS arg: access={}", arg_ACCESS4->access); ACCESS4res *const res_ACCESS4 = &resp->nfs_resop4_u.opaccess; fsal_status_t status; uint32_t max_access = (ACCESS4_READ | ACCESS4_LOOKUP | ACCESS4_MODIFY | ACCESS4_EXTEND | ACCESS4_DELETE | ACCESS4_EXECUTE); /* xattrs are a v4.2+ feature */ if (data->minorversion >= 2) { max_access |= ACCESS4_XAREAD | ACCESS4_XAWRITE | ACCESS4_XALIST; } /* initialize output */ res_ACCESS4->ACCESS4res_u.resok4.supported = 0; res_ACCESS4->ACCESS4res_u.resok4.access = 0; resp->resop = NFS4_OP_ACCESS; res_ACCESS4->status = NFS4_OK; /* Do basic checks on a filehandle */ res_ACCESS4->status = nfs4_sanity_check_FH(data, NO_FILE_TYPE, false); if (res_ACCESS4->status != NFS4_OK) return NFS_REQ_ERROR; /* Check for input parameter's sanity */ if (arg_ACCESS4->access > max_access) { res_ACCESS4->status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } /* Perform the 'access' call */ status = nfs_access_op(data->current_obj, arg_ACCESS4->access, &res_ACCESS4->ACCESS4res_u.resok4.access, &res_ACCESS4->ACCESS4res_u.resok4.supported); if (status.major == ERR_FSAL_NO_ERROR || status.major == ERR_FSAL_ACCESS) res_ACCESS4->status = NFS4_OK; else res_ACCESS4->status = nfs4_Errno_status(status); GSH_AUTO_TRACEPOINT(nfs4, op_access_end, TRACE_INFO, "ACCESS res: status={} access={} supported={}", res_ACCESS4->status, res_ACCESS4->ACCESS4res_u.resok4.access, res_ACCESS4->ACCESS4res_u.resok4.supported); return nfsstat4_to_nfs_req_result(res_ACCESS4->status); } /* nfs4_op_access */ /** * @brief Free memory allocated for ACCESS result * * This function frees any memory allocated for the result of the * NFS4_OP_ACCESS operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_access_Free(nfs_resop4 *resp) { /* Nothing to do */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_allocate.c000066400000000000000000000167161473756622300223710ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2018 Jeff Layton * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_allocate.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions ALLOCATE and * DEALLOCATE. */ #include "config.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include "fsal_pnfs.h" #include "server_stats.h" #include "export_mgr.h" static enum nfs_req_result allocate_deallocate(compound_data_t *data, stateid4 *stateid, uint64_t offset, uint64_t size, bool allocate, nfsstat4 *status) { state_t *state = NULL; fsal_status_t fsal_status = { 0, 0 }; struct fsal_obj_handle *obj = NULL; uint64_t MaxOffsetWrite = atomic_fetch_uint64_t(&op_ctx->ctx_export->MaxOffsetWrite); /* Only files can have their allocation info changed */ *status = nfs4_sanity_check_FH(data, REGULAR_FILE, false); if (*status != NFS4_OK) return NFS_REQ_ERROR; /* if quota support is active, then we should check if the FSAL allows block allocation */ fsal_status = op_ctx->fsal_export->exp_ops.check_quota( op_ctx->fsal_export, CTX_FULLPATH(op_ctx), FSAL_QUOTA_BLOCKS); if (FSAL_IS_ERROR(fsal_status)) { *status = NFS4ERR_DQUOT; return NFS_REQ_ERROR; } obj = data->current_obj; /* Check stateid correctness and get pointer to state * (also checks for special stateids) */ *status = nfs4_Check_Stateid(stateid, obj, &state, data, STATEID_SPECIAL_ANY, 0, false, allocate ? "ALLOCATE" : "DEALLOCATE"); if (*status != NFS4_OK) return NFS_REQ_ERROR; /* NB: After this points, if state == NULL, then * the stateid is all-0 or all-1 */ if (state != NULL) { struct state_t *state_open; struct state_deleg *sdeleg; switch (state->state_type) { case STATE_TYPE_SHARE: break; case STATE_TYPE_LOCK: state_open = nfs4_State_Get_Pointer( state->state_data.lock.openstate_key); if (state_open == NULL) { *status = NFS4ERR_BAD_STATEID; goto out; } dec_state_t_ref(state); state = state_open; break; case STATE_TYPE_DELEG: /* * As with WRITE, the stateid is just here to provide * ordering info with respect to locks and such. * Delegation and layout stateids aren't generally * useful for ordering so we just use a NULL state * pointer here (conceptually similar to the anonymous * stateids). */ /* Check if the delegation state allows WRITE */ sdeleg = &state->state_data.deleg; if (!(sdeleg->sd_type & OPEN_DELEGATE_WRITE)) { /* Invalid delegation for this operation. */ LogDebug(COMPONENT_STATE, "Delegation type:%d state:%d", sdeleg->sd_type, sdeleg->sd_state); *status = NFS4ERR_BAD_STATEID; goto out; } break; default: *status = NFS4ERR_BAD_STATEID; LogDebug(COMPONENT_NFS_V4_LOCK, "ALLOCATE with invalid stateid of type %d", (int)state->state_type); goto out; } /* This is an ALLOCATE operation, this means that the file * MUST have been opened for writing */ if (state != NULL && (state->state_data.share.share_access & OPEN4_SHARE_ACCESS_WRITE) == 0) { /* Bad open mode, return NFS4ERR_OPENMODE */ *status = NFS4ERR_OPENMODE; if (isDebug(COMPONENT_NFS_V4_LOCK)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_stateid(&dspbuf, state); LogDebug( COMPONENT_NFS_V4_LOCK, "ALLOCATE %s doesn't have OPEN4_SHARE_ACCESS_WRITE", str); } goto out; } } else { /* * We have an anonymous stateid -- ensure that it doesn't * conflict with an outstanding delegation. */ if (state_deleg_conflict(obj, true)) { *status = NFS4ERR_DELAY; goto out; } } /* Same permissions as required for a WRITE */ fsal_status = obj->obj_ops->test_access(obj, FSAL_WRITE_ACCESS, NULL, NULL, true); if (FSAL_IS_ERROR(fsal_status)) { *status = nfs4_Errno_status(fsal_status); goto out; } /* Get the characteristics of the I/O to be made */ if (MaxOffsetWrite < UINT64_MAX) { LogFullDebug(COMPONENT_NFS_V4, "Write offset=%" PRIu64 " count=%" PRIu64 " MaxOffSet=%" PRIu64, offset, size, MaxOffsetWrite); if ((offset + size) > MaxOffsetWrite) { LogEvent( COMPONENT_NFS_V4, "A client tried to violate max file size %" PRIu64 " for exportid #%hu", MaxOffsetWrite, op_ctx->ctx_export->export_id); *status = NFS4ERR_FBIG; goto out; } } LogFullDebug(COMPONENT_NFS_V4, "offset = %" PRIu64 " length = %" PRIu64 " allocate = %d", offset, size, allocate); /* if size == 0 , nothing changes -- just say success */ if (size == 0) { *status = NFS4_OK; goto out; } /* Do the actual fallocate */ fsal_status = obj->obj_ops->fallocate(obj, state, offset, size, allocate); if (FSAL_IS_ERROR(fsal_status)) *status = nfs4_Errno_status(fsal_status); out: if (state != NULL) dec_state_t_ref(state); return nfsstat4_to_nfs_req_result(*status); } /* nfs4_op_allocate */ /** * @brief The NFS4_OP_ALLOCATE operation * This functions handles the NFS4_OP_ALLOCATE operation in NFSv4.2. This * function can be called only from nfs4_Compound. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * */ enum nfs_req_result nfs4_op_allocate(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { ALLOCATE4args *const arg_ALLOCATE4 = &op->nfs_argop4_u.opallocate; ALLOCATE4res *const res_ALLOCATE4 = &resp->nfs_resop4_u.opallocate; resp->resop = NFS4_OP_ALLOCATE; return allocate_deallocate(data, &arg_ALLOCATE4->aa_stateid, arg_ALLOCATE4->aa_offset, arg_ALLOCATE4->aa_length, true, &res_ALLOCATE4->ar_status); } /** * @brief The NFS4_OP_DEALLOCATE * This functions handles the NFS4_OP_DEALLOCATE operation in NFSv4.2. This * function can be called only from nfs4_Compound. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * */ enum nfs_req_result nfs4_op_deallocate(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { DEALLOCATE4args *const arg_DEALLOC = &op->nfs_argop4_u.opdeallocate; DEALLOCATE4res *const res_DEALLOC = &resp->nfs_resop4_u.opdeallocate; resp->resop = NFS4_OP_DEALLOCATE; return allocate_deallocate(data, &arg_DEALLOC->da_stateid, arg_DEALLOC->da_offset, arg_DEALLOC->da_length, false, &res_DEALLOC->dr_status); } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_bind_conn.c000066400000000000000000000202401473756622300225210ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2017 Red Hat, Inc. * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_bind_conn.c * @brief Routines used for managing the NFS4_OP_BIND_CONN_TO_SESSION operation */ #include "config.h" #include "fsal.h" #include "sal_functions.h" #include "nfs_rpc_callback.h" #include "nfs_convert.h" #include "nfs_proto_functions.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief Bind connection to the session's backchannel */ static nfsstat4 bind_conn_to_session_backchannel(SVCXPRT *rq_xprt, nfs41_session_t *session) { char session_str[NFS4_SESSIONID_BUFFER_SIZE] = "\0"; struct display_buffer db_session = { sizeof(session_str), session_str, session_str }; char xprt_addr_str[SOCK_NAME_MAX] = "\0"; struct display_buffer db_xprt = { sizeof(xprt_addr_str), xprt_addr_str, xprt_addr_str }; display_session_id(&db_session, session->session_id); display_xprt_sockaddr(&db_xprt, rq_xprt); LogInfo(COMPONENT_SESSIONS, "Set up session: %s backchannel and bind it to current xprt FD: %d socket-address: %s", session_str, rq_xprt->xp_fd, xprt_addr_str); /* For state-protection other than SP4_NONE, there needs to be further * validation (RFC 5661 section-2.10.8.3) before backchannel setup. * Since Ganesha supports only SP4_NONE as of now, we skip processing * of other mechanisms. */ if (session->clientid_record->cid_state_protect_how == SP4_NONE) { int rc; LogInfo(COMPONENT_SESSIONS, "Creating backchannel for session: %s", session_str); /* Create backchannel */ rc = nfs_rpc_create_chan_v41( rq_xprt, session, session->cb_sec_parms.sec_parms_len, session->cb_sec_parms.sec_parms_val); if (unlikely(rc == EINVAL || rc == EPERM)) return NFS4ERR_INVAL; if (unlikely(rc != 0)) return NFS4ERR_SERVERFAULT; LogInfo(COMPONENT_SESSIONS, "Created backchannel for session: %s", session_str); return NFS4_OK; } /* We always set SP4_NONE during client-record creation */ LogFatal( COMPONENT_SESSIONS, "Only SP4_NONE state protection is supported. Code flow should not reach here"); return NFS4ERR_SERVERFAULT; } /** * @brief the NFS4_OP_BIND_CONN_TO_SESSION operation * * @param[in] op nfs4_op arguments * @param[in,out] data Compound request's data * @param[out] resp nfs4_op results * * @return per RFC5661, p. 492 * * @see nfs4_Compound * */ enum nfs_req_result nfs4_op_bind_conn(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { BIND_CONN_TO_SESSION4args *const arg_BIND_CONN_TO_SESSION4 = &op->nfs_argop4_u.opbind_conn_to_session; GSH_AUTO_TRACEPOINT( nfs4, op_bind_conn_start, TRACE_INFO, "BIND_CONN_TO_SESSION arg: session={} dir={} rdma={}", TP_SESSION(arg_BIND_CONN_TO_SESSION4->bctsa_sessid), arg_BIND_CONN_TO_SESSION4->bctsa_dir, arg_BIND_CONN_TO_SESSION4->bctsa_use_conn_in_rdma_mode); BIND_CONN_TO_SESSION4res *const res_BIND_CONN_TO_SESSION4 = &resp->nfs_resop4_u.opbind_conn_to_session; BIND_CONN_TO_SESSION4resok *const resok_BIND_CONN_TO_SESSION4 = &res_BIND_CONN_TO_SESSION4->BIND_CONN_TO_SESSION4res_u .bctsr_resok4; nfs41_session_t *session; channel_dir_from_client4 client_channel_dir; channel_dir_from_server4 server_channel_dir; nfsstat4 bind_to_backchannel; bool added_conn_to_session; resp->resop = NFS4_OP_BIND_CONN_TO_SESSION; res_BIND_CONN_TO_SESSION4->bctsr_status = NFS4_OK; if (data->minorversion == 0) { res_BIND_CONN_TO_SESSION4->bctsr_status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } if (!nfs41_Session_Get_Pointer(arg_BIND_CONN_TO_SESSION4->bctsa_sessid, &session)) { res_BIND_CONN_TO_SESSION4->bctsr_status = NFS4ERR_BADSESSION; LogDebugAlt(COMPONENT_SESSIONS, COMPONENT_CLIENTID, "BIND_CONN_TO_SESSION returning status %s", nfsstat4_to_str( res_BIND_CONN_TO_SESSION4->bctsr_status)); return NFS_REQ_ERROR; } /* session->refcount +1 */ LogDebug(COMPONENT_SESSIONS, "BIND_CONN_TO_SESSION session=%p", session); /* Check if lease is expired and reserve it */ if (!reserve_lease_or_expire(session->clientid_record, false, NULL)) { dec_session_ref(session); res_BIND_CONN_TO_SESSION4->bctsr_status = NFS4ERR_EXPIRED; LogDebugAlt(COMPONENT_SESSIONS, COMPONENT_CLIENTID, "BIND_CONN_TO_SESSION returning status %s", nfsstat4_to_str( res_BIND_CONN_TO_SESSION4->bctsr_status)); return NFS_REQ_ERROR; } data->preserved_clientid = session->clientid_record; /* Keep memory of the session in the COMPOUND's data and indicate no * slot in use. We assume the server will never support UINT32_MAX + 1 * slots... */ data->session = session; data->slotid = UINT32_MAX; /* Check and bind the connection to session */ added_conn_to_session = check_session_conn(session, data, true); if (!added_conn_to_session) { LogWarn(COMPONENT_SESSIONS, "Unable to add connection to the session"); res_BIND_CONN_TO_SESSION4->bctsr_status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } memcpy(resok_BIND_CONN_TO_SESSION4->bctsr_sessid, arg_BIND_CONN_TO_SESSION4->bctsa_sessid, sizeof(resok_BIND_CONN_TO_SESSION4->bctsr_sessid)); client_channel_dir = arg_BIND_CONN_TO_SESSION4->bctsa_dir; switch (client_channel_dir) { case CDFC4_FORE: server_channel_dir = CDFS4_FORE; break; case CDFC4_BACK: case CDFC4_FORE_OR_BOTH: case CDFC4_BACK_OR_BOTH: bind_to_backchannel = bind_conn_to_session_backchannel( data->req->rq_xprt, session); if (bind_to_backchannel != NFS4_OK) { if (client_channel_dir == CDFC4_FORE_OR_BOTH) { /* Since it is not mandatory to bind connection * to backchannel in this scenario, we return * only successful forechannel creation. */ server_channel_dir = CDFS4_FORE; break; } LogCrit(COMPONENT_SESSIONS, "Mandatory backchannel creation failed"); res_BIND_CONN_TO_SESSION4->bctsr_status = bind_to_backchannel; return NFS_REQ_ERROR; } if (client_channel_dir == CDFC4_BACK_OR_BOTH || client_channel_dir == CDFC4_FORE_OR_BOTH) { server_channel_dir = CDFS4_BOTH; break; } server_channel_dir = CDFS4_BACK; break; } resok_BIND_CONN_TO_SESSION4->bctsr_dir = server_channel_dir; resok_BIND_CONN_TO_SESSION4->bctsr_use_conn_in_rdma_mode = arg_BIND_CONN_TO_SESSION4->bctsa_use_conn_in_rdma_mode; #if 0 if (nfs_rpc_get_chan(session->clientid_record, 0) == NULL) { res_BIND_CONN_TO_SESSION4->BIND_CONN_TO_SESSION4res_u .bctsr_resok4.bctsr_status_flags |= SEQ4_STATUS_CB_PATH_DOWN; } #endif /* If we were successful, stash the clientid in the request * context. */ op_ctx->clientid = &data->session->clientid; res_BIND_CONN_TO_SESSION4->bctsr_status = NFS4_OK; GSH_AUTO_TRACEPOINT( nfs4, op_bind_conn_end, TRACE_INFO, "BIND_CONN_TO_SESSION res: status={} session={} dir={} rdma={}", res_BIND_CONN_TO_SESSION4->bctsr_status, TP_SESSION(resok_BIND_CONN_TO_SESSION4->bctsr_sessid), resok_BIND_CONN_TO_SESSION4->bctsr_dir, resok_BIND_CONN_TO_SESSION4->bctsr_use_conn_in_rdma_mode); return NFS_REQ_OK; } /* nfs4_op_bind_conn */ /** * @brief Free memory allocated for BIND_CONN_TO_SESSION result * * This function frees any memory allocated for the result of the * NFS4_OP_BIND_CONN_TO_SESSION operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_nfs4_op_bind_conn_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_close.c000066400000000000000000000225011473756622300216770ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_close.c * * @brief Implementation of the NFS4_OP_CLOSE operation * */ #include "config.h" #include #include "log.h" #include "fsal.h" #include "nfs4.h" #include "sal_functions.h" #include "nfs_proto_tools.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /* Tag passed to state functions */ static const char *close_tag = "CLOSE"; /** * @brief Clean up the current layouts * * @note st_lock MUST be held * * @param[in] data Current compound data */ void cleanup_layouts(compound_data_t *data) { struct glist_head *glist = NULL; struct glist_head *glistn = NULL; struct state_hdl *ostate; ostate = data->current_obj->state_hdl; if (!ostate) return; /* We can't simply grab a pointer to a layout state * and free it later, since a client could have * multiple layout states (since a layout state covers * layouts of only one layout type) each marked * return_on_close. */ glist_for_each(glist, &ostate->file.list_of_states) { state_t *state = glist_entry(glist, state_t, state_list); state_owner_t *owner = get_state_owner_ref(state); if (owner == NULL) { /* Skip states that have gone stale. */ continue; } if ((state->state_type == STATE_TYPE_SHARE) && (owner->so_type == STATE_OPEN_OWNER_NFSV4) && (owner->so_owner.so_nfs4_owner.so_clientid == data->session->clientid)) { dec_state_owner_ref(owner); return; } dec_state_owner_ref(owner); } glist_for_each_safe(glist, glistn, &ostate->file.list_of_states) { state_t *state = glist_entry(glist, state_t, state_list); bool deleted = false; struct pnfs_segment entire = { .io_mode = LAYOUTIOMODE4_ANY, .offset = 0, .length = NFS4_UINT64_MAX }; state_owner_t *owner = get_state_owner_ref(state); if (owner == NULL) { /* Skip states that have gone stale. */ continue; } if ((state->state_type == STATE_TYPE_LAYOUT) && (owner->so_owner.so_nfs4_owner.so_clientrec == data->session->clientid_record) && state->state_data.layout.state_return_on_close) { nfs4_return_one_state(data->current_obj, LAYOUTRETURN4_FILE, circumstance_roc, state, entire, 0, NULL, &deleted); if (!deleted) { LogCrit(COMPONENT_PNFS, "Layout state not destroyed on last close return."); } } dec_state_owner_ref(owner); } } /** * * Brief Implementation of NFS4_OP_CLOSE * * This function implements the NFS4_OP_CLOSE * operation. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 362 */ enum nfs_req_result nfs4_op_close(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { /* Short alias for arguments */ CLOSE4args *const arg_CLOSE4 = &op->nfs_argop4_u.opclose; /* Short alias for response */ CLOSE4res *const res_CLOSE4 = &resp->nfs_resop4_u.opclose; /* Status for NFS protocol functions */ nfsstat4 nfs_status = NFS4_OK; /* The state for the open to be closed */ state_t *state_found = NULL; /* The open owner of the open state being closed */ state_owner_t *open_owner = NULL; /* Iterator over the state list */ struct glist_head *glist = NULL; /* Secondary safe iterator to continue traversal on delete */ struct glist_head *glistn = NULL; struct fsal_obj_handle *state_obj; bool ok; LogDebug(COMPONENT_STATE, "Entering NFS v4 CLOSE handler ----------------------------"); GSH_AUTO_TRACEPOINT(nfs4, op_close_start, TRACE_INFO, "CLOSE arg: seqid={} open_stateid={}", arg_CLOSE4->seqid, arg_CLOSE4->open_stateid.seqid); memset(res_CLOSE4, 0, sizeof(CLOSE4res)); resp->resop = NFS4_OP_CLOSE; res_CLOSE4->status = NFS4_OK; /* Do basic checks on a filehandle Object should be a file */ res_CLOSE4->status = nfs4_sanity_check_FH(data, REGULAR_FILE, false); if (res_CLOSE4->status != NFS4_OK) return NFS_REQ_ERROR; /* Check stateid correctness and get pointer to state */ nfs_status = nfs4_Check_Stateid(&arg_CLOSE4->open_stateid, data->current_obj, &state_found, data, data->minorversion == 0 ? STATEID_SPECIAL_FOR_CLOSE_40 : STATEID_SPECIAL_FOR_CLOSE_41, arg_CLOSE4->seqid, data->minorversion == 0, close_tag); if (nfs_status != NFS4_OK && nfs_status != NFS4ERR_REPLAY) { res_CLOSE4->status = nfs_status; LogDebug(COMPONENT_STATE, "CLOSE failed nfs4_Check_Stateid"); return NFS_REQ_ERROR; } /* We hold the state, but not its object handle. Its object * handle could be freed as soon as the state gets deleted from * the hashtable. * * If there are multiple threads trying to delete the state at * the same time, the object handle could be NULL here. * * Get a ref on the object handle and the open owner. */ ok = get_state_obj_export_owner_refs(state_found, &state_obj, NULL, &open_owner); if (!ok) { /* Assume this is a replayed close */ if (state_found) dec_state_t_ref(state_found); res_CLOSE4->status = NFS4_OK; memcpy(res_CLOSE4->CLOSE4res_u.open_stateid.other, arg_CLOSE4->open_stateid.other, OTHERSIZE); res_CLOSE4->CLOSE4res_u.open_stateid.seqid = arg_CLOSE4->open_stateid.seqid + 1; if (res_CLOSE4->CLOSE4res_u.open_stateid.seqid == 0) res_CLOSE4->CLOSE4res_u.open_stateid.seqid = 1; LogDebug( COMPONENT_STATE, "CLOSE failed nfs4_Check_Stateid must have already been closed. But treating it as replayed close and returning NFS4_OK"); return NFS_REQ_OK; } PTHREAD_MUTEX_lock(&open_owner->so_mutex); /* Check seqid */ if (data->minorversion == 0) { if (!Check_nfs4_seqid(open_owner, arg_CLOSE4->seqid, op, state_obj, resp, close_tag)) { /* Response is all setup for us and LogDebug * told what was wrong */ PTHREAD_MUTEX_unlock(&open_owner->so_mutex); goto out2; } } PTHREAD_MUTEX_unlock(&open_owner->so_mutex); STATELOCK_lock(state_obj); /* Clean all associated lock states */ glist_for_each_safe(glist, glistn, &state_found->state_data.share.share_lockstates) { state_t *lock_state = glist_entry( glist, state_t, state_data.lock.state_sharelist); inc_state_t_ref(lock_state); state_unlock_all(state_obj, lock_state); state_del_locked(lock_state); dec_state_t_ref(lock_state); } if (data->minorversion == 0) { /* Handle stateid/seqid for success for v4.0 */ update_stateid(state_found, &res_CLOSE4->CLOSE4res_u.open_stateid, data, close_tag); } else { /* In NFS V4.1 and later, the server SHOULD return a special * invalid stateid to prevent re-use of a now closed stateid. */ memcpy(&res_CLOSE4->CLOSE4res_u.open_stateid.other, all_zero, sizeof(res_CLOSE4->CLOSE4res_u.open_stateid.other)); res_CLOSE4->CLOSE4res_u.open_stateid.seqid = UINT32_MAX; } /* File is closed, release the corresponding state. If the FSAL * supports extended ops, this will result in closing any open files * the FSAL has for this state. */ state_del_locked(state_found); /* Poison the current stateid */ data->current_stateid_valid = false; if (data->minorversion > 0) cleanup_layouts(data); if (data->minorversion == 0) op_ctx->clientid = NULL; STATELOCK_unlock(state_obj); res_CLOSE4->status = NFS4_OK; if (isFullDebug(COMPONENT_STATE) && isFullDebug(COMPONENT_MEMLEAKS)) { nfs_State_PrintAll(); nfs4_owner_PrintAll(); } /* Save the response in the open owner */ if (data->minorversion == 0) { Copy_nfs4_state_req(open_owner, arg_CLOSE4->seqid, op, state_obj, resp, close_tag); } out2: dec_state_owner_ref(open_owner); state_obj->obj_ops->put_ref(state_obj); dec_state_t_ref(state_found); GSH_AUTO_TRACEPOINT(nfs4, op_close_end, TRACE_INFO, "CLOSE arg: status={} open_stateid={}", res_CLOSE4->status, res_CLOSE4->CLOSE4res_u.open_stateid.seqid); return nfsstat4_to_nfs_req_result(res_CLOSE4->status); } /* nfs4_op_close */ /** * @brief Free memory allocated for CLOSE result * * This function frees any memory allocated for the result of the * NFS4_OP_CLOSE operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_close_Free(nfs_resop4 *resp) { /* Nothing to be done */ } void nfs4_op_close_CopyRes(CLOSE4res *res_dst, CLOSE4res *res_src) { /* Nothing to deep copy */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_commit.c000066400000000000000000000110221473756622300220560ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_commit.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * * */ #include "config.h" #include #include #include #include #include #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include "nfs_file_handle.h" #include "fsal_pnfs.h" static enum nfs_req_result op_dscommit(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp); /** * @brief Implementation of NFS4_OP_COMMIT * * This function implemtats NFS4_OP_COMMIT. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661 p. 362-3 * */ enum nfs_req_result nfs4_op_commit(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { COMMIT4args *const arg_COMMIT4 = &op->nfs_argop4_u.opcommit; COMMIT4res *const res_COMMIT4 = &resp->nfs_resop4_u.opcommit; fsal_status_t fsal_status = { 0, 0 }; struct gsh_buffdesc verf_desc; resp->resop = NFS4_OP_COMMIT; res_COMMIT4->status = NFS4_OK; LogFullDebug(COMPONENT_NFS_V4, "Commit order over offset = %" PRIu64 ", size = %" PRIu32, arg_COMMIT4->offset, arg_COMMIT4->count); if ((nfs4_Is_Fh_DSHandle(&data->currentFH))) return op_dscommit(op, data, resp); /* * Do basic checks on a filehandle Commit is done only on a file */ res_COMMIT4->status = nfs4_sanity_check_FH(data, REGULAR_FILE, true); if (res_COMMIT4->status != NFS4_OK) return NFS_REQ_ERROR; fsal_status = fsal_commit(data->current_obj, arg_COMMIT4->offset, arg_COMMIT4->count); if (FSAL_IS_ERROR(fsal_status)) { res_COMMIT4->status = nfs4_Errno_status(fsal_status); return NFS_REQ_ERROR; } verf_desc.addr = &res_COMMIT4->COMMIT4res_u.resok4.writeverf; verf_desc.len = sizeof(verifier4); op_ctx->fsal_export->exp_ops.get_write_verifier(op_ctx->fsal_export, &verf_desc); LogFullDebug(COMPONENT_NFS_V4, "Commit verifier %d-%d", ((int *)verf_desc.addr)[0], ((int *)verf_desc.addr)[1]); /* If you reach this point, then an error occurred */ res_COMMIT4->status = NFS4_OK; return NFS_REQ_OK; } /* nfs4_op_commit */ /** * @brief Free memory allocated for COMMIT result * * This function frees any memory allocated for the result of the * NFS4_OP_COMMIT operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_commit_Free(nfs_resop4 *resp) { /* Nothing to be done */ } /** * * @brief Call pNFS data server commit * * This function bypasses mdcache and calls down the FSAL to * perform a data-server commit. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661 p. 362-3 * */ static enum nfs_req_result op_dscommit(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { COMMIT4args *const arg_COMMIT4 = &op->nfs_argop4_u.opcommit; COMMIT4res *const res_COMMIT4 = &resp->nfs_resop4_u.opcommit; /* Construct the FSAL file handle */ /* Call the commit operation */ res_COMMIT4->status = op_ctx->ctx_pnfs_ds->s_ops.dsh_commit( data->current_ds, arg_COMMIT4->offset, arg_COMMIT4->count, &res_COMMIT4->COMMIT4res_u.resok4.writeverf); return nfsstat4_to_nfs_req_result(res_COMMIT4->status); } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_create.c000066400000000000000000000241461473756622300220440ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_create.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * */ #include "config.h" #include #include #include #include #include #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_creds.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include "nfs_file_handle.h" #include "export_mgr.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief NFS4_OP_CREATE, creates a non-regular entry * * This function implements the NFS4_OP_CREATE operation, which * creates a non-regular entry. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 363 */ enum nfs_req_result nfs4_op_create(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { CREATE4args *const arg_CREATE4 = &op->nfs_argop4_u.opcreate; CREATE4res *const res_CREATE4 = &resp->nfs_resop4_u.opcreate; struct fsal_obj_handle *obj_parent = NULL; struct fsal_obj_handle *obj_new = NULL; struct fsal_attrlist sattr, parent_pre_attrs, parent_post_attrs; int convrc = 0; char *link_content = NULL; struct fsal_export *exp_hdl; fsal_status_t fsal_status; object_file_type_t type; bool is_parent_pre_attrs_valid, is_parent_post_attrs_valid; /* We don't print the linkdata because it might be considered Core Customer Content */ GSH_AUTO_TRACEPOINT( nfs4, op_create_start, TRACE_INFO, "CREATE arg: type={} specdata1={} specdata2={} name[{}]={}", arg_CREATE4->objtype.type, arg_CREATE4->objtype.createtype4_u.devdata.specdata1, arg_CREATE4->objtype.createtype4_u.devdata.specdata2, arg_CREATE4->objname.utf8string_len, TP_UTF8STR_TRUNCATED(arg_CREATE4->objname)); memset(&sattr, 0, sizeof(sattr)); fsal_prepare_attrs(&parent_pre_attrs, ATTR_CHANGE); fsal_prepare_attrs(&parent_post_attrs, ATTR_CHANGE); resp->resop = NFS4_OP_CREATE; res_CREATE4->status = NFS4_OK; /* Do basic checks on a filehandle */ res_CREATE4->status = nfs4_sanity_check_FH(data, DIRECTORY, false); if (res_CREATE4->status != NFS4_OK) goto out; /* if quota support is active, then we should check is the FSAL allows * inode creation or not */ exp_hdl = op_ctx->fsal_export; fsal_status = exp_hdl->exp_ops.check_quota( exp_hdl, CTX_FULLPATH(op_ctx), FSAL_QUOTA_INODES); if (FSAL_IS_ERROR(fsal_status)) { res_CREATE4->status = NFS4ERR_DQUOT; goto out; } /* Ask only for supported attributes */ if (!nfs4_Fattr_Supported(&arg_CREATE4->createattrs)) { res_CREATE4->status = NFS4ERR_ATTRNOTSUPP; goto out; } /* Do not use READ attr, use WRITE attr */ if (!nfs4_Fattr_Check_Access(&arg_CREATE4->createattrs, FATTR4_ATTR_WRITE)) { res_CREATE4->status = NFS4ERR_INVAL; goto out; } /* This operation is used to create a non-regular file, * this means: - a symbolic link * - a block device file * - a character device file * - a socket file * - a fifo * - a directory * * You can't use this operation to create a regular file, * you have to use NFS4_OP_OPEN for this */ /* Validate and convert the UFT8 objname to a regular string */ res_CREATE4->status = nfs4_utf8string_scan(&arg_CREATE4->objname, UTF8_SCAN_PATH_COMP); if (res_CREATE4->status != NFS4_OK) goto out; /* Convert current FH into a obj, the current_obj (associated with the current FH will be used for this */ obj_parent = data->current_obj; /* The currentFH must point to a directory * (objects are always created within a directory) */ if (data->current_filetype != DIRECTORY) { res_CREATE4->status = NFS4ERR_NOTDIR; goto out; } res_CREATE4->CREATE4res_u.resok4.cinfo.before = fsal_get_changeid4(obj_parent); /* Convert the incoming fattr4 to a vattr structure, * if such arguments are supplied */ if (arg_CREATE4->createattrs.attrmask.bitmap4_len != 0) { /* Arguments were supplied, extract them */ convrc = nfs4_Fattr_To_FSAL_attr( &sattr, &arg_CREATE4->createattrs, data); if (convrc != NFS4_OK) { res_CREATE4->status = convrc; goto out; } } /* Create either a symbolic link or a directory */ switch (arg_CREATE4->objtype.type) { case NF4LNK: /* Validate the symbolic link length, specifically do NOT * validate the content (per RFC 7530 Section 12.4) */ type = SYMBOLIC_LINK; res_CREATE4->status = nfs4_utf8string_scan( &arg_CREATE4->objtype.createtype4_u.linkdata, UTF8_SCAN_PATH); if (res_CREATE4->status != NFS4_OK) goto out; link_content = arg_CREATE4->objtype.createtype4_u.linkdata .utf8string_val; break; case NF4DIR: /* Create a new directory */ type = DIRECTORY; break; case NF4SOCK: /* Create a new socket file */ type = SOCKET_FILE; break; case NF4FIFO: /* Create a new socket file */ type = FIFO_FILE; break; case NF4CHR: /* Create a new socket file */ type = CHARACTER_FILE; sattr.rawdev.major = arg_CREATE4->objtype.createtype4_u.devdata.specdata1; sattr.rawdev.minor = arg_CREATE4->objtype.createtype4_u.devdata.specdata2; sattr.valid_mask |= ATTR_RAWDEV; break; case NF4BLK: /* Create a new socket file */ type = BLOCK_FILE; sattr.rawdev.major = arg_CREATE4->objtype.createtype4_u.devdata.specdata1; sattr.rawdev.minor = arg_CREATE4->objtype.createtype4_u.devdata.specdata2; sattr.valid_mask |= ATTR_RAWDEV; break; default: /* Should never happen, but return NFS4ERR_BADTYPE *in this case */ res_CREATE4->status = NFS4ERR_BADTYPE; goto out; } /* switch( arg_CREATE4.objtype.type ) */ if (!(sattr.valid_mask & ATTR_MODE)) { /* Make sure mode is set. */ if (type == DIRECTORY) sattr.mode = 0700; else sattr.mode = 0600; sattr.valid_mask |= ATTR_MODE; } fsal_status = fsal_create(obj_parent, arg_CREATE4->objname.utf8string_val, type, &sattr, link_content, &obj_new, NULL, &parent_pre_attrs, &parent_post_attrs); /* Release the attributes (may release an inherited ACL) */ fsal_release_attrs(&sattr); if (FSAL_IS_ERROR(fsal_status)) { res_CREATE4->status = nfs4_Errno_status(fsal_status); goto out; } /* Building the new file handle to replace the current FH */ if (!nfs4_FSALToFhandle(false, &data->currentFH, obj_new, op_ctx->ctx_export)) { res_CREATE4->status = NFS4ERR_SERVERFAULT; goto out; } /* Mark current_stateid as invalid */ data->current_stateid_valid = false; /* Set the mode if requested */ /* Use the same fattr mask for reply, if one attribute was not settable, NFS4ERR_ATTRNOTSUPP was replyied */ res_CREATE4->CREATE4res_u.resok4.attrset.bitmap4_len = arg_CREATE4->createattrs.attrmask.bitmap4_len; if (arg_CREATE4->createattrs.attrmask.bitmap4_len != 0) { /* copy over bitmap */ res_CREATE4->CREATE4res_u.resok4.attrset = arg_CREATE4->createattrs.attrmask; } memset(&res_CREATE4->CREATE4res_u.resok4.cinfo.after, 0, sizeof(changeid4)); is_parent_pre_attrs_valid = FSAL_TEST_MASK(parent_pre_attrs.valid_mask, ATTR_CHANGE); if (is_parent_pre_attrs_valid) { res_CREATE4->CREATE4res_u.resok4.cinfo.before = (changeid4)parent_pre_attrs.change; } is_parent_post_attrs_valid = FSAL_TEST_MASK(parent_post_attrs.valid_mask, ATTR_CHANGE); if (is_parent_post_attrs_valid) { res_CREATE4->CREATE4res_u.resok4.cinfo.after = (changeid4)parent_post_attrs.change; } else { res_CREATE4->CREATE4res_u.resok4.cinfo.after = fsal_get_changeid4(obj_parent); } res_CREATE4->CREATE4res_u.resok4.cinfo.atomic = is_parent_pre_attrs_valid && is_parent_post_attrs_valid ? TRUE : FALSE; LogFullDebug(COMPONENT_NFS_V4, "CREATE CINFO before = %" PRIu64 " after = %" PRIu64 " atomic = %d", res_CREATE4->CREATE4res_u.resok4.cinfo.before, res_CREATE4->CREATE4res_u.resok4.cinfo.after, res_CREATE4->CREATE4res_u.resok4.cinfo.atomic); /* @todo : BUGAZOMEU: faire le free dans cette fonction */ /* Keep the vnode entry for the file in the compound data */ set_current_entry(data, obj_new); /* If you reach this point, then no error occurred */ res_CREATE4->status = NFS4_OK; out: fsal_release_attrs(&parent_pre_attrs); fsal_release_attrs(&parent_post_attrs); if (obj_new) { /* Put our ref */ obj_new->obj_ops->put_ref(obj_new); } GSH_AUTO_TRACEPOINT( nfs4, op_create_end, TRACE_INFO, "CREATE res: status={} before={} after={} atomic={}", res_CREATE4->status, res_CREATE4->CREATE4res_u.resok4.cinfo.before, res_CREATE4->CREATE4res_u.resok4.cinfo.after, res_CREATE4->CREATE4res_u.resok4.cinfo.atomic); return nfsstat4_to_nfs_req_result(res_CREATE4->status); } /* nfs4_op_create */ /** * @brief Free memory allocated for CREATE result * * This function frees any memory allocated for the result of the * NFS4_OP_CREATE operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_create_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_create_session.c000066400000000000000000000537261473756622300236150ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_create_session.c * @brief Routines used for managing the NFS4_OP_CREATE_SESSION operation. */ #include "config.h" #include #include #include "log.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_rpc_callback.h" #include "nfs_proto_functions.h" #include "sal_functions.h" #include "nfs_creds.h" #include "client_mgr.h" #include "fsal.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif #define log_channel_attributes(component, chan_attrs, name) \ LogFullDebug(component, \ "%s attributes ca_headerpadsize %" PRIu32 \ " ca_maxrequestsize %" PRIu32 \ " ca_maxresponsesize %" PRIu32 \ " ca_maxresponsesize_cached %" PRIu32 \ " ca_maxoperations %" PRIu32 " ca_maxrequests %" PRIu32, \ name, (chan_attrs)->ca_headerpadsize, \ (chan_attrs)->ca_maxrequestsize, \ (chan_attrs)->ca_maxresponsesize, \ (chan_attrs)->ca_maxresponsesize_cached, \ (chan_attrs)->ca_maxoperations, \ (chan_attrs)->ca_maxrequests) /** * @brief Populate nfs41_session with callback params */ static void populate_callback_params_in_session( uint32_t sec_parms_len, const callback_sec_parms4 *sec_parms_val, uint32_t cb_program, nfs41_session_t *nfs41_session, log_components_t component) { int sp_itr; callback_sec_parms4 *const extracted_sec_params = gsh_malloc(sec_parms_len * sizeof(callback_sec_parms4)); for (sp_itr = 0; sp_itr < sec_parms_len; ++sp_itr) { const callback_sec_parms4 input_sp = sec_parms_val[sp_itr]; callback_sec_parms4 *const curr_sp = &extracted_sec_params[sp_itr]; curr_sp->cb_secflavor = input_sp.cb_secflavor; if (curr_sp->cb_secflavor == AUTH_NONE) { /* Do nothing */ } else if (curr_sp->cb_secflavor == AUTH_SYS) { int gids_itr; size_t machname_len; struct authunix_parms curr_cb_sys_creds; struct authunix_parms input_cb_sys_creds = input_sp.callback_sec_parms4_u.cbsp_sys_cred; curr_cb_sys_creds.aup_uid = input_cb_sys_creds.aup_uid; curr_cb_sys_creds.aup_gid = input_cb_sys_creds.aup_gid; curr_cb_sys_creds.aup_time = input_cb_sys_creds.aup_time; /* Populate aup_machname */ machname_len = strnlen(input_cb_sys_creds.aup_machname, MAX_MACHINE_NAME); curr_cb_sys_creds.aup_machname = gsh_malloc(machname_len + 1); memcpy(curr_cb_sys_creds.aup_machname, input_cb_sys_creds.aup_machname, machname_len); curr_cb_sys_creds.aup_machname[machname_len] = '\0'; curr_cb_sys_creds.aup_len = input_cb_sys_creds.aup_len; curr_cb_sys_creds.aup_gids = gsh_malloc( curr_cb_sys_creds.aup_len * sizeof(gid_t)); for (gids_itr = 0; gids_itr < input_cb_sys_creds.aup_len; ++gids_itr) { curr_cb_sys_creds.aup_gids[gids_itr] = input_cb_sys_creds.aup_gids[gids_itr]; } curr_sp->callback_sec_parms4_u.cbsp_sys_cred = curr_cb_sys_creds; #ifdef _HAVE_GSSAPI } else if (curr_sp->cb_secflavor == RPCSEC_GSS) { LogWarn(component, "We do not support GSS callbacks, skip GSS callback setup"); #endif } } nfs41_session->cb_sec_parms.sec_parms_val = extracted_sec_params; nfs41_session->cb_sec_parms.sec_parms_len = sec_parms_len; nfs41_session->cb_program = cb_program; } /** * * @brief The NFS4_OP_CREATE_SESSION operation * * @param[in] op nfs4_op arguments * @param[in,out] data Compound request's data * @param[out] resp nfs4_op results * * @return Values as per RFC5661 p. 363 * * @see nfs4_Compound * */ enum nfs_req_result nfs4_op_create_session(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { /* Result of looking up the clientid in the confirmed ID table */ nfs_client_id_t *conf = NULL; /* XXX these are not good names */ /* Result of looking up the clientid in the unconfirmed ID table */ nfs_client_id_t *unconf = NULL; /* The found clientid (either one of the preceding) */ nfs_client_id_t *found = NULL; /* The found client record */ nfs_client_record_t *client_record; /* The created session */ nfs41_session_t *nfs41_session = NULL; /* Client supplied clientid */ clientid4 clientid = 0; /* The client address as a string, for gratuitous logging */ const char *str_client_addr = "(unknown)"; /* The client name, for gratuitous logging */ char str_client[CLIENTNAME_BUFSIZE]; /* Display buffer for client name */ struct display_buffer dspbuf_client = { sizeof(str_client), str_client, str_client }; /* The clientid4 broken down into fields */ char str_clientid4[DISPLAY_CLIENTID_SIZE]; /* Display buffer for clientid4 */ struct display_buffer dspbuf_clientid4 = { sizeof(str_clientid4), str_clientid4, str_clientid4 }; /* Return code from clientid calls */ int i, rc = 0; /* Component for logging */ log_components_t component = COMPONENT_CLIENTID; /* Abbreviated alias for arguments */ CREATE_SESSION4args *const arg_CREATE_SESSION4 = &op->nfs_argop4_u.opcreate_session; /* Abbreviated alias for response */ CREATE_SESSION4res *const res_CREATE_SESSION4 = &resp->nfs_resop4_u.opcreate_session; /* Abbreviated alias for successful response */ CREATE_SESSION4resok *const res_CREATE_SESSION4ok = &res_CREATE_SESSION4->CREATE_SESSION4res_u.csr_resok4; bool added_conn_to_session; /* Make sure str_client is always printable even * if log level changes midstream. */ display_printf(&dspbuf_client, "(unknown)"); display_reset_buffer(&dspbuf_client); if (op_ctx->client != NULL) str_client_addr = op_ctx->client->hostaddr_str; if (isDebug(COMPONENT_SESSIONS)) component = COMPONENT_SESSIONS; resp->resop = NFS4_OP_CREATE_SESSION; res_CREATE_SESSION4->csr_status = NFS4_OK; clientid = arg_CREATE_SESSION4->csa_clientid; display_clientid(&dspbuf_clientid4, clientid); if (data->minorversion == 0) { res_CREATE_SESSION4->csr_status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } LogInfo(component, "CREATE_SESSION client addr=%s clientid=%s -------------------", str_client_addr, str_clientid4); GSH_AUTO_TRACEPOINT( nfs4, op_create_session_start, TRACE_INFO, "CREATE_SESSION arg: clientid={} sequence={} flags={} cb_program={} sec_parms_len={}", arg_CREATE_SESSION4->csa_clientid, arg_CREATE_SESSION4->csa_sequence, arg_CREATE_SESSION4->csa_flags, arg_CREATE_SESSION4->csa_cb_program, arg_CREATE_SESSION4->csa_sec_parms.csa_sec_parms_len); /* First try to look up unconfirmed record */ rc = nfs_client_id_get_unconfirmed(clientid, &unconf); if (rc == CLIENT_ID_SUCCESS) { client_record = unconf->cid_client_record; found = unconf; } else { rc = nfs_client_id_get_confirmed(clientid, &conf); if (rc != CLIENT_ID_SUCCESS) { /* No record whatsoever of this clientid */ LogDebug(component, "%s clientid=%s", clientid_error_to_str(rc), str_clientid4); if (rc == CLIENT_ID_EXPIRED) rc = CLIENT_ID_STALE; res_CREATE_SESSION4->csr_status = clientid_error_to_nfsstat_no_expire(rc); return NFS_REQ_ERROR; } client_record = conf->cid_client_record; found = conf; } PTHREAD_MUTEX_lock(&client_record->cr_mutex); inc_client_record_ref(client_record); if (isInfo(component)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_record(&dspbuf, client_record); LogInfo(component, "Client Record %s cr_confirmed_rec=%p cr_unconfirmed_rec=%p", str, client_record->cr_confirmed_rec, client_record->cr_unconfirmed_rec); } /* At this point one and only one of conf and unconf is * non-NULL, and found also references the single clientid * record that was found. */ LogDebug(component, "CREATE_SESSION clientid=%s csa_sequence=%" PRIu32 " clientid_cs_seq=%" PRIu32 " data_oppos=%d", str_clientid4, arg_CREATE_SESSION4->csa_sequence, found->cid_create_session_sequence, data->oppos); if (isFullDebug(component)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, found); LogFullDebug(component, "Found %s", str); } if ((arg_CREATE_SESSION4->csa_sequence + 1) == found->cid_create_session_sequence) { *res_CREATE_SESSION4 = found->cid_create_session_slot; LogDebug( component, "CREATE_SESSION special replay case, used response in cid_create_session_slot"); goto out; } else if (arg_CREATE_SESSION4->csa_sequence != found->cid_create_session_sequence) { res_CREATE_SESSION4->csr_status = NFS4ERR_SEQ_MISORDERED; LogDebug(component, "CREATE_SESSION returning NFS4ERR_SEQ_MISORDERED"); goto out; } if (unconf != NULL) { /* First must match principal */ if (!nfs_compare_clientcred(&unconf->cid_credential, &data->credential)) { if (isDebug(component)) { char *unconfirmed_addr = "(unknown)"; if (unconf->gsh_client != NULL) unconfirmed_addr = unconf->gsh_client->hostaddr_str; LogDebug( component, "Unconfirmed ClientId %s->'%s': Principals do not match... unconfirmed addr=%s Return NFS4ERR_CLID_INUSE", str_clientid4, str_client_addr, unconfirmed_addr); } res_CREATE_SESSION4->csr_status = NFS4ERR_CLID_INUSE; goto out; } } if (conf != NULL) { if (isDebug(component) && conf != NULL) display_clientid_name(&dspbuf_client, conf); /* First must match principal */ if (!nfs_compare_clientcred(&conf->cid_credential, &data->credential)) { if (isDebug(component)) { char *confirmed_addr = "(unknown)"; if (conf->gsh_client != NULL) confirmed_addr = conf->gsh_client->hostaddr_str; LogDebug( component, "Confirmed ClientId %s->%s addr=%s: Principals do not match... confirmed addr=%s Return NFS4ERR_CLID_INUSE", str_clientid4, str_client, str_client_addr, confirmed_addr); } res_CREATE_SESSION4->csr_status = NFS4ERR_CLID_INUSE; goto out; } /* In this case, the record was confirmed proceed with CREATE_SESSION */ } /* We don't need to do any further principal checks, we can't * have a confirmed clientid record with a different principal * than the unconfirmed record. */ /* At this point, we need to try and create the session before * we modify the confirmed and/or unconfirmed clientid * records. */ /* Check flags value (test CSESS15) */ if (arg_CREATE_SESSION4->csa_flags & ~(CREATE_SESSION4_FLAG_PERSIST | CREATE_SESSION4_FLAG_CONN_BACK_CHAN | CREATE_SESSION4_FLAG_CONN_RDMA)) { LogDebug(component, "Invalid create session flags %" PRIu32, arg_CREATE_SESSION4->csa_flags); res_CREATE_SESSION4->csr_status = NFS4ERR_INVAL; goto out; } log_channel_attributes(component, &arg_CREATE_SESSION4->csa_fore_chan_attrs, "Fore Channel"); log_channel_attributes(component, &arg_CREATE_SESSION4->csa_back_chan_attrs, "Back Channel"); /* Let's verify the channel attributes for the session first. */ if (arg_CREATE_SESSION4->csa_fore_chan_attrs.ca_maxrequestsize < NFS41_MIN_REQUEST_SIZE || arg_CREATE_SESSION4->csa_fore_chan_attrs.ca_maxresponsesize < NFS41_MIN_RESPONSE_SIZE || arg_CREATE_SESSION4->csa_fore_chan_attrs.ca_maxoperations < NFS41_MIN_OPERATIONS || arg_CREATE_SESSION4->csa_fore_chan_attrs.ca_maxrequests == 0 || arg_CREATE_SESSION4->csa_back_chan_attrs.ca_maxrequestsize < NFS41_MIN_REQUEST_SIZE || arg_CREATE_SESSION4->csa_back_chan_attrs.ca_maxresponsesize < NFS41_MIN_RESPONSE_SIZE || arg_CREATE_SESSION4->csa_back_chan_attrs.ca_maxoperations < NFS41_MIN_OPERATIONS || arg_CREATE_SESSION4->csa_back_chan_attrs.ca_maxrequests == 0) { LogWarnLimited(component, "Invalid channel attributes for %s", data->tagname); res_CREATE_SESSION4->csr_status = NFS4ERR_TOOSMALL; goto out; } /* Record session related information at the right place */ nfs41_session = pool_alloc(nfs41_session_pool); if (nfs41_session == NULL) { LogCrit(component, "Could not allocate memory for a session"); res_CREATE_SESSION4->csr_status = NFS4ERR_SERVERFAULT; goto out; } nfs41_session->clientid = clientid; nfs41_session->clientid_record = found; nfs41_session->refcount = 2; /* sentinel ref + call path ref */ nfs41_session->fore_channel_attrs = arg_CREATE_SESSION4->csa_fore_chan_attrs; nfs41_session->back_channel_attrs = arg_CREATE_SESSION4->csa_back_chan_attrs; nfs41_session->flags = false; nfs41_session->cb_program = 0; PTHREAD_MUTEX_init(&nfs41_session->cb_mutex, NULL); PTHREAD_COND_init(&nfs41_session->cb_cond, NULL); PTHREAD_RWLOCK_init(&nfs41_session->conn_lock, NULL); PTHREAD_MUTEX_init(&nfs41_session->cb_chan.chan_mtx, NULL); nfs41_session->nb_slots = MIN(nfs_param.nfsv4_param.nb_slots, nfs41_session->fore_channel_attrs.ca_maxrequests); nfs41_session->fc_slots = gsh_calloc(nfs41_session->nb_slots, sizeof(nfs41_session_slot_t)); nfs41_session->bc_slots = gsh_calloc(nfs41_session->nb_slots, sizeof(nfs41_cb_session_slot_t)); for (i = 0; i < nfs41_session->nb_slots; i++) PTHREAD_MUTEX_init(&nfs41_session->fc_slots[i].slot_lock, NULL); /* Take reference to clientid record on behalf the session. */ inc_client_id_ref(found); /* add to head of session list (encapsulate?) */ PTHREAD_MUTEX_lock(&found->cid_mutex); glist_add(&found->cid_cb.v41.cb_session_list, &nfs41_session->session_link); PTHREAD_MUTEX_unlock(&found->cid_mutex); /* Set ca_maxrequests */ nfs41_session->fore_channel_attrs.ca_maxrequests = nfs41_session->nb_slots; nfs41_Build_sessionid(&clientid, nfs41_session->session_id); res_CREATE_SESSION4ok->csr_sequence = arg_CREATE_SESSION4->csa_sequence; /* return the input for wanting of something better (will * change in later versions) */ res_CREATE_SESSION4ok->csr_fore_chan_attrs = nfs41_session->fore_channel_attrs; res_CREATE_SESSION4ok->csr_back_chan_attrs = nfs41_session->back_channel_attrs; res_CREATE_SESSION4ok->csr_flags = 0; memcpy(res_CREATE_SESSION4ok->csr_sessionid, nfs41_session->session_id, NFS4_SESSIONID_SIZE); GSH_AUTO_TRACEPOINT(nfs4, session_create, TRACE_INFO, "Create session. Session: {}, refcount: 2", nfs41_session); if (!nfs41_Session_Set(nfs41_session)) { LogDebug(component, "Could not insert session into table"); /* Release the sentinel session resource (our reference will * be dropped on exit. */ dec_session_ref(nfs41_session); /* Maybe a more precise status would be better */ res_CREATE_SESSION4->csr_status = NFS4ERR_SERVERFAULT; goto out; } nfs41_session->num_conn = 0; glist_init(&nfs41_session->connection_xprts); /* Add the connection to the session */ added_conn_to_session = check_session_conn(nfs41_session, data, true); if (!added_conn_to_session) { LogCrit(component, "Unable to add connection FD: %d to the session", data->req->rq_xprt->xp_fd); /* Need to destroy the session */ if (!nfs41_Session_Del(nfs41_session)) LogDebug(component, "nfs41_Session_Del failed during cleanup"); res_CREATE_SESSION4->csr_status = NFS4ERR_INVAL; goto out; } /* Make sure we have a reference to the confirmed clientid record if any */ if (conf == NULL) { conf = client_record->cr_confirmed_rec; if (isDebug(component) && conf != NULL) display_clientid_name(&dspbuf_client, conf); /* Need a reference to the confirmed record for below */ if (conf != NULL) { /* This is the only point at which we have BOTH an * unconfirmed AND confirmed record. found MUST be * the unconfirmed record. */ inc_client_id_ref(conf); } } if (conf != NULL && conf->cid_clientid != clientid) { /* Old confirmed record - need to expire it */ if (isDebug(component)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, conf); LogDebug(component, "Expiring %s", str); } /* Expire clientid and release our reference. * NOTE: found MUST NOT BE conf (otherwise clientid would have * matched). */ nfs_client_id_expire(conf, false, true); dec_client_id_ref(conf); conf = NULL; } if (conf != NULL) { /* At this point we are updating the confirmed * clientid. Update the confirmed record from the * unconfirmed record. */ display_clientid(&dspbuf_clientid4, conf->cid_clientid); LogDebug(component, "Updating clientid %s->%s cb_program=%u", str_clientid4, str_client, arg_CREATE_SESSION4->csa_cb_program); if (unconf != NULL) { /* Deal with the ONLY situation where we have both a * confirmed and unconfirmed record by unhashing * the unconfirmed clientid record */ remove_unconfirmed_client_id(unconf); /* Release our reference to the unconfirmed entry */ dec_client_id_ref(unconf); /* And now to keep code simple, set found to the * confirmed record. */ found = conf; } if (isDebug(component)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, conf); LogDebug(component, "Updated %s", str); } } else { /* This is a new clientid */ if (isFullDebug(component)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, unconf); LogFullDebug(component, "Confirming new %s", str); } rc = nfs_client_id_confirm(unconf, component); if (rc != CLIENT_ID_SUCCESS) { res_CREATE_SESSION4->csr_status = clientid_error_to_nfsstat_no_expire(rc); /* Need to destroy the session */ if (!nfs41_Session_Del(nfs41_session)) LogDebug(component, "Oops nfs41_Session_Del failed"); goto out; } nfs4_chk_clid(unconf); conf = unconf; unconf = NULL; if (isDebug(component)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, conf); LogDebug(component, "Confirmed %s", str); } } conf->cid_create_session_sequence++; /* Bump the lease timer */ conf->cid_last_renew = time(NULL); /* Once the lease timer is updated then the client is active and * if the unresponsive client was marked as expired earlier, * then moving it out of the expired client list */ if (conf->marked_for_delayed_cleanup) remove_client_from_expired_client_list(conf); if (isFullDebug(component)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_record(&dspbuf, client_record); LogFullDebug( component, "Client Record %s cr_confirmed_rec=%p cr_unconfirmed_rec=%p", str, client_record->cr_confirmed_rec, client_record->cr_unconfirmed_rec); } populate_callback_params_in_session( arg_CREATE_SESSION4->csa_sec_parms.csa_sec_parms_len, arg_CREATE_SESSION4->csa_sec_parms.csa_sec_parms_val, arg_CREATE_SESSION4->csa_cb_program, nfs41_session, component); /* Handle the creation of the back channel, if the client requested one. */ if (arg_CREATE_SESSION4->csa_flags & CREATE_SESSION4_FLAG_CONN_BACK_CHAN) { if (nfs_rpc_create_chan_v41( data->req->rq_xprt, nfs41_session, nfs41_session->cb_sec_parms.sec_parms_len, nfs41_session->cb_sec_parms.sec_parms_val) == 0) { res_CREATE_SESSION4ok->csr_flags |= CREATE_SESSION4_FLAG_CONN_BACK_CHAN; LogDebug(component, "Session backchannel created"); } } if (isDebug(component)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_session(&dspbuf, nfs41_session); LogDebug(component, "success %s csa_flags 0x%X csr_flags 0x%X", str, arg_CREATE_SESSION4->csa_flags, res_CREATE_SESSION4ok->csr_flags); } /* Successful exit */ res_CREATE_SESSION4->csr_status = NFS4_OK; /* Cache response */ /** @todo: Warning, if we ever have ca_rdma_ird_len == 1, we need to be * more careful since there would be allocated memory attached * to the response. */ conf->cid_create_session_slot = *res_CREATE_SESSION4; out: /* Release our reference to the found record (confirmed or unconfirmed) */ dec_client_id_ref(found); if (nfs41_session != NULL) { /* Release our reference to the session */ dec_session_ref(nfs41_session); } PTHREAD_MUTEX_unlock(&client_record->cr_mutex); /* Release our reference to the client record and return */ dec_client_record_ref(client_record); GSH_AUTO_TRACEPOINT( nfs4, op_create_session_end, TRACE_INFO, "CREATE_SESSION res: status={} session={} sequence={} flags={}", res_CREATE_SESSION4->csr_status, TP_SESSION(res_CREATE_SESSION4ok->csr_sessionid), res_CREATE_SESSION4ok->csr_sequence, res_CREATE_SESSION4ok->csr_flags); return nfsstat4_to_nfs_req_result(res_CREATE_SESSION4->csr_status); } /** * @brief free what was allocated to handle nfs41_op_create_session * * @param[in,out] resp nfs4_op results * */ void nfs4_op_create_session_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_delegpurge.c000066400000000000000000000051001473756622300227110ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_delegpurge.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include #include #include #include #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" /** * @brief NFS4_OP_DELEGPURGE * * This function implements the NFS4_OP_DELEGPURGE operation. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC 5661, pp. 363-4 * */ enum nfs_req_result nfs4_op_delegpurge(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { /* Unused for now, but when we actually implement this function it won't be, so remove the attribute. */ DELEGPURGE4args *const arg_DELEGPURGE4 __attribute__((unused)) = &op->nfs_argop4_u.opdelegpurge; DELEGPURGE4res *const res_DELEGPURGE4 = &resp->nfs_resop4_u.opdelegpurge; /* Lock are not supported */ resp->resop = NFS4_OP_DELEGPURGE; res_DELEGPURGE4->status = NFS4ERR_NOTSUPP; return NFS_REQ_ERROR; } /* nfs4_op_delegpurge */ /** * @brief Free memory allocated for DELEGPURGE result * * This function frees any memory allocated for the result of the * NFS4_OP_DELEGPURGE operation. * * @param[in,out] resp nfs4_op results * */ void nfs4_op_delegpurge_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_delegreturn.c000066400000000000000000000104171473756622300231150ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_delegreturn.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * * */ #include "config.h" #include #include #include #include #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_file_handle.h" #include "nfs_proto_functions.h" #include "sal_functions.h" /** * @brief NFS4_OP_DELEGRETURN * * This function implements the NFS4_OP_DELEGRETURN operation. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC 5661, p. 364 */ enum nfs_req_result nfs4_op_delegreturn(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { DELEGRETURN4args *const arg_DELEGRETURN4 = &op->nfs_argop4_u.opdelegreturn; DELEGRETURN4res *const res_DELEGRETURN4 = &resp->nfs_resop4_u.opdelegreturn; state_status_t state_status; state_t *state_found; const char *tag = "DELEGRETURN"; state_owner_t *owner; LogDebug( COMPONENT_NFS_V4_LOCK, "Entering NFS v4 DELEGRETURN handler -----------------------------------------------------"); /* Initialize to sane default */ resp->resop = NFS4_OP_DELEGRETURN; /* If the filehandle is invalid. Delegations are only supported on * regular files at the moment. */ res_DELEGRETURN4->status = nfs4_sanity_check_FH(data, REGULAR_FILE, false); if (res_DELEGRETURN4->status != NFS4_OK) { if (res_DELEGRETURN4->status == NFS4ERR_ISDIR) res_DELEGRETURN4->status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } /* Check stateid correctness and get pointer to state */ res_DELEGRETURN4->status = nfs4_Check_Stateid( &arg_DELEGRETURN4->deleg_stateid, data->current_obj, &state_found, data, STATEID_SPECIAL_FOR_LOCK, 0, false, tag); if (res_DELEGRETURN4->status != NFS4_OK) return NFS_REQ_ERROR; owner = get_state_owner_ref(state_found); if (owner == NULL) { /* Something has gone stale. */ LogDebug(COMPONENT_NFS_V4_LOCK, "Stale state"); res_DELEGRETURN4->status = NFS4ERR_STALE; goto out_unlock; } STATELOCK_lock(data->current_obj); deleg_heuristics_recall(data->current_obj, owner, state_found); reset_cbgetattr_stats(data->current_obj); /* Release reference taken above. */ dec_state_owner_ref(owner); /* Now we have a lock owner and a stateid. * Go ahead and push unlock into SAL (and FSAL) to return * the delegation. */ state_status = release_lease_lock(data->current_obj, state_found); res_DELEGRETURN4->status = nfs4_Errno_state(state_status); if (state_status == STATE_SUCCESS) { /* Successful exit */ LogDebug(COMPONENT_NFS_V4_LOCK, "Successful exit"); state_del_locked(state_found); } STATELOCK_unlock(data->current_obj); out_unlock: dec_state_t_ref(state_found); return nfsstat4_to_nfs_req_result(res_DELEGRETURN4->status); } /* nfs4_op_delegreturn */ /** * @brief Free memory allocated for DELEGRETURN result * * This function frees any memory allocated for the result of the * DELEGRETURN operation. * * @param resp [INOUT] Pointer to nfs4_op results */ void nfs4_op_delegreturn_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_destroy_clientid.c000066400000000000000000000144661473756622300241510ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_destroy_clientid.c * @brief Provides NFS4_OP_DESTROY_CLIENTID implementation */ #include "config.h" #include #include "log.h" #include "nfs4.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_core.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * * @brief The NFS4_OP_DESTROY_CLIENTID operation. * * @param[in] op nfs4_op arguments * @param[in,out] data Compound request data * @param[out] resp nfs4_op results * * @retval NFS4_OK or errors for NFSv4.1. * @retval NFS4ERR_NOTSUPP for NFSv4.0. * */ enum nfs_req_result nfs4_op_destroy_clientid(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { DESTROY_CLIENTID4args *const arg_DESTROY_CLIENTID4 = &op->nfs_argop4_u.opdestroy_clientid; DESTROY_CLIENTID4res *const res_DESTROY_CLIENTID4 = &resp->nfs_resop4_u.opdestroy_clientid; nfs_client_record_t *client_record = NULL; nfs_client_id_t *conf = NULL, *unconf = NULL, *found = NULL; clientid4 clientid; int rc; GSH_AUTO_TRACEPOINT(nfs4, op_destroy_clientid_start, TRACE_INFO, "DESTROY_CLIENTID arg: clientid={}", arg_DESTROY_CLIENTID4->dca_clientid); resp->resop = NFS4_OP_DESTROY_CLIENTID; clientid = arg_DESTROY_CLIENTID4->dca_clientid; if (isDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_clientid(&dspbuf, clientid); LogDebug(COMPONENT_CLIENTID, "DESTROY_CLIENTID clientid=%s", str); } res_DESTROY_CLIENTID4->dcr_status = NFS4_OK; /* First try to look up confirmed record */ rc = nfs_client_id_get_confirmed(clientid, &conf); if (rc == CLIENT_ID_SUCCESS) { client_record = conf->cid_client_record; found = conf; } else { /* fall back to unconfirmed */ rc = nfs_client_id_get_unconfirmed(clientid, &unconf); if (rc == CLIENT_ID_SUCCESS) { client_record = unconf->cid_client_record; found = unconf; } /* handle the perverse case of a clientid being confirmed * in the above interval */ rc = nfs_client_id_get_confirmed(clientid, &conf); if (rc == CLIENT_ID_SUCCESS) { if (found != NULL) dec_client_id_ref(found); client_record = conf->cid_client_record; found = conf; } } /* ref +1 */ if (found == NULL) { /* Fine. We're done. */ res_DESTROY_CLIENTID4->dcr_status = NFS4ERR_STALE_CLIENTID; goto out; } (void)inc_client_record_ref(client_record); PTHREAD_MUTEX_lock(&client_record->cr_mutex); if (isFullDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_record(&dspbuf, client_record); LogFullDebug( COMPONENT_CLIENTID, "Client Record %s cr_confirmed_rec=%p cr_unconfirmed_rec=%p", str, client_record->cr_confirmed_rec, client_record->cr_unconfirmed_rec); } /* per Frank, we must check the confirmed and unconfirmed * state of client_record again now that we hold cr_mutex */ conf = client_record->cr_confirmed_rec; unconf = client_record->cr_unconfirmed_rec; if ((!conf) && (!unconf)) { /* We raced a thread destroying clientid, and lost. * We're done. */ goto cleanup; } if (conf) { /* We MUST NOT destroy a clientid that has nfsv41 sessions or * state. Since the minorversion is 4.1 or higher, this is * equivalent to a session check. */ PTHREAD_MUTEX_lock(&conf->cid_mutex); if (!glist_empty(&conf->cid_cb.v41.cb_session_list)) { res_DESTROY_CLIENTID4->dcr_status = NFS4ERR_CLIENTID_BUSY; PTHREAD_MUTEX_unlock(&conf->cid_mutex); goto cleanup; } PTHREAD_MUTEX_unlock(&conf->cid_mutex); /* Delete the confirmed clientid record. Because we * have the cr_mutex, we have won any race to deal * with this clientid record. */ if (isFullDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, conf); LogDebug(COMPONENT_CLIENTID, "Removing confirmed clientid %s", str); } /* remove stable-storage record (if any) */ nfs4_rm_clid(conf); /* unhash the clientid record */ (void)remove_confirmed_client_id(conf); } if (unconf) { /* Delete the unconfirmed clientid record. Because we * have the cr_mutex, we have won any race to deal * with this clientid record. */ if (isFullDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, unconf); LogDebug(COMPONENT_CLIENTID, "Removing unconfirmed clientid %s", str); } /* unhash the clientid record */ (void)remove_unconfirmed_client_id(unconf); } cleanup: PTHREAD_MUTEX_unlock(&client_record->cr_mutex); dec_client_record_ref(client_record); /* ref +0 */ if (found != NULL) dec_client_id_ref(found); out: GSH_AUTO_TRACEPOINT(nfs4, op_destroy_clientid_end, TRACE_INFO, "DESTROY_CLIENTID res: status={}", res_DESTROY_CLIENTID4->dcr_status); return nfsstat4_to_nfs_req_result(res_DESTROY_CLIENTID4->dcr_status); } /** * @brief Free DESTROY_CLIENTID result * * @param[in,out] resp nfs4_op results */ void nfs4_op_destroy_clientid_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_destroy_session.c000066400000000000000000000072211473756622300240300ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_destroy_session.c * @brief Routines used for managing the NFS4_OP_DESTROY_SESSION operation. * * Routines used for managing the NFS4_OP_DESTROY_SESSION operation. * * */ #include "config.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * * @brief The NFS4_OP_DESTROY_SESSION operation * * This function implements the NFS4_OP_DESTROY_SESSION operation. * * @param[in] op nfs4_op arguments * @param[in,out] data Compound request's data * @param[out] resp nfs4_op results * * @return values as per RFC5661 p. 364 * * @see nfs4_Compound * */ enum nfs_req_result nfs4_op_destroy_session(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { DESTROY_SESSION4args *const arg_DESTROY_SESSION4 = &op->nfs_argop4_u.opdestroy_session; DESTROY_SESSION4res *const res_DESTROY_SESSION4 = &resp->nfs_resop4_u.opdestroy_session; nfs41_session_t *session; GSH_AUTO_TRACEPOINT(nfs4, op_destroy_session_start, TRACE_INFO, "DESTROY_SESSION arg: session={}", TP_SESSION(arg_DESTROY_SESSION4->dsa_sessionid)); resp->resop = NFS4_OP_DESTROY_SESSION; res_DESTROY_SESSION4->dsr_status = NFS4_OK; if (data->minorversion == 0) { res_DESTROY_SESSION4->dsr_status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } if (!nfs41_Session_Get_Pointer(arg_DESTROY_SESSION4->dsa_sessionid, &session)) { res_DESTROY_SESSION4->dsr_status = NFS4ERR_BADSESSION; return NFS_REQ_ERROR; } /* DESTROY_SESSION MUST be invoked on a connection that is associated * with the session being destroyed */ if (!check_session_conn(session, data, false)) { res_DESTROY_SESSION4->dsr_status = NFS4ERR_CONN_NOT_BOUND_TO_SESSION; dec_session_ref(session); return NFS_REQ_ERROR; } if (!nfs41_Session_Del(session)) res_DESTROY_SESSION4->dsr_status = NFS4ERR_BADSESSION; else res_DESTROY_SESSION4->dsr_status = NFS4_OK; /* Release ref taken in get_pointer */ dec_session_ref(session); GSH_AUTO_TRACEPOINT(nfs4, op_destroy_session_end, TRACE_INFO, "DESTROY_SESSION res: status={}", res_DESTROY_SESSION4->dsr_status); return nfsstat4_to_nfs_req_result(res_DESTROY_SESSION4->dsr_status); } /* nfs41_op_destroy_session */ /** * @brief Free memory allocated for result of nfs41_op_destroy_session * * This function frees memory allocated for result of * nfs41_op_destroy_session * * @param[in,out] resp nfs4_op results * */ void nfs4_op_destroy_session_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_exchange_id.c000066400000000000000000000340021473756622300230270ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_exchange_id.c * @brief The NFS4_OP_EXCHANGE_ID operation */ #include "config.h" #include #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "sal_functions.h" #include "nfs_creds.h" #include "sal_metrics.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif int get_raddr(SVCXPRT *xprt) { sockaddr_t *ss = svc_getrpclocal(xprt); int addr = 0; if (ss == NULL) return addr; switch (ss->ss_family) { case AF_INET6: { void *ab = &(((struct sockaddr_in6 *)ss)->sin6_addr.s6_addr[12]); addr = ntohl(*(uint32_t *)ab); } break; case AF_INET: addr = ntohl(((struct sockaddr_in *)ss)->sin_addr.s_addr); break; default: break; } return addr; } /* spi_ops (spo_must_enforce bitmap + spo_must_allow bitmap) + 4 spi_ fields + * len */ #define SSV_PROT_INFO4_BASE_SIZE (2 * sizeof(bitmap4) + 5 * BYTES_PER_XDR_UNIT) /* spr_how + spr_mach_ops + spr_ssv_info */ #define STATE_PROTECT4_R_BASE_SIZE \ (BYTES_PER_XDR_UNIT + sizeof(bitmap4) + SSV_PROT_INFO4_BASE_SIZE) /* nfsstat4 + clientid + sequenceid + eir_flags + eir_state_protect + * so_minor_id + so_major_id_len + eir_server_scope_len + eir_server_impl_id_len */ #define EXCHANGE_ID_BASE_RESP_SIZE \ (BYTES_PER_XDR_UNIT + sizeof(clientid4) + 3 * BYTES_PER_XDR_UNIT + \ STATE_PROTECT4_R_BASE_SIZE + sizeof(uint64_t) + \ 3 * BYTES_PER_XDR_UNIT) /** * @brief The NFS4_OP_EXCHANGE_ID operation * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 364 * * @see nfs4_Compound * */ enum nfs_req_result nfs4_op_exchange_id(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { nfs_client_record_t *client_record; nfs_client_id_t *conf; nfs_client_id_t *unconf; int rc; char *temp; bool update; uint32_t server_pnfs_flags = 0; in_addr_t server_addr = 0; /* Arguments and response */ EXCHANGE_ID4args *const arg_EXCHANGE_ID4 = &op->nfs_argop4_u.opexchange_id; client_owner4 *const clientowner = &arg_EXCHANGE_ID4->eia_clientowner; EXCHANGE_ID4res *const res_EXCHANGE_ID4 = &resp->nfs_resop4_u.opexchange_id; EXCHANGE_ID4resok *const res_EXCHANGE_ID4_ok = &resp->nfs_resop4_u.opexchange_id.EXCHANGE_ID4res_u.eir_resok4; uint32_t resp_size = EXCHANGE_ID_BASE_RESP_SIZE; int owner_len, scope_len; GSH_AUTO_TRACEPOINT( nfs4, op_exchange_id_start, TRACE_INFO, "EXCHANGE_ID arg: verifier={} ownerid={} flags={} state_protect={}", TP_VERIFIER(arg_EXCHANGE_ID4->eia_clientowner.co_verifier), TP_BYTE_ARR(clientowner->co_ownerid.co_ownerid_val, clientowner->co_ownerid.co_ownerid_len), arg_EXCHANGE_ID4->eia_flags, arg_EXCHANGE_ID4->eia_state_protect.spa_how); resp->resop = NFS4_OP_EXCHANGE_ID; if (data->minorversion == 0) { res_EXCHANGE_ID4->eir_status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } if ((arg_EXCHANGE_ID4->eia_flags & ~(EXCHGID4_FLAG_SUPP_MOVED_REFER | EXCHGID4_FLAG_SUPP_MOVED_MIGR | EXCHGID4_FLAG_BIND_PRINC_STATEID | EXCHGID4_FLAG_USE_NON_PNFS | EXCHGID4_FLAG_USE_PNFS_MDS | EXCHGID4_FLAG_USE_PNFS_DS | EXCHGID4_FLAG_UPD_CONFIRMED_REC_A)) != 0) { res_EXCHANGE_ID4->eir_status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } /* These are currently set during init and must be set in future * once Ganesha support runtime enabling of protocols, * else strictly assert off */ assert(cid_server_owner[0] != '\0'); assert(cid_server_scope != NULL); owner_len = strlen(cid_server_owner); scope_len = strlen(cid_server_scope); /* Now check that the response will fit. Use 0 for * eir_server_impl_id_len */ resp_size += RNDUP(owner_len) + RNDUP(scope_len) + 0; res_EXCHANGE_ID4->eir_status = check_resp_room(data, resp_size); if (res_EXCHANGE_ID4->eir_status != NFS4_OK) return NFS_REQ_ERROR; /* * https://tools.ietf.org/html/rfc5661#page-309 * +--------------------------------------------------------+ * | Acceptable Results from EXCHANGE_ID | * +--------------------------------------------------------+ * | EXCHGID4_FLAG_USE_PNFS_MDS | * | EXCHGID4_FLAG_USE_PNFS_MDS | EXCHGID4_FLAG_USE_PNFS_DS | * | EXCHGID4_FLAG_USE_PNFS_DS | * | EXCHGID4_FLAG_USE_NON_PNFS | * | EXCHGID4_FLAG_USE_PNFS_DS | EXCHGID4_FLAG_USE_NON_PNFS | * +--------------------------------------------------------+ */ /* If client did not ask for pNFS related server roles than just set server roles */ if ((arg_EXCHANGE_ID4->eia_flags & EXCHGID4_FLAG_MASK_PNFS) == 0) { if (nfs_param.nfsv4_param.pnfs_mds) server_pnfs_flags |= EXCHGID4_FLAG_USE_PNFS_MDS; else server_pnfs_flags |= EXCHGID4_FLAG_USE_NON_PNFS; if (nfs_param.nfsv4_param.pnfs_ds) server_pnfs_flags |= EXCHGID4_FLAG_USE_PNFS_DS; } /* If client did ask for pNFS related server roles than try to match the server roles to the client request. */ else { if ((arg_EXCHANGE_ID4->eia_flags & EXCHGID4_FLAG_USE_PNFS_MDS) && (nfs_param.nfsv4_param.pnfs_mds)) server_pnfs_flags |= EXCHGID4_FLAG_USE_PNFS_MDS; else server_pnfs_flags |= EXCHGID4_FLAG_USE_NON_PNFS; if ((arg_EXCHANGE_ID4->eia_flags & EXCHGID4_FLAG_USE_PNFS_DS) && (nfs_param.nfsv4_param.pnfs_ds)) server_pnfs_flags |= EXCHGID4_FLAG_USE_PNFS_DS; } LogDebug(COMPONENT_CLIENTID, "EXCHANGE_ID pnfs_flags 0x%08x eia_flags 0x%08x", server_pnfs_flags, arg_EXCHANGE_ID4->eia_flags); update = (arg_EXCHANGE_ID4->eia_flags & EXCHGID4_FLAG_UPD_CONFIRMED_REC_A) != 0; server_addr = get_raddr(data->req->rq_xprt); /* Do we already have one or more records for client id (x)? */ client_record = get_client_record( arg_EXCHANGE_ID4->eia_clientowner.co_ownerid.co_ownerid_val, arg_EXCHANGE_ID4->eia_clientowner.co_ownerid.co_ownerid_len, server_pnfs_flags, server_addr); if (client_record == NULL) { /* Some major failure */ LogCrit(COMPONENT_CLIENTID, "EXCHANGE_ID failed"); res_EXCHANGE_ID4->eir_status = NFS4ERR_SERVERFAULT; return NFS_REQ_ERROR; } /* * The following checks are based on RFC5661 * * This attempts to implement the logic described in * 18.35.4. IMPLEMENTATION */ PTHREAD_MUTEX_lock(&client_record->cr_mutex); conf = client_record->cr_confirmed_rec; if (conf != NULL) { /* Need a reference to the confirmed record for below */ inc_client_id_ref(conf); } if (conf != NULL && !update) { /* EXCHGID4_FLAG_UPD_CONFIRMED_REC_A not set * * Compare the client credentials, but don't compare * the client address. Doing so interferes with * trunking and the ability of a client to reconnect * after being assigned a new address. */ if (!nfs_compare_clientcred(&conf->cid_credential, &data->credential)) { PTHREAD_MUTEX_lock(&conf->cid_mutex); if (!valid_lease(conf, false) || !client_id_has_state(conf)) { PTHREAD_MUTEX_unlock(&conf->cid_mutex); /* CASE 3, client collisions, old * clientid is expired * * Expire clientid and release our reference. */ nfs_client_id_expire(conf, false, true); dec_client_id_ref(conf); conf = NULL; } else { PTHREAD_MUTEX_unlock(&conf->cid_mutex); /* CASE 3, client collisions, old * clientid is not expired */ res_EXCHANGE_ID4->eir_status = NFS4ERR_CLID_INUSE; /* Release our reference to the * confirmed clientid. */ dec_client_id_ref(conf); goto out; } } else if (memcmp(arg_EXCHANGE_ID4->eia_clientowner.co_verifier, conf->cid_incoming_verifier, NFS4_VERIFIER_SIZE) == 0) { /* CASE 2, Non-Update on Existing Client ID * * Return what was last returned without * changing any refcounts */ unconf = conf; res_EXCHANGE_ID4_ok->eir_flags |= EXCHGID4_FLAG_CONFIRMED_R; goto return_ok; } else { /* CASE 5, client restart */ /** @todo FSF: expire old clientid? */ dec_client_id_ref(conf); } } else if (conf != NULL) { /* EXCHGID4_FLAG_UPD_CONFIRMED_REC_A set */ if (memcmp(arg_EXCHANGE_ID4->eia_clientowner.co_verifier, conf->cid_incoming_verifier, NFS4_VERIFIER_SIZE) == 0) { if (!nfs_compare_clientcred(&conf->cid_credential, &data->credential) || op_ctx->client == NULL || conf->gsh_client == NULL || op_ctx->client != conf->gsh_client) { /* CASE 9, Update but wrong principal */ res_EXCHANGE_ID4->eir_status = NFS4ERR_PERM; } else { /* CASE 6, Update */ /** @todo: we don't track or handle the things * that are updated, but we can still * allow the update. */ LogDebug(COMPONENT_CLIENTID, "EXCHANGE_ID Update ignored"); unconf = conf; res_EXCHANGE_ID4_ok->eir_flags |= EXCHGID4_FLAG_CONFIRMED_R; goto return_ok; } } else { /* CASE 8, Update but wrong verifier */ res_EXCHANGE_ID4->eir_status = NFS4ERR_NOT_SAME; } /* Release our reference to the confirmed clientid. */ dec_client_id_ref(conf); goto out; } else if (conf == NULL && update) { /* CASE 7, Update but No Confirmed Record */ res_EXCHANGE_ID4->eir_status = NFS4ERR_NOENT; goto out; } /* At this point, no matter what the case was above, we should * remove any pre-existing unconfirmed record. */ unconf = client_record->cr_unconfirmed_rec; if (unconf != NULL) { /* CASE 4, replacement of unconfirmed record * * Delete the unconfirmed clientid record * unhash the clientid record */ remove_unconfirmed_client_id(unconf); } /* Now we can proceed to build the new unconfirmed record. We * have determined the clientid and setclientid_confirm values * above. */ unconf = create_client_id(0, client_record, &data->credential, data->minorversion); if (unconf == NULL) { /* Error already logged, return */ res_EXCHANGE_ID4->eir_status = NFS4ERR_RESOURCE; goto out; } unconf->cid_create_session_sequence = 1; unconf->cid_create_session_slot.csr_status = NFS4ERR_SEQ_MISORDERED; glist_init(&unconf->cid_cb.v41.cb_session_list); memcpy(unconf->cid_incoming_verifier, arg_EXCHANGE_ID4->eia_clientowner.co_verifier, NFS4_VERIFIER_SIZE); /* Ganesha currently ignores client requested state-protection and * always uses SP4_NONE */ unconf->cid_state_protect_how = SP4_NONE; rc = nfs_client_id_insert(unconf); if (rc != CLIENT_ID_SUCCESS) { /* Record is already freed, return. */ res_EXCHANGE_ID4->eir_status = clientid_error_to_nfsstat_no_expire(rc); goto out; } sal_metrics__client_state_protection( arg_EXCHANGE_ID4->eia_state_protect.spa_how); return_ok: /* Build the reply */ res_EXCHANGE_ID4_ok->eir_clientid = unconf->cid_clientid; res_EXCHANGE_ID4_ok->eir_sequenceid = unconf->cid_create_session_sequence; res_EXCHANGE_ID4_ok->eir_flags |= client_record->cr_pnfs_flags; res_EXCHANGE_ID4_ok->eir_flags |= EXCHGID4_FLAG_SUPP_MOVED_REFER; res_EXCHANGE_ID4_ok->eir_state_protect.spr_how = unconf->cid_state_protect_how; temp = gsh_malloc(owner_len + 1); memcpy(temp, cid_server_owner, owner_len + 1); res_EXCHANGE_ID4_ok->eir_server_owner.so_major_id.so_major_id_len = owner_len; res_EXCHANGE_ID4_ok->eir_server_owner.so_major_id.so_major_id_val = temp; res_EXCHANGE_ID4_ok->eir_server_owner.so_minor_id = 0; temp = gsh_malloc(scope_len + 1); memcpy(temp, cid_server_scope, scope_len + 1); res_EXCHANGE_ID4_ok->eir_server_scope.eir_server_scope_len = scope_len + 1; res_EXCHANGE_ID4_ok->eir_server_scope.eir_server_scope_val = temp; res_EXCHANGE_ID4_ok->eir_server_impl_id.eir_server_impl_id_len = 0; res_EXCHANGE_ID4_ok->eir_server_impl_id.eir_server_impl_id_val = NULL; res_EXCHANGE_ID4->eir_status = NFS4_OK; if (unconf == conf) { /* We just updated a confirmed clientid, release the refcount * now. */ dec_client_id_ref(conf); } out: PTHREAD_MUTEX_unlock(&client_record->cr_mutex); /* Release our reference to the client record */ dec_client_record_ref(client_record); GSH_AUTO_TRACEPOINT( nfs4, op_exchange_id_end, TRACE_INFO, "EXCHANGE_ID res: status={} clientid={} flags={} sequenceid={} state_protect={} minor_id={}", res_EXCHANGE_ID4->eir_status, res_EXCHANGE_ID4_ok->eir_clientid, res_EXCHANGE_ID4_ok->eir_flags, res_EXCHANGE_ID4_ok->eir_sequenceid, res_EXCHANGE_ID4_ok->eir_state_protect.spr_how, res_EXCHANGE_ID4_ok->eir_server_owner.so_minor_id); return nfsstat4_to_nfs_req_result(res_EXCHANGE_ID4->eir_status); } /** * @brief free memory allocated for nfs4_op_exchange_id result * * @param[in,out] resp Pointer to nfs4_op results */ void nfs4_op_exchange_id_Free(nfs_resop4 *res) { EXCHANGE_ID4res *resp = &res->nfs_resop4_u.opexchange_id; EXCHANGE_ID4resok *resok = &resp->EXCHANGE_ID4res_u.eir_resok4; if (resp->eir_status == NFS4_OK) { gsh_free(resok->eir_server_scope.eir_server_scope_val); gsh_free(resok->eir_server_owner.so_major_id.so_major_id_val); gsh_free(resok->eir_server_impl_id.eir_server_impl_id_val); } } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_free_stateid.c000066400000000000000000000107401473756622300232320ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_free_stateid.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * * */ #include "config.h" #include #include #include #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs4.h" #include "nfs_core.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "gsh_list.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * * @brief The NFS4_OP_FREE_STATEID operation. * * This function implements the NFS4_OP_FREE_STATEID operation in * nfs4_Compound. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661 pp. 364-5 * * @see nfs4_Compound */ enum nfs_req_result nfs4_op_free_stateid(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { FREE_STATEID4args *const arg_FREE_STATEID4 __attribute__((unused)) = &op->nfs_argop4_u.opfree_stateid; FREE_STATEID4res *const res_FREE_STATEID4 = &resp->nfs_resop4_u.opfree_stateid; state_t *state; struct saved_export_context saved; struct gsh_export *export; struct fsal_obj_handle *obj; GSH_AUTO_TRACEPOINT( nfs4, op_free_stateid_start, TRACE_INFO, "FREE_STATEID arg: seqid={} other={}", arg_FREE_STATEID4->fsa_stateid.seqid, TP_BYTE_ARR(arg_FREE_STATEID4->fsa_stateid.other, sizeof(arg_FREE_STATEID4->fsa_stateid.other) / sizeof(arg_FREE_STATEID4->fsa_stateid .other[0]))); resp->resop = NFS4_OP_FREE_STATEID; if (data->minorversion == 0) { res_FREE_STATEID4->fsr_status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } res_FREE_STATEID4->fsr_status = nfs4_Check_Stateid( &arg_FREE_STATEID4->fsa_stateid, NULL, &state, data, STATEID_SPECIAL_CURRENT, 0, false, "FREE_STATEID"); if (res_FREE_STATEID4->fsr_status != NFS4_OK) return NFS_REQ_ERROR; if (!get_state_obj_export_owner_refs(state, &obj, &export, NULL)) { /* If this happens, something is going stale, just return * NFS4ERR_BAD_STATEID, whatever is going stale will become * more apparent to the client soon... */ res_FREE_STATEID4->fsr_status = NFS4ERR_BAD_STATEID; dec_state_t_ref(state); return NFS_REQ_ERROR; } save_op_context_export_and_set_export(&saved, export); STATELOCK_lock(obj); if (state->state_type == STATE_TYPE_LOCK && glist_empty(&state->state_data.lock.state_locklist)) { /* At the moment, only return success for a lock stateid with * no locks. */ /** @todo: Do we also have to handle other kinds of stateids? */ res_FREE_STATEID4->fsr_status = NFS4_OK; state_del_locked(state); } else { res_FREE_STATEID4->fsr_status = NFS4ERR_LOCKS_HELD; } STATELOCK_unlock(obj); dec_state_t_ref(state); obj->obj_ops->put_ref(obj); restore_op_context_export(&saved); GSH_AUTO_TRACEPOINT(nfs4, op_free_stateid_end, TRACE_INFO, "FREE_STATEID res: status={}", res_FREE_STATEID4->fsr_status); return nfsstat4_to_nfs_req_result(res_FREE_STATEID4->fsr_status); } /* nfs41_op_free_stateid */ /** * @brief free memory allocated for FREE_STATEID result * * This function frees memory allocated for the NFS4_OP_FREE_STATEID * result. * * @param[in,out] resp nfs4_op results * */ void nfs4_op_free_stateid_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_getattr.c000066400000000000000000000166771473756622300222650ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_getattr.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include #include #include #include #include "log.h" #include "gsh_rpc.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_file_handle.h" #include "nfs_convert.h" #include "sal_functions.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief Gets attributes for an entry in the FSAL. * * Implements the NFS4_OP_GETATTR operation, which gets attributes for * an entry in the FSAL. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 365 * */ enum nfs_req_result nfs4_op_getattr(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { GETATTR4args *const arg_GETATTR4 = &op->nfs_argop4_u.opgetattr; GETATTR4res *const res_GETATTR4 = &resp->nfs_resop4_u.opgetattr; attrmask_t mask; struct fsal_attrlist attrs; bool current_obj_is_referral = false; fattr4 *obj_attributes = &res_GETATTR4->GETATTR4res_u.resok4.obj_attributes; nfs_client_id_t *deleg_client = NULL; struct fsal_obj_handle *obj = data->current_obj; GSH_AUTO_TRACEPOINT(nfs4, op_getattr_start, TRACE_INFO, "GETATTR arg: len={} map={}", arg_GETATTR4->attr_request.bitmap4_len, TP_UINT_ARR(arg_GETATTR4->attr_request.map, BITMAP4_MAPLEN)); /* This is a NFS4_OP_GETTAR */ resp->resop = NFS4_OP_GETATTR; /* Do basic checks on a filehandle */ res_GETATTR4->status = nfs4_sanity_check_FH(data, NO_FILE_TYPE, false); if (res_GETATTR4->status != NFS4_OK) goto out; /* Sanity check: if no attributes are wanted, nothing is to be * done. In this case NFS4_OK is to be returned */ if (arg_GETATTR4->attr_request.bitmap4_len == 0) { res_GETATTR4->status = NFS4_OK; goto out; } /* Get only attributes that are allowed to be read */ if (!nfs4_Fattr_Check_Access_Bitmap(&arg_GETATTR4->attr_request, FATTR4_ATTR_READ)) { res_GETATTR4->status = NFS4ERR_INVAL; goto out; } /* Ignore errors here as they just indicate that we got a request * for an unrecognized attribute. For GETATTR we just won't return it. */ bitmap4_to_attrmask_t(&arg_GETATTR4->attr_request, &mask); /* Add mode to what we actually ask for so we can do fslocations * test. */ fsal_prepare_attrs(&attrs, mask | ATTR_MODE); nfs4_bitmap4_Remove_Unsupported(&arg_GETATTR4->attr_request); if (obj->type == REGULAR_FILE) { /* As per rfc 7530, section:10.4.3 * The server needs to employ special handling for a GETATTR * where the target is a file that has an OPEN_DELEGATE_WRITE * delegation in effect. * * The server may use CB_GETATTR to fetch the right attributes * from the client holding the delegation or may simply recall * the delegation. Till then send EDELAY error. */ STATELOCK_lock(obj); if (is_write_delegated(obj, &deleg_client) && deleg_client && (deleg_client->gsh_client != op_ctx->client)) { res_GETATTR4->status = handle_deleg_getattr(obj, deleg_client); if (res_GETATTR4->status != NFS4_OK) { STATELOCK_unlock(obj); goto out; } else { cbgetattr_t *cbgetattr = NULL; /* CB_GETATTR response handler must have updated * the attributes in md-cache. reset cbgetattr * state and fall through. st_lock is held till * we finish ending response*/ cbgetattr = &obj->state_hdl->file.cbgetattr; cbgetattr->state = CB_GETATTR_NONE; } } /* release st_lock */ STATELOCK_unlock(obj); } res_GETATTR4->status = file_To_Fattr(data, mask, &attrs, obj_attributes, &arg_GETATTR4->attr_request); current_obj_is_referral = obj->obj_ops->is_referral(obj, &attrs, false); /* * If it is a referral point, return the FATTR4_RDATTR_ERROR if * requested along with the requested restricted attrs. */ if (res_GETATTR4->status == NFS4_OK && current_obj_is_referral) { bool fill_rdattr_error = true; bool fslocations_requested = attribute_is_set( &arg_GETATTR4->attr_request, FATTR4_FS_LOCATIONS); if (!fslocations_requested) { if (!attribute_is_set(&arg_GETATTR4->attr_request, FATTR4_RDATTR_ERROR)) { fill_rdattr_error = false; } } if (fill_rdattr_error) { struct xdr_attrs_args args; memset(&args, 0, sizeof(args)); args.attrs = &attrs; args.fsid = data->current_obj->fsid; get_mounted_on_fileid(data, &args.mounted_on_fileid); if (nfs4_Fattr_Fill_Error( data, obj_attributes, NFS4ERR_MOVED, &arg_GETATTR4->attr_request, &args) != 0) { /* Report an error. */ res_GETATTR4->status = NFS4ERR_SERVERFAULT; } } else { /* Report the referral. */ res_GETATTR4->status = NFS4ERR_MOVED; } } /* Done with the attrs */ fsal_release_attrs(&attrs); if (res_GETATTR4->status == NFS4_OK) { /* Fill in and check response size and make sure it fits. */ data->op_resp_size = sizeof(nfsstat4) + res_GETATTR4->GETATTR4res_u.resok4.obj_attributes .attr_vals.attrlist4_len; res_GETATTR4->status = check_resp_room(data, data->op_resp_size); } out: if (deleg_client) dec_client_id_ref(deleg_client); if (res_GETATTR4->status != NFS4_OK) { /* The attributes that may have been allocated will not be * consumed. Since the response array was allocated with * gsh_calloc, the buffer pointer is always NULL or valid. */ nfs4_Fattr_Free(obj_attributes); /* Indicate the failed response size. */ data->op_resp_size = sizeof(nfsstat4); } GSH_AUTO_TRACEPOINT( nfs4, op_getattr_end, TRACE_INFO, "GETATTR res: status={} len={} map={}", res_GETATTR4->status, obj_attributes->attrmask.bitmap4_len, TP_UINT_ARR(obj_attributes->attrmask.map, BITMAP4_MAPLEN)); return nfsstat4_to_nfs_req_result(res_GETATTR4->status); } /* nfs4_op_getattr */ /** * @brief Free memory allocated for GETATTR result * * This function frees any memory allocated for the result of the * NFS4_OP_GETATTR operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_getattr_Free(nfs_resop4 *res) { GETATTR4res *resp = &res->nfs_resop4_u.opgetattr; if (resp->status == NFS4_OK) nfs4_Fattr_Free(&resp->GETATTR4res_u.resok4.obj_attributes); } /* nfs4_op_getattr_Free */ nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_getdeviceinfo.c000066400000000000000000000141341473756622300234100ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_getdeviceinfo.c * @brief Routines used for managing the NFS4_OP_GETDEVICEINFO operation. * * Routines used for managing the GETDEVICEINFO operation. */ #include "config.h" #include #include #include #include #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_tools.h" #include "nfs_proto_functions.h" #include "nfs_file_handle.h" #include "nfs_convert.h" #include "fsal_pnfs.h" #include "nfs_proto_functions.h" #include "nfs_file_handle.h" #include "export_mgr.h" /* nfsstat4 + layout type + da_addr_body_len + gdir_notification */ #define GETDEVICEINFO_RESP_BASE_SIZE (3 * BYTES_PER_XDR_UNIT + sizeof(bitmap4)) /** * * @brief The NFS4_OP_GETDEVICEINFO operation. * * This function returns information on a pNFS device. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661 p. 365-6 * * @see nfs4_Compound * */ enum nfs_req_result nfs4_op_getdeviceinfo(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { /* Convenience alias for arguments */ GETDEVICEINFO4args *const arg_GETDEVICEINFO4 = &op->nfs_argop4_u.opgetdeviceinfo; /* Convenience alias for response */ GETDEVICEINFO4res *const res_GETDEVICEINFO4 = &resp->nfs_resop4_u.opgetdeviceinfo; /* Convenience alias for response */ GETDEVICEINFO4resok *resok = &res_GETDEVICEINFO4->GETDEVICEINFO4res_u.gdir_resok4; /* The separated deviceid passed to the FSAL */ struct pnfs_deviceid *deviceid; /* NFS4 return code */ nfsstat4 nfs_status = 0; /* XDR stream into which the FSAl shall encode the da_addr_body */ XDR da_addr_body; /* The position before any bytes are sent to the stream */ size_t da_beginning = 0; /* The total length of the XDR-encoded da_addr_body */ size_t da_length = 0; /* Address of the buffer that backs the stream */ char *da_buffer = NULL; /* The space necessary to hold one response */ count4 mincount = 0; /* The FSAL's requested size for the da_addr_body opaque */ size_t da_addr_size = 0; /* Pointer to the fsal appropriate to this deviceid */ struct fsal_module *fsal = NULL; uint32_t resp_size = GETDEVICEINFO_RESP_BASE_SIZE; resp->resop = NFS4_OP_GETDEVICEINFO; if (data->minorversion == 0) { res_GETDEVICEINFO4->gdir_status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } /* Overlay Ganesha's pnfs_deviceid on arg */ deviceid = (struct pnfs_deviceid *)arg_GETDEVICEINFO4->gdia_device_id; if (deviceid->fsal_id >= FSAL_ID_COUNT) { LogInfo(COMPONENT_PNFS, "GETDEVICEINFO with invalid fsal id %0hhx", deviceid->fsal_id); res_GETDEVICEINFO4->gdir_status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } fsal = pnfs_fsal[deviceid->fsal_id]; if (fsal == NULL) { LogInfo(COMPONENT_PNFS, "GETDEVICEINFO with inactive fsal id %0hhx", deviceid->fsal_id); res_GETDEVICEINFO4->gdir_status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } /* Check that we have space */ mincount = sizeof(uint32_t) + /* Count for the empty bitmap */ sizeof(layouttype4) + /* Type in the device_addr4 */ sizeof(uint32_t); /* Number of bytes in da_addr_body */ da_addr_size = MIN(fsal->m_ops.fs_da_addr_size(fsal), arg_GETDEVICEINFO4->gdia_maxcount - mincount); if (da_addr_size == 0) { LogCrit(COMPONENT_PNFS, "The FSAL must specify a non-zero da_addr size."); nfs_status = NFS4ERR_NOENT; goto out; } /* Set up the device_addr4 and get stream for FSAL to write into */ resok->gdir_device_addr.da_layout_type = arg_GETDEVICEINFO4->gdia_layout_type; da_buffer = gsh_malloc(da_addr_size); xdrmem_create(&da_addr_body, da_buffer, da_addr_size, XDR_ENCODE); da_beginning = xdr_getpos(&da_addr_body); nfs_status = fsal->m_ops.getdeviceinfo( fsal, &da_addr_body, arg_GETDEVICEINFO4->gdia_layout_type, deviceid); da_length = xdr_getpos(&da_addr_body) - da_beginning; xdr_destroy(&da_addr_body); if (nfs_status != NFS4_OK) goto out; resp_size += RNDUP(da_length); nfs_status = check_resp_room(data, resp_size); if (nfs_status != NFS4_OK) goto out; memset(&resok->gdir_notification, 0, sizeof(resok->gdir_notification)); resok->gdir_device_addr.da_addr_body.da_addr_body_len = da_length; resok->gdir_device_addr.da_addr_body.da_addr_body_val = da_buffer; nfs_status = NFS4_OK; out: if ((nfs_status != NFS4_OK) && da_buffer) gsh_free(da_buffer); res_GETDEVICEINFO4->gdir_status = nfs_status; return nfsstat4_to_nfs_req_result(res_GETDEVICEINFO4->gdir_status); } /* nfs41_op_getdeviceinfo */ /** * @brief Free memory allocated for GETDEVICEINFO result * * This function frees memory allocated for the result of an * NFS4_OP_GETDEVICEINFO response. * * @param[in,out] resp Results for nfs4_op * */ void nfs4_op_getdeviceinfo_Free(nfs_resop4 *res) { GETDEVICEINFO4res *resp = &res->nfs_resop4_u.opgetdeviceinfo; GETDEVICEINFO4resok *resok = &resp->GETDEVICEINFO4res_u.gdir_resok4; if (resp->gdir_status == NFS4_OK) { gsh_free(resok->gdir_device_addr.da_addr_body.da_addr_body_val); } } /* nfs41_op_getdeviceinfo_Free */ nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_getdevicelist.c000066400000000000000000000123521473756622300234300ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_getdevicelist.c * @brief Routines used for managing the NFS4_OP_GETDEVICELIST operation. * * Routines used for managing the GETDEVICELIST operation. * * */ #include "config.h" #include #include #include #include #include #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_tools.h" #include "nfs_proto_functions.h" #include "nfs_file_handle.h" #include "nfs_convert.h" #include "fsal_pnfs.h" #include "export_mgr.h" /* nfsstat4 + layout type + gdlr_deviceid_list_len + nfs_cookie4 + verifier4 + * gdlr_eof */ #define GETDEVICELIST_RESP_BASE_SIZE \ (3 * BYTES_PER_XDR_UNIT + sizeof(nfs_cookie4) + sizeof(verifier4)) struct cb_data { deviceid4 *buffer; size_t count; size_t max; uint64_t swexport; }; static bool cb(void *opaque, uint64_t id) { struct cb_data *data = (struct cb_data *)opaque; if (data->count > data->max) return false; *((uint64_t *)data->buffer[data->count]) = data->swexport; *((uint64_t *)(data->buffer[data->count] + sizeof(uint64_t))) = nfs_htonl64(id); ++data->count; return true; } /** * * @brief The NFS4_OP_GETDEVICELIST operation. * * This function returns a list of pNFS devices for a given * filesystem. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661 p. 365 * * @see nfs4_Compound * */ enum nfs_req_result nfs4_op_getdevicelist(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { /* Convenience alias for arguments */ GETDEVICELIST4args *const arg_GETDEVICELIST4 = &op->nfs_argop4_u.opgetdevicelist; /* Convenience alias for response */ GETDEVICELIST4res *const res_GETDEVICELIST4 = &resp->nfs_resop4_u.opgetdevicelist; /* Convenience alias for response */ GETDEVICELIST4resok *resok = &res_GETDEVICELIST4->GETDEVICELIST4res_u.gdlr_resok4; /* NFS4 return code */ nfsstat4 nfs_status = 0; /* Input/output and output parameters of FSAL function */ struct fsal_getdevicelist_res res; /* Structure for callback */ struct cb_data cb_opaque; uint32_t resp_size = GETDEVICELIST_RESP_BASE_SIZE; resp->resop = NFS4_OP_GETDEVICELIST; if (data->minorversion == 0) { res_GETDEVICELIST4->gdlr_status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } nfs_status = nfs4_sanity_check_FH(data, NO_FILE_TYPE, false); if (nfs_status != NFS4_OK) goto out; memset(&res, 0, sizeof(struct fsal_getdevicelist_res)); res.cookie = arg_GETDEVICELIST4->gdla_cookie; memcpy(&res.cookieverf, arg_GETDEVICELIST4->gdla_cookieverf, NFS4_VERIFIER_SIZE); cb_opaque.count = 0; cb_opaque.max = 32; cb_opaque.swexport = nfs_htonl64(op_ctx->ctx_export->export_id); resok->gdlr_deviceid_list.gdlr_deviceid_list_val = gsh_malloc(cb_opaque.max * sizeof(deviceid4)); cb_opaque.buffer = resok->gdlr_deviceid_list.gdlr_deviceid_list_val; nfs_status = op_ctx->fsal_export->exp_ops.getdevicelist( op_ctx->fsal_export, arg_GETDEVICELIST4->gdla_layout_type, &cb_opaque, cb, &res); if (nfs_status != NFS4_OK) { gsh_free(cb_opaque.buffer); goto out; } resp_size += cb_opaque.count * sizeof(deviceid4); nfs_status = check_resp_room(data, resp_size); if (nfs_status != NFS4_OK) { gsh_free(cb_opaque.buffer); goto out; } resok->gdlr_cookie = res.cookie; memcpy(resok->gdlr_cookieverf, &res.cookieverf, NFS4_VERIFIER_SIZE); resok->gdlr_deviceid_list.gdlr_deviceid_list_len = cb_opaque.count; resok->gdlr_eof = res.eof; nfs_status = NFS4_OK; out: res_GETDEVICELIST4->gdlr_status = nfs_status; return nfsstat4_to_nfs_req_result(res_GETDEVICELIST4->gdlr_status); } /** * @brief Free memory allocated for GETDEVICELIST result * * This function frees the memory allocates for the result of the * NFS4_OP_GETDEVICELIST operation. * * @param[in,out] resp nfs4_op results * */ void nfs4_op_getdevicelist_Free(nfs_resop4 *res) { GETDEVICELIST4res *resp = &res->nfs_resop4_u.opgetdevicelist; GETDEVICELIST4resok *resok = &resp->GETDEVICELIST4res_u.gdlr_resok4; if (resp->gdlr_status == NFS4_OK) { gsh_free(resok->gdlr_deviceid_list.gdlr_deviceid_list_val); } } /* nfs41_op_getdevicelist_Free */ nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_getfh.c000066400000000000000000000102441473756622300216700ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ---------------------------------------*/ #include "config.h" #include #include #include #include #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_file_handle.h" #include "nfs_convert.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief The NFS4_OP_GETFH operation * * Gets the currentFH for the current compound requests. This * operation returns the current FH in the reply structure. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 366 * * @see nfs4_Compound */ enum nfs_req_result nfs4_op_getfh(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { GETFH4res *const res_GETFH = &resp->nfs_resop4_u.opgetfh; struct fsal_attrlist attrs; bool result; resp->resop = NFS4_OP_GETFH; res_GETFH->status = NFS4_OK; LogHandleNFS4("NFS4 GETFH BEFORE: ", &data->currentFH); /* Do basic checks on a filehandle */ res_GETFH->status = nfs4_sanity_check_FH(data, NO_FILE_TYPE, true); if (res_GETFH->status != NFS4_OK) goto out; /* Fill in and check response size and make sure it fits. */ data->op_resp_size = sizeof(nfsstat4) + sizeof(uint32_t) + ((data->currentFH.nfs_fh4_len + sizeof(uint32_t) - 1) & ~(sizeof(uint32_t) - 1)); res_GETFH->status = check_resp_room(data, data->op_resp_size); if (res_GETFH->status != NFS4_OK) goto out; fsal_prepare_attrs(&attrs, op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export)); /* Do not require ACLs and FS_LOCATIONS */ attrs.request_mask &= ~(ATTR_ACL | ATTR4_FS_LOCATIONS); result = data->current_obj->obj_ops->is_referral(data->current_obj, &attrs, true); fsal_release_attrs(&attrs); if (result) { res_GETFH->status = NFS4ERR_MOVED; goto out; } /* Copy the filehandle to the reply structure */ nfs4_AllocateFH(&res_GETFH->GETFH4res_u.resok4.object); /* Put the data in place */ res_GETFH->GETFH4res_u.resok4.object.nfs_fh4_len = data->currentFH.nfs_fh4_len; memcpy(res_GETFH->GETFH4res_u.resok4.object.nfs_fh4_val, data->currentFH.nfs_fh4_val, data->currentFH.nfs_fh4_len); LogHandleNFS4("NFS4 GETFH AFTER: ", &res_GETFH->GETFH4res_u.resok4.object); out: if (res_GETFH->status != NFS4_OK) { /* Indicate the failed response size. */ data->op_resp_size = sizeof(nfsstat4); } GSH_AUTO_TRACEPOINT(nfs4, op_getfh_end, TRACE_INFO, "GETFH res: status={}", res_GETFH->status); return nfsstat4_to_nfs_req_result(res_GETFH->status); } /* nfs4_op_getfh */ /** * @brief Free memory allocated for GETFH result * * This function frees any memory allocated for the result of the * NFS4_OP_GETFH operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_getfh_Free(nfs_resop4 *res) { GETFH4res *resp = &res->nfs_resop4_u.opgetfh; if (resp->status == NFS4_OK) gsh_free(resp->GETFH4res_u.resok4.object.nfs_fh4_val); } /* nfs4_op_getfh_Free */ nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_illegal.c000066400000000000000000000063561473756622300222150ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file nfs4_op_illegal.c * \brief Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include #include #include #include #include "log.h" #include "gsh_rpc.h" #include "nfs23.h" #include "nfs4.h" #include "mount.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief Always fail * * This function is the designated ILLEGAL operation. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op results * * @retval NFS4ERR_OP_ILLEGAL always. * */ enum nfs_req_result nfs4_op_illegal(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { GSH_AUTO_TRACEPOINT(nfs4, op_illegal, TRACE_INFO, "OP_ILLEGAL"); resp->resop = NFS4_OP_ILLEGAL; resp->nfs_resop4_u.opillegal.status = NFS4ERR_OP_ILLEGAL; return NFS_REQ_ERROR; } /* nfs4_op_illegal */ /** * @brief Free memory allocated for ILLEGAL result * * This function frees any memory allocated for the result of the * NFS4_OP_ILLEGAL operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_illegal_Free(nfs_resop4 *resp) { /* Nothing to be done */ } /** * @brief Always fail * * This function is the designated NOTSUPP operation. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op results * * @retval NFS4ERR_NOTSUPP always. * */ enum nfs_req_result nfs4_op_notsupp(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { GSH_AUTO_TRACEPOINT(nfs4, op_notsupp, TRACE_INFO, "OP_NOTSUPP"); resp->resop = op->argop; resp->nfs_resop4_u.opillegal.status = NFS4ERR_NOTSUPP; return NFS_REQ_ERROR; } /* nfs4_op_notsupp */ /** * @brief Free memory allocated for NOTSUPP result * * This function frees any memory allocated for the result of the * NFS4_OP_NOTSUPP operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_notsupp_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_layoutcommit.c000066400000000000000000000130451473756622300233230ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_layoutcommit.c * @brief The NFSv4.1 LAYOUTCOMMIT operation. */ #include "config.h" #include #include #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include "fsal_pnfs.h" #include "sal_functions.h" /** * * @brief The NFS4_OP_LAYOUTCOMMIT operation * * This function implements the NFS4_OP_LAYOUTCOMMIT operation. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661 p. 366 * * @see nfs4_Compound * */ enum nfs_req_result nfs4_op_layoutcommit(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { /* Convenience alias for arguments */ LAYOUTCOMMIT4args *const args = &op->nfs_argop4_u.oplayoutcommit; /* Convenience alias for response */ LAYOUTCOMMIT4res *const res_LAYOUTCOMMIT4 = &resp->nfs_resop4_u.oplayoutcommit; /* Convenience alias for response */ LAYOUTCOMMIT4resok *resok = &res_LAYOUTCOMMIT4->LAYOUTCOMMIT4res_u.locr_resok4; /* NFS4 return code */ nfsstat4 nfs_status = 0; /* State indicated by client */ state_t *layout_state = NULL; /* Tag for logging in state operations */ const char *tag = "LAYOUTCOMMIT"; /* Iterator along linked list */ struct glist_head *glist = NULL; /* Input arguments of FSAL_layoutcommit */ struct fsal_layoutcommit_arg arg; /* Input/output and output arguments of FSAL_layoutcommit */ struct fsal_layoutcommit_res res; /* The segment being traversed */ state_layout_segment_t *segment; /* XDR stream holding the lrf_body opaque */ XDR lou_body; /* The beginning of the stream */ unsigned int beginning = 0; resp->resop = NFS4_OP_LAYOUTCOMMIT; if (data->minorversion == 0) { res_LAYOUTCOMMIT4->locr_status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } nfs_status = nfs4_sanity_check_FH(data, REGULAR_FILE, false); if (nfs_status != NFS4_OK) { res_LAYOUTCOMMIT4->locr_status = nfs_status; return NFS_REQ_ERROR; } memset(&arg, 0, sizeof(struct fsal_layoutcommit_arg)); memset(&res, 0, sizeof(struct fsal_layoutcommit_res)); /* Suggest a new size, if we have it */ if (args->loca_last_write_offset.no_newoffset) { arg.new_offset = true; arg.last_write = args->loca_last_write_offset.newoffset4_u.no_offset; } else arg.new_offset = false; arg.reclaim = args->loca_reclaim; xdrmem_create(&lou_body, args->loca_layoutupdate.lou_body.lou_body_val, args->loca_layoutupdate.lou_body.lou_body_len, XDR_DECODE); beginning = xdr_getpos(&lou_body); /* Suggest a new modification time if we have it */ if (args->loca_time_modify.nt_timechanged) { arg.time_changed = true; arg.new_time.seconds = args->loca_time_modify.newtime4_u.nt_time.seconds; arg.new_time.nseconds = args->loca_time_modify.newtime4_u.nt_time.nseconds; } /* Retrieve state corresponding to supplied ID */ nfs_status = nfs4_Check_Stateid(&args->loca_stateid, data->current_obj, &layout_state, data, STATEID_SPECIAL_CURRENT, 0, false, tag); if (nfs_status != NFS4_OK) goto out; arg.type = layout_state->state_data.layout.state_layout_type; STATELOCK_lock(data->current_obj); glist_for_each(glist, &layout_state->state_data.layout.state_segments) { segment = glist_entry(glist, state_layout_segment_t, sls_state_segments); arg.segment = segment->sls_segment; arg.fsal_seg_data = segment->sls_fsal_data; nfs_status = data->current_obj->obj_ops->layoutcommit( data->current_obj, &lou_body, &arg, &res); if (nfs_status != NFS4_OK) { STATELOCK_unlock(data->current_obj); goto out; } if (res.commit_done) break; /* This really should work in all cases for an in-memory decode stream. */ xdr_setpos(&lou_body, beginning); } STATELOCK_unlock(data->current_obj); resok->locr_newsize.ns_sizechanged = res.size_supplied; if (res.size_supplied) { resok->locr_newsize.newsize4_u.ns_size = res.new_size; } nfs_status = NFS4_OK; out: if (layout_state != NULL) dec_state_t_ref(layout_state); xdr_destroy(&lou_body); res_LAYOUTCOMMIT4->locr_status = nfs_status; return nfsstat4_to_nfs_req_result(res_LAYOUTCOMMIT4->locr_status); } /* nfs41_op_layoutcommit */ /** * @brief free memory allocated for response of LAYOUTCOMMIT * * This function frees the memory allocated for response of * the NFS4_OP_LAYOUTCOMMIT operation. * * @param[in,out] resp Results for nfs4_op * */ void nfs4_op_layoutcommit_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_layoutget.c000066400000000000000000000402501473756622300226100ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ #include "config.h" #include #include #include #include #include #include #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs23.h" #include "nfs4.h" #include "mount.h" #include "nfs_core.h" #include "export_mgr.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_file_handle.h" #include "fsal.h" #include "fsal_api.h" #include "fsal_pnfs.h" #include "sal_data.h" #include "sal_functions.h" /* status, return on close, layout len, stateid */ #define LAYOUTGET_RESP_BASE_SIZE (3 * BYTES_PER_XDR_UNIT + sizeof(stateid4)) /* io_offset, io_length, io_mode, loc_type, body length */ #define LAYOUYSEGMENT_BASE_SIZE \ (sizeof(offset4) + sizeof(length4) + 3 * BYTES_PER_XDR_UNIT) /** * * @brief Get or make a layout state * * If the stateid supplied by the client refers to a layout state, * that state is returned. Otherwise, if it is a share, lock, or * delegation, a new state is created. Any layout state matching * clientid, file, and type is freed. * * @param[in,out] data Compound Compound request's data * @param[in] supplied_stateid The stateid included in * the arguments to layoutget * @param[in] layout_type Type of layout being requested * @param[out] layout_state The layout state * * @return NFS4_OK if successful, other values on error */ static nfsstat4 acquire_layout_state(compound_data_t *data, stateid4 *supplied_stateid, layouttype4 layout_type, state_t **layout_state, const char *tag) { /* State associated with the client-supplied stateid */ state_t *supplied_state = NULL; /* State owner for per-clientid states */ state_owner_t *clientid_owner = NULL; /* Return from this function */ nfsstat4 nfs_status = 0; /* Return from state functions */ state_status_t state_status = 0; /* Layout state, forgotten about by caller */ state_t *condemned_state = NULL; /* Tracking data for the layout state */ struct state_refer refer; bool lock_held = false; memcpy(refer.session, data->session->session_id, sizeof(sessionid4)); refer.sequence = data->sequence; refer.slot = data->slotid; clientid_owner = &data->session->clientid_record->cid_owner; /* Retrieve state corresponding to supplied ID, inspect it * and, if necessary, create a new layout state */ nfs_status = nfs4_Check_Stateid(supplied_stateid, data->current_obj, &supplied_state, data, STATEID_SPECIAL_CURRENT, 0, false, tag); if (nfs_status != NFS4_OK) { /* The supplied stateid was invalid */ return nfs_status; } if (supplied_state->state_type == STATE_TYPE_LAYOUT) { /* If the state supplied is a layout state, we can * simply use it, return with the reference we just * acquired. */ *layout_state = supplied_state; goto out; } else if ((supplied_state->state_type == STATE_TYPE_SHARE) || (supplied_state->state_type == STATE_TYPE_DELEG) || (supplied_state->state_type == STATE_TYPE_LOCK)) { /* For share, delegation, and lock states, create a new layout state. */ union state_data layout_data; memset(&layout_data, 0, sizeof(layout_data)); STATELOCK_lock(data->current_obj); lock_held = true; /* See if a layout state already exists */ state_status = state_lookup_layout_state(data->current_obj, clientid_owner, layout_type, &condemned_state); /* If it does, we assume that the client is using the * forgetful model and has forgotten it had any * layouts. Free all layouts associated with the * state and delete it. */ if (state_status == STATE_SUCCESS) { /* Flag indicating whether all layouts were returned * and the state was deleted */ bool deleted = false; struct pnfs_segment entire = { .io_mode = LAYOUTIOMODE4_ANY, .offset = 0, .length = NFS4_UINT64_MAX }; if (condemned_state->state_data.layout.granting) { nfs_status = NFS4ERR_DELAY; dec_state_t_ref(condemned_state); goto out; } nfs_status = nfs4_return_one_state( data->current_obj, 0, circumstance_forgotten, condemned_state, entire, 0, NULL, &deleted); dec_state_t_ref(condemned_state); if (nfs_status != NFS4_OK) goto out; if (!deleted) { nfs_status = NFS4ERR_SERVERFAULT; goto out; } condemned_state = NULL; } layout_data.layout.state_layout_type = layout_type; layout_data.layout.state_return_on_close = false; state_status = state_add_impl(data->current_obj, STATE_TYPE_LAYOUT, &layout_data, clientid_owner, layout_state, &refer); if (state_status != STATE_SUCCESS) { nfs_status = nfs4_Errno_state(state_status); goto out; } glist_init(&(*layout_state)->state_data.layout.state_segments); } else { /* A state exists but is of an invalid type. */ nfs_status = NFS4ERR_BAD_STATEID; goto out; } out: /* We are done with the supplied_state, release the reference. */ dec_state_t_ref(supplied_state); if (lock_held) STATELOCK_unlock(data->current_obj); return nfs_status; } /** * * @brief Free layouts array. * * @param[in] layouts Layouts array * @param[in] numlayouts Size of array */ void free_layouts(layout4 *layouts, uint32_t numlayouts) { size_t i; for (i = 0; i < numlayouts; i++) { if (layouts[i].lo_content.loc_body.loc_body_val) gsh_free(layouts[i].lo_content.loc_body.loc_body_val); } gsh_free(layouts); } /** * * @brief Grant and add one layout segment * * This is a wrapper around the FSAL call that populates one entry in * the logr_layout array and adds one segment to the state list. * * @param[in] obj File handle * @param[in] arg Input arguments to the FSAL * @param[in,out] res Input/output and output arguments to the FSAL * @param[out] current The current entry in the logr_layout array. * * @return NFS4_OK if successful, other values show an error. */ static nfsstat4 one_segment(struct fsal_obj_handle *obj, state_t *layout_state, const struct fsal_layoutget_arg *arg, struct fsal_layoutget_res *res, layout4 *current) { /* The initial position of the XDR stream after creation, so we can find the total length of encoded data. */ size_t start_position = 0; /* XDR stream to encode loc_body of the current segment */ XDR loc_body; /* Return code from this function */ nfsstat4 nfs_status = 0; /* Return from state calls */ state_status_t state_status = 0; /* Size of a loc_body buffer */ size_t loc_body_size = MIN(op_ctx->fsal_export->exp_ops.fs_loc_body_size( op_ctx->fsal_export), arg->maxcount); if (loc_body_size == 0) { LogCrit(COMPONENT_PNFS, "The FSAL must specify a non-zero loc_body_size."); return NFS4ERR_SERVERFAULT; } /* Initialize the layout_content4 structure, allocate a buffer, and create an XDR stream for the FSAL to encode to. */ current->lo_content.loc_type = arg->type; current->lo_content.loc_body.loc_body_val = gsh_malloc(loc_body_size); xdrmem_create(&loc_body, current->lo_content.loc_body.loc_body_val, loc_body_size, XDR_ENCODE); start_position = xdr_getpos(&loc_body); ++layout_state->state_data.layout.granting; nfs_status = obj->obj_ops->layoutget(obj, &loc_body, arg, res); --layout_state->state_data.layout.granting; current->lo_content.loc_body.loc_body_len = xdr_getpos(&loc_body) - start_position; xdr_destroy(&loc_body); if (nfs_status != NFS4_OK) goto out; current->lo_offset = res->segment.offset; current->lo_length = res->segment.length; current->lo_iomode = res->segment.io_mode; state_status = state_add_segment(layout_state, &res->segment, res->fsal_seg_data, res->return_on_close); if (state_status != STATE_SUCCESS) { nfs_status = nfs4_Errno_state(state_status); goto out; } /** * @todo This is where you would want to record layoutget * operation. You can get the details of every segment added * here, including the segment description in * res->fsal_seg_data and clientid in *op_ctx->clientid. */ out: if (nfs_status != NFS4_OK) gsh_free(current->lo_content.loc_body.loc_body_val); return nfs_status; } /** * @brief The NFS4_OP_LAYOUTGET operation * * This function implements the NFS4_OP_LAYOUTGET operation. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661 pp. 366-7 * * @see nfs4_Compound */ enum nfs_req_result nfs4_op_layoutget(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { /* Convenience alias for arguments */ LAYOUTGET4args *const arg_LAYOUTGET4 = &op->nfs_argop4_u.oplayoutget; /* Convenience alias for response */ LAYOUTGET4res *const res_LAYOUTGET4 = &resp->nfs_resop4_u.oplayoutget; /* Convenience alias for response */ LAYOUTGET4resok *resok = &res_LAYOUTGET4->LAYOUTGET4res_u.logr_resok4; /* NFSv4.1 status code */ nfsstat4 nfs_status = 0; /* Pointer to state governing layouts */ state_t *layout_state = NULL; /* Pointer to the array of layouts */ layout4 *layouts = NULL; /* Total number of layout segments returned by the FSAL */ uint32_t numlayouts = 0; /* Tag supplied to SAL functions for debugging messages */ const char *tag = "LAYOUTGET"; /* Input arguments of layoutget */ struct fsal_layoutget_arg arg; /* Input/output and output arguments of layoutget */ struct fsal_layoutget_res res; /* Maximum number of segments this FSAL will ever return for a single LAYOUTGET */ int max_segment_count = 0; uint32_t resp_size = LAYOUTGET_RESP_BASE_SIZE; resp->resop = NFS4_OP_LAYOUTGET; if (data->minorversion == 0) { res_LAYOUTGET4->logr_status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } nfs_status = nfs4_sanity_check_FH(data, REGULAR_FILE, false); if (nfs_status != NFS4_OK) goto out; /* max_segment_count is also an indication of if fsal supports pnfs */ max_segment_count = op_ctx->fsal_export->exp_ops.fs_maximum_segments( op_ctx->fsal_export); if (max_segment_count == 0) { LogWarn(COMPONENT_PNFS, "The FSAL must specify a non-zero fs_maximum_segments."); nfs_status = NFS4ERR_LAYOUTUNAVAILABLE; goto out; } nfs_status = acquire_layout_state(data, &arg_LAYOUTGET4->loga_stateid, arg_LAYOUTGET4->loga_layout_type, &layout_state, tag); if (nfs_status != NFS4_OK) goto out; /* * Blank out argument structures and get the filehandle. */ memset(&arg, 0, sizeof(struct fsal_layoutget_arg)); memset(&res, 0, sizeof(struct fsal_layoutget_res)); /* * Initialize segment array and fill out input-only arguments */ layouts = gsh_calloc(max_segment_count, sizeof(layout4)); arg.type = arg_LAYOUTGET4->loga_layout_type; arg.minlength = arg_LAYOUTGET4->loga_minlength; arg.export_id = op_ctx->ctx_export->export_id; arg.maxcount = arg_LAYOUTGET4->loga_maxcount; /* Guaranteed on the first call */ res.context = NULL; /** * @todo ACE: Currently we have no callbacks, so it makes no * sense to pass the client-supplied value to the FSAL. When * we get callbacks, it will. */ res.signal_available = false; do { u_int blen; /* Since the FSAL writes to tis structure with every call, we re-initialize it with the operation's arguments */ res.segment.io_mode = arg_LAYOUTGET4->loga_iomode; res.segment.offset = arg_LAYOUTGET4->loga_offset; res.segment.length = arg_LAYOUTGET4->loga_length; /* Clear anything from a previous segment */ res.fsal_seg_data = NULL; nfs_status = one_segment(data->current_obj, layout_state, &arg, &res, layouts + numlayouts); if (nfs_status != NFS4_OK) goto out; blen = layouts[numlayouts].lo_content.loc_body.loc_body_len; resp_size += LAYOUYSEGMENT_BASE_SIZE + blen; arg.maxcount -= LAYOUYSEGMENT_BASE_SIZE + blen; numlayouts++; if ((numlayouts == max_segment_count) && !res.last_segment) { nfs_status = NFS4ERR_SERVERFAULT; goto out; } } while (!res.last_segment); /* Now check response size. */ nfs_status = check_resp_room(data, resp_size); if (nfs_status != NFS4_OK) goto out; /* Update stateid.seqid and copy to current */ update_stateid(layout_state, &resok->logr_stateid, data, tag); resok->logr_return_on_close = layout_state->state_data.layout.state_return_on_close; /* Now the layout specific information */ resok->logr_layout.logr_layout_len = numlayouts; resok->logr_layout.logr_layout_val = layouts; nfs_status = NFS4_OK; out: if (nfs_status != NFS4_OK) { if (layouts != NULL) free_layouts(layouts, numlayouts); if ((layout_state) && (layout_state->state_seqid == 0)) { state_del(layout_state); layout_state = NULL; } /* Poison the current stateid */ data->current_stateid_valid = false; /* reset the response size */ if (nfs_status == NFS4ERR_LAYOUTTRYLATER) resp_size = sizeof(nfsstat4) + BYTES_PER_XDR_UNIT; else resp_size = sizeof(nfsstat4); } if (layout_state != NULL) dec_state_t_ref(layout_state); res_LAYOUTGET4->logr_status = nfs_status; return nfsstat4_to_nfs_req_result(res_LAYOUTGET4->logr_status); } /* nfs4_op_layoutget */ /** * @brief Free memory allocated for LAYOUTGET result * * This function frees any memory allocated for the NFS4_OP_LAYOUTGET * result. * * @param[in,out] resp nfs4_op results */ void nfs4_op_layoutget_Free(nfs_resop4 *res) { LAYOUTGET4res *resp = &res->nfs_resop4_u.oplayoutget; LAYOUTGET4resok *resok = &resp->LAYOUTGET4res_u.logr_resok4; if (resp->logr_status == NFS4_OK) free_layouts(resok->logr_layout.logr_layout_val, resok->logr_layout.logr_layout_len); } /* nfs41_op_layoutget_Free */ enum nfs_req_result nfs4_op_layouterror(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { /* Convenience alias for arguments */ LAYOUTERROR4args *const arg_LAYOUTERROR4 = &op->nfs_argop4_u.oplayouterror; /* Convenience alias for response */ LAYOUTERROR4res *const res_LAYOUTERROR4 = &resp->nfs_resop4_u.oplayouterror; LogEvent(COMPONENT_PNFS, "LAYOUTERROR OP %d status %d offset: %" PRIu64 " length: %" PRIu64, arg_LAYOUTERROR4->lea_errors.de_opnum, arg_LAYOUTERROR4->lea_errors.de_status, arg_LAYOUTERROR4->lea_offset, arg_LAYOUTERROR4->lea_length); /** @todo: what else do we want to do with this error ??? */ res_LAYOUTERROR4->ler_status = NFS4_OK; return NFS_REQ_OK; } void nfs4_op_layouterror_Free(nfs_resop4 *res) { } enum nfs_req_result nfs4_op_layoutstats(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { /* Convenience alias for arguments */ LAYOUTSTATS4args *const arg_LAYOUTSTATS4 = &op->nfs_argop4_u.oplayoutstats; /* Convenience alias for response */ LAYOUTSTATS4res *const res_LAYOUTSTATS4 = &resp->nfs_resop4_u.oplayoutstats; LogEvent(COMPONENT_PNFS, "LAYOUTSTATS offset %" PRIu64 " length %" PRIu64, arg_LAYOUTSTATS4->lsa_offset, arg_LAYOUTSTATS4->lsa_length); LogEvent(COMPONENT_PNFS, "LAYOUTSTATS read count %u bytes %" PRIu64 " write count %u bytes %" PRIu64, arg_LAYOUTSTATS4->lsa_read.ii_count, arg_LAYOUTSTATS4->lsa_read.ii_bytes, arg_LAYOUTSTATS4->lsa_write.ii_count, arg_LAYOUTSTATS4->lsa_write.ii_bytes); /** @todo: what else do we want to do with the stats ??? */ res_LAYOUTSTATS4->lsr_status = NFS4_OK; return NFS_REQ_OK; } void nfs4_op_layoutstats_Free(nfs_resop4 *res) { } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_layoutreturn.c000066400000000000000000000416611473756622300233570ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_layoutreturn.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include #include #include #include #include #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_file_handle.h" #include "nfs_convert.h" #include "fsal.h" #include "pnfs_utils.h" #include "sal_data.h" #include "sal_functions.h" /** * * @brief The NFS4_OP_LAYOUTRETURN operation. * * This function implements the NFS4_OP_LAYOUTRETURN operation. * * @param[in] op Arguments fo nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661 p. 367 * * @see nfs4_Compound */ enum nfs_req_result nfs4_op_layoutreturn(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { /* Convenience alias for arguments */ LAYOUTRETURN4args *const arg_LAYOUTRETURN4 = &op->nfs_argop4_u.oplayoutreturn; /* Convenience alias for arguments */ layoutreturn_file4 *lr_layout = &arg_LAYOUTRETURN4->lora_layoutreturn.layoutreturn4_u.lr_layout; /* Convenience alias for response */ LAYOUTRETURN4res *const res_LAYOUTRETURN4 = &resp->nfs_resop4_u.oplayoutreturn; /* Convenience alias for response */ layoutreturn_stateid *lorr_stateid = &res_LAYOUTRETURN4->LAYOUTRETURN4res_u.lorr_stateid; /* Convenience alias for response */ stateid4 *lrs_stateid = &lorr_stateid->layoutreturn_stateid_u.lrs_stateid; /* NFS4 status code */ nfsstat4 nfs_status = 0; /* FSID of candidate file to return */ fsal_fsid_t fsid = { 0, 0 }; /* True if the supplied layout state was deleted */ bool deleted = false; /* State specified in the case of LAYOUTRETURN4_FILE */ state_t *layout_state = NULL; /* State owner associated with this clientid, for bulk returns */ state_owner_t *clientid_owner = NULL; struct glist_head *state_list; /* Linked list node for iteration */ struct glist_head *glist = NULL; /* Saved next node for safe iteration */ struct glist_head *glistn = NULL; /* Tag to identify caller in tate log messages */ const char *tag = "LAYOUTRETURN"; /* Segment selecting which segments to return. */ struct pnfs_segment spec = { 0, 0, 0 }; /* Remember if we need to do fsid based return */ bool return_fsid = false; /* Referenced file */ struct fsal_obj_handle *obj = NULL; /* Referenced export */ struct gsh_export *export = NULL; /* Root op context for returning fsid or all layouts */ struct req_op_context op_context; /* Keep track of so_mutex */ bool so_mutex_locked = false; state_t *first; resp->resop = NFS4_OP_LAYOUTRETURN; if (data->minorversion == 0) { res_LAYOUTRETURN4->lorr_status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } switch (arg_LAYOUTRETURN4->lora_layoutreturn.lr_returntype) { case LAYOUTRETURN4_FILE: nfs_status = nfs4_sanity_check_FH(data, REGULAR_FILE, false); if (nfs_status != NFS4_OK) { res_LAYOUTRETURN4->lorr_status = nfs_status; break; } /* Retrieve state corresponding to supplied ID */ if (!arg_LAYOUTRETURN4->lora_reclaim) { nfs_status = nfs4_Check_Stateid(&lr_layout->lrf_stateid, data->current_obj, &layout_state, data, STATEID_SPECIAL_CURRENT, 0, false, tag); if (nfs_status != NFS4_OK) { res_LAYOUTRETURN4->lorr_status = nfs_status; break; } } spec.io_mode = arg_LAYOUTRETURN4->lora_iomode; spec.offset = lr_layout->lrf_offset; spec.length = lr_layout->lrf_length; STATELOCK_lock(data->current_obj); res_LAYOUTRETURN4->lorr_status = nfs4_return_one_state( data->current_obj, arg_LAYOUTRETURN4->lora_layoutreturn.lr_returntype, arg_LAYOUTRETURN4->lora_reclaim ? circumstance_reclaim : circumstance_client, layout_state, spec, lr_layout->lrf_body.lrf_body_len, lr_layout->lrf_body.lrf_body_val, &deleted); STATELOCK_unlock(data->current_obj); if (res_LAYOUTRETURN4->lorr_status == NFS4_OK) { if (deleted) { /* Poison the current stateid */ data->current_stateid_valid = false; lorr_stateid->lrs_present = 0; } else { lorr_stateid->lrs_present = 1; /* Update stateid.seqid and copy to current */ update_stateid(layout_state, lrs_stateid, data, tag); } } if (!arg_LAYOUTRETURN4->lora_reclaim) dec_state_t_ref(layout_state); break; case LAYOUTRETURN4_FSID: nfs_status = nfs4_sanity_check_FH(data, NO_FILE_TYPE, false); if (nfs_status != NFS4_OK) { res_LAYOUTRETURN4->lorr_status = nfs_status; break; } fsid = data->current_obj->fsid; return_fsid = true; /* FALLTHROUGH */ case LAYOUTRETURN4_ALL: spec.io_mode = arg_LAYOUTRETURN4->lora_iomode; spec.offset = 0; spec.length = NFS4_UINT64_MAX; clientid_owner = &data->session->clientid_record->cid_owner; /* Initialize op_context */ init_op_context_simple(&op_context, NULL, NULL); /* We need the safe version because return_one_state * can delete the current state. * * Since we can not hold so_mutex (which protects the list) * the entire time, we will have to restart here after * dropping the mutex. * * Since we push each entry to the end of the list, we will * not need to continually examine entries that need to be * skipped, except for one final pass. * * An example flow might be: * skip 1 * skip 2 * do some work on 3 * restart * skip 4 * do some work on 5 * restart * skip 1 * skip 2 * skip 4 * done */ again: PTHREAD_MUTEX_lock(&clientid_owner->so_mutex); so_mutex_locked = true; first = NULL; state_list = &clientid_owner->so_owner.so_nfs4_owner.so_state_list; glist_for_each_safe(glist, glistn, state_list) { layout_state = glist_entry(glist, state_t, state_owner_list); if (first == NULL) first = layout_state; else if (first == layout_state) break; /* Move to end of list in case of error to ease * retries and push off dealing with non-layout * states (which should only be delegations). */ glist_move_tail(state_list, &layout_state->state_owner_list); if (layout_state->state_type != STATE_TYPE_LAYOUT) continue; if (!get_state_obj_export_owner_refs(layout_state, &obj, &export, NULL)) { /* This state is associated with a file or * export that is going stale, skip it (it * will be cleaned up as part of the stale * entry or export processing. */ continue; } /* Set up the root op context for this state */ op_ctx->clientid = &clientid_owner->so_owner .so_nfs4_owner.so_clientid; set_op_context_export(export); /* Take a reference on the state_t */ inc_state_t_ref(layout_state); /* Now we need to drop so_mutex to continue the * processing. */ PTHREAD_MUTEX_unlock(&clientid_owner->so_mutex); so_mutex_locked = false; if (return_fsid) { if (!memcmp(&fsid, &data->current_obj->fsid, sizeof(fsid))) { obj->obj_ops->put_ref(obj); dec_state_t_ref(layout_state); clear_op_context_export(); /* Since we had to drop so_mutex, the * list may have changed under us, we * MUST start over. */ goto again; } } STATELOCK_lock(obj); res_LAYOUTRETURN4->lorr_status = nfs4_return_one_state( obj, arg_LAYOUTRETURN4->lora_layoutreturn .lr_returntype, arg_LAYOUTRETURN4->lora_reclaim ? circumstance_reclaim : circumstance_client, layout_state, spec, 0, NULL, &deleted); STATELOCK_unlock(obj); /* Release the state_t reference */ dec_state_t_ref(layout_state); obj->obj_ops->put_ref(obj); clear_op_context_export(); if (res_LAYOUTRETURN4->lorr_status != NFS4_OK) break; /* Since we had to drop so_mutex, the list may have * changed under us, we MUST start over. */ goto again; } if (so_mutex_locked) PTHREAD_MUTEX_unlock(&clientid_owner->so_mutex); /* Poison the current stateid */ data->current_stateid_valid = false; lorr_stateid->lrs_present = 0; break; default: res_LAYOUTRETURN4->lorr_status = NFS4ERR_INVAL; } if (arg_LAYOUTRETURN4->lora_layoutreturn.lr_returntype == LAYOUTRETURN4_FSID || arg_LAYOUTRETURN4->lora_layoutreturn.lr_returntype == LAYOUTRETURN4_ALL) { /* Release the root op context we setup above */ release_op_context(); } return nfsstat4_to_nfs_req_result(res_LAYOUTRETURN4->lorr_status); } /* nfs41_op_layoutreturn */ /** * @brief Free memory allocated for LAYOUTRETURN result * * This function frees any memory allocated for the result from * the NFS4_OP_LAYOUTRETURN operation. * * @param[in] resp nfs4_op results * */ void nfs4_op_layoutreturn_Free(nfs_resop4 *resp) { /* Nothing to be done */ } /** * @brief Handle recalls corresponding to one stateid * * @note the st_lock MUST be held * * @param[in] args Layout return args * @param[in] ostate File state * @param[in] state The state in question * @param[in] segment Segment specified in return * */ void handle_recalls(struct fsal_layoutreturn_arg *arg, struct state_hdl *ostate, state_t *state, const struct pnfs_segment *segment) { /* Iterator over the recall list */ struct glist_head *recall_iter = NULL; /* Next recall for safe iteration */ struct glist_head *recall_next = NULL; struct glist_head *state_segments = &state->state_data.layout.state_segments; glist_for_each_safe(recall_iter, recall_next, &ostate->file.layoutrecall_list) { /* The current recall state */ struct state_layout_recall_file *r; /* Iteration on states */ struct glist_head *state_iter = NULL; /* Next entry in state list */ struct glist_head *state_next = NULL; r = glist_entry(recall_iter, struct state_layout_recall_file, entry_link); glist_for_each_safe(state_iter, state_next, &r->state_list) { struct recall_state_list *s; /* Iteration on segments */ struct glist_head *seg_iter = NULL; /* We found a segment that satisfies the recall */ bool satisfaction = false; s = glist_entry(state_iter, struct recall_state_list, link); if (s->state != state) continue; glist_for_each(seg_iter, state_segments) { struct state_layout_segment *g; g = glist_entry(seg_iter, struct state_layout_segment, sls_state_segments); if (!pnfs_segments_overlap(&g->sls_segment, segment)) { /* We don't even touch this */ break; } else if (!pnfs_segment_contains( segment, &g->sls_segment)) { /* Not satisfied completely */ } else satisfaction = true; } if (satisfaction && glist_length(state_segments) == 1) { dec_state_t_ref(s->state); glist_del(&s->link); arg->recall_cookies[arg->ncookies++] = r->recall_cookie; gsh_free(s); } } if (glist_empty(&r->state_list)) { /* Remove from entry->layoutrecall_list */ glist_del(&r->entry_link); gsh_free(r); } } } /** * @brief Return layouts corresponding to one stateid * * This function returns one or more layouts corresponding to a layout * stateid, calling FSAL_layoutreturn for each layout falling within * the specified range and iomode. If all layouts have been returned, * it deletes the state. * * @note The st_lock MUST be held * * @param[in] obj File whose layouts we return * @param[in] return_type Whether this is a file, fs, or server return * @param[in] circumstance Why the layout is being returned * @param[in,out] state State whose segments we return * @param[in] spec_segment Segment specified in return * @param[in] body_len Length of type-specific layout return data * @param[in] body_val Type-specific layout return data * @param[out] deleted True if the layout state has been deleted * * @return NFSv4.1 status codes */ nfsstat4 nfs4_return_one_state(struct fsal_obj_handle *obj, layoutreturn_type4 return_type, enum fsal_layoutreturn_circumstance circumstance, state_t *state, struct pnfs_segment spec_segment, size_t body_len, const void *body_val, bool *deleted) { /* Return from SAL calls */ state_status_t state_status = 0; /* Return from this function */ nfsstat4 nfs_status = 0; /* Iterator along segment list */ struct glist_head *seg_iter = NULL; /* Saved 'next' pointer for iterating over segment list */ struct glist_head *seg_next = NULL; /* Input arguments to FSAL_layoutreturn */ struct fsal_layoutreturn_arg *arg; /* XDR stream holding the lrf_body opaque */ XDR lrf_body; /* The beginning of the stream */ unsigned int beginning = 0; /* Number of recalls currently on the entry */ size_t recalls = 0; /* The current segment in iteration */ state_layout_segment_t *g = NULL; struct glist_head *state_segments = &state->state_data.layout.state_segments; recalls = glist_length(&obj->state_hdl->file.layoutrecall_list); if (body_val) { xdrmem_create(&lrf_body, (char *)body_val, /* Decoding won't modify */ body_len, XDR_DECODE); beginning = xdr_getpos(&lrf_body); } arg = alloca(sizeof(struct fsal_layoutreturn_arg) + sizeof(void *) * (recalls - 1)); memset(arg, 0, sizeof(struct fsal_layoutreturn_arg)); arg->circumstance = circumstance; arg->return_type = return_type; arg->spec_segment = spec_segment; arg->ncookies = 0; /** * @todo This is where you would want to record * layoutreturns. There are lots of things that are * effectively layoutreturns that don't go through the * nfs4_op_layoutreturn function, but they do all go through * here. For circumstance values of circumstance_client, * circumstance_roc, and circumstance_forgotten, it should * count as a legitimate client operation. * circumstance_revoke means that we attempted a recall and * the client misbehaved. circumstance_shutdown and * circumstance_reclaim are probably not worth dealing with. */ if (circumstance != circumstance_reclaim) { arg->lo_type = state->state_data.layout.state_layout_type; /* The _safe version of glist_for_each allows us to * delete segments while we iterate. */ glist_for_each_safe(seg_iter, seg_next, state_segments) { /* The current segment in iteration */ g = glist_entry(seg_iter, state_layout_segment_t, sls_state_segments); arg->cur_segment = g->sls_segment; arg->fsal_seg_data = g->sls_fsal_data; /* TODO: why this check does not work */ arg->last_segment = (seg_next->next == seg_next); if (pnfs_segment_contains(&spec_segment, &g->sls_segment)) { arg->dispose = true; } else if (pnfs_segments_overlap(&spec_segment, &g->sls_segment)) arg->dispose = false; else continue; handle_recalls(arg, obj->state_hdl, state, &g->sls_segment); nfs_status = obj->obj_ops->layoutreturn( obj, body_val ? &lrf_body : NULL, arg); if (nfs_status != NFS4_OK) goto out; if (arg->dispose) { state_status = state_delete_segment(g); if (state_status != STATE_SUCCESS) { nfs_status = nfs4_Errno_state(state_status); goto out; } } else { g->sls_segment = pnfs_segment_difference( &spec_segment, &g->sls_segment); } } if (body_val) { /* This really should work in all cases for an * in-memory decode stream. */ xdr_setpos(&lrf_body, beginning); } if (glist_empty(state_segments)) { state_del_locked(state); *deleted = true; } else *deleted = false; } else { /* For a reclaim return, there are no recorded segments in * state. */ arg->cur_segment.io_mode = 0; arg->cur_segment.offset = 0; arg->cur_segment.length = 0; arg->fsal_seg_data = NULL; arg->last_segment = false; arg->dispose = false; nfs_status = obj->obj_ops->layoutreturn( obj, body_val ? &lrf_body : NULL, arg); if (nfs_status != NFS4_OK) goto out; *deleted = true; } nfs_status = NFS4_OK; out: if (body_val) xdr_destroy(&lrf_body); return nfs_status; } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_link.c000066400000000000000000000125231473756622300215320ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file nfs4_op_link.c * \brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * * */ #include "config.h" #include #include #include #include #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs4.h" #include "nfs_core.h" #include "fsal.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include "nfs_file_handle.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief The NFS4_OP_LINK operation. * * This functions handles the NFS4_OP_LINK operation in NFSv4. This * function can be called only from nfs4_Compound. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 367 */ enum nfs_req_result nfs4_op_link(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { LINK4args *const arg_LINK4 = &op->nfs_argop4_u.oplink; LINK4res *const res_LINK4 = &resp->nfs_resop4_u.oplink; struct fsal_obj_handle *dir_obj = NULL; struct fsal_obj_handle *file_obj = NULL; fsal_status_t status = { 0, 0 }; struct fsal_attrlist destdir_pre_attrs, destdir_post_attrs; bool is_destdir_pre_attrs_valid, is_destdir_post_attrs_valid; GSH_AUTO_TRACEPOINT(nfs4, op_link_start, TRACE_INFO, "LINK arg: newname[{}]={}", arg_LINK4->newname.utf8string_len, TP_UTF8STR_TRUNCATED(arg_LINK4->newname)); resp->resop = NFS4_OP_LINK; res_LINK4->status = NFS4_OK; fsal_prepare_attrs(&destdir_pre_attrs, ATTR_CHANGE); fsal_prepare_attrs(&destdir_post_attrs, ATTR_CHANGE); /* Do basic checks on a filehandle */ res_LINK4->status = nfs4_sanity_check_FH(data, DIRECTORY, false); if (res_LINK4->status != NFS4_OK) goto out; res_LINK4->status = nfs4_sanity_check_saved_FH(data, -DIRECTORY, false); if (res_LINK4->status != NFS4_OK) goto out; /* Check that both handles are in the same export. */ if (op_ctx->ctx_export != NULL && data->saved_export != NULL && op_ctx->ctx_export->export_id != data->saved_export->export_id) { res_LINK4->status = NFS4ERR_XDEV; goto out; } /* * This operation creates a hard link, for the file * represented by the saved FH, in directory represented by * currentFH under the name arg_LINK4.target */ /* Validate and convert the UFT8 objname to a regular string */ res_LINK4->status = nfs4_utf8string_scan(&arg_LINK4->newname, UTF8_SCAN_PATH_COMP); if (res_LINK4->status != NFS4_OK) goto out; /* get info from compound data */ dir_obj = data->current_obj; res_LINK4->LINK4res_u.resok4.cinfo.before = fsal_get_changeid4(dir_obj); file_obj = data->saved_obj; /* make the link */ status = fsal_link(file_obj, dir_obj, arg_LINK4->newname.utf8string_val, &destdir_pre_attrs, &destdir_post_attrs); if (FSAL_IS_ERROR(status)) { res_LINK4->status = nfs4_Errno_status(status); goto out; } is_destdir_pre_attrs_valid = FSAL_TEST_MASK(destdir_pre_attrs.valid_mask, ATTR_CHANGE); if (is_destdir_pre_attrs_valid) { res_LINK4->LINK4res_u.resok4.cinfo.before = (changeid4)destdir_pre_attrs.change; } is_destdir_post_attrs_valid = FSAL_TEST_MASK(destdir_post_attrs.valid_mask, ATTR_CHANGE); if (is_destdir_post_attrs_valid) { res_LINK4->LINK4res_u.resok4.cinfo.after = (changeid4)destdir_post_attrs.change; } else { res_LINK4->LINK4res_u.resok4.cinfo.after = fsal_get_changeid4(dir_obj); } res_LINK4->LINK4res_u.resok4.cinfo.atomic = is_destdir_pre_attrs_valid && is_destdir_post_attrs_valid ? TRUE : FALSE; res_LINK4->status = NFS4_OK; out: fsal_release_attrs(&destdir_pre_attrs); fsal_release_attrs(&destdir_post_attrs); GSH_AUTO_TRACEPOINT( nfs4, op_link_end, TRACE_INFO, "LINK res: status={} " TP_CINFO_FORMAT, res_LINK4->status, TP_CINFO_ARGS_EXPAND(res_LINK4->LINK4res_u.resok4.cinfo)); return nfsstat4_to_nfs_req_result(res_LINK4->status); } /* nfs4_op_link */ /** * @brief Free memory allocated for LINK result * * This function frees any memory allocated for the result of the * NFS4_OP_LINK operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_link_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_lock.c000066400000000000000000000633761473756622300215410ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_lock.c * @brief Implementation of NFS4_OP_LOCK */ #include "config.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "gsh_list.h" #include "export_mgr.h" #include "nfs_rpc_callback.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif static const char *lock_tag = "LOCK"; /** * @brief The NFS4_OP_LOCK operation. * * This function implements the NFS4_OP_LOCK operation. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC 5661, pp. 367-8 * * @see nfs4_Compound * */ #define SUCCESS_RESP_SIZE (sizeof(nfsstat4) + sizeof(stateid4)) static void notify_granted_completion(rpc_call_t *call) { nfs41_release_single(call); } static state_status_t nfsv4_granted_callback(struct fsal_obj_handle *obj, state_lock_entry_t *lock_entry) { LogFullDebug(COMPONENT_NFS_V4_LOCK, "Sending granted callback"); int ret; nfs_cb_argop4 argop; CB_NOTIFY_LOCK4args *argslock = &argop.nfs_cb_argop4_u.opcbnotify_lock; state_nfsv4_block_data_t *bdata = &lock_entry->sle_block_data->sbd_prot.sbd_v4; argop.argop = NFS4_OP_CB_NOTIFY_LOCK; if (!nfs4_FSALToFhandle(true, &argslock->cnla_fh, obj, lock_entry->sle_export)) { LogCrit(COMPONENT_NFS_V4_LOCK, "Failed allocating handle"); return STATE_LOCK_BLOCKED; } argslock->cnla_lock_owner.owner.owner_val = gsh_calloc(1, lock_entry->sle_owner->so_owner_len); argslock->cnla_lock_owner.owner.owner_len = lock_entry->sle_owner->so_owner_len; memcpy(argslock->cnla_lock_owner.owner.owner_val, lock_entry->sle_owner->so_owner_val, lock_entry->sle_owner->so_owner_len); argslock->cnla_lock_owner.clientid = lock_entry->sle_owner->so_owner.so_nfs4_owner.so_clientid; ret = nfs_rpc_cb_single( lock_entry->sle_owner->so_owner.so_nfs4_owner.so_clientrec, &argop, NULL, notify_granted_completion, NULL); LogDebug(COMPONENT_FSAL_UP, "nfs_rpc_cb_single returned %d", ret); bdata->snbd_notified_eligible_time = time(NULL); gsh_free(argslock->cnla_lock_owner.owner.owner_val); nfs4_freeFH(&argslock->cnla_fh); return STATE_SUCCESS; } enum nfs_req_result nfs4_op_lock(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { /* Shorter alias for arguments */ LOCK4args *const arg_LOCK4 = &op->nfs_argop4_u.oplock; open_to_lock_owner4 *arg_open_owner = &arg_LOCK4->locker.locker4_u.open_owner; /* Shorter alias for response */ LOCK4res *const res_LOCK4 = &resp->nfs_resop4_u.oplock; /* Status code from state calls */ state_status_t state_status = STATE_SUCCESS; /* Data for lock state to be created */ union state_data candidate_data; /* Status code for protocol functions */ nfsstat4 nfs_status = 0; /* Created or found lock state */ state_t *lock_state = NULL; /* Associated open state */ state_t *state_open = NULL; /* The lock owner */ state_owner_t *lock_owner = NULL; /* The open owner */ state_owner_t *open_owner = NULL; /* The owner of a conflicting lock */ state_owner_t *conflict_owner = NULL; /* The owner in which to store the response for NFSv4.0 */ state_owner_t *resp_owner = NULL; /* Sequence ID, for NFSv4.0 */ seqid4 seqid = 0; /* The client performing these operations */ nfs_client_id_t *clientid = NULL; /* Name for the lock owner */ state_nfs4_owner_name_t owner_name; /* Description of requested lock */ fsal_lock_param_t lock_desc; /* Description of conflicting lock */ fsal_lock_param_t conflict_desc; /* Whether to block */ state_blocking_t blocking = STATE_NON_BLOCKING; /* Tracking data for the lock state */ struct state_refer refer; /* Indicate if we let FSAL to handle requests during grace. */ bool_t fsal_grace = false; bool_t have_grace_ref = false; int rc; struct fsal_obj_handle *obj = data->current_obj; bool st_lock_held = false; uint64_t maxfilesize = op_ctx->fsal_export->exp_ops.fs_maxfilesize( op_ctx->fsal_export); bool new_lock_state = false; state_block_data_t *pblock_data = NULL; LogDebug(COMPONENT_NFS_V4_LOCK, "Entering NFS v4 LOCK handler ----------------------"); if (arg_LOCK4->locker.new_lock_owner) { GSH_AUTO_TRACEPOINT( nfs4, op_lock_start_new, TRACE_INFO, "LOCK arg: type={} reclaim={} offset={} length={} open_seqid={} stateid={} lock_seqid={}", arg_LOCK4->locktype, arg_LOCK4->reclaim, arg_LOCK4->offset, arg_LOCK4->length, arg_open_owner->open_seqid, arg_open_owner->open_stateid.seqid, arg_open_owner->lock_seqid); GSH_AUTO_TRACEPOINT( nfs4, op_lock_start_owner, TRACE_INFO, "LOCK arg: owner[{}]={}", arg_open_owner->lock_owner.owner.owner_len, TP_BYTE_ARR_TRUNCATED( arg_open_owner->lock_owner.owner.owner_val, arg_open_owner->lock_owner.owner.owner_len)); } else GSH_AUTO_TRACEPOINT( nfs4, op_lock_start_existing, TRACE_INFO, "LOCK arg: type={} reclaim={} offset={} length={} stateid={} seqid={}", arg_LOCK4->locktype, arg_LOCK4->reclaim, arg_LOCK4->offset, arg_LOCK4->length, arg_LOCK4->locker.locker4_u.lock_owner.lock_stateid .seqid, arg_LOCK4->locker.locker4_u.lock_owner.lock_seqid); /* Initialize to sane starting values */ resp->resop = NFS4_OP_LOCK; /* Before starting, make sure we have room for succeseful response so * we don't have to undo a successful lock operation (that may not be * reversible if it overlaps an existing lock). */ res_LOCK4->status = check_resp_room(data, SUCCESS_RESP_SIZE); if (res_LOCK4->status != NFS4_OK) return NFS_REQ_ERROR; /* Record the sequence info */ if (data->minorversion > 0) { memcpy(refer.session, data->session->session_id, sizeof(sessionid4)); refer.sequence = data->sequence; refer.slot = data->slotid; } res_LOCK4->status = nfs4_sanity_check_FH(data, REGULAR_FILE, false); if (res_LOCK4->status != NFS4_OK) return NFS_REQ_ERROR; /* Convert lock parameters to internal types */ switch (arg_LOCK4->locktype) { case READW_LT: blocking = STATE_BLOCKING; /* Fall through */ case READ_LT: lock_desc.lock_type = FSAL_LOCK_R; break; case WRITEW_LT: blocking = STATE_BLOCKING; /* Fall through */ case WRITE_LT: lock_desc.lock_type = FSAL_LOCK_W; break; default: LogDebug(COMPONENT_NFS_V4_LOCK, "Invalid lock type"); res_LOCK4->status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } lock_desc.lock_start = arg_LOCK4->offset; lock_desc.lock_sle_type = FSAL_POSIX_LOCK; lock_desc.lock_reclaim = arg_LOCK4->reclaim; if (arg_LOCK4->length != STATE_LOCK_OFFSET_EOF) lock_desc.lock_length = arg_LOCK4->length; else lock_desc.lock_length = 0; if (arg_LOCK4->locker.new_lock_owner) { /* Check stateid correctness and get pointer to state */ nfs_status = nfs4_Check_Stateid( &arg_open_owner->open_stateid, obj, &state_open, data, STATEID_SPECIAL_FOR_LOCK, arg_open_owner->open_seqid, data->minorversion == 0, lock_tag); if (nfs_status != NFS4_OK) { if (nfs_status == NFS4ERR_REPLAY) { open_owner = get_state_owner_ref(state_open); LogStateOwner("Open: ", open_owner); if (open_owner != NULL) { resp_owner = open_owner; seqid = arg_LOCK4->locker.locker4_u .open_owner.open_seqid; goto check_seqid; } } res_LOCK4->status = nfs_status; LogDebug( COMPONENT_NFS_V4_LOCK, "LOCK failed nfs4_Check_Stateid for open owner"); goto out2; } open_owner = get_state_owner_ref(state_open); LogStateOwner("Open: ", open_owner); if (open_owner == NULL) { /* State is going stale. */ res_LOCK4->status = NFS4ERR_STALE; LogDebug( COMPONENT_NFS_V4_LOCK, "LOCK failed nfs4_Check_Stateid, stale open owner"); goto out2; } lock_state = NULL; lock_owner = NULL; resp_owner = open_owner; seqid = arg_open_owner->open_seqid; LogLock(COMPONENT_NFS_V4_LOCK, NIV_FULL_DEBUG, "LOCK New lock owner from open owner", obj, open_owner, &lock_desc); /* Check is the clientid is known or not */ rc = nfs_client_id_get_confirmed( data->minorversion == 0 ? arg_open_owner->lock_owner.clientid : data->session->clientid, &clientid); if (rc != CLIENT_ID_SUCCESS) { res_LOCK4->status = clientid_error_to_nfsstat(rc); LogDebug(COMPONENT_NFS_V4_LOCK, "LOCK failed nfs_client_id_get"); goto out2; } if (isDebug(COMPONENT_CLIENTID) && (clientid != open_owner->so_owner.so_nfs4_owner.so_clientrec)) { char str_open[LOG_BUFF_LEN / 2] = "\0"; struct display_buffer dspbuf_open = { sizeof(str_open), str_open, str_open }; char str_lock[LOG_BUFF_LEN / 2] = "\0"; struct display_buffer dspbuf_lock = { sizeof(str_lock), str_lock, str_lock }; display_client_id_rec( &dspbuf_open, open_owner->so_owner.so_nfs4_owner.so_clientrec); display_client_id_rec(&dspbuf_lock, clientid); LogDebug( COMPONENT_CLIENTID, "Unexpected, new lock owner clientid {%s} doesn't match open owner clientid {%s}", str_lock, str_open); } /* The related stateid is already stored in state_open */ /* An open state has been found. Check its type */ if (state_open->state_type != STATE_TYPE_SHARE) { res_LOCK4->status = NFS4ERR_BAD_STATEID; LogDebug(COMPONENT_NFS_V4_LOCK, "LOCK failed open stateid is not a SHARE"); goto out2; } /* Is this lock_owner known ? */ convert_nfs4_lock_owner(&arg_open_owner->lock_owner, &owner_name); LogStateOwner("Lock: ", lock_owner); } else { /* Existing lock owner Find the lock stateid From * that, get the open_owner * * There was code here before to handle all-0 stateid, * but that really doesn't apply - when we handle * temporary locks for I/O operations (which is where * we will see all-0 or all-1 stateid, those will not * come in through nfs4_op_lock. * * Check stateid correctness and get pointer to state */ nfs_status = nfs4_Check_Stateid( &arg_LOCK4->locker.locker4_u.lock_owner.lock_stateid, obj, &lock_state, data, STATEID_SPECIAL_FOR_LOCK, arg_LOCK4->locker.locker4_u.lock_owner.lock_seqid, data->minorversion == 0, lock_tag); if (nfs_status != NFS4_OK) { if (nfs_status == NFS4ERR_REPLAY) { lock_owner = get_state_owner_ref(lock_state); LogStateOwner("Lock: ", lock_owner); if (lock_owner != NULL) { open_owner = lock_owner->so_owner .so_nfs4_owner .so_related_owner; inc_state_owner_ref(open_owner); resp_owner = lock_owner; seqid = arg_LOCK4->locker.locker4_u .lock_owner.lock_seqid; goto check_seqid; } } res_LOCK4->status = nfs_status; LogDebug( COMPONENT_NFS_V4_LOCK, "LOCK failed nfs4_Check_Stateid for existing lock owner"); goto out2; } /* Check if lock state belongs to same export */ if (!state_same_export(lock_state, op_ctx->ctx_export)) { LogEvent( COMPONENT_STATE, "Lock Owner Export Conflict, Lock held for export %" PRIu16 " request for export %" PRIu16, state_export_id(lock_state), op_ctx->ctx_export->export_id); res_LOCK4->status = NFS4ERR_INVAL; goto out2; } /* A lock state has been found. Check its type */ if (lock_state->state_type != STATE_TYPE_LOCK) { res_LOCK4->status = NFS4ERR_BAD_STATEID; LogDebug( COMPONENT_NFS_V4_LOCK, "LOCK failed existing lock owner, state type is not LOCK"); goto out2; } /* Get the old lockowner. We can do the following * 'cast', in NFSv4 lock_owner4 and open_owner4 are * different types but with the same definition */ lock_owner = get_state_owner_ref(lock_state); LogStateOwner("Lock: ", lock_owner); if (lock_owner == NULL) { /* State is going stale. */ res_LOCK4->status = NFS4ERR_STALE; LogDebug( COMPONENT_NFS_V4_LOCK, "LOCK failed nfs4_Check_Stateid, stale open owner"); goto out2; } open_owner = lock_owner->so_owner.so_nfs4_owner.so_related_owner; LogStateOwner("Open: ", open_owner); inc_state_owner_ref(open_owner); state_open = nfs4_State_Get_Pointer( lock_state->state_data.lock.openstate_key); if (state_open == NULL) { res_LOCK4->status = NFS4ERR_BAD_STATEID; goto out2; } resp_owner = lock_owner; seqid = arg_LOCK4->locker.locker4_u.lock_owner.lock_seqid; LogLock(COMPONENT_NFS_V4_LOCK, NIV_FULL_DEBUG, "LOCK Existing lock owner", obj, lock_owner, &lock_desc); /* Get the client for this open owner */ clientid = open_owner->so_owner.so_nfs4_owner.so_clientrec; inc_client_id_ref(clientid); } check_seqid: /* Check seqid (lock_seqid or open_seqid) */ if (data->minorversion == 0) { if (!Check_nfs4_seqid(resp_owner, seqid, op, obj, resp, lock_tag)) { /* Response is all setup for us and LogDebug * told what was wrong */ goto out2; } } /* Lock length should not be 0 */ if (arg_LOCK4->length == 0LL) { res_LOCK4->status = NFS4ERR_INVAL; LogDebug(COMPONENT_NFS_V4_LOCK, "LOCK failed length == 0"); goto out; } /* Check for range overflow. Comparing beyond 2^64 is not * possible int 64 bits precision, but off+len > 2^64-1 is * equivalent to len > 2^64-1 - off */ if (lock_desc.lock_length > (STATE_LOCK_OFFSET_EOF - lock_desc.lock_start)) { res_LOCK4->status = NFS4ERR_INVAL; LogDebug(COMPONENT_NFS_V4_LOCK, "LOCK failed length overflow start %" PRIx64 " length %" PRIx64, lock_desc.lock_start, lock_desc.lock_length); goto out; } /* Check for range overflow past maxfilesize. Comparing beyond 2^64 is * not possible in 64 bits precision, but off+len > maxfilesize is * equivalent to len > maxfilesize - off. We checked 64-bit overflow * above, so treat overflowing the FS maxsize as a request to lock the * entire file. */ if (lock_desc.lock_length > (maxfilesize - lock_desc.lock_start)) { LogDebug(COMPONENT_NFS_V4_LOCK, "LOCK past maxfilesize %" PRIx64 " start %" PRIx64 " length %" PRIx64, maxfilesize, lock_desc.lock_start, lock_desc.lock_length); lock_desc.lock_length = 0; } /* Check if open state has correct access for type of lock. * * Don't need to check for conflicting states since this open * state assures there are no conflicting states. */ if (((arg_LOCK4->locktype == WRITE_LT || arg_LOCK4->locktype == WRITEW_LT) && ((state_open->state_data.share.share_access & OPEN4_SHARE_ACCESS_WRITE) == 0)) || ((arg_LOCK4->locktype == READ_LT || arg_LOCK4->locktype == READW_LT) && ((state_open->state_data.share.share_access & OPEN4_SHARE_ACCESS_READ) == 0))) { /* The open state doesn't allow access based on the * type of lock */ LogLock(COMPONENT_NFS_V4_LOCK, NIV_DEBUG, "LOCK failed, SHARE doesn't allow access", obj, lock_owner, &lock_desc); res_LOCK4->status = NFS4ERR_OPENMODE; goto out; } fsal_grace = op_ctx->fsal_export->exp_ops.fs_supports( op_ctx->fsal_export, fso_grace_method); /* Do grace period checking (use resp_owner below since a new * lock request with a new lock owner doesn't have a lock owner * yet, but does have an open owner - resp_owner is always one or * the other and non-NULL at this point - so makes for a better log). */ if (!fsal_grace) { if (arg_LOCK4->reclaim) { if (!clientid->cid_allow_reclaim) { LogLock(COMPONENT_NFS_V4_LOCK, NIV_DEBUG, "LOCK failed, invalid reclaim while in grace", obj, resp_owner, &lock_desc); res_LOCK4->status = NFS4ERR_NO_GRACE; goto out; } if (!nfs_get_grace_status(true)) { LogLock(COMPONENT_NFS_V4_LOCK, NIV_DEBUG, "LOCK failed, reclaim while not in grace", obj, resp_owner, &lock_desc); res_LOCK4->status = NFS4ERR_NO_GRACE; goto out; } } else { if (!nfs_get_grace_status(false)) { LogLock(COMPONENT_NFS_V4_LOCK, NIV_DEBUG, "LOCK failed, non-reclaim while in grace", obj, resp_owner, &lock_desc); res_LOCK4->status = NFS4ERR_GRACE; goto out; } } have_grace_ref = true; } /* Test if this request is attempting to create a new lock owner */ if (arg_LOCK4->locker.new_lock_owner) { bool_t isnew; /* A lock owner is always associated with a previously made open which has itself a previously made stateid */ /* This lock owner is not known yet, allocated and set up a new one */ lock_owner = create_nfs4_owner(&owner_name, clientid, STATE_LOCK_OWNER_NFSV4, open_owner, 0, &isnew, CARE_ALWAYS, true); LogStateOwner("Lock: ", lock_owner); if (lock_owner == NULL) { res_LOCK4->status = NFS4ERR_RESOURCE; LogLock(COMPONENT_NFS_V4_LOCK, NIV_EVENT, "LOCK failed to create new lock owner", obj, open_owner, &lock_desc); goto out2; } if (!isnew) { PTHREAD_MUTEX_lock(&lock_owner->so_mutex); /* Check lock_seqid if it has attached locks. */ if (!glist_empty(&lock_owner->so_lock_list) && (data->minorversion == 0) && !Check_nfs4_seqid(lock_owner, arg_open_owner->lock_seqid, op, obj, resp, lock_tag)) { LogLock(COMPONENT_NFS_V4_LOCK, NIV_DEBUG, "LOCK failed to create new lock owner, re-use", obj, open_owner, &lock_desc); dump_all_locks( "All locks (re-use of lock owner)"); PTHREAD_MUTEX_unlock(&lock_owner->so_mutex); /* Response is all setup for us and * LogDebug told what was wrong */ goto out2; } PTHREAD_MUTEX_unlock(&lock_owner->so_mutex); /* Lock owner is known, see if we also already have * a stateid. Do this here since it's impossible for * there to be such a state if the lock owner was * previously unknown. * This handles 4.0 replay locks with an open stateid * and new_lock_owner == true. It also handles * situations where all locks have been released and * the client is claiming a new lock owner to get a * new stateid, we will attempt to recycle. */ STATELOCK_lock(obj); st_lock_held = true; lock_state = nfs4_State_Get_Obj(obj, lock_owner); } else { /* Take the st_lock now */ STATELOCK_lock(obj); st_lock_held = true; } if (lock_state == NULL) { /* Check again whether state_open exist in hashtable * with the guard of STATELOCK. If does not exist, * it means file already closed. */ if (nfs4_State_Get_Pointer(state_open->stateid_other) == NULL) { res_LOCK4->status = NFS4ERR_BAD_STATEID; goto out2; } dec_state_t_ref(state_open); /* Prepare state management structure */ memset(&candidate_data, 0, sizeof(candidate_data)); memcpy(candidate_data.lock.openstate_key, state_open->stateid_other, OTHERSIZE); /* Add the lock state to the lock table */ state_status = state_add_impl( obj, STATE_TYPE_LOCK, &candidate_data, lock_owner, &lock_state, data->minorversion > 0 ? &refer : NULL); if (state_status != STATE_SUCCESS) { res_LOCK4->status = NFS4ERR_RESOURCE; LogLock(COMPONENT_NFS_V4_LOCK, NIV_DEBUG, "LOCK failed to add new stateid", obj, lock_owner, &lock_desc); goto out2; } new_lock_state = true; glist_init(&lock_state->state_data.lock.state_locklist); /* Add lock state to the list of lock states belonging to the open state */ glist_add_tail( &state_open->state_data.share.share_lockstates, &lock_state->state_data.lock.state_sharelist); } } else { /* Take the st_lock now */ STATELOCK_lock(obj); st_lock_held = true; } if (data->minorversion == 0) { op_ctx->clientid = &lock_owner->so_owner.so_nfs4_owner.so_clientid; } /* Handle race with CLOSE and LOCK. Buggy clients could send * CLOSE and LOCK requests at the same time for the same open * stateid. Make sure the open state is still in the hash * table. If we win here, CLOSE will fail. If CLOSE wins, we * fail this LOCK request as though the open state wasn't * available. * * A state is removed from the hash table while holding * state_lock, and state_owner field is set to NULL in * _state_del_locked()). We already have the state_lock so we * can safely check if the state_owner is still valid. */ if (state_open->state_owner == NULL) { if (new_lock_state) { /* Need to destroy new state */ state_del_locked(lock_state); } res_LOCK4->status = NFS4ERR_BAD_STATEID; goto out2; } if (blocking == STATE_BLOCKING) { pblock_data = gsh_calloc(1, sizeof(*pblock_data)); pblock_data->sbd_granted_callback = nfsv4_granted_callback; pblock_data->sbd_prot.sbd_v4.snbd_last_poll_time = time(NULL); pblock_data->sbd_prot.sbd_v4.snbd_notified_eligible_time = 0; } /* Now we have a lock owner and a stateid. Go ahead and push * lock into SAL (and FSAL). */ state_status = state_lock(obj, lock_owner, lock_state, blocking, LOCK_NFSv4, &pblock_data, &lock_desc, &conflict_owner, &conflict_desc); /* If we didn't block, release the block data. Note that * state_lock() would set pblock_data to NULL if the lock was * blocked! */ gsh_free(pblock_data); if (state_status != STATE_SUCCESS) { LogDebug(COMPONENT_NFS_V4_LOCK, "LOCK failed with status %s", state_err_str(state_status)); if (state_status == STATE_LOCK_CONFLICT || state_status == STATE_LOCK_BLOCKED) { /* A conflicting lock from a different lock_owner, * returns NFS4ERR_DENIED, but check that the * response will fit, if not, return response error. */ res_LOCK4->status = Process_nfs4_conflict( &res_LOCK4->LOCK4res_u.denied, conflict_owner, &conflict_desc, data); } else { res_LOCK4->status = nfs4_Errno_state(state_status); } /* Save the response in the lock or open owner */ if (res_LOCK4->status != NFS4ERR_RESOURCE && res_LOCK4->status != NFS4ERR_BAD_STATEID && data->minorversion == 0) { Copy_nfs4_state_req(resp_owner, seqid, op, obj, resp, lock_tag); } if (new_lock_state && state_status != STATE_LOCK_BLOCKED) { /* Keep the state only if it the lock was granted or blocked */ state_del_locked(lock_state); } goto out2; } if (data->minorversion == 0) op_ctx->clientid = NULL; res_LOCK4->status = NFS4_OK; data->op_resp_size = SUCCESS_RESP_SIZE; /* Handle stateid/seqid for success */ update_stateid(lock_state, &res_LOCK4->LOCK4res_u.resok4.lock_stateid, data, lock_tag); if (arg_LOCK4->locker.new_lock_owner) { /* Also save the response in the lock owner */ Copy_nfs4_state_req(lock_owner, arg_open_owner->lock_seqid, op, obj, resp, lock_tag); } if (isFullDebug(COMPONENT_NFS_V4_LOCK)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_stateid(&dspbuf, lock_state); LogFullDebug(COMPONENT_NFS_V4_LOCK, "LOCK stateid %s", str); } LogLock(COMPONENT_NFS_V4_LOCK, NIV_FULL_DEBUG, "LOCK applied", obj, lock_owner, &lock_desc); out: if (data->minorversion == 0) { /* Save the response in the lock or open owner */ Copy_nfs4_state_req(resp_owner, seqid, op, obj, resp, lock_tag); } out2: if (have_grace_ref) nfs_put_grace_status(); if (st_lock_held) { /* Now release the st_lock */ STATELOCK_unlock(obj); } if (state_open != NULL) dec_state_t_ref(state_open); if (lock_state != NULL) dec_state_t_ref(lock_state); LogStateOwner("Open: ", open_owner); LogStateOwner("Lock: ", lock_owner); if (open_owner != NULL) dec_state_owner_ref(open_owner); if (lock_owner != NULL) dec_state_owner_ref(lock_owner); if (clientid != NULL) dec_client_id_ref(clientid); if (res_LOCK4->status == NFS4_OK) GSH_AUTO_TRACEPOINT( nfs4, op_lock_end_ok, TRACE_INFO, "LOCK res: status={} stateid={}", res_LOCK4->status, res_LOCK4->LOCK4res_u.resok4.lock_stateid.seqid); else if (res_LOCK4->status == NFS4ERR_DENIED) { const lock_owner4 *const owner = &res_LOCK4->LOCK4res_u.denied.owner; GSH_AUTO_TRACEPOINT( nfs4, op_lock_end_denied, TRACE_INFO, "LOCK conflict: status={} offset={} length={} type={} clientid={}", res_LOCK4->status, res_LOCK4->LOCK4res_u.denied.offset, res_LOCK4->LOCK4res_u.denied.length, res_LOCK4->LOCK4res_u.denied.locktype, owner->clientid); GSH_AUTO_TRACEPOINT( nfs4, op_lock_end_denied_owner, TRACE_INFO, "LOCK conflict: owner[{}]={}", owner->owner.owner_len, TP_BYTE_ARR_TRUNCATED(owner->owner.owner_val, owner->owner.owner_len)); } else GSH_AUTO_TRACEPOINT(nfs4, op_lock_end_err, TRACE_INFO, "LOCK res: status={}", res_LOCK4->status); return nfsstat4_to_nfs_req_result(res_LOCK4->status); } /* nfs4_op_lock */ /** * @brief Free memory allocated for LOCK result * * This function frees any memory allocated for the result of the * NFS4_OP_LOCK operation. * * @param[in,out] resp nfs4_op results * */ void nfs4_op_lock_Free(nfs_resop4 *res) { LOCK4res *resp = &res->nfs_resop4_u.oplock; if (resp->status == NFS4ERR_DENIED) Release_nfs4_denied(&resp->LOCK4res_u.denied); } /* nfs4_op_lock_Free */ void nfs4_op_lock_CopyRes(LOCK4res *res_dst, LOCK4res *res_src) { if (res_src->status == NFS4ERR_DENIED) Copy_nfs4_denied(&res_dst->LOCK4res_u.denied, &res_src->LOCK4res_u.denied); } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_lockt.c000066400000000000000000000211111473756622300217020ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_lockt.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include #include #include #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * * @brief The NFS4_OP_LOCKT operation * * This function implements the NFS4_OP_LOCKT operation. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 368 * * @see nfs4_Compound */ enum nfs_req_result nfs4_op_lockt(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { /* Alias for arguments */ LOCKT4args *const arg_LOCKT4 = &op->nfs_argop4_u.oplockt; /* Alias for response */ LOCKT4res *const res_LOCKT4 = &resp->nfs_resop4_u.oplockt; /* Return code from state calls */ state_status_t state_status = STATE_SUCCESS; /* Client id record */ nfs_client_id_t *clientid = NULL; /* Lock owner name */ state_nfs4_owner_name_t owner_name; /* Lock owner record */ state_owner_t *lock_owner = NULL; /* Owner of conflicting lock */ state_owner_t *conflict_owner = NULL; /* Description of lock to test */ fsal_lock_param_t lock_desc = { FSAL_POSIX_LOCK, FSAL_NO_LOCK, 0, 0, false }; /* Description of conflicting lock */ fsal_lock_param_t conflict_desc; /* return code from id confirm calls */ int rc; /* stateid if available matching owner and entry */ state_t *state; uint64_t maxfilesize = op_ctx->fsal_export->exp_ops.fs_maxfilesize( op_ctx->fsal_export); LogDebug(COMPONENT_NFS_V4_LOCK, "Entering NFS v4 LOCKT handler ----------------------------"); GSH_AUTO_TRACEPOINT( nfs4, op_lockt_start, TRACE_INFO, "LOCKT arg: type={} offset={} length={} owner[{}]={}", arg_LOCKT4->locktype, arg_LOCKT4->offset, arg_LOCKT4->length, arg_LOCKT4->owner.owner.owner_len, TP_BYTE_ARR_TRUNCATED(arg_LOCKT4->owner.owner.owner_val, arg_LOCKT4->owner.owner.owner_len)); /* Initialize to sane default */ resp->resop = NFS4_OP_LOCKT; res_LOCKT4->status = nfs4_sanity_check_FH(data, REGULAR_FILE, false); if (res_LOCKT4->status != NFS4_OK) return NFS_REQ_ERROR; /* Lock length should not be 0 */ if (arg_LOCKT4->length == 0LL) { res_LOCKT4->status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } if (!nfs_get_grace_status(false)) { res_LOCKT4->status = NFS4ERR_GRACE; return NFS_REQ_ERROR; } /* Convert lock parameters to internal types */ switch (arg_LOCKT4->locktype) { case READ_LT: case READW_LT: lock_desc.lock_type = FSAL_LOCK_R; break; case WRITE_LT: case WRITEW_LT: lock_desc.lock_type = FSAL_LOCK_W; break; default: LogDebug(COMPONENT_NFS_V4_LOCK, "Invalid lock type"); res_LOCKT4->status = NFS4ERR_INVAL; goto out; } lock_desc.lock_start = arg_LOCKT4->offset; if (arg_LOCKT4->length != STATE_LOCK_OFFSET_EOF) lock_desc.lock_length = arg_LOCKT4->length; else lock_desc.lock_length = 0; /* Check for range overflow. Comparing beyond 2^64 is not * possible in 64 bit precision, but off+len > 2^64-1 is * equivalent to len > 2^64-1 - off */ if (lock_desc.lock_length > (STATE_LOCK_OFFSET_EOF - lock_desc.lock_start)) { res_LOCKT4->status = NFS4ERR_INVAL; LogDebug(COMPONENT_NFS_V4_LOCK, "LOCK failed length overflow start %" PRIx64 " length %" PRIx64, lock_desc.lock_start, lock_desc.lock_length); goto out; } /* Check for range overflow past maxfilesize. Comparing beyond 2^64 is * not possible in 64 bits precision, but off+len > maxfilesize is * equivalent to len > maxfilesize - off. We checked 64-bit overflow * above, so treat overflowing the FS maxsize as a request to lock the * entire file. */ if (lock_desc.lock_length > (maxfilesize - lock_desc.lock_start)) { LogDebug(COMPONENT_NFS_V4_LOCK, "LOCK past maxfilesize %" PRIx64 " start %" PRIx64 " length %" PRIx64, maxfilesize, lock_desc.lock_start, lock_desc.lock_length); lock_desc.lock_length = 0; } /* Check clientid */ rc = nfs_client_id_get_confirmed(data->minorversion == 0 ? arg_LOCKT4->owner.clientid : data->session->clientid, &clientid); if (rc != CLIENT_ID_SUCCESS) { res_LOCKT4->status = clientid_error_to_nfsstat(rc); goto out; } if (data->minorversion == 0 && !reserve_lease_or_expire(clientid, false, NULL)) { res_LOCKT4->status = NFS4ERR_EXPIRED; goto out_clientid; } /* Is this lock_owner known ? */ convert_nfs4_lock_owner(&arg_LOCKT4->owner, &owner_name); /* This lock owner is not known yet, allocated and set up a new one */ lock_owner = create_nfs4_owner(&owner_name, clientid, STATE_LOCK_OWNER_NFSV4, NULL, 0, NULL, CARE_ALWAYS, true); LogStateOwner("Lock: ", lock_owner); if (lock_owner == NULL) { LogEvent(COMPONENT_NFS_V4_LOCK, "LOCKT unable to create lock owner"); res_LOCKT4->status = NFS4ERR_SERVERFAULT; goto out_clientid; } LogLock(COMPONENT_NFS_V4_LOCK, NIV_FULL_DEBUG, "LOCKT", data->current_obj, lock_owner, &lock_desc); if (data->minorversion == 0) { op_ctx->clientid = &lock_owner->so_owner.so_nfs4_owner.so_clientid; } /* Get the stateid, if any, related to this entry and owner */ state = nfs4_State_Get_Obj(data->current_obj, lock_owner); /* Now we have a lock owner and a stateid. Go ahead and test * the lock in SAL (and FSAL). */ state_status = state_test(data->current_obj, state, lock_owner, &lock_desc, &conflict_owner, &conflict_desc); if (state_status == STATE_LOCK_CONFLICT) { /* A conflicting lock from a different lock_owner, * returns NFS4ERR_DENIED */ LogStateOwner("Conflict: ", conflict_owner); res_LOCKT4->status = Process_nfs4_conflict( &res_LOCKT4->LOCKT4res_u.denied, conflict_owner, &conflict_desc, data); } else { /* Return result */ res_LOCKT4->status = nfs4_Errno_state(state_status); /* response is just nfsstat4 */ data->op_resp_size = sizeof(nfsstat4); } if (data->minorversion == 0) op_ctx->clientid = NULL; /* Release NFS4 Open Owner reference */ dec_state_owner_ref(lock_owner); /* Release stateid reference */ if (state != NULL) dec_state_t_ref(state); out_clientid: /* Update the lease before exit */ if (data->minorversion == 0) update_lease_simple(clientid); dec_client_id_ref(clientid); out: nfs_put_grace_status(); if (res_LOCKT4->status == NFS4ERR_DENIED) { const LOCK4denied *const denied = &res_LOCKT4->LOCKT4res_u.denied; GSH_AUTO_TRACEPOINT( nfs4, op_lockt_end_denied, TRACE_INFO, "LOCKT denied: status={} offset={} length={} type={} owner[{}] = {}", res_LOCKT4->status, denied->offset, denied->length, denied->locktype, denied->owner.owner.owner_len, TP_BYTE_ARR_TRUNCATED(denied->owner.owner.owner_val, denied->owner.owner.owner_len)); } else GSH_AUTO_TRACEPOINT(nfs4, op_lockt_end, TRACE_INFO, "LOCKT res: status={}", res_LOCKT4->status); return nfsstat4_to_nfs_req_result(res_LOCKT4->status); } /* nfs4_op_lockt */ /** * @brief Free memory allocated for LOCKT result * * This function frees any memory allocated for the result of the * NFS4_OP_LOCKT operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_lockt_Free(nfs_resop4 *res) { LOCKT4res *resp = &res->nfs_resop4_u.oplockt; if (resp->status == NFS4ERR_DENIED) Release_nfs4_denied(&resp->LOCKT4res_u.denied); } /* nfs4_op_lockt_Free */ nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_locku.c000066400000000000000000000163631473756622300217200ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_locku.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * * */ #include "config.h" #include #include #include #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif static const char *locku_tag = "LOCKU"; /** * * @brief The NFS4_OP_LOCKU operation * * This function implements the NFS4_OP_LOCKU operation. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 368 * * @see nfs4_Compound */ enum nfs_req_result nfs4_op_locku(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { /* Alias for arguments */ LOCKU4args *const arg_LOCKU4 = &op->nfs_argop4_u.oplocku; /* Alias for response */ LOCKU4res *const res_LOCKU4 = &resp->nfs_resop4_u.oplocku; /* Return for state functions */ state_status_t state_status = STATE_SUCCESS; /* Found lock state */ state_t *state_found = NULL; /* Owner of lock state */ state_owner_t *lock_owner = NULL; /* Description of lock to free */ fsal_lock_param_t lock_desc; /* */ nfsstat4 nfs_status = NFS4_OK; uint64_t maxfilesize = op_ctx->fsal_export->exp_ops.fs_maxfilesize( op_ctx->fsal_export); LogDebug(COMPONENT_NFS_V4_LOCK, "Entering NFS v4 LOCKU handler ----------------------------"); GSH_AUTO_TRACEPOINT( nfs4, op_locku_start, TRACE_INFO, "LOCKU arg: type={} seqid={} stateid={} offset={} length={}", arg_LOCKU4->locktype, arg_LOCKU4->seqid, arg_LOCKU4->lock_stateid.seqid, arg_LOCKU4->offset, arg_LOCKU4->length); /* Initialize to sane default */ resp->resop = NFS4_OP_LOCKU; res_LOCKU4->status = NFS4_OK; res_LOCKU4->status = nfs4_sanity_check_FH(data, REGULAR_FILE, false); if (res_LOCKU4->status != NFS4_OK) return NFS_REQ_ERROR; /* Convert lock parameters to internal types */ switch (arg_LOCKU4->locktype) { case READ_LT: case READW_LT: lock_desc.lock_type = FSAL_LOCK_R; break; case WRITE_LT: case WRITEW_LT: lock_desc.lock_type = FSAL_LOCK_W; break; default: LogDebug(COMPONENT_NFS_V4_LOCK, "Invalid lock type"); res_LOCKU4->status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } lock_desc.lock_start = arg_LOCKU4->offset; lock_desc.lock_sle_type = FSAL_POSIX_LOCK; lock_desc.lock_reclaim = false; if (arg_LOCKU4->length != STATE_LOCK_OFFSET_EOF) lock_desc.lock_length = arg_LOCKU4->length; else lock_desc.lock_length = 0; /* Check stateid correctness and get pointer to state */ nfs_status = nfs4_Check_Stateid(&arg_LOCKU4->lock_stateid, data->current_obj, &state_found, data, STATEID_SPECIAL_FOR_LOCK, arg_LOCKU4->seqid, data->minorversion == 0, locku_tag); if (nfs_status != NFS4_OK && nfs_status != NFS4ERR_REPLAY) { res_LOCKU4->status = nfs_status; return NFS_REQ_ERROR; } lock_owner = get_state_owner_ref(state_found); if (lock_owner == NULL) { /* State is going stale. */ res_LOCKU4->status = NFS4ERR_STALE; LogDebug(COMPONENT_NFS_V4_LOCK, "UNLOCK failed nfs4_Check_Stateid, stale lock owner"); goto out3; } /* Check seqid (lock_seqid or open_seqid) */ if (data->minorversion == 0) { if (!Check_nfs4_seqid(lock_owner, arg_LOCKU4->seqid, op, data->current_obj, resp, locku_tag)) { /* Response is all setup for us and LogDebug * told what was wrong */ goto out2; } } /* Lock length should not be 0 */ if (arg_LOCKU4->length == 0LL) { res_LOCKU4->status = NFS4ERR_INVAL; goto out; } /* Check for range overflow Remember that a length with all * bits set to 1 means "lock until the end of file" (RFC3530, * page 157) */ if (lock_desc.lock_length > (STATE_LOCK_OFFSET_EOF - lock_desc.lock_start)) { res_LOCKU4->status = NFS4ERR_INVAL; goto out; } /* Check for range overflow past maxfilesize. Comparing beyond 2^64 is * not possible in 64 bits precision, but off+len > maxfilesize is * equivalent to len > maxfilesize - off. We checked 64-bit overflow * above, so treat overflowing the FS maxsize as a request to lock the * entire file. */ if (lock_desc.lock_length > (maxfilesize - lock_desc.lock_start)) { LogDebug(COMPONENT_NFS_V4_LOCK, "LOCK past maxfilesize %" PRIx64 " start %" PRIx64 " length %" PRIx64, maxfilesize, lock_desc.lock_start, lock_desc.lock_length); lock_desc.lock_length = 0; } LogLock(COMPONENT_NFS_V4_LOCK, NIV_FULL_DEBUG, locku_tag, data->current_obj, lock_owner, &lock_desc); if (data->minorversion == 0) { op_ctx->clientid = &lock_owner->so_owner.so_nfs4_owner.so_clientid; } /* Now we have a lock owner and a stateid. Go ahead and push unlock into SAL (and FSAL). */ state_status = state_unlock(data->current_obj, state_found, lock_owner, false, 0, &lock_desc); if (state_status != STATE_SUCCESS) { res_LOCKU4->status = nfs4_Errno_state(state_status); goto out; } if (data->minorversion == 0) op_ctx->clientid = NULL; /* Successful exit */ res_LOCKU4->status = NFS4_OK; /* Handle stateid/seqid for success */ update_stateid(state_found, &res_LOCKU4->LOCKU4res_u.lock_stateid, data, locku_tag); out: if (data->minorversion == 0) { /* Save the response in the lock owner */ Copy_nfs4_state_req(lock_owner, arg_LOCKU4->seqid, op, data->current_obj, resp, locku_tag); } out2: dec_state_owner_ref(lock_owner); out3: dec_state_t_ref(state_found); GSH_AUTO_TRACEPOINT(nfs4, op_locku_end, TRACE_INFO, "LOCKU res: status={} stateid={}", res_LOCKU4->status, res_LOCKU4->LOCKU4res_u.lock_stateid.seqid); return nfsstat4_to_nfs_req_result(res_LOCKU4->status); } /* nfs4_op_locku */ /** * @brief Free memory allocated for LOCKU result * * This function frees any memory allocated for the result of the * NFS4_OP_LOCKU operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_locku_Free(nfs_resop4 *resp) { /* Nothing to be done */ } void nfs4_op_locku_CopyRes(LOCKU4res *res_dst, LOCKU4res *res_src) { /* Nothing to deep copy */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_lookup.c000066400000000000000000000167311473756622300221130ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_lookup.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * * */ #include "config.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_creds.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include "export_mgr.h" #include "nfs_proto_functions.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief NFS4_OP_LOOKUP * * This function implements the NFS4_OP_LOOKUP operation, which looks * a filename up in the FSAL. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, pp. 368-9 * */ enum nfs_req_result nfs4_op_lookup(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { /* Convenient alias for the arguments */ LOOKUP4args *const arg_LOOKUP4 = &op->nfs_argop4_u.oplookup; /* Convenient alias for the response */ LOOKUP4res *const res_LOOKUP4 = &resp->nfs_resop4_u.oplookup; /* The directory in which to look up the name */ struct fsal_obj_handle *dir_obj = NULL; /* The name found */ struct fsal_obj_handle *file_obj = NULL; /* Status code from fsal */ fsal_status_t status = { 0, 0 }; GSH_AUTO_TRACEPOINT(nfs4, op_lookup_start, TRACE_INFO, "LOOKUP arg: name[{}]={}", arg_LOOKUP4->objname.utf8string_len, TP_UTF8STR_TRUNCATED(arg_LOOKUP4->objname)); resp->resop = NFS4_OP_LOOKUP; res_LOOKUP4->status = NFS4_OK; /* Do basic checks on a filehandle */ res_LOOKUP4->status = nfs4_sanity_check_FH(data, DIRECTORY, false); if (res_LOOKUP4->status != NFS4_OK) { /* for some reason lookup is picky. Just not being * dir is not enough. We want to know it is a symlink */ if (res_LOOKUP4->status == NFS4ERR_NOTDIR && data->current_filetype == SYMBOLIC_LINK) res_LOOKUP4->status = NFS4ERR_SYMLINK; goto out; } /* Validate and convert the UFT8 objname to a regular string */ res_LOOKUP4->status = nfs4_utf8string_scan(&arg_LOOKUP4->objname, UTF8_SCAN_PATH_COMP); if (res_LOOKUP4->status != NFS4_OK) goto out; LogDebug(COMPONENT_NFS_V4, "name=%s", arg_LOOKUP4->objname.utf8string_val); /* Do the lookup in the FSAL */ file_obj = NULL; dir_obj = data->current_obj; /* Sanity check: dir_obj should be ACTUALLY a directory */ status = fsal_lookup(dir_obj, arg_LOOKUP4->objname.utf8string_val, &file_obj, NULL); if (FSAL_IS_ERROR(status)) { res_LOOKUP4->status = nfs4_Errno_status(status); goto out; } if (file_obj->type == DIRECTORY) { PTHREAD_RWLOCK_rdlock(&file_obj->state_hdl->jct_lock); if (file_obj->state_hdl->dir.junction_export != NULL) { /* Handle junction */ struct fsal_obj_handle *obj = NULL; /* Attempt to get a reference to the export across the * junction. */ if (!export_ready( file_obj->state_hdl->dir.junction_export)) { /* If we could not get a reference, return * stale. Release jct_lock */ LogDebug(COMPONENT_EXPORT, "NFS4ERR_STALE on LOOKUP of %s", arg_LOOKUP4->objname.utf8string_val); res_LOOKUP4->status = NFS4ERR_STALE; PTHREAD_RWLOCK_unlock( &file_obj->state_hdl->jct_lock); goto out; } get_gsh_export_ref( file_obj->state_hdl->dir.junction_export); /* Stash the new export in the compound data, releasing * any old export reference. */ set_op_context_export( file_obj->state_hdl->dir.junction_export); PTHREAD_RWLOCK_unlock(&file_obj->state_hdl->jct_lock); /* Build credentials */ res_LOOKUP4->status = nfs4_export_check_access(data->req); /* Test for access error (export should not be visible). */ if (res_LOOKUP4->status == NFS4ERR_ACCESS) { /* If return is NFS4ERR_ACCESS then this client * doesn't have access to this export, return * NFS4ERR_NOENT to hide it. It was not visible * in READDIR response. */ LogDebug( COMPONENT_EXPORT, "NFS4ERR_ACCESS Hiding Export_Id %d Pseudo %s with NFS4ERR_NOENT", op_ctx->ctx_export->export_id, CTX_PSEUDOPATH(op_ctx)); res_LOOKUP4->status = NFS4ERR_NOENT; goto out; } if (res_LOOKUP4->status == NFS4ERR_WRONGSEC) { /* LogInfo already documents why */ goto out; } if (res_LOOKUP4->status != NFS4_OK) { /* Should never get here, * nfs4_export_check_access can only return * NFS4_OK, NFS4ERR_ACCESS or NFS4ERR_WRONGSEC. */ LogMajor( COMPONENT_EXPORT, "PSEUDO FS JUNCTION TRAVERSAL: Failed with %s for %s, id=%d", nfsstat4_to_str(res_LOOKUP4->status), CTX_PSEUDOPATH(op_ctx), op_ctx->ctx_export->export_id); goto out; } status = nfs_export_get_root_entry(op_ctx->ctx_export, &obj); if (FSAL_IS_ERROR(status)) { LogMajor( COMPONENT_EXPORT, "PSEUDO FS JUNCTION TRAVERSAL: Failed to get root for %s, id=%d, status = %s", CTX_PSEUDOPATH(op_ctx), op_ctx->ctx_export->export_id, msg_fsal_err(status.major)); res_LOOKUP4->status = nfs4_Errno_status(status); goto out; } LogDebug( COMPONENT_EXPORT, "PSEUDO FS JUNCTION TRAVERSAL: Crossed to %s, id=%d for name=%s", CTX_PSEUDOPATH(op_ctx), op_ctx->ctx_export->export_id, arg_LOOKUP4->objname.utf8string_val); file_obj->obj_ops->put_ref(file_obj); file_obj = obj; } else { PTHREAD_RWLOCK_unlock(&file_obj->state_hdl->jct_lock); } } /* Convert it to a file handle */ if (!nfs4_FSALToFhandle(false, &data->currentFH, file_obj, op_ctx->ctx_export)) { res_LOOKUP4->status = NFS4ERR_SERVERFAULT; goto out; } /* Keep the pointer within the compound data */ set_current_entry(data, file_obj); /* Put our ref */ file_obj->obj_ops->put_ref(file_obj); file_obj = NULL; /* Return successfully */ res_LOOKUP4->status = NFS4_OK; out: /* Release reference on file_obj if we didn't utilze it. */ if (file_obj) file_obj->obj_ops->put_ref(file_obj); GSH_AUTO_TRACEPOINT(nfs4, op_lookup_end, TRACE_INFO, "LOOKUP res: status={}", res_LOOKUP4->status); return nfsstat4_to_nfs_req_result(res_LOOKUP4->status); } /* nfs4_op_lookup */ /** * @brief Free memory allocated for LOOKUP result * * This function frees any memory allocated for the result of the * NFS4_OP_LOOKUP operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_lookup_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_lookupp.c000066400000000000000000000170311473756622300222650ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_lookupp.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * * */ #include "config.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_creds.h" #include "nfs_proto_tools.h" #include "nfs_file_handle.h" #include "nfs_convert.h" #include "export_mgr.h" #include "nfs_proto_functions.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief NFS4_OP_LOOKUPP * * This function implements the NFS4_OP_LOOKUPP operation, which looks * up the parent of the supplied directory. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 369 * */ enum nfs_req_result nfs4_op_lookupp(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { LOOKUPP4res *const res_LOOKUPP4 = &resp->nfs_resop4_u.oplookupp; struct fsal_obj_handle *dir_obj = NULL; struct fsal_obj_handle *file_obj; struct fsal_obj_handle *root_obj; fsal_status_t status; struct gsh_export *original_export = op_ctx->ctx_export; GSH_AUTO_TRACEPOINT(nfs4, op_lookupp_start, TRACE_INFO, "LOOKUPP start"); resp->resop = NFS4_OP_LOOKUPP; res_LOOKUPP4->status = NFS4_OK; /* Do basic checks on a filehandle */ res_LOOKUPP4->status = nfs4_sanity_check_FH(data, DIRECTORY, false); if (res_LOOKUPP4->status != NFS4_OK) return NFS_REQ_ERROR; /* Preparing for fsal_lookupp */ file_obj = NULL; dir_obj = data->current_obj; /* If Filehandle points to the root of the current export, then backup * through junction into the containing export. */ if (data->current_obj->type != DIRECTORY) goto not_junction; status = nfs_export_get_root_entry(original_export, &root_obj); if (FSAL_IS_ERROR(status)) { res_LOOKUPP4->status = nfs4_Errno_status(status); return NFS_REQ_ERROR; } PTHREAD_RWLOCK_rdlock(&original_export->exp_lock); if (data->current_obj == root_obj) { struct gsh_export *parent_exp = NULL; /* Handle reverse junction */ LogDebug( COMPONENT_EXPORT, "Handling reverse junction from Export_Id %d Pseudo %s Parent=%p", original_export->export_id, CTX_PSEUDOPATH(op_ctx), original_export->exp_parent_exp); if (original_export->exp_parent_exp == NULL) { /* lookupp on the root on the pseudofs should return * NFS4ERR_NOENT (RFC3530, page 166) */ root_obj->obj_ops->put_ref(root_obj); PTHREAD_RWLOCK_unlock(&original_export->exp_lock); res_LOOKUPP4->status = NFS4ERR_NOENT; return NFS_REQ_ERROR; } PTHREAD_RWLOCK_unlock(&original_export->exp_lock); /* Clear out data->current entry outside lock * so if it cascades into cleanup, we aren't holding * an export lock that would cause trouble. */ set_current_entry(data, NULL); /* We need to protect accessing the parent information * with the export lock. We use the current export's lock * which is plenty, the parent can't go away without * grabbing the current export's lock to clean out the * parent information. */ PTHREAD_RWLOCK_rdlock(&original_export->exp_lock); /* Get the junction inode into dir_obj and parent_exp * for reference. */ dir_obj = original_export->exp_junction_obj; parent_exp = original_export->exp_parent_exp; /* Check if there is a problem with the export and try and * get a reference to the parent export. */ if (dir_obj == NULL || parent_exp == NULL || !export_ready(parent_exp)) { /* Export is in the process of dying */ root_obj->obj_ops->put_ref(root_obj); PTHREAD_RWLOCK_unlock(&original_export->exp_lock); LogCrit(COMPONENT_EXPORT, "Reverse junction from Export_Id %d Pseudo %s Parent=%p is stale", original_export->export_id, CTX_PSEUDOPATH(op_ctx), parent_exp); res_LOOKUPP4->status = NFS4ERR_STALE; return NFS_REQ_ERROR; } /* Get reference to parent_exp for op context. */ get_gsh_export_ref(parent_exp); dir_obj->obj_ops->get_ref(dir_obj); /* Set up dir_obj as current obj with an LRU reference * while still holding the lock. */ set_current_entry(data, dir_obj); /* Put our ref */ dir_obj->obj_ops->put_ref(dir_obj); /* We are now done with original_export->exp_lock, nothing * following depends on it being held. */ PTHREAD_RWLOCK_unlock(&original_export->exp_lock); /* Release the original_export and put the parent_exp into * the op context. */ set_op_context_export(parent_exp); /* Build credentials */ res_LOOKUPP4->status = nfs4_export_check_access(data->req); /* Test for access error (export should not be visible). */ if (res_LOOKUPP4->status == NFS4ERR_ACCESS) { /* If return is NFS4ERR_ACCESS then this client doesn't * have access to this export, return NFS4ERR_NOENT to * hide it. It was not visible in READDIR response. */ root_obj->obj_ops->put_ref(root_obj); LogDebug( COMPONENT_EXPORT, "NFS4ERR_ACCESS Hiding Export_Id %d Pseudo %s with NFS4ERR_NOENT", parent_exp->export_id, CTX_PSEUDOPATH(op_ctx)); res_LOOKUPP4->status = NFS4ERR_NOENT; return NFS_REQ_ERROR; } } else { /* Release the lock taken above */ PTHREAD_RWLOCK_unlock(&original_export->exp_lock); } /* Return our ref from above */ root_obj->obj_ops->put_ref(root_obj); not_junction: status = fsal_lookupp(dir_obj, &file_obj, NULL); if (file_obj != NULL) { /* Convert it to a file handle */ if (!nfs4_FSALToFhandle(false, &data->currentFH, file_obj, op_ctx->ctx_export)) { res_LOOKUPP4->status = NFS4ERR_SERVERFAULT; file_obj->obj_ops->put_ref(file_obj); return NFS_REQ_ERROR; } /* Keep the pointer within the compound data */ set_current_entry(data, file_obj); /* Put our ref */ file_obj->obj_ops->put_ref(file_obj); /* Return successfully */ res_LOOKUPP4->status = NFS4_OK; } else { /* Unable to look up parent for some reason. * Return error. */ set_current_entry(data, NULL); res_LOOKUPP4->status = nfs4_Errno_status(status); } GSH_AUTO_TRACEPOINT(nfs4, op_lookupp_end, TRACE_INFO, "LOOKUPP res: status={}", res_LOOKUPP4->status); return nfsstat4_to_nfs_req_result(res_LOOKUPP4->status); } /* nfs4_op_lookupp */ /** * @brief Free memory allocated for LOOKUPP result * * This function frees any memory allocated for the result of the * NFS4_OP_LOOKUPP operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_lookupp_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_nverify.c000066400000000000000000000076671473756622300222740ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file nfs4_op_nverify.c * \brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * * */ #include "config.h" #include "log.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * * @brief Implementation of NFS4_OP_NVERIFY * * This function implements the NFS4_OP_NVERIFY operation. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC 5661, p. 369 * */ enum nfs_req_result nfs4_op_nverify(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { NVERIFY4args *const arg_NVERIFY4 = &op->nfs_argop4_u.opnverify; NVERIFY4res *const res_NVERIFY4 = &resp->nfs_resop4_u.opnverify; fattr4 file_attr4; int rc = 0; struct fsal_attrlist attrs; GSH_AUTO_TRACEPOINT(nfs4, op_nverify_start, TRACE_INFO, "NVERIFY start"); resp->resop = NFS4_OP_NVERIFY; res_NVERIFY4->status = NFS4_OK; /* Do basic checks on a filehandle */ res_NVERIFY4->status = nfs4_sanity_check_FH(data, NO_FILE_TYPE, false); if (res_NVERIFY4->status != NFS4_OK) return NFS_REQ_ERROR; /* Get only attributes that are allowed to be read */ if (!nfs4_Fattr_Check_Access(&arg_NVERIFY4->obj_attributes, FATTR4_ATTR_READ)) { res_NVERIFY4->status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } /* Ask only for supported attributes */ if (!nfs4_Fattr_Supported(&arg_NVERIFY4->obj_attributes)) { res_NVERIFY4->status = NFS4ERR_ATTRNOTSUPP; return NFS_REQ_ERROR; } fsal_prepare_attrs(&attrs, 0); res_NVERIFY4->status = bitmap4_to_attrmask_t( &arg_NVERIFY4->obj_attributes.attrmask, &attrs.request_mask); if (res_NVERIFY4->status != NFS4_OK) return NFS_REQ_ERROR; res_NVERIFY4->status = file_To_Fattr(data, attrs.request_mask, &attrs, &file_attr4, &arg_NVERIFY4->obj_attributes.attrmask); if (res_NVERIFY4->status != NFS4_OK) return NFS_REQ_ERROR; /* Done with the attrs */ fsal_release_attrs(&attrs); rc = nfs4_Fattr_cmp(&arg_NVERIFY4->obj_attributes, &file_attr4); if (rc == false) { res_NVERIFY4->status = NFS4_OK; } else { if (rc == -1) res_NVERIFY4->status = NFS4ERR_INVAL; else res_NVERIFY4->status = NFS4ERR_SAME; } nfs4_Fattr_Free(&file_attr4); GSH_AUTO_TRACEPOINT(nfs4, op_nverify_end, TRACE_INFO, "NVERIFY res: status={}", res_NVERIFY4->status); return nfsstat4_to_nfs_req_result(res_NVERIFY4->status); } /* nfs4_op_nverify */ /** * @brief Free memory allocated for NVERIFY result * * This function frees any memory allocated for the result of the * NFS4_OP_NVERIFY operation. * * @param[in] resp nfs4_op results */ void nfs4_op_nverify_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_open.c000066400000000000000000001317501473756622300215420ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_open.c * @brief NFS4_OP_OPEN * * Function implementing the NFS4_OP_OPEN operation and support code. */ #include "config.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include "fsal_convert.h" #include "nfs_creds.h" #include "export_mgr.h" #include "nfs_rpc_callback.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif static const char *open_tag = "OPEN"; /** * @brief Copy an OPEN result * * This function copies an open result to the supplied destination. * * @param[out] res_dst Buffer to which to copy the result * @param[in] res_src The result to copy */ void nfs4_op_open_CopyRes(OPEN4res *res_dst, OPEN4res *res_src) { res_dst->OPEN4res_u.resok4.attrset = res_src->OPEN4res_u.resok4.attrset; } /** * @brief Create an NFSv4 filehandle * * This function creates an NFSv4 filehandle from the supplied file * and sets it to be the current filehandle. * * @note This calls @ref set_current_entry which takes a ref; this then drops * it's ref. * * @param[in,out] data Compound's data * @param[in] obj File * * @retval NFS4_OK on success. * @retval Valid errors for NFS4_OP_OPEN. */ static nfsstat4 open4_create_fh(compound_data_t *data, struct fsal_obj_handle *obj) { /* Building a new fh */ if (!nfs4_FSALToFhandle(false, &data->currentFH, obj, op_ctx->ctx_export)) { obj->obj_ops->put_ref(obj); return NFS4ERR_SERVERFAULT; } /* Update the current entry */ set_current_entry(data, obj); /* Put our ref */ obj->obj_ops->put_ref(obj); return NFS4_OK; } /** * @brief Validate claim type * * Check that the claim type specified is allowed and return the * appropriate error code if not. * * @param[in] data Compound's data * @param[in] claim Claim type * @param[out] grace_ref Did this function acquire a grace reference? * * @retval NFS4_OK claim is valid. * @retval NFS4ERR_GRACE new open not allowed in grace period. * @retval NFS4ERR_NO_GRACE reclaim not allowed after grace period or * reclaim complete. * @retval NFS4ERR_NOTSUPP claim type not supported by minor version. * @retval NFS4ERR_INVAL unknown claim type. */ static nfsstat4 open4_validate_claim(compound_data_t *data, open_claim_type4 claim, nfs_client_id_t *clientid, bool *grace_ref) { /* Return code */ nfsstat4 status = NFS4_OK; /* does this claim require a grace period? */ bool want_grace = false; bool fsal_grace_support = op_ctx->fsal_export->exp_ops.fs_supports( op_ctx->fsal_export, fso_grace_method); /* do we need a reference on the current state of grace? */ bool take_ref = !fsal_grace_support; switch (claim) { case CLAIM_NULL: if ((data->minorversion > 0) && !clientid->cid_cb.v41.cid_reclaim_complete) status = NFS4ERR_GRACE; break; case CLAIM_FH: if (data->minorversion == 0) status = NFS4ERR_NOTSUPP; break; case CLAIM_DELEGATE_PREV: status = NFS4ERR_NOTSUPP; break; case CLAIM_PREVIOUS: want_grace = true; if ((!clientid->cid_allow_reclaim && !fsal_grace_support) || ((data->minorversion > 0) && clientid->cid_cb.v41.cid_reclaim_complete)) status = NFS4ERR_NO_GRACE; break; case CLAIM_DELEGATE_CUR: take_ref = false; break; case CLAIM_DELEG_CUR_FH: take_ref = false; if (data->minorversion == 0) status = NFS4ERR_NOTSUPP; break; case CLAIM_DELEG_PREV_FH: status = NFS4ERR_NOTSUPP; break; default: status = NFS4ERR_INVAL; } if (status == NFS4_OK) { if (take_ref) { if (nfs_get_grace_status(want_grace)) { *grace_ref = true; } else { status = want_grace ? NFS4ERR_NO_GRACE : NFS4ERR_GRACE; } } else { *grace_ref = false; } } return status; } /** * @brief Validate and create an open owner * * This function finds or creates an owner to be associated with the * requested open state. * * @param[in] op Arguments to OPEN4 operation * @param[in,out] data Compound's data * @param[out] res Response to OPEN4 operation * @param[in] clientid The cientid record for this request * @param[out] owner The found/created owner * * @return false if error or replay (res_OPEN4->status is already set), * true otherwise. */ bool open4_open_owner(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *res, nfs_client_id_t *clientid, state_owner_t **owner) { /* Shortcut to open args */ OPEN4args *const arg_OPEN4 = &(op->nfs_argop4_u.opopen); /* Shortcut to open args */ OPEN4res *const res_OPEN4 = &(res->nfs_resop4_u.opopen); /* The parsed-out name of the open owner */ state_nfs4_owner_name_t owner_name; /* Indicates if the owner is new */ bool_t isnew; /* Return value of FSAL operations */ fsal_status_t status = { 0, 0 }; struct fsal_obj_handle *obj_lookup = NULL; /* Is this open_owner known? If so, get it so we can use * replay cache */ convert_nfs4_open_owner(&arg_OPEN4->owner, &owner_name); /* If this open owner is not known yet, allocate and set up a new one */ *owner = create_nfs4_owner(&owner_name, clientid, STATE_OPEN_OWNER_NFSV4, NULL, 0, &isnew, CARE_ALWAYS, data->minorversion != 0); LogStateOwner("Open: ", *owner); if (*owner == NULL) { res_OPEN4->status = NFS4ERR_RESOURCE; LogEvent( COMPONENT_STATE, "NFS4 OPEN returning NFS4ERR_RESOURCE for CLAIM_NULL (could not create NFS4 Owner"); return false; } /* Seqid checking is only proper for reused NFSv4.0 owner */ if (isnew || (data->minorversion != 0)) return true; if (arg_OPEN4->seqid == 0) { LogDebug( COMPONENT_STATE, "Previously known open_owner is used with seqid=0, ask the client to confirm it again"); (*owner)->so_owner.so_nfs4_owner.so_confirmed = false; return true; } /* Check for replay */ if (Check_nfs4_seqid(*owner, arg_OPEN4->seqid, op, data->current_obj, res, open_tag)) { /* No replay */ return true; } /* Response is setup for us and LogDebug told what was * wrong. * * Or if this is a seqid replay, find the file entry * and update currentFH */ if (res_OPEN4->status == NFS4_OK) { utf8string *utfile; open_claim4 *oc = &arg_OPEN4->claim; /* Load up CLAIM_DELEGATE_CUR file */ switch (oc->claim) { case CLAIM_NULL: utfile = &oc->open_claim4_u.file; break; case CLAIM_DELEGATE_CUR: utfile = &oc->open_claim4_u.delegate_cur_info.file; break; case CLAIM_DELEGATE_PREV: default: return false; } /* Check if filename is correct */ res_OPEN4->status = nfs4_utf8string_scan(utfile, UTF8_SCAN_PATH_COMP); if (res_OPEN4->status != NFS4_OK) return false; status = fsal_lookup(data->current_obj, utfile->utf8string_val, &obj_lookup, NULL); if (obj_lookup == NULL) { res_OPEN4->status = nfs4_Errno_status(status); return false; } res_OPEN4->status = open4_create_fh(data, obj_lookup); } return false; } /** * @brief Check delegation claims while opening a file * * This function implements the CLAIM_DELEGATE_CUR claim. * * @param[in] arg OPEN4 arguments * @param[in,out] data Comopund's data */ static nfsstat4 open4_claim_deleg(OPEN4args *arg, compound_data_t *data) { open_claim_type4 claim = arg->claim.claim; stateid4 *rcurr_state; struct fsal_obj_handle *obj_lookup; const utf8string *utfname; fsal_status_t fsal_status; nfsstat4 status; state_t *found_state = NULL; if (!(op_ctx->fsal_export->exp_ops.fs_supports(op_ctx->fsal_export, fso_delegations_w) || op_ctx->fsal_export->exp_ops.fs_supports(op_ctx->fsal_export, fso_delegations_r))) { LogDebug( COMPONENT_STATE, "NFS4 OPEN returning NFS4ERR_NOTSUPP for CLAIM_DELEGATE"); return NFS4ERR_NOTSUPP; } assert((claim == CLAIM_DELEGATE_CUR) || (claim == CLAIM_DELEG_CUR_FH)); if (claim == CLAIM_DELEG_CUR_FH) { rcurr_state = &arg->claim.open_claim4_u.oc_delegate_stateid; goto find_state; } utfname = &arg->claim.open_claim4_u.delegate_cur_info.file; rcurr_state = &arg->claim.open_claim4_u.delegate_cur_info.delegate_stateid; LogDebug(COMPONENT_NFS_V4, "file name: %s", utfname->utf8string_val); /* Check if filename is correct */ status = nfs4_utf8string_scan(utfname, UTF8_SCAN_PATH_COMP); if (status != NFS4_OK) { LogDebug(COMPONENT_NFS_V4, "Invalid filename"); return status; } /* Does a file with this name already exist ? */ fsal_status = fsal_lookup(data->current_obj, utfname->utf8string_val, &obj_lookup, NULL); if (FSAL_IS_ERROR(fsal_status)) { LogDebug(COMPONENT_NFS_V4, "%s lookup failed.", utfname->utf8string_val); return nfs4_Errno_status(fsal_status); } status = open4_create_fh(data, obj_lookup); if (status != NFS4_OK) { LogDebug(COMPONENT_NFS_V4, "open4_create_fh failed"); return status; } find_state: found_state = nfs4_State_Get_Pointer(rcurr_state->other); if (found_state == NULL) { LogDebug(COMPONENT_NFS_V4, "state not found with CLAIM_DELEGATE_CUR"); return NFS4ERR_BAD_STATEID; } else { /* FIXME: vet stateid here */ if (isFullDebug(COMPONENT_NFS_V4)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_stateid(&dspbuf, found_state); LogFullDebug(COMPONENT_NFS_V4, "found matching %s", str); } dec_state_t_ref(found_state); } LogFullDebug(COMPONENT_NFS_V4, "done with CLAIM_DELEGATE_CUR"); return NFS4_OK; } /** * @brief Create a new delegation state then get the delegation. * * Create a new delegation state for this client and file. * Then attempt to get a LEASE lock to delegate the file * according to whether the client opened READ or READ/WRITE. * * @note st_lock must be held * * @param[in] data Compound data for this request * @param[in] op NFS arguments for the request * @param[in] open_state Open state for the inode to be delegated. * @param[in] openowner Open owner of the open state. * @param[in] client The client that will own the delegation. * @param[in/out] resok Delegation attempt result to be returned to client. * @param[in] prerecall flag for reclaims. */ static void get_delegation(compound_data_t *data, OPEN4args *args, state_t *open_state, state_owner_t *openowner, nfs_client_id_t *client, OPEN4resok *resok, bool prerecall) { state_status_t state_status; union state_data state_data; open_delegation_type4 deleg_type; state_owner_t *clientowner = &client->cid_owner; struct state_refer refer; state_t *new_state = NULL; struct state_hdl *ostate; open_write_delegation4 *writeres = &resok->delegation.open_delegation4_u.write; open_read_delegation4 *readres = &resok->delegation.open_delegation4_u.read; open_none_delegation4 *whynone = &resok->delegation.open_delegation4_u.od_whynone; ostate = data->current_obj->state_hdl; if (!ostate) { LogFullDebug(COMPONENT_NFS_V4_LOCK, "Could not get file state"); whynone->ond_why = WND4_RESOURCE; /* passing 0 current delegations since we don't have * ostate and file handle */ DEC_G_Total_Num_Files_Delegated(0); return; } /* Record the sequence info */ if (data->minorversion > 0) { memcpy(refer.session, data->session->session_id, sizeof(sessionid4)); refer.sequence = data->sequence; refer.slot = data->slotid; } if (args->share_access & OPEN4_SHARE_ACCESS_WRITE) { deleg_type = OPEN_DELEGATE_WRITE; } else { assert(args->share_access & OPEN4_SHARE_ACCESS_READ); deleg_type = OPEN_DELEGATE_READ; } LogDebug(COMPONENT_STATE, "Attempting to grant %s delegation", deleg_type == OPEN_DELEGATE_WRITE ? "WRITE" : "READ"); init_new_deleg_state(&state_data, deleg_type, client); /* Add the delegation state */ state_status = state_add_impl(data->current_obj, STATE_TYPE_DELEG, &state_data, clientowner, &new_state, data->minorversion > 0 ? &refer : NULL); if (state_status != STATE_SUCCESS) { LogDebug( COMPONENT_NFS_V4_LOCK, "get delegation call failed to add state with status %s", state_err_str(state_status)); whynone->ond_why = WND4_RESOURCE; DEC_G_Total_Num_Files_Delegated( ostate->file.fdeleg_stats.fds_curr_delegations); return; } new_state->state_seqid++; LogFullDebugOpaque(COMPONENT_STATE, "delegation state added, stateid: %s", 100, new_state->stateid_other, OTHERSIZE); /* acquire_lease_lock() gets the delegation from FSAL */ state_status = acquire_lease_lock(ostate, clientowner, new_state); if (state_status != STATE_SUCCESS) { /* Decrement total num_files_delegated even if this is a * reclaim if fds_curr_delegations is equal to zero and we * can't acquire the lease lock. This is the convention * followed in update_delegation_stats * following the same here */ DEC_G_Total_Num_Files_Delegated( ostate->file.fdeleg_stats.fds_curr_delegations); if (args->claim.claim != CLAIM_PREVIOUS) { LogDebug( COMPONENT_NFS_V4_LOCK, "get delegation call added state but failed to lock with status %s", state_err_str(state_status)); state_del_locked(new_state); dec_state_t_ref(new_state); if (state_status == STATE_LOCK_CONFLICT) whynone->ond_why = WND4_CONTENTION; else whynone->ond_why = WND4_RESOURCE; return; } prerecall = true; } resok->delegation.delegation_type = deleg_type; ostate->file.fdeleg_stats.fds_deleg_type = deleg_type; if (deleg_type == OPEN_DELEGATE_WRITE) { nfs_space_limit4 *space_limit = &writeres->space_limit; space_limit->limitby = NFS_LIMIT_SIZE; space_limit->nfs_space_limit4_u.filesize = DELEG_SPACE_LIMIT_FILESZ; COPY_STATEID(&writeres->stateid, new_state); writeres->recall = prerecall; get_deleg_perm(&writeres->permissions, deleg_type); } else { assert(deleg_type == OPEN_DELEGATE_READ); COPY_STATEID(&readres->stateid, new_state); readres->recall = prerecall; get_deleg_perm(&readres->permissions, deleg_type); } new_state->state_data.deleg.share_access = args->share_access & OPEN4_SHARE_ACCESS_BOTH; new_state->state_data.deleg.share_deny = args->share_deny; if (isDebug(COMPONENT_NFS_V4_LOCK)) { char str1[LOG_BUFF_LEN / 2] = "\0"; char str2[LOG_BUFF_LEN / 2] = "\0"; struct display_buffer dspbuf1 = { sizeof(str1), str1, str1 }; struct display_buffer dspbuf2 = { sizeof(str2), str2, str2 }; display_nfs4_owner(&dspbuf1, openowner); display_nfs4_owner(&dspbuf2, clientowner); LogDebug(COMPONENT_NFS_V4_LOCK, "get delegation openowner %s clientowner %s status %s", str1, str2, state_err_str(state_status)); } dec_state_t_ref(new_state); } static void do_delegation(OPEN4args *arg_OPEN4, OPEN4res *res_OPEN4, compound_data_t *data, state_owner_t *owner, state_t *open_state, nfs_client_id_t *clientid) { OPEN4resok *resok = &res_OPEN4->OPEN4res_u.resok4; bool prerecall; struct state_hdl *ostate; ostate = data->current_obj->state_hdl; if (!ostate) { LogFullDebug(COMPONENT_STATE, "Could not get file state"); return; } /* This will be updated later if we actually delegate */ if (clientid->cid_minorversion != 0 && (arg_OPEN4->share_access & OPEN4_SHARE_ACCESS_WANT_DELEG_MASK) != 0) resok->delegation.delegation_type = OPEN_DELEGATE_NONE_EXT; else resok->delegation.delegation_type = OPEN_DELEGATE_NONE; /* Client doesn't want a delegation. */ if (arg_OPEN4->share_access & OPEN4_SHARE_ACCESS_WANT_NO_DELEG) { resok->delegation.open_delegation4_u.od_whynone.ond_why = WND4_NOT_WANTED; LogFullDebug(COMPONENT_STATE, "Client didn't want delegation."); return; } /* Check if delegations are supported */ if (!deleg_supported(data->current_obj, op_ctx->fsal_export, &op_ctx->export_perms, arg_OPEN4->share_access)) { resok->delegation.open_delegation4_u.od_whynone.ond_why = WND4_NOT_SUPP_FTYPE; LogFullDebug(COMPONENT_STATE, "Delegation type not supported."); return; } /* Decide if we should delegate, then add it. */ if (can_we_grant_deleg(ostate, open_state) && should_we_grant_deleg(ostate, clientid, open_state, arg_OPEN4, resok, owner, &prerecall)) { /* Update delegation open stats */ if (ostate->file.fdeleg_stats.fds_num_opens == 0) ostate->file.fdeleg_stats.fds_first_open = time(NULL); ostate->file.fdeleg_stats.fds_num_opens++; LogDebug(COMPONENT_STATE, "Attempting to grant delegation"); get_delegation(data, arg_OPEN4, open_state, owner, clientid, resok, prerecall); } } /** * @brief NFS4_OP_OPEN create processing for use with extended FSAL API * * @param[in] arg Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] res_OPEN4 Results for nfs4_op * @param[in,out] verifier The verifier for exclusive create * @param[in,out] createmode The method of create * @param[in,out] sattr The attributes to set * @param[in,out] attrset The attributes set on open * */ static void open4_ex_create_args(OPEN4args *arg, compound_data_t *data, OPEN4res *res_OPEN4, void *verifier, enum fsal_create_mode *createmode, struct fsal_attrlist *sattr, struct bitmap4 *attrset) { createhow4 *createhow = &arg->openhow.openflag4_u.how; fattr4 *arg_attrs = NULL; *createmode = nfs4_createmode_to_fsal(createhow->mode); if (createhow->mode == EXCLUSIVE4_1) { memcpy(verifier, createhow->createhow4_u.ch_createboth.cva_verf, sizeof(fsal_verifier_t)); } else if (createhow->mode == EXCLUSIVE4) { memcpy(verifier, createhow->createhow4_u.createverf, sizeof(fsal_verifier_t)); } /* Select the correct attributes */ if (createhow->mode == GUARDED4 || createhow->mode == UNCHECKED4) arg_attrs = &createhow->createhow4_u.createattrs; else if (createhow->mode == EXCLUSIVE4_1) arg_attrs = &createhow->createhow4_u.ch_createboth.cva_attrs; if (arg_attrs != NULL) { /* Check if asked attributes are correct */ if (!nfs4_Fattr_Supported(arg_attrs)) { res_OPEN4->status = NFS4ERR_ATTRNOTSUPP; return; } if (!nfs4_Fattr_Check_Access(arg_attrs, FATTR4_ATTR_WRITE)) { res_OPEN4->status = NFS4ERR_INVAL; return; } /* Convert the attributes */ if (arg_attrs->attrmask.bitmap4_len != 0) { /* Convert fattr4 so nfs4_sattr */ res_OPEN4->status = nfs4_Fattr_To_FSAL_attr(sattr, arg_attrs, data); if (res_OPEN4->status != NFS4_OK) return; } if (createhow->mode == EXCLUSIVE4_1) { /** @todo FSF: this needs to be corrected in case FSAL * uses different attributes for the * verifier. */ /* Check that we aren't trying to set the verifier * attributes. */ if (FSAL_TEST_MASK(sattr->valid_mask, ATTR_ATIME) || FSAL_TEST_MASK(sattr->valid_mask, ATTR_MTIME)) { res_OPEN4->status = NFS4ERR_INVAL; return; } } /* Now fill in attrset with the attributes requested to be set. */ memcpy(attrset, &arg_attrs->attrmask, sizeof(*attrset)); /* If owner or owner_group are set, and the credential was * squashed, then we must squash the set owner and owner_group. */ squash_setattr(sattr); } if (createhow->mode == EXCLUSIVE4 || createhow->mode == EXCLUSIVE4_1) { /* For an exclusive create we must indicate atime and mtime have * been set for the verifier. The client will use this * indication to know it must use a separate SETATTR to set * these attributes. This is especially important for * EXCLUSIVE4_1 creates that come with attributes. */ set_attribute_in_bitmap(attrset, FATTR4_TIME_ACCESS); set_attribute_in_bitmap(attrset, FATTR4_TIME_MODIFY); } if (!(sattr->valid_mask & ATTR_MODE)) { /* Make sure mode is set, even for exclusive create. */ sattr->mode = 0600; sattr->valid_mask |= ATTR_MODE; } } /** * @brief NFS4_OP_OPEN processing using extended FSAL API * * This function implements the NFS4_OP_OPEN operation, which * potentially creates and opens a regular file. * * @param[in] arg Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] res_OPEN4 Results for nfs4_op * @param[out] file_state state_t created for this operation * @param[out] new_state Indicates if the state_t is new * @param[in,out] attrset The attrset bitmap to be passed down * */ static void open4_ex(OPEN4args *arg, compound_data_t *data, OPEN4res *res_OPEN4, nfs_client_id_t *clientid, state_owner_t *owner, state_t **file_state, bool *new_state, struct bitmap4 *attrset, struct fsal_attrlist *parent_pre_attrs, struct fsal_attrlist *parent_post_attrs) { /* Parent directory in which to open the file. */ struct fsal_obj_handle *parent = NULL; /* The entry we associated with the desired file before open. */ struct fsal_obj_handle *file_obj = NULL; /* Indicator that file_obj came from lookup. */ bool looked_up_file_obj = false; /* The in_obj to pass to fsal_open2. */ struct fsal_obj_handle *in_obj = NULL; /* The entry open associated with the file. */ struct fsal_obj_handle *out_obj = NULL; fsal_openflags_t openflags = 0; fsal_openflags_t old_openflags = 0; enum fsal_create_mode createmode = FSAL_NO_CREATE; /* The filename to create */ char *filename = NULL; /* The supplied claim type */ open_claim_type4 claim = arg->claim.claim; fsal_verifier_t verifier; struct fsal_attrlist sattr; /* Status for fsal calls */ fsal_status_t status = { 0, 0 }; /* The open state for the file */ bool st_lock_held = false; /* Make sure the attributes are initialized */ memset(&sattr, 0, sizeof(sattr)); /* Make sure... */ *file_state = NULL; *new_state = false; /* Pre-process the claim type */ switch (claim) { case CLAIM_NULL: /* Check parent */ parent = data->current_obj; in_obj = parent; /* Parent must be a directory */ if (parent->type != DIRECTORY) { if (parent->type == SYMBOLIC_LINK) { res_OPEN4->status = NFS4ERR_SYMLINK; goto out; } else { res_OPEN4->status = NFS4ERR_NOTDIR; goto out; } } /* Validate the utf8 filename */ res_OPEN4->status = nfs4_utf8string_scan( &arg->claim.open_claim4_u.file, UTF8_SCAN_PATH_COMP); if (res_OPEN4->status != NFS4_OK) goto out; filename = arg->claim.open_claim4_u.file.utf8string_val; /* Set the createmode if appropriate), pass down attrset */ if (arg->openhow.opentype == OPEN4_CREATE) { open4_ex_create_args(arg, data, res_OPEN4, verifier, &createmode, &sattr, attrset); if (res_OPEN4->status != NFS4_OK) goto out; } status = fsal_lookup( parent, arg->claim.open_claim4_u.file.utf8string_val, &file_obj, NULL); if (!FSAL_IS_ERROR(status)) { /* Check create situations. */ if (arg->openhow.opentype == OPEN4_CREATE) { if (createmode >= FSAL_EXCLUSIVE) { /* Could be a replay, need to continue. */ LogFullDebug( COMPONENT_STATE, "EXCLUSIVE open with existing file %s", filename); } else if (createmode == FSAL_GUARDED) { /* This will be a failure no matter' * what. */ looked_up_file_obj = true; res_OPEN4->status = NFS4ERR_EXIST; goto out; } else { /* FSAL_UNCHECKED, may be a truncate * and we need to pass in the case * of fsal_reopen2 case. */ if (FSAL_TEST_MASK(sattr.valid_mask, ATTR_SIZE) && sattr.filesize == 0) { LogFullDebug(COMPONENT_STATE, "Truncate"); openflags |= FSAL_O_TRUNC; } } } /* We found the file by lookup, set filename to NULL * and remember that we found the entry by lookup. */ looked_up_file_obj = true; filename = NULL; } else if (status.major != ERR_FSAL_NOENT || arg->openhow.opentype != OPEN4_CREATE) { /* A real error occurred */ res_OPEN4->status = nfs4_Errno_status(status); goto out; } break; /* Both of these just use the current filehandle. */ case CLAIM_PREVIOUS: owner->so_owner.so_nfs4_owner.so_confirmed = true; if (!nfs4_check_deleg_reclaim(clientid, &data->currentFH)) { /* It must have been revoked. Can't reclaim.*/ LogInfo(COMPONENT_NFS_V4, "Can't reclaim delegation"); res_OPEN4->status = NFS4ERR_RECLAIM_BAD; goto out; } openflags |= FSAL_O_RECLAIM; file_obj = data->current_obj; break; case CLAIM_FH: file_obj = data->current_obj; break; case CLAIM_DELEGATE_PREV: /* FIXME: Remove this when we have full support * for CLAIM_DELEGATE_PREV and delegpurge operations */ res_OPEN4->status = NFS4ERR_NOTSUPP; goto out; case CLAIM_DELEGATE_CUR: case CLAIM_DELEG_CUR_FH: res_OPEN4->status = open4_claim_deleg(arg, data); if (res_OPEN4->status != NFS4_OK) goto out; openflags |= FSAL_O_RECLAIM; file_obj = data->current_obj; break; default: LogFatal(COMPONENT_STATE, "Programming error. Invalid claim after check."); } if ((arg->share_access & OPEN4_SHARE_ACCESS_READ) != 0) openflags |= FSAL_O_READ; if ((arg->share_access & OPEN4_SHARE_ACCESS_WRITE) != 0) openflags |= FSAL_O_WRITE; if ((arg->share_deny & OPEN4_SHARE_DENY_READ) != 0) openflags |= FSAL_O_DENY_READ; if ((arg->share_deny & OPEN4_SHARE_DENY_WRITE) != 0) openflags |= FSAL_O_DENY_WRITE_MAND; /* Check if file_obj a REGULAR_FILE */ if (file_obj != NULL && file_obj->type != REGULAR_FILE) { LogDebug(COMPONENT_NFS_V4, "Wrong file type expected REGULAR_FILE actual %s", object_file_type_to_str(file_obj->type)); if (file_obj->type == DIRECTORY) { res_OPEN4->status = NFS4ERR_ISDIR; } else { /* All special nodes must return NFS4ERR_SYMLINK for * proper client behavior per this linux-nfs post: * http://marc.info/?l=linux-nfs&m=131342421825436&w=2 */ res_OPEN4->status = NFS4ERR_SYMLINK; } goto out; } if (file_obj != NULL) { /* Go ahead and take the state lock now. */ STATELOCK_lock(file_obj); st_lock_held = true; in_obj = file_obj; /* Check if any existing delegations conflict with this open. * Delegation recalls will be scheduled if there is a conflict. */ if (arg->claim.claim != CLAIM_DELEGATE_CUR && arg->claim.claim != CLAIM_DELEG_CUR_FH && state_deleg_conflict_impl( file_obj, (arg->share_access & OPEN4_SHARE_ACCESS_WRITE) != 0)) { res_OPEN4->status = NFS4ERR_DELAY; goto out; } /* Check if there is already a state for this entry and owner. */ *file_state = nfs4_State_Get_Obj(file_obj, owner); if (isFullDebug(COMPONENT_STATE) && *file_state != NULL) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_stateid(&dspbuf, *file_state); LogFullDebug(COMPONENT_STATE, "Found existing state %s", str); } /* Check if open from another export */ if (*file_state != NULL && !state_same_export(*file_state, op_ctx->ctx_export)) { LogEvent( COMPONENT_STATE, "Lock Owner Export Conflict, Lock held for export %" PRIu16 " request for export %" PRIu16, state_export_id(*file_state), op_ctx->ctx_export->export_id); res_OPEN4->status = NFS4ERR_INVAL; goto out; } } /* If that did not succeed, allocate a state from the FSAL. */ if (*file_state == NULL) { *file_state = op_ctx->fsal_export->exp_ops.alloc_state( op_ctx->fsal_export, STATE_TYPE_SHARE, NULL); /* Remember we allocated a new state */ *new_state = true; /* We are ready to perform the open (with possible create). * in_obj has been set to the file itself or the parent. * filename is NULL if in_obj is the file itself. * * Permission check has been done on directory if appropriate, * otherwise fsal_open2 will do a directory permission * check. * * fsal_open2 handles the permission check on the file * itself and also handles all the share reservation stuff. * * fsal_open2 returns with a ref on out_obj, which should be * passed to the state. */ LogFullDebug(COMPONENT_STATE, "Calling open2 for %s", filename); retry_open_file: status = fsal_open2(in_obj, *file_state, openflags, createmode, filename, &sattr, verifier, &out_obj, NULL, parent_pre_attrs, parent_post_attrs); if (FSAL_IS_ERROR(status)) { res_OPEN4->status = nfs4_Errno_status(status); LogFullDebug(COMPONENT_STATE, "fsal_open2 for %s, returned %d-%s", filename, res_OPEN4->status, nfsstat4_to_str(res_OPEN4->status)); /* We have an got an share denied with an existing * client. Let's recheck if it was expired. */ if (res_OPEN4->status == NFS4ERR_SHARE_DENIED) { if (st_lock_held) STATELOCK_unlock(in_obj); if (check_and_remove_conflicting_client( in_obj->state_hdl)) { if (st_lock_held) STATELOCK_lock(in_obj); goto retry_open_file; } if (st_lock_held) STATELOCK_lock(in_obj); } goto out; } } else if (createmode >= FSAL_EXCLUSIVE) { /* We have an EXCLUSIVE create with an existing * state. We still need to verify it, but no need * to call reopen2. */ LogFullDebug(COMPONENT_STATE, "Calling verify2 "); status = fsal_verify2(file_obj, verifier); if (FSAL_IS_ERROR(status)) { res_OPEN4->status = nfs4_Errno_status(status); goto out; } /* We need an extra reference below. */ file_obj->obj_ops->get_ref(file_obj); } else { old_openflags = file_obj->obj_ops->status2(file_obj, *file_state); /* Open upgrade */ LogFullDebug(COMPONENT_STATE, "Calling reopen2"); status = fsal_reopen2(file_obj, *file_state, openflags | old_openflags, true); if (FSAL_IS_ERROR(status)) { res_OPEN4->status = nfs4_Errno_status(status); goto out; } /* We need an extra reference below. */ file_obj->obj_ops->get_ref(file_obj); } if (file_obj == NULL) { /* We have a file object, take the state lock. */ file_obj = out_obj; STATELOCK_lock(file_obj); st_lock_held = true; } /* Now the st_lock is held for sure and we have an extra LRU * reference to file_obj, which is the opened file. */ if (*new_state) { /* The state data to be added */ union state_data candidate_data; /* Tracking data for the open state */ struct state_refer refer, *p_refer = NULL; state_status_t state_status; candidate_data.share.share_access = arg->share_access & OPEN4_SHARE_ACCESS_BOTH; candidate_data.share.share_deny = arg->share_deny; candidate_data.share.share_access_prev = (1 << candidate_data.share.share_access); candidate_data.share.share_deny_prev = (1 << candidate_data.share.share_deny); LogFullDebug( COMPONENT_STATE, "Creating new state access=%x deny=%x access_prev=%x deny_prev=%x", candidate_data.share.share_access, candidate_data.share.share_deny, candidate_data.share.share_access_prev, candidate_data.share.share_deny_prev); /* Record the sequence info */ if (data->minorversion > 0) { memcpy(refer.session, data->session->session_id, sizeof(sessionid4)); refer.sequence = data->sequence; refer.slot = data->slotid; p_refer = &refer; } /* We need to register this state now. */ state_status = state_add_impl(file_obj, STATE_TYPE_SHARE, &candidate_data, owner, file_state, p_refer); if (state_status != STATE_SUCCESS) { /* state_add_impl failure closed and freed state. * file_state will also be NULL at this point. Also * release the ref on file_obj, since the state add * failed. */ file_obj->obj_ops->put_ref(file_obj); res_OPEN4->status = nfs4_Errno_state(state_status); *new_state = false; goto out; } glist_init(&(*file_state)->state_data.share.share_lockstates); } res_OPEN4->status = open4_create_fh(data, file_obj); if (res_OPEN4->status != NFS4_OK) { if (*new_state) { /* state_del_locked will close the file. */ state_del_locked(*file_state); *file_state = NULL; *new_state = false; } else { /*Do an open downgrade to the old open flags */ status = fsal_reopen2(file_obj, *file_state, old_openflags, false); if (FSAL_IS_ERROR(status)) { LogCrit(COMPONENT_NFS_V4, "Failed to allocate handle, reopen2 failed with %s", fsal_err_txt(status)); } /* Need to release the st_lock before the put_ref * call. */ STATELOCK_unlock(file_obj); st_lock_held = false; /* Release the extra LRU reference on file_obj. */ file_obj->obj_ops->put_ref(file_obj); goto out; } } /* Since open4_create_fh succeeded the LRU reference to file_obj was * consumed by data->current_obj. */ if (!(*new_state)) { LogFullDebug( COMPONENT_STATE, "Open upgrade old access=%x deny=%x access_prev=%x deny_prev=%x", (*file_state)->state_data.share.share_access, (*file_state)->state_data.share.share_deny, (*file_state)->state_data.share.share_access_prev, (*file_state)->state_data.share.share_deny_prev); LogFullDebug(COMPONENT_STATE, "Open upgrade to access=%x deny=%x", arg->share_access, arg->share_deny); /* Update share_access and share_deny */ (*file_state)->state_data.share.share_access |= arg->share_access & OPEN4_SHARE_ACCESS_BOTH; (*file_state)->state_data.share.share_deny |= arg->share_deny; /* Update share_access_prev and share_deny_prev */ (*file_state)->state_data.share.share_access_prev |= (1 << (arg->share_access & OPEN4_SHARE_ACCESS_BOTH)); (*file_state)->state_data.share.share_deny_prev |= (1 << arg->share_deny); LogFullDebug( COMPONENT_STATE, "Open upgrade new access=%x deny=%x access_prev=%x deny_prev=%x", (*file_state)->state_data.share.share_access, (*file_state)->state_data.share.share_deny, (*file_state)->state_data.share.share_access_prev, (*file_state)->state_data.share.share_deny_prev); } if (!st_lock_held) { /* Acquire st_lock before adding deleg state */ STATELOCK_lock(file_obj); st_lock_held = true; } /* At this point open has succeeded and we are holding the state * lock. */ if (arg->share_access & OPEN4_SHARE_ACCESS_WRITE) file_obj->state_hdl->file.fdeleg_stats.fds_num_write_opens++; do_delegation(arg, res_OPEN4, data, owner, *file_state, clientid); out: /* Release the attributes (may release an inherited ACL) */ fsal_release_attrs(&sattr); if (st_lock_held) { STATELOCK_unlock(file_obj); } if (res_OPEN4->status != NFS4_OK) { /* Cleanup state on error */ if (*new_state) free_state(*file_state); else if (*file_state != NULL) dec_state_t_ref(*file_state); *file_state = NULL; } if (looked_up_file_obj) { /* We got file_obj via lookup, we need to unref it. */ file_obj->obj_ops->put_ref(file_obj); } } /** * @brief NFS4_OP_OPEN * * This function implements the NFS4_OP_OPEN operation, which * potentially creates and opens a regular file. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, pp. 369-70 */ enum nfs_req_result nfs4_op_open(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { /* Shorter alias for OPEN4 arguments */ OPEN4args *const arg_OPEN4 = &(op->nfs_argop4_u.opopen); /* Shorter alias for OPEN4 response */ OPEN4res *const res_OPEN4 = &(resp->nfs_resop4_u.opopen); /* The handle from which the change_info4 is to be * generated. Every mention of change_info4 in RFC5661 * speaks of the parent directory of the file being opened. * However, with CLAIM_FH, CLAIM_DELEG_CUR_FH, and * CLAIM_DELEG_PREV_FH, there is no way to derive the parent * directory from the file handle. It is Unclear what the * correct behavior is. In our implementation, we take the * change_info4 of whatever filehandle is current when the * OPEN operation is invoked. */ struct fsal_obj_handle *obj_change = NULL; /* The found client record */ nfs_client_id_t *clientid = NULL; /* The found or created state owner for this open */ state_owner_t *owner = NULL; /* The supplied claim type */ open_claim_type4 claim = arg_OPEN4->claim.claim; /* The open state for the file */ state_t *file_state = NULL; /* True if the state was newly created */ bool new_state = false; /* True if a grace reference was taken */ bool grace_ref = false; int retval; struct fsal_attrlist parent_pre_attrs, parent_post_attrs; bool is_parent_pre_attrs_valid, is_parent_post_attrs_valid; LogDebug(COMPONENT_STATE, "Entering NFS v4 OPEN handler -----------------------------"); /* What kind of open is it ? */ LogFullDebug( COMPONENT_STATE, "OPEN: Claim type = %d, Open Type = %d, Share Deny = %d, Share Access = %d ", arg_OPEN4->claim.claim, arg_OPEN4->openhow.opentype, arg_OPEN4->share_deny, arg_OPEN4->share_access); GSH_AUTO_TRACEPOINT( nfs4, op_open_start, TRACE_INFO, "OPEN arg: claim={} opentype={} howmode={} share_deny={} share_access={} seqid={}", arg_OPEN4->claim.claim, arg_OPEN4->openhow.opentype, arg_OPEN4->openhow.openflag4_u.how.mode, arg_OPEN4->share_deny, arg_OPEN4->share_access, arg_OPEN4->seqid); resp->resop = NFS4_OP_OPEN; res_OPEN4->status = NFS4_OK; res_OPEN4->OPEN4res_u.resok4.rflags = 0; fsal_prepare_attrs(&parent_pre_attrs, ATTR_CHANGE); fsal_prepare_attrs(&parent_post_attrs, ATTR_CHANGE); /* Check export permissions if OPEN4_CREATE */ if ((arg_OPEN4->openhow.opentype == OPEN4_CREATE) && ((op_ctx->export_perms.options & EXPORT_OPTION_MD_WRITE_ACCESS) == 0)) { res_OPEN4->status = NFS4ERR_ROFS; LogDebug(COMPONENT_NFS_V4, "Status of OP_OPEN due to export permissions = %s", nfsstat4_to_str(res_OPEN4->status)); return NFS_REQ_ERROR; } /* Check export permissions if OPEN4_SHARE_ACCESS_WRITE */ if (((arg_OPEN4->share_access & OPEN4_SHARE_ACCESS_WRITE) != 0) && ((op_ctx->export_perms.options & EXPORT_OPTION_WRITE_ACCESS) == 0)) { res_OPEN4->status = NFS4ERR_ROFS; LogDebug(COMPONENT_NFS_V4, "Status of OP_OPEN due to export permissions = %s", nfsstat4_to_str(res_OPEN4->status)); return NFS_REQ_ERROR; } /* Do basic checks on a filehandle */ res_OPEN4->status = nfs4_sanity_check_FH(data, NO_FILE_TYPE, false); if (res_OPEN4->status != NFS4_OK) return NFS_REQ_ERROR; if (data->current_obj == NULL) { /* This should be impossible, as PUTFH fills in the * current entry and previous checks weed out handles * in the PseudoFS and DS handles. */ res_OPEN4->status = NFS4ERR_SERVERFAULT; LogCrit(COMPONENT_NFS_V4, "Impossible condition in compound data at %s:%u.", __FILE__, __LINE__); return NFS_REQ_ERROR; } /* It this a known client id? */ LogDebug(COMPONENT_STATE, "OPEN Client id = %" PRIx64, arg_OPEN4->owner.clientid); retval = nfs_client_id_get_confirmed(data->minorversion == 0 ? arg_OPEN4->owner.clientid : data->session->clientid, &clientid); if (retval != CLIENT_ID_SUCCESS) { res_OPEN4->status = clientid_error_to_nfsstat(retval); LogDebug(COMPONENT_NFS_V4, "nfs_client_id_get_confirmed failed"); return NFS_REQ_ERROR; } /* Check if lease is expired and reserve it */ if (data->minorversion == 0 && !reserve_lease_or_expire(clientid, false, NULL)) { res_OPEN4->status = NFS4ERR_EXPIRED; LogDebug(COMPONENT_NFS_V4, "Lease expired"); goto out3; } /* Get the open owner */ if (!open4_open_owner(op, data, resp, clientid, &owner)) { LogDebug(COMPONENT_NFS_V4, "open4_open_owner failed"); goto out2; } /* Do the claim check here, so we can save the result in the * owner for NFSv4.0. */ res_OPEN4->status = open4_validate_claim(data, claim, clientid, &grace_ref); if (res_OPEN4->status != NFS4_OK) { LogDebug(COMPONENT_NFS_V4, "open4_validate_claim failed"); goto out; } /* After this point we know we have only CLAIM_NULL, * CLAIM_FH, or CLAIM_PREVIOUS, and that our grace period and * minor version are appropriate for the claim specified. */ if ((arg_OPEN4->openhow.opentype == OPEN4_CREATE) && (claim != CLAIM_NULL)) { res_OPEN4->status = NFS4ERR_INVAL; LogDebug(COMPONENT_NFS_V4, "OPEN4_CREATE but not CLAIM_NULL"); goto out2; } /* So we still have a reference even after we replace the * current FH. */ obj_change = data->current_obj; obj_change->obj_ops->get_ref(obj_change); /* Update the change info for entry_change. */ res_OPEN4->OPEN4res_u.resok4.cinfo.before = fsal_get_changeid4(obj_change); /* Check if share_access does not have any access set, or has * invalid bits that are set. check that share_deny doesn't * have any invalid bits set. */ if (!(arg_OPEN4->share_access & OPEN4_SHARE_ACCESS_BOTH) || (data->minorversion == 0 && arg_OPEN4->share_access & ~OPEN4_SHARE_ACCESS_BOTH) || (arg_OPEN4->share_access & (~OPEN4_SHARE_ACCESS_WANT_DELEG_MASK & ~OPEN4_SHARE_ACCESS_BOTH)) || (arg_OPEN4->share_deny & ~OPEN4_SHARE_DENY_BOTH)) { res_OPEN4->status = NFS4ERR_INVAL; LogDebug(COMPONENT_NFS_V4, "Invalid SHARE_ACCESS or SHARE_DENY"); goto out; } /* Prepare the attrset attribute */ memset(&res_OPEN4->OPEN4res_u.resok4.attrset, 0, sizeof(struct bitmap4)); /* Utilize the extended FSAL API functionality to perform the open. * The usage of atime/mtime for exclusive create verifier will be * handled in open4_ex, so pass in attrset to be handled there. */ open4_ex(arg_OPEN4, data, res_OPEN4, clientid, owner, &file_state, &new_state, &res_OPEN4->OPEN4res_u.resok4.attrset, &parent_pre_attrs, &parent_post_attrs); if (res_OPEN4->status != NFS4_OK) goto out; /* If server use OPEN_CONFIRM4, set the correct flag, * but not for 4.1 */ if (owner->so_owner.so_nfs4_owner.so_confirmed == false) res_OPEN4->OPEN4res_u.resok4.rflags |= OPEN4_RESULT_CONFIRM; res_OPEN4->OPEN4res_u.resok4.rflags |= OPEN4_RESULT_LOCKTYPE_POSIX; if (op_ctx->fsal_export->exp_ops.fs_supports( op_ctx->fsal_export, fso_lock_support_async_block)) { res_OPEN4->OPEN4res_u.resok4.rflags |= OPEN4_RESULT_MAY_NOTIFY_LOCK; } LogFullDebug(COMPONENT_STATE, "NFS4 OPEN returning NFS4_OK"); /* regular exit */ res_OPEN4->status = NFS4_OK; /* Update change_info4 */ is_parent_pre_attrs_valid = FSAL_TEST_MASK(parent_pre_attrs.valid_mask, ATTR_CHANGE); if (is_parent_pre_attrs_valid) { res_OPEN4->OPEN4res_u.resok4.cinfo.before = (changeid4)parent_pre_attrs.change; } is_parent_post_attrs_valid = FSAL_TEST_MASK(parent_post_attrs.valid_mask, ATTR_CHANGE); if (is_parent_post_attrs_valid) { res_OPEN4->OPEN4res_u.resok4.cinfo.after = (changeid4)parent_post_attrs.change; } else { res_OPEN4->OPEN4res_u.resok4.cinfo.after = fsal_get_changeid4(obj_change); } res_OPEN4->OPEN4res_u.resok4.cinfo.atomic = is_parent_pre_attrs_valid && is_parent_post_attrs_valid ? TRUE : FALSE; /* Handle open stateid/seqid for success */ update_stateid(file_state, &res_OPEN4->OPEN4res_u.resok4.stateid, data, open_tag); out: fsal_release_attrs(&parent_pre_attrs); fsal_release_attrs(&parent_post_attrs); if (res_OPEN4->status != NFS4_OK) { LogDebug(COMPONENT_STATE, "failed with status %s", nfsstat4_to_str(res_OPEN4->status)); } /* Save the response in the open owner. * obj_change is either the parent directory or for a CLAIM_PREV is * the entry itself. In either case, it's the right entry to use in * saving the request results. */ if (data->minorversion == 0) { Copy_nfs4_state_req(owner, arg_OPEN4->seqid, op, obj_change, resp, open_tag); } out2: if (grace_ref) nfs_put_grace_status(); /* Update the lease before exit */ if (data->minorversion == 0) update_lease_simple(clientid); if (file_state != NULL) dec_state_t_ref(file_state); /* Clean up if we have an error exit */ if ((file_state != NULL) && new_state && (res_OPEN4->status != NFS4_OK)) { /* Need to destroy open owner and state */ state_del(file_state); } if (obj_change) obj_change->obj_ops->put_ref(obj_change); if (owner != NULL) { /* Need to release the open owner for this call */ dec_state_owner_ref(owner); } out3: dec_client_id_ref(clientid); const OPEN4resok *const resok = &res_OPEN4->OPEN4res_u.resok4; GSH_AUTO_TRACEPOINT(nfs4, op_open_end, TRACE_INFO, "OPEN res: status={} stateid={} " TP_CINFO_FORMAT " rflags={} delegation={}", res_OPEN4->status, resok->stateid.seqid, TP_CINFO_ARGS_EXPAND(resok->cinfo), resok->rflags, resok->delegation.delegation_type); return nfsstat4_to_nfs_req_result(res_OPEN4->status); } /* nfs4_op_open */ /** * @brief Free memory allocated for OPEN result * * This function frees any memory allocated for the result of the * NFS4_OP_OPEN function. * * @param[in,out] resp nfs4_op results */ void nfs4_op_open_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_open_confirm.c000066400000000000000000000112621473756622300232520ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_open_confirm.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include "log.h" #include "gsh_rpc.h" #include "nfs4.h" #include "nfs_core.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" /** * @brief NFS4_OP_OPEN_CONFIRM * * This function implements the NFS4_OP_OPEN_CONFIRM operation. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @retval NFS4_OK or errors for NFSv4.0 * @retval NFS4ERR_NOTSUPP for NFSv4.1 * */ enum nfs_req_result nfs4_op_open_confirm(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { OPEN_CONFIRM4args *const arg_OPEN_CONFIRM4 = &op->nfs_argop4_u.opopen_confirm; OPEN_CONFIRM4res *const res_OPEN_CONFIRM4 = &resp->nfs_resop4_u.opopen_confirm; OPEN_CONFIRM4resok *resok = &res_OPEN_CONFIRM4->OPEN_CONFIRM4res_u.resok4; int rc = 0; state_t *state_found = NULL; state_owner_t *open_owner; const char *tag = "OPEN_CONFIRM"; resp->resop = NFS4_OP_OPEN_CONFIRM; res_OPEN_CONFIRM4->status = NFS4_OK; if (data->minorversion > 0) { res_OPEN_CONFIRM4->status = NFS4ERR_NOTSUPP; return NFS_REQ_ERROR; } /* Do basic checks on a filehandle * Should not operate on non-file objects */ res_OPEN_CONFIRM4->status = nfs4_sanity_check_FH(data, REGULAR_FILE, false); if (res_OPEN_CONFIRM4->status != NFS4_OK) return NFS_REQ_ERROR; /* Check stateid correctness and get pointer to state */ rc = nfs4_Check_Stateid(&arg_OPEN_CONFIRM4->open_stateid, data->current_obj, &state_found, data, STATEID_SPECIAL_FOR_LOCK, arg_OPEN_CONFIRM4->seqid, data->minorversion == 0, tag); if (rc != NFS4_OK && rc != NFS4ERR_REPLAY) { res_OPEN_CONFIRM4->status = rc; return NFS_REQ_ERROR; } open_owner = get_state_owner_ref(state_found); if (open_owner == NULL) { /* State is going stale. */ res_OPEN_CONFIRM4->status = NFS4ERR_STALE; LogDebug( COMPONENT_NFS_V4, "OPEN CONFIRM failed nfs4_Check_Stateid, stale open owner"); goto out2; } PTHREAD_MUTEX_lock(&open_owner->so_mutex); /* Check seqid */ if (!Check_nfs4_seqid(open_owner, arg_OPEN_CONFIRM4->seqid, op, data->current_obj, resp, tag)) { /* Response is all setup for us and LogDebug * told what was wrong */ PTHREAD_MUTEX_unlock(&open_owner->so_mutex); goto out; } /* If opened file is already confirmed, return NFS4ERR_BAD_STATEID */ if (open_owner->so_owner.so_nfs4_owner.so_confirmed) { PTHREAD_MUTEX_unlock(&open_owner->so_mutex); res_OPEN_CONFIRM4->status = NFS4ERR_BAD_STATEID; goto out; } /* Set the state as confirmed */ open_owner->so_owner.so_nfs4_owner.so_confirmed = true; PTHREAD_MUTEX_unlock(&open_owner->so_mutex); /* Handle stateid/seqid for success */ update_stateid(state_found, &resok->open_stateid, data, tag); /* Save the response in the open owner */ Copy_nfs4_state_req(open_owner, arg_OPEN_CONFIRM4->seqid, op, data->current_obj, resp, tag); out: dec_state_owner_ref(open_owner); out2: dec_state_t_ref(state_found); return nfsstat4_to_nfs_req_result(res_OPEN_CONFIRM4->status); } /* nfs4_op_open_confirm */ /** * @brief Free memory allocated for OPEN_CONFIRM result * * Thisf unction frees any memory allocated for the result of the * NFS4_OP_OPEN_CONFIRM operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_open_confirm_Free(nfs_resop4 *resp) { /* Nothing to be done */ } void nfs4_op_open_confirm_CopyRes(OPEN_CONFIRM4res *resp_dst, OPEN_CONFIRM4res *resp_src) { /* Nothing to deep copy */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_open_downgrade.c000066400000000000000000000171151473756622300235720ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_open_downgrade.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include "log.h" #include "gsh_rpc.h" #include "nfs4.h" #include "nfs_core.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "fsal.h" /** * @brief NFS4_OP_OPEN_DOWNGRADE * * This function implements the NFS4_OP_OPEN_DOWNGRADE operation. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 370 * */ static nfsstat4 nfs4_do_open_downgrade(struct nfs_argop4 *op, compound_data_t *data, state_owner_t *owner, state_t *state, char **cause); enum nfs_req_result nfs4_op_open_downgrade(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { OPEN_DOWNGRADE4args *const arg_OPEN_DOWNGRADE4 = &op->nfs_argop4_u.opopen_downgrade; OPEN_DOWNGRADE4res *const res_OPEN_DOWNGRADE4 = &resp->nfs_resop4_u.opopen_downgrade; OPEN_DOWNGRADE4resok *resok = &res_OPEN_DOWNGRADE4->OPEN_DOWNGRADE4res_u.resok4; state_t *state_found = NULL; state_owner_t *open_owner; int rc; const char *tag = "OPEN_DOWNGRADE"; char *cause = ""; resp->resop = NFS4_OP_OPEN_DOWNGRADE; res_OPEN_DOWNGRADE4->status = NFS4_OK; /* Do basic checks on a filehandle */ res_OPEN_DOWNGRADE4->status = nfs4_sanity_check_FH(data, NO_FILE_TYPE, false); if (res_OPEN_DOWNGRADE4->status != NFS4_OK) return NFS_REQ_ERROR; /* Open downgrade is done only on a file */ if (data->current_filetype != REGULAR_FILE) { res_OPEN_DOWNGRADE4->status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } /* Check stateid correctness and get pointer to state */ rc = nfs4_Check_Stateid(&arg_OPEN_DOWNGRADE4->open_stateid, data->current_obj, &state_found, data, STATEID_SPECIAL_FOR_LOCK, arg_OPEN_DOWNGRADE4->seqid, data->minorversion == 0, tag); if (rc != NFS4_OK && rc != NFS4ERR_REPLAY) { res_OPEN_DOWNGRADE4->status = rc; LogDebug(COMPONENT_STATE, "OPEN_DOWNGRADE failed nfs4_Check_Stateid"); return NFS_REQ_ERROR; } open_owner = get_state_owner_ref(state_found); if (open_owner == NULL) { /* Unexpected, but something just went stale. */ res_OPEN_DOWNGRADE4->status = NFS4ERR_STALE; goto out2; } PTHREAD_MUTEX_lock(&open_owner->so_mutex); /* Check seqid */ if (data->minorversion == 0 && !Check_nfs4_seqid(open_owner, arg_OPEN_DOWNGRADE4->seqid, op, data->current_obj, resp, tag)) { /* Response is all setup for us and LogDebug told what was wrong */ PTHREAD_MUTEX_unlock(&open_owner->so_mutex); goto out; } PTHREAD_MUTEX_unlock(&open_owner->so_mutex); /* What kind of open is it ? */ LogFullDebug(COMPONENT_STATE, "OPEN_DOWNGRADE: Share Deny = %d Share Access = %d ", arg_OPEN_DOWNGRADE4->share_deny, arg_OPEN_DOWNGRADE4->share_access); res_OPEN_DOWNGRADE4->status = nfs4_do_open_downgrade( op, data, open_owner, state_found, &cause); if (res_OPEN_DOWNGRADE4->status != NFS4_OK) { LogEvent(COMPONENT_STATE, "Failed to open downgrade: %s", cause); goto out; } /* Successful exit */ res_OPEN_DOWNGRADE4->status = NFS4_OK; /* Handle stateid/seqid for success */ update_stateid(state_found, &resok->open_stateid, data, tag); /* Save the response in the open owner */ if (data->minorversion == 0) { Copy_nfs4_state_req(open_owner, arg_OPEN_DOWNGRADE4->seqid, op, data->current_obj, resp, tag); } out: dec_state_owner_ref(open_owner); out2: dec_state_t_ref(state_found); return nfsstat4_to_nfs_req_result(res_OPEN_DOWNGRADE4->status); } /* nfs4_op_opendowngrade */ /** * @brief Free memory allocated for OPEN_DOWNGRADE result * * This function frees any memory allocated for the result of the * NFS4_OP_OPEN_DOWNGRADE operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_open_downgrade_Free(nfs_resop4 *resp) { /* Nothing to be done */ } void nfs4_op_open_downgrade_CopyRes(OPEN_DOWNGRADE4res *res_dst, OPEN_DOWNGRADE4res *res_src) { /* Nothing to deep copy */ } static nfsstat4 nfs4_do_open_downgrade(struct nfs_argop4 *op, compound_data_t *data, state_owner_t *owner, state_t *state, char **cause) { state_status_t state_status; OPEN_DOWNGRADE4args *args = &op->nfs_argop4_u.opopen_downgrade; fsal_status_t fsal_status; fsal_openflags_t openflags = 0; LogFullDebug( COMPONENT_STATE, "Open downgrade current access=%x deny=%x access_prev=%x deny_prev=%x", state->state_data.share.share_access, state->state_data.share.share_deny, state->state_data.share.share_access_prev, state->state_data.share.share_deny_prev); LogFullDebug(COMPONENT_STATE, "Open downgrade to access=%x deny=%x", args->share_access, args->share_deny); STATELOCK_lock(data->current_obj); /* Check if given share access is subset of current share access */ if ((state->state_data.share.share_access & args->share_access) != (args->share_access)) { /* Open share access is not a superset of * downgrade share access */ *cause = " (invalid share access for downgrade)"; STATELOCK_unlock(data->current_obj); return NFS4ERR_INVAL; } /* Check if given share deny is subset of current share deny */ if ((state->state_data.share.share_deny & args->share_deny) != (args->share_deny)) { /* Open share deny is not a superset of * downgrade share deny */ *cause = " (invalid share deny for downgrade)"; STATELOCK_unlock(data->current_obj); return NFS4ERR_INVAL; } /* Check if given share access is previously seen */ if (((state->state_data.share.share_access_prev & (1 << args->share_access)) == 0) || ((state->state_data.share.share_deny_prev & (1 << args->share_deny)) == 0)) { *cause = " (share access or deny never seen before)"; STATELOCK_unlock(data->current_obj); return NFS4ERR_INVAL; } if ((args->share_access & OPEN4_SHARE_ACCESS_READ) != 0) openflags |= FSAL_O_READ; if ((args->share_access & OPEN4_SHARE_ACCESS_WRITE) != 0) openflags |= FSAL_O_WRITE; if ((args->share_deny & OPEN4_SHARE_DENY_READ) != 0) openflags |= FSAL_O_DENY_READ; if ((args->share_deny & OPEN4_SHARE_DENY_WRITE) != 0) openflags |= FSAL_O_DENY_WRITE_MAND; fsal_status = fsal_reopen2(data->current_obj, state, openflags, true); state_status = state_error_convert(fsal_status); STATELOCK_unlock(data->current_obj); if (state_status != STATE_SUCCESS) { *cause = " (state_share_downgrade failed)"; return NFS4ERR_SERVERFAULT; } return NFS4_OK; } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_openattr.c000066400000000000000000000043241473756622300224310ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_openattr.c * @brief Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include "hashtable.h" #include "log.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_proto_functions.h" /** * * @brief NFS4_OP_OPENATTR * * This function implements the NFS4_OP_OPENATTRR operation. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, pp. 370-1 * */ enum nfs_req_result nfs4_op_openattr(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { OPENATTR4args *const arg_OPENATTR4 __attribute__((unused)) = &op->nfs_argop4_u.opopenattr; OPENATTR4res *const res_OPENATTR4 = &resp->nfs_resop4_u.opopenattr; resp->resop = NFS4_OP_OPENATTR; res_OPENATTR4->status = NFS4ERR_NOTSUPP; return NFS_REQ_ERROR; } /* nfs4_op_openattr */ /** * @brief Free memory allocated for OPENATTR result * * This function frees any memory allocated for the result of the * NFS4_OP_OPENATTR operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_openattr_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_putfh.c000066400000000000000000000215461473756622300217300ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_putfh.c * @brief Routines used for managing the NFS4_OP_PUTFH operation. * * Routines used for managing the NFS4_OP_PUTFH operation. * */ #include "config.h" #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_creds.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "export_mgr.h" #include "client_mgr.h" #include "fsal_convert.h" #include "nfs_file_handle.h" #include "pnfs_utils.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif static int nfs4_ds_putfh(compound_data_t *data) { struct file_handle_v4 *v4_handle = (struct file_handle_v4 *)data->currentFH.nfs_fh4_val; struct fsal_pnfs_ds *pds; struct gsh_buffdesc fh_desc; bool changed = true; LogFullDebug(COMPONENT_FILEHANDLE, "NFS4 Handle 0x%X export id %d", v4_handle->fhflags1, ntohs(v4_handle->id.exports)); /* Find any existing server by the "id" from the handle, * before releasing the old DS (to prevent thrashing). */ pds = pnfs_ds_get(ntohs(v4_handle->id.servers)); if (pds == NULL) { LogInfoAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "NFS4 Request from client (%s) has invalid server identifier %d", op_ctx->client ? op_ctx->client->hostaddr_str : "unknown", ntohs(v4_handle->id.servers)); return NFS4ERR_STALE; } /* If old CurrentFH had a related server, note if there is a change, * the reference to the old fsal_pnfs_ds will be released below. */ if (op_ctx->ctx_pnfs_ds != NULL) { changed = ntohs(v4_handle->id.servers) != op_ctx->ctx_pnfs_ds->id_servers; } /* If old CurrentFH had a related export, note the change, the reference * to the old export will be released below. */ if (op_ctx->ctx_export != NULL) { changed |= op_ctx->ctx_export != pds->mds_export; } /* Take export reference if any */ if (pds->mds_export != NULL) get_gsh_export_ref(pds->mds_export); /* Set up the op_context with fsal_pnfs_ds, and export if any. * Will also clean out any old export or fsal_pnfs_ds, dropping * references if any. */ set_op_context_pnfs_ds(pds); /* Clear out current entry for now */ set_current_entry(data, NULL); if (changed) { int status; /* permissions may have changed */ status = pds->s_ops.ds_permissions(pds, data->req); if (status != NFS4_OK) return status; } fh_desc.len = v4_handle->fs_len; fh_desc.addr = &v4_handle->fsopaque; /* Leave the current_entry as NULL, but indicate a * regular file. */ data->current_filetype = REGULAR_FILE; return pds->s_ops.make_ds_handle(pds, &fh_desc, &data->current_ds, v4_handle->fhflags1); } static int nfs4_mds_putfh(compound_data_t *data) { struct file_handle_v4 *v4_handle = (struct file_handle_v4 *)data->currentFH.nfs_fh4_val; struct gsh_export *exporting; char fhbuf[NFS4_FHSIZE]; struct fsal_export *export; struct gsh_buffdesc fh_desc; struct fsal_obj_handle *new_hdl; fsal_status_t fsal_status = { 0, 0 }; bool changed = true; LogFullDebug(COMPONENT_FILEHANDLE, "NFS4 Handle flags 0x%X export id %d", v4_handle->fhflags1, ntohs(v4_handle->id.exports)); LogFullDebugOpaque(COMPONENT_FILEHANDLE, "NFS4 FSAL Handle %s", LEN_FH_STR, v4_handle->fsopaque, v4_handle->fs_len); /* Find any existing export by the "id" from the handle, * before releasing the old export (to prevent thrashing). */ exporting = get_gsh_export(ntohs(v4_handle->id.exports)); LOG_EXPORT(NIV_DEBUG, "PUTFH", exporting, false); if (exporting == NULL) { LogInfoAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "NFS4 Request from client (%s) has invalid export identifier %d", op_ctx->client ? op_ctx->client->hostaddr_str : "unknown", ntohs(v4_handle->id.exports)); return NFS4ERR_STALE; } /* If old CurrentFH had a related export, check if it changed. The * reference will be released below. */ if (op_ctx->ctx_export != NULL) { changed = ntohs(v4_handle->id.exports) != op_ctx->ctx_export->export_id; } /* Clear out current entry for now */ set_current_entry(data, NULL); /* update _ctx fields needed by nfs4_export_check_access and release * any old ctx_export reference. Will also clean up any old * fsal_pnfs_ds that was attached. */ set_op_context_export(exporting); export = exporting->fsal_export; if (changed) { int status; status = nfs4_export_check_access(data->req); if (status != NFS4_OK) { LogFullDebug(COMPONENT_FILEHANDLE, "Export check access failed %s", nfsstat4_to_str(status)); return status; } } /* * FIXME: the wire handle can obviously be no larger than NFS4_FHSIZE, * but there is no such limit on a host handle. Here, we assume that as * the size limit. Eventually it might be nice to call into the FSAL to * ask how large a buffer it needs for a host handle. */ memcpy(fhbuf, &v4_handle->fsopaque, v4_handle->fs_len); fh_desc.len = v4_handle->fs_len; fh_desc.addr = fhbuf; /* adjust the handle opaque into a cache key */ fsal_status = export->exp_ops.wire_to_host( export, FSAL_DIGEST_NFSV4, &fh_desc, v4_handle->fhflags1); if (FSAL_IS_ERROR(fsal_status)) { LogInfo(COMPONENT_FILEHANDLE, "wire_to_host failed %s", msg_fsal_err(fsal_status.major)); return nfs4_Errno_status(fsal_status); } fsal_status = export->exp_ops.create_handle(export, &fh_desc, &new_hdl, NULL); if (FSAL_IS_ERROR(fsal_status)) { LogInfo(COMPONENT_FILEHANDLE, "could not get create_handle object error %s", msg_fsal_err(fsal_status.major)); return nfs4_Errno_status(fsal_status); } /* Set the current entry using the ref from get */ set_current_entry(data, new_hdl); /* Put our ref */ new_hdl->obj_ops->put_ref(new_hdl); LogFullDebug(COMPONENT_FILEHANDLE, "File handle is of type %s(%d)", object_file_type_to_str(data->current_filetype), data->current_filetype); return NFS4_OK; } /** * @brief The NFS4_OP_PUTFH operation * * Sets the current FH with the value given in argument. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 371 * * @see nfs4_Compound * */ enum nfs_req_result nfs4_op_putfh(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { /* Convenience alias for args */ PUTFH4args *const arg_PUTFH4 = &op->nfs_argop4_u.opputfh; /* Convenience alias for response */ PUTFH4res *const res_PUTFH4 = &resp->nfs_resop4_u.opputfh; GSH_AUTO_TRACEPOINT(nfs4, op_putfh_start, TRACE_INFO, "PUTFH start"); resp->resop = NFS4_OP_PUTFH; /* First check the handle. If it is rubbish, we go no further */ res_PUTFH4->status = nfs4_Is_Fh_Invalid(&arg_PUTFH4->object); if (res_PUTFH4->status != NFS4_OK) return NFS_REQ_ERROR; /* If no currentFH were set, allocate one */ if (data->currentFH.nfs_fh4_val == NULL) nfs4_AllocateFH(&data->currentFH); /* Copy the filehandle from the arg structure */ data->currentFH.nfs_fh4_len = arg_PUTFH4->object.nfs_fh4_len; memcpy(data->currentFH.nfs_fh4_val, arg_PUTFH4->object.nfs_fh4_val, arg_PUTFH4->object.nfs_fh4_len); /* The export and fsalid should be updated, but DS handles * don't support metadata operations. Thus, we can't call into * mdcache to populate the metadata cache. */ if (nfs4_Is_Fh_DSHandle(&data->currentFH)) res_PUTFH4->status = nfs4_ds_putfh(data); else res_PUTFH4->status = nfs4_mds_putfh(data); GSH_AUTO_TRACEPOINT(nfs4, op_putfh_end, TRACE_INFO, "PUTFH res: status={}", res_PUTFH4->status); return nfsstat4_to_nfs_req_result(res_PUTFH4->status); } /* nfs4_op_putfh */ /** * @brief Free memory allocated for PUTFH result * * This function frees any memory allocated for the result of the * NFS4_OP_PUTFH operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_putfh_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_putpubfh.c000066400000000000000000000043751473756622300224400ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_putpubfh.c * @brief Routines used for managing the NFS4_OP_PUTPUBFH operation. * * Routines used for managing the NFS4_OP_PUTPUBFH operation. * */ #include "config.h" #include "log.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_proto_functions.h" /** * @brief The NFS4_OP_PUTFH operation * * This function sets the publicFH for the current compound requests * as the current FH. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 371 * * @see nfs4_Compound * */ enum nfs_req_result nfs4_op_putpubfh(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { /* PUTPUBFH really isn't used, just make PUTROOTFH do our work and * call it our own... */ enum nfs_req_result req_result = nfs4_op_putrootfh(op, data, resp); resp->resop = NFS4_OP_PUTPUBFH; return req_result; } /** * @brief Free memory allocated for PUTPUBFH result * * This function frees the memory allocated for the result of the * NFS4_OP_PUTPUBFH operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_putpubfh_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_putrootfh.c000066400000000000000000000114001473756622300226200ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_putrootfh.c * @brief Routines used for managing the NFS4_OP_PUTROOTFH operation. * * Routines used for managing the NFS4_OP_PUTROOTFH operation. */ #include "config.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_convert.h" #include "nfs_exports.h" #include "nfs_file_handle.h" #include "export_mgr.h" #include "nfs_creds.h" #include "nfs_proto_functions.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * * @brief The NFS4_OP_PUTROOTFH operation. * * This functions handles the NFS4_OP_PUTROOTFH operation in * NFSv4. This function can be called only from nfs4_Compound. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 371 * * @see CreateROOTFH4 * */ enum nfs_req_result nfs4_op_putrootfh(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { fsal_status_t status = { 0, 0 }; struct fsal_obj_handle *file_obj; struct gsh_export *root_export; PUTROOTFH4res *const res_PUTROOTFH4 = &resp->nfs_resop4_u.opputrootfh; GSH_AUTO_TRACEPOINT(nfs4, op_putrootfh_start, TRACE_INFO, "PUTROOTFH start"); /* First of all, set the reply to zero to make sure * it contains no parasite information */ memset(resp, 0, sizeof(struct nfs_resop4)); resp->resop = NFS4_OP_PUTROOTFH; /* Clear out current entry for now */ set_current_entry(data, NULL); /* Get the root export of the Pseudo FS and release any old export * reference */ root_export = get_gsh_export_by_pseudo("/", true); LOG_EXPORT(NIV_DEBUG, "PUTROOTFH", root_export, false); set_op_context_export(root_export); if (op_ctx->ctx_export == NULL) { LogCrit(COMPONENT_EXPORT, "Could not get export for Pseudo Root"); res_PUTROOTFH4->status = NFS4ERR_NOENT; return NFS_REQ_ERROR; } /* Build credentials */ res_PUTROOTFH4->status = nfs4_export_check_access(data->req); /* Test for access error (export should not be visible). */ if (res_PUTROOTFH4->status == NFS4ERR_ACCESS) { /* Client has no access at all */ LogDebug(COMPONENT_EXPORT, "Client doesn't have access to Pseudo Root"); return NFS_REQ_ERROR; } if (res_PUTROOTFH4->status != NFS4_OK) { LogMajor(COMPONENT_EXPORT, "Failed to get FSAL credentials Pseudo Root"); return NFS_REQ_ERROR; } /* Get the Pesudo Root inode of the mounted on export */ status = nfs_export_get_root_entry(op_ctx->ctx_export, &file_obj); if (FSAL_IS_ERROR(status)) { LogCrit(COMPONENT_EXPORT, "Could not get root inode for Pseudo Root"); res_PUTROOTFH4->status = nfs4_Errno_status(status); return NFS_REQ_ERROR; } LogMidDebug(COMPONENT_EXPORT, "Root node %p", data->current_obj); set_current_entry(data, file_obj); /* Put our ref */ file_obj->obj_ops->put_ref(file_obj); /* Convert it to a file handle */ if (!nfs4_FSALToFhandle(data->currentFH.nfs_fh4_val == NULL, &data->currentFH, data->current_obj, op_ctx->ctx_export)) { LogCrit(COMPONENT_EXPORT, "Could not get handle for Pseudo Root"); res_PUTROOTFH4->status = NFS4ERR_SERVERFAULT; return NFS_REQ_ERROR; } LogHandleNFS4("NFS4 PUTROOTFH CURRENT FH: ", &data->currentFH); res_PUTROOTFH4->status = NFS4_OK; GSH_AUTO_TRACEPOINT(nfs4, op_putrootfh_end, TRACE_INFO, "PUTROOTFH res: status={}", res_PUTROOTFH4->status); return NFS_REQ_OK; } /* nfs4_op_putrootfh */ /** * @brief Free memory allocated for PUTROOTFH result * * This function frees any memory allocated for the result of * the NFS4_OP_PUTROOTFH function. * * @param[in,out] resp nfs4_op results */ void nfs4_op_putrootfh_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_read.c000066400000000000000000001032501473756622300215060ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_read.c * @brief NFSv4 read operation * * This file implements NFS4_OP_READ within an NFSv4 compound call. */ #include "config.h" #include #include #include #include "hashtable.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include #include #include "fsal_pnfs.h" #include "server_stats.h" #include "export_mgr.h" #include "nfs_exports.h" #include "gsh_rpc.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif struct nfs4_read_data { /** Results for read */ READ4res *res_READ4; /** Owner of state */ state_owner_t *owner; /* Pointer to compound data */ compound_data_t *data; /** Object being acted on */ struct fsal_obj_handle *obj; /** Flags to control synchronization */ uint32_t flags; /** IO Info for READ_PLUS */ struct io_info info; /** Arguments for read call - must be last */ struct fsal_io_arg read_arg; }; /** * Indicate type of read operation this is. */ typedef enum io_direction__ { IO_READ = 1, /*< Reading */ IO_READ_PLUS = 2, /*< Reading plus */ } io_direction_t; static enum nfs_req_result nfs4_complete_read(struct nfs4_read_data *data) { struct fsal_io_arg *read_arg = &data->read_arg; if (data->res_READ4->status == NFS4_OK) { READ4resok *resok = &data->res_READ4->READ4res_u.resok4; if (nfs_param.core_param.getattrs_in_complete_read && !read_arg->end_of_file) { /* * NFS requires to set the EOF flag for all reads that * reach the EOF, i.e., even the ones returning data. * Most FSALs don't set the flag in this case. The only * client that cares about this is ESXi. Other clients * will just see a short read and continue reading and * then get the EOF flag as 0 bytes are returned. */ struct fsal_attrlist attrs; fsal_status_t status; fsal_prepare_attrs(&attrs, ATTR_SIZE); status = data->obj->obj_ops->getattrs(data->obj, &attrs); if (FSAL_IS_SUCCESS(status)) { read_arg->end_of_file = (read_arg->offset + read_arg->io_amount) >= attrs.filesize; } /* Done with the attrs */ fsal_release_attrs(&attrs); } /* Is EOF met or not ? */ resok->eof = read_arg->end_of_file; resok->data.data_len = read_arg->io_amount; if (read_arg->io_amount == 0) { /* We won't need the FSAL's iovec and buffers if it used * them */ if (read_arg->iov_release != NULL) { read_arg->iov_release(read_arg->release_data); read_arg->iov[0].iov_base = NULL; read_arg->iov_release = NULL; } /* We will use the iov we set up before the call, but * set the length of the buffer to 0. The * io_data->release is already set up. */ resok->data.iov[0].iov_len = 0; } else { if (read_arg->iov != resok->data.iov) { /* FSAL returned a different iovector */ resok->data.iov = read_arg->iov; resok->data.iovcnt = read_arg->iov_count; } if (read_arg->iov_release != resok->data.release) { /* The FSAL replaced the release */ resok->data.release = read_arg->iov_release; resok->data.release_data = read_arg->release_data; } } LogFullDebug(COMPONENT_NFS_V4, "NFS4_OP_READ: offset = %" PRIu64 " read length = %zu eof=%u", read_arg->offset, read_arg->io_amount, read_arg->end_of_file); } else { /* Just in case... We won't need the FSAL's iovec and * buffers if it used them */ if (read_arg->iov_release != NULL) read_arg->iov_release(read_arg->release_data); } server_stats_io_done( read_arg->io_request, read_arg->io_amount, (data->res_READ4->status == NFS4_OK) ? true : false, false); if (data->owner != NULL) { op_ctx->clientid = NULL; dec_state_owner_ref(data->owner); } if (read_arg->state) dec_state_t_ref(read_arg->state); return nfsstat4_to_nfs_req_result(data->res_READ4->status); } static void nfs4_complete_read_plus(struct nfs_resop4 *resp, struct io_info *info) { READ4res *const res_READ4 = &resp->nfs_resop4_u.opread; READ_PLUS4res *const res_RPLUS = &resp->nfs_resop4_u.opread_plus; contents *contentp = &res_RPLUS->rpr_resok4.rpr_contents; /* Fixup the eof status from the res_READ4 that res_RPLUS overlays. */ res_RPLUS->rpr_resok4.rpr_eof = res_READ4->READ4res_u.resok4.eof; /* Now fill in the rest of res_RPLUS */ contentp->what = info->io_content.what; res_RPLUS->rpr_resok4.rpr_contents_count = 1; if (info->io_content.what == NFS4_CONTENT_HOLE) { contentp->hole.di_offset = info->io_content.hole.di_offset; contentp->hole.di_length = info->io_content.hole.di_length; } if (info->io_content.what == NFS4_CONTENT_DATA) { contentp->data.d_offset = info->io_content.data.d_offset; contentp->data.d_data.data_len = info->io_content.data.d_data.data_len; contentp->data.d_data.data_val = info->io_content.data.d_data.data_val; } } static fsal_status_t allow_read(struct fsal_obj_handle *obj, uint32_t read_access_check_policy_to_check) { const uint32_t read_access_check_policy = atomic_fetch_uint32_t( &op_ctx->ctx_export->read_access_check_policy); fsal_status_t ret = { 0, 0 }; if (read_access_check_policy & read_access_check_policy_to_check) { ret = obj->obj_ops->test_access(obj, FSAL_READ_ACCESS, NULL, NULL, true); if (ret.major == ERR_FSAL_ACCESS) { /* Test for execute permission */ ret = fsal_access( obj, FSAL_MODE_MASK_SET(FSAL_X_OK) | FSAL_ACE4_MASK_SET( FSAL_ACE_PERM_EXECUTE)); } } return ret; } /** * @brief Callback for NFS4 read done * * @param[in] obj Object being acted on * @param[in] ret Return status of call * @param[in] read_data Data for read call * @param[in] caller_data Data for caller */ static void nfs4_read_cb(struct fsal_obj_handle *obj, fsal_status_t ret, void *read_data, void *caller_data) { struct nfs4_read_data *data = caller_data; uint32_t flags; /* Fixup FSAL_SHARE_DENIED status */ if (ret.major == ERR_FSAL_SHARE_DENIED) ret = fsalstat(ERR_FSAL_LOCKED, 0); if (!FSAL_IS_ERROR(ret)) { /* Perform permission checks after the FSAL does the read */ ret = allow_read(obj, READ_ACCESS_CHECK_POLICY_POST); } /* Get result */ data->res_READ4->status = nfs4_Errno_status(ret); flags = atomic_postset_uint32_t_bits(&data->flags, ASYNC_PROC_DONE); if ((flags & ASYNC_PROC_EXIT) == ASYNC_PROC_EXIT) { /* nfs4_read has already exited, we will need to reschedule * the request for completion. */ svc_resume(data->data->req); } } enum nfs_req_result nfs4_op_read_resume(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { struct nfs4_read_data *read_data = data->op_data; enum nfs_req_result rc; uint32_t flags; if (read_data->read_arg.fsal_resume) { /* FSAL is requesting another read2 call on resume */ atomic_postclear_uint32_t_bits( &read_data->flags, ASYNC_PROC_EXIT | ASYNC_PROC_DONE); read_data->obj->obj_ops->read2(read_data->obj, true, nfs4_read_cb, &read_data->read_arg, read_data); /* Only atomically set the flags if we actually call read2, * otherwise we will have indicated as having been DONE. */ flags = atomic_postset_uint32_t_bits(&read_data->flags, ASYNC_PROC_EXIT); if ((flags & ASYNC_PROC_DONE) != ASYNC_PROC_DONE) { /* The read was not finished before we got here. When * the read completes, nfs4_read_cb() will have to * reschedule the request for completion. The resume * will be resolved by nfs4_op_read_resume() which will * free read_data and return the appropriate return * result. We will NOT go async again for the read op * (but could for a subsequent op in the compound). */ return NFS_REQ_ASYNC_WAIT; } } rc = nfs4_complete_read(data->op_data); if (rc != NFS_REQ_ASYNC_WAIT) { /* We are completely done with the request. This test wasn't * strictly necessary since nfs4_complete_read doesn't async but * at some future time, the getattr it does might go async so we * might as well be prepared here. Our caller is already * prepared for such a scenario. */ gsh_free(data->op_data); data->op_data = NULL; } return rc; } enum nfs_req_result nfs4_op_read_plus_resume(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { struct nfs4_read_data *read_data = data->op_data; enum nfs_req_result rc; uint32_t flags; if (read_data->read_arg.fsal_resume) { /* FSAL is requesting another read2 call on resume */ atomic_postclear_uint32_t_bits( &read_data->flags, ASYNC_PROC_EXIT | ASYNC_PROC_DONE); read_data->obj->obj_ops->read2(read_data->obj, true, nfs4_read_cb, &read_data->read_arg, read_data); /* Only atomically set the flags if we actually call read2, * otherwise we will have indicated as having been DONE. */ flags = atomic_postset_uint32_t_bits(&read_data->flags, ASYNC_PROC_EXIT); if ((flags & ASYNC_PROC_DONE) != ASYNC_PROC_DONE) { /* The read was not finished before we got here. When * the read completes, nfs4_read_cb() will have to * reschedule the request for completion. The resume * will be resolved by nfs4_op_read_resume() which will * free read_data and return the appropriate return * result. We will NOT go async again for the read op * (but could for a subsequent op in the compound). */ return NFS_REQ_ASYNC_WAIT; } } rc = nfs4_complete_read(read_data); if (rc == NFS_REQ_OK) { nfs4_complete_read_plus(resp, &read_data->info); } if (rc != NFS_REQ_ASYNC_WAIT) { /* We are completely done with the request. This test wasn't * strictly necessary since nfs4_complete_read doesn't async but * at some future time, the getattr it does might go async so we * might as well be prepared here. Our caller is already * prepared for such a scenario. */ gsh_free(read_data); data->op_data = NULL; } return rc; } /** * @brief Read on a pNFS pNFS data server * * This function bypasses mdcache and calls directly into the FSAL * to perform a data-server read. * * @param[in] op Arguments for nfs41_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs41_op * * @return per RFC5661, p. 371 * */ static enum nfs_req_result op_dsread(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { READ4args *const arg_READ4 = &op->nfs_argop4_u.opread; READ4res *const res_READ4 = &resp->nfs_resop4_u.opread; /* NFSv4 return code */ nfsstat4 nfs_status = 0; /* Buffer into which data is to be read */ void *buffer = NULL; /* End of file flag */ bool eof = false; READ4resok *resok = &res_READ4->READ4res_u.resok4; /* Don't bother calling the FSAL if the read length is 0. */ if (arg_READ4->count == 0) { resok->eof = FALSE; resok->data.data_len = 0; resok->data.iovcnt = 1; resok->data.iov = &resok->iov0; resok->iov0.iov_len = 0; resok->iov0.iov_base = NULL; res_READ4->status = NFS4_OK; return NFS_REQ_OK; } /* Construct the FSAL file handle */ /* Must allocate buffer as a multiple of BYTES_PER_XDR_UNIT */ buffer = gsh_malloc_aligned(4096, RNDUP(arg_READ4->count)); resok->iov0.iov_base = buffer; resok->iov0.iov_len = arg_READ4->count; resok->data.data_len = arg_READ4->count; resok->data.iovcnt = 1; resok->data.iov = &resok->iov0; nfs_status = op_ctx->ctx_pnfs_ds->s_ops.dsh_read( data->current_ds, &arg_READ4->stateid, arg_READ4->offset, arg_READ4->count, resok->iov0.iov_base, &resok->data.data_len, &eof); if (nfs_status != NFS4_OK) { gsh_free(buffer); resok->data.data_len = 0; resok->iov0.iov_len = 0; resok->iov0.iov_base = NULL; } else { resok->iov0.iov_len = resok->data.data_len; } resok->eof = eof; res_READ4->status = nfs_status; return nfsstat4_to_nfs_req_result(res_READ4->status); } /** * @brief Read on a pNFS pNFS data server * * This function bypasses mdcache and calls directly into the FSAL * to perform a data-server read. * * @param[in] op Arguments for nfs41_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs41_op * * @return per RFC5661, p. 371 * */ static enum nfs_req_result op_dsread_plus(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp, struct io_info *info) { READ4args *const arg_READ4 = &op->nfs_argop4_u.opread; READ_PLUS4res *const res_RPLUS = &resp->nfs_resop4_u.opread_plus; contents *contentp = &res_RPLUS->rpr_resok4.rpr_contents; /* NFSv4 return code */ nfsstat4 nfs_status = 0; /* Buffer into which data is to be read */ void *buffer = NULL; /* End of file flag */ bool eof = false; /* Don't bother calling the FSAL if the read length is 0. */ if (arg_READ4->count == 0) { res_RPLUS->rpr_resok4.rpr_contents_count = 1; res_RPLUS->rpr_resok4.rpr_eof = FALSE; contentp->what = NFS4_CONTENT_DATA; contentp->data.d_offset = arg_READ4->offset; contentp->data.d_data.data_len = 0; contentp->data.d_data.data_val = NULL; res_RPLUS->rpr_status = NFS4_OK; return NFS_REQ_OK; } /* Construct the FSAL file handle */ buffer = gsh_malloc_aligned(4096, RNDUP(arg_READ4->count)); nfs_status = op_ctx->ctx_pnfs_ds->s_ops.dsh_read_plus( data->current_ds, &arg_READ4->stateid, arg_READ4->offset, arg_READ4->count, buffer, arg_READ4->count, &eof, info); res_RPLUS->rpr_status = nfs_status; if (nfs_status != NFS4_OK) { gsh_free(buffer); return NFS_REQ_ERROR; } contentp->what = info->io_content.what; res_RPLUS->rpr_resok4.rpr_contents_count = 1; res_RPLUS->rpr_resok4.rpr_eof = eof; if (info->io_content.what == NFS4_CONTENT_HOLE) { contentp->hole.di_offset = info->io_content.hole.di_offset; contentp->hole.di_length = info->io_content.hole.di_length; } if (info->io_content.what == NFS4_CONTENT_DATA) { contentp->data.d_offset = info->io_content.data.d_offset; contentp->data.d_data.data_len = info->io_content.data.d_data.data_len; contentp->data.d_data.data_val = info->io_content.data.d_data.data_val; } return nfsstat4_to_nfs_req_result(res_RPLUS->rpr_status); } static void read4_io_data_release(void *release_data) { /* Nothing to do */ } static enum nfs_req_result nfs4_read(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp, io_direction_t io, struct io_info *info) { READ4args *const arg_READ4 = &op->nfs_argop4_u.opread; READ4res *const res_READ4 = &resp->nfs_resop4_u.opread; uint64_t size = 0; uint64_t offset = 0; uint64_t MaxRead = 0; uint64_t MaxOffsetRead = 0; fsal_status_t fsal_status = { 0, 0 }; state_t *state_found = NULL; state_t *state_open = NULL; struct fsal_obj_handle *obj = NULL; bool anonymous_started = false; state_owner_t *owner = NULL; bool bypass = false; struct nfs4_read_data *read_data = NULL; struct fsal_io_arg *read_arg = NULL; uint32_t resp_size; /* In case we don't call read2, we indicate the I/O as already done * since in that case we should go ahead and exit as expected. */ uint32_t flags = ASYNC_PROC_DONE; READ4resok *resok = &res_READ4->READ4res_u.resok4; res_READ4->status = NFS4_OK; /* Do basic checks on a filehandle Only files can be read */ res_READ4->status = nfs4_sanity_check_FH(data, REGULAR_FILE, true); if (res_READ4->status != NFS4_OK) return NFS_REQ_ERROR; obj = data->current_obj; /* Check stateid correctness and get pointer to state (also checks for special stateids) */ res_READ4->status = nfs4_Check_Stateid(&arg_READ4->stateid, obj, &state_found, data, STATEID_SPECIAL_ANY, 0, false, "READ"); if (res_READ4->status != NFS4_OK) return NFS_REQ_ERROR; /* NB: After this point, if state_found == NULL, then the stateid is all-0 or all-1 */ if (state_found != NULL) { if (info) info->io_advise = state_found->state_data.io_advise; switch (state_found->state_type) { case STATE_TYPE_SHARE: state_open = state_found; /* Note this causes an extra refcount, but it * simplifies logic below. */ inc_state_t_ref(state_open); /** * @todo FSF: need to check against existing locks */ break; case STATE_TYPE_LOCK: state_open = nfs4_State_Get_Pointer( state_found->state_data.lock.openstate_key); if (state_open == NULL) { res_READ4->status = NFS4ERR_BAD_STATEID; goto out; } /** * @todo FSF: should check that write is in * range of an byte range lock... */ break; case STATE_TYPE_DELEG: /* While doing read operation, we need not check * for deleg state or share access. If share access * is write or read or both, we will always allow * reads as per page 112 in RFC 7530. */ state_open = NULL; break; default: res_READ4->status = NFS4ERR_BAD_STATEID; LogDebug(COMPONENT_NFS_V4_LOCK, "READ with invalid statid of type %d", state_found->state_type); goto out; } /* This is a read operation, this means that the file MUST have been opened for reading */ if (state_open != NULL && (state_open->state_data.share.share_access & OPEN4_SHARE_ACCESS_READ) == 0) { /* Even if file is open for write, the client * may do accidentally read operation (caching). * Because of this, READ is allowed if not * explicitly denied. See page 112 in RFC 7530 * for more details. */ if (state_open->state_data.share.share_deny & OPEN4_SHARE_DENY_READ) { /* Bad open mode, return NFS4ERR_OPENMODE */ res_READ4->status = NFS4ERR_OPENMODE; if (isDebug(COMPONENT_NFS_V4_LOCK)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_stateid(&dspbuf, state_found); LogDebug( COMPONENT_NFS_V4_LOCK, "READ %s doesn't have OPEN4_SHARE_ACCESS_READ", str); } goto out; } } /** * @todo : this piece of code looks a bit suspicious * (see Rong's mail) * * @todo: ACE: This works for now. How do we want to * handle owner confirmation across NFSv4.0/NFSv4.1? * Do we want to mark every NFSv4.1 owner * pre-confirmed, or make the check conditional on * minorversion like we do here? */ switch (state_found->state_type) { case STATE_TYPE_SHARE: if (!state_owner_confirmed(state_found)) { res_READ4->status = NFS4ERR_BAD_STATEID; goto out; } break; case STATE_TYPE_LOCK: case STATE_TYPE_DELEG: break; default: /* Sanity check: all other types are illegal. * we should not got that place (similar check * above), anyway it costs nothing to add this * test */ res_READ4->status = NFS4ERR_BAD_STATEID; goto out; } } else { /* Special stateid, no open state, check to see if any share conflicts */ state_open = NULL; /* Special stateid, no open state, check to see if any share * conflicts The stateid is all-0 or all-1 */ bypass = arg_READ4->stateid.seqid != 0; /* Check for delegation conflict. */ if (state_deleg_conflict(obj, false)) { res_READ4->status = NFS4ERR_DELAY; goto out; } anonymous_started = true; } /* Perform permission checks before the FSAL does the read */ fsal_status = allow_read(obj, READ_ACCESS_CHECK_POLICY_PRE); if (FSAL_IS_ERROR(fsal_status)) { res_READ4->status = nfs4_Errno_status(fsal_status); goto out; } MaxRead = atomic_fetch_uint64_t(&op_ctx->ctx_export->MaxRead); MaxOffsetRead = atomic_fetch_uint64_t(&op_ctx->ctx_export->MaxOffsetRead); /* Get the size and offset of the read operation */ offset = arg_READ4->offset; size = arg_READ4->count; if (MaxOffsetRead < UINT64_MAX) { LogFullDebug(COMPONENT_NFS_V4, "Read offset=%" PRIu64 " size=%" PRIu64 " MaxOffSet=%" PRIu64, offset, size, MaxOffsetRead); if ((offset + size) > MaxOffsetRead) { LogEvent( COMPONENT_NFS_V4, "A client tried to violate max file size %" PRIu64 " for exportid #%hu", MaxOffsetRead, op_ctx->ctx_export->export_id); res_READ4->status = NFS4ERR_FBIG; goto out; } } if (size > MaxRead) { /* the client asked for too much data, this should normally not happen because client will get FATTR4_MAXREAD value at mount time */ if (info == NULL || info->io_content.what != NFS4_CONTENT_HOLE) { LogFullDebug(COMPONENT_NFS_V4, "read requested size = %" PRIu64 " read allowed size = %" PRIu64, size, MaxRead); size = MaxRead; } } /* Now check response size. * size + space for nfsstat4, eof, and data len */ resp_size = RNDUP(size) + sizeof(nfsstat4) + 2 * sizeof(uint32_t); res_READ4->status = check_resp_room(data, resp_size); if (res_READ4->status != NFS4_OK) goto out; data->op_resp_size = resp_size; /* If size == 0, no I/O is to be made and everything is alright */ if (size == 0) { /** @todo Should we handle this case for READ_PLUS? */ /* A size = 0 can not lead to EOF */ resok->eof = false; resok->data.data_len = 0; resok->data.iovcnt = 1; resok->data.iov = &resok->iov0; resok->iov0.iov_len = 0; resok->iov0.iov_base = NULL; res_READ4->status = NFS4_OK; goto out; } /* Some work is to be done */ if (!anonymous_started && data->minorversion == 0) { owner = get_state_owner_ref(state_found); if (owner != NULL) { op_ctx->clientid = &owner->so_owner.so_nfs4_owner.so_clientid; } } /* Set up result using internal iovec of length 1 that allows FSAL * layer to allocate the read buffer. */ resok->data.data_len = size; resok->data.iovcnt = 1; resok->data.iov = &resok->iov0; resok->data.iov[0].iov_len = size; resok->data.iov[0].iov_base = NULL; resok->data.last_iov_buf_size = 0; resok->data.release = read4_io_data_release; /* Set up args, allocate from heap, iov_len will be 1 */ read_data = gsh_calloc(1, sizeof(*read_data)); LogFullDebug(COMPONENT_NFS_V4, "Allocated read_data %p", read_data); read_arg = &read_data->read_arg; read_arg->info = info; read_arg->state = state_found; read_arg->offset = offset; read_arg->iov_count = resok->data.iovcnt; read_arg->last_iov_buf_size = &resok->data.last_iov_buf_size; read_arg->iov = resok->data.iov; read_arg->io_amount = 0; read_arg->end_of_file = false; read_data->res_READ4 = res_READ4; read_data->owner = owner; read_data->data = data; read_data->obj = obj; data->op_data = read_data; if (info != NULL) { /* We will be using the io_info that is part of read_data */ read_data->info.io_advise = info->io_advise; } again: /* Do the actual read */ fsal_read2(obj, bypass, nfs4_read_cb, read_arg, read_data); /* Only atomically set the flags if we actually call read2, otherwise * we will have indicated as having been DONE. */ flags = atomic_postset_uint32_t_bits(&read_data->flags, ASYNC_PROC_EXIT); out: if (state_open != NULL) { dec_state_t_ref(state_open); state_open = NULL; } if ((flags & ASYNC_PROC_DONE) != ASYNC_PROC_DONE) { /* The read was not finished before we got here. When the * read completes, nfs4_read_cb() will have to reschedule the * request for completion. The resume will be resolved by * nfs4_simple_resume() which will free read_data and return * the appropriate return result. We will NOT go async again for * the read op (but could for a subsequent op in the compound). */ return NFS_REQ_ASYNC_WAIT; } if (read_data != NULL && read_arg->fsal_resume) { /* FSAL is requesting another read2 call */ atomic_postclear_uint32_t_bits( &read_data->flags, ASYNC_PROC_EXIT | ASYNC_PROC_DONE); /* Make the call with the same params, though the FSAL will be * signaled by fsal_resume being set. */ goto again; } return nfsstat4_to_nfs_req_result(res_READ4->status); } /* nfs4_op_read */ /** * @brief The NFS4_OP_READ operation * * This functions handles the READ operation in NFSv4.0 This * function can be called only from nfs4_Compound. * * @param[in] op The nfs4_op arguments * @param[in,out] data The compound request's data * @param[out] resp The nfs4_op results * * @return Errors as specified by RFC3550 RFC5661 p. 371. */ enum nfs_req_result nfs4_op_read(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { READ4args *const arg_READ4 = &op->nfs_argop4_u.opread; READ4res *const res_READ4 = &resp->nfs_resop4_u.opread; enum nfs_req_result rc; GSH_AUTO_TRACEPOINT(nfs4, op_read_start, TRACE_INFO, "READ arg: stateid={} offset={} count={}", arg_READ4->stateid.seqid, arg_READ4->offset, arg_READ4->count); /* Say we are managing NFS4_OP_READ */ resp->resop = NFS4_OP_READ; if ((data->minorversion > 0) && nfs4_Is_Fh_DSHandle(&data->currentFH)) { /* DS handle, call op_dsread */ return op_dsread(op, data, resp); } rc = nfs4_read(op, data, resp, IO_READ, NULL); /* We need to complete the request now if we didn't async wait and * op_data (read_data) is present. */ if (rc != NFS_REQ_ASYNC_WAIT && data->op_data != NULL) { /* Go ahead and complete the read. */ rc = nfs4_complete_read(data->op_data); } if (rc != NFS_REQ_ASYNC_WAIT && data->op_data != NULL) { /* We are completely done with the request. This test wasn't * strictly necessary since nfs4_complete_read doesn't async but * at some future time, the getattr it does might go async so we * might as well be prepared here. Our caller is already * prepared for such a scenario. */ gsh_free(data->op_data); data->op_data = NULL; } GSH_AUTO_TRACEPOINT(nfs4, op_read_end, TRACE_INFO, "READ res: rc={} status={} datalen={}", rc, res_READ4->status, res_READ4->READ4res_u.resok4.data.data_len); return rc; } void xdr_READ4res_uio_release(struct xdr_uio *uio, u_int flags) { int ix; LogFullDebug(COMPONENT_NFS_V4, "Releasing %p, references %" PRIi32 ", count %d", uio, uio->uio_references, (int)uio->uio_count); if (!(--uio->uio_references)) { if (!(op_ctx && op_ctx->is_rdma_buff_used)) { for (ix = 0; ix < uio->uio_count; ix++) gsh_free(uio->uio_vio[ix].vio_base); } gsh_free(uio); } } /** * @brief Free data allocated for READ result. * * This function frees any data allocated for the result of the * NFS4_OP_READ operation. * * @param[in,out] resp Results fo nfs4_op * */ void nfs4_op_read_Free(nfs_resop4 *res) { /* Nothing to clean up. */ } /** * @brief The NFS4_OP_READ_PLUS operation * * This functions handles the READ_PLUS operation in NFSv4.2 This * function can be called only from nfs4_Compound. * * @param[in] op The nfs4_op arguments * @param[in,out] data The compound request's data * @param[out] resp The nfs4_op results * * @return Errors as specified by RFC3550 RFC5661 p. 371. */ enum nfs_req_result nfs4_op_read_plus(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { struct io_info info; enum nfs_req_result req_result; memset(&info, 0, sizeof(info)); /* Say we are managing NFS4_OP_READ_PLUS */ resp->resop = NFS4_OP_READ_PLUS; if ((data->minorversion > 0) && nfs4_Is_Fh_DSHandle(&data->currentFH)) { /* DS handle, call op_dsread */ return op_dsread_plus(op, data, resp, &info); } req_result = nfs4_read(op, data, resp, IO_READ_PLUS, &info); /* We need to complete the request now if we didn't async wait and * op_data (read_data) is present. */ if (req_result != NFS_REQ_ASYNC_WAIT && data->op_data != NULL) { /* Go ahead and complete the read. */ req_result = nfs4_complete_read(data->op_data); } if (req_result == NFS_REQ_OK) { struct nfs4_read_data *read_data = data->op_data; nfs4_complete_read_plus( resp, read_data != NULL ? &read_data->info : &info); } if (req_result != NFS_REQ_ASYNC_WAIT && data->op_data != NULL) { /* We are completely done with the request. This test wasn't * strictly necessary since nfs4_complete_read doesn't async but * at some future time, the getattr it does might go async so we * might as well be prepared here. Our caller is already * prepared for such a scenario. */ gsh_free(data->op_data); data->op_data = NULL; } return req_result; } void nfs4_op_read_plus_Free(nfs_resop4 *res) { READ_PLUS4res *resp = &res->nfs_resop4_u.opread_plus; contents *conp = &resp->rpr_resok4.rpr_contents; if (resp->rpr_status == NFS4_OK && conp->what == NFS4_CONTENT_DATA) { if (!op_ctx->is_rdma_buff_used) { if (conp->data.d_data.data_val != NULL) gsh_free(conp->data.d_data.data_val); } conp->data.d_data.data_val = NULL; } } /** * @brief The NFS4_OP_IO_ADVISE operation * * This functions handles the IO_ADVISE operation in NFSv4.2 This * function can be called only from nfs4_Compound. * * @param[in] op The nfs4_op arguments * @param[out] resp The nfs4_op results * * @return Errors as specified by RFC3550 RFC5661 p. 371. */ enum nfs_req_result nfs4_op_io_advise(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { IO_ADVISE4args *const arg_IO_ADVISE = &op->nfs_argop4_u.opio_advise; IO_ADVISE4res *const res_IO_ADVISE = &resp->nfs_resop4_u.opio_advise; fsal_status_t fsal_status = { 0, 0 }; struct io_hints hints; state_t *state_found = NULL; struct fsal_obj_handle *obj = NULL; /* Say we are managing NFS4_OP_IO_ADVISE */ resp->resop = NFS4_OP_IO_ADVISE; res_IO_ADVISE->iaa_status = NFS4_OK; hints.hints = 0; hints.offset = 0; hints.count = 0; if (data->minorversion < 2) { res_IO_ADVISE->iaa_status = NFS4ERR_NOTSUPP; goto done; } /* Do basic checks on a filehandle Only files can be set */ res_IO_ADVISE->iaa_status = nfs4_sanity_check_FH(data, REGULAR_FILE, true); if (res_IO_ADVISE->iaa_status != NFS4_OK) goto done; obj = data->current_obj; /* Check stateid correctness and get pointer to state (also checks for special stateids) */ res_IO_ADVISE->iaa_status = nfs4_Check_Stateid( &arg_IO_ADVISE->iaa_stateid, obj, &state_found, data, STATEID_SPECIAL_ANY, 0, false, "IO_ADVISE"); if (res_IO_ADVISE->iaa_status != NFS4_OK) goto done; if (state_found && obj) { hints.hints = arg_IO_ADVISE->iaa_hints.map[0]; hints.offset = arg_IO_ADVISE->iaa_offset; hints.count = arg_IO_ADVISE->iaa_count; fsal_status = obj->obj_ops->io_advise(obj, &hints); if (FSAL_IS_ERROR(fsal_status)) { res_IO_ADVISE->iaa_status = NFS4ERR_NOTSUPP; goto done; } /* save hints to use with other operations */ state_found->state_data.io_advise = hints.hints; res_IO_ADVISE->iaa_status = NFS4_OK; res_IO_ADVISE->iaa_hints.bitmap4_len = 1; res_IO_ADVISE->iaa_hints.map[0] = hints.hints; } done: LogDebug(COMPONENT_NFS_V4, "Status %s hints 0x%X offset %" PRIu64 " count %" PRIu64, nfsstat4_to_str(res_IO_ADVISE->iaa_status), hints.hints, hints.offset, hints.count); if (state_found != NULL) dec_state_t_ref(state_found); return nfsstat4_to_nfs_req_result(res_IO_ADVISE->iaa_status); } /* nfs4_op_io_advise */ /** * @brief Free memory allocated for IO_ADVISE result * * This function frees any memory allocated for the result of the * NFS4_OP_IO_ADVISE operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_io_advise_Free(nfs_resop4 *resp) { /* Nothing to be done */ } enum nfs_req_result nfs4_op_seek(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { SEEK4args *const arg_SEEK = &op->nfs_argop4_u.opseek; SEEK4res *const res_SEEK = &resp->nfs_resop4_u.opseek; fsal_status_t fsal_status = { 0, 0 }; state_t *state_found = NULL; struct fsal_obj_handle *obj = NULL; struct io_info info; /* Say we are managing NFS4_OP_SEEK */ resp->resop = NFS4_OP_SEEK; if (data->minorversion < 2) { res_SEEK->sr_status = NFS4ERR_NOTSUPP; goto done; } res_SEEK->sr_status = NFS4_OK; /* Do basic checks on a filehandle Only files can be set */ res_SEEK->sr_status = nfs4_sanity_check_FH(data, REGULAR_FILE, true); if (res_SEEK->sr_status != NFS4_OK) goto done; obj = data->current_obj; /* Check stateid correctness and get pointer to state (also checks for special stateids) */ res_SEEK->sr_status = nfs4_Check_Stateid(&arg_SEEK->sa_stateid, obj, &state_found, data, STATEID_SPECIAL_ANY, 0, false, "SEEK"); if (res_SEEK->sr_status != NFS4_OK) goto done; if (state_found != NULL) { info.io_advise = state_found->state_data.io_advise; info.io_content.what = arg_SEEK->sa_what; if (arg_SEEK->sa_what == NFS4_CONTENT_DATA || arg_SEEK->sa_what == NFS4_CONTENT_HOLE) { info.io_content.hole.di_offset = arg_SEEK->sa_offset; } else info.io_content.adb.adb_offset = arg_SEEK->sa_offset; fsal_status = obj->obj_ops->seek2(obj, state_found, &info); if (FSAL_IS_ERROR(fsal_status)) { res_SEEK->sr_status = nfs4_Errno_status(fsal_status); goto done; } res_SEEK->sr_resok4.sr_eof = info.io_eof; res_SEEK->sr_resok4.sr_offset = info.io_content.hole.di_offset; } done: LogDebug(COMPONENT_NFS_V4, "Status %s type %d offset %" PRIu64, nfsstat4_to_str(res_SEEK->sr_status), arg_SEEK->sa_what, arg_SEEK->sa_offset); if (state_found != NULL) dec_state_t_ref(state_found); return nfsstat4_to_nfs_req_result(res_SEEK->sr_status); } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_readdir.c000066400000000000000000000642301473756622300222110ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ #include "config.h" #include "log.h" #include "gsh_rpc.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_creds.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_file_handle.h" #include "nfs_convert.h" #include "export_mgr.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief Opaque bookkeeping structure for NFSv4 readdir * * This structure keeps track of the process of writing out an NFSv4 * READDIR response between calls to nfs4_readdir_callback. */ struct nfs4_readdir_cb_data { XDR xdr; /*< The xdrmem to serialize the entries */ uint8_t *entries; /*< The array holding individual entries */ size_t mem_avail; /*< The amount of memory remaining before we hit maxcount */ int count; /*< Number of entries accumulated so far. */ uint32_t max_count; /*< Maximum number of entries allowed. */ bool has_entries; /*< Track if at least one entry fit */ nfsstat4 error; /*< Set to a value other than NFS4_OK if the callback function finds a fatal error. */ struct bitmap4 *req_attr; /*< The requested attributes */ compound_data_t *data; /*< The compound data, so we can produce nfs_fh4s. */ struct saved_export_context saved; }; static void restore_data(struct nfs4_readdir_cb_data *tracker) { if (tracker->saved.saved_export == NULL) { LogCrit(COMPONENT_NFS_READDIR, "Nothing to restore!"); return; } /* Restore export stuff */ restore_op_context_export(&tracker->saved); /* Restore creds */ if (nfs_req_creds(tracker->data->req) != NFS4_OK) { LogCrit(COMPONENT_EXPORT, "Failure to restore creds"); } } /* Base response of READDIR res ok includes nfsstat4, and verifier. This is the * part of the data that is not serialized with struct dirlist4 */ #define READDIR_RESOK_BASE_SIZE (sizeof(verifier4)) /* Base response of dirlist4 It includes entry termination and eof */ #define DIR_LIST4_BASE_SIZE (2 * BYTES_PER_XDR_UNIT) /* Base response size includes READDIR_RESOK_BASE_SIZE and * DIR_LIST4_BASE_SIZE. */ #define READDIR_RESP_BASE_SIZE (READDIR_RESOK_BASE_SIZE + DIR_LIST4_BASE_SIZE) /* The base size of a readdir entry includes cookie, name length, and the * indicator if a next entry follows. */ #define BASE_ENTRY_SIZE (sizeof(nfs_cookie4) + 2 * sizeof(uint32_t)) /** * @brief Populate entry4s when called from fsal_readdir * * This function is a callback passed to fsal_readdir. It * fills in a pre-allocated array of entry4 structures and allocates * space for the name and attributes. This space must be freed. * * @param[in,out] opaque A struct nfs4_readdir_cb_data that stores the * location of the array and other bookkeeping * information * @param[in] obj Current file * @param[in] attrs The current file's attributes * @param[in] cookie The readdir cookie for the current entry */ fsal_errors_t nfs4_readdir_callback(void *opaque, struct fsal_obj_handle *obj, const struct fsal_attrlist *attr, uint64_t mounted_on_fileid, uint64_t cookie, enum cb_state cb_state) { struct fsal_readdir_cb_parms *cb_parms = opaque; struct nfs4_readdir_cb_data *tracker = cb_parms->opaque; char val_fh[NFS4_FHSIZE]; nfs_fh4 entryFH = { .nfs_fh4_len = 0, .nfs_fh4_val = val_fh }; struct xdr_attrs_args args; compound_data_t *data = tracker->data; fsal_status_t fsal_status; fsal_accessflags_t access_mask_attr = 0; u_int pos_start = xdr_getpos(&tracker->xdr); /* We must leave space to deserialize DIR_LIST4_BASE_SIZE */ u_int mem_avail = tracker->mem_avail - DIR_LIST4_BASE_SIZE; component4 name; bool_t res_false = false; bool_t lock_dir = false; struct fsal_obj_handle *saved_current_obj = NULL; assert(mem_avail >= pos_start); LogFullDebug(COMPONENT_NFS_READDIR, "Entry %s pos %d mem_left %d", cb_parms->name, (int)pos_start, (int)(mem_avail - pos_start)); memset(&args, 0, sizeof(args)); /* Cleanup after problem with junction processing. */ if (cb_state == CB_PROBLEM) { /* Restore the export. */ LogDebug(COMPONENT_NFS_READDIR, "Cleanup after problem with junction processing."); restore_data(tracker); return ERR_FSAL_NO_ERROR; } /* Test if this is a junction. * * NOTE: If there is a junction within a file system (perhaps setting * up different permissions for part of the file system), the * junction inode will ALSO be the root of the nested export. * By testing cb_state, we allow the call back to process * that root inode to proceed rather than getting stuck in a * junction crossing infinite loop. */ if (obj->type == DIRECTORY && cb_parms->attr_allowed && cb_state == CB_ORIGINAL) { lock_dir = true; PTHREAD_RWLOCK_rdlock(&obj->state_hdl->jct_lock); if (obj->state_hdl->dir.junction_export == NULL) goto not_junction; /* This is a junction. Code used to not recognize this * which resulted in readdir giving different attributes * (including FH, FSid, etc...) to clients from a * lookup. AIX refused to list the directory because of * this. Now we go to the junction to get the * attributes. */ LogDebugAlt( COMPONENT_EXPORT, COMPONENT_NFS_READDIR, "Offspring DIR %s is a junction Export_id %d Pseudo %s", cb_parms->name, obj->state_hdl->dir.junction_export->export_id, JCT_PSEUDOPATH(obj->state_hdl)); /* Get a reference to the export and stash it in * compound data. */ if (!export_ready(obj->state_hdl->dir.junction_export)) { /* Export is in the process of being released. * Pretend it's not actually a junction. */ goto not_junction; } get_gsh_export_ref(obj->state_hdl->dir.junction_export); /* Save the compound data context and cross the junction */ save_op_context_export_and_set_export( &tracker->saved, obj->state_hdl->dir.junction_export); /* Build the credentials */ args.rdattr_error = nfs4_export_check_access(data->req); if (args.rdattr_error == NFS4ERR_ACCESS) { /* If return is NFS4ERR_ACCESS then this client * doesn't have access to this export, quietly * skip the export. */ LogDebugAlt( COMPONENT_EXPORT, COMPONENT_NFS_READDIR, "NFS4ERR_ACCESS Skipping Export_Id %d Pseudo %s", op_ctx->ctx_export->export_id, CTX_PSEUDOPATH(op_ctx)); /* Restore export and creds */ restore_data(tracker); /* Indicate success without adding another entry */ cb_parms->in_result = true; PTHREAD_RWLOCK_unlock(&obj->state_hdl->jct_lock); return ERR_FSAL_NO_ERROR; } if (args.rdattr_error == NFS4ERR_WRONGSEC) { /* Client isn't using the right SecType for this export, * we will report NFS4ERR_WRONGSEC in * FATTR4_RDATTR_ERROR. * * If the ONLY attributes requested are * FATTR4_RDATTR_ERROR and FATTR4_MOUNTED_ON_FILEID we * will not return an error and instead will return * success with FATTR4_MOUNTED_ON_FILEID. AIX clients * make this request and expect it to succeed. */ if (check_for_wrongsec_ok_attr(tracker->req_attr)) { /* Client is requesting attr that are allowed * when NFS4ERR_WRONGSEC occurs. */ LogDebugAlt( COMPONENT_EXPORT, COMPONENT_NFS_READDIR, "Ignoring NFS4ERR_WRONGSEC (only asked for MOUNTED_IN_FILEID) On ReadDir Export_Id %d Path %s", op_ctx->ctx_export->export_id, CTX_PSEUDOPATH(op_ctx)); /* Because we are not asking for any attributes * which are a property of the exported file * system's root, really just asking for * MOUNTED_ON_FILEID, we can just get the attr * for this node since it will result in the * correct value for MOUNTED_ON_FILEID since * the fileid of the junction node is the * MOUNTED_ON_FILEID of the root across the * junction, and the mounted_on_filed passed * is the fileid of the junction (since the * node can't be the root of the current * export). * * Go ahead and proceed without an error. */ args.rdattr_error = NFS4_OK; } else { /* We really must report the NFS4ERR_WRONGSEC. * We will report it below, but we need to get * the name into the entry. */ LogDebugAlt( COMPONENT_EXPORT, COMPONENT_NFS_READDIR, "NFS4ERR_WRONGSEC On ReadDir Export_Id %d Pseudo %s", op_ctx->ctx_export->export_id, CTX_PSEUDOPATH(op_ctx)); } } else if (args.rdattr_error == NFS4_OK) { /* Now we must traverse the junction to get the * attributes. We have already set up the compound data. * * Signal to populate_dirent to call back with the * root node of the export across the junction. Also * signal to ourselves that the call back will be * across the junction. */ LogDebugAlt( COMPONENT_EXPORT, COMPONENT_NFS_READDIR, "Need to cross junction to Export_Id %d Pseudo %s", op_ctx->ctx_export->export_id, CTX_PSEUDOPATH(op_ctx)); PTHREAD_RWLOCK_unlock(&obj->state_hdl->jct_lock); return ERR_FSAL_CROSS_JUNCTION; } /* An error occurred and we will report it, but we need to get * the name into the entry to proceed. * * Restore export and creds. */ LogDebugAlt( COMPONENT_EXPORT, COMPONENT_NFS_READDIR, "Need to report error for junction to Export_Id %d Pseudo %s", op_ctx->ctx_export->export_id, CTX_PSEUDOPATH(op_ctx)); restore_data(tracker); } not_junction: if (lock_dir) PTHREAD_RWLOCK_unlock(&obj->state_hdl->jct_lock); args.attrs = (struct fsal_attrlist *)attr; args.data = data; args.hdl4 = &entryFH; args.mounted_on_fileid = mounted_on_fileid; args.fileid = obj->fileid; args.fsid = obj->fsid; /* Now process the entry */ memset(val_fh, 0, NFS4_FHSIZE); /* See if we have space based on max_count. */ if (tracker->count == tracker->max_count) { LogDebug(COMPONENT_NFS_READDIR, "Skipping because we already have %d entries", tracker->count); goto failure; } /* Bits that don't require allocation */ if (xdr_getpos(&tracker->xdr) + BASE_ENTRY_SIZE > mem_avail) { LogDebug(COMPONENT_NFS_READDIR, "Skipping because too small for BASE_ENTRY_SIZE %d", (int)BASE_ENTRY_SIZE); goto failure; } /* The filename. We don't use str2utf8 because that has an * additional copy into a buffer before copying into the * destination. */ name.utf8string_len = strlen(cb_parms->name); name.utf8string_val = (char *)cb_parms->name; if (xdr_getpos(&tracker->xdr) + BASE_ENTRY_SIZE + RNDUP(name.utf8string_len) > mem_avail) { LogDebug(COMPONENT_NFS_READDIR, "Skipping because of name %s too long %d", (char *)cb_parms->name, (int)name.utf8string_len); goto failure; } /* If we carried an error from above, now that we have * the name set up, go ahead and try and put error in * results. */ if (args.rdattr_error != NFS4_OK) { LogDebug(COMPONENT_NFS_READDIR, "Skipping because of %s", nfsstat4_to_str(args.rdattr_error)); goto skip; } if (cb_parms->attr_allowed && attribute_is_set(tracker->req_attr, FATTR4_FILEHANDLE) && !nfs4_FSALToFhandle(false, &entryFH, obj, op_ctx->ctx_export)) { LogDebug(COMPONENT_NFS_READDIR, "Skipping because of problem with handle"); goto server_fault; } if (!cb_parms->attr_allowed) { /* fsal_readdir is signaling us that client didn't have * search permission in this directory, so we can't return any * attributes, but must indicate NFS4ERR_ACCESS. */ args.rdattr_error = NFS4ERR_ACCESS; LogDebug(COMPONENT_NFS_READDIR, "Skipping because of %s", nfsstat4_to_str(args.rdattr_error)); goto skip; } /* Adjust access mask if ACL is asked for. * NOTE: We intentionally do NOT check ACE4_READ_ATTR. */ if (attribute_is_set(tracker->req_attr, FATTR4_ACL)) access_mask_attr |= FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_READ_ACL); /* Attrs were refreshed before call */ fsal_status = obj->obj_ops->test_access(obj, access_mask_attr, NULL, NULL, false); if (FSAL_IS_ERROR(fsal_status)) { LogDebug(COMPONENT_NFS_READDIR, "permission check for attributes status=%s", msg_fsal_err(fsal_status.major)); args.rdattr_error = nfs4_Errno_status(fsal_status); LogDebug(COMPONENT_NFS_READDIR, "Skipping because of %s", nfsstat4_to_str(args.rdattr_error)); goto skip; } /* Tell is_referral to not cache attrs as it will affect readdir * performance */ if (obj->obj_ops->is_referral(obj, (struct fsal_attrlist *)attr, false)) { args.rdattr_error = NFS4ERR_MOVED; LogDebug(COMPONENT_NFS_READDIR, "Skipping because of %s", nfsstat4_to_str(args.rdattr_error)); goto skip; } /* * At this point, current_obj points to what is setup by PUTFH. * If readdir involves junctions, then it is possible that the * FSAL for current_obj and obj could be different. If attributes * requested includes Filesystem attributes, then a call will be * made to the FSAL that owns the object as populate_dirent would * have changed fsal_export to the new export. So, a call to * get_dynamic_info of the FSAL will be made with data->current_obj * which is actually the parent's FSAL object. * This normally happens if NFS clients tries to query FS attributes * in READDIR of a directory that contains junctions (ex:- pseudo * namespace) */ saved_current_obj = data->current_obj; data->current_obj = obj; if (!xdr_encode_entry4(&tracker->xdr, &args, tracker->req_attr, cookie, &name) || (xdr_getpos(&tracker->xdr) + BYTES_PER_XDR_UNIT) > mem_avail) { /* We had an overflow */ LogFullDebug( COMPONENT_NFS_READDIR, "Overflow of buffer after xdr_encode_entry4 - pos = %d", xdr_getpos(&tracker->xdr)); data->current_obj = saved_current_obj; goto failure; } data->current_obj = saved_current_obj; skip: if (args.rdattr_error != NFS4_OK) { tracker->error = args.rdattr_error; if (!attribute_is_set(tracker->req_attr, FATTR4_RDATTR_ERROR) && !attribute_is_set(tracker->req_attr, FATTR4_FS_LOCATIONS)) { LogDebug( COMPONENT_NFS_READDIR, "Skipping because of %s and didn't ask for FATTR4_RDATTR_ERROR or FATTR4_FS_LOCATIONS", nfsstat4_to_str(args.rdattr_error)); goto failure; } if (!xdr_nfs4_fattr_fill_error(&tracker->xdr, tracker->req_attr, cookie, &name, &args) || (xdr_getpos(&tracker->xdr) + BYTES_PER_XDR_UNIT) > mem_avail) { /* We had an overflow */ LogFullDebug( COMPONENT_NFS_READDIR, "Overflow of buffer after xdr_nfs4_fattr_fill_error - pos = %d", xdr_getpos(&tracker->xdr)); goto failure; } } tracker->has_entries = true; cb_parms->in_result = true; tracker->count++; goto out; server_fault: tracker->error = NFS4ERR_SERVERFAULT; failure: if (!tracker->has_entries && tracker->error == NFS4_OK) tracker->error = NFS4ERR_TOOSMALL; /* Reset to where we started this entry and encode a boolean * false instead (entry_follows is false). */ if (!xdr_setpos(&tracker->xdr, pos_start) || !xdr_bool(&tracker->xdr, &res_false)) { /* Oops, what broke... */ LogCrit(COMPONENT_NFS_READDIR, "Unexpected XDR failure processing readdir result"); tracker->error = NFS4ERR_SERVERFAULT; } cb_parms->in_result = false; out: return ERR_FSAL_NO_ERROR; } void xdr_dirlist4_uio_release(struct xdr_uio *uio, u_int flags) { int ix; LogFullDebug(COMPONENT_NFS_V4, "Releasing %p, references %" PRIi32 ", count %d", uio, uio->uio_references, (int)uio->uio_count); if (!(--uio->uio_references)) { if (!(op_ctx && op_ctx->is_rdma_buff_used)) { for (ix = 0; ix < uio->uio_count; ix++) gsh_free(uio->uio_vio[ix].vio_base); } gsh_free(uio); } } /** * @brief NFS4_OP_READDIR * * Implements the NFS4_OP_READDIR operation. If fh is a pseudo FH, * then call is routed to routine nfs4_op_readdir_pseudo * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, pp. 371-2 * */ enum nfs_req_result nfs4_op_readdir(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { READDIR4args *const arg_READDIR4 = &op->nfs_argop4_u.opreaddir; READDIR4res *res_READDIR4 = &resp->nfs_resop4_u.opreaddir; READDIR4resok *resok = &res_READDIR4->READDIR4res_u.resok4; struct fsal_obj_handle *dir_obj = NULL; bool eod_met = false; unsigned long dircount = 0; unsigned long maxcount = 0; verifier4 cookie_verifier; uint64_t cookie = 0; unsigned int num_entries = 0; struct nfs4_readdir_cb_data tracker; fsal_status_t fsal_status = { 0, 0 }; attrmask_t attrmask; bool use_cookie_verifier = op_ctx_export_has_option(EXPORT_OPTION_USE_COOKIE_VERIFIER); GSH_AUTO_TRACEPOINT( nfs4, op_readir_start, TRACE_INFO, "READDIR arg: cookie={} verifier={} dircount={} maxcount={}", arg_READDIR4->cookie, TP_VERIFIER(arg_READDIR4->cookieverf), arg_READDIR4->dircount, arg_READDIR4->maxcount); resp->resop = NFS4_OP_READDIR; res_READDIR4->status = NFS4_OK; res_READDIR4->status = nfs4_sanity_check_FH(data, DIRECTORY, false); memset(&tracker, 0, sizeof(tracker)); if (res_READDIR4->status != NFS4_OK) goto out; dir_obj = data->current_obj; /* get the characteristic value for readdir operation */ dircount = arg_READDIR4->dircount; cookie = arg_READDIR4->cookie; /* Dont over flow V4.1 maxresponsesize or maxcachedsize */ maxcount = resp_room(data); if (nfs_param.core_param.readdir_res_size < maxcount) maxcount = nfs_param.core_param.readdir_res_size; if (maxcount > (arg_READDIR4->maxcount)) maxcount = arg_READDIR4->maxcount; /* Use the dircount from the request as the max number of entries if * lower than the configured max. */ if (dircount > nfs_param.core_param.readdir_max_count) dircount = nfs_param.core_param.readdir_max_count; LogDebug(COMPONENT_NFS_READDIR, "dircount=%lu maxcount=%lu cookie=%" PRIu64, dircount, maxcount, cookie); /* Since we never send a cookie of 1 or 2, we shouldn't ever get * them back. */ if (cookie == 1 || cookie == 2) { res_READDIR4->status = NFS4ERR_BAD_COOKIE; LogDebug(COMPONENT_NFS_READDIR, "Bad cookie"); goto out; } /* Make sure requested attributes are valid and initialize attrmask */ res_READDIR4->status = bitmap4_to_attrmask_t(&arg_READDIR4->attr_request, &attrmask); if (res_READDIR4->status != NFS4_OK) { LogDebug(COMPONENT_NFS_READDIR, "Requested invalid attributes"); goto out; } /* Make sure the FSAL supports all the requested attributes */ attrmask_t supported_attrs = op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export); if ((attrmask & ~supported_attrs) != 0) { res_READDIR4->status = NFS4ERR_INVAL; LogDebug(COMPONENT_NFS_READDIR, "Requested invalid attributes"); goto out; } /* Get only attributes that are allowed to be read */ if (!nfs4_Fattr_Check_Access_Bitmap(&arg_READDIR4->attr_request, FATTR4_ATTR_READ)) { res_READDIR4->status = NFS4ERR_INVAL; LogDebug(COMPONENT_NFS_READDIR, "Requested invalid attributes"); goto out; } /* If maxcount is way too small return NFS4ERR_TOOSMALL */ if (maxcount < READDIR_RESP_BASE_SIZE) { res_READDIR4->status = NFS4ERR_TOOSMALL; LogInfo(COMPONENT_NFS_READDIR, "Response too small maxcount = %lu need at least %llu", maxcount, (unsigned long long)READDIR_RESP_BASE_SIZE); goto out; } /* To make or check the cookie verifier */ memset(cookie_verifier, 0, sizeof(cookie_verifier)); /* If cookie verifier is used, then an non-trivial value is * returned to the client This value is the change attribute of the * directory. If verifier is unused (as in many NFS Servers) then * only a set of zeros is returned (trivial value) */ if (use_cookie_verifier) { struct fsal_attrlist attrs; fsal_prepare_attrs(&attrs, ATTR_CHANGE); fsal_status = data->current_obj->obj_ops->getattrs( data->current_obj, &attrs); if (FSAL_IS_ERROR(fsal_status)) { res_READDIR4->status = nfs4_Errno_status(fsal_status); LogDebug(COMPONENT_NFS_READDIR, "getattrs returned %s", msg_fsal_err(fsal_status.major)); goto out; } memcpy(cookie_verifier, &attrs.change, MIN(sizeof(cookie_verifier), sizeof(attrs.change))); /* Done with the attrs */ fsal_release_attrs(&attrs); } /* Cookie delivered by the server and used by the client SHOULD * not be 0, 1 or 2 because these values are reserved (see RFC * 3530, p. 192/RFC 5661, p468). * * 0 - cookie for first READDIR * 1 - reserved for . on client * 2 - reserved for .. on client * * '.' and '..' are not returned, so all cookies will be offset by 2 */ if (cookie != 0 && use_cookie_verifier) { if (memcmp(cookie_verifier, arg_READDIR4->cookieverf, NFS4_VERIFIER_SIZE) != 0) { res_READDIR4->status = NFS4ERR_BAD_COOKIE; LogDebug(COMPONENT_NFS_READDIR, "Bad cookie"); goto out; } } /* Prepare to read the entries */ tracker.mem_avail = maxcount - READDIR_RESOK_BASE_SIZE; tracker.max_count = dircount; tracker.entries = get_buffer_for_io_response(tracker.mem_avail, NULL); /* If buffer was not assigned, let's allocate it */ if (tracker.entries == NULL) tracker.entries = gsh_malloc(tracker.mem_avail); tracker.error = NFS4_OK; tracker.req_attr = &arg_READDIR4->attr_request; tracker.data = data; xdrmem_create(&tracker.xdr, (char *)tracker.entries, tracker.mem_avail, XDR_ENCODE); /* Assume we need at least the NFS v3 attr. * Any attr is sufficient for permission checking. */ if (attrmask == 0) attrmask = ATTRS_NFS3; /* If seclabel is requested but we do not have * EXPORT_OPTION_SECLABEL_SET, turn off that bit. */ if (attribute_is_set(tracker.req_attr, FATTR4_SEC_LABEL) && !op_ctx_export_has_option(EXPORT_OPTION_SECLABEL_SET)) { attrmask &= ~ATTR4_SEC_LABEL; } /* Perform the readdir operation */ fsal_status = fsal_readdir(dir_obj, cookie, &num_entries, &eod_met, attrmask, nfs4_readdir_callback, &tracker); if (FSAL_IS_ERROR(fsal_status)) { res_READDIR4->status = nfs4_Errno_status(fsal_status); LogDebug(COMPONENT_NFS_READDIR, "fsal_readdir returned %s", msg_fsal_err(fsal_status.major)); goto out_destroy; } LogDebug(COMPONENT_NFS_READDIR, "fsal_readdir returned %s", msg_fsal_err(fsal_status.major)); res_READDIR4->status = tracker.error; if (res_READDIR4->status != NFS4_OK) { LogDebug(COMPONENT_NFS_READDIR, "Tracker error"); goto out_destroy; } /* Response size is the space we used for the entires + the response * base size + nfsstat4 size. */ data->op_resp_size = xdr_getpos(&tracker.xdr) + READDIR_RESP_BASE_SIZE + sizeof(nfsstat4); if (tracker.has_entries) { struct xdr_uio *uio; u_int pos_end; bool_t tmp; if (eod_met) { /* If we hit end of directory, then the dirlist4 we have * encoded so far has a complete entry (we MUST have * consumed the last entry and encoded it). Now we need * to encode a FALSE to indicate no next entry. */ tmp = FALSE; if (!xdr_bool(&tracker.xdr, &tmp)) { /* Oops... */ LogCrit(COMPONENT_NFS_READDIR, "Encode of no next entry failed."); res_READDIR4->status = NFS4ERR_SERVERFAULT; goto out_destroy; } } /* Serialize eod_met into the entries buffer */ tmp = eod_met; if (!xdr_bool(&tracker.xdr, &tmp)) { /* Oops... */ LogCrit(COMPONENT_NFS_READDIR, "Encode of EOD failed."); res_READDIR4->status = NFS4ERR_SERVERFAULT; goto out_destroy; } pos_end = xdr_getpos(&tracker.xdr); /* Get an xdr_uio and fill it in */ uio = gsh_calloc(1, sizeof(struct xdr_uio) + sizeof(struct xdr_uio)); uio->uio_release = xdr_dirlist4_uio_release; uio->uio_count = 1; uio->uio_vio[0].vio_base = tracker.entries; uio->uio_vio[0].vio_head = tracker.entries; uio->uio_vio[0].vio_tail = tracker.entries + pos_end; uio->uio_vio[0].vio_wrap = tracker.entries + pos_end; uio->uio_vio[0].vio_length = pos_end; uio->uio_vio[0].vio_type = VIO_DATA; /* Take over entries buffer */ tracker.entries = NULL; resok->reply.uio = uio; } else { /* Directory was empty - need to register eof */ /* This slight bit of oddness is caused by most booleans * throughout Ganesha being of C99's bool type (taking the * values true and false), but fields in XDR being of the older * bool_t type i(taking the values TRUE and FALSE) */ if (eod_met) res_READDIR4->READDIR4res_u.resok4.reply.eof = TRUE; else res_READDIR4->READDIR4res_u.resok4.reply.eof = FALSE; } /* Do not forget to set the verifier */ memcpy(resok->cookieverf, cookie_verifier, NFS4_VERIFIER_SIZE); /* We never use reply.entries */ resok->reply.entries = NULL; res_READDIR4->status = NFS4_OK; out_destroy: xdr_destroy(&tracker.xdr); out: /* If we allocated but didn't consume entries, free it now. */ if (!op_ctx->is_rdma_buff_used) gsh_free(tracker.entries); tracker.entries = NULL; LogDebug(COMPONENT_NFS_READDIR, "Returning %s", nfsstat4_to_str(res_READDIR4->status)); GSH_AUTO_TRACEPOINT( nfs4, op_readir_end, TRACE_INFO, "READDIR res: status={} verifier={} eof={}", res_READDIR4->status, TP_VERIFIER(res_READDIR4->READDIR4res_u.resok4.cookieverf), res_READDIR4->READDIR4res_u.resok4.reply.eof); return nfsstat4_to_nfs_req_result(res_READDIR4->status); } /* nfs4_op_readdir */ /** * @brief Free memory allocated for READDIR result * * This function frees any memory allocated for the results of the * NFS4_OP_READDIR operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_readdir_Free(nfs_resop4 *res) { /* Nothing to do, entries has been handed off or already freed. */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_readlink.c000066400000000000000000000077071473756622300223760ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_readlink.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include "log.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include "nfs_file_handle.h" #include "gsh_types.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief The NFS4_OP_READLINK operation. * * This function implements the NFS4_OP_READLINK operation. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 372 * * @see nfs4_Compound * */ enum nfs_req_result nfs4_op_readlink(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { READLINK4res *const res_READLINK4 = &resp->nfs_resop4_u.opreadlink; fsal_status_t fsal_status = { 0, 0 }; struct gsh_buffdesc link_buffer = { .addr = NULL, .len = 0 }; uint32_t resp_size; GSH_AUTO_TRACEPOINT(nfs4, op_readlink_start, TRACE_INFO, "READLINK start"); resp->resop = NFS4_OP_READLINK; res_READLINK4->status = NFS4_OK; /* * Do basic checks on a filehandle You can readlink only on a link * ... */ res_READLINK4->status = nfs4_sanity_check_FH(data, SYMBOLIC_LINK, false); if (res_READLINK4->status != NFS4_OK) return NFS_REQ_ERROR; fsal_status = fsal_readlink(data->current_obj, &link_buffer); if (FSAL_IS_ERROR(fsal_status)) { res_READLINK4->status = nfs4_Errno_status(fsal_status); return NFS_REQ_ERROR; } res_READLINK4->READLINK4res_u.resok4.link.utf8string_val = link_buffer.addr; /* NFSv4 does not require the \NUL terminator. */ res_READLINK4->READLINK4res_u.resok4.link.utf8string_len = link_buffer.len - 1; /* Response size is space for nfsstat4, length, pointer, and the * link itself. */ resp_size = RNDUP(link_buffer.len) + 3 * sizeof(uint32_t); res_READLINK4->status = check_resp_room(data, resp_size); if (res_READLINK4->status != NFS4_OK) { /* No room for response, free link. */ gsh_free(res_READLINK4->READLINK4res_u.resok4.link .utf8string_val); } data->op_resp_size = resp_size; GSH_AUTO_TRACEPOINT( nfs4, op_readlink_end, TRACE_INFO, "READLINK res: status={} len={}", res_READLINK4->status, res_READLINK4->READLINK4res_u.resok4.link.utf8string_len); return nfsstat4_to_nfs_req_result(res_READLINK4->status); } /* nfs4_op_readlink */ /** * @brief Free memory allocated for READLINK result * * This function frees the memory allocated for the resutl of the * NFS4_OP_READLINK operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_readlink_Free(nfs_resop4 *res) { READLINK4res *resp = &res->nfs_resop4_u.opreadlink; if (resp->status == NFS4_OK) gsh_free(resp->READLINK4res_u.resok4.link.utf8string_val); } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_reclaim_complete.c000066400000000000000000000071231473756622300241010ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_reclaim_complete.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * * */ #include "config.h" #include #include #include #include #include #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs23.h" #include "nfs4.h" #include "mount.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_file_handle.h" #include "sal_data.h" #include "sal_functions.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * * @brief The NFS4_OP_RECLAIM_COMPLETE4 operation. * * This function implements the NFS4_OP_RECLAIM_COMPLETE4 operation. * * @param[in] op Arguments for the nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for the nfs4_op * * @return per RFC5661 p. 372 * * @see nfs4_Compound * */ enum nfs_req_result nfs4_op_reclaim_complete(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { RECLAIM_COMPLETE4args *const arg_RECLAIM_COMPLETE4 = &op->nfs_argop4_u.opreclaim_complete; RECLAIM_COMPLETE4res *const res_RECLAIM_COMPLETE4 = &resp->nfs_resop4_u.opreclaim_complete; nfs_client_id_t *clientid = data->session->clientid_record; GSH_AUTO_TRACEPOINT(nfs4, op_reclaim_complete_start, TRACE_INFO, "RECLAIM COMPLETE args: rca_one_fs={}", arg_RECLAIM_COMPLETE4->rca_one_fs); resp->resop = NFS4_OP_RECLAIM_COMPLETE; res_RECLAIM_COMPLETE4->rcr_status = NFS4_OK; /* For now, we don't handle rca_one_fs, so we won't complain about * complete already for it. */ if (clientid->cid_cb.v41.cid_reclaim_complete && !arg_RECLAIM_COMPLETE4->rca_one_fs) { res_RECLAIM_COMPLETE4->rcr_status = NFS4ERR_COMPLETE_ALREADY; return NFS_REQ_ERROR; } if (!arg_RECLAIM_COMPLETE4->rca_one_fs) { clientid->cid_cb.v41.cid_reclaim_complete = true; if (clientid->cid_allow_reclaim) atomic_inc_int32_t(&reclaim_completes); } GSH_AUTO_TRACEPOINT(nfs4, op_reclaim_complete_end, TRACE_INFO, "RECLAIM COMPLETE rs: status={}", res_RECLAIM_COMPLETE4->rcr_status); return NFS_REQ_OK; } /* nfs41_op_reclaim_complete */ /** * @brief Free memory allocated for RECLAIM_COMPLETE result * * This function frees anty memory allocated for the result of the * NFS4_OP_RECLAIM_COMPLETE operation. */ void nfs4_op_reclaim_complete_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_release_lockowner.c000066400000000000000000000102161473756622300242750ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_release_lockowner.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * * */ #include "config.h" #include "log.h" #include "nfs4.h" #include "nfs_core.h" #include "sal_functions.h" #include "nfs_proto_functions.h" /** * @brief NFS4_OP_RELEASE_LOCKOWNER * * This function implements the NFS4_OP_RELEASE_LOCKOWNER function. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @retval NFS4_OK or errors for NFSv4.0. * @retval NFS4ERR_NOTSUPP for NFSv4.1. */ enum nfs_req_result nfs4_op_release_lockowner(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { RELEASE_LOCKOWNER4args *const arg_RELEASE_LOCKOWNER4 = &op->nfs_argop4_u.oprelease_lockowner; RELEASE_LOCKOWNER4res *const res_RELEASE_LOCKOWNER4 = &resp->nfs_resop4_u.oprelease_lockowner; nfs_client_id_t *nfs_client_id; state_owner_t *lock_owner; state_nfs4_owner_name_t owner_name; int rc; LogDebug( COMPONENT_NFS_V4_LOCK, "Entering NFS v4 RELEASE_LOCKOWNER handler ----------------------"); resp->resop = NFS4_OP_RELEASE_LOCKOWNER; res_RELEASE_LOCKOWNER4->status = NFS4_OK; if (data->minorversion > 0) { res_RELEASE_LOCKOWNER4->status = NFS4ERR_NOTSUPP; return NFS_REQ_ERROR; } /* Check clientid */ rc = nfs_client_id_get_confirmed( arg_RELEASE_LOCKOWNER4->lock_owner.clientid, &nfs_client_id); if (rc != CLIENT_ID_SUCCESS) { res_RELEASE_LOCKOWNER4->status = clientid_error_to_nfsstat(rc); goto out2; } if (!reserve_lease_or_expire(nfs_client_id, false, NULL)) { dec_client_id_ref(nfs_client_id); res_RELEASE_LOCKOWNER4->status = NFS4ERR_EXPIRED; goto out2; } /* look up the lock owner and see if we can find it */ convert_nfs4_lock_owner(&arg_RELEASE_LOCKOWNER4->lock_owner, &owner_name); /* If this lock owner is not known yet, allocated * and set up a new one */ lock_owner = create_nfs4_owner(&owner_name, nfs_client_id, STATE_LOCK_OWNER_NFSV4, NULL, 0, NULL, CARE_NOT, true); if (lock_owner == NULL) { /* the owner doesn't exist, we are done */ LogDebug(COMPONENT_NFS_V4_LOCK, "lock owner does not exist"); res_RELEASE_LOCKOWNER4->status = NFS4_OK; goto out1; } res_RELEASE_LOCKOWNER4->status = release_lock_owner(lock_owner); /* Release the reference to the lock owner acquired * via create_nfs4_owner */ dec_state_owner_ref(lock_owner); out1: /* Update the lease before exit */ update_lease_simple(nfs_client_id); dec_client_id_ref(nfs_client_id); out2: LogDebug( COMPONENT_NFS_V4_LOCK, "Leaving NFS v4 RELEASE_LOCKOWNER handler -----------------------"); return nfsstat4_to_nfs_req_result(res_RELEASE_LOCKOWNER4->status); } /* nfs4_op_release_lock_owner */ /** * @brief Free memory allocated for REELASE_LOCKOWNER result * * This function frees any memory allocated for the result of the * NFS4_OP_REELASE_LOCKOWNER operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_release_lockowner_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_remove.c000066400000000000000000000121731473756622300220730ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file nfs4_op_remove.c * \brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include "log.h" #include "nfs4.h" #include "nfs_core.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include "nfs_file_handle.h" #include "sal_functions.h" #include "fsal.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief The NFS4_OP_REMOVE operation. * * This function implements the NFS4_OP_REMOVE operation in * NFSv4. This function can be called only from nfs4_Compound. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, pp. 372-3 */ enum nfs_req_result nfs4_op_remove(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { REMOVE4args *const arg_REMOVE4 = &op->nfs_argop4_u.opremove; REMOVE4res *const res_REMOVE4 = &resp->nfs_resop4_u.opremove; struct fsal_obj_handle *parent_obj = NULL; fsal_status_t fsal_status = { 0, 0 }; struct fsal_attrlist parent_pre_attrs, parent_post_attrs; bool is_parent_pre_attrs_valid, is_parent_post_attrs_valid; GSH_AUTO_TRACEPOINT(nfs4, op_remove_start, TRACE_INFO, "REMOVE args: target[{}]={}", arg_REMOVE4->target.utf8string_len, TP_UTF8STR_TRUNCATED(arg_REMOVE4->target)); resp->resop = NFS4_OP_REMOVE; fsal_prepare_attrs(&parent_pre_attrs, ATTR_CHANGE); fsal_prepare_attrs(&parent_post_attrs, ATTR_CHANGE); /* Do basic checks on a filehandle * Delete arg_REMOVE4.target in directory pointed by currentFH * Make sure the currentFH is pointed a directory */ res_REMOVE4->status = nfs4_sanity_check_FH(data, DIRECTORY, false); if (res_REMOVE4->status != NFS4_OK) goto out; /* Validate and convert the UFT8 target to a regular string */ res_REMOVE4->status = nfs4_utf8string_scan(&arg_REMOVE4->target, UTF8_SCAN_PATH_COMP); if (res_REMOVE4->status != NFS4_OK) goto out; if (!nfs_get_grace_status(false)) { res_REMOVE4->status = NFS4ERR_GRACE; goto out; } /* Get the parent obj (aka the current one in the compound data) */ parent_obj = data->current_obj; /* We have to keep track of the 'change' file attribute * for reply structure */ memset(&res_REMOVE4->REMOVE4res_u.resok4.cinfo.before, 0, sizeof(changeid4)); res_REMOVE4->REMOVE4res_u.resok4.cinfo.before = fsal_get_changeid4(parent_obj); fsal_status = fsal_remove(parent_obj, arg_REMOVE4->target.utf8string_val, &parent_pre_attrs, &parent_post_attrs); if (FSAL_IS_ERROR(fsal_status)) { res_REMOVE4->status = nfs4_Errno_status(fsal_status); goto out_put_grace; } is_parent_pre_attrs_valid = FSAL_TEST_MASK(parent_pre_attrs.valid_mask, ATTR_CHANGE); if (is_parent_pre_attrs_valid) { res_REMOVE4->REMOVE4res_u.resok4.cinfo.before = (changeid4)parent_pre_attrs.change; } is_parent_post_attrs_valid = FSAL_TEST_MASK(parent_post_attrs.valid_mask, ATTR_CHANGE); if (is_parent_post_attrs_valid) { res_REMOVE4->REMOVE4res_u.resok4.cinfo.after = (changeid4)parent_post_attrs.change; } else { res_REMOVE4->REMOVE4res_u.resok4.cinfo.after = fsal_get_changeid4(parent_obj); } res_REMOVE4->REMOVE4res_u.resok4.cinfo.atomic = is_parent_pre_attrs_valid && is_parent_post_attrs_valid ? TRUE : FALSE; out_put_grace: fsal_release_attrs(&parent_pre_attrs); fsal_release_attrs(&parent_post_attrs); nfs_put_grace_status(); out: GSH_AUTO_TRACEPOINT( nfs4, op_remove_end, TRACE_INFO, "REMOVE res: status={} " TP_CINFO_FORMAT, res_REMOVE4->status, TP_CINFO_ARGS_EXPAND(res_REMOVE4->REMOVE4res_u.resok4.cinfo)); return nfsstat4_to_nfs_req_result(res_REMOVE4->status); } /* nfs4_op_remove */ /** * @brief Free memory allocated for REMOVE result * * This function frees any memory allocated for the result of the * NFS4_OP_REMOVE operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_remove_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_rename.c000066400000000000000000000153271473756622300220510ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_rename.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * * */ #include "config.h" #include "log.h" #include "nfs4.h" #include "nfs_core.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include "nfs_file_handle.h" #include "sal_functions.h" #include "fsal.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief The NFS4_OP_RENAME operation * * This function implemenats the NFS4_OP_RENAME operation. This * function can be called only from nfs4_Compound * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 373 */ enum nfs_req_result nfs4_op_rename(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { RENAME4args *const arg_RENAME4 = &op->nfs_argop4_u.oprename; RENAME4res *const res_RENAME4 = &resp->nfs_resop4_u.oprename; struct fsal_obj_handle *dst_obj = NULL; struct fsal_obj_handle *src_obj = NULL; struct fsal_attrlist olddir_pre_attrs_out, olddir_post_attrs_out, newdir_pre_attrs_out, newdir_post_attrs_out; bool is_olddir_pre_attrs_valid, is_olddir_post_attrs_valid, is_newdir_pre_attrs_valid, is_newdir_post_attrs_valid; GSH_AUTO_TRACEPOINT(nfs4, op_rename_start, TRACE_INFO, "RENAME args: oldname[{}]={} newname[{}]={}", arg_RENAME4->oldname.utf8string_len, TP_UTF8STR_TRUNCATED(arg_RENAME4->oldname), arg_RENAME4->newname.utf8string_len, TP_UTF8STR_TRUNCATED(arg_RENAME4->newname)); resp->resop = NFS4_OP_RENAME; res_RENAME4->status = NFS4_OK; fsal_prepare_attrs(&olddir_pre_attrs_out, ATTR_CHANGE); fsal_prepare_attrs(&olddir_post_attrs_out, ATTR_CHANGE); fsal_prepare_attrs(&newdir_pre_attrs_out, ATTR_CHANGE); fsal_prepare_attrs(&newdir_post_attrs_out, ATTR_CHANGE); /* Read and validate oldname and newname from uft8 strings. */ res_RENAME4->status = nfs4_utf8string_scan(&arg_RENAME4->oldname, UTF8_SCAN_PATH_COMP); if (res_RENAME4->status != NFS4_OK) goto out; res_RENAME4->status = nfs4_utf8string_scan(&arg_RENAME4->newname, UTF8_SCAN_PATH_COMP); if (res_RENAME4->status != NFS4_OK) goto out; /* Do basic checks on a filehandle */ res_RENAME4->status = nfs4_sanity_check_FH(data, DIRECTORY, false); if (res_RENAME4->status != NFS4_OK) goto out; res_RENAME4->status = nfs4_sanity_check_saved_FH(data, DIRECTORY, false); if (res_RENAME4->status != NFS4_OK) goto out; /* Check that both handles are in the same export. */ if (op_ctx->ctx_export != NULL && data->saved_export != NULL && op_ctx->ctx_export->export_id != data->saved_export->export_id) { res_RENAME4->status = NFS4ERR_XDEV; goto out; } if (!nfs_get_grace_status(false)) { res_RENAME4->status = NFS4ERR_GRACE; goto out; } dst_obj = data->current_obj; src_obj = data->saved_obj; res_RENAME4->RENAME4res_u.resok4.source_cinfo.before = fsal_get_changeid4(src_obj); res_RENAME4->RENAME4res_u.resok4.target_cinfo.before = fsal_get_changeid4(dst_obj); res_RENAME4->status = nfs4_Errno_status( fsal_rename(src_obj, arg_RENAME4->oldname.utf8string_val, dst_obj, arg_RENAME4->newname.utf8string_val, &olddir_pre_attrs_out, &olddir_post_attrs_out, &newdir_pre_attrs_out, &newdir_post_attrs_out)); is_olddir_pre_attrs_valid = FSAL_TEST_MASK(olddir_pre_attrs_out.valid_mask, ATTR_CHANGE); if (is_olddir_pre_attrs_valid) { res_RENAME4->RENAME4res_u.resok4.source_cinfo.before = (changeid4)olddir_pre_attrs_out.change; } is_olddir_post_attrs_valid = FSAL_TEST_MASK(olddir_post_attrs_out.valid_mask, ATTR_CHANGE); if (is_olddir_post_attrs_valid) { res_RENAME4->RENAME4res_u.resok4.source_cinfo.after = (changeid4)olddir_post_attrs_out.change; } else { res_RENAME4->RENAME4res_u.resok4.source_cinfo.after = fsal_get_changeid4(src_obj); } is_newdir_pre_attrs_valid = FSAL_TEST_MASK(newdir_pre_attrs_out.valid_mask, ATTR_CHANGE); if (is_newdir_pre_attrs_valid) { res_RENAME4->RENAME4res_u.resok4.target_cinfo.before = (changeid4)newdir_pre_attrs_out.change; } is_newdir_post_attrs_valid = FSAL_TEST_MASK(newdir_post_attrs_out.valid_mask, ATTR_CHANGE); if (is_newdir_post_attrs_valid) { res_RENAME4->RENAME4res_u.resok4.target_cinfo.after = (changeid4)newdir_post_attrs_out.change; } else { res_RENAME4->RENAME4res_u.resok4.target_cinfo.after = fsal_get_changeid4(dst_obj); } res_RENAME4->RENAME4res_u.resok4.source_cinfo.atomic = is_olddir_pre_attrs_valid && is_olddir_post_attrs_valid ? TRUE : FALSE; res_RENAME4->RENAME4res_u.resok4.target_cinfo.atomic = is_newdir_pre_attrs_valid && is_newdir_post_attrs_valid ? TRUE : FALSE; nfs_put_grace_status(); out: fsal_release_attrs(&olddir_pre_attrs_out); fsal_release_attrs(&olddir_post_attrs_out); fsal_release_attrs(&newdir_pre_attrs_out); fsal_release_attrs(&newdir_post_attrs_out); GSH_AUTO_TRACEPOINT( nfs4, op_rename_end, TRACE_INFO, "RENAME res: status={} source_cinfo: " TP_CINFO_FORMAT " target_cinfo: " TP_CINFO_FORMAT, res_RENAME4->status, TP_CINFO_ARGS_EXPAND( res_RENAME4->RENAME4res_u.resok4.source_cinfo), TP_CINFO_ARGS_EXPAND( res_RENAME4->RENAME4res_u.resok4.target_cinfo)); return nfsstat4_to_nfs_req_result(res_RENAME4->status); } /** * @brief Free memory allocated for RENAME result * * This function frees any memory allocated for the result of the * NFS4_OP_RENAME operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_rename_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_renew.c000066400000000000000000000073151473756622300217200ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_renew.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * */ #include "config.h" #include "log.h" #include "nfs4.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_core.h" #include "nfs_rpc_callback.h" #include "server_stats.h" /** * @brief The NFS4_OP_RENEW operation. * * This function implements the NFS4_OP_RENEW operation. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @retval NFS4_OK or errors for NFSv4.0. * @retval NFS4ERR_NOTSUPP for NFSv4.1. * * @see nfs4_Compound * */ enum nfs_req_result nfs4_op_renew(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { RENEW4args *const arg_RENEW4 = &op->nfs_argop4_u.oprenew; RENEW4res *const res_RENEW4 = &resp->nfs_resop4_u.oprenew; nfs_client_id_t *clientid; int rc; /* Lock are not supported */ memset(resp, 0, sizeof(struct nfs_resop4)); resp->resop = NFS4_OP_RENEW; if (data->minorversion > 0) { res_RENEW4->status = NFS4ERR_NOTSUPP; return NFS_REQ_ERROR; } /* Tell the admin what I am doing... */ LogFullDebug(COMPONENT_CLIENTID, "RENEW Client id = %" PRIx64, arg_RENEW4->clientid); /* Is this an existing client id ? */ rc = nfs_client_id_get_confirmed(arg_RENEW4->clientid, &clientid); if (rc != CLIENT_ID_SUCCESS) { /* Unknown client id */ res_RENEW4->status = clientid_error_to_nfsstat(rc); return NFS_REQ_ERROR; } /* Check if the lease is already expired, if not update it. */ if (!reserve_lease_or_expire(clientid, true, NULL)) { res_RENEW4->status = NFS4ERR_EXPIRED; } else { /** @todo - it doesn't look like this cb_channel stuff is * thread safe... Maybe it's actually OK? */ /* Check the state of callback path and return correct error */ if (nfs_param.nfsv4_param.allow_delegations && get_cb_chan_down(clientid) && clientid->curr_deleg_grants) { res_RENEW4->status = NFS4ERR_CB_PATH_DOWN; /* Set the time for first PATH_DOWN response */ if (clientid->first_path_down_resp_time == 0) clientid->first_path_down_resp_time = time(NULL); } else { res_RENEW4->status = NFS4_OK; /* Reset */ clientid->first_path_down_resp_time = 0; } } dec_client_id_ref(clientid); return nfsstat4_to_nfs_req_result(res_RENEW4->status); } /* nfs4_op_renew */ /** * @brief Free memory allocated for RENEW result * * This function frees any memory allocated for the result of the * NFS4_OP_RENEW operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_renew_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_restorefh.c000066400000000000000000000120641473756622300225760ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_restorefh.c * @brief The NFS4_OP_RESTOREFH operation. * * Routines used for managing the NFS4_OP_RESTOREFH operation. */ #include "config.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_file_handle.h" #include "export_mgr.h" #include "pnfs_utils.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * * @brief The NFS4_OP_RESTOREFH operation. * * This functions handles the NFS4_OP_RESTOREFH operation in * NFSv4. This function can be called only from nfs4_Compound. This * operation replaces the current FH with the previously saved FH. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 373 * * @see nfs4_Compound * */ enum nfs_req_result nfs4_op_restorefh(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { RESTOREFH4res *const res_RESTOREFH = &resp->nfs_resop4_u.oprestorefh; /* First of all, set the reply to zero to make sure it contains no parasite information */ memset(resp, 0, sizeof(struct nfs_resop4)); resp->resop = NFS4_OP_RESTOREFH; res_RESTOREFH->status = NFS4_OK; LogFullDebugOpaque(COMPONENT_FILEHANDLE, "Saved FH %s", LEN_FH_STR, data->savedFH.nfs_fh4_val, data->savedFH.nfs_fh4_len); GSH_AUTO_TRACEPOINT(nfs4, op_restorefh_start, TRACE_INFO, "RESTOREFH start"); /* If there is no savedFH, then return an error */ if (nfs4_Is_Fh_Empty(&(data->savedFH)) == NFS4ERR_NOFILEHANDLE) { /* There is no current FH, return NFS4ERR_RESTOREFH * (cg RFC3530, page 202) */ res_RESTOREFH->status = NFS4ERR_RESTOREFH; return NFS_REQ_ERROR; } /* Do basic checks on saved filehandle */ res_RESTOREFH->status = nfs4_sanity_check_saved_FH(data, NO_FILE_TYPE, true); if (res_RESTOREFH->status != NFS4_OK) return NFS_REQ_ERROR; /* Determine if we can get a new export reference. If there is * no saved export, don't get a reference to it. */ if (data->saved_export != NULL) { if (!export_ready(data->saved_export)) { /* The SavedFH export has gone bad. */ res_RESTOREFH->status = NFS4ERR_STALE; return NFS_REQ_ERROR; } get_gsh_export_ref(data->saved_export); } /* Copy the data from saved FH to current FH */ memcpy(data->currentFH.nfs_fh4_val, data->savedFH.nfs_fh4_val, data->savedFH.nfs_fh4_len); data->currentFH.nfs_fh4_len = data->savedFH.nfs_fh4_len; /* Restore the export information and release any old export reference * and any old fsal_pnfs_ds reference. */ set_op_context_export(data->saved_export); op_ctx->export_perms = data->saved_export_perms; /* If saved_pnfs_ds is present, get an additional reference and restore * it. */ if (data->saved_pnfs_ds != NULL) { op_ctx->ctx_pnfs_ds = data->saved_pnfs_ds; pnfs_ds_get_ref(op_ctx->ctx_pnfs_ds); } /* No need to call nfs4_SetCompoundExport or nfs4_MakeCred * because we are restoring saved information, and the * credential checking may be skipped. */ /* Update the current entry */ set_current_entry(data, data->saved_obj); /* Restore the saved stateid */ data->current_stateid = data->saved_stateid; data->current_stateid_valid = data->saved_stateid_valid; /* Make RESTOREFH work right for DS handle */ if (data->current_ds != NULL) { data->current_ds = data->saved_ds; data->current_filetype = data->saved_filetype; } LogHandleNFS4("RESTORE FH: Current FH ", &data->currentFH); GSH_AUTO_TRACEPOINT(nfs4, op_restorefh_end, TRACE_INFO, "RESTOREFH res: status={}", res_RESTOREFH->status); return NFS_REQ_OK; } /* nfs4_op_restorefh */ /** * @brief Free memory allocated for RESTOREFH result * * This function frees any memory allocated for the result of the * NFS4_OP_RESTOREFH operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_restorefh_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_savefh.c000066400000000000000000000150321473756622300220470ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_savefh.c * @brief Routines used for managing the NFS4_OP_SAVEFH operation. * * Routines used for managing the NFS4_OP_SAVEFH operation. */ #include "config.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_file_handle.h" #include "export_mgr.h" #include "pnfs_utils.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief Set the saved entry in the context * * This manages refcounting on the object being stored in data. This means it * takes a ref on a new object, and releases it's ref on any old object. If the * caller has it's own ref, it must release it itself. * * @param[in] data Compound data to set entry in * @param[in] obj Object to set */ void set_saved_entry(compound_data_t *data, struct fsal_obj_handle *obj) { struct saved_export_context saved; bool restore_op_ctx = false; if (data->saved_ds != NULL || data->saved_obj != NULL) { /* Setup correct op_ctx for releasing old saved */ get_gsh_export_ref(data->saved_export); save_op_context_export_and_set_export(&saved, data->saved_export); op_ctx->export_perms = data->saved_export_perms; restore_op_ctx = true; } /* Mark saved_stateid as invalid */ data->saved_stateid_valid = false; if (data->saved_ds != NULL && data->saved_ds != data->current_ds) { /* Release the saved_ds because it's different. We don't * bother with refcounting because a ds handle has a limited * lifetime and it's either current_ds or saved_ds. So as long * as current_ds is not the same one here, we can release since * there is no other reference. */ data->saved_pnfs_ds->s_ops.dsh_release(data->saved_ds); } if (data->saved_obj) { /* Release ref on old object */ data->saved_obj->obj_ops->put_ref(data->saved_obj); } data->saved_obj = obj; if (obj == NULL) { data->saved_filetype = NO_FILE_TYPE; } else { /* Get our ref on the new object */ data->saved_obj->obj_ops->get_ref(data->saved_obj); /* Set the saved file type */ data->saved_filetype = obj->type; } if (restore_op_ctx) { /* Restore op_ctx */ restore_op_context_export(&saved); } /* Copy the new current_ds if any */ data->saved_ds = data->current_ds; } /** * * @brief the NFS4_OP_SAVEFH operation * * This functions handles the NFS4_OP_SAVEFH operation in NFSv4. This * function can be called only from nfs4_Compound. The operation set * the savedFH with the value of the currentFH. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 373 * * @see nfs4_Compound * */ enum nfs_req_result nfs4_op_savefh(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { SAVEFH4res *const res_SAVEFH = &resp->nfs_resop4_u.opsavefh; GSH_AUTO_TRACEPOINT(nfs4, op_savefh_start, TRACE_INFO, "SAVEFH start"); /* First of all, set the reply to zero to make sure it contains no * parasite information */ memset(resp, 0, sizeof(struct nfs_resop4)); resp->resop = NFS4_OP_SAVEFH; res_SAVEFH->status = NFS4_OK; /* Do basic checks on a filehandle */ res_SAVEFH->status = nfs4_sanity_check_FH(data, NO_FILE_TYPE, true); if (res_SAVEFH->status != NFS4_OK) return NFS_REQ_ERROR; /* If the savefh is not allocated, do it now */ if (data->savedFH.nfs_fh4_val == NULL) nfs4_AllocateFH(&data->savedFH); /* Determine if we can get a new export reference. If there is * no op_ctx->ctx_export, don't get a reference. */ if (op_ctx->ctx_export != NULL) { if (!export_ready(op_ctx->ctx_export)) { /* The CurrentFH export has gone bad. */ res_SAVEFH->status = NFS4ERR_STALE; return NFS_REQ_ERROR; } get_gsh_export_ref(op_ctx->ctx_export); } /* Copy the data from current FH to saved FH */ memcpy(data->savedFH.nfs_fh4_val, data->currentFH.nfs_fh4_val, data->currentFH.nfs_fh4_len); data->savedFH.nfs_fh4_len = data->currentFH.nfs_fh4_len; /* If saved and current entry are equal, skip the following. */ if (data->saved_obj != data->current_obj) { set_saved_entry(data, data->current_obj); } /* Save the current stateid */ data->saved_stateid = data->current_stateid; data->saved_stateid_valid = data->current_stateid_valid; /* If old SavedFH had a related export, release reference. */ if (data->saved_export != NULL) put_gsh_export(data->saved_export); /* If old saved_pnfs_ds is present, release reference. */ if (data->saved_pnfs_ds != NULL) pnfs_ds_put(data->saved_pnfs_ds); /* Save the export information (reference already taken above) and * the pnfs_ds (if any, otherwise clear it, reference taken below). */ data->saved_export = op_ctx->ctx_export; data->saved_export_perms = op_ctx->export_perms; data->saved_pnfs_ds = op_ctx->ctx_pnfs_ds; /* If ctx_pnfs_ds is present, take a ref and save it. */ if (op_ctx->ctx_pnfs_ds != NULL) pnfs_ds_get_ref(data->saved_pnfs_ds); LogHandleNFS4("SAVE FH: Saved FH ", &data->savedFH); res_SAVEFH->status = NFS4_OK; GSH_AUTO_TRACEPOINT(nfs4, op_savefh_end, TRACE_INFO, "SAVEFH res: status={}", res_SAVEFH->status); return NFS_REQ_OK; } /* nfs4_op_savefh */ /** * @brief Free memory allocated for SAVEFH result * * This function frees any memory allocated for the result of the * NFS4_OP_SAVEFH function. * * @param[in,out] resp nfs4_op results */ void nfs4_op_savefh_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_secinfo.c000066400000000000000000000233361473756622300222270ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_secinfo.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_creds.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include "sal_functions.h" #include "export_mgr.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif #ifdef _HAVE_GSSAPI /* flavor, oid len, qop, service */ #define GSS_RESP_SIZE (4 * BYTES_PER_XDR_UNIT) #endif /* nfsstat4, resok_len, 2 flavors * NOTE this requires space for up to 2 extra xdr units if the export doesn't * allow AUTH_NONE and/or AUTH_UNIX. The response size is overall so small * this op should never be the cause of overflow of maxrespsize... */ #define RESP_SIZE (4 * BYTES_PER_XDR_UNIT) /** * @brief NFSv4 SECINFO operation * * This function implements the NFSv4 SECINFO operation. * * @param[in] op Operation request * @param[in,out] data Compound data * @param[out] resp Response * * @return NFS status codes. */ enum nfs_req_result nfs4_op_secinfo(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { SECINFO4args *const arg_SECINFO4 = &op->nfs_argop4_u.opsecinfo; SECINFO4res *const res_SECINFO4 = &resp->nfs_resop4_u.opsecinfo; secinfo4 *resok_val; fsal_status_t fsal_status = { 0, 0 }; struct fsal_obj_handle *obj_src = NULL; #ifdef _HAVE_GSSAPI sec_oid4 v5oid = { krb5oid.length, (char *)krb5oid.elements }; #endif /* _HAVE_GSSAPI */ int num_entry = 0; struct saved_export_context saved; bool restore_op_ctx = false; uint32_t resp_size = RESP_SIZE; int idx = 0; struct gsh_export *junction_export = NULL; GSH_AUTO_TRACEPOINT(nfs4, op_secinfo_start, TRACE_INFO, "SECINFO arg: name[{}]={}", arg_SECINFO4->name.utf8string_len, TP_UTF8STR_TRUNCATED(arg_SECINFO4->name)); resp->resop = NFS4_OP_SECINFO; res_SECINFO4->status = NFS4_OK; /* Read name from uft8 strings, if one is empty then returns * NFS4ERR_INVAL */ res_SECINFO4->status = nfs4_utf8string_scan(&arg_SECINFO4->name, UTF8_SCAN_PATH_COMP); if (res_SECINFO4->status != NFS4_OK) goto out; /* Do basic checks on a filehandle SecInfo is done only on a directory */ res_SECINFO4->status = nfs4_sanity_check_FH(data, DIRECTORY, false); if (res_SECINFO4->status != NFS4_OK) goto out; fsal_status = fsal_lookup(data->current_obj, arg_SECINFO4->name.utf8string_val, &obj_src, NULL); if (obj_src == NULL) { res_SECINFO4->status = nfs4_Errno_status(fsal_status); goto out; } /* Get state lock for junction_export */ if (obj_src->type == DIRECTORY) { PTHREAD_RWLOCK_rdlock(&obj_src->state_hdl->jct_lock); junction_export = obj_src->state_hdl->dir.junction_export; } if (junction_export != NULL) { /* Handle junction (first phase - junction lock required). */ /* Try to get a reference to the export. */ if (!export_ready(junction_export)) { /* Export has gone bad. */ LogDebug(COMPONENT_EXPORT, "NFS4ERR_STALE On Export_Id %d Pseudo %s", junction_export->export_id, JCT_PSEUDOPATH(obj_src->state_hdl)); res_SECINFO4->status = NFS4ERR_STALE; PTHREAD_RWLOCK_unlock(&obj_src->state_hdl->jct_lock); goto out; } get_gsh_export_ref(junction_export); } if (obj_src->type == DIRECTORY) PTHREAD_RWLOCK_unlock(&obj_src->state_hdl->jct_lock); if (junction_export != NULL) { /* Handle junction (second phase - junction lock not needed). */ struct fsal_obj_handle *obj = NULL; /* Save the compound data context */ save_op_context_export_and_set_export(&saved, junction_export); restore_op_ctx = true; /* Build credentials */ res_SECINFO4->status = nfs4_export_check_access(data->req); /* Test for access error (export should not be visible). */ if (res_SECINFO4->status == NFS4ERR_ACCESS) { /* If return is NFS4ERR_ACCESS then this client doesn't * have access to this export, return NFS4ERR_NOENT to * hide it. It was not visible in READDIR response. */ LogDebug( COMPONENT_EXPORT, "NFS4ERR_ACCESS Hiding Export_Id %d Pseudo %s with NFS4ERR_NOENT", op_ctx->ctx_export->export_id, CTX_PSEUDOPATH(op_ctx)); res_SECINFO4->status = NFS4ERR_NOENT; goto out; } /* Only other error is NFS4ERR_WRONGSEC which is actually * what we expect here. Finish crossing the junction. */ fsal_status = nfs_export_get_root_entry(op_ctx->ctx_export, &obj); if (FSAL_IS_ERROR(fsal_status)) { LogMajor( COMPONENT_EXPORT, "PSEUDO FS JUNCTION TRAVERSAL: Failed to get root for %s, id=%d, status = %s", CTX_PSEUDOPATH(op_ctx), op_ctx->ctx_export->export_id, fsal_err_txt(fsal_status)); res_SECINFO4->status = nfs4_Errno_status(fsal_status); goto out; } LogDebug( COMPONENT_EXPORT, "PSEUDO FS JUNCTION TRAVERSAL: Crossed to %s, id=%d for name=%s", CTX_PSEUDOPATH(op_ctx), op_ctx->ctx_export->export_id, arg_SECINFO4->name.utf8string_val); /* Swap in the obj on the other side of the junction. */ obj_src->obj_ops->put_ref(obj_src); obj_src = obj; } /* Get the number of entries */ #ifdef _HAVE_GSSAPI if (op_ctx->export_perms.options & EXPORT_OPTION_RPCSEC_GSS_NONE) num_entry++; if (op_ctx->export_perms.options & EXPORT_OPTION_RPCSEC_GSS_INTG) num_entry++; if (op_ctx->export_perms.options & EXPORT_OPTION_RPCSEC_GSS_PRIV) num_entry++; resp_size += (RNDUP(krb5oid.length) + GSS_RESP_SIZE) * num_entry; #endif if (op_ctx->export_perms.options & EXPORT_OPTION_AUTH_NONE) num_entry++; if (op_ctx->export_perms.options & EXPORT_OPTION_AUTH_UNIX) num_entry++; /* Check for space in response. */ res_SECINFO4->status = check_resp_room(data, resp_size); if (res_SECINFO4->status != NFS4_OK) goto out; data->op_resp_size = resp_size; resok_val = gsh_calloc(num_entry, sizeof(secinfo4)); res_SECINFO4->SECINFO4res_u.resok4.SECINFO4resok_val = resok_val; /** * @todo We have the opportunity to associate a preferred * security triple with a specific fs/export. For now, list * all implemented. */ /* List the security flavors in the order we prefer */ #ifdef _HAVE_GSSAPI if (op_ctx->export_perms.options & EXPORT_OPTION_RPCSEC_GSS_PRIV) { resok_val[idx].flavor = RPCSEC_GSS; resok_val[idx].secinfo4_u.flavor_info.service = RPCSEC_GSS_SVC_PRIVACY; resok_val[idx].secinfo4_u.flavor_info.qop = GSS_C_QOP_DEFAULT; resok_val[idx++].secinfo4_u.flavor_info.oid = v5oid; } if (op_ctx->export_perms.options & EXPORT_OPTION_RPCSEC_GSS_INTG) { resok_val[idx].flavor = RPCSEC_GSS; resok_val[idx].secinfo4_u.flavor_info.service = RPCSEC_GSS_SVC_INTEGRITY; resok_val[idx].secinfo4_u.flavor_info.qop = GSS_C_QOP_DEFAULT; resok_val[idx++].secinfo4_u.flavor_info.oid = v5oid; } if (op_ctx->export_perms.options & EXPORT_OPTION_RPCSEC_GSS_NONE) { resok_val[idx].flavor = RPCSEC_GSS; resok_val[idx].secinfo4_u.flavor_info.service = RPCSEC_GSS_SVC_NONE; resok_val[idx].secinfo4_u.flavor_info.qop = GSS_C_QOP_DEFAULT; resok_val[idx++].secinfo4_u.flavor_info.oid = v5oid; } #endif /* _HAVE_GSSAPI */ if (op_ctx->export_perms.options & EXPORT_OPTION_AUTH_UNIX) resok_val[idx++].flavor = AUTH_UNIX; if (op_ctx->export_perms.options & EXPORT_OPTION_AUTH_NONE) resok_val[idx++].flavor = AUTH_NONE; res_SECINFO4->SECINFO4res_u.resok4.SECINFO4resok_len = idx; if (data->minorversion != 0) { /* Need to clear out CurrentFH */ set_current_entry(data, NULL); data->currentFH.nfs_fh4_len = 0; /* Release CurrentFH reference to export. */ clear_op_context_export(); if (restore_op_ctx) { /* Don't need saved export */ discard_op_context_export(&saved); restore_op_ctx = false; } } res_SECINFO4->status = NFS4_OK; out: if (restore_op_ctx) { /* Restore export stuff */ restore_op_context_export(&saved); /* Restore creds */ if (nfs_req_creds(data->req) != NFS4_OK) { LogCrit(COMPONENT_EXPORT, "Failure to restore creds"); } } if (obj_src) obj_src->obj_ops->put_ref(obj_src); GSH_AUTO_TRACEPOINT( nfs4, op_secinfo_end, TRACE_INFO, "SECINFO res: status={} len={}", res_SECINFO4->status, res_SECINFO4->SECINFO4res_u.resok4.SECINFO4resok_len); return nfsstat4_to_nfs_req_result(res_SECINFO4->status); } /* nfs4_op_secinfo */ /** * @brief Free memory allocated for SECINFO result * * This function frees any memory allocated for the result of the * NFS4_OP_SECINFO operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_secinfo_Free(nfs_resop4 *res) { SECINFO4res *resp = &res->nfs_resop4_u.opsecinfo; if (resp->status == NFS4_OK) { gsh_free(resp->SECINFO4res_u.resok4.SECINFO4resok_val); resp->SECINFO4res_u.resok4.SECINFO4resok_val = NULL; } } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_secinfo_no_name.c000066400000000000000000000152021473756622300237140ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_secinfo_no_name.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "sal_functions.h" #include "export_mgr.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif #ifdef _HAVE_GSSAPI /* flavor, oid len, qop, service */ #define GSS_RESP_SIZE (4 * BYTES_PER_XDR_UNIT) #endif /* nfsstat4, resok_len, 2 flavors * NOTE this requires space for up to 2 extra xdr units if the export doesn't * allow AUTH_NONE and/or AUTH_UNIX. The response size is overall so small * this op should never be the cause of overflow of maxrespsize... */ #define RESP_SIZE (4 * BYTES_PER_XDR_UNIT) /** * @brief NFSv4 SECINFO_NO_NAME operation * * This function implements the NFSv4 SECINFO_NO_NAME operation. * * @param[in] op Operation request * @param[in,out] data Compound data * @param[out] resp Response * * @return NFS status codes. */ enum nfs_req_result nfs4_op_secinfo_no_name(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { SECINFO_NO_NAME4res *const res_SECINFO_NO_NAME4 = &resp->nfs_resop4_u.opsecinfo_no_name; secinfo4 *resok_val; #ifdef _HAVE_GSSAPI sec_oid4 v5oid = { krb5oid.length, (char *)krb5oid.elements }; #endif /* _HAVE_GSSAPI */ int num_entry = 0; uint32_t resp_size = RESP_SIZE; GSH_AUTO_TRACEPOINT(nfs4, op_secinfo_no_name_start, TRACE_INFO, "SECINFO NO NAME start"); res_SECINFO_NO_NAME4->status = NFS4_OK; /* Do basic checks on a filehandle */ res_SECINFO_NO_NAME4->status = nfs4_sanity_check_FH(data, NO_FILE_TYPE, false); if (res_SECINFO_NO_NAME4->status != NFS4_OK) goto out; if (op->nfs_argop4_u.opsecinfo_no_name == SECINFO_STYLE4_PARENT) { /* Use LOOKUPP to get the parent into CurrentFH. Note that all * ops have status in the same location, so if there is an error * we just need to set the resop and return the result. */ enum nfs_req_result result = nfs4_op_lookupp(op, data, resp); if (result != NFS_REQ_OK) { resp->resop = NFS4_OP_SECINFO_NO_NAME; return result; } } /* Get the number of entries */ #ifdef _HAVE_GSSAPI if (op_ctx->export_perms.options & EXPORT_OPTION_RPCSEC_GSS_NONE) num_entry++; if (op_ctx->export_perms.options & EXPORT_OPTION_RPCSEC_GSS_INTG) num_entry++; if (op_ctx->export_perms.options & EXPORT_OPTION_RPCSEC_GSS_PRIV) num_entry++; resp_size += (RNDUP(krb5oid.length) + GSS_RESP_SIZE) * num_entry; #endif if (op_ctx->export_perms.options & EXPORT_OPTION_AUTH_NONE) num_entry++; if (op_ctx->export_perms.options & EXPORT_OPTION_AUTH_UNIX) num_entry++; /* Check for space in response. */ res_SECINFO_NO_NAME4->status = check_resp_room(data, resp_size); if (res_SECINFO_NO_NAME4->status != NFS4_OK) goto out; data->op_resp_size = resp_size; resok_val = gsh_calloc(num_entry, sizeof(secinfo4)); res_SECINFO_NO_NAME4->SECINFO4res_u.resok4.SECINFO4resok_val = resok_val; /** * @todo We give here the order in which the client should try * different authentications. Might want to give it in the * order given in the config. */ int idx = 0; #ifdef _HAVE_GSSAPI if (op_ctx->export_perms.options & EXPORT_OPTION_RPCSEC_GSS_PRIV) { resok_val[idx].flavor = RPCSEC_GSS; resok_val[idx].secinfo4_u.flavor_info.service = RPCSEC_GSS_SVC_PRIVACY; resok_val[idx].secinfo4_u.flavor_info.qop = GSS_C_QOP_DEFAULT; resok_val[idx++].secinfo4_u.flavor_info.oid = v5oid; } if (op_ctx->export_perms.options & EXPORT_OPTION_RPCSEC_GSS_INTG) { resok_val[idx].flavor = RPCSEC_GSS; resok_val[idx].secinfo4_u.flavor_info.service = RPCSEC_GSS_SVC_INTEGRITY; resok_val[idx].secinfo4_u.flavor_info.qop = GSS_C_QOP_DEFAULT; resok_val[idx++].secinfo4_u.flavor_info.oid = v5oid; } if (op_ctx->export_perms.options & EXPORT_OPTION_RPCSEC_GSS_NONE) { resok_val[idx].flavor = RPCSEC_GSS; resok_val[idx].secinfo4_u.flavor_info.service = RPCSEC_GSS_SVC_NONE; resok_val[idx].secinfo4_u.flavor_info.qop = GSS_C_QOP_DEFAULT; resok_val[idx++].secinfo4_u.flavor_info.oid = v5oid; } #endif /* _HAVE_GSSAPI */ if (op_ctx->export_perms.options & EXPORT_OPTION_AUTH_UNIX) resok_val[idx++].flavor = AUTH_UNIX; if (op_ctx->export_perms.options & EXPORT_OPTION_AUTH_NONE) resok_val[idx++].flavor = AUTH_NONE; res_SECINFO_NO_NAME4->SECINFO4res_u.resok4.SECINFO4resok_len = idx; /* Need to clear out CurrentFH */ set_current_entry(data, NULL); data->currentFH.nfs_fh4_len = 0; /* Release CurrentFH reference to export. */ clear_op_context_export(); res_SECINFO_NO_NAME4->status = NFS4_OK; out: resp->resop = NFS4_OP_SECINFO_NO_NAME; GSH_AUTO_TRACEPOINT( nfs4, op_secinfo_no_name_end, TRACE_INFO, "SECINFO NO NAME res: status={} len={}", res_SECINFO_NO_NAME4->status, res_SECINFO_NO_NAME4->SECINFO4res_u.resok4.SECINFO4resok_len); return nfsstat4_to_nfs_req_result(res_SECINFO_NO_NAME4->status); } /* nfs4_op_secinfo_no_name */ /** * @brief Free memory allocated for SECINFO_NO_NAME result * * This function frees any memory allocated for the result of the * NFS4_OP_SECINFO_NO_NAME operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_secinfo_no_name_Free(nfs_resop4 *res) { SECINFO_NO_NAME4res *resp = &res->nfs_resop4_u.opsecinfo_no_name; if ((resp->status == NFS4_OK) && (resp->SECINFO4res_u.resok4.SECINFO4resok_val)) { gsh_free(resp->SECINFO4res_u.resok4.SECINFO4resok_val); resp->SECINFO4res_u.resok4.SECINFO4resok_val = NULL; } } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_sequence.c000066400000000000000000000262111473756622300224040ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_sequence.c * @brief Routines used for managing the NFS4_OP_SEQUENCE operation */ #include "config.h" #include "fsal.h" #include "sal_functions.h" #include "nfs_rpc_callback.h" #include "nfs_convert.h" #include "nfs_proto_tools.h" #include "nfs_proto_functions.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief check whether request is real a replay * * Check whether current request is a replay of slot's last request, * by checking whether the operations are same. * * @param[in] data Compound request's data * @param[in] slot current request's slot * @param[in] slotid current request's slotid * @param[in] req_seq_id current request's sequence id * */ void check_replay_request(compound_data_t *data, nfs41_session_slot_t *slot, sequenceid4 req_seq_id, uint32_t slotid) { nfs_opnum4 opcodes[NFS4_MAX_OPERATIONS] = { 0 }; uint32_t req_xid, i, opcode_num; req_xid = data->req->rq_msg.rm_xid; if (slot->last_req.seq_id != req_seq_id) return; opcode_num = get_nfs4_opcodes(data, opcodes, NFS4_MAX_OPERATIONS); if (opcode_num != slot->last_req.opcode_num) goto errout; for (i = 0; i < opcode_num; i++) if (opcodes[i] != slot->last_req.opcodes[i]) goto errout; return; errout: /* If this happens, maybe cause client hangs forever, and can not * recover, unless restart ganesha. * For example, if client use kernel 4.14.81 in https://kernel.org/, * OP_SEQUENCE comes first, and then comes OP_GETATTR which share * the same slot and sequenceid with the former OP_SEQUENCE, and * ganesha reply NFS4ERR_RETRY_UNCACHED_REP. Then nfs-client will * still send the OP_GETATTR with the same slot and sequenceid, * and ganesha still reply NFS4ERR_RETRY_UNCACHED_REP, ..., this * will last forever. This bug, i.e. different requests share the * same slot and sequenceid, disapper in kernel 5.4.xx, fixed * by some former version. */ if (likely(component_log_level[COMPONENT_SESSIONS] >= NIV_EVENT)) { char last_operations[NFS4_COMPOUND_OPERATIONS_STR_LEN] = "\0"; char curr_operations[NFS4_COMPOUND_OPERATIONS_STR_LEN] = "\0"; struct display_buffer last_buf = { sizeof(last_operations), last_operations, last_operations }; struct display_buffer curr_buf = { sizeof(curr_operations), curr_operations, curr_operations }; display_nfs4_operations(&last_buf, slot->last_req.opcodes, slot->last_req.opcode_num); display_nfs4_operations(&curr_buf, opcodes, opcode_num); LogEvent( COMPONENT_SESSIONS, "Not a replay request, maybe caused by nfs-client's bug, please try upgrade the nfs-client's kernel"); LogEvent(COMPONENT_SESSIONS, "Last request %s slotid %" PRIu32 " seqid %" PRIu32 " xid %" PRIu32 " finish time_ms %" PRIu64, last_operations, slotid, slot->last_req.seq_id, slot->last_req.xid, slot->last_req.finish_time_ms); LogEvent(COMPONENT_SESSIONS, "Current request %s slotid %" PRIu32 " seqid %" PRIu32 " xid %" PRIu32, curr_operations, slotid, req_seq_id, req_xid); } } /** * @brief the NFS4_OP_SEQUENCE operation * * @param[in] op nfs4_op arguments * @param[in,out] data Compound request's data * @param[out] resp nfs4_op results * * @return per RFC5661, p. 374 * * @see nfs4_Compound * */ enum nfs_req_result nfs4_op_sequence(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { SEQUENCE4args *const arg_SEQUENCE4 = &op->nfs_argop4_u.opsequence; SEQUENCE4res *const res_SEQUENCE4 = &resp->nfs_resop4_u.opsequence; uint32_t slotid; nfs41_session_t *session; nfs41_session_slot_t *slot; GSH_AUTO_TRACEPOINT( nfs4, op_sequence_start, TRACE_INFO, "SEQUENCE arg: session={} sequence={} slotid={} highest_slotid={} cachethis={}", TP_SESSION(arg_SEQUENCE4->sa_sessionid), arg_SEQUENCE4->sa_sequenceid, arg_SEQUENCE4->sa_slotid, arg_SEQUENCE4->sa_highest_slotid, arg_SEQUENCE4->sa_cachethis); resp->resop = NFS4_OP_SEQUENCE; res_SEQUENCE4->sr_status = NFS4_OK; if (data->minorversion == 0) { res_SEQUENCE4->sr_status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } if (!nfs41_Session_Get_Pointer(arg_SEQUENCE4->sa_sessionid, &session)) { res_SEQUENCE4->sr_status = NFS4ERR_BADSESSION; LogDebugAlt(COMPONENT_SESSIONS, COMPONENT_CLIENTID, "SEQUENCE returning status %s", nfsstat4_to_str(res_SEQUENCE4->sr_status)); return NFS_REQ_ERROR; } /* session->refcount +1 */ LogDebug(COMPONENT_SESSIONS, "SEQUENCE session=%p", session); /* Check if lease is expired and reserve it */ if (!reserve_lease_or_expire(session->clientid_record, false, NULL)) { dec_session_ref(session); res_SEQUENCE4->sr_status = NFS4ERR_EXPIRED; LogDebugAlt(COMPONENT_SESSIONS, COMPONENT_CLIENTID, "SEQUENCE returning status %s", nfsstat4_to_str(res_SEQUENCE4->sr_status)); return NFS_REQ_ERROR; } data->preserved_clientid = session->clientid_record; slotid = arg_SEQUENCE4->sa_slotid; /* Check is slot is compliant with ca_maxrequests */ if (slotid >= session->fore_channel_attrs.ca_maxrequests) { dec_session_ref(session); res_SEQUENCE4->sr_status = NFS4ERR_BADSLOT; LogDebugAlt(COMPONENT_SESSIONS, COMPONENT_CLIENTID, "SEQUENCE returning status %s", nfsstat4_to_str(res_SEQUENCE4->sr_status)); return NFS_REQ_ERROR; } slot = &session->fc_slots[slotid]; /* Serialize use of this slot. */ PTHREAD_MUTEX_lock(&slot->slot_lock); if (slot->sequence + 1 != arg_SEQUENCE4->sa_sequenceid) { /* This sequence is NOT the next sequence */ if (slot->sequence == arg_SEQUENCE4->sa_sequenceid) { check_replay_request(data, slot, arg_SEQUENCE4->sa_sequenceid, slotid); /* But it is the previous sequence */ if (slot->cached_result != NULL) { int32_t refcnt; /* And has a cached response. * Replay operation through the DRC * Take a reference to the slot cached response. */ data->slot = slot; /* Free the reply allocated originally */ release_nfs4_res_compound( data->res->res_compound4_extended); refcnt = atomic_inc_int32_t( &slot->cached_result->res_refcnt); /* Copy the reply from the cache (the reference * is already taken by SEQUENCE. */ data->res->res_compound4_extended = data->slot->cached_result; data->cached_result_status = ((COMPOUND4res *) data->slot->cached_result) ->status; LogFullDebugAlt( COMPONENT_SESSIONS, COMPONENT_CLIENTID, "Use sesson slot %" PRIu32 "=%p for replay refcnt=%" PRIi32, slotid, slot->cached_result, refcnt); PTHREAD_MUTEX_unlock(&slot->slot_lock); dec_session_ref(session); return NFS_REQ_REPLAY; } else { /* Illegal replay */ PTHREAD_MUTEX_unlock(&slot->slot_lock); dec_session_ref(session); res_SEQUENCE4->sr_status = NFS4ERR_RETRY_UNCACHED_REP; LogDebugAlt( COMPONENT_SESSIONS, COMPONENT_CLIENTID, "SEQUENCE returning status %s with slot seqid=%" PRIu32 " op seqid=%" PRIu32, nfsstat4_to_str( res_SEQUENCE4->sr_status), slot->sequence, arg_SEQUENCE4->sa_sequenceid); return NFS_REQ_ERROR; } } PTHREAD_MUTEX_unlock(&slot->slot_lock); dec_session_ref(session); res_SEQUENCE4->sr_status = NFS4ERR_SEQ_MISORDERED; LogDebugAlt(COMPONENT_SESSIONS, COMPONENT_CLIENTID, "SEQUENCE returning status %s", nfsstat4_to_str(res_SEQUENCE4->sr_status)); return NFS_REQ_ERROR; } /* Keep memory of the session in the COMPOUND's data */ data->session = session; /* Record the sequenceid and slotid in the COMPOUND's data */ data->sequence = arg_SEQUENCE4->sa_sequenceid; data->slotid = slotid; /* Update the sequence id within the slot */ slot->sequence += 1; /* If the slot cache was in use, free it. */ release_slot(slot); /* Set up the response */ memcpy(res_SEQUENCE4->SEQUENCE4res_u.sr_resok4.sr_sessionid, arg_SEQUENCE4->sa_sessionid, NFS4_SESSIONID_SIZE); res_SEQUENCE4->SEQUENCE4res_u.sr_resok4.sr_sequenceid = slot->sequence; res_SEQUENCE4->SEQUENCE4res_u.sr_resok4.sr_slotid = slotid; res_SEQUENCE4->SEQUENCE4res_u.sr_resok4.sr_highest_slotid = session->nb_slots - 1; res_SEQUENCE4->SEQUENCE4res_u.sr_resok4.sr_target_highest_slotid = session->fore_channel_attrs.ca_maxrequests - 1; res_SEQUENCE4->SEQUENCE4res_u.sr_resok4.sr_status_flags = 0; if (nfs_rpc_get_chan(session->clientid_record, 0) == NULL) { res_SEQUENCE4->SEQUENCE4res_u.sr_resok4.sr_status_flags |= SEQ4_STATUS_CB_PATH_DOWN; } /* Remember if we are caching result and set position to cache. */ data->sa_cachethis = arg_SEQUENCE4->sa_cachethis; data->slot = slot; LogFullDebugAlt(COMPONENT_SESSIONS, COMPONENT_CLIENTID, "%s session slot %" PRIu32 "=%p for DRC", arg_SEQUENCE4->sa_cachethis ? "Use" : "Don't use", slotid, data->slot); /* If we were successful, stash the clientid in the request * context. */ op_ctx->clientid = &data->session->clientid; /* Now check the response size (we check here because we couldn't check * in nfs4_Compound because the session wasn't established yet). */ res_SEQUENCE4->sr_status = check_resp_room(data, data->op_resp_size); if (res_SEQUENCE4->sr_status != NFS4_OK) { /* Indicate the failed response size. */ data->op_resp_size = sizeof(nfsstat4); PTHREAD_MUTEX_unlock(&slot->slot_lock); dec_session_ref(session); data->session = NULL; return NFS_REQ_ERROR; } /* We keep the slot lock to serialize use of the slot. */ (void)check_session_conn(session, data, true); const SEQUENCE4resok *const reok = &res_SEQUENCE4->SEQUENCE4res_u.sr_resok4; GSH_AUTO_TRACEPOINT( nfs4, op_sequence_end, TRACE_INFO, "SEQUENCE res: status={} session={} sequence={} slotid={} status_flags={}", res_SEQUENCE4->sr_status, TP_SESSION(reok->sr_sessionid), reok->sr_sequenceid, reok->sr_slotid, reok->sr_status_flags); return NFS_REQ_OK; } /* nfs41_op_sequence */ /** * @brief Free memory allocated for SEQUENCE result * * This function frees any memory allocated for the result of the * NFS4_OP_SEQUENCE operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_sequence_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_set_ssv.c000066400000000000000000000052701473756622300222640ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_set_ssv.c * @brief Routines for the NFS4_OP_SET_SSV operation * * Routines for the NFS4_OP_SEQUENCE operation. * * */ #include "config.h" #include #include #include #include #include "log.h" #include "gsh_rpc.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_file_handle.h" /** * * @brief The NFS4_OP_SET_SSV operation * * This functions handles the NFS4_OP_SET_SSV operation in NFSv4. This * function can be called only from nfs4_Compound. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, pp. 374-5 * * @see nfs4_Compound * */ enum nfs_req_result nfs4_op_set_ssv(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { SET_SSV4args *const arg_SET_SSV4 __attribute__((unused)) = &op->nfs_argop4_u.opset_ssv; SET_SSV4res *const res_SET_SSV4 = &resp->nfs_resop4_u.opset_ssv; resp->resop = NFS4_OP_SET_SSV; res_SET_SSV4->ssr_status = NFS4_OK; if (data->minorversion == 0) { res_SET_SSV4->ssr_status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } /* I know this is pretty dirty... * But this is an early implementation... */ return nfsstat4_to_nfs_req_result(res_SET_SSV4->ssr_status); } /* nfs41_op_set_ssv */ /** * @brief Free memory allocated for SET_SSV result * * This function frees any memory allocated for the result of the * NFS4_OP_SET_SSV operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_set_ssv_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_setattr.c000066400000000000000000000162521473756622300222660ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_setattr.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * * */ #include "config.h" #include "log.h" #include "nfs4.h" #include "nfs_core.h" #include "fsal.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include "sal_functions.h" #include "nfs_creds.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief The NFS4_OP_SETATTR operation. * * This functions handles the NFS4_OP_SETATTR operation in NFSv4. This * function can be called only from nfs4_Compound * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 373-4 */ enum nfs_req_result nfs4_op_setattr(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { SETATTR4args *const arg_SETATTR4 = &op->nfs_argop4_u.opsetattr; SETATTR4res *const res_SETATTR4 = &resp->nfs_resop4_u.opsetattr; struct fsal_attrlist sattr; fsal_status_t fsal_status = { 0, 0 }; const char *tag = "SETATTR"; state_t *state_found = NULL; state_t *state_open = NULL; const time_t S_NSECS = 1000000000UL; GSH_AUTO_TRACEPOINT(nfs4, op_setattr_start, TRACE_INFO, "SETATTR arg: seqid={}", arg_SETATTR4->stateid.seqid); resp->resop = NFS4_OP_SETATTR; res_SETATTR4->status = NFS4_OK; /* Do basic checks on a filehandle */ res_SETATTR4->status = nfs4_sanity_check_FH(data, NO_FILE_TYPE, false); if (res_SETATTR4->status != NFS4_OK) return NFS_REQ_ERROR; /* Don't allow attribute change while we are in grace period. * Required for delegation reclaims and may be needed for other * reclaimable states as well. */ if (!nfs_get_grace_status(false)) { res_SETATTR4->status = NFS4ERR_GRACE; return NFS_REQ_ERROR; } /* Get only attributes that are allowed to be read */ if (!nfs4_Fattr_Check_Access(&arg_SETATTR4->obj_attributes, FATTR4_ATTR_WRITE)) { res_SETATTR4->status = NFS4ERR_INVAL; goto done; } /* Ask only for supported attributes */ if (!nfs4_Fattr_Supported(&arg_SETATTR4->obj_attributes)) { res_SETATTR4->status = NFS4ERR_ATTRNOTSUPP; goto done; } /* Convert the fattr4 in the request to a fsal sattr structure */ res_SETATTR4->status = nfs4_Fattr_To_FSAL_attr( &sattr, &arg_SETATTR4->obj_attributes, data); if (res_SETATTR4->status != NFS4_OK) goto done; /* Trunc may change Xtime so we have to start with trunc and * finish by the mtime and atime */ if ((FSAL_TEST_MASK(sattr.valid_mask, ATTR_SIZE)) || (FSAL_TEST_MASK(sattr.valid_mask, ATTR4_SPACE_RESERVED))) { /* Setting the size of a directory is prohibited */ if (data->current_filetype == DIRECTORY) { res_SETATTR4->status = NFS4ERR_ISDIR; goto done; } /* Object should be a file */ if (data->current_obj->type != REGULAR_FILE) { res_SETATTR4->status = NFS4ERR_INVAL; goto done; } /* Check stateid correctness and get pointer to state */ res_SETATTR4->status = nfs4_Check_Stateid( &arg_SETATTR4->stateid, data->current_obj, &state_found, data, STATEID_SPECIAL_ANY, 0, false, tag); if (res_SETATTR4->status != NFS4_OK) goto done; /* NB: After this point, if state_found == NULL, then * the stateid is all-0 or all-1 */ if (state_found != NULL) { switch (state_found->state_type) { case STATE_TYPE_SHARE: state_open = state_found; /* Note this causes an extra refcount, but it * simplifies logic below. */ inc_state_t_ref(state_open); break; case STATE_TYPE_LOCK: state_open = nfs4_State_Get_Pointer( state_found->state_data.lock .openstate_key); if (state_open == NULL) { res_SETATTR4->status = NFS4ERR_BAD_STATEID; goto done; } break; case STATE_TYPE_DELEG: state_open = NULL; break; default: res_SETATTR4->status = NFS4ERR_BAD_STATEID; goto done; } /* This is a size operation, this means that * the file MUST have been opened for writing */ if (state_open != NULL && (state_open->state_data.share.share_access & OPEN4_SHARE_ACCESS_WRITE) == 0) { /* Bad open mode, return NFS4ERR_OPENMODE */ res_SETATTR4->status = NFS4ERR_OPENMODE; goto done; } } else { /* Special stateid, no open state, check to * see if any share conflicts */ state_open = NULL; } } /* Set the atime and mtime (ctime is not setable) */ /* A carry into seconds considered invalid */ if (sattr.atime.tv_nsec >= S_NSECS) { res_SETATTR4->status = NFS4ERR_INVAL; goto done; } if (sattr.mtime.tv_nsec >= S_NSECS) { res_SETATTR4->status = NFS4ERR_INVAL; goto done; } /* If owner or owner_group are set, and the credential was * squashed, then we must squash the set owner and owner_group. */ squash_setattr(&sattr); /* If a SETATTR comes with an open stateid, and size is being * set, then the open MUST be for write (checked above), so * is_open_write is simple at this stage, it's just a check that * we have an open owner. */ fsal_status = fsal_setattr(data->current_obj, false, state_found, &sattr); /* Release the attributes (may release an explicit or inherited ACL) */ fsal_release_attrs(&sattr); if (FSAL_IS_ERROR(fsal_status)) { res_SETATTR4->status = nfs4_Errno_status(fsal_status); goto done; } /* Set the replied structure */ res_SETATTR4->attrsset = arg_SETATTR4->obj_attributes.attrmask; /* Exit with no error */ res_SETATTR4->status = NFS4_OK; done: nfs_put_grace_status(); if (state_found != NULL) dec_state_t_ref(state_found); if (state_open != NULL) dec_state_t_ref(state_open); GSH_AUTO_TRACEPOINT(nfs4, op_setattr_end, TRACE_INFO, "SETATTR res: status={}", res_SETATTR4->status); return nfsstat4_to_nfs_req_result(res_SETATTR4->status); } /* nfs4_op_setattr */ /** * @brief Free memory allocated for SETATTR result * * This function fres any memory allocated for the result of the * NFS4_OP_SETATTR operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_setattr_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_setclientid.c000066400000000000000000000262651473756622300231140ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_setclientid.c * @brief Routines used for managing the NFS4_OP_SETCLIENTID operation */ #include "config.h" #include #include "log.h" #include "fsal.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_core.h" #include "nfs_creds.h" #include "nfs_file_handle.h" #include "client_mgr.h" #include "fsal.h" /** * * @brief The NFS4_OP_SETCLIENTID operation. * * @param[in] op nfs4_op arguments * @param[in,out] data Compound request's data * @param[out] resp nfs4_op results * * @retval NFS4_OK or errors for NFSv4.0. * @retval NFS4ERR_NOTSUPP for NFSv4.1. * */ enum nfs_req_result nfs4_op_setclientid(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { SETCLIENTID4args *const arg_SETCLIENTID4 = &op->nfs_argop4_u.opsetclientid; SETCLIENTID4res *const res_SETCLIENTID4 = &resp->nfs_resop4_u.opsetclientid; clientaddr4 *const res_SETCLIENTID4_INUSE = &resp->nfs_resop4_u.opsetclientid.SETCLIENTID4res_u.client_using; char str_verifier[OPAQUE_BYTES_SIZE(NFS4_VERIFIER_SIZE)]; struct display_buffer dspbuf_verifier = { sizeof(str_verifier), str_verifier, str_verifier }; char str_client[NFS4_OPAQUE_LIMIT * 2 + 1]; struct display_buffer dspbuf_client = { sizeof(str_client), str_client, str_client }; const char *str_client_addr = "(unknown)"; /* The clientid4 broken down into fields */ char str_clientid4[DISPLAY_CLIENTID_SIZE]; /* Display buffer for clientid4 */ struct display_buffer dspbuf_clientid4 = { sizeof(str_clientid4), str_clientid4, str_clientid4 }; nfs_client_record_t *client_record; nfs_client_id_t *conf; nfs_client_id_t *unconf; clientid4 clientid; verifier4 verifier; int rc; resp->resop = NFS4_OP_SETCLIENTID; if (data->minorversion > 0) { res_SETCLIENTID4->status = NFS4ERR_NOTSUPP; return NFS_REQ_ERROR; } if (op_ctx->client != NULL) str_client_addr = op_ctx->client->hostaddr_str; if (isDebug(COMPONENT_CLIENTID)) { display_opaque_value(&dspbuf_client, arg_SETCLIENTID4->client.id.id_val, arg_SETCLIENTID4->client.id.id_len); display_opaque_bytes(&dspbuf_verifier, arg_SETCLIENTID4->client.verifier, NFS4_VERIFIER_SIZE); } else { str_client[0] = '\0'; str_verifier[0] = '\0'; } LogDebug( COMPONENT_CLIENTID, "SETCLIENTID Client addr=%s id=%s verf=%s callback={program=%u r_addr=%s r_netid=%s} ident=%u", str_client_addr, str_client, str_verifier, arg_SETCLIENTID4->callback.cb_program, arg_SETCLIENTID4->callback.cb_location.r_addr, arg_SETCLIENTID4->callback.cb_location.r_netid, arg_SETCLIENTID4->callback_ident); /* Do we already have one or more records for client id (x)? */ client_record = get_client_record(arg_SETCLIENTID4->client.id.id_val, arg_SETCLIENTID4->client.id.id_len, 0, 0); if (client_record == NULL) { /* Some major failure */ LogCrit(COMPONENT_CLIENTID, "SETCLIENTID failed"); res_SETCLIENTID4->status = NFS4ERR_SERVERFAULT; return NFS_REQ_ERROR; } /* The following checks are based on RFC3530bis draft 16 * * This attempts to implement the logic described in * 15.35.5. IMPLEMENTATION Consider the major bullets as CASE * 1, CASE 2, CASE 3, CASE 4, and CASE 5. */ PTHREAD_MUTEX_lock(&client_record->cr_mutex); if (isFullDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_record(&dspbuf, client_record); LogFullDebug( COMPONENT_CLIENTID, "Client Record %s cr_confirmed_rec=%p cr_unconfirmed_rec=%p", str, client_record->cr_confirmed_rec, client_record->cr_unconfirmed_rec); } conf = client_record->cr_confirmed_rec; if (conf != NULL) { bool credmatch; /* Need a reference to the confirmed record for below */ inc_client_id_ref(conf); clientid = conf->cid_clientid; display_clientid(&dspbuf_clientid4, clientid); credmatch = nfs_compare_clientcred(&conf->cid_credential, &data->credential) && op_ctx->client != NULL && conf->gsh_client != NULL && op_ctx->client == conf->gsh_client; /* Only reject if the principal doesn't match and the * clientid has live state associated. Per RFC 7530 * Section 9.1.2. Server Release of Client ID. */ if (!credmatch && clientid_has_state(conf)) { /* CASE 1: * * Confirmed record exists and not the same principal */ if (isDebug(COMPONENT_CLIENTID)) { char *confirmed_addr = "(unknown)"; if (conf->gsh_client != NULL) confirmed_addr = conf->gsh_client->hostaddr_str; LogDebug( COMPONENT_CLIENTID, "Confirmed ClientId %s->'%s': Principals do not match... confirmed addr=%s Return NFS4ERR_CLID_INUSE", str_clientid4, str_client, confirmed_addr); } res_SETCLIENTID4->status = NFS4ERR_CLID_INUSE; res_SETCLIENTID4_INUSE->r_netid = (char *)netid_nc_table[conf->cid_cb.v40.cb_addr .nc] .netid; res_SETCLIENTID4_INUSE->r_addr = gsh_strdup(conf->cid_cb.v40.cb_client_r_addr); /* Release our reference to the confirmed clientid. */ dec_client_id_ref(conf); goto out; } /* Check if confirmed record is for (v, x, c, l, s) */ if (credmatch && memcmp(arg_SETCLIENTID4->client.verifier, conf->cid_incoming_verifier, NFS4_VERIFIER_SIZE) == 0) { /* CASE 2: * * A confirmed record exists for this long * form client id and verifier. * * Consider this to be a possible update of * the call-back information. * * Remove any pre-existing unconfirmed record * for (v, x, c). * * Return the same short form client id (c), * but a new setclientid_confirm verifier (t). */ LogFullDebug(COMPONENT_CLIENTID, "Update ClientId %s->%s", str_clientid4, str_client); new_clientid_verifier(verifier); } else { /* Must be CASE 3 or CASE 4 * * Confirmed record is for (u, x, c, l, s). * * These are actually the same, doesn't really * matter if an unconfirmed record exists or * not. Any existing unconfirmed record will * be removed and a new unconfirmed record * added. * * Return a new short form clientid (d) and a * new setclientid_confirm verifier (t). (Note * the spec calls the values e and r for CASE * 4). */ LogFullDebug(COMPONENT_CLIENTID, "Replace ClientId %s->%s", str_clientid4, str_client); clientid = new_clientid(); new_clientid_verifier(verifier); } /* Release our reference to the confirmed clientid. */ dec_client_id_ref(conf); } else { /* CASE 5: * * * Remove any existing unconfirmed record. * * Return a new short form clientid (d) and a new * setclientid_confirm verifier (t). */ clientid = new_clientid(); display_clientid(&dspbuf_clientid4, clientid); LogFullDebug(COMPONENT_CLIENTID, "New client %s", str_clientid4); new_clientid_verifier(verifier); } /* At this point, no matter what the case was above, we should * remove any pre-existing unconfirmed record. */ unconf = client_record->cr_unconfirmed_rec; if (unconf != NULL) { /* Delete the unconfirmed clientid record. Because we * have the cr_mutex, we have won any race to deal * with this clientid record (whether we raced with a * SETCLIENTID_CONFIRM or the reaper thread (if either * of those operations had won the race, * cr_punconfirmed_id would have been NULL). */ if (isDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, unconf); LogDebug(COMPONENT_CLIENTID, "Replacing %s", str); } /* unhash the clientid record */ remove_unconfirmed_client_id(unconf); unconf = NULL; } /* Now we can proceed to build the new unconfirmed record. We * have determined the clientid and setclientid_confirm values * above. */ unconf = create_client_id(clientid, client_record, &data->credential, 0); if (unconf == NULL) { /* Error already logged, return */ res_SETCLIENTID4->status = NFS4ERR_RESOURCE; goto out; } if (strlcpy(unconf->cid_cb.v40.cb_client_r_addr, arg_SETCLIENTID4->callback.cb_location.r_addr, sizeof(unconf->cid_cb.v40.cb_client_r_addr)) >= sizeof(unconf->cid_cb.v40.cb_client_r_addr)) { LogCrit(COMPONENT_CLIENTID, "Callback r_addr %s too long", arg_SETCLIENTID4->callback.cb_location.r_addr); res_SETCLIENTID4->status = NFS4ERR_INVAL; free_client_id(unconf); goto out; } nfs_set_client_location(unconf, &arg_SETCLIENTID4->callback.cb_location); memcpy(unconf->cid_incoming_verifier, arg_SETCLIENTID4->client.verifier, NFS4_VERIFIER_SIZE); memcpy(unconf->cid_verifier, verifier, sizeof(NFS4_write_verifier)); unconf->cid_cb.v40.cb_program = arg_SETCLIENTID4->callback.cb_program; unconf->cid_cb.v40.cb_callback_ident = arg_SETCLIENTID4->callback_ident; rc = nfs_client_id_insert(unconf); if (rc != CLIENT_ID_SUCCESS) { /* Record is already freed, return. */ res_SETCLIENTID4->status = clientid_error_to_nfsstat_no_expire(rc); goto out; } if (isDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_cat(&dspbuf, "Verifier="); display_opaque_bytes(&dspbuf, verifier, NFS4_VERIFIER_SIZE); display_cat(&dspbuf, " "); display_client_id_rec(&dspbuf, unconf); LogDebug(COMPONENT_CLIENTID, "SETCLIENTID reply %s", str); } res_SETCLIENTID4->status = NFS4_OK; res_SETCLIENTID4->SETCLIENTID4res_u.resok4.clientid = clientid; memcpy(res_SETCLIENTID4->SETCLIENTID4res_u.resok4.setclientid_confirm, &verifier, NFS4_VERIFIER_SIZE); out: PTHREAD_MUTEX_unlock(&client_record->cr_mutex); /* Release our reference to the client record */ dec_client_record_ref(client_record); return nfsstat4_to_nfs_req_result(res_SETCLIENTID4->status); } /** * @brief Free memory allocated for SETCLIENTID result * * @param[in,out] resp nfs4_op results */ void nfs4_op_setclientid_Free(nfs_resop4 *res) { SETCLIENTID4res *resp = &res->nfs_resop4_u.opsetclientid; if (resp->status == NFS4ERR_CLID_INUSE) { if (resp->SETCLIENTID4res_u.client_using.r_addr != NULL) gsh_free(resp->SETCLIENTID4res_u.client_using.r_addr); } } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_setclientid_confirm.c000066400000000000000000000364201473756622300246230ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_setclientid_confirm.c * @brief Routines used for managing the NFS4_OP_SETCLIENTID_CONFIRM operation. * * Routines used for managing the NFS4_OP_SETCLIENTID_CONFIRM operation. */ #include "config.h" #include #include "log.h" #include "nfs4.h" #include "nfs_core.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_rpc_callback.h" #include "nfs_creds.h" #include "nfs_file_handle.h" #include "client_mgr.h" #include "fsal.h" /** * * @brief The NFS4_OP_SETCLIENTID_CONFIRM operation * * @param[in] op nfs4_op arguments * @param[in,out] data The compound request's data * @param[out] resp nfs4_op results * * @retval NFS4_OK or errors for NFSv4.0. * @retval NFS4ERR_NOTSUPP for NFSv4.1. * * @see nfs4_Compound */ enum nfs_req_result nfs4_op_setclientid_confirm(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { SETCLIENTID_CONFIRM4args *const arg_SETCLIENTID_CONFIRM4 = &op->nfs_argop4_u.opsetclientid_confirm; SETCLIENTID_CONFIRM4res *const res_SETCLIENTID_CONFIRM4 = &resp->nfs_resop4_u.opsetclientid_confirm; nfs_client_id_t *conf = NULL; nfs_client_id_t *unconf = NULL; nfs_client_record_t *client_record = NULL; clientid4 clientid = 0; char str_verifier[OPAQUE_BYTES_SIZE(NFS4_VERIFIER_SIZE)]; const char *str_client_addr = "(unknown)"; /* The client name, for gratuitous logging */ char str_client[CLIENTNAME_BUFSIZE]; /* Display buffer for client name */ struct display_buffer dspbuf_client = { sizeof(str_client), str_client, str_client }; /* The clientid4 broken down into fields */ char str_clientid4[DISPLAY_CLIENTID_SIZE]; /* Display buffer for clientid4 */ struct display_buffer dspbuf_clientid4 = { sizeof(str_clientid4), str_clientid4, str_clientid4 }; int rc; /* Make sure str_client is always printable even * if log level changes midstream. */ display_printf(&dspbuf_client, "(unknown)"); display_reset_buffer(&dspbuf_client); resp->resop = NFS4_OP_SETCLIENTID_CONFIRM; res_SETCLIENTID_CONFIRM4->status = NFS4_OK; clientid = arg_SETCLIENTID_CONFIRM4->clientid; display_clientid(&dspbuf_clientid4, clientid); if (data->minorversion > 0) { res_SETCLIENTID_CONFIRM4->status = NFS4ERR_NOTSUPP; return NFS_REQ_ERROR; } if (op_ctx->client != NULL) str_client_addr = op_ctx->client->hostaddr_str; if (isDebug(COMPONENT_CLIENTID)) { struct display_buffer dspbuf_verifier = { sizeof(str_verifier), str_verifier, str_verifier }; display_opaque_bytes( &dspbuf_verifier, arg_SETCLIENTID_CONFIRM4->setclientid_confirm, NFS4_VERIFIER_SIZE); } else { str_verifier[0] = '\0'; } LogDebug( COMPONENT_CLIENTID, "SETCLIENTID_CONFIRM client addr=%s clientid=%s setclientid_confirm=%s", str_client_addr, str_clientid4, str_verifier); /* First try to look up unconfirmed record */ rc = nfs_client_id_get_unconfirmed(clientid, &unconf); if (rc == CLIENT_ID_SUCCESS) { PTHREAD_MUTEX_lock(&unconf->cid_mutex); client_record = unconf->cid_client_record; if (client_record) inc_client_record_ref(client_record); PTHREAD_MUTEX_unlock(&unconf->cid_mutex); if (isFullDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, unconf); LogFullDebug(COMPONENT_CLIENTID, "Found %s", str); } } else { rc = nfs_client_id_get_confirmed(clientid, &conf); if (rc != CLIENT_ID_SUCCESS) { /* No record whatsoever of this clientid */ LogDebug(COMPONENT_CLIENTID, "%s clientid = %s", clientid_error_to_str(rc), str_clientid4); res_SETCLIENTID_CONFIRM4->status = clientid_error_to_nfsstat_no_expire(rc); return NFS_REQ_ERROR; } PTHREAD_MUTEX_lock(&conf->cid_mutex); client_record = conf->cid_client_record; if (client_record) inc_client_record_ref(client_record); PTHREAD_MUTEX_unlock(&conf->cid_mutex); if (isFullDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, conf); LogFullDebug(COMPONENT_CLIENTID, "Found %s", str); } } if (!client_record) { res_SETCLIENTID_CONFIRM4->status = NFS4ERR_STALE_CLIENTID; if (unconf) dec_client_id_ref(unconf); else dec_client_id_ref(conf); return NFS_REQ_ERROR; } PTHREAD_MUTEX_lock(&client_record->cr_mutex); if (isFullDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_record(&dspbuf, client_record); LogFullDebug( COMPONENT_CLIENTID, "Client Record %s cr_confirmed_rec=%p cr_unconfirmed_rec=%p", str, client_record->cr_confirmed_rec, client_record->cr_unconfirmed_rec); } /* At this point one and only one of pconf and punconf is non-NULL */ if (unconf != NULL) { /* First must match principal */ if (!nfs_compare_clientcred(&unconf->cid_credential, &data->credential) || op_ctx->client == NULL || unconf->gsh_client == NULL || op_ctx->client != unconf->gsh_client) { if (isDebug(COMPONENT_CLIENTID)) { char *unconfirmed_addr = "(unknown)"; if (unconf->gsh_client != NULL) unconfirmed_addr = unconf->gsh_client->hostaddr_str; LogDebug( COMPONENT_CLIENTID, "Unconfirmed ClientId %s->'%s': Principals do not match... unconfirmed addr=%s Return NFS4ERR_CLID_INUSE", str_clientid4, str_client_addr, unconfirmed_addr); } res_SETCLIENTID_CONFIRM4->status = NFS4ERR_CLID_INUSE; dec_client_id_ref(unconf); goto out; } else if (unconf->cid_confirmed == CONFIRMED_CLIENT_ID && memcmp(unconf->cid_verifier, arg_SETCLIENTID_CONFIRM4->setclientid_confirm, NFS4_VERIFIER_SIZE) == 0) { /* We must have raced with another SETCLIENTID_CONFIRM */ if (isDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, unconf); LogDebug(COMPONENT_CLIENTID, "Race against confirm for %s", str); } res_SETCLIENTID_CONFIRM4->status = NFS4_OK; dec_client_id_ref(unconf); goto out; } else if (unconf->cid_confirmed != UNCONFIRMED_CLIENT_ID) { /* We raced with another thread that dealt * with this unconfirmed record. Release our * reference, and pretend we didn't find a * record. */ if (isDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, unconf); LogDebug(COMPONENT_CLIENTID, "Race against expire for %s", str); } res_SETCLIENTID_CONFIRM4->status = NFS4ERR_STALE_CLIENTID; dec_client_id_ref(unconf); goto out; } } if (conf != NULL) { if (isDebug(COMPONENT_CLIENTID) && conf != NULL) display_clientid_name(&dspbuf_client, conf); /* First must match principal */ if (!nfs_compare_clientcred(&conf->cid_credential, &data->credential) || op_ctx->client == NULL || conf->gsh_client == NULL || op_ctx->client != conf->gsh_client) { if (isDebug(COMPONENT_CLIENTID)) { char *confirmed_addr = "(unknown)"; if (conf->gsh_client != NULL) confirmed_addr = conf->gsh_client->hostaddr_str; LogDebug( COMPONENT_CLIENTID, "Confirmed ClientId %s->%s addr=%s: Principals do not match... confirmed addr=%s Return NFS4ERR_CLID_INUSE", str_clientid4, str_client, str_client_addr, confirmed_addr); } res_SETCLIENTID_CONFIRM4->status = NFS4ERR_CLID_INUSE; } else if (memcmp(conf->cid_verifier, arg_SETCLIENTID_CONFIRM4->setclientid_confirm, NFS4_VERIFIER_SIZE) == 0) { /* In this case, the record was confirmed and * we have received a retry */ if (isDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, conf); LogDebug(COMPONENT_CLIENTID, "Retry confirm for %s", str); } res_SETCLIENTID_CONFIRM4->status = NFS4_OK; } else { /* This is a case not covered... Return * NFS4ERR_CLID_INUSE */ if (isDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_cat(&dspbuf, "Verifier="); display_opaque_bytes(&dspbuf, conf->cid_verifier, NFS4_VERIFIER_SIZE); display_printf( &dspbuf, " doesn't match verifier=%s for ", str_verifier); display_client_id_rec(&dspbuf, conf); LogDebug(COMPONENT_CLIENTID, "Confirm %s", str); } res_SETCLIENTID_CONFIRM4->status = NFS4ERR_CLID_INUSE; } /* Release our reference to the confirmed clientid. */ dec_client_id_ref(conf); goto out; } /* We don't need to do any further principal checks, we can't * have a confirmed clientid record with a different principal * than the unconfirmed record. Also, at this point, we have * a matching unconfirmed clientid (punconf != NULL and pconf * == NULL). */ /* Make sure we have a reference to the confirmed clientid * record if any */ if (conf == NULL) { conf = client_record->cr_confirmed_rec; if (isDebug(COMPONENT_CLIENTID) && conf != NULL) display_clientid_name(&dspbuf_client, conf); /* Need a reference to the confirmed record for below */ if (conf != NULL) inc_client_id_ref(conf); } if (conf != NULL && conf->cid_clientid != clientid) { /* Old confirmed record - need to expire it */ if (isDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, conf); LogDebug(COMPONENT_CLIENTID, "Expiring %s", str); } /* Expire clientid and release our reference. */ nfs_client_id_expire(conf, false, true); dec_client_id_ref(conf); conf = NULL; } if (conf != NULL) { /* At this point we are updating the confirmed * clientid. Update the confirmed record from the * unconfirmed record. */ if (isFullDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, unconf); LogFullDebug(COMPONENT_CLIENTID, "Updating from %s", str); } /* Copy callback information into confirmed clientid record */ memcpy(conf->cid_cb.v40.cb_client_r_addr, unconf->cid_cb.v40.cb_client_r_addr, sizeof(conf->cid_cb.v40.cb_client_r_addr)); conf->cid_cb.v40.cb_addr = unconf->cid_cb.v40.cb_addr; conf->cid_cb.v40.cb_program = unconf->cid_cb.v40.cb_program; conf->cid_cb.v40.cb_callback_ident = unconf->cid_cb.v40.cb_callback_ident; nfs_rpc_destroy_chan(&conf->cid_cb.v40.cb_chan); /* Bump the lease timer*/ conf->cid_last_renew = time(NULL); /* Once the lease timer is updated then the client is active and * if the unresponsive client was marked as expired earlier, * then moving it out of the expired client list */ if (conf->marked_for_delayed_cleanup) remove_client_from_expired_client_list(conf); memcpy(conf->cid_verifier, unconf->cid_verifier, NFS4_VERIFIER_SIZE); /* unhash the unconfirmed clientid record */ remove_unconfirmed_client_id(unconf); /* Release our reference to the unconfirmed entry */ dec_client_id_ref(unconf); if (isDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, conf); LogDebug(COMPONENT_CLIENTID, "Updated %s", str); } /* Check and update call back channel state */ if (nfs_param.nfsv4_param.allow_delegations && nfs_test_cb_chan(conf) != RPC_SUCCESS) { set_cb_chan_down(conf, true); LogCrit(COMPONENT_CLIENTID, "setclid confirm: Callback channel is down"); } else { set_cb_chan_down(conf, false); LogDebug(COMPONENT_CLIENTID, "setclid confirm: Callback channel is UP"); } /* Release our reference to the confirmed clientid. */ dec_client_id_ref(conf); } else { /* This is a new clientid */ if (isFullDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, unconf); LogFullDebug(COMPONENT_CLIENTID, "Confirming new %s", str); } rc = nfs_client_id_confirm(unconf, COMPONENT_CLIENTID); if (rc != CLIENT_ID_SUCCESS) { res_SETCLIENTID_CONFIRM4->status = clientid_error_to_nfsstat_no_expire(rc); LogEvent(COMPONENT_CLIENTID, "FAILED to confirm client"); /* Release our reference to the unconfirmed record */ dec_client_id_ref(unconf); goto out; } /* check if the client can perform reclaims */ nfs4_chk_clid(unconf); if (isDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, unconf); LogDebug(COMPONENT_CLIENTID, "Confirmed %s", str); } /* Check and update call back channel state */ if (nfs_param.nfsv4_param.allow_delegations && nfs_test_cb_chan(unconf) != RPC_SUCCESS) { set_cb_chan_down(unconf, true); LogCrit(COMPONENT_CLIENTID, "setclid confirm: Callback channel is down"); } else { set_cb_chan_down(unconf, false); LogDebug(COMPONENT_CLIENTID, "setclid confirm: Callback channel is UP"); } /* Release our reference to the now confirmed record */ dec_client_id_ref(unconf); } if (isFullDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_record(&dspbuf, client_record); LogFullDebug( COMPONENT_CLIENTID, "Client Record %s cr_confirmed_rec=%p cr_unconfirmed_rec=%p", str, client_record->cr_confirmed_rec, client_record->cr_unconfirmed_rec); } /* Successful exit */ res_SETCLIENTID_CONFIRM4->status = NFS4_OK; out: PTHREAD_MUTEX_unlock(&client_record->cr_mutex); /* Release our reference to the client record and return */ dec_client_record_ref(client_record); return nfsstat4_to_nfs_req_result(res_SETCLIENTID_CONFIRM4->status); } /** * @brief Free memory allocated for SETCLIENTID_CONFIRM result * * @param[in,out] resp nfs4_op results */ void nfs4_op_setclientid_confirm_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_test_stateid.c000066400000000000000000000075511473756622300232760ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_test_stateid.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * * */ #include "config.h" #include #include #include #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs4.h" #include "nfs_core.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "gsh_list.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief The NFS4_OP_TEST_STATEID operation. * * This function implements the NFS4_OP_TEST_STATEID operation. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return RFC 5661, p. 375 * * @see nfs4_Compound * */ enum nfs_req_result nfs4_op_test_stateid(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { TEST_STATEID4args *const arg_TEST_STATEID4 = &op->nfs_argop4_u.optest_stateid; TEST_STATEID4res *const res_TEST_STATEID4 = &resp->nfs_resop4_u.optest_stateid; u_int nr_stateids, i; state_t *state; nfsstat4 ret; TEST_STATEID4resok *res; GSH_AUTO_TRACEPOINT(nfs4, op_test_stateid_start, TRACE_INFO, "TEST STATEID start"); /* Lock are not supported */ resp->resop = NFS4_OP_TEST_STATEID; res_TEST_STATEID4->tsr_status = NFS4_OK; if (data->minorversion == 0) { res_TEST_STATEID4->tsr_status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } nr_stateids = arg_TEST_STATEID4->ts_stateids.ts_stateids_len; res = &res_TEST_STATEID4->TEST_STATEID4res_u.tsr_resok4; res->tsr_status_codes.tsr_status_codes_val = gsh_calloc(nr_stateids, sizeof(nfsstat4)); for (i = 0; i < nr_stateids; i++) { ret = nfs4_Check_Stateid( &arg_TEST_STATEID4->ts_stateids.ts_stateids_val[i], NULL, &state, data, STATEID_NO_SPECIAL, 0, false, "TEST_STATEID"); if (state != NULL) dec_state_t_ref(state); res->tsr_status_codes.tsr_status_codes_val[i] = ret; } res->tsr_status_codes.tsr_status_codes_len = nr_stateids; GSH_AUTO_TRACEPOINT(nfs4, op_test_stateid_end, TRACE_INFO, "TEST STATEID res: status={}", res_TEST_STATEID4->tsr_status); return NFS_REQ_OK; } /* nfs41_op_lock */ /** * @brief Free memory allocated for TEST_STATEID result * * This function frees any memory allocated for the result of the * NFS4_OP_TEST_STATEID operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_test_stateid_Free(nfs_resop4 *resp) { TEST_STATEID4res *const res_TEST_STATEID4 = &resp->nfs_resop4_u.optest_stateid; TEST_STATEID4resok *res = &res_TEST_STATEID4->TEST_STATEID4res_u.tsr_resok4; if (res_TEST_STATEID4->tsr_status == NFS4_OK) { gsh_free(res->tsr_status_codes.tsr_status_codes_val); } } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_verify.c000066400000000000000000000076551473756622300221130ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * \file nfs4_op_verify.c * \brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * */ #include "config.h" #include "log.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_file_handle.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * * @brief Implementation of NFS4_OP_VERIFY * * This function implemtats the NFS4_OP_VERIFY operation. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 375 */ enum nfs_req_result nfs4_op_verify(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { VERIFY4args *const arg_VERIFY4 = &op->nfs_argop4_u.opverify; VERIFY4res *const res_VERIFY4 = &resp->nfs_resop4_u.opverify; fattr4 file_attr4; int rc = 0; struct fsal_attrlist attrs; GSH_AUTO_TRACEPOINT(nfs4, op_verify_start, TRACE_INFO, "VERIFY start"); resp->resop = NFS4_OP_VERIFY; res_VERIFY4->status = NFS4_OK; /* Do basic checks on a filehandle */ res_VERIFY4->status = nfs4_sanity_check_FH(data, NO_FILE_TYPE, false); if (res_VERIFY4->status != NFS4_OK) return NFS_REQ_ERROR; /* Get only attributes that are allowed to be read */ if (!nfs4_Fattr_Check_Access(&arg_VERIFY4->obj_attributes, FATTR4_ATTR_READ)) { res_VERIFY4->status = NFS4ERR_INVAL; return NFS_REQ_ERROR; } /* Ask only for supported attributes */ if (!nfs4_Fattr_Supported(&arg_VERIFY4->obj_attributes)) { res_VERIFY4->status = NFS4ERR_ATTRNOTSUPP; return NFS_REQ_ERROR; } fsal_prepare_attrs(&attrs, 0); res_VERIFY4->status = bitmap4_to_attrmask_t( &arg_VERIFY4->obj_attributes.attrmask, &attrs.request_mask); if (res_VERIFY4->status != NFS4_OK) return NFS_REQ_ERROR; res_VERIFY4->status = file_To_Fattr(data, attrs.request_mask, &attrs, &file_attr4, &arg_VERIFY4->obj_attributes.attrmask); if (res_VERIFY4->status != NFS4_OK) return NFS_REQ_ERROR; /* Done with the attrs */ fsal_release_attrs(&attrs); rc = nfs4_Fattr_cmp(&(arg_VERIFY4->obj_attributes), &file_attr4); if (rc == 1) res_VERIFY4->status = NFS4_OK; else if (rc == -1) res_VERIFY4->status = NFS4ERR_INVAL; else res_VERIFY4->status = NFS4ERR_NOT_SAME; nfs4_Fattr_Free(&file_attr4); GSH_AUTO_TRACEPOINT(nfs4, op_verify_end, TRACE_INFO, "VERIFY res: status={}", res_VERIFY4->status); return nfsstat4_to_nfs_req_result(res_VERIFY4->status); } /* nfs4_op_verify */ /** * @brief Frees memory allocated for VERIFY result. * * This function frees any memory allocated for the result of the * NFS4_OP_VERIFY operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_verify_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_write.c000066400000000000000000000431011473756622300217230ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_write.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. */ #include "config.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include "fsal_pnfs.h" #include "server_stats.h" #include "export_mgr.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif struct nfs4_write_data { /** Results for write */ WRITE4res *res_WRITE4; /** Owner of state */ state_owner_t *owner; /* Pointer to compound data */ compound_data_t *data; /** Object being acted on */ struct fsal_obj_handle *obj; /** Flags to control synchronization */ uint32_t flags; /** Arguments for write call - must be last */ struct fsal_io_arg write_arg; }; static enum nfs_req_result nfs4_complete_write(struct nfs4_write_data *data) { struct fsal_io_arg *write_arg = &data->write_arg; struct gsh_buffdesc verf_desc; if (data->res_WRITE4->status != NFS4_OK) { goto done; } if (write_arg->fsal_stable) data->res_WRITE4->WRITE4res_u.resok4.committed = FILE_SYNC4; else data->res_WRITE4->WRITE4res_u.resok4.committed = UNSTABLE4; data->res_WRITE4->WRITE4res_u.resok4.count = write_arg->io_amount; verf_desc.addr = data->res_WRITE4->WRITE4res_u.resok4.writeverf; verf_desc.len = sizeof(verifier4); op_ctx->fsal_export->exp_ops.get_write_verifier(op_ctx->fsal_export, &verf_desc); done: server_stats_io_done(write_arg->iov[0].iov_len, write_arg->io_amount, (data->res_WRITE4->status == NFS4_OK) ? true : false, true /*is_write*/); if (data->owner != NULL) { op_ctx->clientid = NULL; dec_state_owner_ref(data->owner); } if (write_arg->state) dec_state_t_ref(write_arg->state); return nfsstat4_to_nfs_req_result(data->res_WRITE4->status); } /** * @brief Callback for NFS4 write done * * @param[in] obj Object being acted on * @param[in] ret Return status of call * @param[in] write_data Data for write call * @param[in] caller_data Data for caller */ static void nfs4_write_cb(struct fsal_obj_handle *obj, fsal_status_t ret, void *write_data, void *caller_data) { struct nfs4_write_data *data = caller_data; uint32_t flags; /* Fixup ERR_FSAL_SHARE_DENIED status */ if (ret.major == ERR_FSAL_SHARE_DENIED) ret = fsalstat(ERR_FSAL_LOCKED, 0); /* Get result */ data->res_WRITE4->status = nfs4_Errno_status(ret); flags = atomic_postset_uint32_t_bits(&data->flags, ASYNC_PROC_DONE); if ((flags & ASYNC_PROC_EXIT) == ASYNC_PROC_EXIT) { /* nfs4_op_write has already exited, we will need to reschedule * the request for completion. */ svc_resume(data->data->req); } } enum nfs_req_result nfs4_op_write_resume(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { struct nfs4_write_data *write_data = data->op_data; enum nfs_req_result rc; uint32_t flags; if (write_data->write_arg.fsal_resume) { /* FSAL is requesting another write2 call on resume */ atomic_postclear_uint32_t_bits( &write_data->flags, ASYNC_PROC_EXIT | ASYNC_PROC_DONE); write_data->obj->obj_ops->write2(write_data->obj, true, nfs4_write_cb, &write_data->write_arg, write_data); /* Only atomically set the flags if we actually call write2, * otherwise we will have indicated as having been DONE. */ flags = atomic_postset_uint32_t_bits(&write_data->flags, ASYNC_PROC_EXIT); if ((flags & ASYNC_PROC_DONE) != ASYNC_PROC_DONE) { /* The write was not finished before we got here. When * the write completes, nfs4_write_cb() will have to * reschedule the request for completion. The resume * will be resolved by nfs4_op_write_resume() which will * free write_data and return the appropriate return * result. We will NOT go async again for the write op * (but could for a subsequent op in the compound). */ return NFS_REQ_ASYNC_WAIT; } } rc = nfs4_complete_write(data->op_data); /* NOTE: we do not expect rc == NFS_REQ_ASYNC_WAIT */ assert(rc != NFS_REQ_ASYNC_WAIT); if (rc != NFS_REQ_ASYNC_WAIT) { /* We are completely done with the request. This test wasn't * strictly necessary since nfs4_complete_write doesn't async * but at some future time, the getattr it does might go async * so we might as well be prepared here. Our caller is already * prepared for such a scenario. */ gsh_free(data->op_data); data->op_data = NULL; } return rc; } /** * @brief Write for a data server * * This function bypasses mdcache and calls directly into the FSAL * to perform a pNFS data server write. * * @param[in] op Arguments for nfs41_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs41_op * * @return per RFC5661, p. 376 * */ static enum nfs_req_result op_dswrite(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { WRITE4args *const arg_WRITE4 = &op->nfs_argop4_u.opwrite; WRITE4res *const res_WRITE4 = &resp->nfs_resop4_u.opwrite; /** @todo - this will compile but won't work... */ assert(arg_WRITE4->data.iovcnt == 1); res_WRITE4->status = op_ctx->ctx_pnfs_ds->s_ops.dsh_write( data->current_ds, &arg_WRITE4->stateid, arg_WRITE4->offset, arg_WRITE4->data.iov[0].iov_len, arg_WRITE4->data.iov[0].iov_base, arg_WRITE4->stable, &res_WRITE4->WRITE4res_u.resok4.count, &res_WRITE4->WRITE4res_u.resok4.writeverf, &res_WRITE4->WRITE4res_u.resok4.committed); return nfsstat4_to_nfs_req_result(res_WRITE4->status); } /** * @brief The NFS4_OP_WRITE operation * * This functions handles the NFS4_OP_WRITE operation in NFSv4. This * function can be called only from nfs4_Compound. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 376 */ enum nfs_req_result nfs4_op_write(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { WRITE4args *const arg_WRITE4 = &op->nfs_argop4_u.opwrite; WRITE4res *const res_WRITE4 = &resp->nfs_resop4_u.opwrite; uint64_t size = 0; uint64_t offset; state_t *state_found = NULL; state_t *state_open = NULL; fsal_status_t fsal_status = { 0, 0 }; struct fsal_obj_handle *obj = NULL; bool anonymous_started = false; struct gsh_buffdesc verf_desc; state_owner_t *owner = NULL; uint64_t MaxWrite = atomic_fetch_uint64_t(&op_ctx->ctx_export->MaxWrite); uint64_t MaxOffsetWrite = atomic_fetch_uint64_t(&op_ctx->ctx_export->MaxOffsetWrite); bool force_sync = op_ctx->export_perms.options & EXPORT_OPTION_COMMIT; struct nfs4_write_data *write_data = NULL; struct fsal_io_arg *write_arg = NULL; /* In case we don't call write2, we indicate the I/O as already done * since in that case we should go ahead and exit as expected. */ uint32_t flags = ASYNC_PROC_DONE; GSH_AUTO_TRACEPOINT( nfs4, op_write_start, TRACE_INFO, "WRITE arg: stateid={} offset={} stable={} data_len={}", arg_WRITE4->stateid.seqid, arg_WRITE4->offset, arg_WRITE4->stable, arg_WRITE4->data.data_len); /* Lock are not supported */ resp->resop = NFS4_OP_WRITE; res_WRITE4->status = NFS4_OK; if ((data->minorversion > 0) && (nfs4_Is_Fh_DSHandle(&data->currentFH))) { return op_dswrite(op, data, resp); } /* * Do basic checks on a filehandle * Only files can be written */ res_WRITE4->status = nfs4_sanity_check_FH(data, REGULAR_FILE, true); if (res_WRITE4->status != NFS4_OK) return NFS_REQ_ERROR; /* if quota support is active, then we should check is the FSAL allows inode creation or not */ fsal_status = op_ctx->fsal_export->exp_ops.check_quota( op_ctx->fsal_export, CTX_FULLPATH(op_ctx), FSAL_QUOTA_BLOCKS); if (FSAL_IS_ERROR(fsal_status)) { res_WRITE4->status = NFS4ERR_DQUOT; return NFS_REQ_ERROR; } /* vnode to manage is the current one */ obj = data->current_obj; /* Check stateid correctness and get pointer to state * (also checks for special stateids) */ res_WRITE4->status = nfs4_Check_Stateid(&arg_WRITE4->stateid, obj, &state_found, data, STATEID_SPECIAL_ANY, 0, false, "WRITE"); if (res_WRITE4->status != NFS4_OK) return NFS_REQ_ERROR; /* NB: After this points, if state_found == NULL, then * the stateid is all-0 or all-1 */ if (state_found != NULL) { struct state_deleg *sdeleg; switch (state_found->state_type) { case STATE_TYPE_SHARE: state_open = state_found; /* Note this causes an extra refcount, but it * simplifies logic below. */ inc_state_t_ref(state_open); /** @todo FSF: need to check against existing locks */ break; case STATE_TYPE_LOCK: state_open = nfs4_State_Get_Pointer( state_found->state_data.lock.openstate_key); if (state_open == NULL) { res_WRITE4->status = NFS4ERR_BAD_STATEID; goto out; } /** * @todo FSF: should check that write is in range of an * exclusive lock... */ break; case STATE_TYPE_DELEG: /* Check if the delegation state allows WRITE */ sdeleg = &state_found->state_data.deleg; if (!(sdeleg->sd_type & OPEN_DELEGATE_WRITE)) { /* Invalid delegation for this operation. */ LogDebug(COMPONENT_STATE, "Delegation type:%d state:%d", sdeleg->sd_type, sdeleg->sd_state); res_WRITE4->status = NFS4ERR_BAD_STATEID; goto out; } state_open = NULL; break; default: res_WRITE4->status = NFS4ERR_BAD_STATEID; LogDebug(COMPONENT_NFS_V4_LOCK, "WRITE with invalid stateid of type %d", (int)state_found->state_type); goto out; } /* This is a write operation, this means that the file * MUST have been opened for writing */ if (state_open != NULL && (state_open->state_data.share.share_access & OPEN4_SHARE_ACCESS_WRITE) == 0) { /* Bad open mode, return NFS4ERR_OPENMODE */ res_WRITE4->status = NFS4ERR_OPENMODE; if (isDebug(COMPONENT_NFS_V4_LOCK)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_stateid(&dspbuf, state_found); LogDebug( COMPONENT_NFS_V4_LOCK, "WRITE %s doesn't have OPEN4_SHARE_ACCESS_WRITE", str); } goto out; } } else { /* Special stateid, no open state, check to see if any * share conflicts */ state_open = NULL; /* Check for delegation conflict. */ if (state_deleg_conflict(obj, true)) { res_WRITE4->status = NFS4ERR_DELAY; goto out; } anonymous_started = true; } /* Need to permission check the write. */ fsal_status = obj->obj_ops->test_access(obj, FSAL_WRITE_ACCESS, NULL, NULL, true); if (FSAL_IS_ERROR(fsal_status)) { res_WRITE4->status = nfs4_Errno_status(fsal_status); goto out; } /* Get the characteristics of the I/O to be made */ offset = arg_WRITE4->offset; size = arg_WRITE4->data.data_len; LogFullDebug(COMPONENT_NFS_V4, "offset = %" PRIu64 " length = %" PRIu64 " stable = %d", offset, size, arg_WRITE4->stable); if (MaxOffsetWrite < UINT64_MAX) { LogFullDebug(COMPONENT_NFS_V4, "Write offset=%" PRIu64 " count=%" PRIu64 " MaxOffSet=%" PRIu64, offset, size, MaxOffsetWrite); if ((offset + size) > MaxOffsetWrite) { LogEvent( COMPONENT_NFS_V4, "A client tried to violate max file size %" PRIu64 " for exportid #%hu", MaxOffsetWrite, op_ctx->ctx_export->export_id); res_WRITE4->status = NFS4ERR_FBIG; goto out; } } if (size > MaxWrite) { /* * The client asked for too much data, we * must restrict him */ LogFullDebug(COMPONENT_NFS_V4, "write requested size = %" PRIu64 " write allowed size = %" PRIu64, size, MaxWrite); size = MaxWrite; } LogFullDebug(COMPONENT_NFS_V4, "offset = %" PRIu64 " length = %" PRIu64, offset, size); /* if size == 0 , no I/O) are actually made and everything is alright */ if (size == 0) { res_WRITE4->WRITE4res_u.resok4.count = 0; res_WRITE4->WRITE4res_u.resok4.committed = FILE_SYNC4; verf_desc.addr = res_WRITE4->WRITE4res_u.resok4.writeverf; verf_desc.len = sizeof(verifier4); op_ctx->fsal_export->exp_ops.get_write_verifier( op_ctx->fsal_export, &verf_desc); res_WRITE4->status = NFS4_OK; server_stats_io_done(0, 0, true, true); goto out; } if (!anonymous_started && data->minorversion == 0) { owner = get_state_owner_ref(state_found); if (owner != NULL) { op_ctx->clientid = &owner->so_owner.so_nfs4_owner.so_clientid; } } /* Set up args, allocate from heap, iov_len will be 1 */ write_data = gsh_calloc(1, sizeof(*write_data)); LogFullDebug(COMPONENT_NFS_V4, "Allocated write_data %p", write_data); write_arg = &write_data->write_arg; write_arg->info = NULL; write_arg->state = state_found; write_arg->offset = offset; write_arg->io_request = size; write_arg->iov_count = arg_WRITE4->data.iovcnt; write_arg->iov = arg_WRITE4->data.iov; write_arg->io_amount = 0; write_arg->fsal_stable = arg_WRITE4->stable != UNSTABLE4 || force_sync; write_data->res_WRITE4 = res_WRITE4; write_data->owner = owner; write_data->data = data; write_data->obj = obj; data->op_data = write_data; again: /* Do the actual write */ obj->obj_ops->write2(obj, false, nfs4_write_cb, write_arg, write_data); /* Only atomically set the flags if we actually call write2, otherwise * we will have indicated as having been DONE. */ flags = atomic_postset_uint32_t_bits(&write_data->flags, ASYNC_PROC_EXIT); out: if (state_open != NULL) { dec_state_t_ref(state_open); state_open = NULL; } if ((flags & ASYNC_PROC_DONE) != ASYNC_PROC_DONE) { /* The write was not finished before we got here. When the * write completes, nfs4_write_cb() will have to reschedule the * request for completion. The resume will be resolved by * nfs4_simple_resume() which will free write_data and return * the appropriate return result. We will NOT go async again for * the write op (but could for a subsequent op in the compound). */ return NFS_REQ_ASYNC_WAIT; } if (write_data != NULL && write_arg->fsal_resume) { /* FSAL is requesting another read2 call */ atomic_postclear_uint32_t_bits( &write_data->flags, ASYNC_PROC_EXIT | ASYNC_PROC_DONE); /* Make the call with the same params, though the FSAL will be * signaled by fsal_resume being set. */ goto again; } if (data->op_data != NULL) { enum nfs_req_result rc; /* We did actually call write2 but it has called back already. * Do stuff to finally wrap up the write. */ rc = nfs4_complete_write(data->op_data); /* NOTE: we do not expect rc == NFS_REQ_ASYNC_WAIT */ assert(rc != NFS_REQ_ASYNC_WAIT); if (rc != NFS_REQ_ASYNC_WAIT && data->op_data != NULL) { /* We are completely done with the request. This test * wasn't strictly necessary since nfs4_complete_write * doesn't async but at some future time, maybe it will * do something that does require more async. If it does * might go async so we might as well be prepared here. * Our caller is already prepared for such a scenario. */ gsh_free(data->op_data); data->op_data = NULL; } return rc; } GSH_AUTO_TRACEPOINT( nfs4, op_write_end, TRACE_INFO, "WRITE res: status={} count={} committed={} writeverf={}", res_WRITE4->status, res_WRITE4->WRITE4res_u.resok4.count, res_WRITE4->WRITE4res_u.resok4.committed, TP_VERIFIER(res_WRITE4->WRITE4res_u.resok4.writeverf)); return nfsstat4_to_nfs_req_result(res_WRITE4->status); } /* nfs4_op_write */ /** * @brief Free memory allocated for WRITE result * * This function frees any memory allocated for the result of the * NFS4_OP_WRITE operation. * * @param[in,out] resp nfs4_op results * */ void nfs4_op_write_Free(nfs_resop4 *resp) { /* Nothing to be done */ } /** * @brief The NFS4_OP_WRITE_SAME operation * * This functions handles the NFS4_OP_WRITE_SAME operation in NFSv4.2. This * function can be called only from nfs4_Compound. * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * */ enum nfs_req_result nfs4_op_write_same(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { WRITE_SAME4res *const res_WSAME = &resp->nfs_resop4_u.opwrite_same; resp->resop = NFS4_OP_WRITE_SAME; res_WSAME->wpr_status = NFS4ERR_NOTSUPP; return NFS_REQ_ERROR; } /** * @brief Free memory allocated for WRITE_SAME result * * This function frees any memory allocated for the result of the * NFS4_OP_WRITE_SAME operation. * * @param[in,out] resp nfs4_op results * */ void nfs4_op_write_same_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_op_xattr.c000066400000000000000000000327111473756622300217400ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright IBM Corporation, 2015 * Contributor: Marc Eshel * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_op_xattr.c * @brief Routines used for managing the NFS4 COMPOUND functions. * * Routines used for managing the NFS4 COMPOUND functions. * * */ #include "config.h" #include "log.h" #include "nfs4.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include "sal_functions.h" #include "nfs_creds.h" #define XATTR_VALUE_SIZE 1024 /** * @brief The NFS4_OP_GETXATTR operation. * * This functions handles the NFS4_OP_GETXATTR operation in NFSv4. This * function can be called only from nfs4_Compound * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * */ enum nfs_req_result nfs4_op_getxattr(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { GETXATTR4args *const arg_GETXATTR4 = &op->nfs_argop4_u.opgetxattr; GETXATTR4res *const res_GETXATTR4 = &resp->nfs_resop4_u.opgetxattr; xattrvalue4 gxr_value; fsal_status_t fsal_status; struct fsal_obj_handle *obj_handle = data->current_obj; uint32_t resp_size; resp->resop = NFS4_OP_GETXATTR; res_GETXATTR4->status = NFS4_OK; LogDebug(COMPONENT_NFS_V4, "GetXattr name: %.*s", arg_GETXATTR4->gxa_name.utf8string_len, arg_GETXATTR4->gxa_name.utf8string_val); res_GETXATTR4->GETXATTR4res_u.resok4.gxr_value.utf8string_len = 0; res_GETXATTR4->GETXATTR4res_u.resok4.gxr_value.utf8string_val = NULL; gxr_value.utf8string_len = XATTR_VALUE_SIZE; gxr_value.utf8string_val = gsh_malloc(gxr_value.utf8string_len + 1); /* Do basic checks on a filehandle */ res_GETXATTR4->status = nfs4_sanity_check_FH(data, NO_FILE_TYPE, false); if (res_GETXATTR4->status != NFS4_OK) return NFS_REQ_ERROR; if (!(op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) & ATTR4_XATTR)) { res_GETXATTR4->status = NFS4ERR_NOTSUPP; return NFS_REQ_ERROR; } fsal_status = obj_handle->obj_ops->getxattrs( obj_handle, &arg_GETXATTR4->gxa_name, &gxr_value); if (FSAL_IS_ERROR(fsal_status)) { if (fsal_status.major == ERR_FSAL_XATTR2BIG) { LogDebug(COMPONENT_NFS_V4, "FSAL buffer len %d too small", XATTR_VALUE_SIZE); /* Get size of xattr value */ gsh_free(gxr_value.utf8string_val); gxr_value.utf8string_len = 0; gxr_value.utf8string_val = NULL; fsal_status = obj_handle->obj_ops->getxattrs( obj_handle, &arg_GETXATTR4->gxa_name, &gxr_value); if (FSAL_IS_ERROR(fsal_status)) { res_GETXATTR4->status = nfs4_Errno_state( state_error_convert(fsal_status)); return NFS_REQ_ERROR; } LogDebug(COMPONENT_NFS_V4, "FSAL buffer new len %d", gxr_value.utf8string_len); /* Try again with a bigger buffer */ gxr_value.utf8string_val = gsh_malloc(gxr_value.utf8string_len + 1); fsal_status = obj_handle->obj_ops->getxattrs( obj_handle, &arg_GETXATTR4->gxa_name, &gxr_value); if (FSAL_IS_ERROR(fsal_status)) { res_GETXATTR4->status = nfs4_Errno_state( state_error_convert(fsal_status)); return NFS_REQ_ERROR; } } else { res_GETXATTR4->status = nfs4_Errno_status(fsal_status); return NFS_REQ_ERROR; } } resp_size = sizeof(nfsstat4) + sizeof(uint32_t) + RNDUP(gxr_value.utf8string_len); res_GETXATTR4->status = check_resp_room(data, resp_size); if (res_GETXATTR4->status != NFS4_OK) { gsh_free(gxr_value.utf8string_val); return NFS_REQ_ERROR; } res_GETXATTR4->GETXATTR4res_u.resok4.gxr_value = gxr_value; return NFS_REQ_OK; } /** * @brief Free memory allocated for GETXATTR result * * This function fres any memory allocated for the result of the * NFS4_OP_GETXATTR operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_getxattr_Free(nfs_resop4 *resp) { GETXATTR4res *const res_GETXATTR4 = &resp->nfs_resop4_u.opgetxattr; GETXATTR4resok *res = &res_GETXATTR4->GETXATTR4res_u.resok4; if (res_GETXATTR4->status == NFS4_OK) { gsh_free(res->gxr_value.utf8string_val); } } /** * @brief The NFS4_OP_SETXATTR operation. * * This functions handles the NFS4_OP_SETXATTR operation in NFSv4. This * function can be called only from nfs4_Compound * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * */ enum nfs_req_result nfs4_op_setxattr(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { SETXATTR4args *const arg_SETXATTR4 = &op->nfs_argop4_u.opsetxattr; SETXATTR4res *const res_SETXATTR4 = &resp->nfs_resop4_u.opsetxattr; fsal_status_t fsal_status; struct fsal_obj_handle *obj_handle = data->current_obj; resp->resop = NFS4_OP_SETXATTR; LogDebug(COMPONENT_NFS_V4, "SetXattr option=%d key=%.*s", arg_SETXATTR4->sxa_option, arg_SETXATTR4->sxa_key.utf8string_len, arg_SETXATTR4->sxa_key.utf8string_val); /* Do basic checks on a filehandle */ res_SETXATTR4->status = nfs4_sanity_check_FH(data, NO_FILE_TYPE, false); if (res_SETXATTR4->status != NFS4_OK) return NFS_REQ_ERROR; if (!(op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) & ATTR4_XATTR)) { res_SETXATTR4->status = NFS4ERR_NOTSUPP; return NFS_REQ_ERROR; } /* Don't allow attribute change while we are in grace period. * Required for delegation reclaims and may be needed for other * reclaimable states as well. */ if (!nfs_get_grace_status(false)) { res_SETXATTR4->status = NFS4ERR_GRACE; return NFS_REQ_ERROR; } res_SETXATTR4->SETXATTR4res_u.resok4.sxr_info.atomic = false; res_SETXATTR4->SETXATTR4res_u.resok4.sxr_info.before = fsal_get_changeid4(data->current_obj); fsal_status = obj_handle->obj_ops->setxattrs(obj_handle, arg_SETXATTR4->sxa_option, &arg_SETXATTR4->sxa_key, &arg_SETXATTR4->sxa_value); if (FSAL_IS_ERROR(fsal_status)) res_SETXATTR4->status = nfs4_Errno_status(fsal_status); else res_SETXATTR4->SETXATTR4res_u.resok4.sxr_info.after = fsal_get_changeid4(data->current_obj); nfs_put_grace_status(); return nfsstat4_to_nfs_req_result(res_SETXATTR4->status); } /** * @brief Free memory allocated for SETXATTR result * * This function fres any memory allocated for the result of the * NFS4_OP_SETXATTR operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_setxattr_Free(nfs_resop4 *resp) { /* Nothing to be done */ } /** * @brief The NFS4_OP_LISTXATTR operation. * * This functions handles the NFS4_OP_LISTXATTR operation in NFSv4. This * function can be called only from nfs4_Compound * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * */ enum nfs_req_result nfs4_op_listxattr(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { LISTXATTR4args *const arg_LISTXATTR4 = &op->nfs_argop4_u.oplistxattr; LISTXATTR4res *const res_LISTXATTR4 = &resp->nfs_resop4_u.oplistxattr; fsal_status_t fsal_status; struct fsal_obj_handle *obj_handle = data->current_obj; xattrlist4 list = { 0 }; nfs_cookie4 lxa_cookie = arg_LISTXATTR4->lxa_cookie; bool_t lxr_eof; component4 *entry; uint32_t maxcount, overhead, resp_size; int i; resp->resop = NFS4_OP_LISTXATTR; res_LISTXATTR4->status = NFS4_OK; LogDebug(COMPONENT_NFS_V4, "SetXattr max count %d cookie %" PRIu64, arg_LISTXATTR4->lxa_maxcount, lxa_cookie); /* Do basic checks on a filehandle */ res_LISTXATTR4->status = nfs4_sanity_check_FH(data, NO_FILE_TYPE, false); if (res_LISTXATTR4->status != NFS4_OK) return NFS_REQ_ERROR; /* Do basic checks on a filehandle */ res_LISTXATTR4->status = nfs4_sanity_check_FH(data, NO_FILE_TYPE, false); if (res_LISTXATTR4->status != NFS4_OK) return NFS_REQ_ERROR; if (!(op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) & ATTR4_XATTR)) { res_LISTXATTR4->status = NFS4ERR_NOTSUPP; return NFS_REQ_ERROR; } /* * Send the FSAL a maxcount for the lxr_names field. Each name * takes 4 bytes (for the length field) + length of the actual * data (sans NULL terminators). The names returned should have * the qualifying prefix stripped off (that is, no "user." prefix * on the names). */ overhead = sizeof(nfs_cookie4) + RNDUP(sizeof(bool)); /* Is this maxcount too small for even the tiniest xattr name? */ if (arg_LISTXATTR4->lxa_maxcount < (overhead + sizeof(uint32_t) + RNDUP(1))) { res_LISTXATTR4->status = NFS4ERR_TOOSMALL; return NFS_REQ_ERROR; } maxcount = arg_LISTXATTR4->lxa_maxcount - overhead; fsal_status = obj_handle->obj_ops->listxattrs( obj_handle, maxcount, &lxa_cookie, &lxr_eof, &list); if (FSAL_IS_ERROR(fsal_status)) { res_LISTXATTR4->status = nfs4_Errno_status(fsal_status); res_LISTXATTR4->LISTXATTR4res_u.resok4.lxr_names.xl4_entries = NULL; return NFS_REQ_ERROR; } entry = list.xl4_entries; resp_size = sizeof(nfsstat4) + sizeof(nfs_cookie4) + list.xl4_count * sizeof(uint32_t) + RNDUP(sizeof(bool)); for (i = 0; i < list.xl4_count; i++) { LogDebug(COMPONENT_FSAL, "entry %d len %d name %.*s", i, entry->utf8string_len, entry->utf8string_len, entry->utf8string_val); resp_size += RNDUP(entry->utf8string_len); entry += 1; } res_LISTXATTR4->status = check_resp_room(data, resp_size); if (res_LISTXATTR4->status != NFS4_OK) { for (i = 0; i < list.xl4_count; i++) gsh_free(list.xl4_entries[i].utf8string_val); gsh_free(list.xl4_entries); return NFS_REQ_ERROR; } res_LISTXATTR4->LISTXATTR4res_u.resok4.lxr_cookie = lxa_cookie; res_LISTXATTR4->LISTXATTR4res_u.resok4.lxr_eof = lxr_eof; res_LISTXATTR4->LISTXATTR4res_u.resok4.lxr_names = list; return NFS_REQ_OK; } /** * @brief Free memory allocated for LISTXATTR result * * This function fres any memory allocated for the result of the * NFS4_OP_LISTXATTR operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_listxattr_Free(nfs_resop4 *resp) { LISTXATTR4res *const res_LISTXATTR4 = &resp->nfs_resop4_u.oplistxattr; xattrlist4 *names = &res_LISTXATTR4->LISTXATTR4res_u.resok4.lxr_names; int i; if (res_LISTXATTR4->status == NFS4_OK) { for (i = 0; i < names->xl4_count; i++) gsh_free(names->xl4_entries[i].utf8string_val); gsh_free(names->xl4_entries); } } /** * @brief The NFS4_OP_REMOVEXATTR operation. * * This functions handles the NFS4_OP_REMOVEXATTR operation in NFSv4. This * function can be called only from nfs4_Compound * * @param[in] op Arguments for nfs4_op * @param[in,out] data Compound request's data * @param[out] resp Results for nfs4_op * * @return per RFC5661, p. 373-4 */ enum nfs_req_result nfs4_op_removexattr(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp) { REMOVEXATTR4args *const arg_REMOVEXATTR4 = &op->nfs_argop4_u.opremovexattr; REMOVEXATTR4res *const res_REMOVEXATTR4 = &resp->nfs_resop4_u.opremovexattr; fsal_status_t fsal_status; struct fsal_obj_handle *obj_handle = data->current_obj; resp->resop = NFS4_OP_REMOVEXATTR; res_REMOVEXATTR4->status = NFS4_OK; LogDebug(COMPONENT_NFS_V4, "RemoveXattr len %d name: %s", arg_REMOVEXATTR4->rxa_name.utf8string_len, arg_REMOVEXATTR4->rxa_name.utf8string_val); /* Do basic checks on a filehandle */ res_REMOVEXATTR4->status = nfs4_sanity_check_FH(data, NO_FILE_TYPE, false); if (res_REMOVEXATTR4->status != NFS4_OK) return NFS_REQ_ERROR; if (!(op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export) & ATTR4_XATTR)) { res_REMOVEXATTR4->status = NFS4ERR_NOTSUPP; return NFS_REQ_ERROR; } /* Don't allow attribute change while we are in grace period. * Required for delegation reclaims and may be needed for other * reclaimable states as well. */ if (!nfs_get_grace_status(false)) { res_REMOVEXATTR4->status = NFS4ERR_GRACE; return NFS_REQ_ERROR; } res_REMOVEXATTR4->REMOVEXATTR4res_u.resok4.rxr_info.atomic = false; res_REMOVEXATTR4->REMOVEXATTR4res_u.resok4.rxr_info.before = fsal_get_changeid4(data->current_obj); fsal_status = obj_handle->obj_ops->removexattrs( obj_handle, &arg_REMOVEXATTR4->rxa_name); if (FSAL_IS_ERROR(fsal_status)) res_REMOVEXATTR4->status = nfs4_Errno_status(fsal_status); else res_REMOVEXATTR4->REMOVEXATTR4res_u.resok4.rxr_info.after = fsal_get_changeid4(data->current_obj); nfs_put_grace_status(); return nfsstat4_to_nfs_req_result(res_REMOVEXATTR4->status); } /** * @brief Free memory allocated for REMOVEXATTR result * * This function fres any memory allocated for the result of the * NFS4_OP_REMOVEXATTR operation. * * @param[in,out] resp nfs4_op results */ void nfs4_op_removexattr_Free(nfs_resop4 *resp) { /* Nothing to be done */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs4_pseudo.c000066400000000000000000000620101473756622300213720ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs4_pseudo.c * @brief Routines used for managing the NFS4 pseudo file system. * * Routines used for managing the NFS4 pseudo file system. */ #include "config.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_proto_tools.h" #include "nfs_exports.h" #include "fsal.h" #include "export_mgr.h" struct pseudofs_state { struct gsh_export *export; struct fsal_obj_handle *obj; struct gsh_refstr *ref_pseudopath; const char *st_pseudopath; struct gsh_refstr *ref_fullpath; const char *st_fullpath; }; /** * @brief Check to see if an export is PSEUDO * * Can be PSEUDO, or MDCACHE on PSEUDO * * @param[in] export Export to check * @return true if PSEUDO, false otherwise */ static bool is_export_pseudo(struct gsh_export *export) { /* If it's PSEUDO, it's PSEUDO */ if (strcmp(export->fsal_export->fsal->name, "PSEUDO") == 0) return true; /* If it's !MDCACHE, it's !PSEUDO */ if (strcmp(export->fsal_export->fsal->name, "MDCACHE") != 0) return false; /* If it's MDCACHE stacked on PSEUDO, it's PSEUDO */ if (strcmp(export->fsal_export->sub_export->fsal->name, "PSEUDO") == 0) return true; return false; } /** * @brief Delete the unnecessary directories from pseudo FS * * @param pseudo_path [IN] full path of the node * @param entry [IN] cache entry for the last directory in the path * * If this entry is present is pseudo FSAL, and is unnecessary, then remove it. * Check recursively if the parent entry is needed. * * The pseudo_path is deconstructed in place to create the subsequently shorter * pseudo paths. * * When called the first time, entry is the mount point of an export that has * been unmounted from the PseudoFS. By definition, it is NOT the root of a * PseudoFS. Also, the PseudoFS root filesystem is NOT mounted and thus this * function will not be called for it. The req_op_context references the * export for the PseudoFS entry is within. Note that the caller is * responsible for checking if it is an FSAL_PSEUDO export (we only clean up * directories in FSAL_PSEUDO filesystems). */ void cleanup_pseudofs_node(char *pseudo_path, struct fsal_obj_handle *obj) { struct fsal_obj_handle *parent_obj; char *pos = pseudo_path + strlen(pseudo_path) - 1; char *name; fsal_status_t fsal_status; op_ctx->flags.pseudo_fsal_internal_lookup = true; /* Strip trailing / from pseudo_path */ while (*pos == '/') pos--; /* Replace first trailing / if any with NUL */ pos[1] = '\0'; /* Find the previous slash. * We will NEVER back up PAST the root, so no need to check * for walking off the beginning of the string. */ while (*pos != '/') pos--; /* Remember the element name for remove */ name = pos + 1; LogDebug(COMPONENT_EXPORT, "Checking if pseudo node %s is needed from path %s", name, pseudo_path); fsal_status = fsal_lookupp(obj, &parent_obj, NULL); if (FSAL_IS_ERROR(fsal_status)) { /* Truncate the pseudo_path to be the path to the parent */ *pos = '\0'; LogCrit(COMPONENT_EXPORT, "Could not find cache entry for parent directory %s", pseudo_path); return; } fsal_status = fsal_remove(parent_obj, name, NULL, NULL); if (FSAL_IS_ERROR(fsal_status)) { /* Bailout if we get directory not empty error */ if (fsal_status.major == ERR_FSAL_NOTEMPTY) { LogDebug(COMPONENT_EXPORT, "PseudoFS parent directory %s is not empty", pseudo_path); } else { LogCrit(COMPONENT_EXPORT, "Removing pseudo node %s failed with %s", pseudo_path, msg_fsal_err(fsal_status.major)); } goto out; } /* Before recursing the check the parent, get export lock for looking at * exp_root_obj so we can check if we have reached the root of * the mounted on export. */ PTHREAD_RWLOCK_rdlock(&op_ctx->ctx_export->exp_lock); if (parent_obj == op_ctx->ctx_export->exp_root_obj) { LogDebug(COMPONENT_EXPORT, "Reached root of PseudoFS %s", CTX_PSEUDOPATH(op_ctx)); PTHREAD_RWLOCK_unlock(&op_ctx->ctx_export->exp_lock); goto out; } PTHREAD_RWLOCK_unlock(&op_ctx->ctx_export->exp_lock); /* Truncate the pseudo_path to be the path to the parent */ *pos = '\0'; /* check if the parent directory is needed */ cleanup_pseudofs_node(pseudo_path, parent_obj); out: parent_obj->obj_ops->put_ref(parent_obj); } /** * @brief Find the node for this path component * * If not found, create it. * * @param name [IN] path name component * @param state [IN] pseudofs state * * @return status as bool. false terminates foreach */ bool make_pseudofs_node(char *name, struct pseudofs_state *state) { struct fsal_obj_handle *new_node = NULL; fsal_status_t fsal_status; bool retried = false; struct fsal_attrlist sattr; char const *fsal_name; retry: /* First, try to lookup the entry */ op_ctx->flags.pseudo_fsal_internal_lookup = true; fsal_status = fsal_lookup(state->obj, name, &new_node, NULL); if (!FSAL_IS_ERROR(fsal_status)) { /* Make sure new node is a directory */ if (new_node->type != DIRECTORY) { LogCrit(COMPONENT_EXPORT, "BUILDING PSEUDOFS: Export_Id %d Path %s Pseudo Path %s LOOKUP %s is not a directory", state->export->export_id, state->st_fullpath, state->st_pseudopath, name); /* Release the reference on the new node */ new_node->obj_ops->put_ref(new_node); return false; } LogDebug(COMPONENT_EXPORT, "BUILDING PSEUDOFS: Parent %p fileid %" PRIx64 "entry %p fileid %" PRIx64 " %s FSAL %s already exists", state->obj, state->obj->fileid, new_node, new_node->fileid, name, new_node->fsal->name); state->obj->obj_ops->put_ref(state->obj); /* Make new node the current node */ state->obj = new_node; return true; } /* Now check the FSAL, if it's not FSAL_PSEUDO, any error in the lookup * is a complete failure. */ fsal_name = op_ctx->ctx_export->fsal_export->exp_ops.get_name( op_ctx->ctx_export->fsal_export); /* fsal_name should be "PSEUDO" or "PSEUDO/" */ if (strncmp(fsal_name, "PSEUDO", 6) != 0 || (fsal_name[6] != '/' && fsal_name[6] != '\0')) { LogCrit(COMPONENT_EXPORT, "BUILDING PSEUDOFS: Export_Id %d Path %s Pseudo Path %s LOOKUP %s failed with %s%s", state->export->export_id, state->st_fullpath, state->st_pseudopath, name, msg_fsal_err(fsal_status.major), fsal_status.major == ERR_FSAL_NOENT ? " (can't create directory on non-PSEUDO FSAL)" : ""); return false; } /* The only failure that FSAL_PSEUDO lookup can * have is the entry doesn't exist, however, when an export update is * in progress (which of course it is right now...) it will return * ERR_FSAL_DELAY instead of ERR_FSAL_NOENT. Since this is the ONLY * error condition, if it's FSAL_PSEUDO we just ignore the actual * error. * * Now create the missing node. * */ fsal_prepare_attrs(&sattr, ATTR_MODE); sattr.mode = 0755; fsal_status = fsal_create(state->obj, name, DIRECTORY, &sattr, NULL, &new_node, NULL, NULL, NULL); /* Release the attributes (may release an inherited ACL - which * FSAL_PSEUDO doesn't have...) */ fsal_release_attrs(&sattr); if (fsal_status.major == ERR_FSAL_EXIST && !retried) { /* This is ALMOST dead code... Since we now gate and only a * single export update can be in progress, no one should be * modifying the PseudoFS - EXCEPT - The PseudoFS COULD be * exported read/write and thus a client COULD have created the * directory we are looking for... Yea, not really going to * happen, but since we have the retry code to handle it, might * as well keep the code... */ LogDebug( COMPONENT_EXPORT, "BUILDING PSEUDOFS: Parent %p Node %p %s seems to already exist, try LOOKUP again", state->obj, new_node, name); retried = true; goto retry; } if (FSAL_IS_ERROR(fsal_status)) { /* An error occurred - this actually is technically impossible * for FSAL_PSEUDO unless the PseudoFS export is read/write and * a client manages to change the parent directory to a regular * file... */ LogCrit(COMPONENT_EXPORT, "BUILDING PSEUDOFS: Export_Id %d Path %s Pseudo Path %s CREATE %s failed with %s", state->export->export_id, state->st_fullpath, state->st_pseudopath, name, msg_fsal_err(fsal_status.major)); return false; } LogDebug( COMPONENT_EXPORT, "BUILDING PSEUDOFS: Export_Id %d Path %s Pseudo Path %s CREATE %s obj %p state %p succeeded", state->export->export_id, state->st_fullpath, state->st_pseudopath, name, new_node, new_node->state_hdl); /* Release reference to the old node */ state->obj->obj_ops->put_ref(state->obj); /* Make new node the current node */ state->obj = new_node; return true; } /** * @brief Mount an export in the new Pseudo FS. * * @param exp [IN] export in question * * @return status as bool. */ bool pseudo_mount_export(struct gsh_export *export) { struct pseudofs_state state; char *tmp_pseudopath; char *last_slash; char *p; char *rest; fsal_status_t fsal_status; char *tok; char *saveptr = NULL; int rc; bool result = false; LOG_EXPORT(NIV_DEBUG, "Attempt pseudofs mount for", export, false); /* skip exports that aren't for NFS v4 * Also, nothing to actually do for Pseudo Root * (defer checking pseudopath for Pseudo Root until we have refstr.) */ if (!export_can_be_mounted(export)) return true; /* Initialize state and it's op_context. * Note that a zeroed creds works just fine as root creds. */ state.export = export; rcu_read_lock(); state.ref_pseudopath = gsh_refstr_get(rcu_dereference(export->pseudopath)); state.ref_fullpath = gsh_refstr_get(rcu_dereference(export->fullpath)); rcu_read_unlock(); if (state.ref_pseudopath == NULL) LogFatal(COMPONENT_EXPORT, "BUILDING PSEUDOFS: Export_Id %d missing pseudopath", export->export_id); state.st_pseudopath = state.ref_pseudopath->gr_val; if (state.ref_fullpath == NULL) LogFatal(COMPONENT_EXPORT, "BUILDING PSEUDOFS: Export_Id %d missing fullpath", export->export_id); state.st_fullpath = state.ref_fullpath->gr_val; if (state.st_pseudopath[1] == '\0') { /* Nothing to do for pseudo root */ result = true; goto out_no_context; } LogDebug(COMPONENT_EXPORT, "BUILDING PSEUDOFS: Export_Id %d Path %s Pseudo Path %s", export->export_id, state.st_fullpath, state.st_pseudopath); /* Make a copy of the path */ tmp_pseudopath = gsh_strdupa(state.st_pseudopath); /* Find last '/' in path */ p = tmp_pseudopath; last_slash = tmp_pseudopath; while (*p != '\0') { if (*p == '/') last_slash = p; p++; } /* Terminate path leading to junction. */ *last_slash = '\0'; LogDebug(COMPONENT_EXPORT, "BUILDING PSEUDOFS: Looking for export for %s", tmp_pseudopath); /* Now find the export we are mounted on */ set_op_context_export(get_gsh_export_by_pseudo(tmp_pseudopath, false)); if (op_ctx->ctx_export == NULL) { LogFatal(COMPONENT_EXPORT, "Could not find mounted on export for %s, tmp=%s", state.st_pseudopath, tmp_pseudopath); } /* Put the slash back in */ *last_slash = '/'; /* Point to the portion of this export's pseudo path that is beyond the * mounted on export's pseudo path. */ if (CTX_PSEUDOPATH(op_ctx)[1] == '\0') rest = tmp_pseudopath + 1; else rest = tmp_pseudopath + strlen(CTX_PSEUDOPATH(op_ctx)) + 1; LogDebug( COMPONENT_EXPORT, "BUILDING PSEUDOFS: Export_Id %d Path %s Pseudo Path %s Rest %s Mounted on ID %d Path %s", export->export_id, state.st_fullpath, state.st_pseudopath, rest, op_ctx->ctx_export->export_id, CTX_PSEUDOPATH(op_ctx)); /* Get the root inode of the mounted on export */ fsal_status = nfs_export_get_root_entry(op_ctx->ctx_export, &state.obj); if (FSAL_IS_ERROR(fsal_status)) { LogCrit(COMPONENT_EXPORT, "BUILDING PSEUDOFS: Could not get root entry for Export_Id %d Path %s Pseudo Path %s", export->export_id, state.st_fullpath, state.st_pseudopath); /* Goto out to release the reference on the mounted on export. */ goto out; } /* Now we need to process the rest of the path, creating directories * if necessary. */ for (tok = strtok_r(rest, "/", &saveptr); tok; tok = strtok_r(NULL, "/", &saveptr)) { rc = make_pseudofs_node(tok, &state); if (!rc) { /* Release reference on mount point inode and goto out * to release the reference on the mounted on export */ state.obj->obj_ops->put_ref(state.obj); goto out; } } /* Fill in the mounted on information for the export. */ PTHREAD_RWLOCK_wrlock(&export->exp_lock); LogDebug( COMPONENT_EXPORT, "BUILDING PSEUDOFS: Export_Id %d Path %s Pseudo Path %s mounted_on_fileid %" PRIx64, export->export_id, state.st_fullpath, state.st_pseudopath, state.obj->fileid); export->exp_mounted_on_file_id = state.obj->fileid; /* Get an active reference on the object */ state.obj->obj_ops->get_ref(state.obj); /* Pass object ref off to export */ export->exp_junction_obj = state.obj; export_root_object_get(export->exp_junction_obj); /* Take an export ref for the parent export */ export->exp_parent_exp = op_ctx->ctx_export; get_gsh_export_ref(export->exp_parent_exp); /* Add ourselves to the list of exports mounted on parent */ PTHREAD_RWLOCK_wrlock(&export->exp_parent_exp->exp_lock); glist_add_tail(&export->exp_parent_exp->mounted_exports_list, &export->mounted_exports_node); PTHREAD_RWLOCK_unlock(&export->exp_parent_exp->exp_lock); PTHREAD_RWLOCK_unlock(&export->exp_lock); /* Set this outside the export->exp_lock, protected by EXPORT_ADMIN_LOCK */ export->is_mounted = true; /* Now that all entries are added to pseudofs tree, and we are pointing * to the final node, make it a proper junction. * Note that this is the last thing that should be updated on this * handle, since setting the junction_export is what signals to the * PSEUDO FSAL that this entry is done. */ PTHREAD_RWLOCK_wrlock(&state.obj->state_hdl->jct_lock); state.obj->state_hdl->dir.junction_export = export; rcu_read_lock(); state.obj->state_hdl->dir.jct_pseudopath = gsh_refstr_get(rcu_dereference(export->pseudopath)); rcu_read_unlock(); LogDebug( COMPONENT_EXPORT, "BUILDING PSEUDOFS: Export_Id %d Path %s Pseudo Path %s junction %p", export->export_id, state.st_fullpath, state.st_pseudopath, state.obj->state_hdl->dir.junction_export); PTHREAD_RWLOCK_unlock(&state.obj->state_hdl->jct_lock); result = true; out: /* And we're done with the various references */ clear_op_context_export(); out_no_context: gsh_refstr_put(state.ref_pseudopath); gsh_refstr_put(state.ref_fullpath); return result; } /** * @brief Build a pseudo fs from an exportlist * * foreach through the exports to create pseudofs entries. * * @return status as errno (0 == SUCCESS). */ void create_pseudofs(void) { struct req_op_context op_context; struct gsh_export *export; /* Initialize a root context */ init_op_context(&op_context, NULL, NULL, NULL, NULL, NFS_V4, 0, NFS_RELATED); op_ctx->flags.pseudo_fsal_internal_lookup = true; while (true) { export = export_take_mount_work(); if (export == NULL) break; if (!pseudo_mount_export(export)) LogFatal(COMPONENT_EXPORT, "Could not complete creating PseudoFS"); } release_op_context(); } /** * @brief Unmount an export from the Pseudo FS. * * @param exp [IN] export in question */ void pseudo_unmount_export(struct gsh_export *export) { struct gsh_export *mounted_on_export; struct fsal_obj_handle *junction_inode; struct req_op_context op_context; struct gsh_refstr *ref_pseudopath; /* Take the export write lock to get the junction inode. * We take write lock because if there is no junction inode, * we jump straight to cleaning up our presence in parent * export. */ PTHREAD_RWLOCK_wrlock(&export->exp_lock); junction_inode = export->exp_junction_obj; mounted_on_export = export->exp_parent_exp; if (junction_inode == NULL || mounted_on_export == NULL) { /* This must be the Pseudo Root or a non-NFSv4 export, nothing * to do then. Both better actually be NULL. */ assert(junction_inode == NULL && mounted_on_export == NULL); LogDebug(COMPONENT_EXPORT, "Unmount of export %d unnecessary", export->export_id); PTHREAD_RWLOCK_unlock(&export->exp_lock); return; } /* Take over the reference to the junction pseudopath - it has the * correct path. export->pseudopath may have been changed by update. */ ref_pseudopath = junction_inode->state_hdl->dir.jct_pseudopath; if (ref_pseudopath == NULL) { LogFatal(COMPONENT_EXPORT, "Unmount of Export Id %d failed no pseudopath", export->export_id); } LogDebug(COMPONENT_EXPORT, "Unmount %s", ref_pseudopath->gr_val); /* Clean up the junction inode */ LogDebug(COMPONENT_EXPORT, "Cleanup junction inode %p pseudopath %s", junction_inode, ref_pseudopath->gr_val); /* Make the node not accessible from the junction node. */ PTHREAD_RWLOCK_wrlock(&junction_inode->state_hdl->jct_lock); junction_inode->state_hdl->dir.jct_pseudopath = NULL; junction_inode->state_hdl->dir.junction_export = NULL; PTHREAD_RWLOCK_unlock(&junction_inode->state_hdl->jct_lock); /* Detach the export from the inode */ export_root_object_put(export->exp_junction_obj); export->exp_junction_obj = NULL; /* Detach the export from the export it's mounted on */ LogDebug(COMPONENT_EXPORT, "Remove from mounted on export %d pseudopath %s", mounted_on_export->export_id, mounted_on_export->pseudopath->gr_val); export->exp_parent_exp = NULL; /* Remove ourselves from the list of exports mounted on parent */ PTHREAD_RWLOCK_wrlock(&mounted_on_export->exp_lock); glist_del(&export->mounted_exports_node); PTHREAD_RWLOCK_unlock(&mounted_on_export->exp_lock); /* Release the export lock */ PTHREAD_RWLOCK_unlock(&export->exp_lock); /* Clear this outside the export->exp_lock, protected by * EXPORT_ADMIN_LOCK */ export->is_mounted = false; /* Get a ref to the mounted_on_export and initialize op_context */ get_gsh_export_ref(mounted_on_export); init_op_context(&op_context, mounted_on_export, mounted_on_export->fsal_export, NULL, NULL, NFS_V4, 0, NFS_RELATED); op_ctx->flags.pseudo_fsal_internal_lookup = true; if (is_export_pseudo(mounted_on_export) && junction_inode != NULL) { char *pseudo_path = gsh_strdup(ref_pseudopath->gr_val); /* Remove the unused PseudoFS nodes */ cleanup_pseudofs_node(pseudo_path, junction_inode); gsh_free(pseudo_path); } else { /* Properly unmount at the FSAL layer - primarily this will * allow mdcache to unmap the junction inode from the parent * export. */ mounted_on_export->fsal_export->exp_ops.unmount( mounted_on_export->fsal_export, junction_inode); } /* Release our reference to the export we are mounted on. */ put_gsh_export(mounted_on_export); /* Release the LRU reference */ junction_inode->obj_ops->put_ref(junction_inode); /* Release the active reference */ junction_inode->obj_ops->put_ref(junction_inode); release_op_context(); LogFullDebug(COMPONENT_EXPORT, "Finish unexport %s", ref_pseudopath->gr_val); gsh_refstr_put(ref_pseudopath); } /** * @brief Unmount an export and its descendants from the Pseudo FS. * * @param exp [IN] export in question */ void pseudo_unmount_export_tree(struct gsh_export *export) { /* Unmount any exports mounted on us */ while (true) { struct gsh_export *sub_mounted_export; PTHREAD_RWLOCK_rdlock(&export->exp_lock); /* Find a sub_mounted export */ sub_mounted_export = glist_first_entry( &export->mounted_exports_list, struct gsh_export, mounted_exports_node); if (sub_mounted_export == NULL) { /* If none, break out of the loop */ PTHREAD_RWLOCK_unlock(&export->exp_lock); break; } /* Take a reference to that export. Export may be dead * already, but we should see if we can speed along its * unmounting. */ get_gsh_export_ref(sub_mounted_export); /* Drop the lock */ PTHREAD_RWLOCK_unlock(&export->exp_lock); /* And unmount it */ pseudo_unmount_export_tree(sub_mounted_export); /* And put the reference */ put_gsh_export(sub_mounted_export); } pseudo_unmount_export(export); } /** * @brief Do a depth first search of an export and its descendants seeking any * defunct descendants, and unmounting them and any descendants of those * exports. * * Special inputs of generation 0 and ancestor_is_defunct true causes the * export and all descendants to be unmounted and added to the list to be * remounted. Note that if this was because the export in question is no longer * an NFS v4 export, it will not be remounted but any descendants that are still * NFS v4 exports will be remounted. Thus accomplishing the intended task. * * @param export [IN] export in question, if NULL, prune from root * @param generation [IN] generation of config * @param ancestor_is_defunct [IN] flag indicating an ancestor is defunct */ void prune_pseudofs_subtree(struct gsh_export *export, uint64_t generation, bool ancestor_is_defunct) { struct gsh_export *child_export; struct glist_head *glist, *glistn; bool defunct, need_put = false; struct gsh_refstr *ref_pseudopath; if (export == NULL) { /* Get a reference to the PseudoFS Root Export */ export = get_gsh_export_by_pseudo("/", true); if (export == NULL) { /* No pseudo root? */ return; } need_put = true; } rcu_read_lock(); ref_pseudopath = gsh_refstr_get(rcu_dereference(export->pseudopath)); rcu_read_unlock(); if (ref_pseudopath == NULL) { LogFatal(COMPONENT_EXPORT, "Unmount of Export Id %d failed no pseudopath", export->export_id); } defunct = ancestor_is_defunct || export->config_gen < generation || export->update_prune_unmount; LogDebug(COMPONENT_EXPORT, "Export %d pseudo %s export gen %" PRIu64 " current gen %" PRIu64 " prune unmount %s ancestor_is_defunct %s", export->export_id, ref_pseudopath->gr_val, export->config_gen, generation, export->update_prune_unmount ? "yes" : "no", ancestor_is_defunct ? "yes" : "no"); /* Prune any exports mounted on us. Note that the list WILL change as * child exports are pruned, and note that we drop the lock, however, * this is safe because we hold the export_admin_mutex and that mutex is * held by any thread that will be modifying the PseudoFS structure. * Therefor, glistn will be safe because while the prune may eventually * unmount child_export, it is impossible for any other child exports to * be unmounted during this time, so glistn continues to be valid. */ PTHREAD_RWLOCK_rdlock(&export->exp_lock); glist_for_each_safe(glist, glistn, &export->mounted_exports_list) { /* Find a sub_mounted export */ child_export = glist_entry(glist, struct gsh_export, mounted_exports_node); /* Take a reference to that export. Export may be dead * already, but we should see if we can speed along its * unmounting. */ get_gsh_export_ref(child_export); /* Drop the lock */ PTHREAD_RWLOCK_unlock(&export->exp_lock); /* And prune this child */ prune_pseudofs_subtree(child_export, generation, defunct); /* And put the reference */ put_gsh_export(child_export); /* And take the lock again. */ PTHREAD_RWLOCK_rdlock(&export->exp_lock); } /* Drop the lock */ PTHREAD_RWLOCK_unlock(&export->exp_lock); if (defunct) { LogDebug(COMPONENT_EXPORT, "Export %d pseudo %s unmounted because %s", export->export_id, ref_pseudopath->gr_val, export->config_gen < generation ? "it is defunct" : export->update_prune_unmount ? "update indicates unmount" : ancestor_is_defunct ? "ancestor is defunct" : "????"); pseudo_unmount_export(export); if (export->config_gen >= generation && (export->export_perms.options & EXPORT_OPTION_NFSV4) != 0 && export->export_id != 0 && ref_pseudopath->gr_val[1] != '\0') { export->update_remount = true; } } else { LogDebug(COMPONENT_EXPORT, "Export %d Pseudo %s not unmounted", export->export_id, ref_pseudopath->gr_val); } if (export->update_remount) { LogDebug(COMPONENT_EXPORT, "Export %d Pseudo %s is to be remounted", export->export_id, ref_pseudopath->gr_val); /* Add to mount work */ export_add_to_mount_work(export); } /* Clear flags */ export->update_prune_unmount = false; export->update_remount = false; if (need_put) { /* Put the pseudo root export we found above */ put_gsh_export(export); } gsh_refstr_put(ref_pseudopath); } nfs-ganesha-6.5/src/Protocols/NFS/nfs_null.c000066400000000000000000000037341473756622300207710ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_null.c * @brief NFS NULL procedure for all versions */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" /** * @brief The NFS proc null function, for all versions. * * @param[in] arg ignored * @param[in] req ignored * @param[out] res ignored */ int nfs_null(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { LogDebug(COMPONENT_NFSPROTO, "REQUEST PROCESSING: Calling NFS_NULL"); return NFS3_OK; } /**n * @brief Free memory allocated for the nfs_null result * * This function frees any memory allocated for the result of the * nfs_null operation. * * @param[in,out] res Result structure * */ void nfs_null_free(nfs_res_t *res) { /* Nothing to do here */ } nfs-ganesha-6.5/src/Protocols/NFS/nfs_proto_tools.c000066400000000000000000003712321473756622300224030ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * nfs_fh4 * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_proto_tools.c * @brief A set of functions used to managed NFS. * * A set of functions used to managed NFS. */ #include "log.h" #include "fsal.h" #include "fsal_convert.h" #include "FSAL/fsal_commonlib.h" #include "nfs_core.h" #include "nfs_convert.h" #include "nfs_exports.h" #include "nfs_proto_tools.h" #include "nfs4_acls.h" #include "idmapper.h" #include "export_mgr.h" /* Define mapping of NFS4 who name and type. */ static struct { char *string; int type; } whostr_2_type_map[] = { { .string = "OWNER@", .type = FSAL_ACE_SPECIAL_OWNER, }, { .string = "GROUP@", .type = FSAL_ACE_SPECIAL_GROUP, }, { .string = "EVERYONE@", .type = FSAL_ACE_SPECIAL_EVERYONE, }, }; #ifdef _USE_NFS3 /** * Converts FSAL Attributes to NFSv3 PostOp Attributes structure. * * This function converts FSAL Attributes to NFSv3 PostOp Attributes * structure. * * If attrs is passed in, the caller MUST call fsal_release_attrs. * * @param[in] obj FSAL object * @param[out] Fattr NFSv3 PostOp structure attributes. * @param[in] attrs Optional attributes passed in * */ void nfs_SetPostOpAttr(struct fsal_obj_handle *obj, post_op_attr *Fattr, struct fsal_attrlist *attrs) { struct fsal_attrlist attr_buf, *pattrs = attrs; if (attrs == NULL || ((attrs->valid_mask & ATTRS_NFS3) != ATTRS_NFS3)) { pattrs = &attr_buf; fsal_prepare_attrs(pattrs, ATTRS_NFS3 | ATTR_RDATTR_ERR); (void)obj->obj_ops->getattrs(obj, pattrs); } /* Check if attributes follow and place the following attributes */ Fattr->attributes_follow = nfs3_Fixup_FSALattr(obj, pattrs); Fattr->post_op_attr_u.attributes = *pattrs; if (attrs == NULL) { /* Release any attributes fetched. Caller MUST release any * attributes that were passed in. */ fsal_release_attrs(pattrs); } } /** * @brief Converts FSAL Attributes to NFSv3 PreOp Attributes structure. * * This function Converts FSAL Attributes to NFSv3 PreOp Attributes * structure. * * @param[in] fsal_attrs FSAL attributes * @param[out] out_pre_attr NFSv3 PreOp structure attributes. */ void nfs_PreOpAttrFromFsalAttr(struct fsal_attrlist *fsal_attrs, pre_op_attr *out_pre_attr) { if (FSAL_TEST_MASK(fsal_attrs->valid_mask, ATTR_SIZE | ATTR_CTIME | ATTR_MTIME)) { out_pre_attr->pre_op_attr_u.attributes.size = fsal_attrs->filesize; out_pre_attr->pre_op_attr_u.attributes.mtime.tv_sec = fsal_attrs->mtime.tv_sec; out_pre_attr->pre_op_attr_u.attributes.mtime.tv_nsec = fsal_attrs->mtime.tv_nsec; out_pre_attr->pre_op_attr_u.attributes.ctime.tv_sec = fsal_attrs->ctime.tv_sec; out_pre_attr->pre_op_attr_u.attributes.ctime.tv_nsec = fsal_attrs->ctime.tv_nsec; out_pre_attr->attributes_follow = TRUE; } } /** * @brief Get NFSv3 PreOp Attributes structure. * * This function gets FSAL Attributes for object and converts it to NFSv3 * PreOp Attributes structure. * * @param[in] obj FSAL object * @param[out] attr NFSv3 PreOp structure attributes. */ void nfs_SetPreOpAttr(struct fsal_obj_handle *obj, pre_op_attr *attr) { fsal_status_t status; struct fsal_attrlist attrs; fsal_prepare_attrs(&attrs, ATTR_SIZE | ATTR_CTIME | ATTR_MTIME); status = obj->obj_ops->getattrs(obj, &attrs); if (FSAL_IS_ERROR(status)) attr->attributes_follow = false; else { nfs_PreOpAttrFromFsalAttr(&attrs, attr); } fsal_release_attrs(&attrs); } /** * @brief Set NFSv3 Weak Cache Coherency structure * * This function sets NFSv3 Weak Cache Coherency structure. * * @param[in] before_attr Pre-op attrs for before state * @param[in] obj The FSAL object after operation * @param[in] post_attrs Optional post attrs * @param[out] wcc_data the Weak Cache Coherency structure * */ void nfs_SetWccData(const struct pre_op_attr *before_attr, struct fsal_obj_handle *obj, struct fsal_attrlist *post_attrs, wcc_data *wcc_data) { if (before_attr == NULL) wcc_data->before.attributes_follow = false; else wcc_data->before = *before_attr; /* Build directory post operation attributes */ nfs_SetPostOpAttr(obj, &wcc_data->after, post_attrs); } /* nfs_SetWccData */ #endif /* _USE_NFS3 */ /** * @brief Indicate if an error is retryable * * This function indicates if an error is retryable or not. * * @param fsal_errors [IN] input FSAL error value, to be tested. * * @return true if retryable, false otherwise. * * @todo: Not implemented for NOW BUGAZEOMEU * */ bool nfs_RetryableError(fsal_errors_t fsal_errors) { switch (fsal_errors) { case ERR_FSAL_IO: case ERR_FSAL_NXIO: if (nfs_param.core_param.drop_io_errors) { /* Drop the request */ return true; } else { /* Propagate error to the client */ return false; } break; case ERR_FSAL_INVAL: case ERR_FSAL_OVERFLOW: if (nfs_param.core_param.drop_inval_errors) { /* Drop the request */ return true; } else { /* Propagate error to the client */ return false; } break; case ERR_FSAL_DELAY: if (nfs_param.core_param.drop_delay_errors) { /* Drop the request */ return true; } else { /* Propagate error to the client */ return false; } break; case ERR_FSAL_NO_ERROR: LogCrit(COMPONENT_NFSPROTO, "Possible implementation error: ERR_FSAL_NO_ERROR managed as an error"); return false; case ERR_FSAL_NOMEM: case ERR_FSAL_NOT_OPENED: /* Internal error, should be dropped and retryed */ return true; case ERR_FSAL_NOTDIR: case ERR_FSAL_SYMLINK: case ERR_FSAL_BADTYPE: case ERR_FSAL_STILL_IN_USE: case ERR_FSAL_EXIST: case ERR_FSAL_NOTEMPTY: case ERR_FSAL_NOENT: case ERR_FSAL_ACCESS: case ERR_FSAL_ISDIR: case ERR_FSAL_PERM: case ERR_FSAL_NOSPC: case ERR_FSAL_ROFS: case ERR_FSAL_STALE: case ERR_FSAL_FHEXPIRED: case ERR_FSAL_SEC: case ERR_FSAL_DQUOT: case ERR_FSAL_NO_QUOTA: case ERR_FSAL_NOTSUPP: case ERR_FSAL_ATTRNOTSUPP: case ERR_FSAL_UNION_NOTSUPP: case ERR_FSAL_NAMETOOLONG: case ERR_FSAL_BADCOOKIE: case ERR_FSAL_FBIG: case ERR_FSAL_FILE_OPEN: case ERR_FSAL_XDEV: case ERR_FSAL_MLINK: case ERR_FSAL_TOOSMALL: case ERR_FSAL_SHARE_DENIED: case ERR_FSAL_LOCKED: case ERR_FSAL_FAULT: case ERR_FSAL_SERVERFAULT: case ERR_FSAL_DEADLOCK: case ERR_FSAL_BADNAME: case ERR_FSAL_CROSS_JUNCTION: case ERR_FSAL_IN_GRACE: case ERR_FSAL_BADHANDLE: case ERR_FSAL_NO_DATA: case ERR_FSAL_BLOCKED: case ERR_FSAL_INTERRUPT: case ERR_FSAL_NOT_INIT: case ERR_FSAL_ALREADY_INIT: case ERR_FSAL_BAD_INIT: case ERR_FSAL_TIMEOUT: case ERR_FSAL_NO_ACE: case ERR_FSAL_BAD_RANGE: case ERR_FSAL_NOXATTR: case ERR_FSAL_XATTR2BIG: /* Non retryable error, return error to client */ return false; } /* Should never reach this */ LogCrit(COMPONENT_NFSPROTO, "fsal_errors=%u not managed properly in %s", fsal_errors, __func__); return false; } /** * @brief Returns the maximum attribute index possbile for a 4.x protocol. * * This function returns the maximum attribute index possbile for a * 4.x protocol. * returns -1 if the protocol is not recognized. * * @param minorversion[IN] input minorversion of the protocol. * * @return the max index possbile for the minorversion, -1 if minorversion is * not recognized. * */ static inline int nfs4_max_attr_index(compound_data_t *data) { if (data) { enum nfs4_minor_vers minorversion = data->minorversion; switch (minorversion) { case NFS4_MINOR_VERS_0: return FATTR4_MOUNTED_ON_FILEID; case NFS4_MINOR_VERS_1: return FATTR4_FS_CHARSET_CAP; case NFS4_MINOR_VERS_2: return FATTR4_MAX_ATTR_INDEX; } } else { return FATTR4_MAX_ATTR_INDEX; } /* Should never be here */ LogFatal(COMPONENT_NFS_V4, "Unexpected minor version for NFSv4"); return -1; } /** * @brief Check if a specific attribute is supported by the FSAL or if * the attribute isn't indicated in attrmask, is it at least * supported by Ganesha. * * @param[in] attr The NFSv4 attribute index of interest * @param[in] fsal_supported The FSAL attrmask_t indicating which are supported */ static inline bool atrib_supported(int attr, attrmask_t fsal_supported) { return fattr4tab[attr].supported && (fattr4tab[attr].attrmask == 0 || (fsal_supported & fattr4tab[attr].attrmask) != 0); } /* NFSv4.0+ Attribute management * XDR encode/decode/compare functions for FSAL <-> Fattr4 translations * There is a set of functions for each and every attribute in the tables * on page 39-46 of RFC3530. The translate between internal and the wire. * Ordered by attribute number */ /* * FATTR4_SUPPORTED_ATTRS */ /** supported attributes * this drives off of the table but it really should drive off of the * fs_supports in the export */ static fattr_xdr_result encode_supported_attrs(XDR *xdr, struct xdr_attrs_args *args) { struct bitmap4 bits; int attr, offset; int max_attr_idx; memset(&bits, 0, sizeof(bits)); max_attr_idx = nfs4_max_attr_index(args->data); for (attr = FATTR4_SUPPORTED_ATTRS; attr <= max_attr_idx; attr++) { LogAttrlist(COMPONENT_NFS_V4, NIV_FULL_DEBUG, "attrs ", args->attrs, false); if (atrib_supported(attr, args->attrs->supported)) { bool __attribute__((unused)) res = set_attribute_in_bitmap(&bits, attr); assert(res); } } if (!inline_xdr_u_int32_t(xdr, &bits.bitmap4_len) || bits.bitmap4_len > BITMAP4_MAPLEN) return FATTR_XDR_FAILED; for (offset = 0; offset < bits.bitmap4_len; offset++) { if (!inline_xdr_u_int32_t(xdr, &bits.map[offset])) return FATTR_XDR_FAILED; } return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_supported_attrs(XDR *xdr, struct xdr_attrs_args *args) { struct bitmap4 bits; int attr, offset; int max_attr_idx; max_attr_idx = nfs4_max_attr_index(args->data); if (!inline_xdr_u_int32_t(xdr, &bits.bitmap4_len)) return FATTR_XDR_FAILED; if (bits.bitmap4_len > BITMAP4_MAPLEN) { LogWarn(COMPONENT_NFS_V4, "Decoded a too long bitmap : %d is more than %d", bits.bitmap4_len, BITMAP4_MAPLEN); return FATTR_XDR_FAILED; } for (offset = 0; offset < bits.bitmap4_len; offset++) { if (!inline_xdr_u_int32_t(xdr, &bits.map[offset])) return FATTR_XDR_FAILED; } FSAL_CLEAR_MASK(args->attrs->supported); for (attr = FATTR4_SUPPORTED_ATTRS; attr < bits.bitmap4_len * 32 && attr <= max_attr_idx; attr++) { if (attribute_is_set(&bits, attr) && fattr4tab[attr].attrmask) FSAL_SET_MASK(args->attrs->supported, fattr4tab[attr].attrmask); } return FATTR_XDR_SUCCESS; } /* * FATTR4_TYPE */ static fattr_xdr_result encode_type(XDR *xdr, struct xdr_attrs_args *args) { uint32_t file_type; switch (args->attrs->type) { case REGULAR_FILE: case EXTENDED_ATTR: file_type = NF4REG; /* Regular file */ break; case DIRECTORY: file_type = NF4DIR; /* Directory */ break; case BLOCK_FILE: file_type = NF4BLK; /* Special File - block device */ break; case CHARACTER_FILE: file_type = NF4CHR; /* Special File - character device */ break; case SYMBOLIC_LINK: file_type = NF4LNK; /* Symbolic Link */ break; case SOCKET_FILE: file_type = NF4SOCK; /* Special File - socket */ break; case FIFO_FILE: file_type = NF4FIFO; /* Special File - fifo */ break; default: /* includes NO_FILE_TYPE & FS_JUNCTION: */ return FATTR_XDR_FAILED; /* silently skip bogus? */ } /* switch( pattr->type ) */ if (!xdr_u_int32_t(xdr, &file_type)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_type(XDR *xdr, struct xdr_attrs_args *args) { uint32_t t = 0; if (!xdr_u_int32_t(xdr, &t)) return FATTR_XDR_FAILED; switch (t) { case NF4REG: args->attrs->type = REGULAR_FILE; break; case NF4DIR: args->attrs->type = DIRECTORY; break; case NF4BLK: args->attrs->type = BLOCK_FILE; break; case NF4CHR: args->attrs->type = CHARACTER_FILE; break; case NF4LNK: args->attrs->type = SYMBOLIC_LINK; break; case NF4SOCK: args->attrs->type = SOCKET_FILE; break; case NF4FIFO: args->attrs->type = FIFO_FILE; break; default: /* For wanting of a better solution */ return FATTR_XDR_FAILED; } /*update both args->attrs->type and args->type*/ args->type = args->attrs->type; return FATTR_XDR_SUCCESS; } /* * FATTR4_FH_EXPIRE_TYPE */ static fattr_xdr_result encode_expiretype(XDR *xdr, struct xdr_attrs_args *args) { uint32_t expire_type; /* For the moment, we handle only the persistent filehandle */ expire_type = FH4_PERSISTENT; if (!xdr_u_int32_t(xdr, &expire_type)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_expiretype(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_CHANGE */ static fattr_xdr_result encode_change(XDR *xdr, struct xdr_attrs_args *args) { if (!xdr_u_int64_t(xdr, &args->attrs->change)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_change(XDR *xdr, struct xdr_attrs_args *args) { if (!xdr_u_int64_t(xdr, &args->attrs->change)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } /* * FATTR4_SIZE */ static fattr_xdr_result encode_filesize(XDR *xdr, struct xdr_attrs_args *args) { if (!xdr_u_int64_t(xdr, &args->attrs->filesize)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_filesize(XDR *xdr, struct xdr_attrs_args *args) { if (!xdr_u_int64_t(xdr, &args->attrs->filesize)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } /* * FATTR4_LINK_SUPPORT */ static fattr_xdr_result encode_linksupport(XDR *xdr, struct xdr_attrs_args *args) { struct fsal_export *export; bool_t linksupport = FALSE; if (args->data != NULL) { export = op_ctx->fsal_export; linksupport = export->exp_ops.fs_supports(export, fso_link_support); } if (!xdr_bool(xdr, &linksupport)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_linksupport(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_SYMLINK_SUPPORT */ static fattr_xdr_result encode_symlinksupport(XDR *xdr, struct xdr_attrs_args *args) { struct fsal_export *export; bool_t symlinksupport = FALSE; if (args->data != NULL) { export = op_ctx->fsal_export; symlinksupport = export->exp_ops.fs_supports( export, fso_symlink_support); } if (!xdr_bool(xdr, &symlinksupport)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_symlinksupport(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_NAMED_ATTR */ /* For this version of the binary, named attributes is not supported */ static fattr_xdr_result encode_namedattrsupport(XDR *xdr, struct xdr_attrs_args *args) { struct fsal_export *export; bool_t namedattrsupport = FALSE; if (args->data != NULL) { export = op_ctx->fsal_export; namedattrsupport = export->exp_ops.fs_supports(export, fso_named_attr); } if (!xdr_bool(xdr, &namedattrsupport)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_namedattrsupport(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_FSID */ static fattr_xdr_result xdr_encode_fsid(XDR *xdr, struct xdr_attrs_args *args) { fsid4 fsid = { 0, 0 }; if (args->data != NULL && op_ctx_export_has_option_set(EXPORT_OPTION_FSID_SET)) { fsid.major = op_ctx->ctx_export->filesystem_id.major; fsid.minor = op_ctx->ctx_export->filesystem_id.minor; } else { fsid.major = args->fsid.major; fsid.minor = args->fsid.minor; } LogDebug(COMPONENT_NFS_V4, "fsid.major = %" PRIu64 ", fsid.minor = %" PRIu64, fsid.major, fsid.minor); if (!xdr_u_int64_t(xdr, &fsid.major)) return FATTR_XDR_FAILED; if (!xdr_u_int64_t(xdr, &fsid.minor)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result xdr_decode_fsid(XDR *xdr, struct xdr_attrs_args *args) { if (!xdr_u_int64_t(xdr, &args->fsid.major)) return FATTR_XDR_FAILED; if (!xdr_u_int64_t(xdr, &args->fsid.minor)) return FATTR_XDR_FAILED; /*update both : args->fsid and args->attrs->fsid*/ if (args->attrs != NULL) args->attrs->fsid = args->fsid; return FATTR_XDR_SUCCESS; } /* * FATTR4_UNIQUE_HANDLES */ static fattr_xdr_result encode_uniquehandles(XDR *xdr, struct xdr_attrs_args *args) { struct fsal_export *export; bool_t uniquehandles = FALSE; if (args->data != NULL) { export = op_ctx->fsal_export; uniquehandles = export->exp_ops.fs_supports(export, fso_unique_handles); } if (!inline_xdr_bool(xdr, &uniquehandles)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_uniquehandles(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_LEASE_TIME */ static fattr_xdr_result encode_leaselife(XDR *xdr, struct xdr_attrs_args *args) { if (!inline_xdr_u_int32_t(xdr, &nfs_param.nfsv4_param.lease_lifetime)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_leaselife(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_RDATTR_ERROR */ /** todo we don't really do anything with rdattr_error. It is needed for full * readdir error handling. Check this to be correct when we do... */ static fattr_xdr_result encode_rdattr_error(XDR *xdr, struct xdr_attrs_args *args) { if (!inline_xdr_u_int32_t(xdr, &args->rdattr_error)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_rdattr_error(XDR *xdr, struct xdr_attrs_args *args) { if (!inline_xdr_u_int32_t(xdr, &args->rdattr_error)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } /* * FATTR4_ACL */ static fattr_xdr_result encode_acl(XDR *xdr, struct xdr_attrs_args *args) { if (!args->attrs->acl) { /* In order to follow RFC8881 and knfsd behavior, create ACL * from the mode bits. * Allocated objects will be freed by the normal flow which call * fsal_release_attrs. */ fsal_status_t status = fsal_mode_to_acl(args->attrs, NULL); if (FSAL_IS_ERROR(status)) { LogWarn(COMPONENT_NFS_V4, "Failed in creating ACL from mode bits. Status: %u", status.major); return FATTR_XDR_FAILED; } } assert(args->attrs->acl); fsal_ace_t *ace; int i; char *name = NULL; LogFullDebug(COMPONENT_NFS_V4, "Number of ACEs = %u", args->attrs->acl->naces); if (!inline_xdr_u_int32_t(xdr, &(args->attrs->acl->naces))) return FATTR_XDR_FAILED; for (ace = args->attrs->acl->aces; ace < args->attrs->acl->aces + args->attrs->acl->naces; ace++) { LogFullDebug(COMPONENT_NFS_V4, "type=0X%x, flag=0X%x, perm=0X%x", ace->type, ace->flag, ace->perm); if (!inline_xdr_u_int32_t(xdr, &ace->type)) return FATTR_XDR_FAILED; if (!inline_xdr_u_int32_t(xdr, &ace->flag)) return FATTR_XDR_FAILED; if (!inline_xdr_u_int32_t(xdr, &ace->perm)) return FATTR_XDR_FAILED; if (IS_FSAL_ACE_SPECIAL_ID(*ace)) { for (i = 0; i < FSAL_ACE_SPECIAL_EVERYONE; i++) { if (whostr_2_type_map[i].type == ace->who.uid) { name = whostr_2_type_map[i].string; break; } } if (name == NULL || !xdr_string(xdr, &name, MAXNAMLEN)) return FATTR_XDR_FAILED; } else if (IS_FSAL_ACE_GROUP_ID(*ace)) { /* Encode group name. */ if (!xdr_encode_nfs4_group(xdr, ace->who.gid)) return FATTR_XDR_FAILED; } else { if (!xdr_encode_nfs4_owner(xdr, ace->who.uid)) { return FATTR_XDR_FAILED; } } } /* for ace... */ return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_acl(XDR *xdr, struct xdr_attrs_args *args) { fsal_acl_status_t status; fsal_acl_data_t acldata; fsal_ace_t *ace; char buffer[MAXNAMLEN + 1]; utf8string utf8buffer; fattr_xdr_result res = FATTR_XDR_FAILED; int who = 0; /* not ACE_SPECIAL anything */ acldata.naces = 0; if (!inline_xdr_u_int32_t(xdr, &acldata.naces) || acldata.naces > 4096) return FATTR_XDR_FAILED; if (acldata.naces == 0) return FATTR_XDR_SUCCESS; /* no acls is not a crime */ acldata.aces = (fsal_ace_t *)nfs4_ace_alloc(acldata.naces); if (acldata.aces == NULL) { LogCrit(COMPONENT_NFS_V4, "Failed to allocate ACEs"); args->nfs_status = NFS4ERR_SERVERFAULT; return FATTR_XDR_FAILED; } for (ace = acldata.aces; ace < acldata.aces + acldata.naces; ace++) { int i; who = 0; if (!inline_xdr_u_int32_t(xdr, &ace->type)) goto baderr; if (ace->type >= FSAL_ACE_TYPE_MAX) { LogFullDebug(COMPONENT_NFS_V4, "Bad ACE type 0x%x", ace->type); res = FATTR_XDR_NOOP; goto baderr; } if (!inline_xdr_u_int32_t(xdr, &ace->flag)) goto baderr; if (!inline_xdr_u_int32_t(xdr, &ace->perm)) goto baderr; utf8buffer.utf8string_val = buffer; utf8buffer.utf8string_len = 0; if (!inline_xdr_utf8string(xdr, &utf8buffer, MAXNAMLEN)) goto baderr; for (i = 0; i < FSAL_ACE_SPECIAL_EVERYONE; i++) { if (strcmp(buffer, whostr_2_type_map[i].string) == 0) { who = whostr_2_type_map[i].type; break; } } if (who != 0) { /* Clear group flag for special users */ ace->flag &= ~(FSAL_ACE_FLAG_GROUP_ID); ace->iflag |= FSAL_ACE_IFLAG_SPECIAL_ID; ace->who.uid = who; LogFullDebug(COMPONENT_NFS_V4, "ACE special who.uid = 0x%x", ace->who.uid); } else { if (IS_FSAL_ACE_GROUP_ID(*ace)) { /* Decode group. */ struct gsh_buffdesc gname = { .addr = utf8buffer.utf8string_val, .len = utf8buffer.utf8string_len }; if (!name2gid(&gname, &ace->who.gid, get_anonymous_gid())) goto baderr; LogFullDebug(COMPONENT_NFS_V4, "ACE who.gid = 0x%x", ace->who.gid); } else { /* Decode user. */ struct gsh_buffdesc uname = { .addr = utf8buffer.utf8string_val, .len = utf8buffer.utf8string_len }; if (!name2uid(&uname, &ace->who.uid, get_anonymous_uid())) goto baderr; LogFullDebug(COMPONENT_NFS_V4, "ACE who.uid = 0x%x", ace->who.uid); } } /* Check if we can map a name string to uid or gid. If we * can't, do cleanup and bubble up NFS4ERR_BADOWNER. */ if ((IS_FSAL_ACE_GROUP_ID(*ace) ? ace->who.gid : ace->who.uid) == -1) { LogFullDebug(COMPONENT_NFS_V4, "ACE bad owner"); args->nfs_status = NFS4ERR_BADOWNER; goto baderr; } } args->attrs->acl = nfs4_acl_new_entry(&acldata, &status); if (args->attrs->acl == NULL) { LogCrit(COMPONENT_NFS_V4, "Failed to create a new obj for ACL"); args->nfs_status = NFS4ERR_SERVERFAULT; /* acldata has already been freed */ return FATTR_XDR_FAILED; } else { LogFullDebug( COMPONENT_NFS_V4, "Successfully created a new obj for ACL, status = %u", status); } /* Set new ACL */ LogFullDebug(COMPONENT_NFS_V4, "new acl = %p", args->attrs->acl); return FATTR_XDR_SUCCESS; baderr: nfs4_ace_free(acldata.aces); return res; } /* * FATTR4_ACLSUPPORT */ static fattr_xdr_result encode_aclsupport(XDR *xdr, struct xdr_attrs_args *args) { struct fsal_export *export; uint32_t aclsupport = 0; if (args->data != NULL) { export = op_ctx->fsal_export; aclsupport = export->exp_ops.fs_acl_support(export); } if (!inline_xdr_u_int32_t(xdr, &aclsupport)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_aclsupport(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_ARCHIVE */ static fattr_xdr_result encode_archive(XDR *xdr, struct xdr_attrs_args *args) { uint32_t archive; archive = FALSE; if (!inline_xdr_bool(xdr, &archive)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_archive(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_CANSETTIME */ static fattr_xdr_result encode_cansettime(XDR *xdr, struct xdr_attrs_args *args) { struct fsal_export *export; bool_t cansettime = FALSE; if (args->data != NULL) { export = op_ctx->fsal_export; cansettime = export->exp_ops.fs_supports(export, fso_cansettime); } if (!inline_xdr_bool(xdr, &cansettime)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_cansettime(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_CASE_INSENSITIVE */ static fattr_xdr_result encode_case_insensitive(XDR *xdr, struct xdr_attrs_args *args) { struct fsal_export *export; bool_t caseinsensitive = FALSE; if (args->data != NULL) { export = op_ctx->fsal_export; caseinsensitive = export->exp_ops.fs_supports( export, fso_case_insensitive); } if (!inline_xdr_bool(xdr, &caseinsensitive)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_case_insensitive(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_CASE_PRESERVING */ static fattr_xdr_result encode_case_preserving(XDR *xdr, struct xdr_attrs_args *args) { struct fsal_export *export; bool_t casepreserving = FALSE; if (args->data != NULL) { export = op_ctx->fsal_export; casepreserving = export->exp_ops.fs_supports( export, fso_case_preserving); } if (!inline_xdr_bool(xdr, &casepreserving)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_case_preserving(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_CHOWN_RESTRICTED */ static fattr_xdr_result encode_chown_restricted(XDR *xdr, struct xdr_attrs_args *args) { struct fsal_export *export; bool_t chownrestricted = FALSE; if (args->data != NULL) { export = op_ctx->fsal_export; chownrestricted = export->exp_ops.fs_supports( export, fso_chown_restricted); } if (!inline_xdr_bool(xdr, &chownrestricted)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_chown_restricted(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_FILEHANDLE */ static fattr_xdr_result encode_filehandle(XDR *xdr, struct xdr_attrs_args *args) { if (args->hdl4 == NULL || args->hdl4->nfs_fh4_val == NULL) return FATTR_XDR_FAILED; if (!inline_xdr_bytes(xdr, &args->hdl4->nfs_fh4_val, &args->hdl4->nfs_fh4_len, NFS4_FHSIZE)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } /* zero copy file handle reference dropped as potentially unsafe XDR */ static fattr_xdr_result decode_filehandle(XDR *xdr, struct xdr_attrs_args *args) { uint32_t fhlen = 0, pos; if (args->hdl4 == NULL || args->hdl4->nfs_fh4_val == NULL) { if (!inline_xdr_u_int32_t(xdr, &fhlen)) return FATTR_XDR_FAILED; pos = xdr_getpos(xdr); if (!xdr_setpos(xdr, pos + fhlen)) return FATTR_XDR_FAILED; } else { if (!inline_xdr_bytes(xdr, &args->hdl4->nfs_fh4_val, &args->hdl4->nfs_fh4_len, NFS4_FHSIZE)) return FATTR_XDR_FAILED; } return FATTR_XDR_SUCCESS; } /* * FATTR4_FILEID */ static fattr_xdr_result encode_fileid(XDR *xdr, struct xdr_attrs_args *args) { if (!inline_xdr_u_int64_t(xdr, &args->fileid)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_fileid(XDR *xdr, struct xdr_attrs_args *args) { if (!inline_xdr_u_int64_t(xdr, &args->fileid)) return FATTR_XDR_FAILED; /*update both : args->fileid and args->attrs->fileid*/ if (args->attrs != NULL) args->attrs->fileid = args->fileid; return FATTR_XDR_SUCCESS; } /* * Dynamic file system info */ static fattr_xdr_result encode_fetch_fsinfo(struct xdr_attrs_args *args) { fsal_status_t fsal_status = { 0, 0 }; if (args->data != NULL && args->data->current_obj != NULL) { fsal_status = fsal_statfs(args->data->current_obj, &args->dynamicinfo); } else { /* We don't expect this to actually get used, but fill in * sensible values just as a precaution. */ args->dynamicinfo.total_bytes = 1024000; args->dynamicinfo.avail_bytes = 512000; args->dynamicinfo.free_bytes = 512000; args->dynamicinfo.total_files = 512; args->dynamicinfo.free_files = 512; args->dynamicinfo.avail_files = 512; args->dynamicinfo.maxread = 65536; args->dynamicinfo.maxwrite = 65536; args->dynamicinfo.time_delta.tv_sec = 0; args->dynamicinfo.time_delta.tv_nsec = FSAL_DEFAULT_TIME_DELTA_NSEC; } if (FSAL_IS_ERROR(fsal_status)) return FATTR_XDR_FAILED; args->statfscalled = true; return TRUE; } /* * FATTR4_FILES_AVAIL */ static fattr_xdr_result encode_files_avail(XDR *xdr, struct xdr_attrs_args *args) { if (!args->statfscalled) if (!encode_fetch_fsinfo(args)) return FATTR_XDR_FAILED; if (!inline_xdr_u_int64_t(xdr, &args->dynamicinfo.avail_files)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_files_avail(XDR *xdr, struct xdr_attrs_args *args) { return inline_xdr_u_int64_t(xdr, &args->dynamicinfo.avail_files) ? FATTR_XDR_SUCCESS : FATTR_XDR_FAILED; } /* * FATTR4_FILES_FREE */ static fattr_xdr_result encode_files_free(XDR *xdr, struct xdr_attrs_args *args) { if (!args->statfscalled) if (!encode_fetch_fsinfo(args)) return FATTR_XDR_FAILED; if (!inline_xdr_u_int64_t(xdr, &args->dynamicinfo.free_files)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_files_free(XDR *xdr, struct xdr_attrs_args *args) { return inline_xdr_u_int64_t(xdr, &args->dynamicinfo.free_files) ? FATTR_XDR_SUCCESS : FATTR_XDR_FAILED; } /* * FATTR4_FILES_TOTAL */ static fattr_xdr_result encode_files_total(XDR *xdr, struct xdr_attrs_args *args) { if (!args->statfscalled) if (!encode_fetch_fsinfo(args)) return FATTR_XDR_FAILED; if (!inline_xdr_u_int64_t(xdr, &args->dynamicinfo.total_files)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_files_total(XDR *xdr, struct xdr_attrs_args *args) { return xdr_u_int64_t(xdr, &args->dynamicinfo.total_files) ? FATTR_XDR_SUCCESS : FATTR_XDR_FAILED; } /* * allocate a dynamic pathname4 structure out of a filesystem path */ void nfs4_pathname4_alloc(pathname4 *pathname4, char *path) { char *path_sav, *token, *path_work; int i = 0; if (path == NULL) { pathname4->pathname4_val = gsh_malloc(sizeof(component4)); pathname4->pathname4_len = 1; pathname4->pathname4_val->utf8string_val = gsh_calloc(MAXPATHLEN, sizeof(char)); pathname4->pathname4_val->utf8string_len = MAXPATHLEN; } else { path_sav = gsh_strdup(path); /* count tokens */ path_work = path_sav; while ((token = strsep(&path_work, "/")) != NULL) { if (strlen(token) > 0) { i++; } } LogDebug(COMPONENT_NFS_V4, "%s has %d tokens", path, i); /* reset content of path_sav */ strcpy(path_sav, path); path_work = path_sav; /* fill component4 */ pathname4->pathname4_val = gsh_malloc(i * sizeof(component4)); i = 0; while ((token = strsep(&path_work, "/")) != NULL) { if (strlen(token) > 0) { LogDebug(COMPONENT_NFS_V4, "token %d is %s", i, token); utf8string_dup(&pathname4->pathname4_val[i], token, strlen(token)); i++; } } pathname4->pathname4_len = i; gsh_free(path_sav); } } /* * free dynamic pathname4 structure */ void nfs4_pathname4_free(pathname4 *pathname4) { int i; if (pathname4 == NULL) return; i = pathname4->pathname4_len; LogFullDebug(COMPONENT_NFS_V4, "number of pathname components to free: %d", i); if (pathname4->pathname4_val == NULL) return; while (i-- > 0) { if (pathname4->pathname4_val[i].utf8string_val != NULL) { LogFullDebug( COMPONENT_NFS_V4, "freeing component %d: %s", i + 1, pathname4->pathname4_val[i].utf8string_val); gsh_free(pathname4->pathname4_val[i].utf8string_val); pathname4->pathname4_val[i].utf8string_val = NULL; } } gsh_free(pathname4->pathname4_val); pathname4->pathname4_val = NULL; } /* * FATTR4_FS_LOCATIONS */ static fattr_xdr_result encode_fs_locations(XDR *xdr, struct xdr_attrs_args *args) { fs_locations4 fs_locs = {}; fs_location4 fs_loc = {}; if (args->data == NULL || args->data->current_obj == NULL) return FATTR_XDR_NOOP; /* For now allow for one fs locations, fs_locations() should set: root and update its length, can not be bigger than MAXPATHLEN path and update its length, can not be bigger than MAXPATHLEN server and update its length */ if (args->attrs->fs_locations != NULL) { fsal_fs_locations_t *fs_locations = args->attrs->fs_locations; fs_loc.server.server_len = fs_locations->nservers; fs_loc.server.server_val = fs_locations->server; fs_locs.locations.locations_len = 1; fs_locs.locations.locations_val = &fs_loc; nfs4_pathname4_alloc(&fs_loc.rootpath, fs_locations->rootpath); nfs4_pathname4_alloc(&fs_locs.fs_root, fs_locations->fs_root); LogDebug(COMPONENT_FSAL, "fs_location server %.*s", fs_locations->server[0].utf8string_len, fs_locations->server[0].utf8string_val); LogDebug(COMPONENT_FSAL, "fs_location rootpath %s", fs_locations->rootpath); } else { LogDebug(COMPONENT_FSAL, "NULL fs_locations"); /* * If we don't have an fs_locations structure, just assume that * the fs_root corresponds to the root of the export. */ nfs4_pathname4_alloc(&fs_locs.fs_root, CTX_PSEUDOPATH(op_ctx)); } if (!xdr_fs_locations4(xdr, &fs_locs)) { LogEvent(COMPONENT_NFS_V4, "encode fs_locations xdr_fs_locations failed"); return FATTR_XDR_FAILED; } nfs4_pathname4_free(&fs_locs.fs_root); nfs4_pathname4_free(&fs_loc.rootpath); return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_fs_locations(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_HIDDEN */ static fattr_xdr_result encode_hidden(XDR *xdr, struct xdr_attrs_args *args) { bool_t hidden = FALSE; if (!inline_xdr_bool(xdr, &hidden)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_hidden(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_HOMOGENEOUS */ /* Unix semantic is homogeneous (all objects have the same kind of attributes) */ static fattr_xdr_result encode_homogeneous(XDR *xdr, struct xdr_attrs_args *args) { struct fsal_export *export; bool_t homogeneous = FALSE; if (args->data != NULL) { export = op_ctx->fsal_export; homogeneous = export->exp_ops.fs_supports(export, fso_homogenous); } if (!inline_xdr_bool(xdr, &homogeneous)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_homogeneous(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_MAXFILESIZE */ static fattr_xdr_result encode_maxfilesize(XDR *xdr, struct xdr_attrs_args *args) { struct fsal_export *export; uint64_t maxfilesize = 0; if (args->data != NULL) { export = op_ctx->fsal_export; maxfilesize = export->exp_ops.fs_maxfilesize(export); } if (!inline_xdr_u_int64_t(xdr, &maxfilesize)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_maxfilesize(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_MAXLINK */ static fattr_xdr_result encode_maxlink(XDR *xdr, struct xdr_attrs_args *args) { struct fsal_export *export; uint32_t maxlink = 0; if (args->data != NULL) { export = op_ctx->fsal_export; maxlink = export->exp_ops.fs_maxlink(export); } if (!inline_xdr_u_int32_t(xdr, &maxlink)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_maxlink(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_MAXNAME */ static fattr_xdr_result encode_maxname(XDR *xdr, struct xdr_attrs_args *args) { struct fsal_export *export; uint32_t maxname = 0; if (args->data != NULL) { export = op_ctx->fsal_export; maxname = export->exp_ops.fs_maxnamelen(export); } if (!inline_xdr_u_int32_t(xdr, &maxname)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_maxname(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_MAXREAD */ /* The exports.c MAXREAD-MAXWRITE code establishes these semantics: * a. If you set the MaxWrite and MaxRead defaults in an export file * they apply. * b. If you set the MaxWrite and MaxRead defaults in the main.conf * file they apply unless overwritten by an export file setting. * c. If no settings are present in the export file or the main.conf * file then the defaults values in the FSAL apply. */ /** todo make these conditionals go away. The old code was the 'else' part of * this. this is a fast path. Do both read and write conditionals. */ static fattr_xdr_result encode_maxread(XDR *xdr, struct xdr_attrs_args *args) { uint64_t MaxRead = atomic_fetch_uint64_t(&op_ctx->ctx_export->MaxRead); if (!inline_xdr_u_int64_t(xdr, &MaxRead)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_maxread(XDR *xdr, struct xdr_attrs_args *args) { return xdr_u_int64_t(xdr, &args->dynamicinfo.maxread) ? FATTR_XDR_SUCCESS : FATTR_XDR_FAILED; } /* * FATTR4_MAXWRITE */ static fattr_xdr_result encode_maxwrite(XDR *xdr, struct xdr_attrs_args *args) { uint64_t MaxWrite = atomic_fetch_uint64_t(&op_ctx->ctx_export->MaxWrite); if (!inline_xdr_u_int64_t(xdr, &MaxWrite)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_maxwrite(XDR *xdr, struct xdr_attrs_args *args) { return xdr_u_int64_t(xdr, &args->dynamicinfo.maxwrite) ? FATTR_XDR_SUCCESS : FATTR_XDR_FAILED; } /* * FATTR4_MIMETYPE */ static fattr_xdr_result encode_mimetype(XDR *xdr, struct xdr_attrs_args *args) { bool_t mimetype = FALSE; if (!inline_xdr_bool(xdr, &mimetype)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_mimetype(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_MODE */ static fattr_xdr_result encode_mode(XDR *xdr, struct xdr_attrs_args *args) { uint32_t file_mode = fsal2unix_mode(args->attrs->mode); if (!inline_xdr_u_int32_t(xdr, &file_mode)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_mode(XDR *xdr, struct xdr_attrs_args *args) { uint32_t file_mode = 0; if (!inline_xdr_u_int32_t(xdr, &file_mode)) return FATTR_XDR_FAILED; args->attrs->mode = unix2fsal_mode(file_mode); return FATTR_XDR_SUCCESS; } /* * FATTR4_NO_TRUNC */ static fattr_xdr_result encode_no_trunc(XDR *xdr, struct xdr_attrs_args *args) { struct fsal_export *export; bool_t no_trunc = FALSE; if (args->data != NULL) { export = op_ctx->fsal_export; no_trunc = export->exp_ops.fs_supports(export, fso_no_trunc); } if (!inline_xdr_bool(xdr, &no_trunc)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_no_trunc(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_NUMLINKS */ static fattr_xdr_result encode_numlinks(XDR *xdr, struct xdr_attrs_args *args) { if (!inline_xdr_u_int32_t(xdr, &args->attrs->numlinks)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_numlinks(XDR *xdr, struct xdr_attrs_args *args) { if (!inline_xdr_u_int32_t(xdr, &args->attrs->numlinks)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } /* * FATTR4_OWNER */ static fattr_xdr_result encode_owner(XDR *xdr, struct xdr_attrs_args *args) { return xdr_encode_nfs4_owner(xdr, args->attrs->owner) ? FATTR_XDR_SUCCESS : FATTR_XDR_FAILED; } static fattr_xdr_result decode_owner(XDR *xdr, struct xdr_attrs_args *args) { uid_t uid; uint32_t len = 0; struct gsh_buffdesc ownerdesc; unsigned int pos, newpos; if (!inline_xdr_u_int(xdr, &len)) return FATTR_XDR_FAILED; if (len == 0 || len > 1024) { args->nfs_status = NFS4ERR_INVAL; return FATTR_XDR_FAILED; } pos = xdr_getpos(xdr); newpos = pos + len; if (len % 4 != 0) newpos += (4 - (len % 4)); ownerdesc.len = len; ownerdesc.addr = xdr_inline_decode(xdr, len); if (!ownerdesc.addr) { LogMajor(COMPONENT_NFS_V4, "xdr_inline_decode on xdrmem stream failed!"); return FATTR_XDR_FAILED; } if (!name2uid(&ownerdesc, &uid, get_anonymous_uid())) { args->nfs_status = NFS4ERR_BADOWNER; return FATTR_BADOWNER; } xdr_setpos(xdr, newpos); args->attrs->owner = uid; return FATTR_XDR_SUCCESS; } /* * FATTR4_OWNER_GROUP */ static fattr_xdr_result encode_group(XDR *xdr, struct xdr_attrs_args *args) { return xdr_encode_nfs4_group(xdr, args->attrs->group) ? FATTR_XDR_SUCCESS : FATTR_XDR_FAILED; } static fattr_xdr_result decode_group(XDR *xdr, struct xdr_attrs_args *args) { gid_t gid; uint32_t len = 0; struct gsh_buffdesc groupdesc; unsigned int pos, newpos; if (!inline_xdr_u_int(xdr, &len)) return FATTR_XDR_FAILED; if (len == 0 || len > 1024) { args->nfs_status = NFS4ERR_INVAL; return FATTR_XDR_FAILED; } pos = xdr_getpos(xdr); newpos = pos + len; if (len % 4 != 0) newpos += (4 - (len % 4)); groupdesc.len = len; groupdesc.addr = xdr_inline_decode(xdr, len); if (!groupdesc.addr) { LogMajor(COMPONENT_NFS_V4, "xdr_inline_decode on xdrmem stream failed!"); return FATTR_XDR_FAILED; } if (!name2gid(&groupdesc, &gid, get_anonymous_gid())) { args->nfs_status = NFS4ERR_BADOWNER; return FATTR_BADOWNER; } xdr_setpos(xdr, newpos); args->attrs->group = gid; return FATTR_XDR_SUCCESS; } /* * FATTR4_QUOTA_AVAIL_HARD */ static fattr_xdr_result encode_quota_avail_hard(XDR *xdr, struct xdr_attrs_args *args) { /** @todo: not the right answer, actual quotas should be implemented */ uint64_t quota = NFS_V4_MAX_QUOTA_HARD; if (!inline_xdr_u_int64_t(xdr, "a)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_quota_avail_hard(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_QUOTA_AVAIL_SOFT */ static fattr_xdr_result encode_quota_avail_soft(XDR *xdr, struct xdr_attrs_args *args) { uint64_t quota = NFS_V4_MAX_QUOTA_SOFT; if (!inline_xdr_u_int64_t(xdr, "a)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_quota_avail_soft(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_QUOTA_USED */ static fattr_xdr_result encode_quota_used(XDR *xdr, struct xdr_attrs_args *args) { uint64_t quota = args->attrs->filesize; if (!inline_xdr_u_int64_t(xdr, "a)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_quota_used(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_RAWDEV */ static fattr_xdr_result encode_rawdev(XDR *xdr, struct xdr_attrs_args *args) { struct specdata4 specdata4; specdata4.specdata1 = args->attrs->rawdev.major; specdata4.specdata2 = args->attrs->rawdev.minor; if (!inline_xdr_u_int32_t(xdr, &specdata4.specdata1)) return FATTR_XDR_FAILED; if (!inline_xdr_u_int32_t(xdr, &specdata4.specdata2)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_rawdev(XDR *xdr, struct xdr_attrs_args *args) { struct specdata4 specdata4 = { .specdata1 = 0, .specdata2 = 0 }; if (!inline_xdr_u_int32_t(xdr, &specdata4.specdata1)) return FATTR_XDR_FAILED; if (!inline_xdr_u_int32_t(xdr, &specdata4.specdata2)) return FATTR_XDR_FAILED; args->attrs->rawdev.major = specdata4.specdata1; args->attrs->rawdev.minor = specdata4.specdata2; return FATTR_XDR_SUCCESS; } /* * FATTR4_SPACE_AVAIL */ static fattr_xdr_result encode_space_avail(XDR *xdr, struct xdr_attrs_args *args) { if (!args->statfscalled) if (!encode_fetch_fsinfo(args)) return FATTR_XDR_FAILED; if (!inline_xdr_u_int64_t(xdr, &args->dynamicinfo.avail_bytes)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_space_avail(XDR *xdr, struct xdr_attrs_args *args) { return inline_xdr_u_int64_t(xdr, &args->dynamicinfo.avail_bytes) ? FATTR_XDR_SUCCESS : FATTR_XDR_FAILED; } /* * FATTR4_SPACE_FREE */ static fattr_xdr_result encode_space_free(XDR *xdr, struct xdr_attrs_args *args) { if (!args->statfscalled) if (!encode_fetch_fsinfo(args)) return FATTR_XDR_FAILED; if (!inline_xdr_u_int64_t(xdr, &args->dynamicinfo.free_bytes)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_space_free(XDR *xdr, struct xdr_attrs_args *args) { return inline_xdr_u_int64_t(xdr, &args->dynamicinfo.free_bytes) ? FATTR_XDR_SUCCESS : FATTR_XDR_FAILED; } /* * FATTR4_SPACE_TOTAL */ static fattr_xdr_result encode_space_total(XDR *xdr, struct xdr_attrs_args *args) { if (!args->statfscalled) if (!encode_fetch_fsinfo(args)) return FATTR_XDR_FAILED; if (!inline_xdr_u_int64_t(xdr, &args->dynamicinfo.total_bytes)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_space_total(XDR *xdr, struct xdr_attrs_args *args) { return inline_xdr_u_int64_t(xdr, &args->dynamicinfo.total_bytes) ? FATTR_XDR_SUCCESS : FATTR_XDR_FAILED; } /* * FATTR4_SPACE_USED */ /* the number of bytes on the filesystem used by the object * which is slightly different * from the file's size (there can be hole in the file) */ static fattr_xdr_result encode_spaceused(XDR *xdr, struct xdr_attrs_args *args) { uint64_t sace = args->attrs->spaceused; if (!inline_xdr_u_int64_t(xdr, &sace)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_spaceused(XDR *xdr, struct xdr_attrs_args *args) { uint64_t sace = 0; if (!inline_xdr_u_int64_t(xdr, &sace)) return FATTR_XDR_FAILED; args->attrs->spaceused = sace; return TRUE; } /* * FATTR4_SYSTEM */ /* This is not a windows system File-System with respect to the regarding API */ static fattr_xdr_result encode_system(XDR *xdr, struct xdr_attrs_args *args) { bool_t system = FALSE; if (!inline_xdr_bool(xdr, &system)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_system(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * Time conversions */ static inline fattr_xdr_result encode_time(XDR *xdr, struct timespec *ts) { uint64_t seconds = ts->tv_sec; uint32_t nseconds = ts->tv_nsec; if (!inline_xdr_u_int64_t(xdr, &seconds)) return FATTR_XDR_FAILED; if (!inline_xdr_u_int32_t(xdr, &nseconds)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static inline fattr_xdr_result decode_time(XDR *xdr, struct xdr_attrs_args *args, struct timespec *ts) { uint64_t seconds = 0; uint32_t nseconds = 0; if (!inline_xdr_u_int64_t(xdr, &seconds)) return FATTR_XDR_FAILED; if (!inline_xdr_u_int32_t(xdr, &nseconds)) return FATTR_XDR_FAILED; ts->tv_sec = seconds; ts->tv_nsec = nseconds; if (nseconds >= 1000000000) { /* overflow */ args->nfs_status = NFS4ERR_INVAL; return FATTR_XDR_FAILED; } return FATTR_XDR_SUCCESS; } static inline fattr_xdr_result encode_timeset_server(XDR *xdr) { uint32_t how = SET_TO_SERVER_TIME4; return inline_xdr_u_int32_t(xdr, &how); } static inline fattr_xdr_result encode_timeset(XDR *xdr, struct timespec *ts) { uint32_t how = SET_TO_CLIENT_TIME4; if (!inline_xdr_u_int32_t(xdr, &how)) return FATTR_XDR_FAILED; return encode_time(xdr, ts); } static inline fattr_xdr_result decode_timeset(XDR *xdr, struct xdr_attrs_args *args, struct timespec *ts) { uint32_t how = 0; if (!inline_xdr_u_int32_t(xdr, &how)) return FATTR_XDR_FAILED; if (how == SET_TO_SERVER_TIME4) return FATTR_XDR_SUCCESS_EXP; else return decode_time(xdr, args, ts); } /* * FATTR4_TIME_ACCESS */ static fattr_xdr_result encode_accesstime(XDR *xdr, struct xdr_attrs_args *args) { return encode_time(xdr, &args->attrs->atime); } static fattr_xdr_result decode_accesstime(XDR *xdr, struct xdr_attrs_args *args) { return decode_time(xdr, args, &args->attrs->atime); } /* * FATTR4_TIME_ACCESS_SET */ static fattr_xdr_result encode_accesstimeset(XDR *xdr, struct xdr_attrs_args *args) { if (FSAL_TEST_MASK(args->attrs->valid_mask, ATTR_ATIME_SERVER)) return encode_timeset_server(xdr); else return encode_timeset(xdr, &args->attrs->atime); } static fattr_xdr_result decode_accesstimeset(XDR *xdr, struct xdr_attrs_args *args) { return decode_timeset(xdr, args, &args->attrs->atime); } /* * FATTR4_TIME_BACKUP */ /* No time backup, return unix's beginning of time */ static fattr_xdr_result encode_backuptime(XDR *xdr, struct xdr_attrs_args *args) { struct timespec ts; ts.tv_sec = 0LL; ts.tv_nsec = 0; return encode_time(xdr, &ts); } static fattr_xdr_result decode_backuptime(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_TIME_CREATE */ /* No time create, return unix's beginning of time */ static fattr_xdr_result encode_createtime(XDR *xdr, struct xdr_attrs_args *args) { struct timespec ts; ts.tv_sec = 0LL; ts.tv_nsec = 0; return encode_time(xdr, &ts); } static fattr_xdr_result decode_createtime(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_SUCCESS; } /* * FATTR4_TIME_DELTA */ /* According to RFC3530, this is "the smallest useful server time granularity". */ static fattr_xdr_result encode_deltatime(XDR *xdr, struct xdr_attrs_args *args) { if (!args->statfscalled) if (!encode_fetch_fsinfo(args)) return FATTR_XDR_FAILED; return encode_time(xdr, &args->dynamicinfo.time_delta); } static fattr_xdr_result decode_deltatime(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_TIME_METADATA: */ static fattr_xdr_result encode_metatime(XDR *xdr, struct xdr_attrs_args *args) { return encode_time(xdr, &args->attrs->ctime); } static fattr_xdr_result decode_metatime(XDR *xdr, struct xdr_attrs_args *args) { return decode_time(xdr, args, &args->attrs->ctime); } /* * FATTR4_TIME_MODIFY */ static fattr_xdr_result encode_modifytime(XDR *xdr, struct xdr_attrs_args *args) { return encode_time(xdr, &args->attrs->mtime); } static fattr_xdr_result decode_modifytime(XDR *xdr, struct xdr_attrs_args *args) { return decode_time(xdr, args, &args->attrs->mtime); } /* * FATTR4_TIME_MODIFY_SET */ static fattr_xdr_result encode_modifytimeset(XDR *xdr, struct xdr_attrs_args *args) { if (FSAL_TEST_MASK(args->attrs->valid_mask, ATTR_MTIME_SERVER)) return encode_timeset_server(xdr); else return encode_timeset(xdr, &args->attrs->mtime); } static fattr_xdr_result decode_modifytimeset(XDR *xdr, struct xdr_attrs_args *args) { return decode_timeset(xdr, args, &args->attrs->mtime); } /* * FATTR4_MOUNTED_ON_FILEID */ static fattr_xdr_result encode_mounted_on_fileid(XDR *xdr, struct xdr_attrs_args *args) { if (!inline_xdr_u_int64_t(xdr, &args->mounted_on_fileid)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_mounted_on_fileid(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_DIR_NOTIF_DELAY */ static fattr_xdr_result encode_dir_notif_delay(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result decode_dir_notif_delay(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_DIRENT_NOTIF_DELAY */ static fattr_xdr_result encode_dirent_notif_delay(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result decode_dirent_notif_delay(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_DACL */ static fattr_xdr_result encode_dacl(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result decode_dacl(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_SACL */ static fattr_xdr_result encode_sacl(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result decode_sacl(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_CHANGE_POLICY */ static fattr_xdr_result encode_change_policy(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result decode_change_policy(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_FS_STATUS */ static fattr_xdr_result encode_fs_status(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result decode_fs_status(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_FS_LAYOUT_TYPES: */ static fattr_xdr_result encode_fs_layout_types(XDR *xdr, struct xdr_attrs_args *args) { struct fsal_export *export; layouttype4 layout_type; int32_t typecount = 0; int32_t index = 0; /* layouts */ const layouttype4 *layouttypes = NULL; if (args->data == NULL) return FATTR_XDR_NOOP; export = op_ctx->fsal_export; export->exp_ops.fs_layouttypes(export, &typecount, &layouttypes); if (!inline_xdr_u_int32_t(xdr, (uint32_t *)&typecount)) return FATTR_XDR_FAILED; for (index = 0; index < typecount; index++) { layout_type = layouttypes[index]; if (!inline_xdr_u_int32_t(xdr, &layout_type)) return FATTR_XDR_FAILED; } return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_fs_layout_types(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_LAYOUT_HINT */ static fattr_xdr_result encode_layout_hint(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result decode_layout_hint(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_LAYOUT_TYPES */ static fattr_xdr_result encode_layout_types(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result decode_layout_types(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_LAYOUT_BLKSIZE */ static fattr_xdr_result encode_layout_blocksize(XDR *xdr, struct xdr_attrs_args *args) { if (args->data == NULL) { return FATTR_XDR_NOOP; } else { struct fsal_export *export = op_ctx->fsal_export; uint32_t blocksize = export->exp_ops.fs_layout_blocksize(export); if (!inline_xdr_u_int32_t(xdr, &blocksize)) return FATTR_XDR_FAILED; } return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_layout_blocksize(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_LAYOUT_ALIGNMENT */ static fattr_xdr_result encode_layout_alignment(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result decode_layout_alignment(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_FS_LOCATIONS_INFO */ static fattr_xdr_result encode_fs_locations_info(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result decode_fs_locations_info(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_MDSTHRESHOLD */ static fattr_xdr_result encode_mdsthreshold(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result decode_mdsthreshold(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_RETENTION_GET */ static fattr_xdr_result encode_retention_get(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result decode_retention_get(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_RETENTION_SET */ static fattr_xdr_result encode_retention_set(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result decode_retention_set(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_RETENTEVT_GET */ static fattr_xdr_result encode_retentevt_get(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result decode_retentevt_get(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_RETENTEVT_SET */ static fattr_xdr_result encode_retentevt_set(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result decode_retentevt_set(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_RETENTION_HOLD */ static fattr_xdr_result encode_retention_hold(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result decode_retention_hold(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_MODE_SET_MASKED */ static fattr_xdr_result encode_mode_set_masked(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result decode_mode_set_masked(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_SUPPATTR_EXCLCREAT */ static fattr_xdr_result encode_support_exclusive_create(XDR *xdr, struct xdr_attrs_args *args) { struct bitmap4 bits; int attr, offset; bool __attribute__((unused)) res; memset(&bits, 0, sizeof(bits)); for (attr = FATTR4_SUPPORTED_ATTRS; attr <= FATTR4_MAX_ATTR_INDEX; attr++) { if (fattr4tab[attr].supported) { res = set_attribute_in_bitmap(&bits, attr); assert(res); } } /* For exclusive create we use atime and mtime, indicate such in * FATTR4_SUPPATTR_EXCLCREAT. Note that the _SET attributes have a * time_how4 field to specify how to set the time while the other * attribute is used to fetch the time. For exclusive create we need * to indicate both sets of attributes are used. */ res = clear_attribute_in_bitmap(&bits, FATTR4_TIME_ACCESS_SET); assert(res); res = clear_attribute_in_bitmap(&bits, FATTR4_TIME_ACCESS); assert(res); res = clear_attribute_in_bitmap(&bits, FATTR4_TIME_MODIFY_SET); assert(res); res = clear_attribute_in_bitmap(&bits, FATTR4_TIME_MODIFY); assert(res); if (!inline_xdr_u_int32_t(xdr, &bits.bitmap4_len)) return FATTR_XDR_FAILED; for (offset = 0; offset < bits.bitmap4_len; offset++) { if (!inline_xdr_u_int32_t(xdr, &bits.map[offset])) return FATTR_XDR_FAILED; } return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_support_exclusive_create(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* * FATTR4_FS_CHARSET_CAP */ static fattr_xdr_result encode_fs_charset_cap(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result decode_fs_charset_cap(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } static fattr_xdr_result encdec_sec_label(XDR *xdr, struct xdr_attrs_args *args) { if (!xdr_sec_label4(xdr, &args->attrs->sec_label)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } /* * FATTR4_XATTR_SUPPORT */ static fattr_xdr_result encode_xattr_support(XDR *xdr, struct xdr_attrs_args *args) { bool_t xattr_support = FALSE; if (args->data != NULL) { struct fsal_export *exp = op_ctx->fsal_export; xattr_support = exp->exp_ops.fs_supports(exp, fso_xattr_support); } if (!xdr_bool(xdr, &xattr_support)) return FATTR_XDR_FAILED; return FATTR_XDR_SUCCESS; } static fattr_xdr_result decode_xattr_support(XDR *xdr, struct xdr_attrs_args *args) { return FATTR_XDR_NOOP; } /* NFS V4.0+ attributes * This array reflects the tables on page 39-46 of RFC3530 * indexed by attribute number */ const struct fattr4_dent fattr4tab[FATTR4_MAX_ATTR_INDEX + 1] = { [FATTR4_SUPPORTED_ATTRS] = { .name = "FATTR4_SUPPORTED_ATTRS", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_supported_attrs), .attrmask = 0, .encode = encode_supported_attrs, .decode = decode_supported_attrs, .access = FATTR4_ATTR_READ }, [FATTR4_TYPE] = { .name = "FATTR4_TYPE", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_type), .attrmask = ATTR_TYPE, .encode = encode_type, .decode = decode_type, .access = FATTR4_ATTR_READ }, [FATTR4_FH_EXPIRE_TYPE] = { .name = "FATTR4_FH_EXPIRE_TYPE", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_fh_expire_type), .attrmask = 0, .encode = encode_expiretype, .decode = decode_expiretype, .access = FATTR4_ATTR_READ }, [FATTR4_CHANGE] = { .name = "FATTR4_CHANGE", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_change), .attrmask = ATTR_CHANGE, .encode = encode_change, .decode = decode_change, .access = FATTR4_ATTR_READ }, [FATTR4_SIZE] = { .name = "FATTR4_SIZE", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_size), .attrmask = ATTR_SIZE, .encode = encode_filesize, .decode = decode_filesize, .access = FATTR4_ATTR_READ_WRITE }, [FATTR4_LINK_SUPPORT] = { .name = "FATTR4_LINK_SUPPORT", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_link_support), .attrmask = 0, .encode = encode_linksupport, .decode = decode_linksupport, .access = FATTR4_ATTR_READ }, [FATTR4_SYMLINK_SUPPORT] = { .name = "FATTR4_SYMLINK_SUPPORT", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_symlink_support), .attrmask = 0, .encode = encode_symlinksupport, .decode = decode_symlinksupport, .access = FATTR4_ATTR_READ }, [FATTR4_NAMED_ATTR] = { .name = "FATTR4_NAMED_ATTR", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_named_attr), .attrmask = 0, .encode = encode_namedattrsupport, .decode = decode_namedattrsupport, .access = FATTR4_ATTR_READ }, [FATTR4_FSID] = { .name = "FATTR4_FSID", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_fsid), .encode = xdr_encode_fsid, .decode = xdr_decode_fsid, .attrmask = ATTR_FSID, .access = FATTR4_ATTR_READ }, [FATTR4_UNIQUE_HANDLES] = { .name = "FATTR4_UNIQUE_HANDLES", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_unique_handles), .attrmask = 0, .encode = encode_uniquehandles, .decode = decode_uniquehandles, .access = FATTR4_ATTR_READ }, [FATTR4_LEASE_TIME] = { .name = "FATTR4_LEASE_TIME", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_lease_time), .attrmask = 0, .encode = encode_leaselife, .decode = decode_leaselife, .access = FATTR4_ATTR_READ }, [FATTR4_RDATTR_ERROR] = { .name = "FATTR4_RDATTR_ERROR", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_rdattr_error), .attrmask = 0, .encode = encode_rdattr_error, .decode = decode_rdattr_error, .access = FATTR4_ATTR_READ }, [FATTR4_ACL] = { .name = "FATTR4_ACL", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_acl), .encode = encode_acl, .decode = decode_acl, .attrmask = ATTR_ACL, .access = FATTR4_ATTR_READ_WRITE }, [FATTR4_ACLSUPPORT] = { .name = "FATTR4_ACLSUPPORT", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_aclsupport), .attrmask = 0, .encode = encode_aclsupport, .decode = decode_aclsupport, .access = FATTR4_ATTR_READ }, [FATTR4_ARCHIVE] = { .name = "FATTR4_ARCHIVE", .supported = 0, .encoded = 1, .size_fattr4 = sizeof(fattr4_archive), .attrmask = 0, .encode = encode_archive, .decode = decode_archive, .access = FATTR4_ATTR_READ_WRITE }, [FATTR4_CANSETTIME] = { .name = "FATTR4_CANSETTIME", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_cansettime), .attrmask = 0, .encode = encode_cansettime, .decode = decode_cansettime, .access = FATTR4_ATTR_READ }, [FATTR4_CASE_INSENSITIVE] = { .name = "FATTR4_CASE_INSENSITIVE", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_case_insensitive), .attrmask = 0, .encode = encode_case_insensitive, .decode = decode_case_insensitive, .access = FATTR4_ATTR_READ }, [FATTR4_CASE_PRESERVING] = { .name = "FATTR4_CASE_PRESERVING", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_case_preserving), .attrmask = 0, .encode = encode_case_preserving, .decode = decode_case_preserving, .access = FATTR4_ATTR_READ }, [FATTR4_CHOWN_RESTRICTED] = { .name = "FATTR4_CHOWN_RESTRICTED", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_chown_restricted), .attrmask = 0, .encode = encode_chown_restricted, .decode = decode_chown_restricted, .access = FATTR4_ATTR_READ }, [FATTR4_FILEHANDLE] = { .name = "FATTR4_FILEHANDLE", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_filehandle), .attrmask = 0, .encode = encode_filehandle, .decode = decode_filehandle, .access = FATTR4_ATTR_READ }, [FATTR4_FILEID] = { .name = "FATTR4_FILEID", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_fileid), .encode = encode_fileid, .decode = decode_fileid, .attrmask = ATTR_FILEID, .access = FATTR4_ATTR_READ }, [FATTR4_FILES_AVAIL] = { .name = "FATTR4_FILES_AVAIL", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_files_avail), .attrmask = 0, .encode = encode_files_avail, .decode = decode_files_avail, .access = FATTR4_ATTR_READ }, [FATTR4_FILES_FREE] = { .name = "FATTR4_FILES_FREE", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_files_free), .attrmask = 0, .encode = encode_files_free, .decode = decode_files_free, .access = FATTR4_ATTR_READ }, [FATTR4_FILES_TOTAL] = { .name = "FATTR4_FILES_TOTAL", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_files_total), .attrmask = 0, .encode = encode_files_total, .decode = decode_files_total, .access = FATTR4_ATTR_READ }, [FATTR4_FS_LOCATIONS] = { .name = "FATTR4_FS_LOCATIONS", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_fs_locations), .attrmask = ATTR4_FS_LOCATIONS, .encode = encode_fs_locations, .decode = decode_fs_locations, .access = FATTR4_ATTR_READ }, [FATTR4_HIDDEN] = { .name = "FATTR4_HIDDEN", .supported = 0, .encoded = 1, .size_fattr4 = sizeof(fattr4_hidden), .attrmask = 0, .encode = encode_hidden, .decode = decode_hidden, .access = FATTR4_ATTR_READ_WRITE }, [FATTR4_HOMOGENEOUS] = { .name = "FATTR4_HOMOGENEOUS", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_homogeneous), .attrmask = 0, .encode = encode_homogeneous, .decode = decode_homogeneous, .access = FATTR4_ATTR_READ }, [FATTR4_MAXFILESIZE] = { .name = "FATTR4_MAXFILESIZE", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_maxfilesize), .attrmask = 0, .encode = encode_maxfilesize, .decode = decode_maxfilesize, .access = FATTR4_ATTR_READ }, [FATTR4_MAXLINK] = { .name = "FATTR4_MAXLINK", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_maxlink), .attrmask = 0, .encode = encode_maxlink, .decode = decode_maxlink, .access = FATTR4_ATTR_READ }, [FATTR4_MAXNAME] = { .name = "FATTR4_MAXNAME", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_maxname), .attrmask = 0, .encode = encode_maxname, .decode = decode_maxname, .access = FATTR4_ATTR_READ }, [FATTR4_MAXREAD] = { .name = "FATTR4_MAXREAD", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_maxread), .attrmask = 0, .encode = encode_maxread, .decode = decode_maxread, .access = FATTR4_ATTR_READ }, [FATTR4_MAXWRITE] = { .name = "FATTR4_MAXWRITE", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_maxwrite), .attrmask = 0, .encode = encode_maxwrite, .decode = decode_maxwrite, .access = FATTR4_ATTR_READ }, [FATTR4_MIMETYPE] = { .name = "FATTR4_MIMETYPE", .supported = 0, .encoded = 1, .size_fattr4 = sizeof(fattr4_mimetype), .attrmask = 0, .encode = encode_mimetype, .decode = decode_mimetype, .access = FATTR4_ATTR_READ_WRITE }, [FATTR4_MODE] = { .name = "FATTR4_MODE", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_mode), .encode = encode_mode, .decode = decode_mode, .attrmask = ATTR_MODE, .access = FATTR4_ATTR_READ_WRITE }, [FATTR4_NO_TRUNC] = { .name = "FATTR4_NO_TRUNC", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_no_trunc), .attrmask = 0, .encode = encode_no_trunc, .decode = decode_no_trunc, .access = FATTR4_ATTR_READ }, [FATTR4_NUMLINKS] = { .name = "FATTR4_NUMLINKS", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_numlinks), .encode = encode_numlinks, .decode = decode_numlinks, .attrmask = ATTR_NUMLINKS, .access = FATTR4_ATTR_READ }, [FATTR4_OWNER] = { .name = "FATTR4_OWNER", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_owner), .encode = encode_owner, .decode = decode_owner, .attrmask = ATTR_OWNER, .access = FATTR4_ATTR_READ_WRITE }, [FATTR4_OWNER_GROUP] = { .name = "FATTR4_OWNER_GROUP", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_owner_group), .encode = encode_group, .decode = decode_group, .attrmask = ATTR_GROUP, .access = FATTR4_ATTR_READ_WRITE }, [FATTR4_QUOTA_AVAIL_HARD] = { .name = "FATTR4_QUOTA_AVAIL_HARD", .supported = 0, .encoded = 1, .size_fattr4 = sizeof(fattr4_quota_avail_hard), .attrmask = 0, .encode = encode_quota_avail_hard, .decode = decode_quota_avail_hard, .access = FATTR4_ATTR_READ }, [FATTR4_QUOTA_AVAIL_SOFT] = { .name = "FATTR4_QUOTA_AVAIL_SOFT", .supported = 0, .encoded = 1, .size_fattr4 = sizeof(fattr4_quota_avail_soft), .attrmask = 0, .encode = encode_quota_avail_soft, .decode = decode_quota_avail_soft, .access = FATTR4_ATTR_READ }, [FATTR4_QUOTA_USED] = { .name = "FATTR4_QUOTA_USED", .supported = 0, .encoded = 1, .size_fattr4 = sizeof(fattr4_quota_used), .attrmask = 0, .encode = encode_quota_used, .decode = decode_quota_used, .access = FATTR4_ATTR_READ }, [FATTR4_RAWDEV] = { .name = "FATTR4_RAWDEV", .supported = 1, .encoded = 1, /** @todo use FSAL attrs instead ??? */ .size_fattr4 = sizeof(fattr4_rawdev), .encode = encode_rawdev, .decode = decode_rawdev, .attrmask = ATTR_RAWDEV, .access = FATTR4_ATTR_READ }, [FATTR4_SPACE_AVAIL] = { .name = "FATTR4_SPACE_AVAIL", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_space_avail), .attrmask = 0, .encode = encode_space_avail, .decode = decode_space_avail, .access = FATTR4_ATTR_READ }, [FATTR4_SPACE_FREE] = { .name = "FATTR4_SPACE_FREE", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_space_used), .attrmask = 0, .encode = encode_space_free, .decode = decode_space_free, .access = FATTR4_ATTR_READ }, [FATTR4_SPACE_TOTAL] = { .name = "FATTR4_SPACE_TOTAL", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_space_total), .attrmask = 0, .encode = encode_space_total, .decode = decode_space_total, .access = FATTR4_ATTR_READ }, [FATTR4_SPACE_USED] = { .name = "FATTR4_SPACE_USED", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_space_used), .encode = encode_spaceused, .decode = decode_spaceused, .attrmask = ATTR_SPACEUSED, .access = FATTR4_ATTR_READ }, [FATTR4_SYSTEM] = { .name = "FATTR4_SYSTEM", .supported = 0, .encoded = 1, .size_fattr4 = sizeof(fattr4_system), .attrmask = 0, .encode = encode_system, .decode = decode_system, .access = FATTR4_ATTR_READ_WRITE }, [FATTR4_TIME_ACCESS] = { .name = "FATTR4_TIME_ACCESS", .supported = 1, .encoded = 1, /* ( fattr4_time_access ) not aligned on 32 bits */ .size_fattr4 = 12, .encode = encode_accesstime, .decode = decode_accesstime, .attrmask = ATTR_ATIME, .access = FATTR4_ATTR_READ }, [FATTR4_TIME_ACCESS_SET] = { .name = "FATTR4_TIME_ACCESS_SET", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_time_access_set), .encode = encode_accesstimeset, .decode = decode_accesstimeset, .attrmask = ATTR_ATIME, .exp_attrmask = ATTR_ATIME_SERVER, .access = FATTR4_ATTR_WRITE }, [FATTR4_TIME_BACKUP] = { .name = "FATTR4_TIME_BACKUP", .supported = 0, .encoded = 1, /*( fattr4_time_backup ) not aligned on 32 bits */ .size_fattr4 = 12, .attrmask = 0, .encode = encode_backuptime, .decode = decode_backuptime, .access = FATTR4_ATTR_READ_WRITE }, [FATTR4_TIME_CREATE] = { .name = "FATTR4_TIME_CREATE", .supported = 0, .encoded = 1, /* ( fattr4_time_create ) not aligned on 32 bits */ .size_fattr4 = 12, .attrmask = 0, .encode = encode_createtime, .decode = decode_createtime, .access = FATTR4_ATTR_READ_WRITE }, [FATTR4_TIME_DELTA] = { .name = "FATTR4_TIME_DELTA", .supported = 1, .encoded = 1, /* ( fattr4_time_delta ) not aligned on 32 bits */ .size_fattr4 = 12, .attrmask = 0, .encode = encode_deltatime, .decode = decode_deltatime, .access = FATTR4_ATTR_READ }, [FATTR4_TIME_METADATA] = { .name = "FATTR4_TIME_METADATA", .supported = 1, .encoded = 1, /* ( fattr4_time_metadata ) not aligned on 32 bits */ .size_fattr4 = 12, .encode = encode_metatime, .decode = decode_metatime, .attrmask = ATTR_CTIME, .access = FATTR4_ATTR_READ }, [FATTR4_TIME_MODIFY] = { .name = "FATTR4_TIME_MODIFY", .supported = 1, .encoded = 1, /* ( fattr4_time_modify ) not aligned on 32 bits */ .size_fattr4 = 12, .encode = encode_modifytime, .decode = decode_modifytime, .attrmask = ATTR_MTIME, .access = FATTR4_ATTR_READ }, [FATTR4_TIME_MODIFY_SET] = { .name = "FATTR4_TIME_MODIFY_SET", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_time_modify_set), .encode = encode_modifytimeset, .decode = decode_modifytimeset, .attrmask = ATTR_MTIME, .exp_attrmask = ATTR_MTIME_SERVER, .access = FATTR4_ATTR_WRITE }, [FATTR4_MOUNTED_ON_FILEID] = { .name = "FATTR4_MOUNTED_ON_FILEID", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_mounted_on_fileid), .attrmask = 0, .encode = encode_mounted_on_fileid, .decode = decode_mounted_on_fileid, .access = FATTR4_ATTR_READ }, [FATTR4_DIR_NOTIF_DELAY] = { .name = "FATTR4_DIR_NOTIF_DELAY", .supported = 0, .encoded = 0, .size_fattr4 = sizeof(fattr4_dir_notif_delay), .attrmask = 0, .encode = encode_dir_notif_delay, .decode = decode_dir_notif_delay, .access = FATTR4_ATTR_READ }, [FATTR4_DIRENT_NOTIF_DELAY] = { .name = "FATTR4_DIRENT_NOTIF_DELAY", .supported = 0, .encoded = 0, .size_fattr4 = sizeof( fattr4_dirent_notif_delay), .attrmask = 0, .encode = encode_dirent_notif_delay, .decode = decode_dirent_notif_delay, .access = FATTR4_ATTR_READ }, [FATTR4_DACL] = { .name = "FATTR4_DACL", .supported = 0, .encoded = 0, .size_fattr4 = sizeof(fattr4_dacl), .attrmask = 0, .encode = encode_dacl, .decode = decode_dacl, .access = FATTR4_ATTR_READ_WRITE }, [FATTR4_SACL] = { .name = "FATTR4_SACL", .supported = 0, .encoded = 0, .size_fattr4 = sizeof(fattr4_sacl), .encode = encode_sacl, .decode = decode_sacl, .access = FATTR4_ATTR_READ_WRITE }, [FATTR4_CHANGE_POLICY] = { .name = "FATTR4_CHANGE_POLICY", .supported = 0, .encoded = 0, .size_fattr4 = sizeof(fattr4_change_policy), .attrmask = 0, .encode = encode_change_policy, .decode = decode_change_policy, .access = FATTR4_ATTR_READ }, [FATTR4_FS_STATUS] = { .name = "FATTR4_FS_STATUS", .supported = 0, .encoded = 0, .size_fattr4 = sizeof(fattr4_fs_status), .attrmask = 0, .encode = encode_fs_status, .decode = decode_fs_status, .access = FATTR4_ATTR_READ }, [FATTR4_FS_LAYOUT_TYPES] = { .name = "FATTR4_FS_LAYOUT_TYPES", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_fs_layout_types), .attrmask = 0, .encode = encode_fs_layout_types, .decode = decode_fs_layout_types, .access = FATTR4_ATTR_READ }, [FATTR4_LAYOUT_HINT] = { .name = "FATTR4_LAYOUT_HINT", .supported = 0, .encoded = 0, .size_fattr4 = sizeof(fattr4_layout_hint), .attrmask = 0, .encode = encode_layout_hint, .decode = decode_layout_hint, .access = FATTR4_ATTR_WRITE }, [FATTR4_LAYOUT_TYPES] = { .name = "FATTR4_LAYOUT_TYPES", .supported = 0, .encoded = 0, .size_fattr4 = sizeof(fattr4_layout_types), .attrmask = 0, .encode = encode_layout_types, .decode = decode_layout_types, .access = FATTR4_ATTR_READ }, [FATTR4_LAYOUT_BLKSIZE] = { .name = "FATTR4_LAYOUT_BLKSIZE", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_layout_blksize), .attrmask = 0, .encode = encode_layout_blocksize, .decode = decode_layout_blocksize, .access = FATTR4_ATTR_READ }, [FATTR4_LAYOUT_ALIGNMENT] = { .name = "FATTR4_LAYOUT_ALIGNMENT", .supported = 0, .encoded = 0, .size_fattr4 = sizeof(fattr4_layout_alignment), .attrmask = 0, .encode = encode_layout_alignment, .decode = decode_layout_alignment, .access = FATTR4_ATTR_READ }, [FATTR4_FS_LOCATIONS_INFO] = { .name = "FATTR4_FS_LOCATIONS_INFO", .supported = 0, .encoded = 0, .size_fattr4 = sizeof(fattr4_fs_locations_info), .attrmask = 0, .encode = encode_fs_locations_info, .decode = decode_fs_locations_info, .access = FATTR4_ATTR_READ }, [FATTR4_MDSTHRESHOLD] = { .name = "FATTR4_MDSTHRESHOLD", .supported = 0, .encoded = 0, .size_fattr4 = sizeof(fattr4_mdsthreshold), .attrmask = 0, .encode = encode_mdsthreshold, .decode = decode_mdsthreshold, .access = FATTR4_ATTR_READ }, [FATTR4_RETENTION_GET] = { .name = "FATTR4_RETENTION_GET", .supported = 0, .encoded = 0, .size_fattr4 = sizeof(fattr4_retention_get), .attrmask = 0, .encode = encode_retention_get, .decode = decode_retention_get, .access = FATTR4_ATTR_READ }, [FATTR4_RETENTION_SET] = { .name = "FATTR4_RETENTION_SET", .supported = 0, .encoded = 0, .size_fattr4 = sizeof(fattr4_retention_set), .attrmask = 0, .encode = encode_retention_set, .decode = decode_retention_set, .access = FATTR4_ATTR_WRITE }, [FATTR4_RETENTEVT_GET] = { .name = "FATTR4_RETENTEVT_GET", .supported = 0, .encoded = 0, .size_fattr4 = sizeof(fattr4_retentevt_get), .attrmask = 0, .encode = encode_retentevt_get, .decode = decode_retentevt_get, .access = FATTR4_ATTR_READ }, [FATTR4_RETENTEVT_SET] = { .name = "FATTR4_RETENTEVT_SET", .supported = 0, .encoded = 0, .size_fattr4 = sizeof(fattr4_retentevt_set), .attrmask = 0, .encode = encode_retentevt_set, .decode = decode_retentevt_set, .access = FATTR4_ATTR_WRITE }, [FATTR4_RETENTION_HOLD] = { .name = "FATTR4_RETENTION_HOLD", .supported = 0, .encoded = 0, .size_fattr4 = sizeof(fattr4_retention_hold), .attrmask = 0, .encode = encode_retention_hold, .decode = decode_retention_hold, .access = FATTR4_ATTR_READ_WRITE }, [FATTR4_MODE_SET_MASKED] = { .name = "FATTR4_MODE_SET_MASKED", .supported = 0, .encoded = 0, .size_fattr4 = sizeof(fattr4_mode_set_masked), .attrmask = 0, .encode = encode_mode_set_masked, .decode = decode_mode_set_masked, .access = FATTR4_ATTR_WRITE }, [FATTR4_SUPPATTR_EXCLCREAT] = { .name = "FATTR4_SUPPATTR_EXCLCREAT", .supported = 1, .encoded = 1, .size_fattr4 = sizeof( fattr4_suppattr_exclcreat), .attrmask = 0, .encode = encode_support_exclusive_create, .decode = decode_support_exclusive_create, .access = FATTR4_ATTR_READ }, [FATTR4_FS_CHARSET_CAP] = { .name = "FATTR4_FS_CHARSET_CAP", .supported = 0, .encoded = 0, .size_fattr4 = sizeof(fattr4_fs_charset_cap), .attrmask = 0, .encode = encode_fs_charset_cap, .decode = decode_fs_charset_cap, .access = FATTR4_ATTR_READ }, [FATTR4_SEC_LABEL] = { .name = "ATTR4_SEC_LABEL", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_sec_label), .attrmask = ATTR4_SEC_LABEL, .encode = encdec_sec_label, .decode = encdec_sec_label, .access = FATTR4_ATTR_READ_WRITE }, [FATTR4_XATTR_SUPPORT] = { .name = "FATTR4_XATTR_SUPPORT", .supported = 1, .encoded = 1, .size_fattr4 = sizeof(fattr4_fs_charset_cap), .attrmask = 0, .encode = encode_xattr_support, .decode = decode_xattr_support, .access = FATTR4_ATTR_READ } }; /* goes in a more global header? */ /** path_filter * scan the path we are given for bad filenames. * * scan control: * UTF8_SCAN_NOSLASH - detect and reject '/' in names * UTF8_NODOT - detect and reject "." and ".." as the name * UTF8_SCAN_CKUTF8 - detect invalid utf8 sequences * * NULL termination is required. It also speeds up the scan * UTF-8 scanner courtesy Markus Kuhn * GPL licensed per licensing referenced in source. * */ nfsstat4 path_filter(const char *name, int scan) { const unsigned char *np = (const unsigned char *)name; nfsstat4 status = NFS4_OK; unsigned int c, first; first = 1; c = *np++; while (c) { if (likely(c < 0x80)) { /* ascii */ if (unlikely(c == '/' && (scan & UTF8_SCAN_NOSLASH))) { status = NFS4ERR_BADCHAR; goto error; } if (unlikely(first && c == '.' && (scan & UTF8_SCAN_NODOT))) { if (np[0] == '\0' || (np[0] == '.' && np[1] == '\0')) { status = NFS4ERR_BADNAME; goto error; } } } else if (likely(scan & UTF8_SCAN_CKUTF8)) { /* UTF-8 range */ if ((c & 0xe0) == 0xc0) { /* 2 octet UTF-8 */ if ((*np & 0xc0) != 0x80 || (c & 0xfe) == 0xc0) { /* overlong */ goto badutf8; } else { np++; } } else if ((c & 0xf0) == 0xe0) { /* 3 octet UTF-8 */ if (/* overlong */ (*np & 0xc0) != 0x80 || (np[1] & 0xc0) != 0x80 || (c == 0xe0 && (*np & 0xe0) == 0x80) || /* surrogate */ (c == 0xed && (*np & 0xe0) == 0xa0) || (c == 0xef && *np == 0xbf && (np[1] & 0xfe) == 0xbe)) { /* U+fffe - u+ffff */ goto badutf8; } else { np += 2; } } else if ((c & 0xf8) == 0xf0) { /* 4 octet UTF-8 */ if (/* overlong */ (*np & 0xc0) != 0x80 || (np[1] & 0xc0) != 0x80 || (np[2] & 0xc0) != 0x80 || (c == 0xf0 && (*np & 0xf0) == 0x80) || (c == 0xf4 && *np > 0x8f) || c > 0xf4) { /* > u+10ffff */ goto badutf8; } else { np += 3; } } else { goto badutf8; } } c = *np++; first = 0; } return NFS4_OK; badutf8: status = NFS4ERR_INVAL; error: return status; } void nfs4_Fattr_Free(fattr4 *fattr) { if (fattr->attr_vals.attrlist4_val != NULL) { gsh_free(fattr->attr_vals.attrlist4_val); fattr->attr_vals.attrlist4_val = NULL; fattr->attr_vals.attrlist4_len = 0; } } void get_mounted_on_fileid(compound_data_t *data, uint64_t *mounted_on_fileid) { PTHREAD_RWLOCK_rdlock(&op_ctx->ctx_export->exp_lock); if (data->current_obj == op_ctx->ctx_export->exp_root_obj) { /* This is the root of the current export, find our * mounted_on_fileid and use that. */ *mounted_on_fileid = op_ctx->ctx_export->exp_mounted_on_file_id; } else { /* This is not the root of the current export, just * use fileid. */ *mounted_on_fileid = data->current_obj->fileid; } PTHREAD_RWLOCK_unlock(&op_ctx->ctx_export->exp_lock); } /** * @brief Fill NFSv4 Fattr from a file * * This function fills an NFSv4 Fattr from a file represented by * data->currentFH and data->current-obj. * * Memory for bitmap_val and attr_val is dynamically allocated, the caller is * responsible for freeing it. * * @param[in] data NFSv4 compoud request's data * @param[in] request_mask The original request attribute mask * @param[in/out] attr fsal_attrlist to fill in and mask to request * @param[out] Fattr NFSv4 Fattr buffer * @param[in] Bitmap Bitmap of attributes being requested * * @retval NFSv4 status */ nfsstat4 file_To_Fattr(compound_data_t *data, attrmask_t request_mask, struct fsal_attrlist *attr, fattr4 *Fattr, struct bitmap4 *Bitmap) { fsal_status_t status; struct xdr_attrs_args args = { .attrs = attr, .data = data, .hdl4 = &data->currentFH, }; /* Permission check only if ACL is asked for. * NOTE: We intentionally do NOT check ACE4_READ_ATTR. */ if (attribute_is_set(Bitmap, FATTR4_ACL)) { fsal_status_t status; LogDebug(COMPONENT_NFS_V4_ACL, "Permission check for ACL for obj %p", data->current_obj); status = fsal_access(data->current_obj, FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_READ_ACL)); if (FSAL_IS_ERROR(status)) { LogDebug( COMPONENT_NFS_V4_ACL, "Permission check for ACL for obj %p failed with %s", data->current_obj, msg_fsal_err(status.major)); return nfs4_Errno_status(status); } } else { #ifdef ENABLE_RFC_ACL LogDebug(COMPONENT_NFS_V4_ACL, "Permission check for ATTR for obj %p", data->current_obj); status = fsal_access( data->current_obj, FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_READ_ATTR)); if (FSAL_IS_ERROR(status)) { LogDebug( COMPONENT_NFS_V4_ACL, "Permission check for ATTR for obj %p failed with %s", data->current_obj, fsal_err_txt(status)); return nfs4_Errno_status(status); } #else /* ENABLE_RFC_ACL */ LogDebug(COMPONENT_NFS_V4_ACL, "No permission check for ACL for obj %p", data->current_obj); #endif /* ENABLE_RFC_ACL */ } if (attribute_is_set(Bitmap, FATTR4_MOUNTED_ON_FILEID)) { get_mounted_on_fileid(data, &args.mounted_on_fileid); } /* Fill in fileid and fsid into args */ args.fileid = data->current_obj->fileid; args.fsid = data->current_obj->fsid; status = data->current_obj->obj_ops->getattrs(data->current_obj, attr); if (FSAL_IS_ERROR(status)) return nfs4_Errno_status(status); /* Restore originally requested mask */ attr->request_mask = request_mask; if (nfs4_FSALattr_To_Fattr(&args, Bitmap, Fattr) != 0) { /* Done with the attrs, caller won't release, but we did * fetch them. */ fsal_release_attrs(attr); return NFS4ERR_IO; } return NFS4_OK; } /* * @brief Sets the FATTR4_RDATTR_ERROR in fattrs along with the other restricted * attrs if requested. * * @param[in] Current compound request data * @param[in|out] Attrs to be filled * @param[in] rdattr_error value * @param[in] Requested attrs bitmap mask */ int nfs4_Fattr_Fill_Error(compound_data_t *data, fattr4 *Fattr, nfsstat4 rdattr_error, struct bitmap4 *req_attrmask, struct xdr_attrs_args *args) { struct bitmap4 restricted_attrmask; memset(&restricted_attrmask, 0, sizeof(restricted_attrmask)); if (attribute_is_set(&Fattr->attrmask, FATTR4_FSID)) { set_attribute_in_bitmap(&restricted_attrmask, FATTR4_FSID); } if (attribute_is_set(&Fattr->attrmask, FATTR4_MOUNTED_ON_FILEID)) { set_attribute_in_bitmap(&restricted_attrmask, FATTR4_MOUNTED_ON_FILEID); } /* * Since fslocations are set at the time of encoding * (encode_fs_locations), we should check the requested attrmask * here and not Fattr->attrmask. */ if (attribute_is_set(req_attrmask, FATTR4_FS_LOCATIONS)) { set_attribute_in_bitmap(&restricted_attrmask, FATTR4_FS_LOCATIONS); } /* FATTR4_RDATTR_ERROR should be set only if it is requested */ if (attribute_is_set(req_attrmask, FATTR4_RDATTR_ERROR)) { args->rdattr_error = rdattr_error; set_attribute_in_bitmap(&restricted_attrmask, FATTR4_RDATTR_ERROR); } args->data = data; return nfs4_FSALattr_To_Fattr(args, &restricted_attrmask, Fattr); } bool xdr_fattr4_encode(XDR *xdrs, struct xdr_attrs_args *args, struct bitmap4 *req_bitmap, struct bitmap4 *attr_bitmap) { int attribute_to_set; int max_attr_idx = nfs4_max_attr_index(args->data); fattr_xdr_result xdr_res; struct bitmap4 bitmap_encoded; struct bitmap4 *bitmap; /* Remember where we put the length of the attr data */ u_int pos_len = 0, pos_end = 0; uint32_t attr_len = 0; bitmap = attr_bitmap != NULL ? attr_bitmap : &bitmap_encoded; /* basic init */ if (attr_bitmap == NULL) memset(&bitmap_encoded, 0, sizeof(bitmap_encoded)); LogFullDebug(COMPONENT_NFS_V4, "Maximum allowed attr index = %d", max_attr_idx); for (attribute_to_set = next_attr_from_bitmap(req_bitmap, -1); attribute_to_set != -1; attribute_to_set = next_attr_from_bitmap(req_bitmap, attribute_to_set)) { if (attribute_to_set > max_attr_idx) break; /* skip out of bounds */ /* * Skip any attribute where we have a legitimate attrmask * bit, but where it's not set in the valid_mask. */ if (fattr4tab[attribute_to_set].attrmask && !(fattr4tab[attribute_to_set].attrmask & args->attrs->valid_mask)) continue; /* Check for special cases */ if (fattr4tab[attribute_to_set].encoded && (args->data != NULL || ((attribute_to_set != FATTR4_FS_LOCATIONS || args->data->current_obj != NULL) && (attribute_to_set != FATTR4_FS_LAYOUT_TYPES) && (attribute_to_set != FATTR4_LAYOUT_BLKSIZE)))) { bool __attribute__((unused)) res = set_attribute_in_bitmap(bitmap, attribute_to_set); assert(res); continue; } LogFullDebug(COMPONENT_NFS_V4, "Attr not supported %d name=%s", attribute_to_set, fattr4tab[attribute_to_set].name); } if (attr_bitmap == NULL) { /* Only code bitmap and attr data length if we aren't passed * attr_bitmap */ if (!xdr_bitmap4(xdrs, &bitmap_encoded)) return false; pos_len = xdr_getpos(xdrs); if (!inline_xdr_u_int32_t(xdrs, &attr_len)) return false; } for (attribute_to_set = next_attr_from_bitmap(bitmap, -1); attribute_to_set != -1; attribute_to_set = next_attr_from_bitmap(bitmap, attribute_to_set)) { xdr_res = fattr4tab[attribute_to_set].encode(xdrs, args); if (xdr_res == FATTR_XDR_SUCCESS) { LogFullDebugAlt(COMPONENT_NFS_V4, COMPONENT_NFS_READDIR, "Encoded attr %d, name = %s, pos = %d", attribute_to_set, fattr4tab[attribute_to_set].name, (int)xdr_getpos(xdrs)); } else if (xdr_res == FATTR_XDR_NOOP) { LogWarn(COMPONENT_NFS_V4, "Attr not supported %d name=%s", attribute_to_set, fattr4tab[attribute_to_set].name); return false; } else { LogDebugAlt(COMPONENT_NFS_V4, COMPONENT_NFS_READDIR, "Encode FAILED for attr %d, name = %s", attribute_to_set, fattr4tab[attribute_to_set].name); return false; } } if (attr_bitmap == NULL) { /* Get the end position and compute the length of the attr data */ pos_end = xdr_getpos(xdrs); attr_len = pos_end - pos_len - BYTES_PER_XDR_UNIT; /* Backup so we can put the actual attr data len in */ xdr_setpos(xdrs, pos_len); if (!inline_xdr_u_int32_t(xdrs, &attr_len)) return false; /* And back to where we were */ xdr_setpos(xdrs, pos_end); } return true; } bool xdr_encode_entry4(XDR *xdrs, struct xdr_attrs_args *args, struct bitmap4 *req_bitmap, nfs_cookie4 cookie, component4 *name) { bool_t next = args != NULL; if (!xdr_bool(xdrs, &next)) return false; if (!next) return true; if (!xdr_nfs_cookie4(xdrs, &cookie)) return false; if (!xdr_component4(xdrs, name)) return false; if (!xdr_fattr4_encode(xdrs, args, req_bitmap, NULL)) return false; return true; } /* * @brief Sets the FATTR4_RDATTR_ERROR in fattrs along with the other restricted * attrs if requested. * * @param[in] Current compound request data * @param[in|out] Attrs to be filled * @param[in] rdattr_error value * @param[in] Requested attrs bitmap mask */ bool xdr_nfs4_fattr_fill_error(XDR *xdrs, struct bitmap4 *req_attrmask, nfs_cookie4 cookie, component4 *name, struct xdr_attrs_args *args) { struct bitmap4 attrmask; memset(&attrmask, 0, sizeof(attrmask)); if (attribute_is_set(req_attrmask, FATTR4_FSID)) set_attribute_in_bitmap(&attrmask, FATTR4_FSID); if (attribute_is_set(req_attrmask, FATTR4_MOUNTED_ON_FILEID)) set_attribute_in_bitmap(&attrmask, FATTR4_MOUNTED_ON_FILEID); /* * Since fslocations are set at the time of encoding * (encode_fs_locations), we should check the requested attrmask * here and not Fattr->attrmask. */ if (attribute_is_set(req_attrmask, FATTR4_FS_LOCATIONS)) set_attribute_in_bitmap(&attrmask, FATTR4_FS_LOCATIONS); /* FATTR4_RDATTR_ERROR should be set only if it is requested */ if (attribute_is_set(req_attrmask, FATTR4_RDATTR_ERROR)) set_attribute_in_bitmap(&attrmask, FATTR4_RDATTR_ERROR); return xdr_encode_entry4(xdrs, args, &attrmask, cookie, name); } /** * @brief Converts FSAL Attributes to NFSv4 Fattr buffer. * * Converts FSAL Attributes to NFSv4 Fattr buffer. * * @param[in] args XDR attribute arguments * @param[in] Bitmap Bitmap of attributes being requested * @param[out] Fattr NFSv4 Fattr buffer * Memory for bitmap_val and attr_val is * dynamically allocated, * caller is responsible for freeing it. * * @return -1 if failed, 0 if successful. * */ int nfs4_FSALattr_To_Fattr(struct xdr_attrs_args *args, struct bitmap4 *Bitmap, fattr4 *Fattr) { u_int LastOffset; XDR attr_body; bool xdr_res; uint32_t attrvals_buflen; /* basic init */ memset(Fattr, 0, sizeof(*Fattr)); if (Bitmap->bitmap4_len == 0) return 0; /* they ask for nothing, they get nothing */ attrvals_buflen = NFS4_ATTRVALS_BUFFLEN; if (attribute_is_set(Bitmap, FATTR4_ACL) && args->attrs->acl) { /* Calculating an exact needed xdr buffer size is laborious * and time consuming, so making a rough estimate */ attrvals_buflen += (sizeof(fsal_ace_t) + NFS4_MAX_DOMAIN_LEN) * args->attrs->acl->naces; } /* Check if the calculated len is less than the max send buffer size */ if (attrvals_buflen > nfs_param.core_param.rpc.max_send_buffer_size) attrvals_buflen = nfs_param.core_param.rpc.max_send_buffer_size; Fattr->attr_vals.attrlist4_val = gsh_malloc(attrvals_buflen); memset(&attr_body, 0, sizeof(attr_body)); xdrmem_create(&attr_body, Fattr->attr_vals.attrlist4_val, attrvals_buflen, XDR_ENCODE); xdr_res = xdr_fattr4_encode(&attr_body, args, Bitmap, &Fattr->attrmask); LastOffset = xdr_getpos(&attr_body); /* dumb but for now */ xdr_destroy(&attr_body); if (!xdr_res || LastOffset == 0) { /* failure or no supported attrs so we can free */ nfs4_Fattr_Free(Fattr); } else { Fattr->attr_vals.attrlist4_len = LastOffset; } return xdr_res ? 0 : -1; } /** * * nfs3_Sattr_To_FSALattr: Converts NFSv3 Sattr to FSAL Attributes. * * Converts NFSv3 Sattr to FSAL Attributes. * * @param FSAL_attr [OUT] computed FSAL attributes. * @param sattr [IN] NFSv3 sattr to be set. * * @retval true on success. * @retval false on failure. * */ bool nfs3_Sattr_To_FSALattr(struct fsal_attrlist *FSAL_attr, sattr3 *sattr) { FSAL_attr->valid_mask = 0; if (sattr->mode.set_it) { LogFullDebug(COMPONENT_NFSPROTO, "mode = %o", sattr->mode.set_mode3_u.mode); FSAL_attr->mode = unix2fsal_mode(sattr->mode.set_mode3_u.mode); FSAL_attr->valid_mask |= ATTR_MODE; } if (sattr->uid.set_it) { LogFullDebug(COMPONENT_NFSPROTO, "uid = %d", sattr->uid.set_uid3_u.uid); FSAL_attr->owner = sattr->uid.set_uid3_u.uid; FSAL_attr->valid_mask |= ATTR_OWNER; } if (sattr->gid.set_it) { LogFullDebug(COMPONENT_NFSPROTO, "gid = %d", sattr->gid.set_gid3_u.gid); FSAL_attr->group = sattr->gid.set_gid3_u.gid; FSAL_attr->valid_mask |= ATTR_GROUP; } if (sattr->size.set_it) { LogFullDebug(COMPONENT_NFSPROTO, "size = %" PRIu64, sattr->size.set_size3_u.size); FSAL_attr->filesize = sattr->size.set_size3_u.size; FSAL_attr->valid_mask |= ATTR_SIZE; } if (sattr->atime.set_it != DONT_CHANGE) { LogFullDebug(COMPONENT_NFSPROTO, "set=%d atime = %d,%d", sattr->atime.set_it, sattr->atime.set_atime_u.atime.tv_sec, sattr->atime.set_atime_u.atime.tv_nsec); if (sattr->atime.set_it == SET_TO_CLIENT_TIME) { FSAL_attr->atime.tv_sec = sattr->atime.set_atime_u.atime.tv_sec; FSAL_attr->atime.tv_nsec = sattr->atime.set_atime_u.atime.tv_nsec; FSAL_attr->valid_mask |= ATTR_ATIME; } else if (sattr->atime.set_it == SET_TO_SERVER_TIME) { /* Use the server's current time */ LogFullDebug(COMPONENT_NFSPROTO, "SET_TO_SERVER_TIME atime"); FSAL_attr->valid_mask |= ATTR_ATIME_SERVER; } else { LogCrit(COMPONENT_NFSPROTO, "Unexpected value for sattr->atime.set_it = %d", sattr->atime.set_it); } } if (sattr->mtime.set_it != DONT_CHANGE) { LogFullDebug(COMPONENT_NFSPROTO, "set=%d mtime = %d", sattr->atime.set_it, sattr->mtime.set_mtime_u.mtime.tv_sec); if (sattr->mtime.set_it == SET_TO_CLIENT_TIME) { FSAL_attr->mtime.tv_sec = sattr->mtime.set_mtime_u.mtime.tv_sec; FSAL_attr->mtime.tv_nsec = sattr->mtime.set_mtime_u.mtime.tv_nsec; FSAL_attr->valid_mask |= ATTR_MTIME; } else if (sattr->mtime.set_it == SET_TO_SERVER_TIME) { /* Use the server's current time */ LogFullDebug(COMPONENT_NFSPROTO, "SET_TO_SERVER_TIME Mtime"); FSAL_attr->valid_mask |= ATTR_MTIME_SERVER; } else { LogCrit(COMPONENT_NFSPROTO, "Unexpected value for sattr->mtime.set_it = %d", sattr->mtime.set_it); } } return true; } /* nfs3_Sattr_To_FSALattr */ /**** Glue related functions ****/ /* * Conversion of attributes */ /** * @brief Fixup FSAL Attributes for NFSv3. * * This function validates the FSAL attributes as having all the required * attributes and it fills in fsid3 with a squashed fsid. Since the ONLY * modification to the FSAL attr is to compute fsid3, we consider FSAL_attr * const and just override to set fsid3. * * @param export [IN] the related export entry * @param FSAL_attr [IN,OUT] pointer to FSAL attributes. * * @return true if successful, false otherwise. * */ bool nfs3_Fixup_FSALattr(struct fsal_obj_handle *obj, const struct fsal_attrlist *FSAL_attr) { /* We want to override the FSAL fsid with the export's configured fsid */ attrmask_t want = ATTRS_NFS3; /* If an error occurred when getting object attributes, return false */ if (FSAL_attr->valid_mask == ATTR_RDATTR_ERR) return false; if ((want & FSAL_attr->valid_mask) != want) { LogCrit(COMPONENT_NFSPROTO, "Likely bug: FSAL did not fill in a standard NFSv3 attribute: missing %" PRIx64, want & ~(FSAL_attr->valid_mask)); return false; } if (op_ctx_export_has_option_set(EXPORT_OPTION_FSID_SET)) { /* xor filesystem_id major and rotated minor to create unique * on-wire fsid. */ ((struct fsal_attrlist *)FSAL_attr)->fsid3 = squash_fsid(&op_ctx->ctx_export->filesystem_id); LogFullDebug( COMPONENT_NFSPROTO, "Compressing export filesystem_id for NFS v3 from fsid major %#" PRIX64 " (%" PRIu64 "), minor %#" PRIX64 " (%" PRIu64 ") to nfs3_fsid = %#" PRIX64 " (%" PRIu64 ")", op_ctx->ctx_export->filesystem_id.major, op_ctx->ctx_export->filesystem_id.major, op_ctx->ctx_export->filesystem_id.minor, op_ctx->ctx_export->filesystem_id.minor, FSAL_attr->fsid3, FSAL_attr->fsid3); } else { /* xor filesystem_id major and rotated minor to create unique * on-wire fsid. */ ((struct fsal_attrlist *)FSAL_attr)->fsid3 = squash_fsid(&obj->fsid); LogFullDebug( COMPONENT_NFSPROTO, "Compressing fsid for NFS v3 from fsid major %#" PRIX64 " (%" PRIu64 "), minor %#" PRIX64 " (%" PRIu64 ") to nfs3_fsid = %#" PRIX64 " (%" PRIu64 ")", obj->fsid.major, obj->fsid.major, obj->fsid.minor, obj->fsid.minor, FSAL_attr->fsid3, FSAL_attr->fsid3); } return true; } /** * @brief Checks if attributes have READ or WRITE access * * @param[in] bitmap NFSv4 attribute bitmap * @param[in] access The file access to be checked, either FATTR4_ATTR_READ or * FATTR4_ATTR_WRITE * * @return true if successful, false otherwise. * */ bool nfs4_Fattr_Check_Access_Bitmap(struct bitmap4 *bitmap, int access) { int attribute; assert((access == FATTR4_ATTR_READ) || (access == FATTR4_ATTR_WRITE)); for (attribute = next_attr_from_bitmap(bitmap, -1); attribute != -1; attribute = next_attr_from_bitmap(bitmap, attribute)) { if (attribute > FATTR4_MAX_ATTR_INDEX) { /* Erroneous value... skip */ continue; } if (((int)fattr4tab[attribute].access & access) != access) return false; } return true; } /* nfs4_Fattr_Check_Access */ /** * * nfs4_Fattr_Check_Access: checks if attributes have READ or WRITE access. * * Checks if attributes have READ or WRITE access. * * @param Fattr [IN] pointer to NFSv4 attributes. * @param access [IN] access to be checked, either FATTR4_ATTR_READ or * FATTR4_ATTR_WRITE * * @return true if successful, false otherwise. * */ bool nfs4_Fattr_Check_Access(fattr4 *Fattr, int access) { return nfs4_Fattr_Check_Access_Bitmap(&Fattr->attrmask, access); } /* nfs4_Fattr_Check_Access */ /** * @brief Remove unsupported attributes from bitmap4 * * @todo .supported is not nearly as good as actually checking with * the export. * * @param[in] bitmap NFSv4 attribute bitmap. */ void nfs4_bitmap4_Remove_Unsupported(struct bitmap4 *bitmap) { int attribute; for (attribute = 0; attribute <= FATTR4_MAX_ATTR_INDEX; attribute++) { if (!fattr4tab[attribute].supported) { if (!clear_attribute_in_bitmap(bitmap, attribute)) break; } } } /** * * nfs4_Fattr_Supported: Checks if an attribute is supported. * * Checks if an attribute is supported. * * @param Fattr [IN] pointer to NFSv4 attributes. * * @return true if successful, false otherwise. * */ bool nfs4_Fattr_Supported(fattr4 *Fattr) { int attribute; attrmask_t fsal_supported; /* Get the set of supported attributes from the active export. */ fsal_supported = op_ctx->fsal_export->exp_ops.fs_supported_attrs( op_ctx->fsal_export); for (attribute = next_attr_from_bitmap(&Fattr->attrmask, -1); attribute != -1; attribute = next_attr_from_bitmap(&Fattr->attrmask, attribute)) { bool supported = atrib_supported(attribute, fsal_supported); LogFullDebug(COMPONENT_NFS_V4, "Attribute %s Ganesha %s FSAL %s", fattr4tab[attribute].name, fattr4tab[attribute].supported ? "supported" : "not supported", supported ? "supported" : "not supported"); if (!supported) return false; } return true; } /** * * nfs4_Fattr_cmp: compares 2 fattr4 buffers. * * Compares 2 fattr4 buffers. * * @param Fattr1 [IN] pointer to NFSv4 attributes. * @param Fattr2 [IN] pointer to NFSv4 attributes. * * @return 1 if attributes are the same, 0 otherwise, but -1 if RDATTR_ERROR is * set * */ int nfs4_Fattr_cmp(fattr4 *Fattr1, fattr4 *Fattr2) { u_int LastOffset; uint32_t i; u_int len = 0; int attr1 = 0, attr2 = 0; if (attribute_is_set(&Fattr1->attrmask, FATTR4_RDATTR_ERROR)) { /* Error case */ return -1; } LastOffset = 0; len = 0; if (Fattr1->attr_vals.attrlist4_len == 0) { /* Could have no fsal_attrlist if all the flags in bitmask * are invalid, both buffers are empty so equal */ return 1; } /* We need to iterate both bitmaps, and make sure all set bits match. * It's possible for a client (I'm looking at you VMWare) to send a * bitmap that is longer than it needs to be, with all words zero, so * extend the length of each bitmap to the end of the other one, * treating all bits as zero */ attr1 = next_attr_from_bitmap(&Fattr1->attrmask, -1); attr2 = next_attr_from_bitmap(&Fattr2->attrmask, -1); while (attr1 != -1 && attr2 != -1) { if (attr1 > FATTR4_MAX_ATTR_INDEX) { /* Erroneous value... skip; Just advance attr1, since we * won't ever generate invalid values in our bitmask */ attr1 = next_attr_from_bitmap(&Fattr1->attrmask, attr1); continue; } if (LastOffset + sizeof(uint32_t) > Fattr1->attr_vals.attrlist4_len) { /* Minimum attribute size is 4, given fsal_attrlist has * bits set but no values. */ LogFullDebug(COMPONENT_NFS_V4, "Attrlist missing values for %s", fattr4tab[attr1].name); return 0; } if (attr1 != attr2) { /* The next-set-bits don't match (including one bitmask * being done and the other not). This is a failure * case */ LogFullDebug( COMPONENT_NFS_V4, "Next bits don't match. Given %s expect %s", fattr4tab[attr1].name, fattr4tab[attr2].name); return 0; } LogFullDebug(COMPONENT_NFS_V4, "Comparing %s", fattr4tab[attr1].name); /* We have matching attribute bits. Compare the attribute * values. */ switch (attr1) { case FATTR4_SUPPORTED_ATTRS: memcpy(&len, (char *)(Fattr1->attr_vals.attrlist4_val + LastOffset), sizeof(u_int)); if (memcmp((char *)(Fattr1->attr_vals.attrlist4_val + LastOffset), (char *)(Fattr2->attr_vals.attrlist4_val + LastOffset), sizeof(u_int)) != 0) { LogFullDebug( COMPONENT_NFS_V4, "Attr %s wrong len expected %u got %u", fattr4tab[attr1].name, len, *((u_int *)(Fattr2->attr_vals .attrlist4_val + LastOffset))); return 0; } len = htonl(len); LastOffset += sizeof(u_int); for (i = 0; i < len; i++) { if (memcmp((char *)(Fattr1->attr_vals .attrlist4_val + LastOffset), (char *)(Fattr2->attr_vals .attrlist4_val + LastOffset), sizeof(uint32_t)) != 0) { LogFullDebug(COMPONENT_NFS_V4, "Wrong value for %s", fattr4tab[attr1].name); return 0; } LastOffset += sizeof(uint32_t); } break; case FATTR4_FILEHANDLE: case FATTR4_OWNER: case FATTR4_OWNER_GROUP: /* These are variable size. */ memcpy(&len, (char *)(Fattr1->attr_vals.attrlist4_val + LastOffset), sizeof(u_int)); if (memcmp((char *)(Fattr1->attr_vals.attrlist4_val + LastOffset), (char *)(Fattr2->attr_vals.attrlist4_val + LastOffset), sizeof(u_int)) != 0) { LogFullDebug( COMPONENT_NFS_V4, "Attr %s wrong len expected %u got %u", fattr4tab[attr1].name, len, *((u_int *)(Fattr2->attr_vals .attrlist4_val + LastOffset))); return 0; } len = ntohl(len); /* xdr marshalling on fattr4 */ LastOffset += sizeof(u_int); if (memcmp((char *)(Fattr1->attr_vals.attrlist4_val + LastOffset), (char *)(Fattr2->attr_vals.attrlist4_val + LastOffset), len) != 0) { LogFullDebug(COMPONENT_NFS_V4, "Wrong value for %s", fattr4tab[attr1].name); return 0; } LastOffset += len; break; case FATTR4_TYPE: case FATTR4_FH_EXPIRE_TYPE: case FATTR4_CHANGE: case FATTR4_SIZE: case FATTR4_LINK_SUPPORT: case FATTR4_SYMLINK_SUPPORT: case FATTR4_NAMED_ATTR: case FATTR4_FSID: case FATTR4_UNIQUE_HANDLES: case FATTR4_LEASE_TIME: case FATTR4_RDATTR_ERROR: case FATTR4_ACL: case FATTR4_ACLSUPPORT: case FATTR4_ARCHIVE: case FATTR4_CANSETTIME: case FATTR4_CASE_INSENSITIVE: case FATTR4_CASE_PRESERVING: case FATTR4_CHOWN_RESTRICTED: case FATTR4_FILEID: case FATTR4_FILES_AVAIL: case FATTR4_FILES_FREE: case FATTR4_FILES_TOTAL: case FATTR4_FS_LOCATIONS: case FATTR4_HIDDEN: case FATTR4_HOMOGENEOUS: case FATTR4_MAXFILESIZE: case FATTR4_MAXLINK: case FATTR4_MAXNAME: case FATTR4_MAXREAD: case FATTR4_MAXWRITE: case FATTR4_MIMETYPE: case FATTR4_MODE: case FATTR4_NO_TRUNC: case FATTR4_NUMLINKS: case FATTR4_QUOTA_AVAIL_HARD: case FATTR4_QUOTA_AVAIL_SOFT: case FATTR4_QUOTA_USED: case FATTR4_RAWDEV: case FATTR4_SPACE_AVAIL: case FATTR4_SPACE_FREE: case FATTR4_SPACE_TOTAL: case FATTR4_SPACE_USED: case FATTR4_SYSTEM: case FATTR4_TIME_ACCESS: case FATTR4_TIME_ACCESS_SET: case FATTR4_TIME_BACKUP: case FATTR4_TIME_CREATE: case FATTR4_TIME_DELTA: case FATTR4_TIME_METADATA: case FATTR4_TIME_MODIFY: case FATTR4_TIME_MODIFY_SET: case FATTR4_MOUNTED_ON_FILEID: case FATTR4_XATTR_SUPPORT: /* These are fixed size */ if (memcmp((char *)(Fattr1->attr_vals.attrlist4_val + LastOffset), (char *)(Fattr2->attr_vals.attrlist4_val + LastOffset), fattr4tab[attr1].size_fattr4) != 0) { LogFullDebug(COMPONENT_NFS_V4, "Wrong value for %s", fattr4tab[attr1].name); return 0; } LastOffset += fattr4tab[attr1].size_fattr4; break; default: LogFullDebug(COMPONENT_NFS_V4, "unknown attribute %d", attr1); return 0; } attr1 = next_attr_from_bitmap(&Fattr1->attrmask, attr1); attr2 = next_attr_from_bitmap(&Fattr2->attrmask, attr2); } return 1; } /** * * @brief Converts NFSv4 attributes buffer to a FSAL attributes structure. * * Converts NFSv4 attributes buffer to a FSAL attributes structure. * * NB! If the pointer for the handle is provided the memory is not allocated, * the handle's nfs_fh4_val points inside fattr4. The pointer is valid * as long as fattr4 is valid. * * @param[out] attrs pointer to FSAL attributes. * @param[in] Fattr pointer to NFSv4 attributes. * @param[out] hdl4 optional pointer to return NFSv4 file handle * @param[out] dinfo optional pointer to return 'dynamic info' about FS * * @return NFS4_OK if successful, NFS4ERR codes if not. * */ static int Fattr4_To_FSAL_attr(struct fsal_attrlist *attrs, fattr4 *Fattr, nfs_fh4 *hdl4, fsal_dynamicfsinfo_t *dinfo, compound_data_t *data) { int attribute_to_set = next_attr_from_bitmap(&Fattr->attrmask, -1); int nfs_status = NFS4_OK; XDR attr_body; struct xdr_attrs_args args; fattr_xdr_result xdr_res; /* Check attributes data */ if ((Fattr->attr_vals.attrlist4_val == NULL) || (Fattr->attr_vals.attrlist4_len == 0)) return attribute_to_set == -1 ? NFS4_OK : NFS4ERR_BADXDR; /* Init */ memset(&attr_body, 0, sizeof(attr_body)); xdrmem_create(&attr_body, Fattr->attr_vals.attrlist4_val, Fattr->attr_vals.attrlist4_len, XDR_DECODE); if (attrs) attrs->valid_mask = 0; memset(&args, 0, sizeof(args)); args.attrs = attrs; args.hdl4 = hdl4; if (dinfo) { args.dynamicinfo = *dinfo; } args.nfs_status = NFS4_OK; args.data = data; for (attribute_to_set = next_attr_from_bitmap(&Fattr->attrmask, -1); attribute_to_set != -1; attribute_to_set = next_attr_from_bitmap(&Fattr->attrmask, attribute_to_set)) { const struct fattr4_dent *f4e; if (attribute_to_set > FATTR4_MAX_ATTR_INDEX) { /* Undefined attribute */ nfs_status = NFS4ERR_ATTRNOTSUPP; goto decodeerr; } f4e = fattr4tab + attribute_to_set; xdr_res = f4e->decode(&attr_body, &args); if (xdr_res == FATTR_XDR_SUCCESS) { if (attrs) attrs->valid_mask |= f4e->attrmask; LogFullDebug(COMPONENT_NFS_V4, "Decode attr %d, name = %s", attribute_to_set, f4e->name); } else if (xdr_res == FATTR_XDR_SUCCESS_EXP) { /* Since we are setting a time attribute to * server time, we must use a different mask * position in attrmask_t. */ if (attrs) attrs->valid_mask |= f4e->exp_attrmask; LogFullDebug(COMPONENT_NFS_V4, "Decode (exp) attr %d, name = %s", attribute_to_set, f4e->name); } else if (xdr_res == FATTR_XDR_NOOP) { LogFullDebug(COMPONENT_NFS_V4, "Attr not supported %d name=%s", attribute_to_set, f4e->name); if (nfs_status == NFS4_OK) { /* preserve previous error */ nfs_status = NFS4ERR_ATTRNOTSUPP; } goto decodeerr; } else { LogFullDebug(COMPONENT_NFS_V4, "Decode attr FAILED: %d, name = %s", attribute_to_set, f4e->name); if (args.nfs_status == NFS4_OK) nfs_status = NFS4ERR_BADXDR; else nfs_status = args.nfs_status; goto decodeerr; } } if (xdr_getpos(&attr_body) < Fattr->attr_vals.attrlist4_len) nfs_status = NFS4ERR_BADXDR; /* underrun on attribute */ decodeerr: xdr_destroy(&attr_body); return nfs_status; } /** * * @brief Converts NFSv4 attributes mask to a FSAL attribute mask. * * @param[in] bitmap4 Requested attributes * @param[out] mask FSAL attribute mask * * @return NFS4_OK if successful, NFS4ERR codes if not. * */ int bitmap4_to_attrmask_t(bitmap4 *bitmap4, attrmask_t *mask) { int attribute_to_set; int nfs_status = NFS4_OK; *mask = 0; for (attribute_to_set = next_attr_from_bitmap(bitmap4, -1); attribute_to_set != -1; attribute_to_set = next_attr_from_bitmap(bitmap4, attribute_to_set)) { const struct fattr4_dent *f4e; if (attribute_to_set > FATTR4_MAX_ATTR_INDEX) { /* Undefined attrbute */ nfs_status = NFS4ERR_ATTRNOTSUPP; break; } f4e = fattr4tab + attribute_to_set; *mask |= f4e->attrmask; LogFullDebug(COMPONENT_NFS_V4, "Request attr %d, name = %s", attribute_to_set, f4e->name); } return nfs_status; } /** * @brief Convert NFSv4 attribute buffer to an FSAL attribute list * * @param[out] FSAL_attr FSAL attributes * @param[in] Fattr NFSv4 attributes * @param[in] data Compound data * * @return NFS4_OK if successful, NFS4ERR codes if not. * */ int nfs4_Fattr_To_FSAL_attr(struct fsal_attrlist *FSAL_attr, fattr4 *Fattr, compound_data_t *data) { memset(FSAL_attr, 0, sizeof(struct fsal_attrlist)); return Fattr4_To_FSAL_attr(FSAL_attr, Fattr, NULL, NULL, data); } /** * * nfs4_Fattr_To_fsinfo: Decode filesystem info out of NFSv4 attributes. * * Converts information encoded in NFSv4 attributes buffer to 'dynamic info' * about an exported filesystem. * * It is assumed and expected that the fattr4 blob is not * going to have any other attributes expect those necessary * to fill in details about sace and inode utilization. * * @param dinfo [OUT] pointer to dynamic info * @param Fattr [IN] pointer to NFSv4 attributes. * * @return NFS4_OK if successful, NFS4ERR codes if not. * */ int nfs4_Fattr_To_fsinfo(fsal_dynamicfsinfo_t *dinfo, fattr4 *Fattr) { return Fattr4_To_FSAL_attr(NULL, Fattr, NULL, dinfo, NULL); } /** * @brief: is a directory's sticky bit set? * */ bool is_sticky_bit_set(struct fsal_obj_handle *obj, const struct fsal_attrlist *attr) { if (attr->mode & (S_IXUSR | S_IXGRP | S_IXOTH)) return false; if (!(attr->mode & S_ISVTX)) return false; LogDebug(COMPONENT_NFS_V4, "sticky bit is set on %" PRIi64, obj->fileid); return true; } #define COMPOUND_EXTRA_ROOM 4096 static inline uint32_t default_max_resp_room(void) { /* allow enough room for a compound like PUTFH, LOOKUP, OPEN, READ * with MaxRead read data (cannot be greater than XDR_BYTES_MAXLEN_IO) */ return XDR_BYTES_MAXLEN_IO + COMPOUND_EXTRA_ROOM; } uint32_t resp_room(compound_data_t *data) { uint32_t room; if (data->minorversion == 0 || data->session == NULL) { return default_max_resp_room(); } /* Start with max response size */ room = data->session->fore_channel_attrs.ca_maxresponsesize; /* Now subtract the space for the opnum4 for this response plus at * least one more opnum and status. */ room -= sizeof(nfs_opnum4) + sizeof(nfs_opnum4) + sizeof(nfsstat4); return room; } nfsstat4 check_resp_room(compound_data_t *data, uint32_t op_resp_size) { nfsstat4 status; uint32_t test_response_size = data->resp_size + sizeof(nfs_opnum4) + op_resp_size + sizeof(nfs_opnum4) + sizeof(nfsstat4); if (data->minorversion == 0 || data->session == NULL) { if (test_response_size > default_max_resp_room()) { return NFS4ERR_RESOURCE; } else { return NFS4_OK; } } /* Check that op_resp_size plus nfs_opnum4 plus at least another * response (nfs_opnum4 plus nfsstat4) fits. */ if (test_response_size > data->session->fore_channel_attrs.ca_maxresponsesize) { /* Response is larger than maximum response size. */ status = NFS4ERR_REP_TOO_BIG; goto err; } if (!data->sa_cachethis) { /* Response size is ok, and not cached. */ goto ok; } /* Check that op_resp_size plus nfs_opnum4 plus at least another * response (nfs_opnum4 plus nfsstat4) fits. */ if (test_response_size > data->session->fore_channel_attrs.ca_maxresponsesize_cached) { /* Response is too big to cache. */ status = NFS4ERR_REP_TOO_BIG_TO_CACHE; goto err; } /* Response is ok to cache. */ ok: LogFullDebug( COMPONENT_NFS_V4, "Status of %s in position %d is ok so far, op response size = %" PRIu32 " total response size would be = %" PRIu32 " out of max %" PRIu32 "/%" PRIu32, data->opname, data->oppos, op_resp_size, test_response_size, data->session->fore_channel_attrs.ca_maxresponsesize, data->session->fore_channel_attrs.ca_maxresponsesize_cached); return NFS4_OK; err: LogDebug( COMPONENT_NFS_V4, "Status of %s in position %d is %s, op response size = %" PRIu32 " total response size would have been = %" PRIu32 " out of max %" PRIu32 "/%" PRIu32, data->opname, data->oppos, nfsstat4_to_str(status), op_resp_size, test_response_size, data->session->fore_channel_attrs.ca_maxresponsesize, data->session->fore_channel_attrs.ca_maxresponsesize_cached); return status; } #ifdef USE_NFSACL3 /** * @brief Conversion the posix acl to NFSACL rpc struct * * @param[in] acl Posix ACL * @param[in] type Represents type of posix acl( ACCESS/DEFAULT ) * @param[in] attrs Attribute list for file * @param[out] posix_acl Point to Posix ACL rpc transmission structure * */ posix_acl *encode_posix_acl(const acl_t acl, uint32_t type, struct fsal_attrlist *attrs) { acl_entry_t acl_entry; acl_tag_t tag; acl_permset_t permset; size_t real_size; int count; int entry_id = 0; int ret = 0; posix_acl *encode_acl; posix_acl_entry *encode_acl_e; count = acl_entries(acl); if (count < 0) { LogDebug(COMPONENT_NFSPROTO, "The acl is not a valid pointer to an ACL."); return NULL; } real_size = sizeof(struct posix_acl) + count * sizeof(struct posix_acl_entry); encode_acl = gsh_malloc(real_size); if (!encode_acl) return NULL; encode_acl->count = (uint32_t)count; encode_acl_e = encode_acl->entries; for (entry_id = ACL_FIRST_ENTRY;; entry_id = ACL_NEXT_ENTRY, encode_acl_e++) { ret = acl_get_entry(acl, entry_id, &acl_entry); if (ret == 0 || ret == -1) { LogDebug(COMPONENT_NFSPROTO, "No more ACL entries remaining"); break; } if (acl_get_tag_type(acl_entry, &tag) == -1) { LogWarn(COMPONENT_NFSPROTO, "No entry tag for ACL Entry"); continue; } ret = acl_get_permset(acl_entry, &permset); if (ret) { LogWarn(COMPONENT_NFSPROTO, "Cannot retrieve permission set for the ACL Entry"); continue; } encode_acl_e->e_tag = tag; encode_acl_e->e_perm = 0; if (acl_get_perm(permset, ACL_READ)) encode_acl_e->e_perm |= ACL_READ; if (acl_get_perm(permset, ACL_WRITE)) encode_acl_e->e_perm |= ACL_WRITE; if (acl_get_perm(permset, ACL_EXECUTE)) encode_acl_e->e_perm |= ACL_EXECUTE; switch (tag) { case ACL_USER_OBJ: encode_acl_e->e_id = attrs->owner; break; case ACL_GROUP_OBJ: encode_acl_e->e_id = attrs->group; break; case ACL_MASK: case ACL_OTHER: encode_acl_e->e_id = 0; break; case ACL_USER: encode_acl_e->e_id = posix_acl_get_uid(acl_entry); break; case ACL_GROUP: encode_acl_e->e_id = posix_acl_get_gid(acl_entry); break; default: encode_acl_e->e_id = ACL_UNDEFINED_ID; break; } if (type == ACL_TYPE_DEFAULT) encode_acl_e->e_tag |= NFS_DFACL_MASK; } return encode_acl; } /** * @brief convert ACL rpc struct into equivalent posix acl * * * @param[in] posix_acl Point to Posix ACL rpc transmission structure * @param[in] type Represents type of posix acl( ACCESS/DEFAULT ) * @param[out] acl Posix ACL * */ acl_t decode_posix_acl(posix_acl *nfs3_acl, uint32_t type) { posix_acl_entry *entry = nfs3_acl->entries, *end; int count; int ret = 0; acl_t acl = NULL; acl_entry_t acl_entry; acl_tag_t tag; acl_permset_t permset; uid_t uid; gid_t gid; count = nfs3_acl->count; if (!nfs3_acl->count) { LogDebug(COMPONENT_NFSPROTO, "No entries present in posix_acl"); return NULL; } acl = acl_init(count); if (!acl) { LogMajor(COMPONENT_NFSPROTO, "Failed to ACL INIT: count = %d", count); return NULL; } for (end = entry + count; entry != end; entry++) { ret = acl_create_entry(&acl, &acl_entry); if (ret) { LogMajor(COMPONENT_FSAL, "Failed to create acl entry"); goto out; } tag = entry->e_tag; if (type == ACL_TYPE_DEFAULT) tag = NFS_ACL_MASK & tag; ret = acl_set_tag_type(acl_entry, tag); if (ret) { LogMajor(COMPONENT_FSAL, "Failed to set acl tag type"); goto out; } ret = acl_get_permset(acl_entry, &permset); if (ret) { LogWarn(COMPONENT_FSAL, "Failed to get acl permset"); goto out; } ret = acl_add_perm(permset, entry->e_perm); if (ret) { LogWarn(COMPONENT_FSAL, "Failed to add acl permission"); goto out; } switch (tag) { case ACL_USER_OBJ: case ACL_GROUP_OBJ: case ACL_MASK: case ACL_OTHER: break; case ACL_USER: uid = entry->e_id; ret = acl_set_qualifier(acl_entry, &uid); if (ret) { LogMajor(COMPONENT_FSAL, "Failed to set uid"); goto out; } break; case ACL_GROUP: gid = entry->e_id; ret = acl_set_qualifier(acl_entry, &gid); if (ret) { LogMajor(COMPONENT_FSAL, "Failed to set gid"); goto out; } break; default: LogDebug(COMPONENT_FSAL, "Undefined ACL type"); goto out; } } return acl; out: if (acl) { acl_free((void *)acl); } return NULL; } /* * @brief convert ACL rpc struct into an equivalent FSAL ACL * * @param[in] attrs NFSv3 attr to be set. * @param[in] mask Mask of acl( ACCESS/DEFAULT ) and count * @param[in] a_acl Point to access Posix ACL rpc transmission structure * @param[in] d_acl Point to default Posix ACL rpc transmission structure * @param[in] is_dir Represents file/directory * @param[out] rc errno * * @returns 0 on success and -Errno on failure */ int nfs3_acl_2_fsal_acl(struct fsal_attrlist *attr, nfs3_int32 mask, posix_acl *a_acl, posix_acl *d_acl, bool is_dir) { acl_t e_acl = NULL, i_acl = NULL; fsal_acl_data_t acldata; fsal_ace_t *pace = NULL; fsal_acl_status_t aclstatus; int e_count = 0, i_count = 0, new_count = 0, new_i_count = 0; int rc = 0; attr->valid_mask = 0; /* Decode access acl */ if (mask & (NFS_ACL | NFS_ACLCNT)) { e_acl = decode_posix_acl(a_acl, ACL_TYPE_ACCESS); if (!e_acl) { LogMajor(COMPONENT_NFSPROTO, "failed to decode access posix acl"); rc = -EINVAL; goto out; } e_count = ace_count(e_acl); } /* Decode default acl */ if (is_dir && (mask & (NFS_DFACL | NFS_DFACLCNT)) && d_acl->count) { i_acl = decode_posix_acl(d_acl, ACL_TYPE_DEFAULT); if (!i_acl) { LogMajor(COMPONENT_NFSPROTO, "failed to decode default posix acl"); rc = -EINVAL; goto out; } i_count = ace_count(i_acl); } acldata.naces = 2 * (e_count + i_count); LogDebug(COMPONENT_NFSPROTO, "No of aces present in fsal_acl_t = %d", acldata.naces); if (!acldata.naces) { rc = 0; goto out; } acldata.aces = (fsal_ace_t *)nfs4_ace_alloc(acldata.naces); pace = acldata.aces; if (e_count > 0) { new_count = posix_acl_2_fsal_acl(e_acl, is_dir, false, ACL_FOR_V3, &pace); } else { LogDebug(COMPONENT_NFSPROTO, "No acl set for access acl"); } if (i_count > 0) { new_i_count = posix_acl_2_fsal_acl(i_acl, true, true, ACL_FOR_V3, &pace); new_count += new_i_count; } else { LogDebug(COMPONENT_NFSPROTO, "No acl set for default acl"); } /* Reallocating acldata into the required size */ acldata.aces = (fsal_ace_t *)gsh_realloc( acldata.aces, new_count * sizeof(fsal_ace_t)); acldata.naces = new_count; //Cache? attr->acl = nfs4_acl_new_entry(&acldata, &aclstatus); if (attr->acl == NULL) { LogCrit(COMPONENT_NFSPROTO, "failed to create a new acl entry"); rc = -EFAULT; goto out; } rc = 0; attr->valid_mask |= ATTR_ACL; out: if (e_acl) { acl_free((void *)e_acl); } if (i_acl) { acl_free((void *)i_acl); } return rc; } #endif /* USE_NFSACL3 */ nfs-ganesha-6.5/src/Protocols/NFSACL/000077500000000000000000000000001473756622300173165ustar00rootroot00000000000000nfs-ganesha-6.5/src/Protocols/NFSACL/CMakeLists.txt000066400000000000000000000026711473756622300220640ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- ########### next target ############### SET(nfsacl_STAT_SRCS nfsacl_Null.c nfsacl_getacl.c nfsacl_setacl.c ) add_library(nfsacl OBJECT ${nfsacl_STAT_SRCS}) add_sanitizers(nfsacl) set_target_properties(nfsacl PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(nfsacl gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) ########### install files ############### nfs-ganesha-6.5/src/Protocols/NFSACL/nfsacl_Null.c000066400000000000000000000035511473756622300217260ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright ZTE Corporation, 2019 * Author: Muyao Luo(luo.muyaog@zte.com.cn) * * -------------------------- * * This program 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 3 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 * 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 */ #include "config.h" #include #include #include #include #include #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs23.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_exports.h" #include "mount.h" #include "nfsacl.h" #include "nfs_proto_functions.h" /** * @brief The NFSACL proc null function, for all versions. * * @param[in] arg Ignored * @param[in] req Ignored * @param[out] res Ignored */ int nfsacl_Null(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { LogFullDebug(COMPONENT_NFSPROTO, "REQUEST PROCESSING: Calling NFSACL_NULL"); /* 0 is success */ return 0; } /** * nfsacl_Null_Free: Frees the result structure allocated for nfsacl_Null * * Frees the result structure allocated for nfsacl_Null. Does Nothing in fact. * * @param res [INOUT] Pointer to the result structure. * */ void nfsacl_Null_Free(nfs_res_t *res) { /* Nothing to do */ } nfs-ganesha-6.5/src/Protocols/NFSACL/nfsacl_getacl.c000066400000000000000000000132301473756622300222460ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright ZTE Corporation, 2020 * Author: Muyao Luo (luo.muyao@zte.com.cn) * * -------------------------- * * This program 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 3 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 * 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 */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs23.h" #include "nfs4.h" #include "mount.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_proto_tools.h" #include "export_mgr.h" #include "nfs_proto_data.h" #include "nfsacl.h" /** * @brief The NFSACL getacl function. * * @param[in] arg NFS GETACL arguments union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable * */ int nfsacl_getacl(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { #ifdef USE_NFSACL3 struct fsal_obj_handle *obj = NULL; int rc = NFS_REQ_OK; struct fsal_attrlist *attrs; fsal_status_t status; acl_t acl = NULL; acl_t d_acl = NULL; posix_acl *encode_acl = NULL; posix_acl *encode_df_acl = NULL; attrs = &res->res_getacl.getaclres_u.resok.attr.attr3_u.obj_attributes; LogNFSACL_Operation(COMPONENT_NFSPROTO, req, &arg->arg_getacl.fhandle, ""); fsal_prepare_attrs(attrs, ATTRS_NFS3_ACL); obj = nfs3_FhandleToCache(&arg->arg_getacl.fhandle, &res->res_getacl.status, &rc); if (obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ LogFullDebug(COMPONENT_NFSPROTO, "nfs_Getacl returning %d", rc); goto out; } /*Get fsal attr&acl*/ status = obj->obj_ops->getattrs(obj, attrs); if (FSAL_IS_ERROR(status)) { res->res_getacl.status = nfs3_Errno_status(status); LogFullDebug(COMPONENT_NFSPROTO, "nfsacl_Getacl set failed status v3"); rc = NFS_REQ_OK; goto out; } /*Set attributes_follow*/ res->res_getacl.getaclres_u.resok.attr.attributes_follow = TRUE; /* Set Mask*/ if (arg->arg_getacl.mask & ~(NFS_ACL | NFS_ACLCNT | NFS_DFACL | NFS_DFACLCNT)) { res->res_getacl.status = nfs3_Errno_status(status); LogFullDebug(COMPONENT_NFSPROTO, "Invalid args"); rc = NFS_REQ_OK; goto out; } res->res_getacl.getaclres_u.resok.mask = arg->arg_getacl.mask; /*Get access acl*/ if (arg->arg_getacl.mask & (NFS_ACL | NFS_ACLCNT)) { acl = fsal_acl_2_posix_acl(attrs->acl, ACL_TYPE_ACCESS); if (acl == NULL) { LogFullDebug(COMPONENT_NFSPROTO, "Access ACL is NULL"); res->res_getacl.getaclres_u.resok.acl_access = NULL; res->res_getacl.getaclres_u.resok.acl_access_count = 0; } else if (acl_valid(acl) != 0) { LogWarn(COMPONENT_FSAL, "failed to convert fsal acl to Access posix acl"); status = fsalstat(ERR_FSAL_FAULT, 0); goto error; } else { encode_acl = encode_posix_acl(acl, ACL_TYPE_ACCESS, attrs); if (encode_acl == NULL) { LogFullDebug(COMPONENT_NFSPROTO, "encode_posix_acl return NULL"); status = fsalstat(ERR_FSAL_FAULT, 0); goto error; } res->res_getacl.getaclres_u.resok.acl_access = encode_acl; res->res_getacl.getaclres_u.resok.acl_access_count = encode_acl->count; } } /*Get default acl*/ if (arg->arg_getacl.mask & (NFS_DFACL | NFS_DFACLCNT)) { d_acl = fsal_acl_2_posix_acl(attrs->acl, ACL_TYPE_DEFAULT); if (d_acl == NULL) { LogFullDebug(COMPONENT_NFSPROTO, "Default ACL is NULL"); res->res_getacl.getaclres_u.resok.acl_default = NULL; res->res_getacl.getaclres_u.resok.acl_default_count = 0; } else if (acl_valid(d_acl) != 0) { LogWarn(COMPONENT_FSAL, "failed to convert fsal acl to Default posix acl"); status = fsalstat(ERR_FSAL_FAULT, 0); goto error; } else { encode_df_acl = encode_posix_acl( d_acl, ACL_TYPE_DEFAULT, attrs); if (encode_df_acl == NULL) { LogFullDebug(COMPONENT_NFSPROTO, "encode_posix_acl return NULL"); status = fsalstat(ERR_FSAL_FAULT, 0); goto error; } res->res_getacl.getaclres_u.resok.acl_default = encode_df_acl; res->res_getacl.getaclres_u.resok.acl_default_count = encode_df_acl->count; } } nfs3_Fixup_FSALattr(obj, attrs); res->res_getacl.status = NFS3_OK; LogFullDebug(COMPONENT_NFSPROTO, "nfs_Getacl succeeded"); rc = NFS_REQ_OK; out: /* Done with the attrs (NFSv3 doesn't use ANY of the reffed attributes) */ fsal_release_attrs(attrs); /* return references */ if (obj) obj->obj_ops->put_ref(obj); if (acl) acl_free(acl); if (d_acl) acl_free(d_acl); return rc; error: rc = NFS_REQ_OK; res->res_getacl.status = nfs3_Errno_status(status); goto out; #else return 0; #endif /* USE_NFSACL3 */ } /* nfsacl_getacl */ /** * @brief Free the result structure allocated for nfsacl_getacl * * * @param[in,out] res Pointer to the result structure. */ void nfsacl_getacl_Free(nfs_res_t *res) { /* Nothing to do */ } nfs-ganesha-6.5/src/Protocols/NFSACL/nfsacl_setacl.c000066400000000000000000000123011473756622300222600ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright ZTE Corporation, 2019 * Author: Muyao Luo (luo.muyao@zte.com.cn) * * This program 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 3 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 * 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 */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs23.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_exports.h" #include "mount.h" #include "nfs_creds.h" #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_proto_tools.h" #include "sal_functions.h" #include "nfsacl.h" /** * @brief The NFSACL v3 setacl function, for all versions. * * @param[in] arg NFS SETACL arguments union * @param[in] req SVC request related to this call * @param[out] res Structure to contain the result of the call * * @retval NFS_REQ_OK if successful * @retval NFS_REQ_DROP if failed but retryable * @retval NFS_REQ_FAILED if failed and not retryable */ int nfsacl_setacl(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { #ifdef USE_NFSACL3 struct fsal_attrlist setacl; struct fsal_attrlist *attrs; struct fsal_obj_handle *obj = NULL; fsal_status_t fsal_status = { 0, 0 }; int rc = NFS_REQ_OK; int ret = 0; bool is_dir = FALSE; /* to avoid setting it on each error case */ res->res_setacl.setaclres_u.resok.attr.attributes_follow = FALSE; attrs = &res->res_setacl.setaclres_u.resok.attr.attr3_u.obj_attributes; memset(&setacl, 0, sizeof(setacl)); LogNFSACL_Operation(COMPONENT_NFSPROTO, req, &arg->arg_setacl.fhandle, ""); fsal_prepare_attrs(attrs, ATTRS_NFS3); obj = nfs3_FhandleToCache(&arg->arg_setacl.fhandle, &res->res_setacl.status, &rc); if (obj == NULL) { /* Status and rc have been set by nfs3_FhandleToCache */ LogFullDebug(COMPONENT_NFSPROTO, "nfs3_FhandleToCache failed"); goto out; } /* Whether obj is a directory */ if (obj->type == DIRECTORY) is_dir = TRUE; if (arg->arg_setacl.acl_access == NULL && (!is_dir || arg->arg_setacl.acl_default == NULL)) { res->res_setacl.status = NFS3ERR_INVAL; rc = NFS_REQ_OK; LogFullDebug(COMPONENT_FSAL, "nfs3 setacl failed for invalid parameter"); goto out; } /* Conversion to FSAL ACL */ ret = nfs3_acl_2_fsal_acl(&setacl, arg->arg_setacl.mask, arg->arg_setacl.acl_access, arg->arg_setacl.acl_default, is_dir); if (ret) { res->res_setacl.status = NFS3ERR_INVAL; rc = NFS_REQ_OK; LogFullDebug(COMPONENT_FSAL, "nfs3_acl_2_fsal_acl failed"); goto out; } if (setacl.valid_mask != 0) { /* Don't allow attribute change while we are in grace period. * Required for delegation reclaims and may be needed for other * reclaimable states as well. No NFS4ERR_GRACE in NFS v3, so * send jukebox error. */ if (!nfs_get_grace_status(false)) { res->res_setacl.status = NFS3ERR_JUKEBOX; rc = NFS_REQ_OK; LogFullDebug(COMPONENT_NFSPROTO, "nfs_in_grace is true"); goto out; } /* For now we don't look for states, so indicate bypass so * we will get through an NLM_SHARE with deny. */ fsal_status = fsal_setattr(obj, true, NULL, &setacl); nfs_put_grace_status(); if (FSAL_IS_ERROR(fsal_status)) { res->res_setacl.status = nfs3_Errno_status(fsal_status); LogFullDebug(COMPONENT_NFSPROTO, "fsal_setacl failed"); goto out_fail; } } /*Get fsal attr*/ fsal_status = obj->obj_ops->getattrs(obj, attrs); if (FSAL_IS_ERROR(fsal_status)) { res->res_setacl.status = nfs3_Errno_status(fsal_status); LogFullDebug(COMPONENT_NFSPROTO, "nfsacl_Setacl get attr failed"); rc = NFS_REQ_OK; goto out; } /* Set the NFS return */ res->res_setacl.status = NFS3_OK; res->res_getacl.getaclres_u.resok.attr.attributes_follow = TRUE; LogFullDebug(COMPONENT_NFSPROTO, "nfsacl_Setacl set attributes_follow to TRUE"); rc = NFS_REQ_OK; out: /* Release the attributes (may release an inherited ACL) */ fsal_release_attrs(&setacl); /* return references */ if (obj) obj->obj_ops->put_ref(obj); LogDebug(COMPONENT_NFSPROTO, "Set acl Result %s%s", nfsstat3_to_str(res->res_setacl.status), rc == NFS_REQ_DROP ? " Dropping response" : ""); return rc; out_fail: if (nfs_RetryableError(fsal_status.major)) { /* Drop retryable request. */ rc = NFS_REQ_DROP; } goto out; #else return 0; #endif /* USE_NFSACL3 */ } /** * @brief Frees the result structure allocated for nfsacl_setacl * * @param[in,out] res Pointer to the result structure. * */ void nfsacl_setacl_Free(nfs_res_t *res) { /* Nothing to do */ } nfs-ganesha-6.5/src/Protocols/NLM/000077500000000000000000000000001473756622300167765ustar00rootroot00000000000000nfs-ganesha-6.5/src/Protocols/NLM/CMakeLists.txt000066400000000000000000000030711473756622300215370ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- ########### next target ############### SET(nlm_STAT_SRCS nlm_Cancel.c nlm_Free_All.c nlm_Granted_Res.c nlm_Lock.c nlm_Null.c nlm_Share.c nlm_Sm_Notify.c nlm_Test.c nlm_Unlock.c nlm_Unshare.c nlm_async.c nlm_util.c nsm.c ) add_library(nlm OBJECT ${nlm_STAT_SRCS}) add_sanitizers(nlm) set_target_properties(nlm PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(nlm gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) ########### install files ############### nfs-ganesha-6.5/src/Protocols/NLM/nlm_Cancel.c000066400000000000000000000133451473756622300212030ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2010 * Contributor: Aneesh Kumar K.v * * -------------------------- * * This program 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 3 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 * 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 * * */ #include "config.h" #include #include #include #include "log.h" #include "fsal.h" #include "nfs_proto_functions.h" #include "sal_functions.h" #include "nlm_util.h" #include "nlm_async.h" /** * @brief Cancel a blocked range lock * * @param[in] arg * @param[in] req * @param[out] res * */ int nlm4_Cancel(nfs_arg_t *args, struct svc_req *req, nfs_res_t *res) { nlm4_cancargs *arg = &args->arg_nlm4_cancel; struct fsal_obj_handle *obj; state_status_t state_status = STATE_SUCCESS; char buffer[MAXNETOBJ_SZ * 2] = "\0"; state_nsm_client_t *nsm_client; state_nlm_client_t *nlm_client; state_owner_t *nlm_owner; fsal_lock_param_t lock; int rc; /* NLM doesn't have a BADHANDLE error, nor can rpc_execute deal with * responding to an NLM_*_MSG call, so we check here if the export is * NULL and if so, handle the response. */ if (op_ctx->ctx_export == NULL) { res->res_nlm4.stat.stat = NLM4_STALE_FH; LogInfo(COMPONENT_NLM, "INVALID HANDLE: NLM4_CANCEL"); return NFS_REQ_OK; } netobj_to_string(&arg->cookie, buffer, 1024); LogDebug( COMPONENT_NLM, "REQUEST PROCESSING: Calling NLM4_CANCEL svid=%d off=%llx len=%llx cookie=%s", (int)arg->alock.svid, (unsigned long long)arg->alock.l_offset, (unsigned long long)arg->alock.l_len, buffer); copy_netobj(&res->res_nlm4test.cookie, &arg->cookie); if (!nfs_get_grace_status(false)) { res->res_nlm4.stat.stat = NLM4_DENIED_GRACE_PERIOD; LogDebug(COMPONENT_NLM, "REQUEST RESULT: NLM4_CANCEL %s", lock_result_str(res->res_nlm4.stat.stat)); return NFS_REQ_OK; } /* cancel doesn't care if owner is found */ rc = nlm_process_parameters(req, arg->exclusive, &arg->alock, &lock, &obj, CARE_NOT, &nsm_client, &nlm_client, &nlm_owner, NULL, 0, NULL); if (rc >= 0) { /* resent the error back to the client */ res->res_nlm4.stat.stat = (nlm4_stats)rc; LogDebug(COMPONENT_NLM, "REQUEST RESULT: nlm4_Unlock %s", lock_result_str(res->res_nlm4.stat.stat)); goto out; } state_status = state_cancel(obj, nlm_owner, &lock); if (state_status != STATE_SUCCESS) { /* Cancel could fail in the FSAL and make a bit of a mess, * especially if we are in out of memory situation. Such an * error is already logged. */ res->res_nlm4test.test_stat.stat = nlm_convert_state_error(state_status); } else { res->res_nlm4.stat.stat = NLM4_GRANTED; } /* Release the NLM Client and NLM Owner references we have */ dec_nsm_client_ref(nsm_client); dec_nlm_client_ref(nlm_client); dec_state_owner_ref(nlm_owner); obj->obj_ops->put_ref(obj); LogDebug(COMPONENT_NLM, "REQUEST RESULT: NLM4_CANCEL %s", lock_result_str(res->res_nlm4.stat.stat)); out: nfs_put_grace_status(); return NFS_REQ_OK; } /* nlm4_Cancel */ static void nlm4_cancel_message_resp(state_async_queue_t *arg) { state_nlm_async_data_t *nlm_arg = &arg->state_async_data.state_nlm_async_data; nfs_res_t *res = &nlm_arg->nlm_async_args.nlm_async_res; if (isFullDebug(COMPONENT_NLM)) { char buffer[1024] = "\0"; netobj_to_string(&res->res_nlm4test.cookie, buffer, 1024); LogFullDebug(COMPONENT_NLM, "Calling nlm_send_async cookie=%s status=%s", buffer, lock_result_str(res->res_nlm4.stat.stat)); } nlm_send_async(NLMPROC4_CANCEL_RES, nlm_arg->nlm_async_host, res, NULL); nlm4_Cancel_Free(res); dec_nsm_client_ref(nlm_arg->nlm_async_host->slc_nsm_client); dec_nlm_client_ref(nlm_arg->nlm_async_host); gsh_free(arg); } /* Asynchronous Message Entry Point */ /** * @brief Cancel Lock Message * * @param[in] arg * @param[in] req * @param[out] res * */ int nlm4_Cancel_Message(nfs_arg_t *args, struct svc_req *req, nfs_res_t *res) { state_nlm_client_t *nlm_client = NULL; state_nsm_client_t *nsm_client; nlm4_cancargs *arg = &args->arg_nlm4_cancel; int rc = NFS_REQ_OK; LogDebug(COMPONENT_NLM, "REQUEST PROCESSING: Calling nlm_Cancel_Message"); nsm_client = get_nsm_client(CARE_NO_MONITOR, arg->alock.caller_name); if (nsm_client != NULL) nlm_client = get_nlm_client(CARE_NO_MONITOR, req->rq_xprt, nsm_client, arg->alock.caller_name); if (nlm_client == NULL) rc = NFS_REQ_DROP; else rc = nlm4_Cancel(args, req, res); if (rc == NFS_REQ_OK) rc = nlm_send_async_res_nlm4(nlm_client, nlm4_cancel_message_resp, res); if (rc == NFS_REQ_DROP) { if (nsm_client != NULL) dec_nsm_client_ref(nsm_client); if (nlm_client != NULL) dec_nlm_client_ref(nlm_client); LogCrit(COMPONENT_NLM, "Could not send async response for nlm_Cancel_Message"); } return NFS_REQ_DROP; } /** * nlm4_Lock_Free: Frees the result structure allocated for nlm4_Lock * * Frees the result structure allocated for nlm4_Lock. Does Nothing in fact. * * @param res [INOUT] Pointer to the result structure. * */ void nlm4_Cancel_Free(nfs_res_t *res) { netobj_free(&res->res_nlm4test.cookie); } /* nlm4_Cancel_Free */ nfs-ganesha-6.5/src/Protocols/NLM/nlm_Free_All.c000066400000000000000000000047631473756622300214730ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2012 * Contributor: Frank Filz * * -------------------------- * * This program 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 3 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 * 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 * * */ #include "config.h" #include #include #include #include "gsh_rpc.h" #include "log.h" #include "nlm4.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nlm_util.h" /** * @brief Free All Locks * * @param[in] arg * @param[in] req * @param[out] res */ int nlm4_Free_All(nfs_arg_t *args, struct svc_req *req, nfs_res_t *res) { nlm4_free_allargs *arg = &args->arg_nlm4_free_allargs; state_status_t state_status = STATE_SUCCESS; state_nsm_client_t *nsm_client; LogDebug(COMPONENT_NLM, "REQUEST PROCESSING: Calling NLM4_FREE_ALL for %s", arg->name); nsm_client = get_nsm_client(CARE_NOT, arg->name); if (nsm_client != NULL) { /* NLM_FREE_ALL has the same semantics as handling SM_NOTIFY. * * Cast the state number into a state pointer to protect * locks from a client that has rebooted from being released * by this NLM_FREE_ALL. */ state_status = state_nlm_notify(nsm_client, false, 0); if (state_status != STATE_SUCCESS) { /* NLM_FREE_ALL has void result so all we can do is * log error */ LogWarn(COMPONENT_NLM, "NLM_FREE_ALL failed with result %s", state_err_str(state_status)); } dec_nsm_client_ref(nsm_client); } LogDebug(COMPONENT_NLM, "REQUEST RESULT: NLM4_FREE_ALL DONE"); return NFS_REQ_OK; } /** * nlm4_Free_All_Free: Frees the result structure allocated for nlm4_Free_All * * Frees the result structure allocated for nlm4_Free_All. Does Nothing in fact. * * @param res [INOUT] Pointer to the result structure. * */ void nlm4_Free_All_Free(nfs_res_t *res) { /* Nothing to do */ } nfs-ganesha-6.5/src/Protocols/NLM/nlm_Granted_Res.c000066400000000000000000000067261473756622300222200ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2010 * Contributor: M. Mohan Kumar * * -------------------------- * * This program 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 3 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 * 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 * * */ #include "config.h" #include #include #include #include "log.h" #include "fsal.h" #include "nfs_proto_functions.h" #include "sal_functions.h" #include "nlm_util.h" #include "nlm_async.h" #include "export_mgr.h" /** * @brief Lock Granted Result Handler * * @param[in] arg * @param[in] req * @param[out] res * */ int nlm4_Granted_Res(nfs_arg_t *args, struct svc_req *req, nfs_res_t *res) { nlm4_res *arg = &args->arg_nlm4_res; char buffer[1024] = "\0"; state_status_t state_status = STATE_SUCCESS; state_cookie_entry_t *cookie_entry; netobj_to_string(&arg->cookie, buffer, 1024); LogDebug(COMPONENT_NLM, "REQUEST PROCESSING: Calling nlm_Granted_Res cookie=%s", buffer); state_status = state_find_grant(arg->cookie.n_bytes, arg->cookie.n_len, &cookie_entry); if (state_status != STATE_SUCCESS) { /* This must be an old NLM_GRANTED_RES */ LogFullDebug( COMPONENT_NLM, "Could not find cookie=%s (must be an old NLM_GRANTED_RES)", buffer); return NFS_REQ_OK; } if (cookie_entry->sce_lock_entry == NULL || cookie_entry->sce_lock_entry->sle_block_data == NULL) { /* This must be an old NLM_GRANTED_RES */ LogFullDebug( COMPONENT_NLM, "Could not find block data for cookie=%s (must be an old NLM_GRANTED_RES)", buffer); return NFS_REQ_OK; } /* Fill in op_ctx, nfs_rpc_process_request will release the export ref. * We take an export reference even if the export is stale because * we want to properly clean up the cookie_entry. */ get_gsh_export_ref(cookie_entry->sce_lock_entry->sle_export); set_op_context_export(cookie_entry->sce_lock_entry->sle_export); /* If the client returned an error or the export has gone stale, * release the grant to properly clean up cookie_entry. */ if (arg->stat.stat != NLM4_GRANTED || !export_ready(op_ctx->ctx_export)) { LogEvent(COMPONENT_NLM, "Granted call failed due to %s, releasing lock", arg->stat.stat != NLM4_GRANTED ? "client error" : "export stale"); state_status = state_release_grant(cookie_entry); if (state_status != STATE_SUCCESS) { LogDebug(COMPONENT_NLM, "state_release_grant failed"); } } else { state_complete_grant(cookie_entry); nlm_signal_async_resp(cookie_entry); } return NFS_REQ_OK; } /** * nlm4_Granted_Res_Free: Frees the result structure allocated for * nlm4_Granted_Res * * Frees the result structure allocated for nlm4_Granted_Res. Does Nothing * in fact. * * @param res [INOUT] Pointer to the result structure. * */ void nlm4_Granted_Res_Free(nfs_res_t *res) { /* Nothing to do */ } nfs-ganesha-6.5/src/Protocols/NLM/nlm_Lock.c000066400000000000000000000167151473756622300207120ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2010 * Contributor: Aneesh Kumar K.v * * -------------------------- * * This program 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 3 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 * 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 * */ #include "config.h" #include #include #include #include "log.h" #include "fsal.h" #include "nfs_proto_functions.h" #include "sal_functions.h" #include "nlm_util.h" #include "nlm_async.h" /** * @brief Set a range lock * * @param[in] arg * @param[in] req * @param[out] res * */ int nlm4_Lock(nfs_arg_t *args, struct svc_req *req, nfs_res_t *res) { nlm4_lockargs *arg = &args->arg_nlm4_lock; struct fsal_obj_handle *obj; state_status_t state_status = STATE_SUCCESS; char buffer[MAXNETOBJ_SZ * 2] = "\0"; state_nsm_client_t *nsm_client; state_nlm_client_t *nlm_client; state_owner_t *nlm_owner; state_t *nlm_state; fsal_lock_param_t lock; int rc; state_block_data_t *pblock_data = NULL; const char *proc_name = "nlm4_Lock"; care_t care = CARE_MONITOR; /* Indicate if we let FSAL to handle requests during grace. */ bool grace_ref; if (req->rq_msg.cb_proc == NLMPROC4_NM_LOCK) { /* If call is a NM lock, indicate that we care about NLM * client but will not monitor. */ proc_name = "nlm4_NM_Lock"; care = CARE_NO_MONITOR; } /* NLM doesn't have a BADHANDLE error, nor can rpc_execute deal with * responding to an NLM_*_MSG call, so we check here if the export is * NULL and if so, handle the response. */ if (op_ctx->ctx_export == NULL) { res->res_nlm4.stat.stat = NLM4_STALE_FH; LogInfo(COMPONENT_NLM, "INVALID HANDLE: %s", proc_name); return NFS_REQ_OK; } netobj_to_string(&arg->cookie, buffer, 1024); LogDebug( COMPONENT_NLM, "REQUEST PROCESSING: Calling %s svid=%d off=%llx len=%llx cookie=%s reclaim=%s", proc_name, (int)arg->alock.svid, (unsigned long long)arg->alock.l_offset, (unsigned long long)arg->alock.l_len, buffer, arg->reclaim ? "yes" : "no"); copy_netobj(&res->res_nlm4.cookie, &arg->cookie); grace_ref = !op_ctx->fsal_export->exp_ops.fs_supports( op_ctx->fsal_export, fso_grace_method); if (grace_ref) { if (!nfs_get_grace_status(arg->reclaim)) { grace_ref = false; res->res_nlm4.stat.stat = NLM4_DENIED_GRACE_PERIOD; LogDebug(COMPONENT_NLM, "REQUEST RESULT:%s in grace %s %s", arg->reclaim ? " NOT" : "", proc_name, lock_result_str(res->res_nlm4.stat.stat)); return NFS_REQ_OK; } } rc = nlm_process_parameters(req, arg->exclusive, &arg->alock, &lock, &obj, care, &nsm_client, &nlm_client, &nlm_owner, arg->block ? &pblock_data : NULL, arg->state, &nlm_state); lock.lock_reclaim = arg->reclaim; if (rc >= 0) { /* Present the error back to the client */ res->res_nlm4.stat.stat = (nlm4_stats)rc; LogDebug(COMPONENT_NLM, "REQUEST RESULT: %s %s", proc_name, lock_result_str(res->res_nlm4.stat.stat)); rc = NFS_REQ_OK; goto out; } /* Check if v4 delegations conflict with v3 op */ if (state_deleg_conflict(obj, lock.lock_type == FSAL_LOCK_W)) { LogDebug(COMPONENT_NLM, "NLM lock request DROPPED due to delegation conflict"); rc = NFS_REQ_DROP; goto out_dec; } else { (void)atomic_inc_uint32_t(&obj->state_hdl->file.anon_ops); } /* Cast the state number into a state pointer to protect * locks from a client that has rebooted from the SM_NOTIFY * that will release old locks */ STATELOCK_lock(obj); state_status = state_lock(obj, nlm_owner, nlm_state, arg->block ? STATE_BLOCKING : STATE_NON_BLOCKING, LOCK_NLM, arg->block ? &pblock_data : NULL, &lock, NULL, /* We don't need conflict info */ NULL); STATELOCK_unlock(obj); /* We prevented delegations from being granted while trying to acquire * the lock. However, when attempting to get a delegation in the * future existing locks will result in a conflict. Thus, we can * decrement the anonymous operations counter now. */ (void)atomic_dec_uint32_t(&obj->state_hdl->file.anon_ops); if (state_status != STATE_SUCCESS) { res->res_nlm4.stat.stat = nlm_convert_state_error(state_status); if (state_status == STATE_IN_GRACE) res->res_nlm4.stat.stat = NLM4_DENIED_GRACE_PERIOD; } else { res->res_nlm4.stat.stat = NLM4_GRANTED; } rc = NFS_REQ_OK; out_dec: /* If we didn't block, release the block data. Note that * state_lock() would set pblock_data to NULL if the lock was * blocked! */ gsh_free(pblock_data); /* Release the NLM Client and NLM Owner references we have */ dec_nsm_client_ref(nsm_client); dec_nlm_client_ref(nlm_client); dec_state_owner_ref(nlm_owner); obj->obj_ops->put_ref(obj); dec_nlm_state_ref(nlm_state); LogDebug(COMPONENT_NLM, "REQUEST RESULT: %s %s", proc_name, lock_result_str(res->res_nlm4.stat.stat)); out: if (grace_ref) nfs_put_grace_status(); return rc; } static void nlm4_lock_message_resp(state_async_queue_t *arg) { state_nlm_async_data_t *nlm_arg = &arg->state_async_data.state_nlm_async_data; nfs_res_t *res = &nlm_arg->nlm_async_args.nlm_async_res; if (isFullDebug(COMPONENT_NLM)) { char buffer[1024] = "\0"; netobj_to_string(&res->res_nlm4test.cookie, buffer, 1024); LogFullDebug(COMPONENT_NLM, "Calling nlm_send_async cookie=%s status=%s", buffer, lock_result_str(res->res_nlm4.stat.stat)); } nlm_send_async(NLMPROC4_LOCK_RES, nlm_arg->nlm_async_host, res, NULL); nlm4_Lock_Free(res); dec_nsm_client_ref(nlm_arg->nlm_async_host->slc_nsm_client); dec_nlm_client_ref(nlm_arg->nlm_async_host); gsh_free(arg); } /** * @brief Lock Message * * @param[in] arg * @param[in] req * @param[out] res * */ int nlm4_Lock_Message(nfs_arg_t *args, struct svc_req *req, nfs_res_t *res) { state_nlm_client_t *nlm_client = NULL; state_nsm_client_t *nsm_client; nlm4_lockargs *arg = &args->arg_nlm4_lock; int rc = NFS_REQ_OK; LogDebug(COMPONENT_NLM, "REQUEST PROCESSING: Calling nlm_Lock_Message"); nsm_client = get_nsm_client(CARE_NO_MONITOR, arg->alock.caller_name); if (nsm_client != NULL) nlm_client = get_nlm_client(CARE_NO_MONITOR, req->rq_xprt, nsm_client, arg->alock.caller_name); if (nlm_client == NULL) rc = NFS_REQ_DROP; else rc = nlm4_Lock(args, req, res); if (rc == NFS_REQ_OK) rc = nlm_send_async_res_nlm4(nlm_client, nlm4_lock_message_resp, res); if (rc == NFS_REQ_DROP) { if (nsm_client != NULL) dec_nsm_client_ref(nsm_client); if (nlm_client != NULL) dec_nlm_client_ref(nlm_client); LogCrit(COMPONENT_NLM, "Could not send async response for nlm_Lock_Message"); } return NFS_REQ_DROP; } /** * nlm4_Lock_Free: Frees the result structure allocated for nlm4_Lock * * Frees the result structure allocated for nlm4_Lock. Does Nothing in fact. * * @param res [INOUT] Pointer to the result structure. * */ void nlm4_Lock_Free(nfs_res_t *res) { netobj_free(&res->res_nlm4.cookie); } nfs-ganesha-6.5/src/Protocols/NLM/nlm_Null.c000066400000000000000000000034011473756622300207200ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2010 * Contributor: Aneesh Kumar K.v * * -------------------------- * * This program 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 3 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 * 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 * * */ #include "config.h" #include #include #include #include "log.h" #include "gsh_rpc.h" #include "nlm4.h" #include "nlm_util.h" #include "nlm_async.h" /** * @brief The NLM proc null function, for all versions. * * The NLM proc null function, for all versions. * * @param[in] parg Ignored * @param[in] preq Ignored * @param[out] pres Ignored * */ int nlm_Null(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { LogDebug(COMPONENT_NLM, "REQUEST PROCESSING: Calling NLM_NULL"); /* 0 is success */ return 0; } /** * nlm_Null_Free: Frees the result structure allocated for nlm_Null * * Frees the result structure allocated for nlm_Null. Does Nothing in fact. * * @param res [INOUT] Pointer to the result structure. * */ void nlm_Null_Free(nfs_res_t *res) { /* Nothing to do */ } nfs-ganesha-6.5/src/Protocols/NLM/nlm_Share.c000066400000000000000000000117521473756622300210600ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2012 * Contributor: Frank Filz * * -------------------------- * * This program 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 3 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 * 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 * * */ #include "config.h" #include #include #include #include "log.h" #include "fsal.h" #include "nfs_proto_functions.h" #include "nfs_file_handle.h" #include "sal_functions.h" #include "nlm_util.h" #include "nlm_async.h" /** * @brief Set a share reservation * * @param[in] arg * @param[in] req * @param[out] res */ int nlm4_Share(nfs_arg_t *args, struct svc_req *req, nfs_res_t *res) { nlm4_shareargs *arg = &args->arg_nlm4_share; struct fsal_obj_handle *obj; state_status_t state_status = STATE_SUCCESS; char buffer[MAXNETOBJ_SZ * 2] = "\0"; state_nsm_client_t *nsm_client; state_nlm_client_t *nlm_client; state_owner_t *nlm_owner; state_t *nlm_state; int rc; bool grace_ref = false; if (nfs_param.core_param.disable_NLM_SHARE) { res->res_nlm4share.stat = NLM4_FAILED; LogEvent(COMPONENT_NLM, "NLM4_SHARE call detected, failing it"); return NFS_REQ_OK; } /* NLM doesn't have a BADHANDLE error, nor can rpc_execute deal with * responding to an NLM_*_MSG call, so we check here if the export is * NULL and if so, handle the response. */ if (op_ctx->ctx_export == NULL) { res->res_nlm4share.stat = NLM4_STALE_FH; LogInfo(COMPONENT_NLM, "INVALID HANDLE: NLM4_SHARE"); return NFS_REQ_OK; } res->res_nlm4share.sequence = 0; netobj_to_string(&arg->cookie, buffer, 1024); if (isDebug(COMPONENT_NLM)) { char str[LEN_FH_STR]; struct display_buffer dspbuf = { sizeof(str), str, str }; char oh[MAXNETOBJ_SZ * 2] = "\0"; display_opaque_bytes(&dspbuf, arg->share.fh.n_bytes, arg->share.fh.n_len); netobj_to_string(&arg->share.oh, oh, 1024); LogDebug( COMPONENT_NLM, "REQUEST PROCESSING: Calling NLM4_SHARE File Handle V3: Len=%u %s, cookie=%s, reclaim=%s, owner=%s, access=%d, deny=%d", arg->share.fh.n_len, str, buffer, arg->reclaim ? "yes" : "no", oh, arg->share.access, arg->share.mode); } copy_netobj(&res->res_nlm4share.cookie, &arg->cookie); /* Allow only reclaim share request during recovery and visa versa. * Note: NLM_SHARE is indicated to be non-monitored, however, it does * have a reclaim flag, so we will honor the reclaim flag if used. */ grace_ref = !op_ctx->fsal_export->exp_ops.fs_supports( op_ctx->fsal_export, fso_grace_method); if (grace_ref) { if (!nfs_get_grace_status(arg->reclaim)) { res->res_nlm4share.stat = NLM4_DENIED_GRACE_PERIOD; LogDebug(COMPONENT_NLM, "REQUEST RESULT: NLM4_SHARE %s", lock_result_str(res->res_nlm4share.stat)); return NFS_REQ_OK; } } rc = nlm_process_share_parms(req, &arg->share, op_ctx->fsal_export, &obj, CARE_NO_MONITOR, &nsm_client, &nlm_client, &nlm_owner, &nlm_state); if (rc >= 0) { /* Present the error back to the client */ res->res_nlm4share.stat = (nlm4_stats)rc; LogDebug(COMPONENT_NLM, "REQUEST RESULT: NLM4_SHARE %s", lock_result_str(res->res_nlm4share.stat)); goto out; } retry_nlm_share: state_status = state_nlm_share(obj, arg->share.access, arg->share.mode, nlm_owner, nlm_state, arg->reclaim, false); if (state_status != STATE_SUCCESS) { res->res_nlm4share.stat = nlm_convert_state_error(state_status); /* We have an got an share denied with an existing * client. Let's recheck if it was expired. */ if (res->res_nlm4share.stat == NLM4_DENIED && check_and_remove_conflicting_client(obj->state_hdl)) { goto retry_nlm_share; } } else { res->res_nlm4share.stat = NLM4_GRANTED; } /* Release the NLM Client and NLM Owner references we have */ dec_nsm_client_ref(nsm_client); dec_nlm_client_ref(nlm_client); dec_state_owner_ref(nlm_owner); obj->obj_ops->put_ref(obj); dec_nlm_state_ref(nlm_state); LogDebug(COMPONENT_NLM, "REQUEST RESULT: NLM4_SHARE %s", lock_result_str(res->res_nlm4share.stat)); out: if (grace_ref) nfs_put_grace_status(); return NFS_REQ_OK; } /** * nlm4_Share_Free: Frees the result structure allocated for nlm4_Lock * * Frees the result structure allocated for nlm4_Lock. Does Nothing in fact. * * @param res [INOUT] Pointer to the result structure. * */ void nlm4_Share_Free(nfs_res_t *res) { netobj_free(&res->res_nlm4share.cookie); } nfs-ganesha-6.5/src/Protocols/NLM/nlm_Sm_Notify.c000066400000000000000000000077631473756622300217340ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2010 * Contributor: Aneesh Kumar K.v * * -------------------------- * * This program 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 3 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 * 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 * * */ #include "config.h" #include #include #include #include #include "log.h" #include "gsh_rpc.h" #include "nlm4.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nlm_util.h" #define IN4_LOCALHOST_STRING "127.0.0.1" #define IN6_LOCALHOST_STRING "::1" #define IN6_ENCAPSULATED_IN4_LOCALHOST_STRING "::ffff:127.0.0.1" /** * @brief NSM notification * * @param[in] args * @param[in] req * @param[out] res */ int nlm4_Sm_Notify(nfs_arg_t *args, struct svc_req *req, nfs_res_t *res) { nlm4_sm_notifyargs *arg = &args->arg_nlm4_sm_notify; state_status_t state_status = STATE_SUCCESS; state_nsm_client_t *nsm_client; sockaddr_t *original_caller_addr = op_ctx->caller_addr; struct gsh_client *original_client = op_ctx->client; if (!is_loopback(op_ctx->caller_addr)) { LogEvent(COMPONENT_NLM, "Client %s sent an SM_NOTIFY, ignoring", op_ctx->client->hostaddr_str); return NFS_REQ_OK; } LogDebug( COMPONENT_NLM, "REQUEST PROCESSING: Calling nlm4_sm_notify for %s state %" PRIu32, arg->name, arg->state); /* We don't have a client for the call to get_nsm_client. Note that * nsm_use_caller_name is true or not, note that we ALWAYS look up * the nsm_client using caller name. For nsm_use_caller_name == false * the caller name is the string form of the IP address. In that case * op_ctx->client being NULL will just signal get_nsm_client to use * caller name instead of the op_ctx->client_addr. */ op_ctx->client = NULL; op_ctx->caller_addr = NULL; /* Now find the nsm_client using the provided caller_name */ nsm_client = get_nsm_client(CARE_NOT, arg->name); if (nsm_client != NULL) { /* Now that we have an nsm_client, we can grab the * gsh_client from ssc_client (which SHOULD be non-NULL) * to use, and if it IS non-NULL, we can also fill in * op_ctx->caller_addr. */ op_ctx->client = nsm_client->ssc_client; if (op_ctx->client != NULL) { op_ctx->caller_addr = &op_ctx->client->cl_addrbuf; SetClientIP(op_ctx->client->hostaddr_str); } /* Cast the state number into a state pointer to protect * locks from a client that has rebooted from being released * by this SM_NOTIFY. */ LogFullDebug(COMPONENT_NLM, "Starting cleanup"); state_status = state_nlm_notify(nsm_client, true, arg->state); if (state_status != STATE_SUCCESS) { /** @todo FSF: Deal with error */ } LogFullDebug(COMPONENT_NLM, "Cleanup complete"); dec_nsm_client_ref(nsm_client); } if (op_ctx->caller_addr != original_caller_addr) op_ctx->caller_addr = original_caller_addr; if (op_ctx->client != original_client) { op_ctx->client = original_client; SetClientIP(original_client->hostaddr_str); } LogDebug(COMPONENT_NLM, "REQUEST RESULT: nlm4_sm_notify DONE"); return NFS_REQ_OK; } /** * nlm4_Sm_Notify_Free: Frees the result structure allocated for nlm4_Sm_Notify * * Frees the result structure allocated for nlm4_Sm_Notify. Does Nothing in * fact. * * @param res [INOUT] Pointer to the result structure. * */ void nlm4_Sm_Notify_Free(nfs_res_t *res) { /* Nothing to do */ } nfs-ganesha-6.5/src/Protocols/NLM/nlm_Test.c000066400000000000000000000144271473756622300207370ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2010 * Contributor: Aneesh Kumar K.v * * -------------------------- * * This program 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 3 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 * 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 * * */ #include "config.h" #include #include #include #include "log.h" #include "fsal.h" #include "nfs_proto_functions.h" #include "sal_functions.h" #include "nlm_util.h" #include "nlm_async.h" /** * @brief Test lock * * @param[in] args * @param[in] req * @param[out] res * */ int nlm4_Test(nfs_arg_t *args, struct svc_req *req, nfs_res_t *res) { nlm4_testargs *arg = &args->arg_nlm4_test; struct fsal_obj_handle *obj; state_status_t state_status = STATE_SUCCESS; char buffer[MAXNETOBJ_SZ * 2] = "\0"; state_nsm_client_t *nsm_client; state_nlm_client_t *nlm_client; state_owner_t *nlm_owner; state_owner_t *holder = NULL; fsal_lock_param_t lock, conflict; int rc; state_t *state; nlm4_testrply *test_stat = &res->res_nlm4test.test_stat; /* NLM doesn't have a BADHANDLE error, nor can rpc_execute deal with * responding to an NLM_*_MSG call, so we check here if the export is * NULL and if so, handle the response. */ if (op_ctx->ctx_export == NULL) { test_stat->stat = NLM4_STALE_FH; LogInfo(COMPONENT_NLM, "INVALID HANDLE: NLM4_TEST"); return NFS_REQ_OK; } netobj_to_string(&arg->cookie, buffer, 1024); LogDebug( COMPONENT_NLM, "REQUEST PROCESSING: Calling NLM4_TEST svid=%d off=%llx len=%llx cookie=%s", (int)arg->alock.svid, (unsigned long long)arg->alock.l_offset, (unsigned long long)arg->alock.l_len, buffer); copy_netobj(&res->res_nlm4test.cookie, &arg->cookie); if (!nfs_get_grace_status(false)) { test_stat->stat = NLM4_DENIED_GRACE_PERIOD; LogDebug(COMPONENT_NLM, "REQUEST RESULT: NLM4_TEST %s", lock_result_str(res->res_nlm4.stat.stat)); return NFS_REQ_OK; } /** @todo FSF: * * TEST passes CARE_NO_MONITOR for care because we do need a non-NULL * owner, but we could expand the options to allow for a "free" owner * to be returned, that doesn't need to be in the hash table, so if * the owner isn't found in the Hash table, don't add it, just return * the "free" owner. */ rc = nlm_process_parameters(req, arg->exclusive, &arg->alock, &lock, &obj, CARE_OWNER, &nsm_client, &nlm_client, &nlm_owner, NULL, 0, &state); if (rc >= 0) { /* resent the error back to the client */ res->res_nlm4.stat.stat = (nlm4_stats)rc; LogDebug(COMPONENT_NLM, "REQUEST RESULT: nlm4_Unlock %s", lock_result_str(res->res_nlm4.stat.stat)); goto out; } state_status = state_test(obj, state, nlm_owner, &lock, &holder, &conflict); if (state_status != STATE_SUCCESS) { test_stat->stat = nlm_convert_state_error(state_status); if (state_status == STATE_LOCK_CONFLICT) { nlm_process_conflict(&test_stat->nlm4_testrply_u.holder, holder, &conflict); } } else { res->res_nlm4.stat.stat = NLM4_GRANTED; } LogFullDebug(COMPONENT_NLM, "Back from state_test"); /* Release state_t reference if we got one */ if (state != NULL) dec_nlm_state_ref(state); /* Release the NLM Client and NLM Owner references we have */ dec_nsm_client_ref(nsm_client); dec_nlm_client_ref(nlm_client); dec_state_owner_ref(nlm_owner); obj->obj_ops->put_ref(obj); LogDebug(COMPONENT_NLM, "REQUEST RESULT: NLM4_TEST %s", lock_result_str(res->res_nlm4.stat.stat)); out: nfs_put_grace_status(); return NFS_REQ_OK; } static void nlm4_test_message_resp(state_async_queue_t *arg) { state_nlm_async_data_t *nlm_arg = &arg->state_async_data.state_nlm_async_data; nfs_res_t *res = &nlm_arg->nlm_async_args.nlm_async_res; if (isFullDebug(COMPONENT_NLM)) { char buffer[1024] = "\0"; netobj_to_string(&res->res_nlm4test.cookie, buffer, 1024); LogFullDebug(COMPONENT_NLM, "Calling nlm_send_async cookie=%s status=%s", buffer, lock_result_str(res->res_nlm4test.test_stat.stat)); } nlm_send_async(NLMPROC4_TEST_RES, nlm_arg->nlm_async_host, res, NULL); nlm4_Test_Free(res); dec_nsm_client_ref(nlm_arg->nlm_async_host->slc_nsm_client); dec_nlm_client_ref(nlm_arg->nlm_async_host); gsh_free(arg); } /** * @brief Test lock Message * * @param[in] args * @param[in] req * @param[out] res * */ int nlm4_Test_Message(nfs_arg_t *args, struct svc_req *req, nfs_res_t *res) { state_nlm_client_t *nlm_client = NULL; state_nsm_client_t *nsm_client; nlm4_testargs *arg = &args->arg_nlm4_test; int rc = NFS_REQ_OK; LogDebug(COMPONENT_NLM, "REQUEST PROCESSING: Calling nlm_Test_Message"); nsm_client = get_nsm_client(CARE_NO_MONITOR, arg->alock.caller_name); if (nsm_client != NULL) nlm_client = get_nlm_client(CARE_NO_MONITOR, req->rq_xprt, nsm_client, arg->alock.caller_name); if (nlm_client == NULL) rc = NFS_REQ_DROP; else rc = nlm4_Test(args, req, res); if (rc == NFS_REQ_OK) rc = nlm_send_async_res_nlm4test(nlm_client, nlm4_test_message_resp, res); if (rc == NFS_REQ_DROP) { if (nsm_client != NULL) dec_nsm_client_ref(nsm_client); if (nlm_client != NULL) dec_nlm_client_ref(nlm_client); LogCrit(COMPONENT_NLM, "Could not send async response for nlm_Test_Message"); } return NFS_REQ_DROP; } /** * nlm_Test_Free: Frees the result structure allocated for nlm4_Test * * Frees the result structure allocated for nlm_Null. Does Nothing in fact. * * @param res [INOUT] Pointer to the result structure. * */ void nlm4_Test_Free(nfs_res_t *res) { nlm4_testrply *test_stat = &res->res_nlm4test.test_stat; netobj_free(&res->res_nlm4test.cookie); if (test_stat->stat == NLM4_DENIED) netobj_free(&test_stat->nlm4_testrply_u.holder.oh); } nfs-ganesha-6.5/src/Protocols/NLM/nlm_Unlock.c000066400000000000000000000130021473756622300212370ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2010 * Contributor: Aneesh Kumar K.v * * -------------------------- * * This program 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 3 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 * 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 * */ #include "config.h" #include #include #include #include "log.h" #include "fsal.h" #include "nfs_proto_functions.h" #include "sal_functions.h" #include "nlm_util.h" #include "nlm_async.h" /** * @brief Free a range lock * * @param[in] args * @param[in] req * @param[out] res */ int nlm4_Unlock(nfs_arg_t *args, struct svc_req *req, nfs_res_t *res) { nlm4_unlockargs *arg = &args->arg_nlm4_unlock; struct fsal_obj_handle *obj; state_status_t state_status = STATE_SUCCESS; char buffer[MAXNETOBJ_SZ * 2] = "\0"; state_nsm_client_t *nsm_client; state_nlm_client_t *nlm_client; state_owner_t *nlm_owner; fsal_lock_param_t lock; int rc; state_t *state; /* NLM doesn't have a BADHANDLE error, nor can rpc_execute deal with * responding to an NLM_*_MSG call, so we check here if the export is * NULL and if so, handle the response. */ if (op_ctx->ctx_export == NULL) { res->res_nlm4.stat.stat = NLM4_STALE_FH; LogInfo(COMPONENT_NLM, "INVALID HANDLE: NLM4_UNLOCK"); return NFS_REQ_OK; } netobj_to_string(&arg->cookie, buffer, sizeof(buffer)); LogDebug( COMPONENT_NLM, "REQUEST PROCESSING: Calling NLM4_UNLOCK svid=%d off=%llx len=%llx cookie=%s", (int)arg->alock.svid, (unsigned long long)arg->alock.l_offset, (unsigned long long)arg->alock.l_len, buffer); copy_netobj(&res->res_nlm4test.cookie, &arg->cookie); /* unlock doesn't care if owner is found */ rc = nlm_process_parameters(req, false, &arg->alock, &lock, &obj, CARE_NOT, &nsm_client, &nlm_client, &nlm_owner, NULL, 0, &state); if (rc >= 0) { /* resent the error back to the client */ res->res_nlm4.stat.stat = (nlm4_stats)rc; LogDebug(COMPONENT_NLM, "REQUEST RESULT: NLM4_UNLOCK %s", lock_result_str(res->res_nlm4.stat.stat)); return NFS_REQ_OK; } if (state != NULL) state_status = state_unlock(obj, state, nlm_owner, false, 0, &lock); if (state_status != STATE_SUCCESS) { /* Unlock could fail in the FSAL and make a bit of a mess, * especially if we are in out of memory situation. Such an * error is already logged. */ res->res_nlm4test.test_stat.stat = nlm_convert_state_error(state_status); } else { res->res_nlm4.stat.stat = NLM4_GRANTED; } /* Release the NLM Client and NLM Owner references we have */ if (state != NULL) dec_state_t_ref(state); dec_nsm_client_ref(nsm_client); dec_nlm_client_ref(nlm_client); dec_state_owner_ref(nlm_owner); obj->obj_ops->put_ref(obj); LogDebug(COMPONENT_NLM, "REQUEST RESULT: NLM4_UNLOCK %s", lock_result_str(res->res_nlm4.stat.stat)); return NFS_REQ_OK; } static void nlm4_unlock_message_resp(state_async_queue_t *arg) { state_nlm_async_data_t *nlm_arg = &arg->state_async_data.state_nlm_async_data; nfs_res_t *res = &nlm_arg->nlm_async_args.nlm_async_res; if (isFullDebug(COMPONENT_NLM)) { char buffer[1024] = "\0"; netobj_to_string(&res->res_nlm4test.cookie, buffer, 1024); LogFullDebug(COMPONENT_NLM, "Calling nlm_send_async cookie=%s status=%s", buffer, lock_result_str(res->res_nlm4.stat.stat)); } nlm_send_async(NLMPROC4_UNLOCK_RES, nlm_arg->nlm_async_host, res, NULL); nlm4_Unlock_Free(res); dec_nsm_client_ref(nlm_arg->nlm_async_host->slc_nsm_client); dec_nlm_client_ref(nlm_arg->nlm_async_host); gsh_free(arg); } /** * @brief Unlock Message * * @param[in] args * @param[in] req * @param[out] res * */ int nlm4_Unlock_Message(nfs_arg_t *args, struct svc_req *req, nfs_res_t *res) { state_nlm_client_t *nlm_client = NULL; state_nsm_client_t *nsm_client; nlm4_unlockargs *arg = &args->arg_nlm4_unlock; int rc = NFS_REQ_OK; LogDebug(COMPONENT_NLM, "REQUEST PROCESSING: Calling nlm_Unlock_Message"); nsm_client = get_nsm_client(CARE_NO_MONITOR, arg->alock.caller_name); if (nsm_client != NULL) nlm_client = get_nlm_client(CARE_NO_MONITOR, req->rq_xprt, nsm_client, arg->alock.caller_name); if (nlm_client == NULL) rc = NFS_REQ_DROP; else rc = nlm4_Unlock(args, req, res); if (rc == NFS_REQ_OK) rc = nlm_send_async_res_nlm4(nlm_client, nlm4_unlock_message_resp, res); if (rc == NFS_REQ_DROP) { if (nsm_client != NULL) dec_nsm_client_ref(nsm_client); if (nlm_client != NULL) dec_nlm_client_ref(nlm_client); LogCrit(COMPONENT_NLM, "Could not send async response for nlm_Unlock_Message"); } return NFS_REQ_DROP; } /** * nlm4_Unlock_Free: Frees the result structure allocated for nlm4_Unlock * * Frees the result structure allocated for nlm4_Lock. Does Nothing in fact. * * @param res [INOUT] Pointer to the result structure. * */ void nlm4_Unlock_Free(nfs_res_t *res) { netobj_free(&res->res_nlm4test.cookie); } nfs-ganesha-6.5/src/Protocols/NLM/nlm_Unshare.c000066400000000000000000000101601473756622300214130ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2012 * Contributor: Frank Filz * * -------------------------- * * This program 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 3 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 * 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 * * */ #include "config.h" #include #include #include #include "log.h" #include "fsal.h" #include "nfs_proto_functions.h" #include "nfs_file_handle.h" #include "sal_functions.h" #include "nlm_util.h" #include "nlm_async.h" /** * @brief Set a share reservation * * @param[in] args * @param[in] req * @param[out] res * */ int nlm4_Unshare(nfs_arg_t *args, struct svc_req *req, nfs_res_t *res) { nlm4_shareargs *arg = &args->arg_nlm4_share; struct fsal_obj_handle *obj; state_status_t state_status = STATE_SUCCESS; char buffer[MAXNETOBJ_SZ * 2] = "\0"; state_nsm_client_t *nsm_client; state_nlm_client_t *nlm_client; state_owner_t *nlm_owner; state_t *nlm_state; int rc; if (nfs_param.core_param.disable_NLM_SHARE) { res->res_nlm4share.stat = NLM4_FAILED; LogEvent(COMPONENT_NLM, "NLM4_UNSHARE call detected, failing it"); return NFS_REQ_OK; } /* NLM doesn't have a BADHANDLE error, nor can rpc_execute deal with * responding to an NLM_*_MSG call, so we check here if the export is * NULL and if so, handle the response. */ if (op_ctx->ctx_export == NULL) { res->res_nlm4share.stat = NLM4_STALE_FH; LogInfo(COMPONENT_NLM, "INVALID HANDLE: NLM4_UNSHARE"); return NFS_REQ_OK; } res->res_nlm4share.sequence = 0; netobj_to_string(&arg->cookie, buffer, 1024); if (isDebug(COMPONENT_NLM)) { char str[LEN_FH_STR]; struct display_buffer dspbuf = { sizeof(str), str, str }; char oh[MAXNETOBJ_SZ * 2] = "\0"; display_opaque_bytes(&dspbuf, arg->share.fh.n_bytes, arg->share.fh.n_len); netobj_to_string(&arg->share.oh, oh, 1024); LogDebug( COMPONENT_NLM, "REQUEST PROCESSING: Calling NLM4_UNSHARE File Handle V3: Len=%u %s, cookie=%s, reclaim=%s, owner=%s, access=%d, deny=%d", arg->share.fh.n_len, str, buffer, arg->reclaim ? "yes" : "no", oh, arg->share.access, arg->share.mode); } copy_netobj(&res->res_nlm4share.cookie, &arg->cookie); rc = nlm_process_share_parms(req, &arg->share, op_ctx->fsal_export, &obj, CARE_NOT, &nsm_client, &nlm_client, &nlm_owner, &nlm_state); if (rc >= 0) { /* Present the error back to the client */ res->res_nlm4share.stat = (nlm4_stats)rc; LogDebug(COMPONENT_NLM, "REQUEST RESULT: NLM4_UNSHARE %s", lock_result_str(res->res_nlm4share.stat)); return NFS_REQ_OK; } state_status = state_nlm_share(obj, arg->share.access, arg->share.mode, nlm_owner, nlm_state, false, true); if (state_status != STATE_SUCCESS) { res->res_nlm4share.stat = nlm_convert_state_error(state_status); } else { res->res_nlm4share.stat = NLM4_GRANTED; } /* Release the NLM Client and NLM Owner references we have */ dec_nsm_client_ref(nsm_client); dec_nlm_client_ref(nlm_client); dec_state_owner_ref(nlm_owner); obj->obj_ops->put_ref(obj); dec_nlm_state_ref(nlm_state); LogDebug(COMPONENT_NLM, "REQUEST RESULT: NLM4_UNSHARE %s", lock_result_str(res->res_nlm4share.stat)); return NFS_REQ_OK; } /** * nlm4_Unshare_Free: Frees the result structure allocated for nlm4_Lock * * Frees the result structure allocated for nlm4_Lock. Does Nothing in fact. * * @param res [INOUT] Pointer to the result structure. * */ void nlm4_Unshare_Free(nfs_res_t *res) { netobj_free(&res->res_nlm4share.cookie); } nfs-ganesha-6.5/src/Protocols/NLM/nlm_async.c000066400000000000000000000257721473756622300211420ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2010 * Contributor: Aneesh Kumar K.V * * -------------------------- * * This program 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 3 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 * 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 * * */ #include "config.h" #include #include #include #include #include #include "nfs_core.h" #include "nfs_proto_functions.h" #include "sal_functions.h" #include "nlm_util.h" #include "nlm_async.h" pthread_mutex_t nlm_async_resp_mutex; pthread_cond_t nlm_async_resp_cond; int nlm_send_async_res_nlm4(state_nlm_client_t *host, state_async_func_t func, nfs_res_t *pres) { state_async_queue_t *arg = gsh_malloc(sizeof(*arg)); state_nlm_async_data_t *nlm_arg; state_status_t status; nlm_arg = &arg->state_async_data.state_nlm_async_data; memset(arg, 0, sizeof(*arg)); arg->state_async_func = func; nlm_arg->nlm_async_host = host; nlm_arg->nlm_async_args.nlm_async_res = *pres; copy_netobj(&nlm_arg->nlm_async_args.nlm_async_res.res_nlm4.cookie, &pres->res_nlm4.cookie); status = state_async_schedule(arg); if (status != STATE_SUCCESS) { gsh_free(arg); return NFS_REQ_DROP; } return NFS_REQ_OK; } int nlm_send_async_res_nlm4test(state_nlm_client_t *host, state_async_func_t func, nfs_res_t *pres) { state_async_queue_t *arg = gsh_malloc(sizeof(*arg)); state_nlm_async_data_t *nlm_arg; state_status_t status; nfs_res_t *res; nlm_arg = &arg->state_async_data.state_nlm_async_data; res = &nlm_arg->nlm_async_args.nlm_async_res; memset(arg, 0, sizeof(*arg)); arg->state_async_func = func; nlm_arg->nlm_async_host = host; *res = *pres; copy_netobj(&res->res_nlm4test.cookie, &pres->res_nlm4test.cookie); if (pres->res_nlm4test.test_stat.stat == NLM4_DENIED) { copy_netobj( &res->res_nlm4test.test_stat.nlm4_testrply_u.holder.oh, &pres->res_nlm4test.test_stat.nlm4_testrply_u.holder.oh); } status = state_async_schedule(arg); if (status != STATE_SUCCESS) { nlm4_Test_Free(res); gsh_free(arg); return NFS_REQ_DROP; } return NFS_REQ_OK; } xdrproc_t nlm_reply_proc[] = { [NLMPROC4_GRANTED_MSG] = (xdrproc_t)xdr_nlm4_testargs, [NLMPROC4_TEST_RES] = (xdrproc_t)xdr_nlm4_testres, [NLMPROC4_LOCK_RES] = (xdrproc_t)xdr_nlm4_res, [NLMPROC4_CANCEL_RES] = (xdrproc_t)xdr_nlm4_res, [NLMPROC4_UNLOCK_RES] = (xdrproc_t)xdr_nlm4_res, }; static void *resp_key; static const int MAX_ASYNC_RETRY = 2; static const struct timespec tout = { 0, 0 }; /* one-shot */ int find_peer_addr(char *caller_name, in_port_t sin_port, sockaddr_t *client) { struct addrinfo hints; struct addrinfo *result; char port_str[20]; int retval; bool stats = nfs_param.core_param.enable_AUTHSTATS; struct sockaddr_in *in; struct sockaddr_in6 *in6; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_INET6; /* only INET6 */ hints.ai_socktype = SOCK_STREAM; /* TCP */ hints.ai_protocol = 0; /* Any protocol */ hints.ai_canonname = NULL; hints.ai_addr = NULL; hints.ai_next = NULL; /* convert port to string format */ (void)sprintf(port_str, "%d", htons(sin_port)); /* get the IPv4 mapped IPv6 address */ retval = gsh_getaddrinfo(caller_name, port_str, &hints, &result, stats); if (retval == 0) { memcpy(client, result->ai_addr, result->ai_addrlen); freeaddrinfo(result); return retval; } /* Couldn't find an AF_INET6 address, look for AF_INET address */ hints.ai_family = AF_INET; retval = gsh_getaddrinfo(caller_name, port_str, &hints, &result, stats); if (retval != 0) return retval; /* Format of an IPv4 address encapsulated in IPv6: * |---------------------------------------------------------------| * | 80 bits = 10 bytes | 16 bits = 2 bytes | 32 bits = 4 bytes | * |---------------------------------------------------------------| * | 0 | FFFF | IPv4 address | * |---------------------------------------------------------------| * * An IPv4 loop back address is 127.b.c.d, so we only need to examine * the first byte past ::ffff, or s6_addr[12]. * * Otherwise we compare to ::1 */ in = (struct sockaddr_in *)result->ai_addr; in6 = (struct sockaddr_in6 *)client; /* Copy __SOCKADDR_COMMON part plus sin_port */ memcpy(in6, in, offsetof(struct sockaddr_in, sin_addr)); /* Change to AF_INET6 */ in6->sin6_family = AF_INET6; /* Set all those 0s */ memset(&in6->sin6_addr, 0, 10); /* Set those 16 "1" bits */ in6->sin6_addr.s6_addr16[5] = 0xFFFF; /* And the IPv4 address goes at the end. */ in6->sin6_addr.s6_addr32[3] = in->sin_addr.s_addr; /* Finish up the IPv6 address */ in6->sin6_flowinfo = 0; in6->sin6_scope_id = 0; freeaddrinfo(result); return retval; } /* Client routine to send the asynchronous response, * key is used to wait for a response */ int nlm_send_async(int proc, state_nlm_client_t *host, void *inarg, void *key) { struct clnt_req *cc; char *t; struct timeval start, now; struct timespec timeout; int retval, retry; char *caller_name = host->slc_nsm_client->ssc_nlm_caller_name; const char *client_type_str = xprt_type_to_str(host->slc_client_type); for (retry = 0; retry < MAX_ASYNC_RETRY; retry++) { if (host->slc_callback_clnt == NULL) { LogFullDebug(COMPONENT_NLM, "clnt_ncreate %s", caller_name); if (host->slc_client_type == XPRT_TCP) { int fd; struct sockaddr_in6 server_addr; sockaddr_t client_addr; struct netbuf *buf, local_buf; fd = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP); if (fd < 0) return -1; memcpy(&server_addr, &(host->slc_server_addr), sizeof(struct sockaddr_in6)); server_addr.sin6_port = 0; if (isFullDebug(COMPONENT_NLM)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer db = { sizeof(str), str, str }; display_sockaddr( &db, (sockaddr_t *)&server_addr); LogFullDebug( COMPONENT_NLM, "Server address %s for NLM callback", str); } if (bind(fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { LogMajor(COMPONENT_NLM, "Cannot bind"); close(fd); return -1; } buf = rpcb_find_mapped_addr( (char *)client_type_str, NLMPROG, NLM4_VERS, caller_name); /* handle error here, for example, * client side blocking rpc call */ if (buf == NULL) { LogMajor( COMPONENT_NLM, "Cannot create NLM async %s connection to client %s", client_type_str, caller_name); close(fd); return -1; } retval = find_peer_addr( caller_name, ((struct sockaddr_in *)buf->buf) ->sin_port, &client_addr); /* buf with inet is only needed for the port */ gsh_free(buf->buf); gsh_free(buf); /* retry for spurious EAI_NONAME errors */ if (retval == EAI_NONAME || retval == EAI_AGAIN) { LogEvent( COMPONENT_NLM, "failed to resolve %s to an address: %s", caller_name, gai_strerror(retval)); /* getaddrinfo() failed, retry */ retval = RPC_UNKNOWNADDR; usleep(1000); continue; } else if (retval != 0) { LogMajor( COMPONENT_NLM, "failed to resolve %s to an address: %s", caller_name, gai_strerror(retval)); return -1; } /* setup the netbuf with in6 address */ local_buf.buf = &client_addr; local_buf.len = sizeof(struct sockaddr_in6); host->slc_callback_clnt = clnt_vc_ncreate(fd, &local_buf, NLMPROG, NLM4_VERS, 0, 0); } else { host->slc_callback_clnt = clnt_ncreate( caller_name, NLMPROG, NLM4_VERS, (char *)client_type_str); } if (CLNT_FAILURE(host->slc_callback_clnt)) { char *err = rpc_sperror( &host->slc_callback_clnt->cl_error, "failed"); LogMajor( COMPONENT_NLM, "Create NLM async %s connection to client %s %s", client_type_str, caller_name, err); gsh_free(err); CLNT_DESTROY(host->slc_callback_clnt); host->slc_callback_clnt = NULL; return -1; } /* split auth (for authnone, idempotent) */ host->slc_callback_auth = authnone_ncreate(); } PTHREAD_MUTEX_lock(&nlm_async_resp_mutex); resp_key = key; PTHREAD_MUTEX_unlock(&nlm_async_resp_mutex); LogFullDebug(COMPONENT_NLM, "About to make clnt_call"); cc = gsh_malloc(sizeof(*cc)); clnt_req_fill(cc, host->slc_callback_clnt, host->slc_callback_auth, proc, (xdrproc_t)nlm_reply_proc[proc], inarg, (xdrproc_t)xdr_void, NULL); cc->cc_error.re_status = clnt_req_setup(cc, tout); if (cc->cc_error.re_status == RPC_SUCCESS) { cc->cc_refreshes = 0; cc->cc_error.re_status = CLNT_CALL_ONCE(cc); } LogFullDebug(COMPONENT_NLM, "Done with clnt_call"); if (cc->cc_error.re_status == RPC_TIMEDOUT || cc->cc_error.re_status == RPC_SUCCESS) { retval = RPC_SUCCESS; clnt_req_release(cc); break; } retval = cc->cc_error.re_status; t = rpc_sperror(&cc->cc_error, "failed"); LogCrit(COMPONENT_NLM, "NLM async Client procedure call %d %s", proc, t); gsh_free(t); clnt_req_release(cc); CLNT_DESTROY(host->slc_callback_clnt); host->slc_callback_clnt = NULL; } if (retry == MAX_ASYNC_RETRY) { LogMajor(COMPONENT_NLM, "NLM async Client exceeded retry count %d", MAX_ASYNC_RETRY); PTHREAD_MUTEX_lock(&nlm_async_resp_mutex); resp_key = NULL; PTHREAD_MUTEX_unlock(&nlm_async_resp_mutex); return retval; } PTHREAD_MUTEX_lock(&nlm_async_resp_mutex); if (resp_key != NULL) { /* Wait for 5 seconds or a signal */ gettimeofday(&start, NULL); gettimeofday(&now, NULL); timeout.tv_sec = 5 + start.tv_sec; timeout.tv_nsec = 0; LogFullDebug(COMPONENT_NLM, "About to wait for signal for key %p", resp_key); while (resp_key != NULL && now.tv_sec < (start.tv_sec + 5)) { int rc; rc = pthread_cond_timedwait(&nlm_async_resp_cond, &nlm_async_resp_mutex, &timeout); LogFullDebug(COMPONENT_NLM, "pthread_cond_timedwait returned %d", rc); gettimeofday(&now, NULL); } LogFullDebug(COMPONENT_NLM, "Done waiting"); } PTHREAD_MUTEX_unlock(&nlm_async_resp_mutex); return retval; } void nlm_signal_async_resp(void *key) { PTHREAD_MUTEX_lock(&nlm_async_resp_mutex); if (resp_key == key) { resp_key = NULL; pthread_cond_signal(&nlm_async_resp_cond); LogFullDebug(COMPONENT_NLM, "Signaled condition variable"); } else { LogFullDebug(COMPONENT_NLM, "Didn't signal condition variable"); } PTHREAD_MUTEX_unlock(&nlm_async_resp_mutex); } nfs-ganesha-6.5/src/Protocols/NLM/nlm_util.c000066400000000000000000000427311473756622300207740ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2010 * Contributor: Aneesh Kumar K.v * * * -------------------------- * * This program 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 3 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 * 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 * * */ #include "config.h" #include #include #include #include "log.h" #include "fsal.h" #include "sal_functions.h" #include "nfs_proto_tools.h" #include "nlm_util.h" #include "nsm.h" #include "nlm_async.h" #include "nfs_core.h" #include "export_mgr.h" /* nlm grace time tracking */ static struct timeval nlm_grace_tv; /* We manage our own cookie for GRANTED call backs * Cookie */ struct granted_cookie { unsigned long gc_seconds; unsigned long gc_microseconds; unsigned long gc_cookie; }; struct granted_cookie granted_cookie; pthread_mutex_t granted_mutex; void next_granted_cookie(struct granted_cookie *cookie) { PTHREAD_MUTEX_lock(&granted_mutex); granted_cookie.gc_cookie++; *cookie = granted_cookie; PTHREAD_MUTEX_unlock(&granted_mutex); } const char *lock_result_str(int rc) { switch (rc) { case NLM4_GRANTED: return "NLM4_GRANTED"; case NLM4_DENIED: return "NLM4_DENIED"; case NLM4_DENIED_NOLOCKS: return "NLM4_DENIED_NOLOCKS"; case NLM4_BLOCKED: return "NLM4_BLOCKED"; case NLM4_DENIED_GRACE_PERIOD: return "NLM4_DENIED_GRACE_PERIOD"; case NLM4_DEADLCK: return "NLM4_DEADLCK"; case NLM4_ROFS: return "NLM4_ROFS"; case NLM4_STALE_FH: return "NLM4_STALE_FH"; case NLM4_FBIG: return "NLM4_FBIG"; case NLM4_FAILED: return "NLM4_FAILED"; default: return "Unknown"; } } inline uint64_t lock_end(uint64_t start, uint64_t len) { if (len == 0) return UINT64_MAX; else return start + len - 1; } void fill_netobj(netobj *dst, char *data, int len) { dst->n_len = 0; dst->n_bytes = NULL; if (len != 0) { dst->n_bytes = gsh_malloc(len); dst->n_len = len; memcpy(dst->n_bytes, data, len); } } void copy_netobj(netobj *dst, netobj *src) { if (src->n_len != 0) { dst->n_bytes = gsh_malloc(src->n_len); memcpy(dst->n_bytes, src->n_bytes, src->n_len); } else dst->n_bytes = NULL; dst->n_len = src->n_len; } void netobj_free(netobj *obj) { gsh_free(obj->n_bytes); } void netobj_to_string(netobj *obj, char *buffer, int maxlen) { int len = obj->n_len; struct display_buffer dspbuf = { maxlen, buffer, buffer }; display_opaque_value(&dspbuf, obj->n_bytes, len); } void nlm_init(void) { /* start NLM grace period */ gettimeofday(&nlm_grace_tv, NULL); /* also use this time to initialize granted_cookie */ granted_cookie.gc_seconds = (unsigned long)nlm_grace_tv.tv_sec; granted_cookie.gc_microseconds = (unsigned long)nlm_grace_tv.tv_usec; granted_cookie.gc_cookie = 0; } void free_grant_arg(state_async_queue_t *arg) { state_nlm_async_data_t *nlm_arg = &arg->state_async_data.state_nlm_async_data; netobj_free(&nlm_arg->nlm_async_args.nlm_async_grant.cookie); netobj_free(&nlm_arg->nlm_async_args.nlm_async_grant.alock.oh); netobj_free(&nlm_arg->nlm_async_args.nlm_async_grant.alock.fh); gsh_free(nlm_arg->nlm_async_args.nlm_async_grant.alock.caller_name); gsh_free(arg); } /** * * nlm4_send_grant_msg: Send NLMPROC4_GRANTED_MSG * * This runs in the nlm_asyn_thread context. */ static void nlm4_send_grant_msg(state_async_queue_t *arg) { int retval; char buffer[1024] = "\0"; state_status_t state_status = STATE_SUCCESS; state_cookie_entry_t *cookie_entry; state_nlm_async_data_t *nlm_arg = &arg->state_async_data.state_nlm_async_data; struct req_op_context op_context; struct gsh_export *export; nlm4_testargs *nlm_async_grant; nlm_async_grant = &nlm_arg->nlm_async_args.nlm_async_grant; if (isDebug(COMPONENT_NLM)) { netobj_to_string(&nlm_async_grant->cookie, buffer, sizeof(buffer)); LogDebug(COMPONENT_NLM, "Sending GRANTED for arg=%p svid=%d start=%" PRIx64 " len=%" PRIx64 " cookie=%s", arg, nlm_async_grant->alock.svid, nlm_async_grant->alock.l_offset, nlm_async_grant->alock.l_len, buffer); } retval = nlm_send_async(NLMPROC4_GRANTED_MSG, nlm_arg->nlm_async_host, nlm_async_grant, nlm_arg->nlm_async_key); dec_nlm_client_ref(nlm_arg->nlm_async_host); /* If success, we are done. */ if (retval == RPC_SUCCESS) goto out; /* * We are not able call granted callback. Some client may retry * the lock again. So remove the existing blocked nlm entry */ LogEvent( COMPONENT_NLM, "GRANTED_MSG RPC call failed with return code %d. Removing the blocking lock", retval); state_status = state_find_grant(nlm_async_grant->cookie.n_bytes, nlm_async_grant->cookie.n_len, &cookie_entry); if (state_status != STATE_SUCCESS) { /* This must be an old NLM_GRANTED_RES */ LogFullDebug(COMPONENT_NLM, "Could not find cookie=%s status=%s", buffer, state_err_str(state_status)); goto out; } if (cookie_entry->sce_lock_entry->sle_block_data == NULL) { /* Wow, we're not doing well... */ LogFullDebug( COMPONENT_NLM, "Could not find block data for cookie=%s (must be an old NLM_GRANTED_RES)", buffer); goto out; } /* Initialize a context, it is ok if the export is stale because * we must clean up the cookie_entry. */ export = cookie_entry->sce_lock_entry->sle_export; get_gsh_export_ref(export); init_op_context(&op_context, export, export->fsal_export, NULL, NULL, NFS_V3, 0, NFS_RELATED); state_status = state_release_grant(cookie_entry); release_op_context(); if (state_status != STATE_SUCCESS) { /* Huh? */ LogFullDebug(COMPONENT_NLM, "Could not release cookie=%s status=%s", buffer, state_err_str(state_status)); } out: free_grant_arg(arg); } int nlm_process_parameters(struct svc_req *req, bool exclusive, nlm4_lock *alock, fsal_lock_param_t *plock, struct fsal_obj_handle **ppobj, care_t care, state_nsm_client_t **ppnsm_client, state_nlm_client_t **ppnlm_client, state_owner_t **ppowner, state_block_data_t **block_data, int32_t nsm_state, state_t **state) { nfsstat3 nfsstat3; SVCXPRT *ptr_svc = req->rq_xprt; int rc; uint64_t maxfilesize = op_ctx->fsal_export->exp_ops.fs_maxfilesize( op_ctx->fsal_export); *ppnsm_client = NULL; *ppnlm_client = NULL; *ppowner = NULL; if (state != NULL) *state = NULL; if (alock->l_offset > maxfilesize) { /* Offset larger than max file size */ return NLM4_FBIG; } /* Convert file handle into a fsal object */ *ppobj = nfs3_FhandleToCache((struct nfs_fh3 *)&alock->fh, &nfsstat3, &rc); if (*ppobj == NULL) { /* handle is not valid */ return NLM4_STALE_FH; } if ((*ppobj)->type != REGULAR_FILE) { LogWarn(COMPONENT_NLM, "NLM operation on non-REGULAR_FILE"); return NLM4_FAILED; } *ppnsm_client = get_nsm_client(care, alock->caller_name); if (*ppnsm_client == NULL) { /* If NSM Client is not found, and we don't care (such as * unlock), just return GRANTED (the unlock must succeed, * there can't be any locks). */ if (care != CARE_NOT) rc = NLM4_DENIED_NOLOCKS; else rc = NLM4_GRANTED; goto out_put; } *ppnlm_client = get_nlm_client(care, ptr_svc, *ppnsm_client, alock->caller_name); if (*ppnlm_client == NULL) { /* If NLM Client is not found, and we don't care (such as * unlock), just return GRANTED (the unlock must succeed, * there can't be any locks). */ if (care != CARE_NOT) rc = NLM4_DENIED_NOLOCKS; else rc = NLM4_GRANTED; goto out_put; } *ppowner = get_nlm_owner(care, *ppnlm_client, &alock->oh, alock->svid); if (*ppowner == NULL) { LogDebug(COMPONENT_NLM, "Could not get NLM Owner"); /* If owner is not found, and we don't care (such as unlock), * just return GRANTED (the unlock must succeed, there can't be * any locks). */ if (care != CARE_NOT) rc = NLM4_DENIED_NOLOCKS; else rc = NLM4_GRANTED; goto out_put; } if (state != NULL) { rc = get_nlm_state(STATE_TYPE_NLM_LOCK, *ppobj, *ppowner, care, nsm_state, state); if (rc > 0) { LogDebug(COMPONENT_NLM, "Could not get NLM State"); goto out_put; } } if (block_data != NULL) { state_block_data_t *bdat = gsh_calloc(1, sizeof(*bdat)); *block_data = bdat; /* Fill in the block data. */ bdat->sbd_granted_callback = nlm_granted_callback; bdat->sbd_prot.sbd_nlm.sbd_nlm_fh.n_bytes = bdat->sbd_prot.sbd_nlm.sbd_nlm_fh_buf; bdat->sbd_prot.sbd_nlm.sbd_nlm_fh.n_len = alock->fh.n_len; memcpy(bdat->sbd_prot.sbd_nlm.sbd_nlm_fh_buf, alock->fh.n_bytes, alock->fh.n_len); } /* Fill in plock (caller will reset reclaim if appropriate) */ plock->lock_sle_type = FSAL_POSIX_LOCK; plock->lock_reclaim = false; plock->lock_type = exclusive ? FSAL_LOCK_W : FSAL_LOCK_R; plock->lock_start = alock->l_offset; plock->lock_length = alock->l_len; /* Check for range overflow past maxfilesize. Comparing beyond 2^64 is * not possible in 64 bits precision, but off+len > maxfilesize is * equivalent to len > maxfilesize - off */ if (alock->l_len > (maxfilesize - alock->l_offset)) { /* Fix up lock length to 0 - end of file */ LogFullDebug(COMPONENT_NLM, "Converting lock length %" PRIx64 " to 0", alock->l_len); plock->lock_length = 0; } LogFullDebug(COMPONENT_NLM, "Parameters Processed"); return -1; out_put: (*ppobj)->obj_ops->put_ref((*ppobj)); if (*ppnsm_client != NULL) { dec_nsm_client_ref(*ppnsm_client); *ppnsm_client = NULL; } if (*ppnlm_client != NULL) { dec_nlm_client_ref(*ppnlm_client); *ppnlm_client = NULL; } if (*ppowner != NULL) { dec_state_owner_ref(*ppowner); *ppowner = NULL; } *ppobj = NULL; return rc; } int nlm_process_share_parms(struct svc_req *req, nlm4_share *share, struct fsal_export *exp_hdl, struct fsal_obj_handle **ppobj, care_t care, state_nsm_client_t **ppnsm_client, state_nlm_client_t **ppnlm_client, state_owner_t **ppowner, state_t **state) { nfsstat3 nfsstat3; SVCXPRT *ptr_svc = req->rq_xprt; int rc; *ppnsm_client = NULL; *ppnlm_client = NULL; *ppowner = NULL; /* Convert file handle into a fsal object */ *ppobj = nfs3_FhandleToCache((struct nfs_fh3 *)&share->fh, &nfsstat3, &rc); if (*ppobj == NULL) { /* handle is not valid */ return NLM4_STALE_FH; } if ((*ppobj)->type != REGULAR_FILE) { LogWarn(COMPONENT_NLM, "NLM operation on non-REGULAR_FILE"); return NLM4_FAILED; } *ppnsm_client = get_nsm_client(care, share->caller_name); if (*ppnsm_client == NULL) { /* If NSM Client is not found, and we don't care (for unshare), * just return GRANTED (the unshare must succeed, there can't be * any shares). */ if (care != CARE_NOT) rc = NLM4_DENIED_NOLOCKS; else rc = NLM4_GRANTED; goto out_put; } *ppnlm_client = get_nlm_client(care, ptr_svc, *ppnsm_client, share->caller_name); if (*ppnlm_client == NULL) { /* If NLM Client is not found, and we don't care (such as * unlock), just return GRANTED (the unlock must succeed, there * can't be any locks). */ if (care != CARE_NOT) rc = NLM4_DENIED_NOLOCKS; else rc = NLM4_GRANTED; goto out_put; } *ppowner = get_nlm_owner(care, *ppnlm_client, &share->oh, 0); if (*ppowner == NULL) { LogDebug(COMPONENT_NLM, "Could not get NLM Owner"); /* If owner is not found, and we don't care (such as unlock), * just return GRANTED (the unlock must succeed, there can't be * any locks). */ if (care != CARE_NOT) rc = NLM4_DENIED_NOLOCKS; else rc = NLM4_GRANTED; goto out_put; } if (state != NULL) { rc = get_nlm_state(STATE_TYPE_NLM_SHARE, *ppobj, *ppowner, care, 0, state); if (rc > 0 || !(*state)) { LogDebug(COMPONENT_NLM, "Could not get NLM State"); goto out_put; } } LogFullDebug(COMPONENT_NLM, "Parameters Processed"); /* Return non NLM error code '-1' on success. */ return -1; out_put: if (*ppnsm_client != NULL) { dec_nsm_client_ref(*ppnsm_client); *ppnsm_client = NULL; } if (*ppnlm_client != NULL) { dec_nlm_client_ref(*ppnlm_client); *ppnlm_client = NULL; } if (*ppowner != NULL) { dec_state_owner_ref(*ppowner); *ppowner = NULL; } (*ppobj)->obj_ops->put_ref((*ppobj)); *ppobj = NULL; return rc; } void nlm_process_conflict(nlm4_holder *nlm_holder, state_owner_t *holder, fsal_lock_param_t *conflict) { if (conflict != NULL) { nlm_holder->exclusive = conflict->lock_type == FSAL_LOCK_W; nlm_holder->l_offset = conflict->lock_start; nlm_holder->l_len = conflict->lock_length; } else { /* For some reason, don't have an actual conflict, * just make it exclusive over the whole file * (which would conflict with any lock requested). */ nlm_holder->exclusive = true; nlm_holder->l_offset = 0; nlm_holder->l_len = 0; } if (holder != NULL) { if (holder->so_type == STATE_LOCK_OWNER_NLM) nlm_holder->svid = holder->so_owner.so_nlm_owner.so_nlm_svid; else nlm_holder->svid = 0; fill_netobj(&nlm_holder->oh, holder->so_owner_val, holder->so_owner_len); } else { /* If we don't have an NLM owner, not much we can do. */ nlm_holder->svid = 0; fill_netobj(&nlm_holder->oh, unknown_owner.so_owner_val, unknown_owner.so_owner_len); } /* Release any lock owner reference passed back from SAL */ if (holder != NULL) dec_state_owner_ref(holder); } nlm4_stats nlm_convert_state_error(state_status_t status) { switch (status) { case STATE_SUCCESS: return NLM4_GRANTED; case STATE_LOCK_CONFLICT: return NLM4_DENIED; case STATE_SHARE_DENIED: return NLM4_DENIED; case STATE_MALLOC_ERROR: return NLM4_DENIED_NOLOCKS; case STATE_LOCK_BLOCKED: return NLM4_BLOCKED; case STATE_GRACE_PERIOD: return NLM4_DENIED_GRACE_PERIOD; case STATE_LOCK_DEADLOCK: return NLM4_DEADLCK; case STATE_READ_ONLY_FS: return NLM4_ROFS; case STATE_NOT_FOUND: return NLM4_STALE_FH; case STATE_ESTALE: return NLM4_STALE_FH; case STATE_FILE_BIG: case STATE_BAD_RANGE: return NLM4_FBIG; default: return NLM4_FAILED; } } state_status_t nlm_granted_callback(struct fsal_obj_handle *obj, state_lock_entry_t *lock_entry) { state_block_data_t *block_data = lock_entry->sle_block_data; state_nlm_block_data_t *nlm_block_data = &block_data->sbd_prot.sbd_nlm; state_cookie_entry_t *cookie_entry = NULL; state_async_queue_t *arg; nlm4_testargs *inarg; state_nlm_async_data_t *nlm_async_data; state_nlm_owner_t *nlm_grant_owner = &lock_entry->sle_owner->so_owner.so_nlm_owner; state_nlm_client_t *nlm_grant_client = nlm_grant_owner->so_client; struct granted_cookie nlm_grant_cookie; state_status_t state_status; state_status_t state_status_int; arg = gsh_calloc(1, sizeof(*arg)); nlm_async_data = &arg->state_async_data.state_nlm_async_data; /* Get a cookie to use for this grant */ next_granted_cookie(&nlm_grant_cookie); /* Add a cookie to the blocked lock pending grant. * It will also request lock from FSAL. * Could return STATE_LOCK_BLOCKED because FSAL would have had to block. */ state_status = state_add_grant_cookie(obj, &nlm_grant_cookie, sizeof(nlm_grant_cookie), lock_entry, &cookie_entry); if (state_status != STATE_SUCCESS) { free_grant_arg(arg); return state_status; } /* Fill in the arguments for the NLMPROC4_GRANTED_MSG call */ inc_nlm_client_ref(nlm_grant_client); arg->state_async_func = nlm4_send_grant_msg; nlm_async_data->nlm_async_host = nlm_grant_client; nlm_async_data->nlm_async_key = cookie_entry; inarg = &nlm_async_data->nlm_async_args.nlm_async_grant; copy_netobj(&inarg->alock.fh, &nlm_block_data->sbd_nlm_fh); fill_netobj(&inarg->alock.oh, lock_entry->sle_owner->so_owner_val, lock_entry->sle_owner->so_owner_len); fill_netobj(&inarg->cookie, (char *)&nlm_grant_cookie, sizeof(nlm_grant_cookie)); inarg->alock.caller_name = gsh_strdup(nlm_grant_client->slc_nlm_caller_name); inarg->exclusive = lock_entry->sle_lock.lock_type == FSAL_LOCK_W; inarg->alock.svid = nlm_grant_owner->so_nlm_svid; inarg->alock.l_offset = lock_entry->sle_lock.lock_start; inarg->alock.l_len = lock_entry->sle_lock.lock_length; if (isDebug(COMPONENT_NLM)) { char buffer[1024] = "\0"; netobj_to_string(&inarg->cookie, buffer, sizeof(buffer)); LogDebug( COMPONENT_NLM, "Sending GRANTED for arg=%p svid=%d start=%llx len=%llx cookie=%s", arg, inarg->alock.svid, (unsigned long long)inarg->alock.l_offset, (unsigned long long)inarg->alock.l_len, buffer); } /* Now try to schedule NLMPROC4_GRANTED_MSG call */ state_status = state_async_schedule(arg); if (state_status != STATE_SUCCESS) goto grant_fail; return state_status; grant_fail: /* Something went wrong after we added a grant cookie, * need to clean up */ dec_nlm_client_ref(nlm_grant_client); /* Clean up NLMPROC4_GRANTED_MSG arguments */ free_grant_arg(arg); /* Cancel the pending grant to release the cookie */ state_status_int = state_cancel_grant(cookie_entry); if (state_status_int != STATE_SUCCESS) { /* Not much we can do other than log that something * bad happened. */ LogCrit(COMPONENT_NLM, "Unable to clean up GRANTED lock after error"); } return state_status; } nfs-ganesha-6.5/src/Protocols/NLM/nsm.c000066400000000000000000000173401473756622300177440ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2010 * Contributor: Aneesh Kumar K.v * : M. Mohan Kumar * * -------------------------- * * This program 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 3 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 * 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 * * */ #include "config.h" #include #include "abstract_atomic.h" #include "gsh_rpc.h" #include "nsm.h" #include "sal_data.h" pthread_mutex_t nsm_mutex; CLIENT *nsm_clnt; AUTH *nsm_auth; unsigned long nsm_count; char *nodename; /* retry timeout default to the moon and back */ static const struct timespec tout = { 3, 0 }; bool nsm_connect(void) { struct utsname utsname; if (nsm_clnt != NULL) return true; if (uname(&utsname) == -1) { LogCrit(COMPONENT_NLM, "uname failed with errno %d (%s)", errno, strerror(errno)); return false; } nodename = gsh_strdup(utsname.nodename); nsm_clnt = clnt_ncreate("localhost", SM_PROG, SM_VERS, "tcp"); if (CLNT_FAILURE(nsm_clnt)) { char *err = rpc_sperror(&nsm_clnt->cl_error, "failed"); LogEventLimited(COMPONENT_NLM, "connect to statd %s", err); gsh_free(err); CLNT_DESTROY(nsm_clnt); nsm_clnt = NULL; gsh_free(nodename); nodename = NULL; } /* split auth (for authnone, idempotent) */ nsm_auth = authnone_ncreate(); return nsm_clnt != NULL; } void nsm_disconnect(bool force) { if ((nsm_count == 0 || force) && nsm_clnt != NULL) { CLNT_DESTROY(nsm_clnt); nsm_clnt = NULL; AUTH_DESTROY(nsm_auth); nsm_auth = NULL; gsh_free(nodename); nodename = NULL; } } static bool nsm_monitor_noretry(state_nsm_client_t *host) { struct clnt_req *cc; char *t; struct mon nsm_mon; struct sm_stat_res res; enum clnt_stat ret; if (host == NULL) return true; PTHREAD_MUTEX_lock(&host->ssc_mutex); if (atomic_fetch_int32_t(&host->ssc_monitored)) { PTHREAD_MUTEX_unlock(&host->ssc_mutex); return true; } memset(&nsm_mon, 0, sizeof(nsm_mon)); nsm_mon.mon_id.mon_name = host->ssc_nlm_caller_name; nsm_mon.mon_id.my_id.my_prog = NLMPROG; nsm_mon.mon_id.my_id.my_vers = NLM4_VERS; nsm_mon.mon_id.my_id.my_proc = NLMPROC4_SM_NOTIFY; /* nothing to put in the private data */ LogDebug(COMPONENT_NLM, "Monitor %s", host->ssc_nlm_caller_name); PTHREAD_MUTEX_lock(&nsm_mutex); /* create a connection to nsm on the localhost */ if (!nsm_connect()) { LogEventLimited(COMPONENT_NLM, "Monitor %s nsm_connect failed", nsm_mon.mon_id.mon_name); PTHREAD_MUTEX_unlock(&nsm_mutex); PTHREAD_MUTEX_unlock(&host->ssc_mutex); return false; } /* Set this after we call nsm_connect() */ nsm_mon.mon_id.my_id.my_name = nodename; cc = gsh_malloc(sizeof(*cc)); clnt_req_fill(cc, nsm_clnt, nsm_auth, SM_MON, (xdrproc_t)xdr_mon, &nsm_mon, (xdrproc_t)xdr_sm_stat_res, &res); ret = clnt_req_setup(cc, tout); if (ret == RPC_SUCCESS) { ret = CLNT_CALL_WAIT(cc); } if (ret != RPC_SUCCESS) { t = rpc_sperror(&cc->cc_error, "failed"); LogEventLimited(COMPONENT_NLM, "Monitor %s SM_MON %s", nsm_mon.mon_id.mon_name, t); gsh_free(t); clnt_req_release(cc); nsm_disconnect(true); PTHREAD_MUTEX_unlock(&nsm_mutex); PTHREAD_MUTEX_unlock(&host->ssc_mutex); return false; } clnt_req_release(cc); if (res.res_stat != STAT_SUCC) { LogCrit(COMPONENT_NLM, "Monitor %s SM_MON failed (%d)", nsm_mon.mon_id.mon_name, res.res_stat); nsm_disconnect(true); PTHREAD_MUTEX_unlock(&nsm_mutex); PTHREAD_MUTEX_unlock(&host->ssc_mutex); return false; } nsm_count++; atomic_store_int32_t(&host->ssc_monitored, true); LogDebug(COMPONENT_NLM, "Monitored %s for nodename %s", nsm_mon.mon_id.mon_name, nodename); PTHREAD_MUTEX_unlock(&nsm_mutex); PTHREAD_MUTEX_unlock(&host->ssc_mutex); return true; } bool nsm_monitor(state_nsm_client_t *host) { /* If someone restarts nsm service, nsm_monitor_noretry may fail * and would tear down the old structures. A retry should work! * So let us retry once if there is a failure. */ if (nsm_monitor_noretry(host)) return true; return nsm_monitor_noretry(host); } static bool nsm_unmonitor_noretry(state_nsm_client_t *host) { struct clnt_req *cc; char *t; struct sm_stat res; struct mon_id nsm_mon_id; enum clnt_stat ret; if (host == NULL) return true; PTHREAD_MUTEX_lock(&host->ssc_mutex); if (!atomic_fetch_int32_t(&host->ssc_monitored)) { PTHREAD_MUTEX_unlock(&host->ssc_mutex); return true; } nsm_mon_id.mon_name = host->ssc_nlm_caller_name; nsm_mon_id.my_id.my_prog = NLMPROG; nsm_mon_id.my_id.my_vers = NLM4_VERS; nsm_mon_id.my_id.my_proc = NLMPROC4_SM_NOTIFY; PTHREAD_MUTEX_lock(&nsm_mutex); /* create a connection to nsm on the localhost */ if (!nsm_connect()) { LogEventLimited(COMPONENT_NLM, "Unmonitor %s nsm_connect failed", nsm_mon_id.mon_name); PTHREAD_MUTEX_unlock(&nsm_mutex); PTHREAD_MUTEX_unlock(&host->ssc_mutex); return false; } /* Set this after we call nsm_connect() */ nsm_mon_id.my_id.my_name = nodename; cc = gsh_malloc(sizeof(*cc)); clnt_req_fill(cc, nsm_clnt, nsm_auth, SM_UNMON, (xdrproc_t)xdr_mon_id, &nsm_mon_id, (xdrproc_t)xdr_sm_stat, &res); ret = clnt_req_setup(cc, tout); if (ret == RPC_SUCCESS) { ret = CLNT_CALL_WAIT(cc); } if (ret != RPC_SUCCESS) { t = rpc_sperror(&cc->cc_error, "failed"); LogEventLimited(COMPONENT_NLM, "Unmonitor %s SM_UNMON %s", nsm_mon_id.mon_name, t); gsh_free(t); clnt_req_release(cc); nsm_disconnect(true); PTHREAD_MUTEX_unlock(&nsm_mutex); PTHREAD_MUTEX_unlock(&host->ssc_mutex); return false; } clnt_req_release(cc); atomic_store_int32_t(&host->ssc_monitored, false); nsm_count--; LogDebug(COMPONENT_NLM, "Unmonitored %s for nodename %s", nsm_mon_id.mon_name, nodename); nsm_disconnect(false); PTHREAD_MUTEX_unlock(&nsm_mutex); PTHREAD_MUTEX_unlock(&host->ssc_mutex); return true; } bool nsm_unmonitor(state_nsm_client_t *host) { /* If someone restarts nsm service, nsm_unmonitor_noretry may * fail and would tear down the old structures. A retry should * work! So let us retry once if there is a failure. */ if (nsm_unmonitor_noretry(host)) return true; return nsm_unmonitor_noretry(host); } void nsm_unmonitor_all(void) { struct clnt_req *cc; char *t; struct sm_stat res; struct my_id nsm_id; enum clnt_stat ret; nsm_id.my_prog = NLMPROG; nsm_id.my_vers = NLM4_VERS; nsm_id.my_proc = NLMPROC4_SM_NOTIFY; PTHREAD_MUTEX_lock(&nsm_mutex); /* create a connection to nsm on the localhost */ if (!nsm_connect()) { LogEventLimited(COMPONENT_NLM, "Unmonitor all nsm_connect failed"); PTHREAD_MUTEX_unlock(&nsm_mutex); return; } /* Set this after we call nsm_connect() */ nsm_id.my_name = nodename; cc = gsh_malloc(sizeof(*cc)); clnt_req_fill(cc, nsm_clnt, nsm_auth, SM_UNMON_ALL, (xdrproc_t)xdr_my_id, &nsm_id, (xdrproc_t)xdr_sm_stat, &res); ret = clnt_req_setup(cc, tout); if (ret == RPC_SUCCESS) { ret = CLNT_CALL_WAIT(cc); } if (ret != RPC_SUCCESS) { t = rpc_sperror(&cc->cc_error, "failed"); LogEventLimited(COMPONENT_NLM, "Unmonitor all %s", t); gsh_free(t); nsm_disconnect(true); } else { nsm_disconnect(false); } clnt_req_release(cc); PTHREAD_MUTEX_unlock(&nsm_mutex); } nfs-ganesha-6.5/src/Protocols/NLM/sm_notify.c000066400000000000000000000115221473756622300211520ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2010 * Contributor: Aneesh Kumar K.v * : M. Mohan Kumar * * -------------------------- * * This program 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 3 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 * 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 * * */ #include /* for memset */ #include #include #include #include #include #include #include #include #include #include "nsm.h" #define STR_SIZE 100 #define USAGE \ "usage: %s [-p ] -l " \ "-m -r -s \n" #define ERR_MSG1 "%s address too long\n" /* attempt to match (irrational) behaviour of previous versions */ static const struct timespec tout = { 15, 0 }; /* This function is dragged in by the use of abstract_mem.h, so * we define a simple version that does a printf rather than * pull in the entirety of log_functions.c into this standalone * program. */ void LogMallocFailure(const char *file, int line, const char *function, const char *allocator) { printf("Aborting %s due to out of memory", allocator); } void *nsm_notify_1(notify *argp, CLIENT *clnt) { static char clnt_res; struct clnt_req *cc; enum clnt_stat ret; memset((char *)&clnt_res, 0, sizeof(clnt_res)); cc = gsh_malloc(sizeof(*cc)); clnt_req_fill(cc, clnt, authnone_ncreate(), SM_NOTIFY, (xdrproc_t)xdr_notify, argp, (xdrproc_t)xdr_void, &clnt_res); ret = clnt_req_setup(cc, tout); if (ret == RPC_SUCCESS) { cc->cc_refreshes = 1; ret = CLNT_CALL_WAIT(cc); } clnt_req_release(cc); if (ret != RPC_SUCCESS) return NULL; return (void *)&clnt_res; } int main(int argc, char **argv) { int c; int port = 0; int state = 0, sflag = 0; char mon_client[STR_SIZE], mflag = 0; char remote_addr_s[STR_SIZE], rflag = 0; char local_addr_s[STR_SIZE], lflag = 0; notify arg; CLIENT *clnt; struct netbuf *buf; char port_str[20]; struct sockaddr_in local_addr; int fd; while ((c = getopt(argc, argv, "p:r:m:l:s:")) != EOF) switch (c) { case 'p': port = atoi(optarg); break; case 's': state = atoi(optarg); sflag = 1; break; case 'm': if (strlcpy(mon_client, optarg, sizeof(mon_client)) >= sizeof(mon_client)) { fprintf(stderr, ERR_MSG1, "monitor host"); exit(1); } mflag = 1; break; case 'r': if (strlcpy(remote_addr_s, optarg, sizeof(remote_addr_s)) >= sizeof(remote_addr_s)) { fprintf(stderr, ERR_MSG1, "remote address"); exit(1); } rflag = 1; break; case 'l': if (strlcpy(local_addr_s, optarg, sizeof(local_addr_s)) >= sizeof(local_addr_s)) { fprintf(stderr, ERR_MSG1, "local address"); exit(1); } lflag = 1; break; case '?': default: fprintf(stderr, USAGE, argv[0]); exit(1); break; } if ((sflag + lflag + mflag + rflag) != 4) { fprintf(stderr, USAGE, argv[0]); exit(1); } /* create a udp socket */ fd = socket(PF_INET, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP); if (fd < 0) { fprintf(stderr, "socket call failed. errno=%d\n", errno); exit(1); } /* set up the sockaddr for local endpoint */ memset(&local_addr, 0, sizeof(struct sockaddr_in)); local_addr.sin_family = PF_INET; local_addr.sin_port = htons(port); local_addr.sin_addr.s_addr = inet_addr(local_addr_s); if (bind(fd, (struct sockaddr *)&local_addr, sizeof(struct sockaddr)) < 0) { fprintf(stderr, "bind call failed. errno=%d\n", errno); exit(1); } /* find the port for SM service of the remote server */ buf = rpcb_find_mapped_addr("udp", SM_PROG, SM_VERS, remote_addr_s); /* handle error here, for example, * client side blocking rpc call */ if (buf == NULL) { close(fd); exit(1); } /* convert port to string format */ (void)sprintf(port_str, "%d", htons(((struct sockaddr_in *)buf->buf)->sin_port)); clnt = clnt_dg_ncreate(fd, buf, SM_PROG, SM_VERS, 0, 0); arg.my_name = mon_client; arg.state = state; nsm_notify_1(&arg, clnt); /* free resources */ gsh_free(buf->buf); gsh_free(buf); CLNT_DESTROY(clnt); close(fd); return 0; } nfs-ganesha-6.5/src/Protocols/RQUOTA/000077500000000000000000000000001473756622300173635ustar00rootroot00000000000000nfs-ganesha-6.5/src/Protocols/RQUOTA/CMakeLists.txt000066400000000000000000000030061473756622300221220ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- ########### next target ############### SET(rquota_STAT_SRCS rquota_Null.c rquota_getquota.c rquota_getactivequota.c rquota_setquota.c rquota_setactivequota.c rquota_common.c ) add_library(rquota OBJECT ${rquota_STAT_SRCS}) add_sanitizers(rquota) set_target_properties(rquota PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(rquota gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) ########### install files ############### nfs-ganesha-6.5/src/Protocols/RQUOTA/rquota_Null.c000066400000000000000000000035551473756622300220440ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright CEA/DAM/DIF 2010 * Author: Philippe Deniel (philippe.deniel@cea.fr) * * -------------------------- * * This program 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 3 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 * 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 */ #include "config.h" #include #include #include #include #include #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs23.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_exports.h" #include "mount.h" #include "rquota.h" #include "nfs_proto_functions.h" /** * @brief The RQUOTA proc null function, for all versions. * * @param[in] arg Ignored * @param[in] req Ignored * @param[out] res Ignored */ int rquota_Null(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { LogFullDebug(COMPONENT_NFSPROTO, "REQUEST PROCESSING: Calling RQUOTA_NULL"); /* 0 is success */ return 0; } /** * rquota_Null_Free: Frees the result structure allocated for rquota_Null * * Frees the result structure allocated for rquota_Null. Does Nothing in fact. * * @param res [INOUT] Pointer to the result structure. * */ void rquota_Null_Free(nfs_res_t *res) { /* Nothing to do */ } nfs-ganesha-6.5/src/Protocols/RQUOTA/rquota_common.c000066400000000000000000000043561473756622300224220ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright CEA/DAM/DIF 2016 * Author: Philippe Deniel (philippe.deniel@cea.fr) * * -------------------------- * * This program 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 3 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 * 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 */ #include #include #include "log.h" #include "export_mgr.h" /** * Check if leading slash is missing, if yes then prepend * root path to the pathname */ char *check_handle_lead_slash(char *quota_path, char *temp_path, size_t temp_path_size) { if (quota_path[0] != '/') { /* prepend root path */ struct gsh_export *exp; int pathlen; int qpathlen; struct gsh_refstr *ref_fullpath; exp = get_gsh_export_by_pseudo("/", true); if (exp == NULL) return NULL; rcu_read_lock(); ref_fullpath = gsh_refstr_get(rcu_dereference(exp->fullpath)); rcu_read_unlock(); if (ref_fullpath == NULL) { put_gsh_export(exp); return NULL; } pathlen = strlen(ref_fullpath->gr_val); if (pathlen >= temp_path_size) { gsh_refstr_put(ref_fullpath); put_gsh_export(exp); return NULL; } memcpy(temp_path, ref_fullpath->gr_val, pathlen); gsh_refstr_put(ref_fullpath); put_gsh_export(exp); /* Add trailing slash if it is missing */ if ((pathlen > 0) && (temp_path[pathlen - 1] != '/')) temp_path[pathlen++] = '/'; qpathlen = strlen(quota_path); if ((pathlen + qpathlen) >= temp_path_size) { LogInfo(COMPONENT_NFSPROTO, "Quota path %s too long", quota_path); return NULL; } memcpy(temp_path + pathlen, quota_path, qpathlen + 1); return temp_path; } else { return quota_path; } } nfs-ganesha-6.5/src/Protocols/RQUOTA/rquota_getactivequota.c000066400000000000000000000034701473756622300241530ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright CEA/DAM/DIF 2010 * Author: Philippe Deniel (philippe.deniel@cea.fr) * * * This program 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 3 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 * 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 * */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs23.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_exports.h" #include "mount.h" #include "rquota.h" #include "nfs_proto_functions.h" /** * @brief The Rquota getactivequota function, for all versions. * * @param[in] arg Ignored * @param[in] req Ignored * @param[out] res Ignored * */ int rquota_getactivequota(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { LogFullDebug(COMPONENT_NFSPROTO, "REQUEST PROCESSING: Calling RQUOTA_GETACTIVEQUOTA"); /* 0 is success */ return 0; } /** * @brief Frees the result structure allocated for rquota_getactivequota * * @param[in,out] res Pointer to the result structure. * */ void rquota_getactivequota_Free(nfs_res_t *res) { /* Nothing to do */ } nfs-ganesha-6.5/src/Protocols/RQUOTA/rquota_getquota.c000066400000000000000000000131711473756622300227560ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright CEA/DAM/DIF 2010 * Author: Philippe Deniel (philippe.deniel@cea.fr) * * -------------------------- * * This program 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 3 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 * 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 */ #include "config.h" #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs23.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_exports.h" #include "mount.h" #include /* For USRQUOTA */ #include "rquota.h" #include "nfs_proto_functions.h" #include "export_mgr.h" #include "nfs_creds.h" /** * @brief The Rquota getquota function, for all versions. * * @param[in] arg Ignored * @param[in] req Ignored * @param[out] res Ignored * */ int rquota_getquota(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { fsal_status_t fsal_status; fsal_quota_t fsal_quota; int quota_type = USRQUOTA; struct gsh_export *exp = NULL; char *quota_path; getquota_rslt *qres = &res->res_rquota_getquota; char path[MAXPATHLEN]; int quota_id; uint64_t bhardlimitscaled = 0; uint64_t bsoftlimitscaled = 0; uint64_t curblocksscaled = 0; uint64_t bsizescaled = 0; LogFullDebug(COMPONENT_NFSPROTO, "REQUEST PROCESSING: Calling RQUOTA_GETQUOTA"); if (req->rq_msg.cb_vers == EXT_RQUOTAVERS) { quota_type = arg->arg_ext_rquota_getquota.gqa_type; quota_id = arg->arg_ext_rquota_getquota.gqa_id; } else { quota_id = arg->arg_rquota_getquota.gqa_uid; } qres->status = Q_EPERM; quota_path = check_handle_lead_slash(arg->arg_rquota_getquota.gqa_pathp, path, MAXPATHLEN); if (quota_path == NULL) return NFS_REQ_OK; /* Find the export for the dirname (using as well Path, Pseudo, or Tag) */ if (quota_path[0] != '/') { LogFullDebug(COMPONENT_NFSPROTO, "Searching for export by tag for %s", quota_path); exp = get_gsh_export_by_tag(quota_path); } else if (nfs_param.core_param.mount_path_pseudo) { LogFullDebug(COMPONENT_NFSPROTO, "Searching for export by pseudo for %s", quota_path); exp = get_gsh_export_by_pseudo(quota_path, false); } else { LogFullDebug(COMPONENT_NFSPROTO, "Searching for export by path for %s", quota_path); exp = get_gsh_export_by_path(quota_path, false); } if (exp == NULL) { /* No export found, return ACCESS error. */ LogEvent(COMPONENT_NFSPROTO, "Export entry for %s not found", quota_path); /* entry not found. */ return NFS_REQ_OK; } /* Add export to op_ctx, will be released in free_args */ set_op_context_export(exp); /* Get creds */ if (nfs_req_creds(req) == NFS4ERR_ACCESS) { const char *client_ip = ""; client_ip = op_ctx->client->hostaddr_str; LogInfo(COMPONENT_NFSPROTO, "could not get uid and gid, rejecting client %s", client_ip); return NFS_REQ_OK; } fsal_status = exp->fsal_export->exp_ops.get_quota(exp->fsal_export, CTX_FULLPATH(op_ctx), quota_type, quota_id, &fsal_quota); if (FSAL_IS_ERROR(fsal_status)) { if (fsal_status.major == ERR_FSAL_NO_QUOTA) qres->status = Q_NOQUOTA; return NFS_REQ_OK; } /* success */ bhardlimitscaled = fsal_quota.bhardlimit; bsoftlimitscaled = fsal_quota.bsoftlimit; curblocksscaled = fsal_quota.curblocks; bsizescaled = fsal_quota.bsize; while ((bhardlimitscaled > UINT32_MAX) || (bsoftlimitscaled > UINT32_MAX) || (curblocksscaled > UINT32_MAX)) { /* check if we hit the limit of scaling, then limit to max */ if ((bsizescaled << 1) > UINT32_MAX) { if (bhardlimitscaled > UINT32_MAX) bhardlimitscaled = UINT32_MAX; if (bsoftlimitscaled > UINT32_MAX) bsoftlimitscaled = UINT32_MAX; if (curblocksscaled > UINT32_MAX) curblocksscaled = UINT32_MAX; } else { /* we can still scale */ bhardlimitscaled = bhardlimitscaled >> 1; bsoftlimitscaled = bsoftlimitscaled >> 1; curblocksscaled = curblocksscaled >> 1; bsizescaled = bsizescaled << 1; } } qres->getquota_rslt_u.gqr_rquota.rq_active = TRUE; /* items that have changed due to scaling */ qres->getquota_rslt_u.gqr_rquota.rq_bsize = bsizescaled; qres->getquota_rslt_u.gqr_rquota.rq_bhardlimit = bhardlimitscaled; qres->getquota_rslt_u.gqr_rquota.rq_bsoftlimit = bsoftlimitscaled; qres->getquota_rslt_u.gqr_rquota.rq_curblocks = curblocksscaled; /* items carried over unchanged */ qres->getquota_rslt_u.gqr_rquota.rq_curfiles = fsal_quota.curfiles; qres->getquota_rslt_u.gqr_rquota.rq_fhardlimit = fsal_quota.fhardlimit; qres->getquota_rslt_u.gqr_rquota.rq_fsoftlimit = fsal_quota.fsoftlimit; qres->getquota_rslt_u.gqr_rquota.rq_btimeleft = fsal_quota.btimeleft; qres->getquota_rslt_u.gqr_rquota.rq_ftimeleft = fsal_quota.ftimeleft; qres->status = Q_OK; return NFS_REQ_OK; } /* rquota_getquota */ /** * @brief Free the result structure allocated for rquota_getquota * * * @param[in,out] res Pointer to the result structure. */ void rquota_getquota_Free(nfs_res_t *res) { /* Nothing to do */ } nfs-ganesha-6.5/src/Protocols/RQUOTA/rquota_setactivequota.c000066400000000000000000000034261473756622300241700ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright CEA/DAM/DIF 2010 * Author: Philippe Deniel (philippe.deniel@cea.fr) * * This program 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 3 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 * 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 */ #include "config.h" #include #include #include #include #include #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs23.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_exports.h" #include "mount.h" #include "rquota.h" #include "nfs_proto_functions.h" /** * @brief The Rquota setactivequota function, for all versions. * * @param[in] arg Ignored * @param[in] req Ignored * @param[out] res Ignored */ int rquota_setactivequota(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { LogFullDebug(COMPONENT_NFSPROTO, "REQUEST PROCESSING: Calling RQUOTA_SETACTIVEQUOTA"); /* 0 is success */ return 0; } /** * @brief Frees the result structure allocated for rquota_setactivequota * * @param[in,out] res Pointer to the result structure. * */ void rquota_setactivequota_Free(nfs_res_t *res) { /* Nothing to do */ } nfs-ganesha-6.5/src/Protocols/RQUOTA/rquota_setquota.c000066400000000000000000000133471473756622300227770ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright CEA/DAM/DIF 2010 * Author: Philippe Deniel (philippe.deniel@cea.fr) * * This program 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 3 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 * 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 */ #include "config.h" #include #include #include #include #include #include #include "hashtable.h" #include "log.h" #include "gsh_rpc.h" #include "nfs23.h" #include "nfs4.h" #include "nfs_core.h" #include "nfs_exports.h" #include "mount.h" #include "rquota.h" #include "nfs_proto_functions.h" #include "export_mgr.h" #include "nfs_creds.h" static int do_rquota_setquota(char *quota_path, int quota_type, int quota_id, sq_dqblk *quota_dqblk, struct svc_req *req, setquota_rslt *qres); /** * @brief The Rquota setquota function, for all versions. * * The RQUOTA setquota function, for all versions. * * @param[in] arg quota args * @param[in] req contains quota version * @param[out] res returned quota (modified) * */ int rquota_setquota(nfs_arg_t *arg, struct svc_req *req, nfs_res_t *res) { char *quota_path; int quota_id; int quota_type = USRQUOTA; struct sq_dqblk *quota_dqblk; setquota_rslt *qres = &res->res_rquota_setquota; LogFullDebug(COMPONENT_NFSPROTO, "REQUEST PROCESSING: Calling RQUOTA_SETQUOTA"); /* check rquota version and extract arguments */ if (req->rq_msg.cb_vers == EXT_RQUOTAVERS) { quota_path = arg->arg_ext_rquota_setquota.sqa_pathp; quota_id = arg->arg_ext_rquota_setquota.sqa_id; quota_type = arg->arg_ext_rquota_setquota.sqa_type; quota_dqblk = &arg->arg_ext_rquota_setquota.sqa_dqblk; } else { quota_path = arg->arg_rquota_setquota.sqa_pathp; quota_id = arg->arg_rquota_setquota.sqa_id; quota_dqblk = &arg->arg_rquota_setquota.sqa_dqblk; } return do_rquota_setquota(quota_path, quota_type, quota_id, quota_dqblk, req, qres); } /* rquota_setquota */ static int do_rquota_setquota(char *quota_path, int quota_type, int quota_id, sq_dqblk *quota_dqblk, struct svc_req *req, setquota_rslt *qres) { fsal_status_t fsal_status; fsal_quota_t fsal_quota_in; fsal_quota_t fsal_quota_out; struct gsh_export *exp = NULL; char *qpath; char path[MAXPATHLEN]; qres->status = Q_EPERM; qpath = check_handle_lead_slash(quota_path, path, MAXPATHLEN); if (qpath == NULL) return NFS_REQ_OK; /* Find the export for the dirname (using as well Path, Pseudo, or Tag) */ if (qpath[0] != '/') { LogFullDebug(COMPONENT_NFSPROTO, "Searching for export by tag for %s", qpath); exp = get_gsh_export_by_tag(qpath); } else if (nfs_param.core_param.mount_path_pseudo) { LogFullDebug(COMPONENT_NFSPROTO, "Searching for export by pseudo for %s", qpath); exp = get_gsh_export_by_pseudo(qpath, false); } else { LogFullDebug(COMPONENT_NFSPROTO, "Searching for export by path for %s", qpath); exp = get_gsh_export_by_path(qpath, false); } if (exp == NULL) { /* No export found, return ACCESS error. */ LogEvent(COMPONENT_NFSPROTO, "Export entry for %s not found", qpath); /* entry not found. */ return NFS_REQ_OK; } /* Add export to op_ctx, will be released in free_args */ set_op_context_export(exp); /* Get creds */ if (nfs_req_creds(req) == NFS4ERR_ACCESS) { const char *client_ip = ""; client_ip = op_ctx->client->hostaddr_str; LogInfo(COMPONENT_NFSPROTO, "could not get uid and gid, rejecting client %s", client_ip); return NFS_REQ_OK; } memset(&fsal_quota_in, 0, sizeof(fsal_quota_t)); memset(&fsal_quota_out, 0, sizeof(fsal_quota_t)); fsal_quota_in.bhardlimit = quota_dqblk->rq_bhardlimit; fsal_quota_in.bsoftlimit = quota_dqblk->rq_bsoftlimit; fsal_quota_in.curblocks = quota_dqblk->rq_curblocks; fsal_quota_in.fhardlimit = quota_dqblk->rq_fhardlimit; fsal_quota_in.fsoftlimit = quota_dqblk->rq_fsoftlimit; fsal_quota_in.btimeleft = quota_dqblk->rq_btimeleft; fsal_quota_in.ftimeleft = quota_dqblk->rq_ftimeleft; fsal_status = exp->fsal_export->exp_ops.set_quota( exp->fsal_export, CTX_FULLPATH(op_ctx), quota_type, quota_id, &fsal_quota_in, &fsal_quota_out); if (FSAL_IS_ERROR(fsal_status)) { if (fsal_status.major == ERR_FSAL_NO_QUOTA) qres->status = Q_NOQUOTA; return NFS_REQ_OK; } /* is success */ qres->setquota_rslt_u.sqr_rquota.rq_active = TRUE; qres->setquota_rslt_u.sqr_rquota.rq_bhardlimit = fsal_quota_out.bhardlimit; qres->setquota_rslt_u.sqr_rquota.rq_bsoftlimit = fsal_quota_out.bsoftlimit; qres->setquota_rslt_u.sqr_rquota.rq_curblocks = fsal_quota_out.curblocks; qres->setquota_rslt_u.sqr_rquota.rq_fhardlimit = fsal_quota_out.fhardlimit; qres->setquota_rslt_u.sqr_rquota.rq_fsoftlimit = fsal_quota_out.fsoftlimit; qres->setquota_rslt_u.sqr_rquota.rq_btimeleft = fsal_quota_out.btimeleft; qres->setquota_rslt_u.sqr_rquota.rq_ftimeleft = fsal_quota_out.ftimeleft; qres->status = Q_OK; return NFS_REQ_OK; } /* do_rquota_setquota */ /** * @brief Frees the result structure allocated for rquota_setquota * * @param[in,out] res Pointer to the result structure. * */ void rquota_setquota_Free(nfs_res_t *res) { /* Nothing to do */ } nfs-ganesha-6.5/src/Protocols/XDR/000077500000000000000000000000001473756622300170055ustar00rootroot00000000000000nfs-ganesha-6.5/src/Protocols/XDR/CMakeLists.txt000066400000000000000000000031511473756622300215450ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- ########### next target ############### SET(nfs_mnt_xdr_STAT_SRCS xdr_mount.c xdr_nfs23.c xdr_nfsv41.c xdr_nlm4.c xdr_nsm.c xdr_nfsacl.c ) if(USE_RQUOTA) SET(nfs_mnt_xdr_STAT_SRCS ${nfs_mnt_xdr_STAT_SRCS} xdr_rquota.c ) endif(USE_RQUOTA) add_library(nfs_mnt_xdr OBJECT ${nfs_mnt_xdr_STAT_SRCS}) add_sanitizers(nfs_mnt_xdr) set_target_properties(nfs_mnt_xdr PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(nfs_mnt_xdr gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) ########### install files ############### nfs-ganesha-6.5/src/Protocols/XDR/flex_files_prot.x000066400000000000000000000114421473756622300223640ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2012 IETF Trust and the persons identified * as authors of the code. All rights reserved. * * Redistribution and use in source and binary forms, with * or without modification, are permitted provided that the * following conditions are met: * * o Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * o 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. * * o Neither the name of Internet Society, IETF or IETF * Trust, nor the names of specific contributors, may be * used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS * 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 COPYRIGHT OWNER 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 code was derived from RFCTBD10. * Please reproduce this note if possible. */ /* * flex_files_prot.x */ /* * The following include statements are for example only. * The actual XDR definition files are generated separately * and independently and are likely to have a different name. * %#include * %#include */ struct ff_device_versions4 { uint32_t ffdv_version; uint32_t ffdv_minorversion; uint32_t ffdv_rsize; uint32_t ffdv_wsize; bool ffdv_tightly_coupled; }; struct ff_device_addr4 { multipath_list4 ffda_netaddrs; ff_device_versions4 ffda_versions<>; }; const FF_FLAGS_NO_LAYOUTCOMMIT = 0x00000001; const FF_FLAGS_NO_IO_THRU_MDS = 0x00000002; const FF_FLAGS_NO_READ_IO = 0x00000004; const FF_FLAGS_WRITE_ONE_MIRROR = 0x00000008; typedef uint32_t ff_flags4; struct ff_data_server4 { deviceid4 ffds_deviceid; uint32_t ffds_efficiency; stateid4 ffds_stateid; nfs_fh4 ffds_fh_vers<>; fattr4_owner ffds_user; fattr4_owner_group ffds_group; }; struct ff_mirror4 { ff_data_server4 ffm_data_servers<>; }; struct ff_layout4 { length4 ffl_stripe_unit; ff_mirror4 ffl_mirrors<>; ff_flags4 ffl_flags; uint32_t ffl_stats_collect_hint; }; struct ff_ioerr4 { offset4 ffie_offset; length4 ffie_length; stateid4 ffie_stateid; device_error4 ffie_errors<>; }; struct ff_io_latency4 { uint64_t ffil_ops_requested; uint64_t ffil_bytes_requested; uint64_t ffil_ops_completed; uint64_t ffil_bytes_completed; uint64_t ffil_bytes_not_delivered; nfstime4 ffil_total_busy_time; nfstime4 ffil_aggregate_completion_time; }; struct ff_layoutupdate4 { netaddr4 ffl_addr; nfs_fh4 ffl_fhandle; ff_io_latency4 ffl_read; ff_io_latency4 ffl_write; nfstime4 ffl_duration; bool ffl_local; }; struct io_info4 { uint32_t ii_count; uint64_t ii_bytes; }; struct ff_iostats4 { offset4 ffis_offset; length4 ffis_length; stateid4 ffis_stateid; io_info4 ffis_read; io_info4 ffis_write; deviceid4 ffis_deviceid; ff_layoutupdate4 ffis_layoutupdate; }; struct ff_layoutreturn4 { ff_ioerr4 fflr_ioerr_report<>; ff_iostats4 fflr_iostats_report<>; }; union ff_mirrors_hint switch (bool ffmc_valid) { case TRUE: uint32_t ffmc_mirrors; case FALSE: void; }; struct ff_layouthint4 { ff_mirrors_hint fflh_mirrors_hint; }; const RCA4_TYPE_MASK_FF_LAYOUT_MIN = 16; const RCA4_TYPE_MASK_FF_LAYOUT_MAX = 17; enum ff_cb_recall_any_mask { FF_RCA4_TYPE_MASK_READ = -2, FF_RCA4_TYPE_MASK_RW = -1 }; nfs-ganesha-6.5/src/Protocols/XDR/mount.x000066400000000000000000000043061473756622300203430ustar00rootroot00000000000000/* SPDX-License-Identifier: unknown license... */ const MNTPATHLEN = 1024; /* Maximum bytes in a path name */ const MNTNAMLEN = 255; /* Maximum bytes in a name */ /* const FHSIZE2 = 32; const FHSIZE3 = 64; */ enum mountstat3 { MNT3_OK = 0, /* no error */ MNT3ERR_PERM = 1, /* Not owner */ MNT3ERR_NOENT = 2, /* No such file or directory */ MNT3ERR_IO = 5, /* I/O error */ MNT3ERR_ACCES = 13, /* Permission denied */ MNT3ERR_NOTDIR = 20, /* Not a directory */ MNT3ERR_INVAL = 22, /* Invalid argument */ MNT3ERR_NAMETOOLONG = 63, /* Filename too long */ MNT3ERR_NOTSUPP = 10004, /* Operation not supported */ MNT3ERR_SERVERFAULT = 10006 /* A failure on the server */ }; /* typedef opaque fhandle2[FHSIZE2]; */ typedef opaque fhandle3; typedef string dirpath; typedef string name; typedef struct groupnode *groups; struct groupnode { name gr_name; groups gr_next; }; typedef struct exportnode *exports; struct exportnode { dirpath ex_dir; groups ex_groups; exports ex_next; }; typedef struct mountbody *mountlist; struct mountbody { name ml_hostname; dirpath ml_directory; mountlist ml_next; }; /* union fhstatus2 switch (unsigned fhs_status) { case 0: fhandle2 directory; default: void; }; */ struct mountres3_ok { fhandle3 fhandle; int auth_flavors<>; }; union mountres3 switch (mountstat3 fhs_status) { case MNT3_OK: mountres3_ok mountinfo; default: void; }; program MOUNTPROG { /* * Version 1 of the mount protocol used with * version 2 of the NFS protocol. */ version MOUNT_V1 { void MOUNTPROC2_NULL(void) = 0; fhstatus2 MOUNTPROC2_MNT(dirpath) = 1; mountlist MOUNTPROC2_DUMP(void) = 2; void MOUNTPROC2_UMNT(dirpath) = 3; void MOUNTPROC2_UMNTALL(void) = 4; exports MOUNTPROC2_EXPORT(void) = 5; } = 1; version MOUNT_V3 { void MOUNTPROC3_NULL(void) = 0; mountres3 MOUNTPROC3_MNT(dirpath) = 1; mountlist MOUNTPROC3_DUMP(void) = 2; void MOUNTPROC3_UMNT(dirpath) = 3; void MOUNTPROC3_UMNTALL(void) = 4; exports MOUNTPROC3_EXPORT(void) = 5; } = 3; } = 100005; nfs-ganesha-6.5/src/Protocols/XDR/nfs23.x000066400000000000000000000442571473756622300201450ustar00rootroot00000000000000/* SPDX-License-Identifier: unknown license... */ /* NFS V2 definitions */ const NFS2_MAXDATA = 8192; const NFS2_MAXPATHLEN = 1024; const NFS2_MAXNAMLEN = 255; const NFS2_COOKIESIZE = 4; const NFS2_FHSIZE = 32; const NFS2_MNTPATHLEN = 1024; const NFS2_MNTNAMLEN = 255; typedef string nfspath2; typedef string filename2; typedef opaque fhandle2[NFS2_FHSIZE]; typedef opaque nfsdata2; typedef opaque nfscookie2[NFS2_COOKIESIZE]; enum nfsstat2 { NFS_OK = 0, NFSERR_PERM=1, NFSERR_NOENT=2, NFSERR_IO=5, NFSERR_NXIO=6, NFSERR_ACCES=13, NFSERR_EXIST=17, NFSERR_NODEV=19, NFSERR_NOTDIR=20, NFSERR_ISDIR=21, NFSERR_FBIG=27, NFSERR_NOSPC=28, NFSERR_ROFS=30, NFSERR_NAMETOOLONG=63, NFSERR_NOTEMPTY=66, NFSERR_DQUOT=69, NFSERR_STALE=70, NFSERR_WFLUSH=99 }; enum ftype2 { NFNON = 0, NFREG = 1, NFDIR = 2, NFBLK = 3, NFCHR = 4, NFLNK = 5, NFSOCK = 6, NFBAD = 7, NFFIFO = 8 }; struct nfstime2 { unsigned int seconds; unsigned int useconds; }; struct fattr2 { ftype2 type; unsigned int mode; unsigned int nlink; unsigned int uid; unsigned int gid; unsigned int size; unsigned int blocksize; unsigned int rdev; unsigned int blocks; unsigned int fsid; unsigned int fileid; nfstime2 atime; nfstime2 mtime; nfstime2 ctime; }; union fhstatus2 switch (unsigned status) { case 0: fhandle2 directory; default: void; } ; struct diropargs2 { fhandle2 dir; filename2 name; }; struct DIROP2resok { fhandle2 file; fattr2 attributes; } ; union DIROP2res switch (nfsstat2 status) { case NFS_OK: DIROP2resok diropok ; default: void; }; union ATTR2res switch (nfsstat2 status) { case NFS_OK: fattr2 attributes; default: void; }; struct sattr2 { unsigned int mode; unsigned int uid; unsigned int gid; unsigned int size; nfstime2 atime; nfstime2 mtime; }; struct statinfo2 { unsigned tsize; unsigned bsize; unsigned blocks; unsigned bfree; unsigned bavail; } ; union STATFS2res switch (nfsstat2 status) { case NFS_OK: statinfo2 info; default: void; }; struct READDIR2args { fhandle2 dir; nfscookie2 cookie; unsigned count; }; struct entry2 { unsigned fileid; filename2 name; nfscookie2 cookie; entry2 *nextentry; }; struct READDIR2resok { entry2 *entries; bool eof; } ; union READDIR2res switch (nfsstat2 status) { case NFS_OK: READDIR2resok readdirok; default: void; }; struct SYMLINK2args { diropargs2 from; nfspath2 to; sattr2 attributes; }; struct LINK2args { fhandle2 from; diropargs2 to; }; struct RENAME2args { diropargs2 from; diropargs2 to; }; struct CREATE2args { diropargs2 where; sattr2 attributes; }; struct WRITE2args { fhandle2 file; unsigned beginoffset; unsigned offset; unsigned totalcount; nfsdata2 data; }; struct READ2resok { fattr2 attributes ; nfsdata2 data ; }; union READ2res switch (nfsstat2 status) { case NFS_OK: READ2resok readok; default: void; }; struct READ2args { fhandle2 file; unsigned offset; unsigned count; unsigned totalcount; }; union READLINK2res switch (nfsstat2 status) { case NFS_OK: nfspath2 data; default: void; }; struct SETATTR2args { fhandle2 file; sattr2 attributes; }; /* NFS V3 definitions */ #define NFS3_FHSIZE 64 #define NFS3_COOKIEVERFSIZE 8 #define NFS3_CREATEVERFSIZE 8 #define NFS3_WRITEVERFSIZE 8 typedef unsigned hyper nfs3_uint64; typedef hyper nfs3_int64; typedef unsigned int nfs3_uint32; typedef int nfs3_int32; typedef string filename3<>; typedef string nfspath3<>; typedef nfs3_uint64 fileid3; typedef nfs3_uint64 cookie3; typedef opaque cookieverf3[NFS3_COOKIEVERFSIZE]; typedef opaque createverf3[NFS3_CREATEVERFSIZE]; typedef opaque writeverf3[NFS3_WRITEVERFSIZE]; typedef nfs3_uint32 uid3; typedef nfs3_uint32 gid3; typedef nfs3_uint64 size3; typedef nfs3_uint64 offset3; typedef nfs3_uint32 mode3; typedef nfs3_uint32 count3; enum nfsstat3 { NFS3_OK = 0, NFS3ERR_PERM = 1, NFS3ERR_NOENT = 2, NFS3ERR_IO = 5, NFS3ERR_NXIO = 6, NFS3ERR_ACCES = 13, NFS3ERR_EXIST = 17, NFS3ERR_XDEV = 18, NFS3ERR_NODEV = 19, NFS3ERR_NOTDIR = 20, NFS3ERR_ISDIR = 21, NFS3ERR_INVAL = 22, NFS3ERR_FBIG = 27, NFS3ERR_NOSPC = 28, NFS3ERR_ROFS = 30, NFS3ERR_MLINK = 31, NFS3ERR_NAMETOOLONG = 63, NFS3ERR_NOTEMPTY = 66, NFS3ERR_DQUOT = 69, NFS3ERR_STALE = 70, NFS3ERR_REMOTE = 71, NFS3ERR_BADHANDLE = 10001, NFS3ERR_NOT_SYNC = 10002, NFS3ERR_BAD_COOKIE = 10003, NFS3ERR_NOTSUPP = 10004, NFS3ERR_TOOSMALL = 10005, NFS3ERR_SERVERFAULT = 10006, NFS3ERR_BADTYPE = 10007, NFS3ERR_JUKEBOX = 10008 }; enum ftype3 { NF3REG = 1, NF3DIR = 2, NF3BLK = 3, NF3CHR = 4, NF3LNK = 5, NF3SOCK = 6, NF3FIFO = 7 }; struct specdata3 { nfs3_uint32 specdata1; nfs3_uint32 specdata2; }; struct nfs_fh3 { opaque data; }; struct nfstime3 { nfs3_uint32 seconds; nfs3_uint32 nseconds; }; struct fattr3 { ftype3 type; mode3 mode; nfs3_uint32 nlink; uid3 uid; gid3 gid; size3 size; size3 used; specdata3 rdev; nfs3_uint64 fsid; fileid3 fileid; nfstime3 atime; nfstime3 mtime; nfstime3 ctime; }; union post_op_attr switch (bool attributes_follow) { case TRUE: fattr3 attributes; case FALSE: void; }; struct wcc_attr { size3 size; nfstime3 mtime; nfstime3 ctime; }; union pre_op_attr switch (bool attributes_follow) { case TRUE: wcc_attr attributes; case FALSE: void; }; struct wcc_data { pre_op_attr before; post_op_attr after; }; union post_op_fh3 switch (bool handle_follows) { case TRUE: nfs_fh3 handle; case FALSE: void; }; enum time_how { DONT_CHANGE = 0, SET_TO_SERVER_TIME = 1, SET_TO_CLIENT_TIME = 2 }; union set_mode3 switch (bool set_it) { case TRUE: mode3 mode; default: void; }; union set_uid3 switch (bool set_it) { case TRUE: uid3 uid; default: void; }; union set_gid3 switch (bool set_it) { case TRUE: gid3 gid; default: void; }; union set_size3 switch (bool set_it) { case TRUE: size3 size; default: void; }; union set_atime switch (time_how set_it) { case SET_TO_CLIENT_TIME: nfstime3 atime; default: void; }; union set_mtime switch (time_how set_it) { case SET_TO_CLIENT_TIME: nfstime3 mtime; default: void; }; struct sattr3 { set_mode3 mode; set_uid3 uid; set_gid3 gid; set_size3 size; set_atime atime; set_mtime mtime; }; struct diropargs3 { nfs_fh3 dir; filename3 name; }; struct GETATTR3args { nfs_fh3 object; }; struct GETATTR3resok { fattr3 obj_attributes; }; union GETATTR3res switch (nfsstat3 status) { case NFS3_OK: GETATTR3resok resok; default: void; }; union sattrguard3 switch (bool check) { case TRUE: nfstime3 obj_ctime; case FALSE: void; }; struct SETATTR3args { nfs_fh3 object; sattr3 new_attributes; sattrguard3 guard; }; struct SETATTR3resok { wcc_data obj_wcc; }; struct SETATTR3resfail { wcc_data obj_wcc; }; union SETATTR3res switch (nfsstat3 status) { case NFS3_OK: SETATTR3resok resok; default: SETATTR3resfail resfail; }; struct LOOKUP3args { diropargs3 what; }; struct LOOKUP3resok { nfs_fh3 object; post_op_attr obj_attributes; post_op_attr dir_attributes; }; struct LOOKUP3resfail { post_op_attr dir_attributes; }; union LOOKUP3res switch (nfsstat3 status) { case NFS3_OK: LOOKUP3resok resok; default: LOOKUP3resfail resfail; }; const ACCESS3_READ = 0x0001; const ACCESS3_LOOKUP = 0x0002; const ACCESS3_MODIFY = 0x0004; const ACCESS3_EXTEND = 0x0008; const ACCESS3_DELETE = 0x0010; const ACCESS3_EXECUTE = 0x0020; struct ACCESS3args { nfs_fh3 object; nfs3_uint32 access; }; struct ACCESS3resok { post_op_attr obj_attributes; nfs3_uint32 access; }; struct ACCESS3resfail { post_op_attr obj_attributes; }; union ACCESS3res switch (nfsstat3 status) { case NFS3_OK: ACCESS3resok resok; default: ACCESS3resfail resfail; }; struct READLINK3args { nfs_fh3 symlink; }; struct READLINK3resok { post_op_attr symlink_attributes; nfspath3 data; }; struct READLINK3resfail { post_op_attr symlink_attributes; }; union READLINK3res switch (nfsstat3 status) { case NFS3_OK: READLINK3resok resok; default: READLINK3resfail resfail; }; struct READ3args { nfs_fh3 file; offset3 offset; count3 count; }; struct READ3resok { post_op_attr file_attributes; count3 count; bool eof; opaque data<>; }; struct READ3resfail { post_op_attr file_attributes; }; union READ3res switch (nfsstat3 status) { case NFS3_OK: READ3resok resok; default: READ3resfail resfail; }; enum stable_how { UNSTABLE = 0, DATA_SYNC = 1, FILE_SYNC = 2 }; struct WRITE3args { nfs_fh3 file; offset3 offset; count3 count; stable_how stable; opaque data<>; }; struct WRITE3resok { wcc_data file_wcc; count3 count; stable_how committed; writeverf3 verf; }; struct WRITE3resfail { wcc_data file_wcc; }; union WRITE3res switch (nfsstat3 status) { case NFS3_OK: WRITE3resok resok; default: WRITE3resfail resfail; }; enum createmode3 { UNCHECKED = 0, GUARDED = 1, EXCLUSIVE = 2 }; union createhow3 switch (createmode3 mode) { case UNCHECKED: case GUARDED: sattr3 obj_attributes; case EXCLUSIVE: createverf3 verf; }; struct CREATE3args { diropargs3 where; createhow3 how; }; struct CREATE3resok { post_op_fh3 obj; post_op_attr obj_attributes; wcc_data dir_wcc; }; struct CREATE3resfail { wcc_data dir_wcc; }; union CREATE3res switch (nfsstat3 status) { case NFS3_OK: CREATE3resok resok; default: CREATE3resfail resfail; }; struct MKDIR3args { diropargs3 where; sattr3 attributes; }; struct MKDIR3resok { post_op_fh3 obj; post_op_attr obj_attributes; wcc_data dir_wcc; }; struct MKDIR3resfail { wcc_data dir_wcc; }; union MKDIR3res switch (nfsstat3 status) { case NFS3_OK: MKDIR3resok resok; default: MKDIR3resfail resfail; }; struct symlinkdata3 { sattr3 symlink_attributes; nfspath3 symlink_data; }; struct SYMLINK3args { diropargs3 where; symlinkdata3 symlink; }; struct SYMLINK3resok { post_op_fh3 obj; post_op_attr obj_attributes; wcc_data dir_wcc; }; struct SYMLINK3resfail { wcc_data dir_wcc; }; union SYMLINK3res switch (nfsstat3 status) { case NFS3_OK: SYMLINK3resok resok; default: SYMLINK3resfail resfail; }; struct devicedata3 { sattr3 dev_attributes; specdata3 spec; }; union mknoddata3 switch (ftype3 type) { case NF3CHR: case NF3BLK: devicedata3 device; case NF3SOCK: case NF3FIFO: sattr3 pipe_attributes; default: void; }; struct MKNOD3args { diropargs3 where; mknoddata3 what; }; struct MKNOD3resok { post_op_fh3 obj; post_op_attr obj_attributes; wcc_data dir_wcc; }; struct MKNOD3resfail { wcc_data dir_wcc; }; union MKNOD3res switch (nfsstat3 status) { case NFS3_OK: MKNOD3resok resok; default: MKNOD3resfail resfail; }; struct REMOVE3args { diropargs3 object; }; struct REMOVE3resok { wcc_data dir_wcc; }; struct REMOVE3resfail { wcc_data dir_wcc; }; union REMOVE3res switch (nfsstat3 status) { case NFS3_OK: REMOVE3resok resok; default: REMOVE3resfail resfail; }; struct RMDIR3args { diropargs3 object; }; struct RMDIR3resok { wcc_data dir_wcc; }; struct RMDIR3resfail { wcc_data dir_wcc; }; union RMDIR3res switch (nfsstat3 status) { case NFS3_OK: RMDIR3resok resok; default: RMDIR3resfail resfail; }; struct RENAME3args { diropargs3 from; diropargs3 to; }; struct RENAME3resok { wcc_data fromdir_wcc; wcc_data todir_wcc; }; struct RENAME3resfail { wcc_data fromdir_wcc; wcc_data todir_wcc; }; union RENAME3res switch (nfsstat3 status) { case NFS3_OK: RENAME3resok resok; default: RENAME3resfail resfail; }; struct LINK3args { nfs_fh3 file; diropargs3 link; }; struct LINK3resok { post_op_attr file_attributes; wcc_data linkdir_wcc; }; struct LINK3resfail { post_op_attr file_attributes; wcc_data linkdir_wcc; }; union LINK3res switch (nfsstat3 status) { case NFS3_OK: LINK3resok resok; default: LINK3resfail resfail; }; struct READDIR3args { nfs_fh3 dir; cookie3 cookie; cookieverf3 cookieverf; count3 count; }; struct entry3 { fileid3 fileid; filename3 name; cookie3 cookie; entry3 *nextentry; }; struct dirlist3 { entry3 *entries; bool eof; }; struct READDIR3resok { post_op_attr dir_attributes; cookieverf3 cookieverf; dirlist3 reply; }; struct READDIR3resfail { post_op_attr dir_attributes; }; union READDIR3res switch (nfsstat3 status) { case NFS3_OK: READDIR3resok resok; default: READDIR3resfail resfail; }; struct READDIRPLUS3args { nfs_fh3 dir; cookie3 cookie; cookieverf3 cookieverf; count3 dircount; count3 maxcount; }; struct entryplus3 { fileid3 fileid; filename3 name; cookie3 cookie; post_op_attr name_attributes; post_op_fh3 name_handle; entryplus3 *nextentry; }; struct dirlistplus3 { entryplus3 *entries; bool eof; }; struct READDIRPLUS3resok { post_op_attr dir_attributes; cookieverf3 cookieverf; dirlistplus3 reply; }; struct READDIRPLUS3resfail { post_op_attr dir_attributes; }; union READDIRPLUS3res switch (nfsstat3 status) { case NFS3_OK: READDIRPLUS3resok resok; default: READDIRPLUS3resfail resfail; }; struct FSSTAT3args { nfs_fh3 fsroot; }; struct FSSTAT3resok { post_op_attr obj_attributes; size3 tbytes; size3 fbytes; size3 abytes; size3 tfiles; size3 ffiles; size3 afiles; nfs3_uint32 invarsec; }; struct FSSTAT3resfail { post_op_attr obj_attributes; }; union FSSTAT3res switch (nfsstat3 status) { case NFS3_OK: FSSTAT3resok resok; default: FSSTAT3resfail resfail; }; const FSF3_LINK = 0x0001; const FSF3_SYMLINK = 0x0002; const FSF3_HOMOGENEOUS = 0x0008; const FSF3_CANSETTIME = 0x0010; struct FSINFO3args { nfs_fh3 fsroot; }; struct FSINFO3resok { post_op_attr obj_attributes; nfs3_uint32 rtmax; nfs3_uint32 rtpref; nfs3_uint32 rtmult; nfs3_uint32 wtmax; nfs3_uint32 wtpref; nfs3_uint32 wtmult; nfs3_uint32 dtpref; size3 maxfilesize; nfstime3 time_delta; nfs3_uint32 properties; }; struct FSINFO3resfail { post_op_attr obj_attributes; }; union FSINFO3res switch (nfsstat3 status) { case NFS3_OK: FSINFO3resok resok; default: FSINFO3resfail resfail; }; struct PATHCONF3args { nfs_fh3 object; }; struct PATHCONF3resok { post_op_attr obj_attributes; nfs3_uint32 linkmax; nfs3_uint32 name_max; bool no_trunc; bool chown_restricted; bool case_insensitive; bool case_preserving; }; struct PATHCONF3resfail { post_op_attr obj_attributes; }; union PATHCONF3res switch (nfsstat3 status) { case NFS3_OK: PATHCONF3resok resok; default: PATHCONF3resfail resfail; }; struct COMMIT3args { nfs_fh3 file; offset3 offset; count3 count; }; struct COMMIT3resok { wcc_data file_wcc; writeverf3 verf; }; struct COMMIT3resfail { wcc_data file_wcc; }; union COMMIT3res switch (nfsstat3 status) { case NFS3_OK: COMMIT3resok resok; default: COMMIT3resfail resfail; }; program NFS_PROGRAM { version NFS_V2 { void NFSPROC_NULL(void) = 0; ATTR2res NFSPROC_GETATTR(fhandle2) = 1; ATTR2res NFSPROC_SETATTR(SETATTR2args) = 2; void NFSPROC_ROOT(void) = 3; DIROP2res NFSPROC_LOOKUP(diropargs2) = 4; READLINK2res NFSPROC_READLINK(fhandle2) = 5; READ2res NFSPROC_READ(READ2args) = 6; void NFSPROC_WRITECACHE(void) = 7; ATTR2res NFSPROC_WRITE(WRITE2args) = 8; DIROP2res NFSPROC_CREATE(CREATE2args) = 9; nfsstat2 NFSPROC_REMOVE(diropargs2) = 10; nfsstat2 NFSPROC_RENAME(RENAME2args) = 11; nfsstat2 NFSPROC_LINK(LINK2args) = 12; nfsstat2 NFSPROC_SYMLINK(SYMLINK2args) = 13; DIROP2res NFSPROC_MKDIR(CREATE2args) = 14; nfsstat2 NFSPROC_RMDIR(diropargs2) = 15; READDIR2res NFSPROC_READDIR(READDIR2args) = 16; STATFS2res NFSPROC_STATFS(fhandle2) = 17; } = 2; version NFS_V3 { void NFSPROC3_NULL(void) = 0; GETATTR3res NFSPROC3_GETATTR(GETATTR3args) = 1; SETATTR3res NFSPROC3_SETATTR(SETATTR3args) = 2; LOOKUP3res NFSPROC3_LOOKUP(LOOKUP3args) = 3; ACCESS3res NFSPROC3_ACCESS(ACCESS3args) = 4; READLINK3res NFSPROC3_READLINK(READLINK3args) = 5; READ3res NFSPROC3_READ(READ3args) = 6; WRITE3res NFSPROC3_WRITE(WRITE3args) = 7; CREATE3res NFSPROC3_CREATE(CREATE3args) = 8; MKDIR3res NFSPROC3_MKDIR(MKDIR3args) = 9; SYMLINK3res NFSPROC3_SYMLINK(SYMLINK3args) = 10; MKNOD3res NFSPROC3_MKNOD(MKNOD3args) = 11; REMOVE3res NFSPROC3_REMOVE(REMOVE3args) = 12; RMDIR3res NFSPROC3_RMDIR(RMDIR3args) = 13; RENAME3res NFSPROC3_RENAME(RENAME3args) = 14; LINK3res NFSPROC3_LINK(LINK3args) = 15; READDIR3res NFSPROC3_READDIR(READDIR3args) = 16; READDIRPLUS3res NFSPROC3_READDIRPLUS(READDIRPLUS3args) = 17; FSSTAT3res NFSPROC3_FSSTAT(FSSTAT3args) = 18; FSINFO3res NFSPROC3_FSINFO(FSINFO3args) = 19; PATHCONF3res NFSPROC3_PATHCONF(PATHCONF3args) = 20; COMMIT3res NFSPROC3_COMMIT(COMMIT3args) = 21; } = 3; } = 100003; nfs-ganesha-6.5/src/Protocols/XDR/nfs4.x000066400000000000000000001104611473756622300200530ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ /* * This file was machine generated for [RFC7530]. * * Last updated Tue Mar 10 11:51:21 PDT 2015. */ /* * Copyright (c) 2015 IETF Trust and the persons identified * as authors of the code. All rights reserved. * * Redistribution and use in source and binary forms, with * or without modification, are permitted provided that the * following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - 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. * * - Neither the name of Internet Society, IETF or IETF * Trust, nor the names of specific contributors, may be * used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS * 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 COPYRIGHT OWNER 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 code was derived from RFC 7531. */ /* * nfs4_prot.x * */ %#pragma ident "@(#)nfs4_prot.x 1.122" /* * Basic typedefs for RFC 1832 data type definitions */ /* * typedef int int32_t; * typedef unsigned int uint32_t; * typedef hyper int64_t; * typedef unsigned hyper uint64_t; */ /* * Sizes */ const NFS4_FHSIZE = 128; const NFS4_VERIFIER_SIZE = 8; const NFS4_OTHER_SIZE = 12; const NFS4_OPAQUE_LIMIT = 1024; const NFS4_INT64_MAX = 0x7fffffffffffffff; const NFS4_UINT64_MAX = 0xffffffffffffffff; const NFS4_INT32_MAX = 0x7fffffff; const NFS4_UINT32_MAX = 0xffffffff; /* * File types */ enum nfs_ftype4 { NF4REG = 1, /* Regular File */ NF4DIR = 2, /* Directory */ NF4BLK = 3, /* Special File - block device */ NF4CHR = 4, /* Special File - character device */ NF4LNK = 5, /* Symbolic Link */ NF4SOCK = 6, /* Special File - socket */ NF4FIFO = 7, /* Special File - fifo */ NF4ATTRDIR = 8, /* Attribute Directory */ NF4NAMEDATTR = 9 /* Named Attribute */ }; /* * Error status */ enum nfsstat4 { NFS4_OK = 0, /* everything is okay */ NFS4ERR_PERM = 1, /* caller not privileged */ NFS4ERR_NOENT = 2, /* no such file/directory */ NFS4ERR_IO = 5, /* hard I/O error */ NFS4ERR_NXIO = 6, /* no such device */ NFS4ERR_ACCESS = 13, /* access denied */ NFS4ERR_EXIST = 17, /* file already exists */ NFS4ERR_XDEV = 18, /* different filesystems */ /* Unused/reserved 19 */ NFS4ERR_NOTDIR = 20, /* should be a directory */ NFS4ERR_ISDIR = 21, /* should not be directory */ NFS4ERR_INVAL = 22, /* invalid argument */ NFS4ERR_FBIG = 27, /* file exceeds server max */ NFS4ERR_NOSPC = 28, /* no space on filesystem */ NFS4ERR_ROFS = 30, /* read-only filesystem */ NFS4ERR_MLINK = 31, /* too many hard links */ NFS4ERR_NAMETOOLONG = 63, /* name exceeds server max */ NFS4ERR_NOTEMPTY = 66, /* directory not empty */ NFS4ERR_DQUOT = 69, /* hard quota limit reached*/ NFS4ERR_STALE = 70, /* file no longer exists */ NFS4ERR_BADHANDLE = 10001,/* Illegal filehandle */ NFS4ERR_BAD_COOKIE = 10003,/* READDIR cookie is stale */ NFS4ERR_NOTSUPP = 10004,/* operation not supported */ NFS4ERR_TOOSMALL = 10005,/* response limit exceeded */ NFS4ERR_SERVERFAULT = 10006,/* undefined server error */ NFS4ERR_BADTYPE = 10007,/* type invalid for CREATE */ NFS4ERR_DELAY = 10008,/* file "busy" - retry */ NFS4ERR_SAME = 10009,/* nverify says attrs same */ NFS4ERR_DENIED = 10010,/* lock unavailable */ NFS4ERR_EXPIRED = 10011,/* lock lease expired */ NFS4ERR_LOCKED = 10012,/* I/O failed due to lock */ NFS4ERR_GRACE = 10013,/* in grace period */ NFS4ERR_FHEXPIRED = 10014,/* filehandle expired */ NFS4ERR_SHARE_DENIED = 10015,/* share reserve denied */ NFS4ERR_WRONGSEC = 10016,/* wrong security flavor */ NFS4ERR_CLID_INUSE = 10017,/* clientid in use */ NFS4ERR_RESOURCE = 10018,/* resource exhaustion */ NFS4ERR_MOVED = 10019,/* filesystem relocated */ NFS4ERR_NOFILEHANDLE = 10020,/* current FH is not set */ NFS4ERR_MINOR_VERS_MISMATCH = 10021,/* minor vers not supp */ NFS4ERR_STALE_CLIENTID = 10022,/* server has rebooted */ NFS4ERR_STALE_STATEID = 10023,/* server has rebooted */ NFS4ERR_OLD_STATEID = 10024,/* state is out of sync */ NFS4ERR_BAD_STATEID = 10025,/* incorrect stateid */ NFS4ERR_BAD_SEQID = 10026,/* request is out of seq. */ NFS4ERR_NOT_SAME = 10027,/* verify - attrs not same */ NFS4ERR_LOCK_RANGE = 10028,/* lock range not supported*/ NFS4ERR_SYMLINK = 10029,/* should be file/directory*/ NFS4ERR_RESTOREFH = 10030,/* no saved filehandle */ NFS4ERR_LEASE_MOVED = 10031,/* some filesystem moved */ NFS4ERR_ATTRNOTSUPP = 10032,/* recommended attr not sup*/ NFS4ERR_NO_GRACE = 10033,/* reclaim outside of grace*/ NFS4ERR_RECLAIM_BAD = 10034,/* reclaim error at server */ NFS4ERR_RECLAIM_CONFLICT = 10035,/* conflict on reclaim */ NFS4ERR_BADXDR = 10036,/* XDR decode failed */ NFS4ERR_LOCKS_HELD = 10037,/* file locks held at CLOSE*/ NFS4ERR_OPENMODE = 10038,/* conflict in OPEN and I/O*/ NFS4ERR_BADOWNER = 10039,/* owner translation bad */ NFS4ERR_BADCHAR = 10040,/* UTF-8 char not supported*/ NFS4ERR_BADNAME = 10041,/* name not supported */ NFS4ERR_BAD_RANGE = 10042,/* lock range not supported*/ NFS4ERR_LOCK_NOTSUPP = 10043,/* no atomic up/downgrade */ NFS4ERR_OP_ILLEGAL = 10044,/* undefined operation */ NFS4ERR_DEADLOCK = 10045,/* file locking deadlock */ NFS4ERR_FILE_OPEN = 10046,/* open file blocks op. */ NFS4ERR_ADMIN_REVOKED = 10047,/* lock-owner state revoked */ NFS4ERR_CB_PATH_DOWN = 10048 /* callback path down */ }; /* * Basic data types */ typedef opaque attrlist4<>; typedef uint32_t bitmap4<>; typedef uint64_t changeid4; typedef uint64_t clientid4; typedef uint32_t count4; typedef uint64_t length4; typedef uint32_t mode4; typedef uint64_t nfs_cookie4; typedef opaque nfs_fh4; typedef uint32_t nfs_lease4; typedef uint64_t offset4; typedef uint32_t qop4; typedef opaque sec_oid4<>; typedef uint32_t seqid4; typedef opaque utf8string<>; typedef utf8string utf8str_cis; typedef utf8string utf8str_cs; typedef utf8string utf8str_mixed; typedef utf8str_cs component4; typedef opaque linktext4<>; typedef utf8string ascii_REQUIRED4; typedef component4 pathname4<>; typedef uint64_t nfs_lockid4; typedef opaque verifier4[NFS4_VERIFIER_SIZE]; /* * Timeval */ struct nfstime4 { int64_t seconds; uint32_t nseconds; }; enum time_how4 { SET_TO_SERVER_TIME4 = 0, SET_TO_CLIENT_TIME4 = 1 }; union settime4 switch (time_how4 set_it) { case SET_TO_CLIENT_TIME4: nfstime4 time; default: void; }; /* * File access handle */ typedef opaque nfs_fh4; /* * File attribute definitions */ /* * FSID structure for major/minor */ struct fsid4 { uint64_t major; uint64_t minor; }; /* * Filesystem locations attribute for relocation/migration */ struct fs_location4 { utf8str_cis server<>; pathname4 rootpath; }; struct fs_locations4 { pathname4 fs_root; fs_location4 locations<>; }; /* * Various Access Control Entry definitions */ /* * Mask that indicates which Access Control Entries * are supported. Values for the fattr4_aclsupport attribute. */ const ACL4_SUPPORT_ALLOW_ACL = 0x00000001; const ACL4_SUPPORT_DENY_ACL = 0x00000002; const ACL4_SUPPORT_AUDIT_ACL = 0x00000004; const ACL4_SUPPORT_ALARM_ACL = 0x00000008; typedef uint32_t acetype4; /* * acetype4 values; others can be added as needed. */ const ACE4_ACCESS_ALLOWED_ACE_TYPE = 0x00000000; const ACE4_ACCESS_DENIED_ACE_TYPE = 0x00000001; const ACE4_SYSTEM_AUDIT_ACE_TYPE = 0x00000002; const ACE4_SYSTEM_ALARM_ACE_TYPE = 0x00000003; /* * ACE flag */ typedef uint32_t aceflag4; /* * ACE flag values */ const ACE4_FILE_INHERIT_ACE = 0x00000001; const ACE4_DIRECTORY_INHERIT_ACE = 0x00000002; const ACE4_NO_PROPAGATE_INHERIT_ACE = 0x00000004; const ACE4_INHERIT_ONLY_ACE = 0x00000008; const ACE4_SUCCESSFUL_ACCESS_ACE_FLAG = 0x00000010; const ACE4_FAILED_ACCESS_ACE_FLAG = 0x00000020; const ACE4_IDENTIFIER_GROUP = 0x00000040; /* * ACE mask */ typedef uint32_t acemask4; /* * ACE mask values */ const ACE4_READ_DATA = 0x00000001; const ACE4_LIST_DIRECTORY = 0x00000001; const ACE4_WRITE_DATA = 0x00000002; const ACE4_ADD_FILE = 0x00000002; const ACE4_APPEND_DATA = 0x00000004; const ACE4_ADD_SUBDIRECTORY = 0x00000004; const ACE4_READ_NAMED_ATTRS = 0x00000008; const ACE4_WRITE_NAMED_ATTRS = 0x00000010; const ACE4_EXECUTE = 0x00000020; const ACE4_DELETE_CHILD = 0x00000040; const ACE4_READ_ATTRIBUTES = 0x00000080; const ACE4_WRITE_ATTRIBUTES = 0x00000100; const ACE4_DELETE = 0x00010000; const ACE4_READ_ACL = 0x00020000; const ACE4_WRITE_ACL = 0x00040000; const ACE4_WRITE_OWNER = 0x00080000; const ACE4_SYNCHRONIZE = 0x00100000; /* * ACE4_GENERIC_READ - defined as a combination of * ACE4_READ_ACL | * ACE4_READ_DATA | * ACE4_READ_ATTRIBUTES | * ACE4_SYNCHRONIZE */ const ACE4_GENERIC_READ = 0x00120081; /* * ACE4_GENERIC_WRITE - defined as a combination of * ACE4_READ_ACL | * ACE4_WRITE_DATA | * ACE4_WRITE_ATTRIBUTES | * ACE4_WRITE_ACL | * ACE4_APPEND_DATA | * ACE4_SYNCHRONIZE */ const ACE4_GENERIC_WRITE = 0x00160106; /* * ACE4_GENERIC_EXECUTE - defined as a combination of * ACE4_READ_ACL * ACE4_READ_ATTRIBUTES * ACE4_EXECUTE * ACE4_SYNCHRONIZE */ const ACE4_GENERIC_EXECUTE = 0x001200A0; /* * Access Control Entry definition */ struct nfsace4 { acetype4 type; aceflag4 flag; acemask4 access_mask; utf8str_mixed who; }; /* * Field definitions for the fattr4_mode attribute */ const MODE4_SUID = 0x800; /* set user id on execution */ const MODE4_SGID = 0x400; /* set group id on execution */ const MODE4_SVTX = 0x200; /* save text even after use */ const MODE4_RUSR = 0x100; /* read permission: owner */ const MODE4_WUSR = 0x080; /* write permission: owner */ const MODE4_XUSR = 0x040; /* execute permission: owner */ const MODE4_RGRP = 0x020; /* read permission: group */ const MODE4_WGRP = 0x010; /* write permission: group */ const MODE4_XGRP = 0x008; /* execute permission: group */ const MODE4_ROTH = 0x004; /* read permission: other */ const MODE4_WOTH = 0x002; /* write permission: other */ const MODE4_XOTH = 0x001; /* execute permission: other */ /* * Special data/attribute associated with * file types NF4BLK and NF4CHR. */ struct specdata4 { uint32_t specdata1; /* major device number */ uint32_t specdata2; /* minor device number */ }; /* * Values for fattr4_fh_expire_type */ const FH4_PERSISTENT = 0x00000000; const FH4_NOEXPIRE_WITH_OPEN = 0x00000001; const FH4_VOLATILE_ANY = 0x00000002; const FH4_VOL_MIGRATION = 0x00000004; const FH4_VOL_RENAME = 0x00000008; typedef bitmap4 fattr4_supported_attrs; typedef nfs_ftype4 fattr4_type; typedef uint32_t fattr4_fh_expire_type; typedef changeid4 fattr4_change; typedef uint64_t fattr4_size; typedef bool fattr4_link_support; typedef bool fattr4_symlink_support; typedef bool fattr4_named_attr; typedef fsid4 fattr4_fsid; typedef bool fattr4_unique_handles; typedef nfs_lease4 fattr4_lease_time; typedef nfsstat4 fattr4_rdattr_error; typedef nfsace4 fattr4_acl<>; typedef uint32_t fattr4_aclsupport; typedef bool fattr4_archive; typedef bool fattr4_cansettime; typedef bool fattr4_case_insensitive; typedef bool fattr4_case_preserving; typedef bool fattr4_chown_restricted; typedef uint64_t fattr4_fileid; typedef uint64_t fattr4_files_avail; typedef nfs_fh4 fattr4_filehandle; typedef uint64_t fattr4_files_free; typedef uint64_t fattr4_files_total; typedef fs_locations4 fattr4_fs_locations; typedef bool fattr4_hidden; typedef bool fattr4_homogeneous; typedef uint64_t fattr4_maxfilesize; typedef uint32_t fattr4_maxlink; typedef uint32_t fattr4_maxname; typedef uint64_t fattr4_maxread; typedef uint64_t fattr4_maxwrite; typedef ascii_REQUIRED4 fattr4_mimetype; typedef mode4 fattr4_mode; typedef uint64_t fattr4_mounted_on_fileid; typedef bool fattr4_no_trunc; typedef uint32_t fattr4_numlinks; typedef utf8str_mixed fattr4_owner; typedef utf8str_mixed fattr4_owner_group; typedef uint64_t fattr4_quota_avail_hard; typedef uint64_t fattr4_quota_avail_soft; typedef uint64_t fattr4_quota_used; typedef specdata4 fattr4_rawdev; typedef uint64_t fattr4_space_avail; typedef uint64_t fattr4_space_free; typedef uint64_t fattr4_space_total; typedef uint64_t fattr4_space_used; typedef bool fattr4_system; typedef nfstime4 fattr4_time_access; typedef settime4 fattr4_time_access_set; typedef nfstime4 fattr4_time_backup; typedef nfstime4 fattr4_time_create; typedef nfstime4 fattr4_time_delta; typedef nfstime4 fattr4_time_metadata; typedef nfstime4 fattr4_time_modify; typedef settime4 fattr4_time_modify_set; /* * Mandatory attributes */ const FATTR4_SUPPORTED_ATTRS = 0; const FATTR4_TYPE = 1; const FATTR4_FH_EXPIRE_TYPE = 2; const FATTR4_CHANGE = 3; const FATTR4_SIZE = 4; const FATTR4_LINK_SUPPORT = 5; const FATTR4_SYMLINK_SUPPORT = 6; const FATTR4_NAMED_ATTR = 7; const FATTR4_FSID = 8; const FATTR4_UNIQUE_HANDLES = 9; const FATTR4_LEASE_TIME = 10; const FATTR4_RDATTR_ERROR = 11; const FATTR4_FILEHANDLE = 19; /* * Recommended attributes */ const FATTR4_ACL = 12; const FATTR4_ACLSUPPORT = 13; const FATTR4_ARCHIVE = 14; const FATTR4_CANSETTIME = 15; const FATTR4_CASE_INSENSITIVE = 16; const FATTR4_CASE_PRESERVING = 17; const FATTR4_CHOWN_RESTRICTED = 18; const FATTR4_FILEID = 20; const FATTR4_FILES_AVAIL = 21; const FATTR4_FILES_FREE = 22; const FATTR4_FILES_TOTAL = 23; const FATTR4_FS_LOCATIONS = 24; const FATTR4_HIDDEN = 25; const FATTR4_HOMOGENEOUS = 26; const FATTR4_MAXFILESIZE = 27; const FATTR4_MAXLINK = 28; const FATTR4_MAXNAME = 29; const FATTR4_MAXREAD = 30; const FATTR4_MAXWRITE = 31; const FATTR4_MIMETYPE = 32; const FATTR4_MODE = 33; const FATTR4_NO_TRUNC = 34; const FATTR4_NUMLINKS = 35; const FATTR4_OWNER = 36; const FATTR4_OWNER_GROUP = 37; const FATTR4_QUOTA_AVAIL_HARD = 38; const FATTR4_QUOTA_AVAIL_SOFT = 39; const FATTR4_QUOTA_USED = 40; const FATTR4_RAWDEV = 41; const FATTR4_SPACE_AVAIL = 42; const FATTR4_SPACE_FREE = 43; const FATTR4_SPACE_TOTAL = 44; const FATTR4_SPACE_USED = 45; const FATTR4_SYSTEM = 46; const FATTR4_TIME_ACCESS = 47; const FATTR4_TIME_ACCESS_SET = 48; const FATTR4_TIME_BACKUP = 49; const FATTR4_TIME_CREATE = 50; const FATTR4_TIME_DELTA = 51; const FATTR4_TIME_METADATA = 52; const FATTR4_TIME_MODIFY = 53; const FATTR4_TIME_MODIFY_SET = 54; const FATTR4_MOUNTED_ON_FILEID = 55; /* * File attribute container */ struct fattr4 { bitmap4 attrmask; attrlist4 attr_vals; }; /* * Change info for the client */ struct change_info4 { bool atomic; changeid4 before; changeid4 after; }; struct clientaddr4 { /* see struct rpcb in RFC 1833 */ string r_netid<>; /* network id */ string r_addr<>; /* universal address */ }; /* * Callback program info as provided by the client */ struct cb_client4 { uint32_t cb_program; clientaddr4 cb_location; }; /* * Stateid */ struct stateid4 { uint32_t seqid; opaque other[NFS4_OTHER_SIZE]; }; /* * Client ID */ struct nfs_client_id4 { verifier4 verifier; opaque id; }; struct open_owner4 { clientid4 clientid; opaque owner; }; struct lock_owner4 { clientid4 clientid; opaque owner; }; enum nfs_lock_type4 { READ_LT = 1, WRITE_LT = 2, READW_LT = 3, /* blocking read */ WRITEW_LT = 4 /* blocking write */ }; /* * ACCESS: Check access permission */ const ACCESS4_READ = 0x00000001; const ACCESS4_LOOKUP = 0x00000002; const ACCESS4_MODIFY = 0x00000004; const ACCESS4_EXTEND = 0x00000008; const ACCESS4_DELETE = 0x00000010; const ACCESS4_EXECUTE = 0x00000020; struct ACCESS4args { /* CURRENT_FH: object */ uint32_t access; }; struct ACCESS4resok { uint32_t supported; uint32_t access; }; union ACCESS4res switch (nfsstat4 status) { case NFS4_OK: ACCESS4resok resok4; default: void; }; /* * CLOSE: Close a file and release share reservations */ struct CLOSE4args { /* CURRENT_FH: object */ seqid4 seqid; stateid4 open_stateid; }; union CLOSE4res switch (nfsstat4 status) { case NFS4_OK: stateid4 open_stateid; default: void; }; /* * COMMIT: Commit cached data on server to stable storage */ struct COMMIT4args { /* CURRENT_FH: file */ offset4 offset; count4 count; }; struct COMMIT4resok { verifier4 writeverf; }; union COMMIT4res switch (nfsstat4 status) { case NFS4_OK: COMMIT4resok resok4; default: void; }; /* * CREATE: Create a non-regular file */ union createtype4 switch (nfs_ftype4 type) { case NF4LNK: linktext4 linkdata; case NF4BLK: case NF4CHR: specdata4 devdata; case NF4SOCK: case NF4FIFO: case NF4DIR: void; default: void; /* server should return NFS4ERR_BADTYPE */ }; struct CREATE4args { /* CURRENT_FH: directory for creation */ createtype4 objtype; component4 objname; fattr4 createattrs; }; struct CREATE4resok { change_info4 cinfo; bitmap4 attrset; /* attributes set */ }; union CREATE4res switch (nfsstat4 status) { case NFS4_OK: CREATE4resok resok4; default: void; }; /* * DELEGPURGE: Purge Delegations Awaiting Recovery */ struct DELEGPURGE4args { clientid4 clientid; }; struct DELEGPURGE4res { nfsstat4 status; }; /* * DELEGRETURN: Return a delegation */ struct DELEGRETURN4args { /* CURRENT_FH: delegated file */ stateid4 deleg_stateid; }; struct DELEGRETURN4res { nfsstat4 status; }; /* * GETATTR: Get file attributes */ struct GETATTR4args { /* CURRENT_FH: directory or file */ bitmap4 attr_request; }; struct GETATTR4resok { fattr4 obj_attributes; }; union GETATTR4res switch (nfsstat4 status) { case NFS4_OK: GETATTR4resok resok4; default: void; }; /* * GETFH: Get current filehandle */ struct GETFH4resok { nfs_fh4 object; }; union GETFH4res switch (nfsstat4 status) { case NFS4_OK: GETFH4resok resok4; default: void; }; /* * LINK: Create link to an object */ struct LINK4args { /* SAVED_FH: source object */ /* CURRENT_FH: target directory */ component4 newname; }; struct LINK4resok { change_info4 cinfo; }; union LINK4res switch (nfsstat4 status) { case NFS4_OK: LINK4resok resok4; default: void; }; /* * For LOCK, transition from open_owner to new lock_owner */ struct open_to_lock_owner4 { seqid4 open_seqid; stateid4 open_stateid; seqid4 lock_seqid; lock_owner4 lock_owner; }; /* * For LOCK, existing lock_owner continues to request file locks */ struct exist_lock_owner4 { stateid4 lock_stateid; seqid4 lock_seqid; }; union locker4 switch (bool new_lock_owner) { case TRUE: open_to_lock_owner4 open_owner; case FALSE: exist_lock_owner4 lock_owner; }; /* * LOCK/LOCKT/LOCKU: Record lock management */ struct LOCK4args { /* CURRENT_FH: file */ nfs_lock_type4 locktype; bool reclaim; offset4 offset; length4 length; locker4 locker; }; struct LOCK4denied { offset4 offset; length4 length; nfs_lock_type4 locktype; lock_owner4 owner; }; struct LOCK4resok { stateid4 lock_stateid; }; union LOCK4res switch (nfsstat4 status) { case NFS4_OK: LOCK4resok resok4; case NFS4ERR_DENIED: LOCK4denied denied; default: void; }; struct LOCKT4args { /* CURRENT_FH: file */ nfs_lock_type4 locktype; offset4 offset; length4 length; lock_owner4 owner; }; union LOCKT4res switch (nfsstat4 status) { case NFS4ERR_DENIED: LOCK4denied denied; case NFS4_OK: void; default: void; }; struct LOCKU4args { /* CURRENT_FH: file */ nfs_lock_type4 locktype; seqid4 seqid; stateid4 lock_stateid; offset4 offset; length4 length; }; union LOCKU4res switch (nfsstat4 status) { case NFS4_OK: stateid4 lock_stateid; default: void; }; /* * LOOKUP: Lookup filename */ struct LOOKUP4args { /* CURRENT_FH: directory */ component4 objname; }; struct LOOKUP4res { /* CURRENT_FH: object */ nfsstat4 status; }; /* * LOOKUPP: Lookup parent directory */ struct LOOKUPP4res { /* CURRENT_FH: directory */ nfsstat4 status; }; /* * NVERIFY: Verify attributes different */ struct NVERIFY4args { /* CURRENT_FH: object */ fattr4 obj_attributes; }; struct NVERIFY4res { nfsstat4 status; }; /* * Share Access and Deny constants for open argument */ const OPEN4_SHARE_ACCESS_READ = 0x00000001; const OPEN4_SHARE_ACCESS_WRITE = 0x00000002; const OPEN4_SHARE_ACCESS_BOTH = 0x00000003; const OPEN4_SHARE_DENY_NONE = 0x00000000; const OPEN4_SHARE_DENY_READ = 0x00000001; const OPEN4_SHARE_DENY_WRITE = 0x00000002; const OPEN4_SHARE_DENY_BOTH = 0x00000003; /* * Various definitions for OPEN */ enum createmode4 { UNCHECKED4 = 0, GUARDED4 = 1, EXCLUSIVE4 = 2 }; union createhow4 switch (createmode4 mode) { case UNCHECKED4: case GUARDED4: fattr4 createattrs; case EXCLUSIVE4: verifier4 createverf; }; enum opentype4 { OPEN4_NOCREATE = 0, OPEN4_CREATE = 1 }; union openflag4 switch (opentype4 opentype) { case OPEN4_CREATE: createhow4 how; default: void; }; /* Next definitions used for OPEN delegation */ enum limit_by4 { NFS_LIMIT_SIZE = 1, NFS_LIMIT_BLOCKS = 2 /* others as needed */ }; struct nfs_modified_limit4 { uint32_t num_blocks; uint32_t bytes_per_block; }; union nfs_space_limit4 switch (limit_by4 limitby) { /* limit specified as file size */ case NFS_LIMIT_SIZE: uint64_t filesize; /* limit specified by number of blocks */ case NFS_LIMIT_BLOCKS: nfs_modified_limit4 mod_blocks; } ; enum open_delegation_type4 { OPEN_DELEGATE_NONE = 0, OPEN_DELEGATE_READ = 1, OPEN_DELEGATE_WRITE = 2 }; enum open_claim_type4 { CLAIM_NULL = 0, CLAIM_PREVIOUS = 1, CLAIM_DELEGATE_CUR = 2, CLAIM_DELEGATE_PREV = 3 }; struct open_claim_delegate_cur4 { stateid4 delegate_stateid; component4 file; }; union open_claim4 switch (open_claim_type4 claim) { /* * No special rights to file. * Ordinary OPEN of the specified file. */ case CLAIM_NULL: /* CURRENT_FH: directory */ component4 file; /* * Right to the file established by an * open previous to server reboot. File * identified by filehandle obtained at * that time rather than by name. */ case CLAIM_PREVIOUS: /* CURRENT_FH: file being reclaimed */ open_delegation_type4 delegate_type; /* * Right to file based on a delegation * granted by the server. File is * specified by name. */ case CLAIM_DELEGATE_CUR: /* CURRENT_FH: directory */ open_claim_delegate_cur4 delegate_cur_info; /* * Right to file based on a delegation * granted to a previous boot instance * of the client. File is specified by name. */ case CLAIM_DELEGATE_PREV: /* CURRENT_FH: directory */ component4 file_delegate_prev; }; /* * OPEN: Open a file, potentially receiving an open delegation */ struct OPEN4args { seqid4 seqid; uint32_t share_access; uint32_t share_deny; open_owner4 owner; openflag4 openhow; open_claim4 claim; }; struct open_read_delegation4 { stateid4 stateid; /* Stateid for delegation*/ bool recall; /* Pre-recalled flag for delegations obtained by reclaim (CLAIM_PREVIOUS). */ nfsace4 permissions; /* Defines users who don't need an ACCESS call to open for read. */ }; struct open_write_delegation4 { stateid4 stateid; /* Stateid for delegation */ bool recall; /* Pre-recalled flag for delegations obtained by reclaim (CLAIM_PREVIOUS). */ nfs_space_limit4 space_limit; /* Defines condition that the client must check to determine whether the file needs to be flushed to the server on close. */ nfsace4 permissions; /* Defines users who don't need an ACCESS call as part of a delegated open. */ }; union open_delegation4 switch (open_delegation_type4 delegation_type) { case OPEN_DELEGATE_NONE: void; case OPEN_DELEGATE_READ: open_read_delegation4 read; case OPEN_DELEGATE_WRITE: open_write_delegation4 write; }; /* * Result flags */ /* Client must confirm open */ const OPEN4_RESULT_CONFIRM = 0x00000002; /* Type of file locking behavior at the server */ const OPEN4_RESULT_LOCKTYPE_POSIX = 0x00000004; struct OPEN4resok { stateid4 stateid; /* Stateid for open */ change_info4 cinfo; /* Directory change info */ uint32_t rflags; /* Result flags */ bitmap4 attrset; /* attribute set for create*/ open_delegation4 delegation; /* Info on any open delegation */ }; union OPEN4res switch (nfsstat4 status) { case NFS4_OK: /* CURRENT_FH: opened file */ OPEN4resok resok4; default: void; }; /* * OPENATTR: open named attributes directory */ struct OPENATTR4args { /* CURRENT_FH: object */ bool createdir; }; struct OPENATTR4res { /* CURRENT_FH: named attr directory */ nfsstat4 status; }; /* * OPEN_CONFIRM: confirm the open */ struct OPEN_CONFIRM4args { /* CURRENT_FH: opened file */ stateid4 open_stateid; seqid4 seqid; }; struct OPEN_CONFIRM4resok { stateid4 open_stateid; }; union OPEN_CONFIRM4res switch (nfsstat4 status) { case NFS4_OK: OPEN_CONFIRM4resok resok4; default: void; }; /* * OPEN_DOWNGRADE: downgrade the access/deny for a file */ struct OPEN_DOWNGRADE4args { /* CURRENT_FH: opened file */ stateid4 open_stateid; seqid4 seqid; uint32_t share_access; uint32_t share_deny; }; struct OPEN_DOWNGRADE4resok { stateid4 open_stateid; }; union OPEN_DOWNGRADE4res switch(nfsstat4 status) { case NFS4_OK: OPEN_DOWNGRADE4resok resok4; default: void; }; /* * PUTFH: Set current filehandle */ struct PUTFH4args { nfs_fh4 object; }; struct PUTFH4res { /* CURRENT_FH: */ nfsstat4 status; }; /* * PUTPUBFH: Set public filehandle */ struct PUTPUBFH4res { /* CURRENT_FH: public fh */ nfsstat4 status; }; /* * PUTROOTFH: Set root filehandle */ struct PUTROOTFH4res { /* CURRENT_FH: root fh */ nfsstat4 status; }; /* * READ: Read from file */ struct READ4args { /* CURRENT_FH: file */ stateid4 stateid; offset4 offset; count4 count; }; struct READ4resok { bool eof; opaque data<>; }; union READ4res switch (nfsstat4 status) { case NFS4_OK: READ4resok resok4; default: void; }; /* * READDIR: Read directory */ struct READDIR4args { /* CURRENT_FH: directory */ nfs_cookie4 cookie; verifier4 cookieverf; count4 dircount; count4 maxcount; bitmap4 attr_request; }; struct entry4 { nfs_cookie4 cookie; component4 name; fattr4 attrs; entry4 *nextentry; }; struct dirlist4 { entry4 *entries; bool eof; }; struct READDIR4resok { verifier4 cookieverf; dirlist4 reply; }; union READDIR4res switch (nfsstat4 status) { case NFS4_OK: READDIR4resok resok4; default: void; }; /* * READLINK: Read symbolic link */ struct READLINK4resok { linktext4 link; }; union READLINK4res switch (nfsstat4 status) { case NFS4_OK: READLINK4resok resok4; default: void; }; /* * REMOVE: Remove filesystem object */ struct REMOVE4args { /* CURRENT_FH: directory */ component4 target; }; struct REMOVE4resok { change_info4 cinfo; }; union REMOVE4res switch (nfsstat4 status) { case NFS4_OK: REMOVE4resok resok4; default: void; }; /* * RENAME: Rename directory entry */ struct RENAME4args { /* SAVED_FH: source directory */ component4 oldname; /* CURRENT_FH: target directory */ component4 newname; }; struct RENAME4resok { change_info4 source_cinfo; change_info4 target_cinfo; }; union RENAME4res switch (nfsstat4 status) { case NFS4_OK: RENAME4resok resok4; default: void; }; /* * RENEW: Renew a Lease */ struct RENEW4args { clientid4 clientid; }; struct RENEW4res { nfsstat4 status; }; /* * RESTOREFH: Restore saved filehandle */ struct RESTOREFH4res { /* CURRENT_FH: value of saved fh */ nfsstat4 status; }; /* * SAVEFH: Save current filehandle */ struct SAVEFH4res { /* SAVED_FH: value of current fh */ nfsstat4 status; }; /* * SECINFO: Obtain Available Security Mechanisms */ struct SECINFO4args { /* CURRENT_FH: directory */ component4 name; }; /* * From RFC 2203 */ enum rpc_gss_svc_t { RPC_GSS_SVC_NONE = 1, RPC_GSS_SVC_INTEGRITY = 2, RPC_GSS_SVC_PRIVACY = 3 }; struct rpcsec_gss_info { sec_oid4 oid; qop4 qop; rpc_gss_svc_t service; }; /* RPCSEC_GSS has a value of '6'. See RFC 2203 */ union secinfo4 switch (uint32_t flavor) { case RPCSEC_GSS: rpcsec_gss_info flavor_info; default: void; }; typedef secinfo4 SECINFO4resok<>; union SECINFO4res switch (nfsstat4 status) { case NFS4_OK: SECINFO4resok resok4; default: void; }; /* * SETATTR: Set attributes */ struct SETATTR4args { /* CURRENT_FH: target object */ stateid4 stateid; fattr4 obj_attributes; }; struct SETATTR4res { nfsstat4 status; bitmap4 attrsset; }; /* * SETCLIENTID */ struct SETCLIENTID4args { nfs_client_id4 client; cb_client4 callback; uint32_t callback_ident; }; struct SETCLIENTID4resok { clientid4 clientid; verifier4 setclientid_confirm; }; union SETCLIENTID4res switch (nfsstat4 status) { case NFS4_OK: SETCLIENTID4resok resok4; case NFS4ERR_CLID_INUSE: clientaddr4 client_using; default: void; }; struct SETCLIENTID_CONFIRM4args { clientid4 clientid; verifier4 setclientid_confirm; }; struct SETCLIENTID_CONFIRM4res { nfsstat4 status; }; /* * VERIFY: Verify attributes same */ struct VERIFY4args { /* CURRENT_FH: object */ fattr4 obj_attributes; }; struct VERIFY4res { nfsstat4 status; }; /* * WRITE: Write to file */ enum stable_how4 { UNSTABLE4 = 0, DATA_SYNC4 = 1, FILE_SYNC4 = 2 }; struct WRITE4args { /* CURRENT_FH: file */ stateid4 stateid; offset4 offset; stable_how4 stable; opaque data<>; }; struct WRITE4resok { count4 count; stable_how4 committed; verifier4 writeverf; }; union WRITE4res switch (nfsstat4 status) { case NFS4_OK: WRITE4resok resok4; default: void; }; /* * RELEASE_LOCKOWNER: Notify server to release lockowner */ struct RELEASE_LOCKOWNER4args { lock_owner4 lock_owner; }; struct RELEASE_LOCKOWNER4res { nfsstat4 status; }; /* * ILLEGAL: Response for illegal operation numbers */ struct ILLEGAL4res { nfsstat4 status; }; /* * Operation arrays */ enum nfs_opnum4 { OP_ACCESS = 3, OP_CLOSE = 4, OP_COMMIT = 5, OP_CREATE = 6, OP_DELEGPURGE = 7, OP_DELEGRETURN = 8, OP_GETATTR = 9, OP_GETFH = 10, OP_LINK = 11, OP_LOCK = 12, OP_LOCKT = 13, OP_LOCKU = 14, OP_LOOKUP = 15, OP_LOOKUPP = 16, OP_NVERIFY = 17, OP_OPEN = 18, OP_OPENATTR = 19, OP_OPEN_CONFIRM = 20, OP_OPEN_DOWNGRADE = 21, OP_PUTFH = 22, OP_PUTPUBFH = 23, OP_PUTROOTFH = 24, OP_READ = 25, OP_READDIR = 26, OP_READLINK = 27, OP_REMOVE = 28, OP_RENAME = 29, OP_RENEW = 30, OP_RESTOREFH = 31, OP_SAVEFH = 32, OP_SECINFO = 33, OP_SETATTR = 34, OP_SETCLIENTID = 35, OP_SETCLIENTID_CONFIRM = 36, OP_VERIFY = 37, OP_WRITE = 38, OP_RELEASE_LOCKOWNER = 39, OP_ILLEGAL = 10044 }; union nfs_argop4 switch (nfs_opnum4 argop) { case OP_ACCESS: ACCESS4args opaccess; case OP_CLOSE: CLOSE4args opclose; case OP_COMMIT: COMMIT4args opcommit; case OP_CREATE: CREATE4args opcreate; case OP_DELEGPURGE: DELEGPURGE4args opdelegpurge; case OP_DELEGRETURN: DELEGRETURN4args opdelegreturn; case OP_GETATTR: GETATTR4args opgetattr; case OP_GETFH: void; case OP_LINK: LINK4args oplink; case OP_LOCK: LOCK4args oplock; case OP_LOCKT: LOCKT4args oplockt; case OP_LOCKU: LOCKU4args oplocku; case OP_LOOKUP: LOOKUP4args oplookup; case OP_LOOKUPP: void; case OP_NVERIFY: NVERIFY4args opnverify; case OP_OPEN: OPEN4args opopen; case OP_OPENATTR: OPENATTR4args opopenattr; case OP_OPEN_CONFIRM: OPEN_CONFIRM4args opopen_confirm; case OP_OPEN_DOWNGRADE: OPEN_DOWNGRADE4args opopen_downgrade; case OP_PUTFH: PUTFH4args opputfh; case OP_PUTPUBFH: void; case OP_PUTROOTFH: void; case OP_READ: READ4args opread; case OP_READDIR: READDIR4args opreaddir; case OP_READLINK: void; case OP_REMOVE: REMOVE4args opremove; case OP_RENAME: RENAME4args oprename; case OP_RENEW: RENEW4args oprenew; case OP_RESTOREFH: void; case OP_SAVEFH: void; case OP_SECINFO: SECINFO4args opsecinfo; case OP_SETATTR: SETATTR4args opsetattr; case OP_SETCLIENTID: SETCLIENTID4args opsetclientid; case OP_SETCLIENTID_CONFIRM: SETCLIENTID_CONFIRM4args opsetclientid_confirm; case OP_VERIFY: VERIFY4args opverify; case OP_WRITE: WRITE4args opwrite; case OP_RELEASE_LOCKOWNER: RELEASE_LOCKOWNER4args oprelease_lockowner; case OP_ILLEGAL: void; }; union nfs_resop4 switch (nfs_opnum4 resop){ case OP_ACCESS: ACCESS4res opaccess; case OP_CLOSE: CLOSE4res opclose; case OP_COMMIT: COMMIT4res opcommit; case OP_CREATE: CREATE4res opcreate; case OP_DELEGPURGE: DELEGPURGE4res opdelegpurge; case OP_DELEGRETURN: DELEGRETURN4res opdelegreturn; case OP_GETATTR: GETATTR4res opgetattr; case OP_GETFH: GETFH4res opgetfh; case OP_LINK: LINK4res oplink; case OP_LOCK: LOCK4res oplock; case OP_LOCKT: LOCKT4res oplockt; case OP_LOCKU: LOCKU4res oplocku; case OP_LOOKUP: LOOKUP4res oplookup; case OP_LOOKUPP: LOOKUPP4res oplookupp; case OP_NVERIFY: NVERIFY4res opnverify; case OP_OPEN: OPEN4res opopen; case OP_OPENATTR: OPENATTR4res opopenattr; case OP_OPEN_CONFIRM: OPEN_CONFIRM4res opopen_confirm; case OP_OPEN_DOWNGRADE: OPEN_DOWNGRADE4res opopen_downgrade; case OP_PUTFH: PUTFH4res opputfh; case OP_PUTPUBFH: PUTPUBFH4res opputpubfh; case OP_PUTROOTFH: PUTROOTFH4res opputrootfh; case OP_READ: READ4res opread; case OP_READDIR: READDIR4res opreaddir; case OP_READLINK: READLINK4res opreadlink; case OP_REMOVE: REMOVE4res opremove; case OP_RENAME: RENAME4res oprename; case OP_RENEW: RENEW4res oprenew; case OP_RESTOREFH: RESTOREFH4res oprestorefh; case OP_SAVEFH: SAVEFH4res opsavefh; case OP_SECINFO: SECINFO4res opsecinfo; case OP_SETATTR: SETATTR4res opsetattr; case OP_SETCLIENTID: SETCLIENTID4res opsetclientid; case OP_SETCLIENTID_CONFIRM: SETCLIENTID_CONFIRM4res opsetclientid_confirm; case OP_VERIFY: VERIFY4res opverify; case OP_WRITE: WRITE4res opwrite; case OP_RELEASE_LOCKOWNER: RELEASE_LOCKOWNER4res oprelease_lockowner; case OP_ILLEGAL: ILLEGAL4res opillegal; }; struct COMPOUND4args { utf8str_cs tag; uint32_t minorversion; nfs_argop4 argarray<>; }; struct COMPOUND4res { nfsstat4 status; utf8str_cs tag; nfs_resop4 resarray<>; }; /* * Remote file service routines */ program NFS4_PROGRAM { version NFS_V4 { void NFSPROC4_NULL(void) = 0; COMPOUND4res NFSPROC4_COMPOUND(COMPOUND4args) = 1; } = 4; } = 100003; /* * NFS4 callback procedure definitions and program */ /* * CB_GETATTR: Get Current Attributes */ struct CB_GETATTR4args { nfs_fh4 fh; bitmap4 attr_request; }; struct CB_GETATTR4resok { fattr4 obj_attributes; }; union CB_GETATTR4res switch (nfsstat4 status) { case NFS4_OK: CB_GETATTR4resok resok4; default: void; }; /* * CB_RECALL: Recall an Open Delegation */ struct CB_RECALL4args { stateid4 stateid; bool truncate; nfs_fh4 fh; }; struct CB_RECALL4res { nfsstat4 status; }; /* * CB_ILLEGAL: Response for illegal operation numbers */ struct CB_ILLEGAL4res { nfsstat4 status; }; /* * Various definitions for CB_COMPOUND */ enum nfs_cb_opnum4 { NFS4_OP_CB_GETATTR = 3, NFS4_OP_CB_RECALL = 4, NFS4_OP_CB_ILLEGAL = 10044 }; union nfs_cb_argop4 switch (unsigned argop) { case NFS4_OP_CB_GETATTR: CB_GETATTR4args opcbgetattr; case NFS4_OP_CB_RECALL: CB_RECALL4args opcbrecall; case NFS4_OP_CB_ILLEGAL: void; }; union nfs_cb_resop4 switch (unsigned resop){ case NFS4_OP_CB_GETATTR: CB_GETATTR4res opcbgetattr; case NFS4_OP_CB_RECALL: CB_RECALL4res opcbrecall; case NFS4_OP_CB_ILLEGAL: CB_ILLEGAL4res opcbillegal; }; struct CB_COMPOUND4args { utf8str_cs tag; uint32_t minorversion; uint32_t callback_ident; nfs_cb_argop4 argarray<>; }; struct CB_COMPOUND4res { nfsstat4 status; utf8str_cs tag; nfs_cb_resop4 resarray<>; }; /* * Program number is in the transient range, since the client * will assign the exact transient program number and provide * that to the server via the SETCLIENTID operation. */ program NFS4_CALLBACK { version NFS_CB { void CB_NULL(void) = 0; CB_COMPOUND4res CB_COMPOUND(CB_COMPOUND4args) = 1; } = 1; } = 0x40000000; nfs-ganesha-6.5/src/Protocols/XDR/nfsacl.x000066400000000000000000000024501473756622300204450ustar00rootroot00000000000000/* SPDX-License-Identifier: unknown license... */ /* NFSv3 ACLs definitions */ union attr3 switch (bool attributes_follow) { case TRUE: fattr3 obj_attributes; case FALSE: void; }; struct posix_acl_entry { nfs3_uint32 e_tag; nfs3_uint32 e_id; nfs3_uint32 e_perm; }; struct posix_acl { nfs3_uint32 count; posix_acl_entry entries[0]; }; struct getaclargs { nfs_fh3 fhandle; nfs3_int32 mask; }; struct getaclresok { attr3 attr; nfs3_int32 mask; nfs3_uint32 acl_access_count; posix_acl *acl_access; nfs3_uint32 acl_default_count; posix_acl *acl_default; }; union getaclres switch (nfsstat3 status) { case NFS3_OK: getaclresok resok; default: void; }; struct setaclargs { nfs_fh3 fhandle; nfs3_int32 mask; nfs3_uint32 acl_access_count; posix_acl *acl_access; nfs3_uint32 acl_default_count; posix_acl *acl_default; }; struct setaclresok { attr3 attr; }; union setaclres switch (nfsstat3 status) { case NFS3_OK: setaclresok resok; default: void; }; program NFSACLPROG { version NFSACL_V3 { void NFSACLPROC_NULL(void) = 0; getaclres NFSACLPROC_GETACL(getaclargs) = 1; setaclres NFSACLPROC_SETACL(setaclargs) = 2; } = 3; } = 100227; nfs-ganesha-6.5/src/Protocols/XDR/nfsv41.x000066400000000000000000002522551473756622300203320ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2010 IETF Trust and the persons identified * as the document authors. All rights reserved. * * The document authors are identified in RFC 3530 and * RFC 5661. * * Redistribution and use in source and binary forms, with * or without modification, are permitted provided that the * following conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * - 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. * * - Neither the name of Internet Society, IETF or IETF * Trust, nor the names of specific contributors, may be * used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS * 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 COPYRIGHT OWNER 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 code was derived from RFC 3530. Please * reproduce this note if possible. * * This code was derived from RFC 5661. Please * reproduce this note if possible. * * This file was machine generated from RFC 5662. */ /* * nfs4_prot.x */ %#ifndef _AUTH_SYS_DEFINE_FOR_NFSv41 %#define _AUTH_SYS_DEFINE_FOR_NFSv41 %#include %typedef struct authsys_parms authsys_parms; %#endif /* _AUTH_SYS_DEFINE_FOR_NFSv41 */ /* * Basic typedefs for RFC 1832 data type definitions */ /* * typedef int int32_t; * typedef unsigned int uint32_t; * typedef hyper int64_t; * typedef unsigned hyper uint64_t; */ /* * Sizes */ const NFS4_FHSIZE = 128; const NFS4_VERIFIER_SIZE = 8; const NFS4_OPAQUE_LIMIT = 1024; const NFS4_SESSIONID_SIZE = 16; const NFS4_INT64_MAX = 0x7fffffffffffffff; const NFS4_UINT64_MAX = 0xffffffffffffffff; const NFS4_INT32_MAX = 0x7fffffff; const NFS4_UINT32_MAX = 0xffffffff; const NFS4_MAXFILELEN = 0xffffffffffffffff; const NFS4_MAXFILEOFF = 0xfffffffffffffffe; /* * File types */ enum nfs_ftype4 { NF4REG = 1, /* Regular File */ NF4DIR = 2, /* Directory */ NF4BLK = 3, /* Special File - block device */ NF4CHR = 4, /* Special File - character device */ NF4LNK = 5, /* Symbolic Link */ NF4SOCK = 6, /* Special File - socket */ NF4FIFO = 7, /* Special File - fifo */ NF4ATTRDIR = 8, /* Attribute Directory */ NF4NAMEDATTR = 9 /* Named Attribute */ }; /* * Error status */ enum nfsstat4 { NFS4_OK = 0, /* everything is okay */ NFS4ERR_PERM = 1, /* caller not privileged */ NFS4ERR_NOENT = 2, /* no such file/directory */ NFS4ERR_IO = 5, /* hard I/O error */ NFS4ERR_NXIO = 6, /* no such device */ NFS4ERR_ACCESS = 13, /* access denied */ NFS4ERR_EXIST = 17, /* file already exists */ NFS4ERR_XDEV = 18, /* different filesystems */ /* * Please do not allocate value 19; it was used in NFSv3 * and we do not want a value in NFSv3 to have a different * meaning in NFSv4.x. */ NFS4ERR_NOTDIR = 20, /* should be a directory */ NFS4ERR_ISDIR = 21, /* should not be directory */ NFS4ERR_INVAL = 22, /* invalid argument */ NFS4ERR_FBIG = 27, /* file exceeds server max */ NFS4ERR_NOSPC = 28, /* no space on filesystem */ NFS4ERR_ROFS = 30, /* read-only filesystem */ NFS4ERR_MLINK = 31, /* too many hard links */ NFS4ERR_NAMETOOLONG = 63, /* name exceeds server max */ NFS4ERR_NOTEMPTY = 66, /* directory not empty */ NFS4ERR_DQUOT = 69, /* hard quota limit reached*/ NFS4ERR_STALE = 70, /* file no longer exists */ NFS4ERR_BADHANDLE = 10001,/* Illegal filehandle */ NFS4ERR_BAD_COOKIE = 10003,/* READDIR cookie is stale */ NFS4ERR_NOTSUPP = 10004,/* operation not supported */ NFS4ERR_TOOSMALL = 10005,/* response limit exceeded */ NFS4ERR_SERVERFAULT = 10006,/* undefined server error */ NFS4ERR_BADTYPE = 10007,/* type invalid for CREATE */ NFS4ERR_DELAY = 10008,/* file "busy" - retry */ NFS4ERR_SAME = 10009,/* nverify says attrs same */ NFS4ERR_DENIED = 10010,/* lock unavailable */ NFS4ERR_EXPIRED = 10011,/* lock lease expired */ NFS4ERR_LOCKED = 10012,/* I/O failed due to lock */ NFS4ERR_GRACE = 10013,/* in grace period */ NFS4ERR_FHEXPIRED = 10014,/* filehandle expired */ NFS4ERR_SHARE_DENIED = 10015,/* share reserve denied */ NFS4ERR_WRONGSEC = 10016,/* wrong security flavor */ NFS4ERR_CLID_INUSE = 10017,/* clientid in use */ /* NFS4ERR_RESOURCE is not a valid error in NFSv4.1 */ NFS4ERR_RESOURCE = 10018,/* resource exhaustion */ NFS4ERR_MOVED = 10019,/* filesystem relocated */ NFS4ERR_NOFILEHANDLE = 10020,/* current FH is not set */ NFS4ERR_MINOR_VERS_MISMATCH= 10021,/* minor vers not supp */ NFS4ERR_STALE_CLIENTID = 10022,/* server has rebooted */ NFS4ERR_STALE_STATEID = 10023,/* server has rebooted */ NFS4ERR_OLD_STATEID = 10024,/* state is out of sync */ NFS4ERR_BAD_STATEID = 10025,/* incorrect stateid */ NFS4ERR_BAD_SEQID = 10026,/* request is out of seq. */ NFS4ERR_NOT_SAME = 10027,/* verify - attrs not same */ NFS4ERR_LOCK_RANGE = 10028,/* overlapping lock range */ NFS4ERR_SYMLINK = 10029,/* should be file/directory*/ NFS4ERR_RESTOREFH = 10030,/* no saved filehandle */ NFS4ERR_LEASE_MOVED = 10031,/* some filesystem moved */ NFS4ERR_ATTRNOTSUPP = 10032,/* recommended attr not sup*/ NFS4ERR_NO_GRACE = 10033,/* reclaim outside of grace*/ NFS4ERR_RECLAIM_BAD = 10034,/* reclaim error at server */ NFS4ERR_RECLAIM_CONFLICT= 10035,/* conflict on reclaim */ NFS4ERR_BADXDR = 10036,/* XDR decode failed */ NFS4ERR_LOCKS_HELD = 10037,/* file locks held at CLOSE*/ NFS4ERR_OPENMODE = 10038,/* conflict in OPEN and I/O*/ NFS4ERR_BADOWNER = 10039,/* owner translation bad */ NFS4ERR_BADCHAR = 10040,/* utf-8 char not supported*/ NFS4ERR_BADNAME = 10041,/* name not supported */ NFS4ERR_BAD_RANGE = 10042,/* lock range not supported*/ NFS4ERR_LOCK_NOTSUPP = 10043,/* no atomic up/downgrade */ NFS4ERR_OP_ILLEGAL = 10044,/* undefined operation */ NFS4ERR_DEADLOCK = 10045,/* file locking deadlock */ NFS4ERR_FILE_OPEN = 10046,/* open file blocks op. */ NFS4ERR_ADMIN_REVOKED = 10047,/* lockowner state revoked */ NFS4ERR_CB_PATH_DOWN = 10048,/* callback path down */ /* NFSv4.1 errors start here. */ NFS4ERR_BADIOMODE = 10049, NFS4ERR_BADLAYOUT = 10050, NFS4ERR_BAD_SESSION_DIGEST = 10051, NFS4ERR_BADSESSION = 10052, NFS4ERR_BADSLOT = 10053, NFS4ERR_COMPLETE_ALREADY = 10054, NFS4ERR_CONN_NOT_BOUND_TO_SESSION = 10055, NFS4ERR_DELEG_ALREADY_WANTED = 10056, NFS4ERR_BACK_CHAN_BUSY = 10057,/*backchan reqs outstanding*/ NFS4ERR_LAYOUTTRYLATER = 10058, NFS4ERR_LAYOUTUNAVAILABLE = 10059, NFS4ERR_NOMATCHING_LAYOUT = 10060, NFS4ERR_RECALLCONFLICT = 10061, NFS4ERR_UNKNOWN_LAYOUTTYPE = 10062, NFS4ERR_SEQ_MISORDERED = 10063,/* unexpected seq.ID in req*/ NFS4ERR_SEQUENCE_POS = 10064,/* [CB_]SEQ. op not 1st op */ NFS4ERR_REQ_TOO_BIG = 10065,/* request too big */ NFS4ERR_REP_TOO_BIG = 10066,/* reply too big */ NFS4ERR_REP_TOO_BIG_TO_CACHE =10067,/* rep. not all cached*/ NFS4ERR_RETRY_UNCACHED_REP =10068,/* retry & rep. uncached*/ NFS4ERR_UNSAFE_COMPOUND =10069,/* retry/recovery too hard */ NFS4ERR_TOO_MANY_OPS = 10070,/*too many ops in [CB_]COMP*/ NFS4ERR_OP_NOT_IN_SESSION =10071,/* op needs [CB_]SEQ. op */ NFS4ERR_HASH_ALG_UNSUPP = 10072, /* hash alg. not supp. */ /* Error 10073 is unused. */ NFS4ERR_CLIENTID_BUSY = 10074,/* clientid has state */ NFS4ERR_PNFS_IO_HOLE = 10075,/* IO to _SPARSE file hole */ NFS4ERR_SEQ_FALSE_RETRY= 10076,/* Retry != original req. */ NFS4ERR_BAD_HIGH_SLOT = 10077,/* req has bad highest_slot*/ NFS4ERR_DEADSESSION = 10078,/*new req sent to dead sess*/ NFS4ERR_ENCR_ALG_UNSUPP= 10079,/* encr alg. not supp. */ NFS4ERR_PNFS_NO_LAYOUT = 10080,/* I/O without a layout */ NFS4ERR_NOT_ONLY_OP = 10081,/* addl ops not allowed */ NFS4ERR_WRONG_CRED = 10082,/* op done by wrong cred */ NFS4ERR_WRONG_TYPE = 10083,/* op on wrong type object */ NFS4ERR_DIRDELEG_UNAVAIL=10084,/* delegation not avail. */ NFS4ERR_REJECT_DELEG = 10085,/* cb rejected delegation */ NFS4ERR_RETURNCONFLICT = 10086,/* layout get before return*/ NFS4ERR_DELEG_REVOKED = 10087 /* deleg./layout revoked */ }; /* * Basic data types */ typedef opaque attrlist4<>; typedef uint32_t bitmap4<>; typedef uint64_t changeid4; typedef uint64_t clientid4; typedef uint32_t count4; typedef uint64_t length4; typedef uint32_t mode4; typedef uint64_t nfs_cookie4; typedef opaque nfs_fh4; typedef uint64_t offset4; typedef uint32_t qop4; typedef opaque sec_oid4<>; typedef uint32_t sequenceid4; typedef uint32_t seqid4; typedef opaque sessionid4[NFS4_SESSIONID_SIZE]; typedef uint32_t slotid4; typedef opaque utf8string<>; typedef utf8string utf8str_cis; typedef utf8string utf8str_cs; typedef utf8string utf8str_mixed; typedef utf8str_cs component4; typedef utf8str_cs linktext4; typedef component4 pathname4<>; typedef opaque verifier4[NFS4_VERIFIER_SIZE]; /* * Timeval */ struct nfstime4 { int64_t seconds; uint32_t nseconds; }; enum time_how4 { SET_TO_SERVER_TIME4 = 0, SET_TO_CLIENT_TIME4 = 1 }; union settime4 switch (time_how4 set_it) { case SET_TO_CLIENT_TIME4: nfstime4 time; default: void; }; typedef uint32_t nfs_lease4; /* * File attribute definitions */ /* * FSID structure for major/minor */ struct fsid4 { uint64_t major; uint64_t minor; }; /* * Filesystem locations attribute * for relocation/migration and * related attributes. */ struct change_policy4 { uint64_t cp_major; uint64_t cp_minor; }; struct fs_location4 { utf8str_cis server<>; pathname4 rootpath; }; struct fs_locations4 { pathname4 fs_root; fs_location4 locations<>; }; /* * Various Access Control Entry definitions */ /* * Mask that indicates which * Access Control Entries are supported. * Values for the fattr4_aclsupport attribute. */ const ACL4_SUPPORT_ALLOW_ACL = 0x00000001; const ACL4_SUPPORT_DENY_ACL = 0x00000002; const ACL4_SUPPORT_AUDIT_ACL = 0x00000004; const ACL4_SUPPORT_ALARM_ACL = 0x00000008; typedef uint32_t acetype4; /* * acetype4 values, others can be added as needed. */ const ACE4_ACCESS_ALLOWED_ACE_TYPE = 0x00000000; const ACE4_ACCESS_DENIED_ACE_TYPE = 0x00000001; const ACE4_SYSTEM_AUDIT_ACE_TYPE = 0x00000002; const ACE4_SYSTEM_ALARM_ACE_TYPE = 0x00000003; /* * ACE flag */ typedef uint32_t aceflag4; /* * ACE flag values */ const ACE4_FILE_INHERIT_ACE = 0x00000001; const ACE4_DIRECTORY_INHERIT_ACE = 0x00000002; const ACE4_NO_PROPAGATE_INHERIT_ACE = 0x00000004; const ACE4_INHERIT_ONLY_ACE = 0x00000008; const ACE4_SUCCESSFUL_ACCESS_ACE_FLAG = 0x00000010; const ACE4_FAILED_ACCESS_ACE_FLAG = 0x00000020; const ACE4_IDENTIFIER_GROUP = 0x00000040; const ACE4_INHERITED_ACE = 0x00000080; /* * ACE mask */ typedef uint32_t acemask4; /* * ACE mask values */ const ACE4_READ_DATA = 0x00000001; const ACE4_LIST_DIRECTORY = 0x00000001; const ACE4_WRITE_DATA = 0x00000002; const ACE4_ADD_FILE = 0x00000002; const ACE4_APPEND_DATA = 0x00000004; const ACE4_ADD_SUBDIRECTORY = 0x00000004; const ACE4_READ_NAMED_ATTRS = 0x00000008; const ACE4_WRITE_NAMED_ATTRS = 0x00000010; const ACE4_EXECUTE = 0x00000020; const ACE4_DELETE_CHILD = 0x00000040; const ACE4_READ_ATTRIBUTES = 0x00000080; const ACE4_WRITE_ATTRIBUTES = 0x00000100; const ACE4_WRITE_RETENTION = 0x00000200; const ACE4_WRITE_RETENTION_HOLD = 0x00000400; const ACE4_DELETE = 0x00010000; const ACE4_READ_ACL = 0x00020000; const ACE4_WRITE_ACL = 0x00040000; const ACE4_WRITE_OWNER = 0x00080000; const ACE4_SYNCHRONIZE = 0x00100000; /* * ACE4_GENERIC_READ -- defined as combination of * ACE4_READ_ACL | * ACE4_READ_DATA | * ACE4_READ_ATTRIBUTES | * ACE4_SYNCHRONIZE */ const ACE4_GENERIC_READ = 0x00120081; /* * ACE4_GENERIC_WRITE -- defined as combination of * ACE4_READ_ACL | * ACE4_WRITE_DATA | * ACE4_WRITE_ATTRIBUTES | * ACE4_WRITE_ACL | * ACE4_APPEND_DATA | * ACE4_SYNCHRONIZE */ const ACE4_GENERIC_WRITE = 0x00160106; /* * ACE4_GENERIC_EXECUTE -- defined as combination of * ACE4_READ_ACL * ACE4_READ_ATTRIBUTES * ACE4_EXECUTE * ACE4_SYNCHRONIZE */ const ACE4_GENERIC_EXECUTE = 0x001200A0; /* * Access Control Entry definition */ struct nfsace4 { acetype4 type; aceflag4 flag; acemask4 access_mask; utf8str_mixed who; }; /* * ACL flag */ typedef uint32_t aclflag4; /* * ACL flag values */ const ACL4_AUTO_INHERIT = 0x00000001; const ACL4_PROTECTED = 0x00000002; const ACL4_DEFAULTED = 0x00000004; /* * Version 4.1 Access Control List definition */ struct nfsacl41 { aclflag4 na41_flag; nfsace4 na41_aces<>; }; /* * Field definitions for the fattr4_mode * and fattr4_mode_set_masked attributes. */ const MODE4_SUID = 0x800; /* set user id on execution */ const MODE4_SGID = 0x400; /* set group id on execution */ const MODE4_SVTX = 0x200; /* save text even after use */ const MODE4_RUSR = 0x100; /* read permission: owner */ const MODE4_WUSR = 0x080; /* write permission: owner */ const MODE4_XUSR = 0x040; /* execute permission: owner */ const MODE4_RGRP = 0x020; /* read permission: group */ const MODE4_WGRP = 0x010; /* write permission: group */ const MODE4_XGRP = 0x008; /* execute permission: group */ const MODE4_ROTH = 0x004; /* read permission: other */ const MODE4_WOTH = 0x002; /* write permission: other */ const MODE4_XOTH = 0x001; /* execute permission: other */ /* * Masked mode for the mode_set_masked attribute. */ struct mode_masked4 { mode4 mm_value_to_set; /* Values of bits to set or reset in mode. */ mode4 mm_mask_bits; /* Mask of bits to set or reset in mode. */ }; /* * Special data/attribute associated with * file types NF4BLK and NF4CHR. */ struct specdata4 { uint32_t specdata1; /* major device number */ uint32_t specdata2; /* minor device number */ }; /* * Values for fattr4_fh_expire_type */ const FH4_PERSISTENT = 0x00000000; const FH4_NOEXPIRE_WITH_OPEN = 0x00000001; const FH4_VOLATILE_ANY = 0x00000002; const FH4_VOL_MIGRATION = 0x00000004; const FH4_VOL_RENAME = 0x00000008; struct netaddr4 { /* see struct rpcb in RFC 1833 */ string na_r_netid<>; /* network id */ string na_r_addr<>; /* universal address */ }; /* * data structures new to NFSv4.1 */ struct nfs_impl_id4 { utf8str_cis nii_domain; utf8str_cs nii_name; nfstime4 nii_date; }; /* * Stateid */ struct stateid4 { uint32_t seqid; opaque other[12]; }; enum layouttype4 { LAYOUT4_NFSV4_1_FILES = 0x1, LAYOUT4_OSD2_OBJECTS = 0x2, LAYOUT4_BLOCK_VOLUME = 0x3 }; struct layout_content4 { layouttype4 loc_type; opaque loc_body<>; }; %/* % * LAYOUT4_OSD2_OBJECTS loc_body description % * is in a separate .x file % */ % %/* % * LAYOUT4_BLOCK_VOLUME loc_body description % * is in a separate .x file % */ struct layouthint4 { layouttype4 loh_type; opaque loh_body<>; }; enum layoutiomode4 { LAYOUTIOMODE4_READ = 1, LAYOUTIOMODE4_RW = 2, LAYOUTIOMODE4_ANY = 3 }; struct layout4 { offset4 lo_offset; length4 lo_length; layoutiomode4 lo_iomode; layout_content4 lo_content; }; const NFS4_DEVICEID4_SIZE = 16; typedef opaque deviceid4[NFS4_DEVICEID4_SIZE]; struct device_addr4 { layouttype4 da_layout_type; opaque da_addr_body<>; }; struct layoutupdate4 { layouttype4 lou_type; opaque lou_body<>; }; % /* Constants used for LAYOUTRETURN and CB_LAYOUTRECALL */ const LAYOUT4_RET_REC_FILE = 1; const LAYOUT4_RET_REC_FSID = 2; const LAYOUT4_RET_REC_ALL = 3; % enum layoutreturn_type4 { LAYOUTRETURN4_FILE = LAYOUT4_RET_REC_FILE, LAYOUTRETURN4_FSID = LAYOUT4_RET_REC_FSID, LAYOUTRETURN4_ALL = LAYOUT4_RET_REC_ALL }; struct layoutreturn_file4 { offset4 lrf_offset; length4 lrf_length; stateid4 lrf_stateid; % /* layouttype4 specific data */ opaque lrf_body<>; }; union layoutreturn4 switch(layoutreturn_type4 lr_returntype) { case LAYOUTRETURN4_FILE: layoutreturn_file4 lr_layout; default: void; }; % enum fs4_status_type { STATUS4_FIXED = 1, STATUS4_UPDATED = 2, STATUS4_VERSIONED = 3, STATUS4_WRITABLE = 4, STATUS4_REFERRAL = 5 }; struct fs4_status { bool fss_absent; fs4_status_type fss_type; utf8str_cs fss_source; utf8str_cs fss_current; int32_t fss_age; nfstime4 fss_version; }; const TH4_READ_SIZE = 0; const TH4_WRITE_SIZE = 1; const TH4_READ_IOSIZE = 2; const TH4_WRITE_IOSIZE = 3; typedef length4 threshold4_read_size; typedef length4 threshold4_write_size; typedef length4 threshold4_read_iosize; typedef length4 threshold4_write_iosize; struct threshold_item4 { layouttype4 thi_layout_type; bitmap4 thi_hintset; opaque thi_hintlist<>; }; struct mdsthreshold4 { threshold_item4 mth_hints<>; }; const RET4_DURATION_INFINITE = 0xffffffffffffffff; struct retention_get4 { uint64_t rg_duration; nfstime4 rg_begin_time<1>; }; struct retention_set4 { bool rs_enable; uint64_t rs_duration<1>; }; const FSCHARSET_CAP4_CONTAINS_NON_UTF8 = 0x1; const FSCHARSET_CAP4_ALLOWS_ONLY_UTF8 = 0x2; typedef uint32_t fs_charset_cap4; /* * NFSv4.1 attributes */ typedef bitmap4 fattr4_supported_attrs; typedef nfs_ftype4 fattr4_type; typedef uint32_t fattr4_fh_expire_type; typedef changeid4 fattr4_change; typedef uint64_t fattr4_size; typedef bool fattr4_link_support; typedef bool fattr4_symlink_support; typedef bool fattr4_named_attr; typedef fsid4 fattr4_fsid; typedef bool fattr4_unique_handles; typedef nfs_lease4 fattr4_lease_time; typedef nfsstat4 fattr4_rdattr_error; typedef nfsace4 fattr4_acl<>; typedef uint32_t fattr4_aclsupport; typedef bool fattr4_archive; typedef bool fattr4_cansettime; typedef bool fattr4_case_insensitive; typedef bool fattr4_case_preserving; typedef bool fattr4_chown_restricted; typedef uint64_t fattr4_fileid; typedef uint64_t fattr4_files_avail; typedef nfs_fh4 fattr4_filehandle; typedef uint64_t fattr4_files_free; typedef uint64_t fattr4_files_total; typedef fs_locations4 fattr4_fs_locations; typedef bool fattr4_hidden; typedef bool fattr4_homogeneous; typedef uint64_t fattr4_maxfilesize; typedef uint32_t fattr4_maxlink; typedef uint32_t fattr4_maxname; typedef uint64_t fattr4_maxread; typedef uint64_t fattr4_maxwrite; typedef utf8str_cs fattr4_mimetype; typedef mode4 fattr4_mode; typedef mode_masked4 fattr4_mode_set_masked; typedef uint64_t fattr4_mounted_on_fileid; typedef bool fattr4_no_trunc; typedef uint32_t fattr4_numlinks; typedef utf8str_mixed fattr4_owner; typedef utf8str_mixed fattr4_owner_group; typedef uint64_t fattr4_quota_avail_hard; typedef uint64_t fattr4_quota_avail_soft; typedef uint64_t fattr4_quota_used; typedef specdata4 fattr4_rawdev; typedef uint64_t fattr4_space_avail; typedef uint64_t fattr4_space_free; typedef uint64_t fattr4_space_total; typedef uint64_t fattr4_space_used; typedef bool fattr4_system; typedef nfstime4 fattr4_time_access; typedef settime4 fattr4_time_access_set; typedef nfstime4 fattr4_time_backup; typedef nfstime4 fattr4_time_create; typedef nfstime4 fattr4_time_delta; typedef nfstime4 fattr4_time_metadata; typedef nfstime4 fattr4_time_modify; typedef settime4 fattr4_time_modify_set; /* * attributes new to NFSv4.1 */ typedef bitmap4 fattr4_suppattr_exclcreat; typedef nfstime4 fattr4_dir_notif_delay; typedef nfstime4 fattr4_dirent_notif_delay; typedef layouttype4 fattr4_fs_layout_types<>; typedef fs4_status fattr4_fs_status; typedef fs_charset_cap4 fattr4_fs_charset_cap; typedef uint32_t fattr4_layout_alignment; typedef uint32_t fattr4_layout_blksize; typedef layouthint4 fattr4_layout_hint; typedef layouttype4 fattr4_layout_types<>; typedef mdsthreshold4 fattr4_mdsthreshold; typedef retention_get4 fattr4_retention_get; typedef retention_set4 fattr4_retention_set; typedef retention_get4 fattr4_retentevt_get; typedef retention_set4 fattr4_retentevt_set; typedef uint64_t fattr4_retention_hold; typedef nfsacl41 fattr4_dacl; typedef nfsacl41 fattr4_sacl; typedef change_policy4 fattr4_change_policy; %/* % * REQUIRED Attributes % */ const FATTR4_SUPPORTED_ATTRS = 0; const FATTR4_TYPE = 1; const FATTR4_FH_EXPIRE_TYPE = 2; const FATTR4_CHANGE = 3; const FATTR4_SIZE = 4; const FATTR4_LINK_SUPPORT = 5; const FATTR4_SYMLINK_SUPPORT = 6; const FATTR4_NAMED_ATTR = 7; const FATTR4_FSID = 8; const FATTR4_UNIQUE_HANDLES = 9; const FATTR4_LEASE_TIME = 10; const FATTR4_RDATTR_ERROR = 11; const FATTR4_FILEHANDLE = 19; %/* new to NFSV4.1 */ const FATTR4_SUPPATTR_EXCLCREAT = 75; %/* % * RECOMMENDED Attributes % */ const FATTR4_ACL = 12; const FATTR4_ACLSUPPORT = 13; const FATTR4_ARCHIVE = 14; const FATTR4_CANSETTIME = 15; const FATTR4_CASE_INSENSITIVE = 16; const FATTR4_CASE_PRESERVING = 17; const FATTR4_CHOWN_RESTRICTED = 18; const FATTR4_FILEID = 20; const FATTR4_FILES_AVAIL = 21; const FATTR4_FILES_FREE = 22; const FATTR4_FILES_TOTAL = 23; const FATTR4_FS_LOCATIONS = 24; const FATTR4_HIDDEN = 25; const FATTR4_HOMOGENEOUS = 26; const FATTR4_MAXFILESIZE = 27; const FATTR4_MAXLINK = 28; const FATTR4_MAXNAME = 29; const FATTR4_MAXREAD = 30; const FATTR4_MAXWRITE = 31; const FATTR4_MIMETYPE = 32; const FATTR4_MODE = 33; const FATTR4_NO_TRUNC = 34; const FATTR4_NUMLINKS = 35; const FATTR4_OWNER = 36; const FATTR4_OWNER_GROUP = 37; const FATTR4_QUOTA_AVAIL_HARD = 38; const FATTR4_QUOTA_AVAIL_SOFT = 39; const FATTR4_QUOTA_USED = 40; const FATTR4_RAWDEV = 41; const FATTR4_SPACE_AVAIL = 42; const FATTR4_SPACE_FREE = 43; const FATTR4_SPACE_TOTAL = 44; const FATTR4_SPACE_USED = 45; const FATTR4_SYSTEM = 46; const FATTR4_TIME_ACCESS = 47; const FATTR4_TIME_ACCESS_SET = 48; const FATTR4_TIME_BACKUP = 49; const FATTR4_TIME_CREATE = 50; const FATTR4_TIME_DELTA = 51; const FATTR4_TIME_METADATA = 52; const FATTR4_TIME_MODIFY = 53; const FATTR4_TIME_MODIFY_SET = 54; const FATTR4_MOUNTED_ON_FILEID = 55; % %/* new to NFSV4.1 */ % const FATTR4_DIR_NOTIF_DELAY = 56; const FATTR4_DIRENT_NOTIF_DELAY = 57; const FATTR4_DACL = 58; const FATTR4_SACL = 59; const FATTR4_CHANGE_POLICY = 60; const FATTR4_FS_STATUS = 61; const FATTR4_FS_LAYOUT_TYPES = 62; const FATTR4_LAYOUT_HINT = 63; const FATTR4_LAYOUT_TYPES = 64; const FATTR4_LAYOUT_BLKSIZE = 65; const FATTR4_LAYOUT_ALIGNMENT = 66; const FATTR4_FS_LOCATIONS_INFO = 67; const FATTR4_MDSTHRESHOLD = 68; const FATTR4_RETENTION_GET = 69; const FATTR4_RETENTION_SET = 70; const FATTR4_RETENTEVT_GET = 71; const FATTR4_RETENTEVT_SET = 72; const FATTR4_RETENTION_HOLD = 73; const FATTR4_MODE_SET_MASKED = 74; const FATTR4_FS_CHARSET_CAP = 76; /* * File attribute container */ struct fattr4 { bitmap4 attrmask; attrlist4 attr_vals; }; /* * Change info for the client */ struct change_info4 { bool atomic; changeid4 before; changeid4 after; }; typedef netaddr4 clientaddr4; /* * Callback program info as provided by the client */ struct cb_client4 { uint32_t cb_program; netaddr4 cb_location; }; /* * NFSv4.0 Long Hand Client ID */ struct nfs_client_id4 { verifier4 verifier; opaque id; }; /* * NFSv4.1 Client Owner (aka long hand client ID) */ struct client_owner4 { verifier4 co_verifier; opaque co_ownerid; }; /* * NFSv4.1 server Owner */ struct server_owner4 { uint64_t so_minor_id; opaque so_major_id; }; struct state_owner4 { clientid4 clientid; opaque owner; }; typedef state_owner4 open_owner4; typedef state_owner4 lock_owner4; enum nfs_lock_type4 { READ_LT = 1, WRITE_LT = 2, READW_LT = 3, /* blocking read */ WRITEW_LT = 4 /* blocking write */ }; % %/* Input for computing subkeys */ enum ssv_subkey4 { SSV4_SUBKEY_MIC_I2T = 1, SSV4_SUBKEY_MIC_T2I = 2, SSV4_SUBKEY_SEAL_I2T = 3, SSV4_SUBKEY_SEAL_T2I = 4 }; % % %/* Input for computing smt_hmac */ struct ssv_mic_plain_tkn4 { uint32_t smpt_ssv_seq; opaque smpt_orig_plain<>; }; % % %/* SSV GSS PerMsgToken token */ struct ssv_mic_tkn4 { uint32_t smt_ssv_seq; opaque smt_hmac<>; }; % % %/* Input for computing ssct_encr_data and ssct_hmac */ struct ssv_seal_plain_tkn4 { opaque sspt_confounder<>; uint32_t sspt_ssv_seq; opaque sspt_orig_plain<>; opaque sspt_pad<>; }; % % %/* SSV GSS SealedMessage token */ struct ssv_seal_cipher_tkn4 { uint32_t ssct_ssv_seq; opaque ssct_iv<>; opaque ssct_encr_data<>; opaque ssct_hmac<>; }; % /* * Defines an individual server replica */ struct fs_locations_server4 { int32_t fls_currency; opaque fls_info<>; utf8str_cis fls_server; }; /* * Byte indices of items within * fls_info: flag fields, class numbers, * bytes indicating ranks and orders. */ const FSLI4BX_GFLAGS = 0; const FSLI4BX_TFLAGS = 1; const FSLI4BX_CLSIMUL = 2; const FSLI4BX_CLHANDLE = 3; const FSLI4BX_CLFILEID = 4; const FSLI4BX_CLWRITEVER = 5; const FSLI4BX_CLCHANGE = 6; const FSLI4BX_CLREADDIR = 7; const FSLI4BX_READRANK = 8; const FSLI4BX_WRITERANK = 9; const FSLI4BX_READORDER = 10; const FSLI4BX_WRITEORDER = 11; /* * Bits defined within the general flag byte. */ const FSLI4GF_WRITABLE = 0x01; const FSLI4GF_CUR_REQ = 0x02; const FSLI4GF_ABSENT = 0x04; const FSLI4GF_GOING = 0x08; const FSLI4GF_SPLIT = 0x10; /* * Bits defined within the transport flag byte. */ const FSLI4TF_RDMA = 0x01; /* * Defines a set of replicas sharing * a common value of the root path * with in the corresponding * single-server namespaces. */ struct fs_locations_item4 { fs_locations_server4 fli_entries<>; pathname4 fli_rootpath; }; /* * Defines the overall structure of * the fs_locations_info attribute. */ struct fs_locations_info4 { uint32_t fli_flags; int32_t fli_valid_for; pathname4 fli_fs_root; fs_locations_item4 fli_items<>; }; /* * Flag bits in fli_flags. */ const FSLI4IF_VAR_SUB = 0x00000001; typedef fs_locations_info4 fattr4_fs_locations_info; const NFL4_UFLG_MASK = 0x0000003F; const NFL4_UFLG_DENSE = 0x00000001; const NFL4_UFLG_COMMIT_THRU_MDS = 0x00000002; const NFL4_UFLG_STRIPE_UNIT_SIZE_MASK = 0xFFFFFFC0; typedef uint32_t nfl_util4; % enum filelayout_hint_care4 { NFLH4_CARE_DENSE = NFL4_UFLG_DENSE, NFLH4_CARE_COMMIT_THRU_MDS = NFL4_UFLG_COMMIT_THRU_MDS, NFLH4_CARE_STRIPE_UNIT_SIZE = 0x00000040, NFLH4_CARE_STRIPE_COUNT = 0x00000080 }; % %/* Encoded in the loh_body field of data type layouthint4: */ % struct nfsv4_1_file_layouthint4 { uint32_t nflh_care; nfl_util4 nflh_util; count4 nflh_stripe_count; }; % % typedef netaddr4 multipath_list4<>; % %/* % * Encoded in the da_addr_body field of % * data type device_addr4: % */ struct nfsv4_1_file_layout_ds_addr4 { uint32_t nflda_stripe_indices<>; multipath_list4 nflda_multipath_ds_list<>; }; % % %/* % * Encoded in the loc_body field of % * data type layout_content4: % */ struct nfsv4_1_file_layout4 { deviceid4 nfl_deviceid; nfl_util4 nfl_util; uint32_t nfl_first_stripe_index; offset4 nfl_pattern_offset; nfs_fh4 nfl_fh_list<>; }; % %/* % * Encoded in the lou_body field of data type layoutupdate4: % * Nothing. lou_body is a zero length array of bytes. % */ % %/* % * Encoded in the lrf_body field of % * data type layoutreturn_file4: % * Nothing. lrf_body is a zero length array of bytes. % */ % const ACCESS4_READ = 0x00000001; const ACCESS4_LOOKUP = 0x00000002; const ACCESS4_MODIFY = 0x00000004; const ACCESS4_EXTEND = 0x00000008; const ACCESS4_DELETE = 0x00000010; const ACCESS4_EXECUTE = 0x00000020; struct ACCESS4args { /* CURRENT_FH: object */ uint32_t access; }; struct ACCESS4resok { uint32_t supported; uint32_t access; }; union ACCESS4res switch (nfsstat4 status) { case NFS4_OK: ACCESS4resok resok4; default: void; }; struct CLOSE4args { /* CURRENT_FH: object */ seqid4 seqid; stateid4 open_stateid; }; union CLOSE4res switch (nfsstat4 status) { case NFS4_OK: stateid4 open_stateid; default: void; }; struct COMMIT4args { /* CURRENT_FH: file */ offset4 offset; count4 count; }; struct COMMIT4resok { verifier4 writeverf; }; union COMMIT4res switch (nfsstat4 status) { case NFS4_OK: COMMIT4resok resok4; default: void; }; union createtype4 switch (nfs_ftype4 type) { case NF4LNK: linktext4 linkdata; case NF4BLK: case NF4CHR: specdata4 devdata; case NF4SOCK: case NF4FIFO: case NF4DIR: void; default: void; /* server should return NFS4ERR_BADTYPE */ }; struct CREATE4args { /* CURRENT_FH: directory for creation */ createtype4 objtype; component4 objname; fattr4 createattrs; }; struct CREATE4resok { change_info4 cinfo; bitmap4 attrset; /* attributes set */ }; union CREATE4res switch (nfsstat4 status) { case NFS4_OK: /* new CURRENTFH: created object */ CREATE4resok resok4; default: void; }; struct DELEGPURGE4args { clientid4 clientid; }; struct DELEGPURGE4res { nfsstat4 status; }; struct DELEGRETURN4args { /* CURRENT_FH: delegated object */ stateid4 deleg_stateid; }; struct DELEGRETURN4res { nfsstat4 status; }; struct GETATTR4args { /* CURRENT_FH: object */ bitmap4 attr_request; }; struct GETATTR4resok { fattr4 obj_attributes; }; union GETATTR4res switch (nfsstat4 status) { case NFS4_OK: GETATTR4resok resok4; default: void; }; struct GETFH4resok { nfs_fh4 object; }; union GETFH4res switch (nfsstat4 status) { case NFS4_OK: GETFH4resok resok4; default: void; }; struct LINK4args { /* SAVED_FH: source object */ /* CURRENT_FH: target directory */ component4 newname; }; struct LINK4resok { change_info4 cinfo; }; union LINK4res switch (nfsstat4 status) { case NFS4_OK: LINK4resok resok4; default: void; }; /* * For LOCK, transition from open_stateid and lock_owner * to a lock stateid. */ struct open_to_lock_owner4 { seqid4 open_seqid; stateid4 open_stateid; seqid4 lock_seqid; lock_owner4 lock_owner; }; /* * For LOCK, existing lock stateid continues to request new * file lock for the same lock_owner and open_stateid. */ struct exist_lock_owner4 { stateid4 lock_stateid; seqid4 lock_seqid; }; union locker4 switch (bool new_lock_owner) { case TRUE: open_to_lock_owner4 open_owner; case FALSE: exist_lock_owner4 lock_owner; }; /* * LOCK/LOCKT/LOCKU: Record lock management */ struct LOCK4args { /* CURRENT_FH: file */ nfs_lock_type4 locktype; bool reclaim; offset4 offset; length4 length; locker4 locker; }; struct LOCK4denied { offset4 offset; length4 length; nfs_lock_type4 locktype; lock_owner4 owner; }; struct LOCK4resok { stateid4 lock_stateid; }; union LOCK4res switch (nfsstat4 status) { case NFS4_OK: LOCK4resok resok4; case NFS4ERR_DENIED: LOCK4denied denied; default: void; }; struct LOCKT4args { /* CURRENT_FH: file */ nfs_lock_type4 locktype; offset4 offset; length4 length; lock_owner4 owner; }; union LOCKT4res switch (nfsstat4 status) { case NFS4ERR_DENIED: LOCK4denied denied; case NFS4_OK: void; default: void; }; struct LOCKU4args { /* CURRENT_FH: file */ nfs_lock_type4 locktype; seqid4 seqid; stateid4 lock_stateid; offset4 offset; length4 length; }; union LOCKU4res switch (nfsstat4 status) { case NFS4_OK: stateid4 lock_stateid; default: void; }; struct LOOKUP4args { /* CURRENT_FH: directory */ component4 objname; }; struct LOOKUP4res { /* New CURRENT_FH: object */ nfsstat4 status; }; struct LOOKUPP4res { /* new CURRENT_FH: parent directory */ nfsstat4 status; }; struct NVERIFY4args { /* CURRENT_FH: object */ fattr4 obj_attributes; }; struct NVERIFY4res { nfsstat4 status; }; /* * Various definitions for OPEN */ enum createmode4 { UNCHECKED4 = 0, GUARDED4 = 1, /* Deprecated in NFSv4.1. */ EXCLUSIVE4 = 2, /* * New to NFSv4.1. If session is persistent, * GUARDED4 MUST be used. Otherwise, use * EXCLUSIVE4_1 instead of EXCLUSIVE4. */ EXCLUSIVE4_1 = 3 }; struct creatverfattr { verifier4 cva_verf; fattr4 cva_attrs; }; union createhow4 switch (createmode4 mode) { case UNCHECKED4: case GUARDED4: fattr4 createattrs; case EXCLUSIVE4: verifier4 createverf; case EXCLUSIVE4_1: creatverfattr ch_createboth; }; enum opentype4 { OPEN4_NOCREATE = 0, OPEN4_CREATE = 1 }; union openflag4 switch (opentype4 opentype) { case OPEN4_CREATE: createhow4 how; default: void; }; /* Next definitions used for OPEN delegation */ enum limit_by4 { NFS_LIMIT_SIZE = 1, NFS_LIMIT_BLOCKS = 2 /* others as needed */ }; struct nfs_modified_limit4 { uint32_t num_blocks; uint32_t bytes_per_block; }; union nfs_space_limit4 switch (limit_by4 limitby) { /* limit specified as file size */ case NFS_LIMIT_SIZE: uint64_t filesize; /* limit specified by number of blocks */ case NFS_LIMIT_BLOCKS: nfs_modified_limit4 mod_blocks; } ; /* * Share Access and Deny constants for open argument */ const OPEN4_SHARE_ACCESS_READ = 0x00000001; const OPEN4_SHARE_ACCESS_WRITE = 0x00000002; const OPEN4_SHARE_ACCESS_BOTH = 0x00000003; const OPEN4_SHARE_DENY_NONE = 0x00000000; const OPEN4_SHARE_DENY_READ = 0x00000001; const OPEN4_SHARE_DENY_WRITE = 0x00000002; const OPEN4_SHARE_DENY_BOTH = 0x00000003; /* new flags for share_access field of OPEN4args */ const OPEN4_SHARE_ACCESS_WANT_DELEG_MASK = 0xFF00; const OPEN4_SHARE_ACCESS_WANT_NO_PREFERENCE = 0x0000; const OPEN4_SHARE_ACCESS_WANT_READ_DELEG = 0x0100; const OPEN4_SHARE_ACCESS_WANT_WRITE_DELEG = 0x0200; const OPEN4_SHARE_ACCESS_WANT_ANY_DELEG = 0x0300; const OPEN4_SHARE_ACCESS_WANT_NO_DELEG = 0x0400; const OPEN4_SHARE_ACCESS_WANT_CANCEL = 0x0500; const OPEN4_SHARE_ACCESS_WANT_SIGNAL_DELEG_WHEN_RESRC_AVAIL = 0x10000; const OPEN4_SHARE_ACCESS_WANT_PUSH_DELEG_WHEN_UNCONTENDED = 0x20000; enum open_delegation_type4 { OPEN_DELEGATE_NONE = 0, OPEN_DELEGATE_READ = 1, OPEN_DELEGATE_WRITE = 2, OPEN_DELEGATE_NONE_EXT = 3 /* new to v4.1 */ }; enum open_claim_type4 { /* * Not a reclaim. */ CLAIM_NULL = 0, CLAIM_PREVIOUS = 1, CLAIM_DELEGATE_CUR = 2, CLAIM_DELEGATE_PREV = 3, /* * Not a reclaim. * * Like CLAIM_NULL, but object identified * by the current filehandle. */ CLAIM_FH = 4, /* new to v4.1 */ /* * Like CLAIM_DELEGATE_CUR, but object identified * by current filehandle. */ CLAIM_DELEG_CUR_FH = 5, /* new to v4.1 */ /* * Like CLAIM_DELEGATE_PREV, but object identified * by current filehandle. */ CLAIM_DELEG_PREV_FH = 6 /* new to v4.1 */ }; struct open_claim_delegate_cur4 { stateid4 delegate_stateid; component4 file; }; union open_claim4 switch (open_claim_type4 claim) { /* * No special rights to file. * Ordinary OPEN of the specified file. */ case CLAIM_NULL: /* CURRENT_FH: directory */ component4 file; /* * Right to the file established by an * open previous to server reboot. File * identified by filehandle obtained at * that time rather than by name. */ case CLAIM_PREVIOUS: /* CURRENT_FH: file being reclaimed */ open_delegation_type4 delegate_type; /* * Right to file based on a delegation * granted by the server. File is * specified by name. */ case CLAIM_DELEGATE_CUR: /* CURRENT_FH: directory */ open_claim_delegate_cur4 delegate_cur_info; /* * Right to file based on a delegation * granted to a previous boot instance * of the client. File is specified by name. */ case CLAIM_DELEGATE_PREV: /* CURRENT_FH: directory */ component4 file_delegate_prev; /* * Like CLAIM_NULL. No special rights * to file. Ordinary OPEN of the * specified file by current filehandle. */ case CLAIM_FH: /* new to v4.1 */ /* CURRENT_FH: regular file to open */ void; /* * Like CLAIM_DELEGATE_PREV. Right to file based on a * delegation granted to a previous boot * instance of the client. File is identified by * by filehandle. */ case CLAIM_DELEG_PREV_FH: /* new to v4.1 */ /* CURRENT_FH: file being opened */ void; /* * Like CLAIM_DELEGATE_CUR. Right to file based on * a delegation granted by the server. * File is identified by filehandle. */ case CLAIM_DELEG_CUR_FH: /* new to v4.1 */ /* CURRENT_FH: file being opened */ stateid4 oc_delegate_stateid; }; /* * OPEN: Open a file, potentially receiving an open delegation */ struct OPEN4args { seqid4 seqid; uint32_t share_access; uint32_t share_deny; open_owner4 owner; openflag4 openhow; open_claim4 claim; }; struct open_read_delegation4 { stateid4 stateid; /* Stateid for delegation*/ bool recall; /* Pre-recalled flag for delegations obtained by reclaim (CLAIM_PREVIOUS) */ nfsace4 permissions; /* Defines users who don't need an ACCESS call to open for read */ }; struct open_write_delegation4 { stateid4 stateid; /* Stateid for delegation */ bool recall; /* Pre-recalled flag for delegations obtained by reclaim (CLAIM_PREVIOUS) */ nfs_space_limit4 space_limit; /* Defines condition that the client must check to determine whether the file needs to be flushed to the server on close. */ nfsace4 permissions; /* Defines users who don't need an ACCESS call as part of a delegated open. */ }; enum why_no_delegation4 { /* new to v4.1 */ WND4_NOT_WANTED = 0, WND4_CONTENTION = 1, WND4_RESOURCE = 2, WND4_NOT_SUPP_FTYPE = 3, WND4_WRITE_DELEG_NOT_SUPP_FTYPE = 4, WND4_NOT_SUPP_UPGRADE = 5, WND4_NOT_SUPP_DOWNGRADE = 6, WND4_CANCELLED = 7, WND4_IS_DIR = 8 }; union open_none_delegation4 /* new to v4.1 */ switch (why_no_delegation4 ond_why) { case WND4_CONTENTION: bool ond_server_will_push_deleg; case WND4_RESOURCE: bool ond_server_will_signal_avail; default: void; }; union open_delegation4 switch (open_delegation_type4 delegation_type) { case OPEN_DELEGATE_NONE: void; case OPEN_DELEGATE_READ: open_read_delegation4 read; case OPEN_DELEGATE_WRITE: open_write_delegation4 write; case OPEN_DELEGATE_NONE_EXT: /* new to v4.1 */ open_none_delegation4 od_whynone; }; /* * Result flags */ /* Client must confirm open */ const OPEN4_RESULT_CONFIRM = 0x00000002; /* Type of file locking behavior at the server */ const OPEN4_RESULT_LOCKTYPE_POSIX = 0x00000004; /* Server will preserve file if removed while open */ const OPEN4_RESULT_PRESERVE_UNLINKED = 0x00000008; /* * Server may use CB_NOTIFY_LOCK on locks * derived from this open */ const OPEN4_RESULT_MAY_NOTIFY_LOCK = 0x00000020; struct OPEN4resok { stateid4 stateid; /* Stateid for open */ change_info4 cinfo; /* Directory Change Info */ uint32_t rflags; /* Result flags */ bitmap4 attrset; /* attribute set for create*/ open_delegation4 delegation; /* Info on any open delegation */ }; union OPEN4res switch (nfsstat4 status) { case NFS4_OK: /* New CURRENT_FH: opened file */ OPEN4resok resok4; default: void; }; struct OPENATTR4args { /* CURRENT_FH: object */ bool createdir; }; struct OPENATTR4res { /* * If status is NFS4_OK, * new CURRENT_FH: named attribute * directory */ nfsstat4 status; }; /* obsolete in NFSv4.1 */ struct OPEN_CONFIRM4args { /* CURRENT_FH: opened file */ stateid4 open_stateid; seqid4 seqid; }; struct OPEN_CONFIRM4resok { stateid4 open_stateid; }; union OPEN_CONFIRM4res switch (nfsstat4 status) { case NFS4_OK: OPEN_CONFIRM4resok resok4; default: void; }; struct OPEN_DOWNGRADE4args { /* CURRENT_FH: opened file */ stateid4 open_stateid; seqid4 seqid; uint32_t share_access; uint32_t share_deny; }; struct OPEN_DOWNGRADE4resok { stateid4 open_stateid; }; union OPEN_DOWNGRADE4res switch(nfsstat4 status) { case NFS4_OK: OPEN_DOWNGRADE4resok resok4; default: void; }; struct PUTFH4args { nfs_fh4 object; }; struct PUTFH4res { /* * If status is NFS4_OK, * new CURRENT_FH: argument to PUTFH */ nfsstat4 status; }; struct PUTPUBFH4res { /* * If status is NFS4_OK, * new CURRENT_FH: public fh */ nfsstat4 status; }; struct PUTROOTFH4res { /* * If status is NFS4_OK, * new CURRENT_FH: root fh */ nfsstat4 status; }; struct READ4args { /* CURRENT_FH: file */ stateid4 stateid; offset4 offset; count4 count; }; struct READ4resok { bool eof; opaque data<>; }; union READ4res switch (nfsstat4 status) { case NFS4_OK: READ4resok resok4; default: void; }; struct READDIR4args { /* CURRENT_FH: directory */ nfs_cookie4 cookie; verifier4 cookieverf; count4 dircount; count4 maxcount; bitmap4 attr_request; }; struct entry4 { nfs_cookie4 cookie; component4 name; fattr4 attrs; entry4 *nextentry; }; struct dirlist4 { entry4 *entries; bool eof; }; struct READDIR4resok { verifier4 cookieverf; dirlist4 reply; }; union READDIR4res switch (nfsstat4 status) { case NFS4_OK: READDIR4resok resok4; default: void; }; struct READLINK4resok { linktext4 link; }; union READLINK4res switch (nfsstat4 status) { case NFS4_OK: READLINK4resok resok4; default: void; }; struct REMOVE4args { /* CURRENT_FH: directory */ component4 target; }; struct REMOVE4resok { change_info4 cinfo; }; union REMOVE4res switch (nfsstat4 status) { case NFS4_OK: REMOVE4resok resok4; default: void; }; struct RENAME4args { /* SAVED_FH: source directory */ component4 oldname; /* CURRENT_FH: target directory */ component4 newname; }; struct RENAME4resok { change_info4 source_cinfo; change_info4 target_cinfo; }; union RENAME4res switch (nfsstat4 status) { case NFS4_OK: RENAME4resok resok4; default: void; }; /* Obsolete in NFSv4.1 */ struct RENEW4args { clientid4 clientid; }; struct RENEW4res { nfsstat4 status; }; struct RESTOREFH4res { /* * If status is NFS4_OK, * new CURRENT_FH: value of saved fh */ nfsstat4 status; }; struct SAVEFH4res { /* * If status is NFS4_OK, * new SAVED_FH: value of current fh */ nfsstat4 status; }; struct SECINFO4args { /* CURRENT_FH: directory */ component4 name; }; /* * From RFC 2203 */ enum rpc_gss_svc_t { RPC_GSS_SVC_NONE = 1, RPC_GSS_SVC_INTEGRITY = 2, RPC_GSS_SVC_PRIVACY = 3 }; struct rpcsec_gss_info { sec_oid4 oid; qop4 qop; rpc_gss_svc_t service; }; /* RPCSEC_GSS has a value of '6' - See RFC 2203 */ union secinfo4 switch (uint32_t flavor) { case RPCSEC_GSS: rpcsec_gss_info flavor_info; default: void; }; typedef secinfo4 SECINFO4resok<>; union SECINFO4res switch (nfsstat4 status) { case NFS4_OK: /* CURRENTFH: consumed */ SECINFO4resok resok4; default: void; }; struct SETATTR4args { /* CURRENT_FH: target object */ stateid4 stateid; fattr4 obj_attributes; }; struct SETATTR4res { nfsstat4 status; bitmap4 attrsset; }; /* Obsolete in NFSv4.1 */ struct SETCLIENTID4args { nfs_client_id4 client; cb_client4 callback; uint32_t callback_ident; }; struct SETCLIENTID4resok { clientid4 clientid; verifier4 setclientid_confirm; }; union SETCLIENTID4res switch (nfsstat4 status) { case NFS4_OK: SETCLIENTID4resok resok4; case NFS4ERR_CLID_INUSE: clientaddr4 client_using; default: void; }; /* Obsolete in NFSv4.1 */ struct SETCLIENTID_CONFIRM4args { clientid4 clientid; verifier4 setclientid_confirm; }; struct SETCLIENTID_CONFIRM4res { nfsstat4 status; }; struct VERIFY4args { /* CURRENT_FH: object */ fattr4 obj_attributes; }; struct VERIFY4res { nfsstat4 status; }; enum stable_how4 { UNSTABLE4 = 0, DATA_SYNC4 = 1, FILE_SYNC4 = 2 }; struct WRITE4args { /* CURRENT_FH: file */ stateid4 stateid; offset4 offset; stable_how4 stable; opaque data<>; }; struct WRITE4resok { count4 count; stable_how4 committed; verifier4 writeverf; }; union WRITE4res switch (nfsstat4 status) { case NFS4_OK: WRITE4resok resok4; default: void; }; /* Obsolete in NFSv4.1 */ struct RELEASE_LOCKOWNER4args { lock_owner4 lock_owner; }; struct RELEASE_LOCKOWNER4res { nfsstat4 status; }; struct ILLEGAL4res { nfsstat4 status; }; typedef opaque gsshandle4_t<>; struct gss_cb_handles4 { rpc_gss_svc_t gcbp_service; /* RFC 2203 */ gsshandle4_t gcbp_handle_from_server; gsshandle4_t gcbp_handle_from_client; }; union callback_sec_parms4 switch (uint32_t cb_secflavor) { case AUTH_NONE: void; case AUTH_SYS: authsys_parms cbsp_sys_cred; /* RFC 1831 */ case RPCSEC_GSS: gss_cb_handles4 cbsp_gss_handles; }; struct BACKCHANNEL_CTL4args { uint32_t bca_cb_program; callback_sec_parms4 bca_sec_parms<>; }; struct BACKCHANNEL_CTL4res { nfsstat4 bcr_status; }; enum channel_dir_from_client4 { CDFC4_FORE = 0x1, CDFC4_BACK = 0x2, CDFC4_FORE_OR_BOTH = 0x3, CDFC4_BACK_OR_BOTH = 0x7 }; struct BIND_CONN_TO_SESSION4args { sessionid4 bctsa_sessid; channel_dir_from_client4 bctsa_dir; bool bctsa_use_conn_in_rdma_mode; }; enum channel_dir_from_server4 { CDFS4_FORE = 0x1, CDFS4_BACK = 0x2, CDFS4_BOTH = 0x3 }; struct BIND_CONN_TO_SESSION4resok { sessionid4 bctsr_sessid; channel_dir_from_server4 bctsr_dir; bool bctsr_use_conn_in_rdma_mode; }; union BIND_CONN_TO_SESSION4res switch (nfsstat4 bctsr_status) { case NFS4_OK: BIND_CONN_TO_SESSION4resok bctsr_resok4; default: void; }; const EXCHGID4_FLAG_SUPP_MOVED_REFER = 0x00000001; const EXCHGID4_FLAG_SUPP_MOVED_MIGR = 0x00000002; const EXCHGID4_FLAG_BIND_PRINC_STATEID = 0x00000100; const EXCHGID4_FLAG_USE_NON_PNFS = 0x00010000; const EXCHGID4_FLAG_USE_PNFS_MDS = 0x00020000; const EXCHGID4_FLAG_USE_PNFS_DS = 0x00040000; const EXCHGID4_FLAG_MASK_PNFS = 0x00070000; const EXCHGID4_FLAG_UPD_CONFIRMED_REC_A = 0x40000000; const EXCHGID4_FLAG_CONFIRMED_R = 0x80000000; struct state_protect_ops4 { bitmap4 spo_must_enforce; bitmap4 spo_must_allow; }; struct ssv_sp_parms4 { state_protect_ops4 ssp_ops; sec_oid4 ssp_hash_algs<>; sec_oid4 ssp_encr_algs<>; uint32_t ssp_window; uint32_t ssp_num_gss_handles; }; enum state_protect_how4 { SP4_NONE = 0, SP4_MACH_CRED = 1, SP4_SSV = 2 }; union state_protect4_a switch(state_protect_how4 spa_how) { case SP4_NONE: void; case SP4_MACH_CRED: state_protect_ops4 spa_mach_ops; case SP4_SSV: ssv_sp_parms4 spa_ssv_parms; }; struct EXCHANGE_ID4args { client_owner4 eia_clientowner; uint32_t eia_flags; state_protect4_a eia_state_protect; nfs_impl_id4 eia_client_impl_id<1>; }; struct ssv_prot_info4 { state_protect_ops4 spi_ops; uint32_t spi_hash_alg; uint32_t spi_encr_alg; uint32_t spi_ssv_len; uint32_t spi_window; gsshandle4_t spi_handles<>; }; union state_protect4_r switch(state_protect_how4 spr_how) { case SP4_NONE: void; case SP4_MACH_CRED: state_protect_ops4 spr_mach_ops; case SP4_SSV: ssv_prot_info4 spr_ssv_info; }; struct EXCHANGE_ID4resok { clientid4 eir_clientid; sequenceid4 eir_sequenceid; uint32_t eir_flags; state_protect4_r eir_state_protect; server_owner4 eir_server_owner; opaque eir_server_scope; nfs_impl_id4 eir_server_impl_id<1>; }; union EXCHANGE_ID4res switch (nfsstat4 eir_status) { case NFS4_OK: EXCHANGE_ID4resok eir_resok4; default: void; }; struct channel_attrs4 { count4 ca_headerpadsize; count4 ca_maxrequestsize; count4 ca_maxresponsesize; count4 ca_maxresponsesize_cached; count4 ca_maxoperations; count4 ca_maxrequests; uint32_t ca_rdma_ird<1>; }; const CREATE_SESSION4_FLAG_PERSIST = 0x00000001; const CREATE_SESSION4_FLAG_CONN_BACK_CHAN = 0x00000002; const CREATE_SESSION4_FLAG_CONN_RDMA = 0x00000004; struct CREATE_SESSION4args { clientid4 csa_clientid; sequenceid4 csa_sequence; uint32_t csa_flags; channel_attrs4 csa_fore_chan_attrs; channel_attrs4 csa_back_chan_attrs; uint32_t csa_cb_program; callback_sec_parms4 csa_sec_parms<>; }; struct CREATE_SESSION4resok { sessionid4 csr_sessionid; sequenceid4 csr_sequence; uint32_t csr_flags; channel_attrs4 csr_fore_chan_attrs; channel_attrs4 csr_back_chan_attrs; }; union CREATE_SESSION4res switch (nfsstat4 csr_status) { case NFS4_OK: CREATE_SESSION4resok csr_resok4; default: void; }; struct DESTROY_SESSION4args { sessionid4 dsa_sessionid; }; struct DESTROY_SESSION4res { nfsstat4 dsr_status; }; struct FREE_STATEID4args { stateid4 fsa_stateid; }; struct FREE_STATEID4res { nfsstat4 fsr_status; }; typedef nfstime4 attr_notice4; struct GET_DIR_DELEGATION4args { /* CURRENT_FH: delegated directory */ bool gdda_signal_deleg_avail; bitmap4 gdda_notification_types; attr_notice4 gdda_child_attr_delay; attr_notice4 gdda_dir_attr_delay; bitmap4 gdda_child_attributes; bitmap4 gdda_dir_attributes; }; struct GET_DIR_DELEGATION4resok { verifier4 gddr_cookieverf; /* Stateid for get_dir_delegation */ stateid4 gddr_stateid; /* Which notifications can the server support */ bitmap4 gddr_notification; bitmap4 gddr_child_attributes; bitmap4 gddr_dir_attributes; }; enum gddrnf4_status { GDD4_OK = 0, GDD4_UNAVAIL = 1 }; union GET_DIR_DELEGATION4res_non_fatal switch (gddrnf4_status gddrnf_status) { case GDD4_OK: GET_DIR_DELEGATION4resok gddrnf_resok4; case GDD4_UNAVAIL: bool gddrnf_will_signal_deleg_avail; }; union GET_DIR_DELEGATION4res switch (nfsstat4 gddr_status) { case NFS4_OK: GET_DIR_DELEGATION4res_non_fatal gddr_res_non_fatal4; default: void; }; struct GETDEVICEINFO4args { deviceid4 gdia_device_id; layouttype4 gdia_layout_type; count4 gdia_maxcount; bitmap4 gdia_notify_types; }; struct GETDEVICEINFO4resok { device_addr4 gdir_device_addr; bitmap4 gdir_notification; }; union GETDEVICEINFO4res switch (nfsstat4 gdir_status) { case NFS4_OK: GETDEVICEINFO4resok gdir_resok4; case NFS4ERR_TOOSMALL: count4 gdir_mincount; default: void; }; struct GETDEVICELIST4args { /* CURRENT_FH: object belonging to the file system */ layouttype4 gdla_layout_type; /* number of deviceIDs to return */ count4 gdla_maxdevices; nfs_cookie4 gdla_cookie; verifier4 gdla_cookieverf; }; struct GETDEVICELIST4resok { nfs_cookie4 gdlr_cookie; verifier4 gdlr_cookieverf; deviceid4 gdlr_deviceid_list<>; bool gdlr_eof; }; union GETDEVICELIST4res switch (nfsstat4 gdlr_status) { case NFS4_OK: GETDEVICELIST4resok gdlr_resok4; default: void; }; union newtime4 switch (bool nt_timechanged) { case TRUE: nfstime4 nt_time; case FALSE: void; }; union newoffset4 switch (bool no_newoffset) { case TRUE: offset4 no_offset; case FALSE: void; }; struct LAYOUTCOMMIT4args { /* CURRENT_FH: file */ offset4 loca_offset; length4 loca_length; bool loca_reclaim; stateid4 loca_stateid; newoffset4 loca_last_write_offset; newtime4 loca_time_modify; layoutupdate4 loca_layoutupdate; }; union newsize4 switch (bool ns_sizechanged) { case TRUE: length4 ns_size; case FALSE: void; }; struct LAYOUTCOMMIT4resok { newsize4 locr_newsize; }; union LAYOUTCOMMIT4res switch (nfsstat4 locr_status) { case NFS4_OK: LAYOUTCOMMIT4resok locr_resok4; default: void; }; struct LAYOUTGET4args { /* CURRENT_FH: file */ bool loga_signal_layout_avail; layouttype4 loga_layout_type; layoutiomode4 loga_iomode; offset4 loga_offset; length4 loga_length; length4 loga_minlength; stateid4 loga_stateid; count4 loga_maxcount; }; struct LAYOUTGET4resok { bool logr_return_on_close; stateid4 logr_stateid; layout4 logr_layout<>; }; union LAYOUTGET4res switch (nfsstat4 logr_status) { case NFS4_OK: LAYOUTGET4resok logr_resok4; case NFS4ERR_LAYOUTTRYLATER: bool logr_will_signal_layout_avail; default: void; }; struct LAYOUTRETURN4args { /* CURRENT_FH: file */ bool lora_reclaim; layouttype4 lora_layout_type; layoutiomode4 lora_iomode; layoutreturn4 lora_layoutreturn; }; union layoutreturn_stateid switch (bool lrs_present) { case TRUE: stateid4 lrs_stateid; case FALSE: void; }; union LAYOUTRETURN4res switch (nfsstat4 lorr_status) { case NFS4_OK: layoutreturn_stateid lorr_stateid; default: void; }; enum secinfo_style4 { SECINFO_STYLE4_CURRENT_FH = 0, SECINFO_STYLE4_PARENT = 1 }; /* CURRENT_FH: object or child directory */ typedef secinfo_style4 SECINFO_NO_NAME4args; /* CURRENTFH: consumed if status is NFS4_OK */ typedef SECINFO4res SECINFO_NO_NAME4res; struct SEQUENCE4args { sessionid4 sa_sessionid; sequenceid4 sa_sequenceid; slotid4 sa_slotid; slotid4 sa_highest_slotid; bool sa_cachethis; }; const SEQ4_STATUS_CB_PATH_DOWN = 0x00000001; const SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRING = 0x00000002; const SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRED = 0x00000004; const SEQ4_STATUS_EXPIRED_ALL_STATE_REVOKED = 0x00000008; const SEQ4_STATUS_EXPIRED_SOME_STATE_REVOKED = 0x00000010; const SEQ4_STATUS_ADMIN_STATE_REVOKED = 0x00000020; const SEQ4_STATUS_RECALLABLE_STATE_REVOKED = 0x00000040; const SEQ4_STATUS_LEASE_MOVED = 0x00000080; const SEQ4_STATUS_RESTART_RECLAIM_NEEDED = 0x00000100; const SEQ4_STATUS_CB_PATH_DOWN_SESSION = 0x00000200; const SEQ4_STATUS_BACKCHANNEL_FAULT = 0x00000400; const SEQ4_STATUS_DEVID_CHANGED = 0x00000800; const SEQ4_STATUS_DEVID_DELETED = 0x00001000; struct SEQUENCE4resok { sessionid4 sr_sessionid; sequenceid4 sr_sequenceid; slotid4 sr_slotid; slotid4 sr_highest_slotid; slotid4 sr_target_highest_slotid; uint32_t sr_status_flags; }; union SEQUENCE4res switch (nfsstat4 sr_status) { case NFS4_OK: SEQUENCE4resok sr_resok4; default: void; }; struct ssa_digest_input4 { SEQUENCE4args sdi_seqargs; }; struct SET_SSV4args { opaque ssa_ssv<>; opaque ssa_digest<>; }; struct ssr_digest_input4 { SEQUENCE4res sdi_seqres; }; struct SET_SSV4resok { opaque ssr_digest<>; }; union SET_SSV4res switch (nfsstat4 ssr_status) { case NFS4_OK: SET_SSV4resok ssr_resok4; default: void; }; struct TEST_STATEID4args { stateid4 ts_stateids<>; }; struct TEST_STATEID4resok { nfsstat4 tsr_status_codes<>; }; union TEST_STATEID4res switch (nfsstat4 tsr_status) { case NFS4_OK: TEST_STATEID4resok tsr_resok4; default: void; }; union deleg_claim4 switch (open_claim_type4 dc_claim) { /* * No special rights to object. Ordinary delegation * request of the specified object. Object identified * by filehandle. */ case CLAIM_FH: /* new to v4.1 */ /* CURRENT_FH: object being delegated */ void; /* * Right to file based on a delegation granted * to a previous boot instance of the client. * File is specified by filehandle. */ case CLAIM_DELEG_PREV_FH: /* new to v4.1 */ /* CURRENT_FH: object being delegated */ void; /* * Right to the file established by an open previous * to server reboot. File identified by filehandle. * Used during server reclaim grace period. */ case CLAIM_PREVIOUS: /* CURRENT_FH: object being reclaimed */ open_delegation_type4 dc_delegate_type; }; struct WANT_DELEGATION4args { uint32_t wda_want; deleg_claim4 wda_claim; }; union WANT_DELEGATION4res switch (nfsstat4 wdr_status) { case NFS4_OK: open_delegation4 wdr_resok4; default: void; }; struct DESTROY_CLIENTID4args { clientid4 dca_clientid; }; struct DESTROY_CLIENTID4res { nfsstat4 dcr_status; }; struct RECLAIM_COMPLETE4args { /* * If rca_one_fs TRUE, * * CURRENT_FH: object in * filesystem reclaim is * complete for. */ bool rca_one_fs; }; struct RECLAIM_COMPLETE4res { nfsstat4 rcr_status; }; /* * Operation arrays */ enum nfs_opnum4 { OP_ACCESS = 3, OP_CLOSE = 4, OP_COMMIT = 5, OP_CREATE = 6, OP_DELEGPURGE = 7, OP_DELEGRETURN = 8, OP_GETATTR = 9, OP_GETFH = 10, OP_LINK = 11, OP_LOCK = 12, OP_LOCKT = 13, OP_LOCKU = 14, OP_LOOKUP = 15, OP_LOOKUPP = 16, OP_NVERIFY = 17, OP_OPEN = 18, OP_OPENATTR = 19, OP_OPEN_CONFIRM = 20, /* Mandatory not-to-implement */ OP_OPEN_DOWNGRADE = 21, OP_PUTFH = 22, OP_PUTPUBFH = 23, OP_PUTROOTFH = 24, OP_READ = 25, OP_READDIR = 26, OP_READLINK = 27, OP_REMOVE = 28, OP_RENAME = 29, OP_RENEW = 30, /* Mandatory not-to-implement */ OP_RESTOREFH = 31, OP_SAVEFH = 32, OP_SECINFO = 33, OP_SETATTR = 34, OP_SETCLIENTID = 35, /* Mandatory not-to-implement */ OP_SETCLIENTID_CONFIRM = 36, /* Mandatory not-to-implement */ OP_VERIFY = 37, OP_WRITE = 38, OP_RELEASE_LOCKOWNER = 39, /* Mandatory not-to-implement */ % %/* new operations for NFSv4.1 */ % OP_BACKCHANNEL_CTL = 40, OP_BIND_CONN_TO_SESSION = 41, OP_EXCHANGE_ID = 42, OP_CREATE_SESSION = 43, OP_DESTROY_SESSION = 44, OP_FREE_STATEID = 45, OP_GET_DIR_DELEGATION = 46, OP_GETDEVICEINFO = 47, OP_GETDEVICELIST = 48, OP_LAYOUTCOMMIT = 49, OP_LAYOUTGET = 50, OP_LAYOUTRETURN = 51, OP_SECINFO_NO_NAME = 52, OP_SEQUENCE = 53, OP_SET_SSV = 54, OP_TEST_STATEID = 55, OP_WANT_DELEGATION = 56, OP_DESTROY_CLIENTID = 57, OP_RECLAIM_COMPLETE = 58, OP_ILLEGAL = 10044 }; union nfs_argop4 switch (nfs_opnum4 argop) { case OP_ACCESS: ACCESS4args opaccess; case OP_CLOSE: CLOSE4args opclose; case OP_COMMIT: COMMIT4args opcommit; case OP_CREATE: CREATE4args opcreate; case OP_DELEGPURGE: DELEGPURGE4args opdelegpurge; case OP_DELEGRETURN: DELEGRETURN4args opdelegreturn; case OP_GETATTR: GETATTR4args opgetattr; case OP_GETFH: void; case OP_LINK: LINK4args oplink; case OP_LOCK: LOCK4args oplock; case OP_LOCKT: LOCKT4args oplockt; case OP_LOCKU: LOCKU4args oplocku; case OP_LOOKUP: LOOKUP4args oplookup; case OP_LOOKUPP: void; case OP_NVERIFY: NVERIFY4args opnverify; case OP_OPEN: OPEN4args opopen; case OP_OPENATTR: OPENATTR4args opopenattr; /* Not for NFSv4.1 */ case OP_OPEN_CONFIRM: OPEN_CONFIRM4args opopen_confirm; case OP_OPEN_DOWNGRADE: OPEN_DOWNGRADE4args opopen_downgrade; case OP_PUTFH: PUTFH4args opputfh; case OP_PUTPUBFH: void; case OP_PUTROOTFH: void; case OP_READ: READ4args opread; case OP_READDIR: READDIR4args opreaddir; case OP_READLINK: void; case OP_REMOVE: REMOVE4args opremove; case OP_RENAME: RENAME4args oprename; /* Not for NFSv4.1 */ case OP_RENEW: RENEW4args oprenew; case OP_RESTOREFH: void; case OP_SAVEFH: void; case OP_SECINFO: SECINFO4args opsecinfo; case OP_SETATTR: SETATTR4args opsetattr; /* Not for NFSv4.1 */ case OP_SETCLIENTID: SETCLIENTID4args opsetclientid; /* Not for NFSv4.1 */ case OP_SETCLIENTID_CONFIRM: SETCLIENTID_CONFIRM4args opsetclientid_confirm; case OP_VERIFY: VERIFY4args opverify; case OP_WRITE: WRITE4args opwrite; /* Not for NFSv4.1 */ case OP_RELEASE_LOCKOWNER: RELEASE_LOCKOWNER4args oprelease_lockowner; /* Operations new to NFSv4.1 */ case OP_BACKCHANNEL_CTL: BACKCHANNEL_CTL4args opbackchannel_ctl; case OP_BIND_CONN_TO_SESSION: BIND_CONN_TO_SESSION4args opbind_conn_to_session; case OP_EXCHANGE_ID: EXCHANGE_ID4args opexchange_id; case OP_CREATE_SESSION: CREATE_SESSION4args opcreate_session; case OP_DESTROY_SESSION: DESTROY_SESSION4args opdestroy_session; case OP_FREE_STATEID: FREE_STATEID4args opfree_stateid; case OP_GET_DIR_DELEGATION: GET_DIR_DELEGATION4args opget_dir_delegation; case OP_GETDEVICEINFO: GETDEVICEINFO4args opgetdeviceinfo; case OP_GETDEVICELIST: GETDEVICELIST4args opgetdevicelist; case OP_LAYOUTCOMMIT: LAYOUTCOMMIT4args oplayoutcommit; case OP_LAYOUTGET: LAYOUTGET4args oplayoutget; case OP_LAYOUTRETURN: LAYOUTRETURN4args oplayoutreturn; case OP_SECINFO_NO_NAME: SECINFO_NO_NAME4args opsecinfo_no_name; case OP_SEQUENCE: SEQUENCE4args opsequence; case OP_SET_SSV: SET_SSV4args opset_ssv; case OP_TEST_STATEID: TEST_STATEID4args optest_stateid; case OP_WANT_DELEGATION: WANT_DELEGATION4args opwant_delegation; case OP_DESTROY_CLIENTID: DESTROY_CLIENTID4args opdestroy_clientid; case OP_RECLAIM_COMPLETE: RECLAIM_COMPLETE4args opreclaim_complete; /* Operations not new to NFSv4.1 */ case OP_ILLEGAL: void; }; union nfs_resop4 switch (nfs_opnum4 resop) { case OP_ACCESS: ACCESS4res opaccess; case OP_CLOSE: CLOSE4res opclose; case OP_COMMIT: COMMIT4res opcommit; case OP_CREATE: CREATE4res opcreate; case OP_DELEGPURGE: DELEGPURGE4res opdelegpurge; case OP_DELEGRETURN: DELEGRETURN4res opdelegreturn; case OP_GETATTR: GETATTR4res opgetattr; case OP_GETFH: GETFH4res opgetfh; case OP_LINK: LINK4res oplink; case OP_LOCK: LOCK4res oplock; case OP_LOCKT: LOCKT4res oplockt; case OP_LOCKU: LOCKU4res oplocku; case OP_LOOKUP: LOOKUP4res oplookup; case OP_LOOKUPP: LOOKUPP4res oplookupp; case OP_NVERIFY: NVERIFY4res opnverify; case OP_OPEN: OPEN4res opopen; case OP_OPENATTR: OPENATTR4res opopenattr; /* Not for NFSv4.1 */ case OP_OPEN_CONFIRM: OPEN_CONFIRM4res opopen_confirm; case OP_OPEN_DOWNGRADE: OPEN_DOWNGRADE4res opopen_downgrade; case OP_PUTFH: PUTFH4res opputfh; case OP_PUTPUBFH: PUTPUBFH4res opputpubfh; case OP_PUTROOTFH: PUTROOTFH4res opputrootfh; case OP_READ: READ4res opread; case OP_READDIR: READDIR4res opreaddir; case OP_READLINK: READLINK4res opreadlink; case OP_REMOVE: REMOVE4res opremove; case OP_RENAME: RENAME4res oprename; /* Not for NFSv4.1 */ case OP_RENEW: RENEW4res oprenew; case OP_RESTOREFH: RESTOREFH4res oprestorefh; case OP_SAVEFH: SAVEFH4res opsavefh; case OP_SECINFO: SECINFO4res opsecinfo; case OP_SETATTR: SETATTR4res opsetattr; /* Not for NFSv4.1 */ case OP_SETCLIENTID: SETCLIENTID4res opsetclientid; /* Not for NFSv4.1 */ case OP_SETCLIENTID_CONFIRM: SETCLIENTID_CONFIRM4res opsetclientid_confirm; case OP_VERIFY: VERIFY4res opverify; case OP_WRITE: WRITE4res opwrite; /* Not for NFSv4.1 */ case OP_RELEASE_LOCKOWNER: RELEASE_LOCKOWNER4res oprelease_lockowner; /* Operations new to NFSv4.1 */ case OP_BACKCHANNEL_CTL: BACKCHANNEL_CTL4res opbackchannel_ctl; case OP_BIND_CONN_TO_SESSION: BIND_CONN_TO_SESSION4res opbind_conn_to_session; case OP_EXCHANGE_ID: EXCHANGE_ID4res opexchange_id; case OP_CREATE_SESSION: CREATE_SESSION4res opcreate_session; case OP_DESTROY_SESSION: DESTROY_SESSION4res opdestroy_session; case OP_FREE_STATEID: FREE_STATEID4res opfree_stateid; case OP_GET_DIR_DELEGATION: GET_DIR_DELEGATION4res opget_dir_delegation; case OP_GETDEVICEINFO: GETDEVICEINFO4res opgetdeviceinfo; case OP_GETDEVICELIST: GETDEVICELIST4res opgetdevicelist; case OP_LAYOUTCOMMIT: LAYOUTCOMMIT4res oplayoutcommit; case OP_LAYOUTGET: LAYOUTGET4res oplayoutget; case OP_LAYOUTRETURN: LAYOUTRETURN4res oplayoutreturn; case OP_SECINFO_NO_NAME: SECINFO_NO_NAME4res opsecinfo_no_name; case OP_SEQUENCE: SEQUENCE4res opsequence; case OP_SET_SSV: SET_SSV4res opset_ssv; case OP_TEST_STATEID: TEST_STATEID4res optest_stateid; case OP_WANT_DELEGATION: WANT_DELEGATION4res opwant_delegation; case OP_DESTROY_CLIENTID: DESTROY_CLIENTID4res opdestroy_clientid; case OP_RECLAIM_COMPLETE: RECLAIM_COMPLETE4res opreclaim_complete; /* Operations not new to NFSv4.1 */ case OP_ILLEGAL: ILLEGAL4res opillegal; }; struct COMPOUND4args { utf8str_cs tag; uint32_t minorversion; nfs_argop4 argarray<>; }; struct COMPOUND4res { nfsstat4 status; utf8str_cs tag; nfs_resop4 resarray<>; }; /* * Remote file service routines */ program NFS4_PROGRAM { version NFS_V4 { void NFSPROC4_NULL(void) = 0; COMPOUND4res NFSPROC4_COMPOUND(COMPOUND4args) = 1; } = 4; } = 100003; /* * NFS4 Callback Procedure Definitions and Program */ struct CB_GETATTR4args { nfs_fh4 fh; bitmap4 attr_request; }; struct CB_GETATTR4resok { fattr4 obj_attributes; }; union CB_GETATTR4res switch (nfsstat4 status) { case NFS4_OK: CB_GETATTR4resok resok4; default: void; }; struct CB_RECALL4args { stateid4 stateid; bool truncate; nfs_fh4 fh; }; struct CB_RECALL4res { nfsstat4 status; }; /* * CB_ILLEGAL: Response for illegal operation numbers */ struct CB_ILLEGAL4res { nfsstat4 status; }; /* * NFSv4.1 callback arguments and results */ enum layoutrecall_type4 { LAYOUTRECALL4_FILE = LAYOUT4_RET_REC_FILE, LAYOUTRECALL4_FSID = LAYOUT4_RET_REC_FSID, LAYOUTRECALL4_ALL = LAYOUT4_RET_REC_ALL }; struct layoutrecall_file4 { nfs_fh4 lor_fh; offset4 lor_offset; length4 lor_length; stateid4 lor_stateid; }; union layoutrecall4 switch(layoutrecall_type4 lor_recalltype) { case LAYOUTRECALL4_FILE: layoutrecall_file4 lor_layout; case LAYOUTRECALL4_FSID: fsid4 lor_fsid; case LAYOUTRECALL4_ALL: void; }; struct CB_LAYOUTRECALL4args { layouttype4 clora_type; layoutiomode4 clora_iomode; bool clora_changed; layoutrecall4 clora_recall; }; struct CB_LAYOUTRECALL4res { nfsstat4 clorr_status; }; /* * Directory notification types. */ enum notify_type4 { NOTIFY4_CHANGE_CHILD_ATTRS = 0, NOTIFY4_CHANGE_DIR_ATTRS = 1, NOTIFY4_REMOVE_ENTRY = 2, NOTIFY4_ADD_ENTRY = 3, NOTIFY4_RENAME_ENTRY = 4, NOTIFY4_CHANGE_COOKIE_VERIFIER = 5 }; /* Changed entry information. */ struct notify_entry4 { component4 ne_file; fattr4 ne_attrs; }; /* Previous entry information */ struct prev_entry4 { notify_entry4 pe_prev_entry; /* what READDIR returned for this entry */ nfs_cookie4 pe_prev_entry_cookie; }; struct notify_remove4 { notify_entry4 nrm_old_entry; nfs_cookie4 nrm_old_entry_cookie; }; struct notify_add4 { /* * Information on object * possibly renamed over. */ notify_remove4 nad_old_entry<1>; notify_entry4 nad_new_entry; /* what READDIR would have returned for this entry */ nfs_cookie4 nad_new_entry_cookie<1>; prev_entry4 nad_prev_entry<1>; bool nad_last_entry; }; struct notify_attr4 { notify_entry4 na_changed_entry; }; struct notify_rename4 { notify_remove4 nrn_old_entry; notify_add4 nrn_new_entry; }; struct notify_verifier4 { verifier4 nv_old_cookieverf; verifier4 nv_new_cookieverf; }; /* * Objects of type notify_<>4 and * notify_device_<>4 are encoded in this. */ typedef opaque notifylist4<>; struct notify4 { /* composed from notify_type4 or notify_deviceid_type4 */ bitmap4 notify_mask; notifylist4 notify_vals; }; struct CB_NOTIFY4args { stateid4 cna_stateid; nfs_fh4 cna_fh; notify4 cna_changes<>; }; struct CB_NOTIFY4res { nfsstat4 cnr_status; }; struct CB_PUSH_DELEG4args { nfs_fh4 cpda_fh; open_delegation4 cpda_delegation; }; struct CB_PUSH_DELEG4res { nfsstat4 cpdr_status; }; const RCA4_TYPE_MASK_RDATA_DLG = 0; const RCA4_TYPE_MASK_WDATA_DLG = 1; const RCA4_TYPE_MASK_DIR_DLG = 2; const RCA4_TYPE_MASK_FILE_LAYOUT = 3; const RCA4_TYPE_MASK_BLK_LAYOUT = 4; const RCA4_TYPE_MASK_OBJ_LAYOUT_MIN = 8; const RCA4_TYPE_MASK_OBJ_LAYOUT_MAX = 9; const RCA4_TYPE_MASK_OTHER_LAYOUT_MIN = 12; const RCA4_TYPE_MASK_OTHER_LAYOUT_MAX = 15; struct CB_RECALL_ANY4args { uint32_t craa_objects_to_keep; bitmap4 craa_type_mask; }; struct CB_RECALL_ANY4res { nfsstat4 crar_status; }; typedef CB_RECALL_ANY4args CB_RECALLABLE_OBJ_AVAIL4args; struct CB_RECALLABLE_OBJ_AVAIL4res { nfsstat4 croa_status; }; struct CB_RECALL_SLOT4args { slotid4 rsa_target_highest_slotid; }; struct CB_RECALL_SLOT4res { nfsstat4 rsr_status; }; struct referring_call4 { sequenceid4 rc_sequenceid; slotid4 rc_slotid; }; struct referring_call_list4 { sessionid4 rcl_sessionid; referring_call4 rcl_referring_calls<>; }; struct CB_SEQUENCE4args { sessionid4 csa_sessionid; sequenceid4 csa_sequenceid; slotid4 csa_slotid; slotid4 csa_highest_slotid; bool csa_cachethis; referring_call_list4 csa_referring_call_lists<>; }; struct CB_SEQUENCE4resok { sessionid4 csr_sessionid; sequenceid4 csr_sequenceid; slotid4 csr_slotid; slotid4 csr_highest_slotid; slotid4 csr_target_highest_slotid; }; union CB_SEQUENCE4res switch (nfsstat4 csr_status) { case NFS4_OK: CB_SEQUENCE4resok csr_resok4; default: void; }; struct CB_WANTS_CANCELLED4args { bool cwca_contended_wants_cancelled; bool cwca_resourced_wants_cancelled; }; struct CB_WANTS_CANCELLED4res { nfsstat4 cwcr_status; }; struct CB_NOTIFY_LOCK4args { nfs_fh4 cnla_fh; lock_owner4 cnla_lock_owner; }; struct CB_NOTIFY_LOCK4res { nfsstat4 cnlr_status; }; /* * Device notification types. */ enum notify_deviceid_type4 { NOTIFY_DEVICEID4_CHANGE = 1, NOTIFY_DEVICEID4_DELETE = 2 }; /* For NOTIFY4_DEVICEID4_DELETE */ struct notify_deviceid_delete4 { layouttype4 ndd_layouttype; deviceid4 ndd_deviceid; }; /* For NOTIFY4_DEVICEID4_CHANGE */ struct notify_deviceid_change4 { layouttype4 ndc_layouttype; deviceid4 ndc_deviceid; bool ndc_immediate; }; struct CB_NOTIFY_DEVICEID4args { notify4 cnda_changes<>; }; struct CB_NOTIFY_DEVICEID4res { nfsstat4 cndr_status; }; /* * Various definitions for CB_COMPOUND */ % enum nfs_cb_opnum4 { OP_CB_GETATTR = 3, OP_CB_RECALL = 4, %/* Callback operations new to NFSv4.1 */ OP_CB_LAYOUTRECALL = 5, OP_CB_NOTIFY = 6, OP_CB_PUSH_DELEG = 7, OP_CB_RECALL_ANY = 8, OP_CB_RECALLABLE_OBJ_AVAIL = 9, OP_CB_RECALL_SLOT = 10, OP_CB_SEQUENCE = 11, OP_CB_WANTS_CANCELLED = 12, OP_CB_NOTIFY_LOCK = 13, OP_CB_NOTIFY_DEVICEID = 14, OP_CB_ILLEGAL = 10044 }; union nfs_cb_argop4 switch (unsigned argop) { case OP_CB_GETATTR: CB_GETATTR4args opcbgetattr; case OP_CB_RECALL: CB_RECALL4args opcbrecall; case OP_CB_LAYOUTRECALL: CB_LAYOUTRECALL4args opcblayoutrecall; case OP_CB_NOTIFY: CB_NOTIFY4args opcbnotify; case OP_CB_PUSH_DELEG: CB_PUSH_DELEG4args opcbpush_deleg; case OP_CB_RECALL_ANY: CB_RECALL_ANY4args opcbrecall_any; case OP_CB_RECALLABLE_OBJ_AVAIL: CB_RECALLABLE_OBJ_AVAIL4args opcbrecallable_obj_avail; case OP_CB_RECALL_SLOT: CB_RECALL_SLOT4args opcbrecall_slot; case OP_CB_SEQUENCE: CB_SEQUENCE4args opcbsequence; case OP_CB_WANTS_CANCELLED: CB_WANTS_CANCELLED4args opcbwants_cancelled; case OP_CB_NOTIFY_LOCK: CB_NOTIFY_LOCK4args opcbnotify_lock; case OP_CB_NOTIFY_DEVICEID: CB_NOTIFY_DEVICEID4args opcbnotify_deviceid; case OP_CB_ILLEGAL: void; }; union nfs_cb_resop4 switch (unsigned resop) { case OP_CB_GETATTR: CB_GETATTR4res opcbgetattr; case OP_CB_RECALL: CB_RECALL4res opcbrecall; /* new NFSv4.1 operations */ case OP_CB_LAYOUTRECALL: CB_LAYOUTRECALL4res opcblayoutrecall; case OP_CB_NOTIFY: CB_NOTIFY4res opcbnotify; case OP_CB_PUSH_DELEG: CB_PUSH_DELEG4res opcbpush_deleg; case OP_CB_RECALL_ANY: CB_RECALL_ANY4res opcbrecall_any; case OP_CB_RECALLABLE_OBJ_AVAIL: CB_RECALLABLE_OBJ_AVAIL4res opcbrecallable_obj_avail; case OP_CB_RECALL_SLOT: CB_RECALL_SLOT4res opcbrecall_slot; case OP_CB_SEQUENCE: CB_SEQUENCE4res opcbsequence; case OP_CB_WANTS_CANCELLED: CB_WANTS_CANCELLED4res opcbwants_cancelled; case OP_CB_NOTIFY_LOCK: CB_NOTIFY_LOCK4res opcbnotify_lock; case OP_CB_NOTIFY_DEVICEID: CB_NOTIFY_DEVICEID4res opcbnotify_deviceid; /* Not new operation */ case OP_CB_ILLEGAL: CB_ILLEGAL4res opcbillegal; }; struct CB_COMPOUND4args { utf8str_cs tag; uint32_t minorversion; uint32_t callback_ident; nfs_cb_argop4 argarray<>; }; struct CB_COMPOUND4res { nfsstat4 status; utf8str_cs tag; nfs_cb_resop4 resarray<>; }; /* * Program number is in the transient range since the client * will assign the exact transient program number and provide * that to the server via the SETCLIENTID operation. */ program NFS4_CALLBACK { version NFS_CB { void CB_NULL(void) = 0; CB_COMPOUND4res CB_COMPOUND(CB_COMPOUND4args) = 1; } = 1; } = 0x40000000; nfs-ganesha-6.5/src/Protocols/XDR/nlm4.x000066400000000000000000000074251473756622300200600ustar00rootroot00000000000000/* SPDX-License-Identifier: unknown license... */ /* The maximum length of the string identifying the caller. */ const LM_MAXSTRLEN = 1024; /* The maximum number of bytes in the nlm_notify name argument. */ const LM_MAXNAMELEN = 1025; const MAXNETOBJ_SZ = 1024; /* NSM related constants */ const SM_MAXSTRLEN = 1024; const SM_PRIV_SZ = 16; /* * Basic typedefs for RFC 1832 data type definitions */ typedef int int32_t; typedef unsigned int uint32_t; typedef hyper int64_t; typedef unsigned hyper uint64_t; enum nlm4_stats { NLM4_GRANTED = 0, NLM4_DENIED = 1, NLM4_DENIED_NOLOCKS = 2, NLM4_BLOCKED = 3, NLM4_DENIED_GRACE_PERIOD = 4, NLM4_DEADLCK = 5, NLM4_ROFS = 6, NLM4_STALE_FH = 7, NLM4_FBIG = 8, NLM4_FAILED = 9 }; struct nlm4_stat { nlm4_stats stat; }; struct nlm4_res { netobj cookie; nlm4_stat stat; }; struct nlm4_holder { bool exclusive; int32_t svid; netobj oh; uint64_t l_offset; uint64_t l_len; }; union nlm4_testrply switch (nlm4_stats stat) { case NLM4_DENIED: struct nlm4_holder holder; /* holder of the lock */ default: void; }; struct nlm4_testres { netobj cookie; nlm4_testrply test_stat; }; struct nlm4_lock { string caller_name; netobj fh; netobj oh; int32_t svid; uint64_t l_offset; uint64_t l_len; }; struct nlm4_lockargs { netobj cookie; bool block; /* Flag to indicate blocking behaviour. */ bool exclusive; /* If exclusive access is desired. */ struct nlm4_lock alock; /* The actual lock data (see above) */ bool reclaim; /* used for recovering locks */ int32_t state; /* specify local NSM state */ }; struct nlm4_cancargs { netobj cookie; bool block; bool exclusive; struct nlm4_lock alock; }; struct nlm4_testargs { netobj cookie; bool exclusive; struct nlm4_lock alock; }; struct nlm4_unlockargs { netobj cookie; struct nlm4_lock alock; }; enum fsh4_mode { fsm_DN = 0, /* deny none */ fsm_DR = 1, /* deny read */ fsm_DW = 2, /* deny write */ fsm_DRW = 3 /* deny read/write */ }; enum fsh4_access { fsa_NONE = 0, /* for completeness */ fsa_R = 1, /* read-only */ fsa_W = 2, /* write-only */ fsa_RW = 3 /* read/write */ }; struct nlm4_share { string caller_name; netobj fh; netobj oh; fsh4_mode mode; fsh4_access access; }; struct nlm4_shareargs { netobj cookie; nlm4_share share; /* actual share data */ bool reclaim; /* used for recovering shares */ }; struct nlm4_shareres { netobj cookie; nlm4_stats stat; int32_t sequence; }; struct nlm4_notify { string name; int64_t state; }; struct nlm4_sm_notifyargs { string name; int32_t state; opaque priv[SM_PRIV_SZ]; }; #ifdef RPC_HDR %extern void nlm_init(void); #endif program NLMPROG { version NLM4_VERS { void NLMPROC4_NULL(void) = 0; nlm4_testres NLMPROC4_TEST(nlm4_testargs) = 1; nlm4_res NLMPROC4_LOCK(nlm4_lockargs) = 2; nlm4_res NLMPROC4_CANCEL(nlm4_cancargs) = 3; nlm4_res NLMPROC4_UNLOCK(nlm4_unlockargs) = 4; nlm4_res NLMPROC4_GRANTED(nlm4_testargs) = 5; void NLMPROC4_TEST_MSG(nlm4_testargs) = 6; void NLMPROC4_LOCK_MSG(nlm4_lockargs) = 7; void NLMPROC4_CANCEL_MSG(nlm4_cancargs) = 8; void NLMPROC4_UNLOCK_MSG(nlm4_unlockargs) = 9; void NLMPROC4_GRANTED_MSG(nlm4_testargs) = 10; void NLMPROC4_TEST_RES(nlm4_testres) = 11; void NLMPROC4_LOCK_RES(nlm4_res) = 12; void NLMPROC4_CANCEL_RES(nlm4_res) = 13; void NLMPROC4_UNLOCK_RES(nlm4_res) = 14; void NLMPROC4_GRANTED_RES(nlm4_res) = 15; void NLMPROC4_SM_NOTIFY(nlm4_sm_notifyargs) = 16; nlm4_shareres NLMPROC4_SHARE(nlm4_shareargs) = 20; nlm4_shareres NLMPROC4_UNSHARE(nlm4_shareargs) = 21; nlm4_res NLMPROC4_NM_LOCK(nlm4_lockargs) = 22; void NLMPROC4_FREE_ALL(nlm4_notify) = 23; } = 4; } = 100021; nfs-ganesha-6.5/src/Protocols/XDR/nsm.x000066400000000000000000000024041473756622300177730ustar00rootroot00000000000000/* SPDX-License-Identifier: unknown license... */ /* NSM Interface */ /* * This defines the maximum length of the string * identifying the caller. */ const SM_MAXSTRLEN = 1024; const SM_PROG = 100024; const SM_VERS = 1; const SM_MON = 2; const SM_UNMON = 3; const SM_UNMON_ALL = 4; const SM_NOTIFY = 6; enum res { STAT_SUCC = 0, /* NSM agrees to monitor. */ STAT_FAIL = 1 /* NSM cannot monitor. */ }; struct sm_stat_res { res res_stat; int state; }; struct sm_stat { int state; /* state number of NSM */ }; struct my_id { string my_name; /* hostname */ int my_prog; /* RPC program number */ int my_vers; /* program version number */ int my_proc; /* procedure number */ }; struct mon_id { string mon_name; /* name of the host to be monitored */ struct my_id my_id; }; struct mon { struct mon_id mon_id; opaque priv[16]; /* private information */ }; struct notify { string my_name; /* hostname */ int state; /* state */ }; #ifdef RPC_HDR %extern int nsm_monitor(char *host); %extern int nsm_unmonitor(char *host); %extern int nsm_unmonitor_all(void); %extern int nsm_notify(char *host, int state); #endif nfs-ganesha-6.5/src/Protocols/XDR/rquota.x000066400000000000000000000067001473756622300205140ustar00rootroot00000000000000/* SPDX-License-Identifier: unknown license... */ /* @(#)rquota.x 2.1 88/08/01 4.0 RPCSRC */ /* @(#)rquota.x 1.2 87/09/20 Copyr 1987 Sun Micro */ /* * Remote quota protocol * Requires unix authentication */ const RQ_PATHLEN = 1024; struct sq_dqblk { unsigned int rq_bhardlimit; /* absolute limit on disk blks alloc */ unsigned int rq_bsoftlimit; /* preferred limit on disk blks */ unsigned int rq_curblocks; /* current block count */ unsigned int rq_fhardlimit; /* absolute limit on allocated files */ unsigned int rq_fsoftlimit; /* preferred file limit */ unsigned int rq_curfiles; /* current # allocated files */ unsigned int rq_btimeleft; /* time left for excessive disk use */ unsigned int rq_ftimeleft; /* time left for excessive files */ }; struct getquota_args { string gqa_pathp; /* path to filesystem of interest */ int gqa_uid; /* Inquire about quota for uid */ }; struct setquota_args { int sqa_qcmd; string sqa_pathp; /* path to filesystem of interest */ int sqa_id; /* Set quota for uid */ sq_dqblk sqa_dqblk; }; struct ext_getquota_args { string gqa_pathp; /* path to filesystem of interest */ int gqa_type; /* Type of quota info is needed about */ int gqa_id; /* Inquire about quota for id */ }; struct ext_setquota_args { int sqa_qcmd; string sqa_pathp; /* path to filesystem of interest */ int sqa_id; /* Set quota for id */ int sqa_type; /* Type of quota to set */ sq_dqblk sqa_dqblk; }; /* * remote quota structure */ struct rquota { int rq_bsize; /* block size for block counts */ bool rq_active; /* indicates whether quota is active */ unsigned int rq_bhardlimit; /* absolute limit on disk blks alloc */ unsigned int rq_bsoftlimit; /* preferred limit on disk blks */ unsigned int rq_curblocks; /* current block count */ unsigned int rq_fhardlimit; /* absolute limit on allocated files */ unsigned int rq_fsoftlimit; /* preferred file limit */ unsigned int rq_curfiles; /* current # allocated files */ unsigned int rq_btimeleft; /* time left for excessive disk use */ unsigned int rq_ftimeleft; /* time left for excessive files */ }; enum qr_status { Q_OK = 1, /* quota returned */ Q_NOQUOTA = 2, /* noquota for uid */ Q_EPERM = 3 /* no permission to access quota */ }; union getquota_rslt switch (qr_status status) { case Q_OK: rquota gqr_rquota; /* valid if status == Q_OK */ case Q_NOQUOTA: void; case Q_EPERM: void; }; union setquota_rslt switch (qr_status status) { case Q_OK: rquota sqr_rquota; /* valid if status == Q_OK */ case Q_NOQUOTA: void; case Q_EPERM: void; }; program RQUOTAPROG { version RQUOTAVERS { /* * Get all quotas */ getquota_rslt RQUOTAPROC_GETQUOTA(getquota_args) = 1; /* * Get active quotas only */ getquota_rslt RQUOTAPROC_GETACTIVEQUOTA(getquota_args) = 2; /* * Set all quotas */ setquota_rslt RQUOTAPROC_SETQUOTA(setquota_args) = 3; /* * Get active quotas only */ setquota_rslt RQUOTAPROC_SETACTIVEQUOTA(setquota_args) = 4; } = 1; version EXT_RQUOTAVERS { /* * Get all quotas */ getquota_rslt RQUOTAPROC_GETQUOTA(ext_getquota_args) = 1; /* * Get active quotas only */ getquota_rslt RQUOTAPROC_GETACTIVEQUOTA(ext_getquota_args) = 2; /* * Set all quotas */ setquota_rslt RQUOTAPROC_SETQUOTA(ext_setquota_args) = 3; /* * Set active quotas only */ setquota_rslt RQUOTAPROC_SETACTIVEQUOTA(ext_setquota_args) = 4; } = 2; } = 100011; nfs-ganesha-6.5/src/Protocols/XDR/xdr_mount.c000066400000000000000000000143431473756622300211750ustar00rootroot00000000000000/* SPDX-License-Identifier: unknown license... */ /* * Please do not edit this file. * It was generated using rpcgen. */ #include "config.h" #include "gsh_rpc.h" #include "mount.h" #include "nfs23.h" #include "nfs_fh.h" bool xdr_mountstat3(XDR *xdrs, mountstat3 *objp) { #if defined(_LP64) || defined(_KERNEL) register int __attribute__((__unused__)) * buf; #else register long __attribute__((__unused__)) * buf; #endif if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return (false); return (true); } bool xdr_fhandle3(XDR *xdrs, fhandle3 *objp) { #if defined(_LP64) || defined(_KERNEL) register int __attribute__((__unused__)) * buf; #else register long __attribute__((__unused__)) * buf; #endif if (!inline_xdr_bytes(xdrs, (char **)&objp->fhandle3_val, (u_int *)&objp->fhandle3_len, NFS3_FHSIZE)) return (false); return (true); } bool xdr_dirpath(XDR *xdrs, mnt3_dirpath *objp) { #if defined(_LP64) || defined(_KERNEL) register int __attribute__((__unused__)) * buf; #else register long __attribute__((__unused__)) * buf; #endif if (!inline_xdr_string(xdrs, objp, MNTPATHLEN)) return (false); return (true); } bool xdr_name(XDR *xdrs, mnt3_name *objp) { #if defined(_LP64) || defined(_KERNEL) register int __attribute__((__unused__)) * buf; #else register long __attribute__((__unused__)) * buf; #endif if (!inline_xdr_string(xdrs, objp, MNTNAMLEN)) return (false); return (true); } bool xdr_groupnode_x(XDR *xdrs, groupnode *objp) { #if defined(_LP64) || defined(_KERNEL) register int __attribute__((__unused__)) * buf; #else register long __attribute__((__unused__)) * buf; #endif if (!xdr_name(xdrs, &objp->gr_name)) return (false); return (true); } bool xdr_groups(XDR *xdrs, struct groupnode **objp) { /* * more_elements is pre-computed in case the direction is * XDR_ENCODE or XDR_FREE. more_elements is overwritten by * xdr_bool when the direction is XDR_DECODE. */ int freeing; struct groupnode **next = NULL; /* pacify gcc */ bool_t more_elements = false; /* yes, bool_t */ assert(xdrs != NULL); assert(objp != NULL); freeing = (xdrs->x_op == XDR_FREE); for (;;) { more_elements = (bool_t)(*objp != NULL); if (!xdr_bool(xdrs, &more_elements)) return (false); if (!more_elements) return (true); /* we are done */ /* * the unfortunate side effect of non-recursion is that in * the case of freeing we must remember the next object * before we free the current object ... */ if (freeing) next = &((*objp)->gr_next); if (!xdr_reference(xdrs, (void **)objp, (u_int)sizeof(struct groupnode), (xdrproc_t)xdr_groupnode_x)) return (false); objp = (freeing) ? next : &((*objp)->gr_next); } } bool xdr_exportnode_x(XDR *xdrs, exportnode *objp) { #if defined(_LP64) || defined(_KERNEL) register int __attribute__((__unused__)) * buf; #else register long __attribute__((__unused__)) * buf; #endif if (!xdr_dirpath(xdrs, &objp->ex_dir)) return (false); if (!xdr_groups(xdrs, &objp->ex_groups)) return (false); return (true); } bool xdr_exports(XDR *xdrs, struct exportnode **objp) { /* * more_elements is pre-computed in case the direction is * XDR_ENCODE or XDR_FREE. more_elements is overwritten by * xdr_bool when the direction is XDR_DECODE. */ int freeing; struct exportnode **next = NULL; /* pacify gcc */ bool_t more_elements = false; /* yes, bool_t */ assert(xdrs != NULL); assert(objp != NULL); freeing = (xdrs->x_op == XDR_FREE); for (;;) { more_elements = (bool_t)(*objp != NULL); if (!xdr_bool(xdrs, &more_elements)) return (false); if (!more_elements) return (true); /* we are done */ /* * the unfortunate side effect of non-recursion is that in * the case of freeing we must remember the next object * before we free the current object ... */ if (freeing) next = &((*objp)->ex_next); if (!xdr_reference(xdrs, (void **)objp, (u_int)sizeof(struct exportnode), (xdrproc_t)xdr_exportnode_x)) return (false); objp = (freeing) ? next : &((*objp)->ex_next); } } bool xdr_mountbody_x(XDR *xdrs, mountbody *objp) { #if defined(_LP64) || defined(_KERNEL) register int __attribute__((__unused__)) * buf; #else register long __attribute__((__unused__)) * buf; #endif if (!xdr_name(xdrs, &objp->ml_hostname)) return (false); if (!xdr_dirpath(xdrs, &objp->ml_directory)) return (false); return (true); } bool xdr_mountlist(XDR *xdrs, struct mountbody **objp) { /* * more_elements is pre-computed in case the direction is * XDR_ENCODE or XDR_FREE. more_elements is overwritten by * xdr_bool when the direction is XDR_DECODE. */ int freeing; struct mountbody **next = NULL; /* pacify gcc */ bool_t more_elements = false; /* yes, bool_t */ assert(xdrs != NULL); assert(objp != NULL); freeing = (xdrs->x_op == XDR_FREE); for (;;) { more_elements = (bool_t)(*objp != NULL); if (!xdr_bool(xdrs, &more_elements)) return (false); if (!more_elements) return (true); /* we are done */ /* * the unfortunate side effect of non-recursion is that in * the case of freeing we must remember the next object * before we free the current object ... */ if (freeing) next = &((*objp)->ml_next); if (!xdr_reference(xdrs, (void **)objp, (u_int)sizeof(struct mountbody), (xdrproc_t)xdr_mountbody_x)) return (false); objp = (freeing) ? next : &((*objp)->ml_next); } } bool xdr_mountres3_ok(XDR *xdrs, mountres3_ok *objp) { #if defined(_LP64) || defined(_KERNEL) register int __attribute__((__unused__)) * buf; #else register long __attribute__((__unused__)) * buf; #endif if (!xdr_fhandle3(xdrs, &objp->fhandle)) return (false); if (!xdr_array(xdrs, (char **)&objp->auth_flavors.auth_flavors_val, &objp->auth_flavors.auth_flavors_len, XDR_ARRAY_MAXLEN, sizeof(int), (xdrproc_t)xdr_int)) return (false); return (true); } bool xdr_mountres3(XDR *xdrs, mountres3 *objp) { #if defined(_LP64) || defined(_KERNEL) register int __attribute__((__unused__)) * buf; #else register long __attribute__((__unused__)) * buf; #endif if (!xdr_mountstat3(xdrs, &objp->fhs_status)) return (false); switch (objp->fhs_status) { case MNT3_OK: if (!xdr_mountres3_ok(xdrs, &objp->mountres3_u.mountinfo)) return (false); break; default: return (true); break; } return (true); } nfs-ganesha-6.5/src/Protocols/XDR/xdr_nfs23.c000066400000000000000000001161521473756622300207670ustar00rootroot00000000000000/* SPDX-License-Identifier: unknown license... */ /* * The content of this file is a mix of rpcgen-generated * and hand-edited program text. It is not automatically * generated by, e.g., build processes. * * This file is under version control. */ #include "config.h" #include "gsh_rpc.h" #include "fsal.h" #include "nfs_fh.h" #include "fsal_convert.h" static struct nfs_request_lookahead dummy_lookahead = { .flags = 0, .read = 0, .write = 0 }; bool xdr_nfspath2(XDR *xdrs, nfspath2 *objp) { if (!xdr_string(xdrs, objp, NFS2_MAXPATHLEN)) return (false); return (true); } bool xdr_filename2(XDR *xdrs, filename2 *objp) { if (!xdr_string(xdrs, objp, NFS2_MAXNAMLEN)) return (false); return (true); } bool xdr_fhandle2(XDR *xdrs, fhandle2 objp) { if (!xdr_opaque(xdrs, objp, NFS2_FHSIZE)) return (false); return (true); } bool xdr_nfsdata2(XDR *xdrs, nfsdata2 *objp) { if (!xdr_bytes(xdrs, (char **)&objp->nfsdata2_val, (u_int *)&objp->nfsdata2_len, NFS2_MAXDATA)) return (false); return (true); } bool xdr_nfscookie2(XDR *xdrs, nfscookie2 objp) { if (!xdr_opaque(xdrs, objp, NFS2_COOKIESIZE)) return (false); return (true); } bool xdr_fhstatus2(XDR *xdrs, fhstatus2 *objp) { if (!xdr_u_int(xdrs, &objp->status)) return (false); switch (objp->status) { case 0: if (!xdr_fhandle2(xdrs, objp->fhstatus2_u.directory)) return (false); break; } return (true); } bool xdr_nfs3_uint64(XDR *xdrs, nfs3_uint64 *objp) { if (!xdr_u_longlong_t(xdrs, (quad_t *)objp)) return (false); return (true); } bool xdr_nfs3_int64(XDR *xdrs, nfs3_int64 *objp) { if (!xdr_longlong_t(xdrs, (quad_t *)objp)) return (false); return (true); } bool xdr_nfs3_uint32(XDR *xdrs, nfs3_uint32 *objp) { if (!xdr_u_int(xdrs, objp)) return (false); return (true); } bool xdr_nfs3_int32(XDR *xdrs, nfs3_int32 *objp) { if (!xdr_int(xdrs, objp)) return (false); return (true); } bool xdr_filename3(XDR *xdrs, filename3 *objp) { if (!xdr_string(xdrs, objp, XDR_STRING_MAXLEN)) return (false); return (true); } bool xdr_nfspath3(XDR *xdrs, nfspath3 *objp) { if (!xdr_string(xdrs, objp, XDR_STRING_MAXLEN)) return (false); return (true); } bool xdr_fileid3(XDR *xdrs, fileid3 *objp) { if (!xdr_nfs3_uint64(xdrs, objp)) return (false); return (true); } bool xdr_cookie3(XDR *xdrs, cookie3 *objp) { if (!xdr_nfs3_uint64(xdrs, objp)) return (false); return (true); } bool xdr_cookieverf3(XDR *xdrs, cookieverf3 objp) { if (!xdr_opaque(xdrs, objp, 8)) return (false); return (true); } bool xdr_createverf3(XDR *xdrs, createverf3 objp) { if (!xdr_opaque(xdrs, objp, 8)) return (false); return (true); } bool xdr_writeverf3(XDR *xdrs, writeverf3 objp) { if (!xdr_opaque(xdrs, objp, 8)) return (false); return (true); } bool xdr_uid3(XDR *xdrs, uid3 *objp) { if (!xdr_nfs3_uint32(xdrs, objp)) return (false); return (true); } bool xdr_gid3(XDR *xdrs, gid3 *objp) { if (!xdr_nfs3_uint32(xdrs, objp)) return (false); return (true); } bool xdr_size3(XDR *xdrs, size3 *objp) { if (!xdr_nfs3_uint64(xdrs, objp)) return (false); return (true); } bool xdr_offset3(XDR *xdrs, offset3 *objp) { if (!xdr_nfs3_uint64(xdrs, objp)) return (false); return (true); } bool xdr_mode3(XDR *xdrs, mode3 *objp) { if (!xdr_nfs3_uint32(xdrs, objp)) return (false); return (true); } bool xdr_count3(XDR *xdrs, count3 *objp) { if (!xdr_nfs3_uint32(xdrs, objp)) return (false); return (true); } bool xdr_nfsstat3(XDR *xdrs, nfsstat3 *objp) { if (!xdr_enum(xdrs, (enum_t *)objp)) return (false); return (true); } bool xdr_ftype3(XDR *xdrs, ftype3 *objp) { if (!xdr_enum(xdrs, (enum_t *)objp)) return (false); return (true); } bool xdr_specdata3(XDR *xdrs, specdata3 *objp) { if (!xdr_nfs3_uint32(xdrs, &objp->specdata1)) return (false); if (!xdr_nfs3_uint32(xdrs, &objp->specdata2)) return (false); return (true); } bool xdr_nfs_fh3(XDR *xdrs, nfs_fh3 *objp) { if (!xdr_bytes(xdrs, (char **)&objp->data.data_val, (u_int *)&objp->data.data_len, 64)) return (false); return (true); } bool xdr_nfstime3(XDR *xdrs, nfstime3 *objp) { if (!xdr_nfs3_uint32(xdrs, &objp->tv_sec)) return (false); if (!xdr_nfs3_uint32(xdrs, &objp->tv_nsec)) return (false); return (true); } bool xdr_fattr3(XDR *xdrs, fattr3 *objp) { ftype3 ft; specdata3 rdev; uid3 uid; gid3 gid; nfstime3 atime, mtime, ctime; mode3 mode; if (xdrs->x_op == XDR_ENCODE) { /* Convert object_file_type_t to ftype3 */ switch (objp->type) { case FIFO_FILE: ft = NF3FIFO; break; case CHARACTER_FILE: ft = NF3CHR; break; case DIRECTORY: ft = NF3DIR; break; case BLOCK_FILE: ft = NF3BLK; break; case REGULAR_FILE: case EXTENDED_ATTR: ft = NF3REG; break; case SYMBOLIC_LINK: ft = NF3LNK; break; case SOCKET_FILE: ft = NF3SOCK; break; default: LogEvent(COMPONENT_NFSPROTO, "xdr_fattr3: Bogus type = %d", objp->type); } mode = fsal2unix_mode(objp->mode); rdev.specdata1 = objp->rawdev.major; rdev.specdata2 = objp->rawdev.minor; uid = objp->owner; gid = objp->group; atime.tv_sec = objp->atime.tv_sec; atime.tv_nsec = objp->atime.tv_nsec; mtime.tv_sec = objp->mtime.tv_sec; mtime.tv_nsec = objp->mtime.tv_nsec; ctime.tv_sec = objp->ctime.tv_sec; ctime.tv_nsec = objp->ctime.tv_nsec; } if (!xdr_ftype3(xdrs, &ft)) return (false); if (!xdr_mode3(xdrs, &mode)) return (false); if (!xdr_nfs3_uint32(xdrs, &objp->numlinks)) return (false); if (!xdr_uid3(xdrs, &uid)) return (false); if (!xdr_gid3(xdrs, &gid)) return (false); if (!xdr_size3(xdrs, &objp->filesize)) return (false); if (!xdr_size3(xdrs, &objp->spaceused)) return (false); if (!xdr_specdata3(xdrs, &rdev)) return (false); if (!xdr_nfs3_uint64(xdrs, &objp->fsid3)) return (false); if (!xdr_fileid3(xdrs, &objp->fileid)) return (false); if (!xdr_nfstime3(xdrs, &atime)) return (false); if (!xdr_nfstime3(xdrs, &mtime)) return (false); if (!xdr_nfstime3(xdrs, &ctime)) return (false); if (xdrs->x_op == XDR_DECODE) { /* Convert ftype3 to object_file_type_t */ switch (ft) { case NF3FIFO: objp->type = FIFO_FILE; break; case NF3CHR: objp->type = CHARACTER_FILE; break; case NF3DIR: objp->type = DIRECTORY; break; case NF3BLK: objp->type = BLOCK_FILE; break; case NF3REG: objp->type = REGULAR_FILE; break; case NF3LNK: objp->type = SYMBOLIC_LINK; break; case NF3SOCK: objp->type = SOCKET_FILE; break; default: LogEvent(COMPONENT_NFSPROTO, "xdr_fattr3: Bogus type = %d", ft); } objp->mode = unix2fsal_mode(mode); objp->rawdev.major = rdev.specdata1; objp->rawdev.minor = rdev.specdata2; objp->fsid.major = objp->fsid3; objp->fsid.minor = 0; objp->owner = uid; objp->group = gid; objp->atime.tv_sec = atime.tv_sec; objp->atime.tv_nsec = atime.tv_nsec; objp->mtime.tv_sec = mtime.tv_sec; objp->mtime.tv_nsec = mtime.tv_nsec; objp->ctime.tv_sec = ctime.tv_sec; objp->ctime.tv_nsec = ctime.tv_nsec; } return (true); } bool xdr_post_op_attr(XDR *xdrs, post_op_attr *objp) { if (!xdr_bool(xdrs, &objp->attributes_follow)) return (false); switch (objp->attributes_follow) { case TRUE: if (!xdr_fattr3(xdrs, &objp->post_op_attr_u.attributes)) return (false); break; case FALSE: break; default: return (false); } return (true); } bool xdr_wcc_attr(XDR *xdrs, wcc_attr *objp) { if (!xdr_size3(xdrs, &objp->size)) return (false); if (!xdr_nfstime3(xdrs, &objp->mtime)) return (false); if (!xdr_nfstime3(xdrs, &objp->ctime)) return (false); return (true); } bool xdr_pre_op_attr(XDR *xdrs, pre_op_attr *objp) { if (!xdr_bool(xdrs, &objp->attributes_follow)) return (false); switch (objp->attributes_follow) { case TRUE: if (!xdr_wcc_attr(xdrs, &objp->pre_op_attr_u.attributes)) return (false); break; case FALSE: break; default: return (false); } return (true); } bool xdr_wcc_data(XDR *xdrs, wcc_data *objp) { if (!xdr_pre_op_attr(xdrs, &objp->before)) return (false); if (!xdr_post_op_attr(xdrs, &objp->after)) return (false); return (true); } bool xdr_post_op_fh3(XDR *xdrs, post_op_fh3 *objp) { if (!xdr_bool(xdrs, &objp->handle_follows)) return (false); switch (objp->handle_follows) { case TRUE: if (!xdr_nfs_fh3(xdrs, &objp->post_op_fh3_u.handle)) return (false); break; case FALSE: break; default: return (false); } return (true); } bool xdr_time_how(XDR *xdrs, time_how *objp) { if (!xdr_enum(xdrs, (enum_t *)objp)) return (false); return (true); } bool xdr_set_mode3(XDR *xdrs, set_mode3 *objp) { if (!xdr_bool(xdrs, &objp->set_it)) return (false); switch (objp->set_it) { case TRUE: if (!xdr_mode3(xdrs, &objp->set_mode3_u.mode)) return (false); break; } return (true); } bool xdr_set_uid3(XDR *xdrs, set_uid3 *objp) { if (!xdr_bool(xdrs, &objp->set_it)) return (false); switch (objp->set_it) { case TRUE: if (!xdr_uid3(xdrs, &objp->set_uid3_u.uid)) return (false); break; } return (true); } bool xdr_set_gid3(XDR *xdrs, set_gid3 *objp) { if (!xdr_bool(xdrs, &objp->set_it)) return (false); switch (objp->set_it) { case TRUE: if (!xdr_gid3(xdrs, &objp->set_gid3_u.gid)) return (false); break; } return (true); } bool xdr_set_size3(XDR *xdrs, set_size3 *objp) { if (!xdr_bool(xdrs, &objp->set_it)) return (false); switch (objp->set_it) { case TRUE: if (!xdr_size3(xdrs, &objp->set_size3_u.size)) return (false); break; } return (true); } bool xdr_set_atime(XDR *xdrs, set_atime *objp) { if (!xdr_time_how(xdrs, &objp->set_it)) return (false); switch (objp->set_it) { case SET_TO_CLIENT_TIME: if (!xdr_nfstime3(xdrs, &objp->set_atime_u.atime)) return (false); break; default: return (true); break; } return (true); } bool xdr_set_mtime(XDR *xdrs, set_mtime *objp) { if (!xdr_time_how(xdrs, &objp->set_it)) return (false); switch (objp->set_it) { case SET_TO_CLIENT_TIME: if (!xdr_nfstime3(xdrs, &objp->set_mtime_u.mtime)) return (false); break; default: return (true); break; } return (true); } bool xdr_sattr3(XDR *xdrs, sattr3 *objp) { if (!xdr_set_mode3(xdrs, &objp->mode)) return (false); if (!xdr_set_uid3(xdrs, &objp->uid)) return (false); if (!xdr_set_gid3(xdrs, &objp->gid)) return (false); if (!xdr_set_size3(xdrs, &objp->size)) return (false); if (!xdr_set_atime(xdrs, &objp->atime)) return (false); if (!xdr_set_mtime(xdrs, &objp->mtime)) return (false); return (true); } bool xdr_diropargs3(XDR *xdrs, diropargs3 *objp) { if (!xdr_nfs_fh3(xdrs, &objp->dir)) return (false); if (!xdr_filename3(xdrs, &objp->name)) return (false); return (true); } bool xdr_GETATTR3args(XDR *xdrs, GETATTR3args *objp) { if (!xdr_nfs_fh3(xdrs, &objp->object)) return (false); return (true); } bool xdr_GETATTR3resok(XDR *xdrs, GETATTR3resok *objp) { if (!xdr_fattr3(xdrs, &objp->obj_attributes)) return (false); return (true); } bool xdr_GETATTR3res(XDR *xdrs, GETATTR3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_GETATTR3resok(xdrs, &objp->GETATTR3res_u.resok)) return (false); break; default: return (true); break; } return (true); } bool xdr_sattrguard3(XDR *xdrs, sattrguard3 *objp) { if (!xdr_bool(xdrs, &objp->check)) return (false); switch (objp->check) { case TRUE: if (!xdr_nfstime3(xdrs, &objp->sattrguard3_u.obj_ctime)) return (false); break; case FALSE: break; default: return (false); } return (true); } bool xdr_SETATTR3args(XDR *xdrs, SETATTR3args *objp) { if (!xdr_nfs_fh3(xdrs, &objp->object)) return (false); if (!xdr_sattr3(xdrs, &objp->new_attributes)) return (false); if (!xdr_sattrguard3(xdrs, &objp->guard)) return (false); return (true); } bool xdr_SETATTR3resok(XDR *xdrs, SETATTR3resok *objp) { if (!xdr_wcc_data(xdrs, &objp->obj_wcc)) return (false); return (true); } bool xdr_SETATTR3resfail(XDR *xdrs, SETATTR3resfail *objp) { if (!xdr_wcc_data(xdrs, &objp->obj_wcc)) return (false); return (true); } bool xdr_SETATTR3res(XDR *xdrs, SETATTR3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_SETATTR3resok(xdrs, &objp->SETATTR3res_u.resok)) return (false); break; default: if (!xdr_SETATTR3resfail(xdrs, &objp->SETATTR3res_u.resfail)) return (false); break; } return (true); } bool xdr_LOOKUP3args(XDR *xdrs, LOOKUP3args *objp) { if (!xdr_diropargs3(xdrs, &objp->what)) return (false); return (true); } bool xdr_LOOKUP3resok(XDR *xdrs, LOOKUP3resok *objp) { if (!xdr_nfs_fh3(xdrs, &objp->object)) return (false); if (!xdr_post_op_attr(xdrs, &objp->obj_attributes)) return (false); if (!xdr_post_op_attr(xdrs, &objp->dir_attributes)) return (false); return (true); } bool xdr_LOOKUP3resfail(XDR *xdrs, LOOKUP3resfail *objp) { if (!xdr_post_op_attr(xdrs, &objp->dir_attributes)) return (false); return (true); } bool xdr_LOOKUP3res(XDR *xdrs, LOOKUP3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_LOOKUP3resok(xdrs, &objp->LOOKUP3res_u.resok)) return (false); break; default: if (!xdr_LOOKUP3resfail(xdrs, &objp->LOOKUP3res_u.resfail)) return (false); break; } return (true); } bool xdr_ACCESS3args(XDR *xdrs, ACCESS3args *objp) { if (!xdr_nfs_fh3(xdrs, &objp->object)) return (false); if (!xdr_nfs3_uint32(xdrs, &objp->access)) return (false); return (true); } bool xdr_ACCESS3resok(XDR *xdrs, ACCESS3resok *objp) { if (!xdr_post_op_attr(xdrs, &objp->obj_attributes)) return (false); if (!xdr_nfs3_uint32(xdrs, &objp->access)) return (false); return (true); } bool xdr_ACCESS3resfail(XDR *xdrs, ACCESS3resfail *objp) { if (!xdr_post_op_attr(xdrs, &objp->obj_attributes)) return (false); return (true); } bool xdr_ACCESS3res(XDR *xdrs, ACCESS3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_ACCESS3resok(xdrs, &objp->ACCESS3res_u.resok)) return (false); break; default: if (!xdr_ACCESS3resfail(xdrs, &objp->ACCESS3res_u.resfail)) return (false); break; } return (true); } bool xdr_READLINK3args(XDR *xdrs, READLINK3args *objp) { if (!xdr_nfs_fh3(xdrs, &objp->symlink)) return (false); return (true); } bool xdr_READLINK3resok(XDR *xdrs, READLINK3resok *objp) { if (!xdr_post_op_attr(xdrs, &objp->symlink_attributes)) return (false); if (!xdr_nfspath3(xdrs, &objp->data)) return (false); return (true); } bool xdr_READLINK3resfail(XDR *xdrs, READLINK3resfail *objp) { if (!xdr_post_op_attr(xdrs, &objp->symlink_attributes)) return (false); return (true); } bool xdr_READLINK3res(XDR *xdrs, READLINK3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_READLINK3resok(xdrs, &objp->READLINK3res_u.resok)) return (false); break; default: if (!xdr_READLINK3resfail(xdrs, &objp->READLINK3res_u.resfail)) return (false); break; } return (true); } bool xdr_READ3args(XDR *xdrs, READ3args *objp) { struct nfs_request_lookahead *lkhd = xdrs->x_public ? (struct nfs_request_lookahead *)xdrs->x_public : &dummy_lookahead; if (!xdr_nfs_fh3(xdrs, &objp->file)) return (false); if (!xdr_offset3(xdrs, &objp->offset)) return (false); if (!xdr_count3(xdrs, &objp->count)) return (false); lkhd->flags = NFS_LOOKAHEAD_READ; (lkhd->read)++; return (true); } bool xdr_READ3resok(XDR *xdrs, READ3resok *objp) { if (!xdr_post_op_attr(xdrs, &objp->file_attributes)) return (false); if (!xdr_count3(xdrs, &objp->count)) return (false); if (!xdr_bool(xdrs, &objp->eof)) return (false); if (!xdr_io_data(xdrs, &objp->data)) return (false); return (true); } bool xdr_READ3resfail(XDR *xdrs, READ3resfail *objp) { if (!xdr_post_op_attr(xdrs, &objp->file_attributes)) return (false); return (true); } bool xdr_READ3res(XDR *xdrs, READ3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_READ3resok(xdrs, &objp->READ3res_u.resok)) return (false); break; default: if (!xdr_READ3resfail(xdrs, &objp->READ3res_u.resfail)) return (false); break; } return (true); } bool xdr_stable_how(XDR *xdrs, stable_how *objp) { if (!xdr_enum(xdrs, (enum_t *)objp)) return (false); return (true); } bool xdr_WRITE3args(XDR *xdrs, WRITE3args *objp) { struct nfs_request_lookahead *lkhd = xdrs->x_public ? (struct nfs_request_lookahead *)xdrs->x_public : &dummy_lookahead; if (!xdr_nfs_fh3(xdrs, &objp->file)) return (false); if (!xdr_offset3(xdrs, &objp->offset)) return (false); if (!xdr_count3(xdrs, &objp->count)) return (false); if (!xdr_stable_how(xdrs, &objp->stable)) return (false); if (!xdr_io_data(xdrs, &objp->data)) return (false); lkhd->flags |= NFS_LOOKAHEAD_WRITE; (lkhd->write)++; return (true); } bool xdr_WRITE3resok(XDR *xdrs, WRITE3resok *objp) { if (!xdr_wcc_data(xdrs, &objp->file_wcc)) return (false); if (!xdr_count3(xdrs, &objp->count)) return (false); if (!xdr_stable_how(xdrs, &objp->committed)) return (false); if (!xdr_writeverf3(xdrs, objp->verf)) return (false); return (true); } bool xdr_WRITE3resfail(XDR *xdrs, WRITE3resfail *objp) { if (!xdr_wcc_data(xdrs, &objp->file_wcc)) return (false); return (true); } bool xdr_WRITE3res(XDR *xdrs, WRITE3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_WRITE3resok(xdrs, &objp->WRITE3res_u.resok)) return (false); break; default: if (!xdr_WRITE3resfail(xdrs, &objp->WRITE3res_u.resfail)) return (false); break; } return (true); } bool xdr_createmode3(XDR *xdrs, createmode3 *objp) { if (!xdr_enum(xdrs, (enum_t *)objp)) return (false); return (true); } bool xdr_createhow3(XDR *xdrs, createhow3 *objp) { if (!xdr_createmode3(xdrs, &objp->mode)) return (false); switch (objp->mode) { case UNCHECKED: case GUARDED: if (!xdr_sattr3(xdrs, &objp->createhow3_u.obj_attributes)) return (false); break; case EXCLUSIVE: if (!xdr_createverf3(xdrs, objp->createhow3_u.verf)) return (false); break; default: return (false); } return (true); } bool xdr_CREATE3args(XDR *xdrs, CREATE3args *objp) { struct nfs_request_lookahead *lkhd = xdrs->x_public ? (struct nfs_request_lookahead *)xdrs->x_public : &dummy_lookahead; if (!xdr_diropargs3(xdrs, &objp->where)) return (false); if (!xdr_createhow3(xdrs, &objp->how)) return (false); lkhd->flags |= NFS_LOOKAHEAD_CREATE; return (true); } bool xdr_CREATE3resok(XDR *xdrs, CREATE3resok *objp) { if (!xdr_post_op_fh3(xdrs, &objp->obj)) return (false); if (!xdr_post_op_attr(xdrs, &objp->obj_attributes)) return (false); if (!xdr_wcc_data(xdrs, &objp->dir_wcc)) return (false); return (true); } bool xdr_CREATE3resfail(XDR *xdrs, CREATE3resfail *objp) { if (!xdr_wcc_data(xdrs, &objp->dir_wcc)) return (false); return (true); } bool xdr_CREATE3res(XDR *xdrs, CREATE3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_CREATE3resok(xdrs, &objp->CREATE3res_u.resok)) return (false); break; default: if (!xdr_CREATE3resfail(xdrs, &objp->CREATE3res_u.resfail)) return (false); break; } return (true); } bool xdr_MKDIR3args(XDR *xdrs, MKDIR3args *objp) { if (!xdr_diropargs3(xdrs, &objp->where)) return (false); if (!xdr_sattr3(xdrs, &objp->attributes)) return (false); return (true); } bool xdr_MKDIR3resok(XDR *xdrs, MKDIR3resok *objp) { if (!xdr_post_op_fh3(xdrs, &objp->obj)) return (false); if (!xdr_post_op_attr(xdrs, &objp->obj_attributes)) return (false); if (!xdr_wcc_data(xdrs, &objp->dir_wcc)) return (false); return (true); } bool xdr_MKDIR3resfail(XDR *xdrs, MKDIR3resfail *objp) { if (!xdr_wcc_data(xdrs, &objp->dir_wcc)) return (false); return (true); } bool xdr_MKDIR3res(XDR *xdrs, MKDIR3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_MKDIR3resok(xdrs, &objp->MKDIR3res_u.resok)) return (false); break; default: if (!xdr_MKDIR3resfail(xdrs, &objp->MKDIR3res_u.resfail)) return (false); break; } return (true); } bool xdr_symlinkdata3(XDR *xdrs, symlinkdata3 *objp) { if (!xdr_sattr3(xdrs, &objp->symlink_attributes)) return (false); if (!xdr_nfspath3(xdrs, &objp->symlink_data)) return (false); return (true); } bool xdr_SYMLINK3args(XDR *xdrs, SYMLINK3args *objp) { if (!xdr_diropargs3(xdrs, &objp->where)) return (false); if (!xdr_symlinkdata3(xdrs, &objp->symlink)) return (false); return (true); } bool xdr_SYMLINK3resok(XDR *xdrs, SYMLINK3resok *objp) { if (!xdr_post_op_fh3(xdrs, &objp->obj)) return (false); if (!xdr_post_op_attr(xdrs, &objp->obj_attributes)) return (false); if (!xdr_wcc_data(xdrs, &objp->dir_wcc)) return (false); return (true); } bool xdr_SYMLINK3resfail(XDR *xdrs, SYMLINK3resfail *objp) { if (!xdr_wcc_data(xdrs, &objp->dir_wcc)) return (false); return (true); } bool xdr_SYMLINK3res(XDR *xdrs, SYMLINK3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_SYMLINK3resok(xdrs, &objp->SYMLINK3res_u.resok)) return (false); break; default: if (!xdr_SYMLINK3resfail(xdrs, &objp->SYMLINK3res_u.resfail)) return (false); break; } return (true); } bool xdr_devicedata3(XDR *xdrs, devicedata3 *objp) { if (!xdr_sattr3(xdrs, &objp->dev_attributes)) return (false); if (!xdr_specdata3(xdrs, &objp->spec)) return (false); return (true); } bool xdr_mknoddata3(XDR *xdrs, mknoddata3 *objp) { if (!xdr_ftype3(xdrs, &objp->type)) return (false); switch (objp->type) { case NF3CHR: case NF3BLK: if (!xdr_devicedata3(xdrs, &objp->mknoddata3_u.device)) return (false); break; case NF3SOCK: case NF3FIFO: if (!xdr_sattr3(xdrs, &objp->mknoddata3_u.pipe_attributes)) return (false); break; default: return (true); break; } return (true); } bool xdr_MKNOD3args(XDR *xdrs, MKNOD3args *objp) { if (!xdr_diropargs3(xdrs, &objp->where)) return (false); if (!xdr_mknoddata3(xdrs, &objp->what)) return (false); return (true); } bool xdr_MKNOD3resok(XDR *xdrs, MKNOD3resok *objp) { if (!xdr_post_op_fh3(xdrs, &objp->obj)) return (false); if (!xdr_post_op_attr(xdrs, &objp->obj_attributes)) return (false); if (!xdr_wcc_data(xdrs, &objp->dir_wcc)) return (false); return (true); } bool xdr_MKNOD3resfail(XDR *xdrs, MKNOD3resfail *objp) { if (!xdr_wcc_data(xdrs, &objp->dir_wcc)) return (false); return (true); } bool xdr_MKNOD3res(XDR *xdrs, MKNOD3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_MKNOD3resok(xdrs, &objp->MKNOD3res_u.resok)) return (false); break; default: if (!xdr_MKNOD3resfail(xdrs, &objp->MKNOD3res_u.resfail)) return (false); break; } return (true); } bool xdr_REMOVE3args(XDR *xdrs, REMOVE3args *objp) { struct nfs_request_lookahead *lkhd = xdrs->x_public ? (struct nfs_request_lookahead *)xdrs->x_public : &dummy_lookahead; if (!xdr_diropargs3(xdrs, &objp->object)) return (false); lkhd->flags |= NFS_LOOKAHEAD_REMOVE; return (true); } bool xdr_REMOVE3resok(XDR *xdrs, REMOVE3resok *objp) { if (!xdr_wcc_data(xdrs, &objp->dir_wcc)) return (false); return (true); } bool xdr_REMOVE3resfail(XDR *xdrs, REMOVE3resfail *objp) { if (!xdr_wcc_data(xdrs, &objp->dir_wcc)) return (false); return (true); } bool xdr_REMOVE3res(XDR *xdrs, REMOVE3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_REMOVE3resok(xdrs, &objp->REMOVE3res_u.resok)) return (false); break; default: if (!xdr_REMOVE3resfail(xdrs, &objp->REMOVE3res_u.resfail)) return (false); break; } return (true); } bool xdr_RMDIR3args(XDR *xdrs, RMDIR3args *objp) { if (!xdr_diropargs3(xdrs, &objp->object)) return (false); return (true); } bool xdr_RMDIR3resok(XDR *xdrs, RMDIR3resok *objp) { if (!xdr_wcc_data(xdrs, &objp->dir_wcc)) return (false); return (true); } bool xdr_RMDIR3resfail(XDR *xdrs, RMDIR3resfail *objp) { if (!xdr_wcc_data(xdrs, &objp->dir_wcc)) return (false); return (true); } bool xdr_RMDIR3res(XDR *xdrs, RMDIR3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_RMDIR3resok(xdrs, &objp->RMDIR3res_u.resok)) return (false); break; default: if (!xdr_RMDIR3resfail(xdrs, &objp->RMDIR3res_u.resfail)) return (false); break; } return (true); } bool xdr_RENAME3args(XDR *xdrs, RENAME3args *objp) { struct nfs_request_lookahead *lkhd = xdrs->x_public ? (struct nfs_request_lookahead *)xdrs->x_public : &dummy_lookahead; if (!xdr_diropargs3(xdrs, &objp->from)) return (false); if (!xdr_diropargs3(xdrs, &objp->to)) return (false); lkhd->flags |= NFS_LOOKAHEAD_RENAME; return (true); } bool xdr_RENAME3resok(XDR *xdrs, RENAME3resok *objp) { if (!xdr_wcc_data(xdrs, &objp->fromdir_wcc)) return (false); if (!xdr_wcc_data(xdrs, &objp->todir_wcc)) return (false); return (true); } bool xdr_RENAME3resfail(XDR *xdrs, RENAME3resfail *objp) { if (!xdr_wcc_data(xdrs, &objp->fromdir_wcc)) return (false); if (!xdr_wcc_data(xdrs, &objp->todir_wcc)) return (false); return (true); } bool xdr_RENAME3res(XDR *xdrs, RENAME3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_RENAME3resok(xdrs, &objp->RENAME3res_u.resok)) return (false); break; default: if (!xdr_RENAME3resfail(xdrs, &objp->RENAME3res_u.resfail)) return (false); break; } return (true); } bool xdr_LINK3args(XDR *xdrs, LINK3args *objp) { if (!xdr_nfs_fh3(xdrs, &objp->file)) return (false); if (!xdr_diropargs3(xdrs, &objp->link)) return (false); return (true); } bool xdr_LINK3resok(XDR *xdrs, LINK3resok *objp) { if (!xdr_post_op_attr(xdrs, &objp->file_attributes)) return (false); if (!xdr_wcc_data(xdrs, &objp->linkdir_wcc)) return (false); return (true); } bool xdr_LINK3resfail(XDR *xdrs, LINK3resfail *objp) { if (!xdr_post_op_attr(xdrs, &objp->file_attributes)) return (false); if (!xdr_wcc_data(xdrs, &objp->linkdir_wcc)) return (false); return (true); } bool xdr_LINK3res(XDR *xdrs, LINK3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_LINK3resok(xdrs, &objp->LINK3res_u.resok)) return (false); break; default: if (!xdr_LINK3resfail(xdrs, &objp->LINK3res_u.resfail)) return (false); break; } return (true); } bool xdr_READDIR3args(XDR *xdrs, READDIR3args *objp) { struct nfs_request_lookahead *lkhd = xdrs->x_public ? (struct nfs_request_lookahead *)xdrs->x_public : &dummy_lookahead; if (!xdr_nfs_fh3(xdrs, &objp->dir)) return (false); if (!xdr_cookie3(xdrs, &objp->cookie)) return (false); if (!xdr_cookieverf3(xdrs, objp->cookieverf)) return (false); if (!xdr_count3(xdrs, &objp->count)) return (false); lkhd->flags |= NFS_LOOKAHEAD_READDIR; return (true); } bool xdr_entry3_x(XDR *xdrs, entry3 *objp) { if (!xdr_fileid3(xdrs, &objp->fileid)) return (false); if (!xdr_filename3(xdrs, &objp->name)) return (false); if (!xdr_cookie3(xdrs, &objp->cookie)) return (false); return (true); } bool xdr_entry3(XDR *xdrs, struct entry3 *objp) { /* * more_elements is pre-computed in case the direction is * XDR_ENCODE or XDR_FREE. more_elements is overwritten by * xdr_bool when the direction is XDR_DECODE. */ int freeing; struct entry3 **ent = &objp; struct entry3 **next = NULL; /* pacify gcc */ bool_t more_elements = false; /* yes, bool_t */ assert(xdrs != NULL); assert(objp != NULL); freeing = (xdrs->x_op == XDR_FREE); for (;;) { more_elements = (bool_t)(*ent != NULL); if (!xdr_bool(xdrs, &more_elements)) return (false); if (!more_elements) return (true); /* we are done */ /* * the unfortunate side effect of non-recursion is that in * the case of freeing we must remember the next object * before we free the current object ... */ if (freeing) next = &((*ent)->nextentry); if (!xdr_reference(xdrs, (void **)ent, (u_int)sizeof(struct entry3), (xdrproc_t)xdr_entry3_x)) return (false); ent = (freeing) ? next : &((*ent)->nextentry); } } bool xdr_encode_entry3(XDR *xdrs, entry3 *objp) { bool_t next = objp != NULL; if (!xdr_bool(xdrs, &next)) return false; if (!next) return true; if (!xdr_fileid3(xdrs, &objp->fileid)) return false; if (!xdr_filename3(xdrs, &objp->name)) return false; if (!xdr_cookie3(xdrs, &objp->cookie)) return false; return true; } void xdr_dirlist3_uio_release(struct xdr_uio *uio, u_int flags) { int ix; LogFullDebug(COMPONENT_NFS_READDIR, "Releasing %p, references %" PRIi32 ", count %d", uio, uio->uio_references, (int)uio->uio_count); if (!(--uio->uio_references)) { if (!(op_ctx && op_ctx->is_rdma_buff_used)) { for (ix = 0; ix < uio->uio_count; ix++) gsh_free(uio->uio_vio[ix].vio_base); } gsh_free(uio); } } static inline bool xdr_dirlist3_encode(XDR *xdrs, dirlist3 *objp) { if (!xdr_putbufs(xdrs, objp->uio, UIO_FLAG_NONE)) { objp->uio->uio_release(objp->uio, UIO_FLAG_NONE); return false; } return true; } bool xdr_dirlist3(XDR *xdrs, dirlist3 *objp) { if (objp->uio != NULL) return xdr_dirlist3_encode(xdrs, objp); if (!xdr_pointer(xdrs, (void **)&objp->entries, sizeof(entry3), (xdrproc_t)xdr_entry3)) return (false); if (!xdr_bool(xdrs, &objp->eof)) return (false); return (true); } bool xdr_READDIR3resok(XDR *xdrs, READDIR3resok *objp) { if (!xdr_post_op_attr(xdrs, &objp->dir_attributes)) return (false); if (!xdr_cookieverf3(xdrs, objp->cookieverf)) return (false); if (!xdr_dirlist3(xdrs, &objp->reply)) return (false); return (true); } bool xdr_READDIR3resfail(XDR *xdrs, READDIR3resfail *objp) { if (!xdr_post_op_attr(xdrs, &objp->dir_attributes)) return (false); return (true); } bool xdr_READDIR3res(XDR *xdrs, READDIR3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_READDIR3resok(xdrs, &objp->READDIR3res_u.resok)) return (false); break; default: if (!xdr_READDIR3resfail(xdrs, &objp->READDIR3res_u.resfail)) return (false); break; } return (true); } bool xdr_READDIRPLUS3args(XDR *xdrs, READDIRPLUS3args *objp) { struct nfs_request_lookahead *lkhd = xdrs->x_public ? (struct nfs_request_lookahead *)xdrs->x_public : &dummy_lookahead; if (!xdr_nfs_fh3(xdrs, &objp->dir)) return (false); if (!xdr_cookie3(xdrs, &objp->cookie)) return (false); if (!xdr_cookieverf3(xdrs, objp->cookieverf)) return (false); if (!xdr_count3(xdrs, &objp->dircount)) return (false); if (!xdr_count3(xdrs, &objp->maxcount)) return (false); lkhd->flags |= NFS_LOOKAHEAD_READDIR; return (true); } bool xdr_entryplus3_x(XDR *xdrs, entryplus3 *objp) { if (!xdr_fileid3(xdrs, &objp->fileid)) return (false); if (!xdr_filename3(xdrs, &objp->name)) return (false); if (!xdr_cookie3(xdrs, &objp->cookie)) return (false); if (!xdr_post_op_attr(xdrs, &objp->name_attributes)) return (false); if (!xdr_post_op_fh3(xdrs, &objp->name_handle)) return (false); return (true); } bool xdr_entryplus3(XDR *xdrs, struct entryplus3 *objp) { /* * more_elements is pre-computed in case the direction is * XDR_ENCODE or XDR_FREE. more_elements is overwritten by * xdr_bool when the direction is XDR_DECODE. */ int freeing; struct entryplus3 **ent = &objp; struct entryplus3 **next = NULL; /* pacify gcc */ bool_t more_elements = false; /* yes, bool_t */ assert(xdrs != NULL); assert(objp != NULL); freeing = (xdrs->x_op == XDR_FREE); for (;;) { more_elements = (bool_t)(*ent != NULL); if (!xdr_bool(xdrs, &more_elements)) return (false); if (!more_elements) return (true); /* we are done */ /* * the unfortunate side effect of non-recursion is that in * the case of freeing we must remember the next object * before we free the current object ... */ if (freeing) next = &((*ent)->nextentry); if (!xdr_reference(xdrs, (void **)ent, (u_int)sizeof(struct entryplus3), (xdrproc_t)xdr_entryplus3_x)) return (false); ent = (freeing) ? next : &((*ent)->nextentry); } } bool xdr_encode_entryplus3(XDR *xdrs, entryplus3 *objp, const fattr3 *attrs) { bool_t next = objp != NULL; if (!xdr_bool(xdrs, &next)) return false; if (!next) return true; if (!xdr_fileid3(xdrs, &objp->fileid)) return false; if (!xdr_filename3(xdrs, &objp->name)) return false; if (!xdr_cookie3(xdrs, &objp->cookie)) return false; if (!xdr_bool(xdrs, &objp->name_attributes.attributes_follow)) return false; if (objp->name_attributes.attributes_follow) { if (!xdr_fattr3(xdrs, (fattr3 *)attrs)) return false; } if (!xdr_post_op_fh3(xdrs, &objp->name_handle)) return false; return true; } void xdr_dirlistplus3_uio_release(struct xdr_uio *uio, u_int flags) { int ix; LogFullDebug(COMPONENT_NFS_READDIR, "Releasing %p, references %" PRIi32 ", count %d", uio, uio->uio_references, (int)uio->uio_count); if (!(--uio->uio_references)) { if (!(op_ctx && op_ctx->is_rdma_buff_used)) { for (ix = 0; ix < uio->uio_count; ix++) gsh_free(uio->uio_vio[ix].vio_base); } gsh_free(uio); } } static inline bool xdr_dirlistplus3_encode(XDR *xdrs, dirlistplus3 *objp) { if (!xdr_putbufs(xdrs, objp->uio, UIO_FLAG_NONE)) { objp->uio->uio_release(objp->uio, UIO_FLAG_NONE); return false; } return true; } bool xdr_dirlistplus3(XDR *xdrs, dirlistplus3 *objp) { if (objp->uio != NULL) return xdr_dirlistplus3_encode(xdrs, objp); if (!xdr_pointer(xdrs, (void **)&objp->entries, sizeof(entryplus3), (xdrproc_t)xdr_entryplus3)) return (false); if (!xdr_bool(xdrs, &objp->eof)) return (false); return (true); } bool xdr_READDIRPLUS3resok(XDR *xdrs, READDIRPLUS3resok *objp) { if (!xdr_post_op_attr(xdrs, &objp->dir_attributes)) return (false); if (!xdr_cookieverf3(xdrs, objp->cookieverf)) return (false); if (!xdr_dirlistplus3(xdrs, &objp->reply)) return (false); return (true); } bool xdr_READDIRPLUS3resfail(XDR *xdrs, READDIRPLUS3resfail *objp) { if (!xdr_post_op_attr(xdrs, &objp->dir_attributes)) return (false); return (true); } bool xdr_READDIRPLUS3res(XDR *xdrs, READDIRPLUS3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_READDIRPLUS3resok(xdrs, &objp->READDIRPLUS3res_u.resok)) return (false); break; default: if (!xdr_READDIRPLUS3resfail(xdrs, &objp->READDIRPLUS3res_u.resfail)) return (false); break; } return (true); } bool xdr_FSSTAT3args(XDR *xdrs, FSSTAT3args *objp) { if (!xdr_nfs_fh3(xdrs, &objp->fsroot)) return (false); return (true); } bool xdr_FSSTAT3resok(XDR *xdrs, FSSTAT3resok *objp) { if (!xdr_post_op_attr(xdrs, &objp->obj_attributes)) return (false); if (!xdr_size3(xdrs, &objp->tbytes)) return (false); if (!xdr_size3(xdrs, &objp->fbytes)) return (false); if (!xdr_size3(xdrs, &objp->abytes)) return (false); if (!xdr_size3(xdrs, &objp->tfiles)) return (false); if (!xdr_size3(xdrs, &objp->ffiles)) return (false); if (!xdr_size3(xdrs, &objp->afiles)) return (false); if (!xdr_nfs3_uint32(xdrs, &objp->invarsec)) return (false); return (true); } bool xdr_FSSTAT3resfail(XDR *xdrs, FSSTAT3resfail *objp) { if (!xdr_post_op_attr(xdrs, &objp->obj_attributes)) return (false); return (true); } bool xdr_FSSTAT3res(XDR *xdrs, FSSTAT3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_FSSTAT3resok(xdrs, &objp->FSSTAT3res_u.resok)) return (false); break; default: if (!xdr_FSSTAT3resfail(xdrs, &objp->FSSTAT3res_u.resfail)) return (false); break; } return (true); } bool xdr_FSINFO3args(XDR *xdrs, FSINFO3args *objp) { if (!xdr_nfs_fh3(xdrs, &objp->fsroot)) return (false); return (true); } bool xdr_FSINFO3resok(XDR *xdrs, FSINFO3resok *objp) { if (!xdr_post_op_attr(xdrs, &objp->obj_attributes)) return (false); if (!xdr_nfs3_uint32(xdrs, &objp->rtmax)) return (false); if (!xdr_nfs3_uint32(xdrs, &objp->rtpref)) return (false); if (!xdr_nfs3_uint32(xdrs, &objp->rtmult)) return (false); if (!xdr_nfs3_uint32(xdrs, &objp->wtmax)) return (false); if (!xdr_nfs3_uint32(xdrs, &objp->wtpref)) return (false); if (!xdr_nfs3_uint32(xdrs, &objp->wtmult)) return (false); if (!xdr_nfs3_uint32(xdrs, &objp->dtpref)) return (false); if (!xdr_size3(xdrs, &objp->maxfilesize)) return (false); if (!xdr_nfstime3(xdrs, &objp->time_delta)) return (false); if (!xdr_nfs3_uint32(xdrs, &objp->properties)) return (false); return (true); } bool xdr_FSINFO3resfail(XDR *xdrs, FSINFO3resfail *objp) { if (!xdr_post_op_attr(xdrs, &objp->obj_attributes)) return (false); return (true); } bool xdr_FSINFO3res(XDR *xdrs, FSINFO3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_FSINFO3resok(xdrs, &objp->FSINFO3res_u.resok)) return (false); break; default: if (!xdr_FSINFO3resfail(xdrs, &objp->FSINFO3res_u.resfail)) return (false); break; } return (true); } bool xdr_PATHCONF3args(XDR *xdrs, PATHCONF3args *objp) { if (!xdr_nfs_fh3(xdrs, &objp->object)) return (false); return (true); } bool xdr_PATHCONF3resok(XDR *xdrs, PATHCONF3resok *objp) { if (!xdr_post_op_attr(xdrs, &objp->obj_attributes)) return (false); if (!xdr_nfs3_uint32(xdrs, &objp->linkmax)) return (false); if (!xdr_nfs3_uint32(xdrs, &objp->name_max)) return (false); if (!xdr_bool(xdrs, &objp->no_trunc)) return (false); if (!xdr_bool(xdrs, &objp->chown_restricted)) return (false); if (!xdr_bool(xdrs, &objp->case_insensitive)) return (false); if (!xdr_bool(xdrs, &objp->case_preserving)) return (false); return (true); } bool xdr_PATHCONF3resfail(XDR *xdrs, PATHCONF3resfail *objp) { if (!xdr_post_op_attr(xdrs, &objp->obj_attributes)) return (false); return (true); } bool xdr_PATHCONF3res(XDR *xdrs, PATHCONF3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_PATHCONF3resok(xdrs, &objp->PATHCONF3res_u.resok)) return (false); break; default: if (!xdr_PATHCONF3resfail(xdrs, &objp->PATHCONF3res_u.resfail)) return (false); break; } return (true); } bool xdr_COMMIT3args(XDR *xdrs, COMMIT3args *objp) { struct nfs_request_lookahead *lkhd = xdrs->x_public ? (struct nfs_request_lookahead *)xdrs->x_public : &dummy_lookahead; if (!xdr_nfs_fh3(xdrs, &objp->file)) return (false); if (!xdr_offset3(xdrs, &objp->offset)) return (false); if (!xdr_count3(xdrs, &objp->count)) return (false); lkhd->flags |= NFS_LOOKAHEAD_COMMIT; return (true); } bool xdr_COMMIT3resok(XDR *xdrs, COMMIT3resok *objp) { if (!xdr_wcc_data(xdrs, &objp->file_wcc)) return (false); if (!xdr_writeverf3(xdrs, objp->verf)) return (false); return (true); } bool xdr_COMMIT3resfail(XDR *xdrs, COMMIT3resfail *objp) { if (!xdr_wcc_data(xdrs, &objp->file_wcc)) return (false); return (true); } bool xdr_COMMIT3res(XDR *xdrs, COMMIT3res *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return (false); switch (objp->status) { case NFS3_OK: if (!xdr_COMMIT3resok(xdrs, &objp->COMMIT3res_u.resok)) return (false); break; default: if (!xdr_COMMIT3resfail(xdrs, &objp->COMMIT3res_u.resfail)) return (false); break; } return (true); } nfs-ganesha-6.5/src/Protocols/XDR/xdr_nfsacl.c000066400000000000000000000104701473756622300212760ustar00rootroot00000000000000/* SPDX-License-Identifier: unknown license... */ /* * The content of this file is a mix of rpcgen-generated * and hand-edited program text. It is not automatically * generated by, e.g., build processes. * * This file is under version control. */ #include "nfs23.h" #include "nfsacl.h" bool_t xdr_attr3(XDR *xdrs, attr3 *objp) { if (!xdr_bool(xdrs, &objp->attributes_follow)) return FALSE; switch (objp->attributes_follow) { case TRUE: if (!xdr_fattr3(xdrs, &objp->attr3_u.obj_attributes)) return FALSE; break; case FALSE: break; default: return FALSE; } return TRUE; } bool_t xdr_posix_acl_entry(XDR *xdrs, posix_acl_entry *objp) { if (!xdr_nfs3_uint32(xdrs, &objp->e_tag)) return FALSE; if (!xdr_nfs3_uint32(xdrs, &objp->e_id)) return FALSE; if (!xdr_nfs3_uint32(xdrs, &objp->e_perm)) return FALSE; return TRUE; } bool_t xdr_posix_acl(XDR *xdrs, posix_acl *objp) { if (!xdr_nfs3_uint32(xdrs, &objp->count)) return FALSE; if (objp->count > 4096) return FALSE; if (!xdr_vector(xdrs, (char *)objp->entries, objp->count, sizeof(posix_acl_entry), (xdrproc_t)xdr_posix_acl_entry)) return FALSE; return TRUE; } bool_t xdr_getaclargs(XDR *xdrs, getaclargs *objp) { if (!xdr_nfs_fh3(xdrs, &objp->fhandle)) return FALSE; if (!xdr_nfs3_int32(xdrs, &objp->mask)) return FALSE; return TRUE; } bool_t xdr_getaclresok(XDR *xdrs, getaclresok *objp) { if (!xdr_attr3(xdrs, &objp->attr)) return FALSE; if (!xdr_nfs3_int32(xdrs, &objp->mask)) return FALSE; if (!xdr_nfs3_uint32(xdrs, &objp->acl_access_count)) return FALSE; if (objp->acl_access != NULL) { if (!xdr_reference(xdrs, (void **)&objp->acl_access, sizeof(posix_acl) + objp->acl_access_count * sizeof(posix_acl_entry), (xdrproc_t)xdr_posix_acl)) return FALSE; } else { if (!xdr_pointer(xdrs, (void **)&objp->acl_access, sizeof(posix_acl) + objp->acl_access_count * sizeof(posix_acl_entry), (xdrproc_t)xdr_posix_acl)) return FALSE; } if (!xdr_nfs3_uint32(xdrs, &objp->acl_default_count)) return FALSE; if (objp->acl_default != NULL) { if (!xdr_reference(xdrs, (void **)&objp->acl_default, sizeof(posix_acl) + objp->acl_default_count * sizeof(posix_acl_entry), (xdrproc_t)xdr_posix_acl)) return FALSE; } else { if (!xdr_pointer(xdrs, (void **)&objp->acl_default, sizeof(posix_acl) + objp->acl_default_count * sizeof(posix_acl_entry), (xdrproc_t)xdr_posix_acl)) return FALSE; } return TRUE; } bool_t xdr_getaclres(XDR *xdrs, getaclres *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return FALSE; switch (objp->status) { case NFS3_OK: if (!xdr_getaclresok(xdrs, &objp->getaclres_u.resok)) return FALSE; break; default: break; } return TRUE; } bool_t xdr_setaclargs(XDR *xdrs, setaclargs *objp) { if (!xdr_nfs_fh3(xdrs, &objp->fhandle)) return FALSE; if (!xdr_nfs3_int32(xdrs, &objp->mask)) return FALSE; if (!xdr_nfs3_uint32(xdrs, &objp->acl_access_count)) return FALSE; if (xdrs->x_op == XDR_DECODE) { if (!xdr_reference(xdrs, (void **)&objp->acl_access, sizeof(posix_acl) + objp->acl_access_count * sizeof(posix_acl_entry), (xdrproc_t)xdr_posix_acl)) return FALSE; } else { if (!xdr_pointer(xdrs, (void **)&objp->acl_access, sizeof(posix_acl) + objp->acl_access_count * sizeof(posix_acl_entry), (xdrproc_t)xdr_posix_acl)) return FALSE; } if (!xdr_nfs3_uint32(xdrs, &objp->acl_default_count)) return FALSE; if (xdrs->x_op == XDR_DECODE) { if (!xdr_reference(xdrs, (void **)&objp->acl_default, sizeof(posix_acl) + objp->acl_default_count * sizeof(posix_acl_entry), (xdrproc_t)xdr_posix_acl)) return FALSE; } else { if (!xdr_pointer(xdrs, (void **)&objp->acl_default, sizeof(posix_acl) + objp->acl_default_count * sizeof(posix_acl_entry), (xdrproc_t)xdr_posix_acl)) return FALSE; } return TRUE; } bool_t xdr_setaclresok(XDR *xdrs, setaclresok *objp) { if (!xdr_attr3(xdrs, &objp->attr)) return FALSE; return TRUE; } bool_t xdr_setaclres(XDR *xdrs, setaclres *objp) { if (!xdr_nfsstat3(xdrs, &objp->status)) return FALSE; switch (objp->status) { case NFS3_OK: if (!xdr_setaclresok(xdrs, &objp->setaclres_u.resok)) return FALSE; break; default: break; } return TRUE; } nfs-ganesha-6.5/src/Protocols/XDR/xdr_nfsv41.c000066400000000000000000000043161473756622300211530ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* * Copyright (c) 2012 IETF Trust and the persons identified * as authors of the code. All rights reserved. * * Redistribution and use in source and binary forms, with * or without modification, are permitted provided that the * following conditions are met: * * o Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * o 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. * * o Neither the name of Internet Society, IETF or IETF * Trust, nor the names of specific contributors, may be * used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS * 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 COPYRIGHT OWNER 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 code was derived from RFCTBD10. * Please reproduce this note if possible. */ /* * The content of this file is a mix of rpcgen-generated * and hand-edited program text. It is not automatically * generated by, e.g., build processes. * * This file is under version control. */ #include "config.h" #include "nfsv41.h" /* Most Ganesha XDR decoder routines are inlines, defined in * nfsv41.h. The present file is retained as a location for * XDR decoder routines with external linkage. */ nfs-ganesha-6.5/src/Protocols/XDR/xdr_nlm4.c000066400000000000000000000107071473756622300207050ustar00rootroot00000000000000/* SPDX-License-Identifier: unknown license... */ /* * Please do not edit this file. * It was generated using rpcgen. */ #include "config.h" #include "gsh_rpc.h" #include "nlm4.h" #include "nfs_fh.h" bool xdr_nlm4_stats(XDR *xdrs, nlm4_stats *objp) { if (!xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } bool xdr_nlm4_stat(XDR *xdrs, nlm4_stat *objp) { if (!xdr_nlm4_stats(xdrs, &objp->stat)) return false; return true; } bool xdr_nlm4_res(XDR *xdrs, nlm4_res *objp) { if (!xdr_netobj(xdrs, &objp->cookie)) return false; if (!xdr_nlm4_stat(xdrs, &objp->stat)) return false; return true; } bool xdr_nlm4_holder(XDR *xdrs, nlm4_holder *objp) { if (!xdr_bool(xdrs, &objp->exclusive)) return false; if (!xdr_int32_t(xdrs, &objp->svid)) return false; if (!xdr_netobj(xdrs, &objp->oh)) return false; if (!xdr_uint64_t(xdrs, &objp->l_offset)) return false; if (!xdr_uint64_t(xdrs, &objp->l_len)) return false; return true; } bool xdr_nlm4_testrply(XDR *xdrs, nlm4_testrply *objp) { if (!xdr_nlm4_stats(xdrs, &objp->stat)) return false; switch (objp->stat) { case NLM4_DENIED: if (!xdr_nlm4_holder(xdrs, &objp->nlm4_testrply_u.holder)) return false; break; default: break; } return true; } bool xdr_nlm4_testres(XDR *xdrs, nlm4_testres *objp) { if (!xdr_netobj(xdrs, &objp->cookie)) return false; if (!xdr_nlm4_testrply(xdrs, &objp->test_stat)) return false; return true; } bool xdr_nlm4_lock(XDR *xdrs, nlm4_lock *objp) { if (!xdr_string(xdrs, &objp->caller_name, LM_MAXSTRLEN)) return false; if (!xdr_netobj(xdrs, &objp->fh)) return false; if (!xdr_netobj(xdrs, &objp->oh)) return false; if (!xdr_int32_t(xdrs, &objp->svid)) return false; if (!xdr_uint64_t(xdrs, &objp->l_offset)) return false; if (!xdr_uint64_t(xdrs, &objp->l_len)) return false; return true; } bool xdr_nlm4_lockargs(XDR *xdrs, nlm4_lockargs *objp) { if (!xdr_netobj(xdrs, &objp->cookie)) return false; if (!xdr_bool(xdrs, &objp->block)) return false; if (!xdr_bool(xdrs, &objp->exclusive)) return false; if (!xdr_nlm4_lock(xdrs, &objp->alock)) return false; if (!xdr_bool(xdrs, &objp->reclaim)) return false; if (!xdr_int32_t(xdrs, &objp->state)) return false; return true; } bool xdr_nlm4_cancargs(XDR *xdrs, nlm4_cancargs *objp) { if (!xdr_netobj(xdrs, &objp->cookie)) return false; if (!xdr_bool(xdrs, &objp->block)) return false; if (!xdr_bool(xdrs, &objp->exclusive)) return false; if (!xdr_nlm4_lock(xdrs, &objp->alock)) return false; return true; } bool xdr_nlm4_testargs(XDR *xdrs, nlm4_testargs *objp) { if (!xdr_netobj(xdrs, &objp->cookie)) return false; if (!xdr_bool(xdrs, &objp->exclusive)) return false; if (!xdr_nlm4_lock(xdrs, &objp->alock)) return false; return true; } bool xdr_nlm4_unlockargs(XDR *xdrs, nlm4_unlockargs *objp) { if (!xdr_netobj(xdrs, &objp->cookie)) return false; if (!xdr_nlm4_lock(xdrs, &objp->alock)) return false; return true; } bool xdr_fsh4_mode(XDR *xdrs, fsh4_mode *objp) { if (!xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } bool xdr_fsh4_access(XDR *xdrs, fsh4_access *objp) { if (!xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } bool xdr_nlm4_share(XDR *xdrs, nlm4_share *objp) { if (!xdr_string(xdrs, &objp->caller_name, LM_MAXSTRLEN)) return false; if (!xdr_netobj(xdrs, &objp->fh)) return false; if (!xdr_netobj(xdrs, &objp->oh)) return false; if (!xdr_fsh4_mode(xdrs, &objp->mode)) return false; if (!xdr_fsh4_access(xdrs, &objp->access)) return false; return true; } bool xdr_nlm4_shareargs(XDR *xdrs, nlm4_shareargs *objp) { if (!xdr_netobj(xdrs, &objp->cookie)) return false; if (!xdr_nlm4_share(xdrs, &objp->share)) return false; if (!xdr_bool(xdrs, &objp->reclaim)) return false; return true; } bool xdr_nlm4_shareres(XDR *xdrs, nlm4_shareres *objp) { if (!xdr_netobj(xdrs, &objp->cookie)) return false; if (!xdr_nlm4_stats(xdrs, &objp->stat)) return false; if (!xdr_int32_t(xdrs, &objp->sequence)) return false; return true; } bool xdr_nlm4_free_allargs(XDR *xdrs, nlm4_free_allargs *objp) { if (!xdr_string(xdrs, &objp->name, LM_MAXNAMELEN)) return false; if (!xdr_uint32_t(xdrs, &objp->state)) return false; return true; } bool xdr_nlm4_sm_notifyargs(XDR *xdrs, nlm4_sm_notifyargs *objp) { if (!xdr_string(xdrs, &objp->name, SM_MAXSTRLEN)) return false; if (!xdr_int32_t(xdrs, &objp->state)) return false; if (!xdr_opaque(xdrs, objp->priv, SM_PRIV_SZ)) return false; return true; } nfs-ganesha-6.5/src/Protocols/XDR/xdr_nsm.c000066400000000000000000000025401473756622300206240ustar00rootroot00000000000000/* SPDX-License-Identifier: unknown license... */ /* * Please do not edit this file. * It was generated using rpcgen. */ #include "config.h" #include "nsm.h" bool xdr_res(XDR *xdrs, res *objp) { if (!xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } bool xdr_sm_stat_res(XDR *xdrs, sm_stat_res *objp) { if (!xdr_res(xdrs, &objp->res_stat)) return false; if (!xdr_int(xdrs, &objp->state)) return false; return true; } bool xdr_sm_stat(XDR *xdrs, sm_stat *objp) { if (!xdr_int(xdrs, &objp->state)) return false; return true; } bool xdr_my_id(XDR *xdrs, my_id *objp) { if (!xdr_string(xdrs, &objp->my_name, SM_MAXSTRLEN)) return false; if (!xdr_int(xdrs, &objp->my_prog)) return false; if (!xdr_int(xdrs, &objp->my_vers)) return false; if (!xdr_int(xdrs, &objp->my_proc)) return false; return true; } bool xdr_mon_id(XDR *xdrs, mon_id *objp) { if (!xdr_string(xdrs, &objp->mon_name, SM_MAXSTRLEN)) return false; if (!xdr_my_id(xdrs, &objp->my_id)) return false; return true; } bool xdr_mon(XDR *xdrs, mon *objp) { if (!xdr_mon_id(xdrs, &objp->mon_id)) return false; if (!xdr_opaque(xdrs, objp->priv, 16)) return false; return true; } bool xdr_notify(XDR *xdrs, notify *objp) { if (!xdr_string(xdrs, &objp->my_name, SM_MAXSTRLEN)) return false; if (!xdr_int(xdrs, &objp->state)) return false; return true; } nfs-ganesha-6.5/src/Protocols/XDR/xdr_rquota.c000066400000000000000000000203761473756622300213510ustar00rootroot00000000000000/* SPDX-License-Identifier: unknown license... */ /* * Hand updated. * It was generated using rpcgen. */ #include "config.h" #include "rquota.h" bool xdr_sq_dqblk(XDR *xdrs, sq_dqblk *objp) { register int32_t *buf; if (xdrs->x_op == XDR_ENCODE) { buf = xdr_inline_encode(xdrs, 8 * BYTES_PER_XDR_UNIT); if (buf != NULL) { /* most likely */ IXDR_PUT_U_INT32(buf, objp->rq_bhardlimit); IXDR_PUT_U_INT32(buf, objp->rq_bsoftlimit); IXDR_PUT_U_INT32(buf, objp->rq_curblocks); IXDR_PUT_U_INT32(buf, objp->rq_fhardlimit); IXDR_PUT_U_INT32(buf, objp->rq_fsoftlimit); IXDR_PUT_U_INT32(buf, objp->rq_curfiles); IXDR_PUT_U_INT32(buf, objp->rq_btimeleft); IXDR_PUT_U_INT32(buf, objp->rq_ftimeleft); } else { if (!XDR_PUTUINT32(xdrs, objp->rq_bhardlimit)) return false; if (!XDR_PUTUINT32(xdrs, objp->rq_bsoftlimit)) return false; if (!XDR_PUTUINT32(xdrs, objp->rq_curblocks)) return false; if (!XDR_PUTUINT32(xdrs, objp->rq_fhardlimit)) return false; if (!XDR_PUTUINT32(xdrs, objp->rq_fsoftlimit)) return false; if (!XDR_PUTUINT32(xdrs, objp->rq_curfiles)) return false; if (!XDR_PUTUINT32(xdrs, objp->rq_btimeleft)) return false; if (!XDR_PUTUINT32(xdrs, objp->rq_ftimeleft)) return false; } return true; } if (xdrs->x_op == XDR_DECODE) { buf = xdr_inline_decode(xdrs, 8 * BYTES_PER_XDR_UNIT); if (buf != NULL) { /* most likely */ objp->rq_bhardlimit = IXDR_GET_U_INT32(buf); objp->rq_bsoftlimit = IXDR_GET_U_INT32(buf); objp->rq_curblocks = IXDR_GET_U_INT32(buf); objp->rq_fhardlimit = IXDR_GET_U_INT32(buf); objp->rq_fsoftlimit = IXDR_GET_U_INT32(buf); objp->rq_curfiles = IXDR_GET_U_INT32(buf); objp->rq_btimeleft = IXDR_GET_U_INT32(buf); objp->rq_ftimeleft = IXDR_GET_U_INT32(buf); } else { if (!XDR_GETUINT32(xdrs, &objp->rq_bhardlimit)) return false; if (!XDR_GETUINT32(xdrs, &objp->rq_bsoftlimit)) return false; if (!XDR_GETUINT32(xdrs, &objp->rq_curblocks)) return false; if (!XDR_GETUINT32(xdrs, &objp->rq_fhardlimit)) return false; if (!XDR_GETUINT32(xdrs, &objp->rq_fsoftlimit)) return false; if (!XDR_GETUINT32(xdrs, &objp->rq_curfiles)) return false; if (!XDR_GETUINT32(xdrs, &objp->rq_btimeleft)) return false; if (!XDR_GETUINT32(xdrs, &objp->rq_ftimeleft)) return false; } return true; } if (!xdr_u_int(xdrs, &objp->rq_bhardlimit)) return false; if (!xdr_u_int(xdrs, &objp->rq_bsoftlimit)) return false; if (!xdr_u_int(xdrs, &objp->rq_curblocks)) return false; if (!xdr_u_int(xdrs, &objp->rq_fhardlimit)) return false; if (!xdr_u_int(xdrs, &objp->rq_fsoftlimit)) return false; if (!xdr_u_int(xdrs, &objp->rq_curfiles)) return false; if (!xdr_u_int(xdrs, &objp->rq_btimeleft)) return false; if (!xdr_u_int(xdrs, &objp->rq_ftimeleft)) return false; return true; } bool xdr_getquota_args(XDR *xdrs, getquota_args *objp) { register __attribute__((__unused__)) int32_t *buf; if (!xdr_string(xdrs, &objp->gqa_pathp, RQ_PATHLEN)) return false; if (!xdr_int(xdrs, &objp->gqa_uid)) return false; return true; } bool xdr_setquota_args(XDR *xdrs, setquota_args *objp) { register __attribute__((__unused__)) int32_t *buf; if (!xdr_int(xdrs, &objp->sqa_qcmd)) return false; if (!xdr_string(xdrs, &objp->sqa_pathp, RQ_PATHLEN)) return false; if (!xdr_int(xdrs, &objp->sqa_id)) return false; if (!xdr_sq_dqblk(xdrs, &objp->sqa_dqblk)) return false; return true; } bool xdr_ext_getquota_args(XDR *xdrs, ext_getquota_args *objp) { register __attribute__((__unused__)) int32_t *buf; if (!xdr_string(xdrs, &objp->gqa_pathp, RQ_PATHLEN)) return false; if (!xdr_int(xdrs, &objp->gqa_type)) return false; if (!xdr_int(xdrs, &objp->gqa_id)) return false; return true; } bool xdr_ext_setquota_args(XDR *xdrs, ext_setquota_args *objp) { register __attribute__((__unused__)) int32_t *buf; if (!xdr_int(xdrs, &objp->sqa_qcmd)) return false; if (!xdr_string(xdrs, &objp->sqa_pathp, RQ_PATHLEN)) return false; if (!xdr_int(xdrs, &objp->sqa_id)) return false; if (!xdr_int(xdrs, &objp->sqa_type)) return false; if (!xdr_sq_dqblk(xdrs, &objp->sqa_dqblk)) return false; return true; } bool xdr_rquota(XDR *xdrs, rquota *objp) { register int32_t *buf; if (xdrs->x_op == XDR_ENCODE) { buf = xdr_inline_encode(xdrs, 10 * BYTES_PER_XDR_UNIT); if (buf != NULL) { /* most likely */ IXDR_PUT_INT32(buf, objp->rq_bsize); IXDR_PUT_BOOL(buf, objp->rq_active); IXDR_PUT_U_INT32(buf, objp->rq_bhardlimit); IXDR_PUT_U_INT32(buf, objp->rq_bsoftlimit); IXDR_PUT_U_INT32(buf, objp->rq_curblocks); IXDR_PUT_U_INT32(buf, objp->rq_fhardlimit); IXDR_PUT_U_INT32(buf, objp->rq_fsoftlimit); IXDR_PUT_U_INT32(buf, objp->rq_curfiles); IXDR_PUT_U_INT32(buf, objp->rq_btimeleft); IXDR_PUT_U_INT32(buf, objp->rq_ftimeleft); } else { if (!XDR_PUTINT32(xdrs, objp->rq_bsize)) return false; if (!XDR_PUTBOOL(xdrs, objp->rq_active)) return false; if (!XDR_PUTUINT32(xdrs, objp->rq_bhardlimit)) return false; if (!XDR_PUTUINT32(xdrs, objp->rq_bsoftlimit)) return false; if (!XDR_PUTUINT32(xdrs, objp->rq_curblocks)) return false; if (!XDR_PUTUINT32(xdrs, objp->rq_fhardlimit)) return false; if (!XDR_PUTUINT32(xdrs, objp->rq_fsoftlimit)) return false; if (!XDR_PUTUINT32(xdrs, objp->rq_curfiles)) return false; if (!XDR_PUTUINT32(xdrs, objp->rq_btimeleft)) return false; if (!XDR_PUTUINT32(xdrs, objp->rq_ftimeleft)) return false; } return true; } if (xdrs->x_op == XDR_DECODE) { buf = xdr_inline_decode(xdrs, 10 * BYTES_PER_XDR_UNIT); if (buf != NULL) { /* most likely */ objp->rq_bsize = IXDR_GET_INT32(buf); objp->rq_active = IXDR_GET_BOOL(buf); objp->rq_bhardlimit = IXDR_GET_U_INT32(buf); objp->rq_bsoftlimit = IXDR_GET_U_INT32(buf); objp->rq_curblocks = IXDR_GET_U_INT32(buf); objp->rq_fhardlimit = IXDR_GET_U_INT32(buf); objp->rq_fsoftlimit = IXDR_GET_U_INT32(buf); objp->rq_curfiles = IXDR_GET_U_INT32(buf); objp->rq_btimeleft = IXDR_GET_U_INT32(buf); objp->rq_ftimeleft = IXDR_GET_U_INT32(buf); } else { if (!XDR_GETINT32(xdrs, &objp->rq_bsize)) return false; if (!XDR_GETBOOL(xdrs, &objp->rq_active)) return false; if (!XDR_GETUINT32(xdrs, &objp->rq_bhardlimit)) return false; if (!XDR_GETUINT32(xdrs, &objp->rq_bsoftlimit)) return false; if (!XDR_GETUINT32(xdrs, &objp->rq_curblocks)) return false; if (!XDR_GETUINT32(xdrs, &objp->rq_fhardlimit)) return false; if (!XDR_GETUINT32(xdrs, &objp->rq_fsoftlimit)) return false; if (!XDR_GETUINT32(xdrs, &objp->rq_curfiles)) return false; if (!XDR_GETUINT32(xdrs, &objp->rq_btimeleft)) return false; if (!XDR_GETUINT32(xdrs, &objp->rq_ftimeleft)) return false; } return true; } if (!xdr_int(xdrs, &objp->rq_bsize)) return false; if (!xdr_bool(xdrs, &objp->rq_active)) return false; if (!xdr_u_int(xdrs, &objp->rq_bhardlimit)) return false; if (!xdr_u_int(xdrs, &objp->rq_bsoftlimit)) return false; if (!xdr_u_int(xdrs, &objp->rq_curblocks)) return false; if (!xdr_u_int(xdrs, &objp->rq_fhardlimit)) return false; if (!xdr_u_int(xdrs, &objp->rq_fsoftlimit)) return false; if (!xdr_u_int(xdrs, &objp->rq_curfiles)) return false; if (!xdr_u_int(xdrs, &objp->rq_btimeleft)) return false; if (!xdr_u_int(xdrs, &objp->rq_ftimeleft)) return false; return true; } bool xdr_qr_status(XDR *xdrs, qr_status *objp) { register __attribute__((__unused__)) int32_t *buf; if (!xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } bool xdr_getquota_rslt(XDR *xdrs, getquota_rslt *objp) { register __attribute__((__unused__)) int32_t *buf; if (!xdr_qr_status(xdrs, &objp->status)) return false; switch (objp->status) { case Q_OK: if (!xdr_rquota(xdrs, &objp->getquota_rslt_u.gqr_rquota)) return false; break; case Q_NOQUOTA: break; case Q_EPERM: break; default: return false; } return true; } bool xdr_setquota_rslt(XDR *xdrs, setquota_rslt *objp) { register __attribute__((__unused__)) int32_t *buf; if (!xdr_qr_status(xdrs, &objp->status)) return false; switch (objp->status) { case Q_OK: if (!xdr_rquota(xdrs, &objp->setquota_rslt_u.sqr_rquota)) return false; break; case Q_NOQUOTA: break; case Q_EPERM: break; default: return false; } return true; } nfs-ganesha-6.5/src/README.new_api000066400000000000000000000356301473756622300166740ustar00rootroot00000000000000Request for Comments: New fsal api This README will be expanded as the work progresses. It will/should be turned into a fsal writer's HOWTO at some point. This is a work in progress and therefore still in flux at this point. Note that this document is pretty out of date, but the general scheme is applicable. It pre-dates the conversion from Cache Inode to MDCACHE. State ----- The server builds and runs to the point of processing exports. Only the VFS fsal is functional at this point. Debugging continues. This branch is based on the latest stable tag of the mainline 'next' branch. The first patch of the new api work is this file, titled: "Add README.new_api" Contents -------- The patch series comes in four parts. The "Revert USE_SHARED configuration option from code" is the second commit which prepares the code base for the new api. New infrastructure These are new files in src/FSAL which implement common fsal functions. I have squashed all my incremental changes so that you have a single file to look at for current state. These are subject to further change and squashing. VFS fsal These are new files that implement the VFS fsal over the new infrastructure. The main.c file is the fsal object itself followed by export.c which implements fsal_export for VFS. handle.c implements fsal_obj_object. File management for handles is in file.c and extended attributes, not implemented yet for VFS are in xattr.c. FSAL initialization This set of changes to the main server load and initialize the fsals. Once they are all loaded, fsal_export objects are created as part of the export list initialization. Cache Inode changes Starting with the commit titled "Change cache_inode.h to use new api" we invade the cache_inode_* code. This series of patches replaces fsal_handle_t with a pointer to an allocated fsal_obj_handle and the various FSAL_* calls relevant to it. The readdir operation has been changed to use a callback. This greatly simplifies the FSAL method and eliminates multiple loops, fixed array storage, and a lot of extra work. File descriptors are removed from the API and will will be deprecated entirely from cache entries. An fd is a property of the FSAL and will be managed (cached) there. The VFS fsal and other POSIX based interfaces have an fd but library based FSALs have other structures and apis so fileno and friends are deprecated in the core. Protocol These files manage the protocol on one side and access to the cache entries in CacheInode on the other. The biggest change is to replace fsal_op_context in the compound structure with struct user_cred user_credentials. A large number of places where pcontext was passed around and were (or are no longer) used. These have simply been removed from the parameter lists. State and Locking fsal_op_context pcontext has been for the most part removed from these files. A few places require user credentials for looking up handles etc. As with protocol, user_credentials takes its place. Configuration File Changes -------------------------- In order to support multiple FSAL definitions in the configuration, the configuration file now accepts a new syntax for FSAL initialization. The following fragment defines the loading of the VFS fsal. ################################################### # # FSAL parameters. # # To use the default value for a parameter, # just comment the associated line. # ################################################### FSAL { LogLevel = "Red Alert"; Foo = "baz"; VFS { FSAL_Shared_Library = "/usr/local/lib/libfsalvfs.so"; # logging level (NIV_FULL_DEBUG, NIV_DEBUG, # NIV_EVNMT, NIV_CRIT, NIV_MAJ, NIV_NULL) DebugLevel = "NIV_FULL_DEBUG" ; # Logging file #Options: "/var/log/nfs-ganesha.log" or some other file path # "SYSLOG" prints to syslog # "STDERR" prints stderr messages to the console that # started the ganesha process # "STDOUT" prints stdout messages to the console that # started the ganesha process #LogFile = "SYSLOG"; LogFile = "/var/log/nfs-ganesha.log"; # maximum number of simultaneous calls # to the filesystem. # ( 0 = no limit ). max_FS_calls = 0; } } The "FSAL" block has one sub-block, in this example "VFS", for each fsal it is to load. There can be as many sub-blocks as make sense. This is the power of the new api, to support multiple fsals simultaneously. There is also provision for FSAL global parameters although none are actually defined at this point. The "name" of the FSAL is the tag at the beginning of the sub-block, in this case "VFS". This name is used for fsal module lookups and diagnostics. The "FSAL_Shared_Library" value is the absolute path to the module itself. The path can be anywhere in the local filesystem. The rest of the parameters are the usual FSAL configuration parameters. Exports are connected to a specific FSAL in the following fragment: EXPORT { # Export Id (mandatory) Export_Id = 77 ; # Exported path (mandatory) Path = "/home/tmp"; # Exporting FSAL FSAL = "VFS"; # more export parameters... } The "FSAL" keyword defines which FSAL module to use for this export. If This keyword is missing in an export, the VFS fsal is used. This logic may change in future to have a server global definition within the configuration file. Things to look for ------------------ The new api uses an object oriented design pattern to implement a fsal. Structure definitions The fsal_ops_context_t structure is deprecated and all of the 'pcontext' usage has either been replaced or removed. It was refactored some time back to be common across all FSALs and all it contained was a pointer to the "export" and user credentials. * struct compound_data now has 'user_credentials' in place of 'pcontext'. A pointer to these user credentials are passed through the call stack where access checking applies. * The use of 'pcontext' in all other cases has been removed. Nearly all affected function prototypes also had a pointer to the applicable cache entry 'pentry'. This can be dereferenced anywhere to get the export, i.e. pentry->handle->export. The fsal_path_t and fsal_name_t typedefs are not used in the API. In most instances, they are replaced by a 'const char *'. These arrays attempt to encapulate string management but they are of fixed size (mostly too big but also a potential for truncation or buffer overflows) and both waste space and cause extra structure copying. Existing core code dereferences the buffer and a NULL terminated string. They will eventually be deprecated and removed. The file include/fsal_api.h contains the full fsal api. The only part visible to the server core is defined here. Each file that implements an object has its version of the object definition. One element of this private definition is the public structure defined in fsal_api.h. The rest of the elements are private to the fsal itself. These two parts, the public and private are managed as follows: * A pointer to the public structure is returned when the object is created. * This pointer is used to access methods from it as in: exp_hdl->ops->lookup(exp_hdl, name, ...); Note that exp_hdl is used to dereference the method and it is also *always* the first argument to the method/function. Think of it as the 'this' argument. * Inside the method function, the public pointer gets dereferenced with the following sequence: struct vfs_fsal_export *myself; myself = container_of(exp_hdl, struct vfs_fsal_export, export); The 'container_of' is a macro that takes the public pointer/ handle 'exp_hdl' which is indicated as the element 'export' of structure type 'vfs_fsal_export'. Throughout the function where private elements are dereferenced, the 'myself' pointer is used. 'exp_hdl' is used in the public case. Object usage Mutex locks and reference counts are used to manage both concurrent usage and state. The reference counts are use to determine when the object is "free". Current use is for managing ref counts and lists. This will be expanded as more resources such as attributes and open fds are added. Since we cannot create objects out of thin air, there is an order based on one object being the "context" in which the other is created. In other words, a 'fsal_export' is created from the 'fsal_module' that connects it to the backing store (filesystem). The same applies to a 'fsal_obj_handle' that only makes sense for a specific 'fsal_export'. When an object is created, it is returned with a reference already taken. The callee of the creating method must then either keep a persistent reference to it or 'put' it back. For example, a 'fsal_export' gets created for each export in the configuration. a pointer to it gets saved in exportlist__ and it has a reference to reflect this. It is now safe to use it to do a 'lookup' which will return a 'fsal_obj_handle' which can then be kept in a cache inode entry. If we had done a 'put' on the export, it could be freed at any point and make a 'lookup' using it unsafe. In addition to a reference count, object that create other objects have a list of all the objects they create. This serves two purposes. The obvious case is to keep the object "busy" until all of its children are freed. Second, it provides a means to visit all of the objects it creates. Every object has a pointer to its parent. This is used for such things as managing the object list and for calling methods on the parent. Pointers vs. Structure copies In order to manage multiple fsals which have different sized private object storage, we only pass and save pointers. This keeps all of the referencing structures of constant size. The 'container_of' manages both the differences in private structure sizes and the actual layout of the private structures. This also saves the space and cpu overhead of expensive structure copies. Public FSAL Structure definitions The 'ops' element is the most commonly used element of an object's public structure. Any other elements should be used with care. Very little upper level code should directly reference any other elements. Common methods located in src/FSAL/fsal_commonlib.c are used instead. No size assumptions should be made either because, other than the 'ops' element, all other elements are subject to change. Think of them as 'protected'. The only reason they are declared in the public structure is because they are used in all fsals i.e. every 'fsal_export' has lock and reference count. FSAL structure -------------- The VFS fsal is the first candidate, primarily because it is the most promising base for the new work our group is doing. I have followed a simple pattern for this fsal. There is one file per object to be implemented. 'export.c' implements 'fsal_export' for VFS and 'main.c' implements 'fsal_module'. This allows the declaration of the object methods as 'static'. Only common methods are declared publicly in src/include/FSAL/fsal_commonlib.h and implemented in src/FSAL/fsal_commonlib.c. Likewise common helper functions are in the these files. I have used new names for the public fsal structures on purpose. All of the old api structures will be deprecated and removed from fsal_types.h and at that point, the compiler will find the usages that are still 'hiding' and complain. Otherwise, we could have subtle bugs arise from the change in structure and especially the use of the 'old' structure. At one level, it looks like the VFS fsal is a complete rewrite which is partly true. As with the structures and their names, I am forcing breakage at compile time. * There is a significant amount of code re-use. For example, a 'lookup' for a handle is still the same sequence of syscalls or library calls. That logic and the error paths have all been worked out. * Method functions now do only one thing. The 'extra' bit that acquires attributes, for example, is gone. If the upper layers want attributes, they should call the method to get them. This makes things smaller and smaller is good. * Access checking and all the (see above) is also gone. This is now moved to the core. The fsal can assume that if the method is being called, it is ok to do the work. If the action is not allowed, the core should never make the method call. * There is a 'test_access' method defined in the api and a common function is part of the library. Most fsal implementations would use the library supplied one. However, some implementations may want to supply their own. There are two very important caveats with this method: - This method is only for the core to use. It is *NEVER* called from within a fsal. Ever. - If a fsal supplies its own, all that is required is to substitute the fsal function in the definition of the handle ops vector. If the fsal implementation would also make this configurable, it should manage it by doing the test within its own function and call the library function itself, returning its return values directly. * Along with access checking in one place in the core server, the fsal is responsible for managing the difference between what NFS wants and the resource (filesystem) it manages provides. For example, the server assumes that every fsal supports ACLs. If the resource does not support them or supports them in a different way, the fsal is responsible for managing the differences. * There are some linkages between an object and its 'parent' object. These are managed in two ways. First, common functions are in fsal_commonlib.c so every fsal implementation can use them. The second case is for functions that are fsal specific such as 'lookup' which is a 'fsal_export' method that must have intimate knowledge of what a 'fsal_obj_handle' is. In this case, a function prototype is defined in export.c so it can be included in the ops vector and the method is declared global (not static) in handle.c. Function prototypes are declared in headers only if they are referenced by multiple modules. The goal is to break as much incorrect code at the compile level as we can. Use of _USE_FOO --------------- There are a number of places where fsal enabling config parameters are used in the middle of the core. All of this usage will be deprecated and removed. For example, some data structures have fsal specific elements defined. If the fsal implementation needs these elements, they should be moved to the private portion of the most relevant fsal object. This restriction is necessary to make the core completely fsal implementation agnostic. There should be no #ifdef conditionals on public data structures. If a feature is conditionally built, its data structures should be harmless if the feature is disabled. These conditionals are clutter and can break ABI which is now important with the new api NOTE: There are a few #ifdef conditionals in fsal_api.h. These are temporary and will be removed in the final version. At that time, the contents of fsal_api.h will be version controlled as a fixed ABI. nfs-ganesha-6.5/src/RPCAL/000077500000000000000000000000001473756622300152255ustar00rootroot00000000000000nfs-ganesha-6.5/src/RPCAL/CMakeLists.txt000066400000000000000000000030761473756622300177730ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- ########### next target ############### SET(rpcal_STAT_SRCS connection_manager.c connection_manager_metrics.c nfs_dupreq.c rpc_tools.c ) if(_HAVE_GSSAPI) set(rpcal_STAT_SRCS ${rpcal_STAT_SRCS} gss_credcache.c gss_extra.c) endif(_HAVE_GSSAPI) add_library(rpcal OBJECT ${rpcal_STAT_SRCS}) add_sanitizers(rpcal) set_target_properties(rpcal PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(rpcal gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) ########### install files ############### nfs-ganesha-6.5/src/RPCAL/connection_manager.c000066400000000000000000000660621473756622300212340ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2024 Google LLC * Contributor : Yoni Couriel yonic@google.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file connection_manager.c * @brief Allows a client to be connected to a single Ganesha server at a time. */ #include "connection_manager.h" #include "connection_manager_metrics.h" #include "client_mgr.h" #include "gsh_config.h" #include "xprt_handler.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/connection_manager.h" #endif #define LogDebugClient(client, format, args...) \ LogDebug(COMPONENT_XPRT, "%s: " format, \ get_client_address_for_debugging(client), ##args) #define LogInfoClient(client, format, args...) \ LogInfo(COMPONENT_XPRT, "%s: " format, \ get_client_address_for_debugging(client), ##args) #define LogWarnClient(client, format, args...) \ LogWarn(COMPONENT_XPRT, "%s: " format, \ get_client_address_for_debugging(client), ##args) #define LogFatalClient(client, format, args...) \ LogFatal(COMPONENT_XPRT, "%s: " format, \ get_client_address_for_debugging(client), ##args) #define LogDebugConnection(connection, format, args...) \ LogDebugClient(&(connection)->gsh_client->connection_manager, \ "fd %d: " format, (connection)->xprt->xp_fd, ##args) #define LogInfoConnection(connection, format, args...) \ LogInfoClient(&(connection)->gsh_client->connection_manager, \ "fd %d: " format, (connection)->xprt->xp_fd, ##args) #define LogWarnConnection(connection, format, args...) \ LogWarnClient(&(connection)->gsh_client->connection_manager, \ "fd %d: " format, (connection)->xprt->xp_fd, ##args) #define LogFatalConnection(connection, format, args...) \ LogFatalClient(&(connection)->gsh_client->connection_manager, \ "fd %d: " format, (connection)->xprt->xp_fd, ##args) #define PROV_NAME connection_manager #define CLIENT_FORMAT "{}" #define CLIENT_VARS(client, addr) TP_STR(addr) #define CLIENT_AUTO_TRACEPOINT(client, event, log_level, format, ...) \ do { \ const char *const addr = \ get_client_address_for_debugging(client); \ GSH_AUTO_TRACEPOINT(PROV_NAME, event, log_level, \ CLIENT_FORMAT ": " format, \ CLIENT_VARS(client, addr), ##__VA_ARGS__); \ } while (0) #define CONN_FORMAT "fd {}" #define CONN_VARS(connection) (connection)->xprt->xp_fd #define CONN_AUTO_TRACEPOINT(connection, event, log_level, format, ...) \ CLIENT_AUTO_TRACEPOINT(&(connection)->gsh_client->connection_manager, \ event, log_level, CONN_FORMAT ": " format, \ CONN_VARS(connection), ##__VA_ARGS__) static inline const char * get_client_address_for_debugging(const connection_manager__client_t *client) { const struct gsh_client *const gsh_client = container_of(client, struct gsh_client, connection_manager); return gsh_client->hostaddr_str; } static inline const sockaddr_t * get_client_address(const connection_manager__client_t *client) { const struct gsh_client *const gsh_client = container_of(client, struct gsh_client, connection_manager); return &gsh_client->cl_addrbuf; } static inline struct timespec timeout_seconds(uint32_t seconds) { return (struct timespec){ .tv_sec = time(NULL) + seconds, .tv_nsec = 0 }; } static inline bool is_transition_valid(enum connection_manager__client_state_t from, enum connection_manager__client_state_t to) { switch (from) { case CONNECTION_MANAGER__CLIENT_STATE__DRAINED: return to == CONNECTION_MANAGER__CLIENT_STATE__ACTIVATING; case CONNECTION_MANAGER__CLIENT_STATE__ACTIVATING: return to == CONNECTION_MANAGER__CLIENT_STATE__ACTIVE || to == CONNECTION_MANAGER__CLIENT_STATE__DRAINED; case CONNECTION_MANAGER__CLIENT_STATE__ACTIVE: return to == CONNECTION_MANAGER__CLIENT_STATE__DRAINING; case CONNECTION_MANAGER__CLIENT_STATE__DRAINING: return to == CONNECTION_MANAGER__CLIENT_STATE__ACTIVE || to == CONNECTION_MANAGER__CLIENT_STATE__DRAINED; default: return false; } } /* Assumes the client mutex is held */ static inline void change_state(connection_manager__client_t *client, enum connection_manager__client_state_t new_state) { LogDebugClient(client, "Changing state: %d -> %d", client->state, new_state); CLIENT_AUTO_TRACEPOINT(client, change_state, TRACE_INFO, "Changing state: {} -> {}", client->state, new_state); assert(is_transition_valid(client->state, new_state)); connection_manager_metrics__client_state_inc(new_state); connection_manager_metrics__client_state_dec(client->state); client->state = new_state; PTHREAD_COND_broadcast(&client->cond_change); } /* Assumes the client mutex is held */ static inline void condition_wait(connection_manager__client_t *client) { PTHREAD_COND_wait(&client->cond_change, &client->mutex); } enum condition_wait_t { CONDITION_WAIT__OK = 0, CONDITION_WAIT__TIMEOUT, }; /* Assumes the client mutex is held */ static inline enum condition_wait_t condition_timedwait(connection_manager__client_t *client, struct timespec timeout) { const int rc = pthread_cond_timedwait(&client->cond_change, &client->mutex, &timeout); switch (rc) { case 0: return CONDITION_WAIT__OK; case ETIMEDOUT: return CONDITION_WAIT__TIMEOUT; default: CLIENT_AUTO_TRACEPOINT(client, cond_time, TRACE_CRIT, "Unexpected return code: {}", rc); LogFatalClient(client, "Unexpected return code: %d", rc); } } /* Assumes the client mutex is held */ static inline void wait_for_state_change(connection_manager__client_t *client) { const enum connection_manager__client_state_t initial_state = client->state; LogDebugClient(client, "Waiting until state changes from %d", initial_state); CLIENT_AUTO_TRACEPOINT(client, wait_state_change, TRACE_INFO, "Waiting until state changes from {}", initial_state); while (client->state == initial_state) condition_wait(client); } static enum connection_manager__drain_t callback_default_drain_other_servers( void *context, const sockaddr_t *client_address, const char *client_address_str, const struct timespec *timeout) { LogWarn(COMPONENT_XPRT, "%s: Client connected before Connection Manager callback was registered", client_address_str); GSH_AUTO_TRACEPOINT(PROV_NAME, default_drain, TRACE_WARNING, "{}: Connection is not managed", TP_STR(client_address_str)); return CONNECTION_MANAGER__DRAIN__FAILED; } #define DEFAULT_CALLBACK_CONTEXT \ { /*user_context=*/ \ NULL, callback_default_drain_other_servers \ } static pthread_rwlock_t callback_lock = RWLOCK_INITIALIZER; static const connection_manager__callback_context_t callback_default = DEFAULT_CALLBACK_CONTEXT; static connection_manager__callback_context_t callback_context = DEFAULT_CALLBACK_CONTEXT; void connection_manager__callback_set(connection_manager__callback_context_t new) { PTHREAD_RWLOCK_wrlock(&callback_lock); assert(callback_context.drain_and_disconnect_other_servers == callback_default.drain_and_disconnect_other_servers); callback_context = new; PTHREAD_RWLOCK_unlock(&callback_lock); } connection_manager__callback_context_t connection_manager__callback_clear(void) { PTHREAD_RWLOCK_wrlock(&callback_lock); assert(callback_context.drain_and_disconnect_other_servers != callback_default.drain_and_disconnect_other_servers); const connection_manager__callback_context_t old = callback_context; callback_context = callback_default; PTHREAD_RWLOCK_unlock(&callback_lock); return old; } void connection_manager__client_init(connection_manager__client_t *client) { LogDebugClient(client, "Client init %p", client); CLIENT_AUTO_TRACEPOINT(client, client_init, TRACE_INFO, "Client init {}", client); client->state = CONNECTION_MANAGER__CLIENT_STATE__DRAINED; PTHREAD_MUTEX_init(&client->mutex, NULL); PTHREAD_COND_init(&client->cond_change, NULL); glist_init(&client->connections); client->connections_count = 0; connection_manager_metrics__client_state_inc(client->state); } void connection_manager__client_fini(connection_manager__client_t *client) { LogDebugClient(client, "Client fini %p", client); CLIENT_AUTO_TRACEPOINT(client, client_fini, TRACE_INFO, "Client fini {}", client); assert(client->connections_count == 0); assert(glist_empty(&client->connections)); assert(client->state == CONNECTION_MANAGER__CLIENT_STATE__DRAINED); connection_manager_metrics__client_state_dec(client->state); PTHREAD_MUTEX_destroy(&client->mutex); PTHREAD_COND_destroy(&client->cond_change); } static void update_socket_linger(const connection_manager__connection_t *connection) { /* Setting Timeout=0, so the TCP connection will send RST on close() * without waiting for the client's response. This is needed in case * the client was migrated by a load balancer to another server, and * we want the connection to close quickly. * Linger is still enabled, so close() will block until the connection * is closed. */ const struct linger linger = { .l_onoff = 1, .l_linger = 0 }; if (setsockopt(connection->xprt->xp_fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger)) < 0) { const char *const strerr = strerror(errno); LogWarnConnection(connection, "Could not set linger for connection: %s", strerr); CONN_AUTO_TRACEPOINT(connection, socket_linger, TRACE_WARNING, "Could not set linger for connection: {}", TP_STR(strerr)); } } /* Assumes the client mutex is held */ static enum connection_manager__drain_t try_drain_self(connection_manager__client_t *client, uint32_t timeout_sec) { assert(client->state == CONNECTION_MANAGER__CLIENT_STATE__ACTIVE); change_state(client, CONNECTION_MANAGER__CLIENT_STATE__DRAINING); /* TODO: Extends client state lease (see explanation in header) */ const struct glist_head *node; /* start draining connections */ glist_for_each(node, &client->connections) { connection_manager__connection_t *const connection = glist_entry(node, connection_manager__connection_t, node); LogDebugConnection(connection, "Destroying connection (xp_refcnt %d)", connection->xprt->xp_refcnt); CONN_AUTO_TRACEPOINT(connection, try_drain_self_entry, TRACE_INFO, "Destroying connection (xp_refcnt {})", connection->xprt->xp_refcnt); assert(connection->is_managed); if (connection->is_destroyed) continue; connection->is_destroyed = true; update_socket_linger(connection); SVC_DESTROY(connection->xprt); connection->destroy_start = time(NULL); } LogDebugClient(client, "Waiting for %d connections to terminate, timeout=%d", client->connections_count, timeout_sec); CLIENT_AUTO_TRACEPOINT( client, try_drain_self_wait, TRACE_INFO, "Waiting for {} connections to terminate, timeout={}", client->connections_count, timeout_sec); const struct timespec timeout = timeout_seconds(timeout_sec); enum condition_wait_t wait_result = CONDITION_WAIT__OK; while (client->connections_count != 0 && client->state == CONNECTION_MANAGER__CLIENT_STATE__DRAINING) { /* Note mutex is temporarily released while waiting, and other * threads can abort the draining */ if (condition_timedwait(client, timeout) == CONDITION_WAIT__TIMEOUT) { wait_result = CONDITION_WAIT__TIMEOUT; break; } } LogDebugClient(client, "Finished waiting: state=%d connections=%d wait=%d", client->state, client->connections_count, wait_result); CLIENT_AUTO_TRACEPOINT( client, try_drain_self_finish_wait, TRACE_INFO, "Finished waiting: state={} connections={} wait={}", client->state, client->connections_count, wait_result); if (client->state == CONNECTION_MANAGER__CLIENT_STATE__DRAINING) { /* Since we have (mutex && DRAINING), we're allowed to change * the state to DRAINED/ACTIVE. This also holds in more complex * scenarios where the draining was aborted by another thread * and then restarted by a third thread. */ if (client->connections_count == 0) { change_state(client, CONNECTION_MANAGER__CLIENT_STATE__DRAINED); } else { change_state(client, CONNECTION_MANAGER__CLIENT_STATE__ACTIVE); } } if (client->state == CONNECTION_MANAGER__CLIENT_STATE__DRAINED) { return CONNECTION_MANAGER__DRAIN__SUCCESS; } /* Check for stuck connections */ glist_for_each(node, &client->connections) { const connection_manager__connection_t *const connection = glist_entry(node, connection_manager__connection_t, node); const int delta = time(NULL) - connection->destroy_start; const int max_delta = nfs_param.core_param.connection_manager_timeout_sec * CONNECTION_MANAGER__DRAIN_MAX_EXPECTED_ITERATIONS; /* Must check for "is_destroyed", because this might be a new connection that aborted the drain. */ if (connection->is_destroyed && delta > max_delta) { LogWarnConnection(connection, "Stuck for %d", delta); CONN_AUTO_TRACEPOINT(connection, try_drain_self_stuck, TRACE_WARNING, "Stuck for {}", delta); return CONNECTION_MANAGER__DRAIN__FAILED_STUCK; } } return wait_result == CONDITION_WAIT__TIMEOUT ? CONNECTION_MANAGER__DRAIN__FAILED_TIMEOUT : CONNECTION_MANAGER__DRAIN__FAILED; } enum connection_manager__drain_t connection_manager__drain_and_disconnect_local(sockaddr_t *client_address) { enum connection_manager__drain_t result; struct timespec start_time; now(&start_time); struct gsh_client *const gsh_client = get_gsh_client(client_address, /*lookup_only=*/true); if (gsh_client == NULL) { if (isDebug(COMPONENT_XPRT)) { char address_for_debugging[SOCK_NAME_MAX]; bool ok; ok = sprint_sockip(client_address, address_for_debugging, sizeof(address_for_debugging)); LogDebug(COMPONENT_XPRT, "Client not found: %s", ok ? address_for_debugging : ""); GSH_AUTO_TRACEPOINT(PROV_NAME, disco_local_no_client, TRACE_INFO, "Client not found: {}", TP_STR(ok ? address_for_debugging : "")); } result = CONNECTION_MANAGER__DRAIN__SUCCESS_NO_CONNECTIONS; goto out; } connection_manager__client_t *const client = &gsh_client->connection_manager; PTHREAD_MUTEX_lock(&client->mutex); switch (client->state) { case CONNECTION_MANAGER__CLIENT_STATE__DRAINED: { LogDebugClient(client, "Already drained"); CLIENT_AUTO_TRACEPOINT(client, disco_local_drained, TRACE_INFO, "Already drained"); result = CONNECTION_MANAGER__DRAIN__SUCCESS_NO_CONNECTIONS; break; } case CONNECTION_MANAGER__CLIENT_STATE__ACTIVATING: { LogDebugClient(client, "Busy draining other servers"); CLIENT_AUTO_TRACEPOINT(client, disco_local_activating, TRACE_INFO, "Busy draining other servers"); result = CONNECTION_MANAGER__DRAIN__FAILED; break; } case CONNECTION_MANAGER__CLIENT_STATE__ACTIVE: { LogDebugClient(client, "Starting self drain"); CLIENT_AUTO_TRACEPOINT(client, disco_local_active, TRACE_INFO, "Starting self drain"); result = try_drain_self( client, nfs_param.core_param.connection_manager_timeout_sec); break; } case CONNECTION_MANAGER__CLIENT_STATE__DRAINING: { LogDebugClient(client, "Already self draining, waiting"); CLIENT_AUTO_TRACEPOINT(client, disco_local_draining, TRACE_INFO, "Already self draining, waiting"); wait_for_state_change(client); result = (client->state == CONNECTION_MANAGER__CLIENT_STATE__DRAINED) ? CONNECTION_MANAGER__DRAIN__SUCCESS : CONNECTION_MANAGER__DRAIN__FAILED; break; } default: { CLIENT_AUTO_TRACEPOINT(client, disco_local_state_unknown, TRACE_CRIT, "Unexpected connection manager state {}", client->state); LogFatalClient(client, "Unexpected connection manager state %d", client->state); } } PTHREAD_MUTEX_unlock(&client->mutex); put_gsh_client(gsh_client); switch (result) { case CONNECTION_MANAGER__DRAIN__SUCCESS: /* Fallthrough */ case CONNECTION_MANAGER__DRAIN__SUCCESS_NO_CONNECTIONS: LogDebugClient(client, "Drain was successful: %d", result); CLIENT_AUTO_TRACEPOINT(client, disco_local_no_connections, TRACE_INFO, "Drain was successful: {}", result); break; case CONNECTION_MANAGER__DRAIN__FAILED: /* Fallthrough */ case CONNECTION_MANAGER__DRAIN__FAILED_TIMEOUT: /* Fallthrough */ case CONNECTION_MANAGER__DRAIN__FAILED_STUCK: LogWarnClient(client, "Drain failed: %d", result); CLIENT_AUTO_TRACEPOINT(client, disco_local_failed_stuc, TRACE_INFO, "Drain failed: {}", result); break; default: CLIENT_AUTO_TRACEPOINT(client, disco_local_result_unknown, TRACE_CRIT, "Unknown result: {}", result); LogFatalClient(client, "Unknown result: %d", result); } out: connection_manager_metrics__drain_local_client_done(result, &start_time); return result; } static inline connection_manager__connection_t * xprt_to_connection(const SVCXPRT *xprt) { if (xprt->xp_u1 == NULL) { LogInfo(COMPONENT_XPRT, "fd %d: No custom data allocated", xprt->xp_fd); GSH_AUTO_TRACEPOINT(PROV_NAME, xprt_to_conn, TRACE_INFO, "fd {}: No custom data allocated", xprt->xp_fd); return NULL; } xprt_custom_data_t *const xprt_data = (xprt_custom_data_t *)xprt->xp_u1; return &xprt_data->managed_connection; } static inline bool should_manage_connection(sockaddr_t *client_address) { return nfs_param.core_param.enable_connection_manager && !is_loopback(client_address); } static inline bool is_drain_success(enum connection_manager__drain_t result) { return result == CONNECTION_MANAGER__DRAIN__SUCCESS || result == CONNECTION_MANAGER__DRAIN__SUCCESS_NO_CONNECTIONS; } /** * Tries to activate the client if it's not already activated. * The "connection" parameter is used for logging purposes only, the entity * being activated is the client. * * Assumes the client mutex is held. */ static void try_activate_client_if_needed(connection_manager__connection_t *connection) { connection_manager__client_t *const client = &connection->gsh_client->connection_manager; switch (client->state) { case CONNECTION_MANAGER__CLIENT_STATE__DRAINED: { LogDebugConnection(connection, "Client is drained, activating"); CONN_AUTO_TRACEPOINT(connection, activate_clinet__drained, TRACE_INFO, "Client is drained, activating"); change_state(client, CONNECTION_MANAGER__CLIENT_STATE__ACTIVATING); /* It's OK to unlock because no other thread can change the * state while ACTIVATING. */ PTHREAD_MUTEX_unlock(&client->mutex); LogDebugConnection(connection, "Draining other servers"); CONN_AUTO_TRACEPOINT(connection, activate_clinet__drain_others, TRACE_INFO, "Draining other servers"); const struct timespec timeout = timeout_seconds( nfs_param.core_param.connection_manager_timeout_sec); PTHREAD_RWLOCK_rdlock(&callback_lock); const enum connection_manager__drain_t drain_result = callback_context.drain_and_disconnect_other_servers( callback_context.user_context, get_client_address(client), get_client_address_for_debugging(client), &timeout); PTHREAD_RWLOCK_unlock(&callback_lock); PTHREAD_MUTEX_lock(&client->mutex); assert(client->state == CONNECTION_MANAGER__CLIENT_STATE__ACTIVATING); if (is_drain_success(drain_result)) { change_state(client, CONNECTION_MANAGER__CLIENT_STATE__ACTIVE); } else { change_state(client, CONNECTION_MANAGER__CLIENT_STATE__DRAINED); } break; } case CONNECTION_MANAGER__CLIENT_STATE__ACTIVATING: { LogDebugConnection( connection, "Client is activating in another thread, waiting"); CONN_AUTO_TRACEPOINT( connection, activate_clinet__activating, TRACE_INFO, "Client is activating in another thread, waiting"); wait_for_state_change(client); break; } case CONNECTION_MANAGER__CLIENT_STATE__ACTIVE: { LogDebugConnection(connection, "Client is already active"); CONN_AUTO_TRACEPOINT(connection, activate_clinet__active, TRACE_INFO, "Client is already active"); break; } case CONNECTION_MANAGER__CLIENT_STATE__DRAINING: { LogDebugConnection(connection, "Canceling ongoing drain"); CONN_AUTO_TRACEPOINT(connection, activate_clinet__draining, TRACE_INFO, "Canceling ongoing drain"); change_state(client, CONNECTION_MANAGER__CLIENT_STATE__ACTIVE); break; } default: { CONN_AUTO_TRACEPOINT(connection, activate_clinet__state_unknown, TRACE_CRIT, "Unexpected connection manager state {}", client->state); LogFatalConnection(connection, "Unexpected connection manager state %d", client->state); } } } void connection_manager__connection_init(SVCXPRT *xprt) { LogInfo(COMPONENT_XPRT, "fd %d: Connection init for xprt %p", xprt->xp_fd, xprt); GSH_AUTO_TRACEPOINT(PROV_NAME, conn_init, TRACE_INFO, "fd {}: Connection init for xprt {}", xprt->xp_fd, xprt); connection_manager__connection_t *const connection = xprt_to_connection(xprt); if (!connection) { GSH_AUTO_TRACEPOINT( PROV_NAME, conn_init_no_conn, TRACE_CRIT, "fd {}: Must call nfs_rpc_alloc_user_data before calling {}", xprt->xp_fd, __func__); LogFatal( COMPONENT_XPRT, "fd %d: Must call nfs_rpc_alloc_user_data before calling %s", xprt->xp_fd, __func__); } /* No need to hold XPRT refcount, because the connection struct is * stored in the XPRT custom user data. When the XPRT is destroyed it * calls connection_manager__connection_finished */ connection->xprt = xprt; connection->is_destroyed = false; connection->destroy_start = 0; connection->is_managed = false; /* connection_init is called when the connection is just established. * However, till the first packet on the connection, which can be a * proxy protocol packet, arrives at the connection, the remote address * is not determined, and therefore calling to svc_getrpccaller should * not be used till then. * When the svc_vc handles the first packet, it knows what is the * remote address and update any upper layer registered for this * notification. */ connection->gsh_client = NULL; } enum connection_manager__connection_started_t connection_manager__connection_started(SVCXPRT *xprt) { enum connection_manager__connection_started_t result; struct timespec start_time; now(&start_time); sockaddr_t *const client_address = svc_getrpccaller(xprt); struct gsh_client *const gsh_client = get_gsh_client(client_address, /*lookup_only=*/false); connection_manager__client_t *const client = &gsh_client->connection_manager; LogDebugClient(client, "fd %d: Connection started", xprt->xp_fd); CLIENT_AUTO_TRACEPOINT(client, conn_started, TRACE_INFO, "fd {}: Connection started", xprt->xp_fd); connection_manager__connection_t *const connection = xprt_to_connection(xprt); if (!connection) { CLIENT_AUTO_TRACEPOINT( client, conn_started_no_conn, TRACE_CRIT, "fd {}: Must call nfs_rpc_alloc_user_data before calling {}", xprt->xp_fd, TP_STR(__func__)); LogFatalClient( client, "fd %d: Must call nfs_rpc_alloc_user_data before calling %s", xprt->xp_fd, __func__); } /* assert that connecton_init function was called before. * The init function should have set the xprt in the connection. */ if (connection->xprt != xprt) { CLIENT_AUTO_TRACEPOINT( client, conn_started_xprt, TRACE_CRIT, "found connection xprt {} is different from given xprt {}", connection->xprt, xprt); LogFatalClient( client, "found connection xprt %p is different from given xprt %p ", connection->xprt, xprt); } /* We need connection->gsh_client set here, we will NULL it back out * if we do not end up managing the client. */ connection->gsh_client = gsh_client; connection->is_destroyed = false; connection->destroy_start = 0; connection->is_managed = should_manage_connection(client_address); if (!connection->is_managed) { LogDebugConnection( connection, "Connection is not managed by connection manager"); CONN_AUTO_TRACEPOINT( connection, conn_started_not_mananged, TRACE_INFO, "Connection is not managed by connection manager"); connection->gsh_client = NULL; put_gsh_client(gsh_client); result = CONNECTION_MANAGER__CONNECTION_STARTED__ALLOW; goto out; } PTHREAD_MUTEX_lock(&client->mutex); try_activate_client_if_needed(connection); if (client->state != CONNECTION_MANAGER__CLIENT_STATE__ACTIVE) { LogWarnConnection(connection, "Failed with state %d", client->state); CONN_AUTO_TRACEPOINT(connection, conn_started_not_active, TRACE_WARNING, "Failed with state {}", client->state); connection->is_managed = false; PTHREAD_MUTEX_unlock(&client->mutex); connection->gsh_client = NULL; put_gsh_client(gsh_client); result = CONNECTION_MANAGER__CONNECTION_STARTED__DROP; goto out; } LogDebugConnection(connection, "Success (xp_refcnt %d)", xprt->xp_refcnt); CONN_AUTO_TRACEPOINT(connection, conn_started_done, TRACE_INFO, "Success (xp_refcnt {})", xprt->xp_refcnt); glist_add_tail(&client->connections, &connection->node); client->connections_count++; PTHREAD_MUTEX_unlock(&client->mutex); result = CONNECTION_MANAGER__CONNECTION_STARTED__ALLOW; out: connection_manager_metrics__connection_started_done(result, &start_time); return result; } void connection_manager__connection_finished(const SVCXPRT *xprt) { connection_manager__connection_t *const connection = xprt_to_connection(xprt); if (!connection || !connection->is_managed) { LogInfo(COMPONENT_XPRT, "fd %d: Connection is not managed", xprt->xp_fd); GSH_AUTO_TRACEPOINT(PROV_NAME, conn_fini_no_conn, TRACE_INFO, "fd {}: Connection is not managed", xprt->xp_fd); return; } struct gsh_client *const gsh_client = connection->gsh_client; connection_manager__client_t *const client = &gsh_client->connection_manager; LogDebugConnection(connection, "Connection finished"); CONN_AUTO_TRACEPOINT(connection, conn_finished, TRACE_INFO, "Connection finished"); PTHREAD_MUTEX_lock(&client->mutex); glist_del(&connection->node); assert(client->connections_count > 0); client->connections_count--; if (client->connections_count == 0) PTHREAD_COND_broadcast(&client->cond_change); PTHREAD_MUTEX_unlock(&client->mutex); connection->xprt = NULL; connection->gsh_client = NULL; put_gsh_client(gsh_client); } void connection_manager__init(void) { connection_manager_metrics__init(); } nfs-ganesha-6.5/src/RPCAL/connection_manager_metrics.c000066400000000000000000000127421473756622300227560ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2024 Google LLC * Contributor : Yoni Couriel yonic@google.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file connection_manager_metrics.c * @author Yoni Couriel * @brief Metrics for the Connection Manager module */ #include "connection_manager_metrics.h" static connection_manager__metrics_t metrics = { 0 }; static const char * stringify_client_state(enum connection_manager__client_state_t client_state) { switch (client_state) { case CONNECTION_MANAGER__CLIENT_STATE__DRAINED: return "DRAINED"; case CONNECTION_MANAGER__CLIENT_STATE__ACTIVATING: return "ACTIVATING"; case CONNECTION_MANAGER__CLIENT_STATE__ACTIVE: return "ACTIVE"; case CONNECTION_MANAGER__CLIENT_STATE__DRAINING: return "DRAINING"; default: LogFatal(COMPONENT_XPRT, "Unknown client state: %d", client_state); } } static const char *stringify_connection_started_result( enum connection_manager__connection_started_t result) { switch (result) { case CONNECTION_MANAGER__CONNECTION_STARTED__ALLOW: return "ALLOW"; case CONNECTION_MANAGER__CONNECTION_STARTED__DROP: return "DROP"; default: LogFatal(COMPONENT_XPRT, "Unknown connection stated result: %d", result); } } static const char * stringify_drain_result(enum connection_manager__drain_t result) { switch (result) { case CONNECTION_MANAGER__DRAIN__SUCCESS: return "SUCCESS"; case CONNECTION_MANAGER__DRAIN__SUCCESS_NO_CONNECTIONS: return "SUCCESS_NO_CONNECTIONS"; case CONNECTION_MANAGER__DRAIN__FAILED: return "FAILED"; case CONNECTION_MANAGER__DRAIN__FAILED_STUCK: return "STUCK"; case CONNECTION_MANAGER__DRAIN__FAILED_TIMEOUT: return "FAILED_TIMEOUT"; default: LogFatal(COMPONENT_XPRT, "Unknown drain result: %d", result); } } static void register_clients_metrics(void) { for (uint32_t state = 0; state < ARRAY_SIZE(metrics.clients); state++) { const metric_label_t labels[] = { METRIC_LABEL( "state", stringify_client_state( (enum connection_manager__client_state_t) state)) }; metrics.clients[state] = monitoring__register_gauge( "connection_manager__clients", METRIC_METADATA("Connection Manager Clients per State", METRIC_UNIT_NONE), labels, ARRAY_SIZE(labels)); } } static void register_connection_started_latencies_metrics(void) { for (uint32_t result = 0; result < ARRAY_SIZE(metrics.connection_started_latencies); result++) { const metric_label_t labels[] = { METRIC_LABEL( "result", stringify_connection_started_result( (enum connection_manager__connection_started_t) result)) }; metrics.connection_started_latencies [result] = monitoring__register_histogram( "connection_manager__connection_started_latencies", METRIC_METADATA("Connection Manager Connection Started " "Latencies per Result", METRIC_UNIT_MILLISECOND), labels, ARRAY_SIZE(labels), monitoring__buckets_exp2()); } } static void register_drain_local_client_latencies_metrics(void) { for (uint32_t result = 0; result < ARRAY_SIZE(metrics.drain_local_client_latencies); result++) { const metric_label_t labels[] = { METRIC_LABEL( "result", stringify_drain_result( (enum connection_manager__drain_t)result)) }; metrics.drain_local_client_latencies [result] = monitoring__register_histogram( "connection_manager__drain_local_client_latencies", METRIC_METADATA("Connection Manager Drain Local Client " "Latencies per Result", METRIC_UNIT_MILLISECOND), labels, ARRAY_SIZE(labels), monitoring__buckets_exp2()); } } void connection_manager_metrics__init(void) { register_clients_metrics(); register_connection_started_latencies_metrics(); register_drain_local_client_latencies_metrics(); } static inline int64_t get_latency_ms(const struct timespec *start_time) { struct timespec current_time; now(¤t_time); return timespec_diff(start_time, ¤t_time) / NS_PER_MSEC; } void connection_manager_metrics__client_state_inc( enum connection_manager__client_state_t state) { monitoring__gauge_inc(metrics.clients[state], 1); } void connection_manager_metrics__client_state_dec( enum connection_manager__client_state_t state) { monitoring__gauge_dec(metrics.clients[state], 1); } void connection_manager_metrics__connection_started_done( enum connection_manager__connection_started_t result, const struct timespec *start_time) { monitoring__histogram_observe( metrics.connection_started_latencies[result], get_latency_ms(start_time)); } void connection_manager_metrics__drain_local_client_done( enum connection_manager__drain_t result, const struct timespec *start_time) { monitoring__histogram_observe( metrics.drain_local_client_latencies[result], get_latency_ms(start_time)); } nfs-ganesha-6.5/src/RPCAL/connection_manager_metrics.h000066400000000000000000000037541473756622300227660ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2024 Google LLC * Contributor : Yoni Couriel yonic@google.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file connection_manager_metrics.h * @author Yoni Couriel * @brief Metrics for the Connection Manager module */ #include "connection_manager.h" #include "monitoring.h" typedef struct connection_manager__metrics_t { gauge_metric_handle_t clients[CONNECTION_MANAGER__CLIENT_STATE__LAST]; histogram_metric_handle_t connection_started_latencies [CONNECTION_MANAGER__CONNECTION_STARTED__LAST]; histogram_metric_handle_t drain_local_client_latencies[CONNECTION_MANAGER__DRAIN__LAST]; } connection_manager__metrics_t; void connection_manager_metrics__init(void); void connection_manager_metrics__client_state_inc( enum connection_manager__client_state_t); void connection_manager_metrics__client_state_dec( enum connection_manager__client_state_t); void connection_manager_metrics__connection_started_done( enum connection_manager__connection_started_t, const struct timespec *start_time); void connection_manager_metrics__drain_local_client_done( enum connection_manager__drain_t, const struct timespec *start_time); nfs-ganesha-6.5/src/RPCAL/gss_credcache.c000066400000000000000000000672551473756622300201650ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* Copyright (c) 2004 The Regents of the University of Michigan. 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. 3. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED ``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 REGENTS 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. */ /* GSS Credential Cache, redacted from gssd. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(HAVE_KRB5) && !defined(GSS_C_NT_HOSTBASED_SERVICE) #include #define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name #endif #ifdef HAVE_UNISTD_H #include #endif #include #ifdef HAVE_COM_ERR_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_PRIVATE_KRB5_FUNCTIONS #include #endif #include #include #include "log.h" #include "common_utils.h" #include "abstract_mem.h" #include "gss_credcache.h" #include "nfs_core.h" /* * Hide away some of the MIT vs. Heimdal differences * here with macros... */ #ifdef HAVE_KRB5 #define k5_free_unparsed_name(ctx, name) krb5_free_unparsed_name((ctx), (name)) #define k5_free_default_realm(ctx, realm) \ krb5_free_default_realm((ctx), (realm)) #define k5_free_kt_entry(ctx, kte) krb5_free_keytab_entry_contents((ctx), (kte)) #else /* Heimdal */ #define k5_free_unparsed_name(ctx, name) gsh_free(name) #define k5_free_default_realm(ctx, realm) gsh_free(realm) #define k5_free_kt_entry(ctx, kte) krb5_kt_free_entry((ctx), (kte)) #undef USE_GSS_KRB5_CCACHE_NAME #define USE_GSS_KRB5_CCACHE_NAME 1 #endif #define GSSD_DEFAULT_CRED_PREFIX "krb5cc_" #define GSSD_DEFAULT_MACHINE_CRED_SUFFIX "machine" #define GSSD_MAX_CCACHE_SEARCH 16 struct gssd_k5_kt_princ { struct gssd_k5_kt_princ *next; krb5_principal princ; char *ccname; char *realm; krb5_timestamp endtime; }; typedef void (*gssd_err_func_t)(const char *, ...); static int use_memcache; static struct gssd_k5_kt_princ *gssd_k5_kt_princ_list; static pthread_mutex_t ple_mtx; static char *gssd_k5_err_msg(krb5_context context, krb5_error_code code); static int gssd_get_single_krb5_cred(krb5_context context, krb5_keytab kt, struct gssd_k5_kt_princ *ple, int nocache); static void gssd_set_krb5_ccache_name(char *ccname); void gssd_init_cred_cache(void) { PTHREAD_MUTEX_init(&ple_mtx, NULL); } void gssd_shutdown_cred_cache(void) { PTHREAD_MUTEX_destroy(&ple_mtx); } /* Global list of principals/cache file names for machine credentials */ /* * Obtain credentials via a key in the keytab given * a keytab handle and a gssd_k5_kt_princ structure. * Checks to see if current credentials are expired, * if not, uses the keytab to obtain new credentials. * * Returns: * 0 => success (or credentials have not expired) * nonzero => error */ static int gssd_get_single_krb5_cred(krb5_context context, krb5_keytab kt, struct gssd_k5_kt_princ *ple, int nocache) { #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS krb5_get_init_creds_opt *init_opts = NULL; #else krb5_get_init_creds_opt options; #endif krb5_get_init_creds_opt *opts; krb5_creds my_creds; krb5_ccache ccache = NULL; char kt_name[BUFSIZ]; char cc_name[BUFSIZ]; int code; time_t now = time(0); char *cache_type; char *pname = NULL; char *k5err = NULL; memset(&my_creds, 0, sizeof(my_creds)); if (ple->ccname && ple->endtime > now && !nocache) { LogFullDebug(COMPONENT_NFS_CB, "INFO: Credentials in CC '%s' are good until %d", ple->ccname, ple->endtime); code = 0; goto out; } code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ); if (code != 0) { LogCrit(COMPONENT_NFS_CB, "ERROR: Unable to get keytab name in %s", __func__); goto out; } if ((krb5_unparse_name(context, ple->princ, &pname))) pname = NULL; #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS code = krb5_get_init_creds_opt_alloc(context, &init_opts); if (code) { k5err = gssd_k5_err_msg(context, code); LogCrit(COMPONENT_NFS_CB, "ERROR: %s allocating gic options", k5err); goto out; } if (krb5_get_init_creds_opt_set_addressless(context, init_opts, 1)) LogWarn(COMPONENT_NFS_CB, "WARNING: Unable to set option for addressless tickets. May have problems behind a NAT."); #ifdef TEST_SHORT_LIFETIME /* set a short lifetime (for debugging only!) */ LogCrit(COMPONENT_NFS_CB, "WARNING: Using (debug) short machine cred lifetime!"); krb5_get_init_creds_opt_set_tkt_life(init_opts, 5 * 60); #endif opts = init_opts; #else /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS */ krb5_get_init_creds_opt_init(&options); krb5_get_init_creds_opt_set_address_list(&options, NULL); #ifdef TEST_SHORT_LIFETIME /* set a short lifetime (for debugging only!) */ LogCrit(COMPONENT_NFS_CB, "WARNING: Using (debug) short machine cred lifetime!"); krb5_get_init_creds_opt_set_tkt_life(&options, 5 * 60); #endif opts = &options; #endif code = krb5_get_init_creds_keytab(context, &my_creds, ple->princ, kt, 0, NULL, opts); if (code != 0) { k5err = gssd_k5_err_msg(context, code); LogWarn(COMPONENT_NFS_CB, "WARNING: %s while getting initial ticket for principal '%s' using keytab '%s'", k5err, pname ? pname : "", kt_name); goto out; } /* * Initialize cache file which we're going to be using */ if (use_memcache) cache_type = "MEMORY"; else cache_type = "FILE"; code = snprintf(cc_name, sizeof(cc_name), "%s:%s/%s%s_%s", cache_type, ccachesearch[0], GSSD_DEFAULT_CRED_PREFIX, GSSD_DEFAULT_MACHINE_CRED_SUFFIX, ple->realm); if (code < 0) { /* code and errno already set */ goto out; } else if (code >= sizeof(cc_name)) { code = -1; errno = EINVAL; goto out; } ple->endtime = my_creds.times.endtime; if (ple->ccname != NULL) gsh_free(ple->ccname); ple->ccname = gsh_strdup(cc_name); code = krb5_cc_resolve(context, cc_name, &ccache); if (code != 0) { k5err = gssd_k5_err_msg(context, code); LogCrit(COMPONENT_NFS_CB, "ERROR: %s while opening credential cache '%s'", k5err, cc_name); goto out; } code = krb5_cc_initialize(context, ccache, ple->princ); if (code != 0) { k5err = gssd_k5_err_msg(context, code); LogCrit(COMPONENT_NFS_CB, "ERROR: %s while initializing credential cache '%s'", k5err, cc_name); goto out; } code = krb5_cc_store_cred(context, ccache, &my_creds); if (code != 0) { k5err = gssd_k5_err_msg(context, code); LogCrit(COMPONENT_NFS_CB, "ERROR: %s while storing credentials in '%s'", k5err, cc_name); goto out; } /* if we get this far, let gss mech know */ gssd_set_krb5_ccache_name(cc_name); code = 0; LogFullDebug( COMPONENT_NFS_CB, "Successfully obtained machine credentials for principal '%s' stored in ccache '%s'", pname, cc_name); out: #if HAVE_KRB5_GET_INIT_CREDS_OPT_SET_ADDRESSLESS if (init_opts) krb5_get_init_creds_opt_free(context, init_opts); #endif if (pname) k5_free_unparsed_name(context, pname); if (ccache) krb5_cc_close(context, ccache); krb5_free_cred_contents(context, &my_creds); gsh_free(k5err); return code; } /* * Depending on the version of Kerberos, we either need to use * a private function, or simply set the environment variable. */ static void gssd_set_krb5_ccache_name(char *ccname) { #ifdef USE_GSS_KRB5_CCACHE_NAME u_int maj_stat, min_stat; LogFullDebug(COMPONENT_NFS_CB, "using gss_krb5_ccache_name to select krb5 ccache %s", ccname); maj_stat = gss_krb5_ccache_name(&min_stat, ccname, NULL); if (maj_stat != GSS_S_COMPLETE) { LogCrit(COMPONENT_NFS_CB, "WARNING: gss_krb5_ccache_name with name '%s' failed (%s)", ccname, error_message(min_stat)); } #else /* * Set the KRB5CCNAME environment variable to tell the krb5 code * which credentials cache to use. (Instead of using the private * function above for which there is no generic gssapi * equivalent.) */ LogFullDebug(COMPONENT_NFS_CB, "using environment variable to select krb5 ccache %s", ccname); setenv("KRB5CCNAME", ccname, 1); #endif } /* * Given a principal, find a matching ple structure */ static struct gssd_k5_kt_princ *find_ple_by_princ(krb5_context context, krb5_principal princ) { struct gssd_k5_kt_princ *ple; for (ple = gssd_k5_kt_princ_list; ple != NULL; ple = ple->next) { if (krb5_principal_compare(context, ple->princ, princ)) return ple; } /* no match found */ return NULL; } /* * Create, initialize, and add a new ple structure to the global list */ static struct gssd_k5_kt_princ *new_ple(krb5_context context, krb5_principal princ) { struct gssd_k5_kt_princ *ple = NULL, *p; krb5_error_code code; char *default_realm; int is_default_realm = 0; ple = gsh_calloc(1, sizeof(struct gssd_k5_kt_princ)); #ifdef HAVE_KRB5 ple->realm = gsh_malloc(princ->realm.length + 1); memcpy(ple->realm, princ->realm.data, princ->realm.length); ple->realm[princ->realm.length] = '\0'; #else ple->realm = gsh_strdup(princ->realm); #endif code = krb5_copy_principal(context, princ, &ple->princ); if (code) { gsh_free(ple->realm); gsh_free(ple); return NULL; } /* * Add new entry onto the list (if this is the default * realm, always add to the front of the list) */ code = krb5_get_default_realm(context, &default_realm); if (code == 0) { if (strcmp(ple->realm, default_realm) == 0) is_default_realm = 1; k5_free_default_realm(context, default_realm); } if (is_default_realm) { ple->next = gssd_k5_kt_princ_list; gssd_k5_kt_princ_list = ple; } else { p = gssd_k5_kt_princ_list; while (p != NULL && p->next != NULL) p = p->next; if (p == NULL) gssd_k5_kt_princ_list = ple; else p->next = ple; } return ple; } /* * Given a principal, find an existing ple structure, or create one */ static struct gssd_k5_kt_princ *get_ple_by_princ(krb5_context context, krb5_principal princ) { struct gssd_k5_kt_princ *ple; PTHREAD_MUTEX_lock(&ple_mtx); ple = find_ple_by_princ(context, princ); if (ple == NULL) ple = new_ple(context, princ); PTHREAD_MUTEX_unlock(&ple_mtx); return ple; } /* * Given a (possibly unqualified) hostname, * return the fully qualified (lower-case!) hostname */ static int get_full_hostname(const char *inhost, char *outhost, int outhostlen) { struct addrinfo *addrs = NULL; struct addrinfo hints; int retval; char *c; memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; hints.ai_family = PF_UNSPEC; hints.ai_flags = AI_CANONNAME; /* Get full target hostname */ retval = gsh_getaddrinfo(inhost, NULL, &hints, &addrs, nfs_param.core_param.enable_AUTHSTATS); if (retval) { LogWarn(COMPONENT_NFS_CB, "%s while getting full hostname for '%s'", gai_strerror(retval), inhost); return retval; } if (strlcpy(outhost, addrs->ai_canonname, outhostlen) >= outhostlen) { retval = -1; goto out; } for (c = outhost; *c != '\0'; c++) *c = tolower(*c); LogFullDebug(COMPONENT_NFS_CB, "Full hostname for '%s' is '%s'", inhost, outhost); retval = 0; out: freeaddrinfo(addrs); return retval; } /* * If principal matches the given realm and service name, * and has *any* instance (hostname), return 1. * Otherwise return 0, indicating no match. */ #ifdef HAVE_KRB5 static int realm_and_service_match(krb5_principal p, const char *realm, const char *service) { /* Must have two components */ if (p->length != 2) return 0; if ((strlen(realm) == p->realm.length) && (strncmp(realm, p->realm.data, p->realm.length) == 0) && (strlen(service) == p->data[0].length) && (strncmp(service, p->data[0].data, p->data[0].length) == 0)) return 1; return 0; } #else static int realm_and_service_match(krb5_context context, krb5_principal p, const char *realm, const char *service) { const char *name, *inst; if (p->name.name_string.len != 2) return 0; name = krb5_principal_get_comp_string(context, p, 0); inst = krb5_principal_get_comp_string(context, p, 1); if (name == NULL || inst == NULL) return 0; if ((strcmp(realm, p->realm) == 0) && (strcmp(service, name) == 0)) return 1; return 0; } #endif /* * Search the given keytab file looking for an entry with the given * service name and realm, ignoring hostname (instance). * * Returns: * 0 => No error * non-zero => An error occurred * * If a keytab entry is found, "found" is set to one, and the keytab * entry is returned in "kte". Otherwise, "found" is zero, and the * value of "kte" is unpredictable. */ static int gssd_search_krb5_keytab(krb5_context context, krb5_keytab kt, const char *realm, const char *service, int *found, krb5_keytab_entry *kte) { krb5_kt_cursor cursor; krb5_error_code code; struct gssd_k5_kt_princ *ple; int retval = -1, status; char kt_name[BUFSIZ]; char *pname; char *k5err = NULL; if (found == NULL) { retval = EINVAL; goto out; } *found = 0; /* * Look through each entry in the keytab file and determine * if we might want to use it as machine credentials. If so, * save info in the global principal list (gssd_k5_kt_princ_list). */ code = krb5_kt_get_name(context, kt, kt_name, BUFSIZ); if (code != 0) { k5err = gssd_k5_err_msg(context, code); LogCrit(COMPONENT_NFS_CB, "ERROR: %s attempting to get keytab name", k5err); gsh_free(k5err); retval = code; goto out; } code = krb5_kt_start_seq_get(context, kt, &cursor); if (code != 0) { k5err = gssd_k5_err_msg(context, code); LogCrit(COMPONENT_NFS_CB, "ERROR: %s while beginning keytab scan for keytab '%s'", k5err, kt_name); gsh_free(k5err); retval = code; goto out; } while ((code = krb5_kt_next_entry(context, kt, kte, &cursor)) == 0) { code = krb5_unparse_name(context, kte->principal, &pname); if (code != 0) { k5err = gssd_k5_err_msg(context, code); LogCrit(COMPONENT_NFS_CB, "WARNING: Skipping keytab entry because we failed to unparse principal name: %s", k5err); k5_free_kt_entry(context, kte); gsh_free(k5err); continue; } LogFullDebug(COMPONENT_NFS_CB, "Processing keytab entry for principal '%s'", pname); /* Use the first matching keytab entry found */ #ifdef HAVE_KRB5 status = realm_and_service_match(kte->principal, realm, service); #else status = realm_and_service_match(context, kte->principal, realm, service); #endif if (status) { LogFullDebug(COMPONENT_NFS_CB, "We WILL use this entry (%s)", pname); ple = get_ple_by_princ(context, kte->principal); /* * Return, don't free, keytab entry if * we were successful! */ if (unlikely(ple == NULL)) { retval = ENOMEM; k5_free_kt_entry(context, kte); k5_free_unparsed_name(context, pname); (void)krb5_kt_end_seq_get(context, kt, &cursor); goto out; } else { retval = 0; *found = 1; } k5_free_unparsed_name(context, pname); break; } else { LogFullDebug(COMPONENT_NFS_CB, "We will NOT use this entry (%s)", pname); } k5_free_unparsed_name(context, pname); k5_free_kt_entry(context, kte); } code = krb5_kt_end_seq_get(context, kt, &cursor); if (code != 0) { k5err = gssd_k5_err_msg(context, code); LogCrit(COMPONENT_NFS_CB, "WARNING: %s while ending keytab scan for keytab '%s'", k5err, kt_name); gsh_free(k5err); } retval = 0; out: return retval; } /* * Find a keytab entry to use for a given target hostname. * Tries to find the most appropriate keytab to use given the * name of the host we are trying to connect with. */ static int find_keytab_entry(krb5_context context, krb5_keytab kt, const char *hostname, krb5_keytab_entry *kte, const char **svcnames) { krb5_error_code code; char **realmnames = NULL; char myhostname[NI_MAXHOST], targethostname[NI_MAXHOST]; char myhostad[NI_MAXHOST + 1]; int i, j, retval; char *default_realm = NULL; char *realm; char *k5err = NULL; int tried_all = 0, tried_default = 0; krb5_principal princ; /* Get full target hostname */ retval = get_full_hostname(hostname, targethostname, sizeof(targethostname)); if (retval) goto out; /* Get full local hostname */ retval = gsh_gethostname(myhostname, sizeof(myhostname), nfs_param.core_param.enable_AUTHSTATS); if (retval) { k5err = gssd_k5_err_msg(context, retval); LogWarn(COMPONENT_NFS_CB, "%s while getting local hostname", k5err); gsh_free(k5err); goto out; } /* Compute the active directory machine name HOST$ */ strcpy(myhostad, myhostname); for (i = 0; myhostad[i] != 0; ++i) myhostad[i] = toupper(myhostad[i]); myhostad[i] = '$'; myhostad[i + 1] = 0; retval = get_full_hostname(myhostname, myhostname, sizeof(myhostname)); if (retval) goto out; code = krb5_get_default_realm(context, &default_realm); if (code) { retval = code; k5err = gssd_k5_err_msg(context, code); LogWarn(COMPONENT_NFS_CB, "%s while getting default realm name", k5err); gsh_free(k5err); goto out; } /* * Get the realm name(s) for the target hostname. * In reality, this function currently only returns a * single realm, but we code with the assumption that * someday it may actually return a list. */ code = krb5_get_host_realm(context, targethostname, &realmnames); if (code) { k5err = gssd_k5_err_msg(context, code); LogCrit(COMPONENT_NFS_CB, "ERROR: %s while getting realm(s) for host '%s'", k5err, targethostname); gsh_free(k5err); retval = code; goto out; } /* * Try the "appropriate" realm first, and if nothing found for that * realm, try the default realm (if it hasn't already been tried). */ i = 0; realm = realmnames[i]; while (1) { if (realm == NULL) { tried_all = 1; if (!tried_default) realm = default_realm; } if (tried_all && tried_default) break; if (strcmp(realm, default_realm) == 0) tried_default = 1; for (j = 0; svcnames[j] != NULL; j++) { char spn[1028]; /* * The special svcname "$" means 'try the active * directory machine account' */ if (strcmp(svcnames[j], "$") == 0) { retval = snprintf(spn, sizeof(spn), "%s@%s", myhostad, realm); if (retval < 0) { goto out; } else if (retval >= sizeof(spn)) { retval = -1; goto out; } code = krb5_build_principal_ext( context, &princ, strlen(realm), realm, strlen(myhostad), myhostad, NULL); } else { retval = snprintf(spn, sizeof(spn), "%s/%s@%s", svcnames[j], myhostname, realm); if (retval < 0) { goto out; } else if (retval >= sizeof(spn)) { retval = -1; goto out; } code = krb5_build_principal_ext( context, &princ, strlen(realm), realm, strlen(svcnames[j]), svcnames[j], strlen(myhostname), myhostname, NULL); } if (code) { k5err = gssd_k5_err_msg(context, code); LogWarn(COMPONENT_NFS_CB, "%s while building principal for '%s'", k5err, spn); gsh_free(k5err); continue; } code = krb5_kt_get_entry(context, kt, princ, 0, 0, kte); krb5_free_principal(context, princ); if (code) { k5err = gssd_k5_err_msg(context, code); LogFullDebug( COMPONENT_NFS_CB, "%s while getting keytab entry for '%s'", k5err, spn); gsh_free(k5err); } else { LogFullDebug( COMPONENT_NFS_CB, "Success getting keytab entry for '%s'", spn); retval = 0; goto out; } retval = code; } /* * Nothing found with our hostname instance, now look for * names with any instance (they must have an instance) */ for (j = 0; svcnames[j] != NULL; j++) { int found = 0; if (strcmp(svcnames[j], "$") == 0) continue; code = gssd_search_krb5_keytab( context, kt, realm, svcnames[j], &found, kte); if (!code && found) { LogFullDebug( COMPONENT_NFS_CB, "Success getting keytab entry for %s/*@%s", svcnames[j], realm); retval = 0; goto out; } } if (!tried_all) { i++; realm = realmnames[i]; } } out: if (default_realm) k5_free_default_realm(context, default_realm); if (realmnames) krb5_free_host_realm(context, realmnames); return retval; } /* * A common routine for getting the Kerberos error message */ static char *gssd_k5_err_msg(krb5_context context, krb5_error_code code) { #if HAVE_KRB5_GET_ERROR_MESSAGE if (context != NULL) { const char *origmsg; char *msg = NULL; origmsg = krb5_get_error_message(context, code); msg = gsh_strdup(origmsg); krb5_free_error_message(context, origmsg); return msg; } #endif #if HAVE_KRB5 return gsh_strdup(error_message(code)); #else if (context != NULL) return gsh_strdup(krb5_get_err_text(context, code)); else return gsh_strdup(error_message(code)); #endif } /* Public Interfaces */ char *ccachesearch[GSSD_MAX_CCACHE_SEARCH + 1]; /* * Obtain (or refresh if necessary) Kerberos machine credentials */ int gssd_refresh_krb5_machine_credential(char *hostname, struct gssd_k5_kt_princ *ple, char *service) { krb5_error_code code = 0; krb5_context context; krb5_keytab kt = NULL; int retval = 0; char *k5err = NULL; const char *svcnames[5] = { "$", "root", "nfs", "host", NULL }; char *keytabfile = nfs_param.krb5_param.keytab; /* * If a specific service name was specified, use it. * Otherwise, use the default list. */ if (service != NULL && strcmp(service, "*") != 0) { svcnames[0] = service; svcnames[1] = NULL; } if (hostname == NULL && ple == NULL) return EINVAL; code = krb5_init_context(&context); if (code) { k5err = gssd_k5_err_msg(NULL, code); LogCrit(COMPONENT_NFS_CB, "ERROR: %s: %s while initializing krb5 context", __func__, k5err); retval = code; gsh_free(k5err); goto out_wo_context; } code = krb5_kt_resolve(context, keytabfile, &kt); if (code != 0) { k5err = gssd_k5_err_msg(context, code); LogCrit(COMPONENT_NFS_CB, "ERROR: %s: %s while resolving keytab '%s'", __func__, k5err, keytabfile); gsh_free(k5err); goto out; } if (ple == NULL) { krb5_keytab_entry kte; code = find_keytab_entry(context, kt, hostname, &kte, svcnames); if (code) { LogCrit(COMPONENT_NFS_CB, "ERROR: %s: no usable keytab entry found in keytab %s for connection with host %s", __func__, keytabfile, hostname); retval = code; goto out; } ple = get_ple_by_princ(context, kte.principal); k5_free_kt_entry(context, &kte); if (ple == NULL) { char *pname; if ((krb5_unparse_name(context, kte.principal, &pname))) { pname = NULL; } LogCrit(COMPONENT_NFS_CB, "ERROR: %s: Could not locate or create ple struct for principal %s for connection with host %s", __func__, pname ? pname : "", hostname); if (pname) k5_free_unparsed_name(context, pname); goto out; } } retval = gssd_get_single_krb5_cred(context, kt, ple, 0); out: if (kt) krb5_kt_close(context, kt); krb5_free_context(context); out_wo_context: return retval; } int gssd_check_mechs(void) { u_int32_t maj_stat, min_stat; gss_OID_set supported_mechs = GSS_C_NO_OID_SET; int retval = -1; maj_stat = gss_indicate_mechs(&min_stat, &supported_mechs); if (maj_stat != GSS_S_COMPLETE) { LogCrit(COMPONENT_NFS_CB, "Unable to obtain list of supported mechanisms. Check that gss library is properly configured."); goto out; } if (supported_mechs == GSS_C_NO_OID_SET || supported_mechs->count == 0) { LogCrit(COMPONENT_NFS_CB, "Unable to obtain list of supported mechanisms. Check that gss library is properly configured."); goto out; } maj_stat = gss_release_oid_set(&min_stat, &supported_mechs); retval = 0; out: return retval; } /* This function destroys the krb5 creds cache for the input principal entry */ static void destroy_krb5_creds_cache(struct gssd_k5_kt_princ *ple, krb5_context *context) { krb5_error_code code = 0; char *k5err = NULL; krb5_ccache ccache; /* Skip if there is no cache to be destroyed */ if (!ple->ccname) return; code = krb5_cc_resolve(*context, ple->ccname, &ccache); if (code) { k5err = gssd_k5_err_msg(*context, code); LogCrit(COMPONENT_NFS_CB, "Received %s while resolving krb5 cache %s", k5err, ple->ccname); gsh_free(k5err); return; } code = krb5_cc_destroy(*context, ccache); if (code) { k5err = gssd_k5_err_msg(*context, code); LogCrit(COMPONENT_NFS_CB, "Received %s while destroying krb5 cache %s", k5err, ple->ccname); gsh_free(k5err); return; } LogInfo(COMPONENT_NFS_CB, "krb5 cache %s has been destroyed", ple->ccname); } /* This function frees-up a ple entry */ static void free_ple_entry(struct gssd_k5_kt_princ *ple, krb5_context *context) { if (ple->realm != NULL) gsh_free(ple->realm); if (ple->ccname != NULL) gsh_free(ple->ccname); if (context != NULL) krb5_free_principal(*context, ple->princ); gsh_free(ple); } /** * @brief Free the global krb5 principal entries and their krb5 caches * * If we have the krb5 context, iterate over the in-memory principal list, * and destroy the krb5 ticket cache for each list entry. * If we can't destroy the ticket cache for an entry due to the absence of * krb5 context or due to a failure while destroying the krb5 cache, we still * free the entry from our memory. */ static void clear_global_krb5_principal_list(krb5_context *context) { struct gssd_k5_kt_princ *ple, *next_ple; PTHREAD_MUTEX_lock(&ple_mtx); ple = gssd_k5_kt_princ_list; while (ple != NULL) { /* Store for later retrieval after ple is freed */ next_ple = ple->next; if (context != NULL) destroy_krb5_creds_cache(ple, context); free_ple_entry(ple, context); ple = next_ple; } gssd_k5_kt_princ_list = NULL; PTHREAD_MUTEX_unlock(&ple_mtx); } /** * @brief Clear gss-credentials caches * * This function clears global krb5 principal entries, and destroys their * respective krb5 caches. */ void gssd_clear_cred_cache(void) { krb5_context context; krb5_error_code code = 0; char *k5err = NULL; code = krb5_init_context(&context); if (code) { k5err = gssd_k5_err_msg(NULL, code); LogCrit(COMPONENT_NFS_CB, "Received %s while initializing krb5 context before cache cleanup", k5err); gsh_free(k5err); /* If we cannot init krb5-context (required for destroying krb5 * caches), we still free the cached global principal list. */ clear_global_krb5_principal_list(NULL); return; } /* Clear the cached global krb5 principal list and destroy the krb5 * caches. */ clear_global_krb5_principal_list(&context); krb5_free_context(context); } nfs-ganesha-6.5/src/RPCAL/gss_extra.c000066400000000000000000000063471473756622300174020ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-3-Clause /* Copyright (c) 2000 The Regents of the University of Michigan. All rights reserved. Copyright (c) 2000 Dug Song . All rights reserved, all wrongs reversed. 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. 3. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED ``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 REGENTS 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. Id: svc_auth_gss.c,v 1.28 2002/10/15 21:29:36 kwc Exp */ #include "config.h" #include #include #include #include #include "gsh_rpc.h" #ifdef HAVE_HEIMDAL #include #define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE #else #include #include #endif #include "nfs_core.h" #include "log.h" /** * @brief Convert GSSAPI status to a string * * @param[out] outmsg Output string * @param[in] tag Tag * @param[in] maj_stat GSSAPI major status * @param[in] min_stat GSSAPI minor status */ void log_sperror_gss(char *outmsg, OM_uint32 maj_stat, OM_uint32 min_stat) { OM_uint32 smin; gss_buffer_desc msg; gss_buffer_desc msg2; int msg_ctx = 0; if (gss_display_status(&smin, maj_stat, GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &msg) != GSS_S_COMPLETE) { sprintf(outmsg, "untranslatable error"); return; } if (gss_display_status(&smin, min_stat, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &msg2) != GSS_S_COMPLETE) { gss_release_buffer(&smin, &msg); sprintf(outmsg, "%s : untranslatable error", (char *)msg.value); return; } sprintf(outmsg, "%s : %s ", (char *)msg.value, (char *)msg2.value); gss_release_buffer(&smin, &msg); gss_release_buffer(&smin, &msg2); } const char *str_gc_proc(rpc_gss_proc_t gc_proc) { switch (gc_proc) { case RPCSEC_GSS_DATA: return "RPCSEC_GSS_DATA"; case RPCSEC_GSS_INIT: return "RPCSEC_GSS_INIT"; case RPCSEC_GSS_CONTINUE_INIT: return "RPCSEC_GSS_CONTINUE_INIT"; case RPCSEC_GSS_DESTROY: return "RPCSEC_GSS_DESTROY"; } return "unknown"; } nfs-ganesha-6.5/src/RPCAL/nfs_dupreq.c000066400000000000000000001207021473756622300175410ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Portions Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * Portions Copyright (C) 2012, The Linux Box Corporation * Contributor : Matt Benjamin * * This program 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 3 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 * 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 * * ------------- */ /** * @file nfs_dupreq.c * @author Matt Benjamin * @brief NFS Duplicate Request Cache */ #include "config.h" #include #include #include #include #include #include /* XXX prune: */ #include "log.h" #include "nfs_proto_functions.h" #include "nfs_dupreq.h" #include "city.h" #include "abstract_mem.h" #include "gsh_intrinsic.h" #define DUPREQ_NOCACHE ((void *)0x02) #define DUPREQ_NOCACHE_NORES ((void *)0x03) #define DUPREQ_MAX_RETRIES 5 #define NFS_pcp nfs_param.core_param #define NFS_program NFS_pcp.program pool_t *dupreq_pool; pool_t *nfs_res_pool; pool_t *tcp_drc_pool; /* pool of per-connection DRC objects */ const char *dupreq_status_table[] = { "DUPREQ_SUCCESS", "DUPREQ_BEING_PROCESSED", "DUPREQ_EXISTS", }; const char *dupreq_state_table[] = { "DUPREQ_START", "DUPREQ_COMPLETE" }; /* drc_t holds the request/response cache. There is a single drc_t for * all udp connections. There is a drc_t for each tcp connection (aka * socket). Since a client could close a socket and reconnect, we would * like to use the same drc cache for the reconnection. For this reason, * we don't want to free the drc as soon as the tcp connection gets * closed, but rather keep them in a recycle list for sometime. * * The life of tcp drc: it gets allocated when we process the first * request on the connection. It is put into rbtree (tcp_drc_recycle_t). * drc cache maintains a ref count. Every request as well as the xprt * holds a ref count. Its ref count should go to zero when the * connection's xprt gets freed (all requests should be completed on the * xprt by this time). When the ref count goes to zero, it is also put * into a recycle queue (tcp_drc_recycle_q). When a reconnection * happens, we hope to find the same drc that was used before, and the * ref count goes up again. At the same time, the drc will be removed * from the recycle queue. Only drc's with ref count zero end up in the * recycle queue. If a reconnection doesn't happen in time, the drc gets * freed by drc_free_expired() after some period of inactivity. * * Most ref count methods assume that a ref count doesn't go up from * zero, so a thread that decrements the ref count to zero would be the * only one acting on it, and it could do so without any locks! Since * the drc ref count could go up from zero, care must be taken. The * thread that decrements the ref count to zero will have to put the drc * into the recycle queue. It will do so only after dropping the lock in * the current implementation. If we let nfs_dupreq_get_drc() reuse the * drc before it gets into recycle queue, we could end up with multiple * threads that decrement the ref count to zero. * * Life of a dupreq: A thread processing an NFS request calls the * following functions in the order listed below: * * #1. nfs_dupreq_start(): This creates/gets a dupreq for the request if * it needs DRC. Newly created dupreq is placed in a hash table as well * as in a list. Its refcnt will be 2 at the beginning, one for being in * the hash table and the other for the request call path. If a dupreq * already exists in the hash table, it is provided with a hold! * * #2. nfs_dupreq_finish()/nfs_dupreq_delete(): Only one of these two * functions will be called. If the request is completed successfully, * nfs_dupreq_finish() is called which sets the state of the dupreq to * complete. If the request processing fails, usually there is no reason * to save the response, so nfs_dupreq_delete() is called to remove the * dupreq from the hash table. Neither function releases the hold placed * on the dupreq in nfs_dupreq_start() for the request call path. * nfs_dupreq_delete() does release a hold on the dupreq for removing it * from the hash table itself though. * * #3. Finally nfs_dupreq_rele(): At the end of the request processing, * this function is called. This releases the hold placed on the dupreq * in the nfs_dupreq_start() for the request call path. * * A dupreq exists at least until the nfs_dupreq_rele() call. A hashed * dupreq will exist beyond this call as it is in the hash table. A * dupreq eventually gets removed from the hash table when the drc gets * freed or in nfs_dupreq_finish() that decides to take out few dupreqs! */ struct drc_st { pthread_mutex_t drc_st_mtx; drc_t udp_drc; /* shared DRC */ struct rbtree_x tcp_drc_recycle_t; TAILQ_HEAD(drc_st_tailq, drc) tcp_drc_recycle_q; /* fifo */ int32_t tcp_drc_recycle_qlen; time_t last_expire_check; uint32_t expire_delta; }; static struct drc_st *drc_st; /** * @brief Comparison function for duplicate request entries. * * @param[in] lhs An integer * @param[in] rhs Another integer * * @return -1 if the left-hand is smaller than the right, 0 if they * are equal, and 1 if the left-hand is larger. */ static inline int uint32_cmpf(uint32_t lhs, uint32_t rhs) { if (lhs < rhs) return -1; if (lhs == rhs) return 0; return 1; } /** * @brief Comparison function for duplicate request entries. * * @param[in] lhs An integer * @param[in] rhs Another integer * * @return -1 if the left-hand is smaller than the right, 0 if they * are equal, and 1 if the left-hand is larger. */ static inline int uint64_cmpf(uint64_t lhs, uint64_t rhs) { if (lhs < rhs) return -1; if (lhs == rhs) return 0; return 1; } /** * @brief Comparison function for entries in a shared DRC * * @param[in] lhs Left-hand-side * @param[in] rhs Right-hand-side * * @return -1,0,1. */ static inline int dupreq_shared_cmpf(const struct opr_rbtree_node *lhs, const struct opr_rbtree_node *rhs) { dupreq_entry_t *lk, *rk; lk = opr_containerof(lhs, dupreq_entry_t, rbt_k); rk = opr_containerof(rhs, dupreq_entry_t, rbt_k); switch (sockaddr_cmpf(&lk->hin.addr, &rk->hin.addr, false)) { case -1: return -1; case 0: switch (uint32_cmpf(lk->hin.tcp.rq_xid, rk->hin.tcp.rq_xid)) { case -1: return -1; case 0: return uint64_cmpf(lk->hk, rk->hk); default: break; } /* xid */ break; default: break; } /* addr+port */ return 1; } /** * @brief Comparison function for entries in a per-connection (TCP) DRC * * @param[in] lhs Left-hand-side * @param[in] rhs Right-hand-side * * @return -1,0,1. */ static inline int dupreq_tcp_cmpf(const struct opr_rbtree_node *lhs, const struct opr_rbtree_node *rhs) { dupreq_entry_t *lk, *rk; LogDebug(COMPONENT_DUPREQ, "Entering %s", __func__); lk = opr_containerof(lhs, dupreq_entry_t, rbt_k); rk = opr_containerof(rhs, dupreq_entry_t, rbt_k); if (lk->hin.tcp.rq_xid < rk->hin.tcp.rq_xid) return -1; if (lk->hin.tcp.rq_xid == rk->hin.tcp.rq_xid) { LogDebug(COMPONENT_DUPREQ, "xids eq %" PRIu32 ", ck1 %" PRIu64 " ck2 %" PRIu64, lk->hin.tcp.rq_xid, lk->hk, rk->hk); return uint64_cmpf(lk->hk, rk->hk); } return 1; } /** * @brief Comparison function for recycled per-connection (TCP) DRCs * * @param[in] lhs Left-hand-side * @param[in] rhs Right-hand-side * * @return -1,0,1. */ static inline int drc_recycle_cmpf(const struct opr_rbtree_node *lhs, const struct opr_rbtree_node *rhs) { drc_t *lk, *rk; lk = opr_containerof(lhs, drc_t, d_u.tcp.recycle_k); rk = opr_containerof(rhs, drc_t, d_u.tcp.recycle_k); return sockaddr_cmpf(&lk->d_u.tcp.addr, &rk->d_u.tcp.addr, false); } /** * @brief Initialize a shared duplicate request cache */ static inline void init_shared_drc(void) { drc_t *drc = &drc_st->udp_drc; int ix, code __attribute__((unused)) = 0; drc->type = DRC_UDP_V234; drc->refcnt = 0; drc->retwnd = 0; drc->d_u.tcp.recycle_time = 0; drc->maxsize = nfs_param.core_param.drc.udp.size; drc->cachesz = nfs_param.core_param.drc.udp.cachesz; drc->npart = nfs_param.core_param.drc.udp.npart; drc->hiwat = nfs_param.core_param.drc.udp.hiwat; PTHREAD_MUTEX_init(&drc->drc_mtx, NULL); /* init dict */ code = rbtx_init(&drc->xt, dupreq_shared_cmpf, drc->npart, RBT_X_FLAG_ALLOC | RBT_X_FLAG_CACHE_WT); assert(!code); /* completed requests */ TAILQ_INIT(&drc->dupreq_q); /* init closed-form "cache" partition */ for (ix = 0; ix < drc->npart; ++ix) { struct rbtree_x_part *xp = &(drc->xt.tree[ix]); drc->xt.cachesz = drc->cachesz; xp->cache = gsh_calloc(drc->cachesz, sizeof(struct opr_rbtree_node *)); } } /* Cleanup on shutdown */ void dupreq2_cleanup(void) { PTHREAD_MUTEX_destroy(&drc_st->drc_st_mtx); } struct cleanup_list_element dupreq2_cleanup_element = { .clean = dupreq2_cleanup, }; /** * @brief Initialize the DRC package. */ void dupreq2_pkginit(void) { int code __attribute__((unused)) = 0; dupreq_pool = pool_basic_init("Duplicate Request Pool", sizeof(dupreq_entry_t)); nfs_res_pool = pool_basic_init("nfs_res_t pool", sizeof(nfs_res_t)); tcp_drc_pool = pool_basic_init("TCP DRC Pool", sizeof(drc_t)); drc_st = gsh_calloc(1, sizeof(struct drc_st)); /* init shared statics */ PTHREAD_MUTEX_init(&drc_st->drc_st_mtx, NULL); /* recycle_t */ code = rbtx_init(&drc_st->tcp_drc_recycle_t, drc_recycle_cmpf, nfs_param.core_param.drc.tcp.recycle_npart, RBT_X_FLAG_ALLOC); /* XXX error? */ /* init recycle_q */ TAILQ_INIT(&drc_st->tcp_drc_recycle_q); drc_st->tcp_drc_recycle_qlen = 0; drc_st->last_expire_check = time(NULL); drc_st->expire_delta = nfs_param.core_param.drc.tcp.recycle_expire_s; /* UDP DRC is global, shared */ init_shared_drc(); RegisterCleanup(&dupreq2_cleanup_element); } /** * @brief Determine the protocol of the supplied TI-RPC SVCXPRT* * * @param[in] xprt The SVCXPRT * * @return IPPROTO_UDP or IPPROTO_TCP. */ static inline unsigned int get_ipproto_by_xprt(SVCXPRT *xprt) { switch (xprt->xp_type) { case XPRT_UDP: case XPRT_UDP_RENDEZVOUS: return IPPROTO_UDP; case XPRT_TCP: case XPRT_TCP_RENDEZVOUS: return IPPROTO_TCP; default: break; } return IPPROTO_IP; /* Dummy output */ } /** * @brief Determine the dupreq2 DRC type to handle the supplied svc_req * * @param[in] req The svc_req being processed * * @return a value of type enum_drc_type. */ static inline enum drc_type get_drc_type(struct svc_req *req) { if (get_ipproto_by_xprt(req->rq_xprt) == IPPROTO_UDP) return DRC_UDP_V234; else { if (req->rq_msg.cb_vers == 4) return DRC_TCP_V4; } return DRC_TCP_V3; } /** * @brief Allocate a duplicate request cache * * @param[in] dtype Style DRC to allocate (e.g., TCP, by enum drc_type) * @param[in] maxsz Upper bound on requests to cache * @param[in] cachesz Number of entries in the closed hash partition * @param[in] flags DRC flags * * @return the drc, if successfully allocated, else NULL. */ static inline drc_t *alloc_tcp_drc(enum drc_type dtype) { drc_t *drc = pool_alloc(tcp_drc_pool); int ix, code __attribute__((unused)) = 0; drc->type = dtype; /* DRC_TCP_V3 or DRC_TCP_V4 */ drc->refcnt = 0; drc->retwnd = 0; drc->d_u.tcp.recycle_time = 0; drc->maxsize = nfs_param.core_param.drc.tcp.size; drc->cachesz = nfs_param.core_param.drc.tcp.cachesz; drc->npart = nfs_param.core_param.drc.tcp.npart; drc->hiwat = nfs_param.core_param.drc.tcp.hiwat; PTHREAD_MUTEX_init(&drc->drc_mtx, NULL); /* init dict */ code = rbtx_init(&drc->xt, dupreq_tcp_cmpf, drc->npart, RBT_X_FLAG_ALLOC | RBT_X_FLAG_CACHE_WT); assert(!code); /* completed requests */ TAILQ_INIT(&drc->dupreq_q); /* recycling DRC */ TAILQ_INIT_ENTRY(drc, d_u.tcp.recycle_q); /* init "cache" partition */ for (ix = 0; ix < drc->npart; ++ix) { struct rbtree_x_part *xp = &(drc->xt.tree[ix]); drc->xt.cachesz = drc->cachesz; xp->cache = gsh_calloc(drc->cachesz, sizeof(struct opr_rbtree_node *)); } return drc; } /** * @brief Deep-free a per-connection (TCP) duplicate request cache * * @param[in] drc The DRC to dispose * * Assumes that the DRC has been allocated from the tcp_drc_pool. */ static inline void free_tcp_drc(drc_t *drc) { int ix; for (ix = 0; ix < drc->npart; ++ix) { if (drc->xt.tree[ix].cache) gsh_free(drc->xt.tree[ix].cache); } rbtx_cleanup(&drc->xt); PTHREAD_MUTEX_destroy(&drc->drc_mtx); LogFullDebug(COMPONENT_DUPREQ, "free TCP drc %p", drc); pool_free(tcp_drc_pool, drc); } /** * @brief Increment the reference count on a DRC * * @param[in] drc The DRC to ref * * @return the new value of refcnt. */ static inline uint32_t nfs_dupreq_ref_drc(drc_t *drc) { return ++(drc->refcnt); /* locked */ } /** * @brief Decrement the reference count on a DRC * * @param[in] drc The DRC to unref * * @return the new value of refcnt. */ static inline uint32_t nfs_dupreq_unref_drc(drc_t *drc) { return --(drc->refcnt); /* locked */ } #define DRC_ST_LOCK() PTHREAD_MUTEX_lock(&drc_st->drc_st_mtx) #define DRC_ST_UNLOCK() PTHREAD_MUTEX_unlock(&drc_st->drc_st_mtx) #define DRC_QLEN_EXCEED_HIWAT() \ (drc_st->tcp_drc_recycle_qlen > nfs_param.core_param.drc.recycle_hiwat) /** * @brief Check for expired TCP DRCs. */ static inline void dupreq_entry_put(dupreq_entry_t *dv); static inline void drc_free_expired(void) { drc_t *drc; time_t now = time(NULL); struct rbtree_x_part *t; struct opr_rbtree_node *odrc = NULL; struct dupreq_entry *dv; struct dupreq_entry *tdv; DRC_ST_LOCK(); if (((drc_st->tcp_drc_recycle_qlen < 1) || (now - drc_st->last_expire_check) < 600) && /* 10m */ !DRC_QLEN_EXCEED_HIWAT()) goto unlock; do { drc = TAILQ_FIRST(&drc_st->tcp_drc_recycle_q); if ((drc && (drc->d_u.tcp.recycle_time > 0) && ((now - drc->d_u.tcp.recycle_time) > drc_st->expire_delta)) || DRC_QLEN_EXCEED_HIWAT()) { assert(drc->refcnt == 0); LogFullDebug(COMPONENT_DUPREQ, "remove expired drc %p from recycle queue", drc); t = rbtx_partition_of_scalar(&drc_st->tcp_drc_recycle_t, drc->d_u.tcp.hk); odrc = opr_rbtree_lookup(&t->t, &drc->d_u.tcp.recycle_k); if (!odrc) { LogCrit(COMPONENT_DUPREQ, "BUG: asked to dequeue DRC not on queue"); } else { (void)opr_rbtree_remove( &t->t, &drc->d_u.tcp.recycle_k); } TAILQ_REMOVE(&drc_st->tcp_drc_recycle_q, drc, d_u.tcp.recycle_q); --(drc_st->tcp_drc_recycle_qlen); /* Free any dupreqs in this drc. No need to * remove dupreqs from the hash table or * drc->dupreq_q list individually as the drc is * going to be freed anyway. There shouldn't be * any active requests, so all these dupreqs * will have refcnt of 1 for being in the hash * table. */ TAILQ_FOREACH_SAFE(dv, &drc->dupreq_q, fifo_q, tdv) { assert(dv->refcnt == 1); dupreq_entry_put(dv); } free_tcp_drc(drc); } else { LogFullDebug( COMPONENT_DUPREQ, "unexpired drc %p in recycle queue expire check (nothing happens)", drc); drc_st->last_expire_check = now; break; } } while (1); unlock: DRC_ST_UNLOCK(); } /** * @brief Find and reference a DRC to process the supplied svc_req. * * @param[in] req The svc_req being processed. * * @return The ref'd DRC if successfully located, else NULL. */ static /* inline */ drc_t *nfs_dupreq_get_drc(struct svc_req *req) { enum drc_type dtype = get_drc_type(req); drc_t *drc = NULL; bool drc_check_expired = false; switch (dtype) { case DRC_UDP_V234: LogFullDebug(COMPONENT_DUPREQ, "ref shared UDP DRC"); drc = &(drc_st->udp_drc); DRC_ST_LOCK(); /* we use shared UDP DRC without refcnt */ req->rq_xprt->xp_u2 = (void *)drc; DRC_ST_UNLOCK(); goto out; retry: case DRC_TCP_V4: case DRC_TCP_V3: /* Idempotent address, no need for lock; * xprt will be valid as long as svc_req. */ drc = (drc_t *)req->rq_xprt->xp_u2; if (drc) { /* found, no danger of removal */ LogFullDebug(COMPONENT_DUPREQ, "ref DRC=%p for xprt=%p", drc, req->rq_xprt); PTHREAD_MUTEX_lock(&drc->drc_mtx); /* LOCKED */ } else { drc_t drc_k; struct rbtree_x_part *t = NULL; struct opr_rbtree_node *ndrc = NULL; drc_t *tdrc = NULL; memset(&drc_k, 0, sizeof(drc_k)); drc_k.type = dtype; /* Since the drc can last longer than the xprt, * copy the address. Read operation of constant data, * no xprt lock required. */ copy_xprt_addr(&drc_k.d_u.tcp.addr, req->rq_xprt); drc_k.d_u.tcp.hk = CityHash64WithSeed((char *)&drc_k.d_u.tcp.addr, sizeof(sockaddr_t), 911); if (isFullDebug(COMPONENT_DUPREQ)) { char str[SOCK_NAME_MAX]; struct display_buffer dspbuf = { sizeof(str), str, str }; display_sockaddr(&dspbuf, &drc_k.d_u.tcp.addr); LogFullDebug(COMPONENT_DUPREQ, "get drc for addr: %s", str); } t = rbtx_partition_of_scalar(&drc_st->tcp_drc_recycle_t, drc_k.d_u.tcp.hk); DRC_ST_LOCK(); /* Avoid double reference of drc, * rechecking xp_u2 after DRC_ST_LOCK */ if (req->rq_xprt->xp_u2) { DRC_ST_UNLOCK(); goto retry; } ndrc = opr_rbtree_lookup(&t->t, &drc_k.d_u.tcp.recycle_k); if (ndrc) { /* reuse old DRC */ tdrc = opr_containerof(ndrc, drc_t, d_u.tcp.recycle_k); PTHREAD_MUTEX_lock(&tdrc->drc_mtx); /* LOCKED */ /* If the refcnt is zero and it is not * in the recycle queue, wait for the * other thread to put it in the queue. */ if (tdrc->refcnt == 0) { if (!(tdrc->flags & DRC_FLAG_RECYCLE)) { PTHREAD_MUTEX_unlock( &tdrc->drc_mtx); DRC_ST_UNLOCK(); goto retry; } TAILQ_REMOVE(&drc_st->tcp_drc_recycle_q, tdrc, d_u.tcp.recycle_q); --(drc_st->tcp_drc_recycle_qlen); tdrc->flags &= ~DRC_FLAG_RECYCLE; } drc = tdrc; LogFullDebug(COMPONENT_DUPREQ, "recycle TCP DRC=%p for xprt=%p", tdrc, req->rq_xprt); } if (!drc) { drc = alloc_tcp_drc(dtype); LogFullDebug(COMPONENT_DUPREQ, "alloc new TCP DRC=%p for xprt=%p", drc, req->rq_xprt); /* assign addr */ memcpy(&drc->d_u.tcp.addr, &drc_k.d_u.tcp.addr, sizeof(sockaddr_t)); /* assign already-computed hash */ drc->d_u.tcp.hk = drc_k.d_u.tcp.hk; PTHREAD_MUTEX_lock(&drc->drc_mtx); /* LOCKED */ /* insert dict */ opr_rbtree_insert(&t->t, &drc->d_u.tcp.recycle_k); } /* Avoid double reference of drc, * setting xp_u2 under DRC_ST_LOCK */ req->rq_xprt->xp_u2 = (void *)drc; (void)nfs_dupreq_ref_drc(drc); /* xprt ref */ DRC_ST_UNLOCK(); drc->d_u.tcp.recycle_time = 0; /* try to expire unused DRCs somewhat in proportion to * new connection arrivals */ drc_check_expired = true; LogFullDebug(COMPONENT_DUPREQ, "after ref drc %p refcnt==%u ", drc, drc->refcnt); } break; default: /* XXX error */ break; } /* call path ref */ (void)nfs_dupreq_ref_drc(drc); PTHREAD_MUTEX_unlock(&drc->drc_mtx); if (drc_check_expired) drc_free_expired(); out: return drc; } /** * @brief Release previously-ref'd DRC. * * Release previously-ref'd DRC. If its refcnt drops to 0, the DRC * is queued for later recycling. * * @param[in] drc The DRC * @param[in] flags Control flags */ void nfs_dupreq_put_drc(drc_t *drc) { PTHREAD_MUTEX_lock(&drc->drc_mtx); /* refcnt is not used on shared UDP DRC, so nothing to do */ if (drc->type == DRC_UDP_V234) goto unlock; assert(drc->type == DRC_TCP_V3 || drc->type == DRC_TCP_V4); if (drc->refcnt == 0) { LogCrit(COMPONENT_DUPREQ, "drc %p refcnt will underrun refcnt=%u", drc, drc->refcnt); } nfs_dupreq_unref_drc(drc); LogFullDebug(COMPONENT_DUPREQ, "drc %p refcnt==%u", drc, drc->refcnt); if (drc->refcnt != 0) /* quick path */ goto unlock; /* note t's lock order wrt drc->drc_mtx is the opposite of * drc->xt[*].lock. Drop and reacquire locks in correct * order. */ PTHREAD_MUTEX_unlock(&drc->drc_mtx); DRC_ST_LOCK(); PTHREAD_MUTEX_lock(&drc->drc_mtx); /* Since we dropped and reacquired the drc lock for the * correct lock order, we need to recheck the drc fields * again! */ if (drc->refcnt == 0 && !(drc->flags & DRC_FLAG_RECYCLE)) { drc->d_u.tcp.recycle_time = time(NULL); drc->flags |= DRC_FLAG_RECYCLE; TAILQ_INSERT_TAIL(&drc_st->tcp_drc_recycle_q, drc, d_u.tcp.recycle_q); ++(drc_st->tcp_drc_recycle_qlen); LogFullDebug(COMPONENT_DUPREQ, "enqueue drc %p for recycle", drc); } DRC_ST_UNLOCK(); unlock: PTHREAD_MUTEX_unlock(&drc->drc_mtx); } /** * @brief Resolve indirect request function vector for the supplied DRC entry * * @param[in] dv The duplicate request entry. * * @return The function vector if successful, else NULL. */ static inline const nfs_function_desc_t *nfs_dupreq_func(dupreq_entry_t *dv) { const nfs_function_desc_t *func = NULL; if (dv->hin.rq_prog == NFS_program[P_NFS]) { switch (dv->hin.rq_vers) { #ifdef _USE_NFS3 case NFS_V3: func = &nfs3_func_desc[dv->hin.rq_proc]; break; #endif /* _USE_NFS3 */ case NFS_V4: func = &nfs4_func_desc[dv->hin.rq_proc]; break; default: /* not reached */ LogMajor(COMPONENT_DUPREQ, "NFS Protocol version %" PRIu32 " unknown", dv->hin.rq_vers); } #ifdef _USE_NFS3 } else if (dv->hin.rq_prog == NFS_program[P_MNT]) { switch (dv->hin.rq_vers) { case MOUNT_V1: func = &mnt1_func_desc[dv->hin.rq_proc]; break; case MOUNT_V3: func = &mnt3_func_desc[dv->hin.rq_proc]; break; default: /* not reached */ LogMajor(COMPONENT_DUPREQ, "MOUNT Protocol version %" PRIu32 " unknown", dv->hin.rq_vers); break; } #endif #ifdef _USE_NLM } else if (dv->hin.rq_prog == NFS_program[P_NLM]) { switch (dv->hin.rq_vers) { case NLM4_VERS: func = &nlm4_func_desc[dv->hin.rq_proc]; break; } #endif /* _USE_NLM */ #ifdef _USE_RQUOTA } else if (dv->hin.rq_prog == NFS_program[P_RQUOTA]) { switch (dv->hin.rq_vers) { case RQUOTAVERS: func = &rquota1_func_desc[dv->hin.rq_proc]; break; case EXT_RQUOTAVERS: func = &rquota2_func_desc[dv->hin.rq_proc]; break; } #endif #ifdef USE_NFSACL3 } else if (dv->hin.rq_prog == NFS_program[P_NFSACL]) { switch (dv->hin.rq_vers) { case NFSACL_V3: func = &nfsacl_func_desc[dv->hin.rq_proc]; break; } #endif } else { /* not reached */ LogMajor(COMPONENT_DUPREQ, "protocol %" PRIu32 " is not managed", dv->hin.rq_prog); } return func; } /** * @brief Construct a duplicate request cache entry. * * Entries are allocated from the dupreq_pool. Since dupre_entry_t * presently contains an expanded nfs_arg_t, zeroing of at least corresponding * value pointers is required for XDR allocation. * * @return The newly allocated dupreq entry or NULL. */ static inline dupreq_entry_t *alloc_dupreq(void) { dupreq_entry_t *dv; dv = pool_alloc(dupreq_pool); PTHREAD_MUTEX_init(&dv->dre_mtx, NULL); TAILQ_INIT_ENTRY(dv, fifo_q); TAILQ_INIT(&dv->dupes); return dv; } /** * @brief Deep-free a duplicate request cache entry. * * If the entry has processed request data, the corresponding free * function is called on the result. The cache entry is then returned * to the dupreq_pool. */ static inline void nfs_dupreq_free_dupreq(dupreq_entry_t *dv) { const nfs_function_desc_t *func; assert(dv->refcnt == 0); LogDebug(COMPONENT_DUPREQ, "freeing dupreq entry dv=%p, dv xid=%" PRIu32 " cksum %" PRIu64 " %s", dv, dv->hin.tcp.rq_xid, dv->hk, dupreq_state_table[dv->complete]); if (dv->res) { func = nfs_dupreq_func(dv); func->free_function(dv->res); free_nfs_res(dv->res); } PTHREAD_MUTEX_destroy(&dv->dre_mtx); pool_free(dupreq_pool, dv); } /** * @brief get a ref count on dupreq_entry_t */ static inline void dupreq_entry_get(dupreq_entry_t *dv) { (void)atomic_inc_uint32_t(&dv->refcnt); } /** * @brief release a ref count on dupreq_entry_t * * The caller must not access dv any more after this call as it could be * freed here. */ static inline void dupreq_entry_put(dupreq_entry_t *dv) { int32_t refcnt; refcnt = atomic_dec_uint32_t(&dv->refcnt); assert(refcnt >= 0); /* If ref count is zero, no one should be accessing it other * than us. so no lock is needed. */ if (refcnt == 0) { nfs_dupreq_free_dupreq(dv); } } /** * @page DRC_RETIRE DRC request retire heuristic. * * We add a new, per-drc semaphore like counter, retwnd. The value of * retwnd begins at 0, and is always >= 0. The value of retwnd is increased * when a a duplicate req cache hit occurs. If it was 0, it is increased by * some small constant, say, 16, otherwise, by 1. And retwnd decreases by 1 * when we successfully finish any request. Likewise in finish, a cached * request may be retired iff we are above our water mark, and retwnd is 0. */ #define RETWND_START_BIAS 16 /** * @brief advance retwnd. * * If (drc)->retwnd is 0, advance its value to RETWND_START_BIAS, else * increase its value by 2 (corrects to 1) iff !full. * * @param[in] drc The duplicate request cache */ #define drc_inc_retwnd(drc) \ do { \ if ((drc)->retwnd == 0) \ (drc)->retwnd = RETWND_START_BIAS; \ else if ((drc)->retwnd < (drc)->maxsize) \ (drc)->retwnd += 2; \ } while (0) /** * @brief conditionally decrement retwnd. * * If (drc)->retwnd > 0, decrease its value by 1. * * @param[in] drc The duplicate request cache */ #define drc_dec_retwnd(drc) \ do { \ if ((drc)->retwnd > 0) \ --((drc)->retwnd); \ } while (0) /** * @brief retire request predicate. * * Calculate whether a request may be retired from the provided duplicate * request cache. * * @param[in] drc The duplicate request cache * * @return true if a request may be retired, else false. */ static inline bool drc_should_retire(drc_t *drc) { /* do not exceed the hard bound on cache size */ if (unlikely(drc->size > drc->maxsize)) return true; /* otherwise, are we permitted to retire requests */ if (unlikely(drc->retwnd > 0)) return false; /* finally, retire if drc->size is above intended high water mark */ if (unlikely(drc->size > drc->hiwat)) return true; return false; } static inline bool nfs_dupreq_v4_cacheable(nfs_request_t *reqnfs) { COMPOUND4args *arg_c4 = (COMPOUND4args *)&reqnfs->arg_nfs; if (arg_c4->minorversion > 0) return false; if ((reqnfs->lookahead.flags & (NFS_LOOKAHEAD_CREATE))) /* override OPEN4_CREATE */ return true; if ((reqnfs->lookahead.flags & (NFS_LOOKAHEAD_OPEN | /* all logical OPEN */ NFS_LOOKAHEAD_CLOSE | NFS_LOOKAHEAD_LOCK | /* includes LOCKU */ NFS_LOOKAHEAD_READ | /* because large, though idempotent */ NFS_LOOKAHEAD_READLINK | NFS_LOOKAHEAD_READDIR))) return false; return true; } /** * @brief Start a duplicate request transaction * * Finds any matching request entry in the cache, if one exists, else * creates one in the START state. On any non-error return, the refcnt * of the corresponding entry is incremented. * * @param[in] reqnfs The NFS request data * * @retval DUPREQ_SUCCESS if successful. */ dupreq_status_t nfs_dupreq_start(nfs_request_t *reqnfs) { dupreq_entry_t *dv = NULL, *dk = NULL; drc_t *drc; dupreq_status_t status = DUPREQ_SUCCESS; if (!(reqnfs->funcdesc->dispatch_behaviour & CAN_BE_DUP)) goto no_cache; if (nfs_param.core_param.drc.disabled) goto no_cache; if (reqnfs->funcdesc->service_function == nfs4_Compound && !nfs_dupreq_v4_cacheable(reqnfs)) { /* For such requests, we merely thread the request * through for later cleanup. All v41 caching is * handled by the v41 slot reply cache. */ goto no_cache; } drc = nfs_dupreq_get_drc(&reqnfs->svc); dk = alloc_dupreq(); switch (drc->type) { case DRC_TCP_V4: case DRC_TCP_V3: dk->hin.tcp.rq_xid = reqnfs->svc.rq_msg.rm_xid; /* XXX needed? */ dk->hin.rq_prog = reqnfs->svc.rq_msg.cb_prog; dk->hin.rq_vers = reqnfs->svc.rq_msg.cb_vers; dk->hin.rq_proc = reqnfs->svc.rq_msg.cb_proc; break; case DRC_UDP_V234: dk->hin.tcp.rq_xid = reqnfs->svc.rq_msg.rm_xid; copy_xprt_addr(&dk->hin.addr, reqnfs->svc.rq_xprt); dk->hin.rq_prog = reqnfs->svc.rq_msg.cb_prog; dk->hin.rq_vers = reqnfs->svc.rq_msg.cb_vers; dk->hin.rq_proc = reqnfs->svc.rq_msg.cb_proc; break; default: assert(0); } dk->hk = reqnfs->svc.rq_cksum; /* TI-RPC computed checksum */ { struct opr_rbtree_node *nv; struct rbtree_x_part *t = rbtx_partition_of_scalar(&drc->xt, dk->hk); PTHREAD_MUTEX_lock(&t->mtx); /* partition lock */ nv = rbtree_x_cached_lookup(&drc->xt, t, &dk->rbt_k, dk->hk); if (nv) { /* cached request */ nfs_dupreq_free_dupreq(dk); dv = opr_containerof(nv, dupreq_entry_t, rbt_k); PTHREAD_MUTEX_lock(&dv->dre_mtx); /* Count the number of duplicate requests received. */ dv->dupe_cnt++; /* Check if the original request is complete or not. If * not, we queue up the duplicate request for potential * response later, however, do not queue more than * DUPREQ_MAX_DUPES duplicates, any beyond before the * request is completed will just be dropped. */ if (unlikely(!dv->complete) && dv->dupe_cnt <= DUPREQ_MAX_DUPES) { status = DUPREQ_BEING_PROCESSED; TAILQ_INSERT_TAIL(&dv->dupes, reqnfs, dupes); reqnfs->svc.rq_resume_cb = drc_resume; } else if (unlikely(!dv->complete)) { /* Signal to nfs_dupreq_rele this should be * ignored. */ reqnfs->svc.rq_u1 = DUPREQ_NOCACHE_NORES; LogDebug(COMPONENT_DUPREQ, "dupreq hit dv=%p, dv xid=%" PRIu32 " cksum %" PRIu64 " state=%s dupe_count=%d", dv, dv->hin.tcp.rq_xid, dv->hk, dupreq_state_table[dv->complete], dv->dupe_cnt); PTHREAD_MUTEX_unlock(&dv->dre_mtx); PTHREAD_MUTEX_unlock(&t->mtx); return DUPREQ_DROP; } else { status = DUPREQ_EXISTS; } /* satisfy req from the DRC, incref, extend window */ reqnfs->svc.rq_u1 = dv; reqnfs->res_nfs = reqnfs->svc.rq_u2 = dv->res; dupreq_entry_get(dv); LogDebug(COMPONENT_DUPREQ, "dupreq hit dv=%p, dv xid=%" PRIu32 " cksum %" PRIu64 " state=%s dupe_count=%d", dv, dv->hin.tcp.rq_xid, dv->hk, dupreq_state_table[dv->complete], dv->dupe_cnt); PTHREAD_MUTEX_unlock(&dv->dre_mtx); PTHREAD_MUTEX_lock(&drc->drc_mtx); /* Extend window */ drc_inc_retwnd(drc); PTHREAD_MUTEX_unlock(&drc->drc_mtx); } else { /* new request */ reqnfs->svc.rq_u1 = dk; dk->res = alloc_nfs_res(); reqnfs->res_nfs = reqnfs->svc.rq_u2 = dk->res; /* cache--can exceed drc->maxsize */ (void)rbtree_x_cached_insert(&drc->xt, t, &dk->rbt_k, dk->hk); /* dupreq ref count starts with 2; one for the caller * and another for staying in the hash table. */ dk->refcnt = 2; /* add to q tail */ PTHREAD_MUTEX_lock(&drc->drc_mtx); TAILQ_INSERT_TAIL(&drc->dupreq_q, dk, fifo_q); ++(drc->size); PTHREAD_MUTEX_unlock(&drc->drc_mtx); LogFullDebug( COMPONENT_DUPREQ, "starting dk=%p xid=%" PRIu32 " on DRC=%p state=%s, status=%s, refcnt=%d, drc->size=%d", dk, dk->hin.tcp.rq_xid, drc, dupreq_state_table[dk->complete], dupreq_status_table[status], dk->refcnt, drc->size); } PTHREAD_MUTEX_unlock(&t->mtx); } return status; no_cache: reqnfs->svc.rq_u1 = DUPREQ_NOCACHE; reqnfs->res_nfs = reqnfs->svc.rq_u2 = alloc_nfs_res(); return DUPREQ_SUCCESS; } /** * @brief Completes a request in the cache * * Completes a cache insertion operation begun in nfs_dupreq_start. * The refcnt of the corresponding duplicate request entry is unchanged * (ie, the caller must still call nfs_dupreq_rele). * * In contrast with the prior DRC implementation, completing a request * in the current implementation may under normal conditions cause one * or more cached requests to be retired. Requests are retired in the * order they were inserted. The primary retire algorithm is a high * water mark, and a windowing heuristic. One or more requests will be * retired if the water mark/timeout is exceeded, and if a no duplicate * requests have been found in the cache in a configurable window of * immediately preceding requests. A timeout may supplement the water mark, * in future. * * req->rq_u1 has either a magic value, or points to a duplicate request * cache entry allocated in nfs_dupreq_start. * * @param[in] reqnfs The nfs_request_t. * @param[in] rc The nfs_req_result * */ void nfs_dupreq_finish(nfs_request_t *reqnfs, enum nfs_req_result rc) { dupreq_entry_t *ov = NULL, *dv = reqnfs->svc.rq_u1; struct rbtree_x_part *t; drc_t *drc = NULL; int16_t cnt = 0; /* do nothing if req is marked no-cache */ if (dv == DUPREQ_NOCACHE || dv == DUPREQ_NOCACHE_NORES) return; PTHREAD_MUTEX_lock(&dv->dre_mtx); assert(dv->res == reqnfs->res_nfs); /* Now see if there are any requests waiting. */ if (!TAILQ_EMPTY(&dv->dupes) && rc == NFS_REQ_XPRT_DIED) { /* Return without marking complete. That will stave off any * additional dupes that come in and we will walk the dupes * in order attempting to respond. * * Dequeueing the next duplicate will be done by * nfs_dupreq_rele(). */ PTHREAD_MUTEX_unlock(&dv->dre_mtx); return; } dv->complete = true; dv->rc = rc; PTHREAD_MUTEX_unlock(&dv->dre_mtx); drc = reqnfs->svc.rq_xprt->xp_u2; /* req holds a ref on drc */ PTHREAD_MUTEX_lock(&drc->drc_mtx); LogFullDebug(COMPONENT_DUPREQ, "completing dv=%p xid=%" PRIu32 " on DRC=%p state=%s, refcnt=%d, drc->size=%d", dv, dv->hin.tcp.rq_xid, drc, dupreq_state_table[dv->complete], dv->refcnt, drc->size); /* (all) finished requests count against retwnd */ drc_dec_retwnd(drc); /* conditionally retire entries */ dq_again: if (drc_should_retire(drc)) { ov = TAILQ_FIRST(&drc->dupreq_q); if (likely(ov)) { /* remove dict entry */ t = rbtx_partition_of_scalar(&drc->xt, ov->hk); uint64_t ov_hk = ov->hk; /* Need to acquire partition lock, but the lock * order is partition lock followed by drc lock. * Drop drc lock and reacquire it! */ PTHREAD_MUTEX_unlock(&drc->drc_mtx); PTHREAD_MUTEX_lock(&t->mtx); /* partition lock */ PTHREAD_MUTEX_lock(&drc->drc_mtx); /* Since we dropped drc lock and reacquired it, * the drc dupreq list may have changed. Get the * dupreq entry from the list again. */ ov = TAILQ_FIRST(&drc->dupreq_q); /* Make sure that we are removing the entry we * expected (imperfect, but harmless). */ if (ov == NULL || ov->hk != ov_hk) { PTHREAD_MUTEX_unlock(&t->mtx); goto unlock; } /* remove q entry */ TAILQ_REMOVE(&drc->dupreq_q, ov, fifo_q); TAILQ_INIT_ENTRY(ov, fifo_q); --(drc->size); PTHREAD_MUTEX_unlock(&drc->drc_mtx); rbtree_x_cached_remove(&drc->xt, t, &ov->rbt_k, ov->hk); PTHREAD_MUTEX_unlock(&t->mtx); LogDebug(COMPONENT_DUPREQ, "retiring ov=%p xid=%" PRIu32 " on DRC=%p state=%s, refcnt=%d", ov, ov->hin.tcp.rq_xid, drc, dupreq_state_table[ov->complete], ov->refcnt); /* release hashtable ref count */ dupreq_entry_put(ov); /* conditionally retire another */ if (cnt++ < DUPREQ_MAX_RETRIES) { PTHREAD_MUTEX_lock(&drc->drc_mtx); goto dq_again; /* calls drc_should_retire() */ } return; } } unlock: PTHREAD_MUTEX_unlock(&drc->drc_mtx); } /** * * @brief Remove an entry (request) from a duplicate request cache. * * The expected pattern is that nfs_rpc_process_request shall delete requests * only in error conditions. The refcnt of the corresponding duplicate request * entry is unchanged (ie., the caller must still call nfs_dupreq_rele). * * We assert req->rq_u1 now points to the corresponding duplicate request * cache entry. * * @param[in] reqnfs The nfs_request_t. * @param[in] rc The nfs_req_result * */ void nfs_dupreq_delete(nfs_request_t *reqnfs, enum nfs_req_result rc) { dupreq_entry_t *dv = reqnfs->svc.rq_u1; struct rbtree_x_part *t; drc_t *drc; /* do nothing if req is marked no-cache */ if (dv == DUPREQ_NOCACHE || dv == DUPREQ_NOCACHE_NORES) return; /* Check if this entry has any duplicate requests queued against it. If * so, we will want to resubmit them and NOT delete this drc. It will * attach as primary to the next request in line. */ PTHREAD_MUTEX_lock(&dv->dre_mtx); if (!TAILQ_EMPTY(&dv->dupes)) { /* Return without deleting, we are going to retry this request. * The entry will still be no complete which will * stave off any additional dupes that come in and we will walk * the dupes in order attempting to respond. * * Dequeueing the next duplicate will be done by * nfs_dupreq_rele(). */ dv->rc = rc; PTHREAD_MUTEX_unlock(&dv->dre_mtx); return; } PTHREAD_MUTEX_unlock(&dv->dre_mtx); drc = reqnfs->svc.rq_xprt->xp_u2; LogFullDebug(COMPONENT_DUPREQ, "deleting dv=%p xid=%" PRIu32 " on DRC=%p state=%s, refcnt=%d", dv, dv->hin.tcp.rq_xid, drc, dupreq_state_table[dv->complete], dv->refcnt); /* This function is called to remove this dupreq from the * hashtable/list, but it is possible that another thread * processing a different request calling nfs_dupreq_finish() * might have already deleted this dupreq. * * If this dupreq is already removed from hash table/list, do * nothing. * * req holds a ref on drc, so it should be valid here. * assert(drc == (drc_t *)reqnfs->svc.rq_xprt->xp_u2); */ PTHREAD_MUTEX_lock(&drc->drc_mtx); if (!TAILQ_IS_ENQUEUED(dv, fifo_q)) { PTHREAD_MUTEX_unlock(&drc->drc_mtx); return; /* no more in the hash table/list, nothing todo */ } TAILQ_REMOVE(&drc->dupreq_q, dv, fifo_q); TAILQ_INIT_ENTRY(dv, fifo_q); --(drc->size); PTHREAD_MUTEX_unlock(&drc->drc_mtx); t = rbtx_partition_of_scalar(&drc->xt, dv->hk); PTHREAD_MUTEX_lock(&t->mtx); rbtree_x_cached_remove(&drc->xt, t, &dv->rbt_k, dv->hk); PTHREAD_MUTEX_unlock(&t->mtx); /* we removed the dupreq from hashtable, release a ref */ dupreq_entry_put(dv); } /** * @brief Decrement the call path refcnt on a cache entry. * * We assert reqnfs->svc.rq_u1 now points to the corresponding duplicate request * cache entry (dv). * * @param[in] reqnfs The nfs_request_t. */ void nfs_dupreq_rele(nfs_request_t *reqnfs) { dupreq_entry_t *dv = reqnfs->svc.rq_u1; drc_t *drc; /* no-cache cleanup */ if (dv == DUPREQ_NOCACHE_NORES) { /* Response was never allocated, no need to clean it up. */ LogFullDebug(COMPONENT_DUPREQ, "no cache, no res to free"); goto out; } else if (dv == DUPREQ_NOCACHE) { LogFullDebug(COMPONENT_DUPREQ, "releasing no-cache res %p", reqnfs->svc.rq_u2); reqnfs->funcdesc->free_function(reqnfs->svc.rq_u2); free_nfs_res(reqnfs->svc.rq_u2); goto out; } drc = reqnfs->svc.rq_xprt->xp_u2; LogFullDebug(COMPONENT_DUPREQ, "releasing dv=%p xid=%" PRIu32 " on DRC=%p state=%s, refcnt=%d", dv, dv->hin.tcp.rq_xid, drc, dupreq_state_table[dv->complete], dv->refcnt); /* Now check if there are any duplicate requests waiting for this * one to resolve. */ PTHREAD_MUTEX_lock(&dv->dre_mtx); if (!TAILQ_EMPTY(&dv->dupes)) { /* Pull the next request of the queue and resume it. If this * overall request has been satisfied with a final result, then * any resumed requests will just immediately be released. In * theory we COULD do something other than rescheduling if the * request was just going to be discarded, but there's a bunch * done in svc_resume_task() that needs to be done that isn't * easy for us to do. There could be an ntirpc change to * implement svc_inline_resume() that would just do the * necessary bits. This situation is pretty unlikely and when it * does happen, it's unlikely to be more than one queued request * so the "pain" of doing a full svc_resume() just to return a * duplicate request is minor. */ nfs_request_t *req2 = TAILQ_FIRST(&dv->dupes); TAILQ_REMOVE(&dv->dupes, req2, dupes); svc_resume(&req2->svc); } PTHREAD_MUTEX_unlock(&dv->dre_mtx); /* release req's hold on dupreq and drc */ dupreq_entry_put(dv); nfs_dupreq_put_drc(drc); out: /* dispose RPC header */ if (reqnfs->svc.rq_auth) SVCAUTH_RELEASE(&reqnfs->svc); } nfs-ganesha-6.5/src/RPCAL/rpc_tools.c000066400000000000000000000523371473756622300174070ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file rpc_tools.c * @brief Some tools very useful in the nfs protocol implementation. * */ #include "config.h" #include #include #ifdef RPC_VSOCK #include #endif /* VSOCK */ #include #include /* for having isalnum */ #include /* for having atoi */ #include /* for having MAXNAMLEN */ #include #include #include #include #include #include #include /* for having FNDELAY */ #include #include #include "hashtable.h" #include "log.h" #include "nfs_core.h" #include "nfs23.h" #include "nfs4.h" #include "fsal.h" #include "nfs_exports.h" #include "nfs_file_handle.h" #include "nfs_dupreq.h" /* XXX doesn't ntirpc have an equivalent for all of the following? */ const char *xprt_type_to_str(xprt_type_t type) { switch (type) { case XPRT_UNKNOWN: return "UNKNOWN"; case XPRT_NON_RENDEZVOUS: return "UNUSED"; case XPRT_UDP: return "udp"; case XPRT_UDP_RENDEZVOUS: return "udp rendezvous"; case XPRT_TCP: return "tcp"; case XPRT_TCP_RENDEZVOUS: return "tcp rendezvous"; case XPRT_SCTP: return "sctp"; case XPRT_SCTP_RENDEZVOUS: return "sctp rendezvous"; case XPRT_RDMA: return "rdma"; case XPRT_RDMA_RENDEZVOUS: return "rdma rendezvous"; case XPRT_VSOCK: return "vsock"; case XPRT_VSOCK_RENDEZVOUS: return "vsock rendezvous"; } return "INVALID"; } /** * @brief Copy transport address into an address field * * @param[out] addr Address field to fill in. * @param[in] xprt Transport to get address from. * * @retval true if okay. * @retval false if not. */ void copy_xprt_addr(sockaddr_t *addr, SVCXPRT *xprt) { struct netbuf *phostaddr = svc_getcaller_netbuf(xprt); assert(phostaddr->len <= sizeof(sockaddr_t) && phostaddr->buf != NULL); memcpy(addr, phostaddr->buf, phostaddr->len); } /** * @brief Create a hash value based on the sockaddr_t structure * * This creates a native pointer size (unsigned long int) hash value * from the sockaddr_t structure. It supports both IPv4 and IPv6, * other types can be added in time. * * XXX is this hash...good? * * @param[in] addr sockaddr_t address to hash * @param[in] ignore_port Whether to ignore the port * * @return hash value * */ uint64_t hash_sockaddr(sockaddr_t *addr, bool ignore_port) { unsigned long addr_hash = 0; int port; switch (addr->ss_family) { case AF_INET: { struct sockaddr_in *paddr = (struct sockaddr_in *)addr; addr_hash = paddr->sin_addr.s_addr; if (!ignore_port) { port = paddr->sin_port; addr_hash ^= (port << 16); } break; } case AF_INET6: { struct sockaddr_in6 *paddr = (struct sockaddr_in6 *)addr; uint32_t *va; va = (uint32_t *)&paddr->sin6_addr; addr_hash = va[0] ^ va[1] ^ va[2] ^ va[3]; if (!ignore_port) { port = paddr->sin6_port; addr_hash ^= (port << 16); } break; } #ifdef RPC_VSOCK case AF_VSOCK: { struct sockaddr_vm *svm; /* XXX checkpatch horror */ svm = (struct sockaddr_vm *)addr; addr_hash = svm->svm_cid; if (!ignore_port) addr_hash ^= svm->svm_port; } #endif /* VSOCK */ default: break; } return addr_hash; } int display_sockaddr_port(struct display_buffer *dspbuf, const sockaddr_t *addr, bool ignore_port) { const char *name = NULL; char ipname[SOCK_NAME_MAX]; int port = 0; int b_left = display_start(dspbuf); if (b_left <= 0) return b_left; switch (addr->ss_family) { case AF_INET: name = inet_ntop(addr->ss_family, &(((struct sockaddr_in *)addr)->sin_addr), ipname, sizeof(ipname)); port = ntohs(((struct sockaddr_in *)addr)->sin_port); break; case AF_INET6: name = inet_ntop(addr->ss_family, &(((struct sockaddr_in6 *)addr)->sin6_addr), ipname, sizeof(ipname)); port = ntohs(((struct sockaddr_in6 *)addr)->sin6_port); break; #ifdef RPC_VSOCK case AF_VSOCK: return display_printf(dspbuf, "%s:%d", ((struct sockaddr_vm *)addr)->svm_cid, ((struct sockaddr_vm *)addr)->svm_port); #endif /* VSOCK */ case AF_LOCAL: return display_cat(dspbuf, ((struct sockaddr_un *)addr)->sun_path); } if (name == NULL) return display_cat(dspbuf, ""); else if (ignore_port) return display_cat(dspbuf, name); else return display_printf(dspbuf, "%s:%d", name, port); } /** * * @brief Compare 2 sockaddrs, including ports * * @param[in] addr_1 First address * @param[in] addr_2 Second address * @param[in] ignore_port Whether to ignore the port * * @return Comparator true/false, */ int cmp_sockaddr(sockaddr_t *addr_1, sockaddr_t *addr_2, bool ignore_port) { if (addr_1->ss_family != addr_2->ss_family) return 0; switch (addr_1->ss_family) { case AF_INET: { struct sockaddr_in *inaddr1 = (struct sockaddr_in *)addr_1; struct sockaddr_in *inaddr2 = (struct sockaddr_in *)addr_2; return (inaddr1->sin_addr.s_addr == inaddr2->sin_addr.s_addr && (ignore_port || inaddr1->sin_port == inaddr2->sin_port)); } case AF_INET6: { struct sockaddr_in6 *ip6addr1 = (struct sockaddr_in6 *)addr_1; struct sockaddr_in6 *ip6addr2 = (struct sockaddr_in6 *)addr_2; return (memcmp(ip6addr1->sin6_addr.s6_addr, ip6addr2->sin6_addr.s6_addr, sizeof(ip6addr2->sin6_addr.s6_addr)) == 0) && (ignore_port || ip6addr1->sin6_port == ip6addr2->sin6_port); } break; #ifdef RPC_VSOCK case AF_VSOCK: { struct sockaddr_vm *svm1 = (struct sockaddr_vm *)addr_1; struct sockaddr_vm *svm2 = (struct sockaddr_vm *)addr_2; return (svm1->svm_cid == svm2->svm_cid && (ignore_port || svm1->svm_port == svm2->svm_port)); } break; #endif /* VSOCK */ default: return 0; } } /** * @brief Canonically compare 2 sockaddrs * * @param[in] addr1 First address * @param[in] addr2 Second address * @param[in] ignore_port Whether to ignore the port * * @return Comparator trichotomy */ int sockaddr_cmpf(sockaddr_t *addr1, sockaddr_t *addr2, bool ignore_port) { switch (addr1->ss_family) { case AF_INET: { struct sockaddr_in *in1 = (struct sockaddr_in *)addr1; struct sockaddr_in *in2 = (struct sockaddr_in *)addr2; if (in1->sin_addr.s_addr < in2->sin_addr.s_addr) return -1; if (in1->sin_addr.s_addr == in2->sin_addr.s_addr) { if (ignore_port) return 0; /* else */ if (in1->sin_port < in2->sin_port) return -1; if (in1->sin_port == in2->sin_port) return 0; return 1; } return 1; } case AF_INET6: { struct sockaddr_in6 *in1 = (struct sockaddr_in6 *)addr1; struct sockaddr_in6 *in2 = (struct sockaddr_in6 *)addr2; int acmp = memcmp(in1->sin6_addr.s6_addr, in2->sin6_addr.s6_addr, sizeof(struct in6_addr)); if (acmp == 0) { if (ignore_port) return 0; /* else */ if (in1->sin6_port < in2->sin6_port) return -1; if (in1->sin6_port == in2->sin6_port) return 0; return 1; } else return acmp < 0 ? -1 : 1; } #ifdef RPC_VSOCK case AF_VSOCK: { struct sockaddr_vm *svm1 = (struct sockaddr_vm *)addr1; struct sockaddr_vm *svm2 = (struct sockaddr_vm *)addr2; if (svm1->svm_cid < svm2->svm_cid) return -1; if (svm1->svm_cid == svm2->svm_cid) { if (ignore_port) return 0; /* else */ if (svm1->svm_port < svm2->svm_port) return -1; if (svm1->svm_port == svm2->svm_port) return 0; return 1; } return 1; } break; #endif /* VSOCK */ default: /* unhandled AF */ return -2; } } int get_port(sockaddr_t *addr) { switch (addr->ss_family) { case AF_INET: return ntohs(((struct sockaddr_in *)addr)->sin_port); case AF_INET6: return ntohs(((struct sockaddr_in6 *)addr)->sin6_port); #ifdef RPC_VSOCK case AF_VSOCK: return ((struct sockaddr_vm *)addr)->svm_port; #endif /* VSOCK */ default: return -1; } } static char ten_bytes_all_0[10]; /** * @brief Check if address is IPv4 encapsulated into IPv6, if so convert * the address to IPv4 and return that one, otherwise return the * supplied address. * * @param[in] ipv6 The input address which may be IPv4, IPV6, or encapsulated * @param[in] ipv4 sockattr_t buffer to create IPv4 address into * * @returns ipv6 unless an encapsulated address was converted, then ipv4 */ sockaddr_t *convert_ipv6_to_ipv4(sockaddr_t *ipv6, sockaddr_t *ipv4) { struct sockaddr_in *paddr = (struct sockaddr_in *)ipv4; struct sockaddr_in6 *psockaddr_in6 = (struct sockaddr_in6 *)ipv6; /* If the client socket is IPv4, then it is wrapped into a * ::ffff:a.b.c.d IPv6 address. We check this here. * This kind of address is shaped like this: * |---------------------------------------------------------------| * | 80 bits = 10 bytes | 16 bits = 2 bytes | 32 bits = 4 bytes | * |---------------------------------------------------------------| * | 0 | FFFF | IPv4 address | * |---------------------------------------------------------------| */ if ((ipv6->ss_family == AF_INET6) && !memcmp(psockaddr_in6->sin6_addr.s6_addr, ten_bytes_all_0, 10) && (psockaddr_in6->sin6_addr.s6_addr[10] == 0xFF) && (psockaddr_in6->sin6_addr.s6_addr[11] == 0xFF)) { void *ab; memset(ipv4, 0, sizeof(*ipv4)); ab = &(psockaddr_in6->sin6_addr.s6_addr[12]); paddr->sin_port = psockaddr_in6->sin6_port; paddr->sin_addr.s_addr = *(in_addr_t *)ab; ipv4->ss_family = AF_INET; if (isMidDebug(COMPONENT_EXPORT)) { char ipstring4[SOCK_NAME_MAX]; char ipstring6[SOCK_NAME_MAX]; struct display_buffer dspbuf4 = { sizeof(ipstring4), ipstring4, ipstring4 }; struct display_buffer dspbuf6 = { sizeof(ipstring6), ipstring6, ipstring6 }; display_sockip(&dspbuf4, ipv4); display_sockip(&dspbuf6, ipv6); LogMidDebug( COMPONENT_EXPORT, "Converting IPv6 encapsulated IPv4 address %s to IPv4 %s", ipstring6, ipstring4); } return ipv4; } else { return ipv6; } } /** * * @brief Test if addr is loopback * * @param[in] addr Address * * @return Comparator true/false, */ bool is_loopback(sockaddr_t *addr) { struct sockaddr_in6 *ip6addr = (struct sockaddr_in6 *)addr; if (addr->ss_family == AF_INET) { struct sockaddr_in *inaddr = (struct sockaddr_in *)addr; return (((char *)&(inaddr->sin_addr.s_addr))[0] == 0x7F); } else if (addr->ss_family != AF_INET6) { return false; } /* If the client socket is IPv4, then it is wrapped into a * ::ffff:a.b.c.d IPv6 address. We check this here. * This kind of address is shaped like this: * |---------------------------------------------------------------| * | 80 bits = 10 bytes | 16 bits = 2 bytes | 32 bits = 4 bytes | * |---------------------------------------------------------------| * | 0 | FFFF | IPv4 address | * |---------------------------------------------------------------| * * An IPv4 loop back address is 127.b.c.d, so we only need to examine * the first byte past ::ffff, or s6_addr[12]. * * Otherwise we compare to ::1 */ return (!memcmp(ip6addr->sin6_addr.s6_addr, ten_bytes_all_0, 10) && (ip6addr->sin6_addr.s6_addr[10] == 0xFF) && (ip6addr->sin6_addr.s6_addr[11] == 0xFF) && (ip6addr->sin6_addr.s6_addr[12] == 0x7F)) || (memcmp(ip6addr->sin6_addr.s6_addr, &in6addr_loopback, sizeof(in6addr_loopback)) == 0); } static void xdr_io_data_uio_release(struct xdr_uio *uio, u_int flags) { int ix; io_data *io_data = uio->uio_u2; LogFullDebug(COMPONENT_DISPATCH, "Releasing %p, references %" PRIi32 ", count %d", uio, uio->uio_references, (int)uio->uio_count); if (--uio->uio_references != 0) return; if (io_data && (io_data->release != NULL)) { /* Handle the case where the io_data comes with its * own release method. * * Note if extra buffer was used to handle RNDUP, the * io_data release function doesn't even know about it so will * not free it. */ io_data->release(io_data->release_data); } else { if (uio->uio_u3 != NULL) { /* Don't free the last buffer! It was allocated along * with uio.. */ uio->uio_count--; } /* Free the buffers that had been allocated */ for (ix = 0; ix < uio->uio_count; ix++) { if (!(op_ctx && op_ctx->is_rdma_buff_used)) gsh_free(uio->uio_vio[ix].vio_base); } } gsh_free(uio); if (io_data) gsh_free(io_data); } static inline bool xdr_io_data_encode(XDR *xdrs, io_data *objp) { struct xdr_uio *uio; uint32_t size = objp->data_len; /* The size to actually be written must be a multiple of * BYTES_PER_XDR_UNIT */ uint32_t size2 = RNDUP(size); int i, extra = 0, last; int count = objp->iovcnt; uint32_t remain = size; size_t totlen = 0; if (!inline_xdr_u_int32_t(xdrs, &size)) return false; if (size != size2) { /* Add an extra buffer for round up */ count++; extra = BYTES_PER_XDR_UNIT; last = objp->iovcnt - 1; } uio = gsh_calloc(1, sizeof(struct xdr_uio) + count * sizeof(struct xdr_vio) + extra); uio->uio_release = xdr_io_data_uio_release; uio->uio_count = count; if (objp->release && objp->release_data) { /* Create a copy of io_data, since send path could be async */ io_data *objp_copy = gsh_calloc(1, sizeof(io_data)); objp_copy->release = objp->release; objp_copy->release_data = objp->release_data; uio->uio_u2 = objp_copy; } for (i = 0; i < objp->iovcnt; i++) { size_t i_size = objp->iov[i].iov_len; if (remain < i_size) i_size = remain; uio->uio_vio[i].vio_base = objp->iov[i].iov_base; uio->uio_vio[i].vio_head = objp->iov[i].iov_base; uio->uio_vio[i].vio_tail = objp->iov[i].iov_base + i_size; uio->uio_vio[i].vio_wrap = objp->iov[i].iov_base + i_size; uio->uio_vio[i].vio_length = i_size; uio->uio_vio[i].vio_type = VIO_DATA; totlen += i_size; remain -= i_size; LogFullDebug( COMPONENT_DISPATCH, "iov %p [%d].iov_base %p iov_len %zu for %zu of %u", objp->iov, i, objp->iov[i].iov_base, i_size, totlen, objp->data_len); } if (size != size2) { /* grab the last N bytes of last buffer into extra */ size_t n = size % BYTES_PER_XDR_UNIT; /* Check if last buffer has space */ if (objp->last_iov_buf_size >= (uio->uio_vio[last].vio_length + n)) { uio->uio_vio[last].vio_tail = uio->uio_vio[last].vio_tail + n; uio->uio_vio[last].vio_wrap = uio->uio_vio[last].vio_wrap + n; uio->uio_vio[last].vio_length = uio->uio_vio[last].vio_length + n; uio->uio_count--; goto putbufs; } char *p = uio->uio_vio[last].vio_base + uio->uio_vio[last].vio_length - n; char *extra = (char *)uio + sizeof(struct xdr_uio) + count * sizeof(struct xdr_vio); /* drop those bytes from the last buffer */ uio->uio_vio[last].vio_tail -= n; uio->uio_vio[last].vio_wrap -= n; uio->uio_vio[last].vio_length -= n; LogFullDebug( COMPONENT_DISPATCH, "Extra trim uio_vio[%d].vio_base %p vio_length %" PRIu32, last, uio->uio_vio[last].vio_base, uio->uio_vio[last].vio_length); /* move the bytes to the extra buffer and set it up as a * BYTES_PER_XDR_UNIT (4) byte buffer. Because it is part of the * memory we allocated above with calloc, the extra bytes are * already zeroed. */ memcpy(extra, p, n); i = count - 1; uio->uio_vio[i].vio_base = extra; uio->uio_vio[i].vio_head = extra; uio->uio_vio[i].vio_tail = extra + BYTES_PER_XDR_UNIT; uio->uio_vio[i].vio_wrap = extra + BYTES_PER_XDR_UNIT; uio->uio_vio[i].vio_length = BYTES_PER_XDR_UNIT; uio->uio_vio[i].vio_type = VIO_DATA; LogFullDebug( COMPONENT_DISPATCH, "Extra uio_vio[%d].vio_base %p vio_length %" PRIu32, i, uio->uio_vio[i].vio_base, uio->uio_vio[i].vio_length); /* Remember so we don't free... */ uio->uio_u3 = extra; } putbufs: LogFullDebug(COMPONENT_DISPATCH, "Allocated %p, references %" PRIi32 ", count %d", uio, uio->uio_references, (int)uio->uio_count); if (!xdr_putbufs(xdrs, uio, UIO_FLAG_NONE)) { uio->uio_release(uio, UIO_FLAG_NONE); return false; } return true; } void release_io_data_copy(void *release_data) { io_data *objp = release_data; int i; for (i = 0; i < objp->iovcnt; i++) gsh_free(objp->iov[i].iov_base); } static inline bool xdr_io_data_decode(XDR *xdrs, io_data *objp) { uint32_t start; struct xdr_vio *vio; int i; size_t totlen = 0; /* Get the data_len */ if (!inline_xdr_u_int32_t(xdrs, &objp->data_len)) return false; LogFullDebug(COMPONENT_DISPATCH, "data_len = %u", objp->data_len); if (objp->data_len == 0) { /* Special handling of length 0. */ objp->iov = gsh_calloc(1, sizeof(*objp->iov)); i = 0; objp->iovcnt = 1; objp->iov[i].iov_base = NULL; objp->iov[i].iov_len = 0; LogFullDebug(COMPONENT_DISPATCH, "iov[%d].iov_base %p iov_len %zu for %zu of %u", i, objp->iov[i].iov_base, objp->iov[i].iov_len, totlen, objp->data_len); return true; } /* Get the current position in the stream */ start = XDR_GETPOS(xdrs); /* For rdma_reads data received separately, so get data buffer */ start = XDR_GETSTARTDATAPOS(xdrs, start, objp->data_len); /* Find out how many buffers the data occupies */ objp->iovcnt = XDR_IOVCOUNT(xdrs, start, objp->data_len); LogFullDebug(COMPONENT_DISPATCH, "iovcnt = %u", objp->iovcnt); if (objp->iovcnt > IOV_MAX) { char *buf; LogInfo(COMPONENT_DISPATCH, "bypassing zero-copy, io_data iovcnt %u exceeds IOV_MAX, allocating %u byte buffer", objp->iovcnt, objp->data_len); /** @todo - Can we do something different? Do we really need to? * Does anything other than pynfs with large I/O * trigger this? */ /* The iovec is too big, we will have to copy, allocate and use * a single buffer. */ objp->iovcnt = 1; objp->iov = gsh_calloc(1, sizeof(*objp->iov)); buf = gsh_malloc(objp->data_len); objp->iov[0].iov_base = buf; objp->iov[0].iov_len = objp->data_len; if (!xdr_opaque_decode(xdrs, buf, objp->data_len)) { gsh_free(buf); gsh_free(objp->iov); objp->iov = NULL; return false; } objp->release = release_io_data_copy; objp->release_data = objp; return true; } /* Allocate a vio to extract the data buffers into */ vio = gsh_calloc(objp->iovcnt, sizeof(*vio)); /* Get the data buffers - XDR_FILLBUFS happens to do what we want... */ if (!XDR_FILLBUFS(xdrs, start, vio, objp->data_len)) { gsh_free(vio); return false; } /* Now allocate an iovec to carry the data */ objp->iov = gsh_calloc(objp->iovcnt, sizeof(*objp->iov)); /* Convert the xdr_vio to an iovec */ for (i = 0; i < objp->iovcnt; i++) { objp->iov[i].iov_base = vio[i].vio_head; objp->iov[i].iov_len = vio[i].vio_length; totlen += vio[i].vio_length; LogFullDebug(COMPONENT_DISPATCH, "iov[%d].iov_base %p iov_len %zu for %zu of %u", i, objp->iov[i].iov_base, objp->iov[i].iov_len, totlen, objp->data_len); } /* We're done with the vio */ gsh_free(vio); /* Now advance the position past the data (rounding up data_len) */ if (!XDR_SETPOS(xdrs, XDR_GETENDDATAPOS(xdrs, start, RNDUP(objp->data_len)))) { gsh_free(objp->iov); objp->iov = NULL; return false; } objp->release = NULL; objp->release_data = NULL; return true; } bool xdr_io_data(XDR *xdrs, io_data *objp) { if (xdrs->x_op == XDR_ENCODE) { /* We are going to use putbufs */ return xdr_io_data_encode(xdrs, objp); } if (xdrs->x_op == XDR_DECODE) { /* We are going to use putbufs */ return xdr_io_data_decode(xdrs, objp); } /* All that remains is XDR_FREE */ if (objp->release != NULL) objp->release(objp->release_data); gsh_free(objp->iov); objp->iov = NULL; return true; } /** * * @brief API to get the buffer to fill the IO Payloads * * @param[in] size Requested size of buffer to be allocated. * * @returns The address of the buffer to fill the payload */ void *get_buffer_for_io_response(uint64_t size, size_t *buffer_size) { #ifdef _USE_NFS_RDMA struct nfs_request *nfs_req; struct svc_req *req; if (op_ctx->req_type != NFS_REQUEST) return NULL; nfs_req = container_of(op_ctx, struct nfs_request, op_context); req = &nfs_req->svc; /* Whether it's RDMA enabled xprt and having data chunk */ if (req->rq_xprt->xp_rdma && req->data_chunk) { LogDebug(COMPONENT_TIRPC, "Using data_chunk %p length %d from req %p xprt %p", req->data_chunk, req->data_chunk_length, req, req->rq_xprt); assert(size <= req->data_chunk_length); if (buffer_size) *buffer_size = req->data_chunk_length; op_ctx->is_rdma_buff_used = true; return req->data_chunk; } #endif /* _USE_NFS_RDMA */ /* No special buffer requirements */ return NULL; } nfs-ganesha-6.5/src/SAL/000077500000000000000000000000001473756622300150035ustar00rootroot00000000000000nfs-ganesha-6.5/src/SAL/9p_owner.c000066400000000000000000000147561473756622300167260ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file 9p_owner.c * @brief Management of the 9P owner cache. */ #include "config.h" #include #include #include #include "log.h" #include "hashtable.h" #include "nfs_core.h" #include "sal_functions.h" /** * @brief Hash table for 9p owners */ hash_table_t *ht_9p_owner; /** * @brief Display a 9p owner * * @param[in] key The 9P owner * @param[out] str Output buffer * * @return the bytes remaining in the buffer. */ int display_9p_owner(struct display_buffer *dspbuf, state_owner_t *owner) { int b_left; if (owner == NULL) return display_printf(dspbuf, ""); b_left = display_printf(dspbuf, "STATE_LOCK_OWNER_9P %p", owner); if (b_left <= 0) return b_left; b_left = display_sockaddr(dspbuf, &owner->so_owner.so_9p_owner.client_addr); if (b_left <= 0) return b_left; b_left = display_printf(dspbuf, " proc_id=%u", owner->so_owner.so_9p_owner.proc_id); if (b_left <= 0) return b_left; return display_printf(dspbuf, " so_refcount=%d", atomic_fetch_int32_t(&owner->so_refcount)); } /** * @brief Display owner from hash key * * @param[in] dspbuf display buffer to display into * @param[in] buff Buffer pointing to owner */ int display_9p_owner_key_val(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_9p_owner(dspbuf, buff->addr); } /** * @brief Compare two 9p owners * * @param[in] owner1 One owner * @param[in] owner2 Another owner * * @retval 1 if they differ. * @retval 0 if they're identical. */ int compare_9p_owner(state_owner_t *owner1, state_owner_t *owner2) { if (isFullDebug(COMPONENT_STATE) && isDebug(COMPONENT_HASHTABLE)) { char str1[LOG_BUFF_LEN / 2] = "\0"; char str2[LOG_BUFF_LEN / 2] = "\0"; struct display_buffer dspbuf1 = { sizeof(str1), str1, str1 }; struct display_buffer dspbuf2 = { sizeof(str2), str2, str2 }; display_9p_owner(&dspbuf1, owner1); display_9p_owner(&dspbuf2, owner2); LogFullDebug(COMPONENT_STATE, "{%s} vs {%s}", str1, str2); } if (owner1 == NULL || owner2 == NULL) return 1; if (owner1 == owner2) return 0; if (owner1->so_owner.so_9p_owner.proc_id != owner2->so_owner.so_9p_owner.proc_id) return 1; #if 0 if (memcmp (&owner1->so_owner.so_9p_owner.client_addr, &owner2->so_owner.so_9p_owner.client_addr, sizeof(sockaddr_t))) return 1; #endif /* so_owner_len is always 0, don't compare so_owner_val */ return 0; } /** * @brief Compare two keys in the 9p owner hash table * * @param[in] buff1 One key * @param[in] buff2 Another key * * @retval 1 if they differ. * @retval 0 if they're the same. */ int compare_9p_owner_key(struct gsh_buffdesc *buff1, struct gsh_buffdesc *buff2) { return compare_9p_owner(buff1->addr, buff2->addr); } /** * @brief Get the hash index from a 9p owner * * @param[in] hparam Hash parameters * @param[in] key The key to hash * * @return The hash index. */ uint32_t _9p_owner_value_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { unsigned long res; state_owner_t *pkey = key->addr; struct sockaddr_in *paddr = (struct sockaddr_in *)&pkey->so_owner.so_9p_owner.client_addr; /* so_owner_len is always zero so don't bother with so_owner_val */ /** @todo using sin_addr.s_addr as an int makes this only work for * IPv4. */ res = (unsigned long)(pkey->so_owner.so_9p_owner.proc_id) + (unsigned long)paddr->sin_addr.s_addr; if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "value = %lu", res % hparam->index_size); return (uint32_t)(res % hparam->index_size); } /** * @brief Get the RBT hash from a 9p owner * * @param[in] hparam Hash parameters * @param[in] key The key to hash * * @return The RBT hash. */ uint64_t _9p_owner_rbt_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { uint64_t res; state_owner_t *pkey = key->addr; struct sockaddr_in *paddr = (struct sockaddr_in *)&pkey->so_owner.so_9p_owner.client_addr; /* so_owner_len is always zero so don't bother with so_owner_val */ /** @todo using sin_addr.s_addr as an int makes this only work for * IPv4. */ res = (uint64_t)(pkey->so_owner.so_9p_owner.proc_id) + (uint64_t)paddr->sin_addr.s_addr; if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "rbt = %" PRIu64, res); return res; } static hash_parameter_t _9p_owner_hash_param = { .index_size = PRIME_STATE, .hash_func_key = _9p_owner_value_hash_func, .hash_func_rbt = _9p_owner_rbt_hash_func, .compare_key = compare_9p_owner_key, .display_key = display_9p_owner_key_val, .display_val = display_9p_owner_key_val, .flags = HT_FLAG_NONE, }; /** * @brief Init the hashtable for 9P Owner cache * * @retval 0 if successful. * @retval -1 otherwise. */ int Init_9p_hash(void) { ht_9p_owner = hashtable_init(&_9p_owner_hash_param); if (ht_9p_owner == NULL) { LogCrit(COMPONENT_STATE, "Cannot init 9P Owner cache"); return -1; } return 0; } /** * @brief Look up a 9p owner * * @param[in] client_addr 9p client address * @param[in] proc_id Process ID of owning process * * @return The found owner or NULL. */ state_owner_t *get_9p_owner(sockaddr_t *client_addr, uint32_t proc_id) { state_owner_t key; memset(&key, 0, sizeof(key)); key.so_type = STATE_LOCK_OWNER_9P; key.so_refcount = 1; key.so_owner.so_9p_owner.proc_id = proc_id; memcpy(&key.so_owner.so_9p_owner.client_addr, client_addr, sizeof(*client_addr)); return get_state_owner(CARE_ALWAYS, &key, NULL, NULL); } /** @} */ nfs-ganesha-6.5/src/SAL/CMakeLists.txt000066400000000000000000000050111473756622300175400ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- ########### next target ############### SET(sal_STAT_SRCS sal_metrics.c state_async.c state_lock.c state_share.c state_misc.c state_layout.c state_deleg.c nfs4_clientid.c nfs4_state.c nfs4_state_id.c nfs4_lease.c nfs4_recovery.c nfs41_session_id.c nfs4_owner.c recovery/recovery_fs.c recovery/recovery_fs_ng.c ) if(USE_NLM) set(sal_STAT_SRCS ${sal_STAT_SRCS} nlm_owner.c nlm_state.c ) endif(USE_NLM) if(USE_9P) set(sal_STAT_SRCS ${sal_STAT_SRCS} 9p_owner.c ) endif(USE_9P) add_library(sal OBJECT ${sal_STAT_SRCS}) add_sanitizers(sal) set_target_properties(sal PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(sal gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) if(USE_RADOS_RECOV) set(sal_RADOS_SRCS recovery/recovery_rados_kv.c recovery/recovery_rados_ng.c recovery/recovery_rados_cluster.c ../support/rados_grace.c ) add_library(ganesha_rados_recov MODULE ${sal_RADOS_SRCS}) add_sanitizers(ganesha_rados_recov) target_link_libraries(ganesha_rados_recov ganesha_nfsd ${SYSTEM_LIBRARIES} ${RADOS_LIBRARIES} ${LDFLAG_DISALLOW_UNDEF}) include_directories(${RADOS_INCLUDE_DIR}) set_target_properties(ganesha_rados_recov PROPERTIES SOVERSION "${GANESHA_MAJOR_VERSION}${GANESHA_MINOR_VERSION}") install(TARGETS ganesha_rados_recov LIBRARY DESTINATION ${LIB_INSTALL_DIR}) endif(USE_RADOS_RECOV) ########### install files ############### nfs-ganesha-6.5/src/SAL/nfs41_session_id.c000066400000000000000000000505601473756622300203270ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file nfs41_session_id.c * @brief The management of the session id cache. */ #include "config.h" #include "nfs_convert.h" #include "nfs_core.h" #include "nfs_proto_functions.h" #include "sal_functions.h" #include "xprt_handler.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif #include "sal_metrics.h" /** * @brief Pool for allocating session data */ pool_t *nfs41_session_pool; /** * @param Session ID hash */ hash_table_t *ht_session_id; /** * @param counter for creating session IDs. */ uint64_t global_sequence; /** * @brief Display a session ID * * @param[in/out] dspbuf display_buffer describing output string * @param[in] session_id The session ID * * @return the bytes remaining in the buffer. */ int display_session_id(struct display_buffer *dspbuf, char *session_id) { int b_left = display_cat(dspbuf, "sessionid="); if (b_left > 0) b_left = display_opaque_value(dspbuf, session_id, NFS4_SESSIONID_SIZE); return b_left; } /** * @brief Display a key in the session ID table * * @param[in] dspbuf display buffer to display into * @param[in] buff The key to display */ int display_session_id_key(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_session_id(dspbuf, buff->addr); } /** * @brief Display a session object * * @param[in] buff The key to display * @param[in] session The session to display * * @return Length of output string. */ int display_session(struct display_buffer *dspbuf, nfs41_session_t *session) { int b_left = display_printf(dspbuf, "session %p {", session); if (b_left > 0) b_left = display_session_id(dspbuf, session->session_id); if (b_left > 0) b_left = display_cat(dspbuf, "}"); return b_left; } /** * @brief Display a compound request's operations * * @param[in] dspbuf The key to display * @param[in] opcodes The array of opcode belongs to one compound * @param[in] opcodes_len The number of opcode belongs to one compound * * @return the bytes remaining in the buffer. */ int display_nfs4_operations(struct display_buffer *dspbuf, nfs_opnum4 *opcodes, uint32_t opcode_num) { uint32_t i = 0; int b_left = display_cat(dspbuf, "nfs4 operations {"); while (b_left > 0 && i < opcode_num) { if (i > 0) (void)display_cat(dspbuf, ", "); b_left = display_cat(dspbuf, nfsop4_to_str(opcodes[i])); i++; } if (b_left > 0) b_left = display_cat(dspbuf, "}"); return b_left; } /** * @brief Display a value in the session ID table * * @param[in] dspbuf display buffer to display into * @param[in] buff The value to display */ int display_session_id_val(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_session(dspbuf, buff->addr); } /** * @brief Compare two session IDs in the hash table * * @retval 0 if they are equal. * @retval 1 if they are not. */ int compare_session_id(struct gsh_buffdesc *buff1, struct gsh_buffdesc *buff2) { return memcmp(buff1->addr, buff2->addr, NFS4_SESSIONID_SIZE); } /** * @brief Hash index of a sessionid * * @param[in] hparam Hash table parameters * @param[in] key The session key * * @return The hash index of the key. */ uint32_t session_id_value_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { /* Only need to take the mod of the global counter portion since it is unique */ uint64_t *counter = key->addr + sizeof(clientid4); return *counter % hparam->index_size; } /** * @brief RBT hash of a sessionid * * @param[in] hparam Hash table parameters * @param[in] key The session key * * @return The RBT hash of the key. */ uint64_t session_id_rbt_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { /* Only need to return the global counter portion since it is unique */ uint64_t *counter = key->addr + sizeof(clientid4); return *counter; } static hash_parameter_t session_id_param = { .index_size = PRIME_STATE, .hash_func_key = session_id_value_hash_func, .hash_func_rbt = session_id_rbt_hash_func, .ht_log_component = COMPONENT_SESSIONS, .compare_key = compare_session_id, .display_key = display_session_id_key, .display_val = display_session_id_val, .flags = HT_FLAG_CACHE, }; /** * @brief Init the hashtable for Session Id cache. * * @retval 0 if successful. * @retval -1 otherwise * */ int nfs41_Init_session_id(void) { ht_session_id = hashtable_init(&session_id_param); if (ht_session_id == NULL) { LogCrit(COMPONENT_SESSIONS, "NFS SESSION_ID: Cannot init Session Id cache"); return -1; } return 0; } /** * @brief Build a sessionid from a clientid * * @param[in] clientid Pointer to the related clientid * @param[out] sessionid The sessionid */ void nfs41_Build_sessionid(clientid4 *clientid, char *sessionid) { uint64_t seq; seq = atomic_inc_uint64_t(&global_sequence); memset(sessionid, 0, NFS4_SESSIONID_SIZE); memcpy(sessionid, clientid, sizeof(clientid4)); memcpy(sessionid + sizeof(clientid4), &seq, sizeof(seq)); } int32_t _inc_session_ref(nfs41_session_t *session, const char *func, int line) { int32_t refcnt = atomic_inc_int32_t(&session->refcount); GSH_AUTO_TRACEPOINT(nfs4, incref, TRACE_INFO, "Session incref. Session: {}, refcount: {}", session, refcnt); return refcnt; } int32_t _dec_session_ref(nfs41_session_t *session, const char *func, int line) { int i; int32_t refcnt = atomic_dec_int32_t(&session->refcount); GSH_AUTO_TRACEPOINT(nfs4, decref, TRACE_INFO, "Session decref. Session: {}, refcount: {}", session, refcnt); assert(refcnt >= 0); if (refcnt == 0) { /* Unlink the session from the client's list of sessions */ PTHREAD_MUTEX_lock(&session->clientid_record->cid_mutex); glist_del(&session->session_link); PTHREAD_MUTEX_unlock(&session->clientid_record->cid_mutex); /* Decrement our reference to the clientid record */ dec_client_id_ref(session->clientid_record); /* Destroy this session's mutexes and condition variable */ for (i = 0; i < session->nb_slots; i++) { nfs41_session_slot_t *slot; slot = &session->fc_slots[i]; PTHREAD_MUTEX_destroy(&slot->slot_lock); release_slot(slot); } PTHREAD_RWLOCK_destroy(&session->conn_lock); PTHREAD_COND_destroy(&session->cb_cond); PTHREAD_MUTEX_destroy(&session->cb_mutex); /* Destroy the session's back channel (if any) */ if (session->flags & session_bc_up) nfs_rpc_destroy_chan(&session->cb_chan); PTHREAD_MUTEX_destroy(&session->cb_chan.chan_mtx); /* Free the session's callback security params */ for (i = 0; i < session->cb_sec_parms.sec_parms_len; ++i) { callback_sec_parms4 *const sp = &session->cb_sec_parms.sec_parms_val[i]; if (sp->cb_secflavor == AUTH_NONE) { /* Do nothing */ } else if (sp->cb_secflavor == AUTH_SYS) { struct authunix_parms *cb_auth_sys_params = &sp->callback_sec_parms4_u.cbsp_sys_cred; gsh_free(cb_auth_sys_params->aup_machname); gsh_free(cb_auth_sys_params->aup_gids); #ifdef _HAVE_GSSAPI } else if (sp->cb_secflavor == RPCSEC_GSS) { LogWarn(COMPONENT_SESSIONS, "GSS callbacks unsupported, skip"); #endif } } gsh_free(session->cb_sec_parms.sec_parms_val); /* Free the slot tables */ gsh_free(session->fc_slots); gsh_free(session->bc_slots); /* Free the memory for the session */ pool_free(nfs41_session_pool, session); } return refcnt; } /** * @brief Set a session into the session hashtable. * * @param[in] sessionid Sessionid to add * @param[in] session_data Session data to add * * @retval 1 if successful. * @retval 0 otherwise. * */ int nfs41_Session_Set(nfs41_session_t *session_data) { struct gsh_buffdesc key; struct gsh_buffdesc val; struct hash_latch latch; hash_error_t code; int rc = 0; key.addr = session_data->session_id; key.len = NFS4_SESSIONID_SIZE; val.addr = session_data; val.len = sizeof(nfs41_session_t); /* The latch idiom isn't strictly necessary here */ code = hashtable_getlatch(ht_session_id, &key, &val, true, &latch); if (code == HASHTABLE_SUCCESS) { hashtable_releaselatched(ht_session_id, &latch); goto out; } if (code == HASHTABLE_ERROR_NO_SUCH_KEY) { /* nfs4_op_create_session ensures refcount == 2 for new * session records */ code = hashtable_setlatched(ht_session_id, &key, &val, &latch, false, NULL, NULL); if (code == HASHTABLE_SUCCESS) rc = 1; } out: return rc; } /** * @brief Get a pointer to a session from the session hashtable * * @param[in] sessionid The sessionid to look up * @param[out] session_data The associated session data * * @retval 1 if successful. * @retval 0 otherwise. */ int nfs41_Session_Get_Pointer(char sessionid[NFS4_SESSIONID_SIZE], nfs41_session_t **session_data) { struct gsh_buffdesc key; struct gsh_buffdesc val; struct hash_latch latch; char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; hash_error_t code; if (isFullDebug(COMPONENT_SESSIONS)) { display_session_id(&dspbuf, sessionid); LogFullDebug(COMPONENT_SESSIONS, "Get Session %s", str); str_valid = true; } key.addr = sessionid; key.len = NFS4_SESSIONID_SIZE; code = hashtable_getlatch(ht_session_id, &key, &val, false, &latch); if (code != HASHTABLE_SUCCESS) { hashtable_releaselatched(ht_session_id, &latch); if (str_valid) LogFullDebug(COMPONENT_SESSIONS, "Session %s Not Found", str); return 0; } *session_data = val.addr; inc_session_ref(*session_data); /* XXX more locks? */ hashtable_releaselatched(ht_session_id, &latch); if (str_valid) LogFullDebug(COMPONENT_SESSIONS, "Session %s Found", str); return 1; } /** * @brief Release all connections (SVCXPRT) referenced by the session */ static void release_all_session_connections(nfs41_session_t *session) { struct glist_head *curr_node, *next_node; /* Take connections write-lock */ PTHREAD_RWLOCK_wrlock(&session->conn_lock); glist_for_each_safe(curr_node, next_node, &session->connection_xprts) { connection_xprt_t *const curr_entry = glist_entry(curr_node, connection_xprt_t, node); SVCXPRT *const xprt = curr_entry->xprt; remove_nfs41_session_from_xprt(xprt, session); /* Release the connection-xprt's ref held by the session being * destroyed. */ SVC_RELEASE(xprt, SVC_RELEASE_FLAG_NONE); glist_del(curr_node); gsh_free(curr_entry); } session->num_conn = 0; PTHREAD_RWLOCK_unlock(&session->conn_lock); } /** * @brief Remove a session from the session hashtable. * * This also shuts down any back channel and frees the session data. * * @param[in] sessionid The sessionid to remove * * @return 1 if successful. * @retval 0 otherwise. */ int nfs41_Session_Del(nfs41_session_t *session) { struct gsh_buffdesc key, old_key, old_value; /* Release all session connections */ release_all_session_connections(session); key.addr = session->session_id; key.len = NFS4_SESSIONID_SIZE; if (HashTable_Del(ht_session_id, &key, &old_key, &old_value) == HASHTABLE_SUCCESS) { nfs41_session_t *session = old_value.addr; /* unref session */ dec_session_ref(session); return true; } else { return false; } } /** * @brief Display the content of the session hashtable */ void nfs41_Session_PrintAll(void) { hashtable_log(COMPONENT_SESSIONS, ht_session_id); } bool check_session_conn(nfs41_session_t *session, compound_data_t *data, bool can_associate) { struct glist_head *curr_node; bool added_session_to_xprt; bool associate = false; PTHREAD_RWLOCK_rdlock(&session->conn_lock); retry: glist_for_each(curr_node, &session->connection_xprts) { connection_xprt_t *const curr_entry = glist_entry(curr_node, connection_xprt_t, node); if (isFullDebug(COMPONENT_SESSIONS)) { char str1[SOCK_NAME_MAX] = "\0"; char str2[SOCK_NAME_MAX] = "\0"; struct display_buffer db1 = { sizeof(str1), str1, str1 }; struct display_buffer db2 = { sizeof(str2), str2, str2 }; display_xprt_sockaddr(&db1, data->req->rq_xprt); display_xprt_sockaddr(&db2, curr_entry->xprt); LogFullDebug( COMPONENT_SESSIONS, "Comparing addr %s for %s to Session bound addr %s", str1, data->opname, str2); } if (data->req->rq_xprt == curr_entry->xprt) { /* We found a match */ PTHREAD_RWLOCK_unlock(&session->conn_lock); return true; } } if (!can_associate) { /* We either aren't allowed to associate a new address */ PTHREAD_RWLOCK_unlock(&session->conn_lock); if (isDebug(COMPONENT_SESSIONS)) { char str1[SOCK_NAME_MAX] = "\0"; struct display_buffer db1 = { sizeof(str1), str1, str1 }; display_xprt_sockaddr(&db1, data->req->rq_xprt); LogDebug(COMPONENT_SESSIONS, "Found no match for addr %s for %s", str1, data->opname); } return false; } if (!associate) { /* First pass was with read lock, now acquire write lock and * try again. */ associate = true; PTHREAD_RWLOCK_unlock(&session->conn_lock); PTHREAD_RWLOCK_wrlock(&session->conn_lock); goto retry; } if (session->num_conn == NFS41_MAX_CONNECTIONS) { LogInfo(COMPONENT_SESSIONS, "We hit the session's max-connections limit before adding xprt FD: %d", data->req->rq_xprt->xp_fd); } /* Add session to the xprt */ added_session_to_xprt = add_nfs41_session_to_xprt(data->req->rq_xprt, session); if (!added_session_to_xprt) { PTHREAD_RWLOCK_unlock(&session->conn_lock); LogWarn(COMPONENT_SESSIONS, "Could not associate xprt FD: %d with session", data->req->rq_xprt->xp_fd); return false; } /* Add xprt to the session */ nfs41_Session_Add_Connection(session, data->req->rq_xprt); const int num_connections = session->num_conn; PTHREAD_RWLOCK_unlock(&session->conn_lock); sal_metrics__session_connections(num_connections); return true; } /** * @brief Adds the xprt reference to the nfs41_session * * @note The caller must hold the `session->conn_lock` lock for writes. */ void nfs41_Session_Add_Connection(nfs41_session_t *session, SVCXPRT *xprt) { connection_xprt_t *const new_entry = gsh_malloc(sizeof(connection_xprt_t)); new_entry->xprt = xprt; glist_add_tail(&session->connection_xprts, &new_entry->node); SVC_REF(xprt, SVC_REF_FLAG_NONE); session->num_conn++; } /** * @brief Remove matching connection (SVCXPRT) from the session */ void nfs41_Session_Remove_Connection(nfs41_session_t *session, SVCXPRT *xprt) { struct glist_head *curr_node; connection_xprt_t *found_xprt = NULL; char xprt_addr[SOCK_NAME_MAX] = "\0"; struct display_buffer xprt_db = { sizeof(xprt_addr), xprt_addr, xprt_addr }; display_xprt_sockaddr(&xprt_db, xprt); PTHREAD_RWLOCK_wrlock(&session->conn_lock); glist_for_each(curr_node, &session->connection_xprts) { connection_xprt_t *const curr_entry = glist_entry(curr_node, connection_xprt_t, node); if (isFullDebug(COMPONENT_SESSIONS)) { char curr_xprt_addr[SOCK_NAME_MAX] = "\0"; struct display_buffer db = { sizeof(curr_xprt_addr), curr_xprt_addr, curr_xprt_addr }; display_xprt_sockaddr(&db, curr_entry->xprt); LogFullDebug( COMPONENT_SESSIONS, "Comparing input xprt addr %s to session bound xprt addr %s", xprt_addr, curr_xprt_addr); } /* During removal, the xprt address must match, and not just the * socket-address. We do not want to remove a different xprt * with same socket-address. */ if (curr_entry->xprt == xprt) { found_xprt = curr_entry; break; } } /* Return if the connection is not bound to the session. * This can happen in rare situations when the session is destroyed * and all its connections were removed, just before we obtained * the above conn_lock. */ if (found_xprt == NULL) { PTHREAD_RWLOCK_unlock(&session->conn_lock); assert(session->num_conn == 0); return; } /* Now, remove the matching connection from session */ /* Release the connection-xprt's ref held on the session */ SVC_RELEASE(found_xprt->xprt, SVC_RELEASE_FLAG_NONE); /* Remove the xprt from session's connection-xprts */ glist_del(&found_xprt->node); gsh_free(found_xprt); const int num_connections = --session->num_conn; PTHREAD_RWLOCK_unlock(&session->conn_lock); sal_metrics__session_connections(num_connections); LogDebug(COMPONENT_SESSIONS, "Successfuly removed the connection for xprt addr %s", xprt_addr); } /** * This function destroys the input session's backchannel if it is up, and if * it uses the input xprt. */ void nfs41_Session_Destroy_Backchannel_For_Xprt(nfs41_session_t *session, SVCXPRT *xprt) { char session_str[NFS4_SESSIONID_BUFFER_SIZE] = "\0"; struct display_buffer db2 = { sizeof(session_str), session_str, session_str }; display_session_id(&db2, session->session_id); PTHREAD_MUTEX_lock(&session->cb_chan.chan_mtx); /* After acquiring the lock, we check backchannel availability */ if (session->cb_chan.clnt == NULL) { PTHREAD_MUTEX_unlock(&session->cb_chan.chan_mtx); goto no_backchannel; } /* Given that the backchannel is up, we first check if the session's * backchannel actually uses the xprt being destroyed. * The channel lock ensures that channel's client check (below) and the * channel destroy operation are performed atomically. */ if (clnt_vc_get_client_xprt(session->cb_chan.clnt) != xprt) { PTHREAD_MUTEX_unlock(&session->cb_chan.chan_mtx); LogDebug( COMPONENT_SESSIONS, "Backchannel xprt for session %s does not match the xprt to be destroyed. Skip destroying backchannel", session_str); return; } /* Now destroy the backchannel */ nfs_rpc_destroy_chan_no_lock(&session->cb_chan); atomic_clear_uint32_t_bits(&session->flags, session_bc_up); PTHREAD_MUTEX_unlock(&session->cb_chan.chan_mtx); LogDebug(COMPONENT_SESSIONS, "Backchannel destroyed for current session %s", session_str); return; no_backchannel: LogDebug(COMPONENT_SESSIONS, "Backchannel is not up for session %s, skip destroying it", session_str); } /** * @brief Destroy all session connection-xprts * * @return number of connections destroyed for the session */ int nfs41_Session_Destroy_All_Connections(nfs41_session_t *session) { struct glist_head *curr_node, *next_node; struct glist_head connections_copy; glist_init(&connections_copy); /* Create a duplicate xprt list to avoid conflict with conn_lock * taken during SVC_DESTROY */ PTHREAD_RWLOCK_rdlock(&session->conn_lock); int num_connections = session->num_conn; LogInfo(COMPONENT_SESSIONS, "Found %d connections for the session", num_connections); glist_for_each(curr_node, &session->connection_xprts) { connection_xprt_t *const curr_entry = glist_entry(curr_node, connection_xprt_t, node); connection_xprt_t *const new_entry = gsh_malloc(sizeof(connection_xprt_t)); new_entry->xprt = curr_entry->xprt; glist_add_tail(&connections_copy, &new_entry->node); /* Ref the xprt to prevent it from being destroyed externally */ SVC_REF(new_entry->xprt, SVC_REF_FLAG_NONE); } PTHREAD_RWLOCK_unlock(&session->conn_lock); /* Now for each xprt, destroy it */ glist_for_each_safe(curr_node, next_node, &connections_copy) { connection_xprt_t *const curr_entry = glist_entry(curr_node, connection_xprt_t, node); LogInfo(COMPONENT_SESSIONS, "Destroying xprt with FD %d for the session", curr_entry->xprt->xp_fd); /* Destroy the xprt */ SVC_DESTROY(curr_entry->xprt); /* Release the ref we acquired above */ SVC_RELEASE(curr_entry->xprt, SVC_RELEASE_FLAG_NONE); glist_del(&curr_entry->node); gsh_free(curr_entry); } return num_connections; } /** @} */ nfs-ganesha-6.5/src/SAL/nfs4_clientid.c000066400000000000000000001714061473756622300177050ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file nfs4_clientid.c * @brief The management of the client id cache. * */ #include "config.h" #include #include "hashtable.h" #include "log.h" #include "nfs_core.h" #include "nfs_exports.h" #include "config_parsing.h" #include #include #include #include #include "nfs4.h" #include "fsal.h" #include "sal_functions.h" #include "abstract_atomic.h" #include "city.h" #include "client_mgr.h" #include "sal_metrics.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief Hashtable used to cache NFSv4 clientids */ hash_table_t *ht_client_record; /** * @brief Hash table to store confirmed client IDs */ hash_table_t *ht_confirmed_client_id; /** * @brief Hash table to store unconfirmed client IDs */ hash_table_t *ht_unconfirmed_client_id; /** * @brief Counter to create clientids */ uint32_t clientid_counter; /** * @brief Verifier to construct clientids */ uint64_t clientid_verifier; /** * @brief Pool for client data structures */ pool_t *client_id_pool; static uint64_t num_confirmed_client_ids; uint32_t num_of_curr_expired_clients; /** * @brief Global list to store expired client IDs and it's mutex */ struct glist_head expired_client_ids_list; /** * @brief Mutex to protect the above list. This mutex MUST NOT be taken while * holding the cid_mutex. Taking the cid_mutex while holding this mutex * is OK and expected. */ pthread_mutex_t expired_client_ids_list_lock; /** * @brief Return the NFSv4 status for the client id error code * * @param[in] err Client id error code * * @return the corresponding nfs4 error code */ nfsstat4 clientid_error_to_nfsstat(clientid_status_t err) { switch (err) { case CLIENT_ID_SUCCESS: return NFS4_OK; case CLIENT_ID_INSERT_MALLOC_ERROR: return NFS4ERR_RESOURCE; case CLIENT_ID_INVALID_ARGUMENT: return NFS4ERR_SERVERFAULT; case CLIENT_ID_EXPIRED: return NFS4ERR_EXPIRED; case CLIENT_ID_STALE: return NFS4ERR_STALE_CLIENTID; } LogCrit(COMPONENT_CLIENTID, "Unexpected clientid error %d", err); return NFS4ERR_SERVERFAULT; } /** * @brief Return the NFSv4 status string for the client id error code * * @param[in] err client id error code * * @return the error string corresponding nfs4 error code */ const char *clientid_error_to_str(clientid_status_t err) { switch (err) { case CLIENT_ID_SUCCESS: return "CLIENT_ID_SUCCESS"; case CLIENT_ID_INSERT_MALLOC_ERROR: return "CLIENT_ID_INSERT_MALLOC_ERROR"; case CLIENT_ID_INVALID_ARGUMENT: return "CLIENT_ID_INVALID_ARGUMENT"; case CLIENT_ID_EXPIRED: return "CLIENT_ID_EXPIRED"; case CLIENT_ID_STALE: return "CLIENT_ID_STALE"; } LogCrit(COMPONENT_CLIENTID, "Unexpected clientid error %d", err); return "UNEXPECTED ERROR"; } /** * @brief Return a string corresponding to the confirm state * * @param[in] confirmed Confirm state * * @return Corresponding string. */ const char * clientid_confirm_state_to_str(nfs_clientid_confirm_state_t confirmed) { switch (confirmed) { case CONFIRMED_CLIENT_ID: return "CONFIRMED"; case UNCONFIRMED_CLIENT_ID: return "UNCONFIRMED"; case EXPIRED_CLIENT_ID: return "EXPIRED"; case STALE_CLIENT_ID: return "STALE"; } return "UNKNOWN STATE"; } /** * @brief Display a client record * * @param[in] clientid Client record * @param[out] str Output buffer * * @return Length of display string. */ int display_client_id_rec(struct display_buffer *dspbuf, nfs_client_id_t *clientid) { int delta; int b_left = display_printf(dspbuf, "%p ClientID={", clientid); if (b_left <= 0) return b_left; b_left = display_clientid(dspbuf, clientid->cid_clientid); if (b_left <= 0) return b_left; b_left = display_printf( dspbuf, "} %s ClientRec={", clientid_confirm_state_to_str(clientid->cid_confirmed)); if (b_left <= 0) return b_left; b_left = display_client_record(dspbuf, clientid->cid_client_record); if (b_left <= 0) return b_left; if (clientid->cid_lease_reservations > 0) delta = 0; else delta = time(NULL) - clientid->cid_last_renew; b_left = display_printf( dspbuf, "} t_delta=%d reservations=%d cid_refcount=%d files_opened=%u,", delta, clientid->cid_lease_reservations, atomic_fetch_int32_t(&clientid->cid_refcount), atomic_fetch_uint32_t(&clientid->cid_open_state_counter)); if (b_left <= 0) return b_left; b_left = display_printf(dspbuf, "} cred_flavor=%u", clientid->cid_credential.flavor); if (b_left <= 0) return b_left; if (clientid->cid_minorversion == 0) { b_left = display_printf( dspbuf, " cb_prog=%u r_addr=%s r_netid=%s", clientid->cid_cb.v40.cb_program, clientid->cid_cb.v40.cb_client_r_addr, netid_nc_table[clientid->cid_cb.v40.cb_addr.nc].netid); } return b_left; } /** * @brief Display a client owner * * @param[in] clientid The client record * @param[out] str Output buffer * * @return Length of display string. */ int display_clientid_name(struct display_buffer *dspbuf, nfs_client_id_t *clientid) { return display_opaque_value( dspbuf, clientid->cid_client_record->cr_client_val, clientid->cid_client_record->cr_client_val_len); } /** * @brief Increment the clientid refcount in the hash table * * @param[in] val Buffer pointing to client record */ static void Hash_inc_client_id_ref(struct gsh_buffdesc *val) { nfs_client_id_t *clientid = val->addr; inc_client_id_ref(clientid); } /** * @brief Increment the clientid refcount * * @param[in] clientid Client record */ int32_t inc_client_id_ref(nfs_client_id_t *clientid) { int32_t cid_refcount = atomic_inc_int32_t(&clientid->cid_refcount); GSH_CLIENT_ID_AUTO_TRACEPOINT(nfs4, incref_client_id, TRACE_INFO, clientid, "Incref client id. refcount={}", cid_refcount); if (isFullDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, clientid); LogFullDebug(COMPONENT_CLIENTID, "Increment cid_refcount Clientid {%s} to %" PRId32, str, cid_refcount); } return cid_refcount; } /** * @brief Tests whether state exists on a client id * * We assume that open owners are predictive of open or lock state, * since they're collected when the last state is removed. * * @note The clientid mutex must be held when calling this function. * * @param[in] clientid Client record * * @retval true if there is state. * @retval false if there isn't. */ bool client_id_has_state(nfs_client_id_t *clientid) { bool result; if (glist_empty(&clientid->cid_openowners)) return false; PTHREAD_MUTEX_lock(&clientid->cid_owner.so_mutex); result = !glist_empty( &clientid->cid_owner.so_owner.so_nfs4_owner.so_state_list); PTHREAD_MUTEX_unlock(&clientid->cid_owner.so_mutex); return result; } /** * @brief Deconstruct and free a client record * * @param[in] clientid The client record to free */ void free_client_id(nfs_client_id_t *clientid) { GSH_CLIENT_ID_AUTO_TRACEPOINT(nfs4, free_client_id, TRACE_INFO, clientid, "Free client id"); assert(atomic_fetch_int32_t(&clientid->cid_refcount) == 0); /* This is where we finally let go of the client record. */ dec_client_record_ref(clientid->cid_client_record); #ifdef _HAVE_GSSAPI if (clientid->cid_credential.flavor == RPCSEC_GSS) { struct svc_rpc_gss_data *gd; gd = clientid->cid_credential.auth_union.auth_gss.gd; unref_svc_rpc_gss_data(gd); } #endif /* _HAVE_GSSAPI */ /* For NFSv4.1 clientids, destroy all associated sessions */ if (clientid->cid_minorversion > 0) { struct glist_head *glist = NULL; struct glist_head *glistn = NULL; glist_for_each_safe(glist, glistn, &clientid->cid_cb.v41.cb_session_list) { nfs41_session_t *session = glist_entry( glist, nfs41_session_t, session_link); if (!nfs41_Session_Del(session)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, clientid); LogCrit(COMPONENT_SESSIONS, "Expire session failed for {%s}", str); } } } gsh_free(clientid->cid_recov_tag); clientid->cid_recov_tag = NULL; PTHREAD_MUTEX_destroy(&clientid->cid_mutex); PTHREAD_MUTEX_destroy(&clientid->cid_owner.so_mutex); if (clientid->cid_minorversion == 0) PTHREAD_MUTEX_destroy(&clientid->cid_cb.v40.cb_chan.chan_mtx); put_gsh_client(clientid->gsh_client); pool_free(client_id_pool, clientid); } /** * @brief Decrement the clientid refcount * * @param[in] clientid Client record */ int32_t dec_client_id_ref(nfs_client_id_t *clientid) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; int32_t cid_refcount; if (isFullDebug(COMPONENT_CLIENTID)) display_client_id_rec(&dspbuf, clientid); cid_refcount = atomic_dec_int32_t(&clientid->cid_refcount); GSH_CLIENT_ID_AUTO_TRACEPOINT(nfs4, client_decref, TRACE_INFO, clientid, "Decref, refcount={}", cid_refcount); LogFullDebug(COMPONENT_CLIENTID, "Decrement refcount Clientid {%s} cid_refcount to %d", str, cid_refcount); assert(cid_refcount >= 0); if (cid_refcount > 0) return cid_refcount; /* We don't need a lock to look at cid_confirmed because when * refcount has gone to 0, no other threads can have a pointer * to the clientid record. */ if (clientid->cid_confirmed == EXPIRED_CLIENT_ID) { /* Is not in any hash table, so we can just delete it */ LogFullDebug(COMPONENT_CLIENTID, "Free Clientid cid_refcount now=0 {%s}", str); free_client_id(clientid); } else { /* Clientid records should not be freed unless marked expired */ display_client_id_rec(&dspbuf, clientid); LogCrit(COMPONENT_CLIENTID, "Should not be here, try to remove last ref {%s}", str); assert(clientid->cid_confirmed == EXPIRED_CLIENT_ID); } return cid_refcount; } /** * @brief Computes the hash value for the entry in Client Id cache. * * This function computes the hash value for the entry in Client Id * cache. In fact, it just uses the clientid as the value (identity * function) modulo the size of the hash. This function is called * internal in the HasTable_* function * * @param[in] hparam Hash table parameter * @param[in] key Pointer to the hash key buffer * * @return The computed hash value. * * @see hashtable_init * */ uint32_t client_id_value_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { clientid4 clientid; memcpy(&clientid, key->addr, sizeof(clientid)); return (uint32_t)clientid % hparam->index_size; } /** * @brief Computes the RBT hash for the entry in Client Id cache * * Computes the rbt value for the entry in Client Id cache. In fact, * it just use the address value itself (which is an unsigned integer) * as the rbt value. This function is called internal in the * HasTable_* function * * @param[in] hparam Hash table parameter. * @param[in] key Hash key buffer * * @return The computed RBT value. * * @see hashtable_init * */ uint64_t client_id_rbt_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { clientid4 clientid; memcpy(&clientid, key->addr, sizeof(clientid)); return clientid; } /** * @brief Compares clientids stored in the key buffers. * * This function compares the clientid stored in the key buffers. This * function is to be used as 'compare_key' field in the hashtable * storing the client ids. * * @param[in] buff1 first key * @param[in] buff2 second key * * @retval 0 if keys are identifical. * @retval 1 if the keys are different. * */ int compare_client_id(struct gsh_buffdesc *buff1, struct gsh_buffdesc *buff2) { clientid4 cl1 = *((clientid4 *)(buff1->addr)); clientid4 cl2 = *((clientid4 *)(buff2->addr)); return (cl1 == cl2) ? 0 : 1; } /** * @brief Displays the client_id stored from the hash table * * @param[in] dspbuf display buffer to display into * @param[in] buff Buffer to display */ int display_client_id_key(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_clientid(dspbuf, *((clientid4 *)(buff->addr))); } /** * @brief Displays the client record from a hash table * * @param[in] dspbuf display buffer to display into * @param[in] buff Buffer to display */ int display_client_id_val(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_client_id_rec(dspbuf, buff->addr); } /** * @brief Create a new client record * * @param[in] clientid The associated clientid * @param[in] client_record The client owner record * @param[in] client_addr Client socket address * @param[in] credential Client credential * @param[in] minorversion Minor version client uses * * @return New client record or NULL. */ nfs_client_id_t *create_client_id(clientid4 clientid, nfs_client_record_t *client_record, nfs_client_cred_t *credential, uint32_t minorversion) { nfs_client_id_t *client_rec = pool_alloc(client_id_pool); state_owner_t *owner; PTHREAD_MUTEX_init(&client_rec->cid_mutex, NULL); owner = &client_rec->cid_owner; PTHREAD_MUTEX_init(&owner->so_mutex, NULL); /* initialize the chan mutex for v4 */ if (minorversion == 0) { struct rpc_call_channel *chan = &client_rec->cid_cb.v40.cb_chan; PTHREAD_MUTEX_init(&chan->chan_mtx, NULL); client_rec->cid_cb.v40.cb_chan_down = true; client_rec->first_path_down_resp_time = 0; } if (clientid == 0) clientid = new_clientid(); client_rec->cid_confirmed = UNCONFIRMED_CLIENT_ID; client_rec->cid_clientid = clientid; client_rec->cid_last_renew = time(NULL); client_rec->cid_client_record = client_record; client_rec->cid_credential = *credential; /* We store the credential which includes gss context here for * using it later, so we should make sure that this doesn't go * away until we destroy this nfs clientid. */ #ifdef _HAVE_GSSAPI if (credential->flavor == RPCSEC_GSS) { struct svc_rpc_gss_data *gd; gd = credential->auth_union.auth_gss.gd; (void)atomic_inc_uint32_t(&gd->refcnt); } #endif /* _HAVE_GSSAPI */ client_rec->cid_minorversion = minorversion; client_rec->gsh_client = op_ctx->client; inc_gsh_client_refcount(op_ctx->client); /* need to init the list_head */ glist_init(&client_rec->cid_openowners); glist_init(&client_rec->cid_lockowners); /* set up the content of the clientid_owner */ owner->so_type = STATE_CLIENTID_OWNER_NFSV4; owner->so_owner.so_nfs4_owner.so_clientid = clientid; owner->so_owner.so_nfs4_owner.so_clientrec = client_rec; owner->so_owner.so_nfs4_owner.so_resp.resop = NFS4_OP_ILLEGAL; owner->so_owner.so_nfs4_owner.so_args.argop = NFS4_OP_ILLEGAL; owner->so_refcount = 1; /* Init the lists for the clientid_owner */ glist_init(&owner->so_lock_list); glist_init(&owner->so_owner.so_nfs4_owner.so_state_list); /* Get a reference to the client record */ (void)inc_client_record_ref(client_rec->cid_client_record); /* Init the list head for expired_client */ glist_init(&client_rec->expired_client); client_rec->marked_for_delayed_cleanup = false; return client_rec; } /** * @brief Inserts an entry describing a clientid4 into the cache. * * @param[in] clientid the client id record * * @retval CLIENT_ID_SUCCESS if successful. * @retval CLIENT_ID_INSERT_MALLOC_ERROR if an error occurred during * the insertion process * @retval CLIENT_ID_NETDB_ERROR if an error occurred during the netdb * query (via gethostbyaddr). */ clientid_status_t nfs_client_id_insert(nfs_client_id_t *clientid) { struct gsh_buffdesc buffkey; struct gsh_buffdesc buffdata; int rc; if (nfs_param.nfsv4_param.max_client_ids && atomic_fetch_uint64_t(&num_confirmed_client_ids) > nfs_param.nfsv4_param.max_client_ids) { LogDebug(COMPONENT_CLIENTID, "Max client_id limit reached - clientid %" PRIx64, clientid->cid_clientid); /* Free the clientid record and return */ free_client_id(clientid); return CLIENT_ID_INSERT_MALLOC_ERROR; } /* Create key from cid_clientid */ buffkey.addr = &clientid->cid_clientid; buffkey.len = sizeof(clientid->cid_clientid); buffdata.addr = clientid; buffdata.len = sizeof(nfs_client_id_t); rc = hashtable_test_and_set(ht_unconfirmed_client_id, &buffkey, &buffdata, HASHTABLE_SET_HOW_SET_NO_OVERWRITE); if (rc != HASHTABLE_SUCCESS) { LogDebug(COMPONENT_CLIENTID, "Could not insert unconfirmed clientid %" PRIx64 " error=%s", clientid->cid_clientid, hash_table_err_to_str(rc)); /* Free the clientid record and return */ free_client_id(clientid); return CLIENT_ID_INSERT_MALLOC_ERROR; } /* Take a reference to the unconfirmed clientid for the hash table. */ (void)inc_client_id_ref(clientid); if (isFullDebug(COMPONENT_CLIENTID) && isFullDebug(COMPONENT_HASHTABLE)) { LogFullDebug(COMPONENT_CLIENTID, "-=-=-=-=-=-=-=-=-=-> ht_unconfirmed_client_id "); hashtable_log(COMPONENT_CLIENTID, ht_unconfirmed_client_id); } /* Attach new clientid to client record's cr_punconfirmed_id. */ clientid->cid_client_record->cr_unconfirmed_rec = clientid; return CLIENT_ID_SUCCESS; } /** * @brief Removes a confirmed client id record. * * @param[in] clientid The client id record * * @return hash table error code */ int remove_confirmed_client_id(nfs_client_id_t *clientid) { int rc; struct gsh_buffdesc buffkey; struct gsh_buffdesc old_key; struct gsh_buffdesc old_value; buffkey.addr = &clientid->cid_clientid; buffkey.len = sizeof(clientid->cid_clientid); rc = HashTable_Del(ht_confirmed_client_id, &buffkey, &old_key, &old_value); if (rc != HASHTABLE_SUCCESS) { LogDebug(COMPONENT_CLIENTID, "Could not remove unconfirmed clientid %" PRIx64 " error=%s", clientid->cid_clientid, hash_table_err_to_str(rc)); GSH_CLIENT_ID_AUTO_TRACEPOINT( nfs4, remove_confirmed_client_id_failed, TRACE_ERR, clientid, "Could not remove unconfirmed clientid. error={}", rc); return rc; } GSH_CLIENT_ID_AUTO_TRACEPOINT(nfs4, remove_confirmed_client_id, TRACE_INFO, clientid, "Remove confirmed client id"); clientid->cid_client_record->cr_confirmed_rec = NULL; /* Set this up so this client id record will be freed. */ clientid->cid_confirmed = EXPIRED_CLIENT_ID; /* Release hash table reference to the unconfirmed record */ (void)dec_client_id_ref(clientid); atomic_dec_uint64_t(&num_confirmed_client_ids); sal_metrics__confirmed_clients(num_confirmed_client_ids); return rc; } /** * @brief Removes an unconfirmed client id record. * * @param[in] clientid The client id record * * @return hash table error code */ int remove_unconfirmed_client_id(nfs_client_id_t *clientid) { int rc; struct gsh_buffdesc buffkey; struct gsh_buffdesc old_key; struct gsh_buffdesc old_value; buffkey.addr = &clientid->cid_clientid; buffkey.len = sizeof(clientid->cid_clientid); rc = HashTable_Del(ht_unconfirmed_client_id, &buffkey, &old_key, &old_value); if (rc != HASHTABLE_SUCCESS) { LogCrit(COMPONENT_CLIENTID, "Could not remove unconfirmed clientid %" PRIx64 " error=%s", clientid->cid_clientid, hash_table_err_to_str(rc)); GSH_CLIENT_ID_AUTO_TRACEPOINT( nfs4, remove_unconfirmed_client_id_failed, TRACE_ERR, clientid, "Could not remove ununconfirmed clientid. error={}", rc); return rc; } GSH_CLIENT_ID_AUTO_TRACEPOINT(nfs4, remove_unconfirmed_client_id, TRACE_INFO, clientid, "Remove unconfirmed client id"); /* XXX prevents calling remove_confirmed before removed_confirmed, * if we failed to maintain the invariant that the cases are * disjoint */ clientid->cid_client_record->cr_unconfirmed_rec = NULL; /* Set this up so this client id record will be freed. */ clientid->cid_confirmed = EXPIRED_CLIENT_ID; /* Release hash table reference to the unconfirmed record */ (void)dec_client_id_ref(clientid); return rc; } /** * @brief Confirm a client id record. * * @param[in] clientid The client id record * @param[in] component The component ID for logging * * @retval CLIENT_ID_SUCCESS if successful. * @retval CLIENT_ID_INVALID_ARGUMENT if unable to find record in * unconfirmed table * @retval CLIENT_ID_INSERT_MALLOC_ERROR if unable to insert record * into confirmed table * @retval CLIENT_ID_NETDB_ERROR if an error occurred during the netdb * query (via gethostbyaddr). */ clientid_status_t nfs_client_id_confirm(nfs_client_id_t *clientid, log_components_t component) { int rc; struct gsh_buffdesc buffkey; struct gsh_buffdesc old_key; struct gsh_buffdesc old_value; buffkey.addr = &clientid->cid_clientid; buffkey.len = sizeof(clientid->cid_clientid); /* Remove the clientid as the unconfirmed entry for the client record */ clientid->cid_client_record->cr_unconfirmed_rec = NULL; rc = HashTable_Del(ht_unconfirmed_client_id, &buffkey, &old_key, &old_value); if (rc != HASHTABLE_SUCCESS) { if (isDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, clientid); LogCrit(COMPONENT_CLIENTID, "Unexpected problem %s, could not remove {%s}", hash_table_err_to_str(rc), str); } return CLIENT_ID_INVALID_ARGUMENT; } clientid->cid_confirmed = CONFIRMED_CLIENT_ID; rc = hashtable_test_and_set(ht_confirmed_client_id, &old_key, &old_value, HASHTABLE_SET_HOW_SET_NO_OVERWRITE); if (rc != HASHTABLE_SUCCESS) { if (isDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, clientid); LogCrit(COMPONENT_CLIENTID, "Unexpected problem %s, could not insert {%s}", hash_table_err_to_str(rc), str); } /* Set this up so this client id record will be freed. */ clientid->cid_confirmed = EXPIRED_CLIENT_ID; /* Release hash table reference to the unconfirmed record */ (void)dec_client_id_ref(clientid); return CLIENT_ID_INSERT_MALLOC_ERROR; } atomic_inc_uint64_t(&num_confirmed_client_ids); sal_metrics__confirmed_clients(num_confirmed_client_ids); /* Add the clientid as the confirmed entry for the client record */ clientid->cid_client_record->cr_confirmed_rec = clientid; nfs4_add_clid(clientid); GSH_CLIENT_ID_AUTO_TRACEPOINT(nfs4, confirm_client_id, TRACE_INFO, clientid, "Client id confirmed"); return CLIENT_ID_SUCCESS; } /** * @brief Check if a clientid has state associated with it. * * @param[in] clientid The client id of interest * * @retval true if the clientid has associated state. */ bool clientid_has_state(nfs_client_id_t *clientid) { bool live_state = false; struct glist_head *glist; PTHREAD_MUTEX_lock(&clientid->cid_mutex); /* Don't bother checking lock owners, there must ALSO be an * open owner with active open state in order for there to be * active lock state. */ /* Check if any open owners have active open state. */ glist_for_each(glist, &clientid->cid_openowners) { live_state = owner_has_state( glist_entry(glist, state_owner_t, so_owner.so_nfs4_owner.so_perclient)); if (live_state) break; } /* Delegations and Layouts are owned by clientid, so check for * active state held by the cid_owner. */ if (!live_state) live_state = owner_has_state(&clientid->cid_owner); PTHREAD_MUTEX_unlock(&clientid->cid_mutex); return live_state; } /* * @brief compare routine used to sort expired client items * * Function used to sort the expired client items according * to lease renew time. Conforms to the glist_compare * function signature so it can be used with glist_insert_sorted * * @param a: Pointer to the glist of a expired client * @param b: Pointer to the glist of another expired client * to compare the first to */ int expired_client_item_compare(struct glist_head *a, struct glist_head *b) { nfs_client_id_t *client_item_a; nfs_client_id_t *client_item_b; client_item_a = glist_entry(a, nfs_client_id_t, expired_client); client_item_b = glist_entry(b, nfs_client_id_t, expired_client); if (client_item_a->cid_last_renew <= client_item_b->cid_last_renew) return -1; return 1; } /* * @brief API to print the expired client items * * @param[in] NA * * @retval NA */ void print_expired_client_list(void) { /* expired_client iterator */ struct glist_head *expired_client_i = NULL; /* Next expired_client */ struct glist_head *expired_client_n = NULL; glist_for_each_safe(expired_client_i, expired_client_n, &expired_client_ids_list) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; /* The client to expire finally */ struct nfs_client_id_t *expired_client = glist_entry(expired_client_i, struct nfs_client_id_t, expired_client); display_client_id_rec(&dspbuf, expired_client); LogFullDebug(COMPONENT_CLIENTID, "Expired Client Entry %s", str); } } /** * @brief Remove client from expired client list. * * NOTE: Must not be called with cid_mutex held (lock ordering). * * @param[in] active_clientid The client id of interest * * @retval NA */ void remove_client_from_expired_client_list(nfs_client_id_t *active_clientid) { /* expired_client iterator */ struct glist_head *expired_client_i = NULL; /* Next expired_client */ struct glist_head *expired_client_n = NULL; PTHREAD_MUTEX_lock(&expired_client_ids_list_lock); glist_for_each_safe(expired_client_i, expired_client_n, &expired_client_ids_list) { struct nfs_client_id_t *expired_client = glist_entry(expired_client_i, struct nfs_client_id_t, expired_client); if (active_clientid->cid_clientid != expired_client->cid_clientid) { continue; } /* Found the client to be removed */ PTHREAD_MUTEX_lock(&expired_client->cid_mutex); glist_del(&expired_client->expired_client); expired_client->marked_for_delayed_cleanup = false; atomic_dec_uint32_t(&num_of_curr_expired_clients); PTHREAD_MUTEX_unlock(&expired_client->cid_mutex); /* Drop ref of the expired_client as it's gone valid */ dec_client_id_ref(expired_client); } PTHREAD_MUTEX_unlock(&expired_client_ids_list_lock); } /** * @brief Client expires, need to take care of owners * * If there is a client_record attached to the clientid, * this function assumes caller holds record->cr_mutex and holds a * reference to record also. * * @param[in] clientid The client id to expire * @param[in] make_stale Set if client id expire is due to ip move. * @param[in] force_expire Expire up the client id by force. * * @return true if the clientid is successfully expired. */ bool nfs_client_id_expire(nfs_client_id_t *clientid, bool make_stale, bool force_expire) { int rc, held; struct gsh_buffdesc buffkey; struct gsh_buffdesc old_key; struct gsh_buffdesc old_value; hash_table_t *ht_expire; char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; struct req_op_context op_context; GSH_CLIENT_ID_AUTO_TRACEPOINT(nfs4, expire_client_start, TRACE_INFO, clientid, "nfs_client_id_expire start"); /* Initialize op_context */ init_op_context_simple(&op_context, NULL, NULL); PTHREAD_MUTEX_lock(&clientid->cid_mutex); if (clientid->cid_confirmed == EXPIRED_CLIENT_ID) { if (isFullDebug(COMPONENT_CLIENTID)) { display_client_id_rec(&dspbuf, clientid); LogFullDebug(COMPONENT_CLIENTID, "Expired (skipped) {%s}", str); } PTHREAD_MUTEX_unlock(&clientid->cid_mutex); release_op_context(); return false; } if (!make_stale && !force_expire && nfs_param.nfsv4_param.expired_client_threshold) { /* Judging the amount of states the client owns. * If it has large number of opened files, we may not want * to hold the memory for a client that has gone away. */ if (atomic_fetch_uint32_t(&clientid->cid_open_state_counter) < nfs_param.nfsv4_param.max_open_files_for_expired_client) { /* We have an expired client to be added to list */ atomic_inc_uint32_t(&num_of_curr_expired_clients); /* Take a ref for clientid and add to list */ inc_client_id_ref(clientid); clientid->marked_for_delayed_cleanup = true; /* Drop the cid_mutex now so it is safe to take the * expired_client_ids_list_lock mutex. */ PTHREAD_MUTEX_unlock(&clientid->cid_mutex); if (isFullDebug(COMPONENT_CLIENTID)) { display_client_id_rec(&dspbuf, clientid); LogFullDebug( COMPONENT_CLIENTID, "Adding Expired Client{%s} for delayed cleanup, Threshold(%u) Curr_Expired(%u).", str, nfs_param.nfsv4_param .expired_client_threshold, atomic_fetch_uint32_t( &num_of_curr_expired_clients)); } PTHREAD_MUTEX_lock(&expired_client_ids_list_lock); /* Sort entries based on cid_last_renew */ glist_insert_sorted(&expired_client_ids_list, &clientid->expired_client, &expired_client_item_compare); PTHREAD_MUTEX_unlock(&expired_client_ids_list_lock); if (isDebug(COMPONENT_CLIENTID)) print_expired_client_list(); release_op_context(); return false; } } if (isDebug(COMPONENT_CLIENTID)) { display_client_id_rec(&dspbuf, clientid); LogDebug(COMPONENT_CLIENTID, "Expiring {%s}", str); GSH_CLIENT_ID_AUTO_TRACEPOINT(nfs4, expire_client, TRACE_INFO, clientid, "Expiring client"); } if ((clientid->cid_confirmed == CONFIRMED_CLIENT_ID) || (clientid->cid_confirmed == STALE_CLIENT_ID)) ht_expire = ht_confirmed_client_id; else ht_expire = ht_unconfirmed_client_id; /* Detach the clientid record from the client record */ if (clientid->cid_client_record->cr_confirmed_rec == clientid) clientid->cid_client_record->cr_confirmed_rec = NULL; if (clientid->cid_client_record->cr_unconfirmed_rec == clientid) clientid->cid_client_record->cr_unconfirmed_rec = NULL; if (make_stale) { /* Keep clientid hashed, but mark it as stale */ clientid->cid_confirmed = STALE_CLIENT_ID; PTHREAD_MUTEX_unlock(&clientid->cid_mutex); } else { /* unhash clientids that are truly expired */ clientid->cid_confirmed = EXPIRED_CLIENT_ID; PTHREAD_MUTEX_unlock(&clientid->cid_mutex); buffkey.addr = &clientid->cid_clientid; buffkey.len = sizeof(clientid->cid_clientid); rc = HashTable_Del(ht_expire, &buffkey, &old_key, &old_value); if ((rc != HASHTABLE_SUCCESS) && (clientid->cid_confirmed == STALE_CLIENT_ID)) { /* Try in the unconfirmed hash table */ rc = HashTable_Del(ht_unconfirmed_client_id, &buffkey, &old_key, &old_value); } if (rc != HASHTABLE_SUCCESS) { LogFatal(COMPONENT_CLIENTID, "Could not remove expired clientid %" PRIx64 " error=%s", clientid->cid_clientid, hash_table_err_to_str(rc)); } else if (ht_expire == ht_confirmed_client_id) { atomic_dec_uint64_t(&num_confirmed_client_ids); sal_metrics__confirmed_clients( num_confirmed_client_ids); } } /* Traverse the client's lock owners, and release all * locks and owners. * * Note: If there is an owner refcount bug, this COULD infinite loop, * and it will spam the log with warnings... Such a refcount bug will * be quickly fixed :-). */ while (true) { state_owner_t *owner; int32_t refcount; PTHREAD_MUTEX_lock(&clientid->cid_mutex); owner = glist_first_entry(&clientid->cid_lockowners, state_owner_t, so_owner.so_nfs4_owner.so_perclient); if (owner == NULL) { PTHREAD_MUTEX_unlock(&clientid->cid_mutex); break; } /* Move owner to end of list in case it doesn't get * freed when we decrement the refcount. */ glist_move_tail(&clientid->cid_lockowners, &owner->so_owner.so_nfs4_owner.so_perclient); /* Hold a reference to the owner while we drop the cid_mutex. */ held = hold_state_owner_ref(owner); PTHREAD_MUTEX_unlock(&clientid->cid_mutex); /* If this owner is in the process of being freed, skip * and work on the next owner. We also do yield for the * other thread to complete freeing this owner! */ if (!held) { sched_yield(); continue; } state_nfs4_owner_unlock_all(owner); refcount = atomic_fetch_int32_t(&owner->so_refcount); if ((refcount > 1) || (isFullDebug(COMPONENT_CLIENTID))) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_owner(&dspbuf, owner); if (refcount > 1) LogWarn(COMPONENT_CLIENTID, "Expired State - Lock Owners, Possibly extra references to {%s}", str); else LogFullDebug( COMPONENT_CLIENTID, "Expired State - Lock Owners, for {%s}", str); } dec_state_owner_ref(owner); } /* revoke layouts for this client*/ revoke_owner_layouts(&clientid->cid_owner); /* release the corresponding open states , close files. * * Note: If there is an owner refcount bug, this COULD infinite loop, * and it will spam the log with warnings... Such a refcount bug will * be quickly fixed :-). */ while (true) { state_owner_t *owner; int32_t refcount; PTHREAD_MUTEX_lock(&clientid->cid_mutex); owner = glist_first_entry(&clientid->cid_openowners, state_owner_t, so_owner.so_nfs4_owner.so_perclient); if (owner == NULL) { PTHREAD_MUTEX_unlock(&clientid->cid_mutex); break; } /* Move owner to end of list in case it doesn't get * freed when we decrement the refcount. */ glist_move_tail(&clientid->cid_openowners, &owner->so_owner.so_nfs4_owner.so_perclient); /* Hold a reference to the owner while we drop the cid_mutex. */ held = hold_state_owner_ref(owner); PTHREAD_MUTEX_unlock(&clientid->cid_mutex); /* If this owner is in the process of being freed, skip * and work on the next owner. We also do yield for the * other thread to complete freeing this owner! */ if (!held) { sched_yield(); continue; } release_openstate(owner); refcount = atomic_fetch_int32_t(&owner->so_refcount); if ((refcount > 1) || (isFullDebug(COMPONENT_CLIENTID))) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_owner(&dspbuf, owner); if (refcount > 1) LogWarn(COMPONENT_CLIENTID, "Expired State - Open Owners, Possibly extra references to {%s}", str); else LogFullDebug( COMPONENT_CLIENTID, "Expired State - Open Owners, for {%s}", str); } dec_state_owner_ref(owner); } /* revoke delegations for this client*/ revoke_owner_delegs(&clientid->cid_owner); /* Destroy v4 callback channel */ if (clientid->cid_minorversion == 0 && clientid->cid_cb.v40.cb_chan.clnt) nfs_rpc_destroy_chan(&clientid->cid_cb.v40.cb_chan); /* For NFSv4.1 clientids, destroy all associated sessions */ if (clientid->cid_minorversion > 0) { struct glist_head *glist = NULL; struct glist_head *glistn = NULL; glist_for_each_safe(glist, glistn, &clientid->cid_cb.v41.cb_session_list) { nfs41_session_t *session = glist_entry( glist, nfs41_session_t, session_link); if (!nfs41_Session_Del(session)) { display_reset_buffer(&dspbuf); display_client_id_rec(&dspbuf, clientid); LogCrit(COMPONENT_SESSIONS, "Expire session failed for {%s}", str); } } /* * Decrement reclaim_completes counter if it sent one and was * in the reclaim table. */ if (clientid->cid_allow_reclaim && clientid->cid_cb.v41.cid_reclaim_complete) atomic_dec_int32_t(&reclaim_completes); } if (clientid->cid_recov_tag != NULL && !make_stale) { nfs4_rm_clid(clientid); gsh_free(clientid->cid_recov_tag); clientid->cid_recov_tag = NULL; } if (isDebug(COMPONENT_CLIENTID)) { display_reset_buffer(&dspbuf); display_client_id_rec(&dspbuf, clientid); LogDebug(COMPONENT_CLIENTID, "Expired (done), about to release last reference {%s}", str); } /* Release the hash table reference to the clientid. */ if (!make_stale) (void)dec_client_id_ref(clientid); if (isFullDebug(COMPONENT_CLIENTID)) { display_reset_buffer(&dspbuf); display_client_id_rec(&dspbuf, clientid); LogFullDebug(COMPONENT_CLIENTID, "Expired (done), released last reference {%s}", str); } release_op_context(); sal_metrics__lease_expire(); return true; } /** * @brief Expire the expired clients added to the client list * * Once the threshold of number of expired clients reaches, * this API is invoked to actually start cleaning them up. * * NOTE: Must not be called with the cid_mutex held (lock ordering). * * @param[in] conflicted_client The conflicted client id to expire. * If passed NULL, judge and clean all expired * ones in the list. * * @return the count of clientids expired. */ int reap_expired_client_list(nfs_client_id_t *conflicted_client) { /* expired_client iterator */ struct glist_head *expired_client_i = NULL; /* Next expired_client */ struct glist_head *expired_client_n = NULL; nfs_client_record_t *client_rec; int count = 0; const uint32_t curr_expired_clients = atomic_fetch_uint32_t(&num_of_curr_expired_clients); GSH_AUTO_TRACEPOINT( nfs4, reap_expired_client_list, TRACE_INFO, "expired_client_threshold={}, curr_expired_clients={}", nfs_param.nfsv4_param.expired_client_threshold, curr_expired_clients); /* If feature disabled or no expired clients present * then skip the expired list reaper task */ if (!((nfs_param.nfsv4_param.expired_client_threshold) && (curr_expired_clients))) { return count; } else { LogFullDebug( COMPONENT_CLIENTID, "Reaping expired client list with client(%p) Curr_Expired(%u) Threshold(%u)", conflicted_client, curr_expired_clients, nfs_param.nfsv4_param.expired_client_threshold); } PTHREAD_MUTEX_lock(&expired_client_ids_list_lock); if (isDebug(COMPONENT_CLIENTID)) print_expired_client_list(); /* Let's start cleaning */ glist_for_each_safe(expired_client_i, expired_client_n, &expired_client_ids_list) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; /* The client to expire finally */ struct nfs_client_id_t *expired_client = glist_entry(expired_client_i, struct nfs_client_id_t, expired_client); /* If conflicting client has been passed, we need expire it */ if (conflicted_client) { if (conflicted_client->cid_clientid != expired_client->cid_clientid) continue; /* Breaking if max threshold not reached OR client had got * expired within Max_Alive_Time_For_Expired_Client */ } else if (!(((time(NULL) - expired_client->cid_last_renew) >= nfs_param.nfsv4_param .max_alive_time_for_expired_client) || (atomic_fetch_uint32_t( &num_of_curr_expired_clients) > nfs_param.nfsv4_param.expired_client_threshold))) { PTHREAD_MUTEX_unlock(&expired_client_ids_list_lock); LogFullDebug(COMPONENT_CLIENTID, "No expired clients ready for cleanup"); GSH_AUTO_TRACEPOINT( nfs4, reap_no_expired, TRACE_INFO, "No expired clients ready for cleanup"); return count; } PTHREAD_MUTEX_lock(&expired_client->cid_mutex); /* Recheck once again before cleaning up, if it became active */ if (valid_lease(expired_client, true)) { PTHREAD_MUTEX_unlock(&expired_client->cid_mutex); LogFullDebug(COMPONENT_CLIENTID, "Skipping client(%p), as it's gone valid.", expired_client); GSH_CLIENT_ID_AUTO_TRACEPOINT( nfs4, reap_skipping, TRACE_INFO, expired_client, "Skipping client {}, as it's gone valid", expired_client->cid_clientid); glist_del(&expired_client->expired_client); expired_client->marked_for_delayed_cleanup = false; /* Drop ref of the expired_client as it's gone valid */ dec_client_id_ref(expired_client); atomic_dec_uint32_t(&num_of_curr_expired_clients); continue; } if (isDebug(COMPONENT_CLIENTID)) { display_client_id_rec(&dspbuf, expired_client); LogFullDebug(COMPONENT_CLIENTID, "Expired Client is %s", str); GSH_CLIENT_ID_AUTO_TRACEPOINT(nfs4, reap_expired_client, TRACE_INFO, expired_client, "Expiring client"); } /* Get the client record */ client_rec = expired_client->cid_client_record; /* if record is STALE, the linkage to client_record is * removed already. Acquire a ref on client record * before we drop the mutex on clientid */ if (client_rec != NULL) inc_client_record_ref(client_rec); PTHREAD_MUTEX_unlock(&expired_client->cid_mutex); if (client_rec != NULL) PTHREAD_MUTEX_lock(&client_rec->cr_mutex); nfs_client_id_expire(expired_client, false, true); if (client_rec != NULL) { PTHREAD_MUTEX_unlock(&client_rec->cr_mutex); dec_client_record_ref(client_rec); } if (isFullDebug(COMPONENT_CLIENTID)) { display_reset_buffer(&dspbuf); display_client_id_rec(&dspbuf, expired_client); LogFullDebug(COMPONENT_CLIENTID, "Delayed reaper, Expired {%s}", str); } glist_del(&expired_client->expired_client); expired_client->marked_for_delayed_cleanup = false; /* Drop ref of the expired_client taken before adding to list */ dec_client_id_ref(expired_client); atomic_dec_uint32_t(&num_of_curr_expired_clients); count++; /* If conflicting client has been expired off, let's break */ if (conflicted_client) break; } PTHREAD_MUTEX_unlock(&expired_client_ids_list_lock); return count; } /** * @brief Get a clientid from a hash table * * @param[in] ht Table from which to fetch * @param[in] clientid Clientid to fetch * @param[out] client_rec Fetched client record * * @return Clientid status. */ clientid_status_t nfs_client_id_get(hash_table_t *ht, clientid4 clientid, nfs_client_id_t **client_rec) { struct gsh_buffdesc buffkey; struct gsh_buffdesc buffval; clientid_status_t status; uint64_t unique_prefix = get_unique_server_id() & 0xFFFFFFFF; uint64_t cid_epoch = (uint64_t)(clientid >> (clientid4)32); nfs_client_id_t *pclientid; /* Don't bother to look up clientid if unique prefixes don't match */ if (cid_epoch != unique_prefix) { if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug( COMPONENT_CLIENTID, "%s NOTFOUND (epoch doesn't match, assumed STALE)", ht->parameter.ht_name); *client_rec = NULL; return CLIENT_ID_STALE; } buffkey.addr = &clientid; buffkey.len = sizeof(clientid4); if (isFullDebug(COMPONENT_CLIENTID) && isDebug(COMPONENT_HASHTABLE)) { LogFullDebug(COMPONENT_CLIENTID, "%s KEY {%" PRIx64 "}", ht->parameter.ht_name, clientid); } if (isFullDebug(COMPONENT_CLIENTID) && isFullDebug(COMPONENT_HASHTABLE)) { LogFullDebug(COMPONENT_CLIENTID, "-=-=-=-=-=-=-=-=-=-> %s", ht->parameter.ht_name); hashtable_log(COMPONENT_CLIENTID, ht); } if (hashtable_getref(ht, &buffkey, &buffval, Hash_inc_client_id_ref) == HASHTABLE_SUCCESS) { if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_CLIENTID, "%s FOUND", ht->parameter.ht_name); pclientid = buffval.addr; if (pclientid->cid_confirmed == STALE_CLIENT_ID) { /* Stale client because of ip detach and attach to * same node */ dec_client_id_ref(pclientid); status = CLIENT_ID_STALE; *client_rec = NULL; } else { *client_rec = pclientid; status = CLIENT_ID_SUCCESS; } } else { if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_CLIENTID, "%s NOTFOUND (assumed EXPIRED)", ht->parameter.ht_name); *client_rec = NULL; status = CLIENT_ID_EXPIRED; } return status; } /** * @brief Triy to get a pointer to an unconfirmed entry for client_id cache. * * @param[in] clientid The client id * @param[out] client_rec The found client id structure * * @return Same as nfs_client_id_get */ clientid_status_t nfs_client_id_get_unconfirmed(clientid4 clientid, nfs_client_id_t **client_rec) { return nfs_client_id_get(ht_unconfirmed_client_id, clientid, client_rec); } /** * @brief Tries to get a pointer to an confirmed entry for client_id cache. * * @param[in] clientid The client id * @param[out] client_rec The found client id * * @return the result previously set if the return code CLIENT_ID_SUCCESS * */ clientid_status_t nfs_client_id_get_confirmed(clientid4 clientid, nfs_client_id_t **client_rec) { return nfs_client_id_get(ht_confirmed_client_id, clientid, client_rec); } static hash_parameter_t cid_confirmed_hash_param = { .index_size = PRIME_STATE, .hash_func_key = client_id_value_hash_func, .hash_func_rbt = client_id_rbt_hash_func, .hash_func_both = NULL, .compare_key = compare_client_id, .display_key = display_client_id_key, .display_val = display_client_id_val, .ht_name = "Confirmed Client ID", .flags = HT_FLAG_CACHE, .ht_log_component = COMPONENT_CLIENTID, }; static hash_parameter_t cid_unconfirmed_hash_param = { .index_size = PRIME_STATE, .hash_func_key = client_id_value_hash_func, .hash_func_rbt = client_id_rbt_hash_func, .hash_func_both = NULL, .compare_key = compare_client_id, .display_key = display_client_id_key, .display_val = display_client_id_val, .ht_name = "Unconfirmed Client ID", .flags = HT_FLAG_CACHE, .ht_log_component = COMPONENT_CLIENTID, }; int display_clientid(struct display_buffer *dspbuf, clientid4 clientid) { int b_left = display_buffer_remain(dspbuf); uint32_t counter = clientid & UINT32_MAX; uint32_t unique = clientid >> (clientid4)32; if (b_left <= 0) return b_left; return display_printf(dspbuf, "Unique=0x%08" PRIx32 " Counter=0x%08" PRIx32, unique, counter); } /** * @brief Builds a new clientid4 value * * We use the clientid counter and the server epoch or a value supplied in the * config, the latter should ensure that clientids from old instances of * Ganesha are marked as invalid. * * @return The new clientid. */ clientid4 new_clientid(void) { clientid4 newid = atomic_inc_uint32_t(&clientid_counter); return newid + (clientid4)(get_unique_server_id() << 32); } /** * @brief Builds a new verifier4 value. * * @param[out] verf The verifier */ void new_clientid_verifier(char *verf) { uint64_t my_verifier = atomic_inc_uint64_t(&clientid_verifier); memcpy(verf, &my_verifier, NFS4_VERIFIER_SIZE); } /****************************************************************************** * * Functions to handle lookup of clientid by nfs_client_id4 received from * client. * *****************************************************************************/ /** * @brief Display a client owner record * * @param[in] record The record to display * @param[out] str Output buffer * * @return Length of output string. */ int display_client_record(struct display_buffer *dspbuf, nfs_client_record_t *record) { int b_left = display_printf(dspbuf, "%p name=", record); if (b_left <= 0) return b_left; b_left = display_opaque_value(dspbuf, record->cr_client_val, record->cr_client_val_len); if (b_left <= 0) return b_left; return display_printf(dspbuf, " cr_refcount=%" PRId32, atomic_fetch_int32_t(&record->cr_refcount)); } /** * @brief Increment the refcount on a client owner record * * @param[in] record The record on which to take a reference */ int32_t inc_client_record_ref(nfs_client_record_t *record) { int32_t rec_refcnt = atomic_inc_int32_t(&record->cr_refcount); if (isFullDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_record(&dspbuf, record); LogFullDebug(COMPONENT_CLIENTID, "Increment cr_refcount {%s}", str); } return rec_refcnt; } /** * @brief Deconstruct and free a client owner record * * @param[in] record The record to free */ void free_client_record(nfs_client_record_t *record) { PTHREAD_MUTEX_destroy(&record->cr_mutex); gsh_free(record); } /** * @brief Decrement the refcount on a client owner record * * @param[in] record The record on which to release a reference */ int32_t dec_client_record_ref(nfs_client_record_t *record) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; struct hash_latch latch; hash_error_t rc; struct gsh_buffdesc buffkey; struct gsh_buffdesc old_value; struct gsh_buffdesc old_key; int32_t refcount; bool str_valid = false; if (isDebug(COMPONENT_CLIENTID)) { display_client_record(&dspbuf, record); str_valid = true; } refcount = atomic_dec_int32_t(&record->cr_refcount); if (refcount > 0) { LogFullDebug(COMPONENT_CLIENTID, "Decrement cr_refcount now=%" PRId32 " {%s}", refcount, str); return refcount; } assert(refcount == 0); LogFullDebug(COMPONENT_CLIENTID, "Try to remove {%s}", str); buffkey.addr = record; buffkey.len = sizeof(*record); /* Since the refcount is zero, another thread that needs this * record might have deleted ours, so expect not to find one or * find someone else's record! */ rc = hashtable_getlatch(ht_client_record, &buffkey, &old_value, true, &latch); switch (rc) { case HASHTABLE_SUCCESS: /* If ours, delete from hash table */ if (old_value.addr == record) { hashtable_deletelatched(ht_client_record, &buffkey, &latch, &old_key, &old_value); } break; case HASHTABLE_ERROR_NO_SUCH_KEY: break; default: if (!str_valid) { display_client_record(&dspbuf, record); } LogCrit(COMPONENT_CLIENTID, "Error %s, could not find {%s}", hash_table_err_to_str(rc), str); return refcount; } /* Release the latch */ hashtable_releaselatched(ht_client_record, &latch); if (str_valid) LogFullDebug(COMPONENT_CLIENTID, "Free {%s}", str); free_client_record(record); return refcount; } /** * @brief Hash a client owner record key * * @param[in] key The client owner record * * @return The hash. */ uint64_t client_record_value_hash(nfs_client_record_t *key) { uint64_t other; other = key->cr_pnfs_flags; other = (other << 32) | key->cr_server_addr; return CityHash64WithSeed(key->cr_client_val, key->cr_client_val_len, other); } /** * * @brief Computes the hash value for the entry in Client Record cache. * * @param[in] hparam Hash table parameter. * @param[in] key The hash key buffer * * @return the computed hash value. * * @see hashtable_init * */ uint32_t client_record_value_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { uint64_t res; res = client_record_value_hash(key->addr) % hparam->index_size; if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_CLIENTID, "value = %" PRIu64, res); return (uint32_t)res; } /** * @brief Computes the RBT hash for the entry in Client Id cache. * * @param[in] hparam Hash table parameter * @param[in] key The hash key buffer * * @return The computed rbt value. * * @see hashtable_init * */ uint64_t client_record_rbt_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { uint64_t res; res = client_record_value_hash(key->addr); if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_CLIENTID, "value = %" PRIu64, res); return res; } /** * @brief Compares the cr_client_val the key buffers. * * @param[in] buff1 first key * @param[in] buff2 second key * * @return 0 if keys are identifical, 1 if they are different. * */ int compare_client_record(struct gsh_buffdesc *buff1, struct gsh_buffdesc *buff2) { nfs_client_record_t *pkey1 = buff1->addr; nfs_client_record_t *pkey2 = buff2->addr; if (pkey1->cr_client_val_len != pkey2->cr_client_val_len) return 1; if (pkey1->cr_pnfs_flags != pkey2->cr_pnfs_flags) return 1; return memcmp(pkey1->cr_client_val, pkey2->cr_client_val, pkey1->cr_client_val_len); } /** * @brief Displays the client_record stored in the buffer. * * @param[in] dspbuf display buffer to display into * @param[in] buff Buffer to display */ int display_client_record_key(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_client_record(dspbuf, buff->addr); } /** * @brief Displays the client_record stored in the buffer. * * @param[in] dspbuf display buffer to display into * @param[in] buff Buffer to display */ int display_client_record_val(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_client_record(dspbuf, buff->addr); } /** * @brief Get a client record from the table * * @param[in] value Client owner name * @param[in] len Length of owner name * * @return The client record or NULL. */ nfs_client_record_t *get_client_record(const char *const value, const size_t len, const uint32_t pnfs_flags, const uint32_t server_addr) { nfs_client_record_t *record; nfs_client_record_t *old; struct gsh_buffdesc buffkey; struct gsh_buffdesc buffval; struct hash_latch latch; hash_error_t rc; int32_t refcount; assert(len); record = gsh_malloc(sizeof(nfs_client_record_t) + len); record->cr_refcount = 1; record->cr_client_val_len = len; record->cr_confirmed_rec = NULL; record->cr_unconfirmed_rec = NULL; memcpy(record->cr_client_val, value, len); record->cr_pnfs_flags = pnfs_flags; record->cr_server_addr = server_addr; buffkey.addr = record; buffkey.len = sizeof(*record); rc = hashtable_getlatch(ht_client_record, &buffkey, &buffval, true, &latch); switch (rc) { case HASHTABLE_SUCCESS: old = buffval.addr; refcount = atomic_inc_int32_t(&old->cr_refcount); if (refcount == 1) { /* This record is in the process of getting freed. * Delete from the hash table and pretend as * though we didn't find it! */ (void)atomic_dec_int32_t(&old->cr_refcount); hashtable_deletelatched(ht_client_record, &buffkey, &latch, NULL, NULL); break; } /* Use the existing record */ hashtable_releaselatched(ht_client_record, &latch); gsh_free(record); return old; case HASHTABLE_ERROR_NO_SUCH_KEY: break; default: LogFatal(COMPONENT_CLIENTID, "Client record hash table corrupt."); } /* Initialize and insert the new record */ PTHREAD_MUTEX_init(&record->cr_mutex, NULL); buffval.addr = record; buffval.len = sizeof(nfs_client_record_t) + len; rc = hashtable_setlatched(ht_client_record, &buffkey, &buffval, &latch, false, NULL, NULL); if (rc != HASHTABLE_SUCCESS) { LogFatal(COMPONENT_CLIENTID, "Client record hash table corrupt."); } return record; } struct client_callback_arg { void *state; nfs_client_id_t *pclientid; bool (*cb)(nfs_client_id_t *, void *); }; /** * @brief client callback */ static void client_cb(struct fridgethr_context *ctx) { struct client_callback_arg *cb_arg; cb_arg = ctx->arg; cb_arg->cb(cb_arg->pclientid, cb_arg->state); dec_client_id_ref(cb_arg->pclientid); gsh_free(cb_arg->state); gsh_free(cb_arg); } /** * @brief Walk the client tree and do the callback on each 4.1 nodes * * @param cb [IN] Callback function * @param state [IN] param block to pass */ void nfs41_foreach_client_callback(bool (*cb)(nfs_client_id_t *cl, void *state), void *state) { uint32_t i; hash_table_t *ht = ht_confirmed_client_id; struct rbt_head *head_rbt; struct hash_data *pdata = NULL; struct rbt_node *pn; nfs_client_id_t *pclientid; struct client_callback_arg *cb_arg; int rc; /* For each bucket of the hashtable */ for (i = 0; i < ht->parameter.index_size; i++) { head_rbt = &(ht->partitions[i].rbt); /* acquire mutex */ PTHREAD_RWLOCK_wrlock(&(ht->partitions[i].ht_lock)); /* go through all entries in the red-black-tree */ RBT_LOOP(head_rbt, pn) { pdata = RBT_OPAQ(pn); pclientid = pdata->val.addr; RBT_INCREMENT(pn); if (pclientid->cid_minorversion > 0) { cb_arg = gsh_malloc( sizeof(struct client_callback_arg)); cb_arg->cb = cb; cb_arg->state = state; cb_arg->pclientid = pclientid; inc_client_id_ref(pclientid); rc = fridgethr_submit(state_async_fridge, client_cb, cb_arg); if (rc != 0) { LogCrit(COMPONENT_CLIENTID, "unable to start client cb thread %d", rc); gsh_free(cb_arg); dec_client_id_ref(pclientid); } } } PTHREAD_RWLOCK_unlock(&(ht->partitions[i].ht_lock)); } } /** * @brief For the given gsh_client, get the nfsv41-client and destroy all * its connections * * @param gsh_client [IN] gsh_client to disconnect * * @note This function's implementation is not optimal, and has a * high performance cost. It must be used only for debug or administrative * purposes. */ int destroy_all_client_connections(const struct gsh_client *gsh_client) { uint32_t i; hash_table_t *ht = ht_confirmed_client_id; struct rbt_head *head_rbt; struct hash_data *pdata = NULL; struct rbt_node *pn; nfs_client_id_t *clientid; char client_str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(client_str), client_str, client_str }; int total_connections_destroyed = 0; /* For each bucket of the hashtable */ for (i = 0; i < ht->parameter.index_size; i++) { head_rbt = &(ht->partitions[i].rbt); /* acquire mutex */ PTHREAD_RWLOCK_rdlock(&(ht->partitions[i].ht_lock)); /* go through all entries in the red-black-tree */ RBT_LOOP(head_rbt, pn) { pdata = RBT_OPAQ(pn); clientid = pdata->val.addr; RBT_INCREMENT(pn); if (clientid->cid_minorversion == 0 || clientid->gsh_client != gsh_client) { continue; } display_client_id_rec(&dspbuf, clientid); LogInfo(COMPONENT_CLIENTID, "Found nfsv41-client: %s for input gsh_client: %s", client_str, gsh_client->hostaddr_str); display_reset_buffer(&dspbuf); struct glist_head *curr_node, *next_node; pthread_mutex_lock(&clientid->cid_mutex); glist_for_each_safe( curr_node, next_node, &clientid->cid_cb.v41.cb_session_list) { nfs41_session_t *const session = glist_entry(curr_node, nfs41_session_t, session_link); total_connections_destroyed += nfs41_Session_Destroy_All_Connections( session); } pthread_mutex_unlock(&clientid->cid_mutex); } PTHREAD_RWLOCK_unlock(&(ht->partitions[i].ht_lock)); } LogInfo(COMPONENT_CLIENTID, "Destroyed %d connections for gsh-client: %s", total_connections_destroyed, gsh_client->hostaddr_str); return total_connections_destroyed; } static hash_parameter_t cr_hash_param = { .index_size = PRIME_STATE, .hash_func_key = client_record_value_hash_func, .hash_func_rbt = client_record_rbt_hash_func, .hash_func_both = NULL, .compare_key = compare_client_record, .display_key = display_client_record_key, .display_val = display_client_record_val, .ht_name = "Client Record", .flags = HT_FLAG_CACHE, .ht_log_component = COMPONENT_CLIENTID, }; /** * @brief Init the hashtable for Client Id cache. * * Perform all the required initialization for hashtable Client Id cache * * @return 0 if successful, -1 otherwise */ int nfs_Init_client_id(void) { ht_confirmed_client_id = hashtable_init(&cid_confirmed_hash_param); if (ht_confirmed_client_id == NULL) { LogCrit(COMPONENT_INIT, "NFS CLIENT_ID: Cannot init Client Id cache"); return -1; } ht_unconfirmed_client_id = hashtable_init(&cid_unconfirmed_hash_param); if (ht_unconfirmed_client_id == NULL) { LogCrit(COMPONENT_INIT, "NFS CLIENT_ID: Cannot init Client Id cache"); return -1; } ht_client_record = hashtable_init(&cr_hash_param); if (ht_client_record == NULL) { LogCrit(COMPONENT_INIT, "NFS CLIENT_ID: Cannot init Client Record cache"); return -1; } client_id_pool = pool_basic_init("NFS4 Client ID Pool", sizeof(nfs_client_id_t)); PTHREAD_MUTEX_init(&expired_client_ids_list_lock, NULL); glist_init(&expired_client_ids_list); return CLIENT_ID_SUCCESS; } /** * @brief Get the total count of files open by the clients in the hashtables. * * Loop over all RBT nodes in HashTable buckets to get the count of open files * * @return Total NFSv4 Open Files Count */ uint64_t get_total_count_of_open_states(void) { hash_table_t *ht = ht_confirmed_client_id; struct rbt_head *head_rbt; struct hash_data *addr = NULL; uint32_t i; struct rbt_node *pn; nfs_client_id_t *client_id; uint64_t count = 0; char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; /* For each bucket of the hashtable - ht_confirmed_client_id */ for (i = 0; i < ht->parameter.index_size; i++) { /* acquire read lock */ PTHREAD_RWLOCK_rdlock(&(ht->partitions[i].ht_lock)); head_rbt = &(ht->partitions[i].rbt); /* go through all entries in the red-black-tree */ RBT_LOOP(head_rbt, pn) { addr = RBT_OPAQ(pn); client_id = addr->val.addr; display_client_id_rec(&dspbuf, client_id); LogEvent(COMPONENT_CLIENTID, "Dump Client Entry %s", str); count += client_id->cid_open_state_counter; display_reset_buffer(&dspbuf); RBT_INCREMENT(pn); } PTHREAD_RWLOCK_unlock(&(ht->partitions[i].ht_lock)); } return count; } /** @} */ nfs-ganesha-6.5/src/SAL/nfs4_lease.c000066400000000000000000000167171473756622300172060ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file nfs4_lease.c * @brief NFSv4 lease management */ #include "config.h" #include "log.h" #include "nfs_core.h" #include "nfs4.h" #include "sal_functions.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs4.h" #endif /** * @brief Return the lifetime of a valid lease * * @param[in] clientid The client record to check * @param[in] is_from_reaper Whether expired client list reaper * have invoked the validation. * * @return The lease lifetime or 0 if expired. */ static unsigned int _valid_lease(nfs_client_id_t *clientid, bool is_from_reaper) { time_t t; if (clientid->cid_confirmed == EXPIRED_CLIENT_ID) return 0; if (clientid->cid_lease_reservations != 0) return nfs_param.nfsv4_param.lease_lifetime; t = time(NULL); if (clientid->cid_last_renew + nfs_param.nfsv4_param.lease_lifetime > t) { return (clientid->cid_last_renew + nfs_param.nfsv4_param.lease_lifetime) - t; } else if (!is_from_reaper && clientid->marked_for_delayed_cleanup) { LogFullDebug(COMPONENT_CLIENTID, "Returning as valid as client is added to list"); return 1; } return 0; } /** * @brief Check if lease is valid * * The caller must hold cid_mutex. * * @param[in] clientid Record to check lease for. * @param[in] is_from_reaper Whether expired client list reaper * have invoked the validation. * * @return 1 if lease is valid, 0 if not. * */ bool valid_lease(nfs_client_id_t *clientid, bool is_from_reaper) { unsigned int valid; valid = _valid_lease(clientid, is_from_reaper); if (isFullDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, clientid); LogFullDebug(COMPONENT_CLIENTID, "Check Lease %s (Valid=%s %u seconds left)", str, valid ? "YES" : "NO", valid); } GSH_CLIENT_ID_AUTO_TRACEPOINT(nfs4, lease, TRACE_INFO, clientid, "{} seconds left on lease", valid); return valid != 0; } /** * @brief Check if lease is valid and reserve it. * * Lease reservation prevents any other thread from expiring the lease. Caller * must call update lease to release the reservation. * * @param[in] clientid Client record to check lease for * * @return 1 if lease is valid, 0 if not. * */ int reserve_lease(nfs_client_id_t *clientid) { unsigned int valid; valid = _valid_lease(clientid, false); if (valid != 0) clientid->cid_lease_reservations++; if (isFullDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, clientid); LogFullDebug(COMPONENT_CLIENTID, "Reserve Lease %s (Valid=%s %u seconds left)", str, valid ? "YES" : "NO", valid); } return valid != 0; } /** * @brief Check if lease is valid and reserve it or expire it. * * Also, if valid, and update is true, update the lease. * * Lease reservation prevents any other thread from expiring the lease. Caller * must call update lease to release the reservation. * * @param[in] clientid Client record to check lease for * @param[in] update Indicate that lease should also be updated if valid * @param[in] st_owner Pass down the ref-ed state_owner * * @return true if lease is valid, false if not. * */ bool reserve_lease_or_expire(nfs_client_id_t *clientid, bool update, state_owner_t **st_owner) { unsigned int valid; bool unexpire; PTHREAD_MUTEX_lock(&clientid->cid_mutex); valid = _valid_lease(clientid, false); if (valid != 0) clientid->cid_lease_reservations++; if (isFullDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, clientid); LogFullDebug(COMPONENT_CLIENTID, "Reserve Lease %s (Valid=%s %u seconds left)", str, valid ? "YES" : "NO", valid); } if (valid == 0) { /* Let's expire the lease */ /* Drop the Reference on st_owner, else nfs_client_id_expire * would not be able to clear the state owners */ if (st_owner != NULL) { dec_state_owner_ref(*st_owner); *st_owner = NULL; } /* Get the client record. */ nfs_client_record_t *client_rec = clientid->cid_client_record; /* get a ref to client_id as we might drop the * last reference with expiring. */ inc_client_id_ref(clientid); /* if record is STALE, the linkage to client_record is * removed already. Acquire a ref on client record * before we drop the mutex on clientid */ if (client_rec != NULL) inc_client_record_ref(client_rec); PTHREAD_MUTEX_unlock(&clientid->cid_mutex); if (client_rec != NULL) PTHREAD_MUTEX_lock(&client_rec->cr_mutex); nfs_client_id_expire(clientid, false, true); if (client_rec != NULL) { PTHREAD_MUTEX_unlock(&client_rec->cr_mutex); dec_client_record_ref(client_rec); } /* drop our reference to the client_id */ dec_client_id_ref(clientid); return false; } if (update) unexpire = update_lease(clientid); else unexpire = false; PTHREAD_MUTEX_unlock(&clientid->cid_mutex); if (unexpire) remove_client_from_expired_client_list(clientid); return true; } /** * @brief Release a lease reservation and update lease. * * Lease reservation prevents any other thread from expiring the lease. This * function releases the lease reservation. Before releasing the last * reservation, cid_last_renew will be updated. * * @param[in] clientid The clientid record to update * * @return true if remove_client_from_expired_client_list must be called * after releasing cid_mutex. * false otherwise * */ bool update_lease(nfs_client_id_t *clientid) { bool unexpire = false; clientid->cid_lease_reservations--; /* Renew lease when last reservation is released */ if (clientid->cid_lease_reservations == 0) { clientid->cid_last_renew = time(NULL); /* Once the lease timer is updated then the client is active and * if the unresponsive client was marked as expired earlier, * then request caller to move it out of the expired client list */ unexpire = clientid->marked_for_delayed_cleanup; } if (isFullDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, clientid); LogFullDebug(COMPONENT_CLIENTID, "Update Lease %s", str); } return unexpire; } /** @} */ nfs-ganesha-6.5/src/SAL/nfs4_owner.c000066400000000000000000000516751473756622300172510ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file nfs4_owner.c * @brief The management of the NFS4 Owner cache. */ #include "config.h" #include #include #include "log.h" #include "hashtable.h" #include "nfs4.h" #include "sal_functions.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_core.h" hash_table_t *ht_nfs4_owner; /** * @brief Display an NFSv4 owner key * * @param[in] dspbuf display buffer to display into * @param[in] buff Key to display */ int display_nfs4_owner_key(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_nfs4_owner(dspbuf, buff->addr); } /** * @brief Display NFSv4 owner * * @param[in] owner The state owner * @param[out] str Output string * * @return the bytes remaining in the buffer. */ int display_nfs4_owner(struct display_buffer *dspbuf, state_owner_t *owner) { int b_left; time_t texpire; struct state_nfs4_owner_t *nfs4_owner; if (owner == NULL) return display_cat(dspbuf, ""); nfs4_owner = &owner->so_owner.so_nfs4_owner; b_left = display_printf(dspbuf, "%s %p:", state_owner_type_to_str(owner->so_type), owner); if (b_left <= 0) return b_left; b_left = display_printf(dspbuf, " clientid={"); if (b_left <= 0) return b_left; b_left = display_client_id_rec(dspbuf, nfs4_owner->so_clientrec); if (b_left <= 0) return b_left; b_left = display_printf(dspbuf, "} owner="); if (b_left <= 0) return b_left; b_left = display_opaque_value(dspbuf, owner->so_owner_val, owner->so_owner_len); if (b_left <= 0) return b_left; b_left = display_printf(dspbuf, " confirmed=%u seqid=%u", nfs4_owner->so_confirmed, nfs4_owner->so_seqid); if (b_left <= 0) return b_left; if (nfs4_owner->so_related_owner != NULL) { b_left = display_printf(dspbuf, " related_owner={"); if (b_left <= 0) return b_left; b_left = display_nfs4_owner(dspbuf, nfs4_owner->so_related_owner); if (b_left <= 0) return b_left; b_left = display_printf(dspbuf, "}"); if (b_left <= 0) return b_left; } texpire = atomic_fetch_time_t(&nfs4_owner->so_cache_expire); if (texpire != 0) { b_left = display_printf(dspbuf, " cached(expires in %ld secs)", texpire - time(NULL)); if (b_left <= 0) return b_left; } return display_printf(dspbuf, " so_refcount=%d", atomic_fetch_int32_t(&owner->so_refcount)); } /** * @brief Display owner from hash table * * @param[in] dspbuf display buffer to display into * @param[in] buff Buffer */ int display_nfs4_owner_val(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_nfs4_owner(dspbuf, buff->addr); } /** * @brief Compare two NFSv4 owners * * @param[in] owner1 One owner * @param[in] owner2 Another owner * * @retval 0 on equality. * @retval 1 on inequality. */ int compare_nfs4_owner(state_owner_t *owner1, state_owner_t *owner2) { if (isFullDebug(COMPONENT_STATE) && isDebug(COMPONENT_HASHTABLE)) { char str1[LOG_BUFF_LEN / 2] = "\0"; char str2[LOG_BUFF_LEN / 2] = "\0"; struct display_buffer dspbuf1 = { sizeof(str1), str1, str1 }; struct display_buffer dspbuf2 = { sizeof(str2), str2, str2 }; display_nfs4_owner(&dspbuf1, owner1); display_nfs4_owner(&dspbuf2, owner2); LogFullDebug(COMPONENT_STATE, "{%s} vs {%s}", str1, str2); } if (owner1 == NULL || owner2 == NULL) return 1; if (owner1 == owner2) return 0; if (owner1->so_type != owner2->so_type) return 1; if (owner1->so_owner.so_nfs4_owner.so_clientid != owner2->so_owner.so_nfs4_owner.so_clientid) return 1; if (owner1->so_owner_len != owner2->so_owner_len) return 1; return memcmp(owner1->so_owner_val, owner2->so_owner_val, owner1->so_owner_len); } /** * @brief Compare two NFSv4 owners in the hash table * * @param[in] buff1 One key * @param[in] buff2 Another owner * * @retval 0 on equality. * @retval 1 on inequality. */ int compare_nfs4_owner_key(struct gsh_buffdesc *buff1, struct gsh_buffdesc *buff2) { state_owner_t *pkey1 = buff1->addr; state_owner_t *pkey2 = buff2->addr; if (isFullDebug(COMPONENT_STATE) && isDebug(COMPONENT_HASHTABLE)) { char str1[LOG_BUFF_LEN / 2] = "\0"; char str2[LOG_BUFF_LEN / 2] = "\0"; struct display_buffer dspbuf1 = { sizeof(str1), str1, str1 }; struct display_buffer dspbuf2 = { sizeof(str2), str2, str2 }; display_owner(&dspbuf1, pkey1); display_owner(&dspbuf2, pkey2); if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "{%s} vs {%s}", str1, str2); } if (pkey1 == NULL || pkey2 == NULL) return 1; if (pkey1->so_type != pkey2->so_type) return 1; return compare_nfs4_owner(pkey1, pkey2); } /** * @brief Compute the hash index for an NFSv4 owner * * @todo Destroy this function and replace it with a real hash. * * @param[in] hparam Hash parameter * @param[in] key The key * * @return The hash index. */ uint32_t nfs4_owner_value_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { unsigned int sum = 0; unsigned int i = 0; unsigned char c = 0; uint32_t res = 0; state_owner_t *pkey = key->addr; /* Compute the sum of all the characters */ for (i = 0; i < pkey->so_owner_len; i++) { c = ((char *)pkey->so_owner_val)[i]; sum += c; } res = ((uint32_t)pkey->so_owner.so_nfs4_owner.so_clientid + (uint32_t)sum + pkey->so_owner_len + (uint32_t)pkey->so_type) % (uint32_t)hparam->index_size; if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "value = %" PRIu32, res); return res; } /** * @brief Compute the RBT hash for an NFSv4 owner * * @todo Destroy this function and replace it with a real hash. * * @param[in] hparam Hash parameter * @param[in] key The key * * @return The RBT hash. */ uint64_t nfs4_owner_rbt_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { state_owner_t *pkey = key->addr; unsigned int sum = 0; unsigned int i = 0; unsigned char c = 0; uint64_t res = 0; /* Compute the sum of all the characters */ for (i = 0; i < pkey->so_owner_len; i++) { c = ((char *)pkey->so_owner_val)[i]; sum += c; } res = (uint64_t)pkey->so_owner.so_nfs4_owner.so_clientid + (uint64_t)sum + pkey->so_owner_len + (uint64_t)pkey->so_type; if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "rbt = %" PRIu64, res); return res; } /** * @brief Free an NFS4 owner object * * @param[in] owner The owner to remove */ void free_nfs4_owner(state_owner_t *owner) { state_nfs4_owner_t *nfs4_owner = &owner->so_owner.so_nfs4_owner; if (nfs4_owner->so_related_owner != NULL) dec_state_owner_ref(nfs4_owner->so_related_owner); /* Release the saved response. */ nfs4_Compound_FreeOne(&nfs4_owner->so_resp); /* Remove the owner from the owners per clientid list. */ PTHREAD_MUTEX_lock(&nfs4_owner->so_clientrec->cid_mutex); glist_del(&nfs4_owner->so_perclient); PTHREAD_MUTEX_unlock(&nfs4_owner->so_clientrec->cid_mutex); dec_client_id_ref(nfs4_owner->so_clientrec); nfs4_owner->so_clientrec = NULL; } static hash_parameter_t nfs4_owner_param = { .index_size = PRIME_STATE, .hash_func_key = nfs4_owner_value_hash_func, .hash_func_rbt = nfs4_owner_rbt_hash_func, .compare_key = compare_nfs4_owner_key, .display_key = display_nfs4_owner_key, .display_val = display_nfs4_owner_val, .flags = HT_FLAG_CACHE, }; /** * @brief Init the hashtable for NFSv4 owner cache * * @retval 0 if successful. * @retval -1 if we failed. */ int Init_nfs4_owner(void) { ht_nfs4_owner = hashtable_init(&nfs4_owner_param); if (ht_nfs4_owner == NULL) { LogCrit(COMPONENT_STATE, "Cannot init NFS Open Owner cache"); return -1; } return 0; } /* nfs4_Init_nfs4_owner */ /** * @brief Initialize an NFS4 open owner object * * @param[in] owner The owner record * */ static void init_nfs4_owner(state_owner_t *owner) { state_nfs4_owner_t *nfs4_owner = &owner->so_owner.so_nfs4_owner; glist_init(&nfs4_owner->so_state_list); /* Increment refcount on related owner */ if (nfs4_owner->so_related_owner != NULL) inc_state_owner_ref(nfs4_owner->so_related_owner); /* Increment reference count for clientid record */ inc_client_id_ref(nfs4_owner->so_clientrec); PTHREAD_MUTEX_lock(&nfs4_owner->so_clientrec->cid_mutex); if (owner->so_type == STATE_OPEN_OWNER_NFSV4) { /* If open owner, add to clientid lock owner list */ glist_add_tail(&nfs4_owner->so_clientrec->cid_openowners, &nfs4_owner->so_perclient); } else if (owner->so_type == STATE_LOCK_OWNER_NFSV4) { /* If lock owner, add to clientid open owner list */ glist_add_tail(&nfs4_owner->so_clientrec->cid_lockowners, &nfs4_owner->so_perclient); } PTHREAD_MUTEX_unlock(&nfs4_owner->so_clientrec->cid_mutex); } /** * @brief Display the NFSv4 owner table */ void nfs4_owner_PrintAll(void) { hashtable_log(COMPONENT_STATE, ht_nfs4_owner); } /** * @brief Create an NFSv4 state owner * * @param[in] name Owner name * @param[in] clientid Client record * @param[in] type Owner type * @param[in] related_owner For lock owners, the related open owner * @param[in] init_seqid The starting seqid (for NFSv4.0) * @param[out] pisnew Whether the owner actually is new * @param[in] care Care flag (to unify v3/v4 owners?) * @param[in] confirm Create with it already confirmed? * * @return A new state owner or NULL. */ state_owner_t *create_nfs4_owner(state_nfs4_owner_name_t *name, nfs_client_id_t *clientid, state_owner_type_t type, state_owner_t *related_owner, unsigned int init_seqid, bool_t *pisnew, care_t care, bool_t confirm) { state_owner_t key; state_owner_t *owner; bool_t isnew; /* set up the content of the open_owner */ memset(&key, 0, sizeof(key)); key.so_type = type; key.so_owner.so_nfs4_owner.so_seqid = init_seqid; key.so_owner.so_nfs4_owner.so_related_owner = related_owner; key.so_owner.so_nfs4_owner.so_clientid = clientid->cid_clientid; key.so_owner.so_nfs4_owner.so_clientrec = clientid; key.so_owner_len = name->son_owner_len; key.so_owner_val = name->son_owner_val; key.so_owner.so_nfs4_owner.so_resp.resop = NFS4_OP_ILLEGAL; key.so_owner.so_nfs4_owner.so_args.argop = NFS4_OP_ILLEGAL; key.so_refcount = 1; key.so_owner.so_nfs4_owner.so_confirmed = confirm; if (isFullDebug(COMPONENT_STATE)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_owner(&dspbuf, &key); LogFullDebug(COMPONENT_STATE, "Key=%s", str); } owner = get_state_owner(care, &key, init_nfs4_owner, &isnew); if (owner != NULL && related_owner != NULL) { PTHREAD_MUTEX_lock(&owner->so_mutex); /* Related owner already exists. */ if (owner->so_owner.so_nfs4_owner.so_related_owner == NULL) { /* Attach related owner to owner now that we know it. */ inc_state_owner_ref(related_owner); owner->so_owner.so_nfs4_owner.so_related_owner = related_owner; } else if (owner->so_owner.so_nfs4_owner.so_related_owner != related_owner) { char str1[LOG_BUFF_LEN / 2] = "\0"; char str2[LOG_BUFF_LEN / 2] = "\0"; struct display_buffer dspbuf1 = { sizeof(str1), str1, str1 }; struct display_buffer dspbuf2 = { sizeof(str2), str2, str2 }; display_owner(&dspbuf1, related_owner); display_owner(&dspbuf2, owner); LogCrit(COMPONENT_NFS_V4_LOCK, "Related {%s} doesn't match for {%s}", str1, str2); PTHREAD_MUTEX_unlock(&owner->so_mutex); /* Release the reference to the owner. */ dec_state_owner_ref(owner); return NULL; } PTHREAD_MUTEX_unlock(&owner->so_mutex); } if (!isnew && owner != NULL && pisnew != NULL) { if (isDebug(COMPONENT_STATE)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_owner(&dspbuf, owner); LogDebug(COMPONENT_STATE, "Previously known owner {%s} is being reused", str); } } if (pisnew != NULL) *pisnew = isnew; return owner; } /** * @brief Fill out an NFSv4 lock conflict * * @param[out] denied NFSv4 LOCK4denied structure * @param[in] holder Holder of the conflicting lock * @param[in] conflict The conflicting lock * @param[in] data The nfsv4 compound data */ #define BASE_RESP_SIZE \ (sizeof(nfsstat4) + sizeof(offset4) + sizeof(length4) + \ sizeof(nfs_lock_type4) + sizeof(clientid4) + sizeof(uint32_t)) nfsstat4 Process_nfs4_conflict(LOCK4denied *denied, state_owner_t *holder, fsal_lock_param_t *conflict, compound_data_t *data) { nfsstat4 status; size_t owner_len; if (holder != NULL && holder->so_owner_len != 0) owner_len = holder->so_owner_len; else owner_len = unknown_owner.so_owner_len; /* First check if the response will fit, this is a response to a * LOCK or LOCKT operation. */ status = check_resp_room(data, BASE_RESP_SIZE + owner_len); if (status != NFS4_OK) return status; /* Now set the op_resp_size. */ data->op_resp_size = BASE_RESP_SIZE + owner_len; /* A conflicting lock from a different lock_owner, * returns NFS4ERR_DENIED */ denied->offset = conflict->lock_start; denied->length = conflict->lock_length; if (conflict->lock_type == FSAL_LOCK_R) denied->locktype = READ_LT; else denied->locktype = WRITE_LT; if (holder != NULL && holder->so_owner_len != 0) { denied->owner.owner.owner_val = gsh_malloc(holder->so_owner_len); denied->owner.owner.owner_len = holder->so_owner_len; memcpy(denied->owner.owner.owner_val, holder->so_owner_val, holder->so_owner_len); } else { denied->owner.owner.owner_len = unknown_owner.so_owner_len; denied->owner.owner.owner_val = unknown_owner.so_owner_val; } LogFullDebug(COMPONENT_STATE, "denied->owner.owner.owner_val = %p", denied->owner.owner.owner_val); if (holder != NULL && holder->so_type == STATE_LOCK_OWNER_NFSV4) denied->owner.clientid = holder->so_owner.so_nfs4_owner.so_clientid; else denied->owner.clientid = 0; /* Release any lock owner reference passed back from SAL */ if (holder != NULL) dec_state_owner_ref(holder); return NFS4ERR_DENIED; } /** * @brief Release data allocated for LOCK4denied * * @param[in] denied Structure to release */ void Release_nfs4_denied(LOCK4denied *denied) { if (denied->owner.owner.owner_val != unknown_owner.so_owner_val) { gsh_free(denied->owner.owner.owner_val); denied->owner.owner.owner_val = NULL; } } /** * @brief Deep copy a LOCK4denied * * @param[out] denied_dst Target * @param[in] denied_src Source */ void Copy_nfs4_denied(LOCK4denied *denied_dst, LOCK4denied *denied_src) { memcpy(denied_dst, denied_src, sizeof(*denied_dst)); if (denied_src->owner.owner.owner_val != unknown_owner.so_owner_val && denied_src->owner.owner.owner_val != NULL) { denied_dst->owner.owner.owner_val = gsh_malloc(denied_src->owner.owner.owner_len); LogFullDebug(COMPONENT_STATE, "denied_dst->owner.owner.owner_val = %p", denied_dst->owner.owner.owner_val); memcpy(denied_dst->owner.owner.owner_val, denied_src->owner.owner.owner_val, denied_src->owner.owner.owner_len); } if (denied_dst->owner.owner.owner_val == NULL) { denied_dst->owner.owner.owner_len = unknown_owner.so_owner_len; denied_dst->owner.owner.owner_val = unknown_owner.so_owner_val; } } /** * @brief Copy a operation into a state owner * * This is only used for NFSv4.0 and only for a specific subset of * operations for which it guarantees At-Most Once Semantics. * * @param[in,out] owner The owner to hold the operation * @param[in] seqid The seqid of this operation * @param[in] args Arguments of operation to copy * @param[in] data Compound data * @param[in] resp Response to copy * @param[in] tag Arbitrary string for logging/debugging */ void Copy_nfs4_state_req(state_owner_t *owner, seqid4 seqid, nfs_argop4 *args, struct fsal_obj_handle *obj, nfs_resop4 *resp, const char *tag) { /* Simplify use of this function when we may not be keeping any data * for the state owner */ if (owner == NULL) return; LogFullDebug(COMPONENT_STATE, "%s: saving response %p so_seqid %u new seqid %u", tag, owner, owner->so_owner.so_nfs4_owner.so_seqid, seqid); /* Free previous response */ nfs4_Compound_FreeOne(&owner->so_owner.so_nfs4_owner.so_resp); /* Copy new response */ nfs4_Compound_CopyResOne(&owner->so_owner.so_nfs4_owner.so_resp, resp); /** @todo Deep copy OPEN args? * if (owner->so_owner.so_nfs4_owner.so_args.argop == NFS4_OP_OPEN) */ /* Copy bnew args */ memcpy(&owner->so_owner.so_nfs4_owner.so_args, args, sizeof(owner->so_owner.so_nfs4_owner.so_args)); /* Copy new file, note we don't take any reference, so this entry * might not remain valid, but the pointer value suffices here. */ owner->so_owner.so_nfs4_owner.so_last_entry = obj; /** @todo Deep copy OPEN args? * if (args->argop == NFS4_OP_OPEN) */ /* Store new seqid */ owner->so_owner.so_nfs4_owner.so_seqid = seqid; } /** * @brief Check NFS4 request for valid seqid for replay, next request, or * BAD_SEQID. * * Returns true if the request is the next seqid. If the request is a * replay, copies the saved response and returns false. Otherwise, * sets status to NFS4ERR_BAD_SEQID and returns false. * * In either case, on a false return, the caller should send the * resulting response back to the client. * * @param[in] owner The owner to check * @param[in] seqid The seqid to check * @param[in] args Arguments of operation * @param[in] data Compound data * @param[out] resp Cached request, if replay * @param[in] tag Arbitrary string for logging/debugging * * @retval true if the caller should process the operation. * @retval false if the caller should immediately return the provides response. */ bool Check_nfs4_seqid(state_owner_t *owner, seqid4 seqid, nfs_argop4 *args, struct fsal_obj_handle *obj, nfs_resop4 *resp, const char *tag) { seqid4 next; char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; /* Check if any owner to verify seqid against */ if (owner == NULL) { LogFullDebug( COMPONENT_STATE, "%s: Unknown owner doesn't have saved seqid, req seqid %u", tag, seqid); return true; } if (isDebug(COMPONENT_STATE)) { display_owner(&dspbuf, owner); str_valid = true; } /* If this is a new state owner, client may start with any seqid */ if (owner->so_owner.so_nfs4_owner.so_last_entry == NULL) { if (str_valid) LogFullDebug( COMPONENT_STATE, "%s: New {%s} doesn't have saved seqid, req seqid %u", tag, str, seqid); return true; } /* Check for valid next seqid */ next = owner->so_owner.so_nfs4_owner.so_seqid + 1; if (str_valid) LogFullDebug(COMPONENT_STATE, "%s: Check {%s} next %u req seqid %u", tag, str, next, seqid); if (seqid == next) return true; /* All NFS4 responses have the status in the same place, so use any to * set NFS4ERR_BAD_SEQID */ resp->nfs_resop4_u.oplock.status = NFS4ERR_BAD_SEQID; /* Now check for valid replay */ if (owner->so_owner.so_nfs4_owner.so_seqid != seqid) { if (str_valid) LogDebug( COMPONENT_STATE, "%s: Invalid seqid %u in request (not replay), expected seqid for {%s}, returning NFS4ERR_BAD_SEQID", tag, seqid, str); return false; } if (args->argop != owner->so_owner.so_nfs4_owner.so_args.argop) { if (str_valid) LogDebug( COMPONENT_STATE, "%s: Invalid seqid %u in request (not replay - not same op), expected seqid for {%s}, returning NFS4ERR_BAD_SEQID", tag, seqid, str); return false; } if (owner->so_owner.so_nfs4_owner.so_last_entry != obj) { if (str_valid) LogDebug( COMPONENT_STATE, "%s: Invalid seqid %u in request (not replay - wrong file), expected seqid for {%s}, returning NFS4ERR_BAD_SEQID", tag, seqid, str); return false; } /** @todo FSF: add more checks here... */ if (str_valid) LogDebug(COMPONENT_STATE, "%s: Copying saved response for seqid %u into {%s}", tag, seqid, str); /* Copy the saved response and tell caller to use it */ nfs4_Compound_CopyResOne(resp, &owner->so_owner.so_nfs4_owner.so_resp); return false; } /** @} */ nfs-ganesha-6.5/src/SAL/nfs4_recovery.c000066400000000000000000001000421473756622300177340ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file nfs4_recovery.c * @brief NFSv4 recovery */ #include "config.h" #include "log.h" #include "nfs_core.h" #include "nfs4.h" #include "sal_functions.h" #include #include #include #include #include #include "bsd-base64.h" #include "client_mgr.h" #include "fsal.h" /* The grace_mutex protects current_grace, clid_list, and clid_count */ static pthread_mutex_t grace_mutex; static struct timespec current_grace; /* current grace period timeout */ static int clid_count; /* number of active clients */ static struct glist_head clid_list = GLIST_HEAD_INIT(clid_list); /* clients */ /* * Low two bits of grace_status word are flags. One for whether we're currently * in a grace period and one if a change was requested. */ #define GRACE_STATUS_ACTIVE_SHIFT 0 #define GRACE_STATUS_CHANGE_REQ_SHIFT 1 /* The remaining bits are for the refcount */ #define GRACE_STATUS_COUNTER_SHIFT 2 #define GRACE_STATUS_ACTIVE (1U << GRACE_STATUS_ACTIVE_SHIFT) #define GRACE_STATUS_CHANGE_REQ (1U << GRACE_STATUS_CHANGE_REQ_SHIFT) #define GRACE_STATUS_REF_INCREMENT (1U << GRACE_STATUS_COUNTER_SHIFT) #define GRACE_STATUS_COUNT_MASK ((~0U) << GRACE_STATUS_COUNTER_SHIFT) static uint32_t grace_status; static int default_recovery_init(void) { return 0; } static void default_end_grace(void) { } static void default_recovery_read_clids(nfs_grace_start_t *gsp, add_clid_entry_hook add_clid_entry, add_rfh_entry_hook add_rfs_entry) { } static void default_add_clid(nfs_client_id_t *clientid) { } static void default_rm_clid(nfs_client_id_t *clientid) { } static void default_add_revoke_fh(nfs_client_id_t *dlr_clid, nfs_fh4 *dlr_handle) { } static struct nfs4_recovery_backend default_recovery_backend = { .recovery_init = default_recovery_init, .end_grace = default_end_grace, .recovery_read_clids = default_recovery_read_clids, .add_clid = default_add_clid, .rm_clid = default_rm_clid, .add_revoke_fh = default_add_revoke_fh, }; static struct nfs4_recovery_backend *recovery_backend = &default_recovery_backend; int32_t reclaim_completes; /* atomic */ static void nfs4_recovery_load_clids(nfs_grace_start_t *gsp); static void nfs_release_nlm_state(char *release_ip); static void nfs_release_v4_clients(char *ip); clid_entry_t *nfs4_add_clid_entry(char *cl_name) { clid_entry_t *new_ent = gsh_malloc(sizeof(clid_entry_t)); glist_init(&new_ent->cl_rfh_list); (void)strlcpy(new_ent->cl_name, cl_name, sizeof(new_ent->cl_name)); glist_add(&clid_list, &new_ent->cl_list); ++clid_count; return new_ent; } rdel_fh_t *nfs4_add_rfh_entry(clid_entry_t *clid_ent, char *rfh_name) { rdel_fh_t *new_ent = gsh_malloc(sizeof(rdel_fh_t)); new_ent->rdfh_handle_str = gsh_strdup(rfh_name); glist_add(&clid_ent->cl_rfh_list, &new_ent->rdfh_list); return new_ent; } void nfs4_cleanup_clid_entries(void) { struct clid_entry *clid_entry; /* when not doing a takeover, start with an empty list */ while ((clid_entry = glist_first_entry(&clid_list, struct clid_entry, cl_list)) != NULL) { glist_del(&clid_entry->cl_list); gsh_free(clid_entry); --clid_count; } assert(clid_count == 0); atomic_store_int32_t(&reclaim_completes, 0); } /* * If Sticky_garce enabled, Check the current status of the grace * period against what the caller needs. * If it's different then return false without taking a reference. If a change * has been requested, then we also don't want to give out a reference. * Else return true if in grace period or return false if not. */ bool nfs_get_grace_status(bool want_grace) { uint32_t cur, pro, old; /* check if sticky grace is disabled, * if we are in grace period and return*/ if (!nfs_param.nfsv4_param.sticky_grace) { cur = atomic_fetch_uint32_t(&grace_status); return want_grace == (bool)(cur & GRACE_STATUS_ACTIVE); } old = atomic_fetch_uint32_t(&grace_status); do { cur = old; /* If it's not the state we want, then no reference */ if (want_grace != (bool)(cur & GRACE_STATUS_ACTIVE)) return false; /* If a change was requested, no reference */ if (cur & GRACE_STATUS_CHANGE_REQ) return false; /* Bump the counter */ pro = cur + GRACE_STATUS_REF_INCREMENT; old = __sync_val_compare_and_swap(&grace_status, cur, pro); } while (old != cur); return true; } /* * Put grace status function is a no-op without sticky_grace. * Else if sticky_grace enabled, If the refcount goes to zero, * and a change was requested, * then wake the reaper thread to do its thing. */ void nfs_put_grace_status(void) { uint32_t cur; /* check if sticky grace is disabled, to skip decrementing ref count*/ if (!nfs_param.nfsv4_param.sticky_grace) return; /* no sticky grace mode, no decrementing ref count*/ cur = __sync_sub_and_fetch(&grace_status, GRACE_STATUS_REF_INCREMENT); if (cur & GRACE_STATUS_CHANGE_REQ && !(cur >> GRACE_STATUS_COUNTER_SHIFT)) { nfs_notify_grace_norefs_waiters(); reaper_wake(); } } /** * Lift the grace period if it's still active. */ static void nfs_lift_grace_locked(void) { uint32_t __attribute__((unused)) cur; /* * Caller must hold grace_mutex. Only the thread that actually sets * the value to 0 gets to clean up the recovery db. */ if (nfs_in_grace()) { nfs_end_grace(); __sync_synchronize(); /* Now change the actual status */ cur = __sync_and_and_fetch(&grace_status, ~(GRACE_STATUS_ACTIVE | GRACE_STATUS_CHANGE_REQ)); assert(!nfs_param.nfsv4_param.sticky_grace || !(cur & GRACE_STATUS_COUNT_MASK)); LogEvent(COMPONENT_STATE, "NFS Server Now NOT IN GRACE"); } } /* * Report our new state to the cluster */ static void nfs4_set_enforcing(void) { if (recovery_backend->set_enforcing) recovery_backend->set_enforcing(); } /** * @brief Start grace period * * This routine can be called due to server start/restart or from * failover code. If this node is taking over for a node, that nodeid * will be passed to this routine inside of the grace start structure. * * @param[in] gsp Grace period start information * Returns 0 on success, -EAGAIN on failure to enforce grace. */ int nfs_start_grace(nfs_grace_start_t *gsp) { int ret = 0; bool was_grace; uint32_t cur, old, pro; PTHREAD_MUTEX_lock(&grace_mutex); if (nfs_param.nfsv4_param.graceless) { nfs_lift_grace_locked(); LogEvent(COMPONENT_STATE, "NFS Server skipping GRACE (Graceless is true)"); goto out; } /* grace should always be greater than or equal to lease time, * some clients are known to have problems with grace greater than 60 * seconds Lease_Lifetime should be set to a smaller value for those * setups. * * Checks against the grace period are lockless, so we want to ensure * that the callers see the * Full barrier to ensure enforcement begins ASAP. */ /* * Ensure there are no outstanding references to the current state of * grace. If there are, set flag indicating that a change has been * requested and that no more references will be handed out until it * takes effect. */ ret = clock_gettime(CLOCK_MONOTONIC, ¤t_grace); if (ret != 0) { LogCrit(COMPONENT_MAIN, "Failed to get timestamp"); assert(0); /* if this is broken, we are toast so die */ } cur = atomic_fetch_uint32_t(&grace_status); do { old = cur; was_grace = cur & GRACE_STATUS_ACTIVE; /* If we're already in a grace period then we're done */ if (was_grace) break; /* * Are there outstanding * refs(check only if sticky grace is enabled)? * If so, then set the change req * flag and nothing else. If not, then clear the change req * flag and flip the active bit. */ if ((old & GRACE_STATUS_COUNT_MASK) & nfs_param.nfsv4_param.sticky_grace) { pro = old | GRACE_STATUS_CHANGE_REQ; } else { pro = old | GRACE_STATUS_ACTIVE; pro &= ~GRACE_STATUS_CHANGE_REQ; } /* If there are no changes, then we don't need to update */ if (pro == old) break; cur = __sync_val_compare_and_swap(&grace_status, old, pro); } while (cur != old); /* * If sticky_garce is enabled and we were not in a grace period * before and there were still references outstanding, * then we can't do anything else. * Fail with -EAGAIN so that caller can retry if needed. */ if (!was_grace && (old & GRACE_STATUS_COUNT_MASK) && nfs_param.nfsv4_param.sticky_grace) { LogEvent(COMPONENT_STATE, "Unable to start grace, grace status 0x%x", grace_status); ret = -EAGAIN; goto out; } __sync_synchronize(); if ((int)nfs_param.nfsv4_param.grace_period < (int)nfs_param.nfsv4_param.lease_lifetime) { LogWarn(COMPONENT_STATE, "NFS Server GRACE duration should at least match LEASE period. Current configured values are GRACE(%d), LEASE(%d)", (int)nfs_param.nfsv4_param.grace_period, (int)nfs_param.nfsv4_param.lease_lifetime); } LogEvent(COMPONENT_STATE, "NFS Server Now IN GRACE, duration %d", (int)nfs_param.nfsv4_param.grace_period); /* Set enforcing flag here */ if (!was_grace) nfs4_set_enforcing(); /* * If we're just starting the grace period, then load the * clid database. Don't load it however if we're extending the * existing grace period. */ if (!gsp && !was_grace) { nfs4_cleanup_clid_entries(); nfs4_recovery_load_clids(NULL); } else if (gsp && gsp->event != EVENT_JUST_GRACE) { /* * if called from failover code and given a nodeid, then this * node is doing a take over. read in the client ids from the * failing node. */ LogEvent(COMPONENT_STATE, "NFS Server recovery event %d nodeid %d ip %s", gsp->event, gsp->nodeid, gsp->ipaddr); if (gsp->event == EVENT_CLEAR_BLOCKED) cancel_all_nlm_blocked(); else { nfs_release_nlm_state(gsp->ipaddr); if (gsp->event == EVENT_RELEASE_IP) { PTHREAD_MUTEX_unlock(&grace_mutex); nfs_release_v4_clients(gsp->ipaddr); return ret; } else { /* * If we're already in a grace period, * it can not break the existing count, * other case, which should not be affected * by the last count, should be cleanup. */ if (!was_grace) nfs4_cleanup_clid_entries(); nfs4_recovery_load_clids(gsp); } } } LogEvent(COMPONENT_STATE, "grace reload client info completed from backend"); out: PTHREAD_MUTEX_unlock(&grace_mutex); return ret; } /** * @brief Check if we are in the grace period * * @retval true if so. * @retval false if not. */ bool nfs_in_grace(void) { return atomic_fetch_uint32_t(&grace_status) & GRACE_STATUS_ACTIVE; } /** * @brief Enter the grace period if another node in the cluster needs it * * Singleton servers generally won't use this operation. Clustered servers * call this function to check whether another node might need a grace period. */ void nfs_maybe_start_grace(void) { if (!nfs_in_grace() && recovery_backend->maybe_start_grace) recovery_backend->maybe_start_grace(); } /** * @brief Are all hosts in cluster enforcing the grace period? * * Singleton servers always return true here since the only grace period that * matters is the local one. Clustered backends should check to make sure that * the whole cluster is in grace. */ bool nfs_grace_enforcing(void) { if (recovery_backend->grace_enforcing) return recovery_backend->grace_enforcing(); return true; } /** * @brief Is this host still a member of the cluster? * * Singleton servers are always considered to be cluster members. This call * is mainly for clustered servers, which may need to handle things differently * on a clean shutdown depending on whether they are still a member of the * cluster. */ bool nfs_grace_is_member(void) { if (recovery_backend->is_member) return recovery_backend->is_member(); return true; } /** * @brief Return nodeid for the server * * If the recovery backend specifies a nodeid, return it. If it does not * specify one, default to using the server's hostname. * * Returns 0 on success and fills out pnodeid. Caller must free the returned * value with gsh_free. Returns negative POSIX error code on error. */ int nfs_recovery_get_nodeid(char **pnodeid) { int rc; size_t maxlen; size_t copylen; char *nodeid = NULL; char *hostname = NULL; if (recovery_backend->get_nodeid) { rc = recovery_backend->get_nodeid(&nodeid); /* Return error if we got one */ if (rc) return rc; /* If we got a nodeid, then we're done */ if (nodeid) { *pnodeid = nodeid; return 0; } } /* * Either the backend doesn't support get_nodeid or it handed back a * NULL pointer. Just use hostname. */ hostname = gsh_malloc(MAXNAMLEN + 1); rc = gsh_gethostname(hostname, MAXNAMLEN + 1, nfs_param.core_param.enable_AUTHSTATS); if (rc != 0) { LogEvent(COMPONENT_CLIENTID, "gethostname failed: %d", errno); rc = -errno; gsh_free(hostname); return rc; } maxlen = sysconf(_SC_HOST_NAME_MAX); copylen = strlen(hostname); if (copylen > maxlen) copylen = maxlen; nodeid = gsh_malloc(copylen + 1); memcpy(nodeid, hostname, copylen); nodeid[copylen] = '\0'; *pnodeid = nodeid; gsh_free(hostname); return rc; } void nfs_try_lift_grace(void) { bool in_grace = true; int32_t rc_count = 0; uint32_t cur, old, pro; /* Already lifted? Just return */ if (!(atomic_fetch_uint32_t(&grace_status) & GRACE_STATUS_ACTIVE)) return; /* * If we know there are no NLM clients, then we can consider the grace * period done when all previous clients have sent a RECLAIM_COMPLETE. */ PTHREAD_MUTEX_lock(&grace_mutex); rc_count = atomic_fetch_int32_t(&reclaim_completes); LogEvent(COMPONENT_STATE, "check grace:reclaim complete(%d)" " clid count(%d)", rc_count, clid_count); #ifdef _USE_NLM if (!nfs_param.core_param.enable_NLM) #endif in_grace = (rc_count != clid_count); /* Otherwise, wait for the timeout */ if (in_grace) { struct timespec timeout, now; int ret = clock_gettime(CLOCK_MONOTONIC, &now); if (ret != 0) { LogCrit(COMPONENT_MAIN, "Failed to get timestamp"); assert(0); } timeout = current_grace; timeout.tv_sec += nfs_param.nfsv4_param.grace_period; in_grace = gsh_time_cmp(&timeout, &now) > 0; } /* * Ok, we're basically ready to lift. Ensure there are no outstanding * references to the current status of the grace period. If there are, * then set the flag saying that there is an upcoming change. */ /* * Can we lift the grace period now? If there are any outstanding refs, * then just set the grace_change_req flag to indicate that we don't * want to hand any more refs out. Otherwise, we try to lift. * * Clustered backends may need extra checks before they can do so. If * the backend does not implement a try_lift_grace operation, then we * assume there are no external conditions and that it's always ok. * * If sticky grace is disabled, no additional checks or status updates * are performed; the grace period is lifted relying * only on backend-specific checks (if any). */ if (!in_grace) { cur = atomic_fetch_uint32_t(&grace_status); /* no extra check if sticky grace is disabled.*/ if (nfs_param.nfsv4_param.sticky_grace) { do { old = cur; /* Are we already done? Exit if so */ if (!(cur & GRACE_STATUS_ACTIVE)) { PTHREAD_MUTEX_unlock(&grace_mutex); return; } /* Record that a change * has now been requested */ pro = old | GRACE_STATUS_CHANGE_REQ; if (pro == old) break; cur = __sync_val_compare_and_swap(&grace_status, old, pro); } while (cur != old); /* Otherwise, go ahead and lift if we can */ if (!(old & GRACE_STATUS_COUNT_MASK) && (!recovery_backend->try_lift_grace || recovery_backend->try_lift_grace())) nfs_lift_grace_locked(); } else if (!recovery_backend->try_lift_grace || recovery_backend->try_lift_grace()) nfs_lift_grace_locked(); } PTHREAD_MUTEX_unlock(&grace_mutex); } static pthread_cond_t enforcing_cond; static pthread_mutex_t enforcing_mutex; /* Poll every 5s, just in case we miss the wakeup for some reason */ void nfs_wait_for_grace_enforcement(void) { nfs_grace_start_t gsp = { .event = EVENT_JUST_GRACE }; pthread_mutex_lock(&enforcing_mutex); nfs_try_lift_grace(); while (nfs_in_grace() && !nfs_grace_enforcing()) { struct timespec timeo = { .tv_sec = time(NULL) + 5, .tv_nsec = 0 }; pthread_cond_timedwait(&enforcing_cond, &enforcing_mutex, &timeo); pthread_mutex_unlock(&enforcing_mutex); nfs_start_grace(&gsp); nfs_try_lift_grace(); pthread_mutex_lock(&enforcing_mutex); } pthread_mutex_unlock(&enforcing_mutex); } void nfs_notify_grace_waiters(void) { pthread_mutex_lock(&enforcing_mutex); pthread_cond_broadcast(&enforcing_cond); pthread_mutex_unlock(&enforcing_mutex); } static pthread_cond_t norefs_cond; static pthread_mutex_t norefs_mutex; void nfs_wait_for_grace_norefs(void) { pthread_mutex_lock(&norefs_mutex); struct timespec timeo = { .tv_sec = time(NULL) + 5, .tv_nsec = 0 }; pthread_cond_timedwait(&norefs_cond, &norefs_mutex, &timeo); pthread_mutex_unlock(&norefs_mutex); } void nfs_notify_grace_norefs_waiters(void) { pthread_mutex_lock(&norefs_mutex); pthread_cond_broadcast(&norefs_cond); pthread_mutex_unlock(&norefs_mutex); } /** * @brief Create an entry in the recovery directory * * This entry allows the client to reclaim state after a server * reboot/restart. * * @param[in] clientid Client record */ void nfs4_add_clid(nfs_client_id_t *clientid) { PTHREAD_MUTEX_lock(&clientid->cid_mutex); recovery_backend->add_clid(clientid); PTHREAD_MUTEX_unlock(&clientid->cid_mutex); } /** * @brief Remove a client entry from the recovery directory * * This function would be called when a client expires. * */ void nfs4_rm_clid(nfs_client_id_t *clientid) { PTHREAD_MUTEX_lock(&clientid->cid_mutex); recovery_backend->rm_clid(clientid); PTHREAD_MUTEX_unlock(&clientid->cid_mutex); } static bool check_clid(nfs_client_id_t *clientid, clid_entry_t *clid_ent) { bool ret = false; LogDebug(COMPONENT_CLIENTID, "compare %s to %s", clientid->cid_recov_tag, clid_ent->cl_name); if (clientid->cid_recov_tag && !strncmp(clientid->cid_recov_tag, clid_ent->cl_name, PATH_MAX)) ret = true; return ret; } /** * @brief Determine whether or not this client may reclaim state * * If the server is not in grace period, then no reclaim can happen. * * @param[in] clientid Client record */ void nfs4_chk_clid_impl(nfs_client_id_t *clientid, clid_entry_t **clid_ent_arg) { struct glist_head *node; clid_entry_t *clid_ent; *clid_ent_arg = NULL; LogDebug(COMPONENT_CLIENTID, "chk for %" PRIu64, clientid->cid_clientid); /* If there were no clients at time of restart, we're done */ if (clid_count == 0) return; /* * loop through the list and try to find this client. If we * find it, mark it to allow reclaims. */ PTHREAD_MUTEX_lock(&clientid->cid_mutex); glist_for_each(node, &clid_list) { clid_ent = glist_entry(node, clid_entry_t, cl_list); if (check_clid(clientid, clid_ent)) { if (isDebug(COMPONENT_CLIENTID)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_client_id_rec(&dspbuf, clientid); LogFullDebug(COMPONENT_CLIENTID, "Allowed to reclaim ClientId %s", str); } clientid->cid_allow_reclaim = true; *clid_ent_arg = clid_ent; break; } } PTHREAD_MUTEX_unlock(&clientid->cid_mutex); } void nfs4_chk_clid(nfs_client_id_t *clientid) { clid_entry_t *dummy_clid_ent; PTHREAD_MUTEX_lock(&grace_mutex); nfs4_chk_clid_impl(clientid, &dummy_clid_ent); PTHREAD_MUTEX_unlock(&grace_mutex); } /** * @brief Load clients for recovery * * @param[in] nodeid Node, on takeover * * Caller must hold grace_mutex. */ static void nfs4_recovery_load_clids(nfs_grace_start_t *gsp) { LogDebug(COMPONENT_STATE, "Load recovery cli %p", gsp); recovery_backend->recovery_read_clids(gsp, nfs4_add_clid_entry, nfs4_add_rfh_entry); } #ifdef USE_RADOS_RECOV static struct { void *dl; void (*kv_init)(struct nfs4_recovery_backend **); void (*ng_init)(struct nfs4_recovery_backend **); void (*cluster_init)(struct nfs4_recovery_backend **); int (*load_config_from_parse)(config_file_t, struct config_error_type *); } rados = { NULL, }; static int load_rados_recov(void) { rados.dl = dlopen("libganesha_rados_recov.so", #if defined(LINUX) && !defined(SANITIZE_ADDRESS) RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND); #elif defined(FREEBSD) || defined(SANITIZE_ADDRESS) RTLD_NOW | RTLD_LOCAL); #endif if (rados.dl) { rados.kv_init = dlsym(rados.dl, "rados_kv_backend_init"); rados.ng_init = dlsym(rados.dl, "rados_ng_backend_init"); rados.cluster_init = dlsym(rados.dl, "rados_cluster_backend_init"); rados.load_config_from_parse = dlsym(rados.dl, "rados_load_config_from_parse"); if (!rados.kv_init || !rados.ng_init || !rados.cluster_init || !rados.load_config_from_parse) { dlclose(rados.dl); rados.dl = NULL; return -1; } } else { return -1; } return 0; } #endif const char *recovery_backend_str(enum recovery_backend recovery_backend) { switch (recovery_backend) { case RECOVERY_BACKEND_FS: return "fs"; case RECOVERY_BACKEND_FS_NG: return "fs_ng"; case RECOVERY_BACKEND_RADOS_KV: return "rados_kv"; case RECOVERY_BACKEND_RADOS_NG: return "rados_ng"; case RECOVERY_BACKEND_RADOS_CLUSTER: return "rados_cluster"; } return "Unknown recovery backend"; } /** * @brief Create the recovery directory * * The recovery directory may not exist yet, so create it. This * should only need to be done once (if at all). Also, the location * of the directory could be configurable. */ int nfs4_recovery_init(void) { LogInfo(COMPONENT_CLIENTID, "Recovery Backend Init for %s", recovery_backend_str(nfs_param.nfsv4_param.recovery_backend)); switch (nfs_param.nfsv4_param.recovery_backend) { case RECOVERY_BACKEND_FS: fs_backend_init(&recovery_backend); break; case RECOVERY_BACKEND_FS_NG: fs_ng_backend_init(&recovery_backend); break; #ifdef USE_RADOS_RECOV case RECOVERY_BACKEND_RADOS_KV: rados.kv_init(&recovery_backend); break; case RECOVERY_BACKEND_RADOS_NG: rados.ng_init(&recovery_backend); break; case RECOVERY_BACKEND_RADOS_CLUSTER: rados.cluster_init(&recovery_backend); break; #else case RECOVERY_BACKEND_RADOS_KV: case RECOVERY_BACKEND_RADOS_NG: case RECOVERY_BACKEND_RADOS_CLUSTER: #endif default: LogCrit(COMPONENT_CLIENTID, "Unsupported Backend %s", recovery_backend_str( nfs_param.nfsv4_param.recovery_backend)); return -ENOENT; } return recovery_backend->recovery_init(); } /** * @brief Shut down the recovery backend * * Shut down the recovery backend, cleaning up any clients or tracking * structures in preparation for server shutdown. */ void nfs4_recovery_shutdown(void) { if (recovery_backend->recovery_shutdown) recovery_backend->recovery_shutdown(); #ifdef USE_RADOS_RECOV if (rados.dl) (void)dlclose(rados.dl); rados.dl = NULL; #endif } /** * @brief Clean up recovery directory */ void nfs_end_grace(void) { recovery_backend->end_grace(); } /** * @brief Record revoked filehandle under the client. * * @param[in] clientid Client record * @param[in] filehandle of the revoked file. */ void nfs4_record_revoke(nfs_client_id_t *delr_clid, nfs_fh4 *delr_handle) { /* A client's lease is reserved while recalling or revoking a * delegation which means the client will not expire until we * complete this revoke operation. The only exception is when * the reaper thread revokes delegations of an already expired * client! */ PTHREAD_MUTEX_lock(&delr_clid->cid_mutex); if (delr_clid->cid_confirmed == EXPIRED_CLIENT_ID) { /* Called from reaper thread, no need to record * revoked file handles for an expired client. */ PTHREAD_MUTEX_unlock(&delr_clid->cid_mutex); return; } recovery_backend->add_revoke_fh(delr_clid, delr_handle); PTHREAD_MUTEX_unlock(&delr_clid->cid_mutex); } /** * @brief Decides if it is allowed to reclaim a given delegation * * @param[in] clientid Client record * @param[in] filehandle of the revoked file. * @retval true if allowed and false if not. * */ bool nfs4_check_deleg_reclaim(nfs_client_id_t *clid, nfs_fh4 *fhandle) { char rhdlstr[NAME_MAX]; struct glist_head *node; rdel_fh_t *rfh_entry; clid_entry_t *clid_ent; int __attribute__((unused)) b64ret; bool retval = true; /* Convert nfs_fh4_val into base64 encoded string */ b64ret = base64url_encode(fhandle->nfs_fh4_val, fhandle->nfs_fh4_len, rhdlstr, sizeof(rhdlstr)); assert(b64ret != -1); PTHREAD_MUTEX_lock(&grace_mutex); nfs4_chk_clid_impl(clid, &clid_ent); if (clid_ent) { glist_for_each(node, &clid_ent->cl_rfh_list) { rfh_entry = glist_entry(node, rdel_fh_t, rdfh_list); assert(rfh_entry != NULL); if (!strcmp(rhdlstr, rfh_entry->rdfh_handle_str)) { LogFullDebug(COMPONENT_CLIENTID, "Can't reclaim revoked fh:%s", rfh_entry->rdfh_handle_str); retval = false; break; } } } PTHREAD_MUTEX_unlock(&grace_mutex); LogFullDebug(COMPONENT_CLIENTID, "Returning %s", retval ? "TRUE" : "FALSE"); return retval; } #ifdef _USE_NLM /** * @brief Release NLM state */ static void nlm_releasecall(struct fridgethr_context *ctx) { state_nsm_client_t *nsm_cp; state_status_t err; nsm_cp = ctx->arg; err = state_nlm_notify(nsm_cp, false, 0); if (err != STATE_SUCCESS) LogDebug(COMPONENT_STATE, "state_nlm_notify failed with %d", err); dec_nsm_client_ref(nsm_cp); } #endif /* _USE_NLM */ void extractv4(char *ipv6, char *ipv4, size_t size) { char *token, *saveptr; char *delim = ":"; token = strtok_r(ipv6, delim, &saveptr); while (token != NULL) { /* IPv4 delimiter is '.' */ if (strchr(token, '.') != NULL) { (void)strlcpy(ipv4, token, size); return; } token = strtok_r(NULL, delim, &saveptr); } /* failed, copy a null string */ ipv4[0] = '\0'; } bool ip_str_match(char *release_ip, char *server_ip) { bool ripv6, sipv6; char ipv4[SOCK_NAME_MAX]; /* IPv6 delimiter is ':' */ ripv6 = (strchr(release_ip, ':') != NULL); sipv6 = (strchr(server_ip, ':') != NULL); if (ripv6) { if (sipv6) return !strcmp(release_ip, server_ip); else { /* extract v4 addr from release_ip*/ extractv4(release_ip, ipv4, sizeof(ipv4)); return !strcmp(ipv4, server_ip); } } else { if (sipv6) { /* extract v4 addr from server_ip*/ extractv4(server_ip, ipv4, sizeof(ipv4)); return !strcmp(ipv4, release_ip); } } /* Both are ipv4 addresses */ return !strcmp(release_ip, server_ip); } /** * @brief Release all NLM state */ static void nfs_release_nlm_state(char *release_ip) { #ifdef _USE_NLM hash_table_t *ht = ht_nlm_client; state_nlm_client_t *nlm_cp; state_nsm_client_t *nsm_cp; struct rbt_head *head_rbt; struct rbt_node *pn; struct hash_data *pdata; state_status_t state_status; char serverip[SOCK_NAME_MAX]; int i; if (!nfs_param.core_param.enable_NLM) return; LogDebug(COMPONENT_STATE, "Release all NLM locks"); cancel_all_nlm_blocked(); /* walk the client list and call state_nlm_notify */ for (i = 0; i < ht->parameter.index_size; i++) { PTHREAD_RWLOCK_wrlock(&ht->partitions[i].ht_lock); head_rbt = &ht->partitions[i].rbt; /* go through all entries in the red-black-tree */ RBT_LOOP(head_rbt, pn) { pdata = RBT_OPAQ(pn); nlm_cp = (state_nlm_client_t *)pdata->val.addr; if (sprint_sockip(&nlm_cp->slc_server_addr, serverip, sizeof(serverip)) && ip_str_match(release_ip, serverip)) { nsm_cp = nlm_cp->slc_nsm_client; inc_nsm_client_ref(nsm_cp); state_status = fridgethr_submit( state_async_fridge, nlm_releasecall, nsm_cp); if (state_status != STATE_SUCCESS) { dec_nsm_client_ref(nsm_cp); LogCrit(COMPONENT_STATE, "failed to submit nlm release thread "); } } RBT_INCREMENT(pn); } PTHREAD_RWLOCK_unlock(&ht->partitions[i].ht_lock); } #endif /* _USE_NLM */ } static int ip_match(char *ip, nfs_client_id_t *cid) { char *haystack; char *value = cid->cid_client_record->cr_client_val; int len = cid->cid_client_record->cr_client_val_len; LogDebug(COMPONENT_STATE, "NFS Server V4 match ip %s with (%.*s)", ip, len, value); if (strlen(ip) == 0) /* No IP all are matching */ return 1; haystack = alloca(len + 1); memcpy(haystack, value, len); haystack[len] = '\0'; if (strstr(haystack, ip) != NULL) return 1; return 0; /* no match */ } /* * try to find a V4 clients which match the IP we are releasing. * only search the confirmed clients, unconfirmed clients won't * have any state to release. */ static void nfs_release_v4_clients(char *ip) { hash_table_t *ht = ht_confirmed_client_id; struct rbt_head *head_rbt; struct rbt_node *pn; struct hash_data *pdata; nfs_client_id_t *cp; nfs_client_record_t *recp; int i; LogEvent(COMPONENT_STATE, "NFS Server V4 recovery release ip %s", ip); /* go through the confirmed clients looking for a match */ for (i = 0; i < ht->parameter.index_size; i++) { head_rbt = &ht->partitions[i].rbt; restart: PTHREAD_RWLOCK_wrlock(&ht->partitions[i].ht_lock); /* go through all entries in the red-black-tree */ RBT_LOOP(head_rbt, pn) { pdata = RBT_OPAQ(pn); cp = (nfs_client_id_t *)pdata->val.addr; PTHREAD_MUTEX_lock(&cp->cid_mutex); if ((cp->cid_confirmed == CONFIRMED_CLIENT_ID) && ip_match(ip, cp)) { inc_client_id_ref(cp); /* client_record is always non-NULL. */ recp = cp->cid_client_record; PTHREAD_MUTEX_unlock(&cp->cid_mutex); PTHREAD_RWLOCK_unlock( &ht->partitions[i].ht_lock); /* nfs_client_id_expire requires cr_mutex */ PTHREAD_MUTEX_lock(&recp->cr_mutex); nfs_client_id_expire(cp, true, true); PTHREAD_MUTEX_unlock(&recp->cr_mutex); dec_client_id_ref(cp); goto restart; } else { PTHREAD_MUTEX_unlock(&cp->cid_mutex); } RBT_INCREMENT(pn); } PTHREAD_RWLOCK_unlock(&ht->partitions[i].ht_lock); } } /* Cleanup on shutdown */ void recovery_cleanup(void) { PTHREAD_MUTEX_destroy(&grace_mutex); PTHREAD_COND_destroy(&enforcing_cond); PTHREAD_MUTEX_destroy(&enforcing_mutex); PTHREAD_COND_destroy(&norefs_cond); PTHREAD_MUTEX_destroy(&norefs_mutex); } struct cleanup_list_element recovery_cleanup_element = { .clean = recovery_cleanup, }; int load_recovery_param_from_conf(config_file_t parse_tree, struct config_error_type *err_type) { PTHREAD_MUTEX_init(&grace_mutex, NULL); PTHREAD_COND_init(&enforcing_cond, NULL); PTHREAD_MUTEX_init(&enforcing_mutex, NULL); PTHREAD_COND_init(&norefs_cond, NULL); PTHREAD_MUTEX_init(&norefs_mutex, NULL); RegisterCleanup(&recovery_cleanup_element); switch (nfs_param.nfsv4_param.recovery_backend) { case RECOVERY_BACKEND_FS: case RECOVERY_BACKEND_FS_NG: return 0; case RECOVERY_BACKEND_RADOS_KV: case RECOVERY_BACKEND_RADOS_NG: case RECOVERY_BACKEND_RADOS_CLUSTER: #ifdef USE_RADOS_RECOV /* * see if we actually need the rados_recovery shlib loaded * * we are here because the config (explicitly) calls * for this recovery class. If we can't do it because * the (package with the) libganesha_rados_recovery * library wasn't installed, then we should return * an error and eventually die. */ if (!rados.dl && load_rados_recov() < 0) { LogCrit(COMPONENT_CLIENTID, "Failed to load Backend %s. Please install the appropriate package", recovery_backend_str( nfs_param.nfsv4_param.recovery_backend)); return -1; } return rados.load_config_from_parse(parse_tree, err_type); #endif default: LogCrit(COMPONENT_CLIENTID, "Unsupported Backend %s", recovery_backend_str( nfs_param.nfsv4_param.recovery_backend)); } return -1; } /** @} */ nfs-ganesha-6.5/src/SAL/nfs4_state.c000066400000000000000000001023371473756622300172270ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file nfs4_state.c * @brief NFSv4 state functions. */ #include "config.h" #include #include #include #include #include #include #include #include "log.h" #include "hashtable.h" #include "nfs_core.h" #include "nfs4.h" #include "fsal.h" #include "sal_functions.h" #include "export_mgr.h" #include "fsal_up.h" #include "nfs_file_handle.h" #include "nfs_proto_tools.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/state.h" #endif #ifdef DEBUG_SAL struct glist_head state_v4_all = GLIST_HEAD_INIT(state_v4_all); pthread_mutex_t all_state_v4_mutex; #endif /** * @brief adds a new state to a file * * This version of the function does not take the state lock on the * entry. It exists to allow callers to integrate state into a larger * operation. * * The caller may have already allocated a state, in which case state * need not be NULL. * * @note st_lock MUST be held * * @param[in,out] obj file to operate on * @param[in] state_type State to be defined * @param[in] state_data Data related to this state * @param[in] owner_input Related open_owner * @param[in,out] state The new state * @param[in] refer Reference to compound creating state * * @return Operation status */ state_status_t _state_add_impl(struct fsal_obj_handle *obj, enum state_type state_type, union state_data *state_data, state_owner_t *owner_input, state_t **state, struct state_refer *refer, const char *func, int line) { state_t *pnew_state = *state; struct state_hdl *ostate = obj->state_hdl; char str[DISPLAY_STATEID_OTHER_SIZE] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; bool got_export_ref = false; state_status_t status = 0; bool mutex_init = false; struct state_t *openstate = NULL; nfs_client_id_t *clientid = owner_input->so_owner.so_nfs4_owner.so_clientrec; if (isFullDebug(COMPONENT_STATE) && pnew_state != NULL) { display_stateid(&dspbuf, pnew_state); LogFullDebug(COMPONENT_STATE, "pnew_state=%s", str); display_reset_buffer(&dspbuf); } /* Attempt to get a reference to the export. */ if (!export_ready(op_ctx->ctx_export)) { /* If we could not get a reference, return stale. * Release attr_lock */ LogDebug(COMPONENT_STATE, "Stale export"); status = STATE_ESTALE; goto errout; } get_gsh_export_ref(op_ctx->ctx_export); got_export_ref = true; if (pnew_state == NULL) { if (state_type == STATE_TYPE_LOCK) openstate = nfs4_State_Get_Pointer( state_data->lock.openstate_key); pnew_state = op_ctx->fsal_export->exp_ops.alloc_state( op_ctx->fsal_export, state_type, openstate); if (state_type == STATE_TYPE_LOCK && openstate) dec_state_t_ref(openstate); } /* If Max_Open_States_Per_Client is enabled and too many files are open * from same client then stop going ahead and return EIO */ if (state_type == STATE_TYPE_SHARE && nfs_param.nfsv4_param.max_open_states_per_client && (atomic_fetch_uint32_t(&clientid->cid_open_state_counter) >= nfs_param.nfsv4_param.max_open_states_per_client)) { display_clientid(&dspbuf, clientid->cid_clientid); LogCrit(COMPONENT_STATE, "Too many files(cur:%u>=limit:%u) opened by {CLIENTID %s}", atomic_fetch_uint32_t( &clientid->cid_open_state_counter), nfs_param.nfsv4_param.max_open_states_per_client, str); display_reset_buffer(&dspbuf); status = STATE_IO_ERROR; goto errout; } PTHREAD_MUTEX_init(&pnew_state->state_mutex, NULL); mutex_init = true; /* Add the stateid.other, this will increment cid_stateid_counter */ nfs4_BuildStateId_Other(clientid, pnew_state->stateid_other); /* Set the type and data for this state */ memcpy(&(pnew_state->state_data), state_data, sizeof(*state_data)); pnew_state->state_type = state_type; pnew_state->state_seqid = 0; /* will be incremented to 1 later */ pnew_state->state_refcount = 2; /* sentinel plus returned ref */ if (refer) pnew_state->state_refer = *refer; if (isFullDebug(COMPONENT_STATE)) { display_stateid_other(&dspbuf, pnew_state->stateid_other); str_valid = true; LogFullDebug(COMPONENT_STATE, "About to call nfs4_State_Set for %s", str); } glist_init(&pnew_state->state_list); /* We need to initialize state_owner, state_export, and state_obj now so * that the state can be indexed by owner/entry. We don't insert into * lists and take references yet since no one else can see this state * until we are completely done since we hold the st_lock. Might as * well grab export now also... */ pnew_state->state_export = op_ctx->ctx_export; pnew_state->state_owner = owner_input; pnew_state->state_obj = obj; /* Add the state to the related hashtable */ status = nfs4_State_Set(pnew_state); switch (status) { case STATE_SUCCESS: break; default: if (!str_valid) display_stateid_other(&dspbuf, pnew_state->stateid_other); LogCrit(COMPONENT_STATE, "Can't create a new state id %s for the obj %p (F)", str, obj); goto errout; } /* Each of the following blocks takes the state_mutex and releases it * because we always want state_mutex to be the last lock taken. * * NOTE: We don't have to worry about state_del/state_del_locked being * called in the midst of things because the st_lock is held. */ /* Attach this to an export */ PTHREAD_RWLOCK_wrlock(&op_ctx->ctx_export->exp_lock); PTHREAD_MUTEX_lock(&pnew_state->state_mutex); glist_add_tail(&op_ctx->ctx_export->exp_state_list, &pnew_state->state_export_list); PTHREAD_MUTEX_unlock(&pnew_state->state_mutex); PTHREAD_RWLOCK_unlock(&op_ctx->ctx_export->exp_lock); /* Add state to list for file */ PTHREAD_MUTEX_lock(&pnew_state->state_mutex); glist_add_tail(&ostate->file.list_of_states, &pnew_state->state_list); /* Get active ref for this state entry */ obj->obj_ops->get_ref(obj); PTHREAD_MUTEX_unlock(&pnew_state->state_mutex); GSH_AUTO_TRACEPOINT(state, add, TRACE_INFO, "State add. Obj: {}, new state: {}", obj, pnew_state); /* Add state to list for owner */ PTHREAD_MUTEX_lock(&owner_input->so_mutex); PTHREAD_MUTEX_lock(&pnew_state->state_mutex); inc_state_owner_ref(owner_input); if (pnew_state->state_type == STATE_TYPE_SHARE) atomic_inc_uint32_t(&clientid->cid_open_state_counter); glist_add_tail(&owner_input->so_owner.so_nfs4_owner.so_state_list, &pnew_state->state_owner_list); PTHREAD_MUTEX_unlock(&pnew_state->state_mutex); PTHREAD_MUTEX_unlock(&owner_input->so_mutex); #ifdef DEBUG_SAL PTHREAD_MUTEX_lock(&all_state_v4_mutex); glist_add_tail(&state_v4_all, &pnew_state->state_list_all); PTHREAD_MUTEX_unlock(&all_state_v4_mutex); #endif if (pnew_state->state_type == STATE_TYPE_DELEG && pnew_state->state_data.deleg.sd_type == OPEN_DELEGATE_WRITE) { ostate->file.write_delegated = true; /* take a ref and save client ptr holding delegation for * conflict resolution */ inc_client_id_ref(clientid); ostate->file.write_deleg_client = clientid; } /* Copy the result */ *state = pnew_state; if (str_valid) LogFullDebug(COMPONENT_STATE, "Add State: %p: %s", pnew_state, str); if (clientid->gsh_client) inc_gsh_client_state_stats(clientid->gsh_client, state_type); /* Regular exit */ status = STATE_SUCCESS; return status; errout: if (mutex_init) PTHREAD_MUTEX_destroy(&pnew_state->state_mutex); if (pnew_state != NULL) { /* Make sure the new state is closed (may have been passed in * with file open). */ (void)obj->obj_ops->close2(obj, pnew_state); free_state(pnew_state); } if (got_export_ref) put_gsh_export(op_ctx->ctx_export); *state = NULL; return status; } /* state_add */ /** * @brief Adds a new state to a file * * @param[in,out] obj File to operate on * @param[in] state_type State to be defined * @param[in] state_data Data related to this state * @param[in] owner_input Related open_owner * @param[out] state The new state * @param[in] refer Reference to compound creating state * * @return Operation status */ state_status_t _state_add(struct fsal_obj_handle *obj, enum state_type state_type, union state_data *state_data, state_owner_t *owner_input, state_t **state, struct state_refer *refer, const char *func, int line) { state_status_t status = 0; /* Ensure that states are associated only with the appropriate * owners */ if (((state_type == STATE_TYPE_SHARE) && (owner_input->so_type != STATE_OPEN_OWNER_NFSV4)) || ((state_type == STATE_TYPE_LOCK) && (owner_input->so_type != STATE_LOCK_OWNER_NFSV4)) || (((state_type == STATE_TYPE_DELEG) || (state_type == STATE_TYPE_LAYOUT)) && (owner_input->so_type != STATE_CLIENTID_OWNER_NFSV4))) { return STATE_BAD_TYPE; } STATELOCK_lock(obj); status = _state_add_impl(obj, state_type, state_data, owner_input, state, refer, func, line); STATELOCK_unlock(obj); return status; } /** * @brief Remove a state from a file * * @note The st_lock MUST be held. * * @param[in] state The state to remove * */ void _state_del_locked(state_t *state, const char *func, int line) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; struct fsal_obj_handle *obj; struct gsh_export *export; state_owner_t *owner; nfs_client_id_t *clientid = NULL; if (isDebug(COMPONENT_STATE)) { display_stateid(&dspbuf, state); str_valid = true; } /* Remove the entry from the HashTable. If it fails, we have lost the * race with another caller of state_del/state_del_locked. */ if (!nfs4_State_Del(state)) { if (str_valid) LogDebug(COMPONENT_STATE, "Racing to delete %s", str); return; } if (str_valid) LogFullDebug(COMPONENT_STATE, "Deleting %s", str); /* Protect extraction of all the referenced objects, we don't * actually need to test them or take references because we assure * that there is exactly one state_del_locked call that proceeds * this far, and thus if the references were non-NULL, they must still * be good. Holding the mutex is not strictly necessary for this * reason, however, static and dynamic code analysis have no way of * knowing this reference is safe. In addition, get_state_obj_ref() * would have taken the mutex anyway. */ PTHREAD_MUTEX_lock(&state->state_mutex); obj = get_state_obj_ref_locked(state); if (obj == NULL) { /* There actually is no longer a path for obj to be NULL except * via the call to this function that wins any race and is * proceeding. */ LogFatal(COMPONENT_STATE, "Entry for state is stale"); PTHREAD_MUTEX_unlock(&state->state_mutex); return; } GSH_AUTO_TRACEPOINT(state, delete, TRACE_INFO, "State delete. Obj: {}, state: {}", obj, state); export = state->state_export; owner = state->state_owner; PTHREAD_MUTEX_unlock(&state->state_mutex); if (owner != NULL) { bool owner_retain = false; struct state_nfs4_owner_t *nfs4_owner; nfs4_owner = &owner->so_owner.so_nfs4_owner; clientid = nfs4_owner->so_clientrec; /* Remove from list of states owned by owner and * release the state owner reference. */ PTHREAD_MUTEX_lock(&owner->so_mutex); PTHREAD_MUTEX_lock(&state->state_mutex); glist_del(&state->state_owner_list); state->state_owner = NULL; /* If we are dropping the last open state from an open * owner, we will want to retain a refcount and let the * reaper thread clean up with owner. * * @todo: should we make the following glist_null check * an assert or remove it altogether? */ owner_retain = owner->so_type == STATE_OPEN_OWNER_NFSV4 && glist_empty(&nfs4_owner->so_state_list) && glist_null(&nfs4_owner->so_cache_entry); PTHREAD_MUTEX_unlock(&state->state_mutex); if (owner_retain) { /* Retain the reference held by the state, and track * when this owner was last closed. */ PTHREAD_MUTEX_lock(&cached_open_owners_lock); atomic_store_time_t( &nfs4_owner->so_cache_expire, nfs_param.nfsv4_param.lease_lifetime + time(NULL)); glist_add_tail(&cached_open_owners, &nfs4_owner->so_cache_entry); if (isFullDebug(COMPONENT_STATE)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_owner(&dspbuf, owner); LogFullDebug(COMPONENT_STATE, "Caching open owner {%s}", str); } PTHREAD_MUTEX_unlock(&cached_open_owners_lock); PTHREAD_MUTEX_unlock(&owner->so_mutex); } else { /* Drop the reference held by the state. */ PTHREAD_MUTEX_unlock(&owner->so_mutex); dec_state_owner_ref(owner); } } /* Remove from the list of lock states for a particular open state. * This is safe to do without any special checks. If we are not on * the list, glist_del does nothing, and the st_lock protects the * open state's state_sharelist. */ if (state->state_type == STATE_TYPE_LOCK) glist_del(&state->state_data.lock.state_sharelist); if (state->state_type == STATE_TYPE_SHARE) { assert(glist_empty(&state->state_data.share.share_lockstates)); /* Write open fd count should be decremented, * if the client doesn't close the file. * Else this could result in a file never being delegated */ if ((state->state_data.share.share_access & OPEN4_SHARE_ACCESS_WRITE) && obj->type == REGULAR_FILE) { obj->state_hdl->file.fdeleg_stats.fds_num_write_opens--; } } /* Reset write delegated and release client ref if this is a * write delegation */ if (state->state_type == STATE_TYPE_DELEG && state->state_data.deleg.sd_type == OPEN_DELEGATE_WRITE && obj->state_hdl->file.write_deleg_client) { obj->state_hdl->file.write_delegated = false; dec_client_id_ref(obj->state_hdl->file.write_deleg_client); obj->state_hdl->file.write_deleg_client = NULL; } /* Clean up delegation related flags if file have no active states */ if (state->state_type == STATE_TYPE_DELEG && state->state_data.deleg.sd_type == OPEN_DELEGATE_READ && glist_empty(&obj->state_hdl->file.list_of_states)) { LogEvent( COMPONENT_STATE, "Resetting Deleg Stats(%d/%d) as file have no active states", obj->state_hdl->file.fdeleg_stats.fds_curr_delegations, obj->state_hdl->file.fdeleg_stats.fds_deleg_type); obj->state_hdl->file.fdeleg_stats.fds_curr_delegations = 0; obj->state_hdl->file.fdeleg_stats.fds_deleg_type = OPEN_DELEGATE_NONE; } /* Remove from list of states for a particular export. * In this case, it is safe to look at state_export without yet * holding the state_mutex because this is the only place where it * is removed, and we have guaranteed we are the only thread * proceeding with state deletion. */ PTHREAD_RWLOCK_wrlock(&export->exp_lock); PTHREAD_MUTEX_lock(&state->state_mutex); glist_del(&state->state_export_list); state->state_export = NULL; PTHREAD_MUTEX_unlock(&state->state_mutex); PTHREAD_RWLOCK_unlock(&export->exp_lock); put_gsh_export(export); #ifdef DEBUG_SAL PTHREAD_MUTEX_lock(&all_state_v4_mutex); glist_del(&state->state_list_all); PTHREAD_MUTEX_unlock(&all_state_v4_mutex); #endif /* Remove from the list of states for a particular file */ PTHREAD_MUTEX_lock(&state->state_mutex); glist_del(&state->state_list); /* Put active ref for this state entry */ obj->obj_ops->put_ref(obj); state->state_obj = NULL; PTHREAD_MUTEX_unlock(&state->state_mutex); /* We need to close the state at this point. The state will * eventually be freed and it must be closed before free. This * is the last point we have a valid reference to the object * handle. */ (void)obj->obj_ops->close2(obj, state); if (clientid) { if (state->state_type == STATE_TYPE_SHARE) atomic_dec_uint32_t(&clientid->cid_open_state_counter); if (clientid->gsh_client) dec_gsh_client_state_stats(clientid->gsh_client, state->state_type); } /* Remove the sentinel reference */ dec_state_t_ref(state); obj->obj_ops->put_ref(obj); } /** * @brief Delete a state * * @param[in] state State to delete * */ void state_del(state_t *state) { struct fsal_obj_handle *obj = get_state_obj_ref(state); if (obj == NULL) { LogDebug(COMPONENT_STATE, "Entry for state is stale"); return; } STATELOCK_lock(obj); state_del_locked(state); STATELOCK_unlock(obj); obj->obj_ops->put_ref(obj); } /** * @brief Get references to the various objects a state_t points to. * * @param[in] state The state_t to get references from * @param[in,out] obj Place to return the owning object (NULL if not desired) * @param[in,out] export Place to return the export (NULL if not desired) * @param[in,out] owner Place to return the owner (NULL if not desired) * * @retval true if all desired references were taken * @retval false otherwise (in which case no references are taken) * * For convenience, returns false if state is NULL which helps simplify * code for some callers. */ bool get_state_obj_export_owner_refs(state_t *state, struct fsal_obj_handle **obj, struct gsh_export **export, state_owner_t **owner) { if (obj != NULL) *obj = NULL; if (export != NULL) *export = NULL; if (owner != NULL) *owner = NULL; if (state == NULL) return false; PTHREAD_MUTEX_lock(&state->state_mutex); LogFullDebug(COMPONENT_STATE, "state %p state_obj %p state_export %p state_owner %p", state, state->state_obj, state->state_export, state->state_owner); if (obj != NULL) { *obj = get_state_obj_ref_locked(state); if ((*obj) == NULL) goto fail; } if (export != NULL) { if (state->state_export != NULL && export_ready(state->state_export)) { get_gsh_export_ref(state->state_export); *export = state->state_export; } else goto fail; } if (owner != NULL) { if (state->state_owner != NULL) { *owner = state->state_owner; inc_state_owner_ref(*owner); } else { goto fail; } } PTHREAD_MUTEX_unlock(&state->state_mutex); return true; fail: PTHREAD_MUTEX_unlock(&state->state_mutex); if (export != NULL && *export != NULL) { put_gsh_export(*export); *export = NULL; } if (obj != NULL && *obj != NULL) { (*obj)->obj_ops->put_ref(*obj); *obj = NULL; } return false; } /** * @brief Remove all state from a file * * Used by mdcache_kill_entry in the event that the FSAL says a * handle is stale. * * @note st_lock MUST be held * * @param[in,out] ostate File state to wipe */ void state_nfs4_state_wipe(struct state_hdl *ostate) { struct glist_head *glist, *glistn; state_t *state = NULL; if (glist_empty(&ostate->file.list_of_states)) return; glist_for_each_safe(glist, glistn, &ostate->file.list_of_states) { state = glist_entry(glist, state_t, state_list); if (state->state_type > STATE_TYPE_LAYOUT) continue; /* Skip STATE_TYPE_SHARE * It must be deleted after all the related LOCK states */ if (state->state_type == STATE_TYPE_SHARE) continue; state_del_locked(state); } /* Loop over again to delete any STATE_TYPE_SHARE */ glist_for_each_safe(glist, glistn, &ostate->file.list_of_states) { state = glist_entry(glist, state_t, state_list); if (state->state_type > STATE_TYPE_LAYOUT) continue; state_del_locked(state); } } /** * @brief Remove every state belonging to the lock owner. * * @param[in] lock_owner Lock owner to release */ enum nfsstat4 release_lock_owner(state_owner_t *owner) { struct saved_export_context saved; bool ok; PTHREAD_MUTEX_lock(&owner->so_mutex); if (!glist_empty(&owner->so_lock_list)) { PTHREAD_MUTEX_unlock(&owner->so_mutex); return NFS4ERR_LOCKS_HELD; } if (isDebug(COMPONENT_STATE)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_owner(&dspbuf, owner); LogDebug(COMPONENT_STATE, "Removing state for %s", str); } /* Save the current export and clear it from op context (so if there is * no call to set_op_context_export_fsal before a call to * restore_op_context_export we don't call clear_op_context_export() on * the saved export... */ save_op_context_export_and_clear(&saved); while (true) { state_t *state; struct fsal_obj_handle *obj = NULL; struct gsh_export *exp = NULL; state = glist_first_entry( &owner->so_owner.so_nfs4_owner.so_state_list, state_t, state_owner_list); if (state == NULL) { PTHREAD_MUTEX_unlock(&owner->so_mutex); /* Restore export */ restore_op_context_export(&saved); return NFS4_OK; } /* Get references to the file and export */ ok = get_state_obj_export_owner_refs(state, &obj, &exp, NULL); if (!ok) { /* The file, export, or state must be about to * die, skip for now. */ continue; } /* Make sure the state doesn't go away on us... */ inc_state_t_ref(state); PTHREAD_MUTEX_unlock(&owner->so_mutex); STATELOCK_lock(obj); /* op_ctx may be used by state_del_locked and others set export * from the state and release any old ctx_export reference. * Reference was taken above and will be release by * clear_op_context_export below. */ set_op_context_export(exp); /* If FSAL supports extended operations, file will be closed by * state_del_locked. */ state_del_locked(state); dec_state_t_ref(state); STATELOCK_unlock(obj); PTHREAD_MUTEX_lock(&owner->so_mutex); /* Release refs we held during state_del */ obj->obj_ops->put_ref(obj); clear_op_context_export(); } } /** * @brief Remove all state belonging to the open owner. * * @param[in,out] open_owner Open owner */ void release_openstate(state_owner_t *owner) { int errcnt = 0; bool ok; struct state_nfs4_owner_t *nfs4_owner = &owner->so_owner.so_nfs4_owner; if (isFullDebug(COMPONENT_STATE)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_owner(&dspbuf, owner); LogFullDebug(COMPONENT_STATE, "Release {%s}", str); } /* Only accept so many errors before giving up. */ while (errcnt < STATE_ERR_MAX) { state_t *state; struct fsal_obj_handle *obj = NULL; struct gsh_export *export = NULL; PTHREAD_MUTEX_lock(&owner->so_mutex); if (atomic_fetch_time_t(&nfs4_owner->so_cache_expire) != 0) { /* This owner has no state, it is a cached open owner. * Take cached_open_owners_lock and verify. * * We have to check every iteration since the state * list may have become empty and we are now cached. */ PTHREAD_MUTEX_lock(&cached_open_owners_lock); if (atomic_fetch_time_t(&nfs4_owner->so_cache_expire) != 0) { /* We aren't racing with the reaper thread or * with get_state_owner. * * NOTE: We could be called from the reaper * thead or this could be a clientid * expire due to SETCLIENTID. */ /* This cached owner has expired, uncache it. * uncache_nfs4_owner may destroy the * owner, so unlock so_mutex prior to * the call. so_state_list should be * empty as well, so return early. */ PTHREAD_MUTEX_unlock(&owner->so_mutex); uncache_nfs4_owner(nfs4_owner); PTHREAD_MUTEX_unlock(&cached_open_owners_lock); return; } PTHREAD_MUTEX_unlock(&cached_open_owners_lock); /* We should be done, but will fall through anyway * to remove any remote possibility of a race with * get_state_owner. * * At this point, so_state_list is now properly a list. */ } state = glist_first_entry(&nfs4_owner->so_state_list, state_t, state_owner_list); if (state == NULL) { PTHREAD_MUTEX_unlock(&owner->so_mutex); return; } /* Move to end of list in case of error to ease retries */ glist_move_tail(&nfs4_owner->so_state_list, &state->state_owner_list); /* Get references to the file and export */ ok = get_state_obj_export_owner_refs(state, &obj, &export, NULL); if (!ok) { /* The file, export, or state must be about to * die, skip for now. */ PTHREAD_MUTEX_unlock(&owner->so_mutex); errcnt++; continue; } /* Make sure the state doesn't go away on us... */ inc_state_t_ref(state); PTHREAD_MUTEX_unlock(&owner->so_mutex); STATELOCK_lock(obj); /* op_ctx may be used by state_del_locked and others set export * from the state and release any old ctx_export reference. * Reference was taken above and will be release by * clear_op_context_export below. */ set_op_context_export(export); /* If FSAL supports extended operations, file will be closed by * state_del_locked. */ state_del_locked(state); dec_state_t_ref(state); STATELOCK_unlock(obj); /* Release refs we held during state_del */ obj->obj_ops->put_ref(obj); clear_op_context_export(); } if (errcnt == STATE_ERR_MAX) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_owner(&dspbuf, owner); LogFatal( COMPONENT_STATE, "Could not complete cleanup of lock state for lock owner %s", str); } } /** * @brief Revoke delegations belonging to the client owner. * * @param[in,out] client owner */ void revoke_owner_delegs(state_owner_t *client_owner) { struct glist_head *glist, *glistn; state_t *state, *first; struct fsal_obj_handle *obj; bool so_mutex_held; struct gsh_export *export = NULL; struct req_op_context op_context; bool ok; again: first = NULL; PTHREAD_MUTEX_lock(&client_owner->so_mutex); so_mutex_held = true; glist_for_each_safe(glist, glistn, &client_owner->so_owner.so_nfs4_owner.so_state_list) { state = glist_entry(glist, state_t, state_owner_list); /* We set first to the first state we look in this iteration. * If the current state matches the first state, it implies * that went through the entire list without dropping the lock * guarding the list. So nothing more left to process. */ if (first == NULL) first = state; else if (first == state) break; /* Move entry to end of list to handle errors and skipping of * non-delegation states. */ glist_move_tail( &client_owner->so_owner.so_nfs4_owner.so_state_list, &state->state_owner_list); /* Skip non-delegation states. */ if (state->state_type != STATE_TYPE_DELEG) continue; /* Safely access the cache object associated with the state. * This will get an LRU reference protecting our access * even after state_deleg_revoke releases the reference it * holds. */ ok = get_state_obj_export_owner_refs(state, &obj, &export, NULL); if (!ok || obj == NULL) { LogDebug(COMPONENT_STATE, "Stale state or file"); continue; } PTHREAD_MUTEX_unlock(&client_owner->so_mutex); so_mutex_held = false; /* If FSAL supports extended operations, file will be closed by * state_del_locked which is called from deleg_revoke. */ STATELOCK_lock(obj); /* Initialize the op_context (refcount taken above) */ init_op_context_simple(&op_context, export, export->fsal_export); state_deleg_revoke(obj, state); STATELOCK_unlock(obj); /* Release refs we held */ obj->obj_ops->put_ref(obj); release_op_context(); /* Since we dropped so_mutex, we must restart the loop. */ goto again; } if (so_mutex_held) PTHREAD_MUTEX_unlock(&client_owner->so_mutex); } static void release_export_nfs4_state(enum state_type type) { state_t *state; state_t *first; int errcnt = 0; struct glist_head *glist, *glistn; bool hold_export_lock; /* Because we have to drop the export lock, when we cycle around again * we MUST restart. */ again: first = NULL; PTHREAD_RWLOCK_wrlock(&op_ctx->ctx_export->exp_lock); hold_export_lock = true; glist_for_each_safe(glist, glistn, &op_ctx->ctx_export->exp_state_list) { struct fsal_obj_handle *obj = NULL; state_owner_t *owner = NULL; bool deleted = false; state = glist_entry(glist, state_t, state_export_list); /* We set first to the first state we look in this iteration. * If the current state matches the first state, it implies * that went through the entire list without dropping the lock * guarding the list. So nothing more left to process. */ if (first == NULL) first = state; else if (first == state) break; /* Move state to the end of the list in case an error * occurs or the state is going stale. This also keeps us * from continually re-examining non-layout states when * we restart the loop. */ glist_move_tail(&op_ctx->ctx_export->exp_state_list, &state->state_export_list); /* Release states of specific type. Skip all other states * STATE_TYPE_NONE is used to release ALL states */ if (state->state_type != type && type != STATE_TYPE_NONE) continue; if (!get_state_obj_export_owner_refs(state, &obj, NULL, &owner)) { /* This state_t is in the process of being destroyed, * skip it. */ continue; } inc_state_t_ref(state); PTHREAD_RWLOCK_unlock(&op_ctx->ctx_export->exp_lock); hold_export_lock = false; if (type == STATE_TYPE_LAYOUT) { struct pnfs_segment entire = { .io_mode = LAYOUTIOMODE4_ANY, .offset = 0, .length = NFS4_UINT64_MAX }; STATELOCK_lock(obj); /* this deletes the state too */ (void)nfs4_return_one_state(obj, LAYOUTRETURN4_FILE, circumstance_revoke, state, entire, 0, NULL, &deleted); if (!deleted) { LogCrit(COMPONENT_PNFS, "Layout state not destroyed during export cleanup."); errcnt++; } STATELOCK_unlock(obj); } else state_del(state); /* Release the references taken above */ obj->obj_ops->put_ref(obj); dec_state_owner_ref(owner); dec_state_t_ref(state); if (errcnt < STATE_ERR_MAX) { /* Loop again, but since we dropped the export lock, we * must restart. */ goto again; } /* Too many errors, quit. */ break; } if (hold_export_lock) PTHREAD_RWLOCK_unlock(&op_ctx->ctx_export->exp_lock); if (errcnt == STATE_ERR_MAX) { LogFatal(COMPONENT_STATE, "Could not complete cleanup of layouts for export %s", CTX_PSEUDOPATH(op_ctx)); } } /** * @brief Remove all state belonging to an export. * */ void state_export_release_nfs4_state(void) { /* Revoke layouts first (so that open states are still present). */ release_export_nfs4_state(STATE_TYPE_LAYOUT); /* Release LOCK states before the SHARE states. */ release_export_nfs4_state(STATE_TYPE_LOCK); /* Okay to delete all states */ release_export_nfs4_state(STATE_TYPE_NONE); } #ifdef DEBUG_SAL void dump_all_states(void) { state_t *state; state_owner_t *owner; if (!isFullDebug(COMPONENT_STATE)) return; PTHREAD_MUTEX_lock(&all_state_v4_mutex); if (!glist_empty(&state_v4_all)) { struct glist_head *glist; LogFullDebug(COMPONENT_STATE, " =State List= "); glist_for_each(glist, &state_v4_all) { char str1[LOG_BUFF_LEN / 2] = "\0"; char str2[LOG_BUFF_LEN / 2] = "\0"; struct display_buffer dspbuf1 = { sizeof(str1), str1, str1 }; struct display_buffer dspbuf2 = { sizeof(str2), str2, str2 }; state = glist_entry(glist, state_t, state_list_all); owner = get_state_owner_ref(state); display_owner(&dspbuf1, owner); display_stateid(&dspbuf2, state); LogFullDebug(COMPONENT_STATE, "State {%s} owner {%s}", str2, str1); if (owner != NULL) dec_state_owner_ref(owner); } LogFullDebug(COMPONENT_STATE, " ----------------------"); } else LogFullDebug(COMPONENT_STATE, "All states released"); PTHREAD_MUTEX_unlock(&all_state_v4_mutex); } #endif /** * @brief Check and Expire clients upon conflicts * * @param[in] file_state_hdl File state to wipe off the expired clients * * @retval true if all conflicting clients were found and cleaned * */ bool check_and_remove_conflicting_client(struct state_hdl *file_state_hdl) { struct glist_head *glist, *glistn; bool isFoundAndCleaned = false; if (!atomic_fetch_uint32_t(&num_of_curr_expired_clients) || glist_empty(&file_state_hdl->file.list_of_states)) return isFoundAndCleaned; recheck_for_conflicting_entries: glist_for_each_safe(glist, glistn, &file_state_hdl->file.list_of_states) { state_t *state = glist_entry(glist, state_t, state_list); state_owner_t *owner = state->state_owner; if (owner == NULL) { /* Skip states that have gone stale. */ continue; } nfs_client_id_t *client_id = owner->so_owner.so_nfs4_owner.so_clientrec; if (client_id->marked_for_delayed_cleanup) { reap_expired_client_list(client_id); isFoundAndCleaned = true; /* Continue to recheck for conflicts */ goto recheck_for_conflicting_entries; } } return isFoundAndCleaned; } /** @} */ nfs-ganesha-6.5/src/SAL/nfs4_state_id.c000066400000000000000000000761451473756622300177120ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file nfs4_state_id.c * @brief NFSv4 state ids */ #include "config.h" #include #include #include /* for having isalnum */ #include /* for having atoi */ #include /* for having MAXNAMLEN */ #include #include #include #include #include #include #include /* for having FNDELAY */ #include #include #include #include "log.h" #include "gsh_rpc.h" #include "hashtable.h" #include "nfs_core.h" #include "nfs4.h" #include "fsal.h" #include "nfs_exports.h" #include "nfs_file_handle.h" #include "sal_functions.h" #include "nfs_proto_tools.h" #include "city.h" /** * @brief Hash table for stateids. */ hash_table_t *ht_state_id; hash_table_t *ht_state_obj; /** * @brief All-zeroes stateid4.other */ char all_zero[OTHERSIZE]; /** * @brief All-zeroes stateid4.other */ char all_ones[OTHERSIZE]; #define seqid_all_one 0xFFFFFFFF /** * @brief Display a stateid other * * @param[in/out] dspbuf display_buffer describing output string * @param[in] other The other component of the stateid * * @return the bytes remaining in the buffer. */ int display_stateid_other(struct display_buffer *dspbuf, char *other) { uint64_t clientid = *((uint64_t *)other); uint32_t count = *((uint32_t *)(other + sizeof(uint64_t))); int b_left = display_cat(dspbuf, "OTHER="); if (b_left <= 0) return b_left; b_left = display_opaque_bytes(dspbuf, other, OTHERSIZE); if (b_left <= 0) return b_left; b_left = display_cat(dspbuf, " {{CLIENTID "); if (b_left <= 0) return b_left; b_left = display_clientid(dspbuf, clientid); if (b_left <= 0) return b_left; return display_printf(dspbuf, "} StateIdCounter=0x%08" PRIx32 "}", count); } /** * @brief Display a stateid other in the hash table * * @param[in] dspbuf display buffer to display into * @param[in] buff The key */ int display_state_id_key(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_stateid_other(dspbuf, buff->addr); } /** * @brief Display a stateid4 from the wire * * @param[in/out] dspbuf display_buffer describing output string * @param[in] state The stateid * * @return the bytes remaining in the buffer. */ int display_stateid4(struct display_buffer *dspbuf, stateid4 *stateid) { int b_left = display_stateid_other(dspbuf, stateid->other); if (b_left <= 0) return b_left; return display_printf(dspbuf, " seqid=%" PRIu32, stateid->seqid); } const char *str_state_type(state_t *state) { switch (state->state_type) { case STATE_TYPE_NONE: return "NONE"; case STATE_TYPE_SHARE: return "SHARE"; case STATE_TYPE_DELEG: return "DELEGATION"; case STATE_TYPE_LOCK: return "LOCK"; case STATE_TYPE_LAYOUT: return "LAYOUT"; case STATE_TYPE_NLM_LOCK: return "NLM_LOCK"; case STATE_TYPE_NLM_SHARE: return "NLM_SHARE"; case STATE_TYPE_9P_FID: return "9P_FID"; default: return "UNKNOWN"; } return "UNKNOWN"; } /** * @brief Display a stateid * * @param[in/out] dspbuf display_buffer describing output string * @param[in] state The stateid * * @return the bytes remaining in the buffer. */ int display_stateid(struct display_buffer *dspbuf, state_t *state) { int b_left; if (state == NULL) return display_cat(dspbuf, "STATE "); b_left = display_printf(dspbuf, "STATE %p ", state); if (b_left <= 0) return b_left; b_left = display_stateid_other(dspbuf, state->stateid_other); if (b_left <= 0) return b_left; b_left = display_printf(dspbuf, " obj=%p type=%s seqid=%" PRIu32 " owner={", state->state_obj, str_state_type(state), state->state_seqid); if (b_left <= 0) return b_left; b_left = display_nfs4_owner(dspbuf, state->state_owner); if (b_left <= 0) return b_left; return display_printf(dspbuf, "} state_refcount=%" PRId32, atomic_fetch_int32_t(&state->state_refcount)); } /** * @brief Display a state in the hash table * * @param[in] dspbuf display buffer to display into * @param[in] buff The value * @return Length of output string. */ int display_state_id_val(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_stateid(dspbuf, buff->addr); } /** * @brief Compare two stateids * * @param[in] buff1 One key * @param[in] buff2 Another key * * @retval 0 if equal. * @retval 1 if not equal. */ int compare_state_id(struct gsh_buffdesc *buff1, struct gsh_buffdesc *buff2) { if (isFullDebug(COMPONENT_STATE)) { char str1[DISPLAY_STATEID_OTHER_SIZE] = "\0"; char str2[DISPLAY_STATEID_OTHER_SIZE] = "\0"; struct display_buffer dspbuf1 = { sizeof(str1), str1, str1 }; struct display_buffer dspbuf2 = { sizeof(str2), str2, str2 }; display_stateid_other(&dspbuf1, buff1->addr); display_stateid_other(&dspbuf2, buff2->addr); if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "{%s} vs {%s}", str1, str2); } return memcmp(buff1->addr, buff2->addr, OTHERSIZE); } /* compare_state_id */ /** * @brief Hash a stateid * * @param[in] stateid Array aliased to stateid */ static inline uint32_t compute_stateid_hash_value(uint32_t *stateid) { return stateid[1] ^ stateid[2]; } /** * @brief Hash index for a stateid * * @param[in] hparam Hash parameter * @param[in] key Key to hash * * @return The hash index. */ uint32_t state_id_value_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { uint32_t val = compute_stateid_hash_value(key->addr) % hparam->index_size; if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "val = %" PRIu32, val); return val; } /** * @brief RBT hash for a stateid * * @param[in] hparam Hash parameter * @param[in] key Key to hash * * @return The RBT hash. */ uint64_t state_id_rbt_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { uint64_t val = compute_stateid_hash_value(key->addr); if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "rbt = %" PRIu64, val); return val; } static hash_parameter_t state_id_param = { .index_size = PRIME_STATE, .hash_func_key = state_id_value_hash_func, .hash_func_rbt = state_id_rbt_hash_func, .compare_key = compare_state_id, .display_key = display_state_id_key, .display_val = display_state_id_val, .flags = HT_FLAG_CACHE, .ht_log_component = COMPONENT_STATE, .ht_name = "State ID Table" }; /** * @brief Compare two stateids by entry/owner * * @param[in] buff1 One key * @param[in] buff2 Another key * * @retval 0 if equal. * @retval 1 if not equal. */ int compare_state_obj(struct gsh_buffdesc *buff1, struct gsh_buffdesc *buff2) { state_t *state1 = buff1->addr; state_t *state2 = buff2->addr; if (state1 == NULL || state2 == NULL) return 1; if (state1->state_obj != state2->state_obj) return 1; return compare_nfs4_owner(state1->state_owner, state2->state_owner); } /** * @brief Hash index for a stateid by entry/owner * * @param[in] hparam Hash parameter * @param[in] key Key to hash * * @return The hash index. */ uint32_t state_obj_value_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { unsigned int sum = 0; unsigned int i = 0; unsigned char c = 0; uint32_t res = 0; struct gsh_buffdesc fh_desc; state_t *pkey = key->addr; pkey->state_obj->obj_ops->handle_to_key(pkey->state_obj, &fh_desc); /* Compute the sum of all the characters */ for (i = 0; i < pkey->state_owner->so_owner_len; i++) { c = ((char *)pkey->state_owner->so_owner_val)[i]; sum += c; } res = ((uint32_t)pkey->state_owner->so_owner.so_nfs4_owner.so_clientid + (uint32_t)sum + pkey->state_owner->so_owner_len + (uint32_t)pkey->state_owner->so_type + (uint64_t)CityHash64WithSeed(fh_desc.addr, fh_desc.len, 557)) % (uint32_t)hparam->index_size; if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "value = %" PRIu32, res); return res; } /** * @brief RBT hash for a stateid by entry/owner * * @param[in] hparam Hash parameter * @param[in] key Key to hash * * @return The RBT hash. */ uint64_t state_obj_rbt_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { state_t *pkey = key->addr; struct gsh_buffdesc fh_desc; unsigned int sum = 0; unsigned int i = 0; unsigned char c = 0; uint64_t res = 0; pkey->state_obj->obj_ops->handle_to_key(pkey->state_obj, &fh_desc); /* Compute the sum of all the characters */ for (i = 0; i < pkey->state_owner->so_owner_len; i++) { c = ((char *)pkey->state_owner->so_owner_val)[i]; sum += c; } res = (uint64_t)pkey->state_owner->so_owner.so_nfs4_owner.so_clientid + (uint64_t)sum + pkey->state_owner->so_owner_len + (uint64_t)pkey->state_owner->so_type + (uint64_t)CityHash64WithSeed(fh_desc.addr, fh_desc.len, 557); if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "rbt = %" PRIu64, res); return res; } static hash_parameter_t state_obj_param = { .index_size = PRIME_STATE, .hash_func_key = state_obj_value_hash_func, .hash_func_rbt = state_obj_rbt_hash_func, .compare_key = compare_state_obj, .display_key = display_state_id_val, .display_val = display_state_id_val, .flags = HT_FLAG_CACHE, .ht_log_component = COMPONENT_STATE, .ht_name = "State Obj Table" }; /** * @brief Init the hashtable for stateids * * @retval 0 if successful. * @retval -1 on failure. */ int nfs4_Init_state_id(void) { /* Init all_one */ memset(all_zero, 0, OTHERSIZE); memset(all_ones, 0xFF, OTHERSIZE); ht_state_id = hashtable_init(&state_id_param); if (ht_state_id == NULL) { LogCrit(COMPONENT_STATE, "Cannot init State Id cache"); return -1; } ht_state_obj = hashtable_init(&state_obj_param); if (ht_state_obj == NULL) { LogCrit(COMPONENT_STATE, "Cannot init State Entry cache"); return -1; } return 0; } /** * @brief Build the 12 byte "other" portion of a stateid * * It is built from the ServerEpoch and a 64 bit global counter. * * @param[in] other stateid.other object (a char[OTHERSIZE] string) */ void nfs4_BuildStateId_Other(nfs_client_id_t *clientid, char *other) { uint32_t my_stateid = atomic_inc_uint32_t(&clientid->cid_stateid_counter); /* The first part of the other is the 64 bit clientid, which * consists of the epoch in the high order 32 bits followed by * the clientid counter in the low order 32 bits. */ memcpy(other, &clientid->cid_clientid, sizeof(clientid->cid_clientid)); memcpy(other + sizeof(clientid->cid_clientid), &my_stateid, sizeof(my_stateid)); } /** * @brief Relinquish a reference on a state_t * * @param[in] state The state_t to release */ void dec_nfs4_state_ref(struct state_t *state) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; int32_t refcount; if (isFullDebug(COMPONENT_STATE)) { display_stateid(&dspbuf, state); str_valid = true; } refcount = atomic_dec_int32_t(&state->state_refcount); assert(refcount >= 0); if (refcount > 0) { if (str_valid) LogFullDebug(COMPONENT_STATE, "Decrement state_refcount now=%d {%s}", refcount, str); return; } PTHREAD_MUTEX_destroy(&state->state_mutex); free_state(state); if (str_valid) LogFullDebug(COMPONENT_STATE, "Deleted %s", str); } /** * @brief Set a state into the stateid hashtable. * * @param[in] other stateid4.other * @param[in] state The state to add * * @retval STATE_SUCCESS if able to insert the new state. * @retval STATE_ENTRY_EXISTS if state is already there. */ state_status_t nfs4_State_Set(state_t *state) { struct gsh_buffdesc buffkey; struct gsh_buffdesc buffval; hash_error_t err; buffkey.addr = state->stateid_other; buffkey.len = OTHERSIZE; buffval.addr = state; buffval.len = sizeof(state_t); err = hashtable_test_and_set(ht_state_id, &buffkey, &buffval, HASHTABLE_SET_HOW_SET_NO_OVERWRITE); switch (err) { case HASHTABLE_SUCCESS: break; default: LogCrit(COMPONENT_STATE, "ht_state_id hashtable_test_and_set failed %s for key %p", hash_table_err_to_str(err), buffkey.addr); return STATE_ENTRY_EXISTS; /* likely reason */ } /* If stateid is a LOCK or SHARE state, we also index by entry/owner */ if (state->state_type != STATE_TYPE_LOCK && state->state_type != STATE_TYPE_SHARE) return STATE_SUCCESS; buffkey.addr = state; buffkey.len = sizeof(state_t); buffval.addr = state; buffval.len = sizeof(state_t); err = hashtable_test_and_set(ht_state_obj, &buffkey, &buffval, HASHTABLE_SET_HOW_SET_NO_OVERWRITE); switch (err) { case HASHTABLE_SUCCESS: return STATE_SUCCESS; case HASHTABLE_ERROR_KEY_ALREADY_EXISTS: /* buggy client? */ default: /* error case */ LogCrit(COMPONENT_STATE, "ht_state_obj hashtable_test_and_set failed %s for key %p", hash_table_err_to_str(err), buffkey.addr); if (isFullDebug(COMPONENT_STATE)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; state_t *state2; display_stateid(&dspbuf, state); LogCrit(COMPONENT_STATE, "State %s", str); state2 = nfs4_State_Get_Obj(state->state_obj, state->state_owner); if (state2 != NULL) { display_reset_buffer(&dspbuf); display_stateid(&dspbuf, state2); LogCrit(COMPONENT_STATE, "Duplicate State %s", str); } } buffkey.addr = state->stateid_other; buffkey.len = OTHERSIZE; err = HashTable_Del(ht_state_id, &buffkey, NULL, NULL); if (err != HASHTABLE_SUCCESS) { LogCrit(COMPONENT_STATE, "Failure to delete stateid %s", hash_table_err_to_str(err)); } return STATE_ENTRY_EXISTS; /* likely reason */ } } /** * @brief Get the state from the stateid * * @param[in] other stateid4.other * * @returns The found state_t or NULL if not found. */ struct state_t *nfs4_State_Get_Pointer(char *other) { struct gsh_buffdesc buffkey; struct gsh_buffdesc buffval; hash_error_t rc; struct hash_latch latch; struct state_t *state; buffkey.addr = other; buffkey.len = OTHERSIZE; /* Acquire hashtable latch with may_write == false, as we are not * updating hashtable data structures but rather we are atomically * incrementing state_t refcount below. This allows concurrent reads * from this data structure without contending on hashtable writer * lock and since the state_t refcount itself is atomic, we are * thread safe. Furthermore, all writes to the ht_state_id hashtable * are private to this implementation file and are done under may_write * lock, so that calls to decrement the state reference count would * be done under exclusive writer lock. */ rc = hashtable_getlatch(ht_state_id, &buffkey, &buffval, false /* may_write */, &latch); if (rc != HASHTABLE_SUCCESS) { if (rc == HASHTABLE_ERROR_NO_SUCH_KEY) hashtable_releaselatched(ht_state_id, &latch); LogDebug(COMPONENT_STATE, "HashTable_Get returned %d", rc); return NULL; } state = buffval.addr; /* Take an atomic reference under latch. Hashtable is currently read * locked, so this state can't be invalidated while this atomic * increment is occurring. */ inc_state_t_ref(state); /* Release latch, which releases hashtable read lock */ hashtable_releaselatched(ht_state_id, &latch); return state; } /** * @brief Get the state from the stateid by entry/owner * * @param[in] obj Object containing state * @param[in] owner Owner for state * * @returns The found state_t or NULL if not found. */ struct state_t *nfs4_State_Get_Obj(struct fsal_obj_handle *obj, state_owner_t *owner) { state_t state_key; struct gsh_buffdesc buffkey; struct gsh_buffdesc buffval; hash_error_t rc; struct hash_latch latch; struct state_t *state; memset(&state_key, 0, sizeof(state_key)); buffkey.addr = &state_key; buffkey.len = sizeof(state_key); state_key.state_owner = owner; state_key.state_obj = obj; rc = hashtable_getlatch(ht_state_obj, &buffkey, &buffval, true, &latch); if (rc != HASHTABLE_SUCCESS) { if (rc == HASHTABLE_ERROR_NO_SUCH_KEY) hashtable_releaselatched(ht_state_obj, &latch); LogDebug(COMPONENT_STATE, "HashTable_Get returned %d", rc); return NULL; } state = buffval.addr; /* Take a reference under latch */ inc_state_t_ref(state); /* Release latch */ hashtable_releaselatched(ht_state_obj, &latch); return state; } /** * @brief Remove a state from the stateid table * * @param[in] other stateid4.other * * @retval true if success * @retval false if failure */ bool nfs4_State_Del(state_t *state) { struct gsh_buffdesc buffkey, old_key, old_value; struct hash_latch latch; hash_error_t err; buffkey.addr = state->stateid_other; buffkey.len = OTHERSIZE; err = HashTable_Del(ht_state_id, &buffkey, &old_key, &old_value); if (err == HASHTABLE_ERROR_NO_SUCH_KEY) { /* Already gone */ return false; } else if (err != HASHTABLE_SUCCESS) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_stateid(&dspbuf, state); LogDebug(COMPONENT_STATE, "Failure to delete stateid %s %s", str, hash_table_err_to_str(err)); return false; } assert(state == old_value.addr); /* If stateid is a LOCK or SHARE state, we had also indexed by * entry/owner */ if (state->state_type != STATE_TYPE_LOCK && state->state_type != STATE_TYPE_SHARE) return true; /* Delete the stateid hashed by entry/owner. * Use the old_value from above as the key. */ buffkey.addr = old_value.addr; buffkey.len = old_value.len; /* Get latch: we need to check we're deleting the right state */ err = hashtable_getlatch(ht_state_obj, &buffkey, &old_value, true, &latch); if (err != HASHTABLE_SUCCESS) { if (err == HASHTABLE_ERROR_NO_SUCH_KEY) hashtable_releaselatched(ht_state_obj, &latch); LogCrit(COMPONENT_STATE, "hashtable get latch failed: %d", err); return false; } if (old_value.addr != state) { /* state obj had already been swapped out */ hashtable_releaselatched(ht_state_obj, &latch); return false; } hashtable_deletelatched(ht_state_obj, &buffkey, &latch, NULL, NULL); hashtable_releaselatched(ht_state_obj, &latch); return true; } /** * @brief Check and look up the supplied stateid * * This function yields the state for the stateid if it is valid. * * @param[in] stateid Stateid to look up * @param[in] fsal_obj Associated file (if any) * @param[out] state Found state * @param[in] data Compound data * @param[in] flags Flags governing special stateids * @param[in] owner_seqid seqid on v4.0 owner * @param[in] check_seqid Whether to validate owner_seqid * @param[in] tag Arbitrary string for logging/debugging * * @return NFSv4 status codes */ nfsstat4 nfs4_Check_Stateid(stateid4 *stateid, struct fsal_obj_handle *fsal_obj, state_t **state, compound_data_t *data, int flags, seqid4 owner_seqid, bool check_seqid, const char *tag) { uint32_t client_unique_prefix = 0; uint64_t unique_prefix = get_unique_server_id() & 0xFFFFFFFF; state_t *state2 = NULL; struct fsal_obj_handle *obj2 = NULL; state_owner_t *owner2 = NULL; char str[DISPLAY_STATEID4_SIZE] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; int32_t diff; clientid4 clientid; nfs_client_id_t *pclientid; int rc; nfsstat4 status; if (isDebug(COMPONENT_STATE)) { display_stateid4(&dspbuf, stateid); str_valid = true; } LogFullDebug(COMPONENT_STATE, "Check %s stateid flags%s%s%s%s%s%s", tag, flags & STATEID_SPECIAL_ALL_0 ? " ALL_0" : "", flags & STATEID_SPECIAL_ALL_1 ? " ALL_1" : "", flags & STATEID_SPECIAL_CURRENT ? " CURRENT" : "", flags & STATEID_SPECIAL_CLOSE_40 ? " CLOSE_40" : "", flags & STATEID_SPECIAL_CLOSE_41 ? " CLOSE_41" : "", flags == 0 ? " NONE" : ""); /* Test for OTHER is all zeros */ if (memcmp(stateid->other, all_zero, OTHERSIZE) == 0) { if (stateid->seqid == 0 && (flags & STATEID_SPECIAL_ALL_0) != 0) { /* All 0 stateid */ LogDebug(COMPONENT_STATE, "Check %s stateid found special all 0 stateid", tag); /** @todo FSF: eventually this may want to return an * actual state for use in temporary locks for I/O. */ data->current_stateid_valid = false; goto success; } if (stateid->seqid == 1 && (flags & STATEID_SPECIAL_CURRENT) != 0) { /* Special current stateid */ LogDebug( COMPONENT_STATE, "Check %s stateid found special 'current' stateid", tag); if (!data->current_stateid_valid) { LogDebug( COMPONENT_STATE, "Check %s stateid STATEID_SPECIAL_CURRENT - current stateid is bad", tag); status = NFS4ERR_BAD_STATEID; goto failure; } /* Copy current stateid in and proceed to checks */ *stateid = data->current_stateid; goto check_it; } LogDebug( COMPONENT_STATE, "Check %s stateid with OTHER all zeros, seqid %u unexpected", tag, (unsigned int)stateid->seqid); status = NFS4ERR_BAD_STATEID; goto failure; } /* Test for OTHER is all ones */ if (memcmp(stateid->other, all_ones, OTHERSIZE) == 0) { /* Test for special all ones stateid */ if (stateid->seqid == seqid_all_one && (flags & STATEID_SPECIAL_ALL_1) != 0) { /* All 1 stateid */ LogDebug(COMPONENT_STATE, "Check %s stateid found special all 1 stateid", tag); /** @todo FSF: eventually this may want to return an * actual state for use in temporary locks for I/O. */ data->current_stateid_valid = false; goto success; } LogDebug( COMPONENT_STATE, "Check %s stateid with OTHER all ones, seqid %u unexpected", tag, (unsigned int)stateid->seqid); status = NFS4ERR_BAD_STATEID; goto failure; } check_it: /* Extract the clientid from the stateid other field */ memcpy(&clientid, stateid->other, sizeof(clientid)); /* Extract the epoch from the clientid */ client_unique_prefix = clientid >> (clientid4)32; /* Check if stateid was made from this server instance */ if (client_unique_prefix != unique_prefix) { if (str_valid) LogDebug(COMPONENT_STATE, "Check %s stateid found stale stateid %s", tag, str); if (data->minorversion == 0) status = NFS4ERR_STALE_STATEID; else status = NFS4ERR_BAD_STATEID; goto failure; } /* Try to get the related state */ state2 = nfs4_State_Get_Pointer(stateid->other); /* We also need a reference to the state_obj and state_owner. * If we can't get them, we will check below for lease invalidity. * Note that calling get_state_obj_export_owner_refs with a NULL * state2 returns false. * * NOTE: Don't ask for obj2 if fsal_obj is NULL. We won't need obj2 in * that case, and we may not have an op_ctx to release the object * reference. */ if (!get_state_obj_export_owner_refs( state2, fsal_obj != NULL ? &obj2 : NULL, NULL, &owner2)) { /* We matched this server's epoch, but could not find the * stateid. Chances are, the client was expired and the state * has all been freed. * * We could use another check here for a BAD stateid */ if (str_valid) LogDebug(COMPONENT_STATE, "Check %s stateid could not find %s", tag, str); /* Try and find the clientid */ rc = nfs_client_id_get_confirmed(clientid, &pclientid); if (rc != CLIENT_ID_SUCCESS) { /* Unknown client id (or other problem), * return that result. */ status = clientid_error_to_nfsstat(rc); goto failure; } if ((flags & (STATEID_SPECIAL_CLOSE_40 | STATEID_SPECIAL_CLOSE_41)) != 0) { /* This is a close with a valid clientid, but invalid * stateid counter. We will assume this is a replayed * close. */ if (data->preserved_clientid != NULL) { /* We don't expect this, but, just in case... * Update and release already reserved lease. */ update_lease_simple(data->preserved_clientid); data->preserved_clientid = NULL; } /* Check if lease is expired and reserve it. If it's * valid and this is STATEID_SPECIAL_CLOSE_40, update * the lease. */ if (!reserve_lease_or_expire( pclientid, (flags & STATEID_SPECIAL_CLOSE_40) != 0, NULL)) { LogDebug(COMPONENT_STATE, "Returning NFS4ERR_EXPIRED"); /* Release the clientid reference we just * acquired. */ dec_client_id_ref(pclientid); status = NFS4ERR_EXPIRED; goto failure; } if ((flags & STATEID_SPECIAL_CLOSE_40) == 0) { /* Not STATEID_SPECIAL_CLOSE_40, remember the * reserved clientid for the rest of the * compound. */ data->preserved_clientid = pclientid; } /* Replayed close, it's ok, but stateid doesn't exist */ LogDebug(COMPONENT_STATE, "Check %s stateid is a replayed close", tag); data->current_stateid_valid = false; goto success; } if (state2 == NULL) status = NFS4ERR_BAD_STATEID; else { /* We had a valid stateid, but the entry was stale. * Check if lease is expired and reserve it so we * can distinguish between the state_t being in the * midst of tear down due to expired lease or if * in fact the entry is actually stale. * * If it's valid, Just update the lease and leave the * reserved clientid NULL. */ if (!reserve_lease_or_expire(pclientid, true, NULL)) { LogDebug(COMPONENT_STATE, "Returning NFS4ERR_EXPIRED"); /* Release the clientid reference we just * acquired. */ dec_client_id_ref(pclientid); status = NFS4ERR_EXPIRED; goto failure; } /* The lease was valid, so this must be a stale * entry. */ status = NFS4ERR_STALE; } /* Release the clientid reference we just acquired. */ dec_client_id_ref(pclientid); goto failure; } /* Now, if this lease is not already reserved, reserve it */ if (data->preserved_clientid != owner2->so_owner.so_nfs4_owner.so_clientrec) { if (data->preserved_clientid != NULL) { /* We don't expect this to happen, but, just in case... * Update and release already reserved lease. */ update_lease_simple(data->preserved_clientid); data->preserved_clientid = NULL; } /* Check if lease is expired and reserve it */ if (!reserve_lease_or_expire( owner2->so_owner.so_nfs4_owner.so_clientrec, false, &owner2)) { LogDebug(COMPONENT_STATE, "Returning NFS4ERR_EXPIRED"); status = NFS4ERR_EXPIRED; goto failure; } data->preserved_clientid = owner2->so_owner.so_nfs4_owner.so_clientrec; } /* Sanity check : Is this the right file ? */ if (fsal_obj && !fsal_obj->obj_ops->handle_cmp(fsal_obj, obj2)) { if (str_valid) LogDebug( COMPONENT_STATE, "Check %s stateid found stateid %s has wrong file", tag, str); status = NFS4ERR_BAD_STATEID; goto failure; } /* Whether stateid.seqid may be zero depends on the state type exclusively, See RFC 5661 pp. 161,287-288. */ if ((state2->state_type == STATE_TYPE_LAYOUT) || (stateid->seqid != 0)) { /* Check seqid in stateid */ /** * @todo fsf: maybe change to simple comparison: * stateid->seqid < state2->state_seqid * as good enough and maybe makes pynfs happy. */ diff = stateid->seqid - state2->state_seqid; if (diff < 0) { /* if this is NFSv4.0 and stateid's seqid is one less * than current AND if owner_seqid is current * pass state back to allow replay check */ if ((check_seqid) && ((diff == -1) || ((state2->state_seqid == 1) && (stateid->seqid == seqid_all_one))) && (owner_seqid == owner2->so_owner.so_nfs4_owner.so_seqid)) { LogDebug(COMPONENT_STATE, "possible replay?"); *state = state2; status = NFS4ERR_REPLAY; goto replay; } /* OLD_STATEID */ if (str_valid) LogDebug( COMPONENT_STATE, "Check %s stateid found OLD stateid %s, expected seqid %" PRIu32, tag, str, state2->state_seqid); status = NFS4ERR_OLD_STATEID; goto failure; } /* stateid seqid is current and owner seqid is previous, * replay (should be an error condition that did not change * the stateid, no real need to check since the operation * must be the same) */ else if ((diff == 0) && (check_seqid) && (owner_seqid == owner2->so_owner.so_nfs4_owner.so_seqid)) { LogDebug(COMPONENT_STATE, "possible replay?"); *state = state2; status = NFS4ERR_REPLAY; goto replay; } else if (diff > 0) { /* BAD_STATEID */ if (str_valid) LogDebug( COMPONENT_STATE, "Check %s stateid found BAD stateid %s, expected seqid %" PRIu32, tag, str, state2->state_seqid); status = NFS4ERR_BAD_STATEID; goto failure; } } data->current_stateid_valid = true; if (str_valid) LogFullDebug(COMPONENT_STATE, "Check %s stateid found valid stateid %s - %p", tag, str, state2); /* Copy stateid into current for later use */ data->current_stateid = *stateid; data->current_stateid.seqid = state2->state_seqid; success: if (obj2 != NULL) obj2->obj_ops->put_ref(obj2); if (owner2 != NULL) dec_state_owner_ref(owner2); *state = state2; return NFS4_OK; failure: if (state2 != NULL) dec_state_t_ref(state2); *state = NULL; replay: if (obj2 != NULL) obj2->obj_ops->put_ref(obj2); if (owner2 != NULL) dec_state_owner_ref(owner2); data->current_stateid_valid = false; return status; } /** * @brief Display the stateid table */ void nfs_State_PrintAll(void) { if (isFullDebug(COMPONENT_STATE)) hashtable_log(COMPONENT_STATE, ht_state_id); } /** * @brief Update stateid and set current * * We increment the seqid, handling wraparound, and copy the id into * the response. * * @param[in,out] state The state to update * @param[out] resp Stateid in response * @param[in,out] data Compound data to upddate with current stateid * (may be NULL) * @param[in] tag Arbitrary text for debug/log */ void update_stateid(state_t *state, stateid4 *resp, compound_data_t *data, const char *tag) { /* Increment state_seqid, handling wraparound */ state->state_seqid += 1; if (state->state_seqid == 0) state->state_seqid = 1; /* Copy stateid into current for later use */ if (data) { COPY_STATEID(&data->current_stateid, state); data->current_stateid_valid = true; } /* Copy stateid into response */ COPY_STATEID(resp, state); if (isFullDebug(COMPONENT_STATE)) { char str[DISPLAY_STATEID4_SIZE] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_stateid4(&dspbuf, resp); LogDebug(COMPONENT_STATE, "Update %s stateid to %s for response", tag, str); } } /** @} */ nfs-ganesha-6.5/src/SAL/nlm_owner.c000066400000000000000000001031171473756622300171520ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file nlm_owner.c * @brief The management of the NLM owner cache. */ #include "config.h" #include #include #include #include "gsh_config.h" #include "sal_functions.h" #include "nsm.h" #include "log.h" #include "client_mgr.h" #include "fsal.h" /** * @brief NSM clients */ hash_table_t *ht_nsm_client; /** * @brief NLM Clients */ hash_table_t *ht_nlm_client; /** * @brief NLM owners */ hash_table_t *ht_nlm_owner; /******************************************************************************* * * NSM Client Routines * ******************************************************************************/ /** * @brief Display an NSM client * * @param[in/out] dspbuf display_buffer describing output string * @param[in] key The NSM client * * @return the bytes remaining in the buffer. */ int display_nsm_client(struct display_buffer *dspbuf, state_nsm_client_t *key) { int b_left; if (key == NULL) return display_printf(dspbuf, "NSM Client "); b_left = display_printf(dspbuf, "NSM Client %p: ", key); if (b_left <= 0) return b_left; if (nfs_param.core_param.nsm_use_caller_name) b_left = display_printf(dspbuf, "caller_name="); else b_left = display_printf(dspbuf, "addr="); if (b_left <= 0) return b_left; b_left = display_len_cat(dspbuf, key->ssc_nlm_caller_name, key->ssc_nlm_caller_name_len); if (b_left <= 0) return b_left; return display_printf(dspbuf, " ssc_client=%p %s ssc_refcount=%d", key->ssc_client, atomic_fetch_int32_t(&key->ssc_monitored) ? "monitored" : "unmonitored", atomic_fetch_int32_t(&key->ssc_refcount)); } /** * @brief Display an NSM client in the hash table * * @param[in] dspbuf display buffer to display into * @param[in] buff The key or val */ int display_nsm_client_key_val(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_nsm_client(dspbuf, buff->addr); } /** * @brief Compare NSM clients * * @param[in] client1 A client * @param[in] client2 Another client * * @retval 0 on equality. * @retval 1 on inequality. */ int compare_nsm_client(state_nsm_client_t *client1, state_nsm_client_t *client2) { if (isFullDebug(COMPONENT_STATE) && isDebug(COMPONENT_HASHTABLE)) { char str1[LOG_BUFF_LEN / 2] = "\0"; char str2[LOG_BUFF_LEN / 2] = "\0"; struct display_buffer dspbuf1 = { sizeof(str1), str1, str1 }; struct display_buffer dspbuf2 = { sizeof(str2), str2, str2 }; display_nsm_client(&dspbuf1, client1); display_nsm_client(&dspbuf2, client2); LogFullDebug(COMPONENT_STATE, "{%s} vs {%s}", str1, str2); } if (client1 == NULL || client2 == NULL) return 1; if (client1 == client2) return 0; /* Since we always have a caller name in the key and records whether * nsm_use_caller_name is true or not, we don't ever compare ssc_client, * we always just compare the caller name. * * This makes SM_NOTIFY work because we can't know the port number * which is part of identifying ssc_client. We only care about the * address. */ if (client1->ssc_nlm_caller_name_len != client2->ssc_nlm_caller_name_len) return 1; return memcmp(client1->ssc_nlm_caller_name, client2->ssc_nlm_caller_name, client1->ssc_nlm_caller_name_len); } /** * @brief Compare NSM clients in the hash table * * @param[in] buff1 A key * @param[in] buff2 Another key * * @retval 0 on equality. * @retval 1 on inequality. */ int compare_nsm_client_key(struct gsh_buffdesc *buff1, struct gsh_buffdesc *buff2) { return compare_nsm_client(buff1->addr, buff2->addr); } /** * @brief Calculate hash index for an NSM key * * @todo Replace with a good hash function. * * @param[in] hparam Hash params * @param[out] key Key to hash * * @return The hash index. */ uint32_t nsm_client_value_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { unsigned long res; state_nsm_client_t *pkey = key->addr; unsigned int sum = 0; unsigned int i; /* Since we always have a caller name in the key and records whether * nsm_use_caller_name is true or not, we don't ever compare ssc_client, * we always just compare the caller name. * * This makes SM_NOTIFY work because we can't know the port number * which is part of identifying ssc_client. We only care about the * address. */ /* Compute the sum of all the characters */ for (i = 0; i < pkey->ssc_nlm_caller_name_len; i++) sum += (unsigned char)pkey->ssc_nlm_caller_name[i]; res = (unsigned long)sum + (unsigned long)pkey->ssc_nlm_caller_name_len; if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "value = %lu", res % hparam->index_size); return (unsigned long)(res % hparam->index_size); } /** * @brief Calculate RBT hash for an NSM key * * @todo Replace with a good hash function. * * @param[in] hparam Hash params * @param[out] key Key to hash * * @return The RBT hash. */ uint64_t nsm_client_rbt_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { unsigned long res; state_nsm_client_t *pkey = key->addr; unsigned int sum = 0; unsigned int i; /* Since we always have a caller name in the key and records whether * nsm_use_caller_name is true or not, we don't ever compare ssc_client, * we always just compare the caller name. * * This makes SM_NOTIFY work because we can't know the port number * which is part of identifying ssc_client. We only care about the * address. */ /* Compute the sum of all the characters */ for (i = 0; i < pkey->ssc_nlm_caller_name_len; i++) sum += (unsigned char)pkey->ssc_nlm_caller_name[i]; res = (unsigned long)sum + (unsigned long)pkey->ssc_nlm_caller_name_len; if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "rbt = %lu", res); return res; } /* nsm_client_rbt_hash_func */ /******************************************************************************* * * NLM Client Routines * ******************************************************************************/ /** * @brief Display an NLM client * * @param[in/out] dspbuf display_buffer describing output string * @param[in] key The NLM client * * @return the bytes remaining in the buffer. */ int display_nlm_client(struct display_buffer *dspbuf, state_nlm_client_t *key) { int b_left; if (key == NULL) return display_printf(dspbuf, "NLM Client "); b_left = display_printf(dspbuf, "NLM Client %p: {", key); if (b_left <= 0) return b_left; b_left = display_nsm_client(dspbuf, key->slc_nsm_client); if (b_left <= 0) return b_left; b_left = display_printf(dspbuf, "} caller_name="); if (b_left <= 0) return b_left; b_left = display_len_cat(dspbuf, key->slc_nlm_caller_name, key->slc_nlm_caller_name_len); if (b_left <= 0) return b_left; return display_printf(dspbuf, " type=%s slc_refcount=%d", xprt_type_to_str(key->slc_client_type), atomic_fetch_int32_t(&key->slc_refcount)); } /** * @brief Display an NLM client in the hash table * * @param[in] dspbuf display buffer to display into * @param[in] buff The key or val */ int display_nlm_client_key_val(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_nlm_client(dspbuf, buff->addr); } /** * @brief Compare NLM clients * * @param[in] client1 A client * @param[in] client2 Another client * * @retval 0 on equality. * @retval 1 on inequality. */ int compare_nlm_client(state_nlm_client_t *client1, state_nlm_client_t *client2) { if (isFullDebug(COMPONENT_STATE) && isDebug(COMPONENT_HASHTABLE)) { char str1[LOG_BUFF_LEN / 2] = "\0"; char str2[LOG_BUFF_LEN / 2] = "\0"; struct display_buffer dspbuf1 = { sizeof(str1), str1, str1 }; struct display_buffer dspbuf2 = { sizeof(str2), str2, str2 }; display_nlm_client(&dspbuf1, client1); display_nlm_client(&dspbuf2, client2); LogFullDebug(COMPONENT_STATE, "{%s} vs {%s}", str1, str2); } if (client1 == NULL || client2 == NULL) return 1; if (client1 == client2) return 0; if (compare_nsm_client(client1->slc_nsm_client, client2->slc_nsm_client) != 0) return 1; if (cmp_sockaddr(&client1->slc_server_addr, &client2->slc_server_addr, true) == 0) return 1; if (client1->slc_client_type != client2->slc_client_type) return 1; if (client1->slc_nlm_caller_name_len != client2->slc_nlm_caller_name_len) return 1; return memcmp(client1->slc_nlm_caller_name, client2->slc_nlm_caller_name, client1->slc_nlm_caller_name_len); } /** * @brief Compare NLM clients in the hash table * * @param[in] buff1 A key * @param[in] buff2 Another key * * @retval 0 on equality. * @retval 1 on inequality. */ int compare_nlm_client_key(struct gsh_buffdesc *buff1, struct gsh_buffdesc *buff2) { return compare_nlm_client(buff1->addr, buff2->addr); } /** * @brief Calculate hash index for an NLM key * * @todo Replace with a good hash function. * * @param[in] hparam Hash params * @param[out] key Key to hash * * @return The hash index. */ uint32_t nlm_client_value_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { uint32_t sum = 0; unsigned int i; unsigned long res; state_nlm_client_t *pkey = key->addr; /* Compute the sum of all the characters */ for (i = 0; i < pkey->slc_nlm_caller_name_len; i++) sum += (unsigned char)pkey->slc_nlm_caller_name[i]; res = (unsigned long)sum + (unsigned long)pkey->slc_nlm_caller_name_len; if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "value = %lu", res % hparam->index_size); return (unsigned long)(res % hparam->index_size); } /** * @brief Calculate RBT hash for an NLM key * * @todo Replace with a good hash function. * * @param[in] hparam Hash params * @param[out] key Key to hash * * @return The RBT hash. */ uint64_t nlm_client_rbt_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { unsigned int sum = 0; unsigned int i; unsigned long res; state_nlm_client_t *pkey = key->addr; /* Compute the sum of all the characters */ for (i = 0; i < pkey->slc_nlm_caller_name_len; i++) sum += (unsigned char)pkey->slc_nlm_caller_name[i]; res = (unsigned long)sum + (unsigned long)pkey->slc_nlm_caller_name_len; if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "rbt = %lu", res); return res; } /* nlm_client_rbt_hash_func */ /******************************************************************************* * * NLM Owner Routines * ******************************************************************************/ /** * @brief Display an NLM cowner * * @param[in/out] dspbuf display_buffer describing output string * @param[in] key The NLM owner * * @return the bytes remaining in the buffer. */ int display_nlm_owner(struct display_buffer *dspbuf, state_owner_t *owner) { int b_left; if (owner == NULL) return display_printf(dspbuf, "STATE_LOCK_OWNER_NLM "); b_left = display_printf(dspbuf, "STATE_LOCK_OWNER_NLM %p: {", owner); if (b_left <= 0) return b_left; b_left = display_nlm_client(dspbuf, owner->so_owner.so_nlm_owner.so_client); if (b_left <= 0) return b_left; b_left = display_printf(dspbuf, "} oh="); if (b_left <= 0) return b_left; b_left = display_opaque_value(dspbuf, owner->so_owner_val, owner->so_owner_len); if (b_left <= 0) return b_left; return display_printf(dspbuf, " svid=%d so_refcount=%d", owner->so_owner.so_nlm_owner.so_nlm_svid, atomic_fetch_int32_t(&owner->so_refcount)); } /** * @brief Display an NLM owner in the hash table * * @param[in] dspbuf display buffer to display into * @param[in] buff The key or val */ int display_nlm_owner_key_val(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_nlm_owner(dspbuf, buff->addr); } /** * @brief Compare NLM owners * * @param[in] owner1 A client * @param[in] owner2 Another client * * @retval 0 on equality. * @retval 1 on inequality. */ int compare_nlm_owner(state_owner_t *owner1, state_owner_t *owner2) { if (isFullDebug(COMPONENT_STATE) && isDebug(COMPONENT_HASHTABLE)) { char str1[LOG_BUFF_LEN / 2] = "\0"; char str2[LOG_BUFF_LEN / 2] = "\0"; struct display_buffer dspbuf1 = { sizeof(str1), str1, str1 }; struct display_buffer dspbuf2 = { sizeof(str2), str2, str2 }; display_nlm_owner(&dspbuf1, owner1); display_nlm_owner(&dspbuf2, owner2); LogFullDebug(COMPONENT_STATE, "{%s} vs {%s}", str1, str2); } if (owner1 == NULL || owner2 == NULL) return 1; if (owner1 == owner2) return 0; if (compare_nlm_client(owner1->so_owner.so_nlm_owner.so_client, owner2->so_owner.so_nlm_owner.so_client) != 0) return 1; if (owner1->so_owner.so_nlm_owner.so_nlm_svid != owner2->so_owner.so_nlm_owner.so_nlm_svid) return 1; if (owner1->so_owner_len != owner2->so_owner_len) return 1; return memcmp(owner1->so_owner_val, owner2->so_owner_val, owner1->so_owner_len); } /** * @brief Compare NLM owners in the hash table * * @param[in] buff1 A key * @param[in] buff2 Another key * * @retval 0 on equality. * @retval 1 on inequality. */ int compare_nlm_owner_key(struct gsh_buffdesc *buff1, struct gsh_buffdesc *buff2) { return compare_nlm_owner(buff1->addr, buff2->addr); } /** * @brief Calculate hash index for an NLM owner key * * @todo Replace with a good hash function. * * @param[in] hparam Hash params * @param[out] key Key to hash * * @return The hash index. */ uint32_t nlm_owner_value_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { unsigned int sum = 0; unsigned int i; unsigned long res; state_owner_t *pkey = key->addr; /* Compute the sum of all the characters */ for (i = 0; i < pkey->so_owner_len; i++) sum += (unsigned char)pkey->so_owner_val[i]; res = (unsigned long)(pkey->so_owner.so_nlm_owner.so_nlm_svid) + (unsigned long)sum + (unsigned long)pkey->so_owner_len; if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "value = %lu", res % hparam->index_size); return (unsigned long)(res % hparam->index_size); } /** * @brief Calculate RBT hash for an NLM owner key * * @todo Replace with a good hash function. * * @param[in] hparam Hash params * @param[out] key Key to hash * * @return The RBT hash. */ uint64_t nlm_owner_rbt_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { unsigned int sum = 0; unsigned int i; unsigned long res; state_owner_t *pkey = key->addr; /* Compute the sum of all the characters */ for (i = 0; i < pkey->so_owner_len; i++) sum += (unsigned char)pkey->so_owner_val[i]; res = (unsigned long)(pkey->so_owner.so_nlm_owner.so_nlm_svid) + (unsigned long)sum + (unsigned long)pkey->so_owner_len; if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "rbt = %lu", res); return res; } /* state_id_rbt_hash_func */ static hash_parameter_t nsm_client_hash_param = { .index_size = PRIME_STATE, .hash_func_key = nsm_client_value_hash_func, .hash_func_rbt = nsm_client_rbt_hash_func, .compare_key = compare_nsm_client_key, .display_key = display_nsm_client_key_val, .display_val = display_nsm_client_key_val, .flags = HT_FLAG_NONE, }; static hash_parameter_t nlm_client_hash_param = { .index_size = PRIME_STATE, .hash_func_key = nlm_client_value_hash_func, .hash_func_rbt = nlm_client_rbt_hash_func, .compare_key = compare_nlm_client_key, .display_key = display_nlm_client_key_val, .display_val = display_nlm_client_key_val, .flags = HT_FLAG_NONE, }; static hash_parameter_t nlm_owner_hash_param = { .index_size = PRIME_STATE, .hash_func_key = nlm_owner_value_hash_func, .hash_func_rbt = nlm_owner_rbt_hash_func, .compare_key = compare_nlm_owner_key, .display_key = display_nlm_owner_key_val, .display_val = display_nlm_owner_key_val, .flags = HT_FLAG_NONE, }; /** * @brief Init the hashtables for NLM support * * @return 0 if successful, -1 otherwise */ int Init_nlm_hash(void) { ht_nsm_client = hashtable_init(&nsm_client_hash_param); if (ht_nsm_client == NULL) { LogCrit(COMPONENT_STATE, "Cannot init NSM Client cache"); return -1; } ht_nlm_client = hashtable_init(&nlm_client_hash_param); if (ht_nlm_client == NULL) { LogCrit(COMPONENT_STATE, "Cannot init NLM Client cache"); return -1; } ht_nlm_owner = hashtable_init(&nlm_owner_hash_param); if (ht_nlm_owner == NULL) { LogCrit(COMPONENT_STATE, "Cannot init NLM Owner cache"); return -1; } return 0; } /* Init_nlm_hash */ /******************************************************************************* * * NSM Client Routines * ******************************************************************************/ /** * @brief Take a reference on NSM client * * @param[in] client The client to ref */ void _inc_nsm_client_ref(state_nsm_client_t *client, char *file, int line, char *function) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool strvalid = false; int32_t refcount; if (isFullDebug(COMPONENT_STATE)) { display_nsm_client(&dspbuf, client); strvalid = true; /* Note that the way the logging below works, we will log at * FullDebug even if it is turned off in the middle of the * execution of this function since we don't test subsequently. */ } refcount = atomic_inc_int32_t(&client->ssc_refcount); if (strvalid) { DisplayLogComponentLevel(COMPONENT_STATE, file, line, function, NIV_FULL_DEBUG, "Increment ssc_refcount now=%" PRId32 " {%s}", refcount, str); } } /** * @brief Free an NSM client * * @param[in] client The client to free */ void free_nsm_client(state_nsm_client_t *client) { gsh_free(client->ssc_nlm_caller_name); if (client->ssc_client != NULL) put_gsh_client(client->ssc_client); PTHREAD_MUTEX_destroy(&client->ssc_mutex); gsh_free(client); } /** * @brief Relinquish a reference on an NSM client * * @param[in] client The client to release */ void _dec_nsm_client_ref(state_nsm_client_t *client, char *file, int line, char *function) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; struct hash_latch latch; hash_error_t rc; struct gsh_buffdesc buffkey; struct gsh_buffdesc old_value; int32_t refcount; if (isFullDebug(COMPONENT_STATE)) { display_nsm_client(&dspbuf, client); str_valid = true; /* Note that the way the logging below works, we will log at * FullDebug even if it is turned off in the middle of the * execution of this function since we don't test subsequently. */ } refcount = atomic_dec_int32_t(&client->ssc_refcount); assert(refcount >= 0); if (refcount > 0) { if (str_valid) { DisplayLogComponentLevel( COMPONENT_STATE, file, line, function, NIV_FULL_DEBUG, "Decrement ssc_refcount now=%" PRId32 " {%s}", refcount, str); } return; } if (str_valid) { DisplayLogComponentLevel(COMPONENT_STATE, file, line, function, NIV_FULL_DEBUG, "Try to remove {%s}", str); } buffkey.addr = client; buffkey.len = sizeof(*client); /* Since the refcnt is zero, another thread that needs this * entry may delete this nsm client to insert its own. * So expect not to find this nsm client or find someone * else's nsm client! */ rc = hashtable_getlatch(ht_nsm_client, &buffkey, &old_value, true, &latch); switch (rc) { case HASHTABLE_SUCCESS: if (old_value.addr == client) { /* our nsm client */ hashtable_deletelatched(ht_nsm_client, &buffkey, &latch, NULL, NULL); } break; case HASHTABLE_ERROR_NO_SUCH_KEY: break; default: if (!str_valid) display_nsm_client(&dspbuf, client); if (isLevel(COMPONENT_STATE, NIV_CRIT)) { DisplayLogComponentLevel( COMPONENT_STATE, file, line, function, NIV_CRIT, "Error %s, could not find {%s}", hash_table_err_to_str(rc), str); } return; } hashtable_releaselatched(ht_nsm_client, &latch); if (str_valid) { DisplayLogComponentLevel(COMPONENT_STATE, file, line, function, NIV_FULL_DEBUG, "Free {%s}", str); } nsm_unmonitor(client); free_nsm_client(client); } /** * @brief Get an NSM client * * @param[in] care Care status * @param[in] caller_name Caller name * * @return NSM client or NULL. */ state_nsm_client_t *get_nsm_client(care_t care, char *caller_name) { state_nsm_client_t key; state_nsm_client_t *pclient; char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; struct hash_latch latch; hash_error_t rc; struct gsh_buffdesc buffkey; struct gsh_buffdesc buffval; char hostaddr_str[SOCK_NAME_MAX]; if (caller_name == NULL) return NULL; memset(&key, 0, sizeof(key)); if (nfs_param.core_param.nsm_use_caller_name || op_ctx->client == NULL) { /* If nsm_use_caller_name is false but op_ctx->client is NULL * we are being called for SM_NOTIFY. caller name is supposed to * be an IP address. */ key.ssc_nlm_caller_name_len = strlen(caller_name); if (key.ssc_nlm_caller_name_len > LM_MAXSTRLEN) return NULL; key.ssc_nlm_caller_name = caller_name; LogFullDebug(COMPONENT_STATE, "Using caller_name %s", caller_name); } else { sockaddr_t alt_host; sockaddr_t *host = NULL; if (isFullDebug(COMPONENT_STATE)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer db = { sizeof(str), str, str }; display_sockaddr(&db, op_ctx->caller_addr); LogFullDebug(COMPONENT_STATE, "Using address %s as caller name", str); } /* Fixup any encapsulated IPv4 addresses */ host = convert_ipv6_to_ipv4(op_ctx->caller_addr, &alt_host); /* Generate caller name from fixed up address */ if (!sprint_sockip(host, hostaddr_str, sizeof(hostaddr_str))) { LogCrit(COMPONENT_STATE, "Could not generate caller name"); return NULL; } LogFullDebug(COMPONENT_STATE, "Using caller address %s", hostaddr_str); key.ssc_nlm_caller_name = hostaddr_str; key.ssc_nlm_caller_name_len = strlen(key.ssc_nlm_caller_name); key.ssc_client = op_ctx->client; } if (isFullDebug(COMPONENT_STATE)) { display_nsm_client(&dspbuf, &key); LogFullDebug(COMPONENT_STATE, "Find {%s}", str); } buffkey.addr = &key; buffkey.len = sizeof(key); rc = hashtable_getlatch(ht_nsm_client, &buffkey, &buffval, true, &latch); switch (rc) { case HASHTABLE_SUCCESS: pclient = buffval.addr; if (atomic_inc_unless_0_int32_t(&pclient->ssc_refcount) == 0) { /* This nsm client is in the process of getting * deleted. Delete it from the hash table and * pretend as though we didn't find it. */ hashtable_deletelatched(ht_nsm_client, &buffkey, &latch, NULL, NULL); break; } /* Return the found NSM Client */ if (isFullDebug(COMPONENT_STATE)) { /* Clear out key and display found client */ display_reset_buffer(&dspbuf); display_nsm_client(&dspbuf, pclient); LogFullDebug(COMPONENT_STATE, "Found {%s}", str); } hashtable_releaselatched(ht_nsm_client, &latch); if (care == CARE_MONITOR && !nsm_monitor(pclient)) { dec_nsm_client_ref(pclient); pclient = NULL; } return pclient; case HASHTABLE_ERROR_NO_SUCH_KEY: break; default: display_nsm_client(&dspbuf, &key); LogCrit(COMPONENT_STATE, "Error %s, could not find {%s}", hash_table_err_to_str(rc), str); return NULL; } /* Not found, but we don't care, return NULL */ if (care == CARE_NOT) { /* Return the found NSM Client */ if (isFullDebug(COMPONENT_STATE)) { display_nsm_client(&dspbuf, &key); LogFullDebug(COMPONENT_STATE, "Ignoring {%s}", str); } hashtable_releaselatched(ht_nsm_client, &latch); return NULL; } pclient = gsh_malloc(sizeof(*pclient)); /* Copy everything over */ memcpy(pclient, &key, sizeof(key)); PTHREAD_MUTEX_init(&pclient->ssc_mutex, NULL); /* Need deep copy of caller name */ pclient->ssc_nlm_caller_name = gsh_strdup(key.ssc_nlm_caller_name); glist_init(&pclient->ssc_lock_list); glist_init(&pclient->ssc_share_list); pclient->ssc_refcount = 1; if (op_ctx->client != NULL) { pclient->ssc_client = op_ctx->client; inc_gsh_client_refcount(op_ctx->client); } if (isFullDebug(COMPONENT_STATE)) { display_nsm_client(&dspbuf, pclient); LogFullDebug(COMPONENT_STATE, "New {%s}", str); } buffkey.addr = pclient; buffkey.len = sizeof(*pclient); buffval.addr = pclient; buffval.len = sizeof(*pclient); rc = hashtable_setlatched(ht_nsm_client, &buffval, &buffval, &latch, false, NULL, NULL); /* An error occurred, return NULL */ if (rc != HASHTABLE_SUCCESS) { display_nsm_client(&dspbuf, pclient); LogCrit(COMPONENT_STATE, "Error %s, inserting {%s}", hash_table_err_to_str(rc), str); PTHREAD_MUTEX_destroy(&pclient->ssc_mutex); free_nsm_client(pclient); return NULL; } if (care != CARE_MONITOR || nsm_monitor(pclient)) return pclient; /* Failed to monitor, release client reference * and almost certainly remove it from the hash table. */ dec_nsm_client_ref(pclient); return NULL; } /******************************************************************************* * * NLM Client Routines * ******************************************************************************/ /** * @brief Free an NLM client * * @param[in] client The client to free */ void free_nlm_client(state_nlm_client_t *client) { if (client->slc_nsm_client != NULL) dec_nsm_client_ref(client->slc_nsm_client); gsh_free(client->slc_nlm_caller_name); /* free the callback client */ if (client->slc_callback_clnt != NULL) CLNT_DESTROY(client->slc_callback_clnt); gsh_free(client); } /** * @brief Take a reference on an NLM client * * @param[in] client The client to reference */ void inc_nlm_client_ref(state_nlm_client_t *client) { (void)atomic_inc_int32_t(&client->slc_refcount); } /** * @brief Relinquish a reference on an NLM client * * @param[in] client The client to release */ void dec_nlm_client_ref(state_nlm_client_t *client) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; struct hash_latch latch; hash_error_t rc; struct gsh_buffdesc buffkey; struct gsh_buffdesc old_value; struct gsh_buffdesc old_key; int32_t refcount; if (isDebug(COMPONENT_STATE)) { display_nlm_client(&dspbuf, client); str_valid = true; } refcount = atomic_dec_int32_t(&client->slc_refcount); assert(refcount >= 0); if (refcount > 0) { if (str_valid) LogFullDebug(COMPONENT_STATE, "Decrement slc_refcount now=%" PRId32 " {%s}", refcount, str); return; } if (str_valid) LogFullDebug(COMPONENT_STATE, "Try to remove {%s}", str); buffkey.addr = client; buffkey.len = sizeof(*client); /* Get the hash table entry and hold latch */ rc = hashtable_getlatch(ht_nlm_client, &buffkey, &old_value, true, &latch); /* Since the refcnt is zero, another thread that needs this * entry may delete this nlm client to insert its own nlm * client. So expect not to find this nlm client or find someone * else's nlm client! */ switch (rc) { case HASHTABLE_ERROR_NO_SUCH_KEY: break; case HASHTABLE_SUCCESS: if (old_value.addr == client) { /* our nlm client */ hashtable_deletelatched(ht_nlm_client, &buffkey, &latch, &old_key, &old_value); } break; default: if (!str_valid) display_nlm_client(&dspbuf, client); LogCrit(COMPONENT_STATE, "Error %s, could not find {%s}, client=%p", hash_table_err_to_str(rc), str, client); return; } /* Release the latch */ hashtable_releaselatched(ht_nlm_client, &latch); if (str_valid) LogFullDebug(COMPONENT_STATE, "Free {%s}", str); free_nlm_client(client); } /** * @brief Get an NLM client * * @param[in] care Care status * @param[in] xprt RPC transport * @param[in] nsm_client Related NSM client * @param[in] caller_name Caller name * * @return NLM client or NULL. */ state_nlm_client_t *get_nlm_client(care_t care, SVCXPRT *xprt, state_nsm_client_t *nsm_client, char *caller_name) { state_nlm_client_t key; state_nlm_client_t *pclient; char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; struct hash_latch latch; hash_error_t rc; struct gsh_buffdesc buffkey; struct gsh_buffdesc buffval; sockaddr_t local_addr; socklen_t addr_len; uint32_t refcount; if (caller_name == NULL) return NULL; memset(&key, 0, sizeof(key)); key.slc_nsm_client = nsm_client; key.slc_nlm_caller_name_len = strlen(caller_name); key.slc_client_type = svc_get_xprt_type(xprt); addr_len = sizeof(local_addr); if (getsockname(xprt->xp_fd, (struct sockaddr *)&local_addr, &addr_len) == -1) { LogEvent(COMPONENT_CLIENTID, "Failed to get local addr."); } else { memcpy(&(key.slc_server_addr), &local_addr, sizeof(sockaddr_t)); } if (key.slc_nlm_caller_name_len > LM_MAXSTRLEN) return NULL; key.slc_nlm_caller_name = caller_name; if (isFullDebug(COMPONENT_STATE)) { display_nlm_client(&dspbuf, &key); LogFullDebug(COMPONENT_STATE, "Find {%s}", str); } buffkey.addr = &key; buffkey.len = sizeof(key); rc = hashtable_getlatch(ht_nlm_client, &buffkey, &buffval, true, &latch); switch (rc) { case HASHTABLE_SUCCESS: pclient = buffval.addr; if (isFullDebug(COMPONENT_STATE)) { display_nlm_client(&dspbuf, pclient); LogFullDebug(COMPONENT_STATE, "Found {%s}", str); } refcount = atomic_inc_unless_0_int32_t(&pclient->slc_refcount); if (refcount == 0) { /* This nlm client is in the process of getting * deleted. Let us delete it from the hash table * and pretend as though it isn't found in the * hash table. The thread that is trying to * delete this entry will not find it in the * hash table but will free its nlm client. */ hashtable_deletelatched(ht_nlm_client, &buffkey, &latch, NULL, NULL); goto not_found; } hashtable_releaselatched(ht_nlm_client, &latch); if (care == CARE_MONITOR && !nsm_monitor(nsm_client)) { dec_nlm_client_ref(pclient); pclient = NULL; } return pclient; case HASHTABLE_ERROR_NO_SUCH_KEY: goto not_found; default: display_nlm_client(&dspbuf, &key); LogCrit(COMPONENT_STATE, "Error %s, could not find {%s}", hash_table_err_to_str(rc), str); return NULL; } not_found: /* Not found, but we don't care, return NULL */ if (care == CARE_NOT) { /* Return the found NLM Client */ if (isFullDebug(COMPONENT_STATE)) { display_nlm_client(&dspbuf, &key); LogFullDebug(COMPONENT_STATE, "Ignoring {%s}", str); } hashtable_releaselatched(ht_nlm_client, &latch); return NULL; } pclient = gsh_malloc(sizeof(*pclient)); /* Copy everything over */ memcpy(pclient, &key, sizeof(key)); pclient->slc_nlm_caller_name = gsh_strdup(key.slc_nlm_caller_name); /* Take a reference to the NSM Client */ inc_nsm_client_ref(nsm_client); pclient->slc_refcount = 1; if (isFullDebug(COMPONENT_STATE)) { display_nlm_client(&dspbuf, pclient); LogFullDebug(COMPONENT_STATE, "New {%s}", str); } buffkey.addr = pclient; buffkey.len = sizeof(*pclient); buffval.addr = pclient; buffval.len = sizeof(*pclient); rc = hashtable_setlatched(ht_nlm_client, &buffval, &buffval, &latch, false, NULL, NULL); /* An error occurred, return NULL */ if (rc != HASHTABLE_SUCCESS) { display_nlm_client(&dspbuf, pclient); LogCrit(COMPONENT_STATE, "Error %s, inserting {%s}", hash_table_err_to_str(rc), str); free_nlm_client(pclient); return NULL; } if (care != CARE_MONITOR || nsm_monitor(nsm_client)) return pclient; /* Failed to monitor, release client reference * and almost certainly remove it from the hash table. */ dec_nlm_client_ref(pclient); return NULL; } /******************************************************************************* * * NLM Owner Routines * ******************************************************************************/ /** * @brief Free an NLM owner object * * @param[in] owner Stored owner */ void free_nlm_owner(state_owner_t *owner) { if (owner->so_owner.so_nlm_owner.so_client != NULL) dec_nlm_client_ref(owner->so_owner.so_nlm_owner.so_client); } /** * @brief Initialize an NLM owner object * * @param[in] owner Stored owner */ static void init_nlm_owner(state_owner_t *owner) { owner->so_refcount = 1; inc_nlm_client_ref(owner->so_owner.so_nlm_owner.so_client); glist_init(&owner->so_owner.so_nlm_owner.so_nlm_shares); } /** * @brief Get an NLM owner * * @param[in] care Care status * @param[in] client Related NLM client * @param[in] oh Object handle * @param[in] svid Owner ID */ state_owner_t *get_nlm_owner(care_t care, state_nlm_client_t *client, netobj *oh, uint32_t svid) { state_owner_t key; if (client == NULL || oh == NULL || oh->n_len > MAX_NETOBJ_SZ) return NULL; memset(&key, 0, sizeof(key)); key.so_type = STATE_LOCK_OWNER_NLM; key.so_owner.so_nlm_owner.so_client = client; key.so_owner.so_nlm_owner.so_nlm_svid = svid; key.so_owner_len = oh->n_len; key.so_owner_val = oh->n_bytes; return get_state_owner(care, &key, init_nlm_owner, NULL); } /** @} */ nfs-ganesha-6.5/src/SAL/nlm_state.c000066400000000000000000000302141473756622300171350ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright Panasas Inc (2015) * contributor: Frank S Filz ffilzlnx@mindspring.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file nlm_state.c * @brief The management of the NLM state caches. */ #include "config.h" #include #include #include #include "city.h" #include "sal_functions.h" #include "nsm.h" #include "log.h" #include "fsal.h" /** * @brief NLM States */ hash_table_t *ht_nlm_states; /******************************************************************************* * * NLM State Routines * ******************************************************************************/ /** * @brief Display an NLM State * * @param[in/out] dspbuf display_buffer describing output string * @param[in] key The NLM State * * @return the bytes remaining in the buffer. */ int display_nlm_state(struct display_buffer *dspbuf, state_t *key) { int b_left; if (key == NULL) return display_printf(dspbuf, "NLM State "); b_left = display_printf(dspbuf, "NLM State %p: ", key); if (b_left <= 0) return b_left; return b_left; } /** * @brief Display an NLM State in the hash table * * @param[in] dspbuf display buffer to display into * @param[in] buff The key or val */ int display_nlm_state_key_val(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_nlm_state(dspbuf, buff->addr); } /** * @brief Compare NLM States * * @param[in] state1 An NLM State * @param[in] state2 Another NLM State * * @retval 0 on equality. * @retval 1 on inequality. */ int compare_nlm_state(state_t *state1, state_t *state2) { if (isFullDebug(COMPONENT_STATE) && isDebug(COMPONENT_HASHTABLE)) { char str1[LOG_BUFF_LEN / 2] = "\0"; char str2[LOG_BUFF_LEN / 2] = "\0"; struct display_buffer dspbuf1 = { sizeof(str1), str1, str1 }; struct display_buffer dspbuf2 = { sizeof(str2), str2, str2 }; display_nlm_state(&dspbuf1, state1); display_nlm_state(&dspbuf2, state2); LogFullDebug(COMPONENT_STATE, "{%s} vs {%s}", str1, str2); } if (state1 == NULL || state2 == NULL) return 1; if (state1 == state2) return 0; return state1->state_type != state2->state_type || state1->state_owner != state2->state_owner || state1->state_export != state2->state_export || state1->state_obj != state2->state_obj; } /** * @brief Compare NLM States in the hash table * * @param[in] buff1 A key * @param[in] buff2 Another key * * @retval 0 on equality. * @retval 1 on inequality. */ int compare_nlm_state_key(struct gsh_buffdesc *buff1, struct gsh_buffdesc *buff2) { return compare_nlm_state(buff1->addr, buff2->addr); } /** * @brief Calculate hash index for an NSM key * * @todo Replace with a good hash function. * * @param[in] hparam Hash params * @param[out] key Key to hash * * @return The hash index. */ uint32_t nlm_state_value_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { state_t *pkey = key->addr; uint64_t hk; char *addr = (char *)&pkey->state_owner; /* We hash based on the owner pointer, and the object key. This depends * on them being sequential in memory. */ hk = CityHash64WithSeed( addr, sizeof(pkey->state_owner) + sizeof(pkey->state_obj), 557); if (pkey->state_type == STATE_TYPE_NLM_SHARE) hk = ~hk; if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "value = %" PRIx32, (uint32_t)(hk % hparam->index_size)); return hk % hparam->index_size; } /** * @brief Calculate RBT hash for an NSM key * * @todo Replace with a good hash function. * * @param[in] hparam Hash params * @param[out] key Key to hash * * @return The RBT hash. */ uint64_t nlm_state_rbt_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { state_t *pkey = key->addr; uint64_t hk; char *addr = (char *)&pkey->state_owner; /* We hash based on the owner pointer, and the object key. This depends * on them being sequential in memory. */ hk = CityHash64WithSeed( addr, sizeof(pkey->state_owner) + sizeof(pkey->state_obj), 557); if (pkey->state_type == STATE_TYPE_NLM_SHARE) hk = ~hk; if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "value = %" PRIx64, hk % hparam->index_size); return hk % hparam->index_size; } static hash_parameter_t nlm_state_hash_param = { .index_size = PRIME_STATE, .hash_func_key = nlm_state_value_hash_func, .hash_func_rbt = nlm_state_rbt_hash_func, .compare_key = compare_nlm_state_key, .display_key = display_nlm_state_key_val, .display_val = display_nlm_state_key_val, .flags = HT_FLAG_NONE, }; /** * @brief Init the hashtables for NLM state support * * @return 0 if successful, -1 otherwise */ int Init_nlm_state_hash(void) { ht_nlm_states = hashtable_init(&nlm_state_hash_param); if (ht_nlm_states == NULL) { LogCrit(COMPONENT_STATE, "Cannot init NLM States cache"); return -1; } return 0; } /** * @brief Relinquish a reference on an NLM State * * @param[in] state The NLM State to release */ void dec_nlm_state_ref(state_t *state) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; struct hash_latch latch; hash_error_t rc; struct gsh_buffdesc buffkey; struct gsh_buffdesc old_value; int32_t refcount; struct fsal_obj_handle *obj; if (isDebug(COMPONENT_STATE)) { display_nlm_state(&dspbuf, state); str_valid = true; } refcount = atomic_dec_int32_t(&state->state_refcount); assert(refcount >= 0); if (refcount > 0) { if (str_valid) LogFullDebug(COMPONENT_STATE, "Decrement state_refcount now=%d {%s}", refcount, str); return; } if (str_valid) LogFullDebug(COMPONENT_STATE, "Try to remove {%s}", str); buffkey.addr = state; buffkey.len = sizeof(*state); /* Get the hash table entry and hold latch */ rc = hashtable_getlatch(ht_nlm_states, &buffkey, &old_value, true, &latch); /* Another thread that needs this entry might have deleted this * nlm state to insert its own nlm state. So expect not to find * this nlm state or find someone else's nlm state! */ switch (rc) { case HASHTABLE_SUCCESS: if (old_value.addr == state) { /* our own state */ hashtable_deletelatched(ht_nlm_states, &buffkey, &latch, NULL, NULL); } break; case HASHTABLE_ERROR_NO_SUCH_KEY: break; default: if (!str_valid) display_nlm_state(&dspbuf, state); LogCrit(COMPONENT_STATE, "Error %s, could not find {%s}", hash_table_err_to_str(rc), str); return; } /* Release the latch */ hashtable_releaselatched(ht_nlm_states, &latch); LogFullDebug(COMPONENT_STATE, "Free {%s}", str); put_gsh_export(state->state_export); obj = get_state_obj_ref(state); if (obj == NULL) { LogDebug(COMPONENT_STATE, "Entry for state is stale"); } else { /* We need to close the state before freeing the state. */ (void)obj->obj_ops->close2(obj, state); /* Release the reference just taken */ obj->obj_ops->put_ref(obj); } PTHREAD_MUTEX_destroy(&state->state_mutex); free_state(state); if (obj != NULL) { /* Release the active reference */ obj->obj_ops->put_ref(obj); } } /** * @brief Get an NLM State * * @param[in] state_type type of state (LOCK or SHARE) * @param[in] state_obj FSAL obj state applies to * @param[in] state_owner NLM owner of the state * @param[in] care Indicates to what degree the caller cares about * actually getting a state. * @param[in] nsm_state NSM state value for locks, only valid when * care == CARE_MONITOR * @param[out] pstate Pointer to return the found state in * * @return NLM Status code or 0 if no special return */ int get_nlm_state(enum state_type state_type, struct fsal_obj_handle *state_obj, state_owner_t *state_owner, care_t care, uint32_t nsm_state, state_t **pstate) { state_t key; state_t *state; char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; struct hash_latch latch; hash_error_t rc; struct gsh_buffdesc buffkey; struct gsh_buffdesc buffval; *pstate = NULL; memset(&key, 0, sizeof(key)); key.state_type = state_type; key.state_owner = state_owner; key.state_export = op_ctx->ctx_export; key.state_seqid = nsm_state; key.state_obj = state_obj; if (isFullDebug(COMPONENT_STATE)) { display_nlm_state(&dspbuf, &key); LogFullDebug(COMPONENT_STATE, "Find {%s}", str); } buffkey.addr = &key; buffkey.len = sizeof(key); rc = hashtable_getlatch(ht_nlm_states, &buffkey, &buffval, true, &latch); switch (rc) { case HASHTABLE_SUCCESS: state = buffval.addr; if (care == CARE_MONITOR && state->state_seqid != nsm_state) { /* We are getting new locks before the old ones * are gone. We need to unhash this state_t and * create a new one. * * Keep the latch after the delete to proceed with * the new insert. */ hashtable_deletelatched(ht_nlm_states, &buffkey, &latch, NULL, NULL); break; } if (atomic_inc_unless_0_int32_t(&state->state_refcount) == 0) { /* The state is in the process of getting * deleted. Delete from the hashtable and * pretend as though we didn't find it. */ hashtable_deletelatched(ht_nlm_states, &buffkey, &latch, NULL, NULL); break; } /* Return the found NLM State */ if (isFullDebug(COMPONENT_STATE)) { display_nlm_state(&dspbuf, state); LogFullDebug(COMPONENT_STATE, "Found {%s}", str); } hashtable_releaselatched(ht_nlm_states, &latch); *pstate = state; return 0; case HASHTABLE_ERROR_NO_SUCH_KEY: break; default: /* An error occurred, return NULL */ display_nlm_state(&dspbuf, &key); LogCrit(COMPONENT_STATE, "Error %s, could not find {%s}", hash_table_err_to_str(rc), str); *pstate = NULL; return NLM4_DENIED_NOLOCKS; } /* If we don't care at all, or only care about owner, we don't want to * create a new state. */ if (care == CARE_NOT || care == CARE_OWNER) { hashtable_releaselatched(ht_nlm_states, &latch); return 0; } state = op_ctx->fsal_export->exp_ops.alloc_state(op_ctx->fsal_export, state_type, NULL); /* Copy everything over */ state->state_obj = state_obj; state->state_owner = state_owner; state->state_export = op_ctx->ctx_export; state->state_seqid = nsm_state; PTHREAD_MUTEX_init(&state->state_mutex, NULL); if (state_type == STATE_TYPE_NLM_LOCK) glist_init(&state->state_data.lock.state_locklist); state->state_refcount = 1; if (isFullDebug(COMPONENT_STATE)) { display_nlm_state(&dspbuf, state); LogFullDebug(COMPONENT_STATE, "New {%s}", str); } buffkey.addr = state; buffkey.len = sizeof(*state); buffval.addr = state; buffval.len = sizeof(*state); /* Get active ref for this state entry */ state_obj->obj_ops->get_ref(state_obj); rc = hashtable_setlatched(ht_nlm_states, &buffval, &buffval, &latch, false, NULL, NULL); /* An error occurred, return NULL */ if (rc != HASHTABLE_SUCCESS) { display_nlm_state(&dspbuf, state); LogCrit(COMPONENT_STATE, "Error %s, inserting {%s}", hash_table_err_to_str(rc), str); PTHREAD_MUTEX_destroy(&state->state_mutex); /* Free the ref taken above and the state. * No need to close here, the state was never opened. */ free_state(state); state_obj->obj_ops->put_ref(state_obj); *pstate = NULL; return NLM4_DENIED_NOLOCKS; } get_gsh_export_ref(state->state_export); *pstate = state; return 0; } nfs-ganesha-6.5/src/SAL/recovery/000077500000000000000000000000001473756622300166415ustar00rootroot00000000000000nfs-ganesha-6.5/src/SAL/recovery/recovery_fs.c000066400000000000000000000637221473756622300213450ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * This program 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 3 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 * 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 * */ #include "config.h" #include "log.h" #include "nfs_core.h" #include "nfs4.h" #include "sal_functions.h" #include #include #include #include #include "bsd-base64.h" #include "client_mgr.h" #include "fsal.h" #include "recovery_fs.h" char v4_recov_dir[PATH_MAX]; unsigned int v4_recov_dir_len; char v4_old_dir[PATH_MAX]; unsigned int v4_old_dir_len; /** * @brief convert clientid opaque bytes as a hex string for mkdir purpose. * * @param[in,out] dspbuf The buffer. * @param[in] value The bytes to display * @param[in] len The number of bytes to display * * @return the bytes remaining in the buffer. * */ static int fs_convert_opaque_value_max_for_dir(struct display_buffer *dspbuf, void *value, int len, int max) { unsigned int i = 0; int b_left = display_start(dspbuf); int cpy = len; if (b_left <= 0) return 0; /* Check that the length is ok * If the value is empty, display EMPTY value. */ if (len <= 0 || len > max) return 0; /* If the value is NULL, display NULL value. */ if (value == NULL) return 0; /* Determine if the value is entirely printable characters, */ /* and it contains no slash character (reserved for filename) */ for (i = 0; i < len; i++) if ((!isprint(((char *)value)[i])) || (((char *)value)[i] == '/')) break; if (i == len) { /* Entirely printable character, so we will just copy the * characters into the buffer (to the extent there is room * for them). */ b_left = display_len_cat(dspbuf, value, cpy); } else { b_left = display_opaque_bytes(dspbuf, value, cpy); } if (b_left <= 0) return 0; return b_left; } /** * @brief generate a name that identifies this client * * This name will be used to know that a client was talking to the * server before a restart so that it will be allowed to do reclaims * during grace period. * * @param[in] clientid Client record */ static void fs_create_clid_name(nfs_client_id_t *clientid) { nfs_client_record_t *cl_rec = clientid->cid_client_record; const char *str_client_addr = "(unknown)"; char cidstr[PATH_MAX] = { 0, }; struct display_buffer dspbuf = { sizeof(cidstr), cidstr, cidstr }; char cidstr_lenx[5]; int total_size, cidstr_lenx_len, cidstr_len, str_client_addr_len; /* get the caller's IP addr */ if (clientid->gsh_client != NULL) str_client_addr = clientid->gsh_client->hostaddr_str; if (fs_convert_opaque_value_max_for_dir(&dspbuf, cl_rec->cr_client_val, cl_rec->cr_client_val_len, PATH_MAX) > 0) { cidstr_len = strlen(cidstr); str_client_addr_len = strlen(str_client_addr); /* fs_convert_opaque_value_max_for_dir does not prefix * the "(:". So we need to do it here */ cidstr_lenx_len = snprintf(cidstr_lenx, sizeof(cidstr_lenx), "%d", cidstr_len); if (unlikely(cidstr_lenx_len >= sizeof(cidstr_lenx) || cidstr_lenx_len < 0)) { /* cidrstr can at most be PATH_MAX or 1024, so at most * 4 characters plus NUL are necessary, so we won't * overrun, nor can we get a -1 with EOVERFLOW or EINVAL */ LogFatal(COMPONENT_CLIENTID, "snprintf returned unexpected %d", cidstr_lenx_len); } total_size = cidstr_len + str_client_addr_len + 5 + cidstr_lenx_len; /* hold both long form clientid and IP */ clientid->cid_recov_tag = gsh_malloc(total_size); /* Can't overrun and shouldn't return EOVERFLOW or EINVAL */ (void)snprintf(clientid->cid_recov_tag, total_size, "%s-(%s:%s)", str_client_addr, cidstr_lenx, cidstr); } LogDebug(COMPONENT_CLIENTID, "Created client name [%s]", clientid->cid_recov_tag); } int fs_create_recov_dir(void) { int err, root_len, dir_len, old_len, node_size = 0; char node[15]; /* Note below - all the size tests below are >= which assures space for * the terminating NUL. */ if (nfs_param.core_param.clustered) { node_size = snprintf(node, sizeof(node), "/node%d", g_nodeid); if (unlikely(node_size >= sizeof(node) || node_size < 0)) { LogFatal(COMPONENT_CLIENTID, "snprintf returned unexpected %d", node_size); } } err = mkdir(nfs_param.nfsv4_param.recov_root, 0755); if (err == -1 && errno != EEXIST) { LogEvent( COMPONENT_CLIENTID, "Failed to create v4 recovery dir (%s), errno: %s (%d)", nfs_param.nfsv4_param.recov_root, strerror(errno), errno); } root_len = strlen(nfs_param.nfsv4_param.recov_root); dir_len = strlen(nfs_param.nfsv4_param.recov_dir); /* If not clustered: root + '/' + dir (nodesize = 0) * If clustered: root + '/' + dir + "/node%d" (nodesize != 0) */ v4_recov_dir_len = root_len + 1 + dir_len + node_size; if (v4_recov_dir_len >= sizeof(v4_recov_dir)) LogFatal(COMPONENT_CLIENTID, "v4 recovery dir path (%s/%s) is too long", nfs_param.nfsv4_param.recov_root, nfs_param.nfsv4_param.recov_dir); memcpy(v4_recov_dir, nfs_param.nfsv4_param.recov_root, root_len); v4_recov_dir[root_len] = '/'; memcpy(v4_recov_dir + 1 + root_len, nfs_param.nfsv4_param.recov_dir, dir_len + 1); dir_len = 1 + root_len + dir_len; LogDebug(COMPONENT_CLIENTID, "v4_recov_dir=%s", v4_recov_dir); err = mkdir(v4_recov_dir, 0755); if (err == -1 && errno != EEXIST) { LogEvent(COMPONENT_CLIENTID, "Failed to create v4 recovery dir(%s), errno: %s (%d)", v4_recov_dir, strerror(errno), errno); } root_len = strlen(nfs_param.nfsv4_param.recov_root); old_len = strlen(nfs_param.nfsv4_param.recov_old_dir); /* If not clustered: root + '/' + old (nodesize = 0) * If clustered: root + '/' + old + "/node%d" (nodesize != 0) */ v4_old_dir_len = root_len + 1 + old_len + node_size; if (v4_old_dir_len >= sizeof(v4_old_dir)) LogFatal(COMPONENT_CLIENTID, "v4 recovery dir path (%s/%s) is too long", nfs_param.nfsv4_param.recov_root, nfs_param.nfsv4_param.recov_old_dir); memcpy(v4_old_dir, nfs_param.nfsv4_param.recov_root, root_len); v4_old_dir[root_len] = '/'; memcpy(v4_old_dir + 1 + root_len, nfs_param.nfsv4_param.recov_old_dir, old_len + 1); old_len = 1 + root_len + old_len; LogDebug(COMPONENT_CLIENTID, "v4_old_dir=%s", v4_old_dir); err = mkdir(v4_old_dir, 0755); if (err == -1 && errno != EEXIST) { LogEvent(COMPONENT_CLIENTID, "Failed to create v4 recovery dir(%s), errno: %s (%d)", v4_old_dir, strerror(errno), errno); } if (nfs_param.core_param.clustered) { /* Now make the node specific directory. * Note that node already includes the '/' path separator. */ /* Copy an extra byte to NUL terminate */ memcpy(v4_recov_dir + dir_len, node, node_size + 1); memcpy(v4_old_dir + old_len, node, node_size + 1); LogDebug(COMPONENT_CLIENTID, "v4_recov_dir=%s", v4_recov_dir); LogDebug(COMPONENT_CLIENTID, "v4_old_dir=%s", v4_old_dir); err = mkdir(v4_recov_dir, 0755); if (err == -1 && errno != EEXIST) { LogEvent( COMPONENT_CLIENTID, "Failed to create v4 recovery dir(%s), errno: %s (%d)", v4_recov_dir, strerror(errno), errno); } err = mkdir(v4_old_dir, 0755); if (err == -1 && errno != EEXIST) { LogEvent( COMPONENT_CLIENTID, "Failed to create v4 recovery dir(%s), errno: %s (%d)", v4_old_dir, strerror(errno), errno); } } LogInfo(COMPONENT_CLIENTID, "NFSv4 Recovery Directory %s", v4_recov_dir); LogInfo(COMPONENT_CLIENTID, "NFSv4 Recovery Directory (old) %s", v4_old_dir); return 0; } void fs_add_clid(nfs_client_id_t *clientid) { int err = 0; char path[PATH_MAX] = { 0 }; int length, position; int pathpos = strlen(v4_recov_dir); fs_create_clid_name(clientid); /* break clientid down if it is greater than max dir name */ /* and create a directory hierarchy to represent the clientid. */ memcpy(path, v4_recov_dir, pathpos + 1); length = strlen(clientid->cid_recov_tag); for (position = 0; position < length; position += NAME_MAX) { /* if the (remaining) clientid is shorter than 255 */ /* create the last level of dir and break out */ int len = length - position; /* No matter what, we need a '/' */ path[pathpos++] = '/'; /* Make sure there's still room in path */ if ((pathpos + len) >= sizeof(path)) { errno = ENOMEM; err = -1; break; } if (len <= NAME_MAX) { memcpy(path + pathpos, clientid->cid_recov_tag + position, len + 1); err = mkdir(path, 0700); break; } /* if (remaining) clientid is longer than 255, */ /* get the next 255 bytes and create a subdir */ memcpy(path + pathpos, clientid->cid_recov_tag + position, NAME_MAX); pathpos += NAME_MAX; path[pathpos] = '\0'; err = mkdir(path, 0700); if (err == -1 && errno != EEXIST) break; } if (err == -1 && errno != EEXIST) { LogEvent( COMPONENT_CLIENTID, "Failed to create client in recovery dir (%s), errno: %s (%d)", path, strerror(errno), errno); } else { LogDebug(COMPONENT_CLIENTID, "Created client dir [%s]", path); } } /** * @brief Remove the revoked file handles created under a specific * client-id path on the stable storage. * * @param[in] path The path of the client-id on the stable storage. */ static void fs_rm_revoked_handles(char *path) { DIR *dp; struct dirent *dentp; char del_path[PATH_MAX]; dp = opendir(path); if (dp == NULL) { LogEvent(COMPONENT_CLIENTID, "opendir %s failed errno: %s (%d)", path, strerror(errno), errno); return; } for (dentp = readdir(dp); dentp != NULL; dentp = readdir(dp)) { int rc; if (!strcmp(dentp->d_name, ".") || !strcmp(dentp->d_name, "..") || dentp->d_name[0] != '\x1') { continue; } rc = snprintf(del_path, sizeof(del_path), "%s/%s", path, dentp->d_name); if (unlikely(rc >= sizeof(del_path))) { LogCrit(COMPONENT_CLIENTID, "Path %s/%s too long", path, dentp->d_name); } else if (unlikely(rc < 0)) { LogCrit(COMPONENT_CLIENTID, "Unexpected return from snprintf %d error %s (%d)", rc, strerror(errno), errno); } else if (unlink(del_path) < 0) { LogEvent(COMPONENT_CLIENTID, "unlink of %s failed errno: %s (%d)", del_path, strerror(errno), errno); } } (void)closedir(dp); } static void fs_rm_clid_impl(int position, char *recov_dir, int len, char *parent_path, int parent_len) { int err; char *path; int segment_len; int total_len; if (position == len) { /* We are at the tail directory of the clid, * remove revoked handles, if any. */ fs_rm_revoked_handles(parent_path); return; } if ((len - position) > NAME_MAX) segment_len = NAME_MAX; else segment_len = len - position; /* allocate enough memory for the new part of the string * which is parent path + '/' + new segment */ total_len = parent_len + segment_len + 2; path = gsh_malloc(total_len); memcpy(path, parent_path, parent_len); path[parent_len] = '/'; memcpy(path + parent_len + 1, recov_dir + position, segment_len); path[total_len - 1] = '\0'; /* recursively remove the directory hirerchy which represent the *clientid */ fs_rm_clid_impl(position + segment_len, recov_dir, len, path, total_len - 1); err = rmdir(path); if (err == -1) { LogEvent( COMPONENT_CLIENTID, "Failed to remove client recovery dir (%s), errno: %s (%d)", path, strerror(errno), errno); } else { LogDebug(COMPONENT_CLIENTID, "Removed client dir (%s)", path); } gsh_free(path); } void fs_rm_clid(nfs_client_id_t *clientid) { char *recov_dir = clientid->cid_recov_tag; if (recov_dir == NULL) return; clientid->cid_recov_tag = NULL; fs_rm_clid_impl(0, recov_dir, strlen(recov_dir), v4_recov_dir, v4_recov_dir_len); gsh_free(recov_dir); } /** * @brief Copy and Populate revoked delegations for this client. * * Even after delegation revoke, it is possible for the client to * continue its lease and other operations. Sever saves revoked delegations * in the memory so client will not be granted same delegation with * DELEG_CUR ; but it is possible that the server might reboot and has * no record of the delegatin. This list helps to reject delegations * client is obtaining through DELEG_PREV. * * @param[in] clientid The clientid that is being created. * @param[in] path The path of the directory structure. * @param[in] Target dir to copy. * @param[in] del Delete after populating */ static void fs_cp_pop_revoked_delegs(clid_entry_t *clid_ent, char *path, char *tgtdir, bool del, add_rfh_entry_hook add_rfh_entry) { struct dirent *dentp; DIR *dp; rdel_fh_t *new_ent; /* Read the contents from recov dir of this clientid. */ dp = opendir(path); if (dp == NULL) { LogEvent(COMPONENT_CLIENTID, "opendir %s failed errno: %s (%d)", path, strerror(errno), errno); return; } for (dentp = readdir(dp); dentp != NULL; dentp = readdir(dp)) { if (!strcmp(dentp->d_name, ".") || !strcmp(dentp->d_name, "..")) continue; /* All the revoked filehandles stored with \x1 prefix */ if (dentp->d_name[0] != '\x1') { /* Something wrong; it should not happen */ LogMidDebug( COMPONENT_CLIENTID, "%s showed up along with revoked FHs. Skipping", dentp->d_name); continue; } if (tgtdir) { char lopath[PATH_MAX]; int fd, rc; rc = snprintf(lopath, sizeof(lopath), "%s/%s", tgtdir, dentp->d_name); if (unlikely(rc >= sizeof(lopath))) { LogCrit(COMPONENT_CLIENTID, "Path %s/%s too long", tgtdir, dentp->d_name); } else if (unlikely(rc < 0)) { LogCrit(COMPONENT_CLIENTID, "Unexpected return from snprintf %d error %s (%d)", rc, strerror(errno), errno); } else { fd = creat(lopath, 0700); if (fd < 0) { LogEvent( COMPONENT_CLIENTID, "Failed to copy revoked handle file %s to %s errno: %s(%d)", dentp->d_name, tgtdir, strerror(errno), errno); } else { close(fd); } } } /* Ignore the beginning \x1 and copy the rest (file handle) */ new_ent = add_rfh_entry(clid_ent, dentp->d_name + 1); LogFullDebug(COMPONENT_CLIENTID, "revoked handle: %s", new_ent->rdfh_handle_str); /* Since the handle is loaded into memory, go ahead and * delete it from the stable storage. */ if (del) { char del_path[PATH_MAX]; int rc; rc = snprintf(del_path, sizeof(del_path), "%s/%s", path, dentp->d_name); if (unlikely(rc >= sizeof(del_path))) { LogCrit(COMPONENT_CLIENTID, "Path %s/%s too long", path, dentp->d_name); } else if (unlikely(rc < 0)) { LogCrit(COMPONENT_CLIENTID, "Unexpected return from snprintf %d error %s (%d)", rc, strerror(errno), errno); } else if (unlink(del_path) < 0) { LogEvent(COMPONENT_CLIENTID, "unlink of %s failed errno: %s (%d)", del_path, strerror(errno), errno); } } } (void)closedir(dp); } /** * @brief Create the client reclaim list * * When not doing a take over, first open the old state dir and read * in those entries. The reason for the two directories is in case of * a reboot/restart during grace period. Next, read in entries from * the recovery directory and then move them into the old state * directory. if called due to a take over, nodeid will be nonzero. * in this case, add that node's clientids to the existing list. Then * move those entries into the old state directory. * * @param[in] dp Recovery directory * @param[in] srcdir Path to the source directory on failover * @param[in] takeover Whether this is a takeover. * * @return POSIX error codes. */ static int fs_read_recov_clids_impl(const char *parent_path, char *clid_str, char *tgtdir, int takeover, add_clid_entry_hook add_clid_entry, add_rfh_entry_hook add_rfh_entry) { struct dirent *dentp; DIR *dp; clid_entry_t *new_ent; char *sub_path = NULL; char *new_path = NULL; char *build_clid = NULL; int rc = 0; int num = 0; char *ptr, *ptr2; char temp[10]; int cid_len, len; int segment_len; int total_clid_len; int clid_str_len = (clid_str == NULL) ? 0 : strlen(clid_str); dp = opendir(parent_path); if (dp == NULL) { LogEvent(COMPONENT_CLIENTID, "Failed to open v4 recovery dir (%s), errno: %s (%d)", parent_path, strerror(errno), errno); return -1; } for (dentp = readdir(dp); dentp != NULL; dentp = readdir(dp)) { /* don't add '.' and '..' entry */ if (!strcmp(dentp->d_name, ".") || !strcmp(dentp->d_name, "..")) continue; /* Skip names that start with '\x1' as they are files * representing revoked file handles */ if (dentp->d_name[0] == '\x1') continue; num++; new_path = NULL; /* construct the path by appending the subdir for the * next readdir. This recursion keeps reading the * subdirectory until reaching the end. */ segment_len = strlen(dentp->d_name); sub_path = gsh_concat_sep(parent_path, '/', dentp->d_name); /* if tgtdir is not NULL, we need to build * nfs4old/currentnode */ if (tgtdir) { new_path = gsh_concat_sep(tgtdir, '/', dentp->d_name); rc = mkdir(new_path, 0700); if ((rc == -1) && (errno != EEXIST)) { LogEvent(COMPONENT_CLIENTID, "mkdir %s failed errno: %s (%d)", new_path, strerror(errno), errno); } } /* keep building the clientid str by recursively */ /* reading the directory structure */ total_clid_len = segment_len + 1 + clid_str_len; build_clid = gsh_malloc(total_clid_len); if (clid_str) memcpy(build_clid, clid_str, clid_str_len); memcpy(build_clid + clid_str_len, dentp->d_name, segment_len + 1); rc = fs_read_recov_clids_impl(sub_path, build_clid, new_path, takeover, add_clid_entry, add_rfh_entry); gsh_free(new_path); /* after recursion, if the subdir has no non-hidden * directory this is the end of this clientid str. Add * the clientstr to the list. */ if (rc == 0) { /* the clid format is * -(clid-len:long-form-clid-in-string-form) * make sure this reconstructed string is valid * by comparing clid-len and the actual * long-form-clid length in the string. This is * to prevent getting incompleted strings that * might exist due to program crash. */ if (total_clid_len >= PATH_MAX) { LogEvent(COMPONENT_CLIENTID, "invalid clid format: %s, too long", build_clid); gsh_free(sub_path); gsh_free(build_clid); continue; } ptr = strchr(build_clid, '('); if (ptr == NULL) { LogEvent(COMPONENT_CLIENTID, "invalid clid format: %s", build_clid); gsh_free(sub_path); gsh_free(build_clid); continue; } ptr2 = strchr(ptr, ':'); if (ptr2 == NULL) { LogEvent(COMPONENT_CLIENTID, "invalid clid format: %s", build_clid); gsh_free(sub_path); gsh_free(build_clid); continue; } len = ptr2 - ptr - 1; if (len >= 9) { LogEvent(COMPONENT_CLIENTID, "invalid clid format: %s", build_clid); gsh_free(sub_path); gsh_free(build_clid); continue; } memcpy(temp, ptr + 1, len + 1); cid_len = atoi(temp); len = strlen(ptr2); if ((len == (cid_len + 2)) && (ptr2[len - 1] == ')')) { new_ent = add_clid_entry(build_clid); fs_cp_pop_revoked_delegs(new_ent, sub_path, tgtdir, !takeover, add_rfh_entry); LogDebug(COMPONENT_CLIENTID, "added %s to clid list", new_ent->cl_name); } } gsh_free(build_clid); /* If this is not for takeover, remove the directory * hierarchy that represent the current clientid */ if (!takeover) { rc = rmdir(sub_path); if (rc == -1) { LogEvent(COMPONENT_CLIENTID, "Failed to rmdir (%s), errno: %s (%d)", sub_path, strerror(errno), errno); } } gsh_free(sub_path); } (void)closedir(dp); return num; } static void fs_read_recov_clids_recover(add_clid_entry_hook add_clid_entry, add_rfh_entry_hook add_rfh_entry) { int rc; rc = fs_read_recov_clids_impl(v4_old_dir, NULL, NULL, 0, add_clid_entry, add_rfh_entry); if (rc == -1) { LogEvent(COMPONENT_CLIENTID, "Failed to read v4 recovery dir (%s)", v4_old_dir); return; } rc = fs_read_recov_clids_impl(v4_recov_dir, NULL, v4_old_dir, 0, add_clid_entry, add_rfh_entry); if (rc == -1) { LogEvent(COMPONENT_CLIENTID, "Failed to read v4 recovery dir (%s)", v4_recov_dir); return; } } /** * @brief Load clients for recovery, with no lock * * @param[in] nodeid Node, on takeover */ void fs_read_recov_clids_takeover(nfs_grace_start_t *gsp, add_clid_entry_hook add_clid_entry, add_rfh_entry_hook add_rfh_entry) { int rc; char path[PATH_MAX]; if (!gsp) { fs_read_recov_clids_recover(add_clid_entry, add_rfh_entry); return; } switch (gsp->event) { case EVENT_UPDATE_CLIENTS: rc = snprintf(path, sizeof(path), "%s", v4_recov_dir); if (unlikely(rc >= sizeof(path))) { LogCrit(COMPONENT_CLIENTID, "Path %s too long", v4_recov_dir); return; } else if (unlikely(rc < 0)) { LogCrit(COMPONENT_CLIENTID, "Unexpected return from snprintf %d error %s (%d)", rc, strerror(errno), errno); } break; case EVENT_TAKE_IP: rc = snprintf(path, sizeof(path), "%s/%s/%s", nfs_param.nfsv4_param.recov_root, gsp->ipaddr, nfs_param.nfsv4_param.recov_dir); if (unlikely(rc >= sizeof(path))) { LogCrit(COMPONENT_CLIENTID, "Path %s/%s/%s too long", nfs_param.nfsv4_param.recov_root, gsp->ipaddr, nfs_param.nfsv4_param.recov_dir); return; } else if (unlikely(rc < 0)) { LogCrit(COMPONENT_CLIENTID, "Unexpected return from snprintf %d error %s (%d)", rc, strerror(errno), errno); } break; case EVENT_TAKE_NODEID: rc = snprintf(path, sizeof(path), "%s/%s/node%d", nfs_param.nfsv4_param.recov_root, nfs_param.nfsv4_param.recov_dir, gsp->nodeid); if (unlikely(rc >= sizeof(path))) { LogCrit(COMPONENT_CLIENTID, "Path %s/%s/node%d too long", nfs_param.nfsv4_param.recov_root, nfs_param.nfsv4_param.recov_dir, gsp->nodeid); return; } else if (unlikely(rc < 0)) { LogCrit(COMPONENT_CLIENTID, "Unexpected return from snprintf %d error %s (%d)", rc, strerror(errno), errno); } break; default: LogWarn(COMPONENT_STATE, "Recovery unknown event"); return; } LogEvent(COMPONENT_CLIENTID, "Recovery for nodeid %d dir (%s)", gsp->nodeid, path); rc = fs_read_recov_clids_impl(path, NULL, v4_old_dir, 1, add_clid_entry, add_rfh_entry); if (rc == -1) { LogEvent(COMPONENT_CLIENTID, "Failed to read v4 recovery dir (%s)", path); return; } } void fs_clean_old_recov_dir_impl(char *parent_path) { DIR *dp; struct dirent *dentp; char *path = NULL; int rc; dp = opendir(parent_path); if (dp == NULL) { LogEvent( COMPONENT_CLIENTID, "Failed to open old v4 recovery dir (%s), errno: %s (%d)", v4_old_dir, strerror(errno), errno); return; } for (dentp = readdir(dp); dentp != NULL; dentp = readdir(dp)) { /* don't remove '.' and '..' entry */ if (!strcmp(dentp->d_name, ".") || !strcmp(dentp->d_name, "..")) continue; /* Assemble the path */ path = gsh_concat_sep(parent_path, '/', dentp->d_name); /* If there is a filename starting with '\x1', then it is * a revoked handle, go ahead and remove it. */ if (dentp->d_name[0] == '\x1') { if (unlink(path) < 0) { LogEvent(COMPONENT_CLIENTID, "unlink of %s failed errno: %s (%d)", path, strerror(errno), errno); } } else { /* This is a directory, we need process files in it! */ fs_clean_old_recov_dir_impl(path); rc = rmdir(path); if (rc == -1) { LogEvent(COMPONENT_CLIENTID, "Failed to remove %s, errno: %s (%d)", path, strerror(errno), errno); } } gsh_free(path); } (void)closedir(dp); } void fs_clean_old_recov_dir(void) { fs_clean_old_recov_dir_impl(v4_old_dir); } void fs_add_revoke_fh(nfs_client_id_t *delr_clid, nfs_fh4 *delr_handle) { char rhdlstr[NAME_MAX]; char path[PATH_MAX] = { 0 }; int length, position = 0, pathpos, rhdlstr_len; int fd; int __attribute__((unused)) retval; /* Convert nfs_fh4_val into base64 encoded string */ retval = base64url_encode(delr_handle->nfs_fh4_val, delr_handle->nfs_fh4_len, rhdlstr, sizeof(rhdlstr)); assert(retval != -1); rhdlstr_len = strlen(rhdlstr); /* Parse through the clientid directory structure */ assert(delr_clid->cid_recov_tag != NULL); assert(v4_recov_dir_len < sizeof(path)); memcpy(path, v4_recov_dir, v4_recov_dir_len); pathpos = v4_recov_dir_len; length = strlen(delr_clid->cid_recov_tag); while (position < length) { int len = length - position; if (len <= NAME_MAX) { int new_pathpos = pathpos + 1 + len + 3 + rhdlstr_len; if (new_pathpos >= sizeof(path)) { LogCrit(COMPONENT_CLIENTID, "Could not revoke path %s/%s/%s too long", path, delr_clid->cid_recov_tag + position, rhdlstr); } path[pathpos++] = '/'; memcpy(path + pathpos, delr_clid->cid_recov_tag + position, len); /* Prefix 1 to converted fh */ memcpy(path + pathpos + len, "/\x1", 2); memcpy(path + pathpos + len + 2, rhdlstr, rhdlstr_len + 1); fd = creat(path, 0700); if (fd < 0) { LogEvent( COMPONENT_CLIENTID, "Failed to record revoke errno: %s (%d)", strerror(errno), errno); } else { close(fd); } return; } path[pathpos++] = '/'; memcpy(path + pathpos, delr_clid->cid_recov_tag + position, NAME_MAX); pathpos += NAME_MAX; path[pathpos] = '\0'; position += NAME_MAX; } } struct nfs4_recovery_backend fs_backend = { .recovery_init = fs_create_recov_dir, .end_grace = fs_clean_old_recov_dir, .recovery_read_clids = fs_read_recov_clids_takeover, .add_clid = fs_add_clid, .rm_clid = fs_rm_clid, .add_revoke_fh = fs_add_revoke_fh, }; void fs_backend_init(struct nfs4_recovery_backend **backend) { *backend = &fs_backend; } nfs-ganesha-6.5/src/SAL/recovery/recovery_fs.h000066400000000000000000000023241473756622300213410ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * This program 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 3 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 * 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 * */ #ifndef _RECOVERY_FS_H #define _RECOVERY_FS_H /* * Select bits from recovery_fs.c that we can reuse elsewhere */ extern char v4_recov_dir[PATH_MAX]; extern unsigned int v4_recov_dir_len; void fs_add_clid(nfs_client_id_t *clientid); void fs_rm_clid(nfs_client_id_t *clientid); void fs_add_revoke_fh(nfs_client_id_t *delr_clid, nfs_fh4 *delr_handle); void fs_clean_old_recov_dir_impl(char *parent_path); #endif /* _RECOVERY_FS_H */ nfs-ganesha-6.5/src/SAL/recovery/recovery_fs_ng.c000066400000000000000000000313351473756622300220240ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * This program 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 3 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 * 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 * */ #include "config.h" #include "log.h" #include "nfs_core.h" #include "nfs4.h" #include "sal_functions.h" #include #include #include #include #include #include #include "bsd-base64.h" #include "client_mgr.h" #include "fsal.h" #include "recovery_fs.h" #include static char v4_recov_link[PATH_MAX]; /* * If we have a "legacy" fs driver database, we can allow clients to recover * using that. In order to handle it though, we must rename the thing and * set symlink for it. * * Unfortunately this is not atomic, but it should be a one-time thing. */ static void legacy_fs_db_migrate(void) { int ret; struct stat st; ret = lstat(v4_recov_link, &st); if (!ret && S_ISDIR(st.st_mode)) { char pathbuf[PATH_MAX]; char *dname; /* create empty tmpdir in same parent */ ret = snprintf(pathbuf, sizeof(pathbuf), "%s.XXXXXX", v4_recov_link); if (unlikely(ret >= sizeof(pathbuf))) { LogCrit(COMPONENT_CLIENTID, "Path too long %s.XXXXXX", v4_recov_link); return; } else if (unlikely(ret < 0)) { LogCrit(COMPONENT_CLIENTID, "Unexpected return from snprintf %d error %s (%d)", ret, strerror(errno), errno); return; } dname = mkdtemp(pathbuf); if (!dname) { LogEvent(COMPONENT_CLIENTID, "Failed to create temp file (%s): %s (%d)", pathbuf, strerror(errno), errno); return; } ret = rename(v4_recov_link, dname); if (ret != 0) { LogEvent( COMPONENT_CLIENTID, "Failed to rename v4 recovery dir (%s) to (%s): %s (%d)", v4_recov_link, dname, strerror(errno), errno); return; } ret = symlink(basename(dname), v4_recov_link); if (ret != 0) { LogEvent( COMPONENT_CLIENTID, "Failed to set recoverydir symlink at %s: %s (%d)", dname, strerror(errno), errno); return; } } } static int fs_ng_create_recov_dir(void) { int err; char *newdir; char host[NI_MAXHOST]; err = mkdir(nfs_param.nfsv4_param.recov_root, 0700); if (err == -1 && errno != EEXIST) { LogEvent(COMPONENT_CLIENTID, "Failed to create v4 recovery dir (%s): %s (%d)", nfs_param.nfsv4_param.recov_root, strerror(errno), errno); } err = snprintf(v4_recov_dir, sizeof(v4_recov_dir), "%s/%s", nfs_param.nfsv4_param.recov_root, nfs_param.nfsv4_param.recov_dir); if (unlikely(err >= sizeof(v4_recov_dir))) { LogCrit(COMPONENT_CLIENTID, "Path too long %s/%s", nfs_param.nfsv4_param.recov_root, nfs_param.nfsv4_param.recov_dir); return -EINVAL; } else if (unlikely(err < 0)) { int error = errno; LogCrit(COMPONENT_CLIENTID, "Unexpected return from snprintf %d error %s (%d)", err, strerror(error), error); return -error; } err = mkdir(v4_recov_dir, 0700); if (err == -1 && errno != EEXIST) { LogEvent(COMPONENT_CLIENTID, "Failed to create v4 recovery dir(%s): %s (%d)", v4_recov_dir, strerror(errno), errno); } /* Populate link path string */ if (nfs_param.core_param.clustered) { err = snprintf(host, sizeof(host), "node%d", g_nodeid); if (unlikely(err >= sizeof(host))) { LogCrit(COMPONENT_CLIENTID, "node%d too long", g_nodeid); return -EINVAL; } else if (unlikely(err < 0)) { int error = errno; LogCrit(COMPONENT_CLIENTID, "Unexpected return from snprintf %d error %s (%d)", err, strerror(error), error); return -error; } } else { err = gethostname(host, sizeof(host)); if (err) { LogEvent(COMPONENT_CLIENTID, "Failed to gethostname: %s (%d)", strerror(errno), errno); return -errno; } } err = snprintf(v4_recov_link, sizeof(v4_recov_link), "%s/%s/%s", nfs_param.nfsv4_param.recov_root, nfs_param.nfsv4_param.recov_dir, host); if (unlikely(err >= sizeof(v4_recov_link))) { LogCrit(COMPONENT_CLIENTID, "Path too long %s/%s/%s", nfs_param.nfsv4_param.recov_root, nfs_param.nfsv4_param.recov_dir, host); return -EINVAL; } else if (unlikely(err < 0)) { int error = errno; LogCrit(COMPONENT_CLIENTID, "Unexpected return from snprintf %d error %s (%d)", err, strerror(error), error); return -error; } err = snprintf(v4_recov_dir, sizeof(v4_recov_dir), "%s.XXXXXX", v4_recov_link); if (unlikely(err >= sizeof(v4_recov_dir))) { LogCrit(COMPONENT_CLIENTID, "Path too long %s.XXXXXX", v4_recov_link); return -EINVAL; } else if (unlikely(err < 0)) { int error = errno; LogCrit(COMPONENT_CLIENTID, "Unexpected return from snprintf %d error %s (%d)", err, strerror(error), error); return -error; } v4_recov_dir_len = err; newdir = mkdtemp(v4_recov_dir); if (newdir != v4_recov_dir) { LogEvent(COMPONENT_CLIENTID, "Failed to create v4 recovery dir(%s): %s (%d)", v4_recov_dir, strerror(errno), errno); } legacy_fs_db_migrate(); return 0; } /** * @brief Create the client reclaim list from previous database * * @param[in] dp Recovery directory * @param[in] srcdir Path to the source directory on failover * * @return POSIX error codes. */ static int fs_ng_read_recov_clids_impl(const char *parent_path, char *clid_str, add_clid_entry_hook add_clid_entry, add_rfh_entry_hook add_rfh_entry) { struct dirent *dentp; DIR *dp; clid_entry_t *new_ent; char *sub_path = NULL; char *build_clid = NULL; int rc = 0; int num = 0; char *ptr, *ptr2; char temp[10]; int cid_len, len; int segment_len; int clid_str_len = clid_str ? strlen(clid_str) : 0; dp = opendir(parent_path); if (dp == NULL) { LogEvent(COMPONENT_CLIENTID, "Failed to open v4 recovery dir (%s): %s (%d)", parent_path, strerror(errno), errno); return -1; } for (dentp = readdir(dp); dentp != NULL; dentp = readdir(dp)) { /* don't add '.' and '..' entry */ if (!strcmp(dentp->d_name, ".") || !strcmp(dentp->d_name, "..")) continue; /* Skip names that start with '\x1' as they are files * representing revoked file handles */ if (dentp->d_name[0] == '\x1') continue; num++; /* construct the path by appending the subdir for the * next readdir. This recursion keeps reading the * subdirectory until reaching the end. */ sub_path = gsh_concat_sep(parent_path, '/', dentp->d_name); segment_len = strlen(dentp->d_name); /* keep building the clientid str by recursively */ /* reading the directory structure */ build_clid = gsh_malloc(clid_str_len + segment_len + 1); if (clid_str) memcpy(build_clid, clid_str, clid_str_len); memcpy(build_clid + clid_str_len, dentp->d_name, segment_len + 1); rc = fs_ng_read_recov_clids_impl(sub_path, build_clid, add_clid_entry, add_rfh_entry); /* after recursion, if the subdir has no non-hidden * directory this is the end of this clientid str. Add * the clientstr to the list. */ if (rc == 0) { /* the clid format is * -(clid-len:long-form-clid-in-string-form) * make sure this reconstructed string is valid * by comparing clid-len and the actual * long-form-clid length in the string. This is * to prevent getting incomplete strings that * might exist due to program crash. */ if (strlen(build_clid) >= PATH_MAX) { LogEvent(COMPONENT_CLIENTID, "invalid clid format: %s, too long", build_clid); gsh_free(sub_path); gsh_free(build_clid); continue; } ptr = strchr(build_clid, '('); if (ptr == NULL) { LogEvent(COMPONENT_CLIENTID, "invalid clid format: %s", build_clid); gsh_free(sub_path); gsh_free(build_clid); continue; } ptr2 = strchr(ptr, ':'); if (ptr2 == NULL) { LogEvent(COMPONENT_CLIENTID, "invalid clid format: %s", build_clid); gsh_free(sub_path); gsh_free(build_clid); continue; } len = ptr2 - ptr - 1; if (len >= 9) { LogEvent(COMPONENT_CLIENTID, "invalid clid format: %s", build_clid); gsh_free(sub_path); gsh_free(build_clid); continue; } memcpy(temp, ptr + 1, len + 1); cid_len = atoi(temp); len = strlen(ptr2); if ((len == (cid_len + 2)) && (ptr2[len - 1] == ')')) { new_ent = add_clid_entry(build_clid); LogDebug(COMPONENT_CLIENTID, "added %s to clid list", new_ent->cl_name); } } gsh_free(build_clid); gsh_free(sub_path); } (void)closedir(dp); return num; } static void fs_ng_read_recov_clids_recover(add_clid_entry_hook add_clid_entry, add_rfh_entry_hook add_rfh_entry) { int rc; rc = fs_ng_read_recov_clids_impl(v4_recov_link, NULL, add_clid_entry, add_rfh_entry); if (rc == -1) { LogEvent(COMPONENT_CLIENTID, "Failed to read v4 recovery dir (%s)", v4_recov_link); return; } } /** * @brief Load clients for recovery, with no lock * * @param[in] nodeid Node, on takeover */ static void fs_ng_read_recov_clids(nfs_grace_start_t *gsp, add_clid_entry_hook add_clid_entry, add_rfh_entry_hook add_rfh_entry) { if (!gsp) { fs_ng_read_recov_clids_recover(add_clid_entry, add_rfh_entry); return; } #ifndef FIXME return; #else /** * More investigation/design is required to determine the best * implementation for the remainder of this method. In the interim, * Coverity complains that this is dead code because of the early * return above. Hence the remainder of this method is ifdef'ed * out to avoid the Coverity complaint. * * @todo: FIXME: make the rest of this work */ int rc; char path[PATH_MAX]; switch (gsp->event) { case EVENT_TAKE_NODEID: rc = snprintf(path, sizeof(path), "%s/%s/node%d", nfs_param.nfsv4_param.recov_root, nfs_param.nfsv4_param.recov_dir, gsp->nodeid); if (unlikely(rc >= sizeof(path))) { LogCrit(COMPONENT_CLIENTID, "Path too long %s/%s/node%d", nfs_param.nfsv4_param.recov_root, nfs_param.nfsv4_param.recov_dir, gsp->nodeid); } else if (unlikely(rc < 0)) { LogCrit(COMPONENT_CLIENTID, "Unexpected return from snprintf %d error %s (%d)", rc, strerror(errno), errno); return; } break; default: LogWarn(COMPONENT_CLIENTID, "Recovery unknown event: %d", gsp->event); return; } LogEvent(COMPONENT_CLIENTID, "Recovery for nodeid %d dir (%s)", gsp->nodeid, path); rc = fs_ng_read_recov_clids_impl(path, NULL, add_clid_entry, add_rfh_entry); if (rc == -1) { LogEvent(COMPONENT_CLIENTID, "Failed to read v4 recovery dir (%s)", path); return; } #endif } static void fs_ng_swap_recov_dir(void) { int ret; char old_pathbuf[PATH_MAX]; char tmp_link[PATH_MAX]; char *old_path; /* save off the old link path so we can do some cleanup afterward */ old_path = realpath(v4_recov_link, old_pathbuf); /* Make a new symlink at a temporary location, pointing to new dir */ ret = snprintf(tmp_link, sizeof(tmp_link), "%s.tmp", v4_recov_link); if (unlikely(ret >= sizeof(tmp_link))) { LogCrit(COMPONENT_CLIENTID, "Path too long %s.tmp", v4_recov_link); return; } else if (unlikely(ret < 0)) { LogCrit(COMPONENT_CLIENTID, "Unexpected return from snprintf %d error %s (%d)", ret, strerror(errno), errno); return; } /* unlink old symlink, if any */ ret = unlink(tmp_link); if (ret != 0 && errno != ENOENT) { LogEvent(COMPONENT_CLIENTID, "Unable to remove recoverydir symlink: %s (%d)", strerror(errno), errno); return; } /* make a new symlink in a temporary spot */ ret = symlink(basename(v4_recov_dir), tmp_link); if (ret != 0) { LogEvent(COMPONENT_CLIENTID, "Unable to create recoverydir symlink: %s (%d)", strerror(errno), errno); return; } /* rename tmp link into place */ ret = rename(tmp_link, v4_recov_link); if (ret != 0) { LogEvent(COMPONENT_CLIENTID, "Unable to rename recoverydir symlink: %s (%d)", strerror(errno), errno); return; } /* now clean up old path, if any */ if (old_path) { fs_clean_old_recov_dir_impl(old_path); rmdir(old_path); } } static struct nfs4_recovery_backend fs_ng_backend = { .recovery_init = fs_ng_create_recov_dir, .end_grace = fs_ng_swap_recov_dir, .recovery_read_clids = fs_ng_read_recov_clids, .add_clid = fs_add_clid, .rm_clid = fs_rm_clid, .add_revoke_fh = fs_add_revoke_fh, }; void fs_ng_backend_init(struct nfs4_recovery_backend **backend) { *backend = &fs_ng_backend; } nfs-ganesha-6.5/src/SAL/recovery/recovery_rados.h000066400000000000000000000065401473756622300220450ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2017 Red Hat, Inc. and/or its affiliates. * Author: Jeff Layton * * This program 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 3 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 * 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 */ #ifndef _RECOVERY_RADOS_H #define _RECOVERY_RADOS_H #include #include "gsh_refstr.h" /* A rados key is a decimal string of a uint64_t value which has at most 20 * digits plus 1 for NUL */ #define RADOS_KEY_MAX_LEN 21 #define RADOS_VAL_MAX_LEN PATH_MAX extern rados_t rados_recov_cluster; extern rados_ioctx_t rados_recov_io_ctx; extern struct gsh_refstr *rados_recov_oid; extern struct gsh_refstr *rados_recov_old_oid; struct rados_kv_parameter { /** Connection to ceph cluster */ char *ceph_conf; /** User ID to ceph cluster */ char *userid; /** Pool for client info */ char *pool; /** Namespace for objects within the pool **/ char *namespace; /** rados_cluster grace database OID */ char *grace_oid; /** rados_cluster node_id */ char *nodeid; }; extern struct rados_kv_parameter rados_kv_param; /* Callback for rados_kv_traverse */ struct pop_args { add_clid_entry_hook add_clid_entry; add_rfh_entry_hook add_rfh_entry; bool old; bool takeover; }; typedef void (*pop_clid_entry_t)(char *, char *, size_t, struct pop_args *); int rados_kv_connect(rados_ioctx_t *io_ctx, const char *userid, const char *conf, const char *pool, const char *ns); void rados_kv_shutdown(void); int rados_kv_put(char *key, char *val, char *object); int rados_kv_get(char *key, char *val, char *object); void rados_kv_add_clid(nfs_client_id_t *clientid); void rados_kv_rm_clid(nfs_client_id_t *clientid); void rados_kv_add_revoke_fh(nfs_client_id_t *delr_clid, nfs_fh4 *delr_handle); /** * Convert a clientid into a rados key. * * @param(in) clientid The clientid to convert into a key * @param(in/out) key The string buffer to put the key in * @param(in) size Buffer size - expected to be RADOS_KEY_MAX_LEN */ static inline void rados_kv_create_key(nfs_client_id_t *clientid, char *key, size_t size) { assert(size == RADOS_KEY_MAX_LEN); /* Can't overrun RADOS_KEY_MAX_LEN and shouldn't return EOVERFLOW or * EINVAL */ (void)snprintf(key, size, "%lu", (uint64_t)clientid->cid_clientid); } char *rados_kv_create_val(nfs_client_id_t *clientid, size_t *len); int rados_kv_traverse(pop_clid_entry_t callback, struct pop_args *args, const char *object); void rados_kv_add_revoke_fh(nfs_client_id_t *delr_clid, nfs_fh4 *delr_handle); void rados_ng_pop_clid_entry(char *key, char *val, size_t val_len, struct pop_args *pop_args); int rados_kv_get_nodeid(char **pnodeid); #endif /* _RECOVERY_RADOS_H */ nfs-ganesha-6.5/src/SAL/recovery/recovery_rados_cluster.c000066400000000000000000000305241473756622300236000ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2018 Red Hat, Inc. and/or its affiliates. * Author: Jeff Layton * * This program 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 3 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 * 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 * * recovery_rados_cluster: a clustered recovery backing store * * See ganesha-rados-cluster-design(8) for overall design and theory */ #include "config.h" #include #include #include #include #include "log.h" #include "nfs_core.h" #include "sal_functions.h" #include "recovery_rados.h" /* Use hostname as nodeid in cluster */ char *nodeid; static uint64_t rados_watch_cookie; static void rados_grace_watchcb(void *arg, uint64_t notify_id, uint64_t handle, uint64_t notifier_id, void *data, size_t data_len) { int ret; /* ACK it first, so we keep things moving along */ ret = rados_notify_ack(rados_recov_io_ctx, rados_kv_param.grace_oid, notify_id, rados_watch_cookie, NULL, 0); if (ret < 0) LogEvent(COMPONENT_CLIENTID, "rados_notify_ack failed: %d", ret); /* Now kick the reaper to check things out */ nfs_notify_grace_waiters(); reaper_wake(); } static int rados_cluster_init(void) { int ret; /* If no nodeid is specified, then use the hostname */ if (rados_kv_param.nodeid) { nodeid = gsh_strdup(rados_kv_param.nodeid); } else { long maxlen = sysconf(_SC_HOST_NAME_MAX); nodeid = gsh_malloc(maxlen); ret = gethostname(nodeid, maxlen); if (ret) { LogEvent(COMPONENT_CLIENTID, "gethostname failed: %d", errno); ret = -errno; goto out_free_nodeid; } } ret = rados_kv_connect(&rados_recov_io_ctx, rados_kv_param.userid, rados_kv_param.ceph_conf, rados_kv_param.pool, rados_kv_param.namespace); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to connect to cluster: %d", ret); goto out_shutdown; } ret = rados_grace_member(rados_recov_io_ctx, rados_kv_param.grace_oid, nodeid); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Cluster membership check failed: %d", ret); goto out_shutdown; } /* FIXME: not sure about the 30s timeout value here */ ret = rados_watch3(rados_recov_io_ctx, rados_kv_param.grace_oid, &rados_watch_cookie, rados_grace_watchcb, NULL, 30, NULL); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to set watch on grace db: %d", ret); goto out_shutdown; } return 0; out_shutdown: rados_kv_shutdown(); out_free_nodeid: gsh_free(nodeid); nodeid = NULL; return ret; } /* Try to delete old recovery db */ static void rados_cluster_end_grace(void) { int ret; rados_write_op_t wop; uint64_t cur, rec; struct gsh_refstr *old_oid; old_oid = rcu_xchg_pointer(&rados_recov_old_oid, NULL); if (!old_oid) return; ret = rados_grace_enforcing_off(rados_recov_io_ctx, rados_kv_param.grace_oid, nodeid, &cur, &rec); if (ret) LogEvent(COMPONENT_CLIENTID, "Failed to set grace off for %s: %d", nodeid, ret); wop = rados_create_write_op(); rados_write_op_remove(wop); ret = rados_write_op_operate(wop, rados_recov_io_ctx, old_oid->gr_val, NULL, 0); if (ret) LogEvent(COMPONENT_CLIENTID, "Failed to remove %s: %d", old_oid->gr_val, ret); synchronize_rcu(); gsh_refstr_put(old_oid); } static void rados_cluster_read_clids(nfs_grace_start_t *gsp, add_clid_entry_hook add_clid_entry, add_rfh_entry_hook add_rfh_entry) { int ret; size_t len; uint64_t cur, rec; rados_write_op_t wop; struct gsh_refstr *recov_oid, *old_oid; struct pop_args args = { .add_clid_entry = add_clid_entry, .add_rfh_entry = add_rfh_entry, }; if (gsp) { LogEvent(COMPONENT_CLIENTID, "Clustered rados backend does not support takeover!"); return; } /* Start or join a grace period */ ret = rados_grace_join(rados_recov_io_ctx, rados_kv_param.grace_oid, nodeid, &cur, &rec, true); if (ret) { LogEvent(COMPONENT_CLIENTID, "Failed to join grace period: %d", ret); return; } /* * Recovery db names are "rec-cccccccccccccccc:hostname" * * "rec-" followed by epoch in 16 hex digits + nodeid. */ /* FIXME: assert that rados_recov_oid is NULL? */ len = 4 + 16 + 1 + strlen(nodeid) + 1; recov_oid = gsh_refstr_alloc(len); /* Can't overrun and shouldn't return EOVERFLOW or EINVAL */ (void)snprintf(recov_oid->gr_val, len, "rec-%16.16lx:%s", cur, nodeid); gsh_refstr_get(recov_oid); rcu_set_pointer(&rados_recov_oid, recov_oid); wop = rados_create_write_op(); rados_write_op_create(wop, LIBRADOS_CREATE_IDEMPOTENT, NULL); rados_write_op_omap_clear(wop); ret = rados_write_op_operate(wop, rados_recov_io_ctx, recov_oid->gr_val, NULL, 0); gsh_refstr_put(recov_oid); rados_release_write_op(wop); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to create recovery db"); return; }; old_oid = gsh_refstr_alloc(len); /* Can't overrun and shouldn't return EOVERFLOW or EINVAL */ (void)snprintf(old_oid->gr_val, len, "rec-%16.16lx:%s", rec, nodeid); rcu_set_pointer(&rados_recov_old_oid, old_oid); ret = rados_kv_traverse(rados_ng_pop_clid_entry, &args, old_oid->gr_val); if (ret < 0) LogEvent(COMPONENT_CLIENTID, "Failed to traverse recovery db: %d", ret); } static bool rados_cluster_try_lift_grace(void) { int ret; uint64_t cur, rec; ret = rados_grace_lift(rados_recov_io_ctx, rados_kv_param.grace_oid, nodeid, &cur, &rec); if (ret) { LogEvent(COMPONENT_CLIENTID, "Attempt to lift grace failed: %d", ret); return false; } /* Non-zero rec means grace is still in force */ return (rec == 0); } struct rados_cluster_kv_pairs { size_t slots; /* Current array size */ size_t num; /* Count of populated elements */ char **keys; /* Array of key strings */ char **vals; /* Array of value blobs */ size_t *lens; /* Array of value lengths */ }; /* * FIXME: Since each hash tree is protected by its own mutex, we can't ensure * that we'll get an accurate count before allocating. For now, we just * have a fixed-size cap of 1024 entries in the db, but we should allow * there to be an arbitrary number of entries. */ #define RADOS_KV_STARTING_SLOTS 1024 static void rados_set_client_cb(struct rbt_node *pn, void *arg) { struct hash_data *addr = RBT_OPAQ(pn); nfs_client_id_t *clientid = addr->val.addr; struct rados_cluster_kv_pairs *kvp = arg; char ckey[RADOS_KEY_MAX_LEN]; /* FIXME: resize arrays in this case? */ if (kvp->num >= kvp->slots) { LogEvent(COMPONENT_CLIENTID, "too many clients to copy!"); return; } rados_kv_create_key(clientid, ckey, sizeof(ckey)); kvp->keys[kvp->num] = gsh_strdup(ckey); kvp->vals[kvp->num] = rados_kv_create_val(clientid, &kvp->lens[kvp->num]); ++kvp->num; } /** * @brief Start local grace period if we're in a global one * * In clustered setups, other machines in the cluster can start a new * grace period. Check for that and enter the grace period if so. */ static void rados_cluster_maybe_start_grace(void) { int ret, i; size_t len; nfs_grace_start_t gsp = { .event = EVENT_JUST_GRACE }; rados_write_op_t wop; uint64_t cur, rec; struct gsh_refstr *recov_oid, *old_oid, *prev_recov_oid; char *keys[RADOS_KV_STARTING_SLOTS]; char *vals[RADOS_KV_STARTING_SLOTS]; size_t lens[RADOS_KV_STARTING_SLOTS]; struct rados_cluster_kv_pairs kvp = { .slots = RADOS_KV_STARTING_SLOTS, .num = 0, .keys = keys, .vals = vals, .lens = lens }; /* Fix up the strings */ ret = rados_grace_epochs(rados_recov_io_ctx, rados_kv_param.grace_oid, &cur, &rec); if (ret) { LogEvent(COMPONENT_CLIENTID, "rados_grace_epochs failed: %d", ret); return; } /* No grace period if rec == 0 */ if (rec == 0) return; /* * A new epoch has been started and a cluster-wide grace period has * been requested. Make a new DB for "cur" that has all of the * currently active clients in it. */ /* Allocate new oid string and xchg it into place */ len = 4 + 16 + 1 + strlen(nodeid) + 1; recov_oid = gsh_refstr_alloc(len); /* Get an extra working reference of new string */ gsh_refstr_get(recov_oid); /* Can't overrun and shouldn't return EOVERFLOW or EINVAL */ (void)snprintf(recov_oid->gr_val, len, "rec-%16.16lx:%s", cur, nodeid); prev_recov_oid = rcu_xchg_pointer(&rados_recov_oid, recov_oid); old_oid = gsh_refstr_alloc(len); /* Can't overrun and shouldn't return EOVERFLOW or EINVAL */ (void)snprintf(old_oid->gr_val, len, "rec-%16.16lx:%s", rec, nodeid); old_oid = rcu_xchg_pointer(&rados_recov_old_oid, old_oid); synchronize_rcu(); gsh_refstr_put(prev_recov_oid); if (old_oid) gsh_refstr_put(old_oid); /* Populate key/val/len arrays from confirmed client hash */ hashtable_for_each(ht_confirmed_client_id, rados_set_client_cb, &kvp); /* Create new write op and package it up for callback */ wop = rados_create_write_op(); rados_write_op_create(wop, LIBRADOS_CREATE_IDEMPOTENT, NULL); rados_write_op_omap_clear(wop); rados_write_op_omap_set(wop, (char const *const *)keys, (char const *const *)vals, (const size_t *)lens, kvp.num); ret = rados_write_op_operate(wop, rados_recov_io_ctx, recov_oid->gr_val, NULL, 0); gsh_refstr_put(recov_oid); if (ret) LogEvent(COMPONENT_CLIENTID, "rados_write_op_operate failed: %d", ret); rados_release_write_op(wop); /* Free copied strings */ for (i = 0; i < kvp.num; ++i) { free(kvp.keys[i]); free(kvp.vals[i]); } /* Start a new grace period */ nfs_start_grace(&gsp); } static void rados_cluster_shutdown(void) { int ret; uint64_t cur, rec; /* * Request grace on clean shutdown to minimize the chance that we'll * miss the window and the MDS kills off the old session. * * FIXME: only do this if our key is in the omap, and we have a * non-empty recovery db. */ ret = rados_grace_join(rados_recov_io_ctx, rados_kv_param.grace_oid, nodeid, &cur, &rec, true); if (ret) LogEvent(COMPONENT_CLIENTID, "Failed to start grace period on shutdown: %d", ret); ret = rados_unwatch2(rados_recov_io_ctx, rados_watch_cookie); if (ret) LogEvent(COMPONENT_CLIENTID, "Failed to unwatch grace db: %d", ret); rados_kv_shutdown(); gsh_free(nodeid); nodeid = NULL; } static void rados_cluster_set_enforcing(void) { int ret; uint64_t cur, rec; ret = rados_grace_enforcing_on(rados_recov_io_ctx, rados_kv_param.grace_oid, nodeid, &cur, &rec); if (ret) LogEvent(COMPONENT_CLIENTID, "Failed to set enforcing for %s: %d", nodeid, ret); } static bool rados_cluster_grace_enforcing(void) { int ret; ret = rados_grace_enforcing_check(rados_recov_io_ctx, rados_kv_param.grace_oid, nodeid); LogEvent(COMPONENT_CLIENTID, "%s: ret=%d", __func__, ret); return (ret == 0); } static bool rados_cluster_is_member(void) { int ret = rados_grace_member(rados_recov_io_ctx, rados_kv_param.grace_oid, nodeid); if (ret) { LogEvent(COMPONENT_CLIENTID, "%s: %s is no longer a cluster member (ret=%d)", __func__, nodeid, ret); return false; } return true; } static int rados_cluster_get_nodeid(char **pnodeid) { *pnodeid = gsh_strdup(nodeid); return 0; } struct nfs4_recovery_backend rados_cluster_backend = { .recovery_init = rados_cluster_init, .recovery_shutdown = rados_cluster_shutdown, .recovery_read_clids = rados_cluster_read_clids, .end_grace = rados_cluster_end_grace, .add_clid = rados_kv_add_clid, .rm_clid = rados_kv_rm_clid, .add_revoke_fh = rados_kv_add_revoke_fh, .maybe_start_grace = rados_cluster_maybe_start_grace, .try_lift_grace = rados_cluster_try_lift_grace, .set_enforcing = rados_cluster_set_enforcing, .grace_enforcing = rados_cluster_grace_enforcing, .is_member = rados_cluster_is_member, .get_nodeid = rados_cluster_get_nodeid, }; void rados_cluster_backend_init(struct nfs4_recovery_backend **backend) { *backend = &rados_cluster_backend; } nfs-ganesha-6.5/src/SAL/recovery/recovery_rados_kv.c000066400000000000000000000460461473756622300225450ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * This program 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 3 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 * 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 * */ #include "config.h" #include "log.h" #include "nfs_core.h" #include "nfs4.h" #include "sal_functions.h" #include #include #include #include #include "bsd-base64.h" #include "client_mgr.h" #include "fsal.h" #include "netdb.h" #include #include #include "recovery_rados.h" #include #define MAX_ITEMS 1024 /* relaxed */ static rados_t clnt; rados_ioctx_t rados_recov_io_ctx; struct gsh_refstr *rados_recov_oid; struct gsh_refstr *rados_recov_old_oid; struct rados_kv_parameter rados_kv_param; static struct config_item rados_kv_params[] = { CONF_ITEM_PATH("ceph_conf", 1, MAXPATHLEN, NULL, rados_kv_parameter, ceph_conf), CONF_ITEM_STR("userid", 1, MAXPATHLEN, NULL, rados_kv_parameter, userid), CONF_ITEM_STR("pool", 1, MAXPATHLEN, DEFAULT_RADOS_GRACE_POOL, rados_kv_parameter, pool), CONF_ITEM_STR("namespace", 1, NI_MAXHOST, NULL, rados_kv_parameter, namespace), CONF_ITEM_STR("grace_oid", 1, NI_MAXHOST, DEFAULT_RADOS_GRACE_OID, rados_kv_parameter, grace_oid), CONF_ITEM_STR("nodeid", 1, NI_MAXHOST, NULL, rados_kv_parameter, nodeid), CONFIG_EOL }; static void *rados_kv_param_init(void *link_mem, void *self_struct) { if (self_struct == NULL) return &rados_kv_param; else return NULL; } struct config_block rados_kv_param_blk = { .dbus_interface_name = "org.ganesha.nfsd.config.rados_kv", .blk_desc.name = "RADOS_KV", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = rados_kv_param_init, .blk_desc.u.blk.params = rados_kv_params, .blk_desc.u.blk.commit = noop_conf_commit }; static int convert_opaque_val(struct display_buffer *dspbuf, void *value, int len, int max) { unsigned int i = 0; int b_left = display_start(dspbuf); int cpy = len; if (b_left <= 0) return 0; /* Check that the length is ok * If the value is empty, display EMPTY value. */ if (len <= 0 || len > max) return 0; /* If the value is NULL, display NULL value. */ if (value == NULL) return 0; /* Determine if the value is entirely printable characters, */ /* and it contains no slash character (reserved for filename) */ for (i = 0; i < len; i++) if ((!isprint(((char *)value)[i])) || (((char *)value)[i] == '/')) break; if (i == len) { /* Entirely printable character, so we will just copy the * characters into the buffer (to the extent there is room * for them). */ b_left = display_len_cat(dspbuf, value, cpy); } else { b_left = display_opaque_bytes(dspbuf, value, cpy); } if (b_left <= 0) return 0; return b_left; } char *rados_kv_create_val(nfs_client_id_t *clientid, size_t *size) { char *src = clientid->cid_client_record->cr_client_val; int src_len = clientid->cid_client_record->cr_client_val_len; const char *str_client_addr = "(unknown)"; char cidstr[PATH_MAX] = { 0, }, *val; struct display_buffer dspbuf = { sizeof(cidstr), cidstr, cidstr }; char cidstr_lenx[5]; int total_len, cidstr_len, cidstr_lenx_len, str_client_addr_len; int ret; size_t lsize; /* get the caller's IP addr */ if (clientid->gsh_client != NULL) str_client_addr = clientid->gsh_client->hostaddr_str; str_client_addr_len = strlen(str_client_addr); ret = convert_opaque_val(&dspbuf, src, src_len, sizeof(cidstr)); assert(ret > 0); cidstr_len = display_buffer_len(&dspbuf); cidstr_lenx_len = snprintf(cidstr_lenx, sizeof(cidstr_lenx), "%d", cidstr_len); if (unlikely(cidstr_lenx_len >= sizeof(cidstr_lenx) || cidstr_lenx_len < 0)) { /* cidrstr can at most be PATH_MAX or 1024, so at most * 4 characters plus NUL are necessary, so we won't * overrun, nor can we get a -1 with EOVERFLOW or EINVAL */ LogFatal(COMPONENT_CLIENTID, "snprintf returned unexpected %d", cidstr_lenx_len); } lsize = str_client_addr_len + 2 + cidstr_lenx_len + 1 + cidstr_len + 2; /* hold both long form clientid and IP */ val = gsh_malloc(lsize); memcpy(val, str_client_addr, str_client_addr_len); total_len = str_client_addr_len; memcpy(val + total_len, "-(", 2); total_len += 2; memcpy(val + total_len, cidstr_lenx, cidstr_lenx_len); total_len += cidstr_lenx_len; val[total_len] = ':'; total_len += 1; memcpy(val + total_len, cidstr, cidstr_len); total_len += cidstr_len; memcpy(val + total_len, ")", 2); LogDebug(COMPONENT_CLIENTID, "Created client name [%s]", val); if (size) *size = lsize; return val; } int rados_kv_put(char *key, char *val, char *object) { int ret; char *keys[1]; char *vals[1]; size_t lens[1]; rados_write_op_t write_op; keys[0] = key; vals[0] = val; lens[0] = strlen(val); write_op = rados_create_write_op(); rados_write_op_omap_set(write_op, (const char *const *)keys, (const char *const *)vals, lens, 1); ret = rados_write_op_operate(write_op, rados_recov_io_ctx, object, NULL, 0); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to put kv ret=%d, key=%s, val=%s", ret, key, val); } rados_release_write_op(write_op); return ret; } int rados_kv_get(char *key, char *val, char *object) { int ret; char *keys[1]; char *key_out = NULL; char *val_out = NULL; size_t val_len_out = 0; rados_omap_iter_t iter_vals; rados_read_op_t read_op; keys[0] = key; read_op = rados_create_read_op(); rados_read_op_omap_get_vals_by_keys(read_op, (const char *const *)keys, 1, &iter_vals, NULL); ret = rados_read_op_operate(read_op, rados_recov_io_ctx, object, 0); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to get kv ret=%d, key=%s", ret, key); goto out; } ret = rados_omap_get_next(iter_vals, &key_out, &val_out, &val_len_out); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to get kv ret=%d, key=%s", ret, key); goto out; } /* All internal so buffer length is known to be ok */ memcpy(val, val_out, val_len_out + 1); LogDebug(COMPONENT_CLIENTID, "%s: key=%s val=%s", __func__, key, val); rados_omap_get_end(iter_vals); out: rados_release_read_op(read_op); return ret; } static int rados_kv_del(char *key, char *object) { int ret; char *keys[1]; rados_write_op_t write_op; keys[0] = key; write_op = rados_create_write_op(); rados_write_op_omap_rm_keys(write_op, (const char *const *)keys, 1); ret = rados_write_op_operate(write_op, rados_recov_io_ctx, object, NULL, 0); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to del kv ret=%d, key=%s", ret, key); } rados_release_write_op(write_op); return ret; } int rados_kv_traverse(pop_clid_entry_t callback, struct pop_args *args, const char *object) { int ret; char *key_out = NULL; char *val_out = NULL; size_t val_len_out = 0; bool pmore = false; char *start = ""; rados_omap_iter_t iter_vals; rados_read_op_t read_op; again: read_op = rados_create_read_op(); rados_read_op_omap_get_vals2(read_op, start, "", MAX_ITEMS, &iter_vals, (unsigned char *)&pmore, NULL); ret = rados_read_op_operate(read_op, rados_recov_io_ctx, object, 0); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to lst kv ret=%d", ret); goto out; } while (true) { rados_omap_get_next(iter_vals, &key_out, &val_out, &val_len_out); if (val_len_out == 0 && key_out == NULL && val_out == NULL) break; start = key_out; callback(key_out, val_out, val_len_out, args); } rados_omap_get_end(iter_vals); /* more items, next round */ if (pmore) { rados_release_read_op(read_op); goto again; } out: rados_release_read_op(read_op); return ret; } static void rados_kv_append_val_rdfh(char *val, char *rdfh, int rdfh_len) { char rdfhstr[NAME_MAX]; int ret; size_t buflen; /* Convert nfs_fh4_val into base64 encoded string */ ret = base64url_encode(rdfh, rdfh_len, rdfhstr, NAME_MAX); assert(ret != -1); buflen = RADOS_VAL_MAX_LEN - (strlen(val) + 1); strncat(val, "#", buflen); buflen--; strncat(val, rdfhstr, buflen); } int rados_load_config_from_parse(config_file_t parse_tree, struct config_error_type *err_type) { (void)load_config_from_parse(parse_tree, &rados_kv_param_blk, NULL, true, err_type); if (!config_error_is_harmless(err_type)) { LogCrit(COMPONENT_INIT, "Error while parsing RadosKV specific configuration"); return -1; } return 0; } int rados_kv_connect(rados_ioctx_t *io_ctx, const char *userid, const char *conf, const char *pool, const char *ns) { int ret; ret = rados_create(&clnt, userid); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to create: %d", ret); return ret; } ret = rados_conf_read_file(clnt, conf); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to read conf: %d", ret); rados_shutdown(clnt); return ret; } ret = rados_connect(clnt); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to connect: %d", ret); rados_shutdown(clnt); return ret; } ret = rados_pool_create(clnt, pool); if (ret < 0 && ret != -EEXIST) { LogEvent(COMPONENT_CLIENTID, "Failed to create pool: %d", ret); rados_shutdown(clnt); return ret; } ret = rados_ioctx_create(clnt, pool, io_ctx); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to create ioctx"); rados_shutdown(clnt); } rados_ioctx_set_namespace(*io_ctx, ns); return ret; } void rados_kv_shutdown(void) { struct gsh_refstr *recov_oid; if (rados_recov_io_ctx) { rados_ioctx_destroy(rados_recov_io_ctx); rados_recov_io_ctx = NULL; } if (clnt) { rados_shutdown(clnt); clnt = NULL; } recov_oid = rcu_xchg_pointer(&rados_recov_oid, NULL); synchronize_rcu(); if (recov_oid) gsh_refstr_put(recov_oid); } int rados_kv_init(void) { int ret; size_t len, host_len; struct gsh_refstr *recov_oid = NULL, *old_oid = NULL; char host[NI_MAXHOST]; if (nfs_param.core_param.clustered) { ret = snprintf(host, sizeof(host), "node%d", g_nodeid); if (unlikely(ret >= sizeof(host))) { LogCrit(COMPONENT_CLIENTID, "node%d too long", g_nodeid); return -ENAMETOOLONG; } else if (unlikely(ret < 0)) { ret = errno; LogCrit(COMPONENT_CLIENTID, "Unexpected return from snprintf %d error %s (%d)", ret, strerror(ret), ret); return -ret; } } else { ret = gethostname(host, sizeof(host)); if (ret) { ret = errno; LogEvent(COMPONENT_CLIENTID, "Failed to gethostname: %s (%d)", strerror(ret), ret); return -ret; } } host_len = strlen(host); len = host_len + 6 + 1; recov_oid = gsh_refstr_alloc(len); gsh_refstr_get(recov_oid); /* Can't overrun and shouldn't return EOVERFLOW or EINVAL */ (void)snprintf(recov_oid->gr_val, len, "%s_recov", host); rcu_set_pointer(&rados_recov_oid, recov_oid); len = host_len + 4 + 1; old_oid = gsh_refstr_alloc(len); gsh_refstr_get(old_oid); /* Can't overrun and shouldn't return EOVERFLOW or EINVAL */ (void)snprintf(old_oid->gr_val, len, "%s_old", host); rcu_set_pointer(&rados_recov_old_oid, old_oid); ret = rados_kv_connect(&rados_recov_io_ctx, rados_kv_param.userid, rados_kv_param.ceph_conf, rados_kv_param.pool, rados_kv_param.namespace); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to connect to cluster: %d", ret); goto out; } rados_write_op_t op = rados_create_write_op(); rados_write_op_create(op, LIBRADOS_CREATE_EXCLUSIVE, NULL); ret = rados_write_op_operate(op, rados_recov_io_ctx, old_oid->gr_val, NULL, 0); if (ret < 0 && ret != -EEXIST) { LogEvent(COMPONENT_CLIENTID, "Failed to create object"); rados_release_write_op(op); rados_kv_shutdown(); goto out; } rados_release_write_op(op); op = rados_create_write_op(); rados_write_op_create(op, LIBRADOS_CREATE_EXCLUSIVE, NULL); ret = rados_write_op_operate(op, rados_recov_io_ctx, recov_oid->gr_val, NULL, 0); if (ret < 0 && ret != -EEXIST) { LogEvent(COMPONENT_CLIENTID, "Failed to create object"); rados_release_write_op(op); rados_kv_shutdown(); goto out; } rados_release_write_op(op); LogEvent(COMPONENT_CLIENTID, "Rados kv store init done"); ret = 0; out: gsh_refstr_put(recov_oid); gsh_refstr_put(old_oid); return ret; } void rados_kv_add_clid(nfs_client_id_t *clientid) { char ckey[RADOS_KEY_MAX_LEN]; char *cval; struct gsh_refstr *recov_oid; int ret; rados_kv_create_key(clientid, ckey, sizeof(ckey)); cval = rados_kv_create_val(clientid, NULL); rcu_read_lock(); recov_oid = gsh_refstr_get(rcu_dereference(rados_recov_oid)); rcu_read_unlock(); ret = rados_kv_put(ckey, cval, recov_oid->gr_val); gsh_refstr_put(recov_oid); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to add clid %lu", clientid->cid_clientid); gsh_free(cval); } else { clientid->cid_recov_tag = cval; } } void rados_kv_rm_clid(nfs_client_id_t *clientid) { char ckey[RADOS_KEY_MAX_LEN]; struct gsh_refstr *recov_oid; int ret; rados_kv_create_key(clientid, ckey, sizeof(ckey)); rcu_read_lock(); recov_oid = gsh_refstr_get(rcu_dereference(rados_recov_oid)); rcu_read_unlock(); ret = rados_kv_del(ckey, recov_oid->gr_val); gsh_refstr_put(recov_oid); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to del clid %lu", clientid->cid_clientid); return; } free(clientid->cid_recov_tag); clientid->cid_recov_tag = NULL; } static void rados_kv_pop_clid_entry(char *key, char *val, size_t val_len, struct pop_args *pop_args) { int ret; char *dupval; char *cl_name, *rfh_names, *rfh_name; struct gsh_refstr *old_oid; clid_entry_t *clid_ent; add_clid_entry_hook add_clid_entry = pop_args->add_clid_entry; add_rfh_entry_hook add_rfh_entry = pop_args->add_rfh_entry; bool old = pop_args->old; bool takeover = pop_args->takeover; /* extract clid records */ dupval = gsh_malloc(val_len + 1); memcpy(dupval, val, val_len); dupval[val_len] = '\0'; cl_name = strtok(dupval, "#"); if (!cl_name) cl_name = dupval; clid_ent = add_clid_entry(cl_name); rfh_names = strtok(NULL, "#"); rfh_name = strtok(rfh_names, "#"); while (rfh_name) { add_rfh_entry(clid_ent, rfh_name); rfh_name = strtok(NULL, "#"); } rcu_read_lock(); old_oid = gsh_refstr_get(rcu_dereference(rados_recov_old_oid)); rcu_read_unlock(); if (!old) { ret = rados_kv_put(key, dupval, old_oid->gr_val); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to move %s", key); } } gsh_free(dupval); if (!takeover) { if (old) { ret = rados_kv_del(key, old_oid->gr_val); } else { struct gsh_refstr *recov_oid; rcu_read_lock(); recov_oid = gsh_refstr_get( rcu_dereference(rados_recov_oid)); rcu_read_unlock(); ret = rados_kv_del(key, recov_oid->gr_val); gsh_refstr_put(recov_oid); } if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to del %s", key); } } gsh_refstr_put(old_oid); } static void rados_kv_read_recov_clids_recover(add_clid_entry_hook add_clid_entry, add_rfh_entry_hook add_rfh_entry) { int ret; struct gsh_refstr *recov_oid, *old_oid; struct pop_args args = { .add_clid_entry = add_clid_entry, .add_rfh_entry = add_rfh_entry, .old = true, .takeover = false, }; rcu_read_lock(); old_oid = gsh_refstr_get(rcu_dereference(rados_recov_old_oid)); rcu_read_unlock(); ret = rados_kv_traverse(rados_kv_pop_clid_entry, &args, old_oid->gr_val); gsh_refstr_put(old_oid); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to recover, processing old entries"); return; } args.old = false; rcu_read_lock(); recov_oid = gsh_refstr_get(rcu_dereference(rados_recov_oid)); rcu_read_unlock(); ret = rados_kv_traverse(rados_kv_pop_clid_entry, &args, recov_oid->gr_val); gsh_refstr_put(recov_oid); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to recover, processing recov entries"); } } void rados_kv_read_recov_clids_takeover(nfs_grace_start_t *gsp, add_clid_entry_hook add_clid_entry, add_rfh_entry_hook add_rfh_entry) { int ret; char object_takeover[NI_MAXHOST]; struct pop_args args = { .add_clid_entry = add_clid_entry, .add_rfh_entry = add_rfh_entry, .old = false, .takeover = true, }; if (!gsp) { rados_kv_read_recov_clids_recover(add_clid_entry, add_rfh_entry); return; } ret = snprintf(object_takeover, sizeof(object_takeover), "%s_recov", gsp->ipaddr); if (unlikely(ret >= sizeof(object_takeover))) { LogCrit(COMPONENT_CLIENTID, "object_takeover too long %s_recov", gsp->ipaddr); } else if (unlikely(ret < 0)) { LogCrit(COMPONENT_CLIENTID, "Unexpected return from snprintf %d error %s (%d)", ret, strerror(errno), errno); } ret = rados_kv_traverse(rados_kv_pop_clid_entry, &args, object_takeover); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to takeover"); } } void rados_kv_cleanup_old(void) { int ret; struct gsh_refstr *old_oid; rados_write_op_t write_op = rados_create_write_op(); rcu_read_lock(); old_oid = gsh_refstr_get(rcu_dereference(rados_recov_old_oid)); rcu_read_unlock(); rados_write_op_omap_clear(write_op); ret = rados_write_op_operate(write_op, rados_recov_io_ctx, old_oid->gr_val, NULL, 0); if (ret < 0) LogEvent(COMPONENT_CLIENTID, "Failed to cleanup old"); rados_release_write_op(write_op); gsh_refstr_put(old_oid); } void rados_kv_add_revoke_fh(nfs_client_id_t *delr_clid, nfs_fh4 *delr_handle) { int ret; char ckey[RADOS_KEY_MAX_LEN]; char *cval; struct gsh_refstr *recov_oid; cval = gsh_malloc(RADOS_VAL_MAX_LEN); rados_kv_create_key(delr_clid, ckey, sizeof(ckey)); rcu_read_lock(); recov_oid = gsh_refstr_get(rcu_dereference(rados_recov_oid)); rcu_read_unlock(); ret = rados_kv_get(ckey, cval, recov_oid->gr_val); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to get %s", ckey); goto out; } LogDebug(COMPONENT_CLIENTID, "%s: key=%s val=%s", __func__, ckey, cval); rados_kv_append_val_rdfh(cval, delr_handle->nfs_fh4_val, delr_handle->nfs_fh4_len); ret = rados_kv_put(ckey, cval, recov_oid->gr_val); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to add rdfh for clid %lu", delr_clid->cid_clientid); } out: gsh_refstr_put(recov_oid); gsh_free(cval); } int rados_kv_get_nodeid(char **pnodeid) { /* return the nodeid if we have one */ if (rados_kv_param.nodeid) *pnodeid = gsh_strdup(rados_kv_param.nodeid); return 0; } struct nfs4_recovery_backend rados_kv_backend = { .recovery_init = rados_kv_init, .recovery_shutdown = rados_kv_shutdown, .end_grace = rados_kv_cleanup_old, .recovery_read_clids = rados_kv_read_recov_clids_takeover, .add_clid = rados_kv_add_clid, .rm_clid = rados_kv_rm_clid, .add_revoke_fh = rados_kv_add_revoke_fh, .get_nodeid = rados_kv_get_nodeid, }; void rados_kv_backend_init(struct nfs4_recovery_backend **backend) { *backend = &rados_kv_backend; } nfs-ganesha-6.5/src/SAL/recovery/recovery_rados_ng.c000066400000000000000000000224431473756622300225240ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2017 Red Hat, Inc. and/or its affiliates. * Author: Jeff Layton * * This program 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 3 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 * 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 * * recovery_rados_ng: a "safe by design" recovery backing store * * At startup, create a global write op, and set it up to clear out all of * the old keys. We then will spool up new client creation (and removals) to * that transaction during the grace period. * * When lifting the grace period, synchronously commit the transaction * to the kvstore. After that point, all client creation and removal is done * synchronously to the kvstore. * * This allows for better resilience when the server crashes during the grace * period. No changes are made to the backing store until the grace period * has been lifted. */ #include "config.h" #include #include #include #include "log.h" #include "nfs_core.h" #include "sal_functions.h" #include "recovery_rados.h" static rados_write_op_t grace_op; static pthread_mutex_t grace_op_lock; static int rados_ng_put(char *key, char *val, char *object) { int ret; char *keys[1]; char *vals[1]; size_t lens[1]; rados_write_op_t write_op = NULL; bool in_grace; keys[0] = key; vals[0] = val; lens[0] = strlen(val); /* When there is an active grace_op, spool up the changes to it */ PTHREAD_MUTEX_lock(&grace_op_lock); in_grace = grace_op; write_op = grace_op; if (!write_op) write_op = rados_create_write_op(); rados_write_op_omap_set(write_op, (const char *const *)keys, (const char *const *)vals, lens, 1); PTHREAD_MUTEX_unlock(&grace_op_lock); if (in_grace) return 0; ret = rados_write_op_operate(write_op, rados_recov_io_ctx, object, NULL, 0); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to put kv ret=%d, key=%s, val=%s", ret, key, val); } rados_release_write_op(write_op); return ret; } static int rados_ng_del(char *key, char *object) { int ret; char *keys[1]; rados_write_op_t write_op; bool in_grace; keys[0] = key; PTHREAD_MUTEX_lock(&grace_op_lock); in_grace = grace_op; write_op = in_grace ? grace_op : rados_create_write_op(); rados_write_op_omap_rm_keys(write_op, (const char *const *)keys, 1); PTHREAD_MUTEX_unlock(&grace_op_lock); if (in_grace) return 0; ret = rados_write_op_operate(write_op, rados_recov_io_ctx, object, NULL, 0); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to del kv ret=%d, key=%s", ret, key); } rados_release_write_op(write_op); return ret; } /* Cleanup on shutdown */ void rados_ng_cleanup(void) { PTHREAD_MUTEX_destroy(&grace_op_lock); } struct cleanup_list_element rados_ng_cleanup_element = { .clean = rados_ng_cleanup, }; static int rados_ng_init(void) { int ret; size_t len; struct gsh_refstr *recov_oid; char host[NI_MAXHOST]; rados_write_op_t op; PTHREAD_MUTEX_init(&grace_op_lock, NULL); RegisterCleanup(&rados_ng_cleanup_element); if (nfs_param.core_param.clustered) { ret = snprintf(host, sizeof(host), "node%d", g_nodeid); if (unlikely(ret >= sizeof(host))) { LogCrit(COMPONENT_CLIENTID, "node%d too long", g_nodeid); return -ENAMETOOLONG; } else if (unlikely(ret < 0)) { ret = errno; LogCrit(COMPONENT_CLIENTID, "Unexpected return from snprintf %d error %s (%d)", ret, strerror(ret), ret); return ret; } } else { ret = gethostname(host, sizeof(host)); if (ret) { LogEvent(COMPONENT_CLIENTID, "Failed to gethostname: %s (%d)", strerror(errno), errno); return -errno; } } len = strlen(host) + 6 + 1; recov_oid = gsh_refstr_alloc(len); gsh_refstr_get(recov_oid); /* Can't overrun and shouldn't return EOVERFLOW or EINVAL */ (void)snprintf(recov_oid->gr_val, len, "%s_recov", host); rcu_set_pointer(&rados_recov_oid, recov_oid); ret = rados_kv_connect(&rados_recov_io_ctx, rados_kv_param.userid, rados_kv_param.ceph_conf, rados_kv_param.pool, rados_kv_param.namespace); if (ret < 0) { gsh_refstr_put(recov_oid); LogEvent(COMPONENT_CLIENTID, "Failed to connect to cluster: %d", ret); return ret; } op = rados_create_write_op(); rados_write_op_create(op, LIBRADOS_CREATE_EXCLUSIVE, NULL); ret = rados_write_op_operate(op, rados_recov_io_ctx, recov_oid->gr_val, NULL, 0); gsh_refstr_put(recov_oid); if (ret < 0 && ret != -EEXIST) { LogEvent(COMPONENT_CLIENTID, "Failed to create object"); rados_release_write_op(op); rados_kv_shutdown(); return ret; } rados_release_write_op(op); /* Create new grace_op to spool changes until grace period is done */ grace_op = rados_create_write_op(); rados_write_op_omap_clear(grace_op); LogEvent(COMPONENT_CLIENTID, "Rados kv store init done"); return 0; } static void rados_ng_add_clid(nfs_client_id_t *clientid) { struct gsh_refstr *recov_oid; char ckey[RADOS_KEY_MAX_LEN]; char *cval; int ret; rados_kv_create_key(clientid, ckey, sizeof(ckey)); cval = rados_kv_create_val(clientid, NULL); LogDebug(COMPONENT_CLIENTID, "adding %s :: %s", ckey, cval); rcu_read_lock(); recov_oid = gsh_refstr_get(rcu_dereference(rados_recov_oid)); rcu_read_unlock(); ret = rados_ng_put(ckey, cval, recov_oid->gr_val); gsh_refstr_put(recov_oid); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to add clid %lu", clientid->cid_clientid); gsh_free(cval); } else { clientid->cid_recov_tag = cval; } } static void rados_ng_rm_clid(nfs_client_id_t *clientid) { char ckey[RADOS_KEY_MAX_LEN]; int ret; struct gsh_refstr *recov_oid; rados_kv_create_key(clientid, ckey, sizeof(ckey)); LogDebug(COMPONENT_CLIENTID, "removing %s", ckey); rcu_read_lock(); recov_oid = gsh_refstr_get(rcu_dereference(rados_recov_oid)); rcu_read_unlock(); ret = rados_ng_del(ckey, recov_oid->gr_val); gsh_refstr_put(recov_oid); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to del clid %lu", clientid->cid_clientid); return; } free(clientid->cid_recov_tag); clientid->cid_recov_tag = NULL; } void rados_ng_pop_clid_entry(char *key, char *val, size_t val_len, struct pop_args *pop_args) { char *dupval, *cl_name; char *rfh_names, *rfh_name; clid_entry_t *clid_ent; add_clid_entry_hook add_clid_entry = pop_args->add_clid_entry; add_rfh_entry_hook add_rfh_entry = pop_args->add_rfh_entry; /* extract clid records */ dupval = gsh_malloc(val_len + 1); memcpy(dupval, val, val_len); dupval[val_len] = '\0'; cl_name = strtok(dupval, "#"); if (!cl_name) cl_name = dupval; clid_ent = add_clid_entry(cl_name); rfh_names = strtok(NULL, "#"); rfh_name = strtok(rfh_names, "#"); while (rfh_name) { add_rfh_entry(clid_ent, rfh_name); rfh_name = strtok(NULL, "#"); } gsh_free(dupval); } static void rados_ng_read_recov_clids_recover(add_clid_entry_hook add_clid_entry, add_rfh_entry_hook add_rfh_entry) { int ret; struct gsh_refstr *recov_oid; struct pop_args args = { .add_clid_entry = add_clid_entry, .add_rfh_entry = add_rfh_entry, }; rcu_read_lock(); recov_oid = gsh_refstr_get(rcu_dereference(rados_recov_oid)); rcu_read_unlock(); ret = rados_kv_traverse(rados_ng_pop_clid_entry, &args, recov_oid->gr_val); gsh_refstr_put(recov_oid); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to recover, processing old entries"); return; } } static void rados_ng_read_recov_clids_takeover(nfs_grace_start_t *gsp, add_clid_entry_hook add_clid_entry, add_rfh_entry_hook add_rfh_entry) { if (!gsp) { rados_ng_read_recov_clids_recover(add_clid_entry, add_rfh_entry); return; } LogEvent(COMPONENT_CLIENTID, "Unable to perform takeover with rados_ng recovery backend."); } static void rados_ng_cleanup_old(void) { int ret; struct gsh_refstr *recov_oid; /* Commit pregrace transaction */ PTHREAD_MUTEX_lock(&grace_op_lock); rcu_read_lock(); recov_oid = gsh_refstr_get(rcu_dereference(rados_recov_oid)); rcu_read_unlock(); ret = rados_write_op_operate(grace_op, rados_recov_io_ctx, recov_oid->gr_val, NULL, 0); gsh_refstr_put(recov_oid); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "Failed to commit grace period transactions: %s", strerror(ret)); } rados_release_write_op(grace_op); grace_op = NULL; PTHREAD_MUTEX_unlock(&grace_op_lock); } struct nfs4_recovery_backend rados_ng_backend = { .recovery_init = rados_ng_init, .recovery_shutdown = rados_kv_shutdown, .end_grace = rados_ng_cleanup_old, .recovery_read_clids = rados_ng_read_recov_clids_takeover, .add_clid = rados_ng_add_clid, .rm_clid = rados_ng_rm_clid, .add_revoke_fh = rados_kv_add_revoke_fh, .get_nodeid = rados_kv_get_nodeid, }; void rados_ng_backend_init(struct nfs4_recovery_backend **backend) { *backend = &rados_ng_backend; } nfs-ganesha-6.5/src/SAL/sal_metrics.c000066400000000000000000000171761473756622300174700ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2024 Google LLC * Contributor : Yoni Couriel yonic@google.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file sal_metrics.c * @brief SAL metrics module */ #include "sal_metrics.h" #include "common_utils.h" #include "nfs_convert.h" /* Current number of confirmed clients gauge metric. */ static gauge_metric_handle_t confirmed_clients; /* Clients lease expired event counter metric. */ static counter_metric_handle_t lease_expire_event_count; /* Number of clients for each state-protection type */ static counter_metric_handle_t num_clients_per_state_protection[SP4_COUNT]; static gauge_metric_handle_t num_locks_metric[SAL_METRICS_LOCK_TYPE_COUNT]; /* Distribution of number of session-connections across sessions */ static histogram_metric_handle_t num_session_connections; static int64_t session_connections_buckets[] = { 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 24, 32 }; /* Number of denied session-and-xprt associations due to `dissociating` xprt */ static counter_metric_handle_t num_denied_session_xprt_associations; /* Number of xprts per custom-data status */ static counter_metric_handle_t num_xprts_per_custom_data_status[XPRT_CUSTOM_DATA_STATUS_COUNT]; /* Distribution of number of xprt-sessions across xprts */ static histogram_metric_handle_t num_xprt_sessions; static int64_t xprt_sessions_buckets[] = { 1, 2, 4, 6, 8, 10 }; /* Get strings corresponding to xprt_custom_data_status enum values */ static const char *get_xprt_custom_data_status(xprt_custom_data_status_t status) { switch (status) { case ASSOCIATED_TO_XPRT: return "ASSOCIATED_TO_XPRT"; case DISSOCIATED_FROM_XPRT: return "DISSOCIATED_FROM_XPRT"; case DESTROYED: return "DESTROYED"; default: LogFatal(COMPONENT_XPRT, "Unsupported xprt custom-data status"); } } static void register_num_xprts_per_custom_data_status_metric(void) { for (int i = 0; i < XPRT_CUSTOM_DATA_STATUS_COUNT; i++) { const metric_label_t labels[] = { METRIC_LABEL( "status", get_xprt_custom_data_status(i)) }; num_xprts_per_custom_data_status[i] = monitoring__register_counter( "xprt__per_custom_data_status_count", METRIC_METADATA("Total number of xprts per " "custom-data status", METRIC_UNIT_NONE), labels, ARRAY_SIZE(labels)); } } static void register_num_sessions_per_xprt_metric(void) { const metric_label_t empty_labels[] = {}; const histogram_buckets_t buckets = (histogram_buckets_t){ .buckets = xprt_sessions_buckets, .count = ARRAY_SIZE(xprt_sessions_buckets) }; num_xprt_sessions = monitoring__register_histogram( "xprt__sessions_count", METRIC_METADATA("Distribution of number of sessions " "associated with each xprt", METRIC_UNIT_NONE), empty_labels, ARRAY_SIZE(empty_labels), buckets); } /* Get strings corresponding to state_protectionion enum values */ static const char *get_state_protection_type(state_protect_how4 sp_how) { switch (sp_how) { case SP4_NONE: return "SP4_NONE"; case SP4_MACH_CRED: return "SP4_MACH_CRED"; case SP4_SSV: return "SP4_SSV"; default: LogFatal(COMPONENT_STATE, "Unsupported state protection"); } } static void register_num_clients_per_state_protection_metric(void) { for (int i = 0; i < SP4_COUNT; i++) { const metric_label_t sp_labels[] = { METRIC_LABEL( "sp_how", get_state_protection_type(i)) }; num_clients_per_state_protection[i] = monitoring__register_counter( "clients__per_state_protection_count", METRIC_METADATA("Total number of clients per " "state-protection type", METRIC_UNIT_NONE), sp_labels, ARRAY_SIZE(sp_labels)); } } static void register_client_metrics(void) { const metric_label_t empty_labels[] = {}; const char *const lock_type_string[SAL_METRICS_LOCK_TYPE_COUNT] = { "holders", "waiters" }; confirmed_clients = monitoring__register_gauge( "clients__confirmed_count", METRIC_METADATA("Total Number of Confirmed Clients", METRIC_UNIT_NONE), empty_labels, ARRAY_SIZE(empty_labels)); lease_expire_event_count = monitoring__register_counter( "clients__lease_expire_count", METRIC_METADATA("Total Number of Clients Lease Expired Events", METRIC_UNIT_NONE), empty_labels, ARRAY_SIZE(empty_labels)); for (sal_metrics__lock_type lock_type = 0; lock_type < SAL_METRICS_LOCK_TYPE_COUNT; lock_type++) { const metric_label_t lock_count_labels[] = { METRIC_LABEL( "lock_type", lock_type_string[lock_type]) }; num_locks_metric[lock_type] = monitoring__register_gauge( "locks__count", METRIC_METADATA("Total Number of Locks Record Count", METRIC_UNIT_NONE), lock_count_labels, ARRAY_SIZE(lock_count_labels)); } register_num_clients_per_state_protection_metric(); } static void register_num_session_connections_metric(void) { const metric_label_t empty_labels[] = {}; const histogram_buckets_t buckets = (histogram_buckets_t){ .buckets = session_connections_buckets, .count = ARRAY_SIZE(session_connections_buckets) }; num_session_connections = monitoring__register_histogram( "session__connections_count", METRIC_METADATA("Distribution of number of " "session-connections across sessions", METRIC_UNIT_NONE), empty_labels, ARRAY_SIZE(empty_labels), buckets); } static void register_num_denied_session_xprt_associations_metric(void) { const metric_label_t empty_labels[] = {}; num_denied_session_xprt_associations = monitoring__register_counter( "session__denied_xprt_associations_count", METRIC_METADATA("Total number of denied session-and-xprt " "associations across sessions", METRIC_UNIT_NONE), empty_labels, ARRAY_SIZE(empty_labels)); } void sal_metrics__confirmed_clients(int64_t num) { monitoring__gauge_set(confirmed_clients, num); } void sal_metrics__lease_expire(void) { monitoring__counter_inc(lease_expire_event_count, 1); } void sal_metrics__client_state_protection(state_protect_how4 sp) { monitoring__counter_inc(num_clients_per_state_protection[sp], 1); } void sal_metrics__locks_inc(sal_metrics__lock_type lock_type) { monitoring__gauge_inc(num_locks_metric[lock_type], 1); } void sal_metrics__locks_dec(sal_metrics__lock_type lock_type) { monitoring__gauge_dec(num_locks_metric[lock_type], 1); } void sal_metrics__session_connections(int64_t num) { monitoring__histogram_observe(num_session_connections, num); } void sal_metrics__xprt_association_denied(void) { monitoring__counter_inc(num_denied_session_xprt_associations, 1); } void sal_metrics__xprt_custom_data_status(xprt_custom_data_status_t status) { monitoring__counter_inc(num_xprts_per_custom_data_status[status], 1); } void sal_metrics__xprt_sessions(int64_t num) { monitoring__histogram_observe(num_xprt_sessions, num); } void sal_metrics__init(void) { register_client_metrics(); register_num_session_connections_metric(); register_num_denied_session_xprt_associations_metric(); register_num_xprts_per_custom_data_status_metric(); register_num_sessions_per_xprt_metric(); } nfs-ganesha-6.5/src/SAL/state_async.c000066400000000000000000000215651473756622300174750ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file state_async.c * @brief Management of SAL asynchronous processing */ #include "config.h" #include #include #include #include #include #include #include "log.h" #include "hashtable.h" #include "fsal.h" #include "sal_functions.h" #include "fridgethr.h" #include "gsh_config.h" #include "nfs_core.h" struct fridgethr *state_async_fridge; struct fridgethr *state_poll_fridge; /** * @brief Process a blocked lock request * * We use this wrapper so we can void rewriting stuff. We can change * this later. * * @param[in] ctx Thread fridge context, containing arguments. */ static void state_blocked_lock_caller(struct fridgethr_context *ctx) { state_lock_entry_t *lock_entry = ctx->arg; struct gsh_export *export; bool set_op_ctx = false; struct req_op_context op_context; export = lock_entry->sle_export; if (export_ready(export)) { get_gsh_export_ref(export); /* Initialize a root context, need to get a valid export. */ init_op_context(&op_context, export, export->fsal_export, NULL, NULL, 0, 0, UNKNOWN_REQUEST); set_op_ctx = true; } process_blocked_lock_upcall(lock_entry); /* We are done with the lock_entry, release the reference now. */ lock_entry_dec_ref(lock_entry); if (set_op_ctx) release_op_context(); } /** * @brief Process and cancel blocked lock request * * @param[in] ctx Thread fridge context, containing arguments. */ static void state_blocked_lock_cancel(struct fridgethr_context *ctx) { state_lock_entry_t *lock_entry = ctx->arg; struct gsh_export *export; struct req_op_context op_context; export = lock_entry->sle_export; if (!export_ready(export)) { LogCrit(COMPONENT_STATE, "export not ready for a lock that we want to cancel"); return; } get_gsh_export_ref(export); /* Initialize a root context, need to get a valid export. */ init_op_context(&op_context, export, export->fsal_export, NULL, NULL, 0, 0, UNKNOWN_REQUEST); state_status_t ret = state_cancel_blocked(lock_entry); LogFullDebug(COMPONENT_STATE, "unlock returned %d", ret); /* We are done with the lock_entry, release the reference now. */ lock_entry_dec_ref(lock_entry); release_op_context(); } /** * @brief Test blocking lock eligibility and send granted callback on success * * This can be useful in case the original callback hasn't reached the client. * * @param[in] ctx Thread fridge context, containing arguments. */ static void test_blocking_lock_eligibility(struct fridgethr_context *ctx) { state_lock_entry_t *lock_entry = ctx->arg; struct gsh_export *export; struct req_op_context op_context; export = lock_entry->sle_export; if (!export_ready(export)) { LogCrit(COMPONENT_STATE, "export not ready for the lock that we want to test"); lock_entry_dec_ref(lock_entry); return; } get_gsh_export_ref(export); /* Initialize a root context, needed to get a valid export. */ init_op_context(&op_context, export, export->fsal_export, NULL, NULL, 0, 0, UNKNOWN_REQUEST); state_status_t lock_test_status = state_test(lock_entry->sle_obj, lock_entry->sle_state, lock_entry->sle_owner, &lock_entry->sle_lock, /* holder */ NULL, /* conflict */ NULL); LogFullDebug(COMPONENT_STATE, "lock test returned %d", lock_test_status); if (lock_test_status == STATE_SUCCESS) process_blocked_lock_upcall(lock_entry); lock_entry_dec_ref(lock_entry); release_op_context(); } /** * @brief Process an async request * * We use this wrapper so we can avoid having to rewrite every async * func. Later on we might want to remove it. * * @param[in] ctx Thread fridge context, containing arguments. */ static void state_async_func_caller(struct fridgethr_context *ctx) { state_async_queue_t *entry = ctx->arg; entry->state_async_func(entry); } /** * @brief Schedule an asynchronous action * * @param[in] arg Request to schedule * * @return State status. */ state_status_t state_async_schedule(state_async_queue_t *arg) { int rc; LogFullDebug(COMPONENT_STATE, "Schedule %p", arg); rc = fridgethr_submit(state_async_fridge, state_async_func_caller, arg); if (rc != 0) LogCrit(COMPONENT_STATE, "Unable to schedule request: %d", rc); return rc == 0 ? STATE_SUCCESS : STATE_SIGNAL_ERROR; } /** * @brief Schedule a lock notification * * @param[in] block Lock to schedule * * @return State status. */ state_status_t state_block_schedule(state_lock_entry_t *found_entry) { int rc; LogFullDebug(COMPONENT_STATE, "Schedule notification %p", found_entry); rc = fridgethr_submit(state_async_fridge, state_blocked_lock_caller, found_entry); if (rc != 0) LogMajor(COMPONENT_STATE, "Unable to schedule request: %d", rc); return rc == 0 ? STATE_SUCCESS : STATE_SIGNAL_ERROR; } /** * @brief Schedule a cancel * * @param[in] lock cancel to schedule * * @return State status. */ state_status_t state_block_cancel_schedule(state_lock_entry_t *lock_entry) { int rc; LogFullDebug(COMPONENT_STATE, "Schedule unlock %p", lock_entry); rc = fridgethr_submit(state_async_fridge, state_blocked_lock_cancel, lock_entry); if (rc != 0) LogMajor(COMPONENT_STATE, "Unable to schedule request: %d", rc); return rc == 0 ? STATE_SUCCESS : STATE_SIGNAL_ERROR; } /** * @brief Schedule a blocking lock eligibility test * * @param[in] lock to schedule the eligibility test for * * @return State status. */ state_status_t test_blocking_lock_eligibility_schedule(state_lock_entry_t *lock_entry) { int rc; LogFullDebug(COMPONENT_STATE, "Schedule blocking lock eligibility test %p", lock_entry); rc = fridgethr_submit(state_async_fridge, test_blocking_lock_eligibility, lock_entry); if (rc != 0) LogMajor(COMPONENT_STATE, "Unable to schedule request: %d", rc); return rc == 0 ? STATE_SUCCESS : STATE_SIGNAL_ERROR; } /** * @brief Initialize asynchronous request system * * @return State status. */ state_status_t state_async_init(void) { int rc = 0; struct fridgethr_params frp; memset(&frp, 0, sizeof(struct fridgethr_params)); frp.thr_max = 1; frp.deferment = fridgethr_defer_queue; rc = fridgethr_init(&state_async_fridge, "State_Async", &frp); if (rc != 0) { LogMajor(COMPONENT_STATE, "Unable to initialize state async thread fridge: %d", rc); return STATE_INIT_ENTRY_FAILED; } memset(&frp, 0, sizeof(struct fridgethr_params)); frp.thr_max = 1; frp.thr_min = 1; frp.thread_delay = nfs_param.core_param.blocked_lock_poller_interval; frp.flavor = fridgethr_flavor_looper; rc = fridgethr_init(&state_poll_fridge, "state_poll", &frp); if (rc != 0) { LogMajor( COMPONENT_STATE, "Unable to initialize state blocked lock polling thread fridge: %d", rc); return STATE_INIT_ENTRY_FAILED; } rc = fridgethr_submit(state_poll_fridge, blocked_lock_polling, NULL); if (rc != 0) { LogMajor( COMPONENT_STATE, "Unable to start blocked lock polling thread, error code %d.", rc); return STATE_INIT_ENTRY_FAILED; } return STATE_SUCCESS; } /** * @brief Shut down asynchronous request system * * @return State status. */ state_status_t state_async_shutdown(void) { int rc1, rc2; rc1 = fridgethr_sync_command(state_async_fridge, fridgethr_comm_stop, 120); if (rc1 == ETIMEDOUT) { LogMajor(COMPONENT_STATE, "Shutdown timed out, cancelling threads."); fridgethr_cancel(state_async_fridge); } else if (rc1 != 0) { LogMajor(COMPONENT_STATE, "Failed shutting down state async thread: %d", rc1); } rc2 = fridgethr_sync_command(state_poll_fridge, fridgethr_comm_stop, 120); if (rc2 == ETIMEDOUT) { LogMajor(COMPONENT_STATE, "Shutdown timed out, cancelling threads."); fridgethr_cancel(state_poll_fridge); } else if (rc2 != 0) { LogMajor( COMPONENT_STATE, "Failed shutting down state blocked lock polling thread: %d", rc2); } return ((rc1 == 0) && (rc2 == 0)) ? STATE_SUCCESS : STATE_SIGNAL_ERROR; } /** @} */ nfs-ganesha-6.5/src/SAL/state_deleg.c000066400000000000000000000543521473756622300174400ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright IBM (2014) * contributeur : Jeremy Bongio jbongio@us.ibm.com * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file state_deleg.c * @brief Delegation management */ #include "config.h" #include #include #include #include #include #include #include #include "fsal.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs4.h" #include "sal_functions.h" #include "export_mgr.h" #include "nfs_rpc_callback.h" #include "server_stats.h" #include "fsal_up.h" #include "nfs_file_handle.h" #include "nfs_convert.h" #include "fsal_convert.h" /* Keeps track of total number of files delegated */ int32_t g_total_num_files_delegated; int32_t g_max_files_delegatable; /** * @brief Initialize new delegation state as argument for state_add() * * Initialize delegation state struct. This is then given as an argument * to state_add() * * @param[in/out] deleg_state Delegation state struct to be init. Can't be NULL. * @param[in] sd_type Type of delegation, READ or WRITE. * @param[in] client The client that will own this delegation. */ void init_new_deleg_state(union state_data *deleg_state, open_delegation_type4 deleg_type, nfs_client_id_t *client) { struct cf_deleg_stats *clfile_entry = &deleg_state->deleg.sd_clfile_stats; deleg_state->deleg.sd_type = deleg_type; deleg_state->deleg.sd_state = DELEG_GRANTED; clfile_entry->cfd_rs_time = 0; clfile_entry->cfd_r_time = 0; } /** * @brief Perform a lease lock operation * * We do state management and call down to the FSAL as appropriate, so * that the caller has a single entry point. * * @param[in] obj File on which to operate * @param[in] lock_op Operation to perform * @param[in] owner Lock operation * @param[in] lock Lock description * * @return State status. */ state_status_t do_lease_op(struct fsal_obj_handle *obj, state_t *state, state_owner_t *owner, fsal_deleg_t deleg) { fsal_status_t fsal_status; state_status_t status; /* Perform this delegation operation using the new * multiple file-descriptors. */ fsal_status = obj->obj_ops->lease_op2(obj, state, owner, deleg); status = state_error_convert(fsal_status); LogFullDebug(COMPONENT_STATE, "FSAL lease_op2 returned %s", state_err_str(status)); return status; } /** * @brief Attempt to acquire a lease lock (delegation) * * @note The st_lock MUST be held * * @param[in] ostate File state to get lease lock on * @param[in] owner Owner for the lease lock * @param[in] state Associated state for the lock */ state_status_t acquire_lease_lock(struct state_hdl *ostate, state_owner_t *owner, state_t *state) { state_status_t status; fsal_deleg_t deleg = FSAL_DELEG_RD; if (state->state_data.deleg.sd_type == OPEN_DELEGATE_WRITE) deleg = FSAL_DELEG_WR; /* Create a new deleg data object */ status = do_lease_op(ostate->file.obj, state, owner, deleg); if (status == STATE_SUCCESS) { update_delegation_stats(ostate, owner); reset_cbgetattr_stats(ostate->file.obj); } else { LogDebug(COMPONENT_STATE, "Could not set lease, error=%s", state_err_str(status)); } return status; } /** * @brief Release a lease lock (delegation) * * @param[in] state Associated state * * st_lock must be held while calling this function */ state_status_t release_lease_lock(struct fsal_obj_handle *obj, state_t *state) { state_status_t status; state_owner_t *owner = get_state_owner_ref(state); /* Something is going stale? */ if (owner == NULL) return STATE_ESTALE; status = do_lease_op(obj, state, owner, FSAL_DELEG_NONE); if (status != STATE_SUCCESS) LogMajor(COMPONENT_STATE, "Unable to unlock FSAL, error=%s", state_err_str(status)); dec_state_owner_ref(owner); return status; } /** * @brief Update statistics on successfully granted delegation. * * Update statistics on successfully granted delegation. * Note: This should be called only when a delegation is successfully granted. * So far this should only be called in state_lock(). * * @param[in] Delegation Entry */ void update_delegation_stats(struct state_hdl *ostate, state_owner_t *owner) { nfs_client_id_t *client = owner->so_owner.so_nfs4_owner.so_clientrec; /* Update delegation stats for file. */ struct file_deleg_stats *statistics = &ostate->file.fdeleg_stats; statistics->fds_curr_delegations++; statistics->fds_delegation_count++; statistics->fds_last_delegation = time(NULL); /* Update delegation stats for client. */ inc_grants(client->gsh_client); client->curr_deleg_grants++; } /* Add a new delegation length to the average length stat. */ static int advance_avg(time_t prev_avg, time_t new_time, uint32_t prev_tot, uint32_t curr_tot) { return ((prev_tot * prev_avg) + new_time) / curr_tot; } /* * @brief reset cbgetattr struct args */ void reset_cbgetattr_stats(struct fsal_obj_handle *obj) { cbgetattr_t *cbgetattr = &obj->state_hdl->file.cbgetattr; cbgetattr->state = CB_GETATTR_NONE; cbgetattr->modified = false; } /** * @brief Update statistics on successfully recalled delegation. * * Update statistics on successfully recalled delegation. * Note: This should be called only when a delegation is successfully recalled. * * @param[in] deleg Delegation state */ void deleg_heuristics_recall(struct fsal_obj_handle *obj, state_owner_t *owner, struct state_t *deleg) { nfs_client_id_t *client = owner->so_owner.so_nfs4_owner.so_clientrec; /* Update delegation stats for file. */ struct file_deleg_stats *statistics = &obj->state_hdl->file.fdeleg_stats; statistics->fds_curr_delegations--; statistics->fds_recall_count++; /* Reset the delegation type if no active delegation present. */ if (statistics->fds_curr_delegations == 0) { LogFullDebug( COMPONENT_STATE, "Resetting Deleg type (%d/%d) as file has no delegation", statistics->fds_curr_delegations, statistics->fds_deleg_type); statistics->fds_deleg_type = OPEN_DELEGATE_NONE; DEC_G_Total_Num_Files_Delegated( statistics->fds_curr_delegations); } /* Update delegation stats for client. */ dec_grants(client->gsh_client); client->curr_deleg_grants--; /* Update delegation stats for file. */ statistics->fds_avg_hold = advance_avg( statistics->fds_avg_hold, time(NULL) - statistics->fds_last_delegation, statistics->fds_recall_count - 1, statistics->fds_recall_count); } /** * @brief Initialize the file-specific delegation statistics * * Initialize the file-specific delegation statistics used later for deciding * if a delegation should be granted on this file based on heuristics. * * @param[in] obj File the delegation will be on. */ bool init_deleg_heuristics(struct fsal_obj_handle *obj) { struct file_deleg_stats *statistics; if (obj->type != REGULAR_FILE) { LogCrit(COMPONENT_STATE, "Initialization of delegation stats for an obj that is NOT a regular file!"); return false; } statistics = &obj->state_hdl->file.fdeleg_stats; statistics->fds_curr_delegations = 0; statistics->fds_deleg_type = OPEN_DELEGATE_NONE; statistics->fds_delegation_count = 0; statistics->fds_recall_count = 0; statistics->fds_last_delegation = 0; statistics->fds_last_recall = 0; statistics->fds_avg_hold = 0; statistics->fds_num_opens = 0; statistics->fds_first_open = 0; statistics->fds_num_write_opens = 0; return true; } /* Most clients retry NFS operations after 5 seconds. The following * should be good enough to avoid starving a client's open */ #define RECALL2DELEG_TIME 10 /** * @brief Decide if a delegation should be granted based on heuristics. * * Decide if a delegation should be granted based on heuristics. * * @note The st_lock MUST be held * * @param[in] ostate File state the delegation will be on. * @param[in] client The client that would own the delegation. * @param[in] open_state The open state for the inode to be delegated. * @param[in/out] resok pointer to resok (for setting ond_why, primarily) * @param[in] owner state owner * @param[out] prerecall flag for reclaims. */ bool should_we_grant_deleg(struct state_hdl *ostate, nfs_client_id_t *client, state_t *open_state, OPEN4args *args, OPEN4resok *resok, state_owner_t *owner, bool *prerecall) { /* specific file, all clients, stats */ struct file_deleg_stats *file_stats = &ostate->file.fdeleg_stats; /* specific client, all files stats */ open_claim_type4 claim = args->claim.claim; LogDebug(COMPONENT_STATE, "Checking if we should grant delegation."); assert(open_state->state_type == STATE_TYPE_SHARE); *prerecall = false; if (!nfs_param.nfsv4_param.allow_delegations || !op_ctx->fsal_export->exp_ops.fs_supports(op_ctx->fsal_export, fso_delegations_r) || !(op_ctx->export_perms.options & EXPORT_OPTION_DELEGATIONS) || (!owner->so_owner.so_nfs4_owner.so_confirmed && claim == CLAIM_NULL) || claim == CLAIM_DELEGATE_CUR) { resok->delegation.open_delegation4_u.od_whynone.ond_why = WND4_NOT_SUPP_FTYPE; return false; } /* set the pre-recall flag for reclaims if the server does not want the * delegation to remain in force */ if (get_cb_chan_down(client)) { switch (claim) { case CLAIM_PREVIOUS: *prerecall = true; return args->claim.open_claim4_u.delegate_type == OPEN_DELEGATE_NONE ? false : true; case CLAIM_DELEGATE_PREV: *prerecall = true; return true; default: resok->delegation.open_delegation4_u.od_whynone.ond_why = WND4_RESOURCE; return false; } } else { *prerecall = false; switch (claim) { case CLAIM_PREVIOUS: return args->claim.open_claim4_u.delegate_type == OPEN_DELEGATE_NONE ? false : true; case CLAIM_DELEGATE_PREV: return true; default: break; } } /* If there is a recent recall on this file, the client that made * the conflicting open may retry the open later. Don't give out * delegation to avoid starving the client's open that caused * the recall. */ if (file_stats->fds_last_recall != 0 && time(NULL) - file_stats->fds_last_recall < RECALL2DELEG_TIME) { resok->delegation.open_delegation4_u.od_whynone.ond_why = WND4_CONTENTION; return false; } /* Check if this is a misbehaving or unreliable client */ if (client->num_revokes > 2) { /* more than 2 revokes */ resok->delegation.open_delegation4_u.od_whynone.ond_why = WND4_RESOURCE; return false; } /* If some client is holding a write file descriptor donot * delegate. OPEN4_SHARE_ACCESS_READ and OPEN4_SHARE_ACCESS_WRITE * are handled differently because if we are currently handling * OPEN4_SHARE_ACCESS_WRITE then we would have incremented the * counter before calling this function. */ if ((args->share_access & OPEN4_SHARE_ACCESS_READ && file_stats->fds_num_write_opens > 0) || (args->share_access & OPEN4_SHARE_ACCESS_WRITE && file_stats->fds_num_write_opens > 1)) { resok->delegation.open_delegation4_u.od_whynone.ond_why = WND4_CONTENTION; return false; } /* Deny delegations on this file if this is the first delegation * and we have reached the maximum number of files we can delegate * We have reached the maximum number of files we can delegate * if atomic_add_unless below returns false */ if ((file_stats->fds_curr_delegations == 0) && !atomic_add_unless_int32_t(&g_total_num_files_delegated, 1, g_max_files_delegatable)) { LogFullDebug( COMPONENT_STATE, "Can't delegate file since Files_Delegatable_Percent limit is hit"); resok->delegation.open_delegation4_u.od_whynone.ond_why = WND4_RESOURCE; return false; } LogFullDebug(COMPONENT_STATE, "total_num_files_delegated is %d", atomic_fetch_int32_t(&g_total_num_files_delegated)); LogDebug(COMPONENT_STATE, "Let's delegate!!"); return true; } /** * @brief Form the ACE mask for the delegated file. * * Form the ACE mask for the delegated file. * * @param[in,out] permissions ACE mask for delegated inode. * @param[in] type The type of delegation. Either READ or WRITE. */ void get_deleg_perm(nfsace4 *permissions, open_delegation_type4 type) { /* We need to create an access_mask that shows who * can OPEN this file. */ if (type == OPEN_DELEGATE_WRITE) ; else if (type == OPEN_DELEGATE_READ) ; permissions->type = ACE4_ACCESS_ALLOWED_ACE_TYPE; permissions->flag = 0; permissions->access_mask = 0; permissions->who.utf8string_len = 0; permissions->who.utf8string_val = NULL; } /** * @brief Mark a delegation revoked * * Mark the delegation state revoked, further ops on this state should return * NFS4ERR_REVOKED or NFS4ERR_EXPIRED * * @param[in] deleg state lock entry. * Should be called with state lock held. */ nfsstat4 deleg_revoke(struct fsal_obj_handle *obj, struct state_t *deleg_state) { state_status_t state_status; struct nfs_client_id_t *clid; nfs_fh4 fhandle; struct req_op_context op_context; struct gsh_export *export; state_owner_t *owner; /* Get reference to owner and export. Onwer reference also protects * the clientid. */ if (!get_state_obj_export_owner_refs(deleg_state, NULL, &export, &owner)) { /* Something is going stale. */ LogDebug(COMPONENT_NFS_V4_LOCK, "Stale state, owner, or export"); return NFS4ERR_STALE; } clid = owner->so_owner.so_nfs4_owner.so_clientrec; /* Building a new fh ; Ignore return code, should not fail*/ (void)nfs4_FSALToFhandle(true, &fhandle, obj, export); deleg_heuristics_recall(obj, owner, deleg_state); reset_cbgetattr_stats(obj); /* Build op_context for state_unlock_locked */ init_op_context_simple(&op_context, export, export->fsal_export); op_ctx->clientid = &clid->cid_clientid; /* release_lease_lock() returns delegation to FSAL */ state_status = release_lease_lock(obj, deleg_state); if (state_status != STATE_SUCCESS) { LogDebug(COMPONENT_NFS_V4_LOCK, "state unlock failed: %d", state_status); } /* Put the revoked delegation on the stable storage. */ nfs4_record_revoke(clid, &fhandle); state_del_locked(deleg_state); gsh_free(fhandle.nfs_fh4_val); /* Release references taken above */ dec_state_owner_ref(owner); release_op_context(); return NFS4_OK; } /** * @brief Mark the delegation revoked * * Mark the delegation state revoked, further ops on this state should * return NFS4ERR_REVOKED or NFS4ERR_EXPIRED * * @note The st_lock MUST be held * * @param[in] obj File * @param[in] state Delegation state */ void state_deleg_revoke(struct fsal_obj_handle *obj, state_t *state) { /* If we are already in the process of recalling or revoking * this delegation from elsewhere, skip it here. */ if (state->state_data.deleg.sd_state != DELEG_GRANTED) return; state->state_data.deleg.sd_state = DELEG_RECALL_WIP; (void)deleg_revoke(obj, state); } /** * @brief Check if the file is write delegated under st_lock * * Check if the file is write delegated. If yes, take a ref and return * the client holding the delegation. * * @note: The caller should acquire st_lock before calling this * function. * * @param[in] obj File * @param[out] client holding the delegation * * @retval true if file is write delegated * @retval false otherwise */ bool is_write_delegated(struct fsal_obj_handle *obj, nfs_client_id_t **client) { bool write_delegated = false; struct file_deleg_stats *deleg_stats; if (obj->type != REGULAR_FILE) return false; deleg_stats = &obj->state_hdl->file.fdeleg_stats; if (deleg_stats->fds_curr_delegations == 0) return false; write_delegated = obj->state_hdl->file.write_delegated; if (write_delegated && client) { *client = obj->state_hdl->file.write_deleg_client; inc_client_id_ref(*client); } return write_delegated; } /** * @brief Check if an operation is conflicting with delegations. * * Check if an operation will conflict with current delegations on a file. * Return TRUE if there is a conflict and the delegations have been recalled. * Return FALSE if there is no conflict. * * @note The st_lock MUST be held * * @param[in] obj File * @param[in] write a boolean indicating whether the operation will read or * change the file. * * @retval true if there is a conflict and the delegations have been recalled. * @retval false if there is no delegation conflict. */ bool state_deleg_conflict_impl(struct fsal_obj_handle *obj, bool write) { struct file_deleg_stats *deleg_stats; struct gsh_client *deleg_client = NULL; deleg_stats = &obj->state_hdl->file.fdeleg_stats; if (obj->state_hdl->file.write_delegated) deleg_client = obj->state_hdl->file.write_deleg_client->gsh_client; if (deleg_stats->fds_curr_delegations > 0 && ((deleg_stats->fds_deleg_type == OPEN_DELEGATE_READ && write) || (deleg_stats->fds_deleg_type == OPEN_DELEGATE_WRITE && deleg_client != op_ctx->client))) { LogDebug( COMPONENT_STATE, "While trying to perform a %s op, found a conflicting %s delegation", write ? "write" : "read", (deleg_stats->fds_deleg_type == OPEN_DELEGATE_WRITE) ? "WRITE" : "READ"); if (async_delegrecall(general_fridge, obj) != 0) LogCrit(COMPONENT_STATE, "Failed to start thread to recall delegation from conflicting operation."); return true; } return false; } /** * @brief Acquire st_lock and check if an operation is conflicting * with delegations. * * @param[in] obj File * @param[in] write a boolean indicating whether the operation will read or * change the file. * * @retval true if there is a conflict and the delegations have been recalled. * @retval false if there is no delegation conflict. */ bool state_deleg_conflict(struct fsal_obj_handle *obj, bool write) { bool status = false; /* * Check the type before grabbing the lock. Which lock in state_hdl is * valid depends on the object's type. */ if (obj->type != REGULAR_FILE) return false; STATELOCK_lock(obj); status = state_deleg_conflict_impl(obj, write); STATELOCK_unlock(obj); return status; } /* * @brief: fetch getattr from the write_delegated client * * Send CB_GETATTR to the write_delegated client to fetch * right attributes. If not recall delegation. * * @note: should be called under st_lock. */ nfsstat4 handle_deleg_getattr(struct fsal_obj_handle *obj, nfs_client_id_t *client) { nfsstat4 status = NFS4ERR_DELAY; int rc = 0; enum cbgetattr_state cb_state; /* Check for delegation conflict.*/ LogDebug( COMPONENT_STATE, "While trying to perform a GETATTR op, found a conflicting WRITE delegation"); /* * @todo: Provide an option for user to enable CB_GETATTR */ cb_state = obj->state_hdl->file.cbgetattr.state; switch (cb_state) { case CB_GETATTR_RSP_OK: /* got response for CB_GETATTR */ status = NFS4_OK; goto out; case CB_GETATTR_WIP: /* wait for response */ goto out; case CB_GETATTR_FAILED: goto deleg_recall; default: /* CB_GETATTR_NONE */ goto send_request; } send_request: LogDebug(COMPONENT_STATE, "sending CB_GETATTR"); rc = async_cbgetattr(general_fridge, obj, client); if (rc != 0) { LogCrit(COMPONENT_STATE, "Failed to start thread to send cb_getattr."); goto deleg_recall; } goto out; deleg_recall: LogDebug(COMPONENT_STATE, "CB_GETATTR is either not enabled or failed," " recalling write delegation"); rc = async_delegrecall(general_fridge, obj); if (rc != 0) { LogCrit(COMPONENT_STATE, "Failed to start thread to recall delegation from conflicting operation."); goto out; } out: if (rc != 0) { status = nfs4_Errno_status(fsalstat(posix2fsal_error(rc), rc)); } return status; } bool deleg_supported(struct fsal_obj_handle *obj, struct fsal_export *fsal_export, struct export_perms *export_perms, uint32_t share_access) { if (!nfs_param.nfsv4_param.allow_delegations) return false; if (obj->type != REGULAR_FILE) return false; /* In a read-write case, we handle write delegation. So we should * check for OPEN4_SHARE_ACCESS_WRITE bit first! */ if (share_access & OPEN4_SHARE_ACCESS_WRITE) { if (!fsal_export->exp_ops.fs_supports(fsal_export, fso_delegations_w)) return false; if (!(export_perms->options & EXPORT_OPTION_WRITE_DELEG)) return false; } else { assert(share_access & OPEN4_SHARE_ACCESS_READ); if (!fsal_export->exp_ops.fs_supports(fsal_export, fso_delegations_r)) return false; if (!(export_perms->options & EXPORT_OPTION_READ_DELEG)) return false; } return true; } /** * @brief Check to see if a delegation can be granted * * @note The st_lock MUST be held * * @param[in] ostate State to check * @return true if can grant, false otherwise */ bool can_we_grant_deleg(struct state_hdl *ostate, state_t *open_state) { struct glist_head *glist; state_lock_entry_t *lock_entry; const struct state_share *share = &open_state->state_data.share; /* Can't grant delegation if there is an anonymous operation * in progress */ if (atomic_fetch_uint32_t(&ostate->file.anon_ops) != 0) { LogFullDebug( COMPONENT_STATE, "Anonymous op in progress, not granting delegation"); return false; } /* Check for conflicting NLM locks. Write delegation would conflict * with any kind of NLM lock, and NLM write lock would conflict * with any kind of delegation. */ glist_for_each(glist, &ostate->file.lock_list) { lock_entry = glist_entry(glist, state_lock_entry_t, sle_list); if (lock_entry->sle_lock.lock_type == FSAL_NO_LOCK) continue; /* no lock, skip */ if (share->share_access & OPEN4_SHARE_ACCESS_WRITE || lock_entry->sle_lock.lock_type == FSAL_LOCK_W) { LogFullDebug( COMPONENT_STATE, "Conflicting NLM lock. Not granting delegation"); return false; } } return true; } nfs-ganesha-6.5/src/SAL/state_layout.c000066400000000000000000000163721473756622300176750ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2011, Linux Box Corporation * contributor: Adam C. Emerson * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file state_layout.c * @brief Layout state management. */ #include "config.h" #include #include #include #include #include #include #include "log.h" #include "hashtable.h" #include "fsal.h" #include "sal_functions.h" #include "nfs_core.h" #include "nfs_proto_tools.h" /** * @brief Add a segment to an existing layout state * * This function is intended to be used in nfs41_op_layoutget to add * each segment returned by FSAL_layoutget to an existing state of * type STATE_TYPE_LAYOUT. * * @note st_lock must be held. * * @param[in] state The layout state. * @param[in] segment Layout segment itself granted by the FSAL * @param[in] fsal_data Pointer to FSAL-specific data for this segment. * @param[in] return_on_close True for automatic return on last close * * @return STATE_SUCCESS on completion, other values of state_status_t * on failure. */ state_status_t state_add_segment(state_t *state, struct pnfs_segment *segment, void *fsal_data, bool return_on_close) { /* Pointer to the new segment being added to the state */ state_layout_segment_t *new_segment = NULL; if (state->state_type != STATE_TYPE_LAYOUT) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_stateid(&dspbuf, state); LogCrit(COMPONENT_PNFS, "Attempt to add layout segment to non-layout state: %s", str); return STATE_BAD_TYPE; } new_segment = gsh_calloc(1, sizeof(*new_segment)); new_segment->sls_fsal_data = fsal_data; new_segment->sls_state = state; new_segment->sls_segment = *segment; glist_add_tail(&state->state_data.layout.state_segments, &new_segment->sls_state_segments); /* Based on comments by Benny Halevy, if any segment is marked return_on_close, all segments should be treated as return_on_close. */ if (return_on_close) state->state_data.layout.state_return_on_close = true; return STATE_SUCCESS; } /** * @brief Delete a layout segment * * This function must be called with the mutex lock held. * * @param[in] segment The segment to delete * * @return State status. */ state_status_t state_delete_segment(state_layout_segment_t *segment) { glist_del(&segment->sls_state_segments); gsh_free(segment); return STATE_SUCCESS; } /** * @brief Find pre-existing layouts * * This function finds a layout corresponding to a given file, * clientid, and layout type if one exists. * * @note st_lock MUST be held * * @param[in] obj File * @param[in] owner The state owner. This must be a clientid owner. * @param[in] type Layout type specified by the client. * @param[out] state The found state, NULL if not found. * * @return STATE_SUCCESS if the layout is found, STATE_NOT_FOUND if it * isn't, and an appropriate code if other bad things happen. */ state_status_t state_lookup_layout_state(struct fsal_obj_handle *obj, state_owner_t *owner, layouttype4 type, state_t **state) { /* Pointer for iterating over the list of states on the file */ struct glist_head *glist_iter = NULL; /* The state under inspection in the loop */ state_t *state_iter = NULL; glist_for_each(glist_iter, &obj->state_hdl->file.list_of_states) { state_iter = glist_entry(glist_iter, state_t, state_list); if (state_iter->state_type == STATE_TYPE_LAYOUT && state_same_owner(state_iter, owner) && state_iter->state_data.layout.state_layout_type == type) { inc_state_t_ref(state_iter); *state = state_iter; return STATE_SUCCESS; } } return STATE_NOT_FOUND; } /** * @brief Revoke layouts belonging to the client owner. * * @param[in,out] client owner */ void revoke_owner_layouts(state_owner_t *client_owner) { state_t *state, *first; struct fsal_obj_handle *obj; struct saved_export_context saved; struct gsh_export *export; int errcnt = 0; struct glist_head *glist, *glistn; bool so_mutex_held; again: first = NULL; PTHREAD_MUTEX_lock(&client_owner->so_mutex); so_mutex_held = true; glist_for_each_safe(glist, glistn, &client_owner->so_owner.so_nfs4_owner.so_state_list) { bool deleted = false; struct pnfs_segment entire = { .io_mode = LAYOUTIOMODE4_ANY, .offset = 0, .length = NFS4_UINT64_MAX }; state = glist_entry(glist, state_t, state_owner_list); /* We set first to the first state we look in this iteration. * If the current state matches the first state, it implies * that went through the entire list without dropping the lock * guarding the list. So nothing more left to process. */ if (first == NULL) first = state; else if (first == state) break; /* Move entry to end of list to handle errors and skipping of * non-layout states. */ glist_move_tail( &client_owner->so_owner.so_nfs4_owner.so_state_list, &state->state_owner_list); /* Skip non-layout states. */ if (state->state_type != STATE_TYPE_LAYOUT) continue; if (!get_state_obj_export_owner_refs(state, &obj, &export, NULL)) { LogDebug(COMPONENT_STATE, "Stale state or file"); continue; } inc_state_t_ref(state); /* Get a ref to the proper export and add it to op_ctx */ get_gsh_export_ref(export); save_op_context_export_and_set_export(&saved, export); PTHREAD_MUTEX_unlock(&client_owner->so_mutex); so_mutex_held = false; STATELOCK_lock(obj); (void)nfs4_return_one_state(obj, LAYOUTRETURN4_FILE, circumstance_revoke, state, entire, 0, NULL, &deleted); if (!deleted) { errcnt++; LogCrit(COMPONENT_PNFS, "Layout state not destroyed during lease expiry."); } STATELOCK_unlock(obj); /* Release the reference taken above */ obj->obj_ops->put_ref(obj); dec_state_t_ref(state); restore_op_context_export(&saved); if (errcnt < STATE_ERR_MAX) { /* Loop again, but since we dropped the so_mutex, we * must restart. */ goto again; } /* Too many errors, quit. */ break; } if (so_mutex_held) PTHREAD_MUTEX_unlock(&client_owner->so_mutex); if (errcnt == STATE_ERR_MAX) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_owner(&dspbuf, client_owner); LogFatal( COMPONENT_STATE, "Could not complete cleanup of layouts for client owner %s", str); } } /** @} */ nfs-ganesha-6.5/src/SAL/state_lock.c000066400000000000000000003477421473756622300173200ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file state_lock.c * @brief Functions used in lock management. */ #include "config.h" #include #include #include #include #include #include #include "log.h" #include "hashtable.h" #include "fsal.h" #include "nfs_core.h" #include "nfs4.h" #include "sal_functions.h" /*#include "nlm_util.h"*/ #include "export_mgr.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/lock.h" #endif #include "sal_metrics.h" /** * @page state_lock_entry_locking state_lock_entry_t locking rule * * The value is always updated/read with @c nlm_lock_entry->lock held If * we have @c nlm_lock_list mutex held we can read it safely, because the * value is always updated while walking the list with @c entry->state_lock * held. * * The update happens as below: * @code{.c} * pthread_rwlock_wrlock(&entry->state_mutex) * pthread_mutex_lock(lock_entry->sle_mutex) * update the lock_entry value * ........ * @endcode * * The value is ref counted with nlm_lock_entry->sle_ref_count so that * a parallel cancel/unlock won't endup freeing the datastructure. The * last release on the data structure ensure that it is freed. */ #ifdef DEBUG_SAL /** * @brief All locks. */ static struct glist_head state_all_locks = GLIST_HEAD_INIT(state_all_locks); /** * @brief All locks mutex */ pthread_mutex_t all_locks_mutex; #endif /** * @brief All locks blocked in FSAL */ struct glist_head state_blocked_locks = GLIST_HEAD_INIT(state_blocked_locks); /** * @brief Mutex to protect lock lists */ pthread_mutex_t blocked_locks_mutex; /** * @brief Owner of state with no defined owner */ state_owner_t unknown_owner = { .so_owner_val = "ganesha_unknown_owner", .so_type = STATE_LOCK_OWNER_UNKNOWN, .so_refcount = 1, .so_owner_len = 21, .so_lock_list = GLIST_HEAD_INIT(unknown_owner.so_lock_list), }; static state_status_t do_lock_op(struct fsal_obj_handle *obj, state_t *state, fsal_lock_op_t lock_op, state_owner_t *owner, fsal_lock_param_t *lock, state_owner_t **holder, fsal_lock_param_t *conflict, bool overlap); /** * @brief Blocking lock cookies */ /** * @brief Check whether a lock is from NLM * * @param[in] lock_entry Lock to check * * @retval true if the lock is from NLM. * @retval false if the lock is not from NLM. */ bool lock_owner_is_nlm(state_lock_entry_t *lock_entry) { #ifdef _USE_NLM return lock_entry->sle_owner->so_type == STATE_LOCK_OWNER_NLM; #else /* _USE_NLM */ return false; #endif /* _USE_NLM */ } /****************************************************************************** * * Functions to display various aspects of a lock * ******************************************************************************/ /** * @brief Find the end of lock range * * @param[in] lock The lock to check * * @return Last byte of lock. */ static inline uint64_t lock_end(fsal_lock_param_t *lock) { if (lock->lock_length == 0) return UINT64_MAX; else return lock->lock_start + lock->lock_length - 1; } /** * @brief String for lock type * * @param[in] ltype Lock type * * @return Readable string. */ const char *str_lockt(fsal_lock_t ltype) { switch (ltype) { case FSAL_LOCK_R: return "READ "; case FSAL_LOCK_W: return "WRITE"; case FSAL_NO_LOCK: return "NO LOCK"; } return "?????"; } /** * @brief Return string for blocking status * * @param[in] blocked Blocking status * * @return String for blocking status. */ const char *str_blocked(state_blocking_t blocked) { switch (blocked) { case STATE_NON_BLOCKING: return "NON_BLOCKING"; case STATE_BLOCKING: return "BLOCKING "; case STATE_AVAILABLE: return "AVAILABLE "; case STATE_CANCELED: return "CANCELED "; } return "unknown "; } /** * @brief Return string for protocol type * * @param[in] lock protocol type * * @return String for lock protocol type. */ const char *str_protocol(lock_protocol_t protocol) { switch (protocol) { case LOCK_NLM: return "LOCK_NLM "; case LOCK_NFSv4: return "LOCK_NFSv4"; case LOCK_9P: return "LOCK_9P "; } return "unknown "; } const char *str_block_type(state_block_type_t btype) { switch (btype) { case STATE_BLOCK_NONE: return "STATE_BLOCK_NONE "; case STATE_BLOCK_INTERNAL: return "STATE_BLOCK_INTERNAL"; case STATE_BLOCK_ASYNC: return "STATE_BLOCK_ASYNC "; case STATE_BLOCK_POLL: return "STATE_BLOCK_POLL "; } return "unknown "; } /****************************************************************************** * * Function to compare lock parameters * ******************************************************************************/ /** * @brief Check if locks differ * * @note This is not complete, it doesn't check the owner's IP * address. * * @param[in] lock1 A lock * @param[in] lock2 Another lock * * @retval true if locks differ. * @retval false if locks are the same. */ static inline bool different_lock(fsal_lock_param_t *lock1, fsal_lock_param_t *lock2) { return (lock1->lock_type != lock2->lock_type) || (lock1->lock_start != lock2->lock_start) || (lock1->lock_length != lock2->lock_length); } /****************************************************************************** * * Functions to log locks in various ways * ******************************************************************************/ /** * @brief Log a lock entry with a passed refcount * * @param[in] reason Arbitrary string * @param[in] le Entry to log */ static void log_entry_ref_count(const char *reason, state_lock_entry_t *le, int32_t refcount, char *file, int line, char *function) { if (isFullDebug(COMPONENT_STATE)) { char owner[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(owner), owner, owner }; display_owner(&dspbuf, le->sle_owner); DisplayLogComponentLevel( COMPONENT_STATE, file, line, function, NIV_FULL_DEBUG, "%s Entry: %p obj=%p, fileid=%" PRIu64 ", export=%u, type=%s, start=0x%" PRIx64 ", end=0x%" PRIx64 ", protocol %s, blocked=%s/%p/%s" ", state=%p, sle_refcount=%" PRIu32 ", owner={%s}", reason, le, le->sle_obj, (uint64_t)le->sle_obj->fileid, (unsigned int)le->sle_export->export_id, str_lockt(le->sle_lock.lock_type), le->sle_lock.lock_start, lock_end(&le->sle_lock), str_protocol(le->sle_protocol), str_blocked(le->sle_blocked), le->sle_block_data, le->sle_block_data ? str_block_type( le->sle_block_data->sbd_block_type) : str_block_type(STATE_BLOCK_NONE), le->sle_state, refcount, owner); } } #define LogEntryRefCount(reason, le, refcount) \ log_entry_ref_count(reason, le, refcount, (char *)__FILE__, __LINE__, \ (char *)__func__) /** * @brief Log a lock entry * * @param[in] reason Arbitrary string * @param[in] le Entry to log */ #define LogEntry(reason, le) \ log_entry_ref_count(reason, le, \ atomic_fetch_int32_t(&le->sle_ref_count), \ (char *)__FILE__, __LINE__, (char *)__func__) /** * @brief Log a list of locks * * @param[in] reason Arbitrary string * @param[in] obj FSAL object (mostly unused) * @param[in] list List of lock entries * * @retval true if list is empty. * @retval false if list is non-empty. */ static bool LogList(const char *reason, struct fsal_obj_handle *obj, struct glist_head *list) { if (isFullDebug(COMPONENT_STATE)) { struct glist_head *glist; state_lock_entry_t *found_entry; if (glist_empty(list)) { if (obj != NULL) LogFullDebug(COMPONENT_STATE, "%s for %p is empty", reason, obj); else LogFullDebug(COMPONENT_STATE, "%s is empty", reason); return true; } glist_for_each(glist, list) { found_entry = glist_entry(glist, state_lock_entry_t, sle_list); LogEntry(reason, found_entry); if (found_entry->sle_obj == NULL) break; } } return false; } /** * @brief Log blocked locks on list * * Must hold blocked_locks_mutex. * * @param[in] reason Arbitrary string * @param[in] obj File * @param[in] list List of lock entries * * @retval true if list is empty. * @retval false if list is non-empty. */ static bool LogBlockedList(const char *reason, struct fsal_obj_handle *obj, struct glist_head *list) { if (isFullDebug(COMPONENT_STATE)) { struct glist_head *glist; state_lock_entry_t *found_entry; state_block_data_t *block_entry; if (glist_empty(list)) { if (obj != NULL) LogFullDebug(COMPONENT_STATE, "%s for %p is empty", reason, obj); else LogFullDebug(COMPONENT_STATE, "%s is empty", reason); return true; } glist_for_each(glist, list) { block_entry = glist_entry(glist, state_block_data_t, sbd_list); found_entry = block_entry->sbd_lock_entry; LogEntry(reason, found_entry); if (found_entry->sle_obj == NULL) break; } } return false; } /** * @brief Log a lock * * @param[in] component The component to log to * @param[in] debug Log level * @param[in] reason Arbitrary string * @param[in] obj File * @param[in] owner Lock owner * @param[in] lock Lock description */ void log_lock(log_components_t component, log_levels_t debug, const char *reason, struct fsal_obj_handle *obj, state_owner_t *owner, fsal_lock_param_t *lock, char *file, int line, char *function) { if (isLevel(component, debug)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; if (owner != NULL) display_owner(&dspbuf, owner); else display_cat(&dspbuf, "NONE"); DisplayLogComponentLevel(component, file, line, function, debug, "%s Lock: obj=%p, fileid=%" PRIu64 ", type=%s, start=0x%" PRIx64 ", end=0x%" PRIx64 ", owner={%s}", reason, obj, (uint64_t)obj->fileid, str_lockt(lock->lock_type), lock->lock_start, lock_end(lock), str); } } /** * @brief Log a lock description * * @param[in] component The component to log to * @param[in] debug Log level * @param[in] reason Arbitrary string * @param[in] obj FSAL obj handle * @param[in] owner Lock owner * @param[in] lock Lock description */ void log_lock_desc(log_components_t component, log_levels_t debug, const char *reason, struct fsal_obj_handle *obj, void *owner, fsal_lock_param_t *lock, char *file, int line, char *function) { if (isLevel(component, debug)) { DisplayLogComponentLevel( component, file, line, function, debug, "%s Lock: obj=%p, owner=%p, type=%s, start=0x%llx, end=0x%llx", reason, obj, owner, str_lockt(lock->lock_type), (unsigned long long)lock->lock_start, (unsigned long long)lock_end(lock)); } } #define LogLockDesc(component, debug, reason, obj, owner, lock) \ log_lock_desc(component, debug, reason, obj, owner, lock, \ (char *)__FILE__, __LINE__, (char *)__func__) /** * @brief Log all locks * * @param[in] label Arbitrary string */ void dump_all_locks(const char *label) { #ifdef DEBUG_SAL struct glist_head *glist; PTHREAD_MUTEX_lock(&all_locks_mutex); if (glist_empty(&state_all_locks)) { LogFullDebug(COMPONENT_STATE, "All Locks are freed"); PTHREAD_MUTEX_unlock(&all_locks_mutex); return; } glist_for_each(glist, &state_all_locks) LogEntry( label, glist_entry(glist, state_lock_entry_t, sle_all_locks)); PTHREAD_MUTEX_unlock(&all_locks_mutex); #else return; #endif } /****************************************************************************** * * Functions to manage lock entries and lock list * ******************************************************************************/ /** * @brief Create a lock entry * * This function aborts (in gsh_malloc) if no memory is available. * * @param[in] obj File to lock * @param[in] export Export being accessed * @param[in] blocked Blocking status * @param[in] owner Lock owner * @param[in] state State associated with lock * @param[in] lock Lock description * * @return The new entry. */ static state_lock_entry_t * create_state_lock_entry(struct fsal_obj_handle *obj, struct gsh_export *export, state_blocking_t blocked, lock_protocol_t protocol, state_owner_t *owner, state_t *state, fsal_lock_param_t *lock) { state_lock_entry_t *new_entry; new_entry = gsh_calloc(1, sizeof(*new_entry)); LogFullDebug(COMPONENT_STATE, "new_entry = %p owner %p", new_entry, owner); PTHREAD_MUTEX_init(&new_entry->sle_mutex, NULL); /* sle_block_data will be filled in later if necessary */ new_entry->sle_block_data = NULL; new_entry->sle_ref_count = 1; new_entry->sle_obj = obj; new_entry->sle_blocked = blocked; new_entry->sle_protocol = protocol; new_entry->sle_owner = owner; new_entry->sle_state = state; new_entry->sle_lock = *lock; new_entry->sle_export = export; #ifdef _USE_NLM if (owner->so_type == STATE_LOCK_OWNER_NLM) { /* Add to list of locks owned by client that owner belongs to */ state_nlm_client_t *client = owner->so_owner.so_nlm_owner.so_client; inc_nsm_client_ref(client->slc_nsm_client); PTHREAD_MUTEX_lock(&client->slc_nsm_client->ssc_mutex); glist_add_tail(&client->slc_nsm_client->ssc_lock_list, &new_entry->sle_client_locks); PTHREAD_MUTEX_unlock(&client->slc_nsm_client->ssc_mutex); } #endif /* _USE_NLM */ /* Add to list of locks owned by export */ PTHREAD_RWLOCK_wrlock(&new_entry->sle_export->exp_lock); glist_add_tail(&new_entry->sle_export->exp_lock_list, &new_entry->sle_export_locks); PTHREAD_RWLOCK_unlock(&new_entry->sle_export->exp_lock); get_gsh_export_ref(new_entry->sle_export); /* Get ref for sle_obj */ obj->obj_ops->get_ref(obj); /* Add to list of locks owned by owner */ inc_state_owner_ref(owner); PTHREAD_MUTEX_lock(&owner->so_mutex); if (state != NULL) { glist_add_tail(&state->state_data.lock.state_locklist, &new_entry->sle_state_locks); inc_state_t_ref(state); } glist_add_tail(&owner->so_lock_list, &new_entry->sle_owner_locks); PTHREAD_MUTEX_unlock(&owner->so_mutex); #ifdef DEBUG_SAL PTHREAD_MUTEX_lock(&all_locks_mutex); glist_add_tail(&state_all_locks, &new_entry->sle_all_locks); PTHREAD_MUTEX_unlock(&all_locks_mutex); #endif if (blocked == STATE_NON_BLOCKING) sal_metrics__locks_inc(SAL_METRICS_HOLDERS); else sal_metrics__locks_inc(SAL_METRICS_WAITERS); return new_entry; } /** * @brief Duplicate a lock entry * * @param[in] orig_entry Entry to duplicate * * @return New entry. */ static inline state_lock_entry_t * state_lock_entry_t_dup(state_lock_entry_t *orig_entry) { return create_state_lock_entry( orig_entry->sle_obj, orig_entry->sle_export, orig_entry->sle_blocked, orig_entry->sle_protocol, orig_entry->sle_owner, orig_entry->sle_state, &orig_entry->sle_lock); } /** * @brief Take a reference on a lock entry * * @param[in,out] lock_entry Entry to reference */ static inline void lock_entry_inc_ref(state_lock_entry_t *lock_entry) { int32_t refcount = atomic_inc_int32_t(&lock_entry->sle_ref_count); LogEntryRefCount("Increment sle_ref_count", lock_entry, refcount); } /** * @brief Relinquish a reference on a lock entry * * @param[in,out] lock_entry Entry to release */ void lock_entry_dec_ref(state_lock_entry_t *lock_entry) { int32_t refcount = atomic_dec_int32_t(&lock_entry->sle_ref_count); LogEntryRefCount(refcount != 0 ? "Decrement sle_ref_count" : "Decrement sle_ref_count and freeing", lock_entry, refcount); assert(refcount >= 0); if (refcount == 0) { /* Release block data if present */ if (lock_entry->sle_block_data != NULL) { /* need to remove from the state_blocked_locks list */ PTHREAD_MUTEX_lock(&blocked_locks_mutex); /* While waiting for the blocked_locks_mutex, refcount * might have increased, and so we can't release the * lock entry */ refcount = atomic_fetch_int32_t( &lock_entry->sle_ref_count); if (refcount != 0) { LogEntryRefCount( "Refcount not zero after acquiring lock. Not freeing entry", lock_entry, refcount); PTHREAD_MUTEX_unlock(&blocked_locks_mutex); return; } glist_del(&lock_entry->sle_block_data->sbd_list); PTHREAD_MUTEX_unlock(&blocked_locks_mutex); gsh_free(lock_entry->sle_block_data); lock_entry->sle_block_data = NULL; } #ifdef DEBUG_SAL PTHREAD_MUTEX_lock(&all_locks_mutex); glist_del(&lock_entry->sle_all_locks); PTHREAD_MUTEX_unlock(&all_locks_mutex); #endif if (lock_entry->sle_owner != NULL) { dec_state_owner_ref(lock_entry->sle_owner); lock_entry->sle_owner = NULL; } if (lock_entry->sle_state != NULL) { dec_state_t_ref(lock_entry->sle_state); lock_entry->sle_state = NULL; } if (lock_entry->sle_blocked == STATE_NON_BLOCKING) sal_metrics__locks_dec(SAL_METRICS_HOLDERS); else sal_metrics__locks_dec(SAL_METRICS_WAITERS); lock_entry->sle_obj->obj_ops->put_ref(lock_entry->sle_obj); put_gsh_export(lock_entry->sle_export); PTHREAD_MUTEX_destroy(&lock_entry->sle_mutex); gsh_free(lock_entry); } } /** * @brief Remove an entry from the lock lists * * @param[in,out] lock_entry Entry to remove */ static void remove_from_locklist(state_lock_entry_t *lock_entry) { state_owner_t *owner = lock_entry->sle_owner; LogEntry("Removing", lock_entry); /* * If some other thread is holding a reference to this nlm_lock_entry * don't free the structure. But drop from the lock list */ if (owner != NULL) { #ifdef _USE_NLM if (owner->so_type == STATE_LOCK_OWNER_NLM) { /* Remove from list of locks owned * by client that owner belongs to */ state_nlm_client_t *client = owner->so_owner.so_nlm_owner.so_client; PTHREAD_MUTEX_lock(&client->slc_nsm_client->ssc_mutex); glist_del(&lock_entry->sle_client_locks); PTHREAD_MUTEX_unlock( &client->slc_nsm_client->ssc_mutex); dec_nsm_client_ref(client->slc_nsm_client); } #endif /* _USE_NLM */ /* Remove from list of locks owned by export */ PTHREAD_RWLOCK_wrlock(&lock_entry->sle_export->exp_lock); glist_del(&lock_entry->sle_export_locks); PTHREAD_RWLOCK_unlock(&lock_entry->sle_export->exp_lock); /* Remove from list of locks owned by owner */ PTHREAD_MUTEX_lock(&owner->so_mutex); glist_del(&lock_entry->sle_state_locks); glist_del(&lock_entry->sle_owner_locks); PTHREAD_MUTEX_unlock(&owner->so_mutex); } if (lock_entry->sle_blocked != STATE_NON_BLOCKING && lock_entry->sle_blocked != STATE_CANCELED) { /* Removing a blocking lock, mark as canceled in case of * racing with granting. This may be because the lock was * granted and merged with surrounding locks. */ LogEntry("Removing lock and cancelling block", lock_entry); lock_entry->sle_blocked = STATE_CANCELED; } glist_del(&lock_entry->sle_list); lock_entry_dec_ref(lock_entry); } /** * @brief Find a conflicting entry * * @note The st_lock MUST be held * * @param[in] ostate File state to search * @param[in] owner The lock owner * @param[in] lock Lock to check * * @return A conflicting entry or NULL. */ static state_lock_entry_t *get_overlapping_entry(struct state_hdl *ostate, state_owner_t *owner, fsal_lock_param_t *lock) { struct glist_head *glist; struct glist_head *glist_n; state_lock_entry_t *found_entry = NULL; uint64_t found_entry_end, range_end = lock_end(lock); recheck_for_conflicting_entries: glist_for_each_safe(glist, glist_n, &ostate->file.lock_list) { found_entry = glist_entry(glist, state_lock_entry_t, sle_list); LogEntry("Checking", found_entry); /* Skip blocked or cancelled locks */ if (found_entry->sle_blocked == STATE_BLOCKING || found_entry->sle_blocked == STATE_CANCELED) continue; found_entry_end = lock_end(&found_entry->sle_lock); if ((found_entry_end >= lock->lock_start) && (found_entry->sle_lock.lock_start <= range_end)) { /* lock overlaps see if we can allow: * allow if neither lock is exclusive or * the owner is the same */ if ((found_entry->sle_lock.lock_type == FSAL_LOCK_W || lock->lock_type == FSAL_LOCK_W) && different_owners(found_entry->sle_owner, owner)) { /* Recheck for expiry */ state_owner_t *cf_own = found_entry->sle_owner; nfs_client_id_t *client_id = cf_own->so_owner.so_nfs4_owner .so_clientrec; if ((atomic_fetch_uint32_t( &num_of_curr_expired_clients)) && (cf_own->so_type >= STATE_OPEN_OWNER_NFSV4) && client_id->marked_for_delayed_cleanup) { /* Release the state lock, to clean */ ostate->no_cleanup = false; PTHREAD_MUTEX_unlock(&ostate->st_lock); reap_expired_client_list(client_id); /* Acquire back the state lock */ PTHREAD_MUTEX_lock(&ostate->st_lock); ostate->no_cleanup = true; /* Continue to recheck for conflicts */ goto recheck_for_conflicting_entries; } /* found a conflicting lock, return it */ return found_entry; } } } return NULL; } /** * @brief Add a lock, potentially merging with existing locks * * We need to iterate over the full lock list and remove * any mapping entry. And l_offset = 0 and sle_lock.lock_length = 0 lock_entry * implies remove all entries * * @note The st_lock MUST be held * * @param[in,out] ostate File state to operate on * @param[in] lock_entry Lock to add */ static void merge_lock_entry(struct state_hdl *ostate, state_lock_entry_t *lock_entry) { state_lock_entry_t *check_entry; state_lock_entry_t *check_entry_right; uint64_t check_entry_end; uint64_t lock_entry_end; struct glist_head *glist; struct glist_head *glistn; /* lock_entry might be STATE_NON_BLOCKING */ glist_for_each_safe(glist, glistn, &ostate->file.lock_list) { check_entry = glist_entry(glist, state_lock_entry_t, sle_list); /* Skip entry being merged - it could be in the list */ if (check_entry == lock_entry) continue; if (different_owners(check_entry->sle_owner, lock_entry->sle_owner)) continue; /* Only merge fully granted locks */ if (check_entry->sle_blocked != STATE_NON_BLOCKING) continue; check_entry_end = lock_end(&check_entry->sle_lock); lock_entry_end = lock_end(&lock_entry->sle_lock); if ((check_entry_end + 1) < lock_entry->sle_lock.lock_start) /* nothing to merge */ continue; if ((lock_entry_end + 1) < check_entry->sle_lock.lock_start) /* nothing to merge */ continue; /* Need to handle locks of different types differently, may * split an old lock. If new lock totally overlaps old lock, * the new lock will replace the old lock so no special work * to be done. */ if ((check_entry->sle_lock.lock_type != lock_entry->sle_lock.lock_type) && ((lock_entry_end < check_entry_end) || (check_entry->sle_lock.lock_start < lock_entry->sle_lock.lock_start))) { if (lock_entry_end < check_entry_end && check_entry->sle_lock.lock_start < lock_entry->sle_lock.lock_start) { /* Need to split old lock */ check_entry_right = state_lock_entry_t_dup(check_entry); glist_add_tail(&ostate->file.lock_list, &(check_entry_right->sle_list)); } else { /* No split, just shrink, make the logic below * work on original lock */ check_entry_right = check_entry; } if (lock_entry_end < check_entry_end) { /* Need to shrink old lock from beginning * (right lock if split) */ LogEntry("Merge shrinking right", check_entry_right); check_entry_right->sle_lock.lock_start = lock_entry_end + 1; check_entry_right->sle_lock.lock_length = check_entry_end - lock_entry_end; LogEntry("Merge shrunk right", check_entry_right); } if (check_entry->sle_lock.lock_start < lock_entry->sle_lock.lock_start) { /* Need to shrink old lock from end * (left lock if split) */ LogEntry("Merge shrinking left", check_entry); check_entry->sle_lock.lock_length = lock_entry->sle_lock.lock_start - check_entry->sle_lock.lock_start; LogEntry("Merge shrunk left", check_entry); } /* Done splitting/shrinking old lock */ continue; } /* check_entry touches or overlaps lock_entry, expand * lock_entry */ if (lock_entry_end < check_entry_end) /* Expand end of lock_entry */ lock_entry_end = check_entry_end; if (check_entry->sle_lock.lock_start < lock_entry->sle_lock.lock_start) /* Expand start of lock_entry */ lock_entry->sle_lock.lock_start = check_entry->sle_lock.lock_start; /* Compute new lock length */ lock_entry->sle_lock.lock_length = lock_entry_end - lock_entry->sle_lock.lock_start + 1; /* Remove merged entry */ LogEntry("Merged", lock_entry); LogEntry("Merging removing", check_entry); remove_from_locklist(check_entry); } } /** * @brief Free a list of lock entries * * @param[in] list The list of locks to free */ static void free_list(struct glist_head *list) { state_lock_entry_t *found_entry; struct glist_head *glist, *glistn; glist_for_each_safe(glist, glistn, list) { found_entry = glist_entry(glist, state_lock_entry_t, sle_list); remove_from_locklist(found_entry); } } /** * @brief Subtract a lock from a lock entry. * * This function places any remaining bits into the split list. * * @param[in,out] found_entry Lock being modified * @param[in] lock Lock being removed * @param[out] split_list Remaining fragments of found_entry * @param[out] remove_list Removed lock entries * @param[out] removed True if lock is removed * * @return State status. */ static state_status_t subtract_lock_from_entry(state_lock_entry_t *found_entry, fsal_lock_param_t *lock, struct glist_head *split_list, struct glist_head *remove_list, bool *removed) { uint64_t found_entry_end = lock_end(&found_entry->sle_lock); uint64_t range_end = lock_end(lock); state_lock_entry_t *found_entry_left = NULL; state_lock_entry_t *found_entry_right = NULL; state_status_t status = STATE_SUCCESS; if (range_end < found_entry->sle_lock.lock_start) { /* nothing to split */ *removed = false; return status; } if (found_entry_end < lock->lock_start) { /* nothing to split */ *removed = false; return status; } if ((lock->lock_start <= found_entry->sle_lock.lock_start) && range_end >= found_entry_end) { /* Fully overlap */ LogEntry("Remove Complete", found_entry); goto complete_remove; } LogEntry("Split", found_entry); /* Delete the old entry and add one or two new entries */ if (lock->lock_start > found_entry->sle_lock.lock_start) { found_entry_left = state_lock_entry_t_dup(found_entry); found_entry_left->sle_lock.lock_length = lock->lock_start - found_entry->sle_lock.lock_start; LogEntry("Left split", found_entry_left); glist_add_tail(split_list, &(found_entry_left->sle_list)); } if (range_end < found_entry_end) { found_entry_right = state_lock_entry_t_dup(found_entry); found_entry_right->sle_lock.lock_start = range_end + 1; /* found_entry_end being UINT64_MAX indicates that the * sle_lock.lock_length is zero and the lock is held till * the end of the file. In such case assign the split lock * length too to zero to indicate the file end. */ if (found_entry_end == UINT64_MAX) found_entry_right->sle_lock.lock_length = 0; else found_entry_right->sle_lock.lock_length = found_entry_end - range_end; LogEntry("Right split", found_entry_right); glist_add_tail(split_list, &(found_entry_right->sle_list)); } complete_remove: /* Remove the lock from the list it's * on and put it on the remove_list */ glist_move_tail(remove_list, &(found_entry->sle_list)); *removed = true; return status; } /** * @brief Subtract a lock from a list of locks * * This function possibly splits entries in the list. * * @param[in] owner Lock owner * @param[in] state Associated lock state * @param[in] lock Lock to remove * @param[out] removed True if an entry was removed * @param[in,out] list List of locks to modify * * @return State status. */ static state_status_t subtract_lock_from_list(state_owner_t *owner, bool state_applies, int32_t state, fsal_lock_param_t *lock, bool *removed, struct glist_head *list) { state_lock_entry_t *found_entry; struct glist_head split_lock_list, remove_list; struct glist_head *glist, *glistn; state_status_t status = STATE_SUCCESS; bool removed_one = false; *removed = false; glist_init(&split_lock_list); glist_init(&remove_list); glist_for_each_safe(glist, glistn, list) { found_entry = glist_entry(glist, state_lock_entry_t, sle_list); if (owner != NULL && different_owners(found_entry->sle_owner, owner)) continue; /* Only care about granted locks */ if (found_entry->sle_blocked != STATE_NON_BLOCKING) continue; /* Skip locks owned by this NLM state. * This protects NLM locks from the current iteration of an NLM * client from being released by SM_NOTIFY. */ if (state_applies && found_entry->sle_state->state_seqid == state) continue; /* We have matched owner. Even though we are taking a reference * to found_entry, we don't inc the ref count because we want * to drop the lock entry. */ status = subtract_lock_from_entry(found_entry, lock, &split_lock_list, &remove_list, &removed_one); *removed |= removed_one; if (status != STATE_SUCCESS) { /* We ran out of memory while splitting, * deal with it outside loop */ break; } } if (status != STATE_SUCCESS) { /* We ran out of memory while splitting. split_lock_list * has been freed. For each entry on the remove_list, put * it back on the list. */ LogDebug(COMPONENT_STATE, "Failed %s", state_err_str(status)); glist_for_each_safe(glist, glistn, &remove_list) { found_entry = glist_entry(glist, state_lock_entry_t, sle_list); glist_move_tail(list, &(found_entry->sle_list)); } } else { /* free the enttries on the remove_list */ free_list(&remove_list); /* now add the split lock list */ glist_add_list_tail(list, &split_lock_list); } LogFullDebug(COMPONENT_STATE, "List of all locks for list=%p returning %d", list, status); return status; } /****************************************************************************** * * Implement hash table to hash blocked lock entries by cookie * ******************************************************************************/ static void grant_blocked_locks(struct state_hdl *); static inline int display_lock_cookie(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_opaque_value(dspbuf, buff->addr, buff->len); } /** * @brief Display lock cookie in hash table * * @param[in] dspbuf display buffer to display into * @param[in] buff Key to display */ int display_lock_cookie_key(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_lock_cookie(dspbuf, buff); } /** * @brief Display lock cookie entry * * @param[in/out] dspbuf display_buffer describing output string * @param[in] he Cookie entry to display * * @return the bytes remaining in the buffer. */ int display_lock_cookie_entry(struct display_buffer *dspbuf, state_cookie_entry_t *he) { int b_left = display_printf(dspbuf, "%p: cookie ", he); if (b_left <= 0) return b_left; b_left = display_opaque_value(dspbuf, he->sce_cookie, he->sce_cookie_size); if (b_left <= 0) return b_left; b_left = display_printf(dspbuf, " obj {%p fileid=%" PRIu64 "} lock {", he->sce_obj, he->sce_obj->fileid); if (b_left <= 0) return b_left; if (he->sce_lock_entry != NULL) { b_left = display_printf(dspbuf, "%p owner {", he->sce_lock_entry); if (b_left <= 0) return b_left; b_left = display_owner(dspbuf, he->sce_lock_entry->sle_owner); if (b_left <= 0) return b_left; b_left = display_printf( dspbuf, "} type=%s start=0x%" PRIx64 " end=0x%" PRIx64 " protocol=%s, blocked=%s}", str_lockt(he->sce_lock_entry->sle_lock.lock_type), he->sce_lock_entry->sle_lock.lock_start, lock_end(&he->sce_lock_entry->sle_lock), str_protocol(he->sce_lock_entry->sle_protocol), str_blocked(he->sce_lock_entry->sle_blocked)); } else { b_left = display_printf(dspbuf, "}"); } return b_left; } /** * @brief Display lock cookie entry in hash table * * @param[in] dspbuf display buffer to display into * @param[in] buff Value to display */ int display_lock_cookie_val(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_lock_cookie_entry(dspbuf, buff->addr); } /** * @brief Compare lock cookie in hash table * * @param[in] buff1 A key * @param[in] buff2 Another key * * @retval 0 on equality. * @retval 1 on inequality. */ int compare_lock_cookie_key(struct gsh_buffdesc *buff1, struct gsh_buffdesc *buff2) { if (isFullDebug(COMPONENT_STATE) && isDebug(COMPONENT_HASHTABLE)) { char str1[LOG_BUFF_LEN / 2] = "\0"; char str2[LOG_BUFF_LEN / 2] = "\0"; struct display_buffer dspbuf1 = { sizeof(str1), str1, str1 }; struct display_buffer dspbuf2 = { sizeof(str2), str2, str2 }; display_lock_cookie(&dspbuf1, buff1); display_lock_cookie(&dspbuf2, buff2); LogFullDebug(COMPONENT_STATE, "{%s} vs {%s}", str1, str2); } if (buff1->addr == buff2->addr) return 0; if (buff1->len != buff2->len) return 1; if (buff1->addr == NULL || buff2->addr == NULL) return 1; return memcmp(buff1->addr, buff2->addr, buff1->len); } /** * @brief Hash index for lock cookie * * @todo Replace with a good hash function. * * @param[in] hparam Hash parameters * @param[in] key Key to hash * * @return Hash index. */ uint32_t lock_cookie_value_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { unsigned int sum = 0; unsigned int i; unsigned long res; unsigned char *addr = key->addr; /* Compute the sum of all the characters */ for (i = 0; i < key->len; i++) sum += (unsigned char)addr[i]; res = (unsigned long)sum + (unsigned long)key->len; if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "value = %lu", res % hparam->index_size); return (unsigned long)(res % hparam->index_size); } /** * @brief RBT hash for lock cookie * * @todo Replace with a good hash function. * * @param[in] hparam Hash parameters * @param[in] key Key to hash * * @return RBT hash. */ uint64_t lock_cookie_rbt_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *key) { unsigned int sum = 0; unsigned int i; unsigned long res; unsigned char *addr = key->addr; /* Compute the sum of all the characters */ for (i = 0; i < key->len; i++) sum += (unsigned char)addr[i]; res = (unsigned long)sum + (unsigned long)key->len; if (isDebug(COMPONENT_HASHTABLE)) LogFullDebug(COMPONENT_STATE, "rbt = %lu", res); return res; } /** * @brief Free a cookie entry * * @param[in] cookie_entry Entry to free * @param[in] unblock Whether to remove block data */ void free_cookie(state_cookie_entry_t *cookie_entry, bool unblock) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; void *cookie = cookie_entry->sce_cookie; state_lock_entry_t *lock_entry = cookie_entry->sce_lock_entry; if (isFullDebug(COMPONENT_STATE)) { display_lock_cookie_entry(&dspbuf, cookie_entry); str_valid = true; } /* Since the cookie is not in the hash table, * we can just free the memory */ if (str_valid) LogFullDebug(COMPONENT_STATE, "Free Lock Cookie {%s}", str); /* If block data is still attached to lock entry, remove it */ if (lock_entry != NULL && unblock) { if (lock_entry->sle_block_data != NULL) lock_entry->sle_block_data->sbd_blocked_cookie = NULL; lock_entry_dec_ref(lock_entry); cookie_entry->sce_obj->obj_ops->put_ref(cookie_entry->sce_obj); } /* Free the memory for the cookie and the cookie entry */ gsh_free(cookie); gsh_free(cookie_entry); } /** * Parameters used for lock cookie hash table initialization. * * @todo Switch the cookie table to something else and get rid * of this. */ static hash_parameter_t cookie_param = { .index_size = PRIME_STATE, .hash_func_key = lock_cookie_value_hash_func, .hash_func_rbt = lock_cookie_rbt_hash_func, .compare_key = compare_lock_cookie_key, .display_key = display_lock_cookie_key, .display_val = display_lock_cookie_val, .flags = HT_FLAG_NONE, }; static hash_table_t *ht_lock_cookies; /** * @brief Add a grant cookie to a blocked lock * * @param[in] obj File to operate on * @param[in] cookie Cookie to add * @param[in] cookie_size Cookie length * @param[in] lock_entry Lock entry * @param[out] cookie_entry New cookie entry * * @return State status. */ state_status_t state_add_grant_cookie(struct fsal_obj_handle *obj, void *cookie, int cookie_size, state_lock_entry_t *lock_entry, state_cookie_entry_t **cookie_entry) { struct gsh_buffdesc buffkey, buffval; state_cookie_entry_t *hash_entry; char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; state_status_t status = 0; *cookie_entry = NULL; if (lock_entry->sle_block_data == NULL || cookie == NULL || cookie_size == 0) { /* Something's wrong with this entry */ status = STATE_INCONSISTENT_ENTRY; return status; } if (isFullDebug(COMPONENT_STATE)) { display_opaque_value(&dspbuf, cookie, cookie_size); str_valid = true; } hash_entry = gsh_calloc(1, sizeof(*hash_entry)); buffkey.addr = gsh_malloc(cookie_size); hash_entry->sce_obj = obj; hash_entry->sce_lock_entry = lock_entry; hash_entry->sce_cookie = buffkey.addr; hash_entry->sce_cookie_size = cookie_size; memcpy(buffkey.addr, cookie, cookie_size); buffkey.len = cookie_size; buffval.addr = (void *)hash_entry; buffval.len = sizeof(*hash_entry); if (isFullDebug(COMPONENT_STATE)) { display_lock_cookie_entry(&dspbuf, hash_entry); str_valid = true; } if (hashtable_test_and_set(ht_lock_cookies, &buffkey, &buffval, HASHTABLE_SET_HOW_SET_NO_OVERWRITE) != HASHTABLE_SUCCESS) { gsh_free(hash_entry); if (str_valid) LogFullDebug(COMPONENT_STATE, "Lock Cookie {%s} HASH TABLE ERROR", str); status = STATE_HASH_TABLE_ERROR; return status; } if (str_valid) LogFullDebug(COMPONENT_STATE, "Lock Cookie {%s} Added", str); switch (lock_entry->sle_block_data->sbd_grant_type) { case STATE_GRANT_NONE: /* Shouldn't get here */ status = STATE_INCONSISTENT_ENTRY; break; case STATE_GRANT_POLL: case STATE_GRANT_FSAL_AVAILABLE: /* A poll triggered by the polling thread actually looks * exactly like a poll triggered by an FSAL upcall... * * Now that we are sure we can continue, acquire the FSAL lock. * If we get STATE_LOCK_BLOCKED we need to return... */ status = do_lock_op(obj, lock_entry->sle_state, FSAL_OP_LOCKB, lock_entry->sle_owner, &lock_entry->sle_lock, NULL, NULL, false); break; case STATE_GRANT_INTERNAL: /* Now that we are sure we can continue, acquire the FSAL lock. * If we get STATE_LOCK_BLOCKED we need to return... */ status = do_lock_op(obj, lock_entry->sle_state, FSAL_OP_LOCK, lock_entry->sle_owner, &lock_entry->sle_lock, NULL, NULL, false); break; case STATE_GRANT_FSAL: /* No need to go to FSAL for lock */ status = STATE_SUCCESS; break; } if (status != STATE_SUCCESS) { struct gsh_buffdesc buffused_key; hash_error_t err; /* Lock will be returned to right blocking type if it is * still blocking. We could lose a block if we failed for * any other reason */ if (status == STATE_LOCK_BLOCKED) LogDebug(COMPONENT_STATE, "Unable to lock FSAL for %s/%s lock, error=%s", str_protocol(lock_entry->sle_protocol), str_blocked(lock_entry->sle_blocked), state_err_str(status)); else LogMajor(COMPONENT_STATE, "Unable to lock FSAL for %s/%s lock, error=%s", str_protocol(lock_entry->sle_protocol), str_blocked(lock_entry->sle_blocked), state_err_str(status)); LogEntry("Entry", lock_entry); /* Remove the hashtable entry */ err = HashTable_Del(ht_lock_cookies, &buffkey, &buffused_key, &buffval); if (err != HASHTABLE_SUCCESS) { LogCrit(COMPONENT_STATE, "Failure to delete lock cookie %s", hash_table_err_to_str(err)); } /* And release the cookie without unblocking the lock. * grant_blocked_locks() will decide whether to keep or * free the block. */ free_cookie(hash_entry, false); return status; } /* Increment lock entry reference count and link it to the cookie */ lock_entry_inc_ref(lock_entry); lock_entry->sle_block_data->sbd_blocked_cookie = hash_entry; *cookie_entry = hash_entry; /* Also take an obj reference. */ obj->obj_ops->get_ref(obj); return status; } /** * @brief Cancel a lock grant from the FSAL * * @param[in] cookie_entry Entry for the lock grant * * @return State status. */ state_status_t state_cancel_grant(state_cookie_entry_t *cookie_entry) { state_status_t status = 0; /* We had acquired an FSAL lock, need to release it. */ status = do_lock_op(cookie_entry->sce_obj, cookie_entry->sce_lock_entry->sle_state, FSAL_OP_UNLOCK, cookie_entry->sce_lock_entry->sle_owner, &cookie_entry->sce_lock_entry->sle_lock, NULL, /* no conflict expected */ NULL, false); if (status != STATE_SUCCESS) LogMajor( COMPONENT_STATE, "Unable to unlock FSAL for canceled GRANTED lock, error=%s", state_err_str(status)); /* And release the cookie and unblock lock * (because lock will be removed) */ free_cookie(cookie_entry, true); return status; } /** * @brief Find a grant matching a cookie * * @param[in] cookie Cookie to look up * @param[in] cookie_size Length of cookie * @param[out] cookie_entry Found entry * * @return State status. */ state_status_t state_find_grant(void *cookie, int cookie_size, state_cookie_entry_t **cookie_entry) { struct gsh_buffdesc buffkey; struct gsh_buffdesc buffval; struct gsh_buffdesc buffused_key; char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; state_status_t status = 0; buffkey.addr = cookie; buffkey.len = cookie_size; if (isFullDebug(COMPONENT_STATE) && isDebug(COMPONENT_HASHTABLE)) { display_lock_cookie(&dspbuf, &buffkey); LogFullDebug(COMPONENT_STATE, "KEY {%s}", str); str_valid = true; } if (HashTable_Del(ht_lock_cookies, &buffkey, &buffused_key, &buffval) != HASHTABLE_SUCCESS) { if (str_valid) LogFullDebug(COMPONENT_STATE, "KEY {%s} NOTFOUND", str); status = STATE_BAD_COOKIE; return status; } *cookie_entry = buffval.addr; if (isFullDebug(COMPONENT_STATE) && isDebug(COMPONENT_HASHTABLE)) { display_lock_cookie_entry(&dspbuf, *cookie_entry); LogFullDebug(COMPONENT_STATE, "Found Lock Cookie {%s}", str); } status = STATE_SUCCESS; return status; } /** * @brief Grant a blocked lock * * @param[in] ostate File state on which to grant it * @param[in] lock_entry Lock entry */ void grant_blocked_lock_immediate(struct state_hdl *ostate, state_lock_entry_t *lock_entry) { state_cookie_entry_t *cookie = NULL; state_status_t state_status; /* Try to clean up blocked lock. */ if (lock_entry->sle_block_data != NULL) { if (lock_entry->sle_block_data->sbd_blocked_cookie != NULL) { /* Cookie is attached, try to get it */ cookie = lock_entry->sle_block_data->sbd_blocked_cookie; state_status = state_find_grant(cookie->sce_cookie, cookie->sce_cookie_size, &cookie); if (state_status == STATE_SUCCESS) { /* We've got the cookie, * free the cookie and the blocked lock */ free_cookie(cookie, true); } else { /* Otherwise, another thread has the cookie, * let it do it's business. */ return; } } else { /* We have block data but no cookie, * so we can just free the block data */ memset(lock_entry->sle_block_data, 0, sizeof(*lock_entry->sle_block_data)); gsh_free(lock_entry->sle_block_data); lock_entry->sle_block_data = NULL; } } if (lock_entry->sle_blocked != STATE_NON_BLOCKING) { /* Lock granted. Increase holders and decrease waiters */ sal_metrics__locks_inc(SAL_METRICS_HOLDERS); sal_metrics__locks_dec(SAL_METRICS_WAITERS); } /* Mark lock as granted */ lock_entry->sle_blocked = STATE_NON_BLOCKING; /* Merge any touching or overlapping locks into this one. */ LogEntry("Granted immediate, merging locks for", lock_entry); merge_lock_entry(ostate, lock_entry); LogEntry("Immediate Granted entry", lock_entry); /* A lock downgrade could unblock blocked locks */ grant_blocked_locks(ostate); } static void grant_nfsv4_blocking_lock(struct fsal_obj_handle *obj, state_lock_entry_t *lock_entry) { state_status_t status = do_lock_op(obj, lock_entry->sle_state, FSAL_OP_LOCKB, lock_entry->sle_owner, &lock_entry->sle_lock, NULL, NULL, false); if (status != STATE_SUCCESS) { LogMajor(COMPONENT_STATE, "Unable to lock a granted lock, error=%s", state_err_str(status)); } PTHREAD_MUTEX_lock(&blocked_locks_mutex); glist_del(&lock_entry->sle_block_data->sbd_list); PTHREAD_MUTEX_unlock(&blocked_locks_mutex); grant_blocked_lock_immediate(obj->state_hdl, lock_entry); } /** * @brief Finish granting a lock * * Do bookkeeping and merge the lock into the lock list. * * @param[in] cookie_entry Entry describing the grant */ void state_complete_grant(state_cookie_entry_t *cookie_entry) { state_lock_entry_t *lock_entry; struct fsal_obj_handle *obj; lock_entry = cookie_entry->sce_lock_entry; obj = cookie_entry->sce_obj; /* Call obj->obj_ops->get_ref() because even though there MUST be at * least one lock present for there to be a cookie_entry to even allow * this routine to be called, that may be cleaned up by free_cookie * and thus before we release the state lock. */ obj->obj_ops->get_ref(obj); STATELOCK_lock(obj); /* We need to make sure lock is ready to be granted */ if (lock_entry->sle_blocked == STATE_AVAILABLE) { /* Mark lock as granted */ lock_entry->sle_blocked = STATE_NON_BLOCKING; /* Merge any touching or overlapping locks into this one. */ LogEntry("Granted, merging locks for", lock_entry); merge_lock_entry(obj->state_hdl, lock_entry); LogEntry("Granted entry", lock_entry); /* A lock downgrade could unblock blocked locks */ grant_blocked_locks(obj->state_hdl); /* Lock granted. Increase holders and decrease waiters */ sal_metrics__locks_inc(SAL_METRICS_HOLDERS); sal_metrics__locks_dec(SAL_METRICS_WAITERS); } /* Free cookie and unblock lock. * If somehow the lock was unlocked/canceled while the GRANT * was in progress, this will completely clean up the lock. */ free_cookie(cookie_entry, true); STATELOCK_unlock(obj); /* Release the obj reference taken above. */ obj->obj_ops->put_ref(obj); } /** * @brief Attempt to grant a blocked lock * * @param[in] lock_entry Lock entry to grant */ void try_to_grant_lock(state_lock_entry_t *lock_entry) { granted_callback_t call_back; state_blocking_t blocked; state_status_t status; struct gsh_export *export = lock_entry->sle_export; const char *reason; /* Try to grant if not cancelled and has block data and we are able * to get an export reference. */ if (lock_entry->sle_blocked == STATE_NON_BLOCKING) { /* Nothing to do if already granted */ LogEntry("Lock already granted", lock_entry); return; } else if (lock_entry->sle_blocked == STATE_AVAILABLE) { /* Should not happen, but just in case... Nothing to do. */ LogEntry("Lock grant already in progress", lock_entry); return; } else if (lock_entry->sle_blocked == STATE_CANCELED) { reason = "Removing canceled blocked lock entry"; } else if (lock_entry->sle_block_data == NULL) { reason = "Removing blocked lock entry with no block data"; } else if (!export_ready(export)) { reason = "Removing blocked lock entry due to stale export"; } else { call_back = lock_entry->sle_block_data->sbd_granted_callback; /* Mark the lock_entry as provisionally granted and make the * granted call back. The granted call back is responsible * for acquiring a reference to the lock entry if needed. */ blocked = lock_entry->sle_blocked; lock_entry->sle_blocked = STATE_AVAILABLE; if (lock_entry->sle_block_data->sbd_grant_type == STATE_GRANT_NONE) lock_entry->sle_block_data->sbd_grant_type = STATE_GRANT_INTERNAL; status = call_back(lock_entry->sle_obj, lock_entry); LOCK_AUTO_TRACEPOINT(lock_entry, granted_callback, TRACE_INFO, "Sent granted callback and got {}", status); if (status == STATE_LOCK_BLOCKED) { /* The lock is still blocked, restore it's type and * leave it in the list. */ lock_entry->sle_blocked = blocked; lock_entry->sle_block_data->sbd_grant_type = STATE_GRANT_NONE; LogEntry("Granting callback left lock still blocked", lock_entry); return; } /* At this point, we no longer need the entry on the * blocked lock list in nlm, in nfsv4 we still need it to * poll in case it is never requested by client after grant * so that we could cancel it. */ if (lock_entry->sle_protocol == LOCK_NLM) { PTHREAD_MUTEX_lock(&blocked_locks_mutex); glist_del(&lock_entry->sle_block_data->sbd_list); PTHREAD_MUTEX_unlock(&blocked_locks_mutex); } if (status == STATE_SUCCESS) return; reason = "Removing unsuccessfully granted blocked lock"; } /* There was no call back data, the call back failed, * or the block was cancelled. * Remove lock from list. */ LogEntry(reason, lock_entry); remove_from_locklist(lock_entry); } /** * @brief Routine to be called from the FSAL upcall handler * * @param[in] block_data Data describing blocked lock */ void process_blocked_lock_upcall(state_lock_entry_t *lock_entry) { /* A lock entry reference was taken when this work was scheduled. */ STATELOCK_lock(lock_entry->sle_obj); if (glist_null(&lock_entry->sle_list)) { LogEntry( "Received up-call for lock entry that was already removed from the lock list. Ignoring", lock_entry); goto out_upcall; } try_to_grant_lock(lock_entry); out_upcall: STATELOCK_unlock(lock_entry->sle_obj); } /** * @brief Attempt to grant all blocked locks on a file * * @param[in] ostate File state */ static void grant_blocked_locks(struct state_hdl *ostate) { state_lock_entry_t *found_entry; struct glist_head *glist, *glistn; struct fsal_export *export = op_ctx->ctx_export->fsal_export; if (!ostate) return; /* If FSAL supports async blocking locks, * allow it to grant blocked locks. */ if (export->exp_ops.fs_supports(export, fso_lock_support_async_block)) return; glist_for_each_safe(glist, glistn, &ostate->file.lock_list) { found_entry = glist_entry(glist, state_lock_entry_t, sle_list); if (found_entry->sle_blocked != STATE_BLOCKING) continue; /* Found a blocked entry for this file, * see if we can place the lock. */ if (get_overlapping_entry(ostate, found_entry->sle_owner, &found_entry->sle_lock) != NULL) continue; /* Found an entry that might work, try to grant it. */ try_to_grant_lock(found_entry); } } /** * @brief Cancel a blocked lock * * @param[in] obj File on which to cancel the lock * @param[in] lock_entry Lock to cancel * * @return State status. */ static void cancel_blocked_lock(struct fsal_obj_handle *obj, state_lock_entry_t *lock_entry) { state_cookie_entry_t *cookie = NULL; state_status_t state_status; /* Mark lock as canceled */ LogEntry("Cancelling blocked", lock_entry); lock_entry->sle_blocked = STATE_CANCELED; /* Unlocking the entire region will remove any FSAL locks we held, * whether from fully granted locks, or from blocking locks that were * in the process of being granted. */ /* Try to clean up blocked lock if a cookie is present */ if (lock_entry->sle_block_data != NULL && lock_entry->sle_block_data->sbd_blocked_cookie != NULL) { /* Cookie is attached, try to get it */ cookie = lock_entry->sle_block_data->sbd_blocked_cookie; state_status = state_find_grant( cookie->sce_cookie, cookie->sce_cookie_size, &cookie); if (state_status == STATE_SUCCESS) { /* We've got the cookie, * free the cookie and the blocked lock */ free_cookie(cookie, true); } /* otherwise, another thread has the cookie, let it do it's * business, which won't be much, since we've already marked * the lock CANCELED. */ } else { /* Otherwise, if block data is present, it will be freed when * the lock entry is freed. If the cookie is held, the refcount * it holds will prevent the lock entry from being released * until the cookie is freed. */ /* Since a cookie was not found, * the lock must still be in a state of needing cancelling. */ state_status = do_lock_op(obj, lock_entry->sle_state, FSAL_OP_CANCEL, lock_entry->sle_owner, &lock_entry->sle_lock, NULL, /* no conflict expected */ NULL, false); /* overlap not relevant */ LOCK_AUTO_TRACEPOINT(lock_entry, cancel_request_end, TRACE_INFO, "cancel request end {}", state_status); if (state_status != STATE_SUCCESS) { /* Unable to cancel, * assume that granted upcall is on it's way. */ LogFullDebug(COMPONENT_STATE, "Unable to cancel lock %d", state_status); LogEntry("Unable to cancel (grant upcall expected)", lock_entry); } /* If there is a pending upcall at this point, the * following request will release it. The upcall * processing code can just ignore the granted lock, if * a lock isn't found in the state_blocked_lock list. * This is only true for nlm, as in nfsv4 lockers only * become holders after a direct lock request which would have * meant we wouldn't have reached here but to the unlock path * * @todo: Maybe an issue if this was a lock upgrade? */ if (lock_entry->sle_protocol == LOCK_NLM) { state_status = do_lock_op( obj, lock_entry->sle_state, FSAL_OP_UNLOCK, lock_entry->sle_owner, &lock_entry->sle_lock, NULL, /* no conflict expected */ NULL, false); /* overlap not relevant */ if (state_status != STATE_SUCCESS) { /* lock was probably not granted */ LogFullDebug( COMPONENT_STATE, "Unable to unlock a blocked lock %d", state_status); } } } /* Remove the lock from the lock list */ LogEntry("Removing", lock_entry); remove_from_locklist(lock_entry); } /** * * @brief Cancel blocked locks that overlap a lock * * Handle the situation where we have granted a lock and the client now * assumes it holds the lock, but we haven't received the GRANTED RSP, and * now the client is unlocking the lock. * * This will also handle the case of a client that uses UNLOCK to cancel * a blocked lock. * * Because this will release any blocked lock that was in the process of * being granted that overlaps the lock at all, we protect ourselves from * having a stuck lock at the risk of the client thinking it has a lock * it now doesn't. * * If the client unlock doesn't happen to fully overlap a blocked lock, * the blocked lock will be cancelled in full. Hopefully the client will * retry the remainder lock that should have still been blocking. * * @param[in,out] ostate File state on which to operate * @param[in] owner The state owner for the lock * @param[in] state Associated state * @param[in] lock Lock description */ void cancel_blocked_locks_range(struct state_hdl *ostate, state_owner_t *owner, bool state_applies, int32_t state, fsal_lock_param_t *lock) { struct glist_head *glist, *glistn; state_lock_entry_t *found_entry = NULL; uint64_t found_entry_end, range_end = lock_end(lock); glist_for_each_safe(glist, glistn, &ostate->file.lock_list) { found_entry = glist_entry(glist, state_lock_entry_t, sle_list); /* Skip locks not owned by owner */ if (owner != NULL && different_owners(found_entry->sle_owner, owner)) continue; /* Skip locks owned by this NLM state. * This protects NLM locks from the current iteration of an NLM * client from being released by SM_NOTIFY. */ if (state_applies && found_entry->sle_state->state_seqid == state) continue; /* Skip granted locks */ if (found_entry->sle_blocked == STATE_NON_BLOCKING) continue; LogEntry("Checking", found_entry); found_entry_end = lock_end(&found_entry->sle_lock); if ((found_entry_end >= lock->lock_start) && (found_entry->sle_lock.lock_start <= range_end)) { /* lock overlaps, cancel it. */ cancel_blocked_lock(ostate->file.obj, found_entry); } } } /** * @brief Release a lock grant * * @param[in] cookie_entry Grant entry * * @return State status. */ state_status_t state_release_grant(state_cookie_entry_t *cookie_entry) { state_lock_entry_t *lock_entry; struct fsal_obj_handle *obj; state_status_t status = STATE_SUCCESS; lock_entry = cookie_entry->sce_lock_entry; obj = cookie_entry->sce_obj; STATELOCK_lock(obj); /* We need to make sure lock is only "granted" once... * It's (remotely) possible that due to latency, we might end up * processing two GRANTED_RSP calls at the same time. */ if (lock_entry->sle_blocked == STATE_AVAILABLE) { /* Mark lock as canceled */ lock_entry->sle_blocked = STATE_CANCELED; /* We had acquired an FSAL lock, need to release it. */ status = do_lock_op(obj, lock_entry->sle_state, FSAL_OP_UNLOCK, lock_entry->sle_owner, &lock_entry->sle_lock, NULL, /* no conflict expected */ NULL, false); if (status != STATE_SUCCESS) LogMajor( COMPONENT_STATE, "Unable to unlock FSAL for released GRANTED lock, error=%s", state_err_str(status)); else { /* Remove the lock from the lock list. * Will not free yet because of cookie reference to * lock entry. */ LogEntry("Release Grant Removing", lock_entry); remove_from_locklist(lock_entry); } } /* Free the cookie and unblock the lock. This will release our final * reference on the lock entry and should free it. (Unless another * thread has a reference for some reason. */ free_cookie(cookie_entry, true); /* Check to see if we can grant any blocked locks. */ grant_blocked_locks(obj->state_hdl); STATELOCK_unlock(obj); return status; } /****************************************************************************** * * Functions to interact with FSAL * ******************************************************************************/ /** * @brief Human-readable string from the lock operation * * @param[in] op The lock operation * * @return The human-readable string. */ static inline const char *fsal_lock_op_str(fsal_lock_op_t op) { switch (op) { case FSAL_OP_LOCKT: return "FSAL_OP_LOCKT "; case FSAL_OP_LOCK: return "FSAL_OP_LOCK "; case FSAL_OP_LOCKB: return "FSAL_OP_LOCKB "; case FSAL_OP_UNLOCK: return "FSAL_OP_UNLOCK"; case FSAL_OP_CANCEL: return "FSAL_OP_CANCEL"; } return "unknown"; } /** * @brief Perform a lock operation * * We do state management and call down to the FSAL as appropriate, so * that the caller has a single entry point. * * @note The st_lock MUST be held * * @param[in] obj File on which to operate * @param[in] state state_t associated with lock if any * @param[in] lock_op Operation to perform * @param[in] owner Lock operation * @param[in] lock Lock description * @param[out] holder Owner of conflicting lock * @param[out] conflict Description of conflicting lock * @param[in] overlap Hint that lock overlaps * * @return State status. */ state_status_t do_lock_op(struct fsal_obj_handle *obj, state_t *state, fsal_lock_op_t lock_op, state_owner_t *owner, fsal_lock_param_t *lock, state_owner_t **holder, fsal_lock_param_t *conflict, bool overlap) { fsal_status_t fsal_status; state_status_t status = STATE_SUCCESS; fsal_lock_param_t conflicting_lock; struct fsal_export *fsal_export = op_ctx->fsal_export; fsal_lock_op_t fsal_lock_op = lock_op; struct gsh_client *saved_client = op_ctx->client; struct gsh_client *owner_client = NULL; #ifdef _USE_NLM struct state_nlm_client_t *nlm_client; #endif struct nfs_client_id_t *nfs4_clientid = NULL; lock->lock_sle_type = FSAL_POSIX_LOCK; /* Quick exit if: * Locks are not supported by FSAL * Async blocking locks are not supported and this is a cancel * Lock owners are not supported and hint tells us that lock fully * overlaps a lock we already have (no need to make another FSAL * call in that case) * Unlock of stale nfs4_clientid and some FSAL need the existence * of lock record to check the future lock reclaim. * * We do NOT need to quick exit if async blocking locks are not * supported and there is an overlap because we won't get here if * the overlap includes a write lock (which would cause a block). */ LogFullDebug( COMPONENT_STATE, "Reasons to quick exit fso_lock_support=%s fso_lock_support_async_block=%s overlap=%s", fsal_export->exp_ops.fs_supports(fsal_export, fso_lock_support) ? "yes" : "no", fsal_export->exp_ops.fs_supports(fsal_export, fso_lock_support_async_block) ? "yes" : "no", overlap ? "yes" : "no"); if (!fsal_export->exp_ops.fs_supports(fsal_export, fso_lock_support) || (!fsal_export->exp_ops.fs_supports(fsal_export, fso_lock_support_async_block) && lock_op == FSAL_OP_CANCEL)) return STATE_SUCCESS; LogLock(COMPONENT_STATE, NIV_FULL_DEBUG, fsal_lock_op_str(lock_op), obj, owner, lock); memset(&conflicting_lock, 0, sizeof(conflicting_lock)); if (lock_op == FSAL_OP_LOCKB && !fsal_export->exp_ops.fs_supports(fsal_export, fso_lock_support_async_block)) { fsal_lock_op = FSAL_OP_LOCK; } /* Fetch the gsh_client associated with the lock owner if available */ switch (owner->so_type) { case STATE_LOCK_OWNER_UNKNOWN: /* Should never get here. */ break; #ifdef _USE_NLM case STATE_LOCK_OWNER_NLM: nlm_client = owner->so_owner.so_nlm_owner.so_client; owner_client = nlm_client->slc_nsm_client->ssc_client; break; #endif #ifdef _USE_9P case STATE_LOCK_OWNER_9P: /* OOPS - 9P won't work with FSALs that need * op_ctx->client because it doesn't set it... * They will crash soon enough... */ LogDebug(COMPONENT_STATE, "9P doesn't set op_ctx->client..."); break; #endif case STATE_OPEN_OWNER_NFSV4: case STATE_LOCK_OWNER_NFSV4: case STATE_CLIENTID_OWNER_NFSV4: nfs4_clientid = owner->so_owner.so_nfs4_owner.so_clientrec; owner_client = nfs4_clientid->gsh_client; break; } if (fsal_lock_op == FSAL_OP_UNLOCK && nfs4_clientid != NULL && nfs4_clientid->cid_confirmed == STALE_CLIENT_ID && fsal_export->exp_ops.fs_supports(fsal_export, fso_lock_full_control)) { LogDebug( COMPONENT_STATE, "skip unlock of stale nfs4_clientid. FSAL need the lock record to check lock reclaim"); return STATE_SUCCESS; } /* If the owner gsh_client doesn't match the op_ctx and is not NULL * then save the op_ctx->client and switch to owner_client. */ if (owner_client != op_ctx->client && owner_client != NULL) op_ctx->client = owner_client; /* Perform this lock operation using the support_ex lock op. */ fsal_status = obj->obj_ops->lock_op2(obj, state, owner, fsal_lock_op, lock, &conflicting_lock); op_ctx->client = saved_client; status = state_error_convert(fsal_status); LogFullDebug(COMPONENT_STATE, "FSAL_lock_op returned %s", state_err_str(status)); if (status == STATE_LOCK_BLOCKED && fsal_lock_op != FSAL_OP_LOCKB) { /* This is an unexpected return code, * make sure caller reports an error */ LogMajor(COMPONENT_STATE, "FSAL returned unexpected STATE_LOCK_BLOCKED result"); status = STATE_FSAL_ERROR; } else if (status == STATE_LOCK_CONFLICT && lock_op == FSAL_OP_LOCKB) { /* This must be a non-async blocking lock that was * blocked, where we actually made a non-blocking * call. In that case, actually return * STATE_LOCK_BLOCKED. */ status = STATE_LOCK_BLOCKED; } if (status == STATE_LOCK_CONFLICT) { if (holder != NULL) { *holder = &unknown_owner; inc_state_owner_ref(&unknown_owner); } if (conflict != NULL) *conflict = conflicting_lock; } return status; } /** * @brief Fill out conflict information * * @param[in] found_entry Conflicting lock * @param[out] holder Owner that holds conflicting lock * @param[out] conflict Description of conflicting lock */ void copy_conflict(state_lock_entry_t *found_entry, state_owner_t **holder, fsal_lock_param_t *conflict) { if (found_entry == NULL) return; if (holder != NULL) { *holder = found_entry->sle_owner; inc_state_owner_ref(found_entry->sle_owner); } if (conflict != NULL) *conflict = found_entry->sle_lock; } /****************************************************************************** * * Primary lock interface functions * ******************************************************************************/ /** * @brief Test for lock availability * * This function acquires the state lock on an entry and thus is only * suitable for operations like lockt. If one wishes to use it as * part of a larger lock or state operation one would need to split it * out. * * @param[in] obj File to test * @param[in] state Optional state_t to relate this test to a fsal_fd * @param[in] owner Lock owner making the test * @param[in] lock Lock description * @param[out] holder Owner that holds conflicting lock * @param[out] conflict Description of conflicting lock * * @return State status. */ state_status_t state_test(struct fsal_obj_handle *obj, state_t *state, state_owner_t *owner, fsal_lock_param_t *lock, state_owner_t **holder, fsal_lock_param_t *conflict) { state_lock_entry_t *found_entry; state_status_t status = 0; LogLock(COMPONENT_STATE, NIV_FULL_DEBUG, "TEST", obj, owner, lock); LOCK__REQUEST_AUTO_TRACEPOINT(lock, obj, test_request_start, TRACE_INFO, "test request started"); STATELOCK_lock(obj); found_entry = get_overlapping_entry(obj->state_hdl, owner, lock); if (found_entry != NULL) { /* found a conflicting lock, return it */ LogEntry("Found conflict", found_entry); copy_conflict(found_entry, holder, conflict); status = STATE_LOCK_CONFLICT; } else { /* Prepare to make call to FSAL for this lock */ status = do_lock_op(obj, state, FSAL_OP_LOCKT, owner, lock, holder, conflict, false); switch (status) { case STATE_SUCCESS: LogFullDebug(COMPONENT_STATE, "Lock success"); break; case STATE_LOCK_CONFLICT: if (holder != NULL && conflict != NULL) { LogLock(COMPONENT_STATE, NIV_FULL_DEBUG, "Conflict from FSAL", obj, *holder, conflict); } break; case STATE_ESTALE: LogDebug(COMPONENT_STATE, "Got error %s from FSAL lock operation", state_err_str(status)); break; default: LogMajor(COMPONENT_STATE, "Got error from FSAL lock operation, error=%s", state_err_str(status)); break; } } LOCK__REQUEST_AUTO_TRACEPOINT(lock, obj, test_request_end, TRACE_INFO, "test request ended {}", status); if (isFullDebug(COMPONENT_STATE) && isFullDebug(COMPONENT_MEMLEAKS)) LogList("Lock List", obj, &obj->state_hdl->file.lock_list); STATELOCK_unlock(obj); return status; } /** * @brief Cancel lock on given file * * @param[in] obj File to operate on * @param[in] owner Lock operation * @param[in] lock Lock description */ static void state_cancel_internal(struct fsal_obj_handle *obj, state_owner_t *owner, fsal_lock_param_t *lock) { struct glist_head *glist; state_lock_entry_t *found_entry; /* If lock list is empty, there really isn't any work for us to do. */ if (glist_empty(&obj->state_hdl->file.lock_list)) { LogDebug(COMPONENT_STATE, "Cancel success on file with no locks"); return; } LOCK__REQUEST_AUTO_TRACEPOINT(lock, obj, cancel_request_start, TRACE_INFO, "cancel request started"); glist_for_each(glist, &obj->state_hdl->file.lock_list) { found_entry = glist_entry(glist, state_lock_entry_t, sle_list); if (different_owners(found_entry->sle_owner, owner)) continue; /* Can not cancel a lock once it is granted */ if (found_entry->sle_blocked == STATE_NON_BLOCKING) continue; if (different_lock(&found_entry->sle_lock, lock)) continue; /* Cancel the blocked lock */ cancel_blocked_lock(obj, found_entry); /* Check to see if we can grant any blocked locks. */ grant_blocked_locks(obj->state_hdl); break; } } /** * @brief handle the case where requested lock is either contained or overlaps * an existing lock * * @param[in] obj File to operate on * @param[in] owner Lock operation * @param[in] state state_t associated with lock if any * @param[in] blocking lock block type * @param[in] lock Lock description * @param[in] found_entry entry to check for * @param[in] found_entry_end end of entry * @param[in] range_end end of range to test for * @param[out] overlap Hint that lock overlaps * @param[out] lock_granted Was lock granted from cache * @param[out] lock_cancel Was request treated as a lock cancel */ static void handle_contained_range_if_present( struct fsal_obj_handle *obj, state_owner_t *owner, state_t *state, state_blocking_t blocking, fsal_lock_param_t *lock, state_lock_entry_t *found_entry, uint64_t found_entry_end, uint64_t range_end, bool *overlap, bool *lock_granted, bool *lock_cancel) { *lock_granted = false; *lock_cancel = false; if (found_entry_end < range_end || found_entry->sle_lock.lock_start > lock->lock_start || found_entry->sle_lock.lock_type != lock->lock_type) { /* Lock is not fully overlap or contained */ return; } if (different_owners(found_entry->sle_owner, owner)) { /* Found a compatible lock with a different lock owner * that fully overlaps, set hint if conflicting lock exists. */ LogEntry("Found overlapping", found_entry); *overlap = true; return; } if (found_entry->sle_blocked == STATE_AVAILABLE && found_entry->sle_protocol == LOCK_NLM) { /* Need to handle completion of granting * of this lock because a GRANT was in * progress. This could be a client * retrying a blocked lock due to * mis-trust of server. If the client * also accepts the GRANT_MSG with a * GRANT_RESP, that will be just fine. */ LogEntry("Immediate grant for", found_entry); LOCK_AUTO_TRACEPOINT(found_entry, immediate_grant1, TRACE_INFO, "Immediate grant"); grant_blocked_lock_immediate(obj->state_hdl, found_entry); } else if (found_entry->sle_blocked == STATE_AVAILABLE && found_entry->sle_protocol == LOCK_NFSv4) { /* Nfsv4 locks can only be granted by an exact match */ if (different_lock(&found_entry->sle_lock, lock)) { return; } else { LOCK_AUTO_TRACEPOINT(found_entry, immediate_grant2, TRACE_INFO, "Immediate grant"); LogEntry("Immediate grant for", found_entry); grant_nfsv4_blocking_lock(obj, found_entry); } } else if ((found_entry->sle_blocked == STATE_BLOCKING) && (found_entry->sle_protocol == LOCK_NFSv4) && (blocking == STATE_NON_BLOCKING) && (!different_lock(&found_entry->sle_lock, lock))) { /* In Nfsv4 when client sends same lock but changes from * blocking to non blocking we need to remove the lock from * non blocking list, and return the lock result */ LOCK_AUTO_TRACEPOINT(found_entry, cancel_blocked, TRACE_INFO, "cancel blocked lock"); LogEntry("cancel blocked lock", found_entry); /* Cancel the blocked lock */ cancel_blocked_lock(obj, found_entry); /* Check to see if we can grant any blocked locks. */ grant_blocked_locks(obj->state_hdl); *lock_cancel = true; return; } else if (found_entry->sle_blocked != STATE_NON_BLOCKING) { return; } /* Found an entry that entirely overlaps the new entry * (and due to the preceding test does not prevent * granting this lock - therefore there can't be any * other locks that would prevent granting this lock */ *lock_granted = true; LogEntry("Found existing", found_entry); if (found_entry->sle_state != state) { state_t *old_state = NULL; LogFullDebug(COMPONENT_STATE, "Existing lock entry has old state"); old_state = found_entry->sle_state; found_entry->sle_state = state; inc_state_t_ref(state); if (old_state != NULL) dec_state_t_ref(old_state); glist_add_tail(&state->state_data.lock.state_locklist, &found_entry->sle_state_locks); LogEntry("sle after state change", found_entry); } } /** * @brief Attempt to acquire a lock * * Must hold the st_lock * * @param[in] obj File to lock * @param[in] owner Lock owner * @param[in] state Associated state for the lock * @param[in] blocking Blocking type * @param[in,out] bdata Blocking lock data * @param[in] lock Lock description * @param[out] holder Holder of conflicting lock * @param[out] conflict Conflicting lock description * * @return State status. */ state_status_t state_lock(struct fsal_obj_handle *obj, state_owner_t *owner, state_t *state, state_blocking_t blocking, lock_protocol_t protocol, state_block_data_t **bdata, fsal_lock_param_t *lock, state_owner_t **holder, fsal_lock_param_t *conflict) { bool allow = true, overlap = false; struct glist_head *glist; struct glist_head *glist_n; state_lock_entry_t *found_entry; state_lock_entry_t *new_entry; uint64_t found_entry_end; uint64_t range_end = lock_end(lock); struct fsal_export *fsal_export = op_ctx->fsal_export; fsal_lock_op_t lock_op; state_status_t status = 0; bool async; state_block_data_t *block_data; bool lock_granted; bool lock_cancel; LOCK__REQUEST_AUTO_TRACEPOINT(lock, obj, lock_request_start, TRACE_INFO, "lock request started"); if (blocking != STATE_NON_BLOCKING) { /* First search for a blocked request. Client can ignore the * blocked request and keep sending us new lock request again * and again. So if we have a mapping blocked request return * that */ glist_for_each(glist, &obj->state_hdl->file.lock_list) { found_entry = glist_entry(glist, state_lock_entry_t, sle_list); if (different_owners(found_entry->sle_owner, owner)) continue; /* Need to reject lock request if this lock owner * already has a lock on this file via a different * export. */ if (found_entry->sle_export != op_ctx->ctx_export) { struct tmp_export_paths tmp = { NULL, NULL }; tmp_get_exp_paths(&tmp, found_entry->sle_export); LogEvent( COMPONENT_STATE, "Lock Owner Export Conflict, Lock held for export %d (%s), request for export %d (%s)", found_entry->sle_export->export_id, op_ctx_tmp_export_path(op_ctx, &tmp), op_ctx->ctx_export->export_id, op_ctx_export_path(op_ctx)); LogEntry( "Found lock entry belonging to another export", found_entry); tmp_put_exp_paths(&tmp); status = STATE_INVALID_ARGUMENT; return status; } if (found_entry->sle_blocked != blocking) continue; if (different_lock(&found_entry->sle_lock, lock)) continue; /* We have matched all attribute of the existing lock. * Just return with blocked status. Client may be * polling. */ LogEntry("Found blocked", found_entry); LOCK_AUTO_TRACEPOINT(found_entry, found_blocked, TRACE_INFO, "Found blocked"); status = STATE_LOCK_BLOCKED; return status; } } recheck_for_conflicting_entries: glist_for_each_safe(glist, glist_n, &obj->state_hdl->file.lock_list) { found_entry = glist_entry(glist, state_lock_entry_t, sle_list); /* Need to reject lock request if this lock owner already has * a lock on this file via a different export. */ if (found_entry->sle_export != op_ctx->ctx_export && !different_owners(found_entry->sle_owner, owner)) { struct tmp_export_paths tmp = { NULL, NULL }; tmp_get_exp_paths(&tmp, found_entry->sle_export); LogEvent( COMPONENT_STATE, "Lock Owner Export Conflict, Lock held for export %d (%s), request for export %d (%s)", found_entry->sle_export->export_id, op_ctx_tmp_export_path(op_ctx, &tmp), op_ctx->ctx_export->export_id, op_ctx_export_path(op_ctx)); LogEntry("Found lock entry belonging to another export", found_entry); tmp_put_exp_paths(&tmp); status = STATE_INVALID_ARGUMENT; return status; } /* Don't skip blocked locks for fairness */ found_entry_end = lock_end(&found_entry->sle_lock); if (!(lock->lock_reclaim) && (found_entry_end >= lock->lock_start) && (found_entry->sle_lock.lock_start <= range_end) && allow) { /* lock overlaps see if we can allow: * allow if neither lock is exclusive or * the owner is the same */ if ((found_entry->sle_lock.lock_type == FSAL_LOCK_W || lock->lock_type == FSAL_LOCK_W) && different_owners(found_entry->sle_owner, owner)) { /* Found a conflicting lock, break out of loop. * Also indicate overlap hint. */ LOCK_AUTO_TRACEPOINT(found_entry, lock_conflict, TRACE_INFO, "Found conflict"); LogEntry("Conflicts with", found_entry); LogList("Locks", obj, &obj->state_hdl->file.lock_list); state_owner_t *cf_own = found_entry->sle_owner; state_nfs4_owner_t *owner = &cf_own->so_owner.so_nfs4_owner; nfs_client_id_t *c_id = owner->so_clientrec; if ((atomic_fetch_uint32_t( &num_of_curr_expired_clients)) && (cf_own->so_type >= STATE_OPEN_OWNER_NFSV4) && c_id->marked_for_delayed_cleanup) { /* Release the state lock, to clean */ STATELOCK_unlock(obj); reap_expired_client_list(c_id); /* Acquire back the state lock */ STATELOCK_lock(obj); /* Continue to recheck for conflicts */ goto recheck_for_conflicting_entries; } if (blocking != STATE_BLOCKING) { copy_conflict(found_entry, holder, conflict); } allow = false; overlap = true; /* We must continue as the request can be an * nfsv4 cancel request */ continue; } } handle_contained_range_if_present(obj, owner, state, blocking, lock, found_entry, found_entry_end, range_end, &overlap, &lock_granted, &lock_cancel); if (lock_granted) { status = STATE_SUCCESS; return status; } if (lock_cancel) { status = STATE_LOCK_BLOCKED; return status; } } /* Decide how to proceed */ if (blocking == STATE_BLOCKING) { /* do_lock_op will handle FSAL_OP_LOCKB for those FSALs that * do not support async blocking locks. It will make a * non-blocking call in that case, and it will return * STATE_LOCK_BLOCKED if the lock was not granted. */ lock_op = FSAL_OP_LOCKB; } else if (allow) { /* No conflict found in Ganesha, and this is not a blocking * request. */ lock_op = FSAL_OP_LOCK; } else { /* This is not a blocking lock and has a conflict. * Return it. */ status = STATE_LOCK_CONFLICT; return status; } /* We have already returned if: * + we have found an identical blocking lock * + we have found an entirely overlapping lock with the same lock owner * + this was not a supported blocking lock and we found a conflict * * So at this point, we are either going to do one of the following (all * descriptions below assume no problems occur): * * (1) FSAL supports async blocking locks, we know there is a conflict, ( and this is a supported blocking lock request * * Make FSAL_OP_LOCKB call anyway, we will rely on FSAL to grant * blocking locks. We will return the conflict we know about rather * than what the FSAL returns. Insert blocking lock into queue. * * (2) FSAL supports async blocking locks, we don't know about any * conflict, and this is a supported blocking lock request * * Make FSAL_OP_LOCKB call, if it indicates block, insert blocking * lock into queue, and return the conflict the FSAL indicates. If * FSAL grants lock, then return granted lock and insert into lock * list, otherwise insert blocking lock into queue. * * (3) FSAL doesn't support async blocking locks, this is a supported * blocking lock and we know there is a conflict * * Insert blocking lock into queue, we will grant lock when * possible. * * (4) FSAL doesn't support async blocking locks and we don't know * about any conflict * * Make FSAL_OP_LOCK call, if it indicates conflict, return that. * Even if this is a supported blocking lock call, there is no way * to block. If lock is granted, return that and insert lock into * list. * * (5) FSAL supports async blocking locks, we don't know about any * conflict, and this is not a supported blocking lock request * * Make FSAL_OP_LOCK call, if it indicates conflict, return that. * If lock is granted, return that and insert lock into list. */ /* Create the new lock entry. * Provisionally mark this lock as granted. */ new_entry = create_state_lock_entry(obj, op_ctx->ctx_export, STATE_NON_BLOCKING, protocol, owner, state, lock); /* Check for async blocking lock support. */ async = fsal_export->exp_ops.fs_supports(fsal_export, fso_lock_support_async_block); /* If no conflict in lock list, or FSAL supports async blocking locks, * make FSAL call. Don't ask for conflict if we know about a conflict. */ if (allow || async) { /* Prepare to make call to FSAL for this lock */ status = do_lock_op(obj, state, lock_op, owner, lock, allow ? holder : NULL, allow ? conflict : NULL, overlap); } else { /* FSAL does not support async blocking locks and we have * a blocking lock within Ganesha, no need to make an * FSAL call that will just return denied. */ status = STATE_LOCK_BLOCKED; } LOCK__REQUEST_AUTO_TRACEPOINT(lock, obj, lock_request, TRACE_INFO, "lock request ended with status {}", status); if (status == STATE_SUCCESS) { /* Merge any touching or overlapping locks into this one */ LogEntry("FSAL lock acquired, merging locks for", new_entry); merge_lock_entry(obj->state_hdl, new_entry); /* Insert entry into lock list */ LogEntry("New lock", new_entry); glist_add_tail(&obj->state_hdl->file.lock_list, &new_entry->sle_list); /* A lock downgrade could unblock blocked locks */ grant_blocked_locks(obj->state_hdl); } else if (status == STATE_LOCK_CONFLICT) { LogEntry("Conflict in FSAL for", new_entry); /* Discard lock entry */ remove_from_locklist(new_entry); } else if (status == STATE_LOCK_BLOCKED) { /* We are going to use the bdata, set it to NULL so that * the caller doesn't free it! */ block_data = *bdata; *bdata = NULL; /* Mark entry as blocking and attach block_data */ new_entry->sle_block_data = block_data; new_entry->sle_blocked = blocking; block_data->sbd_lock_entry = new_entry; if (async) { /* Allow FSAL to signal when lock is granted or * available for retry. */ block_data->sbd_block_type = STATE_BLOCK_ASYNC; } else if (allow) { /* Actively poll for the lock only for nlm locks. */ block_data->sbd_block_type = (protocol == LOCK_NLM) ? STATE_BLOCK_ASYNC : STATE_BLOCK_POLL; } else { /* Ganesha will attempt to grant the lock when * a conflicting lock is released. */ block_data->sbd_block_type = STATE_BLOCK_INTERNAL; } /* Insert entry into lock list */ LogEntry("FSAL block for", new_entry); glist_add_tail(&obj->state_hdl->file.lock_list, &new_entry->sle_list); PTHREAD_MUTEX_lock(&blocked_locks_mutex); glist_add_tail(&state_blocked_locks, &block_data->sbd_list); PTHREAD_MUTEX_unlock(&blocked_locks_mutex); } else { if (status == STATE_ESTALE) LogDebug(COMPONENT_STATE, "Unable to lock FSAL, error=%s", state_err_str(status)); else LogMajor(COMPONENT_STATE, "Unable to lock FSAL, error=%s", state_err_str(status)); /* Discard lock entry */ remove_from_locklist(new_entry); } return status; } /* Like state_unlock- but assumes obj state lock is taken */ static state_status_t state_unlock_internal(struct fsal_obj_handle *obj, state_t *state, state_owner_t *owner, bool state_applies, int32_t nsm_state, fsal_lock_param_t *lock) { bool empty = false; bool removed = false; state_status_t status = 0; if (obj->type != REGULAR_FILE) { LogLock(COMPONENT_STATE, NIV_DEBUG, "Bad Unlock", obj, owner, lock); return STATE_BAD_TYPE; } /* If lock list is empty, there really isn't any work for us to do. */ if (glist_empty(&obj->state_hdl->file.lock_list)) { LogDebug(COMPONENT_STATE, "Unlock success on file with no locks"); status = STATE_SUCCESS; goto out_unlock; } LOCK__REQUEST_AUTO_TRACEPOINT(lock, obj, unlock_request_start, TRACE_INFO, "unlock request started"); LogFullDebug( COMPONENT_STATE, "----------------------------------------------------------------------"); LogLock(COMPONENT_STATE, NIV_FULL_DEBUG, "Subtracting", obj, owner, lock); LogFullDebug( COMPONENT_STATE, "----------------------------------------------------------------------"); /* First cancel any blocking locks that might * overlap the unlocked range. */ cancel_blocked_locks_range(obj->state_hdl, owner, state_applies, nsm_state, lock); /* Release the lock from lock list for entry */ status = subtract_lock_from_list(owner, state_applies, nsm_state, lock, &removed, &obj->state_hdl->file.lock_list); if (status != STATE_SUCCESS) { /* The unlock has not taken affect (other than canceling any * blocking locks. */ LogMajor(COMPONENT_STATE, "Unable to remove lock from list for unlock, error=%s", state_err_str(status)); goto out_unlock; } /* Unlocking the entire region will remove any FSAL locks we held, * whether from fully granted locks, or from blocking locks that were * in the process of being granted. */ status = do_lock_op(obj, state, FSAL_OP_UNLOCK, owner, lock, NULL, /* no conflict expected */ NULL, false); LOCK__REQUEST_AUTO_TRACEPOINT(lock, obj, unlock_request_end, TRACE_INFO, "lock request started {}", status); if (status != STATE_SUCCESS) LogMajor(COMPONENT_STATE, "Unable to unlock FSAL, error=%s", state_err_str(status)); LogFullDebug( COMPONENT_STATE, "----------------------------------------------------------------------"); LogLock(COMPONENT_STATE, NIV_FULL_DEBUG, "Done", obj, owner, lock); LogFullDebug( COMPONENT_STATE, "----------------------------------------------------------------------"); if (isFullDebug(COMPONENT_STATE) && isFullDebug(COMPONENT_MEMLEAKS) && lock->lock_start == 0 && lock->lock_length == 0) empty = LogList("Lock List", obj, &obj->state_hdl->file.lock_list); grant_blocked_locks(obj->state_hdl); if (isFullDebug(COMPONENT_STATE) && isFullDebug(COMPONENT_MEMLEAKS) && lock->lock_start == 0 && lock->lock_length == 0 && empty) dump_all_locks("All locks (after unlock)"); out_unlock: return status; } /** * @brief Release a lock * * @param[in] obj File to unlock * @param[in] state Associated state_t (if any) * @param[in] owner Owner of lock * @param[in] state_applies Indicator if nsm_state is relevant * @param[in] nsm_state NSM state number * @param[in] lock Lock description */ state_status_t state_unlock(struct fsal_obj_handle *obj, state_t *state, state_owner_t *owner, bool state_applies, int32_t nsm_state, fsal_lock_param_t *lock) { state_status_t status; STATELOCK_lock(obj); status = state_unlock_internal(obj, state, owner, state_applies, nsm_state, lock); STATELOCK_unlock(obj); return status; } /** * @brief Release all locks in a state * Assumes obj state lock is taken * * @param[in] obj File to unlock * @param[in] state The state */ void state_unlock_all(struct fsal_obj_handle *obj, state_t *state) { /* The state lock associated with the state */ struct state_lock *locks; /* Specific lock entry */ state_lock_entry_t *lock_entry; state_status_t status = 0; if (state->state_type != STATE_TYPE_LOCK) { /* Only relevant for lock state */ return; } locks = &state->state_data.lock; while (true) { PTHREAD_MUTEX_lock(&state->state_mutex); lock_entry = glist_first_entry(&locks->state_locklist, struct state_lock_entry_t, sle_state_locks); if (lock_entry == NULL) { PTHREAD_MUTEX_unlock(&state->state_mutex); break; } lock_entry_inc_ref(lock_entry); /* Move this state to the end of the list * (this will help if errors occur) */ glist_move_tail(&locks->state_locklist, &lock_entry->sle_state_locks); PTHREAD_MUTEX_unlock(&state->state_mutex); status = state_unlock_internal(state->state_obj, state, state->state_owner, false, 0, &lock_entry->sle_lock); lock_entry_dec_ref(lock_entry); if (status != STATE_SUCCESS) { LogCrit(COMPONENT_STATE, "Failed to unlock lock in a state. This will be retried (status = %s)", state_err_str(status)); continue; } } } /** * @brief Cancel a blocking lock * * @param[in] obj File on which to cancel the lock * @param[in] owner Lock owner * @param[in] lock Lock description * * @return State status. */ state_status_t state_cancel(struct fsal_obj_handle *obj, state_owner_t *owner, fsal_lock_param_t *lock) { if (obj->type != REGULAR_FILE) { LogLock(COMPONENT_STATE, NIV_DEBUG, "Bad Cancel", obj, owner, lock); return STATE_BAD_TYPE; } STATELOCK_lock(obj); state_cancel_internal(obj, owner, lock); STATELOCK_unlock(obj); return STATE_SUCCESS; } /** * @brief Release a lock if blocked * * @param[in] lock_entry lock entry to cancel */ state_status_t state_cancel_blocked(state_lock_entry_t *lock_entry) { state_status_t status; STATELOCK_lock(lock_entry->sle_obj); switch (lock_entry->sle_blocked) { case STATE_BLOCKING: state_cancel_internal(lock_entry->sle_obj, lock_entry->sle_owner, &lock_entry->sle_lock); status = STATE_SUCCESS; break; case STATE_AVAILABLE: switch (lock_entry->sle_protocol) { case LOCK_NFSv4: state_cancel_internal(lock_entry->sle_obj, lock_entry->sle_owner, &lock_entry->sle_lock); status = STATE_SUCCESS; break; case LOCK_NLM: status = STATE_LOCKED; break; default: LogFatal(COMPONENT_STATE, "Got an protocol type %s", str_protocol(lock_entry->sle_protocol)); } break; case STATE_NON_BLOCKING: status = STATE_LOCKED; break; case STATE_CANCELED: status = STATE_SUCCESS; break; default: LogFatal(COMPONENT_STATE, "Got an unexpected block type %s", str_blocked(lock_entry->sle_blocked)); } STATELOCK_unlock(lock_entry->sle_obj); return status; } #ifdef _USE_NLM /** * @brief Handle an SM_NOTIFY from NLM * * Also used to handle NLM_FREE_ALL * * @param[in] nsmclient NSM client data * @param[in] state_applies Indicates if nsm_state is valid * @param[in] nsm_state NSM state value * * @return State status. */ state_status_t state_nlm_notify(state_nsm_client_t *nsmclient, bool state_applies, int32_t nsm_state) { state_owner_t *owner; state_lock_entry_t *found_entry; fsal_lock_param_t lock; struct fsal_obj_handle *obj; int errcnt = 0; struct glist_head newlocks; state_t *found_share; state_status_t status = 0; struct req_op_context op_context; struct gsh_export *export; state_t *state; /* Initialize a context */ init_op_context_simple(&op_context, NULL, NULL); if (isFullDebug(COMPONENT_STATE)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_nsm_client(&dspbuf, nsmclient); LogFullDebug(COMPONENT_STATE, "Notify for %s", str); } glist_init(&newlocks); /* First remove byte range locks. * Only accept so many errors before giving up. */ while (errcnt < STATE_ERR_MAX) { PTHREAD_MUTEX_lock(&nsmclient->ssc_mutex); /* We just need to find any file this client has locks on. * We pick the first lock the client holds, and use it's file. */ found_entry = glist_first_entry(&nsmclient->ssc_lock_list, state_lock_entry_t, sle_client_locks); /* If we don't find any entries, then we are done. */ if (found_entry == NULL) { PTHREAD_MUTEX_unlock(&nsmclient->ssc_mutex); break; } /* Remove from the client lock list */ glist_del(&found_entry->sle_client_locks); /* If called as a result of SM_NOTIFY, we don't drop the * locks belonging to the "current" NSM state counter passed * in the SM_NOTIFTY. Otherwise, we drop all locks. * We expect never to get an SM_NOTIFY from a client that uses * non-monitored locks (it will send a FREE_ALL instead (which * will not set the state_applies flag). */ if (state_applies && found_entry->sle_state->state_seqid == nsm_state) { /* This is a new lock acquired since the client * rebooted, retain it. * * Note that although this entry is temporarily * not on the ssc_lock_list, we don't need to hold * an extra reference to the entry since the list * still effectively protected by the ssc_mutex. * If something happens to remove this entry, it will * actually be removed from the newlocks list, which * in the end is correct. So we don't need to do * anything special. */ /** @todo FSF: vulnerability - if a SECOND SM_NOTIFY * comes in while this one is still being processed * the locks put on the newlocks list would not be * cleaned up. This list REALLY should be attached * to the nsm_client in some way, or we should block * handling of the 2nd SM_NOTIFY until this one is * complete. */ LogEntry("Don't release new lock", found_entry); glist_add_tail(&newlocks, &found_entry->sle_client_locks); PTHREAD_MUTEX_unlock(&nsmclient->ssc_mutex); continue; } LogEntry("Release client locks based on", found_entry); /* Extract the bits from the lock entry that we will need * to proceed with the operation (file, owner, and * export). */ obj = found_entry->sle_obj; owner = found_entry->sle_owner; export = found_entry->sle_export; state = found_entry->sle_state; /* Get a reference to the export while we still hold the * ssc_mutex. This assures that the export definitely can * not have had it's last refcount released. We will check * later to see if the export is being removed. */ get_gsh_export_ref(export); set_op_context_export(export); /* Get a reference to the owner */ inc_state_owner_ref(owner); /* Get a reference to the state_t */ inc_state_t_ref(state); /* Move this entry to the end of the list * (this will help if errors occur) */ glist_add_tail(&nsmclient->ssc_lock_list, &found_entry->sle_client_locks); /* Now we are done with this specific entry, release the mutex. */ PTHREAD_MUTEX_unlock(&nsmclient->ssc_mutex); /* Make lock that covers the whole file. * type doesn't matter for unlock */ lock.lock_type = FSAL_LOCK_R; lock.lock_start = 0; lock.lock_length = 0; if (export_ready(export)) { /* Remove all locks held by this NLM Client on * the file. */ status = state_unlock(obj, state, owner, state_applies, nsm_state, &lock); } else { /* The export is being removed, we didn't bother * calling state_unlock() because export cleanup * will remove all the state. This is assured by * the call to put_gsh_export from * clear_op_context_export. Pretend success. */ status = STATE_SUCCESS; } /* Release the refcounts we took above. */ dec_state_owner_ref(owner); clear_op_context_export(); if (!state_unlock_err_ok(status)) { /* Increment the error count and try the next lock, * with any luck the memory pressure which is causing * the problem will resolve itself. */ LogFullDebug(COMPONENT_STATE, "state_unlock returned %s", state_err_str(status)); } } /* Now remove NLM_SHARE reservations. * Only accept so many errors before giving up. * We always drop all NLM_SHARE reservations since they are * non-monitored. */ while (errcnt < STATE_ERR_MAX) { PTHREAD_MUTEX_lock(&nsmclient->ssc_mutex); /* We just need to find any file this client has locks on. * We pick the first lock the client holds, and use it's file. */ found_share = glist_first_entry(&nsmclient->ssc_share_list, state_t, state_data.nlm_share.share_perclient); /* If we don't find any entries, then we are done. */ if (found_share == NULL) { PTHREAD_MUTEX_unlock(&nsmclient->ssc_mutex); break; } /* Extract the bits from the lock entry that we will need * to proceed with the operation (object, owner, and * export). */ obj = get_state_obj_ref(found_share); if (obj == NULL) { LogDebug(COMPONENT_STATE, "Entry for state is stale"); PTHREAD_MUTEX_unlock(&nsmclient->ssc_mutex); break; } owner = found_share->state_owner; export = found_share->state_export; /* Get a reference to the export while we still hold the * ssc_mutex. This assures that the export definitely can * not have had it's last refcount released. We will check * later to see if the export is being removed. */ get_gsh_export_ref(export); set_op_context_export(export); /* Get a reference to the owner */ inc_state_owner_ref(owner); /* Get a reference to the state_t */ inc_state_t_ref(found_share); /* Move this entry to the end of the list * (this will help if errors occur) */ glist_move_tail( &nsmclient->ssc_share_list, &found_share->state_data.nlm_share.share_perclient); PTHREAD_MUTEX_unlock(&nsmclient->ssc_mutex); if (export_ready(export)) { /* Remove all shares held by this NSM Client and * Owner on the file (on all exports) */ status = state_nlm_share(obj, OPEN4_SHARE_ACCESS_ALL, OPEN4_SHARE_DENY_ALL, owner, found_share, false, true); } else { /* The export is being removed, we didn't bother * calling state_unlock() because export cleanup * will remove all the state. This is assured by * the call to put_gsh_export from * clear_op_context_export. Pretend success. */ status = STATE_SUCCESS; } /* Release the refcounts we took above. */ dec_state_owner_ref(owner); obj->obj_ops->put_ref(obj); dec_state_t_ref(found_share); clear_op_context_export(); if (!state_unlock_err_ok(status)) { /* Increment the error count and try the next share, * with any luck the memory pressure which is causing * the problem will resolve itself. */ LogFullDebug(COMPONENT_STATE, "state_nlm_unshare returned %s", state_err_str(status)); } } if (errcnt == STATE_ERR_MAX) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_nsm_client(&dspbuf, nsmclient); LogFatal(COMPONENT_STATE, "Could not complete NLM notify for %s", str); } /* Put locks from current client incarnation onto end of list. * * Note that it's possible one or more of the locks we originally * put on the newlocks list have been removed. This is actually * just fine. Since everything is protected by the ssc_mutex, * even the fact that the newlocks list head is a stack local * variable is local will actually work just fine. */ PTHREAD_MUTEX_lock(&nsmclient->ssc_mutex); glist_add_list_tail(&nsmclient->ssc_lock_list, &newlocks); PTHREAD_MUTEX_unlock(&nsmclient->ssc_mutex); LogFullDebug(COMPONENT_STATE, "DONE"); release_op_context(); return status; } #endif /* _USE_NLM */ /** * @brief Release all locks held by an NFS v4 lock owner * * @param[in] owner Lock owner * */ void state_nfs4_owner_unlock_all(state_owner_t *owner) { fsal_lock_param_t lock; struct fsal_obj_handle *obj; int errcnt = 0; state_status_t status = 0; struct saved_export_context saved; struct gsh_export *export; state_t *state; bool ok; /* Only accept so many errors before giving up. */ while (errcnt < STATE_ERR_MAX) { PTHREAD_MUTEX_lock(&owner->so_mutex); /* We just need to find any file this owner has locks on. * We pick the first lock the owner holds, and use it's file. */ state = glist_first_entry( &owner->so_owner.so_nfs4_owner.so_state_list, state_t, state_owner_list); /* If we don't find any entries, then we are done. */ if (state == NULL) { PTHREAD_MUTEX_unlock(&owner->so_mutex); break; } inc_state_t_ref(state); /* Move this state to the end of the list * (this will help if errors occur) */ glist_move_tail(&owner->so_owner.so_nfs4_owner.so_state_list, &state->state_owner_list); /* Get references to the obj and export */ ok = get_state_obj_export_owner_refs(state, &obj, &export, NULL); PTHREAD_MUTEX_unlock(&owner->so_mutex); if (!ok) { /* Entry and/or export is dying, skip this state, * it will be cleaned up soon enough. */ continue; } /* Set up the op_context with the proper export */ save_op_context_export_and_set_export(&saved, export); /* Make lock that covers the whole file. * type doesn't matter for unlock */ lock.lock_type = FSAL_LOCK_R; lock.lock_start = 0; lock.lock_length = 0; /* Remove all locks held by this owner on the file */ status = state_unlock(obj, state, owner, false, 0, &lock); if (!state_unlock_err_ok(status)) { /* Increment the error count and try the next lock, * with any luck the memory pressure which is causing * the problem will resolve itself. */ LogCrit(COMPONENT_STATE, "state_unlock failed %s", state_err_str(status)); errcnt++; } else if (status == STATE_SUCCESS) { /* Delete the state_t */ state_del(state); } dec_state_t_ref(state); /* Release the obj ref and export ref. */ obj->obj_ops->put_ref(obj); restore_op_context_export(&saved); } if (errcnt == STATE_ERR_MAX) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_owner(&dspbuf, owner); LogFatal( COMPONENT_STATE, "Could not complete cleanup of lock state for lock owner %s", str); } } /** * @brief Release all locks held on an export * */ void state_export_unlock_all(void) { state_lock_entry_t *found_entry; fsal_lock_param_t lock; struct fsal_obj_handle *obj; int errcnt = 0; state_status_t status = 0; state_owner_t *owner; state_t *state; /* Only accept so many errors before giving up. */ while (errcnt < STATE_ERR_MAX) { PTHREAD_RWLOCK_wrlock(&op_ctx->ctx_export->exp_lock); /* We just need to find any file this owner has locks on. * We pick the first lock the owner holds, and use it's file. */ found_entry = glist_first_entry(&op_ctx->ctx_export->exp_lock_list, state_lock_entry_t, sle_export_locks); /* If we don't find any entries, then we are done. */ if (found_entry == NULL) { PTHREAD_RWLOCK_unlock(&op_ctx->ctx_export->exp_lock); break; } /* Extract the bits from the lock entry that we will need * to proceed with the operation (obj, owner, and * export). */ obj = found_entry->sle_obj; owner = found_entry->sle_owner; state = found_entry->sle_state; /* take a reference on the state_t */ inc_state_t_ref(state); /* Get a reference to the file object while we still hold * the ssc_mutex (since we hold this mutex, any other function * that might be cleaning up this lock CAN NOT have released * the last LRU reference, thus it is safe to grab another. */ obj->obj_ops->get_ref(obj); /* Get a reference to the owner */ inc_state_owner_ref(owner); /* Move this entry to the end of the list * (this will help if errors occur) */ glist_move_tail(&op_ctx->ctx_export->exp_lock_list, &found_entry->sle_export_locks); /* Now we are done with this specific entry, release the lock. */ PTHREAD_RWLOCK_unlock(&op_ctx->ctx_export->exp_lock); /* Make lock that covers the whole file. * type doesn't matter for unlock */ lock.lock_type = FSAL_LOCK_R; lock.lock_start = 0; lock.lock_length = 0; /* Remove all locks held by this NLM Client on * the file. */ status = state_unlock(obj, state, owner, false, 0, &lock); /* call state_del regardless of status as we're killing * the export */ if (owner->so_type == STATE_LOCK_OWNER_NFSV4) state_del(state); /* Release the refcounts we took above. */ dec_state_t_ref(state); dec_state_owner_ref(owner); obj->obj_ops->put_ref(obj); if (!state_unlock_err_ok(status)) { /* Increment the error count and try the next lock, * with any luck the memory pressure which is causing * the problem will resolve itself. */ LogDebug(COMPONENT_STATE, "state_unlock failed %s", state_err_str(status)); } } if (errcnt == STATE_ERR_MAX) { LogFatal(COMPONENT_STATE, "Could not complete cleanup of locks for %s", op_ctx_export_path(op_ctx)); } } /** * @brief handle nlm lock polling logic * * @param[in] pblock lock block data * @param[in] found_entry entry to poll */ static void handle_nlm_lock(state_block_data_t *pblock, state_lock_entry_t *found_entry) { /* Check if right type */ if (pblock->sbd_block_type != STATE_BLOCK_POLL) return; /* Schedule async processing, leave the lock on the blocked * lock list since we might not succeed in granting this lock. */ pblock->sbd_grant_type = STATE_GRANT_POLL; /* Since we're scheduling this block to be handled in * another thread, we need to hold a reference on the * lock entry to prevent the entry from release before * we are done processing. */ lock_entry_inc_ref(found_entry); if (state_block_schedule(found_entry) != STATE_SUCCESS) { LogMajor(COMPONENT_STATE, "Unable to schedule lock notification."); /* scheduling failed, release the reference to lock entry */ lock_entry_dec_ref(found_entry); } LogEntry("Blocked Lock found", found_entry); } /* It looks like when we report OPEN4_RESULT_MAY_NOTIFY_LOCK, the client polls * exactly every lease time. This creates a race where the poll request arrives * after we cancel the lock. For this reason, we add a small time buffer. */ #define POLL_TIME_CANCEL_BUFFER_SEC (5) /** * @brief handle nfsv4 lock polling logic * * @param[in] pblock lock block data * @param[in] found_entry entry to poll * @param[in] check_time time we performed the poll */ static void handle_nfsv4_lock(state_block_data_t *pblock, state_lock_entry_t *found_entry, time_t check_time) { assert(found_entry->sle_protocol == LOCK_NFSv4); if (found_entry->sle_blocked == STATE_AVAILABLE) { const uint64_t expire_time = pblock->sbd_prot.sbd_v4.snbd_notified_eligible_time + nfs_param.nfsv4_param.lease_lifetime + POLL_TIME_CANCEL_BUFFER_SEC; if (check_time < expire_time) { LOCK_AUTO_TRACEPOINT( found_entry, lock_poll_unexpired, TRACE_INFO, "lock not expired check_time {} expire {}", check_time, expire_time); return; } LOCK_AUTO_TRACEPOINT(found_entry, lock_poll_expired, TRACE_INFO, "lock not expired check_time {} expire {}", check_time, expire_time); /* Since we are scheduling this lock to be unlocked in * another thread, we need to hold a reference on the * lock entry to prevent the entry from being released * before we are done processing. */ lock_entry_inc_ref(found_entry); if (state_block_cancel_schedule(found_entry) != STATE_SUCCESS) { LogMajor(COMPONENT_STATE, "Unable to schedule cancel."); /* scheduling failed, release the reference to lock * entry */ lock_entry_dec_ref(found_entry); } } else { /* We set minimum time between polls to be twice the lease time, * but this is just an internal parameter */ const uint32_t min_time_between_polls = 2 * nfs_param.nfsv4_param.lease_lifetime; const uint64_t next_poll_time = pblock->sbd_prot.sbd_v4.snbd_last_poll_time + min_time_between_polls; if (check_time < next_poll_time) { LOCK_AUTO_TRACEPOINT( found_entry, lock_poll_not_check, TRACE_INFO, "poll eligibility check_time {} next_poll_time {}", check_time, next_poll_time); return; } LOCK_AUTO_TRACEPOINT( found_entry, lock_poll_check, TRACE_INFO, "poll eligibility check_time {} next_poll_time {}", check_time, next_poll_time); lock_entry_inc_ref(found_entry); if (test_blocking_lock_eligibility_schedule(found_entry) != STATE_SUCCESS) { LogMajor(COMPONENT_STATE, "Unable to schedule lock elgibility test."); lock_entry_dec_ref(found_entry); } } } /** * @brief Poll any blocked locks of type STATE_BLOCK_POLL * * @param[in] ctx Fridge Thread Context * */ void blocked_lock_polling(struct fridgethr_context *ctx) { state_lock_entry_t *found_entry; struct glist_head *glist; state_block_data_t *pblock; SetNameFunction("lk_poll"); PTHREAD_MUTEX_lock(&blocked_locks_mutex); time_t check_time = time(NULL); if (isFullDebug(COMPONENT_STATE) && isFullDebug(COMPONENT_MEMLEAKS)) LogBlockedList("Blocked Lock List", NULL, &state_blocked_locks); glist_for_each(glist, &state_blocked_locks) { pblock = glist_entry(glist, state_block_data_t, sbd_list); found_entry = pblock->sbd_lock_entry; /* Check if got an entry */ if (found_entry == NULL) continue; /* Check if this is a non polled nfsv4 lock*/ if (found_entry->sle_protocol == LOCK_NFSv4) handle_nfsv4_lock(pblock, found_entry, check_time); else handle_nlm_lock(pblock, found_entry); } /* glist_for_each_safe */ PTHREAD_MUTEX_unlock(&blocked_locks_mutex); } /** * @brief Find a lock and add to grant list * * @param[in] obj File to search * @param[in] owner Lock owner * @param[in] lock Lock description * @param[in] grant_type Grant type */ static void find_blocked_lock_upcall(struct fsal_obj_handle *obj, void *owner, fsal_lock_param_t *lock, state_grant_type_t grant_type) { state_lock_entry_t *found_entry; struct glist_head *glist; state_block_data_t *pblock; PTHREAD_MUTEX_lock(&blocked_locks_mutex); glist_for_each(glist, &state_blocked_locks) { pblock = glist_entry(glist, state_block_data_t, sbd_list); found_entry = pblock->sbd_lock_entry; /* Check if got an entry */ if (found_entry == NULL) continue; /* Check if for same file */ if (found_entry->sle_obj != obj) continue; /* Check if for same owner */ if (found_entry->sle_owner != owner) continue; /* Check if same lock */ if (different_lock(&found_entry->sle_lock, lock)) continue; /* Schedule async processing, leave the lock on the blocked * lock list for now (if we get multiple upcalls because our * async processing is slow for some reason, we don't want to * not find this lock entry and hit the LogFatal below... */ pblock->sbd_grant_type = grant_type; /* Since we are scheduling this block to be handled in * another thread, we need to hold a reference on the * lock entry to prevent the entry from releasing before * we are done processing. */ lock_entry_inc_ref(found_entry); if (state_block_schedule(found_entry) != STATE_SUCCESS) { LogMajor(COMPONENT_STATE, "Unable to schedule lock notification."); /* scheduling failed, release the reference to lock * entry */ lock_entry_dec_ref(found_entry); } LogEntry("Blocked Lock found", found_entry); PTHREAD_MUTEX_unlock(&blocked_locks_mutex); return; } /* glist_for_each_safe */ if (isFullDebug(COMPONENT_STATE) && isFullDebug(COMPONENT_MEMLEAKS)) LogBlockedList("Blocked Lock List", NULL, &state_blocked_locks); PTHREAD_MUTEX_unlock(&blocked_locks_mutex); if (isFullDebug(COMPONENT_STATE) && isFullDebug(COMPONENT_MEMLEAKS)) { STATELOCK_lock(obj); LogList("File Lock List", obj, &obj->state_hdl->file.lock_list); STATELOCK_unlock(obj); } /* It is likely that we got an upcall before the cancel request. * The cancel code will also unlock, so there is nothing to do * even if this is a granted lock. */ LogLockDesc(COMPONENT_STATE, NIV_EVENT, "Blocked Lock Not Found for", obj, owner, lock); } /** * @brief Handle upcall for granted lock * * @param[in] obj File on which lock is granted * @param[in] owner Lock owner * @param[in] lock Lock description */ void grant_blocked_lock_upcall(struct fsal_obj_handle *obj, void *owner, fsal_lock_param_t *lock) { LogLockDesc(COMPONENT_STATE, NIV_DEBUG, "Grant Upcall for", obj, owner, lock); find_blocked_lock_upcall(obj, owner, lock, STATE_GRANT_FSAL); } /** * @brief Handle upcall for available lock * * @param[in] obj File on which lock has become available * @param[in] owner Lock owner * @param[in] lock Lock description */ void available_blocked_lock_upcall(struct fsal_obj_handle *obj, void *owner, fsal_lock_param_t *lock) { LogLockDesc(COMPONENT_STATE, NIV_DEBUG, "Available Upcall for", obj, owner, lock); find_blocked_lock_upcall(obj, owner, lock, STATE_GRANT_FSAL_AVAILABLE); } /** * @brief Free all locks on a file * * @param[in] obj File to free */ void state_lock_wipe(struct state_hdl *hstate) { if (glist_empty(&hstate->file.lock_list)) return; free_list(&hstate->file.lock_list); } void cancel_all_nlm_blocked(void) { state_lock_entry_t *found_entry; state_block_data_t *pblock; struct req_op_context op_context; /* Initialize context */ init_op_context(&op_context, NULL, NULL, NULL, NULL, 0, 0, NFS_RELATED); LogDebug(COMPONENT_STATE, "Cancel all blocked locks"); PTHREAD_MUTEX_lock(&blocked_locks_mutex); pblock = glist_first_entry(&state_blocked_locks, state_block_data_t, sbd_list); if (pblock == NULL) { LogFullDebug(COMPONENT_STATE, "No blocked locks"); goto out; } while (pblock != NULL) { found_entry = pblock->sbd_lock_entry; /* Remove lock from blocked list */ glist_del(&pblock->sbd_list); lock_entry_inc_ref(found_entry); PTHREAD_MUTEX_unlock(&blocked_locks_mutex); get_gsh_export_ref(found_entry->sle_export); set_op_context_export(found_entry->sle_export); /** @todo also look at the LRU ref for pentry */ LogEntry("Blocked Lock found", found_entry); cancel_blocked_lock(found_entry->sle_obj, found_entry); gsh_free(pblock->sbd_blocked_cookie); gsh_free(found_entry->sle_block_data); found_entry->sle_block_data = NULL; LogEntry("Canceled Lock", found_entry); lock_entry_dec_ref(found_entry); clear_op_context_export(); PTHREAD_MUTEX_lock(&blocked_locks_mutex); /* Get next item off list */ pblock = glist_first_entry(&state_blocked_locks, state_block_data_t, sbd_list); } out: PTHREAD_MUTEX_unlock(&blocked_locks_mutex); release_op_context(); } /* Cleanup on shutdown */ void state_cleanup(void) { PTHREAD_MUTEX_destroy(&unknown_owner.so_mutex); #ifdef DEBUG_SAL PTHREAD_MUTEX_destroy(&all_locks_mutex); #endif PTHREAD_MUTEX_destroy(&blocked_locks_mutex); PTHREAD_MUTEX_destroy(&cached_open_owners_lock); #ifdef _USE_NLM PTHREAD_MUTEX_destroy(&granted_mutex); PTHREAD_MUTEX_destroy(&nlm_async_resp_mutex); PTHREAD_COND_destroy(&nlm_async_resp_cond); PTHREAD_MUTEX_destroy(&nsm_mutex); #endif #ifdef DEBUG_SAL PTHREAD_MUTEX_destroy(&all_state_owners_mutex); PTHREAD_MUTEX_destroy(&all_state_v4_mutex); #endif } struct cleanup_list_element state_cleanup_element = { .clean = state_cleanup, }; /** * @brief Initialize locking * * @return State status. */ state_status_t state_lock_init(void) { state_status_t status = STATE_SUCCESS; ht_lock_cookies = hashtable_init(&cookie_param); if (ht_lock_cookies == NULL) { LogCrit(COMPONENT_STATE, "Cannot init NLM Client cache"); status = STATE_INIT_ENTRY_FAILED; return status; } PTHREAD_MUTEX_init(&unknown_owner.so_mutex, NULL); #ifdef DEBUG_SAL PTHREAD_MUTEX_init(&all_locks_mutex, NULL); #endif PTHREAD_MUTEX_init(&blocked_locks_mutex, NULL); PTHREAD_MUTEX_init(&cached_open_owners_lock, NULL); #ifdef _USE_NLM PTHREAD_MUTEX_init(&granted_mutex, NULL); PTHREAD_MUTEX_init(&nlm_async_resp_mutex, NULL); PTHREAD_COND_init(&nlm_async_resp_cond, NULL); PTHREAD_MUTEX_init(&nsm_mutex, NULL); #endif #ifdef DEBUG_SAL PTHREAD_MUTEX_init(&all_state_owners_mutex, NULL); PTHREAD_MUTEX_init(&all_state_v4_mutex, NULL); #endif RegisterCleanup(&state_cleanup_element); status = state_async_init(); state_owner_pool = pool_basic_init("NFSv4 state owners", sizeof(state_owner_t)); return status; } /** @} */ nfs-ganesha-6.5/src/SAL/state_misc.c000066400000000000000000000746541473756622300173220ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file state_misc.c * @brief Misc exported routines for the state abstraction layer */ #include "config.h" #include #include #include #include #include #include #include #include #include "log.h" #include "hashtable.h" #include "fsal.h" #include "nfs_core.h" #include "sal_functions.h" #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/state.h" #endif struct glist_head cached_open_owners = GLIST_HEAD_INIT(cached_open_owners); pthread_mutex_t cached_open_owners_lock; pool_t *state_owner_pool; /*< Pool for NFSv4 files's open owner */ #ifdef DEBUG_SAL struct glist_head state_owners_all = GLIST_HEAD_INIT(state_owners_all); pthread_mutex_t all_state_owners_mutex; #endif /* Error conversion routines */ /** * @brief Get a string from an error code * * @param[in] err Error code * * @return Error string. */ const char *state_err_str(state_status_t err) { switch (err) { case STATE_SUCCESS: return "STATE_SUCCESS"; case STATE_MALLOC_ERROR: return "STATE_MALLOC_ERROR"; case STATE_POOL_MUTEX_INIT_ERROR: return "STATE_POOL_MUTEX_INIT_ERROR"; case STATE_GET_NEW_LRU_ENTRY: return "STATE_GET_NEW_LRU_ENTRY"; case STATE_INIT_ENTRY_FAILED: return "STATE_INIT_ENTRY_FAILED"; case STATE_FSAL_ERROR: return "STATE_FSAL_ERROR"; case STATE_LRU_ERROR: return "STATE_LRU_ERROR"; case STATE_HASH_SET_ERROR: return "STATE_HASH_SET_ERROR"; case STATE_NOT_A_DIRECTORY: return "STATE_NOT_A_DIRECTORY"; case STATE_INCONSISTENT_ENTRY: return "STATE_INCONSISTENT_ENTRY"; case STATE_BAD_TYPE: return "STATE_BAD_TYPE"; case STATE_ENTRY_EXISTS: return "STATE_ENTRY_EXISTS"; case STATE_DIR_NOT_EMPTY: return "STATE_DIR_NOT_EMPTY"; case STATE_NOT_FOUND: return "STATE_NOT_FOUND"; case STATE_INVALID_ARGUMENT: return "STATE_INVALID_ARGUMENT"; case STATE_INSERT_ERROR: return "STATE_INSERT_ERROR"; case STATE_HASH_TABLE_ERROR: return "STATE_HASH_TABLE_ERROR"; case STATE_FSAL_EACCESS: return "STATE_FSAL_EACCESS"; case STATE_IS_A_DIRECTORY: return "STATE_IS_A_DIRECTORY"; case STATE_FSAL_EPERM: return "STATE_FSAL_EPERM"; case STATE_NO_SPACE_LEFT: return "STATE_NO_SPACE_LEFT"; case STATE_READ_ONLY_FS: return "STATE_READ_ONLY_FS"; case STATE_IO_ERROR: return "STATE_IO_ERROR"; case STATE_ESTALE: return "STATE_ESTALE"; case STATE_FSAL_ERR_SEC: return "STATE_FSAL_ERR_SEC"; case STATE_QUOTA_EXCEEDED: return "STATE_QUOTA_EXCEEDED"; case STATE_ASYNC_POST_ERROR: return "STATE_ASYNC_POST_ERROR"; case STATE_NOT_SUPPORTED: return "STATE_NOT_SUPPORTED"; case STATE_STATE_ERROR: return "STATE_STATE_ERROR"; case STATE_FSAL_DELAY: return "STATE_FSAL_DELAY"; case STATE_NAME_TOO_LONG: return "STATE_NAME_TOO_LONG"; case STATE_LOCK_CONFLICT: return "STATE_LOCK_CONFLICT"; case STATE_LOCKED: return "STATE_LOCKED"; case STATE_LOCK_BLOCKED: return "STATE_LOCK_BLOCKED"; case STATE_LOCK_DEADLOCK: return "STATE_LOCK_DEADLOCK"; case STATE_BAD_COOKIE: return "STATE_BAD_COOKIE"; case STATE_FILE_BIG: return "STATE_FILE_BIG"; case STATE_GRACE_PERIOD: return "STATE_GRACE_PERIOD"; case STATE_SIGNAL_ERROR: return "STATE_SIGNAL_ERROR"; case STATE_FILE_OPEN: return "STATE_FILE_OPEN"; case STATE_SHARE_DENIED: return "STATE_SHARE_DENIED"; case STATE_MLINK: return "STATE_MLINK"; case STATE_SERVERFAULT: return "STATE_SERVERFAULT"; case STATE_TOOSMALL: return "STATE_TOOSMALL"; case STATE_XDEV: return "STATE_XDEV"; case STATE_IN_GRACE: return "STATE_IN_GRACE"; case STATE_BADHANDLE: return "STATE_BADHANDLE"; case STATE_BAD_RANGE: return "STATE_BAD_RANGE"; } return "unknown"; } /** * @brief converts an FSAL error to the corresponding state error. * * @param[in] fsal_status Fsal error to be converted * * @return State status. */ state_status_t state_error_convert(fsal_status_t fsal_status) { switch (fsal_status.major) { case ERR_FSAL_NO_ERROR: return STATE_SUCCESS; case ERR_FSAL_NOENT: return STATE_NOT_FOUND; case ERR_FSAL_DELAY: case ERR_FSAL_ACCESS: /* EDELAY and EACCESS are documented by fcntl as * indicating lock conflict */ return STATE_LOCK_CONFLICT; case ERR_FSAL_PERM: return STATE_FSAL_EPERM; case ERR_FSAL_NOSPC: return STATE_NO_SPACE_LEFT; case ERR_FSAL_ROFS: return STATE_READ_ONLY_FS; case ERR_FSAL_IO: case ERR_FSAL_NXIO: return STATE_IO_ERROR; case ERR_FSAL_STALE: case ERR_FSAL_FHEXPIRED: return STATE_ESTALE; case ERR_FSAL_INVAL: case ERR_FSAL_OVERFLOW: return STATE_INVALID_ARGUMENT; case ERR_FSAL_SEC: return STATE_FSAL_ERR_SEC; case ERR_FSAL_NOTSUPP: case ERR_FSAL_ATTRNOTSUPP: case ERR_FSAL_UNION_NOTSUPP: return STATE_NOT_SUPPORTED; case ERR_FSAL_NOMEM: return STATE_MALLOC_ERROR; case ERR_FSAL_DEADLOCK: return STATE_LOCK_DEADLOCK; case ERR_FSAL_BADCOOKIE: return STATE_BAD_COOKIE; case ERR_FSAL_NOT_OPENED: LogCrit(COMPONENT_STATE, "Conversion of ERR_FSAL_NOT_OPENED to STATE_FSAL_ERROR"); return STATE_FSAL_ERROR; case ERR_FSAL_SYMLINK: case ERR_FSAL_BADTYPE: return STATE_BAD_TYPE; case ERR_FSAL_ISDIR: return STATE_IS_A_DIRECTORY; case ERR_FSAL_FBIG: return STATE_FILE_BIG; case ERR_FSAL_FILE_OPEN: return STATE_FILE_OPEN; case ERR_FSAL_SHARE_DENIED: return STATE_SHARE_DENIED; case ERR_FSAL_BLOCKED: return STATE_LOCK_BLOCKED; case ERR_FSAL_IN_GRACE: return STATE_IN_GRACE; case ERR_FSAL_BADHANDLE: return STATE_BADHANDLE; case ERR_FSAL_BAD_RANGE: return STATE_BAD_RANGE; case ERR_FSAL_LOCKED: return STATE_LOCKED; case ERR_FSAL_TOOSMALL: return STATE_TOOSMALL; case ERR_FSAL_DQUOT: case ERR_FSAL_NAMETOOLONG: case ERR_FSAL_STILL_IN_USE: case ERR_FSAL_EXIST: case ERR_FSAL_NOTEMPTY: case ERR_FSAL_NOTDIR: case ERR_FSAL_INTERRUPT: case ERR_FSAL_FAULT: case ERR_FSAL_NOT_INIT: case ERR_FSAL_ALREADY_INIT: case ERR_FSAL_BAD_INIT: case ERR_FSAL_NO_QUOTA: case ERR_FSAL_XDEV: case ERR_FSAL_MLINK: case ERR_FSAL_TIMEOUT: case ERR_FSAL_SERVERFAULT: case ERR_FSAL_NO_DATA: case ERR_FSAL_NO_ACE: case ERR_FSAL_CROSS_JUNCTION: case ERR_FSAL_BADNAME: case ERR_FSAL_NOXATTR: case ERR_FSAL_XATTR2BIG: /* These errors should be handled inside state * (or should never be seen by state) */ LogDebug(COMPONENT_STATE, "Conversion of FSAL error %d,%d to STATE_FSAL_ERROR", fsal_status.major, fsal_status.minor); return STATE_FSAL_ERROR; } /* We should never reach this line, this may produce a warning with * certain compiler */ LogCrit(COMPONENT_STATE, "Default conversion to STATE_FSAL_ERROR for error %d, line %u should never be reached", fsal_status.major, __LINE__); return STATE_FSAL_ERROR; } /** * @brief Converts a state status to a nfsv4 status * * @param[in] error Input state error * * @return the converted NFSv4 status. * */ nfsstat4 nfs4_Errno_state(state_status_t error) { nfsstat4 nfserror = NFS4ERR_INVAL; switch (error) { case STATE_SUCCESS: nfserror = NFS4_OK; break; case STATE_MALLOC_ERROR: nfserror = NFS4ERR_RESOURCE; break; case STATE_POOL_MUTEX_INIT_ERROR: case STATE_GET_NEW_LRU_ENTRY: case STATE_INIT_ENTRY_FAILED: nfserror = NFS4ERR_SERVERFAULT; break; case STATE_BAD_TYPE: nfserror = NFS4ERR_INVAL; break; case STATE_NOT_A_DIRECTORY: nfserror = NFS4ERR_NOTDIR; break; case STATE_ENTRY_EXISTS: nfserror = NFS4ERR_EXIST; break; case STATE_DIR_NOT_EMPTY: nfserror = NFS4ERR_NOTEMPTY; break; case STATE_NOT_FOUND: nfserror = NFS4ERR_NOENT; break; case STATE_FSAL_ERROR: case STATE_INSERT_ERROR: case STATE_LRU_ERROR: case STATE_HASH_SET_ERROR: nfserror = NFS4ERR_IO; break; case STATE_FSAL_EACCESS: nfserror = NFS4ERR_ACCESS; break; case STATE_FSAL_EPERM: case STATE_FSAL_ERR_SEC: nfserror = NFS4ERR_PERM; break; case STATE_NO_SPACE_LEFT: nfserror = NFS4ERR_NOSPC; break; case STATE_IS_A_DIRECTORY: nfserror = NFS4ERR_ISDIR; break; case STATE_READ_ONLY_FS: nfserror = NFS4ERR_ROFS; break; case STATE_IO_ERROR: nfserror = NFS4ERR_IO; break; case STATE_FILE_OPEN: nfserror = NFS4ERR_FILE_OPEN; break; case STATE_NAME_TOO_LONG: nfserror = NFS4ERR_NAMETOOLONG; break; case STATE_ESTALE: nfserror = NFS4ERR_STALE; break; case STATE_SHARE_DENIED: nfserror = NFS4ERR_SHARE_DENIED; break; case STATE_LOCKED: nfserror = NFS4ERR_LOCKED; break; case STATE_QUOTA_EXCEEDED: nfserror = NFS4ERR_DQUOT; break; case STATE_NOT_SUPPORTED: nfserror = NFS4ERR_NOTSUPP; break; case STATE_FSAL_DELAY: nfserror = NFS4ERR_DELAY; break; case STATE_FILE_BIG: nfserror = NFS4ERR_FBIG; break; case STATE_LOCK_DEADLOCK: nfserror = NFS4ERR_DEADLOCK; break; case STATE_LOCK_BLOCKED: case STATE_LOCK_CONFLICT: nfserror = NFS4ERR_DENIED; break; case STATE_STATE_ERROR: nfserror = NFS4ERR_BAD_STATEID; break; case STATE_BAD_COOKIE: nfserror = NFS4ERR_BAD_COOKIE; break; case STATE_GRACE_PERIOD: nfserror = NFS4ERR_GRACE; break; case STATE_SERVERFAULT: nfserror = NFS4ERR_SERVERFAULT; break; case STATE_MLINK: nfserror = NFS4ERR_MLINK; break; case STATE_TOOSMALL: nfserror = NFS4ERR_TOOSMALL; break; case STATE_IN_GRACE: nfserror = NFS4ERR_GRACE; break; case STATE_XDEV: nfserror = NFS4ERR_XDEV; break; case STATE_BADHANDLE: nfserror = NFS4ERR_BADHANDLE; break; case STATE_BAD_RANGE: nfserror = NFS4ERR_BAD_RANGE; break; case STATE_INVALID_ARGUMENT: case STATE_INCONSISTENT_ENTRY: case STATE_HASH_TABLE_ERROR: case STATE_ASYNC_POST_ERROR: case STATE_SIGNAL_ERROR: /* Should not occur */ nfserror = NFS4ERR_INVAL; break; } return nfserror; } /** * @brief Converts a state status to an NFSv3 status * * @param[in] error State error * * @return Converted NFSv3 status. */ nfsstat3 nfs3_Errno_state(state_status_t error) { nfsstat3 nfserror = NFS3ERR_INVAL; switch (error) { case STATE_SUCCESS: nfserror = NFS3_OK; break; case STATE_MALLOC_ERROR: case STATE_POOL_MUTEX_INIT_ERROR: case STATE_GET_NEW_LRU_ENTRY: case STATE_INIT_ENTRY_FAILED: case STATE_INSERT_ERROR: case STATE_LRU_ERROR: case STATE_HASH_SET_ERROR: case STATE_FILE_OPEN: LogCrit(COMPONENT_NFSPROTO, "Error %u converted to NFS3ERR_IO but was set non-retryable", error); nfserror = NFS3ERR_IO; break; case STATE_INVALID_ARGUMENT: nfserror = NFS3ERR_INVAL; break; case STATE_FSAL_ERROR: /** @todo: Check if this works by making stress tests */ LogCrit(COMPONENT_NFSPROTO, "Error STATE_FSAL_ERROR converted to NFS3ERR_IO but was set non-retryable"); nfserror = NFS3ERR_IO; break; case STATE_NOT_A_DIRECTORY: nfserror = NFS3ERR_NOTDIR; break; case STATE_ENTRY_EXISTS: nfserror = NFS3ERR_EXIST; break; case STATE_DIR_NOT_EMPTY: nfserror = NFS3ERR_NOTEMPTY; break; case STATE_NOT_FOUND: nfserror = NFS3ERR_NOENT; break; case STATE_FSAL_EACCESS: nfserror = NFS3ERR_ACCES; break; case STATE_LOCKED: case STATE_FSAL_EPERM: case STATE_FSAL_ERR_SEC: nfserror = NFS3ERR_PERM; break; case STATE_NO_SPACE_LEFT: nfserror = NFS3ERR_NOSPC; break; case STATE_IS_A_DIRECTORY: nfserror = NFS3ERR_ISDIR; break; case STATE_READ_ONLY_FS: nfserror = NFS3ERR_ROFS; break; case STATE_ESTALE: nfserror = NFS3ERR_STALE; break; case STATE_QUOTA_EXCEEDED: nfserror = NFS3ERR_DQUOT; break; case STATE_BAD_TYPE: nfserror = NFS3ERR_BADTYPE; break; case STATE_NOT_SUPPORTED: nfserror = NFS3ERR_NOTSUPP; break; case STATE_FSAL_DELAY: case STATE_SHARE_DENIED: nfserror = NFS3ERR_JUKEBOX; break; case STATE_IO_ERROR: LogCrit(COMPONENT_NFSPROTO, "Error STATE_IO_ERROR converted to NFS3ERR_IO but was set non-retryable"); nfserror = NFS3ERR_IO; break; case STATE_NAME_TOO_LONG: nfserror = NFS3ERR_NAMETOOLONG; break; case STATE_FILE_BIG: nfserror = NFS3ERR_FBIG; break; case STATE_BAD_COOKIE: nfserror = NFS3ERR_BAD_COOKIE; break; case STATE_MLINK: nfserror = NFS3ERR_MLINK; break; case STATE_SERVERFAULT: nfserror = NFS3ERR_SERVERFAULT; break; case STATE_TOOSMALL: nfserror = NFS3ERR_TOOSMALL; break; case STATE_XDEV: nfserror = NFS3ERR_XDEV; break; case STATE_IN_GRACE: nfserror = NFS3ERR_JUKEBOX; break; case STATE_BADHANDLE: nfserror = NFS3ERR_BADHANDLE; break; case STATE_INCONSISTENT_ENTRY: case STATE_HASH_TABLE_ERROR: case STATE_ASYNC_POST_ERROR: case STATE_STATE_ERROR: case STATE_LOCK_CONFLICT: case STATE_LOCK_BLOCKED: case STATE_LOCK_DEADLOCK: case STATE_GRACE_PERIOD: case STATE_SIGNAL_ERROR: case STATE_BAD_RANGE: /* Should not occur */ LogCrit(COMPONENT_NFSPROTO, "Unexpected status for conversion = %s", state_err_str(error)); nfserror = NFS3ERR_INVAL; break; } return nfserror; } bool state_unlock_err_ok(state_status_t status) { return status == STATE_SUCCESS || status == STATE_ESTALE; } /** String for undefined state owner types */ const char *invalid_state_owner_type = "INVALID STATE OWNER TYPE"; /** * @brief Return a string describing a state owner type * * @param[in] type The state owner type * * @return The string representation of the type. */ const char *state_owner_type_to_str(state_owner_type_t type) { switch (type) { case STATE_LOCK_OWNER_UNKNOWN: return "STATE_LOCK_OWNER_UNKNOWN"; #ifdef _USE_NLM case STATE_LOCK_OWNER_NLM: return "STATE_LOCK_OWNER_NLM"; #endif /* _USE_NLM */ #ifdef _USE_9P case STATE_LOCK_OWNER_9P: return "STALE_LOCK_OWNER_9P"; #endif case STATE_OPEN_OWNER_NFSV4: return "STATE_OPEN_OWNER_NFSV4"; case STATE_LOCK_OWNER_NFSV4: return "STATE_LOCK_OWNER_NFSV4"; case STATE_CLIENTID_OWNER_NFSV4: return "STATE_CLIENTID_OWNER_NFSV4"; } return invalid_state_owner_type; } /** * @brief See if two owners differ * * @param[in] owner1 An owner * @param[in] owner2 Another owner * * @retval true if owners differ. * @retval false if owners are the same. */ bool different_owners(state_owner_t *owner1, state_owner_t *owner2) { if (owner1 == NULL || owner2 == NULL) return true; /* Shortcut if we actually are pointing to the same owner structure */ if (owner1 == owner2) return false; if (owner1->so_type != owner2->so_type) return true; switch (owner1->so_type) { #ifdef _USE_NLM case STATE_LOCK_OWNER_NLM: return compare_nlm_owner(owner1, owner2); #endif /* _USE_NLM */ #ifdef _USE_9P case STATE_LOCK_OWNER_9P: return compare_9p_owner(owner1, owner2); #endif case STATE_OPEN_OWNER_NFSV4: case STATE_LOCK_OWNER_NFSV4: case STATE_CLIENTID_OWNER_NFSV4: return compare_nfs4_owner(owner1, owner2); case STATE_LOCK_OWNER_UNKNOWN: break; } return true; } /** * @brief Display a state owner * * @param[in/out] dspbuf display_buffer describing output string * @param[in] owner Owner to display * * @return the bytes remaining in the buffer. */ int display_owner(struct display_buffer *dspbuf, state_owner_t *owner) { if (owner == NULL) return display_printf(dspbuf, ""); switch (owner->so_type) { #ifdef _USE_NLM case STATE_LOCK_OWNER_NLM: return display_nlm_owner(dspbuf, owner); #endif /* _USE_NLM */ #ifdef _USE_9P case STATE_LOCK_OWNER_9P: return display_9p_owner(dspbuf, owner); #endif case STATE_OPEN_OWNER_NFSV4: case STATE_LOCK_OWNER_NFSV4: case STATE_CLIENTID_OWNER_NFSV4: return display_nfs4_owner(dspbuf, owner); case STATE_LOCK_OWNER_UNKNOWN: return display_printf( dspbuf, "%s powner=%p: so_refcount=%d", state_owner_type_to_str(owner->so_type), owner, atomic_fetch_int32_t(&owner->so_refcount)); } return display_printf(dspbuf, "%s powner=%p", invalid_state_owner_type, owner); } /** * @brief Acquire a reference on a state owner * * @param[in] owner The owner to acquire */ void inc_state_owner_ref(state_owner_t *owner) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; int32_t refcount; if (isFullDebug(COMPONENT_STATE)) { display_owner(&dspbuf, owner); str_valid = true; } refcount = atomic_inc_int32_t(&owner->so_refcount); if (str_valid) LogFullDebug(COMPONENT_STATE, "Increment so_refcount now=%" PRId32 " {%s}", refcount, str); GSH_AUTO_TRACEPOINT(state, incref, TRACE_INFO, "State ({}) incref. Refcount={}", owner, refcount); } /** * @brief Free a state owner object * * @param[in] owner The owner to free */ void free_state_owner(state_owner_t *owner) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; GSH_AUTO_TRACEPOINT(state, free, TRACE_INFO, "State ({}) free. Type={}", owner, owner->so_type); switch (owner->so_type) { #ifdef _USE_NLM case STATE_LOCK_OWNER_NLM: free_nlm_owner(owner); break; #endif /* _USE_NLM */ #ifdef _USE_9P case STATE_LOCK_OWNER_9P: break; #endif case STATE_OPEN_OWNER_NFSV4: case STATE_LOCK_OWNER_NFSV4: case STATE_CLIENTID_OWNER_NFSV4: free_nfs4_owner(owner); break; case STATE_LOCK_OWNER_UNKNOWN: display_owner(&dspbuf, owner); LogCrit(COMPONENT_STATE, "Unexpected removal of {%s}", str); return; } gsh_free(owner->so_owner_val); PTHREAD_MUTEX_destroy(&owner->so_mutex); #ifdef DEBUG_SAL PTHREAD_MUTEX_lock(&all_state_owners_mutex); glist_del(&owner->so_all_owners); PTHREAD_MUTEX_unlock(&all_state_owners_mutex); #endif pool_free(state_owner_pool, owner); } /** * @brief Get the hash table associated with a state owner object * * @param[in] owner The owner to get associated hash table for */ hash_table_t *get_state_owner_hash_table(state_owner_t *owner) { switch (owner->so_type) { #ifdef _USE_NLM case STATE_LOCK_OWNER_NLM: return ht_nlm_owner; #endif /* _USE_NLM */ #ifdef _USE_9P case STATE_LOCK_OWNER_9P: return ht_9p_owner; #endif case STATE_OPEN_OWNER_NFSV4: case STATE_LOCK_OWNER_NFSV4: case STATE_CLIENTID_OWNER_NFSV4: return ht_nfs4_owner; case STATE_LOCK_OWNER_UNKNOWN: break; } return NULL; } /** * @brief Relinquish a reference on a state owner * * @param[in] owner The owner to release */ void dec_state_owner_ref(state_owner_t *owner) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; struct hash_latch latch; hash_error_t rc; struct gsh_buffdesc buffkey; struct gsh_buffdesc old_value; int32_t refcount; hash_table_t *ht_owner; if (isDebug(COMPONENT_STATE)) { display_owner(&dspbuf, owner); str_valid = true; } refcount = atomic_dec_int32_t(&owner->so_refcount); GSH_AUTO_TRACEPOINT(state, decref, TRACE_INFO, "State ({}) decref. Refcount={}", owner, refcount); if (refcount != 0) { if (str_valid) LogFullDebug(COMPONENT_STATE, "Decrement so_refcount now=%d {%s}", refcount, str); assert(refcount > 0); return; } ht_owner = get_state_owner_hash_table(owner); if (ht_owner == NULL) { if (!str_valid) display_printf(&dspbuf, "Invalid owner %p", owner); LogCrit(COMPONENT_STATE, "Unexpected owner {%s}, type {%d}", str, owner->so_type); return; } buffkey.addr = owner; buffkey.len = sizeof(*owner); /* Since the refcount is zero, another thread that needs this * owner might have deleted ours, so expect not to find one or * find someone else's owner! */ rc = hashtable_getlatch(ht_owner, &buffkey, &old_value, true, &latch); switch (rc) { case HASHTABLE_SUCCESS: /* If ours, delete from hash table */ if (old_value.addr == owner) { hashtable_deletelatched(ht_owner, &buffkey, &latch, NULL, NULL); } break; case HASHTABLE_ERROR_NO_SUCH_KEY: break; default: if (!str_valid) display_printf(&dspbuf, "Invalid owner %p", owner); LogCrit(COMPONENT_STATE, "Error %s, could not find {%s}", hash_table_err_to_str(rc), str); return; } /* Release the latch */ hashtable_releaselatched(ht_owner, &latch); if (str_valid) LogFullDebug(COMPONENT_STATE, "Free {%s}", str); free_state_owner(owner); } /** @brief Remove an NFS 4 open owner from the cached owners list. * * The caller MUST hold the cached_open_owners_lock, also must NOT hold * so_mutex as the so_mutex may get destroyed after this call. * * If this owner is being revived, the refcount should have already been * incremented for the new primary reference. This function will release the * refcount that held it in the cache. * * @param[in] nfs4_owner The owner to release. * */ void uncache_nfs4_owner(struct state_nfs4_owner_t *nfs4_owner) { state_owner_t *owner = container_of(nfs4_owner, state_owner_t, so_owner.so_nfs4_owner); /* This owner is to be removed from the open owner cache: * 1. Remove it from the list. * 2. Make sure this is now a proper list head again. * 3. Indicate it is no longer cached. * 4. Release the reference held on behalf of the cache. */ if (isFullDebug(COMPONENT_STATE)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; display_owner(&dspbuf, owner); LogFullDebug(COMPONENT_STATE, "Uncache {%s}", str); } /* Remove owner from cached_open_owners */ glist_del(&nfs4_owner->so_cache_entry); atomic_store_time_t(&nfs4_owner->so_cache_expire, 0); dec_state_owner_ref(owner); } static inline void refresh_nfs4_open_owner(struct state_nfs4_owner_t *nfs4_owner) { time_t cache_expire; /* Since this owner is active, reset cache_expire. */ cache_expire = atomic_fetch_time_t(&nfs4_owner->so_cache_expire); if (cache_expire != 0) { PTHREAD_MUTEX_lock(&cached_open_owners_lock); /* Check again while holding the mutex. */ if (atomic_fetch_time_t(&nfs4_owner->so_cache_expire) != 0) { /* This is a cached open owner, uncache it for use. */ uncache_nfs4_owner(nfs4_owner); } PTHREAD_MUTEX_unlock(&cached_open_owners_lock); } } /** * @brief Get a state owner * * @param[in] care indicates how we care about the owner * @param[in] key the owner key we are searching for * @param[in] init_owner routine to initialize a new owner * @param[in,out] isnew pointer to flag indicating a new owner was created * * @return the owner found or NULL if no owner was found or created */ state_owner_t *get_state_owner(care_t care, state_owner_t *key, state_owner_init_t init_owner, bool_t *isnew) { state_owner_t *owner; char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; struct hash_latch latch; hash_error_t rc; struct gsh_buffdesc buffkey; struct gsh_buffdesc buffval; hash_table_t *ht_owner; int32_t refcount; if (isnew != NULL) *isnew = false; if (isFullDebug(COMPONENT_STATE)) { display_owner(&dspbuf, key); LogFullDebug(COMPONENT_STATE, "Find {%s}", str); str_valid = true; } ht_owner = get_state_owner_hash_table(key); if (ht_owner == NULL) { if (!str_valid) display_owner(&dspbuf, key); LogCrit(COMPONENT_STATE, "ht=%p Unexpected key {%s}", ht_owner, str); return NULL; } buffkey.addr = key; buffkey.len = sizeof(*key); rc = hashtable_getlatch(ht_owner, &buffkey, &buffval, true, &latch); switch (rc) { case HASHTABLE_SUCCESS: owner = buffval.addr; refcount = atomic_inc_unless_0_int32_t(&owner->so_refcount); if (refcount == 0) { /* This owner is in the process of getting freed. * Delete from the hash table and pretend as * though we didn't find it! */ hashtable_deletelatched(ht_owner, &buffkey, &latch, NULL, NULL); goto not_found; } /* Return the found NSM Client */ if (isFullDebug(COMPONENT_STATE)) { display_owner(&dspbuf, owner); str_valid = true; } hashtable_releaselatched(ht_owner, &latch); /* Refresh an nfs4 open owner if needed. */ if (owner->so_type == STATE_OPEN_OWNER_NFSV4) { refresh_nfs4_open_owner(&owner->so_owner.so_nfs4_owner); } if (isFullDebug(COMPONENT_STATE)) { if (!str_valid) display_owner(&dspbuf, owner); LogFullDebug(COMPONENT_STATE, "Found {%s} so_refcount now=%" PRId32, str, refcount); } return owner; case HASHTABLE_ERROR_NO_SUCH_KEY: goto not_found; default: if (!str_valid) display_owner(&dspbuf, key); LogCrit(COMPONENT_STATE, "Error %s, could not find {%s}", hash_table_err_to_str(rc), str); return NULL; } not_found: /* Not found, but we don't care, return NULL */ if (care == CARE_NOT) { if (str_valid) LogFullDebug(COMPONENT_STATE, "Ignoring {%s}", str); hashtable_releaselatched(ht_owner, &latch); return NULL; } owner = pool_alloc(state_owner_pool); /* Copy everything over */ memcpy(owner, key, sizeof(*key)); PTHREAD_MUTEX_init(&owner->so_mutex, NULL); #ifdef DEBUG_SAL PTHREAD_MUTEX_lock(&all_state_owners_mutex); glist_add_tail(&state_owners_all, &owner->so_all_owners); PTHREAD_MUTEX_unlock(&all_state_owners_mutex); #endif /* Do any owner type specific initialization */ if (init_owner != NULL) init_owner(owner); if (key->so_owner_len != 0) { owner->so_owner_val = gsh_malloc(key->so_owner_len); memcpy(owner->so_owner_val, key->so_owner_val, key->so_owner_len); } glist_init(&owner->so_lock_list); if (isFullDebug(COMPONENT_STATE)) { display_reset_buffer(&dspbuf); display_owner(&dspbuf, owner); LogFullDebug(COMPONENT_STATE, "New {%s}", str); str_valid = true; } else { /* If we had the key, we don't want it anymore */ str_valid = false; } buffkey.addr = owner; buffkey.len = sizeof(*owner); buffval.addr = owner; buffval.len = sizeof(*owner); rc = hashtable_setlatched(ht_owner, &buffval, &buffval, &latch, false, NULL, NULL); /* An error occurred, return NULL */ if (rc != HASHTABLE_SUCCESS) { if (!str_valid) display_owner(&dspbuf, owner); LogCrit(COMPONENT_STATE, "Error %s, inserting {%s}", hash_table_err_to_str(rc), str); free_state_owner(owner); return NULL; } if (isnew != NULL) *isnew = true; return owner; } /** * @brief Acquire a reference on a state owner that might be getting freed. * * @param[in] owner The owner to acquire * * inc_state_owner_ref() can be called to hold a reference provided * someone already has a valid refcount. In other words, one can't call * inc_state_owner_ref on a owner whose refcount has possibly gone to * zero so we use atomic_inc_unless_0_int32_t which will not increment * the refcount if it is 0. Since that returns either 0, or the new refcount, * we can easily detect the case where a reference was NOT taken. * * @return true if we are able to hold a reference, false otherwise. */ bool hold_state_owner_ref(state_owner_t *owner) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; bool str_valid = false; int32_t refcount; if (isFullDebug(COMPONENT_STATE)) { display_owner(&dspbuf, owner); str_valid = true; } refcount = atomic_inc_unless_0_int32_t(&owner->so_refcount); GSH_AUTO_TRACEPOINT(state, hold_state, TRACE_INFO, "State ({}) hold. Refcount={}", owner, refcount); if (str_valid) { if (refcount == 0) { LogFullDebug( COMPONENT_STATE, "Did not increment so_refcount from 0 {%s}", str); } else { LogFullDebug(COMPONENT_STATE, "Increment so_refcount now=%d {%s}", refcount, str); } } return refcount != 0; } /** * @brief Release all state on a file * * This function may not be called in any context which could hold * entry->st_lock. It will now be reliably called in cleanup * processing. * * @param[in,out] obj File to be wiped */ void state_wipe_file(struct fsal_obj_handle *obj) { /* * currently, only REGULAR files can have state; byte range locks and * stateid (for v4). In the future, 4.1, directories could have * delegations, which is state. At that point, we may need to modify * this routine to clear state on directories. */ if (obj->type != REGULAR_FILE) return; STATELOCK_lock(obj); state_lock_wipe(obj->state_hdl); #ifdef _USE_NLM state_share_wipe(obj->state_hdl); #endif /* _USE_NLM */ state_nfs4_state_wipe(obj->state_hdl); STATELOCK_unlock(obj); #ifdef DEBUG_SAL dump_all_states(); #endif } #ifdef DEBUG_SAL void dump_all_owners(void) { if (!isFullDebug(COMPONENT_STATE)) return; PTHREAD_MUTEX_lock(&all_state_owners_mutex); if (!glist_empty(&state_owners_all)) { char str[LOG_BUFF_LEN] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; struct glist_head *glist; LogFullDebug( COMPONENT_STATE, " ---------------------- State Owner List ----------------------"); glist_for_each(glist, &state_owners_all) { display_reset_buffer(&dspbuf); display_owner(&dspbuf, glist_entry(glist, state_owner_t, so_all_owners)); LogFullDebug(COMPONENT_STATE, "{%s}", str); } LogFullDebug(COMPONENT_STATE, " ----------------------"); } else LogFullDebug(COMPONENT_STATE, "All state owners released"); PTHREAD_MUTEX_unlock(&all_state_owners_mutex); } #endif /** * @brief Release all the state belonging to an export. * * @param[in] exp The export to release state for. * */ void state_release_export(struct gsh_export *export) { struct req_op_context op_context; GSH_AUTO_TRACEPOINT(state, release_export, TRACE_INFO, "Release all export states for export_id={}", export->export_id); /* Get a ref to the export and initialize op_context */ get_gsh_export_ref(export); init_op_context_simple(&op_context, export, export->fsal_export); state_export_unlock_all(); state_export_release_nfs4_state(); #ifdef _USE_NLM state_export_unshare_all(); #endif /* _USE_NLM */ release_op_context(); } /** * @brief Gets a unique server id to be used for base of client id * the value should be diffrent from old and diffrent instances of ganesha * * @return unique server id. */ uint64_t get_unique_server_id(void) { if (nfs_param.core_param.unique_server_id != 0) return nfs_param.core_param.unique_server_id; return nfs_ServerEpoch & UINT32_MAX; } /** @} */ nfs-ganesha-6.5/src/SAL/state_share.c000066400000000000000000000272401473756622300174560ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file state_share.c * @brief Share reservation management */ #include "config.h" #include #include #include #include #include #include #include #include "fsal.h" #include "nfs_core.h" #include "nfs4.h" #include "sal_functions.h" /*#include "nlm_util.h"*/ #include "export_mgr.h" #ifdef _USE_NLM /** * @brief Remove an NLM share * * @param[in] state The state_t describing the share to remove * */ void remove_nlm_share(state_t *state) { state_owner_t *owner = state->state_owner; state_nlm_client_t *client = owner->so_owner.so_nlm_owner.so_client; /* Remove from share list for export */ PTHREAD_RWLOCK_wrlock(&op_ctx->ctx_export->exp_lock); glist_del(&state->state_export_list); PTHREAD_RWLOCK_unlock(&op_ctx->ctx_export->exp_lock); /* Remove the share from the list for the file. */ glist_del(&state->state_list); /* Remove the share from the NSM Client list */ PTHREAD_MUTEX_lock(&client->slc_nsm_client->ssc_mutex); glist_del(&state->state_data.nlm_share.share_perclient); PTHREAD_MUTEX_unlock(&client->slc_nsm_client->ssc_mutex); dec_nsm_client_ref(client->slc_nsm_client); /* Remove the share from the NLM Owner list */ PTHREAD_MUTEX_lock(&owner->so_mutex); glist_del(&state->state_owner_list); PTHREAD_MUTEX_unlock(&owner->so_mutex); /* Release the state_t reference for active share. If extended FSAL * operations are supported, this will close the file when the last * reference is released. */ dec_state_t_ref(state); } /** * @brief Implement NLM share call with FSAL extended ops * * @param[in,out] obj File on which to operate * @param[in] export Export through which file is accessed * @param[in] share_access Share mode requested * @param[in] share_deny Deny mode requested * @param[in] owner Share owner * @param[in] state state_t to manage the share * @param[in] reclaim Indicates if this is a reclaim * @param[in] unshare Indicates if this was an unshare * * @return State status. */ state_status_t state_nlm_share(struct fsal_obj_handle *obj, int share_access, int share_deny, state_owner_t *owner, state_t *state, bool reclaim, bool unshare) { fsal_status_t fsal_status = { 0, 0 }; fsal_openflags_t openflags = 0; state_nlm_client_t *client = owner->so_owner.so_nlm_owner.so_client; unsigned int old_access; unsigned int old_deny; unsigned int new_access = 0; unsigned int new_deny = 0; struct state_nlm_share *nlm_share = &state->state_data.nlm_share; int i, acount = 0, dcount = 0; fsal_accessflags_t access_mask = 0; STATELOCK_lock(obj); old_access = nlm_share->share_access; old_deny = nlm_share->share_deny; LogFullDebugAlt(COMPONENT_STATE, COMPONENT_NLM, "%s access %d, deny %d", unshare ? "UNSHARE" : "SHARE", share_access, share_deny); if (unshare) { /* For FREE_ALL case need to release all shares */ if (share_access == OPEN4_SHARE_ACCESS_ALL) for (i = 0; i <= fsa_RW; i++) nlm_share->share_access_counts[i] = 0; else if (nlm_share->share_access_counts[share_access] > 0) nlm_share->share_access_counts[share_access]--; else LogDebugAlt(COMPONENT_STATE, COMPONENT_NLM, "UNSHARE access %d did not match", share_access); if (share_deny == OPEN4_SHARE_DENY_ALL) for (i = 0; i <= fsm_DRW; i++) nlm_share->share_deny_counts[i] = 0; else if (nlm_share->share_deny_counts[share_deny] > 0) nlm_share->share_deny_counts[share_deny]--; else LogDebugAlt(COMPONENT_STATE, COMPONENT_NLM, "UNSHARE deny %d did not match", share_deny); } else { nlm_share->share_access_counts[share_access]++; nlm_share->share_deny_counts[share_deny]++; } /* Compute new share_access as union of all remaining shares. */ for (i = 0; i <= fsa_RW; i++) { if (nlm_share->share_access_counts[i] != 0) { new_access |= i; acount += nlm_share->share_access_counts[i]; } } /* Compute new share_deny as union of all remaining shares. */ for (i = 0; i <= fsm_DRW; i++) { if (nlm_share->share_deny_counts[i] != 0) { new_deny |= i; dcount += nlm_share->share_deny_counts[i]; } } if (share_access == OPEN4_SHARE_ACCESS_ALL || share_deny == OPEN4_SHARE_DENY_ALL) { LogFullDebugAlt( COMPONENT_STATE, COMPONENT_NLM, "%s share access total = %d, share deny total = %d", unshare ? "UNSHARE" : "SHARE", acount, dcount); } else { LogFullDebugAlt( COMPONENT_STATE, COMPONENT_NLM, "%s share_access_counts[%d] = %d, total = %d, share_deny_counts[%d] = %d, total = %d", unshare ? "UNSHARE" : "SHARE", share_access, nlm_share->share_access_counts[share_access], acount, share_deny, nlm_share->share_deny_counts[share_deny], dcount); } if (new_access == old_access && new_deny == old_deny) { /* The share or unshare did not affect the union of shares so * there is no more work to do. */ LogFullDebugAlt( COMPONENT_STATE, COMPONENT_NLM, "%s union share did not change from access %d, deny %d", unshare ? "UNSHARE" : "SHARE", old_access, old_deny); goto out_unlock; } /* Assume new access/deny is update in determining the openflags. */ if ((new_access & fsa_R) != 0) openflags |= FSAL_O_READ; if ((new_access & fsa_W) != 0) openflags |= FSAL_O_WRITE; if (openflags == FSAL_O_CLOSED && unshare) { /* This unshare is removing the final share. The file will be * closed when the final reference to the state is released. */ LogFullDebugAlt( COMPONENT_STATE, COMPONENT_NLM, "UNSHARE removed state_t %p, share_access %u, share_deny %u", state, old_access, old_deny); remove_nlm_share(state); goto out_unlock; } if (openflags == FSAL_O_CLOSED) { LogFullDebugAlt( COMPONENT_STATE, COMPONENT_NLM, "SHARE with access none, deny %d and file is not already open, modify to read", share_deny); openflags |= FSAL_O_READ; } if ((new_deny & fsm_DR) != 0) openflags |= FSAL_O_DENY_READ; if ((new_deny & fsm_DW) != 0) openflags |= FSAL_O_DENY_WRITE; if (reclaim) openflags |= FSAL_O_RECLAIM; if (openflags & FSAL_O_READ) access_mask |= FSAL_READ_ACCESS; if (openflags & FSAL_O_WRITE) access_mask |= FSAL_WRITE_ACCESS; /* The access check must be same as the read and write calls. * Use owner_skip for the access checks * The reopen2 call does not use the owner_skip * perform test_access first and then call reopen2. */ fsal_status = obj->obj_ops->test_access(obj, access_mask, NULL, NULL, true); if (FSAL_IS_ERROR(fsal_status)) { LogDebug(COMPONENT_STATE, "test_access failed with %s", fsal_err_txt(fsal_status)); goto out_unlock; } /* Use reopen2 to open or re-open the file and check for share * conflict. */ fsal_status = fsal_reopen2(obj, state, openflags, false); if (FSAL_IS_ERROR(fsal_status)) { LogDebugAlt(COMPONENT_STATE, COMPONENT_NLM, "fsal_reopen2 failed with %s", fsal_err_txt(fsal_status)); goto out_unlock; } else { LogFullDebugAlt(COMPONENT_STATE, COMPONENT_NLM, "fsal_reopen2 succeeded"); } /* If we already had a share, skip all the book keeping. */ if (old_access != OPEN4_SHARE_ACCESS_NONE) { LogFullDebugAlt( COMPONENT_STATE, COMPONENT_NLM, "%s updated state_t %p, share_access %u, share_deny %u", unshare ? "UNSHARE" : "SHARE", state, new_access, new_deny); goto update; } /* Take a reference on the state_t. */ inc_state_t_ref(state); /* Add share to list for NLM Owner */ PTHREAD_MUTEX_lock(&owner->so_mutex); glist_add_tail(&owner->so_owner.so_nlm_owner.so_nlm_shares, &state->state_owner_list); PTHREAD_MUTEX_unlock(&owner->so_mutex); /* Add share to list for NSM Client */ inc_nsm_client_ref(client->slc_nsm_client); PTHREAD_MUTEX_lock(&client->slc_nsm_client->ssc_mutex); glist_add_tail(&client->slc_nsm_client->ssc_share_list, &nlm_share->share_perclient); PTHREAD_MUTEX_unlock(&client->slc_nsm_client->ssc_mutex); /* Add share to list for file. */ glist_add_tail(&obj->state_hdl->file.nlm_share_list, &state->state_list); /* Add to share list for export */ PTHREAD_RWLOCK_wrlock(&op_ctx->ctx_export->exp_lock); glist_add_tail(&op_ctx->ctx_export->exp_nlm_share_list, &state->state_export_list); PTHREAD_RWLOCK_unlock(&op_ctx->ctx_export->exp_lock); LogFullDebugAlt( COMPONENT_STATE, COMPONENT_NLM, "SHARE added state_t %p, share_access %u, share_deny %u", state, new_access, new_deny); update: /* Update the current share type */ nlm_share->share_access = new_access; nlm_share->share_deny = new_deny; out_unlock: STATELOCK_unlock(obj); return state_error_convert(fsal_status); } /** * @brief Remove all share state from a file * * @param[in] obj File to wipe */ void state_share_wipe(struct state_hdl *hstate) { state_t *state; struct glist_head *glist; struct glist_head *glistn; glist_for_each_safe(glist, glistn, &hstate->file.nlm_share_list) { state = glist_entry(glist, state_t, state_list); remove_nlm_share(state); } } void state_export_unshare_all(void) { int errcnt = 0; state_t *state; state_owner_t *owner; struct fsal_obj_handle *obj; state_status_t status; while (errcnt < STATE_ERR_MAX) { PTHREAD_RWLOCK_wrlock(&op_ctx->ctx_export->exp_lock); state = glist_first_entry( &op_ctx->ctx_export->exp_nlm_share_list, state_t, state_export_list); if (state == NULL) { PTHREAD_RWLOCK_unlock(&op_ctx->ctx_export->exp_lock); break; } obj = get_state_obj_ref(state); if (obj == NULL) { LogDebugAlt(COMPONENT_STATE, COMPONENT_NLM, "Entry for state is stale"); PTHREAD_RWLOCK_unlock(&op_ctx->ctx_export->exp_lock); break; } owner = state->state_owner; /* Get a reference to the state_t */ inc_state_t_ref(state); /* get a reference to the owner */ inc_state_owner_ref(owner); /* Drop the export mutex to call unshare */ PTHREAD_RWLOCK_unlock(&op_ctx->ctx_export->exp_lock); /* Remove all shares held by this Owner on this export */ status = state_nlm_share(obj, OPEN4_SHARE_ACCESS_ALL, OPEN4_SHARE_DENY_ALL, owner, state, false, true); /* Release references taken above. Should free the state_t. */ dec_state_owner_ref(owner); obj->obj_ops->put_ref(obj); dec_state_t_ref(state); if (!state_unlock_err_ok(status)) { /* Increment the error count and try the next share, * with any luck the memory pressure which is causing * the problem will resolve itself. */ LogCrit(COMPONENT_STATE, "state_unlock failed %s", state_err_str(status)); errcnt++; } } if (errcnt == STATE_ERR_MAX) { LogFatal(COMPONENT_STATE, "Could not complete cleanup of NLM shares for %s", ctx_export_path(op_ctx)); } } #endif /* _USE_NLM */ /** @} */ nfs-ganesha-6.5/src/THANKS000066400000000000000000000031421473756622300152770ustar00rootroot00000000000000This files list the people, outside of the original development team that help in the project. Their contributions were very helpful, so I guess they should have their names listed somewhere. Thanks to: Contributors that are not colleagues or students - Tom "spot" Callaway , for his experience and wiseness in RPM packaging. - People from mailing-list nfsv4@linux-nfs.org (with special attention to J. Bruce Fields and Trond Myklebust) for their help understanding the complex logic within NFSv4 - Eric Sesterhenn who provided a nice patch fixing memleaks. - Erik Levinson who did lots of work to make libnfsidmap work with gssrpc in nfs-ganesha - Sean Dague who provides me with several typos and memleaks patches - Aneesh Kumar K. V. who implemented NLMv4 support (locking for NFSv3) - Frank Filz who provided me with several small fixes on the server's behavior. He designed and wrote as well the new Log layer API. Students that came to make an internship with us and worked on the project - Mickael Guerin who wrote the first release of the FSAL_POSIX - Laureline Provost who wrote a PERL library that had been very useful to quickly write benchmarks and non-regression test - Cedric Cabessa who worked on the SNMP admin module - Adrien Grellier who provided us with the first TI-RPC support in the product - Rémi Duraffort who wrote the FSAL_ZFS implementation as well as the libzfswrap contrib. He fixed many other small bugs too nfs-ganesha-6.5/src/avl/000077500000000000000000000000001473756622300151465ustar00rootroot00000000000000nfs-ganesha-6.5/src/avl/CMakeLists.txt000066400000000000000000000026551473756622300177160ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- ########### next target ############### SET(avltree_STAT_SRCS avl.c bst.c rb.c splay.c ) add_library(avltree OBJECT ${avltree_STAT_SRCS}) add_sanitizers(avltree) set_target_properties(avltree PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(avltree gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) ########### install files ############### nfs-ganesha-6.5/src/avl/avl.c000066400000000000000000000306431473756622300161020ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later /* * avltree - Implements an AVL tree with parent pointers. * * Copyright (C) 2010-2014 Franck Bui-Huu * * This file is part of libtree which 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 LICENSE file for license rights and limitations. */ #include #include "avltree.h" #if !defined UINTPTR_MAX || UINTPTR_MAX != UINT64_MAX static inline int is_root(struct avltree_node *node) { return node->parent == NULL; } static inline void INIT_NODE(struct avltree_node *node) { node->left = NULL; node->right = NULL; node->parent = NULL; node->balance = 0; } static inline void set_balance(int balance, struct avltree_node *node) { node->balance = balance; } static inline int inc_balance(struct avltree_node *node) { return ++node->balance; } static inline int dec_balance(struct avltree_node *node) { return --node->balance; } static inline struct avltree_node *get_parent(const struct avltree_node *node) { return node->parent; } static inline void set_parent(struct avltree_node *parent, struct avltree_node *node) { node->parent = parent; } #else static inline int is_root(struct avltree_node *node) { return !(node->parent & ~7UL); } static inline void INIT_NODE(struct avltree_node *node) { node->left = NULL; node->right = NULL; node->parent = 2; } static inline void set_balance(int balance, struct avltree_node *node) { node->parent = (node->parent & ~7UL) | (balance + 2); } static inline int inc_balance(struct avltree_node *node) { return (int)(++node->parent & 7) - 2; } static inline int dec_balance(struct avltree_node *node) { return (int)(--node->parent & 7) - 2; } static inline struct avltree_node *get_parent(const struct avltree_node *node) { return (struct avltree_node *)(node->parent & ~7UL); } static inline void set_parent(const struct avltree_node *parent, struct avltree_node *node) { node->parent = (uintptr_t)parent | (node->parent & 7); } #endif /* * Iterators */ static inline struct avltree_node *get_first(struct avltree_node *node) { while (node->left) node = node->left; return node; } static inline struct avltree_node *get_last(struct avltree_node *node) { while (node->right) node = node->right; return node; } struct avltree_node *avltree_next(const struct avltree_node *node) { struct avltree_node *parent; if (node->right) return get_first(node->right); while ((parent = get_parent(node)) && parent->right == node) node = parent; return parent; } struct avltree_node *avltree_prev(const struct avltree_node *node) { struct avltree_node *parent; if (node->left) return get_last(node->left); while ((parent = get_parent(node)) && parent->left == node) node = parent; return parent; } uint64_t avltree_size(const struct avltree *tree) { return tree->size; } /* * The AVL tree is more rigidly balanced than Red-Black trees, leading * to slower insertion and removal but faster retrieval. */ /* node->balance = height(node->right) - height(node->left); */ static void rotate_left(struct avltree_node *node, struct avltree *tree) { struct avltree_node *p = node; struct avltree_node *q = node->right; /* can't be NULL */ struct avltree_node *parent = get_parent(p); if (!is_root(p)) { if (parent->left == p) parent->left = q; else parent->right = q; } else tree->root = q; set_parent(parent, q); set_parent(q, p); p->right = q->left; if (p->right) set_parent(p, p->right); q->left = p; } static void rotate_right(struct avltree_node *node, struct avltree *tree) { struct avltree_node *p = node; struct avltree_node *q = node->left; /* can't be NULL */ struct avltree_node *parent = get_parent(p); if (!is_root(p)) { if (parent->left == p) parent->left = q; else parent->right = q; } else tree->root = q; set_parent(parent, q); set_parent(q, p); p->left = q->right; if (p->left) set_parent(p, p->left); q->right = p; } struct avltree_node *avltree_inf(const struct avltree_node *key, const struct avltree *tree) { struct avltree_node *parent __attribute__((unused)); struct avltree_node *lb; struct avltree_node *unbalanced __attribute__((unused)); struct avltree_node *node = tree->root; int is_left = 0; int res = 0; lb = avltree_first(tree); /* at worst, the first entry */ unbalanced = node; parent = NULL; is_left = 0; while (node) { if (get_balance(node) != 0) unbalanced = node; res = tree->cmp_fn(node, key); if (res == 0) { /* node is the infimum */ return node; } else if (res < 1) /* lb is less than key */ lb = node; parent = node; if ((is_left = res > 0)) node = node->left; else node = node->right; } /* while */ return (lb); } struct avltree_node *avltree_sup(const struct avltree_node *key, const struct avltree *tree) { struct avltree_node *parent __attribute__((unused)); struct avltree_node *ub = NULL; struct avltree_node *unbalanced __attribute__((unused)); struct avltree_node *node = tree->root; int is_left = 0; int res = 0; unbalanced = node; parent = NULL; is_left = 0; ub = node; while (node) { if (get_balance(node) != 0) unbalanced = node; res = tree->cmp_fn(node, key); if (res == 0) { /* node is the supremum */ return (node); } parent = node; if ((is_left = res > 0)) node = node->left; else node = node->right; if (node) { res = tree->cmp_fn(node, key); if (res > 0) /* XXX do we need reciprocal test on ub? */ ub = node; } } /* while */ return (ub); } static void set_child(struct avltree_node *child, struct avltree_node *node, int left) { if (left) node->left = child; else node->right = child; } /* Insertion never needs more than 2 rotations */ void avltree_do_insert(struct avltree_node *node, struct avltree *tree, struct avltree_node *parent, struct avltree_node *unbalanced, int is_left) { INIT_NODE(node); if (!parent) { tree->root = node; tree->first = tree->last = node; tree->height++; tree->size++; return; } if (is_left) { if (parent == tree->first) tree->first = node; } else { if (parent == tree->last) tree->last = node; } set_parent(parent, node); set_child(node, parent, is_left); for (;;) { if (parent->left == node) dec_balance(parent); else inc_balance(parent); if (parent == unbalanced) break; node = parent; parent = get_parent(parent); } /* inserted */ tree->size++; switch (get_balance(unbalanced)) { case 1: case -1: tree->height++; /* fall through */ case 0: break; case 2: { struct avltree_node *right = unbalanced->right; if (get_balance(right) == 1) { set_balance(0, unbalanced); set_balance(0, right); } else { switch (get_balance(right->left)) { case 1: set_balance(-1, unbalanced); set_balance(0, right); break; case 0: set_balance(0, unbalanced); set_balance(0, right); break; case -1: set_balance(0, unbalanced); set_balance(1, right); break; } set_balance(0, right->left); rotate_right(right, tree); } rotate_left(unbalanced, tree); break; } case -2: { struct avltree_node *left = unbalanced->left; if (get_balance(left) == -1) { set_balance(0, unbalanced); set_balance(0, left); } else { switch (get_balance(left->right)) { case 1: set_balance(0, unbalanced); set_balance(-1, left); break; case 0: set_balance(0, unbalanced); set_balance(0, left); break; case -1: set_balance(1, unbalanced); set_balance(0, left); break; } set_balance(0, left->right); rotate_left(left, tree); } rotate_right(unbalanced, tree); break; } } } /* Deletion might require up to log(n) rotations */ void avltree_remove(struct avltree_node *node, struct avltree *tree) { struct avltree_node *parent = get_parent(node); struct avltree_node *left = node->left; struct avltree_node *right = node->right; struct avltree_node *next; int is_left = 0; if (node == tree->first) tree->first = avltree_next(node); if (node == tree->last) tree->last = avltree_prev(node); if (!left) next = right; else if (!right) next = left; else next = get_first(right); if (parent) { is_left = parent->left == node; set_child(next, parent, is_left); } else tree->root = next; if (left && right) { set_balance(get_balance(node), next); next->left = left; set_parent(next, left); if (next != right) { parent = get_parent(next); set_parent(get_parent(node), next); node = next->right; parent->left = node; is_left = 1; next->right = right; set_parent(next, right); } else { set_parent(parent, next); parent = next; node = parent->right; is_left = 0; } assert(parent != NULL); } else node = next; if (node) set_parent(parent, node); /* removed */ tree->size--; /* * At this point, 'parent' can only be null, if 'node' is the * tree's root and has at most one child. * * case 1: the subtree is now balanced but its height has * decreased. * * case 2: the subtree is mostly balanced and its height is * unchanged. * * case 3: the subtree is unbalanced and its height may have * been changed during the rebalancing process, see below. * * case 3.1: after a left rotation, the subtree becomes mostly * balanced and its height is unchanged. * * case 3.2: after a left rotation, the subtree becomes * balanced but its height has decreased. * * case 3.3: after a left and a right rotation, the subtree * becomes balanced or mostly balanced but its height has * decreased for all cases. */ while (parent) { int balance; node = parent; parent = get_parent(parent); if (is_left) { is_left = parent && parent->left == node; balance = inc_balance(node); if (balance == 0) /* case 1 */ continue; if (balance == 1) /* case 2 */ return; right = node->right; /* case 3 */ switch (get_balance(right)) { case 0: /* case 3.1 */ set_balance(1, node); set_balance(-1, right); rotate_left(node, tree); return; case 1: /* case 3.2 */ set_balance(0, node); set_balance(0, right); break; case -1: /* case 3.3 */ switch (get_balance(right->left)) { case 1: set_balance(-1, node); set_balance(0, right); break; case 0: set_balance(0, node); set_balance(0, right); break; case -1: set_balance(0, node); set_balance(1, right); break; } set_balance(0, right->left); rotate_right(right, tree); } rotate_left(node, tree); } else { is_left = parent && parent->left == node; balance = dec_balance(node); if (balance == 0) continue; if (balance == -1) return; left = node->left; switch (get_balance(left)) { case 0: set_balance(-1, node); set_balance(1, left); rotate_right(node, tree); return; case -1: set_balance(0, node); set_balance(0, left); break; case 1: switch (get_balance(left->right)) { case 1: set_balance(0, node); set_balance(-1, left); break; case 0: set_balance(0, node); set_balance(0, left); break; case -1: set_balance(1, node); set_balance(0, left); break; } set_balance(0, left->right); rotate_left(left, tree); } rotate_right(node, tree); } } tree->height--; } void avltree_replace(struct avltree_node *old, struct avltree_node *new, struct avltree *tree) { struct avltree_node *parent = get_parent(old); if (parent) set_child(new, parent, parent->left == old); else { /* I'm skeptical this case should be permitted--this * says that if old is not in the tree, just make new * the root of the tree */ tree->root = new; tree->size++; } if (old->left) set_parent(new, old->left); if (old->right) set_parent(new, old->right); if (tree->first == old) tree->first = new; if (tree->last == old) tree->last = new; *new = *old; } int avltree_init(struct avltree *tree, avltree_cmp_fn_t cmp, unsigned long flags) { if (flags) return -1; tree->root = NULL; tree->cmp_fn = cmp; tree->height = -1; tree->first = NULL; tree->last = NULL; tree->size = 0; return 0; } nfs-ganesha-6.5/src/avl/bst.c000066400000000000000000000173231473756622300161100ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later /* * bstree - Implements a threaded binary search tree. * * Copyright (C) 2010-2014 Franck Bui-Huu * * This file is part of libtree which 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 LICENSE file for license rights and limitations. */ #include "avltree.h" /* * This is where the black magic is defined. If you're lucky enough to * work on a system that doesn't support these kind of tricks then we * assume UINTPTR_MAX and uintptr_t type are not defined. * * Note that the getters returns a pointer when appropriate otherwise * NULL; */ #ifdef UINTPTR_MAX #define NODE_INIT \ { \ 0, \ } static inline void INIT_NODE(struct bstree_node *node) { node->left = 0; node->right = 0; } static inline void set_thread(struct bstree_node *t, uintptr_t *p) { *p = (uintptr_t)t | 1; } static inline struct bstree_node *get_thread(uintptr_t u) { return (struct bstree_node *)((u & -(int)(u & 1)) & ~1UL); } static inline void set_link(struct bstree_node *n, uintptr_t *p) { *p = (uintptr_t)n; } static inline struct bstree_node *get_link(uintptr_t u) { return (struct bstree_node *)(u & ((int)(u & 1) - 1)); } #define set_left(l, n) set_link(l, &(n)->left) #define set_right(r, n) set_link(r, &(n)->right) #define set_prev(p, n) set_thread(p, &(n)->left) #define set_next(s, n) set_thread(s, &(n)->right) #define get_left(n) get_link((n)->left) #define get_right(n) get_link((n)->right) #define get_prev(n) get_thread((n)->left) #define get_next(n) get_thread((n)->right) #else #define NODE_INIT \ { \ NULL, \ } static inline void INIT_NODE(struct bstree_node *node) { node->left = NULL; node->right = NULL; node->left_is_thread = 0; node->right_is_thread = 0; } static inline void set_left(struct bstree_node *l, struct bstree_node *n) { n->left = l; n->left_is_thread = 0; } static inline void set_right(struct bstree_node *r, struct bstree_node *n) { n->right = r; n->right_is_thread = 0; } static inline void set_prev(struct bstree_node *t, struct bstree_node *n) { n->left = t; n->left_is_thread = 1; } static inline void set_next(struct bstree_node *t, struct bstree_node *n) { n->right = t; n->right_is_thread = 1; } static inline struct bstree_node *get_left(const struct bstree_node *n) { if (n->left_is_thread) return NULL; return n->left; } static inline struct bstree_node *get_right(const struct bstree_node *n) { if (n->right_is_thread) return NULL; return n->right; } static inline struct bstree_node *get_prev(const struct bstree_node *n) { if (!n->left_is_thread) return NULL; return n->left; } static inline struct bstree_node *get_next(const struct bstree_node *n) { if (!n->right_is_thread) return NULL; return n->right; } #endif /* UINTPTR_MAX */ /* * Iterators */ static inline struct bstree_node *get_first(struct bstree_node *node) { struct bstree_node *left; while ((left = get_left(node))) node = left; return node; } static inline struct bstree_node *get_last(struct bstree_node *node) { struct bstree_node *right; while ((right = get_right(node))) node = right; return node; } struct bstree_node *bstree_first(const struct bstree *tree) { if (tree->root) return tree->first; return NULL; } struct bstree_node *bstree_last(const struct bstree *tree) { if (tree->root) return tree->last; return NULL; } struct bstree_node *bstree_next(const struct bstree_node *node) { struct bstree_node *right = get_right(node); if (right) return get_first(right); return get_next(node); } struct bstree_node *bstree_prev(const struct bstree_node *node) { struct bstree_node *left = get_left(node); if (left) return get_last(left); return get_prev(node); } /* * Main ops: lookup, insert, remove. */ static struct bstree_node *do_lookup(const struct bstree_node *key, const struct bstree *tree, struct bstree_node **pparent, int *is_left) { struct bstree_node *node = tree->root; *pparent = NULL; *is_left = 0; while (node) { int res = tree->cmp_fn(node, key); if (res == 0) return node; *pparent = node; if ((*is_left = res > 0)) node = get_left(node); else node = get_right(node); } return NULL; } struct bstree_node *bstree_lookup(const struct bstree_node *key, const struct bstree *tree) { struct bstree_node *parent; int is_left; return do_lookup(key, tree, &parent, &is_left); } struct bstree_node *bstree_insert(struct bstree_node *node, struct bstree *tree) { struct bstree_node *key, *parent; int is_left; key = do_lookup(node, tree, &parent, &is_left); if (key) return key; if (!parent) { INIT_NODE(node); tree->root = tree->first = tree->last = node; return NULL; } if (is_left) { if (parent == tree->first) tree->first = node; set_prev(get_prev(parent), node); set_next(parent, node); set_left(node, parent); } else { if (parent == tree->last) tree->last = node; set_prev(parent, node); set_next(get_next(parent), node); set_right(node, parent); } return NULL; } static void set_child(struct bstree_node *child, struct bstree_node *node, int left) { if (left) set_left(child, node); else set_right(child, node); } void bstree_remove(struct bstree_node *node, struct bstree *tree) { struct bstree_node *left, *right, *next; struct bstree_node fake_parent, *parent; int is_left; do_lookup(node, tree, &parent, &is_left); if (!parent) { INIT_NODE(&fake_parent); parent = &fake_parent; is_left = 0; } left = get_left(node); right = get_right(node); if (!left && !right) { if (is_left) set_prev(get_prev(node), parent); else set_next(get_next(node), parent); next = parent; goto update_first_last; } if (!left) { next = get_first(right); set_prev(get_prev(node), next); set_child(right, parent, is_left); goto update_first_last; } if (!right) { next = get_last(left); set_next(get_next(node), next); set_child(left, parent, is_left); goto update_first_last; } next = get_first(right); if (next != right) { /* 'm' is the parent of 'next' */ struct bstree_node *m = get_next(get_last(next)); if (get_right(next)) set_left(get_right(next), m); else set_prev(next, m); set_right(right, next); } set_child(next, parent, is_left); set_left(left, next); set_next(next, get_last(left)); out: if (parent == &fake_parent) tree->root = get_right(parent); return; update_first_last: if (node == tree->first) tree->first = next; if (node == tree->last) tree->last = next; goto out; } void bstree_replace(struct bstree_node *old, struct bstree_node *new, struct bstree *tree) { struct bstree_node *parent, *next, *prev; int is_left; if (tree->first == old) tree->first = new; if (tree->last == old) tree->last = new; if (tree->root == old) tree->root = new; else { /* * Update the parent: do a full lookup to retrieve * it. There's probably a better way but it's bst... */ do_lookup(old, tree, &parent, &is_left); if (parent) set_child(new, parent, is_left); } /* update the thread links */ prev = bstree_prev(old); if (prev && get_next(prev) == old) set_next(new, prev); next = bstree_next(old); if (next && get_prev(next) == old) set_prev(new, next); *new = *old; } int bstree_init(struct bstree *tree, bstree_cmp_fn_t cmp, unsigned long flags) { if (flags) return -1; tree->root = NULL; tree->cmp_fn = cmp; return 0; } nfs-ganesha-6.5/src/avl/rb.c000066400000000000000000000247031473756622300157230ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later /* * rbtree - Implements a red-black tree with parent pointers. * * Copyright (C) 2010-2014 Franck Bui-Huu * * This file is part of libtree which 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 LICENSE file for license rights and limitations. */ /* * For recall a red-black tree has the following properties: * * 1. All nodes are either BLACK or RED * 2. Leafs are BLACK * 3. A RED node has BLACK children only * 4. Path from a node to any leafs has the same number of BLACK nodes. * */ #include "avltree.h" /* * Some helpers */ #ifdef UINTPTR_MAX static inline enum rb_color get_color(const struct rbtree_node *node) { return node->parent & 1; } static inline void set_color(enum rb_color color, struct rbtree_node *node) { node->parent = (node->parent & ~1UL) | color; } static inline struct rbtree_node *get_parent(const struct rbtree_node *node) { return (struct rbtree_node *)(node->parent & ~1UL); } static inline void set_parent(struct rbtree_node *parent, struct rbtree_node *node) { node->parent = (uintptr_t)parent | (node->parent & 1); } #else static inline enum rb_color get_color(const struct rbtree_node *node) { return node->color; } static inline void set_color(enum rb_color color, struct rbtree_node *node) { node->color = color; } static inline struct rbtree_node *get_parent(const struct rbtree_node *node) { return node->parent; } static inline void set_parent(struct rbtree_node *parent, struct rbtree_node *node) { node->parent = parent; } #endif /* UINTPTR_MAX */ static inline int is_root(struct rbtree_node *node) { return get_parent(node) == NULL; } static inline int is_black(struct rbtree_node *node) { return get_color(node) == RB_BLACK; } static inline int is_red(struct rbtree_node *node) { return !is_black(node); } /* * Iterators */ static inline struct rbtree_node *get_first(struct rbtree_node *node) { while (node->left) node = node->left; return node; } static inline struct rbtree_node *get_last(struct rbtree_node *node) { while (node->right) node = node->right; return node; } struct rbtree_node *rbtree_first(const struct rbtree *tree) { return tree->first; } struct rbtree_node *rbtree_last(const struct rbtree *tree) { return tree->last; } struct rbtree_node *rbtree_next(const struct rbtree_node *node) { struct rbtree_node *parent; if (node->right) return get_first(node->right); while ((parent = get_parent(node)) && parent->right == node) node = parent; return parent; } struct rbtree_node *rbtree_prev(const struct rbtree_node *node) { struct rbtree_node *parent; if (node->left) return get_last(node->left); while ((parent = get_parent(node)) && parent->left == node) node = parent; return parent; } /* * 'pparent' and 'is_left' are only used for insertions. Normally GCC * will notice this and get rid of them for lookups. */ static inline struct rbtree_node *do_lookup(const struct rbtree_node *key, const struct rbtree *tree, struct rbtree_node **pparent, int *is_left) { struct rbtree_node *node = tree->root; *pparent = NULL; *is_left = 0; while (node) { int res = tree->cmp_fn(node, key); if (res == 0) return node; *pparent = node; if ((*is_left = res > 0)) node = node->left; else node = node->right; } return NULL; } /* * Rotate operations (They preserve the binary search tree property, * assuming that the keys are unique). */ static void rotate_left(struct rbtree_node *node, struct rbtree *tree) { struct rbtree_node *p = node; struct rbtree_node *q = node->right; /* can't be NULL */ struct rbtree_node *parent = get_parent(p); if (!is_root(p)) { if (parent->left == p) parent->left = q; else parent->right = q; } else tree->root = q; set_parent(parent, q); set_parent(q, p); p->right = q->left; if (p->right) set_parent(p, p->right); q->left = p; } static void rotate_right(struct rbtree_node *node, struct rbtree *tree) { struct rbtree_node *p = node; struct rbtree_node *q = node->left; /* can't be NULL */ struct rbtree_node *parent = get_parent(p); if (!is_root(p)) { if (parent->left == p) parent->left = q; else parent->right = q; } else tree->root = q; set_parent(parent, q); set_parent(q, p); p->left = q->right; if (p->left) set_parent(p, p->left); q->right = p; } struct rbtree_node *rbtree_lookup(const struct rbtree_node *key, const struct rbtree *tree) { struct rbtree_node *parent; int is_left; return do_lookup(key, tree, &parent, &is_left); } static void set_child(struct rbtree_node *child, struct rbtree_node *node, int left) { if (left) node->left = child; else node->right = child; } struct rbtree_node *rbtree_insert(struct rbtree_node *node, struct rbtree *tree) { struct rbtree_node *key, *parent; int is_left; key = do_lookup(node, tree, &parent, &is_left); if (key) return key; node->left = NULL; node->right = NULL; set_color(RB_RED, node); set_parent(parent, node); if (parent) { if (is_left) { if (parent == tree->first) tree->first = node; } else { if (parent == tree->last) tree->last = node; } set_child(node, parent, is_left); } else { tree->root = node; tree->first = node; tree->last = node; } /* * Fixup the modified tree by recoloring nodes and performing * rotations (2 at most) hence the red-black tree properties are * preserved. */ while ((parent = get_parent(node)) && is_red(parent)) { struct rbtree_node *grandpa = get_parent(parent); if (parent == grandpa->left) { struct rbtree_node *uncle = grandpa->right; if (uncle && is_red(uncle)) { set_color(RB_BLACK, parent); set_color(RB_BLACK, uncle); set_color(RB_RED, grandpa); node = grandpa; } else { if (node == parent->right) { rotate_left(parent, tree); node = parent; parent = get_parent(node); } set_color(RB_BLACK, parent); set_color(RB_RED, grandpa); rotate_right(grandpa, tree); } } else { struct rbtree_node *uncle = grandpa->left; if (uncle && is_red(uncle)) { set_color(RB_BLACK, parent); set_color(RB_BLACK, uncle); set_color(RB_RED, grandpa); node = grandpa; } else { if (node == parent->left) { rotate_right(parent, tree); node = parent; parent = get_parent(node); } set_color(RB_BLACK, parent); set_color(RB_RED, grandpa); rotate_left(grandpa, tree); } } } set_color(RB_BLACK, tree->root); return NULL; } void rbtree_remove(struct rbtree_node *node, struct rbtree *tree) { struct rbtree_node *parent = get_parent(node); struct rbtree_node *left = node->left; struct rbtree_node *right = node->right; struct rbtree_node *next; enum rb_color color; if (node == tree->first) tree->first = rbtree_next(node); if (node == tree->last) tree->last = rbtree_prev(node); if (!left) next = right; else if (!right) next = left; else next = get_first(right); if (parent) set_child(next, parent, parent->left == node); else tree->root = next; if (left && right) { color = get_color(next); set_color(get_color(node), next); next->left = left; set_parent(next, left); if (next != right) { parent = get_parent(next); set_parent(get_parent(node), next); node = next->right; parent->left = node; next->right = right; set_parent(next, right); } else { set_parent(parent, next); parent = next; node = next->right; } } else { color = get_color(node); node = next; } /* * 'node' is now the sole successor's child and 'parent' its * new parent (since the successor can have been moved). */ if (node) set_parent(parent, node); /* * The 'easy' cases. */ if (color == RB_RED) return; if (node && is_red(node)) { set_color(RB_BLACK, node); return; } do { if (node == tree->root) break; if (node == parent->left) { struct rbtree_node *sibling = parent->right; if (is_red(sibling)) { set_color(RB_BLACK, sibling); set_color(RB_RED, parent); rotate_left(parent, tree); sibling = parent->right; } if ((!sibling->left || is_black(sibling->left)) && (!sibling->right || is_black(sibling->right))) { set_color(RB_RED, sibling); node = parent; parent = get_parent(parent); continue; } if (!sibling->right || is_black(sibling->right)) { set_color(RB_BLACK, sibling->left); set_color(RB_RED, sibling); rotate_right(sibling, tree); sibling = parent->right; } set_color(get_color(parent), sibling); set_color(RB_BLACK, parent); set_color(RB_BLACK, sibling->right); rotate_left(parent, tree); node = tree->root; break; } else { struct rbtree_node *sibling = parent->left; if (is_red(sibling)) { set_color(RB_BLACK, sibling); set_color(RB_RED, parent); rotate_right(parent, tree); sibling = parent->left; } if ((!sibling->left || is_black(sibling->left)) && (!sibling->right || is_black(sibling->right))) { set_color(RB_RED, sibling); node = parent; parent = get_parent(parent); continue; } if (!sibling->left || is_black(sibling->left)) { set_color(RB_BLACK, sibling->right); set_color(RB_RED, sibling); rotate_left(sibling, tree); sibling = parent->left; } set_color(get_color(parent), sibling); set_color(RB_BLACK, parent); set_color(RB_BLACK, sibling->left); rotate_right(parent, tree); node = tree->root; break; } } while (is_black(node)); if (node) set_color(RB_BLACK, node); } void rbtree_replace(struct rbtree_node *old, struct rbtree_node *new, struct rbtree *tree) { struct rbtree_node *parent = get_parent(old); if (parent) set_child(new, parent, parent->left == old); else tree->root = new; if (old->left) set_parent(new, old->left); if (old->right) set_parent(new, old->right); if (tree->first == old) tree->first = new; if (tree->last == old) tree->last = new; *new = *old; } int rbtree_init(struct rbtree *tree, rbtree_cmp_fn_t fn, unsigned long flags) { if (flags) return -1; tree->root = NULL; tree->cmp_fn = fn; tree->first = NULL; tree->last = NULL; return 0; } nfs-ganesha-6.5/src/avl/splay.c000066400000000000000000000173431473756622300164520ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-2.1-or-later /* * splaytree - Implements a top-down threaded splay tree. * * Copyright (C) 2010-2014 Franck Bui-Huu * * This file is part of libtree which 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 LICENSE file for license rights and limitations. */ #include #include "avltree.h" #ifdef UINTPTR_MAX #define NODE_INIT \ { \ 0, \ } static inline void INIT_NODE(struct splaytree_node *node) { node->left = 0; node->right = 0; } static inline void set_thread(struct splaytree_node *t, uintptr_t *p) { *p = (uintptr_t)t | 1; } static inline struct splaytree_node *get_thread(uintptr_t u) { return (struct splaytree_node *)((u & -(int)(u & 1)) & ~1UL); } static inline void set_link(struct splaytree_node *n, uintptr_t *p) { *p = (uintptr_t)n; } static inline struct splaytree_node *get_link(uintptr_t u) { return (struct splaytree_node *)(u & ((int)(u & 1) - 1)); } #define set_left(l, n) set_link(l, &(n)->left) #define set_right(r, n) set_link(r, &(n)->right) #define set_prev(p, n) set_thread(p, &(n)->left) #define set_next(s, n) set_thread(s, &(n)->right) #define get_left(n) get_link((n)->left) #define get_right(n) get_link((n)->right) #define get_prev(n) get_thread((n)->left) #define get_next(n) get_thread((n)->right) #else /* !UINTPTR_MAX */ #define NODE_INIT \ { \ NULL, \ } static inline void INIT_NODE(struct splaytree_node *node) { node->left = NULL; node->right = NULL; node->left_is_thread = 0; node->right_is_thread = 0; } static inline void set_left(struct splaytree_node *l, struct splaytree_node *n) { n->left = l; n->left_is_thread = 0; } static inline void set_right(struct splaytree_node *r, struct splaytree_node *n) { n->right = r; n->right_is_thread = 0; } static inline void set_prev(struct splaytree_node *t, struct splaytree_node *n) { n->left = t; n->left_is_thread = 1; } static inline void set_next(struct splaytree_node *t, struct splaytree_node *n) { n->right = t; n->right_is_thread = 1; } static inline struct splaytree_node *get_left(const struct splaytree_node *n) { if (!n->left_is_thread) return n->left; return NULL; } static inline struct splaytree_node *get_right(const struct splaytree_node *n) { if (!n->right_is_thread) return n->right; return NULL; } static inline struct splaytree_node *get_prev(const struct splaytree_node *n) { if (n->left_is_thread) return n->left; return NULL; } static inline struct splaytree_node *get_next(const struct splaytree_node *n) { if (n->right_is_thread) return n->right; return NULL; } #endif /* UINTPTR_MAX */ /* * Iterators */ static inline struct splaytree_node *get_first(struct splaytree_node *node) { struct splaytree_node *left; while ((left = get_left(node))) node = left; return node; } static inline struct splaytree_node *get_last(struct splaytree_node *node) { struct splaytree_node *right; while ((right = get_right(node))) node = right; return node; } struct splaytree_node *splaytree_first(const struct splaytree *tree) { return tree->first; } struct splaytree_node *splaytree_last(const struct splaytree *tree) { return tree->last; } struct splaytree_node *splaytree_next(const struct splaytree_node *node) { struct splaytree_node *right = get_right(node); if (right) return get_first(right); return get_next(node); } struct splaytree_node *splaytree_prev(const struct splaytree_node *node) { struct splaytree_node *left = get_left(node); if (left) return get_last(left); return get_prev(node); } static inline void rotate_right(struct splaytree_node *node) { struct splaytree_node *left = get_left(node); /* can't be NULL */ struct splaytree_node *r = get_right(left); if (r) set_left(r, node); else set_prev(left, node); set_right(node, left); } static inline void rotate_left(struct splaytree_node *node) { struct splaytree_node *right = get_right(node); /* can't be NULL */ struct splaytree_node *l = get_left(right); if (l) set_right(l, node); else set_next(right, node); set_left(node, right); } static int do_splay(const struct splaytree_node *key, struct splaytree *tree) { struct splaytree_node subroots = NODE_INIT; struct splaytree_node *subleft = &subroots, *subright = &subroots; struct splaytree_node *root = tree->root; splaytree_cmp_fn_t cmp = tree->cmp_fn; int rv; for (;;) { rv = cmp(key, root); if (rv == 0) break; if (rv < 0) { struct splaytree_node *left; left = get_left(root); if (!left) break; if ((rv = cmp(key, left)) < 0) { rotate_right(root); root = left; left = get_left(root); if (!left) break; } /* link left */ set_left(root, subright); subright = root; root = left; } else { struct splaytree_node *right; right = get_right(root); if (!right) break; if ((rv = cmp(key, right)) > 0) { rotate_left(root); root = right; right = get_right(root); if (!right) break; } /* link right */ set_right(root, subleft); subleft = root; root = right; } } /* assemble */ if (get_left(root)) set_right(get_left(root), subleft); else set_next(root, subleft); if (get_right(root)) set_left(get_right(root), subright); else set_prev(root, subright); set_left(get_right(&subroots), root); set_right(get_left(&subroots), root); tree->root = root; return rv; } struct splaytree_node *splaytree_lookup(const struct splaytree_node *key, struct splaytree *tree) { if (!tree->root) return NULL; if (do_splay(key, tree) != 0) return NULL; return tree->root; } struct splaytree_node *splaytree_insert(struct splaytree_node *node, struct splaytree *tree) { struct splaytree_node *root = tree->root; int res; if (!root) { INIT_NODE(node); tree->root = node; tree->first = node; tree->last = node; return NULL; } res = do_splay(node, tree); if (res == 0) return tree->root; root = tree->root; if (res < 0) { struct splaytree_node *left = get_left(root); set_left(left, node); set_right(root, node); if (left) set_next(node, get_last(left)); else tree->first = node; set_prev(node, root); } else { struct splaytree_node *right = get_right(root); set_right(right, node); set_left(root, node); if (right) set_prev(node, get_first(right)); else tree->last = node; set_next(node, root); } tree->root = node; return NULL; } void splaytree_remove(struct splaytree_node *node, struct splaytree *tree) { struct splaytree_node *right, *left, *prev; do_splay(node, tree); assert(tree->root == node); /* 'node' must be present */ right = get_right(node); left = get_left(node); if (!left) { tree->root = right; tree->first = splaytree_next(node); prev = NULL; } else { tree->root = left; do_splay(node, tree); set_right(right, tree->root); prev = tree->root; } if (right) set_prev(prev, get_first(right)); else tree->last = prev; } void splaytree_replace(struct splaytree_node *old, struct splaytree_node *new, struct splaytree *tree) { do_splay(old, tree); assert(tree->root == old); tree->root = new; if (tree->first == old) tree->first = new; if (tree->last == old) tree->last = new; *new = *old; } int splaytree_init(struct splaytree *tree, splaytree_cmp_fn_t cmp, unsigned long flags) { if (flags) return -1; tree->root = NULL; tree->first = NULL; tree->last = NULL; tree->cmp_fn = cmp; return 0; } nfs-ganesha-6.5/src/cidr/000077500000000000000000000000001473756622300153055ustar00rootroot00000000000000nfs-ganesha-6.5/src/cidr/CMakeLists.txt000066400000000000000000000030441473756622300200460ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- ########### next target ############### SET(cidr_STAT_SRCS cidr_addr.c cidr_compare.c cidr_from_str.c cidr_get.c cidr_inaddr.c cidr_mem.c cidr_misc.c cidr_net.c cidr_num.c cidr_to_str.c cidr_pow2_p.h ) add_library(cidr OBJECT ${cidr_STAT_SRCS}) add_sanitizers(cidr) set_target_properties(cidr PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(cidr gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) ########### install files ############### nfs-ganesha-6.5/src/cidr/LICENSE000066400000000000000000000031241473756622300163120ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause /* * Copyright (c) 2005, 2006 * Matthew D. Fuller * 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 THE AUTHOR(S) ``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 AUTHORS 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 package also contains in tools/lorder a script borrowed from the FreeBSD operating system which is necessary to build (but not to run) libcidr. See that file for its license terms. nfs-ganesha-6.5/src/cidr/README.reindent000066400000000000000000000005211473756622300177720ustar00rootroot00000000000000This code is imported from: http://www.over-yonder.net/~fullermd/projects/libcidr It was inadvertently re-indented. I has been run through checkpatch.pl but the errors have not been fixed. Since this is upstream code, it should be removed and replaced by build linkage to the library itself, built separately. This is a V2.1+ task. nfs-ganesha-6.5/src/cidr/cidr_addr.c000066400000000000000000000066621473756622300173760ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause /* * Copyright (c) 2005, 2006 * Matthew D. Fuller * 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 THE AUTHOR(S) ``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 AUTHORS 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. * */ /* * Functions to generate various addresses based on a CIDR */ #include "config.h" #include #include #include "../include/cidr.h" /* Create a network address */ CIDR *cidr_addr_network(const CIDR *addr) { int i, j; CIDR *toret; toret = cidr_alloc(); toret->proto = addr->proto; /* The netmask is the same */ memcpy(toret->mask, addr->mask, (16 * sizeof(toret->mask[0]))); /* Now just figure out the network address and spit it out */ for (i = 0; i <= 15; i++) { for (j = 7; j >= 0; j--) { /* If we're into host bits, hop out */ if ((addr->mask[i] & 1 << j) == 0) return (toret); /* Else, copy this network bit */ toret->addr[i] |= (addr->addr[i] & 1 << j); } } /* * We only get here on host (/32 or /128) addresses; shorter masks * return earlier. But it's as correct as can be to just say the * same here, so... */ return (toret); } /* And a broadcast */ CIDR *cidr_addr_broadcast(const CIDR *addr) { int i, j; CIDR *toret; toret = cidr_alloc(); toret->proto = addr->proto; /* The netmask is the same */ memcpy(toret->mask, addr->mask, (16 * sizeof(toret->mask[0]))); /* Copy all the network bits */ for (i = 0; i <= 15; i++) { for (j = 7; j >= 0; j--) { /* If we're into host bits, hop out */ if ((addr->mask[i] & 1 << j) == 0) goto post; /* Else, copy this network bit */ toret->addr[i] |= (addr->addr[i] & 1 << j); } } post: /* Now set the remaining bits to 1 */ for (/* i */; i <= 15; i++) { for (/* j */; j >= 0; j--) toret->addr[i] |= (1 << j); j = 7; } /* And send it back */ return (toret); } /* Get the first host in a CIDR block */ CIDR *cidr_addr_hostmin(const CIDR *addr) { CIDR *toret; toret = cidr_addr_network(addr); if (toret == NULL) return (NULL); /* Preserve errno */ toret->addr[15] |= 1; return (toret); } /* Get the last host in a CIDR block */ CIDR *cidr_addr_hostmax(const CIDR *addr) { CIDR *toret; toret = cidr_addr_broadcast(addr); if (toret == NULL) return (NULL); /* Preserve errno */ toret->addr[15] &= 0xfe; return (toret); } nfs-ganesha-6.5/src/cidr/cidr_compare.c000066400000000000000000000074671473756622300201160ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause /* * Copyright (c) 2005, 2006 * Matthew D. Fuller * 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 THE AUTHOR(S) ``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 AUTHORS 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. * */ /* * Various comparison functions */ #include "config.h" #include #include /* For NULL */ #include "../include/cidr.h" /* Is one block entirely contained in another? */ int cidr_contains(const CIDR *big, const CIDR *little) { int i, oct, bit; int pflen; /* First off, they better be the same type */ if (big->proto != little->proto) { errno = EPROTO; return (-1); } /* We better understand the protocol, too */ if (big->proto != CIDR_IPV4 && big->proto != CIDR_IPV6) { errno = EINVAL; return (-1); } /* * little better be SMALL enough to fit in big. Note: The prefix * lengths CAN be the same, and little could still 'fit' in big if * the network bits are all the same. No need to special-case it, as * the normal tests below will DTRT. Save big's pflen for the test * loop. */ if (cidr_get_pflen(little) < (pflen = cidr_get_pflen(big))) { errno = 0; return (-1); } /* * Now let's compare. Note that for IPv4 addresses, the first 12 * octets are irrelevant. We take care throughout to keep them * zero'd out, so we don't _need_ to explicitly ignore them. But, * it's wasteful; it quadrules the amount of work needed to be done * to compare to v4 blocks, and this function may be useful in fairly * performance-sensitive parts of the application. Sure, an extra 12 * uint8_t compares better not be the make-or-break performance point * for anything real, but why make it harder unnecessarily? */ if (big->proto == CIDR_IPV4) { i = 96; pflen += 96; } else if (big->proto == CIDR_IPV6) i = 0; else { /* Shouldn't happen */ errno = ENOENT; /* This is a really bad choice of errno */ return (-1); } /* Start comparing */ for (/* i */; i < pflen; i++) { /* For convenience, set temp. vars to the octet/bit */ oct = i / 8; bit = 7 - (i % 8); if ((big->addr[oct] & (1 << bit)) != (little->addr[oct] & (1 << bit))) { errno = 0; return (-1); } } /* If we get here, all their network bits are the same */ return (0); } /* Are two CIDR's the same? */ int cidr_equals(const CIDR *one, const CIDR *two) { int i; /* Check protocols */ if (one->proto != two->proto) return (-1); /* Check addresses/masks */ if (one->proto == CIDR_IPV4) i = 12; else i = 0; for (/* i */; i <= 15; i++) { if (one->addr[i] != two->addr[i]) return (-1); if (one->mask[i] != two->mask[i]) return (-1); } /* If we make it here, they're the same */ return (0); } nfs-ganesha-6.5/src/cidr/cidr_from_str.c000066400000000000000000000657601473756622300203230ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause /* * Copyright (c) 2005, 2006 * Matthew D. Fuller * 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 THE AUTHOR(S) ``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 AUTHORS 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. * */ /* * cidr_from_str() - Generate a CIDR structure from a string in addr/len * form. */ #include "config.h" #include #include #include /* I'm always stuffing debug printf's into here */ #include #include #include #include "../include/cidr.h" CIDR *cidr_from_str(const char *addr) { size_t alen; CIDR *toret, *ctmp; const char *pfx, *buf; char *buf2; /* strtoul() can't use a (const char *) */ int i, j; int pflen; unsigned long octet; int nocts, eocts; short foundpf, foundmask, nsect; alen = strlen(addr); /* There has to be *SOMETHING* to work with */ if (alen == 0) { errno = EINVAL; return (NULL); } /* And we know it can only contain a given set of chars */ buf = addr + strspn(addr, "0123456789abcdefABCDEFxX.:/in-rpt"); if (*buf != '\0') { errno = EINVAL; return (NULL); } toret = cidr_alloc(); /* First check if we're a PTR-style string */ /* * XXX This could be folded with *pfx; they aren't used in code paths * that overlap. I'm keeping them separate just to keep my sanity * though. */ buf = NULL; /* Handle the deprecated RFC1886 form of v6 PTR */ if ((alen >= 8) && strcasecmp(addr + alen - 8, ".ip6.int") == 0) { toret->proto = CIDR_IPV6; buf = addr + alen - 8; } if (buf != NULL || ((alen >= 5) && strcasecmp(addr + alen - 5, ".arpa") == 0)) { /* * Do all this processing here, instead of trying to intermix it * with the rest of the formats. This might lead to some code * duplication, but it'll be easier to read. */ if (buf == NULL) { /* If not set by .ip6.int above */ /* First, see what protocol it is */ if ((alen >= 9) && strncasecmp(addr + alen - 9, ".ip6", 3) == 0) { toret->proto = CIDR_IPV6; buf = addr + alen - 9; } else if ((alen >= 13) && strncasecmp(addr + alen - 13, ".in-addr", 7) == 0) { toret->proto = CIDR_IPV4; buf = addr + alen - 13; } else { /* Unknown */ cidr_free(toret); errno = EINVAL; return (NULL); } } /* * buf now points to the period after the last (first) bit of * address numbering in the PTR name. */ /* * Now convert based on that protocol. Note that we're going to * be slightly asymmetrical to the way cidr_to_str() works, in * how we handle the netmask. cidr_to_str() ignores it, and * treats the PTR-style output solely as host addresses. We'll * use the netmask bits to specify how much of the address is * given in the PTR we get. That is, if we get * "3.2.1.in-addr.arpa", we'll set a /24 netmask on the returned * result. This way, the calling program can tell the difference * between "3.2.1..." and "0.3.2.1..." if it really cares to. */ buf--; /* Step before the period */ if (toret->proto == CIDR_IPV4) { for (i = 11; i <= 14; /* */) { /* If we're before the beginning, we're done */ if (buf < addr) break; /* Step backward until we at the start of an octet */ while (isdigit(*buf) && buf >= addr) buf--; /* * Save that number (++i here to show that this octet is * now set. */ octet = strtoul(buf + 1, NULL, 10); if (octet > (unsigned long)0xff) { /* Bad octet! No biscuit! */ cidr_free(toret); errno = EINVAL; return (NULL); } toret->addr[++i] = octet; /* * Back up a step to get before the '.', and process the * next [previous] octet. If we were at the beginning of * the string already, the test at the top of the loop * will drop us out. */ buf--; } /* Too much? */ if (buf >= addr) { cidr_free(toret); errno = EINVAL; return (NULL); } /* * Now, what about the mask? We set the netmask bits to * describe how much information we've actually gotten, if we * didn't get all 4 octets. Because of the way .in-addr.arpa * works, the mask can only fall on an octet boundary, so we * don't need too many fancy tricks. 'i' is still set from * the above loop to whatever the last octet we filled in is, * so we don't even have to special case anything. */ for (j = 0; j <= i; j++) toret->mask[j] = 0xff; /* Done processing */ } else if (toret->proto == CIDR_IPV6) { /* * This processing happens somewhat similarly to IPV4 above, * the format is simpler, and we need to be a little * sneakier about the mask, since it can fall on a half-octet * boundary with .ip6.arpa format. */ for (i = 0; i <= 15; i++) { /* If we're before the beginning, we're done */ if (buf < addr) break; /* We better point at a number */ if (!isxdigit(*buf)) { /* Bad input */ cidr_free(toret); errno = EINVAL; return (NULL); } /* Save the current number */ octet = strtoul(buf, NULL, 16); if (octet > (unsigned long)0xff) { /* Bad octet! No biscuit! */ cidr_free(toret); errno = EINVAL; return (NULL); } toret->addr[i] = octet << 4; toret->mask[i] = 0xf0; /* If we're at the beginning of the string, we're thru */ if (buf == addr) { /* Shift back to skip error condition at end of loop */ buf--; break; } /* If we're not, stepping back should give us a period */ if (*--buf != '.') { /* Bad input */ cidr_free(toret); errno = EINVAL; return (NULL); } /* Stepping back again should give us a number */ if (!isxdigit(*--buf)) { /* Bad input */ cidr_free(toret); errno = EINVAL; return (NULL); } /* Save that one */ octet = strtoul(buf, NULL, 16); if (octet > (unsigned long)0xff) { /* Bad octet! No biscuit! */ cidr_free(toret); errno = EINVAL; return (NULL); } toret->addr[i] |= octet & 0x0f; toret->mask[i] |= 0x0f; /* * Step back and loop back around. If that last step * back moves us to before the beginning of the string, * the condition at the top of the loop will drop us out. */ while (*--buf == '.' && buf >= addr) /* nothing */; } /* Too much? */ if (buf >= addr) { cidr_free(toret); errno = EINVAL; return (NULL); } /* Mask is set in the loop for v6 */ } else { /* Shouldn't happen */ cidr_free(toret); errno = ENOENT; /* Bad choice of errno */ return (NULL); } /* Return the value we built up, and we're done! */ return (toret); /* NOTREACHED */ } buf = NULL; /* Done */ /* * It's not a PTR form, so find the '/' prefix marker if we can. We * support both prefix length and netmasks after the /, so flag if we * find a mask. */ foundpf = foundmask = 0; for (i = alen - 1; i >= 0; i--) { /* Handle both possible forms of netmasks */ if (addr[i] == '.' || addr[i] == ':') foundmask = 1; /* Are we at the beginning of the prefix? */ if (addr[i] == '/') { foundpf = 1; break; } } if (foundpf == 0) { /* We didn't actually find a prefix, so reset the foundmask */ foundmask = 0; /* * pfx is only used if foundpf==1, but set it to NULL here to * quiet gcc down. */ pfx = NULL; } else { /* Remember where the prefix is */ pfx = addr + i; if (foundmask == 0) { /* * If we didn't find a netmask, it may be that it's one of * the v4 forms without dots. Technically, it COULD be * expressed as a single (32-bit) number that happens to be * between 0 and 32 inclusive, so there's no way to be * ABSOLUTELY sure when we have a prefix length and not a * netmask. But, that would be a non-contiguous netmask, * which we don't attempt to support, so we can probably * safely ignore that case. So try a few things... */ /* If it's a hex or octal number, assume it's a mask */ if (pfx[1] == '0' && tolower(pfx[2]) == 'x') foundmask = 1; /* Hex */ else if (pfx[1] == '0') foundmask = 1; /* Oct */ else if (isdigit(pfx[1])) { /* * If we get here, it looks like a decimal number, and we * know there aren't any periods or colons in it, so if * it's valid, it can ONLY be a single 32-bit decimal * spanning the whole 4-byte v4 address range. If that's * true, it's GOTTA be a valid number, it's GOTTA reach * to the end of the strong, and it's GOTTA be at least * 2**31 and less than 2**32. */ octet = strtoul(pfx + 1, &buf2, 10); if (*buf2 == '\0' && octet >= (unsigned long)(1 << 31) && octet <= (unsigned long)0xffffffff) foundmask = 1; /* Valid! */ octet = 0; buf2 = NULL; /* Done */ } } } i = 0; /* Done */ /* * Now, let's figure out what kind of address this is. A v6 address * will contain a : within the first 5 characters ('0000:'), a v4 * address will have a . within the first 4 ('123.'), UNLESS it's * just a single number (in hex, octal, or decimal). Anything else * isn't an address we know anything about, so fail. */ if ((buf = strchr(addr, ':')) != NULL && (buf - addr) <= 5) toret->proto = CIDR_IPV6; else if ((buf = strchr(addr, '.')) != NULL && (buf - addr) <= 4) toret->proto = CIDR_IPV4; else { /* * Special v4 forms */ if (*addr == '0' && tolower(*(addr + 1)) == 'x') { /* Hex? */ buf = (addr + 2) + strspn(addr + 2, "0123456789abcdefABCDEF"); if (*buf == '\0' || *buf == '/') toret->proto = CIDR_IPV4; /* Yep */ } else if (*addr == '0') { /* Oct? */ /* (note: this also catches the [decimal] address '0' */ buf = (addr + 1) + strspn(addr + 1, "01234567"); if (*buf == '\0' || *buf == '/') toret->proto = CIDR_IPV4; /* Yep */ } else { /* Dec? */ buf = (addr) + strspn(addr, "0123456789"); if (*buf == '\0' || *buf == '/') toret->proto = CIDR_IPV4; /* Yep */ } /* Did we catch anything? */ if (toret->proto == 0) { /* Unknown */ cidr_free(toret); errno = EINVAL; return (NULL); } } buf = NULL; /* Done */ /* * So now we know what sort of address it is, we can go ahead and * have a parser for either. */ if (toret->proto == CIDR_IPV4) { /* * Parse a v4 address. Now, we're being a little tricksy here, * and parsing it from the end instead of from the front. */ /* * First, find out how many bits we have. We need to have 4 or * less... */ buf = strchr(addr, '.'); /* Through here, nsect counts dots */ for (nsect = 0; buf != NULL && (pfx != NULL ? buf < pfx : 1); buf = strchr(buf, '.')) { nsect++; /* One more section */ buf++; /* Move past . */ if (nsect > 3) { /* Bad! We can't have more than 4 sections... */ cidr_free(toret); errno = EINVAL; return (NULL); } } buf = NULL; /* Done */ nsect++; /* sects = dots+1 */ /* * First, initialize this so we can skip building the bits if we * don't have to. */ pflen = -1; /* * Initialize the first 12 octets of the address/mask to look * like a v6-mapped address. This is the correct info for those * octets to have if/when we decide to use this v4 address as a * v6 one. */ for (i = 0; i <= 9; i++) toret->addr[i] = 0; for (i = 10; i <= 11; i++) toret->addr[i] = 0xff; for (i = 0; i <= 11; i++) toret->mask[i] = 0xff; /* * Handle the prefix/netmask. If it's not set at all, slam it to * the maximum, and put us at the end of the string to start out. * Ditto if the '/' is the end of the string. */ if (foundpf == 0) { pflen = 32; i = alen - 1; } else if (foundpf == 1 && *(pfx + 1) == '\0') { pflen = 32; i = pfx - addr - 1; } /* * Or, if we found it, and it's a NETMASK, we need to parse it * just like an address. So, cheat a little and call ourself * recursively, and then just count the bits in our returned * address for the pflen. */ if (foundpf == 1 && foundmask == 1 && pflen == -1) { ctmp = cidr_from_str(pfx + 1); if (ctmp == NULL) { /* This shouldn't happen */ cidr_free(toret); return (NULL); /* Preserve errno */ } /* Stick it in the mask */ for (i = 0; i <= 11; i++) ctmp->mask[i] = 0; for (i = 12; i <= 15; i++) ctmp->mask[i] = ctmp->addr[i]; /* Get our prefix length */ pflen = cidr_get_pflen(ctmp); cidr_free(ctmp); if (pflen == -1) { /* Failed; probably non-contiguous */ cidr_free(toret); return (NULL); /* Preserve errno */ } /* And set us to before the '/' like below */ i = pfx - addr - 1; } /* * Finally, if we did find it and it's a normal prefix length, * just pull it it, parse it out, and set ourselves to the first * character before the / for the address reading */ if (foundpf == 1 && foundmask == 0 && pflen == -1) { pflen = (int)strtol(pfx + 1, NULL, 10); i = pfx - addr - 1; } /* * If pflen is set, we need to turn it into a mask for the bits. * XXX pflen actually should ALWAYS be set, so we might not need * to make this conditional at all... */ if (pflen > 0) { /* 0 < pflen <= 32 */ if (pflen < 0 || pflen > 32) { /* Always bad */ cidr_free(toret); errno = EINVAL; return (NULL); } /* * Now pflen is in the 0...32 range and thus good. Set it in * the structure. Note that memset zero'd the whole thing to * start. We ignore mask[<12] with v4 addresses normally, * but they're already set to all-1 anyway, since if we ever * DO care about them, that's the most appropriate thing for * them to be. * * This is a horribly grody set of macros. I'm only using * them here to test them out before using them in the v6 * section, where I'll need them more due to the sheer number * of clauses I'll have to get written. Here's the straight * code I had written that the macro should be writing for me * now: * * if(pflen>24) * for(j=24 ; jmask[15] |= 1<<(31-j); * if(pflen>16) * for(j=16 ; jmask[14] |= 1<<(23-j); * if(pflen>8) * for(j=8 ; jmask[13] |= 1<<(15-j); * if(pflen>0) * for(j=0 ; jmask[12] |= 1<<(7-j); */ #define UMIN(x, y) ((x) < (y) ? (x) : (y)) #define MASKNUM(x) (24 - ((15 - x) * 8)) #define WRMASKSET(x) \ if (pflen > MASKNUM(x)) \ for (j = MASKNUM(x); j < UMIN(pflen, MASKNUM(x) + 8); j++) \ toret->mask[x] |= 1 << (MASKNUM(x) + 7 - j); WRMASKSET(15); WRMASKSET(14); WRMASKSET(13); WRMASKSET(12); #undef WRMASKET #undef MASKNUM #undef UMIN } /* Normal v4 prefix */ /* * Now we have 4 octets to grab. If any of 'em fail, or are * outside the 0...255 range, bomb. */ nocts = 0; /* Here, i should be before the /, but we may have multiple */ while (i > 0 && addr[i] == '/') i--; for (/* i */; i >= 0; i--) { /* * As long as it's still a number or an 'x' (as in '0x'), * keep backing up. Could be hex, so don't just use * isdigit(). */ if ((isxdigit(addr[i]) || tolower(addr[i]) == 'x') && i > 0) continue; /* * It's no longer a number. So, grab the number we just * moved before. */ /* Cheat for "beginning-of-string" rather than "NaN" */ if (i == 0) i--; /* Theoretically, this can be in hex/oct/dec... */ if (addr[i + 1] == '0' && tolower(addr[i + 2]) == 'x') octet = strtoul(addr + i + 1, &buf2, 16); else if (addr[i + 1] == '0') octet = strtoul(addr + i + 1, &buf2, 8); else octet = strtoul(addr + i + 1, &buf2, 10); /* If buf isn't pointing at one of [./'\0'], it's screwed */ if (!(*buf2 == '.' || *buf2 == '/' || *buf2 == '\0')) { cidr_free(toret); errno = EINVAL; return (NULL); } buf2 = NULL; /* Done */ /* * Now, because of the way compressed IPv4 addresses work, * this number CAN be greater than 255, IF it's the last bit * in the address (the first bit we parse), in which case it * must be no bigger than needed to fill the unaccounted-for * 'slots' in the address. * * See * * for details. */ if ((nocts != 0 && octet > 255) || (nocts == 0 && octet > (0xffffffff >> (8 * (nsect - 1))))) { cidr_free(toret); errno = EINVAL; return (NULL); } /* Save the lower 8 bits into this octet */ toret->addr[15 - nocts++] = octet & 0xff; /* * If this is the 'last' piece of the address (the first we * process), and there are fewer than 4 pieces total, we need * to extend it out into additional fields. See above * reference. */ if (nocts == 1) { if (nsect <= 3) toret->addr[15 - nocts++] = (octet >> 8) & 0xff; if (nsect <= 2) toret->addr[15 - nocts++] = (octet >> 16) & 0xff; if (nsect == 1) toret->addr[15 - nocts++] = (octet >> 24) & 0xff; } /* * If we've got 4 of 'em, we're actually done. We got the * prefix above, so just return direct from here. */ if (nocts == 4) return (toret); } /* * If we get here, it failed to get all 4. That shouldn't * happen, since we catch proper abbreviated forms above. */ cidr_free(toret); errno = EINVAL; return (NULL); } else if (toret->proto == CIDR_IPV6) { /* * Parse a v6 address. Like the v4, we start from the end and * parse backward. However, to handle compressed form, if we hit * a ::, we drop off and start parsing from the beginning, * because at the end we'll then have a hole that is what the :: * is supposed to contain, which is already automagically 0 from * the memset() we did earlier. Neat! * * Initialize the prefix length */ pflen = -1; /* If no prefix was found, assume the max */ if (foundpf == 0) { pflen = 128; /* Stretch back to the end of the string */ i = alen - 1; } else if (foundpf == 1 && *(pfx + 1) == '\0') { pflen = 128; i = pfx - addr - 1; } /* * If we got a netmask, rather than a prefix length, parse it and * count the bits, like we did for v4. */ if (foundpf == 1 && foundmask == 1 && pflen == -1) { ctmp = cidr_from_str(pfx + 1); if (ctmp == NULL) { /* This shouldn't happen */ cidr_free(toret); return (NULL); /* Preserve errno */ } /* Stick it in the mask */ for (i = 0; i <= 15; i++) ctmp->mask[i] = ctmp->addr[i]; /* Get the prefix length */ pflen = cidr_get_pflen(ctmp); cidr_free(ctmp); if (pflen == -1) { /* Failed; probably non-contiguous */ cidr_free(toret); return (NULL); /* Preserve errno */ } /* And set us to before the '/' like below */ i = pfx - addr - 1; } /* Finally, the normal prefix case */ if (foundpf == 1 && foundmask == 0 && pflen == -1) { pflen = (int)strtol(pfx + 1, NULL, 10); i = pfx - addr - 1; } /* * Now, if we have a pflen, turn it into a mask. * XXX pflen actually should ALWAYS be set, so we might not need * to make this conditional at all... */ if (pflen > 0) { /* Better be 0...128 */ if (pflen < 0 || pflen > 128) { /* Always bad */ cidr_free(toret); errno = EINVAL; return (NULL); } /* * Now save the pflen. See comments on the similar code up in * the v4 section about the macros. */ #define UMIN(x, y) ((x) < (y) ? (x) : (y)) #define MASKNUM(x) (120 - ((15 - x) * 8)) #define WRMASKSET(x) \ if (pflen > MASKNUM(x)) \ for (j = MASKNUM(x); j < UMIN(pflen, MASKNUM(x) + 8); j++) \ toret->mask[x] |= 1 << (MASKNUM(x) + 7 - j); WRMASKSET(15); WRMASKSET(14); WRMASKSET(13); WRMASKSET(12); WRMASKSET(11); WRMASKSET(10); WRMASKSET(9); WRMASKSET(8); WRMASKSET(7); WRMASKSET(6); WRMASKSET(5); WRMASKSET(4); WRMASKSET(3); WRMASKSET(2); WRMASKSET(1); WRMASKSET(0); #undef WRMASKET #undef MASKNUM #undef UMIN } /* * Now we have 16 octets to grab. If any of 'em fail, or are * outside the 0...0xff range, bomb. However, we MAY have a * v4-ish form, whether it's a formal v4 mapped/compat address, * or just a v4 address written in a v6 block. So, look for * .-separated octets, but there better be exactly 4 of them * before we hit a :. */ nocts = 0; /* Bump before / (or multiple /'s */ while (i > 0 && addr[i] == '/') i--; for (/* i */; i >= 0; i--) { /* * First, check the . cases, and handle them all in one * place. These can only happen at the beginning, when we * have no octets yet, and if it happens at all, we need to * have 4 of them. */ if (nocts == 0 && addr[i] == '.') { i++; /* Shift back to after the '.' */ for (/* i */; i > 0 && nocts < 4; i--) { /* This shouldn't happen except at the end */ if (addr[i] == ':' && nocts < 3) { cidr_free(toret); errno = EINVAL; return (NULL); } /* If it's not a . or :, move back 1 */ if (addr[i] != '.' && addr[i] != ':') continue; /* Should be a [decimal] octet right after here */ octet = strtoul(addr + i + 1, NULL, 10); /* Be sure */ if (octet > 255) { cidr_free(toret); errno = EINVAL; return (NULL); } /* Save it */ toret->addr[15 - nocts] = octet & 0xff; nocts++; /* And find the next octet */ } /* * At this point, 4 dotted-decimal octets should be * consumed. i has gone back one step past the : before * the decimal, so addr[i+1] should be the ':' that * precedes them. Verify. */ if (nocts != 4 || addr[i + 1] != ':') { cidr_free(toret); errno = EINVAL; return (NULL); } } /* * Now we've either gotten 4 octets filled in from * dotted-decimal stuff, or we've filled in nothing and have * no dotted decimal. */ /* As long as it's not our separator, keep moving */ if (addr[i] != ':' && i > 0) continue; /* If it's a :, and our NEXT char is a : too, flee */ if (addr[i] == ':' && addr[i + 1] == ':') { /* * If i is 0, we're already at the beginning of the * string, so we can just return; we've already filled in * everything but the leading 0's, which are already * zero-filled from the memory */ if (i == 0) return (toret); /* Else, i!=0, and we break out */ break; } /* If it's not a number either... well, bad data */ if (!isxdigit(addr[i]) && addr[i] != ':' && i > 0) { cidr_free(toret); errno = EINVAL; return (NULL); } /* * It's no longer a number. So, grab the number we just * moved before. */ /* Cheat for "beginning-of-string" rather than "NaN" */ if (i == 0) i--; octet = strtoul(addr + i + 1, &buf2, 16); if (*buf2 != ':' && *buf2 != '/' && *buf2 != '\0') { /* Got something unexpected */ cidr_free(toret); errno = EINVAL; return (NULL); } buf2 = NULL; /* Remember, this is TWO octets */ if (octet > 0xffff) { cidr_free(toret); errno = EINVAL; return (NULL); } /* Save it */ toret->addr[15 - nocts] = octet & 0xff; nocts++; toret->addr[15 - nocts] = (octet >> 8) & 0xff; nocts++; /* If we've got all of 'em, just return from here. */ if (nocts == 16) return (toret); } /* * Now, if i is >=0 and we've got two :'s, jump around to the * front of the string and start parsing inward. */ if (i >= 0 && addr[i] == ':' && addr[i + 1] == ':') { /* Remember how many octets we put on the end */ eocts = nocts; /* Remember how far we were into the string */ j = i; /* Going this way, we do things a little differently */ i = 0; while (i < j) { /* * The first char better be a number. If it's not, bail * (a leading '::' was already handled in the loop above * by just returning). */ if (i == 0 && !isxdigit(addr[i])) { cidr_free(toret); errno = EINVAL; return (NULL); } /* * We should be pointing at the beginning of a digit * string now. Translate it into an octet. */ octet = strtoul(addr + i, &buf2, 16); if (*buf2 != ':' && *buf2 != '/' && *buf2 != '\0') { /* Got something unexpected */ cidr_free(toret); errno = EINVAL; return (NULL); } buf2 = NULL; /* Sanity (again, 2 octets) */ if (octet > 0xffff) { cidr_free(toret); errno = EINVAL; return (NULL); } /* Save it */ toret->addr[nocts - eocts] = (octet >> 8) & 0xff; nocts++; toret->addr[nocts - eocts] = octet & 0xff; nocts++; /* * Discussion: If we're in this code block, it's because * we hit a ::-compression while parsing from the end * backward. So, if we hit 15 octets here, it's an * error, because with the at-least-2 that were minimized, * that makes 17 total, which is too many. So, error * out. */ if (nocts == 15) { cidr_free(toret); return (NULL); } /* Now skip around to the end of this number */ while (isxdigit(addr[i]) && i < j) i++; /* * If i==j, we're back where we started. So we've filled * in all the leading stuff, and the struct is ready to * return. */ if (i == j) return (toret); /* * Else, there's more to come. We better be pointing at * a ':', else die. */ if (addr[i] != ':') { cidr_free(toret); return (NULL); } /* Skip past : */ i++; /* If we're at j now, we had a ':::', which is invalid */ if (i == j) { cidr_free(toret); return (NULL); } /* Head back around */ } } /* If we get here, it failed somewhere odd */ cidr_free(toret); errno = EINVAL; return (NULL); } else { /* Shouldn't happen */ cidr_free(toret); errno = ENOENT; /* Bad choice of errno */ return (NULL); } /* NOTREACHED */ errno = ENOENT; return (NULL); } nfs-ganesha-6.5/src/cidr/cidr_get.c000066400000000000000000000056221473756622300172360ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause /* * Copyright (c) 2005, 2006 * Matthew D. Fuller * 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 THE AUTHOR(S) ``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 AUTHORS 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. * */ /* * cidr_get - Get and return various semi-raw bits of info */ #include "config.h" #include #include #include #include "cidr.h" #include "abstract_mem.h" /* Get the prefix length */ int cidr_get_pflen(const CIDR *block) { int i, j; int foundnmh; int pflen; /* Where do we start? */ if (block->proto == CIDR_IPV4) i = 12; else if (block->proto == CIDR_IPV6) i = 0; else { errno = ENOENT; /* Bad errno */ return (-1); /* Unknown */ } /* * We're intentionally not supporting non-contiguous netmasks. So, * if we find one, bomb out. */ foundnmh = 0; pflen = 0; for (/* i */; i <= 15; i++) { for (j = 7; j >= 0; j--) { if ((block->mask)[i] & (1 << j)) { /* * This is a network bit (1). If we've already seen a * host bit (0), we need to bomb. */ if (foundnmh == 1) { errno = EINVAL; return (-1); } pflen++; } else foundnmh = 1; /* A host bit */ } } /* If we get here, return the length */ return (pflen); } /* Get the address bits */ uint8_t *cidr_get_addr(const CIDR *addr) { uint8_t *toret; toret = gsh_calloc(16, sizeof(uint8_t)); /* Copy 'em in */ memcpy(toret, addr->addr, sizeof(addr->addr)); return (toret); } /* Get the netmask bits */ uint8_t *cidr_get_mask(const CIDR *addr) { uint8_t *toret; toret = gsh_calloc(16, sizeof(uint8_t)); /* Copy 'em in */ memcpy(toret, addr->mask, sizeof(addr->mask)); return (toret); } /* Get the protocol */ int cidr_get_proto(const CIDR *addr) { return (addr->proto); } nfs-ganesha-6.5/src/cidr/cidr_inaddr.c000066400000000000000000000124151473756622300177160ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause /* * Copyright (c) 2005, 2006 * Matthew D. Fuller * 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 THE AUTHOR(S) ``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 AUTHORS 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. * */ /* * Functions to convert to/from in[6]_addr structs */ #include "config.h" #include #include #include #include "abstract_mem.h" #include "cidr.h" /* Create a struct in_addr with the given v4 address */ struct in_addr *cidr_to_inaddr(const CIDR *addr, struct in_addr *uptr) { struct in_addr *toret; /* Better be a v4 address... */ if (addr->proto != CIDR_IPV4) { errno = EPROTOTYPE; return (NULL); } /* * Use the user's struct if possible, otherwise allocate one. It's * _their_ responsibility to give us the right type of struct to not * stomp all over the address space... */ toret = uptr; if (toret == NULL) toret = gsh_calloc(1, sizeof(struct in_addr)); /* Add 'em up and stuff 'em in */ toret->s_addr = ((addr->addr)[12] << 24) + ((addr->addr)[13] << 16) + ((addr->addr)[14] << 8) + ((addr->addr)[15]); /* * in_addr's are USUALLY used inside sockaddr_in's to do socket * stuff. The upshot of this is that they generally need to be in * network byte order. We'll do that transition here; if the user * wants to be different, they'll have to manually convert. */ toret->s_addr = htonl(toret->s_addr); return (toret); } /* Build up a CIDR struct from a given in_addr */ CIDR *cidr_from_inaddr(const struct in_addr *uaddr) { int i; CIDR *toret; in_addr_t taddr; toret = cidr_alloc(); toret->proto = CIDR_IPV4; /* * For IPv4, pretty straightforward, except that we need to jump * through a temp variable to convert into host byte order. */ taddr = ntohl(uaddr->s_addr); /* Mask these just to be safe */ toret->addr[15] = (taddr & 0xff); toret->addr[14] = ((taddr >> 8) & 0xff); toret->addr[13] = ((taddr >> 16) & 0xff); toret->addr[12] = ((taddr >> 24) & 0xff); /* Give it a single-host mask */ toret->mask[15] = toret->mask[14] = toret->mask[13] = toret->mask[12] = 0xff; /* Standard v4 overrides of addr and mask for mapped form */ for (i = 0; i <= 9; i++) toret->addr[i] = 0; for (i = 10; i <= 11; i++) toret->addr[i] = 0xff; for (i = 0; i <= 11; i++) toret->mask[i] = 0xff; /* That's it */ return (toret); } /* Create a struct in5_addr with the given v6 address */ struct in6_addr *cidr_to_in6addr(const CIDR *addr, struct in6_addr *uptr) { struct in6_addr *toret; int i; /* * Note: We're allowing BOTH IPv4 and IPv6 addresses to go through * this function. The reason is that this allows us to build up an * in6_addr struct to be used to connect to a v4 host (via a * v4-mapped address) through a v6 socket connection. A v4 * cidr_address, when built, has the upper bits of the address set * correctly for this to work. We don't support "compat"-mode * addresses here, though, and won't. */ if (addr->proto != CIDR_IPV6 && addr->proto != CIDR_IPV4) { errno = EPROTOTYPE; return (NULL); } /* Use their struct if they gave us one */ toret = uptr; if (toret == NULL) toret = gsh_calloc(1, sizeof(struct in6_addr)); /* * The in6_addr is defined to store it in 16 octets, just like we do. * But just to be safe, we're not going to stuff a giant copy in. * Most systems also use some union trickery to force alignment, but * we don't need to worry about that. * Now, this is defined to be in network byte order, which is * MSB-first. Since this is a structure of bytes, and we're filling * them in from the MSB onward ourself, we don't actually have to do * any conversions. */ for (i = 0; i <= 15; i++) toret->s6_addr[i] = addr->addr[i]; return (toret); } /* And create up a CIDR struct from a given in6_addr */ CIDR *cidr_from_in6addr(const struct in6_addr *uaddr) { int i; CIDR *toret; toret = cidr_alloc(); toret->proto = CIDR_IPV6; /* * For v6, just iterate over the arrays and return. Set all 1's in * the mask while we're at it, since this is a single host. */ for (i = 0; i <= 15; i++) { toret->addr[i] = uaddr->s6_addr[i]; toret->mask[i] = 0xff; } return (toret); } nfs-ganesha-6.5/src/cidr/cidr_mem.c000066400000000000000000000035741473756622300172410ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause /* * Copyright (c) 2005, 2006 * Matthew D. Fuller * 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 THE AUTHOR(S) ``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 AUTHORS 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. * */ /* * Various libcidr memory-related functions */ #include "config.h" #include #include #include #include "abstract_mem.h" #include "cidr.h" /* Allocate a struct cidr_addr */ CIDR *cidr_alloc(void) { return gsh_calloc(1, sizeof(CIDR)); } /* Duplicate a CIDR */ CIDR *cidr_dup(const CIDR *src) { CIDR *toret; toret = cidr_alloc(); memcpy(toret, src, sizeof(CIDR)); return (toret); } /* Free a struct cidr_addr */ void cidr_free(CIDR *tofree) { gsh_free(tofree); } nfs-ganesha-6.5/src/cidr/cidr_misc.c000066400000000000000000000037151473756622300174130ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause /* * Copyright (c) 2005, 2006 * Matthew D. Fuller * 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 THE AUTHOR(S) ``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 AUTHORS 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. * */ /* * Misc pieces */ #include "config.h" #include "cidr.h" static const char *__libcidr_version = CIDR_VERSION_STR; /* Library version info */ const char *cidr_version(void) { return (__libcidr_version); } /* Is a CIDR a v4-mapped IPv6 address? */ int cidr_is_v4mapped(const CIDR *addr) { int i; if (addr->proto != CIDR_IPV6) return (-1); /* First 10 octets should be 0 */ for (i = 0; i <= 9; i++) if (addr->addr[i] != 0) return (-1); /* Next 2 should be 0xff */ for (i = 10; i <= 11; i++) if (addr->addr[i] != 0xff) return (-1); /* Then it is */ return (0); } nfs-ganesha-6.5/src/cidr/cidr_net.c000066400000000000000000000066661473756622300172560ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause /* * Copyright (c) 2005, 2006 * Matthew D. Fuller * 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 THE AUTHOR(S) ``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 AUTHORS 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. * */ /* * Functions to generate various networks based on a CIDR */ #include "config.h" #include #include #include #include "cidr.h" #include "abstract_mem.h" /* Get the CIDR's immediate supernet */ CIDR *cidr_net_supernet(const CIDR *addr) { int i, j; int pflen; CIDR *toret; /* If it's already a /0 in its protocol, return nothing */ pflen = cidr_get_pflen(addr); if (pflen == 0) { errno = 0; return (NULL); } toret = cidr_dup(addr); if (toret == NULL) return (NULL); /* Preserve errno */ /* Chop a bit off the netmask */ /* This gets the last network bit */ if (toret->proto == CIDR_IPV4) pflen += 96; pflen--; i = pflen / 8; j = 7 - (pflen % 8); /* Make that bit a host bit */ (toret->mask)[i] &= ~(1 << j); /* * Now zero out the host bits in the addr. Do this manually instead * of calling cidr_addr_network() to save some extra copies and * allocations and so forth. */ for (/* i */; i <= 15; i++) { for (/* j */; j >= 0; j--) (toret->addr)[i] &= ~(1 << j); j = 7; } /* And send it back */ return (toret); } /* Get the CIDR's two children */ CIDR **cidr_net_subnets(const CIDR *addr) { int i, j; int pflen; CIDR **toret; /* You can't split a host address! */ pflen = cidr_get_pflen(addr); if ((addr->proto == CIDR_IPV4 && pflen == 32) || (addr->proto == CIDR_IPV6 && pflen == 128)) { errno = 0; return (NULL); } toret = gsh_calloc(2, sizeof(CIDR *)); /* Get a blank-ish slate for the first kid */ toret[0] = cidr_addr_network(addr); if (toret[0] == NULL) { gsh_free(toret); return (NULL); /* Preserve errno */ } /* Find its first host bit */ if (toret[0]->proto == CIDR_IPV4) pflen += 96; i = pflen / 8; j = 7 - (pflen % 8); /* Make it a network bit */ (toret[0])->mask[i] |= 1 << j; /* Now dup the second kid off that */ toret[1] = cidr_dup(toret[0]); if (toret[1] == NULL) { cidr_free(toret[0]); gsh_free(toret); return (NULL); /* Preserve errno */ } /* And set that first host bit */ (toret[1])->addr[i] |= 1 << j; /* Return the pair */ return (toret); } nfs-ganesha-6.5/src/cidr/cidr_num.c000066400000000000000000000044601473756622300172550ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause /* * Copyright (c) 2005, 2006 * Matthew D. Fuller * 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 THE AUTHOR(S) ``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 AUTHORS 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. * */ /* * Show some numbers */ #include "config.h" #include #include #include "cidr.h" #include "cidr_pow2_p.h" /* Number of total addresses in a given prefix length */ const char *cidr_numaddr_pflen(int pflen) { if (pflen < 0 || pflen > 128) { errno = EINVAL; return (NULL); } return (__cidr_pow2[128 - pflen]); } /* Addresses in a CIDR block */ const char *cidr_numaddr(const CIDR *addr) { int pflen; pflen = cidr_get_pflen(addr); if (addr->proto == CIDR_IPV4) pflen += 96; return (cidr_numaddr_pflen(pflen)); } /* Hosts in a prefix length */ const char *cidr_numhost_pflen(int pflen) { if (pflen < 0 || pflen > 128) { errno = EINVAL; return (NULL); } return (__cidr_pow2m2[128 - pflen]); } /* Addresses in a CIDR block */ const char *cidr_numhost(const CIDR *addr) { int pflen; pflen = cidr_get_pflen(addr); if (addr->proto == CIDR_IPV4) pflen += 96; return (cidr_numhost_pflen(pflen)); } nfs-ganesha-6.5/src/cidr/cidr_pow2_p.h000066400000000000000000000231141473756622300176660ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (c) 2005, 2006 * Matthew D. Fuller * 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 THE AUTHOR(S) ``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 AUTHORS 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. * */ /* * Some lookup tables of powers of 2, used for describing the number of * hosts or networks in a given group. * XXX This isn't really well suited for localization... */ #ifndef __LIBCIDR__POW2_P_H #define __LIBCIDR__POW2_P_H /* Powers of two */ static const char *__cidr_pow2[] = { "1", "2", "4", "8", "16", "32", "64", "128", "256", "512", "1,024", "2,048", "4,096", "8,192", "16,384", "32,768", "65,536", "131,072", "262,144", "524,288", "1,048,576", "2,097,152", "4,194,304", "8,388,608", "16,777,216", "33,554,432", "67,108,864", "134,217,728", "268,435,456", "536,870,912", "1,073,741,824", "2,147,483,648", "4,294,967,296", "8,589,934,592", "17,179,869,184", "34,359,738,368", "68,719,476,736", "137,438,953,472", "274,877,906,944", "549,755,813,888", "1,099,511,627,776", "2,199,023,255,552", "4,398,046,511,104", "8,796,093,022,208", "17,592,186,044,416", "35,184,372,088,832", "70,368,744,177,664", "140,737,488,355,328", "281,474,976,710,656", "562,949,953,421,312", "1,125,899,906,842,624", "2,251,799,813,685,248", "4,503,599,627,370,496", "9,007,199,254,740,992", "18,014,398,509,481,984", "36,028,797,018,963,968", "72,057,594,037,927,936", "144,115,188,075,855,872", "288,230,376,151,711,744", "576,460,752,303,423,488", "1,152,921,504,606,846,976", "2,305,843,009,213,693,952", "4,611,686,018,427,387,904", "9,223,372,036,854,775,808", "18,446,744,073,709,551,616", "36,893,488,147,419,103,232", "73,786,976,294,838,206,464", "147,573,952,589,676,412,928", "295,147,905,179,352,825,856", "590,295,810,358,705,651,712", "1,180,591,620,717,411,303,424", "2,361,183,241,434,822,606,848", "4,722,366,482,869,645,213,696", "9,444,732,965,739,290,427,392", "18,889,465,931,478,580,854,784", "37,778,931,862,957,161,709,568", "75,557,863,725,914,323,419,136", "151,115,727,451,828,646,838,272", "302,231,454,903,657,293,676,544", "604,462,909,807,314,587,353,088", "1,208,925,819,614,629,174,706,176", "2,417,851,639,229,258,349,412,352", "4,835,703,278,458,516,698,824,704", "9,671,406,556,917,033,397,649,408", "19,342,813,113,834,066,795,298,816", "38,685,626,227,668,133,590,597,632", "77,371,252,455,336,267,181,195,264", "154,742,504,910,672,534,362,390,528", "309,485,009,821,345,068,724,781,056", "618,970,019,642,690,137,449,562,112", "1,237,940,039,285,380,274,899,124,224", "2,475,880,078,570,760,549,798,248,448", "4,951,760,157,141,521,099,596,496,896", "9,903,520,314,283,042,199,192,993,792", "19,807,040,628,566,084,398,385,987,584", "39,614,081,257,132,168,796,771,975,168", "79,228,162,514,264,337,593,543,950,336", "158,456,325,028,528,675,187,087,900,672", "316,912,650,057,057,350,374,175,801,344", "633,825,300,114,114,700,748,351,602,688", "1,267,650,600,228,229,401,496,703,205,376", "2,535,301,200,456,458,802,993,406,410,752", "5,070,602,400,912,917,605,986,812,821,504", "10,141,204,801,825,835,211,973,625,643,008", "20,282,409,603,651,670,423,947,251,286,016", "40,564,819,207,303,340,847,894,502,572,032", "81,129,638,414,606,681,695,789,005,144,064", "162,259,276,829,213,363,391,578,010,288,128", "324,518,553,658,426,726,783,156,020,576,256", "649,037,107,316,853,453,566,312,041,152,512", "1,298,074,214,633,706,907,132,624,082,305,024", "2,596,148,429,267,413,814,265,248,164,610,048", "5,192,296,858,534,827,628,530,496,329,220,096", "10,384,593,717,069,655,257,060,992,658,440,192", "20,769,187,434,139,310,514,121,985,316,880,384", "41,538,374,868,278,621,028,243,970,633,760,768", "83,076,749,736,557,242,056,487,941,267,521,536", "166,153,499,473,114,484,112,975,882,535,043,072", "332,306,998,946,228,968,225,951,765,070,086,144", "664,613,997,892,457,936,451,903,530,140,172,288", "1,329,227,995,784,915,872,903,807,060,280,344,576", "2,658,455,991,569,831,745,807,614,120,560,689,152", "5,316,911,983,139,663,491,615,228,241,121,378,304", "10,633,823,966,279,326,983,230,456,482,242,756,608", "21,267,647,932,558,653,966,460,912,964,485,513,216", "42,535,295,865,117,307,932,921,825,928,971,026,432", "85,070,591,730,234,615,865,843,651,857,942,052,864", "170,141,183,460,469,231,731,687,303,715,884,105,728", "340,282,366,920,938,463,463,374,607,431,768,211,456" }; /* Powers of 2 minus two; hosts in a subnet with this many host bit */ static const char *__cidr_pow2m2[] = { "1", /* Special */ "2", /* Special */ "2", "6", "14", "30", "62", "126", "254", "510", "1,022", "2,046", "4,094", "8,190", "16,382", "32,766", "65,534", "131,070", "262,142", "524,286", "1,048,574", "2,097,150", "4,194,302", "8,388,606", "16,777,214", "33,554,430", "67,108,862", "134,217,726", "268,435,454", "536,870,910", "1,073,741,822", "2,147,483,646", "4,294,967,294", "8,589,934,590", "17,179,869,182", "34,359,738,366", "68,719,476,734", "137,438,953,470", "274,877,906,942", "549,755,813,886", "1,099,511,627,774", "2,199,023,255,550", "4,398,046,511,102", "8,796,093,022,206", "17,592,186,044,414", "35,184,372,088,830", "70,368,744,177,662", "140,737,488,355,326", "281,474,976,710,654", "562,949,953,421,310", "1,125,899,906,842,622", "2,251,799,813,685,246", "4,503,599,627,370,494", "9,007,199,254,740,990", "18,014,398,509,481,982", "36,028,797,018,963,966", "72,057,594,037,927,934", "144,115,188,075,855,870", "288,230,376,151,711,742", "576,460,752,303,423,486", "1,152,921,504,606,846,974", "2,305,843,009,213,693,950", "4,611,686,018,427,387,902", "9,223,372,036,854,775,806", "18,446,744,073,709,551,614", "36,893,488,147,419,103,230", "73,786,976,294,838,206,462", "147,573,952,589,676,412,926", "295,147,905,179,352,825,854", "590,295,810,358,705,651,710", "1,180,591,620,717,411,303,422", "2,361,183,241,434,822,606,846", "4,722,366,482,869,645,213,694", "9,444,732,965,739,290,427,390", "18,889,465,931,478,580,854,782", "37,778,931,862,957,161,709,566", "75,557,863,725,914,323,419,134", "151,115,727,451,828,646,838,270", "302,231,454,903,657,293,676,542", "604,462,909,807,314,587,353,086", "1,208,925,819,614,629,174,706,174", "2,417,851,639,229,258,349,412,350", "4,835,703,278,458,516,698,824,702", "9,671,406,556,917,033,397,649,406", "19,342,813,113,834,066,795,298,814", "38,685,626,227,668,133,590,597,630", "77,371,252,455,336,267,181,195,262", "154,742,504,910,672,534,362,390,526", "309,485,009,821,345,068,724,781,054", "618,970,019,642,690,137,449,562,110", "1,237,940,039,285,380,274,899,124,222", "2,475,880,078,570,760,549,798,248,446", "4,951,760,157,141,521,099,596,496,894", "9,903,520,314,283,042,199,192,993,790", "19,807,040,628,566,084,398,385,987,582", "39,614,081,257,132,168,796,771,975,166", "79,228,162,514,264,337,593,543,950,334", "158,456,325,028,528,675,187,087,900,670", "316,912,650,057,057,350,374,175,801,342", "633,825,300,114,114,700,748,351,602,686", "1,267,650,600,228,229,401,496,703,205,374", "2,535,301,200,456,458,802,993,406,410,750", "5,070,602,400,912,917,605,986,812,821,502", "10,141,204,801,825,835,211,973,625,643,006", "20,282,409,603,651,670,423,947,251,286,014", "40,564,819,207,303,340,847,894,502,572,030", "81,129,638,414,606,681,695,789,005,144,062", "162,259,276,829,213,363,391,578,010,288,126", "324,518,553,658,426,726,783,156,020,576,254", "649,037,107,316,853,453,566,312,041,152,510", "1,298,074,214,633,706,907,132,624,082,305,022", "2,596,148,429,267,413,814,265,248,164,610,046", "5,192,296,858,534,827,628,530,496,329,220,094", "10,384,593,717,069,655,257,060,992,658,440,190", "20,769,187,434,139,310,514,121,985,316,880,382", "41,538,374,868,278,621,028,243,970,633,760,766", "83,076,749,736,557,242,056,487,941,267,521,534", "166,153,499,473,114,484,112,975,882,535,043,070", "332,306,998,946,228,968,225,951,765,070,086,142", "664,613,997,892,457,936,451,903,530,140,172,286", "1,329,227,995,784,915,872,903,807,060,280,344,574", "2,658,455,991,569,831,745,807,614,120,560,689,150", "5,316,911,983,139,663,491,615,228,241,121,378,302", "10,633,823,966,279,326,983,230,456,482,242,756,606", "21,267,647,932,558,653,966,460,912,964,485,513,214", "42,535,295,865,117,307,932,921,825,928,971,026,430", "85,070,591,730,234,615,865,843,651,857,942,052,862", "170,141,183,460,469,231,731,687,303,715,884,105,726", "340,282,366,920,938,463,463,374,607,431,768,211,454" }; #endif /* __LIBCIDR__POW2_P_H */ nfs-ganesha-6.5/src/cidr/cidr_to_str.c000066400000000000000000000264271473756622300177770ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-2-Clause /* * Copyright (c) 2005, 2006 * Matthew D. Fuller * 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 THE AUTHOR(S) ``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 AUTHORS 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. * */ /* * cidr_to_str() - Generate a textual representation of the given CIDR * subnet. */ #include "config.h" #include #include #include #include #include "cidr.h" #include "abstract_mem.h" char *cidr_to_str(const CIDR *block, int flags) { int i; int zst, zcur, zlen, zmax; short pflen; short lzer; /* Last zero */ char *toret; char tmpbuf[128]; /* We shouldn't need more than ~5 anywhere */ CIDR *nmtmp; char *nmstr; int nmflags; uint8_t moct; uint16_t v6sect; /* Just in case */ if (block->proto == CIDR_NOPROTO) { errno = EINVAL; return (NULL); } /* * Sanity: If we have both ONLYADDR and ONLYPFLEN, we really don't * have anything to *DO*... */ if ((flags & CIDR_ONLYADDR) && (flags & CIDR_ONLYPFLEN)) { errno = EINVAL; return (NULL); } /* * Now, in any case, there's a maximum length for any address, which * is the completely expanded form of a v6-{mapped,compat} address * with a netmask instead of a prefix. That's 8 pieces of 4 * characters each (32), separated by :'s (+7=39), plus the slash * (+1=40), plus another separated-8*4 (+39=79), plus the trailing * null (+1=80). We'll just allocate 128 for kicks. * * I'm not, at this time anyway, going to try and allocate only and * exactly as much as we need for any given address. Whether * consumers of the library can count on this behavior... well, I * haven't decided yet. Lemme alone. */ toret = gsh_calloc(1, 128); /* * If it's a v4 address, we mask off everything but the last 4 * octets, and just proceed from there. */ if ((block->proto == CIDR_IPV4 && !(flags & CIDR_FORCEV6)) || (flags & CIDR_FORCEV4)) { /* First off, creating the in-addr.arpa form is special */ if (flags & CIDR_REVERSE) { /* * Build the d.c.b.a.in-addr.arpa form. Note that we ignore * flags like CIDR_VERBOSE and the like here, since they lead * to non-valid reverse paths (or at least, paths that no DNS * implementation will look for). So it pretty much always * looks exactly the same. Also, we don't mess with dealing * with netmaks or anything here; we just assume it's a * host address, and treat it as such. */ (void)sprintf(toret, "%d.%d.%d.%d.in-addr.arpa", block->addr[15], block->addr[14], block->addr[13], block->addr[12]); return (toret); } /* Are we bothering to show the address? */ if (!(flags & CIDR_ONLYPFLEN)) { /* If we're USEV6'ing, add whatever prefixes we need */ if (flags & CIDR_USEV6) { if (flags & CIDR_NOCOMPACT) { if (flags & CIDR_VERBOSE) strcat(toret, "0000:0000:0000:0000:0000:"); else strcat(toret, "0:0:0:0:0:"); } else strcat(toret, "::"); if (flags & CIDR_USEV4COMPAT) { if (flags & CIDR_NOCOMPACT) { if (flags & CIDR_VERBOSE) strcat(toret, "0000:"); else strcat(toret, "0:"); } } else strcat(toret, "ffff:"); } /* USEV6 */ /* Now, slap on the v4 address */ for (i = 12; i <= 15; i++) { (void)sprintf(tmpbuf, "%u", (block->addr)[i]); strcat(toret, tmpbuf); if (i < 15) strcat(toret, "."); } } /* ! ONLYPFLEN */ /* Are we bothering to show the pf/mask? */ if (!(flags & CIDR_ONLYADDR)) { /* * And the prefix/netmask. Don't show the '/' if we're only * showing the pflen/mask. */ if (!(flags & CIDR_ONLYPFLEN)) strcat(toret, "/"); /* Which are we showing? */ if (flags & CIDR_NETMASK) { /* * In this case, we can just print out like the address * above. */ for (i = 12; i <= 15; i++) { moct = (block->mask)[i]; if (flags & CIDR_WILDCARD) moct = ~(moct); (void)sprintf(tmpbuf, "%u", moct); strcat(toret, tmpbuf); if (i < 15) strcat(toret, "."); } } else { /* * For this, iterate over each octet, * then each bit within the octet. */ pflen = cidr_get_pflen(block); if (pflen == -1) { gsh_free(toret); return (NULL); /* Preserve errno */ } /* Special handling for forced modes */ if (block->proto == CIDR_IPV6 && (flags & CIDR_FORCEV4)) pflen -= 96; (void)sprintf(tmpbuf, "%u", (flags & CIDR_USEV6) ? pflen + 96 : pflen); strcat(toret, tmpbuf); } } /* ! ONLYADDR */ /* That's it for a v4 address, in any of our forms */ } else if ((block->proto == CIDR_IPV6 && !(flags & CIDR_FORCEV4)) || (flags & CIDR_FORCEV6)) { /* First off, creating the .ip6.arpa form is special */ if (flags & CIDR_REVERSE) { /* * Build the ...ip6.arpa form. See notes in the CIDR_REVERSE * section of PROTO_IPV4 above for various notes. */ (void)sprintf( toret, "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." "%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x." "%x.%x.%x.%x.%x.ip6.arpa", block->addr[15] & 0x0f, block->addr[15] >> 4, block->addr[14] & 0x0f, block->addr[14] >> 4, block->addr[13] & 0x0f, block->addr[13] >> 4, block->addr[12] & 0x0f, block->addr[12] >> 4, block->addr[11] & 0x0f, block->addr[11] >> 4, block->addr[10] & 0x0f, block->addr[10] >> 4, block->addr[9] & 0x0f, block->addr[9] >> 4, block->addr[8] & 0x0f, block->addr[8] >> 4, block->addr[7] & 0x0f, block->addr[7] >> 4, block->addr[6] & 0x0f, block->addr[6] >> 4, block->addr[5] & 0x0f, block->addr[5] >> 4, block->addr[4] & 0x0f, block->addr[4] >> 4, block->addr[3] & 0x0f, block->addr[3] >> 4, block->addr[2] & 0x0f, block->addr[2] >> 4, block->addr[1] & 0x0f, block->addr[1] >> 4, block->addr[0] & 0x0f, block->addr[0] >> 4); return (toret); } /* Are we showing the address part? */ if (!(flags & CIDR_ONLYPFLEN)) { /* It's a simple, boring, normal v6 address */ /* First, find the longest string of 0's, if there is one */ zst = zcur = -1; zlen = zmax = 0; for (i = 0; i <= 15; i += 2) { if (block->addr[i] == 0 && block->addr[i + 1] == 0) { /* This section is zero */ if (zcur != -1) { /* We're already in a block of 0's */ zlen++; } else { /* Starting a new block */ zcur = i; zlen = 1; } } else { /* This section is non-zero */ if (zcur != -1) { /* * We were in 0's. See if we set a new record, * and if we did, note it and move on. */ if (zlen > zmax) { zst = zcur; zmax = zlen; } /* We're out of 0's, so reset start */ zcur = -1; } } } /* * If zcur is !=-1, we were in 0's when the loop ended. Redo * the "if we have a record, update" logic. */ if (zcur != -1 && zlen > zmax) { zst = zcur; zmax = zlen; } /* * Now, what makes it HARD is the options we have. To make * some things simpler, we'll take two octets at a time for * our run through. */ lzer = 0; for (i = 0; i <= 15; i += 2) { /* * Start with a cheat; if this begins our already-found * longest block of 0's, and we're not NOCOMPACT'ing, * stick in a ::, increment past them, and keep on * playing. */ if (i == zst && !(flags & CIDR_NOCOMPACT)) { strcat(toret, "::"); i += (zmax * 2) - 2; lzer = 1; continue; } /* * First, if we're not the first set, we may need a : * before us. If we're not compacting, we always want * it. If we ARE compacting, we want it unless the * previous octet was a 0 that we're minimizing. */ if (i != 0 && ((flags & CIDR_NOCOMPACT) || lzer == 0)) strcat(toret, ":"); lzer = 0; /* Reset */ /* * From here on, we no longer have to worry about * CIDR_NOCOMPACT. */ /* Combine the pair of octets into one number */ v6sect = 0; v6sect |= (block->addr)[i] << 8; v6sect |= (block->addr)[i + 1]; /* * If we're being VERBOSE, use leading 0's. Otherwise, * only use as many digits as we need. */ if (flags & CIDR_VERBOSE) (void)sprintf(tmpbuf, "%.4x", v6sect); else (void)sprintf(tmpbuf, "%x", v6sect); strcat(toret, tmpbuf); /* And loop back around to the next 2-octet set */ } /* for(each 16-bit set) */ } /* ! ONLYPFLEN */ /* Prefix/netmask */ if (!(flags & CIDR_ONLYADDR)) { /* Only show the / if we're not showing just the prefix */ if (!(flags & CIDR_ONLYPFLEN)) strcat(toret, "/"); if (flags & CIDR_NETMASK) { /* * We already wrote how to build the whole v6 form, so * just call ourselves recursively for this. */ nmtmp = cidr_alloc(); nmtmp->proto = block->proto; for (i = 0; i <= 15; i++) if (flags & CIDR_WILDCARD) nmtmp->addr[i] = ~(block->mask[i]); else nmtmp->addr[i] = block->mask[i]; /* * Strip flags: * - CIDR_NETMASK would make us recurse forever. * - CIDR_ONLYPFLEN would not show the address bit, which * is the part we want here. * Add flag CIDR_ONLYADDR because that's the bit we care * about. */ nmflags = flags; nmflags &= ~(CIDR_NETMASK) & ~(CIDR_ONLYPFLEN); nmflags |= CIDR_ONLYADDR; nmstr = cidr_to_str(nmtmp, nmflags); cidr_free(nmtmp); if (nmstr == NULL) { gsh_free(toret); return (NULL); /* Preserve errno */ } /* No need to strip the prefix, it doesn't have it */ /* Just add it on */ strcat(toret, nmstr); gsh_free(nmstr); } else { /* Just figure the and show prefix length */ pflen = cidr_get_pflen(block); if (pflen == -1) { gsh_free(toret); return (NULL); /* Preserve errno */ } /* Special handling for forced modes */ if (block->proto == CIDR_IPV4 && (flags & CIDR_FORCEV6)) pflen += 96; (void)sprintf(tmpbuf, "%u", pflen); strcat(toret, tmpbuf); } } /* ! ONLYADDR */ } else { /* Well, *I* dunno what the fuck it is */ gsh_free(toret); errno = ENOENT; /* Bad choice of errno */ return (NULL); } /* Give back the string */ return (toret); } nfs-ganesha-6.5/src/cmake/000077500000000000000000000000001473756622300154445ustar00rootroot00000000000000nfs-ganesha-6.5/src/cmake/COPYING-CMAKE-SCRIPTS000066400000000000000000000043431473756622300204460ustar00rootroot00000000000000NFS-Ganesha clarifications: The conditions set below apply only to the Cmake specific files in this directory. They do not apply to the NFS-ganesha project as a whole. See the top level licensing documentation for details about NFS-Ganesha licensing. Some scripts in this directory are a combination of modules copied from other cmake projects, net forums, or HOWTOs. Any files that were copied were, to the best of our ability, audited to have a proper open source license or come from a project with an appropriate open source license. Where the author placed a license clause in the cmake file, this has been preserved. If a file in this directory does not have a license notice attached, the following Cmake appropriate license and disclaimer applies. This is for the convenience of other developers who wish to use these files in their own open source projects. ---------------- Cmake modules and scripts license ------------------- 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 copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. nfs-ganesha-6.5/src/cmake/build_configurations/000077500000000000000000000000001473756622300216555ustar00rootroot00000000000000nfs-ganesha-6.5/src/cmake/build_configurations/bsd10.cmake000066400000000000000000000023131473756622300235670ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # Only build VFS fsal for FreeBSD 10.1 set(USE_FSAL_PROXY_V4 OFF) set(USE_FSAL_CEPH OFF) set(USE_FSAL_GPFS OFF) set(_MSPAC_SUPPORT OFF) set(USE_9P OFF) set(USE_DBUS OFF) message(STATUS "Building BSD 10.1 configuration") nfs-ganesha-6.5/src/cmake/build_configurations/debian.cmake000066400000000000000000000021571473756622300241060ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # Turn on everything in the options for a complete build set(USE_DBUS ON) set(USE_ADMIN_TOOLS ON) message(STATUS "Building DPKG") nfs-ganesha-6.5/src/cmake/build_configurations/everything.cmake000066400000000000000000000024121473756622300250420ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # Turn on everything in the options for a complete build set(PROXYV4_HANDLE_MAPPING ON) set(USE_DBUS ON) set(USE_CB_SIMULATOR ON) set(USE_FSAL_XFS ON) set(USE_FSAL_CEPH ON) set(USE_FSAL_RGW ON) set(USE_FSAL_GLUSTER ON) set(USE_TOOL_MULTILOCK ON) message(STATUS "Building everything") nfs-ganesha-6.5/src/cmake/build_configurations/gpfs.cmake000066400000000000000000000024711473756622300236220ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # where `make install` will place files set(CMAKE_PREFIX_PATH "/usr/") # FSAL's to build set(USE_FSAL_GPFS ON) set(USE_FSAL_VFS ON) set(USE_FSAL_PROXY_V4 ON) set(USE_DBUS ON) # Disable FSAL's we don't use set(USE_FSAL_CEPH OFF) set(_MSPAC_SUPPORT OFF) set(USE_9P OFF) message(STATUS "Building gpfs_vfs_pnfs_only configuration") nfs-ganesha-6.5/src/cmake/build_configurations/rpmbuild.cmake000066400000000000000000000021051473756622300244730ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # Turn on everything in the options for a complete build message(STATUS "Building RPM") nfs-ganesha-6.5/src/cmake/build_configurations/vfs_only.cmake000066400000000000000000000023221473756622300245150ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # Only build VFS fsal and other useful options set(USE_FSAL_PROXY_V4 OFF) set(USE_FSAL_CEPH OFF) set(USE_FSAL_GPFS OFF) set(_MSPAC_SUPPORT OFF) set(USE_9P OFF) set(USE_DBUS ON) message(STATUS "Building vfs_only configuration") nfs-ganesha-6.5/src/cmake/cpack_config.cmake000066400000000000000000000033171473756622300210600ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # A few CPack related variables set(CPACK_PACKAGE_NAME "nfs-ganesha" ) set(CPACK_PACKAGE_VERSION "${GANESHA_VERSION}" ) set(CPACK_PACKAGE_VENDOR "NFS-Ganesha Project") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "NFS-Ganesha - An NFS Server running in user space") # CPack's debian stuff SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "nfs-ganesha-devel@lists.sourceforge.net") set(CPACK_RPM_COMPONENT_INSTALL OFF) set(CPACK_COMPONENTS_IGNORE_GROUPS "IGNORE") # Tell CPack the kind of packages to be generated set(CPACK_GENERATOR "TGZ") set(CPACK_SOURCE_GENERATOR "TGZ") set(CPACK_SOURCE_IGNORE_FILES "/.git/;/.gitignore/;/.bzr/;~$;${CPACK_SOURCE_IGNORE_FILES}") set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}") nfs-ganesha-6.5/src/cmake/gitdesc_from_path.sh000077500000000000000000000022071473756622300214650ustar00rootroot00000000000000#!/bin/sh # SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- res=`echo $1 | sed -e 's/^.*_desc_//g' | sed -e 's/-[0-9]*\.[0-9]*\.[0-9]*$//g'` if [ -z "$res" ] ; then echo "NO-GIT" else echo $res fi nfs-ganesha-6.5/src/cmake/githead_from_path.sh000077500000000000000000000021721473756622300214510ustar00rootroot00000000000000#!/bin/sh # SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- res=`echo $1 | sed -e 's/[_-]/\n/g' | grep git | sed -e 's/git//g'` if [ -z "$res" ] ; then echo "NOT-GIT" else echo $res fi nfs-ganesha-6.5/src/cmake/goption.cmake000066400000000000000000000064151473756622300201330ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # Check to see if a FSAL is on or off, and required # # This is to work around the broken option() system, which doesn't indicate # whether or not an option was given on the command line, but only whether it's # on or off (but not defaulted on or off). # # This consists of several macros: # # goption( [ON | OFF]) # This sets up an option, and it's default value # # gopt_test() # This tests the value of and sets it to ON or OFF, and sets # _REQUIRED to "" or "REQUIRED" # # This should be used as follows: # goption(USE_FSAL_TEST ON) # gopt_test(USE_FSAL_TEST) # if(USE_FSAL_TEST) # find_package(test ${USE_FSAL_TEST_REQUIRED}) # if(NOT TEST_FOUND) # message(WARNING "libtest not found; disabling FSAL_TEST") # set(USE_FSAL_TEST OFF) # endif(NOT TEST_FOUND) # endif(USE_FSAL_TEST) # # Alternatively, if your option doesn't use find_package(), the test can look like this: # if(USE_FSAL_TEST) # # if(NOT TEST_FOUND) # if (USE_FSAL_TEST_REQUIRED) # message(FATAL_ERROR "libtest not found but requested on command line") # elseif (USE_FSAL_TEST_REQUIRED) # message(WARNING "libtest not found; disabling FSAL_TEST") # set(USE_FSAL_TEST OFF) # endif (USE_FSAL_TEST_REQUIRED) # endif(NOT TEST_FOUND) # endif(USE_FSAL_TEST) macro (goption OPTNAME DESC DEFVAL) set(${OPTNAME} DEFAULT_${DEFVAL} CACHE STRING "${DESC}") set_property(CACHE ${OPTNAME} PROPERTY STRINGS ON OFF) endmacro() macro (gopt_test OPTNAME) #message(WARNING "${OPTNAME}: ${${OPTNAME}}") if (${${OPTNAME}} MATCHES "DEFAULT_ON") #message(WARNING "Trying to set ${OPTNAME}: to ON") set(${OPTNAME} ON) set(${OPTNAME}_REQUIRED "") elseif (${${OPTNAME}} MATCHES "DEFAULT_OFF") #message(WARNING "Trying to set ${OPTNAME}: to OFF") set(${OPTNAME} OFF) set(${OPTNAME}_REQUIRED "") elseif (${OPTNAME}) #message(WARNING "Trying to set ${OPTNAME}: to ON REQ") set(${OPTNAME} ON CACHE STRING "Option for ${OPTNAME}" FORCE) set(${OPTNAME}_REQUIRED "REQUIRED") else(${OPTNAME} MATCHES "OFF") #message(WARNING "Trying to set ${OPTNAME}: to OFF REQ") set(${OPTNAME} OFF CACHE STRING "Option for ${OPTNAME}" FORCE) set(${OPTNAME}_REQUIRED "REQUIRED") endif() endmacro() nfs-ganesha-6.5/src/cmake/maintainer_mode.cmake000066400000000000000000000061601473756622300216040ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- SET( CMAKE_CXX_FLAGS_MAINTAINER "-Wall" CACHE STRING "Flags used by the C++ compiler during maintainer builds." FORCE ) SET( CMAKE_C_FLAGS_MAINTAINER "-Werror -Wall -Wimplicit -Wformat -Wmissing-braces -Wreturn-type -Wunused-variable -Wuninitialized -Wno-pointer-sign -Wno-strict-aliasing" CACHE STRING "Flags used by the C compiler during maintainer builds." FORCE ) SET( CMAKE_EXE_LINKER_FLAGS_MAINTAINER CACHE STRING "Flags used for linking binaries during maintainer builds." FORCE ) SET( CMAKE_SHARED_LINKER_FLAGS_MAINTAINER CACHE STRING "Flags used by the shared libraries linker during maintainer builds." FORCE ) MARK_AS_ADVANCED( CMAKE_CXX_FLAGS_MAINTAINER CMAKE_C_FLAGS_MAINTAINER CMAKE_EXE_LINKER_FLAGS_MAINTAINER CMAKE_SHARED_LINKER_FLAGS_MAINTAINER ) # Debug wants the same flags, plus -g SET( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_MAINTAINER} -g" CACHE STRING "Debug CXX flags" FORCE ) SET( CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_MAINTAINER} -g" CACHE STRING "Debug C FLAGS" FORCE ) SET( CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_MAINTAINER}" CACHE STRING "Debug exe linker flags" FORCE ) SET( CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_MAINTAINER}" CACHE STRING "Debug exe linker flags" FORCE ) MARK_AS_ADVANCED( CMAKE_CXX_FLAGS_DEBUG CMAKE_C_FLAGS_DEBUG CMAKE_EXE_LINKER_FLAGS_DEBUG CMAKE_SHARED_LINKER_FLAGS_DEBUG ) SET(ALLOWED_BUILD_TYPES None Debug Release RelWithDebInfo MinSizeRel Maintainer) STRING(REGEX REPLACE ";" " " ALLOWED_BUILD_TYPES_PRETTY "${ALLOWED_BUILD_TYPES}") # Update the documentation string of CMAKE_BUILD_TYPE for GUIs SET( CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING "Choose the type of build, options are: ${ALLOWED_BUILD_TYPES_PRETTY}." FORCE ) if( CMAKE_BUILD_TYPE STREQUAL "" ) message( WARNING "CMAKE_BUILD_TYPE is not set, defaulting to Debug" ) set( CMAKE_BUILD_TYPE "Debug" ) endif( CMAKE_BUILD_TYPE STREQUAL "" ) list(FIND ALLOWED_BUILD_TYPES ${CMAKE_BUILD_TYPE} BUILD_TYPE_INDEX) if (BUILD_TYPE_INDEX EQUAL -1) message(SEND_ERROR "${CMAKE_BUILD_TYPE} is not a valid build type.") endif() nfs-ganesha-6.5/src/cmake/modules/000077500000000000000000000000001473756622300171145ustar00rootroot00000000000000nfs-ganesha-6.5/src/cmake/modules/FindASan.cmake000066400000000000000000000042011473756622300215360ustar00rootroot00000000000000# SPDX-License-Identifier: MIT # # The MIT License (MIT) # # Copyright (c) # 2013 Matthew Arsenault # 2015-2016 RWTH Aachen University, Federal Republic of Germany # # 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. option(SANITIZE_ADDRESS "Enable AddressSanitizer for sanitized targets." Off) set(FLAG_CANDIDATES # Clang 3.2+ use this version. The no-omit-frame-pointer option is optional. "-g -fsanitize=address -fno-omit-frame-pointer" "-g -fsanitize=address" # Older deprecated flag for ASan "-g -faddress-sanitizer" ) if (SANITIZE_ADDRESS AND (SANITIZE_THREAD OR SANITIZE_MEMORY)) message(FATAL_ERROR "AddressSanitizer is not compatible with " "ThreadSanitizer or MemorySanitizer.") endif () include(sanitize-helpers) if (SANITIZE_ADDRESS) sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "AddressSanitizer" "ASan") find_program(ASan_WRAPPER "asan-wrapper" PATHS ${CMAKE_MODULE_PATH}) mark_as_advanced(ASan_WRAPPER) endif () function (add_sanitize_address TARGET) if (NOT SANITIZE_ADDRESS) return() endif () sanitizer_add_flags(${TARGET} "AddressSanitizer" "ASan") endfunction () nfs-ganesha-6.5/src/cmake/modules/FindCEPHFS.cmake000066400000000000000000000213331473756622300216710ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # - Find CephFS # Find the Linux Trace Toolkit - next generation with associated includes path. # See http://ceph.org/ # # This module accepts the following optional variables: # CEPH_PREFIX = A hint on CEPHFS install path. # # This module defines the following variables: # CEPHFS_FOUND = Was CephFS found or not? # CEPHFS_LIBRARIES = The list of libraries to link to when using CephFS # CEPHFS_INCLUDE_DIR = The path to CephFS include directory # # On can set CEPH_PREFIX before using find_package(CephFS) and the # module with use the PATH as a hint to find CephFS. # # The hint can be given on the command line too: # cmake -DCEPH_PREFIX=/DATA/ERIC/CephFS /path/to/source if(CEPH_PREFIX) message(STATUS "FindCephFS: using PATH HINT: ${CEPH_PREFIX}") # Try to make the prefix override the normal paths find_path(CEPHFS_INCLUDE_DIR NAMES cephfs/libcephfs.h PATHS ${CEPH_PREFIX} PATH_SUFFIXES include NO_DEFAULT_PATH DOC "The CephFS include headers") find_path(CEPHFS_LIBRARY_DIR NAMES libcephfs.so PATHS ${CEPH_PREFIX} PATH_SUFFIXES lib/${CMAKE_LIBRARY_ARCHITECTURE} lib lib64 NO_DEFAULT_PATH DOC "The CephFS libraries") endif(CEPH_PREFIX) if (NOT CEPHFS_INCLUDE_DIR) find_path(CEPHFS_INCLUDE_DIR NAMES cephfs/libcephfs.h PATHS ${CEPH_PREFIX} PATH_SUFFIXES include DOC "The CephFS include headers") endif (NOT CEPHFS_INCLUDE_DIR) if (NOT CEPHFS_LIBRARY_DIR) find_path(CEPHFS_LIBRARY_DIR NAMES libcephfs.so PATHS ${CEPH_PREFIX} PATH_SUFFIXES lib/${CMAKE_LIBRARY_ARCHITECTURE} lib lib64 DOC "The CephFS libraries") endif (NOT CEPHFS_LIBRARY_DIR) message(STATUS "CEPHFS_INCLUDE_DIR = ${CEPHFS_INCLUDE_DIR}") message(STATUS "CEPHFS_LIBRARY_DIR = ${CEPHFS_LIBRARY_DIR}") find_library(CEPHFS_LIBRARY cephfs PATHS ${CEPHFS_LIBRARY_DIR} NO_DEFAULT_PATH) check_library_exists(cephfs ceph_ll_lookup ${CEPHFS_LIBRARY_DIR} CEPH_FS) if (NOT CEPH_FS) unset(CEPHFS_LIBRARY_DIR CACHE) unset(CEPHFS_INCLUDE_DIR CACHE) else (NOT CEPH_FS) check_library_exists(cephfs ceph_ll_mknod ${CEPHFS_LIBRARY_DIR} CEPH_FS_MKNOD) if(NOT CEPH_FS_MKNOD) message("Cannot find ceph_ll_mknod. Disabling CEPH fsal mknod method") set(USE_FSAL_CEPH_MKNOD OFF) else(CEPH_FS_MKNOD) set(USE_FSAL_CEPH_MKNOD ON) endif(NOT CEPH_FS_MKNOD) check_library_exists(cephfs ceph_ll_setlk ${CEPHFS_LIBRARY_DIR} CEPH_FS_SETLK) if(NOT CEPH_FS_SETLK) message("Cannot find ceph_ll_setlk. Disabling CEPH fsal lock2 method") set(USE_FSAL_CEPH_SETLK OFF) else(CEPH_FS_SETLK) set(USE_FSAL_CEPH_SETLK ON) endif(NOT CEPH_FS_SETLK) check_library_exists(cephfs ceph_ll_lookup_root ${CEPHFS_LIBRARY_DIR} CEPH_FS_LOOKUP_ROOT) if(NOT CEPH_FS_LOOKUP_ROOT) message("Cannot find ceph_ll_lookup_root. Working around it...") set(USE_FSAL_CEPH_LL_LOOKUP_ROOT OFF) else(NOT CEPH_FS_LOOKUP_ROOT) set(USE_FSAL_CEPH_LL_LOOKUP_ROOT ON) endif(NOT CEPH_FS_LOOKUP_ROOT) check_library_exists(cephfs ceph_ll_delegation ${CEPHFS_LIBRARY_DIR} CEPH_FS_DELEGATION) if(NOT CEPH_FS_DELEGATION) message("Cannot find ceph_ll_delegation. Disabling support for delegations.") set(USE_FSAL_CEPH_LL_DELEGATION OFF) else(NOT CEPH_FS_DELEGATION) set(USE_FSAL_CEPH_LL_DELEGATION ON) endif(NOT CEPH_FS_DELEGATION) check_library_exists(cephfs ceph_ll_sync_inode ${CEPHFS_LIBRARY_DIR} CEPH_FS_SYNC_INODE) if(NOT CEPH_FS_SYNC_INODE) message("Cannot find ceph_ll_sync_inode. SETATTR requests may be cached!") set(USE_FSAL_CEPH_LL_SYNC_INODE OFF) else(NOT CEPH_FS_SYNC_INODE) set(USE_FSAL_CEPH_LL_SYNC_INODE ON) endif(NOT CEPH_FS_SYNC_INODE) check_library_exists(cephfs ceph_ll_fallocate ${CEPHFS_LIBRARY_DIR} CEPH_FALLOCATE) if(NOT CEPH_FALLOCATE) message("Cannot find ceph_ll_fallocate. No ALLOCATE or DEALLOCATE support!") set(USE_CEPH_FALLOCATE OFF) else(NOT CEPH_FALLOCATE) set(USE_CEPH_LL_FALLOCATE ON) endif(NOT CEPH_FALLOCATE) check_library_exists(cephfs ceph_abort_conn ${CEPHFS_LIBRARY_DIR} CEPH_FS_ABORT_CONN) if(NOT CEPH_FS_ABORT_CONN) message("Cannot find ceph_abort_conn. FSAL_CEPH will not leave session intact on clean shutdown.") set(USE_FSAL_CEPH_ABORT_CONN OFF) else(NOT CEPH_FS_ABORT_CONN) set(USE_FSAL_CEPH_ABORT_CONN ON) endif(NOT CEPH_FS_ABORT_CONN) check_library_exists(cephfs ceph_start_reclaim ${CEPHFS_LIBRARY_DIR} CEPH_FS_RECLAIM_RESET) if(NOT CEPH_FS_RECLAIM_RESET) message("Cannot find ceph_start_reclaim. FSAL_CEPH will not kill off old sessions.") set(USE_FSAL_CEPH_RECLAIM_RESET OFF) else(NOT CEPH_FS_RECLAIM_RESET) set(USE_FSAL_CEPH_RECLAIM_RESET ON) endif(NOT CEPH_FS_RECLAIM_RESET) check_library_exists(cephfs ceph_select_filesystem ${CEPHFS_LIBRARY_DIR} CEPH_FS_GET_FS_CID) if(NOT CEPH_FS_GET_FS_CID) message("Cannot find ceph_set_filesystem. FSAL_CEPH will only mount the default filesystem.") set(USE_FSAL_CEPH_GET_FS_CID OFF) else(NOT CEPH_FS_GET_FS_CID) set(USE_FSAL_CEPH_GET_FS_CID ON) endif(NOT CEPH_FS_GET_FS_CID) check_library_exists(cephfs ceph_ll_register_callbacks ${CEPHFS_LIBRARY_DIR} CEPH_FS_REGISTER_CALLBACKS) if(NOT CEPH_FS_REGISTER_CALLBACKS) message("Cannot find ceph_ll_register_callbacks. FSAL_CEPH will not respond to cache pressure requests from the MDS.") set(USE_FSAL_CEPH_REGISTER_CALLBACKS OFF) else(NOT CEPH_FS_REGISTER_CALLBACKS) set(USE_FSAL_CEPH_REGISTER_CALLBACKS ON) endif(NOT CEPH_FS_REGISTER_CALLBACKS) check_library_exists(cephfs ceph_ll_lookup_vino ${CEPHFS_LIBRARY_DIR} CEPH_FS_LOOKUP_VINO) if(NOT CEPH_FS_LOOKUP_VINO) message("Cannot find ceph_ll_lookup_vino. FSAL_CEPH will not be able to reliably look up snap inodes by handle.") set(USE_FSAL_CEPH_LOOKUP_VINO OFF) else(NOT CEPH_FS_LOOKUP_VINO) set(USE_FSAL_CEPH_LOOKUP_VINO ON) endif(NOT CEPH_FS_LOOKUP_VINO) set(CMAKE_REQUIRED_INCLUDES ${CEPHFS_INCLUDE_DIR}) if (CMAKE_MAJOR_VERSION VERSION_EQUAL 3 AND CMAKE_MINOR_VERSION VERSION_GREATER 14) include(CheckSymbolExists) endif(CMAKE_MAJOR_VERSION VERSION_EQUAL 3 AND CMAKE_MINOR_VERSION VERSION_GREATER 14) check_symbol_exists(CEPH_STATX_INO "cephfs/libcephfs.h" CEPH_FS_CEPH_STATX) if(NOT CEPH_FS_CEPH_STATX) message("Cannot find CEPH_STATX_INO. Enabling backward compatibility for pre-ceph_statx APIs.") set(USE_FSAL_CEPH_STATX OFF) else(NOT CEPH_FS_CEPH_STATX) set(USE_FSAL_CEPH_STATX ON) endif(NOT CEPH_FS_CEPH_STATX) check_library_exists(cephfs ceph_ll_nonblocking_readv_writev ${CEPHFS_LIBRARY_DIR} CEPH_FS_NONBLOCKING_IO) if(NOT CEPH_FS_NONBLOCKING_IO) message("Cannot find ceph_ll_nonblocking_readv_writev. Disabling nonblocking I/O.") set(USE_FSAL_CEPH_FS_NONBLOCKING_IO OFF) else(NOT CEPH_FS_NONBLOCKING_IO) set(USE_FSAL_CEPH_FS_NONBLOCKING_IO ON) endif(NOT CEPH_FS_NONBLOCKING_IO) check_library_exists(cephfs ceph_ll_readv_writev ${CEPHFS_LIBRARY_DIR} CEPH_FS_ZEROCOPY_IO) if(NOT CEPH_FS_ZEROCOPY_IO) message("Cannot find ceph_ll_nonblocking_readv_writev. Disabling zero copy.") set(USE_FSAL_CEPH_FS_ZEROCOPY_IO OFF) else(NOT CEPH_FS_ZEROCOPY_IO) set(USE_FSAL_CEPH_FS_ZEROCOPY_IO ON) endif(NOT CEPH_FS_ZEROCOPY_IO) endif (NOT CEPH_FS) set(CEPHFS_LIBRARIES ${CEPHFS_LIBRARY}) message(STATUS "Found cephfs libraries: ${CEPHFS_LIBRARIES}") # handle the QUIETLY and REQUIRED arguments and set PRELUDE_FOUND to TRUE if # all listed variables are TRUE include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(CEPHFS REQUIRED_VARS CEPHFS_INCLUDE_DIR CEPHFS_LIBRARY_DIR) # VERSION FPHSA options not handled by CMake version < 2.8.2) # VERSION_VAR) mark_as_advanced(CEPHFS_INCLUDE_DIR) mark_as_advanced(CEPHFS_LIBRARY_DIR) mark_as_advanced(USE_FSAL_CEPH_MKNOD) mark_as_advanced(USE_FSAL_CEPH_SETLK) mark_as_advanced(USE_FSAL_CEPH_LL_LOOKUP_ROOT) mark_as_advanced(USE_FSAL_CEPH_STATX) nfs-ganesha-6.5/src/cmake/modules/FindCUnit.cmake000066400000000000000000000036741473756622300217530ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # Tries to find CUnit # # Usage of this module as follows: # # find_package(CUnit) # # Variables used by this module, they can change the default behaviour and need # to be set before calling find_package: # # CUNIT_PREFIX Set this variable to the root installation of # CUnit if the module has problems finding # the proper installation path. # # Variables defined by this module: # # CUNIT_FOUND System has CUnit libs/headers # CUNIT_LIBRARIES The CUnit libraries (tcmalloc & profiler) # CUNIT_INCLUDE_DIR The location of CUnit headers find_library(CUNIT_LIBRARIES NAMES cunit PATHS "${CUNIT_PREFIX}") find_path(CUNIT_INCLUDE_DIR NAMES CUnit/Basic.h HINTS ${CUNIT_PREFIX}/include) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( CUnit DEFAULT_MSG CUNIT_LIBRARIES CUNIT_INCLUDE_DIR) mark_as_advanced( CUNIT_PREFIX CUNIT_LIBRARIES CUNIT_INCLUDE_DIR) nfs-ganesha-6.5/src/cmake/modules/FindCaps.cmake000066400000000000000000000040701473756622300216060ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # Tries to find Capabilities libraries # # Usage of this module as follows: # # find_package(Caps) # # Variables used by this module, they can change the default behaviour and need # to be set before calling find_package: # # CAPS_PREFIX Set this variable to the root installation of # Caps if the module has problems finding # the proper installation path. # # Variables defined by this module: # # CAPS_FOUND System has Caps libs/headers # CAPS_LIBRARIES The Caps libraries (tcmalloc & profiler) # CAPS_INCLUDE_DIR The location of Caps headers find_library(CAPS NAMES cap PATHS "${CAPS_PREFIX}") check_library_exists( cap cap_set_proc "" HAVE_SET_PROC ) find_path(CAPS_INCLUDE_DIR NAMES sys/capability.h HINTS ${CAPS_PREFIX}/include) if (HAVE_SET_PROC) set(CAPS_LIBRARIES ${CAPS}) endif (HAVE_SET_PROC) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( Caps DEFAULT_MSG CAPS_LIBRARIES CAPS_INCLUDE_DIR) mark_as_advanced( CAPS_PREFIX CAPS_LIBRARIES CAPS_INCLUDE_DIR) nfs-ganesha-6.5/src/cmake/modules/FindEPOLL.cmake000066400000000000000000000034541473756622300216000ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # - Find EPOLL # # This module defines the following variables: # EPOLL_FOUND = Was EPOLL found or not? # # On can set EPOLL_PATH_HINT before using find_package(EPOLL) and the # module with use the PATH as a hint to find EPOLL. # # The hint can be given on the command line too: # cmake -DEPOLL_PATH_HINT=/DATA/ERIC/EPOLL /path/to/source # epoll is emulated on FreeBSD if (BSDBASED) set (EPOLL_FOUND ON) return () endif (BSDBASED) include(CheckIncludeFiles) include(CheckFunctionExists) check_include_files("sys/epoll.h" EPOLL_HEADER) check_function_exists(epoll_create EPOLL_FUNC) # handle the QUIETLY and REQUIRED arguments and set PRELUDE_FOUND to TRUE if # all listed variables are TRUE include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(EPOLL REQUIRED_VARS EPOLL_HEADER EPOLL_FUNC) nfs-ganesha-6.5/src/cmake/modules/FindExecInfo.cmake000066400000000000000000000030001473756622300224100ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- FIND_PATH(EXECINFO_INCLUDE_DIR execinfo.h) FIND_LIBRARY(EXECINFO_LIBRARY NAMES execinfo) IF (EXECINFO_INCLUDE_DIR AND EXECINFO_LIBRARY) SET(EXECINFO_FOUND TRUE) ENDIF (EXECINFO_INCLUDE_DIR AND EXECINFO_LIBRARY) IF (EXECINFO_FOUND) IF (NOT EXECINFO_FIND_QUIETLY) MESSAGE(STATUS "Found execinfo library: ${EXECINFO_LIBRARY}") ENDIF (NOT EXECINFO_FIND_QUIETLY) ELSE (EXECINFO_FOUND) IF (ExecInfo_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could not find libexecinfo") ENDIF (ExecInfo_FIND_REQUIRED) ENDIF (EXECINFO_FOUND) nfs-ganesha-6.5/src/cmake/modules/FindGTest.cmake000066400000000000000000000040421473756622300217450ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # Tries to find GTest. # # Usage of this module as follows: # # find_package(GTest) # # Variables used by this module, they can change the default behaviour and need # to be set before calling find_package: # # GTEST_PREFIX Set this variable to the root installation of # GTest if the module has problems finding # the proper installation path. # # Variables defined by this module: # # GTEST_FOUND System has GTest libs/headers # GTEST_LIBRARIES The GTest libraries (tcmalloc & profiler) # GTEST_INCLUDE_DIR The location of GTest headers find_library(GTEST NAMES gtest PATHS "${GTEST_PREFIX}") find_library(GTEST_MAIN NAMES gtest_main PATHS "${GTEST_PREFIX}") find_path(GTEST_INCLUDE_DIR NAMES gtest/gtest.h HINTS ${GTEST_PREFIX}/include) set(GTEST_LIBRARIES ${GTEST} ${GTEST_MAIN}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( GTest DEFAULT_MSG GTEST_LIBRARIES GTEST_INCLUDE_DIR) mark_as_advanced( GTEST_PREFIX GTEST_LIBRARIES GTEST_INCLUDE_DIR) nfs-ganesha-6.5/src/cmake/modules/FindGperftools.cmake000066400000000000000000000042101473756622300230400ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # Tries to find Gperftools. # # Usage of this module as follows: # # find_package(Gperftools) # # Variables used by this module, they can change the default behaviour and need # to be set before calling find_package: # # Gperftools_ROOT_DIR Set this variable to the root installation of # Gperftools if the module has problems finding # the proper installation path. # # Variables defined by this module: # # GPERFTOOLS_FOUND System has Gperftools libs/headers # GPERFTOOLS_LIBRARIES The Gperftools libraries (tcmalloc & profiler) # GPERFTOOLS_INCLUDE_DIR The location of Gperftools headers find_library(GPERFTOOLS_PROFILER NAMES profiler HINTS ${Gperftools_ROOT_DIR}/lib) find_path(GPERFTOOLS_INCLUDE_DIR NAMES gperftools/heap-profiler.h HINTS ${Gperftools_ROOT_DIR}/include) set(GPERFTOOLS_LIBRARIES ${GPERFTOOLS_PROFILER}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( Gperftools DEFAULT_MSG GPERFTOOLS_LIBRARIES GPERFTOOLS_INCLUDE_DIR) mark_as_advanced( Gperftools_ROOT_DIR GPERFTOOLS_PROFILER GPERFTOOLS_LIBRARIES GPERFTOOLS_INCLUDE_DIR) nfs-ganesha-6.5/src/cmake/modules/FindJeMalloc.cmake000066400000000000000000000041311473756622300224040ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause # # - Find JeMalloc library # Find the native JeMalloc includes and library # This module defines # JEMALLOC_INCLUDE_DIRS, where to find jemalloc.h, Set when # JEMALLOC_INCLUDE_DIR is found. # JEMALLOC_LIBRARIES, libraries to link against to use JeMalloc. # JEMALLOC_ROOT_DIR, The base directory to search for JeMalloc. # This can also be an environment variable. # JEMALLOC_FOUND, If false, do not try to use JeMalloc. # # also defined, but not for general use are # JEMALLOC_LIBRARY, where to find the JeMalloc library. #============================================================================= # Copyright 2011 Blender Foundation. # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.txt for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. #============================================================================= # If JEMALLOC_ROOT_DIR was defined in the environment, use it. IF(NOT JEMALLOC_ROOT_DIR AND NOT $ENV{JEMALLOC_ROOT_DIR} STREQUAL "") SET(JEMALLOC_ROOT_DIR $ENV{JEMALLOC_ROOT_DIR}) ENDIF() SET(_jemalloc_SEARCH_DIRS ${JEMALLOC_ROOT_DIR} /usr/local /sw # Fink /opt/local # DarwinPorts /opt/csw # Blastwave ) FIND_PATH(JEMALLOC_INCLUDE_DIR NAMES jemalloc.h HINTS ${_jemalloc_SEARCH_DIRS} PATH_SUFFIXES include/jemalloc ) FIND_LIBRARY(JEMALLOC_LIBRARY NAMES jemalloc HINTS ${_jemalloc_SEARCH_DIRS} PATH_SUFFIXES lib64 lib ) # handle the QUIETLY and REQUIRED arguments and set JEMALLOC_FOUND to TRUE if # all listed variables are TRUE INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(JeMalloc DEFAULT_MSG JEMALLOC_LIBRARY JEMALLOC_INCLUDE_DIR) IF(JEMALLOC_FOUND) SET(JEMALLOC_LIBRARIES ${JEMALLOC_LIBRARY}) SET(JEMALLOC_INCLUDE_DIRS ${JEMALLOC_INCLUDE_DIR}) ENDIF(JEMALLOC_FOUND) MARK_AS_ADVANCED( JEMALLOC_INCLUDE_DIR JEMALLOC_LIBRARY ) nfs-ganesha-6.5/src/cmake/modules/FindKrb5.cmake000066400000000000000000000146431473756622300215320ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # - Find kerberos 5 # Find the native Kerberos 5 headers and libraries. # KRB5_INCLUDE_DIRS - where to find krb5.h, etc. # KRB5_LIBRARIES - List of libraries when using kerberos 5. # KRB5_FOUND - True if kerberos 5 found. # KRB5 modules may be specified as components for this find module. # Modules may be listed by running "krb5-config". Modules include: # krb5 Kerberos 5 application # gssapi GSSAPI application with Kerberos 5 bindings # krb4 Kerberos 4 application # kadm-client Kadmin client # kadm-server Kadmin server # kdb Application that accesses the kerberos database # Typical usage: # FIND_PACKAGE(KRB5 REQUIRED gssapi) # First find the config script from which to obtain other values. IF(KRB5_PREFIX) FIND_PROGRAM(KRB5_C_CONFIG NAMES krb5-config PATHS ${KRB5_PREFIX} NO_SYSTEM_ENVIRONMENT_PATH NO_DEFAULT_PATH ) ENDIF(KRB5_PREFIX) FIND_PROGRAM(KRB5_C_CONFIG NAMES krb5-config) MESSAGE(STATUS "found krb5-config here ${KRB5_C_CONFIG}") # Check whether we found anything. IF(KRB5_C_CONFIG) SET(KRB5_FOUND 1) ELSE(KRB5_C_CONFIG) SET(KRB5_FOUND 0) ENDIF(KRB5_C_CONFIG) # Lookup the include directories needed for the components requested. IF(KRB5_FOUND) # Use the newer EXECUTE_PROCESS command if it is available. IF(COMMAND EXECUTE_PROCESS) EXECUTE_PROCESS( COMMAND ${KRB5_C_CONFIG} ${KRB5_FIND_COMPONENTS} --cflags OUTPUT_VARIABLE KRB5_C_CONFIG_CFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE KRB5_C_CONFIG_RESULT ) ELSE(COMMAND EXECUTE_PROCESS) EXEC_PROGRAM(${KRB5_C_CONFIG} ARGS "${KRB5_FIND_COMPONENTS} --cflags" OUTPUT_VARIABLE KRB5_C_CONFIG_CFLAGS RETURN_VALUE KRB5_C_CONFIG_RESULT ) ENDIF(COMMAND EXECUTE_PROCESS) # Parse the include flags. IF("${KRB5_C_CONFIG_RESULT}" MATCHES "^0$") # Convert the compile flags to a CMake list. STRING(REGEX REPLACE " +" ";" KRB5_C_CONFIG_CFLAGS "${KRB5_C_CONFIG_CFLAGS}") # Look for -I options. SET(KRB5_INCLUDE_DIRS) FOREACH(flag ${KRB5_C_CONFIG_CFLAGS}) IF("${flag}" MATCHES "^-I") STRING(REGEX REPLACE "^-I" "" DIR "${flag}") FILE(TO_CMAKE_PATH "${DIR}" DIR) SET(KRB5_INCLUDE_DIRS ${KRB5_INCLUDE_DIRS} "${DIR}") ENDIF("${flag}" MATCHES "^-I") ENDFOREACH(flag) ELSE("${KRB5_C_CONFIG_RESULT}" MATCHES "^0$") MESSAGE("Error running ${KRB5_C_CONFIG}: [${KRB5_C_CONFIG_RESULT}]") SET(KRB5_FOUND 0) ENDIF("${KRB5_C_CONFIG_RESULT}" MATCHES "^0$") ENDIF(KRB5_FOUND) SET(KRB5_INCLUDE_DIRS "${KRB5_PREFIX}/include" ${KRB5_INCLUDE_DIRS}) # Lookup the libraries needed for the components requested. IF(KRB5_FOUND) # Use the newer EXECUTE_PROCESS command if it is available. IF(COMMAND EXECUTE_PROCESS) EXECUTE_PROCESS( COMMAND ${KRB5_C_CONFIG} ${KRB5_FIND_COMPONENTS} --libs OUTPUT_VARIABLE KRB5_C_CONFIG_LIBS OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE KRB5_C_CONFIG_RESULT ) ELSE(COMMAND EXECUTE_PROCESS) EXEC_PROGRAM(${KRB5_C_CONFIG} ARGS "${KRB5_FIND_COMPONENTS} --libs" OUTPUT_VARIABLE KRB5_C_CONFIG_LIBS RETURN_VALUE KRB5_C_CONFIG_RESULT ) ENDIF(COMMAND EXECUTE_PROCESS) # Parse the library names and directories. IF("${KRB5_C_CONFIG_RESULT}" MATCHES "^0$") STRING(REGEX REPLACE " +" ";" KRB5_C_CONFIG_LIBS "${KRB5_C_CONFIG_LIBS}") # Look for -L flags for directories and -l flags for library names. SET(KRB5_LIBRARY_DIRS) SET(KRB5_LIBRARY_NAMES) FOREACH(flag ${KRB5_C_CONFIG_LIBS}) IF("${flag}" MATCHES "^-L") STRING(REGEX REPLACE "^-L" "" DIR "${flag}") FILE(TO_CMAKE_PATH "${DIR}" DIR) SET(KRB5_LIBRARY_DIRS ${KRB5_LIBRARY_DIRS} "${DIR}") ELSEIF("${flag}" MATCHES "^-l") STRING(REGEX REPLACE "^-l" "" NAME "${flag}") SET(KRB5_LIBRARY_NAMES ${KRB5_LIBRARY_NAMES} "${NAME}") ENDIF("${flag}" MATCHES "^-L") ENDFOREACH(flag) # add gssapi_krb5 (MIT) SET(KRB5_LIBRARY_NAMES ${KRB5_LIBRARY_NAMES} "gssapi_krb5") # Search for each library needed using the directories given. FOREACH(name ${KRB5_LIBRARY_NAMES}) # Look for this library. FIND_LIBRARY(KRB5_${name}_LIBRARY NAMES ${name} PATHS ${KRB5_LIBRARY_DIRS} NO_DEFAULT_PATH ) FIND_LIBRARY(KRB5_${name}_LIBRARY NAMES ${name}) MARK_AS_ADVANCED(KRB5_${name}_LIBRARY) # If any library is not found then the whole package is not found. IF(NOT KRB5_${name}_LIBRARY) SET(KRB5_FOUND 0) ENDIF(NOT KRB5_${name}_LIBRARY) # Build an ordered list of all the libraries needed. SET(KRB5_LIBRARIES ${KRB5_LIBRARIES} "${KRB5_${name}_LIBRARY}") ENDFOREACH(name) ELSE("${KRB5_C_CONFIG_RESULT}" MATCHES "^0$") MESSAGE("Error running ${KRB5_C_CONFIG}: [${KRB5_C_CONFIG_RESULT}]") SET(KRB5_FOUND 0) ENDIF("${KRB5_C_CONFIG_RESULT}" MATCHES "^0$") ENDIF(KRB5_FOUND) # Report the results. IF(NOT KRB5_FOUND) SET(KRB5_DIR_MESSAGE "KRB5 was not found. Make sure the entries KRB5_* are set.") IF(NOT KRB5_FIND_QUIETLY) MESSAGE(STATUS "${KRB5_DIR_MESSAGE}") ELSE(NOT KRB5_FIND_QUIETLY) IF(KRB5_FIND_REQUIRED) MESSAGE(FATAL_ERROR "${KRB5_DIR_MESSAGE}") ENDIF(KRB5_FIND_REQUIRED) ENDIF(NOT KRB5_FIND_QUIETLY) ELSE(NOT KRB5_FOUND) MESSAGE(STATUS "Found kerberos 5 headers: ${KRB5_INCLUDE_DIRS}") MESSAGE(STATUS "Found kerberos 5 libs: ${KRB5_LIBRARIES}") ENDIF(NOT KRB5_FOUND) nfs-ganesha-6.5/src/cmake/modules/FindLSB_release.cmake000066400000000000000000000071371473756622300230470ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause # # - Find Linux Standard Base Release Tools # This module defines the following variables: # LSB_RELEASE_EXECUTABLE - path to lsb_release program # LSB_RELEASE_VERSION_SHORT - Output of "lsb_release -vs" # LSB_RELEASE_ID_SHORT - Output of "lsb_release -is" # LSB_RELEASE_DESCRIPTION_SHORT - Output of "lsb_release -ds" # LSB_RELEASE_RELEASE_SHORT - Output of "lsb_release -rs" # LSB_RELEASE_CODENAME_SHORT - Output of "lsb_release -cs" # #---------------------------------------------------------------------------- # Copyright (c) 2012, Ben Morgan, University of Warwick # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * 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. # * Neither the name of Ben Morgan, or the University of Warwick nor the # names of its contributors may be used to endorse or promote products # derived from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 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. #---------------------------------------------------------------------------- find_program(LSB_RELEASE_EXECUTABLE lsb_release DOC "Linux Standard Base and Distribution command line query client") mark_as_advanced(LSB_RELEASE_EXECUTABLE) if(LSB_RELEASE_EXECUTABLE) # Extract the standard information in short format into CMake variables # - Version (strictly a colon separated list, kept as string for now) execute_process(COMMAND ${LSB_RELEASE_EXECUTABLE} -vs OUTPUT_VARIABLE LSB_RELEASE_VERSION_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE ) # - Distributor ID execute_process(COMMAND ${LSB_RELEASE_EXECUTABLE} -is OUTPUT_VARIABLE LSB_RELEASE_ID_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE ) # - Description execute_process(COMMAND ${LSB_RELEASE_EXECUTABLE} -ds OUTPUT_VARIABLE LSB_RELEASE_DESCRIPTION_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE ) # Description might be quoted, so strip out if they're there string(REPLACE "\"" "" LSB_RELEASE_DESCRIPTION_SHORT "${LSB_RELEASE_DESCRIPTION_SHORT}") # - Release execute_process(COMMAND ${LSB_RELEASE_EXECUTABLE} -rs OUTPUT_VARIABLE LSB_RELEASE_RELEASE_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE ) # - Codename execute_process(COMMAND ${LSB_RELEASE_EXECUTABLE} -cs OUTPUT_VARIABLE LSB_RELEASE_CODENAME_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE ) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(LSB_release DEFAULT_MSG LSB_RELEASE_EXECUTABLE) nfs-ganesha-6.5/src/cmake/modules/FindLTTng.cmake000066400000000000000000000105311473756622300217070ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # - Find LTTng # Find the Linux Trace Toolkit - next generation with associated includes path. # See http://lttng.org/ # # This module accepts the following optional variables: # LTTNG_PATH_HINT = A hint on LTTNG install path. # # This module defines the following variables: # LTTNG_FOUND = Was LTTng found or not? # LTTNG_EXECUTABLE = The path to lttng command # LTTNG_LIBRARIES = The list of libraries to link to when using LTTng # LTTNG_INCLUDE_DIR = The path to LTTng include directory # # On can set LTTNG_PATH_HINT before using find_package(LTTng) and the # module with use the PATH as a hint to find LTTng. # # The hint can be given on the command line too: # cmake -DLTTNG_PATH_HINT=/DATA/ERIC/LTTng /path/to/source if(LTTNG_PATH_HINT) message(STATUS "FindLTTng: using PATH HINT: ${LTTNG_PATH_HINT}") else() set(LTTNG_PATH_HINT) endif() #One can add his/her own builtin PATH. #FILE(TO_CMAKE_PATH "/DATA/ERIC/LTTng" MYPATH) #list(APPEND LTTNG_PATH_HINT ${MYPATH}) find_path(LTTNG_INCLUDE_DIR NAMES lttng/tracepoint.h PATHS ${LTTNG_PATH_HINT} PATH_SUFFIXES include DOC "The LTTng include headers") find_path(LTTNG_LIBRARY_DIR NAMES liblttng-ust.so PATHS ${LTTNG_PATH_HINT} PATH_SUFFIXES lib/${CMAKE_LIBRARY_ARCHITECTURE} lib lib64 DOC "The LTTng libraries") find_library(LTTNG_UST_LIBRARY lttng-ust PATHS ${LTTNG_LIBRARY_DIR}) find_library(UUID_LIBRARY uuid) find_path(LTTNG_LIBRARY_DIR NAMES liblttng-ust-common.so PATHS ${LTTNG_PATH_HINT} PATH_SUFFIXES lib/${CMAKE_LIBRARY_ARCHITECTURE} lib lib64 DOC "The LTTng common library") find_library(LTTNG_UST_COMMON_LIBRARY lttng-ust-common PATHS ${LTTNG_LIBRARY_DIR}) set(LTTNG_LIBRARIES ${LTTNG_UST_LIBRARY} ${UUID_LIBRARY}) # lttng-ust-common only exists in some distributions, we add it when we can find it if(LTTNG_UST_COMMON_LIBRARY) set(LTTNG_LIBRARIES ${LTTNG_LIBRARIES} ${LTTNG_UST_COMMON_LIBRARY}) endif() find_path(LTTNG_CTL_INCLUDE_DIR NAMES lttng/lttng.h PATHS ${LTTNG_PATH_HINT} PATH_SUFFIXES include DOC "The LTTng CTL include headers") find_path(LTTNG_CTL_LIBRARY_DIR NAMES liblttng-ctl.so PATHS ${LTTNG_PATH_HINT} PATH_SUFFIXES lib/${CMAKE_LIBRARY_ARCHITECTURE} lib lib64 DOC "The LTTng libraries") find_library(LTTNG_CTL_LIBRARY lttng-ctl PATHS ${LTTNG_CTL_LIBRARY_DIR}) set(LTTNG_CTL_LIBRARIES ${LTTNG_CTL_LIBRARY}) message(STATUS "Looking for lttng executable...") set(LTTNG_NAMES "lttng;lttng-ctl") # FIND_PROGRAM twice using NO_DEFAULT_PATH on first shot find_program(LTTNG_EXECUTABLE NAMES ${LTTNG_NAMES} PATHS ${LTTNG_PATH_HINT}/bin NO_DEFAULT_PATH DOC "The LTTNG command line tool") find_program(LEX_PROGRAM NAMES ${LTTNG_NAMES} PATHS ${LTTNG_PATH_HINT}/bin DOC "The LTTNG command line tool") # handle the QUIETLY and REQUIRED arguments and set PRELUDE_FOUND to TRUE if # all listed variables are TRUE include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS( LTTng REQUIRED_VARS LTTNG_INCLUDE_DIR LTTNG_LIBRARY_DIR LTTNG_UST_LIBRARY UUID_LIBRARY LTTNG_CTL_INCLUDE_DIR LTTNG_CTL_LIBRARY_DIR ) # VERSION FPHSA options not handled by CMake version < 2.8.2) # VERSION_VAR) mark_as_advanced(LTTNG_INCLUDE_DIR) mark_as_advanced(LTTNG_LIBRARY_DIR) nfs-ganesha-6.5/src/cmake/modules/FindLibACL.cmake000066400000000000000000000030221473756622300217420ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- check_include_files("sys/acl.h" HAVE_SYS_ACL_H) check_include_files("acl/libacl.h" HAVE_ACL_LIBACL_H) find_library(LIBACL_LIBRARY NAMES acl) if(HAVE_ACL_LIBACL_H) # Partially repeats above check, but is a useful sanity check check_library_exists(acl acl_get_file "" HAVE_LIBACL) endif(HAVE_ACL_LIBACL_H) if(HAVE_SYS_ACL_H) # Available on FreeBSD (and perhaps others) - replace on Linux check_symbol_exists(acl_get_fd_np sys/acl.h HAVE_ACL_GET_FD_NP) check_symbol_exists(acl_set_fd_np sys/acl.h HAVE_ACL_SET_FD_NP) endif(HAVE_SYS_ACL_H) nfs-ganesha-6.5/src/cmake/modules/FindMSan.cmake000066400000000000000000000044251473756622300215620ustar00rootroot00000000000000# SPDX-License-Identifier: MIT # # The MIT License (MIT) # # Copyright (c) # 2013 Matthew Arsenault # 2015-2016 RWTH Aachen University, Federal Republic of Germany # # 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. option(SANITIZE_MEMORY "Enable MemorySanitizer for sanitized targets." Off) set(FLAG_CANDIDATES "-g -fsanitize=memory" ) include(sanitize-helpers) if (SANITIZE_MEMORY) if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux") message(WARNING "MemorySanitizer disabled for target ${TARGET} because " "MemorySanitizer is supported for Linux systems only.") set(SANITIZE_MEMORY Off CACHE BOOL "Enable MemorySanitizer for sanitized targets." FORCE) elseif (NOT ${CMAKE_SIZEOF_VOID_P} EQUAL 8) message(WARNING "MemorySanitizer disabled for target ${TARGET} because " "MemorySanitizer is supported for 64bit systems only.") set(SANITIZE_MEMORY Off CACHE BOOL "Enable MemorySanitizer for sanitized targets." FORCE) else () sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "MemorySanitizer" "MSan") endif () endif () function (add_sanitize_memory TARGET) if (NOT SANITIZE_MEMORY) return() endif () sanitizer_add_flags(${TARGET} "MemorySanitizer" "MSan") endfunction () nfs-ganesha-6.5/src/cmake/modules/FindNTIRPC.cmake000066400000000000000000000071471473756622300217270ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # - Find NTIRPC # Find the New TIRPC RPC library # # This module accepts the following optional variables: # NTIRPC_PREFIX = A hint on NTIRPC install path. # # This module defines the following variables: # NTIRPC_FOUND = Was NTIRPC found or not? # NTIRPC_LIBRARY = The list of libraries to link to when using NTIRPC # NTIRPC_INCLUDE_DIR = The path to NTIRPC include directory(s) # # On can set NTIRPC_PREFIX before using find_package(NTIRPC) and the # module with use the PATH as a hint to find NTIRPC. # # The hint can be given on the command line too: # cmake -DNTIRPC_PREFIX=/DATA/ERIC/NTIRPC /path/to/source include(LibFindMacros) if(NTIRPC_PREFIX) message(STATUS "FindNTIRPC: using PATH HINT: ${NTIRPC_PREFIX}") # Try to make the prefix override the normal paths find_path(NTIRPC_INCLUDE_DIR NAMES rpc/xdr.h PATHS ${NTIRPC_PREFIX} PATH_SUFFIXES include/ntirpc NO_DEFAULT_PATH DOC "The NTIRPC include headers") message("NTIRPC_INCLUDE_DIR ${NTIRPC_INCLUDE_DIR}") find_path(NTIRPC_LIBRARY_DIR NAMES libntirpc.so PATHS ${NTIRPC_PREFIX} PATH_SUFFIXES lib/${CMAKE_LIBRARY_ARCHITECTURE} lib lib64 NO_DEFAULT_PATH DOC "The NTIRPC libraries") endif() if (NOT NTIRPC_INCLUDE_DIR) find_path(NTIRPC_INCLUDE_DIR NAMES rpc/xdr.h PATHS ${NTIRPC_PREFIX} PATH_SUFFIXES include/ntirpc DOC "The NTIRPC include headers") endif (NOT NTIRPC_INCLUDE_DIR) if (NOT NTIRPC_LIBRARY_DIR) find_path(NTIRPC_LIBRARY_DIR NAMES libntirpc.so PATHS ${NTIRPC_PREFIX} PATH_SUFFIXES lib/${CMAKE_LIBRARY_ARCHITECTURE} lib lib64 DOC "The NTIRPC libraries") endif (NOT NTIRPC_LIBRARY_DIR) find_library(NTIRPC_LIBRARY ntirpc PATHS ${NTIRPC_LIBRARY_DIR} NO_DEFAULT_PATH) find_library(NTIRPC_TRACEPOINTS ntirpc_tracepoints PATHS ${NTIRPC_LIBRARY_DIR} NO_DEFAULT_PATH) find_library(NTIRPC_LTTNG ntirpc_lttng PATHS ${NTIRPC_LIBRARY_DIR} NO_DEFAULT_PATH) set(NTIRPC_VERSION_HEADER "${NTIRPC_INCLUDE_DIR}/version.h") if (EXISTS ${NTIRPC_VERSION_HEADER}) file(READ "${NTIRPC_VERSION_HEADER}" header) string(REGEX REPLACE ".*#[ \t]*define[ \t]*NTIRPC_VERSION[ \t]*\"([^\n]*)\".*" "\\1" match "${header}") set(NTIRPC_VERSION "${match}") else() set(NTIRPC_VERSION "0.0.0") endif() # handle the QUIETLY and REQUIRED arguments and set PRELUDE_FOUND to TRUE if # all listed variables are TRUE include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(NTIRPC REQUIRED_VARS NTIRPC_INCLUDE_DIR NTIRPC_LIBRARY VERSION_VAR NTIRPC_VERSION) # VERSION FPHSA options not handled by CMake version < 2.8.2) # VERSION_VAR) mark_as_advanced(NTIRPC_INCLUDE_DIR) mark_as_advanced(NTIRPC_LIBRARY) nfs-ganesha-6.5/src/cmake/modules/FindNfsIdmap.cmake000066400000000000000000000030011473756622300224120ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- FIND_PATH(NFSIDMAP_INCLUDE_DIR nfsidmap.h) FIND_LIBRARY(NFSIDMAP_LIBRARY NAMES nfsidmap) IF (NFSIDMAP_INCLUDE_DIR AND NFSIDMAP_LIBRARY) SET(NFSIDMAP_FOUND TRUE) ENDIF (NFSIDMAP_INCLUDE_DIR AND NFSIDMAP_LIBRARY) IF (NFSIDMAP_FOUND) IF (NOT NFSIDMAP_FIND_QUIETLY) MESSAGE(STATUS "Found nfs idmap library: ${NFSIDMAP_LIBRARY}") ENDIF (NOT NFSIDMAP_FIND_QUIETLY) ELSE (NFSIDMAP_FOUND) IF (NfsIdmap_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could not find libnfsidmap") ENDIF (NfsIdmap_FIND_REQUIRED) ENDIF (NFSIDMAP_FOUND) nfs-ganesha-6.5/src/cmake/modules/FindRADOS.cmake000066400000000000000000000064071473756622300215760ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # - Find RADOS # This module accepts the following optional variables: # RADOS_PREFIX = A hint on RADOS install path. # # This module defines the following variables: # RADOS_FOUND = Was RADOS found or not? # RADOS_LIBRARIES = The list of libraries to link to when using RADOS # RADOS_INCLUDE_DIR = The path to RADOS include directory # # On can set RADOS_PREFIX before using find_package(RADOS) and the # module with use the PATH as a hint to find RADOS. # # The hint can be given on the command line too: # cmake -DRADOS_PREFIX=/DATA/ERIC/RADOS /path/to/source if(RADOS_PREFIX) message(STATUS "FindRADOS: using PATH HINT: ${RADOS_PREFIX}") # Try to make the prefix override the normal paths find_path(RADOS_INCLUDE_DIR NAMES rados/librados.h PATHS ${RADOS_PREFIX} PATH_SUFFIXES include NO_DEFAULT_PATH DOC "The RADOS include headers") find_path(RADOS_LIBRARY_DIR NAMES librados.so PATHS ${RADOS_PREFIX} PATH_SUFFIXES lib/${CMAKE_LIBRARY_ARCHITECTURE} lib lib64 NO_DEFAULT_PATH DOC "The RADOS libraries") endif(RADOS_PREFIX) if (NOT RADOS_INCLUDE_DIR) find_path(RADOS_INCLUDE_DIR NAMES rados/librados.h PATHS ${RADOS_PREFIX} PATH_SUFFIXES include DOC "The RADOS include headers") endif (NOT RADOS_INCLUDE_DIR) if (NOT RADOS_LIBRARY_DIR) find_path(RADOS_LIBRARY_DIR NAMES librados.so PATHS ${RADOS_PREFIX} PATH_SUFFIXES lib/${CMAKE_LIBRARY_ARCHITECTURE} lib lib64 DOC "The RADOS libraries") endif (NOT RADOS_LIBRARY_DIR) find_library(RADOS_LIBRARY rados PATHS ${RADOS_LIBRARY_DIR} NO_DEFAULT_PATH) check_library_exists(rados rados_read_op_omap_get_vals2 ${RADOS_LIBRARY_DIR} RADOSLIB) if (NOT RADOSLIB) unset(RADOS_LIBRARY_DIR CACHE) unset(RADOS_INCLUDE_DIR CACHE) endif (NOT RADOSLIB) set(RADOS_LIBRARIES ${RADOS_LIBRARY}) message(STATUS "Found rados libraries: ${RADOS_LIBRARIES}") # handle the QUIETLY and REQUIRED arguments and set PRELUDE_FOUND to TRUE if # all listed variables are TRUE include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(RADOS REQUIRED_VARS RADOS_INCLUDE_DIR RADOS_LIBRARY_DIR ) # VERSION FPHSA options not handled by CMake version < 2.8.2) # VERSION_VAR) mark_as_advanced(RADOS_INCLUDE_DIR) mark_as_advanced(RADOS_LIBRARY_DIR) nfs-ganesha-6.5/src/cmake/modules/FindRDMA.cmake000066400000000000000000000072521473756622300214500ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # - Find RDMA # Find the New TIRPC RPC library # # This module accepts the following optional variables: # RDMA_PATH_HINT = A hint on RDMA install path. # # This module defines the following variables: # RDMA_FOUND = Was RDMA found or not? # RDMA_LIBRARY = The list of libraries to link to when using RDMA # RDMA_INCLUDE_DIR = The path to RDMA include directory(s) # # One can set RDMA_PATH_HINT before using find_package(RDMA) and the # module with use the PATH as a hint to find RDMA. # Alternatively, one can set LIBIBVERBS_PREFIX and LIBRDMACM_PREFIX to the individual # hints for those libraries. # # The hint can be given on the command line too: # cmake -DRDMA_PATH_HINT=/DATA/ERIC/RDMA /path/to/source include(LibFindMacros) message(STATUS "Looking for IBVERBS & RDMACM ...") # ibverbs if (LIBIBVERBS_PREFIX) set(IBVERBS_PKGCONF_INCLUDE_DIRS ${LIBIBVERBS_PREFIX}/include) set(IBVERBS_PKGCONF_LIBRARY_DIRS ${LIBIBVERBS_PREFIX}/lib64 ${LIBIBVERBS_PREFIX}/lib) else (LIBIBVERBS_PREFIX) set(IBVERBS_PKGCONF_INCLUDE_DIRS ${RDMA_PATH_HINT}/include) set(IBVERBS_PKGCONF_LIBRARY_DIRS ${RDMA_PATH_HINT}/lib64 ${RDMA_PATH_HINT}/lib) endif (LIBIBVERBS_PREFIX) libfind_pkg_detect(IBVERBS libibverbs FIND_PATH infiniband/verbs.h FIND_LIBRARY ibverbs) libfind_process(IBVERBS) # rdmacm if (LIBRDMACM_PREFIX) set(RDMACM_PKGCONF_INCLUDE_DIRS ${LIBRDMACM_PREFIX}/include) set(RDMACM_PKGCONF_LIBRARY_DIRS ${LIBRDMACM_PREFIX}/lib64 ${LIBRDMACM_PREFIX}/lib) else (LIBRDMACM_PREFIX) set(RDMACM_PKGCONF_INCLUDE_DIRS ${RDMA_PATH_HINT}/include) set(RDMACM_PKGCONF_LIBRARY_DIRS ${RDMA_PATH_HINT}/lib64 ${RDMA_PATH_HINT}/lib) endif (LIBRDMACM_PREFIX) libfind_pkg_detect(RDMACM librdmacm FIND_PATH rdma/rdma_cma.h FIND_LIBRARY rdmacm) libfind_process(RDMACM) if (IBVERBS_FOUND AND RDMACM_FOUND) set(RDMA_FOUND true) set(RDMA_LIBRARY ${IBVERBS_LIBRARY} ${RDMACM_LIBRARY}) set(RDMA_INCLUDE_DIR ${IBVERBS_INCLUDE_DIR} ${RDMACM_INCLUDE_DIR}) message(STATUS "Found RDMA_LIBRARY=${RDMA_LIBRARY} RDMA_INCLUDE_DIR=${RDMA_INCLUDE_DIR}") else (IBVERBS_FOUND AND RDMACM_FOUND) set(RDMA_NOTFOUND true) endif (IBVERBS_FOUND AND RDMACM_FOUND) #if (RDMA_LIBRARY) #libfind_version_header(RDMA version.h RDMA_VERSION) #endif (RDMA_LIBRARY) # handle the QUIETLY and REQUIRED arguments and set PRELUDE_FOUND to TRUE if # all listed variables are TRUE include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(RDMA REQUIRED_VARS RDMA_INCLUDE_DIR RDMA_LIBRARY VERSION_VAR RDMA_VERSION) # VERSION FPHSA options not handled by CMake version < 2.8.2) # VERSION_VAR) mark_as_advanced(RDMA_INCLUDE_DIR) mark_as_advanced(RDMA_LIBRARY) nfs-ganesha-6.5/src/cmake/modules/FindRGW.cmake000066400000000000000000000110501473756622300213530ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # - Find RGW # Find the Linux Trace Toolkit - next generation with associated includes path. # See http://ceph.org/ # # This module accepts the following optional variables: # RGW_PREFIX = A hint on RGW install path. # # This module defines the following variables: # RGW_FOUND = Was RGW found or not? # RGW_LIBRARIES = The list of libraries to link to when using RGW # RGW_INCLUDE_DIR = The path to RGW include directory # # On can set RGW_PREFIX before using find_package(RGW) and the # module with use the PATH as a hint to find RGW. # # The hint can be given on the command line too: # cmake -DRGW_PREFIX=/DATA/ERIC/RGW /path/to/source if(RGW_PREFIX) message(STATUS "FindRGW: using PATH HINT: ${RGW_PREFIX}") # Try to make the prefix override the normal paths find_path(RGW_INCLUDE_DIR NAMES include/rados/librgw.h PATHS ${RGW_PREFIX} NO_DEFAULT_PATH DOC "The RGW include headers") message("RGW_INCLUDE_DIR ${RGW_INCLUDE_DIR}") find_path(RGW_LIBRARY_DIR NAMES librgw.so PATHS ${RGW_PREFIX} PATH_SUFFIXES lib/${CMAKE_LIBRARY_ARCHITECTURE} lib lib64 NO_DEFAULT_PATH DOC "The RGW libraries") endif() if (NOT RGW_INCLUDE_DIR) find_path(RGW_INCLUDE_DIR NAMES include/rados/librgw.h PATHS ${RGW_PREFIX} DOC "The RGW include headers") endif (NOT RGW_INCLUDE_DIR) if (NOT RGW_LIBRARY_DIR) find_path(RGW_LIBRARY_DIR NAMES librgw.so PATHS ${RGW_PREFIX} PATH_SUFFIXES lib/${CMAKE_LIBRARY_ARCHITECTURE} lib lib64 DOC "The RGW libraries") endif (NOT RGW_LIBRARY_DIR) find_library(RGW_LIBRARY rgw PATHS ${RGW_LIBRARY_DIR} NO_DEFAULT_PATH) check_library_exists(rgw rgw_mount ${RGW_LIBRARY_DIR} RGWLIB) if (NOT RGWLIB) unset(RGW_LIBRARY_DIR CACHE) unset(RGW_INCLUDE_DIR CACHE) else (NOT RGWLIB) check_library_exists(rgw rgw_mount2 ${RGW_LIBRARY_DIR} RGW_MOUNT2) if(NOT RGW_MOUNT2) message("Cannot find rgw_mount2. Fallback to use rgw_mount") set(USE_FSAL_RGW_MOUNT2 OFF) else(RGW_MOUNT2) set(USE_FSAL_RGW_MOUNT2 ON) endif(NOT RGW_MOUNT2) check_library_exists(rgw rgw_getxattrs ${RGW_LIBRARY_DIR} RGW_XATTRS) if(NOT RGW_XATTRS) message("Cannot find xattrs") set(USE_FSAL_RGW_XATTRS OFF) else(RGW_XATTRS) # set(USE_FSAL_RGW_XATTRS ON) set(USE_FSAL_RGW_XATTRS ON) endif(NOT RGW_XATTRS) endif (NOT RGWLIB) set(RGW_LIBRARIES ${RGW_LIBRARY}) message(STATUS "Found rgw libraries: ${RGW_LIBRARIES}") set(RGW_FILE_HEADER "${RGW_INCLUDE_DIR}/include/rados/rgw_file.h") if (EXISTS ${RGW_FILE_HEADER}) file(STRINGS ${RGW_FILE_HEADER} RGW_MAJOR REGEX "LIBRGW_FILE_VER_MAJOR (\\d*).*$") string(REGEX REPLACE ".+LIBRGW_FILE_VER_MAJOR (\\d*)" "\\1" RGW_MAJOR "${RGW_MAJOR}") file(STRINGS ${RGW_FILE_HEADER} RGW_MINOR REGEX "LIBRGW_FILE_VER_MINOR (\\d*).*$") string(REGEX REPLACE ".+LIBRGW_FILE_VER_MINOR (\\d*)" "\\1" RGW_MINOR "${RGW_MINOR}") file(STRINGS ${RGW_FILE_HEADER} RGW_EXTRA REGEX "LIBRGW_FILE_VER_EXTRA (\\d*).*$") string(REGEX REPLACE ".+LIBRGW_FILE_VER_EXTRA (\\d*)" "\\1" RGW_EXTRA "${RGW_EXTRA}") set(RGW_FILE_VERSION "${RGW_MAJOR}.${RGW_MINOR}.${RGW_EXTRA}") else() set(RGW_FILE_VERSION "0.0.0") endif() # handle the QUIETLY and REQUIRED arguments and set PRELUDE_FOUND to TRUE if # all listed variables are TRUE include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(RGW REQUIRED_VARS RGW_INCLUDE_DIR RGW_LIBRARY_DIR VERSION_VAR RGW_FILE_VERSION ) # VERSION FPHSA options not handled by CMake version < 2.8.2) # VERSION_VAR) mark_as_advanced(RGW_INCLUDE_DIR) mark_as_advanced(RGW_LIBRARY_DIR) nfs-ganesha-6.5/src/cmake/modules/FindReadline.cmake000066400000000000000000000027641473756622300224530ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- FIND_PATH(READLINE_INCLUDE_DIR readline/readline.h) FIND_LIBRARY(READLINE_LIBRARY NAMES readline) IF (READLINE_INCLUDE_DIR AND READLINE_LIBRARY) SET(READLINE_FOUND TRUE) ENDIF (READLINE_INCLUDE_DIR AND READLINE_LIBRARY) IF (READLINE_FOUND) IF (NOT Readline_FIND_QUIETLY) MESSAGE(STATUS "Found GNU readline: ${READLINE_LIBRARY}") ENDIF (NOT Readline_FIND_QUIETLY) ELSE (READLINE_FOUND) IF (Readline_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could not find GNU readline") ENDIF (Readline_FIND_REQUIRED) ENDIF (READLINE_FOUND) nfs-ganesha-6.5/src/cmake/modules/FindSanitizers.cmake000066400000000000000000000043331473756622300230550ustar00rootroot00000000000000# SPDX-License-Identifier: MIT # # The MIT License (MIT) # # Copyright (c) # 2013 Matthew Arsenault # 2015-2016 RWTH Aachen University, Federal Republic of Germany # # 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. # If any of the used compiler is a GNU compiler, add a second option to static # link against the sanitizers. option(SANITIZE_LINK_STATIC "Try to link static against sanitizers." Off) set(FIND_QUIETLY_FLAG "") if (DEFINED Sanitizers_FIND_QUIETLY) set(FIND_QUIETLY_FLAG "QUIET") endif () find_package(ASan ${FIND_QUIETLY_FLAG}) find_package(TSan ${FIND_QUIETLY_FLAG}) find_package(MSan ${FIND_QUIETLY_FLAG}) find_package(UBSan ${FIND_QUIETLY_FLAG}) function(sanitizer_add_blacklist_file FILE) if(NOT IS_ABSOLUTE ${FILE}) set(FILE "${CMAKE_CURRENT_SOURCE_DIR}/${FILE}") endif() get_filename_component(FILE "${FILE}" REALPATH) sanitizer_check_compiler_flags("-fsanitize-blacklist=${FILE}" "SanitizerBlacklist" "SanBlist") endfunction() function(add_sanitizers ...) foreach (TARGET ${ARGV}) add_sanitize_address(${TARGET}) add_sanitize_thread(${TARGET}) add_sanitize_memory(${TARGET}) add_sanitize_undefined(${TARGET}) endforeach () endfunction(add_sanitizers) nfs-ganesha-6.5/src/cmake/modules/FindTSan.cmake000066400000000000000000000047411473756622300215720ustar00rootroot00000000000000# SPDX-License-Identifier: MIT # # The MIT License (MIT) # # Copyright (c) # 2013 Matthew Arsenault # 2015-2016 RWTH Aachen University, Federal Republic of Germany # # 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. option(SANITIZE_THREAD "Enable ThreadSanitizer for sanitized targets." Off) set(FLAG_CANDIDATES "-g -fsanitize=thread" ) # ThreadSanitizer is not compatible with MemorySanitizer. if (SANITIZE_THREAD AND SANITIZE_MEMORY) message(FATAL_ERROR "ThreadSanitizer is not compatible with " "MemorySanitizer.") endif () include(sanitize-helpers) if (SANITIZE_THREAD) if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux") message(WARNING "ThreadSanitizer disabled for target ${TARGET} because " "ThreadSanitizer is supported for Linux systems only.") set(SANITIZE_THREAD Off CACHE BOOL "Enable ThreadSanitizer for sanitized targets." FORCE) elseif (NOT ${CMAKE_SIZEOF_VOID_P} EQUAL 8) message(WARNING "ThreadSanitizer disabled for target ${TARGET} because " "ThreadSanitizer is supported for 64bit systems only.") set(SANITIZE_THREAD Off CACHE BOOL "Enable ThreadSanitizer for sanitized targets." FORCE) else () sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "ThreadSanitizer" "TSan") endif () endif () function (add_sanitize_thread TARGET) if (NOT SANITIZE_THREAD) return() endif () sanitizer_add_flags(${TARGET} "ThreadSanitizer" "TSan") endfunction () nfs-ganesha-6.5/src/cmake/modules/FindTcMalloc.cmake000066400000000000000000000042671473756622300224260ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause # # - Find Tcmalloc library # Find the native Tcmalloc includes and library # This module defines # TCMALLOC_INCLUDE_DIRS, where to find tcmalloc.h, Set when # TCMALLOC_INCLUDE_DIR is found. # TCMALLOC_LIBRARIES, libraries to link against to use Tcmalloc. # TCMALLOC_ROOT_DIR, The base directory to search for Tcmalloc. # This can also be an environment variable. # TCMALLOC_FOUND, If false, do not try to use Tcmalloc. # # also defined, but not for general use are # TCMALLOC_LIBRARY, where to find the Tcmalloc library. #============================================================================= # Copyright 2011 Blender Foundation. # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.txt for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. #============================================================================= # If TCMALLOC_ROOT_DIR was defined in the environment, use it. IF(NOT TCMALLOC_ROOT_DIR AND NOT $ENV{TCMALLOC_ROOT_DIR} STREQUAL "") SET(TCMALLOC_ROOT_DIR $ENV{TCMALLOC_ROOT_DIR}) ENDIF() SET(_tcmalloc_SEARCH_DIRS ${TCMALLOC_ROOT_DIR} /usr/local /sw # Fink /opt/local # DarwinPorts /opt/csw # Blastwave /usr/include/google # Debian tcmalloc minimal /usr/include/gperftools # Debian gperftools ) FIND_PATH(TCMALLOC_INCLUDE_DIR NAMES tcmalloc.h HINTS ${_tcmalloc_SEARCH_DIRS} PATH_SUFFIXES include/tcmalloc ) FIND_LIBRARY(TCMALLOC_LIBRARY NAMES tcmalloc HINTS ${_tcmalloc_SEARCH_DIRS} PATH_SUFFIXES lib64 lib ) # handle the QUIETLY and REQUIRED arguments and set TCMALLOC_FOUND to TRUE if # all listed variables are TRUE INCLUDE(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Tcmalloc DEFAULT_MSG TCMALLOC_LIBRARY TCMALLOC_INCLUDE_DIR) IF(TCMALLOC_FOUND) SET(TCMALLOC_LIBRARIES ${TCMALLOC_LIBRARY}) SET(TCMALLOC_INCLUDE_DIRS ${TCMALLOC_INCLUDE_DIR}) ENDIF(TCMALLOC_FOUND) MARK_AS_ADVANCED( TCMALLOC_INCLUDE_DIR TCMALLOC_LIBRARY ) nfs-ganesha-6.5/src/cmake/modules/FindToolchain.cmake000066400000000000000000000032231473756622300226370ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") if(${CMAKE_CXX_COMPILER_ID} MATCHES "MSVC") set(MSVC ON) endif(${CMAKE_CXX_COMPILER_ID} MATCHES "MSVC") endif(${CMAKE_SYSTEM_NAME} MATCHES "Windows") if(UNIX) execute_process( COMMAND ld -V OUTPUT_VARIABLE LINKER_VERS OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE LINKER_VERS_RESULT ) if("${LINKER_VERS_RESULT}" MATCHES "^0$") if("${LINKER_VERS}" MATCHES "GNU gold") set(GOLD_LINKER ON) else("${LINKER_VERS}" MATCHES "GNU gold") endif("${LINKER_VERS}" MATCHES "GNU gold") endif("${LINKER_VERS_RESULT}" MATCHES "^0$") endif(UNIX) message(STATUS "toolchain options processed") nfs-ganesha-6.5/src/cmake/modules/FindUBSan.cmake000066400000000000000000000033011473756622300216640ustar00rootroot00000000000000# SPDX-License-Identifier: MIT # # The MIT License (MIT) # # Copyright (c) # 2013 Matthew Arsenault # 2015-2016 RWTH Aachen University, Federal Republic of Germany # # 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. option(SANITIZE_UNDEFINED "Enable UndefinedBehaviorSanitizer for sanitized targets." Off) set(FLAG_CANDIDATES "-g -fsanitize=undefined" ) include(sanitize-helpers) if (SANITIZE_UNDEFINED) sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "UndefinedBehaviorSanitizer" "UBSan") endif () function (add_sanitize_undefined TARGET) if (NOT SANITIZE_UNDEFINED) return() endif () sanitizer_add_flags(${TARGET} "UndefinedBehaviorSanitizer" "UBSan") endfunction () nfs-ganesha-6.5/src/cmake/modules/FindUnwind.cmake000066400000000000000000000012011473756622300221550ustar00rootroot00000000000000# FindUnwind.cmake if(UNWIND_PATH_HINT) message(STATUS "Using UNWIND_PATH_HINT: ${UNWIND_PATH_HINT}") else() set(UNWIND_PATH_HINT) endif() find_path(UNWIND_INCLUDE_DIR NAMES libunwind.h PATHS ${UNWIND_PATH_HINT} PATH_SUFFIXES include DOC "The libunwind include directory") find_library(UNWIND_LIBRARY NAMES unwind PATHS ${UNWIND_PATH_HINT} PATH_SUFFIXES lib lib64 DOC "The libunwind library") set(UNWIND_LIBRARIES ${UNWIND_LIBRARY}) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(Unwind REQUIRED_VARS UNWIND_LIBRARY UNWIND_INCLUDE_DIR) mark_as_advanced(UNWIND_INCLUDE_DIR UNWIND_LIBRARY) nfs-ganesha-6.5/src/cmake/modules/FindWBclient.cmake000066400000000000000000000050211473756622300224240ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # Try to find a sufficiently recent wbclient if(SAMBA4_PREFIX) set(SAMBA4_INCLUDE_DIRS ${SAMBA4_PREFIX}/include) set(SAMBA4_LIBRARIES ${SAMBA4_PREFIX}/lib${LIB_SUFFIX}) endif() if(NOT WIN32) find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_check_modules(_WBCLIENT_PC QUIET wbclient) endif(PKG_CONFIG_FOUND) endif(NOT WIN32) find_path(WBCLIENT_INCLUDE_DIR wbclient.h ${_WBCLIENT_PC_INCLUDE_DIRS} ${SAMBA4_INCLUDE_DIRS} /usr/include /usr/local/include ) find_library(WBCLIENT_LIBRARIES NAMES wbclient PATHS ${_WBCLIENT_PC_LIBDIR} ) check_library_exists( wbclient wbcLookupSids ${WBCLIENT_LIBRARIES} WBCLIENT_LIB_OK ) # the stdint and stdbool includes are required (silly Cmake) if(WBCLIENT_LIB_OK) LIST(APPEND CMAKE_REQUIRED_INCLUDES ${WBCLIENT_INCLUDE_DIR}) check_include_files("stdint.h;stdbool.h;wbclient.h" WBCLIENT_H) endif(WBCLIENT_LIB_OK) # now see if this is a winbind 4 header if(WBCLIENT_H) check_c_source_compiles(" /* do the enum */ #include #include #include #include int main(void) { enum wbcAuthUserLevel level = WBC_AUTH_USER_LEVEL_PAC; return (0); }" WBCLIENT4_H) endif(WBCLIENT_H) if(WBCLIENT4_H) set(WBCLIENT_FOUND 1) message(STATUS "Found Winbind4 client: ${WBCLIENT_LIB}") else(WBCLIENT4_H) if(WBclient_FIND_REQUIRED) message(FATAL_ERROR "Winbind4 client not found ${SAMBA4_PREFIX}/lib") else(WBclient_FIND_REQUIRED) message(STATUS "Winbind4 client not found ${SAMBA4_PREFIX}/lib") endif(WBclient_FIND_REQUIRED) endif(WBCLIENT4_H) nfs-ganesha-6.5/src/cmake/modules/GetGitRevisionDescription.cmake000066400000000000000000000073551473756622300252360ustar00rootroot00000000000000# SPDX-License-Identifier: BSL-1.0 # # - Returns a version string from Git # # These functions force a re-configure on each git commit so that you can # trust the values of the variables in your build system. # # get_git_head_revision( [ ...]) # # Returns the refspec and sha hash of the current head revision # # git_describe( [ ...]) # # Returns the results of git describe on the source tree, and adjusting # the output so that it tests false if an error occurs. # # git_get_exact_tag( [ ...]) # # Returns the results of git describe --exact-match on the source tree, # and adjusting the output so that it tests false if there was no exact # matching tag. # # Requires CMake 2.6 or newer (uses the 'function' command) # # Original Author: # 2009-2010 Ryan Pavlik # http://academic.cleardefinition.com # Iowa State University HCI Graduate Program/VRAC # # Copyright Iowa State University 2009-2010. # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) if(__get_git_revision_description) return() endif() set(__get_git_revision_description YES) # We must run the following at "include" time, not at function call time, # to find the path to this module rather than the path to a calling list file get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) function(get_git_head_revision _refspecvar _hashvar) set(GIT_PARENT_DIR "${GANESHA_TOP_CMAKE_DIR}") set(GIT_DIR "${GIT_PARENT_DIR}/.git") while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) # We have reached the root directory, we are not in git set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) return() endif() set(GIT_DIR "${GIT_PARENT_DIR}/.git") endwhile() set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") if(NOT EXISTS "${GIT_DATA}") file(MAKE_DIRECTORY "${GIT_DATA}") endif() if(NOT EXISTS "${GIT_DIR}/HEAD") return() endif() set(HEAD_FILE "${GIT_DATA}/HEAD") configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" "${GIT_DATA}/grabRef.cmake" @ONLY) include("${GIT_DATA}/grabRef.cmake") set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) endfunction() function(git_describe _var) if(NOT GIT_FOUND) find_package(Git QUIET) endif() get_git_head_revision(refspec hash) if(NOT GIT_FOUND) set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) return() endif() if(NOT hash) set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) return() endif() # TODO sanitize #if((${ARGN}" MATCHES "&&") OR # (ARGN MATCHES "||") OR # (ARGN MATCHES "\\;")) # message("Please report the following error to the project!") # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") #endif() #message(STATUS "Arguments to execute_process: ${ARGN}") execute_process(COMMAND "${GIT_EXECUTABLE}" describe ${hash} ${ARGN} WORKING_DIRECTORY "${GANESHA_TOP_CMAKE_DIR}" RESULT_VARIABLE res OUTPUT_VARIABLE out ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT res EQUAL 0) set(out "${out}-${res}-NOTFOUND") endif() set(${_var} "${out}" PARENT_SCOPE) endfunction() function(git_get_exact_tag _var) git_describe(out --exact-match ${ARGN}) set(${_var} "${out}" PARENT_SCOPE) endfunction() nfs-ganesha-6.5/src/cmake/modules/GetGitRevisionDescription.cmake.in000066400000000000000000000023251473756622300256330ustar00rootroot00000000000000# SPDX-License-Identifier: BSL-1.0 # # Internal file for GetGitRevisionDescription.cmake # # Requires CMake 2.6 or newer (uses the 'function' command) # # Original Author: # 2009-2010 Ryan Pavlik # http://academic.cleardefinition.com # Iowa State University HCI Graduate Program/VRAC # # Copyright Iowa State University 2009-2010. # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) set(HEAD_HASH) file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) if(HEAD_CONTENTS MATCHES "ref") # named branch string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") if(EXISTS "@GIT_DIR@/${HEAD_REF}") configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}") configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) set(HEAD_HASH "${HEAD_REF}") endif() else() # detached HEAD configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) endif() if(NOT HEAD_HASH) file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) string(STRIP "${HEAD_HASH}" HEAD_HASH) endif() nfs-ganesha-6.5/src/cmake/modules/InstallClobberImmune.cmake000066400000000000000000000045321473756622300241740ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # Determines at `make install` time if a file, typically a configuration # file placed in $PREFIX/etc, shouldn't be installed to prevent overwrite # of an existing file. # # _srcfile: the file to install # _dstfile: the absolute file name after installation macro(InstallClobberImmune _srcfile _dstfile) install(CODE " set(_destfile \"${_dstfile}\") if (NOT \"\$ENV{DESTDIR}\" STREQUAL \"\") # prepend install root prefix with install-time DESTDIR set(_destfile \"\$ENV{DESTDIR}/${_dstfile}\") endif () if (EXISTS \${_destfile}) message(STATUS \"Skipping: \${_destfile} (already exists)\") execute_process(COMMAND \"${CMAKE_COMMAND}\" -E compare_files ${_srcfile} \${_destfile} RESULT_VARIABLE _diff) if (NOT \"\${_diff}\" STREQUAL \"0\") message(STATUS \"Installing: \${_destfile}.example\") configure_file(${_srcfile} \${_destfile}.example COPYONLY) endif () else () message(STATUS \"Installing: \${_destfile}\") # install() is not scriptable within install(), and # configure_file() is the next best thing configure_file(${_srcfile} \${_destfile} COPYONLY) # TODO: create additional install_manifest files? endif () ") endmacro(InstallClobberImmune) nfs-ganesha-6.5/src/cmake/modules/InstallPackageConfigFile.cmake000066400000000000000000000072471473756622300247400ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- include(InstallClobberImmune) # This macro can be used to install configuration files which # users are expected to modify after installation. It will: # # - If binary packaging is enabled: # Install the file in the typical CMake fashion, but append to the # INSTALLED_CONFIG_FILES cache variable for use with the Mac package's # pre/post install scripts # # - If binary packaging is not enabled: # Install the script in a way such that it will check at `make install` # time whether the file does not exist. See InstallClobberImmune.cmake # # - Always create a target "install-example-configs" which installs an # example version of the config file. # # _srcfile: the absolute path to the file to install # _dstdir: absolute path to the directory in which to install the file # _dstfilename: how to (re)name the file inside _dstdir macro(InstallPackageConfigFile _srcfile _dstdir _dstfilename) set(_dstfile ${_dstdir}/${_dstfilename}) if (BINARY_PACKAGING_MODE) # If packaging mode is enabled, always install the distribution's # version of the file. The Mac package's pre/post install scripts # or native functionality of RPMs will take care of not clobbering it. install(FILES ${_srcfile} DESTINATION ${_dstdir} RENAME ${_dstfilename}) # This cache variable is what the Mac package pre/post install scripts # use to avoid clobbering user-modified config files set(INSTALLED_CONFIG_FILES "${INSTALLED_CONFIG_FILES} ${_dstfile}" CACHE STRING "" FORCE) # Additionally, the Mac PackageMaker packages don't have any automatic # handling of configuration file conflicts so install an example file # that the post install script will cleanup in the case it's extraneous if (APPLE) install(FILES ${_srcfile} DESTINATION ${_dstdir} RENAME ${_dstfilename}.example) endif () else () # Have `make install` check at run time whether the file does not exist InstallClobberImmune(${_srcfile} ${_dstfile}) endif () if (NOT TARGET install-example-configs) add_custom_target(install-example-configs COMMENT "Installed example configuration files") endif () # '/' is invalid in target names, so replace w/ '.' string(REGEX REPLACE "/" "." _flatsrc ${_srcfile}) set(_example ${_dstfile}.example) add_custom_target(install-example-config-${_flatsrc} COMMAND "${CMAKE_COMMAND}" -E copy ${_srcfile} \${DESTDIR}${_example} COMMENT "Installing ${_example}") add_dependencies(install-example-configs install-example-config-${_flatsrc}) endmacro(InstallPackageConfigFile) nfs-ganesha-6.5/src/cmake/modules/LibFindMacros.cmake000066400000000000000000000266611473756622300226050ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # Version 2.2 # Public Domain, originally written by Lasse Kärkkäinen # Maintained at https://github.com/Tronic/cmake-modules # Please send your improvements as pull requests on Github. # Find another package and make it a dependency of the current package. # This also automatically forwards the "REQUIRED" argument. # Usage: libfind_package( [extra args to find_package]) macro (libfind_package PREFIX PKG) set(${PREFIX}_args ${PKG} ${ARGN}) if (${PREFIX}_FIND_REQUIRED) set(${PREFIX}_args ${${PREFIX}_args} REQUIRED) endif() find_package(${${PREFIX}_args}) set(${PREFIX}_DEPENDENCIES ${${PREFIX}_DEPENDENCIES};${PKG}) unset(${PREFIX}_args) endmacro() # A simple wrapper to make pkg-config searches a bit easier. # Works the same as CMake's internal pkg_check_modules but is always quiet. macro (libfind_pkg_check_modules) find_package(PkgConfig QUIET) if (PKG_CONFIG_FOUND) pkg_check_modules(${ARGN} QUIET) endif() endmacro() # Avoid useless copy&pasta by doing what most simple libraries do anyway: # pkg-config, find headers, find library. # Usage: libfind_pkg_detect( FIND_PATH [other args] FIND_LIBRARY [other args]) # E.g. libfind_pkg_detect(SDL2 sdl2 FIND_PATH SDL.h PATH_SUFFIXES SDL2 FIND_LIBRARY SDL2) function (libfind_pkg_detect PREFIX) # Parse arguments set(argname pkgargs) foreach (i ${ARGN}) if ("${i}" STREQUAL "FIND_PATH") set(argname pathargs) elseif ("${i}" STREQUAL "FIND_LIBRARY") set(argname libraryargs) else() set(${argname} ${${argname}} ${i}) endif() endforeach() if (NOT pkgargs) message(FATAL_ERROR "libfind_pkg_detect requires at least a pkg_config package name to be passed.") endif() # Find library libfind_pkg_check_modules(${PREFIX}_PKGCONF ${pkgargs}) if (pathargs) find_path(${PREFIX}_INCLUDE_DIR NAMES ${pathargs} HINTS ${${PREFIX}_PKGCONF_INCLUDE_DIRS}) endif() if (libraryargs) find_library(${PREFIX}_LIBRARY NAMES ${libraryargs} HINTS ${${PREFIX}_PKGCONF_LIBRARY_DIRS}) endif() endfunction() # Extracts a version #define from a version.h file, output stored to _VERSION. # Usage: libfind_version_header(Foobar foobar/version.h FOOBAR_VERSION_STR) # Fourth argument "QUIET" may be used for silently testing different define names. # This function does nothing if the version variable is already defined. function (libfind_version_header PREFIX VERSION_H DEFINE_NAME) # Skip processing if we already have a version or if the include dir was not found if (${PREFIX}_VERSION OR NOT ${PREFIX}_INCLUDE_DIR) return() endif() set(quiet ${${PREFIX}_FIND_QUIETLY}) # Process optional arguments foreach(arg ${ARGN}) if (arg STREQUAL "QUIET") set(quiet TRUE) else() message(AUTHOR_WARNING "Unknown argument ${arg} to libfind_version_header ignored.") endif() endforeach() # Read the header and parse for version number set(filename "${${PREFIX}_INCLUDE_DIR}/${VERSION_H}") if (NOT EXISTS ${filename}) if (NOT quiet) message(AUTHOR_WARNING "Unable to find ${${PREFIX}_INCLUDE_DIR}/${VERSION_H}") endif() return() endif() file(READ "${filename}" header) string(REGEX REPLACE ".*#[ \t]*define[ \t]*${DEFINE_NAME}[ \t]*\"([^\n]*)\".*" "\\1" match "${header}") # No regex match? if (match STREQUAL header) if (NOT quiet) message(AUTHOR_WARNING "Unable to find \#define ${DEFINE_NAME} \"\" from ${${PREFIX}_INCLUDE_DIR}/${VERSION_H}") endif() return() endif() # Export the version string set(${PREFIX}_VERSION "${match}" PARENT_SCOPE) endfunction() # Do the final processing once the paths have been detected. # If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain # all the variables, each of which contain one include directory. # Ditto for ${PREFIX}_PROCESS_LIBS and library files. # Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES. # Also handles errors in case library detection was required, etc. function (libfind_process PREFIX) # Skip processing if already processed during this configuration run if (${PREFIX}_FOUND) return() endif() set(found TRUE) # Start with the assumption that the package was found # Did we find any files? Did we miss includes? These are for formatting better error messages. set(some_files FALSE) set(missing_headers FALSE) # Shorthands for some variables that we need often set(quiet ${${PREFIX}_FIND_QUIETLY}) set(required ${${PREFIX}_FIND_REQUIRED}) set(exactver ${${PREFIX}_FIND_VERSION_EXACT}) set(findver "${${PREFIX}_FIND_VERSION}") set(version "${${PREFIX}_VERSION}") # Lists of config option names (all, includes, libs) unset(configopts) set(includeopts ${${PREFIX}_PROCESS_INCLUDES}) set(libraryopts ${${PREFIX}_PROCESS_LIBS}) # Process deps to add to foreach (i ${PREFIX} ${${PREFIX}_DEPENDENCIES}) if (DEFINED ${i}_INCLUDE_OPTS OR DEFINED ${i}_LIBRARY_OPTS) # The package seems to export option lists that we can use, woohoo! list(APPEND includeopts ${${i}_INCLUDE_OPTS}) list(APPEND libraryopts ${${i}_LIBRARY_OPTS}) else() # If plural forms don't exist or they equal singular forms if ((NOT DEFINED ${i}_INCLUDE_DIRS AND NOT DEFINED ${i}_LIBRARIES) OR ({i}_INCLUDE_DIR STREQUAL ${i}_INCLUDE_DIRS AND ${i}_LIBRARY STREQUAL ${i}_LIBRARIES)) # Singular forms can be used if (DEFINED ${i}_INCLUDE_DIR) list(APPEND includeopts ${i}_INCLUDE_DIR) endif() if (DEFINED ${i}_LIBRARY) list(APPEND libraryopts ${i}_LIBRARY) endif() else() # Oh no, we don't know the option names message(FATAL_ERROR "We couldn't determine config variable names for ${i} includes and libs. Aieeh!") endif() endif() endforeach() if (includeopts) list(REMOVE_DUPLICATES includeopts) endif() if (libraryopts) list(REMOVE_DUPLICATES libraryopts) endif() string(REGEX REPLACE ".*[ ;]([^ ;]*(_INCLUDE_DIRS|_LIBRARIES))" "\\1" tmp "${includeopts} ${libraryopts}") if (NOT tmp STREQUAL "${includeopts} ${libraryopts}") message(AUTHOR_WARNING "Plural form ${tmp} found in config options of ${PREFIX}. This works as before but is now deprecated. Please only use singular forms INCLUDE_DIR and LIBRARY, and update your find scripts for LibFindMacros > 2.0 automatic dependency system (most often you can simply remove the PROCESS variables entirely).") endif() # Include/library names separated by spaces (notice: not CMake lists) unset(includes) unset(libs) # Process all includes and set found false if any are missing foreach (i ${includeopts}) list(APPEND configopts ${i}) if (NOT "${${i}}" STREQUAL "${i}-NOTFOUND") list(APPEND includes "${${i}}") else() set(found FALSE) set(missing_headers TRUE) endif() endforeach() # Process all libraries and set found false if any are missing foreach (i ${libraryopts}) list(APPEND configopts ${i}) if (NOT "${${i}}" STREQUAL "${i}-NOTFOUND") list(APPEND libs "${${i}}") else() set (found FALSE) endif() endforeach() # Version checks if (found AND findver) if (NOT version) message(WARNING "The find module for ${PREFIX} does not provide version information, so we'll just assume that it is OK. Please fix the module or remove package version requirements to get rid of this warning.") elseif (version VERSION_LESS findver OR (exactver AND NOT version VERSION_EQUAL findver)) set(found FALSE) set(version_unsuitable TRUE) endif() endif() # If all-OK, hide all config options, export variables, print status and exit if (found) foreach (i ${configopts}) mark_as_advanced(${i}) endforeach() if (NOT quiet) message(STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}") if (LIBFIND_DEBUG) message(STATUS " ${PREFIX}_DEPENDENCIES=${${PREFIX}_DEPENDENCIES}") message(STATUS " ${PREFIX}_INCLUDE_OPTS=${includeopts}") message(STATUS " ${PREFIX}_INCLUDE_DIRS=${includes}") message(STATUS " ${PREFIX}_LIBRARY_OPTS=${libraryopts}") message(STATUS " ${PREFIX}_LIBRARIES=${libs}") endif() set (${PREFIX}_INCLUDE_OPTS ${includeopts} PARENT_SCOPE) set (${PREFIX}_LIBRARY_OPTS ${libraryopts} PARENT_SCOPE) set (${PREFIX}_INCLUDE_DIRS ${includes} PARENT_SCOPE) set (${PREFIX}_LIBRARIES ${libs} PARENT_SCOPE) set (${PREFIX}_FOUND TRUE PARENT_SCOPE) endif() return() endif() # Format messages for debug info and the type of error set(vars "Relevant CMake configuration variables:\n") foreach (i ${configopts}) mark_as_advanced(CLEAR ${i}) set(val ${${i}}) if ("${val}" STREQUAL "${i}-NOTFOUND") set (val "") elseif (val AND NOT EXISTS ${val}) set (val "${val} (does not exist)") else() set(some_files TRUE) endif() set(vars "${vars} ${i}=${val}\n") endforeach() set(vars "${vars}You may use CMake GUI, cmake -D or ccmake to modify the values. Delete CMakeCache.txt to discard all values and force full re-detection if necessary.\n") if (version_unsuitable) set(msg "${PREFIX} ${${PREFIX}_VERSION} was found but") if (exactver) set(msg "${msg} only version ${findver} is acceptable.") else() set(msg "${msg} version ${findver} is the minimum requirement.") endif() else() if (missing_headers) set(msg "We could not find development headers for ${PREFIX}. Do you have the necessary dev package installed?") elseif (some_files) set(msg "We only found some files of ${PREFIX}, not all of them. Perhaps your installation is incomplete or maybe we just didn't look in the right place?") if(findver) set(msg "${msg} This could also be caused by incompatible version (if it helps, at least ${PREFIX} ${findver} should work).") endif() else() set(msg "We were unable to find package ${PREFIX}.") endif() endif() # Fatal error out if REQUIRED if (required) set(msg "REQUIRED PACKAGE NOT FOUND\n${msg} This package is REQUIRED and you need to install it or adjust CMake configuration in order to continue building ${CMAKE_PROJECT_NAME}.") message(FATAL_ERROR "${msg}\n${vars}") endif() # Otherwise just print a nasty warning if (NOT quiet) message(WARNING "WARNING: MISSING PACKAGE\n${msg} This package is NOT REQUIRED and you may ignore this warning but by doing so you may miss some functionality of ${CMAKE_PROJECT_NAME}. \n${vars}") endif() endfunction() nfs-ganesha-6.5/src/cmake/modules/sanitize-helpers.cmake000066400000000000000000000164141473756622300234120ustar00rootroot00000000000000# SPDX-License-Identifier: MIT # # The MIT License (MIT) # # Copyright (c) # 2013 Matthew Arsenault # 2015-2016 RWTH Aachen University, Federal Republic of Germany # # 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. # Helper function to get the language of a source file. function (sanitizer_lang_of_source FILE RETURN_VAR) get_filename_component(FILE_EXT "${FILE}" EXT) string(TOLOWER "${FILE_EXT}" FILE_EXT) string(SUBSTRING "${FILE_EXT}" 1 -1 FILE_EXT) get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) foreach (LANG ${ENABLED_LANGUAGES}) list(FIND CMAKE_${LANG}_SOURCE_FILE_EXTENSIONS "${FILE_EXT}" TEMP) if (NOT ${TEMP} EQUAL -1) set(${RETURN_VAR} "${LANG}" PARENT_SCOPE) return() endif () endforeach() set(${RETURN_VAR} "" PARENT_SCOPE) endfunction () # Helper function to get compilers used by a target. function (sanitizer_target_compilers TARGET RETURN_VAR) # Check if all sources for target use the same compiler. If a target uses # e.g. C and Fortran mixed and uses different compilers (e.g. clang and # gfortran) this can trigger huge problems, because different compilers may # use different implementations for sanitizers. set(BUFFER "") get_target_property(TSOURCES ${TARGET} SOURCES) foreach (FILE ${TSOURCES}) # If expression was found, FILE is a generator-expression for an object # library. Object libraries will be ignored. string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _file ${FILE}) if ("${_file}" STREQUAL "") sanitizer_lang_of_source(${FILE} LANG) if (LANG) list(APPEND BUFFER ${CMAKE_${LANG}_COMPILER_ID}) endif () endif () endforeach () list(REMOVE_DUPLICATES BUFFER) set(${RETURN_VAR} "${BUFFER}" PARENT_SCOPE) endfunction () # Helper function to check compiler flags for language compiler. function (sanitizer_check_compiler_flag FLAG LANG VARIABLE) if (${LANG} STREQUAL "C") include(CheckCCompilerFlag) check_c_compiler_flag("${FLAG}" ${VARIABLE}) elseif (${LANG} STREQUAL "CXX") include(CheckCXXCompilerFlag) check_cxx_compiler_flag("${FLAG}" ${VARIABLE}) elseif (${LANG} STREQUAL "Fortran") # CheckFortranCompilerFlag was introduced in CMake 3.x. To be compatible # with older Cmake versions, we will check if this module is present # before we use it. Otherwise we will define Fortran coverage support as # not available. include(CheckFortranCompilerFlag OPTIONAL RESULT_VARIABLE INCLUDED) if (INCLUDED) check_fortran_compiler_flag("${FLAG}" ${VARIABLE}) elseif (NOT CMAKE_REQUIRED_QUIET) message(STATUS "Performing Test ${VARIABLE}") message(STATUS "Performing Test ${VARIABLE}" " - Failed (Check not supported)") endif () endif() endfunction () # Helper function to test compiler flags. function (sanitizer_check_compiler_flags FLAG_CANDIDATES NAME PREFIX) set(CMAKE_REQUIRED_QUIET ${${PREFIX}_FIND_QUIETLY}) get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) foreach (LANG ${ENABLED_LANGUAGES}) # Sanitizer flags are not dependent on language, but the used compiler. # So instead of searching flags foreach language, search flags foreach # compiler used. set(COMPILER ${CMAKE_${LANG}_COMPILER_ID}) if (NOT DEFINED ${PREFIX}_${COMPILER}_FLAGS) foreach (FLAG ${FLAG_CANDIDATES}) if(NOT CMAKE_REQUIRED_QUIET) message(STATUS "Try ${COMPILER} ${NAME} flag = [${FLAG}]") endif() set(CMAKE_REQUIRED_FLAGS "${FLAG}") unset(${PREFIX}_FLAG_DETECTED CACHE) sanitizer_check_compiler_flag("${FLAG}" ${LANG} ${PREFIX}_FLAG_DETECTED) if (${PREFIX}_FLAG_DETECTED) # If compiler is a GNU compiler, search for static flag, if # SANITIZE_LINK_STATIC is enabled. if (SANITIZE_LINK_STATIC AND (${COMPILER} STREQUAL "GNU")) string(TOLOWER ${PREFIX} PREFIX_lower) sanitizer_check_compiler_flag( "-static-lib${PREFIX_lower}" ${LANG} ${PREFIX}_STATIC_FLAG_DETECTED) if (${PREFIX}_STATIC_FLAG_DETECTED) set(FLAG "-static-lib${PREFIX_lower} ${FLAG}") endif () endif () set(${PREFIX}_${COMPILER}_FLAGS "${FLAG}" CACHE STRING "${NAME} flags for ${COMPILER} compiler.") mark_as_advanced(${PREFIX}_${COMPILER}_FLAGS) break() endif () endforeach () if (NOT ${PREFIX}_FLAG_DETECTED) set(${PREFIX}_${COMPILER}_FLAGS "" CACHE STRING "${NAME} flags for ${COMPILER} compiler.") mark_as_advanced(${PREFIX}_${COMPILER}_FLAGS) endif () endif () endforeach () endfunction () # Helper to assign sanitizer flags for TARGET. function (sanitizer_add_flags TARGET NAME PREFIX) # Get list of compilers used by target and check, if target can be checked # by sanitizer. sanitizer_target_compilers(${TARGET} TARGET_COMPILER) list(LENGTH TARGET_COMPILER NUM_COMPILERS) if (NUM_COMPILERS GREATER 1) message(WARNING "${NAME} disabled for target ${TARGET} because it will " "be compiled by different compilers.") return() elseif ((NUM_COMPILERS EQUAL 0) OR ("${${PREFIX}_${TARGET_COMPILER}_FLAGS}" STREQUAL "")) message(WARNING "${NAME} disabled for target ${TARGET} because there is" " no sanitizer available for target sources.") return() endif() # Set compile- and link-flags for target. set_property(TARGET ${TARGET} APPEND_STRING PROPERTY COMPILE_FLAGS " ${${PREFIX}_${TARGET_COMPILER}_FLAGS}") set_property(TARGET ${TARGET} APPEND_STRING PROPERTY COMPILE_FLAGS " ${SanBlist_${TARGET_COMPILER}_FLAGS}") set_property(TARGET ${TARGET} APPEND_STRING PROPERTY LINK_FLAGS " ${${PREFIX}_${TARGET_COMPILER}_FLAGS}") endfunction () nfs-ganesha-6.5/src/cmake/portability_cmake_2.8/000077500000000000000000000000001473756622300215355ustar00rootroot00000000000000nfs-ganesha-6.5/src/cmake/portability_cmake_2.8/FindBISON.cmake000066400000000000000000000162371473756622300242230ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause # # - Find bison executable and provides macros to generate custom build rules # The module defines the following variables: # # BISON_EXECUTABLE - path to the bison program # BISON_VERSION - version of bison # BISON_FOUND - true if the program was found # # The minimum required version of bison can be specified using the # standard CMake syntax, e.g. find_package(BISON 2.1.3) # # If bison is found, the module defines the macros: # BISON_TARGET( [VERBOSE ] # [COMPILE_FLAGS ]) # which will create a custom rule to generate a parser. is # the path to a yacc file. is the name of the source file # generated by bison. A header file is also be generated, and contains # the token list. If COMPILE_FLAGS option is specified, the next # parameter is added in the bison command line. if VERBOSE option is # specified, is created and contains verbose descriptions of the # grammar and parser. The macro defines a set of variables: # BISON_${Name}_DEFINED - true is the macro ran successfully # BISON_${Name}_INPUT - The input source file, an alias for # BISON_${Name}_OUTPUT_SOURCE - The source file generated by bison # BISON_${Name}_OUTPUT_HEADER - The header file generated by bison # BISON_${Name}_OUTPUTS - The sources files generated by bison # BISON_${Name}_COMPILE_FLAGS - Options used in the bison command line # # ==================================================================== # Example: # # find_package(BISON) # BISON_TARGET(MyParser parser.y ${CMAKE_CURRENT_BINARY_DIR}/parser.cpp) # add_executable(Foo main.cpp ${BISON_MyParser_OUTPUTS}) # ==================================================================== #============================================================================= # Copyright 2009 Kitware, Inc. # Copyright 2006 Tristan Carel # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.txt for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. #============================================================================= # (To distribute this file outside of CMake, substitute the full # License text for the above reference.) find_program(BISON_EXECUTABLE NAMES bison win_bison DOC "path to the bison executable") mark_as_advanced(BISON_EXECUTABLE) if(BISON_EXECUTABLE) # the bison commands should be executed with the C locale, otherwise # the message (which are parsed) may be translated set(_Bison_SAVED_LC_ALL "$ENV{LC_ALL}") set(ENV{LC_ALL} C) execute_process(COMMAND ${BISON_EXECUTABLE} --version OUTPUT_VARIABLE BISON_version_output ERROR_VARIABLE BISON_version_error RESULT_VARIABLE BISON_version_result OUTPUT_STRIP_TRAILING_WHITESPACE) set(ENV{LC_ALL} ${_Bison_SAVED_LC_ALL}) if(NOT ${BISON_version_result} EQUAL 0) message(SEND_ERROR "Command \"${BISON_EXECUTABLE} --version\" failed with output:\n${BISON_version_error}") else() # Bison++ if("${BISON_version_output}" MATCHES "^bison\\+\\+") string(REGEX REPLACE "^bison\\+\\+ Version ([^,]+).*" "\\1" BISON_VERSION "${BISON_version_output}") # GNU Bison elseif("${BISON_version_output}" MATCHES "^bison[^+]") string(REGEX REPLACE "^bison \\(GNU Bison\\) ([^\n]+)\n.*" "\\1" BISON_VERSION "${BISON_version_output}") elseif("${BISON_version_output}" MATCHES "^GNU Bison ") string(REGEX REPLACE "^GNU Bison (version )?([^\n]+).*" "\\2" BISON_VERSION "${BISON_version_output}") endif() endif() # internal macro macro(BISON_TARGET_option_verbose Name BisonOutput filename) list(APPEND BISON_TARGET_cmdopt "--verbose") get_filename_component(BISON_TARGET_output_path "${BisonOutput}" PATH) get_filename_component(BISON_TARGET_output_name "${BisonOutput}" NAME_WE) add_custom_command(OUTPUT ${filename} COMMAND ${CMAKE_COMMAND} ARGS -E copy "${BISON_TARGET_output_path}/${BISON_TARGET_output_name}.output" "${filename}" DEPENDS "${BISON_TARGET_output_path}/${BISON_TARGET_output_name}.output" COMMENT "[BISON][${Name}] Copying bison verbose table to ${filename}" WORKING_DIRECTORY ${GANESHA_TOP_CMAKE_DIR}) set(BISON_${Name}_VERBOSE_FILE ${filename}) list(APPEND BISON_TARGET_extraoutputs "${BISON_TARGET_output_path}/${BISON_TARGET_output_name}.output") endmacro() # internal macro macro(BISON_TARGET_option_extraopts Options) set(BISON_TARGET_extraopts "${Options}") separate_arguments(BISON_TARGET_extraopts) list(APPEND BISON_TARGET_cmdopt ${BISON_TARGET_extraopts}) endmacro() #============================================================ # BISON_TARGET (public macro) #============================================================ # macro(BISON_TARGET Name BisonInput BisonOutput) set(BISON_TARGET_output_header "") set(BISON_TARGET_cmdopt "") set(BISON_TARGET_outputs "${BisonOutput}") if(NOT ${ARGC} EQUAL 3 AND NOT ${ARGC} EQUAL 5 AND NOT ${ARGC} EQUAL 7) message(SEND_ERROR "Usage") else() # Parsing parameters if(${ARGC} GREATER 5 OR ${ARGC} EQUAL 5) if("${ARGV3}" STREQUAL "VERBOSE") BISON_TARGET_option_verbose(${Name} ${BisonOutput} "${ARGV4}") endif() if("${ARGV3}" STREQUAL "COMPILE_FLAGS") BISON_TARGET_option_extraopts("${ARGV4}") endif() endif() if(${ARGC} EQUAL 7) if("${ARGV5}" STREQUAL "VERBOSE") BISON_TARGET_option_verbose(${Name} ${BisonOutput} "${ARGV6}") endif() if("${ARGV5}" STREQUAL "COMPILE_FLAGS") BISON_TARGET_option_extraopts("${ARGV6}") endif() endif() # Header's name generated by bison (see option -d) list(APPEND BISON_TARGET_cmdopt "-d") string(REGEX REPLACE "^(.*)(\\.[^.]*)$" "\\2" _fileext "${ARGV2}") string(REPLACE "c" "h" _fileext ${_fileext}) string(REGEX REPLACE "^(.*)(\\.[^.]*)$" "\\1${_fileext}" BISON_${Name}_OUTPUT_HEADER "${ARGV2}") list(APPEND BISON_TARGET_outputs "${BISON_${Name}_OUTPUT_HEADER}") add_custom_command(OUTPUT ${BISON_TARGET_outputs} ${BISON_TARGET_extraoutputs} COMMAND ${BISON_EXECUTABLE} ARGS ${BISON_TARGET_cmdopt} -o ${ARGV2} ${ARGV1} DEPENDS ${ARGV1} COMMENT "[BISON][${Name}] Building parser with bison ${BISON_VERSION}" WORKING_DIRECTORY ${GANESHA_TOP_CMAKE_DIR}) # define target variables set(BISON_${Name}_DEFINED TRUE) set(BISON_${Name}_INPUT ${ARGV1}) set(BISON_${Name}_OUTPUTS ${BISON_TARGET_outputs}) set(BISON_${Name}_COMPILE_FLAGS ${BISON_TARGET_cmdopt}) set(BISON_${Name}_OUTPUT_SOURCE "${BisonOutput}") endif() endmacro() # #============================================================ endif() include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake) FIND_PACKAGE_HANDLE_STANDARD_ARGS(BISON REQUIRED_VARS BISON_EXECUTABLE VERSION_VAR BISON_VERSION) # FindBISON.cmake ends here nfs-ganesha-6.5/src/cmake/portability_cmake_2.8/FindFLEX.cmake000066400000000000000000000144771473756622300241130ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause # # - Find flex executable and provides a macro to generate custom build rules # # The module defines the following variables: # FLEX_FOUND - true is flex executable is found # FLEX_EXECUTABLE - the path to the flex executable # FLEX_VERSION - the version of flex # FLEX_LIBRARIES - The flex libraries # FLEX_INCLUDE_DIRS - The path to the flex headers # # The minimum required version of flex can be specified using the # standard syntax, e.g. find_package(FLEX 2.5.13) # # # If flex is found on the system, the module provides the macro: # FLEX_TARGET(Name FlexInput FlexOutput [COMPILE_FLAGS ]) # which creates a custom command to generate the file from # the file. If COMPILE_FLAGS option is specified, the next # parameter is added to the flex command line. Name is an alias used to # get details of this custom command. Indeed the macro defines the # following variables: # FLEX_${Name}_DEFINED - true is the macro ran successfully # FLEX_${Name}_OUTPUTS - the source file generated by the custom rule, an # alias for FlexOutput # FLEX_${Name}_INPUT - the flex source file, an alias for ${FlexInput} # # Flex scanners often use tokens defined by Bison: the code generated # by Flex depends of the header generated by Bison. This module also # defines a macro: # ADD_FLEX_BISON_DEPENDENCY(FlexTarget BisonTarget) # which adds the required dependency between a scanner and a parser # where and are the first parameters of # respectively FLEX_TARGET and BISON_TARGET macros. # # ==================================================================== # Example: # # find_package(BISON) # find_package(FLEX) # # BISON_TARGET(MyParser parser.y ${CMAKE_CURRENT_BINARY_DIR}/parser.cpp) # FLEX_TARGET(MyScanner lexer.l ${CMAKE_CURRENT_BINARY_DIR}/lexer.cpp) # ADD_FLEX_BISON_DEPENDENCY(MyScanner MyParser) # # include_directories(${CMAKE_CURRENT_BINARY_DIR}) # add_executable(Foo # Foo.cc # ${BISON_MyParser_OUTPUTS} # ${FLEX_MyScanner_OUTPUTS} # ) # ==================================================================== #============================================================================= # Copyright 2009 Kitware, Inc. # Copyright 2006 Tristan Carel # # Distributed under the OSI-approved BSD License (the "License"); # see accompanying file Copyright.txt for details. # # This software is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the License for more information. #============================================================================= # (To distribute this file outside of CMake, substitute the full # License text for the above reference.) find_program(FLEX_EXECUTABLE NAMES flex win_flex DOC "path to the flex executable") mark_as_advanced(FLEX_EXECUTABLE) find_library(FL_LIBRARY NAMES fl DOC "Path to the fl library") find_path(FLEX_INCLUDE_DIR FlexLexer.h DOC "Path to the flex headers") mark_as_advanced(FL_LIBRARY FLEX_INCLUDE_DIR) set(FLEX_INCLUDE_DIRS ${FLEX_INCLUDE_DIR}) set(FLEX_LIBRARIES ${FL_LIBRARY}) if(FLEX_EXECUTABLE) execute_process(COMMAND ${FLEX_EXECUTABLE} --version OUTPUT_VARIABLE FLEX_version_output ERROR_VARIABLE FLEX_version_error RESULT_VARIABLE FLEX_version_result OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT ${FLEX_version_result} EQUAL 0) if(FLEX_FIND_REQUIRED) message(SEND_ERROR "Command \"${FLEX_EXECUTABLE} --version\" failed with output:\n${FLEX_version_output}\n${FLEX_version_error}") else() message("Command \"${FLEX_EXECUTABLE} --version\" failed with output:\n${FLEX_version_output}\n${FLEX_version_error}\nFLEX_VERSION will not be available") endif() else() # older versions of flex printed "/full/path/to/executable version X.Y" # newer versions use "basename(executable) X.Y" get_filename_component(FLEX_EXE_NAME_WE "${FLEX_EXECUTABLE}" NAME_WE) get_filename_component(FLEX_EXE_EXT "${FLEX_EXECUTABLE}" EXT) string(REGEX REPLACE "^.*${FLEX_EXE_NAME_WE}(${FLEX_EXE_EXT})?\"? (version )?([0-9]+[^ ]*)( .*)?$" "\\3" FLEX_VERSION "${FLEX_version_output}") unset(FLEX_EXE_EXT) unset(FLEX_EXE_NAME_WE) endif() #============================================================ # FLEX_TARGET (public macro) #============================================================ # macro(FLEX_TARGET Name Input Output) set(FLEX_TARGET_usage "FLEX_TARGET( [COMPILE_FLAGS ]") if(${ARGC} GREATER 3) if(${ARGC} EQUAL 5) if("${ARGV3}" STREQUAL "COMPILE_FLAGS") set(FLEX_EXECUTABLE_opts "${ARGV4}") separate_arguments(FLEX_EXECUTABLE_opts) else() message(SEND_ERROR ${FLEX_TARGET_usage}) endif() else() message(SEND_ERROR ${FLEX_TARGET_usage}) endif() endif() add_custom_command(OUTPUT ${Output} COMMAND ${FLEX_EXECUTABLE} ARGS ${FLEX_EXECUTABLE_opts} -o${Output} ${Input} DEPENDS ${Input} COMMENT "[FLEX][${Name}] Building scanner with flex ${FLEX_VERSION}" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) set(FLEX_${Name}_DEFINED TRUE) set(FLEX_${Name}_OUTPUTS ${Output}) set(FLEX_${Name}_INPUT ${Input}) set(FLEX_${Name}_COMPILE_FLAGS ${FLEX_EXECUTABLE_opts}) endmacro() #============================================================ #============================================================ # ADD_FLEX_BISON_DEPENDENCY (public macro) #============================================================ # macro(ADD_FLEX_BISON_DEPENDENCY FlexTarget BisonTarget) if(NOT FLEX_${FlexTarget}_OUTPUTS) message(SEND_ERROR "Flex target `${FlexTarget}' does not exists.") endif() if(NOT BISON_${BisonTarget}_OUTPUT_HEADER) message(SEND_ERROR "Bison target `${BisonTarget}' does not exists.") endif() set_source_files_properties(${FLEX_${FlexTarget}_OUTPUTS} PROPERTIES OBJECT_DEPENDS ${BISON_${BisonTarget}_OUTPUT_HEADER}) endmacro() #============================================================ endif() include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake) FIND_PACKAGE_HANDLE_STANDARD_ARGS(FLEX REQUIRED_VARS FLEX_EXECUTABLE VERSION_VAR FLEX_VERSION) # FindFLEX.cmake ends here nfs-ganesha-6.5/src/cmake/rpmtools_config.cmake000066400000000000000000000033051473756622300216530ustar00rootroot00000000000000# SPDX-License-Identifier: BSD-3-Clause #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- set( RPM_NAME ${PROJECT_NAME} ) set( PACKAGE_VERSION "${${PROJECT_NAME}_MAJOR_VERSION}.${${PROJECT_NAME}_MINOR_VERSION}.${${PROJECT_NAME}_PATCH_LEVEL}" ) set( RPM_SUMMARY "NFS-Ganesha is a NFS Server running in user space" ) set( RPM_RELEASE_BASE 1 ) set( RPM_RELEASE ${RPM_RELEASE_BASE}.git${_GIT_HEAD_COMMIT_ABBREV} ) set( RPM_PACKAGE_LICENSE "LGPLv3" ) set( RPM_PACKAGE_GROUP "Applications/System" ) set( RPM_URL "http://nfs-ganesha.sourceforge.net" ) set( RPM_CHANGELOG_FILE ${PROJECT_SOURCE_DIR}/rpm_changelog ) set( RPM_DESCRIPTION "NFS-GANESHA is a NFS Server running in user space. It comes with various back-end modules (called FSALs) provided as shared objects to support different file systems and name-spaces." ) nfs-ganesha-6.5/src/config_parsing/000077500000000000000000000000001473756622300173545ustar00rootroot00000000000000nfs-ganesha-6.5/src/config_parsing/.gitignore000066400000000000000000000000751473756622300213460ustar00rootroot00000000000000conf_lex.c conf_yacc.c conf_yacc.h test_parsing verif_syntax nfs-ganesha-6.5/src/config_parsing/CMakeLists.txt000066400000000000000000000052361473756622300221220ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- find_package(BISON) find_package(FLEX) add_definitions( -D__USE_GNU ) BISON_TARGET( ConfigParser ${CMAKE_CURRENT_SOURCE_DIR}/conf_yacc.y ${CMAKE_CURRENT_BINARY_DIR}/conf_yacc.c COMPILE_FLAGS "--defines -pganesha_yy" ) FLEX_TARGET( ConfigScanner ${CMAKE_CURRENT_SOURCE_DIR}/conf_lex.l ${CMAKE_CURRENT_BINARY_DIR}/conf_lex.c COMPILE_FLAGS "-Pganeshun_yy -olex.yy.c" ) ADD_FLEX_BISON_DEPENDENCY(ConfigScanner ConfigParser) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) ########### next target ############### SET(config_parsing_STAT_SRCS analyse.c config_parsing.c conf_url.c analyse.h ) add_library(config_parsing OBJECT ${config_parsing_STAT_SRCS} ${BISON_ConfigParser_OUTPUTS} ${FLEX_ConfigScanner_OUTPUTS} ) if (USE_LTTNG) add_dependencies(config_parsing gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) add_sanitizers(config_parsing) set_target_properties(config_parsing PROPERTIES COMPILE_FLAGS "-fPIC") if(RADOS_URLS) set(config_parsing_RADOS_SRCS ${config_parsing_RADOS_SRCS} conf_url_rados.c ) add_library(ganesha_rados_urls MODULE ${config_parsing_RADOS_SRCS}) add_sanitizers(ganesha_rados_urls) target_link_libraries(ganesha_rados_urls ganesha_nfsd ${SYSTEM_LIBRARIES} ${RADOS_LIBRARIES} ${LDFLAG_DISALLOW_UNDEF}) include_directories(${RADOS_INCLUDE_DIR}) set_target_properties(ganesha_rados_urls PROPERTIES SOVERSION "${GANESHA_MAJOR_VERSION}${GANESHA_MINOR_VERSION}") install(TARGETS ganesha_rados_urls LIBRARY DESTINATION ${LIB_INSTALL_DIR}) endif(RADOS_URLS) ########### install files ############### nfs-ganesha-6.5/src/config_parsing/analyse.c000066400000000000000000000234551473756622300211650ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ #include "config.h" #include "config_parsing.h" #include "analyse.h" #include #include #include #if HAVE_STRING_H #include #endif #include "abstract_mem.h" /** * AVL tree of tokens with keys * being the hash of tokens */ static struct avltree token_tree; static inline int token_cmpf_hash(const struct avltree_node *lhs, const struct avltree_node *rhs) { struct token_tab *lk, *rk; lk = avltree_container_of(lhs, struct token_tab, token_node); rk = avltree_container_of(rhs, struct token_tab, token_node); return token_compare_hash(lk->token_hash, rk->token_hash, lk->token, rk->token); } static inline struct token_tab * avltree_inline_hash_lookup(const struct avltree_node *key) { struct avltree_node *node; node = avltree_inline_lookup(key, &token_tree, token_cmpf_hash); if (node != NULL) return avltree_container_of(node, struct token_tab, token_node); else return NULL; } /** * Sanitize the token string */ void sanitize_token_str(char *token, int esc) { char *sp, *dp; int c; size_t token_len; sp = token; dp = token; /* Change dp to point to token itself */ token_len = strlen(token); c = *sp++; if (esc) { if (c == '\"') c = *sp++; /* gobble leading '"' from regexp */ while (c != '\0') { if (c == '\\') { c = *sp++; if (c == '\0') break; switch (c) { case 'n': c = '\n'; break; case 't': c = '\t'; break; case 'r': c = '\r'; break; default: break; } } else if (c == '"' && *sp == '\0') break; /* skip trailing '"' from regexp */ *dp++ = c; c = *sp++; } *dp = '\0'; /* Null-terminate the string*/ } else { if (*token == '\'') /* skip and chomp "'" in an SQUOTE */ token++; if (token[token_len - 1] == '\'') token[token_len - 1] = '\0'; } } /** * @brief Insert a scanner token into the token table * * Look up the token in the avl tree matching case insensitive. * If there is a match, return a pointer to the token in the table. * Otherwise, allocate space, link it and return the pointer. * if 'esc' == true, this is a double quoted string which needs to * be filtered. Turn the escaped non-printable into the non-printable. * * @param token [IN] pointer to the yytext token from flex * @param esc [IN] bool, filter if true * @param st [IN] pointer to parser state * @return pointer to persistent storage for token or NULL; */ char *save_token(char *token, bool esc, struct parser_state *st) { struct token_tab *ret_tok, *new_tok; u_int64_t hash; size_t token_len; sanitize_token_str(token, esc); token_len = strlen(token); hash = CityHash64(token, token_len); if (!st->root_node->token_tree_initialized) { avltree_init(&token_tree, token_cmpf_hash, 0); st->root_node->token_tree_initialized = true; } new_tok = gsh_calloc(1, (sizeof(struct token_tab) + token_len + 1)); if (new_tok == NULL) return NULL; strcpy(new_tok->token, token); new_tok->token_hash = hash; ret_tok = avltree_inline_hash_lookup(&new_tok->token_node); if (ret_tok != NULL) { gsh_free(new_tok); return ret_tok->token; } avltree_insert(&new_tok->token_node, &token_tree); return new_tok->token; } struct { const char *name; const char *desc; } config_term_type[] = { [TERM_TOKEN] = { "TOKEN", "option name or number" }, [TERM_REGEX] = { "REGEX", "regular expression option}" }, [TERM_PATH] = { "PATH", "file path name" }, [TERM_STRING] = { "STRING", "simple string" }, [TERM_DQUOTE] = { "DQUOTE", "double quoted string" }, [TERM_SQUOTE] = { "SQUOTE", "single quoted string" }, [TERM_TRUE] = { "TRUE", "boolean TRUE" }, [TERM_FALSE] = { "FALSE", "boolean FALSE" }, [TERM_DECNUM] = { "DECNUM", "decimal number" }, [TERM_HEXNUM] = { "HEXNUM", "hexadecimal number" }, [TERM_OCTNUM] = { "OCTNUM", "octal number" }, [TERM_V4_ANY] = { "V4_ANY", "IPv4 any address" }, [TERM_V4ADDR] = { "V4ADDR", "IPv4 numeric address" }, [TERM_V4CIDR] = { "V4CIDR", "IPv4 CIDR subnet" }, [TERM_V6ADDR] = { "V6ADDR", "IPv6 numeric address" }, [TERM_V6CIDR] = { "V6CIDR", "IPv6 CIDR subnet" }, [TERM_FSID] = { "FSID", "file system ID" }, [TERM_NETGROUP] = { "NETGROUP", "NIS netgroup" } }; const char *config_term_name(enum term_type type) { return config_term_type[(int)type].name; } const char *config_term_desc(enum term_type type) { return config_term_type[(int)type].desc; } /** * Displays the content of a list of blocks. */ static void print_node(FILE *output, struct config_node *node, unsigned int indent) { struct config_node *sub_node; struct glist_head *nsi, *nsn; if (node->type == TYPE_BLOCK) { fprintf(output, "%*s\n", indent, " ", node->u.nterm.name, node->filename, node->linenumber); glist_for_each_safe(nsi, nsn, &node->u.nterm.sub_nodes) { sub_node = glist_entry(nsi, struct config_node, node); print_node(output, sub_node, indent + 3); } fprintf(output, "%*s\n", indent, " ", node->u.nterm.name); } else if (node->type == TYPE_STMT) { fprintf(output, "%*s\n", indent, " ", node->u.nterm.name, node->filename, node->linenumber); glist_for_each_safe(nsi, nsn, &node->u.nterm.sub_nodes) { sub_node = glist_entry(nsi, struct config_node, node); print_node(output, sub_node, indent + 3); } fprintf(output, "%*s\n", indent, " ", node->u.nterm.name); } else { /* a statement value */ fprintf(output, "%*s(%s)'%s' '%s'\n", indent, " ", (node->u.term.type != 0 ? config_term_type[node->u.term.type].name : "unknown"), (node->u.term.op_code != NULL ? node->u.term.op_code : " "), node->u.term.varvalue); } } static void print_token_tree(FILE *output, struct avltree_node *node) { struct token_tab *token; if (node == NULL) return; print_token_tree(output, node->left); token = avltree_container_of(node, struct token_tab, token_node); fprintf(output, " %s\n", token->token); print_token_tree(output, node->right); } void print_parse_tree(FILE *output, struct config_root *tree) { struct config_node *node; struct file_list *file; struct glist_head *nsi, *nsn; assert(tree->root.type == TYPE_ROOT); fprintf(output, "\n"); fprintf(output, " %zd \n", glist_length(&tree->root.u.nterm.sub_nodes)); fprintf(output, " \n"); for (file = tree->files; file != NULL; file = file->next) fprintf(output, " \"%s\" \n", file->pathname); fprintf(output, " \n"); fprintf(output, " \n"); print_token_tree(output, token_tree.root); fprintf(output, " \n"); fprintf(output, "\n"); fprintf(output, "\n"); glist_for_each_safe(nsi, nsn, &tree->root.u.nterm.sub_nodes) { node = glist_entry(nsi, struct config_node, node); print_node(output, node, 3); } fprintf(output, "\n"); return; } /** * @brief Free a parse tree node. * * Note that we do not free either u.nterm.name or u.term.varvalue. * this is because these are pointers into the token table which * is freed elsewhere. */ static void free_node(struct config_node *node) { if (node->type == TYPE_BLOCK || node->type == TYPE_STMT) { struct config_node *sub_node; struct glist_head *nsi, *nsn; glist_for_each_safe(nsi, nsn, &node->u.nterm.sub_nodes) { sub_node = glist_entry(nsi, struct config_node, node); glist_del(&sub_node->node); free_node(sub_node); } } gsh_free(node); return; } static void free_token_tree(struct avltree_node *node) { struct token_tab *token; if (node == NULL) return; free_token_tree(node->left); free_token_tree(node->right); token = avltree_container_of(node, struct token_tab, token_node); gsh_free(token); } void free_parse_tree(struct config_root *tree) { struct file_list *file, *next_file; struct config_node *node; struct glist_head *nsi, *nsn; glist_for_each_safe(nsi, nsn, &tree->root.u.nterm.sub_nodes) { node = glist_entry(nsi, struct config_node, node); glist_del(&node->node); free_node(node); } gsh_free(tree->root.filename); if (tree->conf_dir != NULL) gsh_free(tree->conf_dir); file = tree->files; while (file != NULL) { next_file = file->next; gsh_free(file->pathname); gsh_free(file); file = next_file; } free_token_tree(token_tree.root); gsh_free(tree); return; } void config_error(FILE *fp, const char *filename, int linenum, char *fmt, va_list args) { char str[LOG_BUFF_LEN]; vsprintf(str, fmt, args); fprintf(fp, "Config File (%s:%d): %s", filename, linenum, str); fputc('\f', fp); /* form feed (remember those?) used as msg sep */ if (likely(component_log_level[COMPONENT_CONFIG] < NIV_FULL_DEBUG)) return; DisplayLogComponentLevel(COMPONENT_CONFIG, filename, linenum, (char *)__func__, NIV_FULL_DEBUG, "%s", str); } nfs-ganesha-6.5/src/config_parsing/analyse.h000066400000000000000000000100231473756622300211550ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* ---------------------------------------------------------------------------- * Copyright CEA/DAM/DIF (2007) * contributeur : Thomas LEIBOVICI thomas.leibovici@cea.fr * * This program 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 3 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 * 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 * * --------------------------------------- */ #ifndef CONFPARSER_H #define CONFPARSER_H #include #include #include #include #include "gsh_list.h" #include "avltree.h" #include "city.h" /** * @brief Configuration parser data structures * and linkage betweek the parser, analyse.c and config_parsing.c */ /* * Parse tree node. */ /* Config nodes are either terminals or non-terminals. * BLOCK and STMT are non-terminals. * ROOT is a special case BLOCK (root of tree...) * TERM is a value/token terminal. */ enum node_type { TYPE_ROOT = 1, TYPE_BLOCK, TYPE_STMT, TYPE_TERM }; struct config_node { struct glist_head node; struct glist_head blocks; char *filename; /* pointer to filename in file list */ int linenumber; bool found; /* use accounting private in do_block_load */ enum node_type type; /* switches union contents */ struct config_node *parent; union { /* sub_nodes are always struct config_node */ struct { /* TYPE_TERM */ enum term_type type; char *op_code; char *varvalue; } term; struct { /* TYPE_BLOCK | TYPE_STMT */ char *name; /* name */ struct glist_head sub_nodes; } nterm; } u; }; /* * File list * Every config_node points to a pathname in this list */ struct file_list { struct file_list *next; char *pathname; }; /* * Symbol table * Every token the scanner keeps goes here. It is an avl * tree but we don't need much more than that. What we do * need is an accounting of all that memory so we can free it * when the parser barfs and leaves stuff on its FSM stack. */ struct token_tab { struct avltree_node token_node; u_int64_t token_hash; char token[]; }; /* * Parse tree root * A parse tree consists of several blocks, * each block consists of variables definitions * and subblocks. * All storage allocated into the parse tree is here */ struct bufstack; /* defined in conf_lex.l */ struct config_root { struct config_node root; char *conf_dir; struct file_list *files; uint64_t generation; bool token_tree_initialized; }; /* * parser/lexer linkage */ struct parser_state { struct config_root *root_node; void *scanner; struct bufstack *curbs; char *current_file; int block_depth; /* block/subblock nesting level */ struct config_error_type *err_type; }; char *save_token(char *token, bool esc, struct parser_state *st); int ganesha_yyparse(struct parser_state *st); int ganeshun_yy_init_parser(char *srcfile, struct parser_state *st); void ganeshun_yy_cleanup_parser(struct parser_state *st); static inline int token_compare_hash(const u_int64_t key1, const u_int16_t key2, const char *token1, const char *token2) { if (key1 < key2) return -1; else if (key1 > key2) return 1; else return strcmp(token1, token2); } /** * Error reporting */ void config_error(FILE *fp, const char *filename, int linenum, char *format, va_list args); /** * Displays the content of parse tree. */ void print_parse_tree(FILE *output, struct config_root *tree); /** * Free resources of parse tree */ void free_parse_tree(struct config_root *tree); #endif /* CONFPARSER_H */ nfs-ganesha-6.5/src/config_parsing/conf_lex.l000066400000000000000000000455031473756622300213350ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* ---------------------------------------------------------------------------- * Copyright CEA/DAM/DIF (2007) * contributeur : Thomas LEIBOVICI thomas.leibovici@cea.fr * * This program 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 3 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 * 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 * * --------------------------------------- */ %{ #pragma GCC diagnostic ignored "-Wunused-value" #pragma GCC diagnostic ignored "-Wunused-variable" #pragma GCC diagnostic ignored "-Wunused-function" #include "config.h" #include "config_parsing.h" #include "conf_url.h" #include "analyse.h" #include "abstract_mem.h" #include "conf_yacc.h" #include "abstract_atomic.h" #include #include #include #include #include #include #include "log.h" #include "common_utils.h" #if HAVE_STRING_H # include #endif /* Our versions of parser macros */ #define YY_USER_INIT \ do { \ BEGIN YY_INIT; \ } while (0); #define YY_USER_ACTION \ yylloc->first_line = yylloc->last_line = yylineno; \ yylloc->first_column = yylloc->last_column = yycolumn + yyleng -1; \ yycolumn += yyleng; \ yylloc->filename = stp->current_file; #ifdef _DEBUG_PARSING #define DEBUG_LEX printf #else #define DEBUG_LEX(...) (void)0 #endif #define BS_FLAG_NONE 0 #define BS_FLAG_URL 1 struct bufstack { struct bufstack *prev; YY_BUFFER_STATE bs; int lineno; char *filename; FILE *f; char *fbuf; uint32_t flags; }; static char *filter_string(char *src, int esc); static int new_file(char *filename, struct parser_state *st); static int process_dir(char *d_name, struct parser_state *st); static int fetch_url(char *name_tok, struct parser_state *st); static int pop_file(struct parser_state *st); %} %option nounput %option yylineno %option reentrant %option bison-bridge %option bison-locations %option extra-type="struct parser_state *" SPACE [ \t\r\f] NL [\n] EQUALS "=" COMMA "," SEMI ";" LCURLY "\{" RCURLY "\}" MINUS "\-" TWIDDLE "\~" SPLAT "\*" HUH "\?" BANG "\!" DOT "\." AT "@" CIDR \/[1-9][0-9]{0,1} V4OCTET [0-9]{1,3} IPV4ADDR {V4OCTET}{DOT}{V4OCTET}{DOT}{V4OCTET}{DOT}{V4OCTET} H16 [0-9A-Fa-f]{1,4} LS32 {H16}:{H16}|{IPV4ADDR} IPV6ADDR ({H16}:){6}{LS32}|::({H16}:){5}{LS32}|({H16})?::({H16}:){4}{LS32}|(({H16}:){0,1}{H16})?::({H16}:){3}{LS32}|(({H16}:){0,2}{H16})?::({H16}:){2}{LS32}|(({H16}:){0,3}{H16})?::{H16}:{LS32}|(({H16}:){0,4}{H16})?::{LS32}|(({H16}:){0,5}{H16})?::{H16}|(({H16}:){0,6}{H16})?:: SQUOTE \'[^\']*\' DQUOTE \"(\\.|[^\"])*\" YES [Yy][Ee][Ss] TRUE [Tt][Rr][Uu][Ee] ON [Oo][Nn] NO [Nn][Oo] FALSE [Ff][Aa][Ll][Ss][Ee] OFF [Oo][Ff][Ff] OCTNUM 0[0-7][0-7]* HEXNUM 0[xX][0-9a-fA-F]+ DECNUM (0|[1-9][0-9]*) NINEP 9[pP] INCLPATH \/?([a-zA-Z0-9\-\.\_])+(\/[a-zA-Z0-9\-\.\_]+)* PATHNAME \/([a-zA-Z0-9\-\.\_]+\/?)? LONGPATH (\/?[a-zA-Z0-9\-\.\_]+(\/[a-zA-Z0-9\-\.\_]+)+)\/? TOKEN_CHARS [a-zA-Z_\?][a-zA-Z0-9\._\-]* DIR_PATH \/?([a-zA-Z0-9\-\.\_])+(\/[a-zA-Z0-9\-\.\_\*]+)*\/? WC [a-zA-Z0-9\._\-] WR \[{BANG}?({WC})+\] WP ({WR}|{SPLAT}|{HUH}) WILDCARD ({WC}*{WP})+{WC}* COMMENTEXT #.*$ ID_CHARS [a-zA-Z_][a-zA-Z0-9_\-]* NETGROUP_CHARS [a-zA-Z_][a-zA-Z0-9_.\-]* /* URL types, e.g., (rados|http|ftp) */ URLTYPES (rados) INCLUDE_URL {URLTYPES}:\/\/[a-zA-Z0-9\-\.\_&=\/]+ /* INCLUDE state is used for picking the name of the include file */ %START YY_INIT DEFINITION TERM INCLUDE URL INCL_DIR %% %{ struct parser_state *stp = yyextra; %} "%include" { /* include file start */ DEBUG_LEX("INCLUDE\n"); BEGIN INCLUDE; /* not a token, return nothing */ } {INCLPATH} { { int c; DEBUG_LEX("Calling new_file with unquoted %s\n", yytext); c = new_file(yytext, stp); if (c == ENOMEM) yyterminate(); BEGIN YY_INIT; DEBUG_LEX("done new file\n"); } } \"{INCLPATH}\" { { int c; DEBUG_LEX("Calling new_file with quoted %s\n", yytext); c = new_file(yytext, stp); if (c == ENOMEM) yyterminate(); BEGIN YY_INIT; DEBUG_LEX("done new file\n"); } } "%dir" { /* include file start */ DEBUG_LEX("INCL_DIR\n"); BEGIN INCL_DIR; /* not a token, return nothing */ } {DIR_PATH} { { int rc; DEBUG_LEX("Calling process_dir with unquoted %s\n", yytext); rc = process_dir(yytext, stp); if (rc) { yyterminate(); } BEGIN YY_INIT; DEBUG_LEX("done process_dir\n"); } } \"{DIR_PATH}\" { { int rc; DEBUG_LEX("Calling process_dir with unquoted %s\n", yytext); rc = process_dir(yytext, stp); if (rc) { yyterminate(); } BEGIN YY_INIT; DEBUG_LEX("done process_dir\n"); } } "%url" { /* URL include file start */ DEBUG_LEX("URL\n"); BEGIN URL; /* not a token, return nothing */ } {INCLUDE_URL} { { int c; DEBUG_LEX("Calling fetch_url with unquoted %s\n", yytext); c = fetch_url(yytext, stp); if (c == ENOMEM) yyterminate(); BEGIN YY_INIT; DEBUG_LEX("done fetch url\n"); } } \"{INCLUDE_URL}\" { { int c; DEBUG_LEX("Calling fetch_url with quoted %s\n", yytext); c = fetch_url(yytext, stp); if (c == ENOMEM) yyterminate(); BEGIN YY_INIT; DEBUG_LEX("done fetch url\n"); } } <> { /* end of included file */ DEBUG_LEX("\n"); if (pop_file(stp) == 0) yyterminate(); } /* Initial State. We start with a block identifier */ {ID_CHARS} { /* first block */ /* identifier */ DEBUG_LEX("[block:%s]\n",yytext); yylval->token = save_token(yytext, false, stp); BEGIN DEFINITION; return IDENTIFIER; } {ID_CHARS} { DEBUG_LEX("[id:%s",yytext); yylval->token = save_token(yytext, false, stp); return IDENTIFIER; } {EQUALS} { DEBUG_LEX(" EQUALS "); BEGIN TERM; return EQUAL_OP; } {LCURLY} { DEBUG_LEX("BEGIN_BLOCK\n"); BEGIN DEFINITION; stp->block_depth++; return LCURLY_OP; } {RCURLY} { /* end of block */ DEBUG_LEX("END_BLOCK\n"); stp->block_depth --; if (stp->block_depth <= 0) BEGIN YY_INIT; return RCURLY_OP; } {COMMA} { /* another terminal to follow ',' */ DEBUG_LEX(" ',' "); return COMMA_OP; } /* End of statement */ {SEMI} { /* end of statement */ DEBUG_LEX("]\n"); BEGIN DEFINITION; return SEMI_OP; } /* Double Quote, allows char escaping */ {DQUOTE} { /* start of a double quote string */ DEBUG_LEX("quote value:<%s>", yytext); yylval->token = save_token(yytext, true, stp); return DQUOTE; } /* Single Quote, single line with no escaping */ {SQUOTE} { /* start of a single quote string */ DEBUG_LEX("lit value:<%s>", yytext); yylval->token = save_token(yytext, false, stp); return SQUOTE; } {YES}|{TRUE}|{ON} { /* a boolean TRUE */ DEBUG_LEX("boolean TRUE:%s", yytext); yylval->token = save_token(yytext, false, stp); return TOK_TRUE; } {NO}|{FALSE}|{OFF} { /* a boolean FALSE */ DEBUG_LEX("boolean FALSE:%s", yytext); yylval->token = save_token(yytext, false, stp); return TOK_FALSE; } {MINUS}|{TWIDDLE} { /* an arithmetic op */ DEBUG_LEX(" arith op:%s", yytext); yylval->token = save_token(yytext, false, stp); return TOK_ARITH_OP; } {NINEP} { /* "9P" is here to take precedence over numbers, this is a special */ DEBUG_LEX("token value:%s",yytext); yylval->token = save_token(yytext, false, stp); return TOKEN; } ({OCTNUM}|{DECNUM}|{HEXNUM}){DOT}({OCTNUM}|{DECNUM}|{HEXNUM}) { /* an FSID */ DEBUG_LEX(" FSID :%s", yytext); yylval->token = save_token(yytext, false, stp); return TOK_FSID; } {OCTNUM} { /* an octal number */ DEBUG_LEX(" octal number:%s", yytext); yylval->token = save_token(yytext, false, stp); return TOK_OCTNUM; } {HEXNUM} { /* a hexadecimal number */ DEBUG_LEX(" hex number:%s", yytext); yylval->token = save_token(yytext, false, stp); return TOK_HEXNUM; } {DECNUM} { /* a decimal number */ DEBUG_LEX(" dec number:%s", yytext); yylval->token = save_token(yytext, false, stp); return TOK_DECNUM; } {SPLAT}|(0{DOT}0{DOT}0{DOT}0) { /* v4 address wildcard, ganesha only, not IETF */ DEBUG_LEX(" V4 any:%s", yytext); yylval->token = save_token(yytext, false, stp); return TOK_V4_ANY; } {IPV4ADDR}{CIDR}? { /* V4 CIDR */ DEBUG_LEX(" IPv4 :%s", yytext); yylval->token = save_token(yytext, false, stp); if (index(yylval->token, '/') == NULL) return TOK_V4ADDR; else return TOK_V4CIDR; } /* Mere mortals are not supposed to grok the pattern for IPV6ADDR. */ /* I got it from the Flex manual. */ {IPV6ADDR}{CIDR}? { /* V6 CIDR */ DEBUG_LEX(" IPv6 :%s", yytext); yylval->token = save_token(yytext, false, stp); if (index(yylval->token, '/') == NULL) return TOK_V6ADDR; else return TOK_V6CIDR; } {AT}{NETGROUP_CHARS} { /* a netgroup used for clients */ DEBUG_LEX(" netgroup :%s", yytext); yylval->token = save_token(yytext, false, stp); return TOK_NETGROUP; } /* Last resort terminals. PATHNAME is here because it can confuse */ /* with a CIDR (precedence) and */ /* TOKEN_CHARS gobbles anything other than white and ";" */ {PATHNAME}|{LONGPATH} { /* a POSIX pathname */ DEBUG_LEX("pathname:%s", yytext); yylval->token = save_token(yytext, false, stp); return TOK_PATH; } {TOKEN_CHARS} { /* start of a number or label/tag */ DEBUG_LEX("token value:%s",yytext); yylval->token = save_token(yytext, false, stp); return TOKEN; } {WILDCARD} { /* start of a number or label/tag as glob(7) string */ DEBUG_LEX("token value:%s",yytext); yylval->token = save_token(yytext, false, stp); return REGEX_TOKEN; } /* Skip over stuff we don't send upstairs */ {COMMENTEXT} ;/* ignore */ {SPACE} ;/* ignore */ {NL} ;/* ignore */ /* Unrecognized chars. Must do better... */ . { /* ERROR: out of character character */ DEBUG_LEX("unexpected stuff (%s)\n", yytext); config_parse_error(yylloc, stp, "Unexpected character (%s)", yytext); stp->err_type->scan = true; yylval->token = save_token(yytext, false, stp); /* for error rpt */ return _ERROR_; } %% int ganeshun_yywrap(void *yyscanner){ return 1; } /* * This value represents a unique value for _this_ config_root. By tagging * each root with a value, we can propagate that value down to the structures * that this parsed tree touches. Then, later we can remove structures that * should no longer be present by looking to see if their generation number * predates this one. */ static uint64_t config_generation; int ganeshun_yy_init_parser(char *srcfile, struct parser_state *st) { FILE *in_file; void *yyscanner = st->scanner; /* reentrant scanner macro magic requires this... */ struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; struct file_list *flist; struct config_root *confroot; YY_BUFFER_STATE inbuf; int rc = ENOMEM; confroot = gsh_calloc(1, sizeof(struct config_root)); glist_init(&confroot->root.node); glist_init(&confroot->root.u.nterm.sub_nodes); confroot->root.type = TYPE_ROOT; confroot->generation = atomic_inc_uint64_t(&config_generation); st->root_node = confroot; st->root_node->token_tree_initialized = false; ganeshun_yylex_init_extra(st, &st->scanner); rc = new_file(srcfile, st); if (rc == 0) confroot->root.filename = gsh_strdup(srcfile); return rc; } void ganeshun_yy_cleanup_parser(struct parser_state *st) { int rc; if (st->curbs != NULL) { st->err_type->parse = true; while(pop_file(st) != 0); } ganeshun_yylex_destroy(st->scanner); } static int is_pattern(const char *str) { if (strchr(str, '*') != NULL || strchr(str, '?') != NULL || strchr(str, '[') != NULL) return 1; return 0; } static int process_dir(char *d_name, struct parser_state *st) { DIR *d; struct dirent *dir; void *yyscanner = st->scanner; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; char fname[MAXPATHLEN], dname[MAXPATHLEN], *tmp_dpath = NULL; char *pattern = NULL , *laststr = NULL, *dir_path = NULL; int rc = 0, dir_strlen; dir_strlen = strlen(d_name); if (dir_strlen > MAXPATHLEN) { config_parse_error(yylloc, st, "Error: Directory path length is > %d", MAXPATHLEN); return 1; } if (*d_name == '\"') { strlcpy(dname, d_name + 1 , dir_strlen-1); } else { /* strlcpy copies n-1 char, so +1 */ strlcpy(dname, d_name, dir_strlen+1); } DEBUG_LEX("Processing directory : %s\n", dname); /* check last string in path is pattern or not */ tmp_dpath = gsh_strdupa(dname); laststr = gsh_strdup(basename(tmp_dpath)); if (is_pattern(laststr)) { pattern = laststr; dir_path = gsh_strdup(dirname(tmp_dpath)); } else { dir_path = gsh_strdup(tmp_dpath); } DEBUG_LEX("Processing directory : %s pattern : \"%s\" \n", dir_path, pattern); d = opendir(dir_path); if (d) { while ((dir = readdir(d)) != NULL) { if (strlen(dir->d_name) + dir_strlen + 1 > MAXPATHLEN) { DEBUG_LEX("Warning: Path(%s/%s) length is > %d, \ ignored", dir_path, dir->d_name, MAXPATHLEN); config_parse_error(yylloc, st, "Warning: Path(%s/%s) length is > %d, \ ignored", dir_path, dir->d_name, MAXPATHLEN); continue; } if (dir->d_type != DT_REG || strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue; if (pattern == NULL || fnmatch(pattern, dir->d_name, 0) == 0) { sprintf(fname, "%s/%s", dir_path, dir->d_name); DEBUG_LEX("Processing File : %s \n", fname); rc = new_file(fname, st); if (rc == ENOMEM) { DEBUG_LEX("Error: %s", strerror(rc)); config_parse_error(yylloc, st, "Error: %s", strerror(rc)); rc = 1; goto errout; } } } closedir(d); goto out; } else { rc = errno; DEBUG_LEX("opendir() failed :%s ", strerror(rc)); config_parse_error(yylloc, st, "opendir(%s) failed. Error: %s, ignored", dir_path, strerror(rc)); goto out; } errout: closedir(d); out: gsh_free(dir_path); gsh_free(pattern); return rc; } static int new_file(char *name_tok, struct parser_state *st) { struct bufstack *bs = NULL; FILE *in_file; YY_BUFFER_STATE inbuf; struct file_list *flist = NULL; struct file_list *fp; void *yyscanner = st->scanner; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; struct config_root *confroot = st->root_node; char *fullpath = NULL; int rc = ENOMEM; char *filename; if (*name_tok == '\"') { /* alloca'd memory freed on exit */ filename = gsh_strdupa(name_tok + 1); filename[strlen(filename) - 1] = '\0'; } else { /* alloca'd memory freed on exit */ filename = gsh_strdupa(name_tok); } if (confroot->files == NULL) { if (filename[0] == '/') { char *path = gsh_strdupa(filename); confroot->conf_dir = gsh_strdup(dirname(path)); } else { confroot->conf_dir = gsh_strdup("."); } } if (filename[0] == '/') { fullpath = gsh_strdup(filename); } else { fullpath = gsh_concat_sep(confroot->conf_dir, '/', filename); } /* loop detection */ for (fp = confroot->files; fp != NULL; fp = fp->next) { if (!strcmp(fp->pathname, fullpath)) { config_parse_error(yylloc, st, "file (%s)already parsed, ignored", fullpath); rc = EINVAL; goto errout; } } bs = gsh_calloc(1, sizeof(struct bufstack)); flist = gsh_calloc(1, sizeof(struct file_list)); in_file = fopen(fullpath, "r" ); if (in_file == NULL) { rc = errno; config_parse_error(yylloc, st, "new file (%s) open error (%s), ignored", fullpath, strerror(rc)); goto errout; } bs->bs = ganeshun_yy_create_buffer(in_file, YY_BUF_SIZE, yyscanner); if (st->curbs) st->curbs->lineno = yylineno; bs->prev = st->curbs; bs->f = in_file; bs->filename = fullpath; ganeshun_yy_switch_to_buffer(bs->bs, yyscanner); st->current_file = fullpath; st->curbs = bs; flist->pathname = fullpath; flist->next = confroot->files; confroot->files = flist; return 0; errout: if (rc == ENOMEM) st->err_type->resource = true; else st->err_type->scan = true; gsh_free(flist); gsh_free(bs); gsh_free(fullpath); return rc; } /* fetch_url */ static int fetch_url(char *name_tok, struct parser_state *st) { struct bufstack *bs = NULL; YY_BUFFER_STATE inbuf; struct file_list *flist = NULL; struct file_list *fp; void *yyscanner = st->scanner; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; struct config_root *confroot = st->root_node; char *filename = NULL; int rc = ENOMEM; #ifdef NO_URL_RECURSION /* forbid URL chasing */ if (st->curbs && (st->curbs->flags & BS_FLAG_URL)) { config_parse_error(yylloc, st, "new url (%s) transitive fetch from (%s), ignored", name_tok, st->curbs->filename); goto errout; } #endif filename = gsh_strdup(name_tok); bs = gsh_calloc(1, sizeof(struct bufstack)); flist = gsh_calloc(1, sizeof(struct file_list)); rc = config_url_fetch(filename, &bs->f, &bs->fbuf); if (bs->f == NULL) { config_parse_error(yylloc, st, "new url (%s) open error (%s), ignored", filename, strerror(rc)); goto errout; } bs->bs = ganeshun_yy_create_buffer(bs->f, YY_BUF_SIZE, yyscanner); if (st->curbs) st->curbs->lineno = yylineno; bs->prev = st->curbs; bs->filename = filename; bs->flags = BS_FLAG_URL; ganeshun_yy_switch_to_buffer(bs->bs, yyscanner); st->current_file = gsh_strdup(bs->filename); st->curbs = bs; flist->pathname = gsh_strdup(bs->filename); /* XXX */ flist->next = confroot->files; confroot->files = flist; return 0; errout: if (rc == ENOMEM) st->err_type->resource = true; else st->err_type->scan = true; gsh_free(flist); gsh_free(bs); gsh_free(filename); return rc; } /* fetch_url() */ static int pop_file(struct parser_state *st) { struct bufstack *bs = st->curbs; struct bufstack *prevbs; void *yyscanner = st->scanner; struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; if (bs == NULL) return 0; if (bs->flags & BS_FLAG_URL) { config_url_release(bs->f, bs->fbuf); } else { fclose(bs->f); } ganeshun_yy_delete_buffer(bs->bs, yyscanner); prevbs = bs->prev; st->curbs = prevbs; gsh_free(bs); if (prevbs == NULL) return 0; ganeshun_yy_switch_to_buffer(prevbs->bs, yyscanner); yylineno = st->curbs->lineno; st->current_file = st->curbs->filename; return 1; } nfs-ganesha-6.5/src/config_parsing/conf_url.c000066400000000000000000000132571473756622300213370ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* ---------------------------------------------------------------------------- * Copyright (C) 2017, Red Hat, Inc. * contributeur : Matt Benjamin mbenjamin@redhat.com * * This program 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 3 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 * 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 * --------------------------------------- */ #include "config.h" #include #include #include "log.h" #include "sal_functions.h" #include "conf_url.h" static pthread_rwlock_t url_rwlock; static struct glist_head url_providers; static regex_t url_regex; /** @brief register handler for new url type */ int register_url_provider(struct gsh_url_provider *nurl_p) { struct gsh_url_provider *url_p; struct glist_head *gl; int code = 0; PTHREAD_RWLOCK_wrlock(&url_rwlock); glist_for_each(gl, &url_providers) { url_p = glist_entry(gl, struct gsh_url_provider, link); if (!strcasecmp(url_p->name, nurl_p->name)) { code = EEXIST; break; } } nurl_p->url_init(); glist_add_tail(&url_providers, &nurl_p->link); PTHREAD_RWLOCK_unlock(&url_rwlock); return code; } /* simplistic URL syntax */ #define CONFIG_URL_REGEX "^\"?(rados)://([^\"]+)\"?" /** @brief url regex initializer */ static void init_url_regex(void) { int r; r = regcomp(&url_regex, CONFIG_URL_REGEX, REG_EXTENDED); if (!!r) { LogFatal(COMPONENT_INIT, "Error initializing config url regex"); } } #ifdef RADOS_URLS static struct { void *dl; void (*pkginit)(void); int (*setup_watch)(void); void (*shutdown_watch)(void); } rados_urls = { NULL, }; static void load_rados_config(void) { rados_urls.dl = dlopen("libganesha_rados_urls.so", #if defined(LINUX) && !defined(SANITIZE_ADDRESS) RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND); #elif defined(FREEBSD) || defined(SANITIZE_ADDRESS) RTLD_NOW | RTLD_LOCAL); #endif if (rados_urls.dl) { rados_urls.pkginit = dlsym(rados_urls.dl, "conf_url_rados_pkginit"); rados_urls.setup_watch = dlsym(rados_urls.dl, "rados_url_setup_watch"); rados_urls.shutdown_watch = dlsym(rados_urls.dl, "rados_url_shutdown_watch"); if (!rados_urls.pkginit || !rados_urls.setup_watch || !rados_urls.shutdown_watch) { dlclose(rados_urls.dl); rados_urls.dl = NULL; LogCrit(COMPONENT_CONFIG, "Unknown urls backend"); } } else { LogWarn(COMPONENT_CONFIG, "Missing RADOS URLs backend library"); } } #endif /** @brief package initializer */ void config_url_init(void) { glist_init(&url_providers); PTHREAD_RWLOCK_init(&url_rwlock, NULL); /* init well-known URL providers */ #ifdef RADOS_URLS if (!rados_urls.dl) load_rados_config(); if (rados_urls.pkginit) rados_urls.pkginit(); #endif init_url_regex(); } /** @brief package shutdown */ void config_url_shutdown(void) { struct gsh_url_provider *url_p; PTHREAD_RWLOCK_wrlock(&url_rwlock); while ((url_p = glist_first_entry(&url_providers, struct gsh_url_provider, link))) { glist_del(&url_p->link); url_p->url_shutdown(); } PTHREAD_RWLOCK_unlock(&url_rwlock); regfree(&url_regex); #ifdef RADOS_URLS if (rados_urls.dl) dlclose(rados_urls.dl); rados_urls.dl = NULL; #endif PTHREAD_RWLOCK_destroy(&url_rwlock); } int gsh_rados_url_setup_watch(void) { #ifdef RADOS_URLS return rados_urls.setup_watch ? rados_urls.setup_watch() : 0; #else return 0; #endif } void gsh_rados_url_shutdown_watch(void) { #ifdef RADOS_URLS if (rados_urls.shutdown_watch) rados_urls.shutdown_watch(); #else return; /* non-empty fn to avoid compile warning/error */ #endif } static inline char *match_dup(regmatch_t *m, char *in) { char *s = NULL; if (m->rm_so >= 0) { int size; size = m->rm_eo - m->rm_so + 1; s = (char *)gsh_malloc(size); (void)snprintf(s, size, "%s", in + m->rm_so); } return s; } /** @brief generic url dispatch */ int config_url_fetch(const char *url, FILE **f, char **fbuf) { struct gsh_url_provider *url_p; struct glist_head *gl; regmatch_t match[3]; char *url_type = NULL, *m_url = NULL; int code = EINVAL; code = regexec(&url_regex, url, 3, match, 0); if (likely(!code)) { /* matched */ regmatch_t *m; m = &(match[1]); url_type = match_dup(m, (char *)url); m = &(match[2]); m_url = match_dup(m, (char *)url); if (!(url_type && m_url)) { LogWarn(COMPONENT_CONFIG, "%s: Failed to match %s as a config URL", __func__, url); goto out; } } else if (code == REG_NOMATCH) { LogWarn(COMPONENT_CONFIG, "%s: Failed to match %s as a config URL", __func__, url); goto out; } else { char ebuf[100]; regerror(code, &url_regex, ebuf, sizeof(ebuf)); LogWarn(COMPONENT_CONFIG, "%s: Error in regexec: %s", __func__, ebuf); goto out; } PTHREAD_RWLOCK_rdlock(&url_rwlock); glist_for_each(gl, &url_providers) { url_p = glist_entry(gl, struct gsh_url_provider, link); if (!strcasecmp(url_type, url_p->name)) { code = url_p->url_fetch(m_url, f, fbuf); break; } } PTHREAD_RWLOCK_unlock(&url_rwlock); out: gsh_free(url_type); gsh_free(m_url); return code; } /** @brief return resources allocated by url_fetch */ void config_url_release(FILE *f, char *fbuf) { fclose(f); free(fbuf); } nfs-ganesha-6.5/src/config_parsing/conf_url_rados.c000066400000000000000000000246401473756622300225250ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* ---------------------------------------------------------------------------- * Copyright (C) 2017, Red Hat, Inc. * contributeur : Matt Benjamin mbenjamin@redhat.com * * This program 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 3 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 * 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 * --------------------------------------- */ #include "conf_url.h" #include "conf_url_rados.h" #include #include #include #include "log.h" #include "sal_functions.h" #include static regex_t url_regex; static rados_t cluster; static bool initialized; static rados_ioctx_t rados_watch_io_ctx; static uint64_t rados_watch_cookie; static char *rados_watch_oid; static struct rados_url_parameter { /** Path to ceph.conf */ char *ceph_conf; /** Userid (?) */ char *userid; /** watch URL */ char *watch_url; } rados_url_param; static struct config_item rados_url_params[] = { CONF_ITEM_PATH("ceph_conf", 1, MAXPATHLEN, NULL, rados_url_parameter, ceph_conf), CONF_ITEM_STR("userid", 1, MAXPATHLEN, NULL, rados_url_parameter, userid), CONF_ITEM_STR("watch_url", 1, MAXPATHLEN, NULL, rados_url_parameter, watch_url), CONFIG_EOL }; static void *rados_url_param_init(void *link_mem, void *self_struct) { if (self_struct == NULL) return &rados_url_param; else return NULL; } struct config_block rados_url_param_blk = { .dbus_interface_name = "org.ganesha.nfsd.config.rados_urls", .blk_desc.name = "RADOS_URLS", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = rados_url_param_init, .blk_desc.u.blk.params = rados_url_params, .blk_desc.u.blk.commit = noop_conf_commit }; static int rados_urls_set_param_from_conf(void *tree_node, struct config_error_type *err_type) { (void)load_config_from_node(tree_node, &rados_url_param_blk, NULL, true, err_type); if (!config_error_is_harmless(err_type)) { LogCrit(COMPONENT_INIT, "Error while parsing RADOS_URLS config block"); return -1; } LogFullDebug(COMPONENT_CONFIG, "%s parsed RADOS_URLS block, have ceph_conf=%s " " userid=%s", __func__, rados_url_param.ceph_conf, rados_url_param.userid); return 0; } /* decompose RADOS URL into (/(/))object * * verified to match each of the following: * * #define URL1 "my_rados_object" * #define URL2 "mypool_baby/myobject_baby" * #define URL3 "mypool-baby/myobject-baby" */ #define RADOS_URL_REGEX \ "([-a-zA-Z0-9_&=.]+)/?([-a-zA-Z0-9_&=.]+)?/?([-a-zA-Z0-9_&=/.]+)?" /** @brief url regex initializer */ static void init_url_regex(void) { int r; r = regcomp(&url_regex, RADOS_URL_REGEX, REG_EXTENDED); if (!!r) { LogFatal(COMPONENT_INIT, "Error initializing rados url regex"); } } static void cu_rados_url_early_init(void) { init_url_regex(); } extern struct config_error_type err_type; static int rados_url_client_setup(void) { int ret; if (initialized) return 0; ret = rados_create(&cluster, rados_url_param.userid); if (ret < 0) { LogEvent(COMPONENT_CONFIG, "%s: Failed in rados_create", __func__); return ret; } ret = rados_conf_read_file(cluster, rados_url_param.ceph_conf); if (ret < 0) { LogEvent(COMPONENT_CLIENTID, "%s: Failed to read ceph_conf", __func__); rados_shutdown(cluster); return ret; } ret = rados_connect(cluster); if (ret < 0) { LogEvent(COMPONENT_CONFIG, "%s: Failed to connect to cluster", __func__); rados_shutdown(cluster); return ret; } init_url_regex(); initialized = true; return 0; } static void cu_rados_url_init(void) { int ret; void *node; node = config_GetBlockNode("RADOS_URLS"); if (node) { ret = rados_urls_set_param_from_conf(node, &err_type); if (ret < 0) { LogEvent(COMPONENT_CONFIG, "%s: Failed to parse RADOS_URLS %d", __func__, ret); } } else { LogWarn(COMPONENT_CONFIG, "%s: RADOS_URLS config block not found", __func__); } rados_url_client_setup(); } static void cu_rados_url_shutdown(void) { if (initialized) { rados_shutdown(cluster); regfree(&url_regex); initialized = false; } } static inline char *match_dup(regmatch_t *m, const char *in) { char *s = NULL; if (m->rm_so >= 0) { int size; size = m->rm_eo - m->rm_so + 1; s = (char *)gsh_malloc(size); memcpy(s, in + m->rm_so, size - 1); s[size - 1] = '\0'; } return s; } static int rados_url_parse(const char *url, char **pool, char **ns, char **obj) { int ret; regmatch_t match[4]; ret = regexec(&url_regex, url, 4, match, 0); if (likely(!ret)) { regmatch_t *m; char *x1, *x2, *x3; m = &(match[1]); x1 = match_dup(m, url); m = &(match[2]); x2 = match_dup(m, url); m = &(match[3]); x3 = match_dup(m, url); *pool = NULL; *ns = NULL; *obj = NULL; if (x1) { if (!x2) { /* * object only * * @todo FIXME: should we reject this case? * I don't think there is such a * thing as a default pool */ *obj = x1; x1 = NULL; } else { *pool = x1; x1 = NULL; if (!x3) { *obj = x2; x2 = NULL; } else { *ns = x2; *obj = x3; x2 = NULL; x3 = NULL; } } } /* If any of x1, x2, or x3 are not consumed, free them. */ gsh_free(x1); gsh_free(x2); gsh_free(x3); } else if (ret == REG_NOMATCH) { LogWarn(COMPONENT_CONFIG, "%s: Failed to match %s as a config URL", __func__, url); } else { char ebuf[100]; regerror(ret, &url_regex, ebuf, sizeof(ebuf)); LogWarn(COMPONENT_CONFIG, "%s: Error in regexec: %s", __func__, ebuf); } return ret; } static int cu_rados_url_fetch(const char *url, FILE **f, char **fbuf) { rados_ioctx_t io_ctx; char *pool_name = NULL; char *object_name = NULL; char *rados_ns = NULL; char *streambuf = NULL; /* not optional (buggy open_memstream) */ FILE *stream = NULL; char buf[1024]; size_t streamsz; uint64_t off1 = 0; int ret; if (!initialized) { cu_rados_url_init(); } ret = rados_url_parse(url, &pool_name, &rados_ns, &object_name); if (ret) goto out; ret = rados_ioctx_create(cluster, pool_name, &io_ctx); if (ret < 0) { LogEvent(COMPONENT_CONFIG, "%s: Failed to create ioctx", __func__); cu_rados_url_shutdown(); goto out; } rados_ioctx_set_namespace(io_ctx, rados_ns); do { int nread, wrt, nwrt; uint64_t off2 = 0; nread = ret = rados_read(io_ctx, object_name, buf, 1024, off1); if (ret < 0) { LogEvent(COMPONENT_CONFIG, "%s: Failed reading %s/%s %s", __func__, pool_name, object_name, strerror(ret)); goto err; } off1 += nread; if (!stream) { streamsz = 1024; stream = open_memstream(&streambuf, &streamsz); } do { wrt = fwrite(buf + off2, 1, nread, stream); if (wrt > 0) { nwrt = MIN(nread, 1024); nread -= nwrt; off2 += nwrt; } } while (wrt > 0 && nread > 0); } while (ret > 0); if (likely(stream)) { /* rewind */ fseek(stream, 0L, SEEK_SET); /* return--caller will release */ *f = stream; *fbuf = streambuf; stream = NULL; streambuf = NULL; } err: rados_ioctx_destroy(io_ctx); out: if (stream) (void)fclose(stream); if (streambuf) { /* Was allocated via open_memstream so use free NOT gsh_free. */ free(streambuf); } /* allocated or NULL */ gsh_free(pool_name); gsh_free(rados_ns); gsh_free(object_name); return ret; } static struct gsh_url_provider rados_url_provider = { .name = "rados", .url_init = cu_rados_url_early_init, .url_shutdown = cu_rados_url_shutdown, .url_fetch = cu_rados_url_fetch }; void conf_url_rados_pkginit(void) { register_url_provider(&rados_url_provider); } static void rados_url_watchcb(void *arg, uint64_t notify_id, uint64_t handle, uint64_t notifier_id, void *data, size_t data_len) { int ret; /* ACK it to keep things moving */ ret = rados_notify_ack(rados_watch_io_ctx, rados_watch_oid, notify_id, rados_watch_cookie, NULL, 0); if (ret < 0) LogEvent(COMPONENT_CONFIG, "rados_notify_ack failed: %d", ret); /* Send myself a SIGHUP */ kill(getpid(), SIGHUP); } int rados_url_setup_watch(void) { int ret; void *node; char *pool = NULL, *ns = NULL, *obj = NULL; char *url; /* No RADOS_URLs block? Just return */ node = config_GetBlockNode("RADOS_URLS"); if (!node) return 0; ret = rados_urls_set_param_from_conf(node, &err_type); if (ret < 0) { LogEvent(COMPONENT_CONFIG, "%s: Failed to parse RADOS_URLS %d", __func__, ret); return ret; } /* No watch parameter? Just return */ if (rados_url_param.watch_url == NULL) return 0; if (strncmp(rados_url_param.watch_url, "rados://", 8)) { LogEvent(COMPONENT_CONFIG, "watch_url doesn't start with rados://"); return -1; } url = rados_url_param.watch_url + 8; /* Parse the URL */ ret = rados_url_parse(url, &pool, &ns, &obj); if (ret) return ret; ret = rados_url_client_setup(); if (ret) goto out; /* Set up an ioctx */ ret = rados_ioctx_create(cluster, pool, &rados_watch_io_ctx); if (ret < 0) { LogEvent(COMPONENT_CONFIG, "%s: Failed to create ioctx", __func__); goto out; } rados_ioctx_set_namespace(rados_watch_io_ctx, ns); ret = rados_watch3(rados_watch_io_ctx, obj, &rados_watch_cookie, rados_url_watchcb, NULL, 30, NULL); if (ret) { rados_ioctx_destroy(rados_watch_io_ctx); LogEvent(COMPONENT_CONFIG, "Failed to set watch on RADOS_URLS object: %d", ret); } else { rados_watch_oid = obj; obj = NULL; } out: gsh_free(pool); gsh_free(ns); gsh_free(obj); return ret; } void rados_url_shutdown_watch(void) { int ret; if (rados_watch_oid) { ret = rados_unwatch2(rados_watch_io_ctx, rados_watch_cookie); if (ret) LogEvent(COMPONENT_CONFIG, "Failed to unwatch RADOS_URLS object: %d", ret); rados_ioctx_destroy(rados_watch_io_ctx); rados_watch_io_ctx = NULL; gsh_free(rados_watch_oid); rados_watch_oid = NULL; /* Leave teardown of client to the %url parser shutdown */ } } nfs-ganesha-6.5/src/config_parsing/conf_yacc.y000066400000000000000000000271701473756622300215010ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* ---------------------------------------------------------------------------- * Copyright CEA/DAM/DIF (2007) * contributeur : Thomas LEIBOVICI thomas.leibovici@cea.fr * * This program 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 3 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 * 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 * * --------------------------------------- */ %code top { #pragma GCC diagnostic ignored "-Wunused-value" #pragma GCC diagnostic ignored "-Wunused-variable" #include "config.h" #include "config_parsing.h" #include "analyse.h" #include "abstract_mem.h" #include #include "log.h" #if HAVE_STRING_H # include #endif } /* Options and variants */ %define api.pure %lex-param {struct parser_state *st} %parse-param {struct parser_state *st} %locations %code requires { /* alert the parser that we have our own definition */ # define YYLTYPE_IS_DECLARED 1 } %union { char *token; struct config_node *node; } %code provides { typedef struct YYLTYPE { int first_line; int first_column; int last_line; int last_column; char *filename; } YYLTYPE; # define YYLLOC_DEFAULT(Current, Rhs, N) \ do \ if (N) \ { \ (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ (Current).filename = YYRHSLOC (Rhs, 1).filename; \ } \ else \ { /* empty RHS */ \ (Current).first_line = (Current).last_line = \ YYRHSLOC (Rhs, 0).last_line; \ (Current).first_column = (Current).last_column = \ YYRHSLOC (Rhs, 0).last_column; \ (Current).filename = NULL; /* new */ \ } \ while (0) int ganeshun_yylex(YYSTYPE *yylval_param, YYLTYPE *yylloc_param, void *scanner); int ganesha_yylex(YYSTYPE *yylval_param, YYLTYPE *yylloc_param, struct parser_state *st); void config_parse_error(YYLTYPE *yylloc_param, struct parser_state *st, char *format, ...); void ganesha_yyerror(YYLTYPE *yylloc_param, void *yyscanner, char*); extern struct glist_head all_blocks; struct config_node *config_block(char *blockname, struct config_node *list, YYLTYPE *yylloc_param, struct parser_state *st); void link_node(struct config_node *node); struct config_node *link_sibling(struct config_node *first, struct config_node *second); struct config_node *config_stmt(char *varname, struct config_node *exprlist, YYLTYPE *yylloc_param, struct parser_state *st); struct config_node *config_term(char *opcode, char *varval, enum term_type type, YYLTYPE *yylloc_param, struct parser_state *st); } %token _ERROR_ %token LCURLY_OP %token RCURLY_OP %token EQUAL_OP %token COMMA_OP %token SEMI_OP %token IDENTIFIER %token STRING %token DQUOTE %token SQUOTE %token TOKEN %token REGEX_TOKEN %token TOK_PATH %token TOK_TRUE %token TOK_FALSE %token TOK_DECNUM %token TOK_HEXNUM %token TOK_OCTNUM %token TOK_ARITH_OP %token TOK_V4_ANY %token TOK_V4ADDR %token TOK_V4CIDR %token TOK_V6ADDR %token TOK_V6CIDR %token TOK_FSID %token TOK_NETGROUP %type deflist %type definition %type block %type statement %type exprlist %type expression %start config %% config: { /* empty */ config_parse_error(&yyloc, st, "Empty configuration file"); } | deflist { if ($1 != NULL) glist_add_tail(&($1)->node, &st->root_node->root.u.nterm.sub_nodes); link_node(&st->root_node->root); } ; deflist: definition { $$ = $1; } | deflist definition { $$ = link_sibling($1, $2); } ; /* definition: statement | block ; */ definition: IDENTIFIER EQUAL_OP statement { $$ = config_stmt($1, $3, &@$, st); } | IDENTIFIER LCURLY_OP block { $$=config_block($1, $3, &@$, st); } ; statement: exprlist SEMI_OP { $$ = $1; } | error SEMI_OP { config_parse_error(&@$, st, "Syntax error in statement"); yyerrok; $$ = NULL; } ; block: RCURLY_OP { /* empty */ $$ = NULL; } | deflist RCURLY_OP { $$ = $1; } | error RCURLY_OP { config_parse_error(&@$, st, "Syntax error in block"); yyerrok; $$ = NULL; } ; /* A statement is a comma separated sequence of option/value tokens */ exprlist: { /* empty */ $$ = NULL; } | expression { $$ = $1; } | exprlist COMMA_OP expression { $$ = link_sibling($1, $3); } ; expression: TOK_PATH { $$ = config_term(NULL, $1, TERM_PATH, &@$, st); } | TOKEN { $$ = config_term(NULL, $1, TERM_TOKEN, &@$, st); } | REGEX_TOKEN { $$ = config_term(NULL, $1, TERM_REGEX, &@$, st); } | STRING { $$ = config_term(NULL, $1, TERM_STRING, &@$, st); } | DQUOTE { $$ = config_term(NULL, $1, TERM_DQUOTE, &@$, st); } | SQUOTE { $$ = config_term(NULL, $1, TERM_SQUOTE, &@$, st); } | TOK_TRUE { $$ = config_term(NULL, $1, TERM_TRUE, &@$, st); } | TOK_FALSE { $$ = config_term(NULL, $1, TERM_FALSE, &@$, st); } | TOK_OCTNUM { $$ = config_term(NULL, $1, TERM_OCTNUM, &@$, st); } | TOK_HEXNUM { $$ = config_term(NULL, $1, TERM_HEXNUM, &@$, st); } | TOK_DECNUM { $$ = config_term(NULL, $1, TERM_DECNUM, &@$, st); } | TOK_ARITH_OP TOK_OCTNUM { $$ = config_term($1, $2, TERM_OCTNUM, &@$, st); } | TOK_ARITH_OP TOK_HEXNUM { $$ = config_term($1, $2, TERM_HEXNUM, &@$, st); } | TOK_ARITH_OP TOK_DECNUM { $$ = config_term($1, $2, TERM_DECNUM, &@$, st); } | TOK_V4_ANY { $$ = config_term(NULL, $1, TERM_V4_ANY, &@$, st); } | TOK_V4ADDR { $$ = config_term(NULL, $1, TERM_V4ADDR, &@$, st); } | TOK_V4CIDR { $$ = config_term(NULL, $1, TERM_V4CIDR, &@$, st); } | TOK_V6ADDR { $$ = config_term(NULL, $1, TERM_V6ADDR, &@$, st); } | TOK_V6CIDR { $$ = config_term(NULL, $1, TERM_V6CIDR, &@$, st); } | TOK_FSID { $$ = config_term(NULL, $1, TERM_FSID, &@$, st); } | TOK_NETGROUP { $$ = config_term(NULL, $1, TERM_NETGROUP, &@$, st); } ; %% /** * @brief Report an scanner/parser error * * Replacement for yyerror() to get more info. * HACK ALERT: new_file() does not have yylloc initialized yet for * first file so create a string and init line number for it. */ void config_parse_error(YYLTYPE *yylloc_param, struct parser_state *st, char *format, ...) { FILE *fp = st->err_type->fp; va_list arguments; char *filename = ""; int linenum = 0;; if (fp == NULL) return; /* no stream, no message */ if (yylloc_param != NULL) { filename = yylloc_param->filename; linenum = yylloc_param->first_line; } va_start(arguments, format); config_error(fp, filename, linenum, format, arguments); va_end(arguments); } /* This is here because bison wants it declared. * We do not use it because we can't get around the API. * Use config_parse_error() instead. */ void ganesha_yyerror(YYLTYPE *yylloc_param, void *yyscanner, char *s){ LogCrit(COMPONENT_CONFIG, "Config file (%s:%d) error: %s\n", yylloc_param->filename, yylloc_param->first_line, s); } /** * @brief Notes on parse tree linkage * * We use glist a little differently so beware. * Elsewhere in the server, we only glist_init() the head * and leave the 'node' members alone (both next and prev == NULL). * However, the parse FSM handles siblings as a list via the LR rules. * This means that while sub_nodes is the "head" of the list, it only * gets linked in when the rules add the already formed list is fully * parsed. Therefore, to make this all work, each node's 'node' member * gets a turn as the head which requires it to be glist_init()'d * contrary to what the rest of the code does. The last node to play * 'head' is then the 'sub_nodes' member of the parent. */ /** * Create a block item with the given content */ struct glist_head all_blocks; void dump_all_blocks(void) { struct glist_head *glh; struct config_node *node; int ix = 0; glist_for_each(glh, &all_blocks) { node = glist_entry(glh, struct config_node, blocks); printf("%s: ix: %d node blockname: %s\n", __func__, ix, node->u.nterm.name); ++ix; } } struct config_node *config_block(char *blockname, struct config_node *list, YYLTYPE *yylloc_param, struct parser_state *st) { struct config_node *node; node = gsh_calloc(1, sizeof(struct config_node)); if (node == NULL) { st->err_type->resource = true; return NULL; } glist_init(&node->node); glist_init(&node->blocks); node->u.nterm.name = blockname; node->filename = yylloc_param->filename; node->linenumber = yylloc_param->first_line; node->type = TYPE_BLOCK; glist_init(&node->u.nterm.sub_nodes); if (list != NULL) { glist_add_tail(&list->node, &node->u.nterm.sub_nodes); link_node(node); } glist_add_tail(&all_blocks, &node->blocks); return node; } /** * @brief Walk the subnode list and update all the sub-blocks in it * so we can find the root of the parse tree when we need it. */ void link_node(struct config_node *node) { struct config_node *subnode; struct glist_head *ns; assert(node->type == TYPE_BLOCK || node->type == TYPE_ROOT || node->type == TYPE_STMT); glist_for_each(ns, &node->u.nterm.sub_nodes) { subnode = glist_entry(ns, struct config_node, node); subnode->parent = node; } } /** * @brief Link siblings together * */ struct config_node *link_sibling(struct config_node *first, struct config_node *second) { if (first == NULL) { return second; } else { if (second != NULL) glist_add_tail(&first->node, &second->node); return first; } } /** * Create a term (value) */ struct config_node *config_term(char *opcode, char *varval, enum term_type type, YYLTYPE *yylloc_param, struct parser_state *st) { struct config_node *node; node = gsh_calloc(1, sizeof(struct config_node)); if (node == NULL) { st->err_type->resource = true; return NULL; } glist_init(&node->node); node->filename = yylloc_param->filename; node->linenumber = yylloc_param->first_line; node->type = TYPE_TERM; node->u.term.type = type; node->u.term.op_code = opcode; node->u.term.varvalue = varval; return node; } /** * Create a statement node (key = list of terms) */ struct config_node *config_stmt(char *varname, struct config_node *exprlist, YYLTYPE *yylloc_param, struct parser_state *st) { struct config_node *node; node = gsh_calloc(1, sizeof(struct config_node)); if (node == NULL) { st->err_type->resource = true; return NULL; } glist_init(&node->node); glist_init(&node->u.nterm.sub_nodes); node->filename = yylloc_param->filename; node->linenumber = yylloc_param->first_line; node->type = TYPE_STMT; node->u.nterm.name = varname; if (exprlist != NULL) { glist_add_tail(&exprlist->node, &node->u.nterm.sub_nodes); link_node(node); } return node; } int ganesha_yylex(YYSTYPE *yylval_param, YYLTYPE *yylloc_param, struct parser_state *st) { return ganeshun_yylex(yylval_param, yylloc_param, st->scanner); } nfs-ganesha-6.5/src/config_parsing/config_parsing.c000066400000000000000000001475541473756622300225300ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* ---------------------------------------------------------------------------- * Copyright CEA/DAM/DIF (2007) * contributeur : Thomas LEIBOVICI thomas.leibovici@cea.fr * * This program 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 3 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 * 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 * * --------------------------------------- */ #include "config.h" #include #include #include #if HAVE_STRING_H #include #endif #include #include #include #include #include #include "config_parsing.h" #include "analyse.h" #include "abstract_mem.h" #include "conf_yacc.h" #include "log.h" #include "fsal_convert.h" /* config_ParseFile: * Reads the content of a configuration file and * stores it in a memory structure. */ config_file_t config_ParseFile(char *file_path, struct config_error_type *err_type) { struct parser_state st; struct config_root *root; int rc; glist_init(&all_blocks); memset(&st, 0, sizeof(struct parser_state)); st.err_type = err_type; rc = ganeshun_yy_init_parser(file_path, &st); if (rc) { return NULL; } rc = ganesha_yyparse(&st); root = st.root_node; if (rc != 0) config_proc_error( root, err_type, (rc == 1 ? "Configuration syntax errors found" : "Configuration parse ran out of memory")); #ifdef DUMP_PARSE_TREE print_parse_tree(stderr, root); #endif ganeshun_yy_cleanup_parser(&st); return (config_file_t)root; } /** * Return the first node in the global config block list with * name == block_name */ void *config_GetBlockNode(const char *block_name) { struct glist_head *glh; struct config_node *node; glist_for_each(glh, &all_blocks) { node = glist_entry(glh, struct config_node, blocks); if (!strcasecmp(node->u.nterm.name, block_name)) { return node; } } return NULL; } /** * config_Print: * Print the content of the syntax tree * to a file. */ void config_Print(FILE *output, config_file_t config) { print_parse_tree(output, (struct config_root *)config); } /** * config_Free: * Free the memory structure that store the configuration. */ void config_Free(config_file_t config) { if (config != NULL) free_parse_tree((struct config_root *)config); return; } /** * @brief Return an error string constructed from an err_type * * The string is constructed in allocate memory that must be freed * by the caller. * * @param err_type [IN] the err_type struct in question. * * @return a NULL term'd string or NULL on failure. */ char *err_type_str(struct config_error_type *err_type) { char *buf = NULL; size_t bufsize; FILE *fp; if (config_error_no_error(err_type)) return gsh_strdup("(no errors)"); fp = open_memstream(&buf, &bufsize); if (fp == NULL) { LogCrit(COMPONENT_CONFIG, "Could not open memstream for err_type string"); return NULL; } fputc('(', fp); if (err_type->scan) fputs("token scan, ", fp); if (err_type->parse) fputs("parser rule, ", fp); if (err_type->init) fputs("block init, ", fp); if (err_type->fsal) fputs("fsal load, ", fp); if (err_type->all_exp_create_err) fputs("export create error, ", fp); if (err_type->resource) fputs("resource alloc, ", fp); if (err_type->unique) fputs("not unique param, ", fp); if (err_type->invalid) fputs("invalid param value, ", fp); if (err_type->missing) fputs("missing mandatory param, ", fp); if (err_type->validate) fputs("block validation, ", fp); if (err_type->exists) fputs("block exists, ", fp); if (err_type->internal) fputs("internal error, ", fp); if (err_type->bogus) fputs("unknown param, ", fp); if (err_type->deprecated) fputs("deprecated param, ", fp); if (ferror(fp)) LogCrit(COMPONENT_CONFIG, "file error while constructing err_type string"); fclose(fp); if (buf == NULL) { LogCrit(COMPONENT_CONFIG, "close of memstream for err_type string failed"); return NULL; } /* each of the above strings (had better) have ', ' at the end! */ if (buf[strlen(buf) - 1] == ' ') { buf[bufsize - 2] = ')'; buf[bufsize - 1] = '\0'; } return buf; } bool init_error_type(struct config_error_type *err_type) { memset(err_type, 0, sizeof(struct config_error_type)); err_type->fp = open_memstream(&err_type->diag_buf, &err_type->diag_buf_size); if (err_type->fp == NULL) { LogCrit(COMPONENT_MAIN, "Could not open memory stream for parser errors"); return false; } return true; } /** * @brief Log an error to the parse error stream * * cnode is void * because struct config_node is hidden outside parse code * and we can report errors in fsal_manager.c etc. */ void config_proc_error(void *cnode, struct config_error_type *err_type, char *format, ...) { struct config_node *node = cnode; FILE *fp = err_type->fp; char *filename = ""; int linenumber = 0; va_list arguments; if (fp == NULL) return; /* no stream, no message */ if (node != NULL && node->filename != NULL) { filename = node->filename; linenumber = node->linenumber; } va_start(arguments, format); config_error(fp, filename, linenumber, format, arguments); va_end(arguments); } void config_errs_to_log(char *err, void *dest, struct config_error_type *err_type) { log_levels_t log_level; if (config_error_is_fatal(err_type) || config_error_is_crit(err_type)) log_level = NIV_CRIT; else if (config_error_is_harmless(err_type)) log_level = NIV_WARN; else log_level = NIV_EVENT; DisplayLogComponentLevel(COMPONENT_CONFIG, __FILE__, __LINE__, (char *)__func__, log_level, "%s", err); } int report_config_errors(struct config_error_type *err_type, void *dest, void (*logger)(char *msg, void *dest, struct config_error_type *err_type)) { char *msgp, *cp; int count = 0; if (err_type->fp == NULL) return 0; fclose(err_type->fp); err_type->fp = NULL; msgp = err_type->diag_buf; if (msgp == NULL) return 0; while (*msgp != '\0') { cp = index(msgp, '\f'); if (cp != NULL) { *cp++ = '\0'; logger(msgp, dest, err_type); msgp = cp; } else { logger(msgp, dest, err_type); break; } count++; } gsh_free(err_type->diag_buf); err_type->diag_buf = NULL; return count; } static bool convert_bool(struct config_node *node, bool *b, struct config_error_type *err_type) { if (node->u.term.type == TERM_TRUE) *b = true; else if (node->u.term.type == TERM_FALSE) *b = false; else { config_proc_error(node, err_type, "Expected boolean (true/false) got (%s)", node->u.term.varvalue); err_type->errors++; err_type->invalid = true; return false; } return true; } static bool convert_number(struct config_node *node, struct config_item *item, uint64_t *num, struct config_error_type *err_type) { uint64_t val, mask, min, max; int64_t sval, smin, smax; char *endptr; int base; bool signed_int = false; bool zero_ok = false, inrange; if (node->type != TYPE_TERM) { config_proc_error(node, err_type, "Expected a number, got a %s", (node->type == TYPE_ROOT ? "root node" : (node->type == TYPE_BLOCK ? "block" : "statement"))); goto errout; } else if (node->u.term.type == TERM_DECNUM) { base = 10; } else if (node->u.term.type == TERM_HEXNUM) { base = 16; } else if (node->u.term.type == TERM_OCTNUM) { base = 8; } else { config_proc_error(node, err_type, "Expected a number, got a %s", config_term_desc(node->u.term.type)); goto errout; } errno = 0; assert(*node->u.term.varvalue != '\0'); val = strtoull(node->u.term.varvalue, &endptr, base); if (*endptr != '\0' || errno != 0) { config_proc_error(node, err_type, "(%s) is not an integer", node->u.term.varvalue); goto errout; } switch (item->type) { case CONFIG_INT16: smin = item->u.i16.minval; smax = item->u.i16.maxval; zero_ok = item->u.i16.zero_ok; signed_int = true; break; case CONFIG_UINT16: mask = UINT16_MAX; min = item->u.ui16.minval; max = item->u.ui16.maxval; zero_ok = item->u.ui16.zero_ok; break; case CONFIG_INT32: smin = item->u.i32.minval; smax = item->u.i32.maxval; zero_ok = item->u.i32.zero_ok; signed_int = true; break; case CONFIG_UINT32: mask = UINT32_MAX; min = item->u.ui32.minval; max = item->u.ui32.maxval; zero_ok = item->u.ui32.zero_ok; break; case CONFIG_ANON_ID: /* Internal to config, anonymous id is treated as int64_t */ smin = item->u.i64.minval; smax = item->u.i64.maxval; zero_ok = item->u.i64.zero_ok; signed_int = true; break; case CONFIG_INT64: smin = item->u.i64.minval; smax = item->u.i64.maxval; zero_ok = item->u.i64.zero_ok; signed_int = true; break; case CONFIG_UINT64: mask = UINT64_MAX; min = item->u.ui64.minval; max = item->u.ui64.maxval; zero_ok = item->u.ui64.zero_ok; break; default: goto errout; } if (signed_int) { if (node->u.term.op_code == NULL) { /* Check for overflow of int64_t on positive */ if (val > (uint64_t)INT64_MAX) { config_proc_error(node, err_type, "(%s) is out of range", node->u.term.varvalue); goto errout; } sval = val; } else if (*node->u.term.op_code == '-') { /* Check for underflow of int64_t on negative */ if (val > ((uint64_t)INT64_MAX) + 1) { config_proc_error(node, err_type, "(%s) is out of range", node->u.term.varvalue); goto errout; } sval = -((int64_t)val); } else { config_proc_error( node, err_type, "(%c) is not allowed for signed values", *node->u.term.op_code); goto errout; } inrange = (sval >= smin && sval <= smax) || (zero_ok && sval == 0); if (!inrange) { config_proc_error(node, err_type, "(%s) is out of range", node->u.term.varvalue); goto errout; } val = (uint64_t)sval; } else { if (node->u.term.op_code != NULL && *node->u.term.op_code == '~') { /* Check for overflow before negation */ if ((val & ~mask) != 0) { config_proc_error(node, err_type, "(%s) is out of range", node->u.term.varvalue); goto errout; } val = ~val & mask; } else if (node->u.term.op_code != NULL) { config_proc_error( node, err_type, "(%c) is not allowed for signed values", *node->u.term.op_code); goto errout; } inrange = (val >= min && val <= max) || (zero_ok && val == 0); if (!inrange) { config_proc_error(node, err_type, "(%s) is out of range", node->u.term.varvalue); goto errout; } } *num = val; return true; errout: err_type->errors++; err_type->invalid = true; return false; } /** * @brief convert an fsid which is a "<64bit num>.<64bit num" * * NOTE: using assert() here because the parser has already * validated so bad things have happened to the parse tree if... * */ static bool convert_fsid(struct config_node *node, void *param, struct config_error_type *err_type) { struct fsal_fsid__ *fsid = (struct fsal_fsid__ *)param; uint64_t major, minor; char *endptr, *sp; int base; if (node->type != TYPE_TERM) { config_proc_error(node, err_type, "Expected an FSID, got a %s", (node->type == TYPE_ROOT ? "root node" : (node->type == TYPE_BLOCK ? "block" : "statement"))); goto errout; } if (node->u.term.type != TERM_FSID) { config_proc_error(node, err_type, "Expected an FSID, got a %s", config_term_desc(node->u.term.type)); goto errout; } errno = 0; sp = node->u.term.varvalue; if (sp[0] == '0') { if (sp[1] == 'x' || sp[1] == 'X') base = 16; else base = 8; } else base = 10; major = strtoull(sp, &endptr, base); assert(*endptr == '.'); if (errno != 0 || major == ULLONG_MAX) { config_proc_error(node, err_type, "(%s) major is out of range", node->u.term.varvalue); goto errout; } sp = endptr + 1; if (sp[0] == '0') { if (sp[1] == 'x' || sp[1] == 'X') base = 16; else base = 8; } else base = 10; minor = strtoull(sp, &endptr, base); assert(*endptr == '\0'); if (errno != 0 || minor == ULLONG_MAX) { config_proc_error(node, err_type, "(%s) minor is out of range", node->u.term.varvalue); goto errout; } fsid->major = major; fsid->minor = minor; return true; errout: err_type->invalid = true; err_type->errors++; return false; } /** * @brief Scan a list of CSV tokens. * */ static bool convert_list(struct config_node *node, struct config_item *item, uint32_t *flags, struct config_error_type *err_type) { struct config_item_list *tok; struct config_node *sub_node; struct glist_head *nsi, *nsn; bool found; int errors = 0; *flags = 0; glist_for_each_safe(nsi, nsn, &node->u.nterm.sub_nodes) { sub_node = glist_entry(nsi, struct config_node, node); assert(sub_node->type == TYPE_TERM); found = false; for (tok = item->u.lst.tokens; tok->token != NULL; tok++) { if (strcasecmp(sub_node->u.term.varvalue, tok->token) == 0) { *flags |= tok->value; found = true; } } if (!found) { config_proc_error(node, err_type, "Unknown token (%s)", sub_node->u.term.varvalue); err_type->bogus = true; errors++; } } err_type->errors += errors; return errors == 0; } static bool convert_enum(struct config_node *node, struct config_item *item, uint32_t *val, struct config_error_type *err_type) { struct config_item_list *tok; bool found; tok = item->u.lst.tokens; found = false; while (tok->token != NULL) { if (strcasecmp(node->u.term.varvalue, tok->token) == 0) { *val = tok->value; found = true; } tok++; } if (!found) { config_proc_error(node, err_type, "Unknown token (%s)", node->u.term.varvalue); err_type->bogus = true; err_type->errors++; } return found; } static void convert_inet_addr(struct config_node *node, struct config_item *item, sockaddr_t *sock, struct config_error_type *err_type) { struct addrinfo hints; struct addrinfo *res = NULL; int rc; if (node->u.term.type != TERM_V4ADDR && node->u.term.type != TERM_V4_ANY && node->u.term.type != TERM_V6ADDR) { config_proc_error(node, err_type, "Expected an IP address, got a %s", config_term_desc(node->u.term.type)); err_type->invalid = true; err_type->errors++; return; } /* Try IPv6 (with mapping) first. If this fails, fall back on IPv4, if * a v4 address was given. */ memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_INET6; hints.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED; rc = getaddrinfo(node->u.term.varvalue, NULL, &hints, &res); if (rc != 0 && (node->u.term.type == TERM_V4ADDR || node->u.term.type == TERM_V4_ANY)) { hints.ai_family = AF_INET; rc = getaddrinfo(node->u.term.varvalue, NULL, &hints, &res); } if (rc == 0) { memcpy(sock, res->ai_addr, res->ai_addrlen); } else { config_proc_error(node, err_type, "No IP address found for %s because:%s", node->u.term.varvalue, gai_strerror(rc)); err_type->invalid = true; err_type->errors++; } if (res != NULL) freeaddrinfo(res); return; } /** * @brief Walk the term node list and call handler for each node * * This is effectively a callback on each token in the list. * Pass along a type hint based on what the parser recognized. * * @param node [IN] pointer to the statement node * @param item [IN] pointer to the config_item table entry * @param param_addr [IN] pointer to target struct member * @param err_type [OUT] error handling ref * @return number of errors */ static void do_proc(struct config_node *node, struct config_item *item, void *param_addr, struct config_error_type *err_type) { struct config_node *term_node; struct glist_head *nsi, *nsn; int rc = 0; assert(node->type == TYPE_STMT); glist_for_each_safe(nsi, nsn, &node->u.nterm.sub_nodes) { term_node = glist_entry(nsi, struct config_node, node); rc += item->u.proc.handler(term_node->u.term.varvalue, term_node->u.term.type, item, param_addr, term_node, err_type); } err_type->errors += rc; } /** * @brief Lookup the first node in the list by this name * * @param list - head of the glist * @param name - node name of interest * * @return first matching node or NULL */ static struct config_node *lookup_node(struct glist_head *list, const char *name) { struct config_node *node; struct glist_head *ns; glist_for_each(ns, list) { node = glist_entry(ns, struct config_node, node); assert(node->type == TYPE_BLOCK || node->type == TYPE_STMT); if (strcasecmp(name, node->u.nterm.name) == 0) { node->found = true; return node; } } return NULL; } /** * @brief Lookup the next node in list * * @param list - head of the glist * @param start - continue the lookup from here * @param name - node name of interest * * @return first matching node or NULL */ static struct config_node *lookup_next_node(struct glist_head *list, struct glist_head *start, const char *name) { struct config_node *node; struct glist_head *ns; glist_for_each_next(start, ns, list) { node = glist_entry(ns, struct config_node, node); assert(node->type == TYPE_BLOCK || node->type == TYPE_STMT); if (strcasecmp(name, node->u.nterm.name) == 0) { node->found = true; return node; } } return NULL; } static const char *config_type_str(enum config_type type) { switch (type) { case CONFIG_NULL: return "CONFIG_NULL"; case CONFIG_INT16: return "CONFIG_INT16"; case CONFIG_UINT16: return "CONFIG_UINT16"; case CONFIG_INT32: return "CONFIG_INT32"; case CONFIG_UINT32: return "CONFIG_UINT32"; case CONFIG_INT64: return "CONFIG_INT64"; case CONFIG_UINT64: return "CONFIG_UINT64"; case CONFIG_FSID: return "CONFIG_FSID"; case CONFIG_ANON_ID: return "CONFIG_ANON_ID"; case CONFIG_STRING: return "CONFIG_STRING"; case CONFIG_PATH: return "CONFIG_PATH"; case CONFIG_LIST: return "CONFIG_LIST"; case CONFIG_ENUM: return "CONFIG_ENUM"; case CONFIG_TOKEN: return "CONFIG_TOKEN"; case CONFIG_BOOL: return "CONFIG_BOOL"; case CONFIG_BOOLBIT: return "CONFIG_BOOLBIT"; case CONFIG_IP_ADDR: return "CONFIG_IP_ADDR"; case CONFIG_BLOCK: return "CONFIG_BLOCK"; case CONFIG_PROC: return "CONFIG_PROC"; case CONFIG_DEPRECATED: return "CONFIG_DEPRECATED"; } return "unknown"; } static bool do_block_init(struct config_node *blk_node, struct config_item *params, void *param_struct, struct config_error_type *err_type) { struct config_item *item; void *param_addr; sockaddr_t *sock; struct addrinfo hints; struct addrinfo *res = NULL; int rc; int errors = 0; for (item = params; item->name != NULL; item++) { param_addr = ((char *)param_struct + item->off); LogFullDebug(COMPONENT_CONFIG, "%p name=%s type=%s", param_addr, item->name, config_type_str(item->type)); switch (item->type) { case CONFIG_NULL: break; case CONFIG_INT16: *(int16_t *)param_addr = item->u.i16.def; break; case CONFIG_UINT16: *(uint16_t *)param_addr = item->u.ui16.def; break; case CONFIG_INT32: *(int32_t *)param_addr = item->u.i32.def; break; case CONFIG_UINT32: *(uint32_t *)param_addr = item->u.ui32.def; break; case CONFIG_INT64: *(int64_t *)param_addr = item->u.i64.def; break; case CONFIG_UINT64: *(uint64_t *)param_addr = item->u.ui64.def; break; case CONFIG_ANON_ID: *(uid_t *)param_addr = item->u.i64.def; break; case CONFIG_FSID: ((struct fsal_fsid__ *)param_addr)->major = item->u.fsid.def_maj; ((struct fsal_fsid__ *)param_addr)->minor = item->u.fsid.def_min; break; case CONFIG_STRING: case CONFIG_PATH: if (item->u.str.def) *(char **)param_addr = gsh_strdup(item->u.str.def); else *(char **)param_addr = NULL; break; case CONFIG_TOKEN: *(uint32_t *)param_addr = item->u.lst.def; break; case CONFIG_BOOL: *(bool *)param_addr = item->u.b.def; break; case CONFIG_BOOLBIT: if (item->u.bit.def) *(uint32_t *)param_addr |= item->u.bit.bit; else *(uint32_t *)param_addr &= ~item->u.bit.bit; break; case CONFIG_LIST: *(uint32_t *)param_addr |= item->u.lst.def; LogFullDebug(COMPONENT_CONFIG, "%p CONFIG_LIST %s mask=%08x def=%08x" " value=%08" PRIx32, param_addr, item->name, item->u.lst.mask, item->u.lst.def, *(uint32_t *)param_addr); break; case CONFIG_ENUM: *(uint32_t *)param_addr |= item->u.lst.def; LogFullDebug(COMPONENT_CONFIG, "%p CONFIG_ENUM %s mask=%08x def=%08x" " value=%08" PRIx32, param_addr, item->name, item->u.lst.mask, item->u.lst.def, *(uint32_t *)param_addr); break; case CONFIG_IP_ADDR: sock = (sockaddr_t *)param_addr; memset(sock, 0, sizeof(sockaddr_t)); errno = 0; /* Try IPv6 (with mapping) first. If this fails, fall * back on IPv4, if a v4 address was given. */ memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_INET6; hints.ai_flags = AI_PASSIVE; hints.ai_socktype = 0; hints.ai_protocol = 0; /* We don't actually pass "0.0.0.0" to this, so that it * gets the correct address for each address family. * AI_PASSIVE assures this. */ rc = getaddrinfo(NULL, "0", &hints, &res); if (rc != 0) { hints.ai_family = AF_INET; rc = getaddrinfo(NULL, "0", &hints, &res); } if (rc == 0) { memcpy(sock, res->ai_addr, res->ai_addrlen); } else { config_proc_error( blk_node, err_type, "Cannot set IP default for %s to %s because %s", item->name, item->u.ip.def, gai_strerror(rc)); errors++; } if (res != NULL) { freeaddrinfo(res); res = NULL; } break; case CONFIG_BLOCK: (void)item->u.blk.init(NULL, param_addr); break; case CONFIG_PROC: (void)item->u.proc.init(NULL, param_addr); break; case CONFIG_DEPRECATED: break; default: config_proc_error( blk_node, err_type, "Cannot set default for parameter %s, type(%d) yet", item->name, item->type); errors++; break; } } err_type->errors += errors; return errors == 0; } /** * @brief This is the NOOP init for block and proc parsing * * @param link_mem [IN] pointer to member in referencing structure * @param self_struct [IN] pointer to space reserved/allocated for block * * @return a pointer depending on context */ void *noop_conf_init(void *link_mem, void *self_struct) { assert(link_mem != NULL || self_struct != NULL); if (link_mem == NULL) return self_struct; else if (self_struct == NULL) return link_mem; else return NULL; } int noop_conf_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { return 0; } static bool proc_block(struct config_node *node, struct config_item *item, void *link_mem, struct config_error_type *err_type); /* * All the types of integers supported by the config processor */ union gen_int { bool b; int16_t i16; uint16_t ui16; int32_t i32; uint32_t ui32; int64_t i64; uint64_t ui64; }; /** * @brief Process the defined tokens in the params table * * The order of the parameter table is important. If any * parameter requires that another parameter in the table be * processed first, order the table appropriately. * * @param blk [IN] a parse tree node of type CONFIG_BLOCK * @param params [IN] points to a NULL term'd table of config_item's * @param relax [IN] if true, don't report unrecognized params. * @param param_struct [IN/OUT] the structure to be filled. * * @returns 0 on success, number of errors on failure. */ static int do_block_load(struct config_node *blk, struct config_item *params, bool relax, void *param_struct, struct config_error_type *err_type) { struct config_item *item; void *param_addr; struct config_node *node, *term_node, *next_node = NULL; struct glist_head *ns; int errors = 0, prev_errors = err_type->errors; for (item = params; item->name != NULL; item++) { uint64_t num64; bool bool_val; uint32_t num32 = 0; node = lookup_node(&blk->u.nterm.sub_nodes, item->name); if ((item->flags & CONFIG_MANDATORY) && (node == NULL)) { err_type->missing = true; errors = ++err_type->errors; config_proc_error( blk, err_type, "Mandatory field, %s is missing from block (%s)", item->name, blk->u.nterm.name); continue; } while (node != NULL) { next_node = lookup_next_node(&blk->u.nterm.sub_nodes, &node->node, item->name); if (next_node != NULL && (item->flags & CONFIG_UNIQUE)) { config_proc_error( next_node, err_type, "Parameter %s set more than once", next_node->u.nterm.name); err_type->unique = true; errors = ++err_type->errors; node = next_node; continue; } param_addr = ((char *)param_struct + item->off); LogFullDebug(COMPONENT_CONFIG, "%p name=%s type=%s", param_addr, item->name, config_type_str(item->type)); if (glist_empty(&node->u.nterm.sub_nodes)) { LogInfo(COMPONENT_CONFIG, "%s %s is empty", (node->type == TYPE_STMT ? "Statement" : "Block"), node->u.nterm.name); node = next_node; continue; } term_node = glist_first_entry(&node->u.nterm.sub_nodes, struct config_node, node); if ((item->type != CONFIG_BLOCK && item->type != CONFIG_PROC && item->type != CONFIG_LIST) && glist_length(&node->u.nterm.sub_nodes) > 1) { config_proc_error( node, err_type, "%s can have only one option. First one is (%s)", node->u.nterm.name, term_node->u.term.varvalue); err_type->invalid = true; errors = ++err_type->errors; node = next_node; continue; } switch (item->type) { case CONFIG_NULL: break; case CONFIG_INT16: if (convert_number(term_node, item, &num64, err_type)) *(int16_t *)param_addr = num64; break; case CONFIG_UINT16: if (convert_number(term_node, item, &num64, err_type)) *(uint16_t *)param_addr = num64; break; case CONFIG_INT32: if (convert_number(term_node, item, &num64, err_type)) { *(int32_t *)param_addr = num64; if (item->flags & CONFIG_MARK_SET) { void *mask_addr; mask_addr = ((char *)param_struct + item->u.i32.set_off); *(uint32_t *)mask_addr |= item->u.i32.bit; } } break; case CONFIG_UINT32: if (convert_number(term_node, item, &num64, err_type)) *(uint32_t *)param_addr = num64; break; case CONFIG_INT64: if (convert_number(term_node, item, &num64, err_type)) *(int64_t *)param_addr = num64; break; case CONFIG_UINT64: if (convert_number(term_node, item, &num64, err_type)) { *(uint64_t *)param_addr = num64; if (item->flags & CONFIG_MARK_SET) { void *mask_addr; mask_addr = ((char *)param_struct + item->u.ui64.set_off); *(uint32_t *)mask_addr |= item->u.ui64.bit; } } break; case CONFIG_ANON_ID: if (convert_number(term_node, item, &num64, err_type)) { *(uid_t *)param_addr = num64; if (item->flags & CONFIG_MARK_SET) { void *mask_addr; mask_addr = ((char *)param_struct + item->u.i64.set_off); *(uint32_t *)mask_addr |= item->u.i64.bit; } } break; case CONFIG_FSID: if (convert_fsid(term_node, param_addr, err_type)) { if (item->flags & CONFIG_MARK_SET) { void *mask_addr; mask_addr = ((char *)param_struct + item->u.fsid.set_off); *(uint32_t *)mask_addr |= item->u.fsid.bit; } } break; case CONFIG_STRING: if (*(char **)param_addr != NULL) gsh_free(*(char **)param_addr); *(char **)param_addr = gsh_strdup(term_node->u.term.varvalue); break; case CONFIG_PATH: if (*(char **)param_addr != NULL) gsh_free(*(char **)param_addr); /** @todo validate path with access() */ *(char **)param_addr = gsh_strdup(term_node->u.term.varvalue); break; case CONFIG_TOKEN: if (convert_enum(term_node, item, &num32, err_type)) *(uint32_t *)param_addr = num32; break; case CONFIG_BOOL: if (convert_bool(term_node, &bool_val, err_type)) *(bool *)param_addr = bool_val; break; case CONFIG_BOOLBIT: if (convert_bool(term_node, &bool_val, err_type)) { if (bool_val) *(uint32_t *)param_addr |= item->u.bit.bit; else *(uint32_t *)param_addr &= ~item->u.bit.bit; if (item->flags & CONFIG_MARK_SET) { void *mask_addr; mask_addr = ((char *)param_struct + item->u.bit.set_off); *(uint32_t *)mask_addr |= item->u.bit.bit; } } break; case CONFIG_LIST: if (item->u.lst.def == (*(uint32_t *)param_addr & item->u.lst.mask)) *(uint32_t *)param_addr &= ~item->u.lst.mask; if (convert_list(node, item, &num32, err_type)) { *(uint32_t *)param_addr |= num32; if (item->flags & CONFIG_MARK_SET) { void *mask_addr; mask_addr = ((char *)param_struct + item->u.lst.set_off); *(uint32_t *)mask_addr |= item->u.lst.mask; } } LogFullDebug( COMPONENT_CONFIG, "%p CONFIG_LIST %s mask=%08x flags=%08x" " value=%08" PRIx32, param_addr, item->name, item->u.lst.mask, num32, *(uint32_t *)param_addr); break; case CONFIG_ENUM: if (item->u.lst.def == (*(uint32_t *)param_addr & item->u.lst.mask)) *(uint32_t *)param_addr &= ~item->u.lst.mask; if (convert_enum(term_node, item, &num32, err_type)) { *(uint32_t *)param_addr |= num32; if (item->flags & CONFIG_MARK_SET) { void *mask_addr; mask_addr = ((char *)param_struct + item->u.lst.set_off); *(uint32_t *)mask_addr |= item->u.lst.mask; } } LogFullDebug( COMPONENT_CONFIG, "%p CONFIG_ENUM %s mask=%08x flags=%08x" " value=%08" PRIx32, param_addr, item->name, item->u.lst.mask, num32, *(uint32_t *)param_addr); break; case CONFIG_IP_ADDR: convert_inet_addr(term_node, item, (sockaddr_t *)param_addr, err_type); break; case CONFIG_BLOCK: if (!proc_block(node, item, param_addr, err_type)) config_proc_error( node, err_type, "Errors processing block (%s)", node->u.nterm.name); break; case CONFIG_PROC: do_proc(node, item, param_addr, err_type); break; case CONFIG_DEPRECATED: config_proc_error( node, err_type, "Deprecated parameter (%s)%s%s", item->name, item->u.deprecated.message ? " - " : "", item->u.deprecated.message ? item->u.deprecated.message : ""); err_type->deprecated = true; errors++; break; default: config_proc_error( term_node, err_type, "Cannot set value for type(%d) yet", item->type); err_type->internal = true; errors = ++err_type->errors; break; } node = next_node; } } /* Check for any errors in parsing params. * It will set to default value if parsing fails. * For params like Export_Id if parsing fails, we * will end up setting it to 1, which could cause issue * if we set it to 1 for multiple exports. */ if (err_type->errors > prev_errors) errors = err_type->errors; if (relax) return errors; /* We've been marking config nodes as being "seen" during the * scans. Report the bogus and typo inflicted bits. */ glist_for_each(ns, &blk->u.nterm.sub_nodes) { node = glist_entry(ns, struct config_node, node); if (node->found) node->found = false; else { config_proc_error(node, err_type, "Unknown parameter (%s)", node->u.nterm.name); err_type->bogus = true; errors++; } } return errors; } /** * @brief Process a block * * The item arg supplies two function pointers that * are defined as follows: * * init * This function manages memory for the sub-block's processing. * It has two arguments, a pointer to the link_mem param struct and * a pointer to the self_struct param struct. * If the self_struct argument is NULL, it returns a pointer to a usable * self_struct param struct. This can either be allocate memory * or a pointer to existing memory. * * If the self_struct argument is not NULL, it is the pointer it returned above. * The function reverts whatever it did above. * * commit * This function attaches the build param struct to its link_mem. * Before it does the attach, it will do validation of input if required. * Returns 0 if validation passes and the attach is successful. Otherwise, * it returns an error which will trigger a release of resources acquired * by the self_struct_init. * * Both of these functions are called in the context of the link_mem parse. * It is assumed that the link_mem has already been initialized including * the init of any glists before this function is called. * The self_struct does its own init of things like glist in param_mem. * * @param node - parse node of the subblock * @param item - config_item describing block * @param link_mem - pointer to the link_mem structure * @param err_type [OUT] pointer to error type return * * @ return true on success, false on errors. */ static bool proc_block(struct config_node *node, struct config_item *item, void *link_mem, struct config_error_type *err_type) { void *param_struct; int errors = 0; assert(item->type == CONFIG_BLOCK); if (node->type != TYPE_BLOCK) { config_proc_error(node, err_type, "%s is not a block!", item->name); err_type->invalid = true; err_type->errors++; return false; } LogFullDebug(COMPONENT_CONFIG, "------ At (%s:%d): process block %s link_mem = %p", node->filename, node->linenumber, item->name, link_mem); param_struct = item->u.blk.init(link_mem, NULL); if (param_struct == NULL) { config_proc_error(node, err_type, "Could not init block for %s", item->name); err_type->init = true; err_type->errors++; return false; } LogFullDebug(COMPONENT_CONFIG, "------ At (%s:%d): do_block_init %s", node->filename, node->linenumber, item->name); if (!do_block_init(node, item->u.blk.params, param_struct, err_type)) { config_proc_error(node, err_type, "Could not initialize parameters for %s", item->name); err_type->init = true; goto err_out; } if (item->u.blk.display != NULL) item->u.blk.display("DEFAULTS", node, link_mem, param_struct); LogFullDebug(COMPONENT_CONFIG, "------ At (%s:%d): do_block_load %s", node->filename, node->linenumber, item->name); errors = do_block_load(node, item->u.blk.params, (item->flags & CONFIG_RELAX) ? true : false, param_struct, err_type); if (errors > 0 && !cur_exp_config_error_is_harmless(err_type)) { config_proc_error( node, err_type, "%d errors while processing parameters for %s", errors, item->name); goto err_out; } if (item->u.blk.check && item->u.blk.check(param_struct, err_type)) { goto err_out; } LogFullDebug(COMPONENT_CONFIG, "------ At (%s:%d): commit %s", node->filename, node->linenumber, item->name); errors = item->u.blk.commit(node, link_mem, param_struct, err_type); if (errors > 0 && !cur_exp_config_error_is_harmless(err_type)) { config_proc_error(node, err_type, "%d validation errors in block %s", errors, item->name); goto err_out; } if (item->u.blk.display != NULL) item->u.blk.display("RESULT", node, link_mem, param_struct); if (err_type->dispose) { /* We had a config update case where this block must be * disposed of. Need to clear the flag so the next config * block processed gets a clear slate. */ LogFullDebug(COMPONENT_CONFIG, "Releasing block %p/%p", link_mem, param_struct); (void)item->u.blk.init(link_mem, param_struct); err_type->dispose = false; } return true; err_out: LogFullDebug(COMPONENT_CONFIG, "Releasing block %p/%p", link_mem, param_struct); (void)item->u.blk.init(link_mem, param_struct); err_type->dispose = false; return false; } void find_unused_blocks(config_file_t config, struct config_error_type *err_type) { struct config_root *tree = (struct config_root *)config; struct glist_head *ns; /* We've been marking config nodes as being "seen" during the * scans. Report the bogus and typo inflicted bits. */ glist_for_each(ns, &tree->root.u.nterm.sub_nodes) { struct config_node *node; node = glist_entry(ns, struct config_node, node); if (node->found) node->found = false; else { config_proc_error(node, err_type, "Unknown block (%s)", node->u.nterm.name); err_type->bogus = true; } } } /** * @brief Find the root of the parse tree given a node. * * @param node [IN] pointer to a TYPE_BLOCK node. * * @return the root of the tree. Errors are asserted. */ config_file_t get_parse_root(void *node) { struct config_node *parent; struct config_root *root; parent = (struct config_node *)node; while (parent->parent != NULL) { parent = parent->parent; assert(parent->type == TYPE_ROOT || parent->type == TYPE_BLOCK || parent->type == TYPE_STMT); } assert(parent->type == TYPE_ROOT); root = container_of(parent, struct config_root, root); return (config_file_t)root; } uint64_t get_config_generation(struct config_root *root) { return root->generation; } uint64_t get_parse_root_generation(void *node) { struct config_root *root = (struct config_root *)get_parse_root(node); return get_config_generation(root); } /** * @brief Data structures for walking parse trees * * These structures hold the result of the parse of a block * description string that is passed to find_config_nodes * * expr_parse is for blocks * * expr_parse_arg is for indexing/matching parameters. */ struct expr_parse_arg { char *name; char *value; struct expr_parse_arg *next; }; struct expr_parse { char *name; struct expr_parse_arg *arg; struct expr_parse *next; }; /** * @brief Skip 0 or more white space chars * * @return pointer to first non-white space char */ static inline char *skip_white(char *sp) { while (isspace(*sp)) sp++; return sp; } /** * @brief Find the end of a token, i.e. [A-Za-z0-9_]+ * * @return pointer to first unmatched char. */ static inline char *end_of_token(char *sp) { while (isalnum(*sp) || *sp == '_') sp++; return sp; } /** * @brief Find end of a value string. * * @return pointer to first white or syntax token */ static inline char *end_of_value(char *sp) { while (*sp != '\0') { if (isspace(*sp) || *sp == ',' || *sp == ')' || *sp == '(') break; sp++; } return sp; } /** * @brief Release storage for arg list */ static void free_expr_parse_arg(struct expr_parse_arg *argp) { struct expr_parse_arg *nxtarg, *arg = NULL; if (argp == NULL) return; for (arg = argp; arg != NULL; arg = nxtarg) { nxtarg = arg->next; gsh_free(arg->name); gsh_free(arg->value); gsh_free(arg); } } /** * @brief Release storage for the expression parse tree */ static void free_expr_parse(struct expr_parse *snode) { struct expr_parse *nxtnode, *node = NULL; if (snode == NULL) return; for (node = snode; node != NULL; node = nxtnode) { nxtnode = node->next; gsh_free(node->name); free_expr_parse_arg(node->arg); gsh_free(node); } } /** * @brief Parse "token = string" or "token1 = string1, ..." * * @param arg_str [IN] pointer to first char to parse * @param argp [OUT] list of parsed expr_parse_arg * * @return pointer to first unmatching char or NULL on parse error */ static char *parse_args(char *arg_str, struct expr_parse_arg **argp) { char *sp, *name, *val; int saved_char; struct expr_parse_arg *arg = NULL; sp = skip_white(arg_str); if (*sp == '\0') return NULL; name = sp; /* name matches [A-Za-z_][A-Za-z0-9_]* */ if (!(isalpha(*sp) || *sp == '_')) return NULL; sp = end_of_token(sp); if (isspace(*sp)) *sp++ = '\0'; sp = skip_white(sp); if (*sp != '=') return NULL; *sp++ = '\0'; /* name = ... */ sp = skip_white(sp); if (*sp == '\0') return NULL; val = sp; sp = end_of_value(sp); if (*sp == '\0') return NULL; if (isspace(*sp)) { /* name = val */ *sp++ = '\0'; sp = skip_white(sp); } if (*sp == '\0') return NULL; saved_char = *sp; *sp = '\0'; arg = gsh_calloc(1, sizeof(struct expr_parse_arg)); arg->name = gsh_strdup(name); arg->value = gsh_strdup(val); *argp = arg; if (saved_char != ',') { *sp = saved_char; return sp; } sp++; /* name = val , ... */ return parse_args(sp, &arg->next); } /** * @brief Parse "token ( .... )" and "token ( .... ) . token ( ... )" * * @param str [IN] pointer to first char to be parsed * @param node [OUT] reference pointer to returned expr_parse list * * @return pointer to first char after parse or NULL on errors */ static char *parse_block(char *str, struct expr_parse **node) { char *sp, *name; struct expr_parse_arg *arg = NULL; struct expr_parse *new_node; sp = skip_white(str); if (*sp == '\0') return NULL; name = sp; /* name matches [A-Za-z_][A-Za-z0-9_]* */ if (!(isalpha(*sp) || *sp != '_')) return NULL; if (*sp == '_') sp++; sp = end_of_token(sp); if (isspace(*sp)) *sp++ = '\0'; sp = skip_white(sp); if (*sp != '(') return NULL; *sp++ = '\0'; /* name ( ... */ sp = parse_args(sp, &arg); if (sp == NULL) goto errout; sp = skip_white(sp); if (*sp == ')') { /* name ( ... ) */ new_node = gsh_calloc(1, sizeof(struct expr_parse)); new_node->name = gsh_strdup(name); new_node->arg = arg; *node = new_node; return sp + 1; } errout: free_expr_parse_arg(arg); return NULL; } /** * @brief Parse a treewalk expression * * A full expression describes the path down a parse tree with * each node described as "block_name ( qualifiers )". * Each node in the path is a series of block name and qualifiers * separated by '.'. * * The parse errors are detected as either ret val == NULL * or *retval != '\0', i.e. pointing to a garbage char * * @param expr [IN] pointer to the expression to be parsed * @param expr_node [OUT] pointer to expression parse tree * * @return pointer to first char past parse or NULL for errors. */ static char *parse_expr(char *expr, struct expr_parse **expr_node) { char *sp; struct expr_parse *node = NULL, *prev_node = NULL, *root_node = NULL; char *lexpr = gsh_strdupa(expr); sp = lexpr; while (sp != NULL && *sp != '\0') { sp = parse_block(sp, &node); /* block is name ( ... ) */ if (root_node == NULL) root_node = node; else prev_node->next = node; prev_node = node; if (sp == NULL) break; sp = skip_white(sp); if (*sp == '.') { sp++; /* boock '.' block ... */ } else if (*sp != '\0') { sp = NULL; break; } } *expr_node = root_node; if (sp != NULL) return expr + (sp - lexpr); else return NULL; } static inline bool match_one_term(char *value, struct config_node *node) { struct config_node *term_node; struct glist_head *ts; glist_for_each(ts, &node->u.nterm.sub_nodes) { term_node = glist_entry(ts, struct config_node, node); assert(term_node->type == TYPE_TERM); if (strcasecmp(value, term_node->u.term.varvalue) == 0) return true; } return false; } /** * @brief Select block based on the evaluation of the qualifier args * * use each qualifier to match. The token name is found in the block. * once found, the token value is compared. '*' matches anything. else * it is a case-insensitive string compare. * * @param blk [IN] pointer to config_node describing the block * @param expr [IN] expr_parse node to match * * @return true if matched, else false */ static bool match_block(struct config_node *blk, struct expr_parse *expr) { struct glist_head *ns; struct config_node *sub_node; struct expr_parse_arg *arg; bool found = false; assert(blk->type == TYPE_BLOCK); for (arg = expr->arg; arg != NULL; arg = arg->next) { glist_for_each(ns, &blk->u.nterm.sub_nodes) { sub_node = glist_entry(ns, struct config_node, node); if (sub_node->type == TYPE_STMT && strcasecmp(arg->name, sub_node->u.nterm.name) == 0) { found = true; if (expr->next == NULL && strcasecmp(arg->value, "*") == 0) continue; if (!match_one_term(arg->value, sub_node)) return false; } } } return found; } /** * @brief Find nodes in parse tree using expression * * Lookup signature describes the nested block it is of the form: * block_name '(' param_name '=' param_value ')' ... * where block_name and param_name are alphanumerics and param_value is * and arbitrary token. * This can name a subblock (the ...) part by a '.' sub-block_name... * as in: * some_block(indexing_param = foo).the_subblock(its_index = baz) * * NOTE: This will return ENOENT not only if the the comparison * fails but also if the search cannot find the token. In other words, * the match succeeds only if there is a node in the parse tree and it * matches. * * @param config [IN] root of parse tree * @param expr_str [IN] expression description of block * @param node_list [OUT] pointer to store node list * @param err_type [OUT] error processing * * @return 0 on success, errno for errors */ int find_config_nodes(config_file_t config, char *expr_str, struct config_node_list **node_list, struct config_error_type *err_type) { struct config_root *tree = (struct config_root *)config; struct glist_head *ns; struct config_node *sub_node; struct config_node *top; struct expr_parse *expr, *expr_head = NULL; struct config_node_list *list = NULL, *list_tail = NULL; char *ep; int rc = EINVAL; bool found = false; if (tree->root.type != TYPE_ROOT) { config_proc_error( &tree->root, err_type, "Expected to start at parse tree root for (%s)", expr_str); goto out; } top = &tree->root; ep = parse_expr(expr_str, &expr_head); if (ep == NULL || *ep != '\0') goto out; expr = expr_head; *node_list = NULL; again: glist_for_each(ns, &top->u.nterm.sub_nodes) { #ifdef DS_ONLY_WAS /* recent changes to parsing may prevent this, * but retain code here for future reference. * -- WAS */ if (ns == NULL) { config_proc_error(top, err_type, "Missing sub_node for (%s)", expr_str); break; } #endif sub_node = glist_entry(ns, struct config_node, node); if (strcasecmp(expr->name, sub_node->u.nterm.name) == 0 && sub_node->type == TYPE_BLOCK && match_block(sub_node, expr)) { if (expr->next != NULL) { top = sub_node; expr = expr->next; goto again; } list = gsh_calloc(1, sizeof(struct config_node_list)); list->tree_node = sub_node; if (*node_list == NULL) *node_list = list; else list_tail->next = list; list_tail = list; found = true; } } if (found) rc = 0; else rc = ENOENT; out: free_expr_parse(expr_head); return rc; } /** * @brief Fill configuration structure from a parse tree node * * If param == NULL, there is no link_mem and the self_struct storage * is allocated and attached to its destination dynamically. * * @param tree_node [IN] A CONFIG_BLOCK node in the parse tree * @param conf_blk [IN] pointer to configuration description * @param param [IN] pointer to struct to fill or NULL * @param unique [IN] bool if true, more than one is an error * @param err_type [OUT] error type return * * @returns -1 on errors, 0 for success */ int load_config_from_node(void *tree_node, struct config_block *conf_blk, void *param, bool unique, struct config_error_type *err_type) { struct config_node *node = (struct config_node *)tree_node; char *blkname = conf_blk->blk_desc.name; char *altblkname = conf_blk->blk_desc.altname; if (node == NULL) { config_proc_error(NULL, err_type, "Missing tree_node for (%s)", blkname); err_type->missing = true; return -1; } if (node->type == TYPE_BLOCK) { if (strcasecmp(node->u.nterm.name, blkname) != 0 && (altblkname == NULL || strcasecmp(node->u.nterm.name, altblkname) != 0)) { config_proc_error(node, err_type, "Looking for block (%s), got (%s)", blkname, node->u.nterm.name); err_type->invalid = true; err_type->errors++; return -1; } } else { config_proc_error( node, err_type, "Unrecognized parse tree node type for block (%s)", blkname); err_type->invalid = true; err_type->errors++; return -1; } if (!proc_block(node, &conf_blk->blk_desc, param, err_type)) { config_proc_error(node, err_type, "Errors found in configuration block %s", blkname); return -1; } return 0; } /** * @brief Fill configuration structure from parse tree * * If param == NULL, there is no link_mem and the self_struct storage * is allocated and attached to its destination dynamically. * * @param config [IN] root of parse tree * @param conf_blk [IN] pointer to configuration description * @param param [IN] pointer to struct to fill or NULL * @param unique [IN] bool if true, more than one is an error * @param err_type [OUT] pointer to error type return * * @returns number of blocks found. -1 on errors, errors are in err_type */ int load_config_from_parse(config_file_t config, struct config_block *conf_blk, void *param, bool unique, struct config_error_type *err_type) { struct config_root *tree = (struct config_root *)config; struct config_node *node = NULL; struct glist_head *ns; char *blkname = conf_blk->blk_desc.name; char *altblkname = conf_blk->blk_desc.altname; int found = 0; int prev_errs = err_type->errors; void *blk_mem = NULL; if (tree == NULL) { config_proc_error(NULL, err_type, "Missing parse tree root for (%s)", blkname); err_type->missing = true; return -1; } if (tree->root.type != TYPE_ROOT) { config_proc_error( &tree->root, err_type, "Expected to start at parse tree root for (%s)", blkname); err_type->internal = true; return -1; } if (param != NULL) { blk_mem = conf_blk->blk_desc.u.blk.init(NULL, param); if (blk_mem == NULL) { config_proc_error( &tree->root, err_type, "Top level block init failed for (%s)", blkname); err_type->internal = true; return -1; } } glist_for_each(ns, &tree->root.u.nterm.sub_nodes) { node = glist_entry(ns, struct config_node, node); if (node->type == TYPE_BLOCK && (strcasecmp(blkname, node->u.nterm.name) == 0 || (altblkname != NULL && strcasecmp(altblkname, node->u.nterm.name) == 0))) { if (found > 0 && (conf_blk->blk_desc.flags & CONFIG_UNIQUE)) { config_proc_error(node, err_type, "Only one %s block allowed", blkname); } else { node->found = true; /* Reset cur_exp_create_err which may be used * if an EXPORT block is processed. */ err_type->cur_exp_create_err = false; if (!proc_block(node, &conf_blk->blk_desc, blk_mem, err_type)) config_proc_error( node, err_type, "Errors processing block (%s)", blkname); else found++; /* If EXPORT block was handled and export * creation failed then set * all_exp_create_err = true */ if (strcmp(blkname, "EXPORT") == 0 && err_type->cur_exp_create_err == true) { err_type->all_exp_create_err = true; } } } } if (found == 0 && (conf_blk->blk_desc.flags & CONFIG_NO_DEFAULT) == 0) { /* Found nothing but we have to do the allocate and init * at least. Use a fake, not NULL link_mem */ blk_mem = param != NULL ? param : conf_blk->blk_desc.u.blk.init((void *)~0UL, NULL); assert(blk_mem != NULL); if (!do_block_init(&tree->root, conf_blk->blk_desc.u.blk.params, blk_mem, err_type)) { config_proc_error( &tree->root, err_type, "Could not initialize defaults for block %s", blkname); err_type->init = true; } } if (err_type->errors > prev_errs) { char *errstr = err_type_str(err_type); assert(!config_error_no_error(err_type)); config_proc_error(node, err_type, "%d %s errors found block %s", err_type->errors - prev_errs, errstr != NULL ? errstr : "unknown", blkname); if (errstr != NULL) gsh_free(errstr); } return found; } nfs-ganesha-6.5/src/config_parsing/test_parse.c000066400000000000000000000035731473756622300217010ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* ---------------------------------------------------------------------------- * Copyright CEA/DAM/DIF (2007) * contributeur : Thomas LEIBOVICI thomas.leibovici@cea.fr * * This program 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 3 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 * 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 * * --------------------------------------- */ #include "config_parsing.h" #include "log.h" #include int main(int argc, char **argv) { SetDefaultLogging("TEST"); SetNamePgm("test_parse"); config_file_t config; char *fichier; char *errtxt; if ((argc > 1) && (argv[1])) { fichier = argv[1]; } else { LogTest("Usage %s ", argv[0]); exit(EINVAL); } /* Parsing example */ config = config_ParseFile(fichier); LogTest("config_pointer = %p", config); if (config == NULL) { errtxt = config_GetErrorMsg(); LogTest("Error in parsing %s : %s", argv[1], errtxt); exit(EINVAL); } config_Print(stdout, config); /* free and reload the file */ config_Free(config); config = config_ParseFile(fichier); LogTest("config_pointer = %p", config); if (config == NULL) { LogTest("Parsing error for %s", argv[1], errtxt); exit(EINVAL); } config_Print(stdout, config); config_Free(config); exit(0); } nfs-ganesha-6.5/src/config_parsing/verif_syntax.c000066400000000000000000000032631473756622300222450ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* ---------------------------------------------------------------------------- * Copyright CEA/DAM/DIF (2007) * contributeur : Thomas LEIBOVICI thomas.leibovici@cea.fr * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file verif_syntax.c * @brief Test the syntax of the configuration file. */ #include "config_parsing.h" #include "log.h" #include int main(int argc, char **argv) { SetDefaultLogging("TEST"); SetNamePgm("verif_syntax"); char *errtxt; char *fichier; config_file_t config; if ((argc > 1) && (argv[1])) { fichier = argv[1]; } else { LogTest("Usage %s ", argv[0]); exit(EINVAL); } /* test de la syntaxe du fichier */ config = config_ParseFile(fichier); if (config == NULL) { LogTest("Error parsing %s", argv[1]); exit(EINVAL); } else { LogTest("The syntax of the file %s is correct!", argv[1]); exit(0); } config_Free(config); return 0; } nfs-ganesha-6.5/src/config_samples/000077500000000000000000000000001473756622300173555ustar00rootroot00000000000000nfs-ganesha-6.5/src/config_samples/README000066400000000000000000000121121473756622300202320ustar00rootroot00000000000000This directory contains some example config files for various FSALs. It also contains a few files to help document config options. config.txt - this file documents all the config options logging.txt - this file documents the LOG config in more detail export.txt - this file documents the export config in more detail The sample FSAL files: ceph.conf gpfs.conf vfs.conf lustre.conf xfs.conf The most readily used FSAL for experimentation with Ganesha given a recent kernel is FSAL_VFS using vfs.conf. Configuration File Processing ============================= A configuration file uses the following syntax rules. Processing errors are reported to the log or, in the case of an 'AddExport' DBus command, in the reply message. System administrators should always check the log or DBus reply whenever they make changes to the configuration. The processing will attempt to process the whole file and report all errors, some of which can be serious enough to prevent the server from starting. Like all compilers that make a best effort to keep processing, there are some errors that cannot be cleanly recovered from. Comments -------- The syntax provides for comments. A comment is any text following a '#' character to the end of the line. The one exception where it is ignored is when the '#' is enclosed in a quoted string. Examples are: # This whole line is a comment Protocol = TCP; # The rest of this line is a comment user_date_format = "%D #"; # The '#' in the quoted string is just another character. Including Other files --------------------- Additional files can be referenced in a configuration, using %include or %dir or %url directives. An included file is inserted into the configuration text in place of the %include or %dir or %url line. The configuration following the inclusion is resumed after the end of the included files. File inclusion can be to any depth. %include This line replaces the line with the contents of the file . Examples are: %include base.conf or %include "base.conf" The quotes are optional. %dir This line replaces the line with the contents of all the files in the directory or matching files. Examples are: %dir /etc/ganesha or %dir "/etc/ganesha/" or %dir "/etc/ganesha/*.conf" The quotes are optional. %url %url rados://mypool/mynamespace/myobject or %url "rados://mypool/mynamespace/myobject" The quotes are optional, as is the namespace. If the URL has only two components, then the default namespace is used. Symbols ------- Symbols are case insensitive words separated by white space or punctuation. Symbols used for block and parameter names must be an alphabetic optionally followed by more alphabetics, numbers, '-', '.', or '_'. Symbols used for option values follow the same syntax unless enclosed between quote characters. Examples are: LOG { Default_Log_Level = EVENT; } is equivalent to log{default_Log_LEVEL=event;} Numeric and Boolean Option Values --------------------------------- Numeric options can be defined in octal, decimal, or hexadecimal. The format follows ANSI C syntax. Examples are: mode = 0755; # This is octal 0755, 493 (decimal) maxwrite = 65535; maxread = 0xffff; Maxread and maxwrite are equal. Values can be input in any base although log messages will only display in one base. For example, the log will report the value for 'mode' in octal regardless of the base used to set it in the configuration. Numeric values can also be negated or logical NOT'd. Examples are: anonomousuid = -2; # this is a negative 2 mask = ~0xff; # this is equivalent to 0xffffff00 (for 32 bit integers) The operator can have white space between the '-' or '~' and the numeric value. Boolean types are also recognized. The values 'true', 'yes', and 'on' are TRUE and the values 'false', 'no', and 'off' are FALSE. Note that '1' and '0' are rejected. Booleans and integers different. Single "'" and double '"' quoted strings can contain any arbitrary, multiline sequence of characters. The C escaped non-printables \n, \t, and \c as well as any escaped printable, e.g. \", can be escaped within a double quoted string. A single quoted string contains all the characters between the first "'" and the next "'". If the string must contain a "'", use a double quoted string. All other values must start with an alphanumeric, '_', or '.'. Any subsequent characters can also contain a '-'. Any value that has characters that does not fit this pattern must be quoted. Blocks ------ Options are grouped into "blocks" of related parameters. A block is a name followed by parameters enclosed between '{' and '}'. Examples are: export { export_id = 17; fsal { name = VFS; } } Note that 'fsal' is a sub-block. Statements ---------- A statement within a block is a parameter name followed by a '=' that is followed by an option value or a comma separated list of option values. A statement is terminated by a ';'. Examples are: CanSetTime = true; Protocols = 3, 4, 9p; The first case is a simple boolean parameter. The second is a list. Note that the list can span lines. nfs-ganesha-6.5/src/config_samples/ceph.conf000066400000000000000000000153671473756622300211570ustar00rootroot00000000000000# # It is possible to use FSAL_CEPH to provide an NFS gateway to CephFS. The # following sample config should be useful as a starting point for # configuration. This basic configuration is suitable for a standalone NFS # server, or an active/passive configuration managed by some sort of clustering # software (e.g. pacemaker, docker, etc.). # # Note too that it is also possible to put a config file in RADOS, and give # ganesha a rados URL from which to fetch it. For instance, if the config # file is stored in a RADOS pool called "nfs-ganesha", in a namespace called # "ganesha-namespace" with an object name of "ganesha-config": # # %url rados://nfs-ganesha/ganesha-namespace/ganesha-config # # If we only export cephfs (or RGW), store the configs and recovery data in # RADOS, and mandate NFSv4.1+ for access, we can avoid any sort of local # storage, and ganesha can run as an unprivileged user (even inside a # locked-down container). # NFS_CORE_PARAM { # Ganesha can lift the NFS grace period early if NLM is disabled. Enable_NLM = false; # rquotad doesn't add any value here. CephFS doesn't support per-uid # quotas anyway. Enable_RQUOTA = false; # In this configuration, we're just exporting NFSv4. In practice, it's # best to use NFSv4.1+ to get the benefit of sessions. Protocols = 4; } NFSv4 { # Modern versions of libcephfs have delegation support, though they # are not currently recommended in clustered configurations. They are # disabled by default but can be re-enabled for singleton or # active/passive configurations. # Delegations = false; # One can use any recovery backend with this configuration, but being # able to store it in RADOS is a nice feature that makes it easy to # migrate the daemon to another host. # # For a single-node or active/passive configuration, rados_ng driver # is preferred. For active/active clustered configurations, the # rados_cluster backend can be used instead. See the # ganesha-rados-grace manpage for more information. RecoveryBackend = rados_ng; # NFSv4.0 clients do not send a RECLAIM_COMPLETE, so we end up having # to wait out the entire grace period if there are any. Avoid them. Minor_Versions = 1,2; } # The libcephfs client will aggressively cache information while it # can, so there is little benefit to ganesha actively caching the same # objects. Doing so can also hurt cache coherency. Here, we disable # as much attribute and directory caching as we can. MDCACHE { # Size the dirent cache down as small as possible. Dir_Chunk = 0; } EXPORT { # Unique export ID number for this export Export_ID=100; # We're only interested in NFSv4 in this configuration Protocols = 4; # NFSv4 does not allow UDP transport Transports = TCP; # # Path into the cephfs tree. # # Note that FSAL_CEPH does not support subtree checking, so there is # no way to validate that a filehandle presented by a client is # reachable via an exported subtree. # # For that reason, we just export "/" here. Path = /; # # The pseudoroot path. This is where the export will appear in the # NFS pseudoroot namespace. # Pseudo = /cephfs_a/; # We want to be able to read and write Access_Type = RW; # Time out attribute cache entries immediately Attr_Expiration_Time = 0; # Enable read delegations? libcephfs v13.0.1 and later allow the # ceph client to set a delegation. While it's possible to allow RW # delegations it's not recommended to enable them until ganesha # acquires CB_GETATTR support. # # Note too that delegations may not be safe in clustered # configurations, so it's probably best to just disable them until # this problem is resolved: # # http://tracker.ceph.com/issues/24802 # # Delegations = R; # NFS servers usually decide to "squash" incoming requests from the # root user to a "nobody" user. It's possible to disable that, but for # now, we leave it enabled. # Squash = root; FSAL { # FSAL_CEPH export Name = CEPH; # # Ceph filesystems have a name string associated with them, and # modern versions of libcephfs can mount them based on the # name. The default is to mount the default filesystem in the # cluster (usually the first one created). # # Filesystem = "cephfs_a"; # # Ceph clusters have their own authentication scheme (cephx). # Ganesha acts as a cephfs client. This is the client username # to use. This user will need to be created before running # ganesha. # # Typically ceph clients have a name like "client.foo". This # setting should not contain the "client." prefix. # # See: # # http://docs.ceph.com/docs/jewel/rados/operations/user-management/ # # The default is to set this to NULL, which means that the # userid is set to the default in libcephfs (which is # typically "admin"). # # User_Id = "ganesha"; # # Key to use for the session (if any). If not set, it uses the # normal search path for cephx keyring files to find a key: # # Secret_Access_Key = "YOUR SECRET KEY HERE"; } } # Config block for FSAL_CEPH CEPH { # Path to a ceph.conf file for this ceph cluster. # Ceph_Conf = /etc/ceph/ceph.conf; # User file-creation mask. These bits will be masked off from the unix # permissions on newly-created inodes. # umask = 0; } # # This is the config block for the RADOS RecoveryBackend. This is only # used if you're storing the client recovery records in a RADOS object. # RADOS_KV { # Path to a ceph.conf file for this cluster. # Ceph_Conf = /etc/ceph/ceph.conf; # The recoverybackend has its own ceph client. The default is to # let libcephfs autogenerate the userid. Note that RADOS_KV block does # not have a setting for Secret_Access_Key. A cephx keyring file must # be used for authenticated access. # UserId = "ganesharecov"; # Pool ID of the ceph storage pool that contains the recovery objects. # The default is "nfs-ganesha". # pool = "nfs-ganesha"; # Consider setting a unique nodeid for each running daemon here, # particularly if this daemon could end up migrating to a host with # a different hostname (i.e. if you're running an active/passive cluster # with rados_ng/rados_kv and/or a scale-out rados_cluster). The default # is to use the hostname of the node where ganesha is running. # nodeid = hostname.example.com } # Config block for rados:// URL access. It too uses its own client to access # the object, separate from the FSAL_CEPH and RADOS_KV client. RADOS_URLS { # Path to a ceph.conf file for this cluster. # Ceph_Conf = /etc/ceph/ceph.conf; # RADOS_URLS use their own ceph client too. Authenticated access # requires a cephx keyring file. # UserId = "ganeshaurls"; # We can also have ganesha watch a RADOS object for notifications, and # have it force a configuration reload when one comes in. Set this to # a valid rados:// URL to enable this feature. # watch_url = "rados://pool/namespace/object"; } nfs-ganesha-6.5/src/config_samples/config.txt000066400000000000000000000702771473756622300214000ustar00rootroot00000000000000The following config blocks exist: NFS_CORE_PARAM {} NFS_IP_NAME {} NFS_KRB5 {} NFSV4 {} EXPORT_DEFAULTS {} EXPORT_DEFAULTS { CLIENT {} } EXPORT {} EXPORT { CLIENT {} } EXPORT { FSAL {} } EXPORT { FSAL { FSAL {} } } EXPORT { FSAL { PNFS {} } } PSEUDOFS {} PSEUDOFS { CLIENT {} } LOG {} LOG { COMPONENTS {} } LOG { FACILITY {} } LOG { FORMAT {} } _9P {} MDCACHE {} FSAL_LIST {} CEPH {} GPFS {} MEM {} RGW {} VFS {} XFS {} PROXY_V3 {} PROXY_V4 {} RADOS_KV {} RADOS_URLS {} Notably the following FSALs do not have a global config block: PSEUDO, NULL, GLUSTER NFS_CORE_PARAM {} ----------------- HAProxy_Hosts (host list, empty) * Host list entries can take on one of the following forms. This parameter can be repeated to extend the list. * Match any host @name Netgroup name x.x.x.x/y IPv4 network address, IPv6 addresses are also allowed but the format is too complex to show here wildcarded If the string contains at least one ? or * character (and is not simply "*"), the string is used to pattern match host names. Note that [] may also be used, but the pattern MUST have at least one ? or * hostname Match a single host (match is by IP address, all addresses returned by getaddrinfo will match, the getaddrinfo call is made at config parsing time) IP address Match a single host NFS_Port (uint16, range 0 to UINT16_MAX, default 2049) MNT_Port (uint16, range 0 to UINT16_MAX, default 0) NLM_Port (uint16, range 0 to UINT16_MAX, default 0) Rquota_Port (uint16, range 0 to UINT16_MAX, default 875) NFS_RDMA_Port (uint16, range 0 to UINT16_MAX, default 20049) NFS_RDMA_Protocol_Versions(enum list, values [NONE, 3, v3, NFS3, NFSv3, 4.0, v4.0, NFS4.0, NFSv4.0, ALL], default 4.0) Monitoring_Port (uint16, range 0 to UINT16_MAX, default 9587) Enable_Dynamic_Metrics (bool, default true) Bind_addr(IPv4 or IPv6 addr, default 0.0.0.0) NFS_Program(uint32, range 1 to INT32_MAX, default 100003) MNT_Program(uint32, range 1 to INT32_MAX, default 100005) NLM_Program(uint32, range 1 to INT32_MAX, default 100021) Rquota_Program(uint32, range 1 to INT32_MAX, default 100011) Drop_IO_Errors(bool, default false) Drop_Inval_Errors(bool, default false) Drop_Delay_Errors(bool, default false) DRC_Disabled(boo, default false) DRC_Recycle_Hiwat(uint32, range 1 to 1000000, default 1024) DRC_TCP_Npart(uint32, range 1 to 20, default 1) DRC_TCP_Size(uint32, range 1 to 32767, default 1024) DRC_TCP_Cachesz(uint32, range 1 to 255, default 127) DRC_TCP_Hiwat(uint32, range 1 to 256, default 64) DRC_TCP_Recycle_Npart(uint32, range 1 to 20, default 7) DRC_TCP_Recycle_Expire_S(uint32, range 0 to 60*60, default 600) DRC_TCP_Checksum(bool, default true) DRC_UDP_Npart(uint32, range 1 to 100, default 7) DRC_UDP_Size(uint32, range 512, to 32768, default 32768) DRC_UDP_Cachesz(uint32, range 1 to 2047, default 599) DRC_UDP_Hiwat(uint32, range 1 to 32768, default 16384) DRC_UDP_Checksum(bool, default true) RPC_Max_Connections(uint32, range 1 to 1000000, default 1024) RPC_Idle_Timeout_S(uint32, range 0 to 60*60, default 300) MaxRPCSendBufferSize(uint32, range 1 to 1048576*9, default 1048576) MaxRPCRecvBufferSize(uint32, range 1 to 1048576*9, default 1048576) RPC_Max_RDMA_Connections(uint32, range 1 to 1024, default 64) MaxRPCRdmaCredits(uint32, range 1 to 4096, default 64) RPC_Ioq_ThrdMax(uint32, range 2 to 1024*128 default 200) rpc_ioq_thrdmin(uint32, range 2 to 1024*128 default 2) RPC_GSS_Npart(uint32, range 1 to 1021, default 13) RPC_GSS_Max_Ctx(uint32, range 1 to 1048576, default 16384) RPC_GSS_Max_Gc(uint32, range 1 to 1048576, default 200) Blocked_Lock_Poller_Interval(int64, range 0 to 180, default 10) NFS_Protocols(list, valid values [3, 4], default 3,4) NSM_Use_Caller_Name(bool, default false) Clustered(bool, default true) Enable_NLM(bool, default true) Disable_NLM_SHARE(bool, default false) Enable_RQUOTA(bool, default true) Enable_NFSACL(bool, default false) Enable_TCP_keepalive(bool, default true) TCP_KEEPCNT(UINT32, range 0 to 255, default 0 -> use system defaults) TCP_KEEPIDLE(UINT32, range 0 to 65535, default 0 -> use system defaults) TCP_KEEPINTVL(INT32, range 0 to 65535, default 0 -> use system defaults) Enable_NFS_Stats(bool, default true) Enable_Fast_Stats(bool, default false) Enable_FSAL_Stats(bool, default false) Enable_FULLV3_Stats(bool, default false) Enable_FULLV4_Stats(bool, default false) Enable_CLNT_AllOps_Stats(bool, default false) Short_File_Handle(bool, default false) Manage_Gids_Expiration(int64, range 0 to 7*24*60*60, default 30*60) Plugins_Dir(path, default "/usr/lib64/ganesha") heartbeat_freq(uint32, range 0 to 5000 default 1000) fsid_device(bool, default false) resolve_fs_retries(uint32_t, range 1 to 1000, default 10) resolve_fs_delay(uint32_t, range 1 to 1000, default 100) mount_path_pseudo(bool, default false) Enable_UDP(enum, values [False, True, Mount], default True) Dbus_Name_Prefix(string, default NULL) Max_Uid_To_Group_Reqs(uint32, range 0 to INT32_MAX, default 0) Enable_V3fh_Validation_For_V4(bool, default false) Readdir_Res_Size(uint32, range 4096 to 64*1024*1024, default 32*1024) Readdir_Max_Count(uint32, range 32 to 1024*1024, default 1024*1024) Getattrs_In_Complete_Read(bool, default true) Enable_malloc_trim(bool, default false) Malloc_trim_MinThreshold(uint32, range 1 to INT32_MAX, default 15*1024) enable_rpc_cred_fallback(bool, default false) Unique_Server_Id(uint32, range 0 to UINT32_MAX, default 0) Enable_Connection_Manager(bool, default false) Connection_Manager_Timeout_sec(uint32, range 0 to UINT32_MAX, default 2*60) Allow_Set_Io_Flusher_Fail(bool, default false) NFS_IP_NAME {} -------------- Index_Size(uint32, range 1 to 51, default 17) Expiration_Time(uint32, range 1 to 60*60*24, default 3600) NFS_KRB5 {} ----------- PrincipalName(string, default "nfs") KeytabPath(path, default "") CCacheDir(path, default "/var/run/ganesha") Active_krb5(bool, default true) NFSV4 {} -------- Sticky_Grace(bool, default false) Graceless(bool, default false) Lease_Lifetime(uint32, range 1 to 120, default 60) Grace_Period(uint32, range 0 to 180, default 90) IdmapConf(path, default "/etc/idmapd.conf") UseGetpwnam(bool, default false if using idmap, true otherwise) Allow_Numeric_Owners(bool, default true) Only_Numeric_Owners(bool, default false) Delegations(bool, default false) RecoveryBackend(enum, values [fs, fs_ng, rados_kv, rados_ng], default fs) RecoveryRoot(path, default "/var/lib/nfs/ganesha") RecoveryDir(path, default "v4recov") RecoveryOldDir(path, "v4old") Minor_Versions(enum list, values [0, 1, 2], default [0, 1, 2]) Slot_Table_Size(uint32, range 1 to 1024, default 64) Enforce_UTF8_Validation(bool, default false) Max_Client_Ids(uint32, range 0 to UINT32_MAX, default 0) Server_Scope(string, default "") Server_Owner(string, default "") Max_Open_States_Per_Client(uint32, range 0 to UINT32_MAX, default 0) Expired_Client_Threshold(uint32, range 0 to 256, default 16) Max_Open_Files_For_Expired_Client(uint32, range 0 to UINT32_MAX, default 4000) Max_Alive_Time_For_Expired_Client(uint64, range 0 to UINT64_MAX, default 86400) DIRECTORY_SERVICES {} --------------------- DomainName(string, default "localdomain") Idmapping_Active(bool, default true) EXPORT_DEFAULTS {} ------------------ These options are all "export permissions" options, and will be repeated in the EXPORT {} and EXPORT { CLIENT {} } blocks. These options will all be dynamically updateable. Access_Type(enum, values [None, RW, RO, MDONLY, MDONLY_RO], default None) Protocols(enum list, values [3, 4, NFS3, NFS4, V3, V4, NFSv3, NFSv4, 9P], default none) Transports(enum list, values [UDP, TCP, RDMA], default [UDP, TCP]) Anonymous_uid(anonid, range INT32MIN to UINT32MAX, default -2) Anonymous_gid(anonid, range INT32MIN to UINT32MAX, default -2) SecType(enum list, values [none, sys, krb5, krb5i, krb5p], default [none, sys]) PrivilegedPort(bool, default false) Manage_Gids(bool, default false) Squash(enum, values [root, root_squash, rootsquash, rootid, root_id_squash, rootidsquash, all, all_squash, allsquash, all_anomnymous, allanonymous, no_root_squash, none, noidsquash], default root_squash) * Each line of defaults above are synonyms NFS_Commit(bool, default false) Delegations(enum, values [None, read, write, readwrite, r, w, rw], default None) -------------------------- The following options are not permission options but may be set in EXPORT_DEFAULTS Attr_Expiration_Time(int32, range -1 to INT32_MAX, default 60) EXPORT_DEFAULTS { CLIENT {} } ------------------------------ EXPORT_DEFAULTS can also have CLIENT blocks * To override a restrictive default client list with "everyone" a non-empty client list would have to be specified. Clients = "*" would do the trick. * This block may be repeated to define multiple client lists with different permissions. * Take all the "export permissions" options from EXPORT_DEFAULTS. * The client lists are dynamically updateable. Clients(client list, empty) * Client list entries can take on one of the following forms. This parameter may be repeated to extend the list. * Match any client @name Netgroup name x.x.x.x/y IPv4 network address, IPv6 addresses are also allowed but the format is too complex to show here wildcarded If the string contains at least one ? or * character (and is not simply "*"), the string is used to pattern match host names. Note that [] may also be used, but the pattern MUST have at least one ? or * hostname Match a single client (match is by IP address, all addresses returned by getaddrinfo will match, the getaddrinfo call is made at config parsing time) IP address Match a single client EXPORT {} --------- * This block may be repeated to define multiple exports. * Take all the "export permissions" options from EXPORT_DEFAULTS. * The first 5 options are considered static from the perspective of dynamic update. Path(path, no default, must be supplied) Pseudo(path, no default) Tag(string, no default) Export_id(uint16, range 0 to UINT16_MAX, default 1) Filesystem_id(fsid, format is uint64.uint64, default unused) * Updating Filesystem_id would have an adverse impact on existing client mounts and it's not currently a type that can be atomically updated. * The following options may be dynamically updated MaxRead(uint64, range 512 to 64*1024*1024, default 64*1024*1024) MaxWrite(uint64, range 512 to 64*1024*1024, default 64*1024*1024) * Note that some older nfs client (e.g. libnfs 1.x) would not handle well for large Prefer Read/Write size. If so, please try to decrease the Prefer Read/Write size (usually less than 1M is suitable for older nfs client). PrefRead(uint64, range 512 to 64*1024*1024, default 64*1024*1024) PrefWrite(uint64, range 512 to 64*1024*1024, default 64*1024*1024) PrefReaddir(uint64, range 512 to 64*1024*1024, default 16384) MaxOffsetWrite(uint64, range 512 to UINT64_MAX, default INT64_MAX) MaxOffsetRead(uint64, range 512 to UINT64_MAX, default INT64_MAX) DisableReaddirPlus(bool, default false) Trust_Readdir_Negative_Cache(bool, default false) * The following options may have limits on dynamic effect UseCookieVerifier(bool, default true) * Updating UseCookieVerifier while a readdir is in progress may result in unexpected behavior. Disable_ACL(bool, default false) * Disable_ACL is processed at create_export time currently which makes it effectively a static option. Security_Label(bool, default false) Attr_Expiration_Time(int32, range -1 to INT32_MAX, default 60) * Attr_Expiration_Time is evaluated when an MDCACHE entry is created, so the dynamic effect of this option may be constrained to new entries. EXPORT { CLIENT {} } --------------------- See EXPORT_DEFAULTS { CLIENT {} } EXPORT { FSAL {} } ------------------ Name(string, default "VFS") FSAL_CEPH: ---------- User_Id(string, no default) * User_Id: cephx userid used to open the MDS session. This string is what gets appended to "client.". If not set, the ceph client libs will sort this out based on ceph configuration. Secret_Access_Key(string, no default) * Secret_Access_Key: key to use for the session (if any). If not set, then it uses the normal search path for cephx keyring files to find a key. sec_label_xattr(char, default "security.selinux xattr of the file") * Enable NFSv4.2 security label attribute. Ganesha supports "Limited Server Mode" as detailed in RFC 7204. Note that not all FSALs support security labels. filesystem(string, no default) * Ceph filesystem name string, for mounting an alternate filesystem within the cluster. The default is to mount the default filesystem in the cluster (usually, the first one created). cmount_path(string, no default) * If specified, the path within the ceph filesystem to mount this export on. It must be a subset of the EXPORT { Path } parameter. If this and the other EXPORT { FSAL {} } options are the same between multiple exports, those exports will share a single cephfs client. With the default, this effectively defaults to the same path as EXPORT { Path }. FSAL_GLUSTER: ------------- volume(string, no default, must be supplied) hostname(string, no default, must be supplied) volpath(path, default "/") glfs_log(path, default "/tmp/gfapi.log") up_poll_usec(uint64, range 1 to 60*1000*1000, default 10) * up_poll_usec: The time interval (in micro-seconds) between any two consecutive upcall polls. By default set to 10us. enable_upcall(bool, default true) transport(enum, values [tcp, rdma], default tcp) sec_label_xattr(char, default "security.selinux xattr of the file") * Enable NFSv4.2 security label attribute. Ganesha supports "Limited Server Mode" as detailed in RFC 7204. Note that not all FSALs support security labels. FSAL_VFS or FSAL_LUSTRE: ------------------------ pnfs(bool, default false) fsid_type(enum, values [None, One64, Major64, Two64, uuid, Two32, Dev, Device], no default) FSAL_LUSTRE: ------------ async_hsm_restore(bool, default true) FSAL_PROXY_V3: ----------- Srv_Addr(ipv4_addr default "127.0.0.1") FSAL_PROXY_V4: ----------- Retry_SleepTime(uint32, range 0 to 60, default 10) Srv_Addr(ipv4_addr default "127.0.0.1") NFS_Service(uint32, range 0 to UINT32_MAX, default 100003) /*NFS_SendSize must be greater than maxwrite+SEND_RECV_HEADER_SPACE .*/ /*NFS_RecvSize must be greater than maxread+SEND_RECV_HEADER_SPACE .*/ /*MAX_READ_WRITE_SIZE == 1 MB*/ /*SEND_RECV_HEADER_SPACE == 512 Bytes*/ /*FSAL_MAXIOSIZE = 64 MB*/ NFS_SendSize(uint64, range 512 + SEND_RECV_HEADER_SPACE to FSAL_MAXIOSIZE, default MAX_READ_WRITE_SIZE + SEND_RECV_HEADER_SPACE) NFS_RecvSize(uint64, range 512 + SEND_RECV_HEADER_SPACE to FSAL_MAXIOSIZE, default MAX_READ_WRITE_SIZE + SEND_RECV_HEADER_SPACE) NFS_Port(uint16, range 0 to UINT16_MAX, default 2049) Use_Privileged_Client_Port(bool, default true) RPC_Client_Timeout(uint32, range 1 to 60*4, default 60) Remote_PrincipalName(string, no default) KeytabPath(string, default "/etc/krb5.keytab") Credential_LifeTime(uint32, range 0 to 86400*2, default 86400) Sec_Type(enum, values [krb5, krb5i, krb5p], default krb5) Active_krb5(bool, default false) Enable_Handle_Mapping(bool, default false) HandleMap_DB_Dir(string, default "/var/ganesha/handlemap") HandleMap_Tmp_Dir(string, default "/var/ganesha/tmp") HandleMap_DB_Count(uint32, range 1 to 16, default 8) HandleMap_HashTable_Size(uint32, range 1 to 127, default 103) FSAL_MEM: --------- Async_Delay(uint32, range 0 to 1000, defaults to 0) Async_Type(enum, values [inline, fixed, random, random_or_inline], defaults to inline) Async_Stall_Delay(uint32, range 0 to 1000, defaults to 0) EXPORT { FSAL { PNFS { } } } ---------------------------- Stripe_Unit(uint32, range 1024 to 1024*1024, default 8192) pnfs_enabled(bool, default false) FSAL_NULL: ---------- EXPORT { FSAL { FSAL {} } } describes the stacked FSAL's parameters PSEUDOFS {} ----------- * While this block looks similar to an EXPORT block, only the following options may be specified including CLIENT sub-blocks. * This block provides a simple way to override some parameters of the pseudofs root, especially to specify CLIENT blocks to override the CLIENT blocks in EXPORT_DEFAULTS. * This block may only be specified once. * This block is fully dynamically updateable. Access_Type(enum, values [None, MDONLY_RO], default MDONLY_RO) Transports(enum list, values [UDP, TCP, RDMA], default [TCP]) SecType(enum list, values [none, sys, krb5, krb5i, krb5p], default [none, sys, krb5, krb5i, krb5p]) PrivilegedPort(bool, default false) Export_id(uint16, range 0 to UINT16_MAX, default 0) Filesystem_id(fsid, format is uint64.uint64, default 152.152) * Updating Filesystem_id would have an adverse impact on existing client mounts and it's not currently a type that can be atomically updated. * The following options may be dynamically updated DisableReaddirPlus(bool, default false) Trust_Readdir_Negative_Cache(bool, default false) * The following options may have limits on dynamic effect UseCookieVerifier(bool, default true) * Updating UseCookieVerifier while a readdir is in progress may result in unexpected behavior. PSEUDOFS { CLIENT {} } ----------------------- PSEUDOFS can also have CLIENT blocks * This block may be repeated to define multiple client lists with different permissions. * The client lists are dynamically updateable. Access_Type(enum, values [None, MDONLY_RO], default MDONLY_RO) Transports(enum list, values [UDP, TCP, RDMA], default [TCP]) SecType(enum list, values [none, sys, krb5, krb5i, krb5p], default [none, sys, krb5, krb5i, krb5p]) PrivilegedPort(bool, default false) Clients(client list, empty) * Client list entries can take on one of the following forms. This parameter may be repeated to extend the list. * Match any client @name Netgroup name x.x.x.x/y IPv4 network address, IPv6 addresses are also allowed but the format is too complex to show here wildcarded If the string contains at least one ? or * character (and is not simply "*"), the string is used to pattern match host names. Note that [] may also be used, but the pattern MUST have at least one ? or * hostname Match a single client (match is by IP address, all addresses returned by getaddrinfo will match, the getaddrinfo call is made at config parsing time) IP address Match a single client LOG {} ------ Default_log_level(token, values [NULL, FATAL, MAJ, CRIT, WARN, EVENT, INFO, DEBUG, MID_DEBUG, M_DBG, FULL_DEBUG, F_DBG], default none) If this option is NOT set, the fall back log level will be that specified in the -N option on the command line if that is set, otherwise the fallback level is EVENT. If a SIGHUP is issued, any components not specified in LOG { COMPONENTS {} } will be reset to this value. RPC_Debug_Flags(uint32, range 0 to UINT32_MAX, default 7) Debug flags for TIRPC (default 7 matches log level default EVENT). These flags are only used if the TIRPC component is set to DEBUG Display_UTC_Timestamp(bool, default false) If this is set to true, date and time in ganesha log file will use UTC timezone. LOG { COMPONENTS {} } --------------------- These entries are of the form: COMPONENT = LEVEL; The components are: ALL, LOG, MEMLEAKS, FSAL, NFSPROTO, NFS_V4, EXPORT, FILEHANDLE, DISPATCH, MDCACHE, MDCACHE_LRU, HASHTABLE, HASHTABLE_CACHE, DUPREQ, INIT, MAIN, IDMAPPER, NFS_READDIR, NFS_V4_LOCK, CONFIG, CLIENTID, SESSIONS, PNFS, RW_LOCK, NLM, TIRPC, NFS_CB, THREAD, NFS_V4_ACL, STATE, 9P, 9P_DISPATCH, FSAL_UP, DBUS, NFS_MSK, XPRT Some synonyms are: FH = FILEHANDLE HT = HASHTABLE CACHE_INODE_LRU = MDCAHCE_LRU CACHE_INODE = MDCACHE INODE_LRU = MDCAHCE_LRU INODE = MDCACHE DISP = DISPATCH LEAKS = MEMLEAKS NFS3 = NFSPROTO NFS4 = NFS_V4 HT_CACHE = HASHTABLE_CACHE NFS_STARTUP = INIT NFS4_LOCK = NFS_V4_LOCK NFS4_ACL = NFS_V4_ACL 9P_DISP = 9P_DISPATCH The log levels are: NULL, FATAL, MAJ, CRIT, WARN, EVENT, INFO, DEBUG, MID_DEBUG, M_DBG, FULL_DEBUG, F_DBG], default none ALL is a special component that when set, sets all components to the specified value, overriding any that are explicitly set. Note that if ALL is then removed from the config and SIGHUP is issued, all components will revert to what is explicitly set, or Default_Log_Level if that is specified, or the original log level from the -N command line option if that was set, or the code default of EVENT. TIRPC is a special component that also sets the active RPC_Debug_Flags. If the level for TIRPC is DEBUG or MID_DEBUG, the custom RPC_Debug_Flags set by that parameter will be used, otherwise flags will depend on the level the TIRPC component is set to: NULL or FATAL: 0 CRIT or MAJ: TIRPC_DEBUG_FLAG_ERROR WARN: TIRPC_DEBUG_FLAG_ERROR | TIRPC_DEBUG_FLAG_WARN EVENT or INFO: TIRPC_DEBUG_FLAG_ERROR | TIRPC_DEBUG_FLAG_WARN | TIRPC_DEBUG_FLAG_EVENT DEBUG or MID_DEBUG: RPC_Debug_Flags FULL_DEBUG: 0xffffffff LOG { FACILITY {} } ------------------- This block may be repeated to configure multiple facilities. name(string, no default) destination(string, no default, must be supplied) max_level(token, values [NULL, FATAL, MAJ, CRIT, WARN, EVENT, INFO, DEBUG, MID_DEBUG, M_DBG, FULL_DEBUG, F_DBG], default FULL_DEBUG) headers(token, values [none, component, all], default all) enable(token, values [idle, active, default], default idle) LOG { FORMAT {} } ----------------- date_format(enum, values [ganesha, true, local, 8601, ISO-8601, ISO 8601, ISO, syslog, syslog_usec, false, none, user_defined], default ganesha) time_format(enum, values [ganesha, true, local, 8601, ISO-8601, ISO 8601, ISO, syslog, syslog_usec, false, none, user_defined], default ganesha) user_date_format(string, no default) user_time_format(string, no default) EPOCH(bool, default true) CLIENTIP(bool, default false) HOSTNAME(bool, default true) PROGNAME(bool, default true) PID(bool, default true) THREAD_NAME(bool, default true) FILE_NAME(bool, default true) LINE_NUM(bool, default true) FUNCTION_NAME(bool, default true) COMPONENT(bool, default true) LEVEL(bool, default true) OP_ID(bool, default false) CLIENT_REQ_XID(bool, default false) MDCACHE {} ---------- NParts(uint32, range 1 to 32633, default 7) Cache_Size(uint32, range 1 to UINT32_MAX, default 32633) Use_Getattr_Directory_Invalidation(bool, default false) Dir_Chunk(uint32, range 0 to UINT32_MAX, default 128) Detached_Mult(uint32, range 1 to UINT32_MAX, default 1) Chunks_HWMark(uint32, range 1 to UINT32_MAX, default 1000) Chunks_LWMark(uint32, range 1 to UINT32_MAX, default 1000) Entries_HWMark(uint32, range 1 to UINT32_MAX, default 100000) Entries_Release_Size(uint32, range 0 to UINT32_MAX, default 100) LRU_Run_Interval(uint32, range 1 to 24 * 3600, default 90) Cache_FDs(bool, default true) Close_Fast(bool, default false) FD_Limit_Percent(uint32, range 0 to 100, default 99) FD_HWMark_Percent(uint32, range 0 to 100, default 90) FD_LWMark_Percent(uint32, range 0 to 100, default 50) Reaper_Work(uint32, range 1 to 2000, default 0) Reaper_Work_Per_Lane(uint32, range 1 to UINT32_MAX, default 50) Biggest_Window(uint32, range 1 to 100, default 40) Required_Progress(uint32, range 1 to 50, default 5) Futility_Count(uint32, range 1 to 50, default 8) Dirmap_HWMark(uint32, range 1 to UINT32_MAX, default 10000) Files_Delegatable_Percent(int32, range 10 to 90, default 90) Use_Cached_Owner_On_Owner_Override(bool, true) _9P {} ------ Nb_Worker(uint32, range 1 to 1024*128, default 256) _9P_TCP_Port(uint16, range 1 to UINT16_MAX, default 564) _9P_RDMA_Port(uint16, range 1 to UINT16_MAX, default 5640) _9P_TCP_Msize(uint32, range 1024 to UINT32_MAX, default 65536) _9P_RDMA_Msize(uint32, range 1024 to UINT32_MAX, default 1048576) _9P_RDMA_Backlog(uint16, range 1 to UINT16_MAX, default 10) _9P_RDMA_Inpool_size(uint16, range 1 to UINT16_MAX, default 64) _9P_RDMA_Outpool_Size(uint16, range 1 to UINT16_MAX, default 32) FSAL_LIST {} ------------ Name(string, no default) This allows listing the names of the following FSAL specific config blocks to avoid config errors if there are no exports of that FSAL. This parameter may be repeated to extend the list. CEPH {} ------- Ceph_Conf(path, default "") umask(mode, range 0 to 0777, default 0) client_oc(bool, default false) async(bool, default false) zerocopy(bool, default false) GPFS {} ------- link_support(bool, default true) symlink_support(bool, default true) cansettime(bool, default true) umask(mode, range 0 to 0777, default 0) auth_xdev_export(bool, default false) Delegations(enum, values [None, read, write, readwrite, r, w, rw], default read) pnfs_file(bool, default false) fsal_trace(bool, default true) fsal_grace(bool, default false) MEM {} ------- Inode_Size(uint32, range 0 to 2097152, default 0) Up_Test_Interval(uint32, range 0 to UINT32_MAX, default 0) Async_Threads(uint32, range 0 to 100, default to 0) RGW {} ------- The following configuration variables customize the startup of the FSAL's radosgw instance. * ceph_conf -- optional full-path to the Ceph configuration file (equivalent to passing "-c /path/to/ceph.conf" to any Ceph binary * name -- optional instance name (equivalent to passing "--name client.rgw.foohost" to the radosgw binary); the value provided here should be the same as the section name (sans brackets) of the radosgw facility in the Ceph configuration file (which must exist) * cluster -- optional cluster name (equivalent to passing "--cluster foo" to any Ceph binary); use of a non-default value for cluster name is uncommon, but can be verified by examining the startup options of Ceph binaries * init_args -- additional argument strings which will be passed verbatim to the radosgw instance startup process as if they had been given on the radosgw command line; provided for customization in uncommon setups ceph_conf(path, default "") name(string, default "") cluster(string, default "") init_args(string, default "") VFS {} ------ link_support(bool, default true) symlink_support(bool, default true) cansettime(bool, default true) maxread(uint64, range 512 to 64*1024*1024, default 64*1024*1024) maxwrite(uint64, range 512 to 64*1024*1024, default 64*1024*1024) umask(mode, range 0 to 0777, default 0) auth_xdev_export(bool, default false) only_one_user(bool, default false) XFS {} ------ link_support(bool, default true) symlink_support(bool, default true) cansettime(bool, default true) maxread(uint64, range 512 to 64*1024*1024, default 64*1024*1024) maxwrite(uint64, range 512 to 64*1024*1024, default 64*1024*1024) umask(mode, range 0 to 0777, default 0) auth_xdev_export(bool, default false) PROXY_V3 {} -------- /*MAX_READ_WRITE_SIZE == 1MB*/ /*FSAL_MAXIOSIZE = 64 MB*/ /*maxread and maxwrite will be clamped by the backend's FSINFO*/ maxread(uint64, range 1024 to FSAL_MAXIOSIZE, default MAX_READ_WRITE_SIZE) maxwrite(uint64, range 1024 to FSAL_MAXIOSIZE, default MAX_READ_WRITE_SIZE) allow_lookup_optimization(bool, default true) PROXY_V4 {} -------- link_support(bool, default true) symlink_support(bool, default true) cansettime(bool, default true) /*MAX_READ_WRITE_SIZE == 1MB*/ /*FSAL_MAXIOSIZE = 64 MB*/ /*SEND_RECV_HEADER_SPACE == 512 Bytes*/ maxread(uint64, range 512 to FSAL_MAXIOSIZE - SEND_RECV_HEADER_SPACE, default MAX_READ_WRITE_SIZE) maxwrite(uint64, range 512 to FSAL_MAXIOSIZE - SEND_RECV_HEADER_SPACE, default MAX_READ_WRITE_SIZE) umask(mode, range 0 to 0777, default 0) auth_xdev_export(bool, default false) RADOS_KV {} -------- ceph_conf(string, no default) userid(path, no default) namespace(string, default NULL) pool(string, default "nfs-ganesha") grace_oid(string, default "grace") nodeid(string, default result of gethostname()) RADOS_URLS {} -------- ceph_conf(string, no default) userid(path, no default) watch_url(url, no default) nfs-ganesha-6.5/src/config_samples/ds.conf000066400000000000000000000005221473756622300206310ustar00rootroot00000000000000################################################### # # DS # # To function, all that is required is one DS # # Define the absolutely minimal DS # ################################################### DS { # Identifier (optional, but each DS must be unique) Number = 0; # default # Related FSAL (mandatory) FSAL { Name = GPFS; } } nfs-ganesha-6.5/src/config_samples/export.txt000066400000000000000000000210361473756622300214410ustar00rootroot00000000000000# Sample export config # # This sample is not intended to be used as is, rather it is an illustration of # some of the flexibility of EXPORT configuration. There are a couple simple # EXPORT configurations at the end that are more usable. # # Options documentation: # # Export permission options available in EXPORT_DEFAULTS, EXPORT, and CLIENT # blocks. If an option is not set in a more specific block, the next less # specific block will be considered, until finally the default is taken if # the option is not specified in any applicable block, following this order: # CLIENT, EXPORT, EXPORT_DEFAULTS, baked in default. # # Access_Type (NONE): RW, RO, MDONLY, MDONLY_RO, NONE # RW allows all opertations # RO allows only operations that do not modify the server # MDONLY does not allow READ or WRITE operations, but # allows any other operation. # MDONLY_RO does not allow READ, WRITE, or any operation # that modifies file attributes or directory # content # NONE allows no access at all # # Protocols (3,4) The Protocols allowed. NFSV3, NFSV4, and 9P may be # specified. 3, 4, V3, V4, NFS3, and NFS4 may also be # used. # # Transports (UDP, TCP) The transport protocols allowed (UDP, TCP, and RDMA may # be specified) # # Squash (Root_Squash) What kind of user id squashing is performed: # No_Root_Squash, NoIdSquash, None # No user id squashing is performed # RootId, Root_Id_Squash, RootIdSquash # uid 0 and gid 0 are squashed to the # Anonymous_Uid and Anonymous_Gid # gid 0 in alt_groups lists is also squashed # Root, Root_Squash, RootSquash # uid 0 and gid of any value are squashed to the # Anonymous_Uid and Anonymous_Gid # alt_groups lists is discarded # All, All_Squash, AllSquash, All_Anonymous, AllAnonymous # All users are squashed # # Anonymous_Uid (-2) If a user id is squashed, this is the uid used # Ranges from -2147483648 to 4294967295 # uid are traditionally uint32_t however, tradition # has long been to specify NFS anonymous uid as -2 # so negative values are allowed # # Anonymous_Gid (-2) If a group id is squashed, this is the gid used # Ranges from -2147483648 to 4294967295 # gid are traditionally uint32_t however, tradition # has long been to specify NFS anonymous gid as -2 # so negative values are allowed # # SecType (none, sys) The RPC security flavors allowed, none (AUTH_NONE), # sys (AUTH_SYS/AUTH_UNIX), krb5 (RPCSEC_GSS), # krb5i (RPCSEC_GSS), krb5p (RCSEC_GSS) # # PrivilegedPort (false) If this option is true, client connections # must originate from port < 1024. This is # tradition based on some operating systems # requiring a user to be a privileged user to # create a socket with a source port < 1024 # # Manage_Gids (false) If this option is true, the alt groups list in # AUTH_SYS credentials will be replaced by a server # lookup of the group list. This allows bypassing the # 16 group limit of AUTH_SYS. # # Delegations (None) The types of delegations that may be granted. (None, Read, Write, # ReadWrite, R, W, and RW may be specified). # EXPORT_DEFAULTS block: # # All export permission options are usable. # # WARNING: If Access_Type is specified, that access type will be granted to # all clients on any export for which there is not an applicable CLIENT # block that explicitly provides a different Access_Type or for which the # EXPORT block does not provide a different Access_Type. # # If you desire to set a default Access_Type for all allowed clients, you # may then want to specify Access_Type = None; in every EXPORT block. EXPORT_DEFAULTS { SecType = sys, krb5, krb5i, krb5p; # Restrict all exports to NFS v4 unless otherwise specified Protocols = 4; } # EXPORT block # # All export permissions options are available, as well as the following: # # Export_id (required) An identifier for the export, must be unique and # between 0 and 65535. If Export_Id 0 is specified, Pseudo # must be the root path (/). # # Path (required) The directory in the exported file system this export # is rooted on (may be ignored for some FSALs). It need # not be unique if Pseudo and/or Tag are specified. # # Pseudo (required v4) This option specifies the position in the Pseudo FS # this export occupies if this is an NFS v4 export. It # must be unique. By using different Pseudo options, # the same Path may be exported multiple times. # # Tag (no default) This option allows an alternative access for NFS v3 # mounts. The option MUST not have a leading /. Clients # may not mount subdirectories (i.e. if Tag = foo, the # client may not mount foo/baz). By using different # Tag options, the same Path may be exported multiple # times. # # MaxRead (4194304) The maximum read size on this export # MaxWrite (4194304) The maximum write size on this export # PrefRead (4194304) The preferred read size on this export # PrefWrite (4194304) The preferred write size on this export # PrefReaddir (16384) The preferred readdir size on this export # These 5 options have the same range of values from # 512 to 9 megabytes. # # MaxOffsetWrite (18446744073709551615) Maximum file offset that may be written # MaxOffsetRead (18446744073709551615) Maximum file offset that may be read # These options may be used to restrict # the offsets within files. # # CLIENT (optional) See the CLIENT block below # # FSAL (required) See the FSAL block below EXPORT { Export_Id = 1; Path = /export/exp1; Pseudo = /export/exp1; Tag = exp1; # Override the default set in EXPORT_DEFAULTS Protocols = 3,4; MaxRead = 65536; MaxWrite = 65536; PrefRead = 65536; PrefWrite = 65536; # All clients for which there is no CLIENT block that specifies a # different Access_Type will have RW access (this would be an unusual # specification in the real world since barring a firewall, this # export is world readable and writeable). Access_Type = RW; # FSAL block # # This is required to indicate which Ganesha File System Abstraction # Layer (FSAL) will be used for this export. # # The only option available for all FSALs is: # # Name (required) The name of the FSAL # # Some FSALs have additional options, see individual FSAL documentation. FSAL { Name = VFS; } # CLIENT blocks # # An export may optionally have one or more CLIENT blocks. These blocks # specify export options for a restricted set of clients. The export # permission options specified in the EXPORT block will apply to any # client for which there is no applicable CLIENT block. # # All export permissions options are available, as well as the # following: # # Clients (required) The list of clients these export permissions # apply to. Clients may be specified by hostname, # ip address, netgroup, CIDR network address, # host name wild card, or simply "*" to apply to # all clients. CLIENT { Clients = 192.168.0.10, 192.168.1.0/8; Squash = None; } CLIENT { # Note the following specification is a larger network than # the first block, however, the first applicable CLIENT block # is used. Clients = 192.168.0.0/16; Squash = All; Access_Type = RO; } CLIENT { # This block is actually meaningless since 192.168.0.22 will # match the network address in the second CLIENT block. Clients = 192.168.0.22; Squash = None; Access_Type = RW; } } # Here is a simple sample EXPORT that should be used without an EXPORT_DEFAULTS # block. It takes advantage of the fact that whatever export permissions are # in the EXPORT block are applied to all clients for which there is no # matching CLIENT block. EXPORT { Export_Id = 2; Path = /export; Pseudo = /export; Access_Type = RW; Squash = None; FSAL { Name = VFS; } } # Here is an example with a simple CLIENT block EXPORT { Export_Id = 3; Path = /export2; Pseudo = /export2; FSAL { Name = VFS; } CLIENT { Clients = your, list, of, clients; Access_Type = RW; Squash = None; } } # Finally here is an example of how you can specify options for the Pseudo FS. # Note that even without specifying the Pseudo Root EXPORT, EXPORT_DEFAULTS will # still apply to it (except for Access_Type, Protocols, Transports, and Squash, # since those are all "set" options). EXPORT { Export_Id - 0; Path = /; Pseudo = /; CLIENT { Clients = 192.168.0.0/16; Access_Type = MDONLY_RO; SecType=sys,krb5,krb5i,krb5p; } } # The Automatically Generated Pseudo Root is effectively: EXPORT { Export_Id - 0; Path = /; Pseudo = /; Squash = None; Protocols = NFSV4; Transports = TCP; Access_Type = MDONLY_RO; Filesystem_Id = 152.152; MaxWrite = 67108864; MaxRead = 67108864; PrefWrite = 67108864; PrefRead = 67108864; PrefReaddir = 67108864; } nfs-ganesha-6.5/src/config_samples/ganesha.conf.example000066400000000000000000000044461473756622300232740ustar00rootroot00000000000000################################################### # # Ganesha Config Example # # This is a commented example configuration file for Ganesha. It is not # complete, but only has some common configuration options. See the man pages # for complete documentation. # ################################################### ## These are core parameters that affect Ganesha as a whole. #NFS_CORE_PARAM { ## Allow NFSv3 to mount paths with the Pseudo path, the same as NFSv4, ## instead of using the physical paths. #mount_path_pseudo = true; ## Configure the protocols that Ganesha will listen for. This is a hard ## limit, as this list determines which sockets are opened. This list ## can be restricted per export, but cannot be expanded. #Protocols = 3,4,9P; #} ## These are defaults for exports. They can be overridden per-export. #EXPORT_DEFAULTS { ## Access type for clients. Default is None, so some access must be ## given either here or in the export itself. #Access_Type = RW; #} ## Configure settings for the object handle cache #MDCACHE { ## The point at which object cache entries will start being reused. #Entries_HWMark = 100000; #} ## Configure an export for some file tree #EXPORT #{ ## Export Id (mandatory, each EXPORT must have a unique Export_Id) #Export_Id = 12345; ## Exported path (mandatory) #Path = /nonexistent; ## Pseudo Path (required for NFSv4 or if mount_path_pseudo = true) #Pseudo = /nonexistent; ## Restrict the protocols that may use this export. This cannot allow ## access that is denied in NFS_CORE_PARAM. #Protocols = 3,4; ## Access type for clients. Default is None, so some access must be ## given. It can be here, in the EXPORT_DEFAULTS, or in a CLIENT block #Access_Type = RW; ## Whether to squash various users. #Squash = root_squash; ## Allowed security types for this export #Sectype = sys,krb5,krb5i,krb5p; ## Exporting FSAL #FSAL { #Name = VFS; #} #} ## Configure logging. Default is to log to Syslog. Basic logging can also be ## configured from the command line #LOG { ## Default log level for all components #Default_Log_Level = WARN; ## Configure per-component log levels. #Components { #FSAL = INFO; #NFS4 = EVENT; #} ## Where to log #Facility { #name = FILE; #destination = "/var/log/ganesha.log"; #enable = active; #} #} nfs-ganesha-6.5/src/config_samples/gluster.conf000066400000000000000000000013661473756622300217170ustar00rootroot00000000000000################################################### # # EXPORT # # To function, all that is required is an EXPORT # # Define the absolute minimal export # ################################################### EXPORT { # Export Id (mandatory, each EXPORT must have a unique Export_Id) Export_Id = 77; # Exported path (mandatory) Path = "/testvol"; # Pseudo Path (required for NFS v4) Pseudo = "/testvol"; # Required for access (default is None) # Could use CLIENT blocks instead Access_Type = RW; # Allow root access Squash = No_Root_Squash; # Security flavor supported SecType = "sys"; # Exporting FSAL FSAL { Name = "GLUSTER"; Hostname = localhost; Volume = "testvol"; enable_upcall = true; Transport = tcp; # tcp or rdma } } nfs-ganesha-6.5/src/config_samples/gpfs.conf000066400000000000000000000021011473756622300211550ustar00rootroot00000000000000################################################### # # EXPORT_DEFAULTS Parameter # ################################################### EXPORT_DEFAULTS { # GPFS has an invalidate upcall that allows it # to trust attributes longer. Attr_Expiration_Time = 600; } ################################################### # # NFS_Core_Param # ################################################### NFS_Core_Param { # GPFS is clustered Clustered = TRUE; } ################################################### # # NFSv4 Specific configuration stuff # ################################################### NFSv4 { Lease_Lifetime=90; } ################################################### # # FSAL Specific config to enable READ delegation support. # ################################################### GPFS { delegations = R; } ################################################### # Export entries ################################################### # First export entry EXPORT { Export_Id = 77; Path = /ibm/gpfs0; Pseudo = /ibm/gpfs0; Access_Type=RW; FSAL { Name = GPFS; } } nfs-ganesha-6.5/src/config_samples/gpfs.ganesha.exports.conf000066400000000000000000000000001473756622300242610ustar00rootroot00000000000000nfs-ganesha-6.5/src/config_samples/gpfs.ganesha.log.conf000066400000000000000000000014661473756622300233570ustar00rootroot00000000000000LOG { Default_log_level = EVENT; Facility { name = FILE; destination = /var/log/ganesha/nfs-ganesha.log; max_level = FULL_DEBUG; headers = all; enable = idle; } Format { date_format = ISO-8601; time_format = ISO-8601; EPOCH = TRUE; CLIENTIP = FALSE; HOSTNAME = TRUE; PID = TRUE; THREAD_NAME = TRUE; FILE_NAME = FALSE; LINE_NUM = FALSE; FUNCTION_NAME = TRUE; COMPONENT = TRUE; LEVEL = TRUE; } Components { # Eg. COMPONENT = LEVEL; ALL = EVENT; } } nfs-ganesha-6.5/src/config_samples/gpfs.ganesha.main.conf000066400000000000000000000017261473756622300235210ustar00rootroot00000000000000GPFS { fsal_trace = TRUE; fsal_grace = FALSE; } NFS_Core_Param { Nb_Worker = 256; Clustered = TRUE; NFS_Protocols= 3,4; NFS_Port = 2049; MNT_Port = 0; NLM_Port = 0; RQUOTA_Port = 0; RPC_Max_Connections = 10000; heartbeat_freq = 0; short_file_handle = FALSE; } NFSv4 { Lease_Lifetime = 60; Delegations = FALSE; } DIRECTORY_SERVICES { DomainName = virtual1.com; Idmapping_Active = TRUE; } MDCACHE { Entries_HWMark = 1500000; LRU_Run_Interval = 90; FD_HWMark_Percent = 60; FD_LWMark_Percent = 20; FD_Limit_Percent = 90; } Export_Defaults { Access_Type = none; Protocols = 3,4; Transports = TCP; Anonymous_uid = -2; Anonymous_gid = -2; SecType = sys; PrivilegedPort = FALSE; Manage_Gids = FALSE; Squash = root_squash; NFS_Commit = FALSE; } nfs-ganesha-6.5/src/config_samples/gpfs.ganesha.nfsd.conf000066400000000000000000000002111473756622300235130ustar00rootroot00000000000000%include /etc/ganesha/gpfs.ganesha.main.conf %include /etc/ganesha/gpfs.ganesha.log.conf %include /etc/ganesha/gpfs.ganesha.exports.conf nfs-ganesha-6.5/src/config_samples/grafana.dashboard.json000066400000000000000000002242101473756622300235760ustar00rootroot00000000000000{ "__inputs": [ { "name": "Prometheus", "label": "Prometheus", "description": "", "type": "datasource", "pluginId": "prometheus", "pluginName": "Prometheus" } ], "__requires": [ { "type": "grafana", "id": "grafana", "name": "Grafana", "version": "7.0.4" }, { "type": "panel", "id": "graph", "name": "Graph", "version": "" }, { "type": "datasource", "id": "prometheus", "name": "Prometheus", "version": "1.0.0" } ], "annotations": { "list": [ { "builtIn": 1, "datasource": "-- Grafana --", "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", "type": "dashboard" } ] }, "editable": true, "gnetId": null, "graphTooltip": 0, "id": 2, "links": [], "panels": [ { "collapsed": false, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "id": 35, "panels": [], "title": "Summary", "type": "row" }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 1 }, "hiddenSeries": false, "id": 2, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:nfs_requests:rate1m", "interval": "", "legendFormat": "{{ operation}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Total request rate", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 1 }, "hiddenSeries": false, "id": 28, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:latency_ms_percentile:rate1m{percentile=\"50\"}", "interval": "", "legendFormat": "{{ operation}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Mean (50%) latency", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 9 }, "hiddenSeries": false, "id": 8, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:nfs_bytes_transferred:rate1m", "interval": "", "legendFormat": "{{ operation}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Total throughput (Bytes/s)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "Bps", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {}, "links": [] }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 9 }, "hiddenSeries": false, "id": 66, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:mdcache_cache_hit_ratio:rate1m * 100", "interval": "", "legendFormat": "{{ operation }}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Metadata cache hit ratios", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "decimals": null, "format": "percent", "label": null, "logBase": 1, "max": "100", "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "collapsed": true, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 17 }, "id": 27, "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 18 }, "hiddenSeries": false, "id": 42, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:latency_ms_percentile:rate1m{percentile=\"1\"}", "interval": "", "legendFormat": "{{ operation}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "1% latency", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 18 }, "hiddenSeries": false, "id": 43, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:latency_ms_percentile:rate1m{percentile=\"90\"}", "interval": "", "legendFormat": "{{ operation}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "90% latency", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 26 }, "hiddenSeries": false, "id": 45, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:latency_ms_percentile:rate1m{percentile=\"50\"}", "interval": "", "legendFormat": "{{ operation}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Mean (50%) latency", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 26 }, "hiddenSeries": false, "id": 44, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:latency_ms_percentile:rate1m{percentile=\"95\"}", "interval": "", "legendFormat": "{{ operation}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "95% latency", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "title": "Latency", "type": "row" }, { "collapsed": true, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 18 }, "id": 16, "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 19 }, "hiddenSeries": false, "id": 7, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:latency_ms_percentile:rate1m{operation=\"read\"}", "interval": "", "legendFormat": "{{ percentile }}%", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Read latency distribution", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 19 }, "hiddenSeries": false, "id": 22, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:latency_ms_percentile:rate1m{operation=\"write\"}", "interval": "", "legendFormat": "{{ percentile }}%", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Write latency distribution", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 27 }, "hiddenSeries": false, "id": 74, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:latency_ms_percentile:rate1m{operation=\"getattr\"}", "interval": "", "legendFormat": "{{ percentile }}%", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Getattr latency distribution", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 27 }, "hiddenSeries": false, "id": 75, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:latency_ms_percentile:rate1m{operation=\"lookup\"}", "interval": "", "legendFormat": "{{ percentile }}%", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Lookup latency distribution", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 35 }, "hiddenSeries": false, "id": 76, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:latency_ms_percentile:rate1m{operation=\"access\"}", "interval": "", "legendFormat": "{{ percentile }}%", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Access latency distribution", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "title": "Latency distributions", "type": "row" }, { "collapsed": true, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 19 }, "id": 14, "panels": [ { "datasource": null, "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "gridPos": { "h": 1, "w": 12, "x": 0, "y": 20 }, "id": 36, "options": { "content": " ", "mode": "html" }, "pluginVersion": "7.4.3", "timeFrom": null, "timeShift": null, "title": "Reads", "transparent": true, "type": "text" }, { "datasource": null, "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "gridPos": { "h": 1, "w": 12, "x": 12, "y": 20 }, "id": 37, "options": { "content": " ", "mode": "html" }, "pluginVersion": "7.4.3", "timeFrom": null, "timeShift": null, "title": "Writes", "transparent": true, "type": "text" }, { "cards": { "cardPadding": 0, "cardRound": null }, "color": { "cardColor": "#b4ff00", "colorScale": "sqrt", "colorScheme": "interpolateCool", "exponent": 0.5, "max": null, "min": 0, "mode": "spectrum" }, "dataFormat": "tsbuckets", "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {}, "thresholds": { "mode": "absolute", "steps": [] } }, "overrides": [] }, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 21 }, "heatmap": {}, "hideZeroBuckets": true, "highlightCards": true, "id": 38, "legend": { "show": true }, "pluginVersion": "7.0.4", "reverseYBuckets": false, "targets": [ { "expr": "ganesha:nfs_response_size_bytes:rate1m{operation=\"read\"}", "format": "heatmap", "interval": "", "legendFormat": "{{ le }}", "refId": "A" } ], "timeFrom": null, "timeShift": null, "title": "NFS read response size heatmap", "tooltip": { "show": true, "showHistogram": false }, "tooltipDecimals": null, "type": "heatmap", "xAxis": { "show": true }, "xBucketNumber": null, "xBucketSize": null, "yAxis": { "decimals": 0, "format": "decbytes", "logBase": 1, "max": "2000000", "min": "0", "show": true, "splitFactor": null }, "yBucketBound": "upper", "yBucketNumber": 200, "yBucketSize": 200000 }, { "cards": { "cardPadding": 0, "cardRound": null }, "color": { "cardColor": "#b4ff00", "colorScale": "sqrt", "colorScheme": "interpolateCool", "exponent": 0.5, "max": null, "min": 0, "mode": "spectrum" }, "dataFormat": "tsbuckets", "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {}, "thresholds": { "mode": "absolute", "steps": [] } }, "overrides": [] }, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 21 }, "heatmap": {}, "hideZeroBuckets": true, "highlightCards": true, "id": 39, "legend": { "show": true }, "pluginVersion": "7.0.4", "reverseYBuckets": false, "targets": [ { "expr": "ganesha:nfs_request_size_bytes:rate1m{operation=\"write\"}", "format": "heatmap", "interval": "", "legendFormat": "{{ le }}", "refId": "A" } ], "timeFrom": null, "timeShift": null, "title": "NFS write request size heatmap", "tooltip": { "show": true, "showHistogram": false }, "tooltipDecimals": null, "type": "heatmap", "xAxis": { "show": true }, "xBucketNumber": null, "xBucketSize": null, "yAxis": { "decimals": 0, "format": "decbytes", "logBase": 1, "max": "2000000", "min": "0", "show": true, "splitFactor": null }, "yBucketBound": "upper", "yBucketNumber": 200, "yBucketSize": 200000 }, { "cards": { "cardPadding": 0, "cardRound": null }, "color": { "cardColor": "#b4ff00", "colorScale": "sqrt", "colorScheme": "interpolateCool", "exponent": 0.5, "max": null, "min": 0, "mode": "spectrum" }, "dataFormat": "tsbuckets", "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {}, "thresholds": { "mode": "absolute", "steps": [] } }, "overrides": [] }, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 29 }, "heatmap": {}, "hideZeroBuckets": true, "highlightCards": true, "id": 31, "legend": { "show": true }, "pluginVersion": "7.0.4", "reverseYBuckets": false, "targets": [ { "expr": "ganesha:nfs_request_size_bytes:rate1m{operation=\"read\"}", "format": "heatmap", "interval": "", "legendFormat": "{{ le }}", "refId": "A" } ], "timeFrom": null, "timeShift": null, "title": "NFS read request size heatmap", "tooltip": { "show": true, "showHistogram": false }, "tooltipDecimals": null, "type": "heatmap", "xAxis": { "show": true }, "xBucketNumber": null, "xBucketSize": null, "yAxis": { "decimals": 0, "format": "decbytes", "logBase": 1, "max": "2000000", "min": "0", "show": true, "splitFactor": null }, "yBucketBound": "upper", "yBucketNumber": 200, "yBucketSize": 200000 } ], "title": "Request / response sizes", "type": "row" }, { "collapsed": true, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 20 }, "id": 78, "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 21 }, "hiddenSeries": false, "id": 79, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:nfs_errors:rate1m{status!~\"NFS3_OK|NFS4_OK\"}", "interval": "", "legendFormat": "{{ version }} {{ operation }} {{ status }}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Errors / second", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 21 }, "hiddenSeries": false, "id": 80, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:nfs_errors:ratio:rate1m", "interval": "", "legendFormat": "{{ version }} {{ operation }}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Error rate (%)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "decimals": null, "format": "percentunit", "label": null, "logBase": 1, "max": "1", "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 29 }, "hiddenSeries": false, "id": 81, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:nfs_errors:sum{status!~\"NFS3_OK|NFS4_OK\"}", "interval": "", "legendFormat": "{{ version }} {{ operation }} {{ status }}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Total error count", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "title": "Errors", "type": "row" }, { "collapsed": true, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 21 }, "id": 53, "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 21 }, "hiddenSeries": false, "id": 54, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:nfs_requests_by_export:sum:rate1m", "interval": "", "legendFormat": "{{ export}}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Total request rate per export", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {}, "links": [] }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 21 }, "hiddenSeries": false, "id": 69, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:mdcache_cache_hit_ratio_by_export:rate1m{operation=\"getattr\"} * 100", "interval": "", "legendFormat": "{{ export }}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Cache hit ratio per export [ getattr ]", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "decimals": null, "format": "percent", "label": null, "logBase": 1, "max": "100", "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 29 }, "hiddenSeries": false, "id": 56, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:nfs_bytes_transferred_by_export:rate1m{operation=\"read\"}", "interval": "", "legendFormat": "{{ export }}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Read throughput per export (Bytes/s)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "Bps", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {}, "links": [] }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 29 }, "hiddenSeries": false, "id": 71, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:mdcache_cache_hit_ratio_by_export:rate1m{operation=\"lookup\"} * 100", "interval": "", "legendFormat": "{{ export }}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Cache hit ratio per export [ lookup ]", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "decimals": null, "format": "percent", "label": null, "logBase": 1, "max": "100", "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 37 }, "hiddenSeries": false, "id": 57, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:nfs_bytes_transferred_by_export:rate1m{operation=\"write\"}", "interval": "", "legendFormat": "{{ export }}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Write throughput per export (Bytes/s)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "Bps", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "title": "Per export metrics", "type": "row" }, { "collapsed": true, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 22 }, "id": 62, "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 46 }, "hiddenSeries": false, "id": 58, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:latency_ms_by_export_percentile:rate1m{percentile=\"50\", operation=\"read\"}", "interval": "", "legendFormat": "{{ export }}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Mean (50%) read latency per export", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 12, "y": 46 }, "hiddenSeries": false, "id": 60, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:latency_ms_by_export_percentile:rate1m{percentile=\"50\"}", "interval": "", "legendFormat": "{{ operation }} {{ export }}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Mean (50%) latency per export (all operations)", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 54 }, "hiddenSeries": false, "id": 59, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:latency_ms_by_export_percentile:rate1m{percentile=\"90\", operation=\"read\"}", "interval": "", "legendFormat": "{{ export }}", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "90% read latency per export", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "ms", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "title": "Per export latency", "type": "row" }, { "collapsed": true, "datasource": null, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 23 }, "id": 10, "panels": [ { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 47 }, "hiddenSeries": false, "id": 25, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:rpcs_in_flight", "interval": "", "legendFormat": "rpcs", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "RPCs in flight", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 55 }, "hiddenSeries": false, "id": 29, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:rpcs_received:rate1m", "interval": "", "legendFormat": "received", "refId": "A" }, { "expr": "ganesha:rpcs_completed:rate1m", "interval": "", "legendFormat": "completed", "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Rate of RPCs received / completed", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } }, { "aliasColors": {}, "bars": false, "dashLength": 10, "dashes": false, "datasource": "Prometheus", "description": "", "fieldConfig": { "defaults": { "custom": {} }, "overrides": [] }, "fill": 1, "fillGradient": 0, "gridPos": { "h": 8, "w": 12, "x": 0, "y": 55 }, "hiddenSeries": false, "id": 82, "legend": { "avg": false, "current": false, "max": false, "min": false, "show": true, "total": false, "values": false }, "lines": true, "linewidth": 1, "nullPointMode": "null", "options": { "alertThreshold": true }, "percentage": false, "pluginVersion": "7.4.3", "pointradius": 2, "points": false, "renderer": "flot", "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { "expr": "ganesha:active_clients_60s:sum", "interval": "", "legendFormat": "Active clients", "refId": "A" } ], "thresholds": [], "timeFrom": null, "timeRegions": [], "timeShift": null, "title": "Active NFS clients", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, "type": "graph", "xaxis": { "buckets": null, "mode": "time", "name": null, "show": true, "values": [] }, "yaxes": [ { "format": "short", "label": null, "logBase": 1, "max": null, "min": "0", "show": true }, { "format": "short", "label": null, "logBase": 1, "max": null, "min": null, "show": true } ], "yaxis": { "align": false, "alignLevel": null } } ], "title": "Other metrics", "type": "row" } ], "refresh": false, "schemaVersion": 27, "style": "dark", "tags": [], "templating": { "list": [] }, "time": { "from": "now-30m", "to": "now" }, "timepicker": {}, "timezone": "", "title": "Ganesha (local)", "uid": "", "version": 1 } nfs-ganesha-6.5/src/config_samples/kvsfs.ganesha.nfsd.conf000066400000000000000000000006751473756622300237260ustar00rootroot00000000000000 ################################################### EXPORT { # Export Id (mandatory) Export_Id = 77 ; Path = "/"; FSAL { name = KVSFS; kvsns_config = /etc/kvsns.d/kvsns.ini; } Pseudo = /kvsfs; Protocols= NFSV3, 4, 9p; SecType = sys; MaxRead = 32768; MaxWrite = 32768; Filesystem_id = 192.168; client { clients = *; Squash=no_root_squash; access_type=RW; protocols = 3, 4, 9p; } } nfs-ganesha-6.5/src/config_samples/logging.txt000066400000000000000000000103561473756622300215510ustar00rootroot00000000000000## # # This block controls the logging system # ## LOG { # The components block contains one or more logging components # and the setting to be used. components { # The ALL component is special. When set it defines the level # for all components and overrides any other setting in this block. # For convenience, many component names can be specified using two # different values. One is the #define constant without the # COMPONENT_ prefix, for example, HASHTABLE_CACHE. The other is # the string that shows up in the actual log with blank replaced with # underscore, for example HT_CACHE. # # Log levels are: # # NULL, FATAL, MAJ, CRIT, WARN, EVENT, INFO, DEBUG, MID_DEBUG, # FULL_DEBUG # # ALL = FULL_DEBUG; # this will likely kill performance # ALL = EVENT; # this is the default # INIT = INFO; # RPC = FULL_DEBUG; # DBUS = DEBUG; } # Formatting of Log Messages # Each parameter is a field in the log message Format { # date formats are: ganesha, local, ISO-8601, syslog, syslog_usec # user_defined, none # ganesha time format %d/%m/%Y (DD/MM/YYYY) # compatible with older Ganesha (pre 1.5) # ganesha date format %H:%M:%S (HH:MM:SS) # compatible with older Ganesha (pre 1.5) # local date format is the local format as would show using %c # in format string to strftime. # local time format %X (preferred local format) # ISO-8601 date format %F (YYYY-MM-DD) # ISO-8601 time format %X (preferred local format) # syslog date format %b %e (Mon MM) # syslog time format %X (preferred local format) # syslog_usec date format %F (YYYY-MM-DD) # syslog_usec time format T%H:%M:%S.%%06u%z (THH:MM:SS.UUUUUU+hhmm) # date and time are separated by "T" # instead of " ", +hhmm is the current UTC # offset (can be - of course) # none date format no date # none time format no time # user_defined date format specify a strftime format string # user_defined time format specify a strftime format string # you may specify the entire string for # either time or date, and set the # other to none # date and time default to ganesha. # # date_format = ganesha; # time_format = ganesha; # # If user_defined is set for date or time, these fields take a # strftime type format. These are examples. The default is empty. # Note that you will need single or double quotes because it could # have spaces and, in this particular example, it has a leading '%' # which is not part of a regular token which must have a leading alphabetic. # # user_date_format = "%D"; # user_time_format = "%T"; # the following, if true, adds that field to the message # these are the defaults # HOSTNAME=true; # PROGNAME=true; # PID=true; # EPOCH=true; # CLIENTIP=false; # THREAD_NAME=true; # FUNCTION_NAME=true; # COMPONENT=true; # LEVEL=true; # FILE_NAME= true; # LINE_NUM= true; # OP_ID=false; # CLIENT_REQ_XID=false; } # Facilities # these can be added or modified. # Three are defined by default, STDERR, STDOUT, and SYSLOG # if the '-L' option is used on the command line, a FILE # facility is created at startup with the option argument as # the output file. # facility { # an arbitrary name. If it matches an existing, the other # parameters are used to modify it. # name = FILE; # Any higher level than this is not reported to this facility # max_level = FULL_DEBUG; # This can be stdout, stderr, or a file path # destination = /var/log/ganesha/nfs-ganesha.log; # facility state. Can be idle, active, or default # An idle facility just sits there # An active facility will accept log messages # The default facility is special in that it cannot # be removed or made idle. You can switch another in its # place however # enable = default; # } # The wired default level is EVENT. You change it here. # The default is set for any components not defined in the # components block. default_log_level = EVENT; } nfs-ganesha-6.5/src/config_samples/logrotate_fsal_gluster000066400000000000000000000001701473756622300240500ustar00rootroot00000000000000/var/log/ganesha/ganesha-gfapi.log { weekly rotate 52 copytruncate dateext compress missingok } nfs-ganesha-6.5/src/config_samples/logrotate_ganesha000066400000000000000000000001621473756622300227650ustar00rootroot00000000000000/var/log/ganesha/ganesha.log { weekly rotate 52 copytruncate dateext compress missingok } nfs-ganesha-6.5/src/config_samples/lustre.conf000066400000000000000000000010601473756622300215370ustar00rootroot00000000000000################################################### # # EXPORT # # To function, all that is required is an EXPORT # # Define the absolute minimal export # ################################################### EXPORT { # Export Id (mandatory, each EXPORT must have a unique Export_Id) Export_Id = 77; # Exported path (mandatory) Path = /nonexistent; # Pseudo Path (required for NFS v4) Pseudo = /nonexistent; # Required for access (default is None) # Could use CLIENT blocks instead Access_Type = RW; # Exporting FSAL FSAL { Name = LUSTRE; } } nfs-ganesha-6.5/src/config_samples/mem.conf000066400000000000000000000004631473756622300210050ustar00rootroot00000000000000EXPORT { Export_ID=1234; Path = "/any/path/you/want"; Pseudo = "/some/pseudo/path"; Access_Type = RW; FSAL { Name = MEM; } } MEM { # This is the size needed to pass pyNFS. Default is 0 Inode_Size = 1114112; # This creates a thread that exercises UP calls UP_Test_Interval = 20; } nfs-ganesha-6.5/src/config_samples/prometheus.rules.yml000066400000000000000000000240351473756622300234300ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later # Copyright (C) Google Inc., 2021 # Author: Bjorn Leffler leffler@google.com groups: - name: ganesha-rules rules: # RPCs. - record: ganesha:rpcs_in_flight expr: rpcs_in_flight - record: ganesha:rpcs_completed:rate1m expr: rate(rpcs_completed_total[1m]) - record: ganesha:rpcs_received:rate1m expr: rate(rpcs_received_total[1m]) # Estimate "active clients" as "clients active in the last 60 seconds" # For global client count, aggregate on last_client_update to avoid double counting. - record: ganesha:last_client_update expr: last_client_update - record: ganesha:last_client_update_60s expr: max by (client) (last_client_update > time() - 60) - record: ganesha:active_clients_60s:sum expr: count(ganesha:last_client_update_60s) # QPS per {operation}. - record: ganesha:nfs_requests:rate1m expr: sum(rate(nfs_requests_total[1m])) without (instance, job) - record: ganesha:client_requests:rate1m expr: sum(rate(client_requests_total[1m])) without (instance, job) - record: ganesha:nfs_requests_by_export:rate1m expr: sum(rate(nfs_requests_by_export_total[1m])) without (instance, job) # Total QPS across all operations. - record: ganesha:nfs_requests:sum:rate1m expr: sum(rate(nfs_requests_total[1m])) without (instance, job, operation) - record: ganesha:client_requests:sum:rate1m expr: sum(rate(client_requests_total[1m])) without (instance, job, operation) - record: ganesha:nfs_requests_by_export:sum:rate1m expr: sum(rate(nfs_requests_by_export_total[1m])) without (instance, job, operation) # Bytes sent throughput per {operation}. - record: ganesha:nfs_bytes_sent:rate1m expr: sum(rate(nfs_bytes_sent_total[1m])) without (instance, job) - record: ganesha:client_bytes_sent:rate1m expr: sum(rate(client_bytes_sent_total[1m])) without (instance, job) - record: ganesha:nfs_bytes_sent_by_export:rate1m expr: sum(rate(nfs_bytes_sent_by_export_total[1m])) without (instance, job) # Bytes received throughput per {operation}. - record: ganesha:nfs_bytes_received:rate1m expr: sum(rate(nfs_bytes_received_total[1m])) without (instance, job) - record: ganesha:client_bytes_received:rate1m expr: sum(rate(client_bytes_received_total[1m])) without (instance, job) - record: ganesha:nfs_bytes_received_by_export:rate1m expr: sum(rate(nfs_bytes_received_by_export_total[1m])) without (instance, job) # Total bytes transferred throughput per {operation}. - record: ganesha:nfs_bytes_transferred:rate1m expr: ganesha:nfs_bytes_sent:rate1m + ganesha:nfs_bytes_received:rate1m - record: ganesha:client_bytes_transferred:rate1m expr: ganesha:client_bytes_sent:rate1m + ganesha:client_bytes_received:rate1m - record: ganesha:nfs_bytes_transferred_by_export:rate1m expr: ganesha:nfs_bytes_sent_by_export:rate1m + ganesha:nfs_bytes_received_by_export:rate1m # Total bytes sent throughput across all operations. - record: ganesha:nfs_bytes_sent:sum:rate1m expr: sum(rate(nfs_bytes_sent_total[1m])) without (instance, job, operation) - record: ganesha:nfs_bytes_sent_by_export:sum:rate1m expr: sum(rate(nfs_bytes_sent_by_export_total[1m])) without (instance, job, operation) # Total bytes received throughput across all operations. - record: ganesha:nfs_bytes_received:sum:rate1m expr: sum(rate(nfs_bytes_received_total[1m])) without (instance, job, operation) - record: ganesha:nfs_bytes_received_by_export:sum:rate1m expr: sum(rate(nfs_bytes_received_by_export_total[1m])) without (instance, job, operation) # Total bytes transferred throughput across all operations. - record: ganesha:nfs_bytes_transferred:sum:rate1m expr: ganesha:nfs_bytes_sent:sum:rate1m + ganesha:nfs_bytes_received:sum:rate1m - record: ganesha:nfs_bytes_transferred_by_export:sum:rate1m expr: ganesha:nfs_bytes_sent_by_export:sum:rate1m + ganesha:nfs_bytes_received_by_export:sum:rate1m # Request and response sizes. - record: ganesha:nfs_request_size_bytes:rate1m expr: sum(rate(nfs_request_size_bytes_bucket[1m])) without (instance, job) - record: ganesha:nfs_response_size_bytes:rate1m expr: sum(rate(nfs_response_size_bytes_bucket[1m])) without (instance, job) # NFS error rates and ratios. - record: ganesha:nfs_errors:rate1m expr: sum(rate(nfs_errors_total[1m])) without (instance, job) - record: ganesha:nfs_errors:sum:rate1m expr: sum(rate(nfs_errors_total[1m])) without (instance, job, status) - record: ganesha:nfs_errors:bad:rate1m expr: sum(rate(nfs_errors_total{status!~"NFS3_OK|NFS4_OK"}[1m])) without (instance, job, status) - record: ganesha:nfs_errors:good:rate1m expr: sum(rate(nfs_errors_total{status=~"NFS3_OK|NFS4_OK"}[1m])) without (instance, job, status) - record: ganesha:nfs_errors:ratio:rate1m expr: ganesha:nfs_errors:bad:rate1m / ganesha:nfs_errors:sum:rate1m # Total NFS error count. - record: ganesha:nfs_errors:sum expr: sum(nfs_errors_total) without (instance, job) # MD cache hits and misses per {operation}. - record: ganesha:mdcache_cache_hits:rate1m expr: sum(rate(mdcache_cache_hits_total[1m])) without (instance, job) - record: ganesha:mdcache_cache_misses:rate1m expr: sum(rate(mdcache_cache_misses_total[1m])) without (instance, job) - record: ganesha:mdcache_cache_hit_ratio:rate1m expr: ganesha:mdcache_cache_hits:rate1m / (ganesha:mdcache_cache_hits:rate1m + ganesha:mdcache_cache_misses:rate1m) # Cache hits and misses per {operation, export}. - record: ganesha:mdcache_cache_hits_by_export:rate1m expr: sum(rate(mdcache_cache_hits_by_export_total[1m])) without (instance, job) - record: ganesha:mdcache_cache_misses_by_export:rate1m expr: sum(rate(mdcache_cache_misses_by_export_total[1m])) without (instance, job) - record: ganesha:mdcache_cache_hit_ratio_by_export:rate1m expr: ganesha:mdcache_cache_hits_by_export:rate1m / (ganesha:mdcache_cache_hits_by_export:rate1m + ganesha:mdcache_cache_misses_by_export:rate1m) # MDCache wait times. - record: ganesha:mdcache_wait_time_ns:rate1m expr: sum(rate(mdcache_wait_time_ns[1m])) without (instance, job) # Latency per {operation} - record: ganesha:latency_ms:rate1m expr: sum(rate(nfs_latency_ms_bucket[1m])) without (instance, job) - record: ganesha:latency_ms_by_export:rate1m expr: sum(rate(nfs_latency_ms_by_export_bucket[1m])) without (instance, job) # Latency percentiles [1m] per {operation} - record: ganesha:latency_ms_percentile:rate1m expr: histogram_quantile(0.01, ganesha:latency_ms:rate1m) labels: percentile: 1 - record: ganesha:latency_ms_percentile:rate1m expr: histogram_quantile(0.10, ganesha:latency_ms:rate1m) labels: percentile: 10 - record: ganesha:latency_ms_percentile:rate1m expr: histogram_quantile(0.20, ganesha:latency_ms:rate1m) labels: percentile: 20 - record: ganesha:latency_ms_percentile:rate1m expr: histogram_quantile(0.30, ganesha:latency_ms:rate1m) labels: percentile: 30 - record: ganesha:latency_ms_percentile:rate1m expr: histogram_quantile(0.40, ganesha:latency_ms:rate1m) labels: percentile: 40 - record: ganesha:latency_ms_percentile:rate1m expr: histogram_quantile(0.50, ganesha:latency_ms:rate1m) labels: percentile: 50 - record: ganesha:latency_ms_percentile:rate1m expr: histogram_quantile(0.60, ganesha:latency_ms:rate1m) labels: percentile: 60 - record: ganesha:latency_ms_percentile:rate1m expr: histogram_quantile(0.70, ganesha:latency_ms:rate1m) labels: percentile: 70 - record: ganesha:latency_ms_percentile:rate1m expr: histogram_quantile(0.80, ganesha:latency_ms:rate1m) labels: percentile: 80 - record: ganesha:latency_ms_percentile:rate1m expr: histogram_quantile(0.90, ganesha:latency_ms:rate1m) labels: percentile: 90 - record: ganesha:latency_ms_percentile:rate1m expr: histogram_quantile(0.95, ganesha:latency_ms:rate1m) labels: percentile: 95 - record: ganesha:latency_ms_percentile:rate1m expr: histogram_quantile(0.99, ganesha:latency_ms:rate1m) labels: percentile: 99 # Latency percentiles [1m] per {operation, export} - record: ganesha:latency_ms_by_export_percentile:rate1m expr: histogram_quantile(0.01, ganesha:latency_ms_by_export:rate1m) labels: percentile: 1 - record: ganesha:latency_ms_by_export_percentile:rate1m expr: histogram_quantile(0.10, ganesha:latency_ms_by_export:rate1m) labels: percentile: 10 - record: ganesha:latency_ms_by_export_percentile:rate1m expr: histogram_quantile(0.20, ganesha:latency_ms_by_export:rate1m) labels: percentile: 20 - record: ganesha:latency_ms_by_export_percentile:rate1m expr: histogram_quantile(0.30, ganesha:latency_ms_by_export:rate1m) labels: percentile: 30 - record: ganesha:latency_ms_by_export_percentile:rate1m expr: histogram_quantile(0.40, ganesha:latency_ms_by_export:rate1m) labels: percentile: 40 - record: ganesha:latency_ms_by_export_percentile:rate1m expr: histogram_quantile(0.50, ganesha:latency_ms_by_export:rate1m) labels: percentile: 50 - record: ganesha:latency_ms_by_export_percentile:rate1m expr: histogram_quantile(0.60, ganesha:latency_ms_by_export:rate1m) labels: percentile: 60 - record: ganesha:latency_ms_by_export_percentile:rate1m expr: histogram_quantile(0.70, ganesha:latency_ms_by_export:rate1m) labels: percentile: 70 - record: ganesha:latency_ms_by_export_percentile:rate1m expr: histogram_quantile(0.80, ganesha:latency_ms_by_export:rate1m) labels: percentile: 80 - record: ganesha:latency_ms_by_export_percentile:rate1m expr: histogram_quantile(0.90, ganesha:latency_ms_by_export:rate1m) labels: percentile: 90 - record: ganesha:latency_ms_by_export_percentile:rate1m expr: histogram_quantile(0.95, ganesha:latency_ms_by_export:rate1m) labels: percentile: 95 - record: ganesha:latency_ms_by_export_percentile:rate1m expr: histogram_quantile(0.99, ganesha:latency_ms_by_export:rate1m) labels: percentile: 99 nfs-ganesha-6.5/src/config_samples/proxy_v3.conf000066400000000000000000000010361473756622300220150ustar00rootroot00000000000000PROXY_V3 { # How many sockets to keep open in the connection pool. num_sockets = 64; } EXPORT { # Export Id (mandatory, each EXPORT must have a unique Export_Id) Export_Id = 77; # Exported path (mandatory, path to export on the remote server) Path = /tmp; # Pseudo Path. This is required for NFS v4 *exporting*, so fill this in # even though this proxies v3 servers. Pseudo = /tmp_proxy; Access_Type = RW; Squash = no_root_squash; # Exporting FSAL FSAL { Name = PROXY_V3; Srv_Addr = 10.0.0.3; } } nfs-ganesha-6.5/src/config_samples/proxy_v4.conf000066400000000000000000000006101473756622300220130ustar00rootroot00000000000000EXPORT { # Export Id (mandatory, each EXPORT must have a unique Export_Id) Export_Id = 77; # Exported path (mandatory, path to export on the remote server) Path = /tmp; # Pseudo Path (required for NFS v4) Pseudo = /tmp_proxy; Access_Type = RW; Squash = no_root_squash; # Exporting FSAL FSAL { Name = PROXY_V4; Srv_Addr = 10.0.0.3; Use_Privileged_Client_Port = true; } } nfs-ganesha-6.5/src/config_samples/rgw.conf000066400000000000000000000010621473756622300210220ustar00rootroot00000000000000EXPORT { Export_ID=1; Path = "/"; Pseudo = "/"; Access_Type = RW; Protocols = 4; Transports = TCP; FSAL { Name = RGW; User_Id = "testuser"; Access_Key_Id =""; Secret_Access_Key = ""; } } MDCACHE { # FSAL_RGW needs Dir_Chunk to do `readdir` as POSIX style. # Because of that, we should never set Dir_Chunk to `0`. } RGW { ceph_conf = "//ceph.conf"; # for vstart cluster, name = "client.admin" name = "client.rgw.foohost"; cluster = "ceph"; # init_args = "-d --debug-rgw=16"; } nfs-ganesha-6.5/src/config_samples/rgw_bucket.conf000066400000000000000000000007021473756622300223570ustar00rootroot00000000000000EXPORT { Export_ID=1; Path = "testbucket"; Pseudo = "/testbucket"; Access_Type = RW; Protocols = 4; Transports = TCP; FSAL { Name = RGW; User_Id = "testuser"; Access_Key_Id =""; Secret_Access_Key = ""; } } RGW { ceph_conf = "//ceph.conf"; # for vstart cluster, name = "client.admin" name = "client.rgw.foohost"; cluster = "ceph"; # init_args = "-d --debug-rgw=16"; } nfs-ganesha-6.5/src/config_samples/vfs.conf000066400000000000000000000010551473756622300210230ustar00rootroot00000000000000################################################### # # EXPORT # # To function, all that is required is an EXPORT # # Define the absolute minimal export # ################################################### EXPORT { # Export Id (mandatory, each EXPORT must have a unique Export_Id) Export_Id = 77; # Exported path (mandatory) Path = /nonexistent; # Pseudo Path (required for NFS v4) Pseudo = /nonexistent; # Required for access (default is None) # Could use CLIENT blocks instead Access_Type = RW; # Exporting FSAL FSAL { Name = VFS; } } nfs-ganesha-6.5/src/config_samples/xfs.conf000066400000000000000000000010361473756622300210240ustar00rootroot00000000000000################################################### # # EXPORT # # To function, all that is required is an EXPORT # # Define the absolute minimal export # ################################################### EXPORT { # Export Id (mandatory, each EXPORT must have a unique Export_Id) Export_Id = 77; # Exported path (mandatory) Path = /xfs; # Pseudo Path (required for NFS v4) Pseudo = /xfs; # Required for access (default is None) # Could use CLIENT blocks instead Access_Type = RW; # Exporting FSAL FSAL { Name = XFS; } } nfs-ganesha-6.5/src/dbus/000077500000000000000000000000001473756622300153215ustar00rootroot00000000000000nfs-ganesha-6.5/src/dbus/CMakeLists.txt000066400000000000000000000030071473756622300200610ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- include_directories( ${DBUS_INCLUDE_DIRS} ) ########### next target ############### SET(gshdbus_STAT_SRCS dbus_server.c properties_handler.c signal_handler.c dbus_heartbeat.c ) add_library(gshdbus OBJECT ${gshdbus_STAT_SRCS}) add_sanitizers(gshdbus) set_target_properties(gshdbus PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(gshdbus gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) ########### install files ############### nfs-ganesha-6.5/src/dbus/dbus_heartbeat.c000066400000000000000000000037211473756622300204440ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright (C) IBM Inc., 2014 * Author: Jeremy Bongio * * This program 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 3 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 * 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 * * ------------- */ /* * @file dbus_heartbeat.c * @author Jeremy Bongio * @brief DBUS Heartbeat */ #include "config.h" #include #include #include #include #include #include #include #include "fsal.h" #include "nfs_core.h" #include "log.h" #include "avltree.h" #include "gsh_types.h" #include "gsh_dbus.h" #include "abstract_atomic.h" #include "gsh_intrinsic.h" int dbus_heartbeat_cb(void *arg) { SetNameFunction("dbus_heartbeat"); int err = 0; int rc = BCAST_STATUS_OK; dbus_bool_t ishealthy = nfs_health(); if (ishealthy) { /* send the heartbeat pulse */ err = gsh_dbus_broadcast(DBUS_PATH HEARTBEAT_NAME, DBUS_ADMIN_IFACE, HEARTBEAT_NAME, DBUS_TYPE_BOOLEAN, &ishealthy, DBUS_TYPE_INVALID); if (err) { LogCrit(COMPONENT_DBUS, "heartbeat broadcast failed. err:%d", err); rc = BCAST_STATUS_WARN; } } return rc; } void init_heartbeat(void) { add_dbus_broadcast(&dbus_heartbeat_cb, NULL, nfs_param.core_param.heartbeat_freq * NS_PER_MSEC, BCAST_FOREVER); } nfs-ganesha-6.5/src/dbus/dbus_priv.h000066400000000000000000000030631473756622300174710ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2013, Panasas Inc. * Contributor : Jim Lieb * * Some portions Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #ifndef DBUS_PRIV_H #define DBUS_PRIV_H bool dbus_proc_property(const char *method, DBusMessage *msg, DBusMessage *reply, DBusError *error, struct gsh_dbus_interface **interfaces); int dbus_append_signal_string(DBusMessageIter *args, void *sig_string); int dbus_send_signal(DBusConnection *conn, char *obj_name, char *int_name, char *sig_name, int (*payload)(DBusMessageIter *signal, void *args), void *sig_args); #endif /* DBUS_PRIV_H */ nfs-ganesha-6.5/src/dbus/dbus_server.c000066400000000000000000000624071473756622300200210ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2010, The Linux Box Corporation * Contributor : Matt Benjamin * * Some portions Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #include "config.h" #include #include #include #include #include #include #include "gsh_list.h" #include #include #include #include #include "fsal.h" #include "nfs_core.h" #include "log.h" #include "nfs_rpc_callback.h" #include "gsh_dbus.h" #include #include "dbus_priv.h" #include "nfs_init.h" /** * * \file dbus_server.c * \author Matt Benjamin * \brief Low-level DBUS message server and callout framework. * * \section DESCRIPTION * * This module implements a (somewhat) generic service handler, initially to * support a planned callback simulator. Developers are encouraged to expand * on this interface to support other use cases. * * This module should be initialized before any service provider module * calls gsh_dbus_register_msg(); * */ #define GSH_DBUS_NONE 0x0000 #define GSH_DBUS_SHUTDOWN 0x0001 #define GSH_DBUS_SLEEPING 0x0002 static const char dbus_name[] = "org.ganesha.nfsd"; /* * List and mutex used by the dbus broadcast service */ struct glist_head dbus_broadcast_list; pthread_mutex_t dbus_bcast_lock; struct ganesha_dbus_handler { char *name; struct avltree_node node_k; DBusObjectPathVTable vtable; }; struct _dbus_thread_state { int initialized; pthread_t thread_id; wait_entry_t we; DBusConnection *dbus_conn; DBusError dbus_err; uint32_t dbus_serial; struct avltree callouts; uint32_t flags; }; static struct _dbus_thread_state thread_state; static inline int dbus_callout_cmpf(const struct avltree_node *lhs, const struct avltree_node *rhs) { struct ganesha_dbus_handler *lk, *rk; lk = avltree_container_of(lhs, struct ganesha_dbus_handler, node_k); rk = avltree_container_of(rhs, struct ganesha_dbus_handler, node_k); return strcmp(lk->name, rk->name); } /* Validate dbus prefix, returning 0 if empty, -1 if invalid, otherwise the * length of the prefix. */ static inline int is_valid_dbus_prefix(const char *prefix) { int i = 0; if (prefix == NULL || *prefix == '\0') return 0; if (!isalpha(prefix[i]) && prefix[i] != '_') return -1; i++; while (prefix[i] != '\0') { if (!isalnum(prefix[i]) && prefix[i] != '_') return -1; i++; } return i; } static inline void dbus_name_with_prefix(char *prefixed_dbus_name, size_t prefixed_dbus_name_size, const char *default_name, const char *prefix) { int prefix_len, total_len, default_len = strlen(default_name); assert(default_len < prefixed_dbus_name_size); prefix_len = is_valid_dbus_prefix(prefix); if (prefix_len <= 0) { if (prefix_len < 0) { LogEvent( COMPONENT_DBUS, "Dbus name prefix is invalid. Ignoring the prefix."); } memcpy(prefixed_dbus_name, default_name, default_len + 1); return; } /* Additional length for separator (.) and null character */ total_len = default_len + prefix_len + 2; if (total_len >= prefixed_dbus_name_size) { LogEvent(COMPONENT_DBUS, "Dbus name prefix too long. Ignoring the prefix."); memcpy(prefixed_dbus_name, default_name, default_len + 1); return; } memcpy(prefixed_dbus_name, prefix, prefix_len); prefixed_dbus_name[prefix_len] = '.'; memcpy(prefixed_dbus_name + prefix_len + 1, default_name, default_len + 1); } /* * @brief compare routine used to sort broadcast items * * Function used to sort the broadcast items according * to time expirey. Conforms to the glist_compare * function signature so it can be used with glist_insert_sorted * * @param a: Pointer to the glist of a dbus_bcast_item * @param b: Pointer to the glist of another dbus_bcast_item * to compare the first to */ int dbus_bcast_item_compare(struct glist_head *a, struct glist_head *b) { struct dbus_bcast_item *bcast_item_a; struct dbus_bcast_item *bcast_item_b; bcast_item_a = glist_entry(a, struct dbus_bcast_item, dbus_bcast_q); bcast_item_b = glist_entry(b, struct dbus_bcast_item, dbus_bcast_q); return gsh_time_cmp(&bcast_item_a->next_bcast_time, &bcast_item_b->next_bcast_time); } /* * @brief del_dbus_broadcast: Delete a broadcast item from the * broadcast service * * Function to be called by any thread wanting to remove a broadcast item * from the dbus broadcast service * * @param to_remove: The pointer to the dbus_bcast_item * returned from add_dbus_broadcast */ void del_dbus_broadcast(struct dbus_bcast_item *to_remove) { PTHREAD_MUTEX_lock(&dbus_bcast_lock); glist_del(&to_remove->dbus_bcast_q); PTHREAD_MUTEX_unlock(&dbus_bcast_lock); gsh_free(to_remove); } /* * @brief add_dbus_broadcast: Add a callback to the broadcast service * * Function to be called by any thread that wants to add a callback * to the dbus broadcast service * * @param bcast_callback: Function pointer to be called * @param arg: Arg that will be passed to the callback * @param bcast_interval: The time in nsec between calls * @param count: The number of times to invoke the callback * Pass BCAST_FOREVER to call indefinitely * * @return: The pointer to the dbus_bcast_item created */ struct dbus_bcast_item *add_dbus_broadcast(dbus_bcast_callback bcast_callback, void *bcast_arg, uint32_t bcast_interval, int count) { struct dbus_bcast_item *new_bcast = NULL; new_bcast = (struct dbus_bcast_item *)gsh_malloc( sizeof(struct dbus_bcast_item)); now(&new_bcast->next_bcast_time); new_bcast->bcast_interval = bcast_interval; new_bcast->count = count; new_bcast->bcast_arg = bcast_arg; new_bcast->bcast_callback = bcast_callback; PTHREAD_MUTEX_lock(&dbus_bcast_lock); glist_insert_sorted(&dbus_broadcast_list, &(new_bcast->dbus_bcast_q), &dbus_bcast_item_compare); PTHREAD_MUTEX_unlock(&dbus_bcast_lock); return new_bcast; } /* * @brief init_dbus_broadcast: Initializes broadcast list and mutex */ void init_dbus_broadcast(void) { PTHREAD_MUTEX_init(&dbus_bcast_lock, NULL); glist_init(&dbus_broadcast_list); if (nfs_param.core_param.heartbeat_freq > 0) init_heartbeat(); } void gsh_dbus_pkginit(void) { int code = 0; char prefixed_dbus_name[NAME_MAX + 1]; LogDebug(COMPONENT_DBUS, "init"); avltree_init(&thread_state.callouts, dbus_callout_cmpf, 0 /* must be 0 */); dbus_error_init(&thread_state.dbus_err); /* sigh */ thread_state.dbus_conn = dbus_bus_get(DBUS_BUS_SYSTEM, &thread_state.dbus_err); if (dbus_error_is_set(&thread_state.dbus_err)) { LogCrit(COMPONENT_DBUS, "dbus_bus_get failed (%s)", thread_state.dbus_err.message); dbus_error_free(&thread_state.dbus_err); goto out; } dbus_name_with_prefix(prefixed_dbus_name, sizeof(prefixed_dbus_name), dbus_name, nfs_param.core_param.dbus_name_prefix); code = dbus_bus_request_name(thread_state.dbus_conn, prefixed_dbus_name, DBUS_NAME_FLAG_REPLACE_EXISTING, &thread_state.dbus_err); if (dbus_error_is_set(&thread_state.dbus_err)) { LogCrit(COMPONENT_DBUS, "server bus reg failed (%s, %s)", prefixed_dbus_name, thread_state.dbus_err.message); dbus_error_free(&thread_state.dbus_err); if (!code) code = EINVAL; goto out; } if (code != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { LogCrit(COMPONENT_DBUS, "server failed becoming primary bus owner (%s, %d)", prefixed_dbus_name, code); goto out; } init_dbus_broadcast(); thread_state.initialized = true; out: return; } #define INTROSPECT_HEAD \ "\n" \ "\n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" #define INTROSPECT_TAIL "\n" #define PROPERTIES_INTERFACE_HEAD \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" #define PROPERTIES_INTERFACE_SIGNAL \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" #define PROPERTIES_INTERFACE_TAIL " \n" #define INTROSPECT_INTERFACE_HEAD " \n" #define INTROSPECT_INTERFACE_TAIL " \n" #define INTROSPECT_METHOD_HEAD " \n" #define INTROSPECT_METHOD_TAIL " \n" #define INTROSPECT_METHOD_ARG \ " \n" #define INTROSPECT_PROPERTY \ " \n" #define INTROSPECT_SIGNAL_HEAD " \n" #define INTROSPECT_SIGNAL_TAIL " \n" #define INTROSPECT_SIGNAL_ARG " \n" static const char *const prop_access[] = { [DBUS_PROP_READ] = "read", [DBUS_PROP_WRITE] = "write", [DBUS_PROP_READWRITE] = "readwrite" }; static bool dbus_reply_introspection(DBusMessage *reply, struct gsh_dbus_interface **interfaces) { DBusMessageIter iter; FILE *fp; char *introspection_xml = NULL; size_t xml_size = 0; struct gsh_dbus_interface **iface; bool have_props = false; bool props_signal = false; bool retval = true; fp = open_memstream(&introspection_xml, &xml_size); if (fp == NULL) { LogCrit(COMPONENT_DBUS, "open_memstream for introspection failed"); retval = false; goto out; } fputs(INTROSPECT_HEAD, fp); for (iface = interfaces; *iface; iface++) { fprintf(fp, INTROSPECT_INTERFACE_HEAD, (*iface)->name); if ((*iface)->props != NULL) { struct gsh_dbus_prop **prop; for (prop = (*iface)->props; *prop; prop++) { fprintf(fp, INTROSPECT_PROPERTY, (*prop)->name, (*prop)->type, prop_access[(*prop)->access]); } have_props = true; if ((*iface)->signal_props) props_signal = true; } if ((*iface)->methods != NULL) { struct gsh_dbus_method **method, *mp; struct gsh_dbus_arg *arg; for (method = (*iface)->methods; *method; method++) { mp = *method; fprintf(fp, INTROSPECT_METHOD_HEAD, mp->name); for (arg = mp->args; arg->name != NULL; arg++) { fprintf(fp, INTROSPECT_METHOD_ARG, arg->name, arg->direction, arg->type); } fputs(INTROSPECT_METHOD_TAIL, fp); } } if ((*iface)->signals != NULL) { struct gsh_dbus_signal **signal; struct gsh_dbus_arg *arg; for (signal = (*iface)->signals; *signal; signal++) { fprintf(fp, INTROSPECT_SIGNAL_HEAD, (*signal)->name); for (arg = (*signal)->args; arg->name != NULL; arg++) { fprintf(fp, INTROSPECT_SIGNAL_ARG, arg->name, arg->type); } fputs(INTROSPECT_SIGNAL_TAIL, fp); } } fputs(INTROSPECT_INTERFACE_TAIL, fp); } if (have_props) { fputs(PROPERTIES_INTERFACE_HEAD, fp); if (props_signal) fputs(PROPERTIES_INTERFACE_SIGNAL, fp); fputs(PROPERTIES_INTERFACE_TAIL, fp); } fputs(INTROSPECT_TAIL, fp); if (ferror(fp)) { LogCrit(COMPONENT_DBUS, "file error while constructing introspect xml"); } fclose(fp); if (introspection_xml == NULL) { LogCrit(COMPONENT_DBUS, "close of memstream for introspection failed"); retval = false; goto out; } /* create a reply from the message */ dbus_message_iter_init_append(reply, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &introspection_xml); gsh_free(introspection_xml); out: return retval; } /* @brief Stuff a status into the reply * * status reply is the first part of every reply message * dbus has its own error handling but that is for the connection. * this status is for ganesha level method result reporting. * If a NULL is passed for error message, we stuff a default * "BUSY". The error message is for things like GUI display or * logging but use the status bool for code flow. * * @param iter [IN] the iterator to append to * @param success [IN] the method status * @param errmessage [IN] an error message string */ void gsh_dbus_status_reply(DBusMessageIter *iter, bool success, char *errormsg) { char *error; dbus_bool_t retcode = success; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &retcode); if (errormsg == NULL) error = success ? "OK" : "BUSY"; else error = errormsg; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &error); } void gsh_dbus_append_timestamp(DBusMessageIter *iterp, struct timespec *timestamp) { DBusMessageIter ts_iter; /* tv_sec and tv_nsec may not be same size as dbus_uint64_t on * 32 bit systems, so copy them here to dbus_uint64_t sized * symbols. */ dbus_uint64_t sec = timestamp->tv_sec; dbus_uint64_t nsec = timestamp->tv_nsec; dbus_message_iter_open_container(iterp, DBUS_TYPE_STRUCT, NULL, &ts_iter); dbus_message_iter_append_basic(&ts_iter, DBUS_TYPE_UINT64, &sec); dbus_message_iter_append_basic(&ts_iter, DBUS_TYPE_UINT64, &nsec); dbus_message_iter_close_container(iterp, &ts_iter); } static DBusHandlerResult dbus_message_entrypoint(DBusConnection *conn, DBusMessage *msg, void *user_data) { const char *interface = dbus_message_get_interface(msg); const char *method = dbus_message_get_member(msg); struct gsh_dbus_interface **interfaces = user_data; DBusMessage *reply = NULL; DBusError error; DBusMessageIter args, *argsp; bool success = false; DBusHandlerResult result = DBUS_HANDLER_RESULT_HANDLED; static uint32_t serial = 1; dbus_error_init(&error); if (interface == NULL) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; reply = dbus_message_new_method_return(msg); if ((!strcmp(interface, DBUS_INTERFACE_INTROSPECTABLE)) || (method && (!strcmp(method, "Introspect")))) { success = dbus_reply_introspection(reply, interfaces); goto done; } if (method == NULL) { method = "No method arg"; goto done; } if (!strcmp(interface, DBUS_INTERFACE_PROPERTIES)) { success = dbus_proc_property(method, msg, reply, &error, interfaces); } else { struct gsh_dbus_interface **iface; if (dbus_message_iter_init(msg, &args)) argsp = &args; else argsp = NULL; for (iface = interfaces; *iface; iface++) { if (strcmp(interface, (*iface)->name) == 0) { struct gsh_dbus_method **m; for (m = (*iface)->methods; m && *m; m++) { if (strcmp(method, (*m)->name) == 0) { success = (*m)->method( argsp, reply, &error); goto done; } } LogMajor( COMPONENT_DBUS, "Unknown method (%s) on interface (%s)", method, interface); result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; goto done; } } LogMajor(COMPONENT_DBUS, "Unknown interface (%s)", interface); } done: if (!success) { const char *err_name, *err_text; if (dbus_error_is_set(&error)) { err_name = error.name; err_text = error.message; } else { err_name = interface; err_text = method; } LogMajor( COMPONENT_DBUS, "Method (%s) on (%s) failed: name = (%s), message = (%s)", method, interface, err_name, err_text); dbus_message_unref(reply); reply = dbus_message_new_error(msg, err_name, err_text); } success = dbus_connection_send(conn, reply, &serial); if (!success) { LogCrit(COMPONENT_DBUS, "reply failed"); result = DBUS_HANDLER_RESULT_NEED_MEMORY; dbus_connection_flush(conn); } if (reply) dbus_message_unref(reply); dbus_error_free(&error); serial++; return result; } static void path_unregistered_func(DBusConnection *connection, void *user_data) { /* connection was finalized -- do nothing */ } int32_t gsh_dbus_register_path(const char *name, struct gsh_dbus_interface **interfaces) { struct ganesha_dbus_handler *handler; struct avltree_node *node; int code = 0; const char *dbus_path = DBUS_PATH; handler = gsh_malloc(sizeof(struct ganesha_dbus_handler)); handler->name = gsh_concat(dbus_path, name); handler->vtable.unregister_function = path_unregistered_func; handler->vtable.message_function = dbus_message_entrypoint; if (!thread_state.dbus_conn) { LogCrit(COMPONENT_DBUS, "dbus_connection_register_object_path called with no DBUS connection"); gsh_free(handler->name); gsh_free(handler); goto out; } code = dbus_connection_register_object_path(thread_state.dbus_conn, handler->name, &handler->vtable, (void *)interfaces); if (!code) { LogFatal(COMPONENT_DBUS, "dbus_connection_register_object_path failed"); gsh_free(handler->name); gsh_free(handler); goto out; } node = avltree_insert(&handler->node_k, &thread_state.callouts); if (node) { LogFatal(COMPONENT_DBUS, "failed inserting method %s", handler->name); code = EINVAL; } LogDebug(COMPONENT_DBUS, "registered handler for %s", handler->name); out: return code; } pthread_t gsh_dbus_thrid; void gsh_dbus_pkgshutdown(void) { struct avltree_node *node, *next_node; struct ganesha_dbus_handler *handler; int code = 0; char prefixed_dbus_name[NAME_MAX + 1]; LogEvent(COMPONENT_DBUS, "Start DBUS shutdown"); /* Shutdown gsh_dbus_thread */ thread_state.flags |= GSH_DBUS_SHUTDOWN; pthread_join(gsh_dbus_thrid, NULL); /* remove and free handlers */ node = avltree_first(&thread_state.callouts); while (node) { next_node = avltree_next(node); handler = avltree_container_of( node, struct ganesha_dbus_handler, node_k); /* Unregister handler */ code = dbus_connection_unregister_object_path( thread_state.dbus_conn, handler->name); if (!code) { LogCrit(COMPONENT_DBUS, "dbus_connection_unregister_object_path called with no DBUS connection"); } avltree_remove(&handler->node_k, &thread_state.callouts); gsh_free(handler->name); gsh_free(handler); node = next_node; } avltree_init(&thread_state.callouts, dbus_callout_cmpf, 0); /* Unassign the name from dbus connection */ if (thread_state.dbus_conn) { dbus_name_with_prefix(prefixed_dbus_name, sizeof(prefixed_dbus_name), dbus_name, nfs_param.core_param.dbus_name_prefix); dbus_bus_release_name(thread_state.dbus_conn, prefixed_dbus_name, &thread_state.dbus_err); if (dbus_error_is_set(&thread_state.dbus_err)) { LogCrit(COMPONENT_DBUS, "err releasing name (%s, %s)", prefixed_dbus_name, thread_state.dbus_err.message); dbus_error_free(&thread_state.dbus_err); } /* * Shutdown bus: As per D-Bus documentation, a shared * connection created with dbus_connection_open() or * dbus_bus_get() should not be closed but instead be unref'ed */ dbus_connection_unref(thread_state.dbus_conn); } PTHREAD_MUTEX_destroy(&dbus_bcast_lock); LogEvent(COMPONENT_DBUS, "DBUS shutdown complete"); } void *gsh_dbus_thread(void *arg) { struct glist_head *glist = NULL; struct glist_head *glistn = NULL; struct timespec current_time; int time_expired; int rc = 0; SetNameFunction("dbus"); rcu_register_thread(); if (!thread_state.initialized) { LogCrit(COMPONENT_DBUS, "DBUS not initialized, service thread exiting"); goto out; } while (1) { if (thread_state.flags & GSH_DBUS_SHUTDOWN) break; LogFullDebug(COMPONENT_DBUS, "top of poll loop"); PTHREAD_MUTEX_lock(&dbus_bcast_lock); glist_for_each_safe(glist, glistn, &dbus_broadcast_list) { struct dbus_bcast_item *bcast_item = glist_entry( glist, struct dbus_bcast_item, dbus_bcast_q); now(¤t_time); time_expired = gsh_time_cmp( ¤t_time, &bcast_item->next_bcast_time); /* * list is sorted by soonest to latest * Break now if the next is not ready */ if (time_expired < 0) break; bcast_item->next_bcast_time = current_time; timespec_add_nsecs(bcast_item->bcast_interval, &bcast_item->next_bcast_time); rc = bcast_item->bcast_callback(bcast_item->bcast_arg); if (rc == BCAST_STATUS_WARN) { LogWarn(COMPONENT_DBUS, "Broadcast callback %p returned BCAST_STATUS_WARN", bcast_item); } else if (rc == BCAST_STATUS_FATAL) { LogWarn(COMPONENT_DBUS, "Broadcast callback %p returned BCAST_STATUS_FATAL", bcast_item); glist_del(&bcast_item->dbus_bcast_q); continue; } if (bcast_item->count > 0) bcast_item->count--; glist_del(&bcast_item->dbus_bcast_q); /* * If the callback should be called again, put it * back in the list sorted by soonest to longest */ if (bcast_item->count > 0 || bcast_item->count == BCAST_FOREVER) { glist_insert_sorted(&dbus_broadcast_list, &bcast_item->dbus_bcast_q, &dbus_bcast_item_compare); } } PTHREAD_MUTEX_unlock(&dbus_bcast_lock); /* do stuff */ if (!dbus_connection_read_write_dispatch(thread_state.dbus_conn, 100)) { LogCrit(COMPONENT_DBUS, "read_write_dispatch, got disconnected signal"); break; } /* here is where we do other stuff between messages */ } /* 1 */ out: LogEvent(COMPONENT_DBUS, "shutdown"); rcu_unregister_thread(); return NULL; } void gsh_dbus_wake_thread(uint32_t flags) { if (thread_state.flags & GSH_DBUS_SLEEPING) pthread_cond_signal(&thread_state.we.wq_cv); } /* * @brief gsh_dbus_broadcast: Broadcast a dbus message * * Function to be called by a thread's callback routine * in order to broadcast a message over dbus * * @param obj_name: The path to the object emitting the signal * Ex: "/org/ganesha/nfsd/heartbeat" * * @param int_name: The interface the signal is emitted from * Ex: "org.ganesha.nfsd.heartbeat" * * @param sig_name: The name of the signal * Ex: "heartbeat" * * @param first_arg_type: The type of the first argument passed * Ex: DBUS_TYPE_STRING, DBUS_TYPE_UINT32, etc... * * @param ... : An alternating list of types and data * All data args must be passed by reference * Ex: &my_int, * DBUS_TYPE_STRING, &charPtr, * DBUS_TYPE_BOOLEAN, &my_bool * * @return 0 on success or errno on failure */ int gsh_dbus_broadcast(char *obj_name, char *int_name, char *sig_name, int first_arg_type, ...) { static dbus_uint32_t serial; DBusMessage *msg; va_list arguments; int rc = 0; msg = dbus_message_new_signal(obj_name, int_name, sig_name); if (msg == NULL) return -EINVAL; va_start(arguments, first_arg_type); dbus_message_append_args_valist(msg, first_arg_type, arguments); va_end(arguments); if (!dbus_connection_send(thread_state.dbus_conn, msg, &serial)) rc = -ENOMEM; dbus_message_unref(msg); return rc; } #ifdef _USE_9P /* parse the 9P operation in args */ bool arg_9p_op(DBusMessageIter *args, u8 *opcode, char **errormsg) { char *opname; u8 opc; bool success = true; if (args == NULL) { success = false; *errormsg = "message is missing argument"; } else if (dbus_message_iter_get_arg_type(args) != DBUS_TYPE_STRING) { success = false; *errormsg = "arg not a string"; } else { dbus_message_iter_get_basic(args, &opname); for (opc = _9P_TSTATFS; opc <= _9P_TWSTAT; opc++) { if (_9pfuncdesc[opc].funcname != NULL && !strcmp(opname, _9pfuncdesc[opc].funcname)) break; } if (opc > _9P_TWSTAT) { success = false; *errormsg = "arg not a known 9P operation"; } else { *opcode = opc; } } return success; } #endif nfs-ganesha-6.5/src/dbus/properties_handler.c000066400000000000000000000206151473756622300213620ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2013, Panasas Inc. * Contributor : Jim Lieb * * Some portions Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #include "config.h" #include #include #include #include #include #include #include "gsh_list.h" #include "fsal.h" #include "nfs_core.h" #include "log.h" #include "nfs_rpc_callback.h" #include "gsh_dbus.h" #include #include "dbus_priv.h" #ifndef DBUS_ERROR_UNKNOWN_INTERFACE #define DBUS_ERROR_UNKNOWN_INTERFACE \ "org.freedesktop.DBus.Error.UnknownInterface" #endif #ifndef DBUS_ERROR_UNKNOWN_PROPERTY #define DBUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty" #endif #ifndef DBUS_ERROR_PROPERTY_READ_ONLY #define DBUS_ERROR_PROPERTY_READ_ONLY \ "org.freedesktop.DBus.Error.PropertyReadOnly" #endif /** * @brief The properties interface for properties interfaces * * Seems if introspect says we have a properties interface for an * object, scanners want to wall it to see if it has its own props. * Fake one here because properties doesn't have properties; signals * maybe but no props. */ static struct gsh_dbus_interface props_interface = { .name = DBUS_INTERFACE_PROPERTIES, .props = NULL, .methods = NULL, .signals = NULL }; static struct gsh_dbus_interface *props_ptr = &props_interface; static inline struct gsh_dbus_interface ** lookup_interface(const char *interface, struct gsh_dbus_interface **interfaces, DBusError *error) { struct gsh_dbus_interface **iface; if (strcmp(interface, DBUS_INTERFACE_PROPERTIES) == 0) return &props_ptr; for (iface = interfaces; *iface; iface++) { if (strcmp(interface, (*iface)->name) == 0) break; } if (*iface == NULL) { dbus_set_error(error, DBUS_ERROR_UNKNOWN_INTERFACE, "Requested interface: %s", interface); } return iface; } static inline struct gsh_dbus_prop ** lookup_property(const char *prop_name, struct gsh_dbus_interface **iface, DBusError *error) { struct gsh_dbus_prop **prop; for (prop = (*iface)->props; prop && *prop; prop++) { if (strcmp(prop_name, (*prop)->name) == 0) break; } if (prop == NULL || *prop == NULL) { dbus_set_error(error, DBUS_ERROR_UNKNOWN_PROPERTY, "Requested property: %s from %s", prop_name, (*iface)->name); return NULL; } return prop; } /** * * @brief Handle object properties * * Handle the three methods of the properties interface. * * @param argsp [IN] The message * @param reply [IN/OUT] Our reply * * @return bool success or failure if bad request */ bool dbus_proc_property(const char *method, DBusMessage *msg, DBusMessage *reply, DBusError *error, struct gsh_dbus_interface **interfaces) { const char *interface; const char *prop_name; bool retval = false; struct gsh_dbus_interface **iface; struct gsh_dbus_prop **prop; DBusMessageIter reply_iter; dbus_message_iter_init_append(reply, &reply_iter); if (strcmp(method, "GetAll") == 0) { DBusMessageIter getall_dict, dict_entry, val_iter; if (!dbus_message_get_args(msg, error, DBUS_TYPE_STRING, &interface, DBUS_TYPE_INVALID)) goto err_out; iface = lookup_interface(interface, interfaces, error); if (*iface == NULL) goto err_out; if (!dbus_message_iter_open_container( &reply_iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &getall_dict)) goto getall_err; for (prop = (*iface)->props; prop && *prop; prop++) { prop_name = (*prop)->name; if ((*prop)->access == DBUS_PROP_READ || (*prop)->access == DBUS_PROP_READWRITE) { if (!dbus_message_iter_open_container( &getall_dict, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry)) goto getall_err; if (!dbus_message_iter_append_basic( &dict_entry, DBUS_TYPE_STRING, &prop_name)) goto getall_err; if (!dbus_message_iter_open_container( &dict_entry, DBUS_TYPE_VARIANT, (*prop)->type, &val_iter)) goto getall_err; if (!(*prop)->get(&val_iter)) goto getall_err; if (!dbus_message_iter_close_container( &dict_entry, &val_iter)) goto getall_err; if (!dbus_message_iter_close_container( &getall_dict, &dict_entry)) goto getall_err; } else { dbus_set_error(error, DBUS_ERROR_PROPERTY_READ_ONLY, "%s of %s from %s (write only?)", method, prop_name, interface); /** @todo@ check does write only make sense?? */ goto err_out; } } if (!dbus_message_iter_close_container(&reply_iter, &getall_dict)) goto getall_err; return true; /* DONE! */ } else if (strcmp(method, "Get") == 0) { if (!dbus_message_get_args(msg, error, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &prop_name, DBUS_TYPE_INVALID)) goto err_out; iface = lookup_interface(interface, interfaces, error); if (*iface == NULL) goto err_out; prop = lookup_property(prop_name, iface, error); if (prop == NULL) goto err_out; if ((*prop)->access == DBUS_PROP_READ || (*prop)->access == DBUS_PROP_READWRITE) { DBusMessageIter variant_iter; if (!dbus_message_iter_open_container( &reply_iter, DBUS_TYPE_VARIANT, (*prop)->type, &variant_iter)) { dbus_set_error_const( error, DBUS_ERROR_FAILED, "Couldn't open Get container"); goto err_out; } retval = (*prop)->get(&variant_iter); if (retval == false || !dbus_message_iter_close_container(&reply_iter, &variant_iter)) { dbus_set_error_const( error, DBUS_ERROR_FAILED, "Couldn't close Get container"); goto err_out; } } else { dbus_set_error(error, DBUS_ERROR_PROPERTY_READ_ONLY, "%s of %s from %s (write only?)", method, prop_name, interface); /** @todo@ check does write only make sense?? */ goto err_out; } return true; /* DONE! */ } else if (strcmp(method, "Set") == 0) { DBusMessageIter iter_args; if (!dbus_message_iter_init(msg, &iter_args) || dbus_message_iter_get_arg_type(&iter_args) != DBUS_TYPE_STRING) { goto invalid_args; } dbus_message_iter_get_basic(&iter_args, &interface); if (!dbus_message_iter_next(&iter_args) || dbus_message_iter_get_arg_type(&iter_args) != DBUS_TYPE_STRING) { goto invalid_args; } dbus_message_iter_get_basic(&iter_args, &prop_name); if (!dbus_message_iter_next(&iter_args) || dbus_message_iter_get_arg_type(&iter_args) != DBUS_TYPE_VARIANT || dbus_message_iter_has_next(&iter_args)) { goto invalid_args; } iface = lookup_interface(interface, interfaces, error); if (*iface == NULL) goto err_out; prop = lookup_property(prop_name, iface, error); if (prop == NULL) goto err_out; if ((*prop)->access == DBUS_PROP_WRITE || (*prop)->access == DBUS_PROP_READWRITE) { DBusMessageIter arg; dbus_message_iter_recurse(&iter_args, &arg); return (*prop)->set(&arg); /* DONE! */ } else { dbus_set_error(error, DBUS_ERROR_PROPERTY_READ_ONLY, "%s of %s from %s", method, prop_name, interface); goto err_out; } } else { dbus_set_error(error, DBUS_ERROR_UNKNOWN_METHOD, "Requested method: %s", method); } return retval; getall_err: dbus_set_error(error, DBUS_ERROR_FAILED, "GetAll container failure"); goto err_out; invalid_args: dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, "Method %s", method); err_out: return retval; } nfs-ganesha-6.5/src/dbus/signal_handler.c000066400000000000000000000047351473756622300204500ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2013, Panasas Inc. * Contributor : Jim Lieb * * Some portions Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #include "config.h" #include #include #include #include #include #include #include "gsh_list.h" #include "fsal.h" #include "nfs_core.h" #include "log.h" #include "nfs_rpc_callback.h" #include "gsh_dbus.h" #include #include "dbus_priv.h" /** * @TODO@ This is a preliminary implementation to get * the basics down. Subject to change when we get real signals to send... */ /** * @brief Append a string to a signal */ int dbus_append_signal_string(DBusMessageIter *args, void *sig_string) { char *sigvalue = (char *)sig_string; if (!dbus_message_iter_append_basic(args, DBUS_TYPE_STRING, &sigvalue)) return ENOMEM; return 0; } /** * @brief Process a signal and send it */ int dbus_send_signal(DBusConnection *conn, char *obj_name, char *int_name, char *sig_name, int (*payload)(DBusMessageIter *signal, void *args), void *sig_args) { static dbus_uint32_t serial; DBusMessage *msg; DBusMessageIter sig_iter; int retval = 0; msg = dbus_message_new_signal(obj_name, int_name, sig_name); if (msg == NULL) return EINVAL; dbus_message_iter_init_append(msg, &sig_iter); retval = payload(&sig_iter, sig_args); if (retval != 0) return retval; if (!dbus_connection_send(conn, msg, &serial)) return ENOMEM; dbus_connection_flush(conn); dbus_message_unref(msg); return retval; } nfs-ganesha-6.5/src/doc/000077500000000000000000000000001473756622300151315ustar00rootroot00000000000000nfs-ganesha-6.5/src/doc/CMakeLists.txt000066400000000000000000000020561473756622300176740ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- if(USE_MAN_PAGE) add_subdirectory(man) endif(USE_MAN_PAGE) nfs-ganesha-6.5/src/doc/Resources.txt000066400000000000000000000033271473756622300176510ustar00rootroot00000000000000Here is a list of things that may be useful with Ganesha. * bonnie++ A filesystem benchmarking tool. It can be retrieved from http://www.coker.com.au/bonnie++/ * libgssapi A gssapi interface, available from CITI at http://www.citi.umich.edu/projects/nfsv4/linux/libgssapi/ * libgssglue A gssapi interface, available from CITI at http://www.citi.umich.edu/projects/nfsv4/linux/libgssglue/ * libnfsidmap A library from CITI for mapping NFSv4 user/group IDs. http://www.citi.umich.edu/projects/nfsv4/linux/libnfsidmap/ * librpcsecgss An implementation of RPCSEC_GSS from CITI. http://www.citi.umich.edu/projects/nfsv4/linux/librpcsecgss/ * locktests-net An fcntl lock stress tester from Bull, targeting NFSv4 locks https://sourceforge.net/projects/locktests/ * PyNFS Client and server libraries for NFSv4.0 and NFSv4.1 with a collection of tests. Originally by CITI, available from git://git.linux-nfs.org/projects/bfields/pynfs.git * NFS Shell A user-mode NFSv3 client and call exerciser, available from https://github.com/NetDirect/nfsshell * NFS Connectathon 04 Test NFS servers through POSIX calls. Available from git://fedorapeople.org/home/fedora/steved/public_git/cthon04.git * NFS Utils Linux NFS user space support utilities, available from git://git.linux-nfs.org/projects/steved/nfs-utils.git * NFSWatch Monitor traffic to and from an NFS server. Available from http://nfswatch.sourceforge.net/ * RPCBind Implements the RPC port mapping service, required for running an NFSv3. Available from http://rpcbind.sourceforge.net/ * SGI NFS Test Tools An NFS test suite available from SGI at http://oss.sgi.com/projects/nfs/testtools/ nfs-ganesha-6.5/src/doc/USEFUL-RFCs.txt000066400000000000000000000035551473756622300175000ustar00rootroot00000000000000The following RFCs are useful references for Ganesha RPC Version 2 https://datatracker.ietf.org/doc/rfc5531/ RPC Bind https://datatracker.ietf.org/doc/rfc1833/ NFS v2 We no longer implement V2 but for reference https://www.rfc-editor.org/info/rfc1094 NFS v3 https://www.rfc-editor.org/info/rfc1813 NFS v4.0 https://www.rfc-editor.org/info/rfc7530 obsolete but we coded to it originally https://www.rfc-editor.org/info/rfc3530 NFS v4.0 XDR https://www.rfc-editor.org/info/rfc7531 NFS v4.0 Migration https://datatracker.ietf.org/doc/draft-ietf-nfsv4-rfc3530-migration-update/ https://datatracker.ietf.org/doc/draft-ietf-nfsv4-migration-issues/ NFS v4.1 https://www.rfc-editor.org/info/rfc5661 NFS v4.1 XDR https://www.rfc-editor.org/info/rfc5662 NFS v4.1 PNFS Block Layout https://www.rfc-editor.org/info/rfc5663 NFS v4.1 PNFS Object Layout https://www.rfc-editor.org/info/rfc5664 NFS v4.1 PNFS Flex Files Layout http://datatracker.ietf.org/doc/draft-ietf-nfsv4-flex-files/ NFS v4.2 https://datatracker.ietf.org/doc/draft-ietf-nfsv4-minorversion2/ NFS v4.2 XDR https://datatracker.ietf.org/doc/draft-ietf-nfsv4-minorversion2-dot-x/ NFS v4.x Versioning https://datatracker.ietf.org/doc/draft-ietf-nfsv4-versioning/ NFS V4 XATTRS https://datatracker.ietf.org/doc/draft-ietf-nfsv4-xattrs/ IANA Considerations for Remote Procedure Call (RPC) Network Identifiers and Universal Address Formats https://www.rfc-editor.org/info/rfc5665 Kerberos/GSS https://www.rfc-editor.org/info/rfc1964 https://www.rfc-editor.org/info/rfc2025 https://www.rfc-editor.org/info/rfc2203 https://www.rfc-editor.org/info/rfc2623 https://www.rfc-editor.org/info/rfc4120 https://www.rfc-editor.org/info/rfc4121 https://www.rfc-editor.org/info/rfc5403 https://www.rfc-editor.org/info/rfc6112 https://www.rfc-editor.org/info/rfc6542 https://www.rfc-editor.org/info/rfc6649 and more...nfs-ganesha-6.5/src/doc/coding_style/000077500000000000000000000000001473756622300176145ustar00rootroot00000000000000nfs-ganesha-6.5/src/doc/coding_style/doxygen.txt000066400000000000000000000136131473756622300220360ustar00rootroot00000000000000A quick guide to Doxygen in Ganesha Adam C. Emerson Here are my suggestions for how to make better use of doxygen. This is not a complete manual on doxygen, just a short guide for hacking on ganesha. First and foremost, whenever you edit a function, please check the function comment to make sure that it accurately describes the function, that the parameters it lists are actually the parameters the function takes, and that the documented return matches the values the function may return. Please do likewise for data structures, enumerations, and other types. Every file should have a file comment. At minimum this should be of the form: /** * @file filename.c * @brief Do this, that, and the other thing */ Please do not prefix anything with the filename. If you add a long description, just separate it with a blank line, like so. /** * @file filename.c * @brief Functions for for this, that, and the other thing. * * This file contains functions that perform many important * operations, among them processing arguments and returning values. * They also allocate stack frames. */ There is no reason to copy the @brief description into the long description. Doxygen (assuming I've configured it properly) already handles that. If you don't have anything longer to say, don't include a long description at all. If you add an @author tag, please include your full name and email address, like so. /** * @file filename.c * @author Adam C. Emerson * @brief Functions for for this, that, and the other thing. */ The old tags with the $cvs stuff$ in them are really not very useful and should be removed. My opinion is that the @author tag is best used to indicate the person to contact if one has questions about a file, rather than the first person to create a file with that name. Therefore, if you have made extensive changes to a file, you could add yourself as an @author. (You may have more than one @author tag per file.) Every function should have a function comment. At a minimum, this should contain @brief information, a description of the parameters, and a description of the return value. If the function is void, do not include a @return tag. As with files, if the @brief description conveys all information worth conveying about the function, do not include a long comment and skip directly from the @brief description to the parameters. Here is an example: /** * @brief Do some stuff * * This function does a large number of interesting things with bits. * It ANDs them together. it ORs them. It shifts them left and * shifts them right. It even returns some. * * @param[in] param1 Some bits to work on * @param[out] param2 Some bits that we output * @param[in,out] param3 Some bits we read and output * * @return The result of doing things to the bits */ uint64_t do_some_stuff(uint64_t param1, uint64_t *param2, uint64_t *param3) If you wish to list possible return values and their meanings, please do so as follows: /** * @brief Check a value * * @param[in] v The value to check * * @retval VALUE_OKAY if the value is passable. * @retval VALUE_PU the value is not very nice. * @retval VALUE_HORRIBLE the value is really quite dreadful. * @retval VALUE_SUPERLATIVE the value is really quite good. */ Please do not prefix anything in the function comment with the function's name. Doxygen gets the function's name from the function itself. Every data structure should also have a comment consisting of, at least, a @brief. Members should be commented individually. This may be done one of two ways. /** * @brief A structure holding some state. */ struct state { char *name; /*< The name of the state */ char abbrev[2]; /*< The two-letter abbreviation for the state */ struct person *governor; /*< The state's executive */ }; Please note that an inline comment following a data member should be prefixed with /*<. Many structures have /**< which is incorrect. For readability, if the types or member names start to get long, you can also precede the data members with their comments, like so. struct city { /** The name of the city */ char *name; /** A linked list of officers responsible for city government */ struct glist_head city_council; /** The head of city government */ struct person *lord_mayor; }; Doxygen is happy with either one. I recommend the second if you're starting to get seriously word-wrapped member comments. Modules and layers can be indicated with the group command. We aren't doing this yet but should start. Pick a name and description for the module, wrap every file that implements it like so: /** * @defgroup Foolayer The Foo Layer * @{ */ /** * @file foo.c * @brief Functions for the foo layer */ /** * @brief Foo Function */ void foo(void) { } /** @} */ /** * @defgroup Foolayer The Foo Layer * @{ */ /** * @file foo.h * @brief Structures for the foo layer */ /** * @brief Foo structure */ struct foodata { uint64_t blah; } /** @} */ You may also define information pages. These are ideal for describing the high level design or detailing behaviors of a module that are not bound to a specific function or structure. Use the @page command like so: /** * @page LockDisc Locking Discipline * * When using this layer, one should always take the A lock before the * B lock, and the B lock before the C lock. One must have both the B * and C lock before modifying the Q area... */ The first token after @page is an arbitrary name for the page. Be unique. Following the unique page name is the page title. If you are describing the behavior of a subsystem, please make sure the @page comment is included within the grouping command for that subsystem. You can use markdown in any long description. For more information please see the doxygen manual, but this should be enough to get started. nfs-ganesha-6.5/src/doc/man/000077500000000000000000000000001473756622300157045ustar00rootroot00000000000000nfs-ganesha-6.5/src/doc/man/CMakeLists.txt000066400000000000000000000054051473756622300204500ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- set(sphinx_output_dir ${CMAKE_BINARY_DIR}/doc) set(man_srcs ganesha-config.rst ganesha-log-config.rst ganesha-cache-config.rst ganesha-export-config.rst ganesha-core-config.rst) if(USE_9P) list(APPEND man_srcs ganesha-9p-config.rst) endif() if(USE_FSAL_CEPH) list(APPEND man_srcs ganesha-ceph-config.rst) endif() if(USE_FSAL_RGW) list(APPEND man_srcs ganesha-rgw-config.rst) endif() if(USE_FSAL_XFS) list(APPEND man_srcs ganesha-xfs-config.rst) endif() if(USE_FSAL_GLUSTER) list(APPEND man_srcs ganesha-gluster-config.rst) endif() if(USE_FSAL_VFS) list(APPEND man_srcs ganesha-vfs-config.rst) endif() if(USE_FSAL_LUSTRE) list(APPEND man_srcs ganesha-lustre-config.rst) endif() if(USE_FSAL_PROXY_V4) list(APPEND man_srcs ganesha-proxy-v4-config.rst) endif() if(USE_FSAL_PROXY_V3) list(APPEND man_srcs ganesha-proxy-v3-config.rst) endif() if(USE_FSAL_GPFS) list(APPEND man_srcs ganesha-gpfs-config.rst) endif() if(USE_RADOS_RECOV) list(APPEND man_srcs ganesha-rados-cluster-design.rst ganesha-rados-grace.rst) endif() foreach(man ${man_srcs}) list(APPEND sphinx_input ${CMAKE_CURRENT_SOURCE_DIR}/${man}) string(REGEX REPLACE ".rst$" "" cmd ${man}) list(APPEND sphinx_output ${sphinx_output_dir}/${cmd}.8) install(FILES ${sphinx_output_dir}/${cmd}.8 DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man8/) endforeach() add_custom_command( OUTPUT ${sphinx_output} COMMAND ${SPHINX_BUILD} -b man -t man -d ${CMAKE_BINARY_DIR}/doc/doctrees -c ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${sphinx_output_dir} DEPENDS ${sphinx_input}) add_custom_target( manpages ALL DEPENDS ${sphinx_output} COMMENT "manpages building") nfs-ganesha-6.5/src/doc/man/conf.py000066400000000000000000000023631473756622300172070ustar00rootroot00000000000000import os project = u'NFS-Ganesha' def _get_description(fname, base): with open(fname) as f: one = None while True: line = f.readline().rstrip('\n') if not line: continue if line.startswith(':') and line.endswith(':'): continue one = line break two = f.readline().rstrip('\n') three = f.readline().rstrip('\n') assert one == three assert all(c=='=' for c in one) name, description = two.split('--', 1) assert name.strip() == base return description.strip() def _get_manpages(): man_dir = os.path.dirname(__file__) for filename in os.listdir(man_dir): base, ext = os.path.splitext(filename) if ext != '.rst': continue if base == 'index': continue path = os.path.join(man_dir, filename) description = _get_description(path, base) yield ( os.path.join(base), base, description, '', 8, ) man_pages = list(_get_manpages()) # sphinx warns if no toc is found, so feed it with a random file # which is also rendered in this run. master_doc = 'index' nfs-ganesha-6.5/src/doc/man/ganesha-9p-config.rst000066400000000000000000000026221473756622300216370ustar00rootroot00000000000000=================================================================== ganesha-9p-config -- NFS Ganesha 9p Configuration File =================================================================== .. program:: ganesha-9p-config SYNOPSIS ========================================================== | /etc/ganesha/ganesha.conf DESCRIPTION ========================================================== NFS-Ganesha obtains configuration data from the configuration file: /etc/ganesha/ganesha.conf This file lists 9p specific config options. _9P {} -------------------------------------------------------------------------------- **Nb_Worker(uint32, range 1 to 1024*128, default 256)** Number of worker threads. **_9P_TCP_Port(uint16, range 1 to UINT16_MAX, default 564)** **_9P_RDMA_Port(uint16, range 1 to UINT16_MAX, default 5640)** **_9P_TCP_Msize(uint32, range 1024 to UINT32_MAX, default 65536)** **_9P_RDMA_Msize(uint32, range 1024 to UINT32_MAX, default 1048576)** **_9P_RDMA_Backlog(uint16, range 1 to UINT16_MAX, default 10)** **_9P_RDMA_Inpool_size(uint16, range 1 to UINT16_MAX, default 64)** **_9P_RDMA_Outpool_Size(uint16, range 1 to UINT16_MAX, default 32)** See also ============================== :doc:`ganesha-config `\(8) :doc:`ganesha-log-config `\(8) :doc:`ganesha-core-config `\(8) :doc:`ganesha-export-config `\(8) nfs-ganesha-6.5/src/doc/man/ganesha-cache-config.rst000066400000000000000000000136501473756622300223550ustar00rootroot00000000000000=================================================================== ganesha-cache-config -- NFS Ganesha Cache Configuration File =================================================================== .. program:: ganesha-cache-config SYNOPSIS ========================================================== | /etc/ganesha/ganesha.conf DESCRIPTION ========================================================== NFS-Ganesha reads the configuration data from: | /etc/ganesha/ganesha.conf This file lists NFS-Ganesha Cache config options. MDCACHE {} -------------------------------------------------------------------------------- NParts (uint32, range 1 to 32633, default 7) Partitions in the MDCACHE tree. Cache_Size(uint32, range 1 to UINT32_MAX, default 32633) Per-partition hash table size. Use_Getattr_Directory_Invalidation(bool, default false) Use getattr for directory invalidation. Dir_Chunk(uint32, range 0 to UINT32_MAX, default 128) Size of per-directory dirent cache chunks, 0 means directory chunking is not enabled. Dir_Chunk should always be enabled. Most FSAL modules especially FSAL_RGW/FSAL_GLUSTER need it to make readdir work well. Detached_Mult(uint32, range 1 to UINT32_MAX, default 1) Max number of detached directory entries expressed as a multiple of the chunk size. Entries_HWMark(uint32, range 1 to UINT32_MAX, default 100000) The point at which object cache entries will start being reused. Entries_Release_Size(uint32, range 0 to UINT32_MAX, default 100) The number of entries attempted to release each time when the handle cache has exceeded the entries high water mark. Chunks_HWMark(uint32, range 1 to UINT32_MAX, default 1000) The point at which dirent cache chunks will start being reused. Chunks_LWMark(uint32, range 1 to UINT32_MAX, default 1000) The target for reaping dirent cache chunks to drain the cache. Entries_HWMark, Dir_Chunk, Chunks_HWMark, Chunks_LWMark all play together. While a dirent chunk is cached, the object cache entries that are part of that dirent chunk will be retained in the cache indefinitely. Note that this means that possibly Chunks_LWMark * Dir_Chunk entries will be retained. If this is larger than Entries_HWMark, then the object cache may end up remaining above Entries_HWMark for a significant time. Consider the average size of the directories and the number of directories desired to be maintained in the cache and set Chunks_LWMark appropriately. Note that the Chunks_LWMark defaults to the same value as Chunks_HWMark and these are set to 1/100 of Entries_HWMark suggesting an average directory size of 100. It may be desirable to set Chunks_LWMark less than Chunks_HWMark if it is desirable to allow large directory chunks to not immediately be re-used, but it is desirable to in the short term drain the dirent cache down to a smaller number. LRU_Run_Interval(uint32, range 1 to 24 * 3600, default 90) Base interval in seconds between runs of the LRU cleaner thread. Cache_FDs(bool, default true) If "Cache_FDs" is set to false, the reaper thread aggressively closes FDs , significantly reducing the number of open FDs. This will help to maintain a minimal number of open FDs. If "Cache_FDs" is set to true (default), FDs are cached, and the LRU reaper thread closes FDs only when the current open FD count reaches or exceeds the "fds_lowat" threshold. Note, this setting has no effect when Close_Fast is set to true. Close_Fast(bool, default false) Whether to close files immediately after opening files and using them for read/write/commit. FD_Limit_Percent(uint32, range 0 to 100, default 99) The percentage of the system-imposed maximum of file descriptors at which Ganesha will deny requests. FD_HWMark_Percent(uint32, range 0 to 100, default 90) The percentage of the system-imposed maximum of file descriptors above which Ganesha will make greater efforts at reaping. FD_LWMark_Percent(uint32, range 0 to 100, default 50) The percentage of the system-imposed maximum of file descriptors below which Ganesha will not reap file descriptors. Reaper_Work(uint32, range 1 to 2000, default 0) Roughly, the amount of work to do on each pass through the thread under normal conditions. (Ideally, a multiple of the number of lanes.) *This setting is deprecated. Please use Reaper_Work_Per_Lane* Reaper_Work_Per_Lane(uint32, range 1 to UINT32_MAX, default 50) This is the number of handles per lane to scan when performing LRU maintenance. This task is performed by the Reaper thread. Biggest_Window(uint32, range 1 to 100, default 40) The largest window (as a percentage of the system-imposed limit on FDs) of work that we will do in extremis. Required_Progress(uint32, range 1 to 50, default 5) Percentage of progress toward the high water mark required in in a pass through the thread when in extremis Futility_Count(uint32, range 1 to 50, default 8) Number of failures to approach the high watermark before we disable caching, when in extremis. Dirmap_HWMark(uint32, range 1 to UINT32_MAX, default 10000) The point at which dirmap entries are reused. This puts a practical limit on the number of simultaneous readdirs that may be in progress on an export for a whence-is-name FSAL (currently only FSAL_RGW) Files_Delegatable_Percent(int32, range 10 to 90, default 90) Total number of files ganesha can delegate to clients as a percent of Entries_HWMark Use_Cached_Owner_On_Owner_Override(bool, true) This option allows mdcache_test_access to use the cached owner value for an object in case of owner override, even if the cache entry is not up to date. This could potentially lead to the wrong result, but can significantly improve performance saving the need to update attributes on many read/write operations. See also ============================== :doc:`ganesha-config `\(8) nfs-ganesha-6.5/src/doc/man/ganesha-ceph-config.rst000066400000000000000000000055031473756622300222270ustar00rootroot00000000000000=================================================================== ganesha-ceph-config -- NFS Ganesha CEPH Configuration File =================================================================== .. program:: ganesha-ceph-config SYNOPSIS ========================================================== | /etc/ganesha/ceph.conf DESCRIPTION ========================================================== NFS-Ganesha install config example for CEPH FSAL: | /etc/ganesha/ceph.conf This file lists CEPH specific config options. EXPORT { FSAL {} } -------------------------------------------------------------------------------- Name(string, "Ceph") Name of FSAL should always be Ceph. Filesystem(string, no default) Ceph filesystem name string, for mounting an alternate filesystem within the cluster. The default is to mount the default filesystem in the cluster (usually, the first one created). User_Id(string, no default) cephx userid used to open the MDS session. This string is what gets appended to "client.". If not set, the ceph client libs will sort this out based on ceph configuration. Secret_Access_Key(string, no default) Key to use for the session (if any). If not set, then it uses the normal search path for cephx keyring files to find a key. sec_label_xattr(char, default "security.selinux xattr of the file") Enable NFSv4.2 security label attribute. Ganesha supports "Limited Server Mode" as detailed in RFC 7204. Note that not all FSALs support security labels. cmount_path(string, no default) If specified, the path within the ceph filesystem to mount this export on. It is allowed to be any complete path hierarchy between `/` and the EXPORT {path}. (i.e. if EXPORT { Path } parameter is `/foo/bar` then cmount_path could be `/`, `/foo` or `/foo/bar`). If this and the other EXPORT { FSAL {} } options are the same between multiple exports, those exports will share a single cephfs client. With the default, this effectively defaults to the same path as EXPORT { Path }. CEPH {} -------------------------------------------------------------------------------- Ceph_Conf(path, default "") Path to the ceph config file to inherit ceph configuration from. umask(mode, range 0 to 0777, default 0) client_oc(bool, default false) Enable or disable client_oc (object cache). This defaults to false because Ganesha runs better with it disabled. async(bool, default false) Enable ceph async operations (read and write). zerocopy(bool, default false) Enable ceph zero copy I/O. Zero copy and client_oc are incompatible. See also ============================== :doc:`ganesha-config `\(8) :doc:`ganesha-log-config `\(8) :doc:`ganesha-core-config `\(8) :doc:`ganesha-export-config `\(8) nfs-ganesha-6.5/src/doc/man/ganesha-config.rst000066400000000000000000000175671473756622300213270ustar00rootroot00000000000000=================================================================== ganesha-config -- NFS Ganesha Configuration File =================================================================== .. program:: ganesha-config SYNOPSIS ========================================================== | /etc/ganesha/ganesha.conf DESCRIPTION ========================================================== NFS-Ganesha obtains configuration data from the configuration file: /etc/ganesha/ganesha.conf The configuration file consists of following parts: Comments -------------------------------------------------------------------------------- Empty lines and lines starting with '#' are comments.:: # This whole line is a comment Protocol = TCP; # The rest of this line is a comment Blocks -------------------------------------------------------------------------------- Related options are grouped together into "blocks". A block is a name followed by parameters enclosed between "{" and "}". A block can contain other sub blocks as well.:: EXPORT { Export_ID = 1; FSAL { Name = VFS: } } NOTE: FSAL is a sub block. Refer to ``BLOCKS`` section for list of blocks and options. Options -------------------------------------------------------------------------------- Configuration options can be of following types. 1. **Numeric.** Numeric options can be defined in octal, decimal, or hexadecimal. The format follows ANSI C syntax. eg.:: mode = 0755; # This is octal 0755, 493 (decimal) Numeric values can also be negated or logical NOT'd. eg.:: anonymousuid = -2; # this is a negative mask = ~0xff; # Equivalent to 0xffffff00 (for 32 bit integers) 2. **Boolean.** Possible values are true, false, yes and no. 1 and 0 are not acceptable. 3. **List.** The option can contain a list of possible applicable values. Protocols = 3, 4, 9p; 4. **String.** String options such as export Path need not be enclosed in quotes but is recommended. If special characters are included, particularly non-ASCII characters, use of either single or double quotes may be required. Another set of strings that should be quoted are any strings that are a keyword or if spaces are part of the string. Including other config files -------------------------------------------------------------------------------- Additional files can be referenced in a configuration using '%include' or '%dir' or '%url' directives.:: %include %dir %url The included file is inserted into the configuration text in place of the %include or %dir or %url line. Sub-inclusions may be to any depth. Filenames or directory or URLs may optionally use '"':: %include base.conf %include "base.conf" %dir /etc/ganesha/ganesha_config/ %dir "/etc/ganesha/ganesha_config/" %url rados://mypool/mynamespace/myobject %url "rados://mypool/mynamespace/myobject" %url rados://mypool/myobject %url "rados://mypool/myobject" In the case of rados:// URLs, providing a two-component URL indicates that the default namespace should be used. Reloading Config -------------------------------------------------------------------------------- A config reload can be triggered by sending the ganesha.nfsd process a SIGHUP. Not all config options may be changed with reload, those that can will be documented in the individual sections. In general, currently dynamic config is supported for EXPORT and LOG options. BLOCKS ========================================================== NFS-Ganesha supports the following blocks: EXPORT {} -------------------------------------------------------------------------------- Along with its configuration options, the **EXPORT** block supports **FSAL** and **CLIENT** sub-blocks. See :doc:`ganesha-export-config `\(8) for usage of this block and its sub-blocks. EXPORT_DEFAULTS {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-export-config `\(8) for usage MDCACHE {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-cache-config `\(8) for usage NFS_CORE_PARAM {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-core-config `\(8) for usage NFS_IP_NAME {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-core-config `\(8) for usage NFS_KRB5 {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-core-config `\(8) for usage NFSv4 {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-core-config `\(8) for usage _9P {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-9p-config `\(8) for usage LOG {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-log-config `\(8) for usage 1.**LOG { FACILITY {} }** 2.**LOG { FORMAT {} }** RADOS_KV {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-core-config `\(8) for usage RADOS_URLS {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-core-config `\(8) for usage FSAL_LIST {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-core-config `\(8) for usage CEPH {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-ceph-config `\(8) for usage GLUSTER {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-gluster-config `\(8) for usage GPFS {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-gpfs-config `\(8) for usage PROXY_V4 {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-proxy-config `\(8) for usage PROXY_V3 {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-proxy-v3-config `\(8) for usage RGW {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-rgw-config `\(8) for usage VFS {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-vfs-config `\(8) for usage XFS {} -------------------------------------------------------------------------------- Refer to :doc:`ganesha-xfs-config `\(8) for usage EXAMPLE ========================================================== Along with "ganesha.conf", for each installed FSAL, a sample config file is added at: | /etc/ganesha See also ============================== :doc:`ganesha-log-config `\(8) :doc:`ganesha-rgw-config `\(8) :doc:`ganesha-vfs-config `\(8) :doc:`ganesha-lustre-config `\(8) :doc:`ganesha-xfs-config `\(8) :doc:`ganesha-gpfs-config `\(8) :doc:`ganesha-gluster-config `\(8) :doc:`ganesha-9p-config `\(8) :doc:`ganesha-proxy-config `\(8) :doc:`ganesha-proxy-v3-config `\(8) :doc:`ganesha-ceph-config `\(8) :doc:`ganesha-core-config `\(8) :doc:`ganesha-export-config `\(8) nfs-ganesha-6.5/src/doc/man/ganesha-core-config.rst000066400000000000000000000570701473756622300222460ustar00rootroot00000000000000=================================================================== ganesha-core-config -- NFS Ganesha Core Configuration File =================================================================== .. program:: ganesha-core-config SYNOPSIS ========================================================== | /etc/ganesha/ganesha.conf DESCRIPTION ========================================================== NFS-Ganesha reads the configuration data from: | /etc/ganesha/ganesha.conf This file lists NFS related core config options. NFS_CORE_PARAM {} -------------------------------------------------------------------------------- Core parameters: HAProxy_Hosts (host list, empty) This is the list of hosts that can serve as HAProxy load balancers/proxies that will use the HAProxy protocol to indicate to Ganesha the actual end client IP address. This parameter may be repeated to extend the list. Host list entries can take on one of the following forms: \* Match any host @name Netgroup name x.x.x.x/y IPv4 network address, IPv6 addresses are also allowed but the format is too complex to show here wildcarded If the string contains at least one ? or * character (and is not simply "*"), the string is used to pattern match host names. Note that [] may also be used, but the pattern MUST have at least one ? or * hostname Match a single host (match is by IP address, all addresses returned by getaddrinfo will match, the getaddrinfo call is made at config parsing time) IP address Match a single host NFS_Port (uint16, range 0 to UINT16_MAX, default 2049) Port number used by NFS Protocol. MNT_Port (uint16, range 0 to UINT16_MAX, default 0) Port number used by MNT Protocol. NLM_Port (uint16, range 0 to UINT16_MAX, default 0) Port number used by NLM Protocol. Rquota_Port (uint16, range 0 to UINT16_MAX, default 875) Port number used by Rquota Protocol. NFS_RDMA_Port (uint16, range 0 to UINT16_MAX, default 20049) Port number used by NFS Over RDMA Protocol. NFS_RDMA_Protocol_Versions(enum list, default [4.0]) Possible values: (NONE, 3, v3, NFS3, NFSv3, 4.0, v4.0, NFS4.0, NFSv4.0, 4.1, ALL) Supported NFS Version for NFS Over RDMA. By default, NFSv4.0 is enabled. Monitoring_Port (uint16, range 0 to UINT16_MAX, default 9587) Port number used to export monitoring metrics. Enable_Dynamic_Metrics (bool, default true) Whether to create metrics labels on the fly based on client-ip, export name, etc. Provides more debugging information, but significantly reduces performance. Enabled by default for backward compatibility. Bind_addr(IPv4 or IPv6 addr, default 0.0.0.0) The address to which to bind for our listening port. NFS_Program(uint32, range 1 to INT32_MAX, default 100003) RPC program number for NFS. MNT_Program(uint32, range 1 to INT32_MAX, default 100005) RPC program number for MNT. NLM_Program(uint32, range 1 to INT32_MAX, default 100021) RPC program number for NLM. Drop_IO_Errors(bool, default false) For NFSv3, whether to drop rather than reply to requests yielding I/O errors. It results in client retry. Drop_Inval_Errors(bool, default false) For NFSv3, whether to drop rather than reply to requests yielding invalid argument errors. False by default and settable with Drop_Inval_Errors. Drop_Delay_Errors(bool, default false) For NFSv3, whether to drop rather than reply to requests yielding delay errors. False by default and settable with Drop_Delay_Errors. Plugins_Dir(path, default "/usr/lib64/ganesha") Path to the directory containing server specific modules Enable_NFS_Stats(bool, default true) Whether to collect performance statistics. By default the performance counting is enabled. Enable_NFS_Stats can be enabled or disabled dynamically via ganesha_stats. Enable_Fast_Stats(bool, default false) Whether to use fast stats. If enabled this will skip statistics counters collection for per client and per export. Enable_FSAL_Stats(bool, default false) Whether to count and collect FSAL specific performance statistics. Enable_FSAL_Stats can be enabled or disabled dynamically via ganesha_stats Enable_FULLV3_Stats(bool, default false) Whether to count and collect "detailed statistics" for NFSv3. Enable_FULLV3_Stats can be enabled or disabled dynamically via ganesha_stats. Enable_FULLV4_Stats(bool, default false) Whether to count and collect "detailed statistics" for NFSv4. Enable_FULLV4_Stats can be enabled or disabled dynamically via ganesha_stats. Enable_CLNT_AllOps_Stats(bool, default false) Whether to count and collect statistics for all NFS operations requested by NFS clients. Enable_CLNT_AllOps_Stats can be enabled or disabled dynamically via ganesha_stats. Short_File_Handle(bool, default false) Whether to use short NFS file handle to accommodate VMware NFS client. Enable this if you have a VMware NFSv3 client. VMware NFSv3 client has a max limit of 56 byte file handles. Manage_Gids_Expiration(int64, range 0 to 7*24*60*60, default 30*60) How long the server will trust information it got by calling getgroups() when "Manage_Gids = TRUE" is used in a export entry. heartbeat_freq(uint32, range 0 to 5000 default 1000) Frequency of dbus health heartbeat in ms. Enable_NLM(bool, default true) Whether to support the Network Lock Manager protocol. Disable_NLM_SHARE(bool, default false) This option allows disabling support for the NLM4PROC_SHARE and NLM4PROC_UNSHARE RPC procedures that implement share reservations for NFSv3 via NLM. With this set to true, these procedures will fail. Blocked_Lock_Poller_Interval(int64, range 0 to 180, default 10) Polling interval for blocked lock polling thread Protocols(enum list, default [3,4,9P]) Possible values: 3, 4, NFS3, NFS4, V3, V4, NFSv3, NFSv4, 9P The protocols that Ganesha will listen for. This is a hard limit, as this list determines which sockets are opened. This list can be restricted per export, but cannot be expanded. NSM_Use_Caller_Name(bool, default false) Whether to use the supplied name rather than the IP address in NSM operations. Clustered(bool, default true) Whether this Ganesha is part of a cluster of Ganeshas. Its vendor specific option. fsid_device(bool, default false) Whether to use device major/minor for fsid. resolve_fs_retries(uint32_t, range 1 to 1000, default 10) How many times to attempt stat while resolving POSIX filesystems for exports. resolve_fs_delay(uint32_t, range 1 to 1000, default 100) How long to delay between stat attempts while resolving POSIX filesystems for exports. mount_path_pseudo(bool, default false) Whether to use Pseudo (true) or Path (false) for NFS v3 and 9P mounts. This option defaults to false for backward compatibility, however, for new setups, it's strongly recommended to be set true since it then means the same server path for the mount is used for both v3 and v4.x. Note that as an export related option, it seems very desirable to be able to change this on config reload, unfortunately, at the moment it is NOT changeable on config reload. A restart is necessary to change this. Dbus_Name_Prefix DBus name prefix. Required if one wants to run multiple ganesha instances on single host. The prefix should be different for every ganesha instance. If this is set, the dbus name will be .org.ganesha.nfsd Enable_UDP(enum, values [False, True, Mount], default True) Whether to create UDP listeners for Mount, NFS, NLM, RQUOTA, and register them with portmapper. Set to false, e.g., to run as non-root. Set to Mount to enable only Mount UDP listener. Max_Uid_To_Group_Reqs(uint32, range 0 to INT32_MAX, default 0) Maximum number of concurrent uid2grp requests that can be made by ganesha. In environments with a slow Directory Service Provider, where users are part of large number of groups, and Manage_Gids is set to True, uid2grp queries made by ganesha can fail if a large number of them are made in parallel. This option throttles the number of concurrent uid2grp queries that ganesha makes. Enable_V3fh_Validation_For_V4(bool, default false) Set true to enforce when v3 file handle used for v4 Readdir_Res_Size(uint32, range 4096 to 64*1024*1024, default 32*1024) Response size of readdir request. Suggested values are 4096,8192,16384 and 32768. Recommended 16384(16K) if readdir(ls command) operation performed on directory which has more files. Readdir_Max_Count(uint32, range 32 to 1024*1024, default 1024*1024) Maximum number of directory entries returned for a readdir request. Suggested values are 4096,8192,16384 and 32768. Recommended 16384(16K) if readdir(ls command) operation performed on directory which has more files. Getattrs_In_Complete_Read(bool, default true) Whether to call extra getattrs after read, in order to check file size and validate the EOF flag correctness. Needed for ESXi client compatibility when FSAL's don't set it correctly. Enable_malloc_trim(bool, default false) Set true to enable dynamic malloc_trim support. Malloc_trim_MinThreshold(uint32, range 1 to INT32_MAX, default 15*1024) Minimum threshold value to call malloc_trim. The malloc_trim will be called once memory allocation exceeds minimum value. Size in MB's. Note, this setting has no effect when Enable_malloc_trim is set to false. enable_rpc_cred_fallback(bool, default false) if Manage_Gids=True and group resolution fails, then use gid data from rpc request. Enable_Connection_Manager(bool, default false) When enabled, a client (from the same source IP address), is allowed to be connected to a single Ganesha server at a specific point in time. See details in connection_manager.h Connection_Manager_Timeout_sec(uint32, range 0 to UINT32_MAX, default 2*60) Timeout for waiting until client is fully disconnected from other Ganesha servers. Unique_Server_Id(uint32, range 0 to UINT32_MAX, default 0) Unique value to the ganesha node, to diffrintiate it for the rest of the node. will be used as prefix for the Client id, to make sure it is unique between ganesha nodes and file write verifier. if 0 is supplied server boot epoch time in seconds will be used Allow_Set_Io_Flusher_Fail(bool, default false) In linux, we need to set PR_SET_IO_FLUSHER to avoid a potential deadlock when mounting ganesha locally in a system that nears out memory. This is not possible due to lacking permissions in some environments (specifically unprivileged containers). If you are running in an unprivileged environment and you are sure your use case doesn't require setting PR_SET_IO_FLUSHER, you can enable this option and Ganesha will not fail on startup if it can't set it. For more info, see: https://git.kernel.org/torvalds/p/8d19f1c8e1937baf74e1962aae9f90fa3aeab463 Parameters controlling TCP DRC behavior: ---------------------------------------- DRC_Disabled(bool, default false) Whether to disable the DRC entirely. DRC_Recycle_Hiwat(uint32, range 1 to 1000000, default 1024) High water mark for number of DRCs in recycle queue. TCP_Npart(uint32, range 1 to 20, default 1) Number of partitions in the tree for the TCP DRC. DRC_TCP_Size(uint32, range 1 to 32767, default 1024) Maximum number of requests in a transport's DRC. DRC_TCP_Cachesz(uint32, range 1 to 255, default 127) Number of entries in the O(1) front-end cache to a TCP Duplicate Request Cache. DRC_TCP_Hiwat(uint32, range 1 to 256, default 64) High water mark for a TCP connection's DRC at which to start retiring entries if we can. DRC_TCP_Recycle_Npart(uint32, range 1 to 20, default 7) Number of partitions in the recycle tree that holds per-connection DRCs so they can be used on reconnection (or recycled.) DRC_TCP_Recycle_Expire_S(uint32, range 0 to 60*60, default 600) How long to wait (in seconds) before freeing the DRC of a disconnected client. DRC_TCP_Checksum(bool, default true) Whether to use a checksum to match requests as well as the XID Parameters controlling UDP DRC behavior: ---------------------------------------- DRC_UDP_Npart(uint32, range 1 to 100, default 7) Number of partitions in the tree for the UDP DRC. DRC_UDP_Size(uint32, range 512, to 32768, default 32768) Maximum number of requests in the UDP DRC. DRC_UDP_Cachesz(uint32, range 1 to 2047, default 599) Number of entries in the O(1) front-end cache to the UDP Duplicate Request Cache. DRC_UDP_Hiwat(uint32, range 1 to 32768, default 16384) High water mark for the UDP DRC at which to start retiring entries if we can DRC_UDP_Checksum(bool, default true) Whether to use a checksum to match requests as well as the XID. Parameters affecting the relation with TIRPC: -------------------------------------------------------------------------------- RPC_Max_Connections(uint32, range 1 to 1000000, default 1024) Maximum number of connections for TIRPC. RPC_Idle_Timeout_S(uint32, range 0 to 60*60, default 300) Idle timeout (seconds). Default to 300 seconds. MaxRPCSendBufferSize(uint32, range 1 to 1048576*9, default 1048576) Size of RPC send buffer. MaxRPCRecvBufferSize(uint32, range 1 to 1048576*9, default 1048576) Size of RPC receive buffer. RPC_Max_RDMA_Connections(uint32, range 1 to 1024, default 64) Maximum number of RDMA connections for TIRPC. Limiting the number of RDMA connections to 64 by default to avoid memory exhaustion. MaxRPCRdmaCredits(uint32, range 1 to 4096, default 64) Max credits of RDMA channel, representing the max number of outstanding NFS operations on the channel. RPC_Ioq_ThrdMax(uint32, range 1 to 1024*128 default 200) TIRPC ioq max simultaneous io threads RPC_GSS_Npart(uint32, range 1 to 1021, default 13) Partitions in GSS ctx cache table RPC_GSS_Max_Ctx(uint32, range 1 to 1048576, default 16384) Max GSS contexts in cache. Default 16k RPC_GSS_Max_Gc(uint32, range 1 to 1048576, default 200) Max entries to expire in one idle check Parameters for TCP: -------------------------------------------------------------------------------- Enable_TCP_keepalive(bool, default true) Whether tcp sockets should use SO_KEEPALIVE TCP_KEEPCNT(UINT32, range 0 to 255, default 0 -> use system defaults) Maximum number of TCP probes before dropping the connection TCP_KEEPIDLE(UINT32, range 0 to 65535, default 0 -> use system defaults) Idle time before TCP starts to send keepalive probes TCP_KEEPINTVL(INT32, range 0 to 65535, default 0 -> use system defaults) Time between each keepalive probe NFS_IP_NAME {} -------------------------------------------------------------------------------- Index_Size(uint32, range 1 to 51, default 17) Configuration for hash table for NFS Name/IP map. Expiration_Time(uint32, range 1 to 60*60*24, default 3600) Expiration time for ip-name mappings. NFS_KRB5 {} -------------------------------------------------------------------------------- **PrincipalName(string, default "nfs")** KeytabPath(path, default "") Kerberos keytab. CCacheDir(path, default "/var/run/ganesha") The ganesha credential cache. Active_krb5(bool, default false) Whether to activate Kerberos 5. Defaults to true (if Kerberos support is compiled in) DIRECTORY_SERVICES {} -------------------------------------------------------------------------------- DomainName(string, default "localdomain") Domain to use if we aren't using the nfsidmap. Idmapping_Active(bool, default true) Whether to enable idmapping Idmapped_User_Time_Validity(int64, range -1 to INT64_MAX, default -1) Cache validity in seconds for idmapped-user entries. The default value is -1, which indicates fallback to older config -- "NFS_CORE_PARAM.Manage_Gids_Expiration", for backward compatibility. Idmapped_Group_Time_Validity(int64, range -1 to INT64_MAX, default -1) Cache validity in seconds for idmapped-group entries. The default value is -1, which indicates fallback to older config -- "NFS_CORE_PARAM.Manage_Gids_Expiration", for backward compatibility. Root_Kerberos_Principal(enum list, values [none,nfs,root,host,all], default [all]) List of primary users/service name parts of the principal that should be assigned root privilege. Setting "all" will assign root privilege to all the options. Setting "none" in the list will override the other options in the list. Cache_Users_Max_Count(uint32, range 0 to INT32_MAX, default INT32_MAX) Max number of cached idmapped users Cache_Groups_Max_Count(uint32, range 0 to INT32_MAX, default INT32_MAX) Max number of cached idmapped groups Cache_User_Groups_Max_Count(uint32, range 0 to INT32_MAX, default INT32_MAX) Max number of cached user-groups entries Negative_Cache_Time_Validity(int64, range 0 to INT64_MAX, default 300) Cache validity in seconds for negative entries Negative_Cache_Users_Max_Count(uint32, range 0 to INT32_MAX, default 50000) Max number of negative cache users (the ones that failed idmapping) Negative_Cache_Groups_Max_Count(uint32, range 0 to INT32_MAX, default 50000) Max number of negative cache groups (the ones that failed idmapping) Cache_Reaping_Interval(int64, range 0 to 3650*86400, default 0) Cache reaping interval in seconds for idmapped cached entites. Its default value is set to 0, which basically means that the cache-reaping is disabled. Pwutils_Use_Fully_Qualified_Names(bool, default false) Whether to use fully qualified names for idmapping with pw-utils NFSv4 {} -------------------------------------------------------------------------------- Sticky_Grace(bool, default false) Whether to disable the sticky grace. Graceless(bool, default false) Whether to disable the NFSv4 grace period. Lease_Lifetime(uint32, range 1 to 120, default 60) The NFSv4 lease lifetime. Grace_Period(uint32, range 0 to 180, default 90) The NFS grace period. DomainName(string, default NULL) This config param is deprecated. Use `DomainName` in `DIRECTORY_SERVICES` config section. IdmapConf(path, default "/etc/idmapd.conf") Path to the idmap configuration file. UseGetpwnam(bool, default false if using idmap, true otherwise) Whether to use local password (PAM, on Linux) rather than nfsidmap. Allow_Numeric_Owners(bool, default true) Whether to allow bare numeric IDs in NFSv4 owner and group identifiers. Only_Numeric_Owners(bool, default false) Whether to ONLY use bare numeric IDs in NFSv4 owner and group identifiers. Delegations(bool, default false) Whether to allow delegations. Deleg_Recall_Retry_Delay(uint32_t, range 0 to 10, default 1) Delay after which server will retry a recall in case of failures pnfs_mds(bool, default false) Whether this a pNFS MDS server. For FSAL Gluster, if this is true, set pnfs_mds in gluster block as well. pnfs_ds(bool, default false) Whether this a pNFS DS server. RecoveryBackend(enum, default "fs") Use different backend for client info: - fs : filesystem - fs_ng: filesystem (better resiliency) - rados_kv : rados key-value - rados_ng : rados key-value (better resiliency) - rados_cluster: clustered rados backend (active/active) RecoveryRoot(path, default "/var/lib/nfs/ganesha") Specify the root recovery directory for fs or fs_ng recovery backends. RecoveryDir(path, default "v4recov") Specify the recovery directory name for fs or fs_ng recovery backends. RecoveryOldDir(path, "v4old") Specify the recovery old directory name for fs recovery backend. Minor_Versions(enum list, values [0, 1, 2], default [0, 1, 2]) List of supported NFSV4 minor version numbers. Slot_Table_Size(uint32, range 1 to 1024, default 64) Size of the NFSv4.1 slot table Enforce_UTF8_Validation(bool, default false) Set true to enforce valid UTF-8 for path components and compound tags Max_Client_Ids(uint32, range 0 to UINT32_MAX, default 0) Specify a max limit on number of NFS4 ClientIDs supported by the server. With filesystem recovery backend, each ClientID translates to one directory. With certain workloads, this could result in reaching inode limits of the filesystem that /var/lib/nfs/ganesha is part of. The above limit can be used as a guardrail to prevent getting into this situation. Server_Scope(string, default "") Specify the value which is common for all cluster nodes. For e.g., Name of the cluster or cluster-id. Server_Owner(string, default "") Connections to servers with the same server owner can be shared by the client. This is advertised to the client on EXCHANGE_ID. Max_Open_States_Per_Client(uint32, range 0 to UINT32_MAX, default 0) Specify the maximum number of files that could be opened by a client. One misbehaving client could potentially open multiple files and exhaust the open FD limit allowed by ganesha's cgroup. Beyond this limit, client gets denied if it tries to open too many files. To disable set to ZERO. Expired_Client_Threshold(uint32, range 0 to 256, default 16) Specify the threshold of number of expired clients to be kept in memory post lease period, unless the number of unresponsive clients go over this limit. Ganesha keeps track of all expired clients in LRU fashion and picks the oldest expired client when the number of clients exceeds the max limit. This allows Ganesha to retain the open & lock state and there by helping certain client workloads like MLPerf to run smoothly, even after a network partition. Max_Open_Files_For_Expired_Client(uint32, range 0 to UINT32_MAX, default 4000) Specify the maximum number of open files that an unresponsive client could have, beyond which Ganesha won't keep client intact in memory and expire it. Comes to play if the config Expired_Client_Threshold is not set to ZERO. Max_Alive_Time_For_Expired_Client(uint64, range 0 to UINT64_MAX, default 86400) Specify the max amount of time till which to keep the unresponsive client in memory, beyond which Ganesha would start reaping and expire it off. Comes to play if the config Expired_Client_Threshold is not set to ZERO. RADOS_KV {} -------------------------------------------------------------------------------- ceph_conf(string, no default) Connection to ceph cluster, should be file path for ceph configuration. userid(path, no default) User ID to ceph cluster. namespace(string, default NULL) RADOS Namespace in which to store objects pool(string, default "nfs-ganesha") Pool for client info. grace_oid(string, default "grace") Name of the object containing the rados_cluster grace DB nodeid(string, default result of gethostname()) Unique node identifier within rados_cluster RADOS_URLS {} -------------------------------------------------------------------------------- ceph_conf(string, no default) Connection to ceph cluster, should be file path for ceph configuration. userid(path, no default) User ID to ceph cluster. watch_url(url, no default) rados:// URL to watch for notifications of config changes. When a notification is received, the server will issue a SIGHUP to itself. FSAL_LIST {} -------------------------------------------------------------------------------- name(string, no default) This allows listing of the FSALs that will be used. This assures that the config blocks for those FSALs will not result in an error if no exports are configured using that FSAL. This parameter takes a list of FSAL names and the parameter may be listed multiple times. nfs-ganesha-6.5/src/doc/man/ganesha-export-config.rst000066400000000000000000000527631473756622300226430ustar00rootroot00000000000000=================================================================== ganesha-export-config -- NFS Ganesha Export Configuration File =================================================================== .. program:: ganesha-export-config SYNOPSIS ========================================================== /etc/ganesha/ganesha.conf DESCRIPTION ========================================================== NFS-Ganesha obtains configuration data from the configuration file: /etc/ganesha/ganesha.conf This file lists NFS-Ganesha Export block config options. EXPORT PERMISSIONS -------------------------------------------------------------------------------- These options are all "export permissions" options, and are available in EXPORT_DEFAULTS {}, EXPORT {} and CLIENT {} blocks. These options will all be dynamically updateable. Access_Type(enum, default None) Possible values: None, RW, RO, MDONLY, MDONLY_RO Protocols(enum list, default none) Possible values: 3, 4, NFS3, NFS4, V3, V4, NFSv3, NFSv4, 9P Transports(enum list, values [UDP, TCP, RDMA], default [UDP, TCP]) Anonymous_uid(anonid, range INT32_MIN to UINT32_MAX, default -2) Anonymous_gid(anonid, range INT32_MIN to UINT32_MAX, default -2) SecType(enum list, default [none, sys]) Possible values: none, sys, krb5, krb5i, krb5p PrivilegedPort(bool, default false) Manage_Gids(bool, default false) Squash(enum, default root_sqaush) Possible values: root, root_squash, rootsquash, rootid, root_id_squash, rootidsquash, all, all_squash, allsquash, all_anomnymous, allanonymous, no_root_squash, none, noidsquash Each line of defaults above are synonyms **NFS_Commit(bool, default false)** Delegations(enum, default None) Possible values: None, read, write, readwrite, r, w, rw CLIENT {} -------------------------------------------------------------------------------- CLIENT blocks are used in EXPORT_DEFAULTS {}, EXPORT {}, and PSEUDOFS {} blocks. Each CLIENT block provides a list of clients and the permissions granted those clients. These blocks form an ordered "access control list" for the export. If no client block matches for a particular client, then the permissions in the EXPORT {} block will be used (and if any permissions are not set between the CLIENT and the EXPORT, then the permissions set in EXPORT_DEFAULTS will be used and if still not set, then the code default will be used. If an EXPORT does not have any CLIENT blocks then the list of CLIENT blocks in EXPORT_DEFAULTS (if any) will be used. No matter the source of the CLIENT blocks, if the client matches none, then the permissions in the EXPORT will be applied next. To override a restrictive default client list with "everyone" a non-empty client list would have to be specified. Clients = "*" would do the trick. Even when a CLIENT block matches a client, if a particular export permission is not explicit in that CLIENT block, the permission specified in the EXPORT block will be used, or if not specified there, from the EXPORT_DEFAULTS block, and if not specified there, the permission will default to the default code value noted in the permission option descriptions above. Note that when the CLIENT blocks are processed on config reload, a new client access list is constructed and atomically swapped in. This allows adding, removing, and re-arranging clients as well as changing the access for any give client. CLIENT blocks use all the EXPORT PERMISSIONS options (except PSEUDOFS CLIENT blocks which are limited to the same options the PSEUDOFS block is allowed) plus: Clients(client list, empty) Client list entries can take on one of the following forms. This parameter may be repeated to extend the list. \* Match any client @name Netgroup name x.x.x.x/y IPv4 network address, IPv6 addresses are also allowed but the format is too complex to show here wildcarded If the string contains at least one ? or * character (and is not simply "*"), the string is used to pattern match host names. Note that [] may also be used, but the pattern MUST have at least one ? or * hostname Match a single client (match is by IP address, all addresses returned by getaddrinfo will match, the getaddrinfo call is made at config parsing time) IP address Match a single client EXPORT_DEFAULTS {} -------------------------------------------------------------------------------- All the EXPORT PERMISSIONS options plus: **Attr_Expiration_Time(int32, range -1 to INT32_MAX, default 60)** CLIENT (optional) See the ``CLIENT {}`` block description. There may be any number of these. EXPORT_DEFAULTS { CLIENT {} } -------------------------------------------------------------------------------- See the ``CLIENT {}`` block description. EXPORT {} -------------------------------------------------------------------------------- All options below are dynamically changeable with config update unless specified below. This block may be repeated to define multiple exports. All the EXPORT_PERMISSIONS plus: Export_id (required): An identifier for the export, must be unique and between 0 and 65535. If Export_Id 0 is specified, Pseudo must be the root path (/). Export_id is not dynamic per se, changing it essentially removes the old export and introduces a new export. Path (required) The directory in the exported file system this export is rooted on (may be ignored for some FSALs). It need not be unique if Pseudo and/or Tag are specified. Note that if it is not unique, and the core option mount_path_pseudo is not set true, a v3 mount using the path will ONLY be able to access the first export configured. To access other exports the Tag option would need to be used. This option is NOT dynamically updateable since it fundamentally changes the export. To change the path exported, export_id should be changed also. Pseudo (required v4) This option specifies the position in the Pseudo Filesystem this export occupies if this is an NFS v4 export. It must be unique. By using different Pseudo options, the same Path may be exported multiple times. This option is used to place the export within the NFS v4 Pseudo Filesystem. This creates a single name space for NFS v4. Clients may mount the root of the Pseudo Filesystem and navigate to exports. Note that the Path and Tag options are not at all visible to NFS v4 clients. Export id 0 is automatically created to provide the root and any directories necessary to navigate to exports if there is no other export specified with Pseudo = /;. Note that if an export is specified with Pseudo = /;, it need not be export id 0. Specifying such an export with FSAL { name = PSEUDO; } may be used to create a Pseudo Filesystem with specific options. Such an export may also use other FSALs (though directories to reach exports will ONLY be automatically created on FSAL PSEUDO exports). This option is dynamically changeable and changing it will move the export within the pseudo filesystem. This may be disruptive to clients. Note that if the mount_path_pseudo NFS_CORE_PARAM option is true, the NFSv3 mount path will also change (that should not be disruptive to clients that have the export mounted). Tag (no default) This option allows an alternative access for NFS v3 mounts. The option MUST not have a leading /. Clients may not mount subdirectories (i.e. if Tag = foo, the client may not mount foo/baz). By using different Tag options, the same Path may be exported multiple times. This option is not dynamically updatable. Filesystem_id(fsid, format is uint64.uint64, default unused) This option allows overriding the filesystem ID provided by the underlying filesystem. Use of this option is discouraged, It will not work if VFS or GPFS is exporting a filesystem that has other filesystems mounted on sub-directories and exported with the same export. This option is not dynamically updateable. Read_Access_Check_Policy(enum, values [pre, post, all], default pre) Whether to run permission check for read before sending the read to the FSAL, after getting the read response from the FSAL, or both before and after. This allows to optimize performance for failure flow by always checking access before sending the read, or to optimize performance for success path by storing access check result in the FSAL cache during the read and perform the access check after the read (requires the FSAL implementation to support it, so should only be used with supported FSALs). It also allow to optimize for security by running permission check both before and after. MaxRead (64*1024*1024) The maximum read size on this export MaxWrite (64*1024*1024) The maximum write size on this export PrefRead (64*1024*1024) The preferred read size on this export. Note that some older nfs client (e.g. libnfs 1.x) would not handle well for large preferred read size. If so, please try to decrease this size (usually less than 1M is suitable for older nfs client). PrefWrite (64*1024*1024) The preferred write size on this export. Note that some older nfs client (e.g. libnfs 1.x) would not handle well for large preferred write size. If so, please try to decrease this size (usually less than 1M is suitable for older nfs client). PrefReaddir (16384) The preferred readdir size on this export MaxOffsetWrite (INT64_MAX) Maximum file offset that may be written Range is 512 to UINT64_MAX MaxOffsetRead (INT64_MAX) Maximum file offset that may be read Range is 512 to UINT64_MAX DisableReaddirPlus(bool, default false) Trust_Readdir_Negative_Cache(bool, default false) The following options may have limits on dynamic effect UseCookieVerifier(bool, default true) Updating UseCookieVerifier while a readdir is in progress may result in unexpected behavior. Disable_ACL(bool, default false) Disable_ACL is processed at create_export time currently which makes it effectively a static option. Security_Label(bool, default false) Attr_Expiration_Time(int32, range -1 to INT32_MAX, default 60) Attr_Expiration_Time is evaluated when an MDCACHE entry is created, so the dynamic effect of this option may be constrained to new entries. CLIENT (optional) See the ``CLIENT {}`` block description. There may be any number of these. FSAL (required) See the ``EXPORT { FSAL {} }`` block. The FSAL for an export can not be changed dynamically. In order to change the FSAL, a new export must be created. At this time, no FSAL actually supports any updatable options. EXPORT { CLIENT {} } -------------------------------------------------------------------------------- See the ``CLIENT {}`` block description. EXPORT { FSAL {} } -------------------------------------------------------------------------------- NFS-Ganesha supports the following FSALs: **Ceph** **Gluster** **GPFS** **PROXY_V3** **PROXY_V4** **RGW** **VFS** **XFS** **LUSTRE** **LIzardFS** **KVSFS** Refer to individual FSAL config file for list of config options. The FSAL blocks generally are less updatable .. FSAL PNFS Stripe_Unit(uint32, range 1024 to 1024*1024, default 8192) pnfs_enabled(bool, default false) FSAL_NULL: EXPORT { FSAL { FSAL {} } } describes the stacked FSAL's parameters PSEUDOFS {} -------------------------------------------------------------------------------- This block allows specifying some options for the pseudofs root export. It is very similar to an EXPORT block, except only the following options may be specified. CLIENT blocks may be used just like an EXPORT block, however, they are also limited to the same options. This is basically a shortcut rather than having to fill out options in an EXPORT block. Note that Path, Pseudo, and Export_Id are not included as those values will be fixed ("/" for the paths, and 0 for Export_Id). Other options that don't make sense for the pseudofs root are also not allowed. An empty PSEUDOFS {} block will produce the same default pseudofs root export as generated if no pseudofs root export is otherwise specified. This block is most useful to override a restrictive CLIENT list in EXPORT_DEFAULTS {}. These options will all be dynamically updateable. Access_Type(enum, default MDONLY_RO) Possible values: None, MDONLY_RO Transports(enum list, values [UDP, TCP, RDMA], default [TCP]) SecType(enum list, default [none, sys, krb5, krb5i, krb5p]) Possible values: none, sys, krb5, krb5i, krb5p PrivilegedPort(bool, default false) Export_id(uint16, range 0 to UINT16_MAX, default 0) An identifier for the export, must be unique and between 0 and 65535. Export_id is not dynamic per se, changing it essentially removes the old export and introduces a new export. Filesystem_id(fsid, format is uint64.uint64, default 152.152) Unlike standard exports, there is no underlying filesystem to get an ID from, so this option is important, however the default value may be used so it need not be specified. This option is not dynamically updateable. DisableReaddirPlus(bool, default false) Trust_Readdir_Negative_Cache(bool, default false) The following options may have limits on dynamic effect UseCookieVerifier(bool, default true) Updating UseCookieVerifier while a readdir is in progress may result in unexpected behavior. PSEUDOFS { CLIENT {} } -------------------------------------------------------------------------------- See the ``CLIENT {}`` block description but note that beyond the Clients options that works as described, the other options available are as for the PSEUDOFS {} block. If it is desired to override a restrictive CLIENT list in EXPORT_DEFAULTS, the following PSEUDOFS could be defined that will give all clients the default access to the pseudofs root that otherwise would have been granted if EXPORT_DEFAULTS was not used. PSEUDOFS { CLIENT { Clients = *; } } DISCUSSION ========================================================== The EXPORT blocks define the file namespaces that are served by NFS-Ganesha. In best practice, each underlying filesystem has a single EXPORT defining how that filesystem is to be shared, however, in some cases, it is desirable to sub-divide a filesystem into multiple exports. The danger when this is done is that rogue clients may be able to spoof file handles and access portions of the filesystem not intended to be accessible to that client. Some FSALs (currently FSAL_VFS, FSAL_GPFS, FSAL_XFS, and FSAL_LUSTRE) are built to support nested filesystems, for example: /export/fs1 /export/fs1/some/path/fs2 In this case, it is possible to create a single export that exports both filesystems. There is a lot of complexity of what can be done there. In discussions of filesystems, btrfs filesystems exported by FSAL_VFS may have subvolumes. Starting in NFS-Ganesha V4.0 FSAL_VFS treats these as separate filesystems that are integrated with all the richness of FSAL_VFS exports. Another significant FSAL from an export point of view is FSAL_PSEUDO. This is used to build glue exports to build the unified NFSv4 name space. This name space may also be used by NFSv3 by setting the NFS_CORE_PARAM option: mount_path_pseudo = TRUE; If no FSAL_PSEUDO export is explicitly defined, and there is no EXPORT with: Pseudo = "/"; NFS-Ganesha will build a FSAL_PSEUDO EXPORT with this Pseudo Path using Export_Id = 0. This automatic EXPORT may be replaced with an explicit EXPORT which need not have Export_Id = 0, it just must have Pseudo = "/" and Protocols = 4. In building the Pseudo Filesystem, there is a subtle gotcha. Since NFSv4 clients actually mount the root of the Pseudo Filesystem and then use LOOKUP to traverse into the actual directory the sysadmin has mounted from the client, any EXPORTs from "/" to the desired EXPORT MUST have Protocols = 4 specified either in EXPORT_DEFAULTS {}, EXPORT {}, or EXPORT { CLIENT {} }. This is to assure that the client is allowed to traverse each EXPORT. If Mount_Path_Pseudo = TRUE is being used and an export is desired to be NFSv3 only, Protocols = 3 MUST be specified in the EXPORT {} block. If Protocols is not specified in the EXPORT {} block and is only specified in an EXPORT { CLIENT {} } block, then that export will still be mounted in the Pseudo Filesystem but might not be traversable. Thus if the following two filesystems are exported: /export/fs1 /export/fs1/some/path/fs2 And the EXPORTs look something like this: EXPORT { Export_Id = 1; Path = /export/fs1; Pseudo = /export/fs1; FSAL { Name = VFS; } CLIENT { Clients="*"; Protocols=3; } } EXPORT { Export_Id = 1; Path = /export/fs1/some/path/fs2; Pseudo = /export/fs1/some/path/fs2; FSAL { Name = VFS; } CLIENT { Clients="*"; Protocols=3,4; } } NFSv4 clients will not be able to access /export/fs1/some/path/fs2. The correct way to accomplish this is: EXPORT { Export_Id = 1; Path = /export/fs1; Pseudo = /export/fs1; Protocols=3; FSAL { Name = VFS; } } Note that an EXPORT { CLIENT {} } block is not necessary if the default export permissions are workable. Note that in order for an EXPORT to be usable with NSFv4 it MUST either have Protocols = 4 specified in the EXPORT block, or the EXPORT block must not have the Protocols option at all such that it defaults to 3,4,9P. Note though that if it is not set and EXPORT_DEFAULTS just has Protocols = 3; then even though the export is mounted in the Pseudo Filesystem, it will not be accessible and the gotcha discussed above may be in play. CONFIGURATION RELOAD ============================== In addition to the LOG {} configuration, EXPORT {} config is the main configuration that can be updated while NFS-Ganesha is running by issuing a SIGHUP. This causes all EXPORT and EXPORT_DEFAULTS blocks to be reloaded. NFS-Ganesha V4.0 and later have some significant improvements to this since it was introduced in NFS-Ganesha V2.4.0. V2.8.0 introduced the ability to remove EXPORTs via SIGHUP configuration reload. Significantly how things work now is: On SIGHUP all the EXPORT and EXPORT_DEFAULTS blocks are re-read. There are three conditions that may occur: An export may be added An export may be removed An export may be updated A note on Export_Id and Path. These are the primary options that define an export. If Export_Id is changed, the change is treated as a remove of the old Export_Id and an addition of the new Export_Id. Path can not be changed without also changing Export_Id. The Tag and Pseudo options that also contribute to the uniqueness of an EXPORT may be changed. Any removed exports are removed from the internal tables and if they are NFSv4 exports, unmounted from the Pseudo Filesystem, which will then be re-built as if those exports had not been present. Any new exports are added to the internal tables, and if the export is an NFSv4 export, they are mounted into the Pseudo Filesystem. Any updated exports will be modified with the least disruption possible. If the Pseduo option is changed, the export is unmounted from the Pseduo Filesystem in it's original location, and re-mounted in it's new location. Other options are updated atomically, though serially, so for a short period of time, the options may be mixed between old and new. In most cases this should not cause problems. Notably though, the CLIENT blocks are processed to form a new access control list and that list is atomically swapped with the old list. If the Protocols for an EXPORT are changed to include or remove NFSv4, the Pseduo Filesystem will also be updated. Note that there is no pause in operations other than a lock being taken when the client list is being swapped out, however the export permissions are applied to an operation once. Notably for NFSv4, this is on a PUTFH or LOOKUP which changes the Current File Handle. As an example, if a write is in progress, having passed the permission check with the previous export permissions, the write will complete without interruption. If the write is part of an NFSv4 COMPOUND, the other operations in that COMPOUND that operate on the same file handle will also complete with the previous export permissions. An update of EXPORT_DEFAULTS changes the export options atomically. These options are only used for those options not otherwise set in an EXPORT {} or CLIENT {} block and are applied when export permissions are evaluated when a new file handle is encountered. The FSAL { Name } may not be changed and FSALs offer limited support for changing any options in the FSAL block. Some FSALs may validate and warn if any options in the FSAL block are changed when such a change is not supported. SEE ALSO ============================== :doc:`ganesha-config `\(8) :doc:`ganesha-rgw-config `\(8) :doc:`ganesha-vfs-config `\(8) :doc:`ganesha-lustre-config `\(8) :doc:`ganesha-xfs-config `\(8) :doc:`ganesha-gpfs-config `\(8) :doc:`ganesha-9p-config `\(8) :doc:`ganesha-proxy-config `\(8) :doc:`ganesha-ceph-config `\(8) nfs-ganesha-6.5/src/doc/man/ganesha-gluster-config.rst000066400000000000000000000027671473756622300230060ustar00rootroot00000000000000=================================================================== ganesha-gluster-config -- NFS Ganesha Gluster Configuration File =================================================================== .. program:: ganesha-gluster-config SYNOPSIS ========================================================== | /etc/ganesha/gluster.conf DESCRIPTION ========================================================== NFS-Ganesha installs the following sample config file for Gluster FSAL: | /etc/ganesha/gluster.conf This file lists Gluster specific config options. EXPORT { FSAL {} } -------------------------------------------------------------------------------- Name(string, "GLUSTER") Name of FSAL should always be GLUSTER. **volume(string, no default, required)** **hostname(string, no default, required)** **volpath(path, default "/")** **glfs_log(path, default "/var/log/ganesha/ganesha-gfapi.log")** **up_poll_usec(uint64, range 1 to 60*1000*1000, default 10)** **enable_upcall(bool, default true)** **transport(enum, values [tcp, rdma], default tcp)** **sec_label_xattr(char, default "security.selinux xattr of the file")** GLUSTER {} -------------------------------------------------------------------------------- **PNFS_MDS(bool, default FALSE)** Set this parameter to true to select this node as MDS See also ============================== :doc:`ganesha-log-config `\(8) :doc:`ganesha-core-config `\(8) :doc:`ganesha-export-config `\(8) nfs-ganesha-6.5/src/doc/man/ganesha-gpfs-config.rst000066400000000000000000000023201473756622300222410ustar00rootroot00000000000000=================================================================== ganesha-gpfs-config -- NFS Ganesha GPFS Configuration File =================================================================== .. program:: ganesha-gpfs-config SYNOPSIS ========================================================== | /etc/ganesha/gpfs.conf DESCRIPTION ========================================================== NFS-Ganesha install the following config file for GPFS FSAL: | /etc/ganesha/gpfs.conf This file lists GPFS specific config options. GPFS {} -------------------------------------------------------------------------------- **link_support(bool, default true)** **symlink_support(bool, default true)** **cansettime(bool, default true)** **umask(mode, range 0 to 0777, default 0)** **auth_xdev_export(bool, default false)** **Delegations(enum, default read)** Possible values: None, read, write, readwrite, r, w, rw **pnfs_file(bool, default false)** **fsal_trace(bool, default true)** **fsal_grace(bool, default false)** See also ============================== :doc:`ganesha-log-config `\(8) :doc:`ganesha-core-config `\(8) :doc:`ganesha-export-config `\(8) nfs-ganesha-6.5/src/doc/man/ganesha-kvsfs-config.rst000066400000000000000000000020241473756622300224370ustar00rootroot00000000000000=================================================================== ganesha-kvsfs-config -- NFS Ganesha KVSFS Configuration File =================================================================== .. program:: ganesha-kvsfs-config SYNOPSIS ========================================================== | /etc/ganesha/kvsfs.conf DESCRIPTION ========================================================== NFS-Ganesha install the following config file for KVSFS FSAL: | /etc/ganesha/kvsfs.conf This file lists KVSFS specific config options. EXPORT { FSAL {} } -------------------------------------------------------------------------------- Name(string, "KVSFS") Name of FSAL should always be KVSFS. **kvsns_config(string default "/etc/kvsns/d/kvsns.ini")** the path to the kvsns.ini file. If not specified, default value is used See also ============================== :doc:`ganesha-log-config `\(8) :doc:`ganesha-core-config `\(8) :doc:`ganesha-export-config `\(8) nfs-ganesha-6.5/src/doc/man/ganesha-log-config.rst000066400000000000000000000122701473756622300220700ustar00rootroot00000000000000=================================================================== ganesha-log-config -- NFS Ganesha Log Configuration File =================================================================== .. program:: ganesha-log-config SYNOPSIS ========================================================== | /etc/ganesha/ganesha.conf DESCRIPTION ========================================================== NFS-Ganesha reads the configuration data from: | /etc/ganesha/ganesha.conf This file lists NFS-Ganesha Log config options. These options may be dynamically updated by issuing a SIGHUP to the ganesha.nfsd process. LOG {} -------------------------------------------------------------------------------- Default_log_level(token,default EVENT) If this option is NOT set, the fall back log level will be that specified in the -N option on the command line if that is set, otherwise the fallback level is EVENT. If a SIGHUP is issued, any components not specified in LOG { COMPONENTS {} } will be reset to this value. The log levels are: NULL, FATAL, MAJ, CRIT, WARN, EVENT, INFO, DEBUG, MID_DEBUG, M_DBG, FULL_DEBUG, F_DBG RPC_Debug_Flags(uint32, range 0 to UINT32_MAX, default 7) Debug flags for TIRPC (default 7 matches log level default EVENT). These flags are only used if the TIRPC component is set to DEBUG Display_UTC_Timestamp(bool, default false) Flag to enable displaying UTC date/time in log messages instead of localtime. LOG { COMPONENTS {} } -------------------------------------------------------------------------------- **Default_log_level(token,default EVENT)** These entries are of the form: COMPONENT = LEVEL; The components are: ALL, LOG, MEMLEAKS, FSAL, NFSPROTO, NFS_V4, EXPORT, FILEHANDLE, DISPATCH, MDCACHE, MDCACHE_LRU, HASHTABLE, HASHTABLE_CACHE, DUPREQ, INIT, MAIN, IDMAPPER, NFS_READDIR, NFS_V4_LOCK, CONFIG, CLIENTID, SESSIONS, PNFS, RW_LOCK, NLM, TIRPC, NFS_CB, THREAD, NFS_V4_ACL, STATE, 9P, 9P_DISPATCH, FSAL_UP, DBUS, NFS_MSK, XPRT Some synonyms are: FH = FILEHANDLE HT = HASHTABLE CACHE_INODE_LRU = MDCAHCE_LRU CACHE_INODE = MDCACHE INODE_LRU = MDCAHCE_LRU INODE = MDCACHE DISP = DISPATCH LEAKS = MEMLEAKS NFS3 = NFSPROTO NFS4 = NFS_V4 HT_CACHE = HASHTABLE_CACHE NFS_STARTUP = INIT NFS4_LOCK = NFS_V4_LOCK NFS4_ACL = NFS_V4_ACL 9P_DISP = 9P_DISPATCH The log levels are: NULL, FATAL, MAJ, CRIT, WARN, EVENT, INFO, DEBUG, MID_DEBUG, M_DBG, FULL_DEBUG, F_DBG default none ALL is a special component that when set, sets all components to the specified value, overriding any that are explicitly set. Note that if ALL is then removed from the config and SIGHUP is issued, all components will revert to what is explicitly set, or Default_Log_Level if that is specified, or the original log level from the -N command line option if that was set, or the code default of EVENT. TIRPC is a special component that also sets the active RPC_Debug_Flags. If the level for TIRPC is DEBUG or MID_DEBUG, the custom RPC_Debug_Flags set by that parameter will be used, otherwise flags will depend on the level the TIRPC component is set to: NULL or FATAL: 0 CRIT or MAJ: TIRPC_DEBUG_FLAG_ERROR WARN: TIRPC_DEBUG_FLAG_ERROR | TIRPC_DEBUG_FLAG_WARN EVENT or INFO: TIRPC_DEBUG_FLAG_ERROR | TIRPC_DEBUG_FLAG_WARN | TIRPC_DEBUG_FLAG_EVENT DEBUG or MID_DEBUG: RPC_Debug_Flags FULL_DEBUG: 0xffffffff LOG { FACILITY {} } -------------------------------------------------------------------------------- This block may be repeated to configure multiple log facilities. **name(string, no default)** **destination(string, no default, must be supplied)** **max_level(token,default FULL_DEBUG)** The log levels are: NULL, FATAL, MAJ, CRIT, WARN, EVENT, INFO, DEBUG, MID_DEBUG, M_DBG, FULL_DEBUG, F_DBG **headers(token, values [none, component, all], default all)** **enable(token, values [idle, active, default], default idle)** LOG { FORMAT {} } -------------------------------------------------------------------------------- date_format(enum,default ganesha) Possible values: ganesha, true, local, 8601, ISO-8601, ISO 8601, ISO, syslog, syslog_usec, false, none, user_defined time_format(enum,default ganesha) Possible values: ganesha, true, local, 8601, ISO-8601, ISO 8601, ISO, syslog, syslog_usec, false, none, user_defined **user_date_format(string, no default)** **user_time_format(string, no default)** **EPOCH(bool, default true)** **CLIENTIP(bool, default false)** **HOSTNAME(bool, default true)** **PROGNAME(bool, default true)** **PID(bool, default true)** **THREAD_NAME(bool, default true)** **FILE_NAME(bool, default true)** **LINE_NUM(bool, default true)** **FUNCTION_NAME(bool, default true)** **COMPONENT(bool, default true)** **LEVEL(bool, default true)** **OP_ID(bool, default false)** **CLIENT_REQ_XID(bool, default false)** See also ============================== :doc:`ganesha-config `\(8) nfs-ganesha-6.5/src/doc/man/ganesha-lustre-config.rst000066400000000000000000000021661473756622300226300ustar00rootroot00000000000000=================================================================== ganesha-lustre-config -- NFS Ganesha LUSTRE Configuration File =================================================================== .. program:: ganesha-lustre-config SYNOPSIS ========================================================== | /etc/ganesha/lustre.conf DESCRIPTION ========================================================== NFS-Ganesha installs the config example for LUSTRE FSAL: | /etc/ganesha/lustre.conf This file lists LUSTRE specific config options. EXPORT { FSAL {} } -------------------------------------------------------------------------------- Name(string, "lustre") Name of FSAL should always be lustre. **async_hsm_restore(bool, default true)** All options of VFS export and module could be used for a FSAL_LUSTRE export and module. :doc:`ganesha-vfs-config `\(8) See also ============================== :doc:`ganesha-vfs-config `\(8) :doc:`ganesha-log-config `\(8) :doc:`ganesha-core-config `\(8) :doc:`ganesha-export-config `\(8) nfs-ganesha-6.5/src/doc/man/ganesha-proxy-v3-config.rst000066400000000000000000000034421473756622300230170ustar00rootroot00000000000000=================================================================== ganesha-proxy-v3-config -- NFSv3 Ganesha Proxy Configuration File =================================================================== .. program:: ganesha-proxy-v3-config SYNOPSIS ========================================================== | /etc/ganesha/ganesha.conf DESCRIPTION ========================================================== NFS-Ganesha install the following config file for Proxy FSAL: | /etc/ganesha/ganesha.conf This file lists Proxy specific config options. EXPORT { FSAL {} } -------------------------------------------------------------------------------- Name(string, "proxy_v3") Name of FSAL should always be proxy_v3. **Srv_Addr(ipv4_addr default "127.0.0.1")** **FSAL_MAXIOSIZE(default 64 MB)** PROXY_V3 {} -------------------------------------------------------------------------------- **FSAL_MAXIOSIZE(default 64 MB)** **maxread(uint64, default 1 MB)** range 1024 to FSAL_MAXIOSIZE Note that this value will get clamped to the backend's FSINFO response. **maxwrite(uint64, default 1 MB)** range 1024 to FSAL_MAXIOSIZE Note that this value will get clamped to the backend's FSINFO response. **num_sockets(uint32, default 32)** range 1 to 1000 Note that these sockets must be privileged ports, which may be limited on a given system. It is unlikely that 1000 privileged ports are available. **allow_lookup_optimization(bool, default true)** Set this to false to disable lookup optimization for current working directory and current working directory parent from the cache See also ============================== :doc:`ganesha-log-config `\(8) :doc:`ganesha-core-config `\(8) :doc:`ganesha-export-config `\(8) nfs-ganesha-6.5/src/doc/man/ganesha-proxy-v4-config.rst000066400000000000000000000056611473756622300230250ustar00rootroot00000000000000=================================================================== ganesha-proxy-v4-config -- NFS Ganesha Proxy V4 Configuration File =================================================================== .. program:: ganesha-proxy-v4-config SYNOPSIS ========================================================== | /etc/ganesha/ganesha.conf DESCRIPTION ========================================================== NFS-Ganesha install the following config file for Proxy V4 FSAL: | /etc/ganesha/ganesha.conf This file lists Proxy V4 specific config options. EXPORT { FSAL {} } -------------------------------------------------------------------------------- Name(string, "proxy_v4") Name of FSAL should always be proxy_v4. **Retry_SleepTime(uint32, range 0 to 60, default 10)** **Srv_Addr(ipv4_addr default "127.0.0.1")** **NFS_Service(uint32, range 0 to UINT32_MAX, default 100003)** **NFS_SendSize** must be greater than maxwrite+SEND_RECV_HEADER_SPACE **NFS_RecvSize** must be greater than maxread+SEND_RECV_HEADER_SPACE **MAX_READ_WRITE_SIZE(default 1 MB)** **SEND_RECV_HEADER_SPACE(default 512 Bytes)** **FSAL_MAXIOSIZE(default 64 MB)** NFS_SendSize(uint64, default MAX_READ_WRITE_SIZE + SEND_RECV_HEADER_SPACE) range 512 + SEND_RECV_HEADER_SPACE to FSAL_MAXIOSIZE NFS_RecvSize(uint64, default MAX_READ_WRITE_SIZE + SEND_RECV_HEADER_SPACE) range 512 + SEND_RECV_HEADER_SPACE to FSAL_MAXIOSIZE **NFS_Port(uint16, range 0 to UINT16_MAX, default 2049)** **Use_Privileged_Client_Port(bool, default true)** **RPC_Client_Timeout(uint32, range 1 to 60*4, default 60)** **Remote_PrincipalName(string, no default)** **KeytabPath(string, default "/etc/krb5.keytab")** **Credential_LifeTime(uint32, range 0 to 86400*2, default 86400)** **Sec_Type(enum, values [krb5, krb5i, krb5p], default krb5)** **Active_krb5(bool, default false)** **Enable_Handle_Mapping(bool, default false)** **HandleMap_DB_Dir(string, default "/var/ganesha/handlemap")** **HandleMap_Tmp_Dir(string, default "/var/ganesha/tmp")** **HandleMap_DB_Count(uint32, range 1 to 16, default 8)** **HandleMap_HashTable_Size(uint32, range 1 to 127, default 103)** PROXY_V4 {} -------------------------------------------------------------------------------- **link_support(bool, default true)** **symlink_support(bool, default true)** **cansettime(bool, default true)** **MAX_READ_WRITE_SIZE(default 1MB)** **FSAL_MAXIOSIZE(default 64 MB)** **SEND_RECV_HEADER_SPACE(default, 512 Bytes)** **maxread(uint64, default MAX_READ_WRITE_SIZE)** range 512 to FSAL_MAXIOSIZE - SEND_RECV_HEADER_SPACE **maxwrite(uint64, default MAX_READ_WRITE_SIZE)** range 512 to FSAL_MAXIOSIZE - SEND_RECV_HEADER_SPACE **umask(mode, range 0 to 0777, default 0)** **auth_xdev_export(bool, default false)** See also ============================== :doc:`ganesha-log-config `\(8) :doc:`ganesha-core-config `\(8) :doc:`ganesha-export-config `\(8) nfs-ganesha-6.5/src/doc/man/ganesha-rados-cluster-design.rst000066400000000000000000000240721473756622300241050ustar00rootroot00000000000000============================================================================== ganesha-rados-cluster-design -- Clustered RADOS Recovery Backend Design ============================================================================== Overview -------- This document aims to explain the theory and design behind the rados_cluster recovery backend, which coordinates grace period enforcement among multiple, independent NFS servers. In order to understand the clustered recovery backend, it's first necessary to understand how recovery works with a single server: Singleton Server Recovery ------------------------- NFSv4 is a lease-based protocol. Clients set up a relationship to the server and must periodically renew their lease in order to maintain their ephemeral state (open files, locks, delegations or layouts). When a singleton NFS server is restarted, any ephemeral state is lost. When the server comes comes back online, NFS clients detect that the server has been restarted and will reclaim the ephemeral state that they held at the time of their last contact with the server. Singleton Grace Period ---------------------- In order to ensure that we don't end up with conflicts, clients are barred from acquiring any new state while in the Recovery phase. Only reclaim operations are allowed. This period of time is called the **grace period**. Most NFS servers have a grace period that lasts around two lease periods, however nfs-ganesha can and will lift the grace period early if it determines that no more clients will be allowed to recover. Once the grace period ends, the server will move into its Normal operation state. During this period, no more recovery is allowed and new state can be acquired by NFS clients. Reboot Epochs ------------- The lifecycle of a singleton NFS server can be considered to be a series of transitions from the Recovery period to Normal operation and back. In the remainder of this document we'll consider such a period to be an **epoch**, and assign each a number beginning with 1. Visually, we can represent it like this, such that each Normal -> Recovery transition is marked by a change in the epoch value: :: +-------+-------+-------+---------------+-------+ | State | R | N | R | N | R | R | R | N | R | N | +-------+-------+-------+---------------+-------+ | Epoch | 1 | 2 | 3 | 4 | +-------+-------+-------+---------------+-------+ Note that it is possible to restart during the grace period (as shown above during epoch 3). That just serves to extend the recovery period and the epoch. A new epoch is only declared during a Recovery -> Normal transition. Client Recovery Database ------------------------ There are some potential edge cases that can occur involving network partitions and multiple reboots. In order to prevent those, the server must maintain a list of clients that hold state on the server at any given time. This list must be maintained on stable storage. If a client sends a request to reclaim some state, then the server must check to make sure it's on that list before allowing the request. Thus when the server allows reclaim requests it must always gate it against the recovery database from the previous epoch. As clients come in to reclaim, we establish records for them in a new database associated with the current epoch. The transition from recovery to normal operation should perform an atomic switch of recovery databases. A recovery database only becomes legitimate on a recovery to normal transition. Until that point, the recovery database from the previous epoch is the canonical one. Exporting a Clustered Filesystem -------------------------------- Let's consider a set of independent NFS servers, all serving out the same content from a clustered backend filesystem of any flavor. Each NFS server in this case can itself be considered a clustered FS client. This means that the NFS server is really just a proxy for state on the clustered filesystem. The filesystem must make some guarantees to the NFS server. First filesystem guarantee: 1. The filesystem ensures that the NFS servers (aka the FS clients) cannot obtain state that conflicts with that of another NFS server. This is somewhat obvious and is what we expect from any clustered filesystem outside of any requirements of NFS. If the clustered filesystem can provide this, then we know that conflicting state during normal operations cannot be granted. The recovery period has a different set of rules. If an NFS server crashes and is restarted, then we have a window of time when that NFS server does not know what state was held by its clients. If the state held by the crashed NFS server is immediately released after the crash, another NFS server could hand out conflicting state before the original NFS client has a chance to recover it. This *must* be prevented. Second filesystem guarantee: 2. The filesystem must not release state held by a server during the previous epoch until all servers in the cluster are enforcing the grace period. In practical terms, we want the filesystem to provide a way for an NFS server to tell it when it's safe to release state held by a previous instance of itself. The server should do this once it knows that all of its siblings are enforcing the grace period. Note that we do not require that all servers restart and allow reclaim at that point. It's sufficient for them to simply begin grace period enforcement as soon as possible once one server needs it. Clustered Grace Period Database ------------------------------- At this point the cluster siblings are no longer completely independent, and the grace period has become a cluster-wide property. This means that we must track the current epoch on some sort of shared storage that the servers can all access. Additionally we must also keep track of whether a cluster-wide grace period is in effect. Any running nodes should all be informed when either of this info changes, so they can take appropriate steps when it occurs. In the rados_cluster backend, we track these using two epoch values: **C**: is the current epoch. This represents the current epoch value of the cluster **R**: is the recovery epoch. This represents the epoch from which clients are allowed to recover. A non-zero value here means that a cluster-wide grace period is in effect. Setting this to 0 ends that grace period. In order to decide when to make grace period transitions, each server must also advertise its state to the other nodes. Specifically, each server must be able to determine these two things about each of its siblings: 1. Does this server have clients from the previous epoch that will require recovery? (NEED) 2. Is this server enforcing the grace period by refusing non-reclaim locks? (ENFORCING) We do this with a pair of flags per sibling (NEED and ENFORCING). Each server typically manages its own flags. The rados_cluster backend stores all of this information in a single RADOS object that is modified using read/modify/write cycles. Typically we'll read the whole object, modify it, and then attempt to write it back. If something changes between the read and write, we redo the read and try it again. Clustered Client Recovery Databases ----------------------------------- In rados_cluster the client recovery databases are stored as RADOS objects. Each NFS server has its own set of them and they are given names that have the current epoch (C) embedded in it. This ensures that recovery databases are specific to a particular epoch. In general, it's safe to delete any recovery database that precedes R when R is non-zero, and safe to remove any recovery database except for the current one (the one with C in the name) when the grace period is not in effect (R==0). Establishing a New Grace Period ------------------------------- When a server restarts and wants to allow clients to reclaim their state, it must establish a new epoch by incrementing the current epoch to declare a new grace period (R=C; C=C+1). The exception to this rule is when the cluster is already in a grace period. Servers can just join an in-progress grace period instead of establishing a new one if one is already active. In either case, the server should also set its NEED and ENFORCING flags at the same time. The other surviving cluster siblings should take steps to begin grace period enforcement as soon as possible. This entails "draining off" any in-progress state morphing operations and then blocking the acquisition of any new state (usually with a return of NFS4ERR_GRACE to clients that attempt it). Again, there is no need for the survivors from the previous epoch to allow recovery here. The surviving servers must however establish a new client recovery database at this point to ensure that their clients can do recovery in the event of a crash afterward. Once all of the siblings are enforcing the grace period, the recovering server can then request that the filesystem release the old state, and allow clients to begin reclaiming their state. In the rados_cluster backend driver, we do this by stalling server startup until all hosts in the cluster are enforcing the grace period. Lifting the Grace Period ------------------------ Transitioning from recovery to normal operation really consists of two different steps: 1. the server decides that it no longer requires a grace period, either due to it timing out or there not being any clients that would be allowed to reclaim. 2. the server stops enforcing the grace period and transitions to normal operation These concepts are often conflated in singleton servers, but in a cluster we must consider them independently. When a server is finished with its own local recovery period, it should clear its NEED flag. That server should continue enforcing the grace period however until the grace period is fully lifted. The server must not permit reclaims after clearing its NEED flag, however. If the servers' own NEED flag is the last one set, then it can lift the grace period (by setting R=0). At that point, all servers in the cluster can end grace period enforcement, and communicate that fact to the others by clearing their ENFORCING flags. nfs-ganesha-6.5/src/doc/man/ganesha-rados-grace.rst000066400000000000000000000145011473756622300222320ustar00rootroot00000000000000====================================================================== ganesha-rados-grace -- manipulate the shared grace management database ====================================================================== SYNOPSIS =================================================================== | ganesha-rados-grace [ --cephconf /path/to/ceph.conf ] [--ns namespace] [ --oid obj_id ] [ --pool pool_id ] [ --userid cephuser ] dump|add|start|join|lift|remove|enforce|noenforce|member [ nodeid ... ] DESCRIPTION =================================================================== This tool allows the administrator to directly manipulate the database used by the rados_cluster recovery backend. Cluster nodes use that database to indicate their current state in order to coordinate a cluster-wide grace period. The first argument should be a command to execute against the database. Any remaining arguments represent the nodeids of nodes in the cluster that should be acted upon. Most commands will just fail if the grace database is not present. The exception to this rule is the **add** command which will create the pool, database and namespace if they do not already exist. Note that this program does not consult ganesha.conf. If you use non-default values for **ceph_conf**, **userid**, **grace_oid**, **namespace** or **pool** in your RADOS_KV config block, then they will need to passed in via command-line options. OPTIONS =================================================================== **--cephconf** Specify the ceph.conf configuration that should be used (default is to use the normal search path to find one) **--ns** Set the RADOS namespace to use within the pool (default is NULL) **--oid** Set the object id of the grace database RADOS object (default is "grace") **--pool** Set the RADOS poolid in which the grace database object resides (default is "nfs-ganesha") **--userid** Set the cephx user ID to use when contacting the cluster (default is NULL) COMMANDS =================================================================== **dump** Dump the current status of the grace period database to stdout. This will show the current and recovery epoch serial numbers, as well as a list of hosts currently in the cluster and what flags they have set in their individual records. **add** Add the specified hosts to the cluster. This must be done before the given hosts can take part in the cluster. Attempts to modify the database by cluster hosts that have not yet been added will generally fail. New hosts are added with the enforcing flag set, as they are unable to hand out new state until their own grace period has been lifted. **start** Start a new grace period. This will begin a new grace period in the cluster if one is not already active and set the record for the listed cluster hosts as both needing a grace period and enforcing the grace period. If a grace period is already active, then this is equivalent to **join**. **join** Attempt to join an existing grace period. This works like **start**, but only if there is already an existing grace period in force. **lift** Attempt to lift the current grace period. This will clear the need grace flags for the listed hosts. If there are no more hosts in the cluster that require a grace period, then it will be fully lifted and the cluster will transition to normal operations. **remove** Remove one or more existing hosts from the cluster. This will remove the listed hosts from the grace database, possibly lifting the current grace period if there are no more hosts that need one. **enforce** Set the flag for the given hosts that indicates that they are currently enforcing the grace period; not allowing the acquisition of new state by clients. **noenforce** Clear the enforcing flag for the given hosts, meaning that those hosts are now allowing clients to acquire new state. **member** Test whether the given hosts are members of the cluster. Returns an error if any of the hosts are not present in the grace db omap. FLAGS ===== When the **dump** command is issued, ganesha-rados-grace will display a list of all of the nodes in the grace database, and any flags they have set. The flags are as follows: **E (Enforcing)** The node is currently enforcing the grace period by rejecting requests from clients to acquire new state. **N (Need Grace)** The node currently requires a grace period. Generally, this means that the node has clients that need to perform recovery. NODEID ASSIGNMENT ================= Each running ganesha daemon requires a **nodeid** string that is unique within the cluster. This can be any value as ganesha treats it as an opaque string. By default, the ganesha daemon will use the hostname of the node where it is running. This may not be suitable when running under certain HA clustering infrastructure, so it's generally recommended to manually assign nodeid values to the hosts in the **RADOS_KV** config block of **ganesha.conf**. GANESHA CONFIGURATION ===================== The ganesha daemon will need to be configured with the RecoveryBackend set to **rados_cluster**. If you use a non-default pool, namespace or oid, nodeid then those values will need to be set accordingly in the **RADOS_KV** config block as well. STARTING A NEW CLUSTER ====================== First, add the given cluster nodes to the grace database. Assuming that the nodes in our cluster will have nodeids ganesha-1 through ganesha-3: **ganesha-rados-grace add ganesha-1 ganesha-2 ganesha-3** Once this is done, you can start the daemons on each host and they will coordinate to start and lift the grace periods as-needed. ADDING NODES TO A RUNNING CLUSTER ================================= After this point, new nodes can then be added to the cluster as needed using the **add** command: **ganesha-rados-grace add ganesha-4** After the node has been added, ganesha.nfsd can then be started. It will then request a new grace period as-needed. REMOVING A NODE FROM THE CLUSTER ================================ To remove a node from the cluster, first unmount any clients that have that node mounted (possibly moving them to other servers). Then execute the remove command with the nodeids to be removed from the cluster. For example: **ganesha-rados-grace remove ganesha-4** This will remove the ganesha-4's record from the database, and possibly lift the current grace period if one is active and it was the last one to need it. nfs-ganesha-6.5/src/doc/man/ganesha-rgw-config.rst000066400000000000000000000044051473756622300221070ustar00rootroot00000000000000=================================================================== ganesha-rgw-config -- NFS Ganesha RGW Configuration File =================================================================== .. program:: ganesha-rgw-config SYNOPSIS ========================================================== | /etc/ganesha/rgw.conf | /etc/ganesha/rgw_bucket.conf DESCRIPTION ========================================================== NFS-Ganesha install two config examples for RGW FSAL: | /etc/ganesha/rgw.conf | /etc/ganesha/rgw_bucket.conf This file lists RGW specific config options. EXPORT { } -------------------------------------------------------------------------------- RGW supports exporting both the buckets and filesystem. .. Explain in detail about exporting bucket and filesystem EXPORT { FSAL {} } -------------------------------------------------------------------------------- Name(string, "RGW") Name of FSAL should always be RGW. **User_Id(string, no default)** **Access_Key(string, no default)** **Secret_Access_Key(string, no default)** RGW {} -------------------------------------------------------------------------------- The following configuration variables customize the startup of the FSAL's radosgw instance. ceph_conf optional full-path to the Ceph configuration file (equivalent to passing "-c /path/to/ceph.conf" to any Ceph binary name optional instance name (equivalent to passing "--name client.rgw.foohost" to the radosgw binary); the value provided here should be the same as the section name (sans brackets) of the radosgw facility in the Ceph configuration file (which must exist) cluster optional cluster name (equivalent to passing "--cluster foo" to any Ceph binary); use of a non-default value for cluster name is uncommon, but can be verified by examining the startup options of Ceph binaries init_args additional argument strings which will be passed verbatim to the radosgw instance startup process as if they had been given on the radosgw command line provided for customization in uncommon setups See also ============================== :doc:`ganesha-log-config `\(8) :doc:`ganesha-core-config `\(8) :doc:`ganesha-export-config `\(8) nfs-ganesha-6.5/src/doc/man/ganesha-top.rst000066400000000000000000000020331473756622300206420ustar00rootroot00000000000000====================================================================== ganesha-top -- A tool for the information monitoring like top ====================================================================== SYNOPSIS =================================================================== | ganesha-top [--help] [--interval INTERVAL] DESCRIPTION =================================================================== This tool allows the administrator to monitor the system and NFS-Ganesha status. In the header block, there are pieces of information like memory usage (including RSIZE/VSIZE/SWAP), CPU usage, number of Exports, Clients, and operations of NFS-Ganesha. Also included is the cache (MDCache) information. This tool can also display the detail about Exports, Clients, and all NFSv4 OPs. For the administrator, that can help analyze the system status by OPs, latency, and other information. OPTIONS =================================================================== **--interval** Specify the update interval, the default value is 5 seconds. nfs-ganesha-6.5/src/doc/man/ganesha-vfs-config.rst000066400000000000000000000027031473756622300221050ustar00rootroot00000000000000=================================================================== ganesha-vfs-config -- NFS Ganesha VFS Configuration File =================================================================== .. program:: ganesha-vfs-config SYNOPSIS ========================================================== | /etc/ganesha/vfs.conf DESCRIPTION ========================================================== NFS-Ganesha installs the config example for VFS FSAL: | /etc/ganesha/vfs.conf This file lists VFS specific config options. EXPORT { FSAL {} } -------------------------------------------------------------------------------- Name(string, "vfs") Name of FSAL should always be vfs. **pnfs(bool, default false)** fsid_type(enum) Possible values: None, One64, Major64, Two64, uuid, Two32, Dev,Device VFS {} -------------------------------------------------------------------------------- **link_support(bool, default true)** **symlink_support(bool, default true)** **cansettime(bool, default true)** **maxread(uint64, range 512 to 64*1024*1024, default 64*1024*1024)** **maxwrite(uint64, range 512 to 64*1024*1024, default 64*1024*1024)** **umask(mode, range 0 to 0777, default 0)** **auth_xdev_export(bool, default false)** **only_one_user(bool, default false)** See also ============================== :doc:`ganesha-log-config `\(8) :doc:`ganesha-core-config `\(8) :doc:`ganesha-export-config `\(8) nfs-ganesha-6.5/src/doc/man/ganesha-xfs-config.rst000066400000000000000000000024461473756622300221130ustar00rootroot00000000000000=================================================================== ganesha-xfs-config -- NFS Ganesha XFS Configuration File =================================================================== .. program:: ganesha-xfs-config SYNOPSIS ========================================================== /etc/ganesha/xfs.conf DESCRIPTION ========================================================== NFS-Ganesha installs the config example for XFS FSAL: /etc/ganesha/xfs.conf This file lists xfs specific config options. EXPORT { FSAL {} } -------------------------------------------------------------------------------- Name(string, "XFS") Name of FSAL should always be XFS. XFS {} -------------------------------------------------------------------------------- **link_support(bool, default true)** **symlink_support(bool, default true)** **cansettime(bool, default true)** **maxread(uint64, range 512 to 64*1024*1024, default 64*1024*1024)** **maxwrite(uint64, range 512 to 64*1024*1024, default 64*1024*1024)** **umask(mode, range 0 to 0777, default 0)** **auth_xdev_export(bool, default false)** See also ============================== :doc:`ganesha-log-config `\(8) :doc:`ganesha-core-config `\(8) :doc:`ganesha-export-config `\(8) nfs-ganesha-6.5/src/doc/man/index.rst000066400000000000000000000007041473756622300175460ustar00rootroot00000000000000.. toctree:: :maxdepth: 1 :caption: Contents: ganesha-config ganesha-9p-config ganesha-ceph-config ganesha-log-config ganesha-gluster-config ganesha-gpfs-config ganesha-proxy-config ganesha-rgw-config ganesha-vfs-config ganesha-lustre-config ganesha-xfs-config ganesha-kvsfs-config ganesha-cache-config ganesha-export-config ganesha-core-config ganesha-rados-cluster-design ganesha-rados-grace nfs-ganesha-6.5/src/ganesha.el000066400000000000000000000026771473756622300163300ustar00rootroot00000000000000;; ;; ganesha.el ;; ;; Made by Sean Dague ;; Login ;; ;; Started on Wed Mar 17 14:17:25 2010 Sean Dague ;; Last update Wed Mar 17 14:17:25 2010 Sean Dague ;; ;; The following defines ganesha C code style for emacs to the best of ;; my current understanding. This is useful for those developing for ;; ganesha that wish to keep their code in line with the existing ;; project style. I'm not an expert at such things, so corrections ;; are appreciated. ;; ;; To use this include this file in your .emacs, then C-c . to set ;; c-mode, and set it to "ganesha". (defconst ganesha-c-style '((c-tab-always-indent . t) (c-basic-offset . 4) (c-comment-only-line-offset . 0) (c-hanging-braces-alist . ((brace-entry-open before after) (substatement-open before after) (block-close . c-snug-do-while) (arglist-cont-nonempty))) (c-cleanup-list . (brace-else-brace brace-elseif-brace)) (c-offsets-alist . ((statement-block-intro . +) (knr-argdecl-intro . 0) (substatement-open . +) (substatement-label . 0) (label . 0) (brace-list-open . +) (statement-cont . +))) (indent-tabs-mode nil)) "Ganesha C Style") (c-add-style "ganesha" ganesha-c-style) nfs-ganesha-6.5/src/gtest/000077500000000000000000000000001473756622300155125ustar00rootroot00000000000000nfs-ganesha-6.5/src/gtest/CMakeLists.txt000066400000000000000000000050251473756622300202540ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # Set CMAKE_CXX_FLAGS, for gtest presently set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -fpermissive -Wnon-virtual-dtor -Wno-invalid-offsetof") set(UNITTEST_LIBS ${GTEST_LIBRARIES} boost_program_options boost_system ${PTHREAD_LIBS}) set(UNITTEST_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${PROJECT_SOURCE_DIR}/gtest -I${GTEST_INCLUDE_DIR} -fno-strict-aliasing") add_subdirectory(fsal_api) add_subdirectory(nfs4) # generic test set(test_example_SRCS test_example.cc ) add_executable(test_example EXCLUDE_FROM_ALL ${test_example_SRCS}) add_sanitizers(test_example) target_link_libraries(test_example ${CMAKE_THREAD_LIBS_INIT} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} pthread ) set_target_properties(test_example PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") # Test using ganesha internals set(test_ci_hash_dist1_SRCS test_ci_hash_dist1.cc ) add_executable(test_ci_hash_dist1 ${test_ci_hash_dist1_SRCS}) add_sanitizers(test_ci_hash_dist1) target_link_libraries(test_ci_hash_dist1 ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_ci_hash_dist1 PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_rbt_SRCS test_rbt.cc ) add_executable(test_rbt ${test_rbt_SRCS}) add_sanitizers(test_rbt) target_link_libraries(test_rbt ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_rbt PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") nfs-ganesha-6.5/src/gtest/README000066400000000000000000000016051473756622300163740ustar00rootroot00000000000000Google publishes gmock and gtest from the official/external Chromium repositories. I found that these repositories are not linked, but that the gmock source expects gtest to be checked out within. (The Ceph developers maintain a pair of linked repositories where this is already done, and do pullups from the external repositories.) The provided CMakeLists.txt files just work, but don't provide an install target. I did an in-tree build in gmock and assumed that the build tree would be dropped into some public location (e.g., /opt/gmock). Sources: git clone https://chromium.googlesource.com/external/gmock git clone https://chromium.googlesource.com/external/gtest I created a minimal example test driver with a single true assertion for reference (src/gtest/test_example.cc). For more information on how to use Google Test, see: http://code.google.com/p/googletest/wiki/Primer Mattnfs-ganesha-6.5/src/gtest/fsal_api/000077500000000000000000000000001473756622300172705ustar00rootroot00000000000000nfs-ganesha-6.5/src/gtest/fsal_api/CMakeLists.txt000066400000000000000000000256511473756622300220410ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- set(test_lookup_latency_SRCS test_lookup_latency.cc ) add_executable(test_lookup_latency ${test_lookup_latency_SRCS}) add_sanitizers(test_lookup_latency) target_link_libraries(test_lookup_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_lookup_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_readlink_latency_SRCS test_readlink_latency.cc ) add_executable(test_readlink_latency ${test_readlink_latency_SRCS}) add_sanitizers(test_readlink_latency) target_link_libraries(test_readlink_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_readlink_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_mkdir_latency_SRCS test_mkdir_latency.cc ) add_executable(test_mkdir_latency ${test_mkdir_latency_SRCS}) add_sanitizers(test_mkdir_latency) target_link_libraries(test_mkdir_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_mkdir_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_symlink_latency_SRCS test_symlink_latency.cc ) add_executable(test_symlink_latency ${test_symlink_latency_SRCS}) add_sanitizers(test_symlink_latency) target_link_libraries(test_symlink_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_symlink_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_link_latency_SRCS test_link_latency.cc ) add_executable(test_link_latency ${test_link_latency_SRCS}) add_sanitizers(test_link_latency) target_link_libraries(test_link_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_link_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_unlink_latency_SRCS test_unlink_latency.cc ) add_executable(test_unlink_latency ${test_unlink_latency_SRCS}) add_sanitizers(test_unlink_latency) target_link_libraries(test_unlink_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_unlink_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_rename_latency_SRCS test_rename_latency.cc ) add_executable(test_rename_latency ${test_rename_latency_SRCS}) add_sanitizers(test_rename_latency) target_link_libraries(test_rename_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_rename_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_getattrs_latency_SRCS test_getattrs_latency.cc ) add_executable(test_getattrs_latency ${test_getattrs_latency_SRCS}) add_sanitizers(test_getattrs_latency) target_link_libraries(test_getattrs_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_getattrs_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_close_latency_SRCS test_close_latency.cc ) add_executable(test_close_latency ${test_close_latency_SRCS}) add_sanitizers(test_close_latency) target_link_libraries(test_close_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_close_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_commit2_latency_SRCS test_commit2_latency.cc ) add_executable(test_commit2_latency ${test_commit2_latency_SRCS}) add_sanitizers(test_commit2_latency) target_link_libraries(test_commit2_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_commit2_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_write2_latency_SRCS test_write2_latency.cc ) add_executable(test_write2_latency ${test_write2_latency_SRCS}) add_sanitizers(test_write2_latency) target_link_libraries(test_write2_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_write2_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_read2_latency_SRCS test_read2_latency.cc ) add_executable(test_read2_latency ${test_read2_latency_SRCS}) add_sanitizers(test_read2_latency) target_link_libraries(test_read2_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_read2_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_open2_latency_SRCS test_open2_latency.cc ) add_executable(test_open2_latency ${test_open2_latency_SRCS}) add_sanitizers(test_open2_latency) target_link_libraries(test_open2_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_open2_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_close2_latency_SRCS test_close2_latency.cc ) add_executable(test_close2_latency ${test_close2_latency_SRCS}) add_sanitizers(test_close2_latency) target_link_libraries(test_close2_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_close2_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_reopen2_latency_SRCS test_reopen2_latency.cc ) add_executable(test_reopen2_latency ${test_reopen2_latency_SRCS}) add_sanitizers(test_reopen2_latency) target_link_libraries(test_reopen2_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_reopen2_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_setattr2_latency_SRCS test_setattr2_latency.cc ) add_executable(test_setattr2_latency ${test_setattr2_latency_SRCS}) add_sanitizers(test_setattr2_latency) target_link_libraries(test_setattr2_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_setattr2_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_readdir_latency_SRCS test_readdir_latency.cc ) add_executable(test_readdir_latency ${test_readdir_latency_SRCS}) add_sanitizers(test_readdir_latency) target_link_libraries(test_readdir_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_readdir_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_mknode_latency_SRCS test_mknode_latency.cc ) add_executable(test_mknode_latency ${test_mknode_latency_SRCS}) add_sanitizers(test_mknode_latency) target_link_libraries(test_mknode_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_mknode_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_lock_op2_latency_SRCS test_lock_op2_latency.cc ) add_executable(test_lock_op2_latency ${test_lock_op2_latency_SRCS}) add_sanitizers(test_lock_op2_latency) target_link_libraries(test_lock_op2_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_lock_op2_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_handle_to_key_latency_SRCS test_handle_to_key_latency.cc ) add_executable(test_handle_to_key_latency ${test_handle_to_key_latency_SRCS}) add_sanitizers(test_handle_to_key_latency) target_link_libraries(test_handle_to_key_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_handle_to_key_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_release_latency_SRCS test_release_latency.cc ) add_executable(test_release_latency ${test_release_latency_SRCS}) add_sanitizers(test_release_latency) target_link_libraries(test_release_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_release_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_handle_to_wire_latency_SRCS test_handle_to_wire_latency.cc ) add_executable(test_handle_to_wire_latency ${test_handle_to_wire_latency_SRCS}) add_sanitizers(test_handle_to_wire_latency) target_link_libraries(test_handle_to_wire_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_handle_to_wire_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_readdir_correctness_SRCS test_readdir_correctness.cc ) add_executable(test_readdir_correctness ${test_readdir_correctness_SRCS}) add_sanitizers(test_readdir_correctness) target_link_libraries(test_readdir_correctness ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_readdir_correctness PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") nfs-ganesha-6.5/src/gtest/fsal_api/test_close2_latency.cc000066400000000000000000000217011473756622300235450ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "close2_latency" #define TEST_FILE "close2_latency_file" #define LOOP_COUNT 100000 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class Close2EmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { gtest::GaneshaFSALBaseTest::SetUp(); } virtual void TearDown() { gtest::GaneshaFSALBaseTest::TearDown(); } }; class Close2LoopLatencyTest : public Close2EmptyLatencyTest { protected: virtual void SetUp() { Close2EmptyLatencyTest::SetUp(); for (int i = 0; i < LOOP_COUNT; ++i) { file_state[i] = op_ctx->fsal_export->exp_ops.alloc_state( op_ctx->fsal_export, STATE_TYPE_SHARE, NULL); ASSERT_NE(file_state[i], nullptr); } } virtual void TearDown() { for (int i = 0; i < LOOP_COUNT; ++i) { free_state(file_state[i]); } Close2EmptyLatencyTest::TearDown(); } struct fsal_obj_handle *obj[LOOP_COUNT]; struct state_t *file_state[LOOP_COUNT]; }; } /* namespace */ TEST_F(Close2EmptyLatencyTest, SIMPLE) { fsal_status_t status; struct fsal_obj_handle *obj; bool caller_perm_check = false; struct state_t *file_state; file_state = op_ctx->fsal_export->exp_ops.alloc_state( op_ctx->fsal_export, STATE_TYPE_SHARE, NULL); ASSERT_NE(file_state, nullptr); // create and open a file for test status = test_root->obj_ops->open2(test_root, file_state, FSAL_O_RDWR, FSAL_UNCHECKED, TEST_FILE, NULL, NULL, &obj, NULL, &caller_perm_check, nullptr, nullptr); ASSERT_EQ(status.major, 0); status = obj->obj_ops->close2(obj, file_state); EXPECT_EQ(status.major, 0); // delete the file created for test status = fsal_remove(test_root, TEST_FILE, NULL, NULL); ASSERT_EQ(status.major, 0); obj->obj_ops->put_ref(obj); free_state(file_state); } TEST_F(Close2EmptyLatencyTest, SIMPLE_BYPASS) { fsal_status_t status; struct fsal_obj_handle *obj; bool caller_perm_check = false; struct state_t *file_state; struct fsal_obj_handle *sub_hdl; file_state = op_ctx->fsal_export->exp_ops.alloc_state( op_ctx->fsal_export, STATE_TYPE_SHARE, NULL); ASSERT_NE(file_state, nullptr); // create and open a file for test status = test_root->obj_ops->open2(test_root, file_state, FSAL_O_RDWR, FSAL_UNCHECKED, TEST_FILE, NULL, NULL, &obj, NULL, &caller_perm_check, nullptr, nullptr); ASSERT_EQ(status.major, 0); sub_hdl = mdcdb_get_sub_handle(obj); ASSERT_NE(sub_hdl, nullptr); status = sub_hdl->obj_ops->close2(sub_hdl, file_state); EXPECT_EQ(status.major, 0); // delete the file created for test status = fsal_remove(test_root, TEST_FILE, NULL, NULL); ASSERT_EQ(status.major, 0); obj->obj_ops->put_ref(obj); free_state(file_state); } TEST_F(Close2LoopLatencyTest, LOOP) { fsal_status_t status; char fname[NAMELEN]; bool caller_perm_check = false; struct timespec s_time, e_time; // create and open a files for test for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = test_root->obj_ops->open2(test_root, file_state[i], FSAL_O_RDWR, FSAL_UNCHECKED, fname, NULL, NULL, &obj[i], NULL, &caller_perm_check, nullptr, nullptr); ASSERT_EQ(status.major, 0); } now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = obj[i]->obj_ops->close2(obj[i], file_state[i]); EXPECT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per close2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); // delete the files created for test for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = fsal_remove(test_root, fname, NULL, NULL); ASSERT_EQ(status.major, 0); obj[i]->obj_ops->put_ref(obj[i]); } } TEST_F(Close2LoopLatencyTest, LOOP_BYPASS) { fsal_status_t status; char fname[NAMELEN]; bool caller_perm_check = false; struct fsal_obj_handle *sub_hdl[LOOP_COUNT]; struct timespec s_time, e_time; // create and open a files for test for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = test_root->obj_ops->open2(test_root, file_state[i], FSAL_O_RDWR, FSAL_UNCHECKED, fname, NULL, NULL, &obj[i], NULL, &caller_perm_check, nullptr, nullptr); ASSERT_EQ(status.major, 0); sub_hdl[i] = mdcdb_get_sub_handle(obj[i]); ASSERT_NE(sub_hdl[i], nullptr); } now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = sub_hdl[i]->obj_ops->close2(sub_hdl[i], file_state[i]); EXPECT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per close2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); // delete the files created for test for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = fsal_remove(test_root, fname, NULL, NULL); ASSERT_EQ(status.major, 0); obj[i]->obj_ops->put_ref(obj[i]); } } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)") opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_close_latency.cc000066400000000000000000000143661473756622300234740ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "close_latency" #define TEST_FILE "close_latency_file" #define LOOP_COUNT 100000 // Needs to be less than available FD count namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class CloseEmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { gtest::GaneshaFSALBaseTest::SetUp(); } virtual void TearDown() { gtest::GaneshaFSALBaseTest::TearDown(); } }; class CloseFullLatencyTest : public CloseEmptyLatencyTest { protected: virtual void SetUp() { CloseEmptyLatencyTest::SetUp(); create_and_prime_many(LOOP_COUNT, NULL); } virtual void TearDown() { remove_many(LOOP_COUNT, NULL); CloseEmptyLatencyTest::TearDown(); } }; } /* namespace */ TEST_F(CloseEmptyLatencyTest, SIMPLE) { fsal_status_t status; struct fsal_obj_handle *obj; // create and open a file for test status = fsal_open2(test_root, NULL, FSAL_O_RDWR, FSAL_UNCHECKED, TEST_FILE, NULL, NULL, &obj, NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0); status = fsal_close(obj); EXPECT_EQ(status.major, 0); // delete the file created for test status = fsal_remove(test_root, TEST_FILE, NULL, NULL); ASSERT_EQ(status.major, 0); obj->obj_ops->put_ref(obj); } TEST_F(CloseEmptyLatencyTest, LOOP) { fsal_status_t status; struct fsal_obj_handle *obj[LOOP_COUNT]; char fname[NAMELEN]; struct timespec s_time, e_time; // create and open a file for test for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = fsal_open2(test_root, NULL, FSAL_O_RDWR, FSAL_UNCHECKED, fname, NULL, NULL, &obj[i], NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0); } now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = fsal_close(obj[i]); EXPECT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per fsal_close: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); // delete the file created for test for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = fsal_remove(test_root, fname, NULL, NULL); ASSERT_EQ(status.major, 0); obj[i]->obj_ops->put_ref(obj[i]); } } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_commit2_latency.cc000066400000000000000000000250131473756622300237300ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "commit2_latency" #define TEST_FILE "test_file" #define LOOP_COUNT 1000000 #define OFFSET 0 #define LENGTH 10 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; class Commit2EmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { fsal_status_t status; bool caller_perm_check = false; gtest::GaneshaFSALBaseTest::SetUp(); test_file_state = op_ctx->fsal_export->exp_ops.alloc_state( op_ctx->fsal_export, STATE_TYPE_SHARE, NULL); ASSERT_NE(test_file_state, nullptr); status = test_root->obj_ops->open2( test_root, test_file_state, FSAL_O_RDWR, FSAL_UNCHECKED, TEST_FILE, NULL, NULL, &test_file, NULL, &caller_perm_check, nullptr, nullptr); ASSERT_EQ(status.major, 0); } virtual void TearDown() { fsal_status_t status; status = test_file->obj_ops->close2(test_file, test_file_state); EXPECT_EQ(0, status.major); free_state(test_file_state); EXPECT_EQ(0, status.major); status = fsal_remove(test_root, TEST_FILE, NULL, NULL); EXPECT_EQ(status.major, 0); test_file->obj_ops->put_ref(test_file); test_file = NULL; gtest::GaneshaFSALBaseTest::TearDown(); } struct fsal_obj_handle *test_file = nullptr; struct state_t *test_file_state = nullptr; }; } /* namespace */ TEST_F(Commit2EmptyLatencyTest, SIMPLE) { fsal_status_t status; status = test_file->obj_ops->commit2(test_file, OFFSET, LENGTH); EXPECT_EQ(status.major, 0); } TEST_F(Commit2EmptyLatencyTest, SIMPLE_BYPASS) { fsal_status_t status; struct fsal_obj_handle *sub_hdl; sub_hdl = mdcdb_get_sub_handle(test_file); ASSERT_NE(sub_hdl, nullptr); status = sub_hdl->obj_ops->commit2(sub_hdl, OFFSET, LENGTH); EXPECT_EQ(status.major, 0); } TEST_F(Commit2EmptyLatencyTest, SMALL_UNSTABLE_WRITE) { fsal_status_t status; char *databuffer; struct fsal_io_arg *write_arg = (struct fsal_io_arg *)alloca( sizeof(*write_arg) + sizeof(struct iovec)); struct async_process_data write_data; int bytes = 64; databuffer = (char *)malloc(bytes); memset(databuffer, 'a', bytes); write_arg->info = NULL; write_arg->state = NULL; write_arg->offset = 0; write_arg->io_request = bytes; write_arg->iov_count = 1; write_arg->iov[0].iov_len = bytes; write_arg->iov[0].iov_base = databuffer; write_arg->io_amount = 0; write_arg->fsal_stable = false; write_data.ret.major = ERR_FSAL_NO_ERROR; write_data.ret.minor = 0; write_data.done = false; write_data.fsa_cond = &cond; write_data.fsa_mutex = &mutex; fsal_write(test_file, true, write_arg, &write_data); EXPECT_EQ(write_data.ret.major, 0); status = test_file->obj_ops->commit2(test_file, OFFSET, bytes); EXPECT_EQ(status.major, 0); free(databuffer); } TEST_F(Commit2EmptyLatencyTest, SMALL_STABLE_WRITE) { fsal_status_t status; char *databuffer; struct fsal_io_arg *write_arg = (struct fsal_io_arg *)alloca( sizeof(*write_arg) + sizeof(struct iovec)); struct async_process_data write_data; int bytes = 64; databuffer = (char *)malloc(bytes); memset(databuffer, 'a', bytes); write_arg->info = NULL; write_arg->state = NULL; write_arg->offset = 0; write_arg->io_request = bytes; write_arg->iov_count = 1; write_arg->iov[0].iov_len = bytes; write_arg->iov[0].iov_base = databuffer; write_arg->io_amount = 0; write_arg->fsal_stable = true; write_data.ret.major = ERR_FSAL_NO_ERROR; write_data.ret.minor = 0; write_data.done = false; write_data.fsa_cond = &cond; write_data.fsa_mutex = &mutex; fsal_write(test_file, true, write_arg, &write_data); EXPECT_EQ(write_data.ret.major, 0); status = test_file->obj_ops->commit2(test_file, OFFSET, bytes); EXPECT_EQ(status.major, 0); free(databuffer); } TEST_F(Commit2EmptyLatencyTest, LARGE_UNSTABLE_WRITE) { fsal_status_t status; char *databuffer; struct fsal_io_arg *write_arg = (struct fsal_io_arg *)alloca( sizeof(*write_arg) + sizeof(struct iovec)); struct async_process_data write_data; int bytes = (2 * 1024 * 1024); databuffer = (char *)malloc(bytes); memset(databuffer, 'a', bytes); write_arg->info = NULL; write_arg->state = NULL; write_arg->offset = 0; write_arg->io_request = bytes; write_arg->iov_count = 1; write_arg->iov[0].iov_len = bytes; write_arg->iov[0].iov_base = databuffer; write_arg->io_amount = 0; write_arg->fsal_stable = false; write_data.ret.major = ERR_FSAL_NO_ERROR; write_data.ret.minor = 0; write_data.done = false; write_data.fsa_cond = &cond; write_data.fsa_mutex = &mutex; fsal_write(test_file, true, write_arg, &write_data); EXPECT_EQ(write_data.ret.major, 0); status = test_file->obj_ops->commit2(test_file, OFFSET, bytes); EXPECT_EQ(status.major, 0); free(databuffer); } TEST_F(Commit2EmptyLatencyTest, LARGE_STABLE_WRITE) { fsal_status_t status; char *databuffer; struct fsal_io_arg *write_arg = (struct fsal_io_arg *)alloca( sizeof(*write_arg) + sizeof(struct iovec)); struct async_process_data write_data; int bytes = (2 * 1024 * 1024); databuffer = (char *)malloc(bytes); memset(databuffer, 'a', bytes); write_arg->info = NULL; write_arg->state = NULL; write_arg->offset = 0; write_arg->io_request = bytes; write_arg->iov_count = 1; write_arg->iov[0].iov_len = bytes; write_arg->iov[0].iov_base = databuffer; write_arg->io_amount = 0; write_arg->fsal_stable = true; write_data.ret.major = ERR_FSAL_NO_ERROR; write_data.ret.minor = 0; write_data.done = false; write_data.fsa_cond = &cond; write_data.fsa_mutex = &mutex; fsal_write(test_file, true, write_arg, &write_data); EXPECT_EQ(write_data.ret.major, 0); status = test_file->obj_ops->commit2(test_file, OFFSET, bytes); EXPECT_EQ(status.major, 0); free(databuffer); } TEST_F(Commit2EmptyLatencyTest, LOOP) { fsal_status_t status; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = test_file->obj_ops->commit2(test_file, OFFSET, LENGTH); ASSERT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per commit2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(Commit2EmptyLatencyTest, FSAL_COMMIT) { fsal_status_t status; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = fsal_commit(test_file, OFFSET, LENGTH); ASSERT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per fsal_commit: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_getattrs_latency.cc000066400000000000000000000202721473756622300242150ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "getattrs_latency" #define DIR_COUNT 100000 #define LOOP_COUNT 1000000 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class GetattrsEmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { gtest::GaneshaFSALBaseTest::SetUp(); } virtual void TearDown() { gtest::GaneshaFSALBaseTest::TearDown(); } }; class GetattrsFullLatencyTest : public GetattrsEmptyLatencyTest { protected: virtual void SetUp() { GetattrsEmptyLatencyTest::SetUp(); create_and_prime_many(DIR_COUNT, NULL); } virtual void TearDown() { remove_many(DIR_COUNT, NULL); GetattrsEmptyLatencyTest::TearDown(); } }; } /* namespace */ TEST_F(GetattrsEmptyLatencyTest, SIMPLE) { fsal_status_t status; struct fsal_attrlist outattrs; status = test_root->obj_ops->getattrs(test_root, &outattrs); EXPECT_EQ(status.major, 0); } TEST_F(GetattrsEmptyLatencyTest, SIMPLE_BYPASS) { fsal_status_t status; struct fsal_obj_handle *sub_hdl; struct fsal_attrlist outattrs; sub_hdl = mdcdb_get_sub_handle(test_root); ASSERT_NE(sub_hdl, nullptr); status = sub_hdl->obj_ops->getattrs(sub_hdl, &outattrs); EXPECT_EQ(status.major, 0); } TEST_F(GetattrsEmptyLatencyTest, GET_OPTIONAL_ATTRS) { fsal_status_t status; struct fsal_attrlist outattrs; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = get_optional_attrs(test_root, &outattrs); EXPECT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per get_optional_attrs: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(GetattrsFullLatencyTest, BIG_CACHED) { fsal_status_t status; struct fsal_attrlist outattrs; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = test_root->obj_ops->getattrs(test_root, &outattrs); ASSERT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per getattrs: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(GetattrsFullLatencyTest, BIG_UNCACHED) { fsal_status_t status; struct fsal_attrlist outattrs; struct fsal_obj_handle *obj[LOOP_COUNT]; char fname[NAMELEN]; struct timespec s_time, e_time; for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i % DIR_COUNT); status = test_root->obj_ops->lookup(test_root, fname, &obj[i], NULL); ASSERT_EQ(status.major, 0); ASSERT_NE(obj[i], nullptr); } now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = obj[i]->obj_ops->getattrs(obj[i], &outattrs); ASSERT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per getattrs: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); for (int i = 0; i < LOOP_COUNT; ++i) { obj[i]->obj_ops->put_ref(obj[i]); } } TEST_F(GetattrsFullLatencyTest, BIG_BYPASS_CACHED) { fsal_status_t status; struct fsal_obj_handle *sub_hdl; struct fsal_attrlist outattrs; struct timespec s_time, e_time; sub_hdl = mdcdb_get_sub_handle(test_root); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = sub_hdl->obj_ops->getattrs(sub_hdl, &outattrs); ASSERT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per getattrs: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(GetattrsFullLatencyTest, BIG_BYPASS_UNCACHED) { fsal_status_t status; struct fsal_obj_handle *sub_hdl[LOOP_COUNT]; struct fsal_attrlist outattrs; struct fsal_obj_handle *obj; char fname[NAMELEN]; struct timespec s_time, e_time; for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i % DIR_COUNT); status = test_root->obj_ops->lookup(test_root, fname, &obj, NULL); ASSERT_EQ(status.major, 0); ASSERT_NE(obj, nullptr); sub_hdl[i] = mdcdb_get_sub_handle(obj); obj->obj_ops->put_ref(obj); } now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = sub_hdl[i]->obj_ops->getattrs(sub_hdl[i], &outattrs); ASSERT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per getattrs: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_handle_to_key_latency.cc000066400000000000000000000154151473756622300251700ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "handle_to_key_latency" #define TEST_FILE "handle_to_key_latency_file" #define TEST_NODE "test_node" #define LOOP_COUNT 1000000 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class HandleToKeyEmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { fsal_status_t status; struct fsal_attrlist attrs_out; gtest::GaneshaFSALBaseTest::SetUp(); fsal_prepare_attrs(&attrs_out, 0); status = fsal_create(test_root, TEST_FILE, REGULAR_FILE, &attrs, NULL, &test_file, &attrs_out, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(test_file, nullptr); fsal_release_attrs(&attrs_out); } virtual void TearDown() { fsal_status_t status; status = fsal_remove(test_root, TEST_FILE, NULL, NULL); EXPECT_EQ(status.major, 0); test_file->obj_ops->put_ref(test_file); test_file = NULL; gtest::GaneshaFSALBaseTest::TearDown(); } struct fsal_obj_handle *test_file = nullptr; }; } /* namespace */ TEST_F(HandleToKeyEmptyLatencyTest, SIMPLE) { struct gsh_buffdesc fh_desc; fh_desc.addr = nullptr; fh_desc.len = 0; test_file->obj_ops->handle_to_key(test_file, &fh_desc); EXPECT_NE(fh_desc.addr, nullptr); EXPECT_NE(fh_desc.len, 0); } TEST_F(HandleToKeyEmptyLatencyTest, SIMPLE_BYPASS) { struct gsh_buffdesc fh_desc; struct fsal_obj_handle *sub_hdl; fh_desc.addr = nullptr; fh_desc.len = 0; sub_hdl = mdcdb_get_sub_handle(test_file); ASSERT_NE(sub_hdl, nullptr); sub_hdl->obj_ops->handle_to_key(sub_hdl, &fh_desc); EXPECT_NE(fh_desc.addr, nullptr); EXPECT_NE(fh_desc.len, 0); } TEST_F(HandleToKeyEmptyLatencyTest, LOOP) { struct gsh_buffdesc fh_desc; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { fh_desc.addr = nullptr; fh_desc.len = 0; test_file->obj_ops->handle_to_key(test_file, &fh_desc); EXPECT_NE(fh_desc.addr, nullptr); EXPECT_NE(fh_desc.len, 0); } now(&e_time); fprintf(stderr, "Average time per handle_to_key: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(HandleToKeyEmptyLatencyTest, LOOP_BYPASS) { struct gsh_buffdesc fh_desc; struct fsal_obj_handle *sub_hdl; struct timespec s_time, e_time; fh_desc.addr = nullptr; fh_desc.len = 0; sub_hdl = mdcdb_get_sub_handle(test_file); ASSERT_NE(sub_hdl, nullptr); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { fh_desc.addr = nullptr; fh_desc.len = 0; sub_hdl->obj_ops->handle_to_key(sub_hdl, &fh_desc); EXPECT_NE(fh_desc.addr, nullptr); EXPECT_NE(fh_desc.len, 0); } now(&e_time); fprintf(stderr, "Average time per handle_to_key: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_handle_to_wire_latency.cc000066400000000000000000000157601473756622300253510ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "handle_to_wire_latency" #define TEST_FILE "handle_to_wire_latency_file" #define TEST_NODE "test_node" #define LOOP_COUNT 1000000 #define BYTES 58 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class HandleToWireEmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { fsal_status_t status; struct fsal_attrlist attrs_out; gtest::GaneshaFSALBaseTest::SetUp(); fsal_prepare_attrs(&attrs_out, 0); status = fsal_create(test_root, TEST_FILE, REGULAR_FILE, &attrs, NULL, &test_file, &attrs_out, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(test_file, nullptr); fsal_release_attrs(&attrs_out); } virtual void TearDown() { fsal_status_t status; status = fsal_remove(test_root, TEST_FILE, NULL, NULL); EXPECT_EQ(status.major, 0); test_file->obj_ops->put_ref(test_file); test_file = NULL; gtest::GaneshaFSALBaseTest::TearDown(); } struct fsal_obj_handle *test_file = nullptr; }; } /* namespace */ TEST_F(HandleToWireEmptyLatencyTest, SIMPLE) { fsal_status_t status; struct gsh_buffdesc fh_desc; fh_desc.len = BYTES; fh_desc.addr = (void *)malloc(BYTES); status = test_file->obj_ops->handle_to_wire( test_file, FSAL_DIGEST_NFSV4, &fh_desc); EXPECT_EQ(status.major, 0); free(fh_desc.addr); } TEST_F(HandleToWireEmptyLatencyTest, SIMPLE_BYPASS) { fsal_status_t status; struct fsal_obj_handle *sub_hdl; struct gsh_buffdesc fh_desc; sub_hdl = mdcdb_get_sub_handle(test_file); ASSERT_NE(sub_hdl, nullptr); fh_desc.len = BYTES; fh_desc.addr = (void *)malloc(BYTES); status = sub_hdl->obj_ops->handle_to_wire(sub_hdl, FSAL_DIGEST_NFSV4, &fh_desc); EXPECT_EQ(status.major, 0); free(fh_desc.addr); } TEST_F(HandleToWireEmptyLatencyTest, LOOP) { fsal_status_t status; struct gsh_buffdesc fh_desc; struct timespec s_time, e_time; fh_desc.len = BYTES; fh_desc.addr = (void *)malloc(BYTES); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = test_file->obj_ops->handle_to_wire( test_file, FSAL_DIGEST_NFSV4, &fh_desc); EXPECT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per handle_to_wire: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); free(fh_desc.addr); } TEST_F(HandleToWireEmptyLatencyTest, LOOP_BYPASS) { fsal_status_t status; struct fsal_obj_handle *sub_hdl; struct gsh_buffdesc fh_desc; struct timespec s_time, e_time; sub_hdl = mdcdb_get_sub_handle(test_file); ASSERT_NE(sub_hdl, nullptr); fh_desc.len = BYTES; fh_desc.addr = (void *)malloc(BYTES); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = sub_hdl->obj_ops->handle_to_wire( sub_hdl, FSAL_DIGEST_NFSV4, &fh_desc); ASSERT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per handle_to_wire: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); free(fh_desc.addr); } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_link_latency.cc000066400000000000000000000246301473756622300233170ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "link_latency" #define TEST_FILE "link_source" #define TEST_FILE_LINK "link_to_link_source" #define DIR_COUNT 100000 #define LOOP_COUNT 1000000 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class LinkEmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { fsal_status_t status; struct fsal_attrlist attrs_out; gtest::GaneshaFSALBaseTest::SetUp(); fsal_prepare_attrs(&attrs_out, 0); status = fsal_create(test_root, TEST_FILE, REGULAR_FILE, &attrs, NULL, &test_file, &attrs_out, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(test_file, nullptr); fsal_release_attrs(&attrs_out); } virtual void TearDown() { fsal_status_t status; status = fsal_remove(test_root, TEST_FILE, NULL, NULL); EXPECT_EQ(status.major, 0); test_file->obj_ops->put_ref(test_file); test_file = NULL; gtest::GaneshaFSALBaseTest::TearDown(); } struct fsal_obj_handle *test_file = nullptr; }; class LinkFullLatencyTest : public LinkEmptyLatencyTest { protected: virtual void SetUp() { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *obj; struct fsal_attrlist attrs_out; LinkEmptyLatencyTest::SetUp(); /* create a bunch of dirents */ for (int i = 0; i < DIR_COUNT; ++i) { fsal_prepare_attrs(&attrs_out, 0); sprintf(fname, "file-%08x", i); status = fsal_create(test_root, fname, REGULAR_FILE, &attrs, NULL, &obj, &attrs_out, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(obj, nullptr); fsal_release_attrs(&attrs_out); obj->obj_ops->put_ref(obj); } } virtual void TearDown() { fsal_status_t status; char fname[NAMELEN]; for (int i = 0; i < DIR_COUNT; ++i) { sprintf(fname, "file-%08x", i); status = fsal_remove(test_root, fname, NULL, NULL); EXPECT_EQ(status.major, 0); } LinkEmptyLatencyTest::TearDown(); } }; } /* namespace */ TEST_F(LinkEmptyLatencyTest, SIMPLE) { fsal_status_t status; struct fsal_obj_handle *link; struct fsal_obj_handle *lookup; enableEvents(event_list); status = test_file->obj_ops->link(test_file, test_root, TEST_FILE_LINK, nullptr, nullptr); EXPECT_EQ(status.major, 0); test_root->obj_ops->lookup(test_root, TEST_FILE_LINK, &link, NULL); test_root->obj_ops->lookup(test_root, TEST_FILE, &lookup, NULL); EXPECT_EQ(lookup, link); link->obj_ops->put_ref(link); lookup->obj_ops->put_ref(lookup); /* Remove link created while running test */ status = fsal_remove(test_root, TEST_FILE_LINK, NULL, NULL); ASSERT_EQ(status.major, 0); disableEvents(event_list); } TEST_F(LinkEmptyLatencyTest, SIMPLE_BYPASS) { fsal_status_t status; struct fsal_obj_handle *sub_root; struct fsal_obj_handle *sub_file; struct fsal_obj_handle *link; struct fsal_obj_handle *lookup; sub_root = mdcdb_get_sub_handle(test_root); ASSERT_NE(sub_root, nullptr); sub_file = mdcdb_get_sub_handle(test_file); ASSERT_NE(sub_file, nullptr); status = sub_root->obj_ops->link(sub_file, sub_root, TEST_FILE_LINK, nullptr, nullptr); EXPECT_EQ(status.major, 0); sub_root->obj_ops->lookup(sub_root, TEST_FILE_LINK, &link, NULL); sub_root->obj_ops->lookup(sub_root, TEST_FILE, &lookup, NULL); EXPECT_EQ(lookup, link); link->obj_ops->put_ref(link); lookup->obj_ops->put_ref(lookup); /* Remove link created while running test */ status = fsal_remove(sub_root, TEST_FILE_LINK, NULL, NULL); ASSERT_EQ(status.major, 0); } TEST_F(LinkEmptyLatencyTest, LOOP) { fsal_status_t status; char fname[NAMELEN]; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "link-%08x", i); status = test_root->obj_ops->link(test_file, test_root, fname, nullptr, nullptr); EXPECT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per link: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove link created while running test */ for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "link-%08x", i); status = fsal_remove(test_root, fname, NULL, NULL); ASSERT_EQ(status.major, 0); } } TEST_F(LinkEmptyLatencyTest, FSALLINK) { fsal_status_t status; char fname[NAMELEN]; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "link-%08x", i); status = fsal_link(test_file, test_root, fname, NULL, NULL); EXPECT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per fsal_link: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove link created while running test */ for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "link-%08x", i); status = fsal_remove(test_root, fname, NULL, NULL); ASSERT_EQ(status.major, 0); } } TEST_F(LinkFullLatencyTest, BIG) { fsal_status_t status; char fname[NAMELEN]; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "link-%08x", i); status = test_root->obj_ops->link(test_file, test_root, fname, nullptr, nullptr); ASSERT_EQ(status.major, 0) << " failed to link " << fname; } now(&e_time); fprintf(stderr, "Average time per link: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove link created while running test */ for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "link-%08x", i); status = fsal_remove(test_root, fname, NULL, NULL); ASSERT_EQ(status.major, 0); } } TEST_F(LinkFullLatencyTest, BIG_BYPASS) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *sub_root; struct fsal_obj_handle *sub_file; struct timespec s_time, e_time; sub_root = mdcdb_get_sub_handle(test_root); ASSERT_NE(sub_root, nullptr); sub_file = mdcdb_get_sub_handle(test_file); ASSERT_NE(sub_file, nullptr); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "link-%08x", i); status = sub_root->obj_ops->link(sub_file, sub_root, fname, nullptr, nullptr); ASSERT_EQ(status.major, 0) << " failed to link " << fname; } now(&e_time); fprintf(stderr, "Average time per link: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove link created while running test */ for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "link-%08x", i); status = fsal_remove(sub_root, fname, NULL, NULL); ASSERT_EQ(status.major, 0); } } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_lock_op2_latency.cc000066400000000000000000000162351473756622300240740ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "lock_op2_latency" #define TEST_FILE "lock_op2_test_file" #define TEST_NODE "test_node" #define LOOP_COUNT 1000000 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class Lockop2EmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { fsal_status_t status; struct fsal_attrlist attrs_out; gtest::GaneshaFSALBaseTest::SetUp(); fsal_prepare_attrs(&attrs_out, 0); status = fsal_create(test_root, TEST_FILE, REGULAR_FILE, &attrs, NULL, &test_file, &attrs_out, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(test_file, nullptr); fsal_release_attrs(&attrs_out); } virtual void TearDown() { fsal_status_t status; status = fsal_remove(test_root, TEST_FILE, NULL, NULL); EXPECT_EQ(status.major, 0); test_file->obj_ops->put_ref(test_file); test_file = NULL; gtest::GaneshaFSALBaseTest::TearDown(); } struct fsal_obj_handle *test_file = nullptr; }; } /* namespace */ TEST_F(Lockop2EmptyLatencyTest, SIMPLE) { fsal_status_t status; fsal_lock_param_t request_lock; request_lock.lock_type = FSAL_LOCK_W; request_lock.lock_sle_type = FSAL_POSIX_LOCK; request_lock.lock_reclaim = true; status = test_file->obj_ops->lock_op2( test_file, NULL, NULL, FSAL_OP_LOCK, &request_lock, NULL); EXPECT_EQ(status.major, 0); } TEST_F(Lockop2EmptyLatencyTest, SIMPLE_BYPASS) { fsal_status_t status; struct fsal_obj_handle *sub_hdl; fsal_lock_param_t request_lock; sub_hdl = mdcdb_get_sub_handle(test_file); ASSERT_NE(sub_hdl, nullptr); request_lock.lock_type = FSAL_LOCK_W; request_lock.lock_sle_type = FSAL_POSIX_LOCK; request_lock.lock_reclaim = true; status = sub_hdl->obj_ops->lock_op2(sub_hdl, NULL, NULL, FSAL_OP_LOCK, &request_lock, NULL); EXPECT_EQ(status.major, 0); } TEST_F(Lockop2EmptyLatencyTest, LOOP) { fsal_status_t status; fsal_lock_param_t request_lock; struct timespec s_time, e_time; request_lock.lock_type = FSAL_LOCK_W; request_lock.lock_sle_type = FSAL_POSIX_LOCK; request_lock.lock_reclaim = true; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = test_file->obj_ops->lock_op2(test_file, NULL, NULL, FSAL_OP_LOCK, &request_lock, NULL); EXPECT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per lock_op2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(Lockop2EmptyLatencyTest, LOOP_BYPASS) { fsal_status_t status; struct fsal_obj_handle *sub_hdl; fsal_lock_param_t request_lock; struct timespec s_time, e_time; sub_hdl = mdcdb_get_sub_handle(test_file); ASSERT_NE(sub_hdl, nullptr); request_lock.lock_type = FSAL_LOCK_W; request_lock.lock_sle_type = FSAL_POSIX_LOCK; request_lock.lock_reclaim = true; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = sub_hdl->obj_ops->lock_op2( sub_hdl, NULL, NULL, FSAL_OP_LOCK, &request_lock, NULL); ASSERT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per lock_op2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_lookup_latency.cc000066400000000000000000000214731473756622300236750ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Daniel Gryniewicz * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "lookup_latency" #define FILE_COUNT 100000 #define LOOP_COUNT 1000000 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class LookupEmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { gtest::GaneshaFSALBaseTest::SetUp(); } virtual void TearDown() { gtest::GaneshaFSALBaseTest::TearDown(); } }; class LookupFullLatencyTest : public LookupEmptyLatencyTest { protected: virtual void SetUp() { LookupEmptyLatencyTest::SetUp(); create_and_prime_many(FILE_COUNT, NULL); } virtual void TearDown() { remove_many(FILE_COUNT, NULL); LookupEmptyLatencyTest::TearDown(); } }; } /* namespace */ TEST_F(LookupEmptyLatencyTest, SIMPLE) { fsal_status_t status; struct fsal_obj_handle *lookup; enableEvents(event_list); status = root_entry->obj_ops->lookup(root_entry, TEST_ROOT, &lookup, NULL); EXPECT_EQ(status.major, 0); EXPECT_EQ(test_root, lookup); disableEvents(event_list); lookup->obj_ops->put_ref(lookup); } TEST_F(LookupEmptyLatencyTest, SIMPLE_BYPASS) { fsal_status_t status; struct fsal_obj_handle *sub_hdl; struct fsal_obj_handle *lookup; enableEvents(event_list); sub_hdl = mdcdb_get_sub_handle(root_entry); ASSERT_NE(sub_hdl, nullptr); status = sub_hdl->obj_ops->lookup(sub_hdl, TEST_ROOT, &lookup, NULL); EXPECT_EQ(status.major, 0); EXPECT_EQ(mdcdb_get_sub_handle(test_root), lookup); disableEvents(event_list); lookup->obj_ops->put_ref(lookup); } TEST_F(LookupEmptyLatencyTest, LOOP) { fsal_status_t status; struct fsal_obj_handle *lookup; struct timespec s_time, e_time; enableEvents(event_list); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = root_entry->obj_ops->lookup(root_entry, TEST_ROOT, &lookup, NULL); EXPECT_EQ(status.major, 0); EXPECT_EQ(test_root, lookup); } now(&e_time); disableEvents(event_list); /* Have the put_ref()'s outside the latency loop */ for (int i = 0; i < LOOP_COUNT; ++i) { lookup->obj_ops->put_ref(lookup); } fprintf(stderr, "Average time per lookup: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(LookupEmptyLatencyTest, FSALLOOKUP) { fsal_status_t status; struct fsal_obj_handle *lookup; struct timespec s_time, e_time; enableEvents(event_list); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = fsal_lookup(root_entry, TEST_ROOT, &lookup, NULL); EXPECT_EQ(status.major, 0); EXPECT_EQ(test_root, lookup); } now(&e_time); disableEvents(event_list); /* Have the put_ref()'s outside the latency loop */ for (int i = 0; i < LOOP_COUNT; ++i) { lookup->obj_ops->put_ref(lookup); } fprintf(stderr, "Average time per fsal_lookup: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(LookupFullLatencyTest, BIG_SINGLE) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *obj; struct timespec s_time, e_time; enableEvents(event_list); now(&s_time); sprintf(fname, "f-%08x", FILE_COUNT / 5); status = test_root->obj_ops->lookup(test_root, fname, &obj, NULL); ASSERT_EQ(status.major, 0) << " failed to lookup " << fname; obj->obj_ops->put_ref(obj); now(&e_time); disableEvents(event_list); fprintf(stderr, "Average time per lookup: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time)); } TEST_F(LookupFullLatencyTest, BIG) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *obj; struct timespec s_time, e_time; enableEvents(event_list); if (profile_out) ProfilerStart(profile_out); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i % FILE_COUNT); status = test_root->obj_ops->lookup(test_root, fname, &obj, NULL); ASSERT_EQ(status.major, 0) << " failed to lookup " << fname; obj->obj_ops->put_ref(obj); } now(&e_time); if (profile_out) ProfilerStop(); disableEvents(event_list); fprintf(stderr, "Average time per lookup: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(LookupFullLatencyTest, BIG_BYPASS) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *sub_hdl; struct fsal_obj_handle *obj; struct timespec s_time, e_time; sub_hdl = mdcdb_get_sub_handle(test_root); enableEvents(event_list); if (profile_out) ProfilerStart(profile_out); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i % FILE_COUNT); status = sub_hdl->obj_ops->lookup(sub_hdl, fname, &obj, NULL); ASSERT_EQ(status.major, 0) << " failed to lookup " << fname; obj->obj_ops->put_ref(obj); } now(&e_time); if (profile_out) ProfilerStop(); disableEvents(event_list); fprintf(stderr, "Average time per lookup: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_mkdir_latency.cc000066400000000000000000000236511473756622300234720ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "mkdir_latency" #define FILE_COUNT 100000 #define LOOP_COUNT 1000000 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class MkdirEmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { gtest::GaneshaFSALBaseTest::SetUp(); } virtual void TearDown() { gtest::GaneshaFSALBaseTest::TearDown(); } }; class MkdirFullLatencyTest : public MkdirEmptyLatencyTest { protected: virtual void SetUp() { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *obj; struct fsal_attrlist attrs_out; MkdirEmptyLatencyTest::SetUp(); /* create a bunch of dirents */ for (int i = 0; i < FILE_COUNT; ++i) { fsal_prepare_attrs(&attrs_out, 0); sprintf(fname, "f-%08x", i); status = fsal_create(test_root, fname, REGULAR_FILE, &attrs, NULL, &obj, &attrs_out, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(obj, nullptr); fsal_release_attrs(&attrs_out); obj->obj_ops->put_ref(obj); } } virtual void TearDown() { fsal_status_t status; char fname[NAMELEN]; for (int i = 0; i < FILE_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = fsal_remove(test_root, fname, NULL, NULL); EXPECT_EQ(status.major, 0); } MkdirEmptyLatencyTest::TearDown(); } }; } /* namespace */ TEST_F(MkdirEmptyLatencyTest, SIMPLE) { fsal_status_t status; struct fsal_obj_handle *mkdir; struct fsal_obj_handle *lookup; status = test_root->obj_ops->mkdir(test_root, TEST_ROOT, &attrs, &mkdir, NULL, nullptr, nullptr); EXPECT_EQ(status.major, 0); test_root->obj_ops->lookup(test_root, TEST_ROOT, &lookup, NULL); EXPECT_EQ(lookup, mkdir); mkdir->obj_ops->put_ref(mkdir); lookup->obj_ops->put_ref(lookup); /* Remove directory created while running test */ status = fsal_remove(test_root, TEST_ROOT, NULL, NULL); ASSERT_EQ(status.major, 0); } TEST_F(MkdirEmptyLatencyTest, SIMPLE_BYPASS) { fsal_status_t status; struct fsal_obj_handle *sub_hdl; struct fsal_obj_handle *mkdir; struct fsal_obj_handle *lookup; sub_hdl = mdcdb_get_sub_handle(test_root); ASSERT_NE(sub_hdl, nullptr); gtws_subcall(status = sub_hdl->obj_ops->mkdir(sub_hdl, TEST_ROOT, &attrs, &mkdir, NULL, nullptr, nullptr)); EXPECT_EQ(status.major, 0); sub_hdl->obj_ops->lookup(sub_hdl, TEST_ROOT, &lookup, NULL); EXPECT_EQ(lookup, mkdir); lookup->obj_ops->put_ref(lookup); /* Remove directory created while running test */ status = sub_hdl->obj_ops->unlink(sub_hdl, mkdir, TEST_ROOT, nullptr, nullptr); ASSERT_EQ(status.major, 0); mkdir->obj_ops->put_ref(mkdir); } TEST_F(MkdirEmptyLatencyTest, LOOP) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *obj; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "d-%08x", i); status = test_root->obj_ops->mkdir( test_root, fname, &attrs, &obj, NULL, nullptr, nullptr); EXPECT_EQ(status.major, 0); obj->obj_ops->put_ref(obj); } now(&e_time); fprintf(stderr, "Average time per mkdir: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove directories created while running test */ for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "d-%08x", i); status = fsal_remove(test_root, fname, NULL, NULL); ASSERT_EQ(status.major, 0); } } TEST_F(MkdirEmptyLatencyTest, FSALCREATE) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *obj; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "d-%08x", i); status = fsal_create(test_root, fname, DIRECTORY, &attrs, NULL, &obj, NULL, nullptr, nullptr); EXPECT_EQ(status.major, 0); obj->obj_ops->put_ref(obj); } now(&e_time); fprintf(stderr, "Average time per fsal_create: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove directories created while running test */ for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "d-%08x", i); status = fsal_remove(test_root, fname, NULL, NULL); ASSERT_EQ(status.major, 0); } } TEST_F(MkdirFullLatencyTest, BIG) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *obj; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "d-%08x", i); status = test_root->obj_ops->mkdir( test_root, fname, &attrs, &obj, NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0) << " failed to mkdir " << fname; obj->obj_ops->put_ref(obj); } now(&e_time); fprintf(stderr, "Average time per mkdir: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove directories created while running test */ for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "d-%08x", i); status = fsal_remove(test_root, fname, NULL, NULL); ASSERT_EQ(status.major, 0); } } TEST_F(MkdirFullLatencyTest, BIG_BYPASS) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *sub_hdl; struct fsal_obj_handle *obj; struct timespec s_time, e_time; sub_hdl = mdcdb_get_sub_handle(test_root); ASSERT_NE(sub_hdl, nullptr); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "d-%08x", i); gtws_subcall(status = sub_hdl->obj_ops->mkdir( sub_hdl, fname, &attrs, &obj, NULL, nullptr, nullptr)); ASSERT_EQ(status.major, 0) << " failed to mkdir " << fname; obj->obj_ops->put_ref(obj); } now(&e_time); fprintf(stderr, "Average time per mkdir: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove directories created while running test */ for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "d-%08x", i); sub_hdl->obj_ops->lookup(sub_hdl, fname, &obj, NULL); status = sub_hdl->obj_ops->unlink(sub_hdl, obj, fname, nullptr, nullptr); ASSERT_EQ(status.major, 0); obj->obj_ops->put_ref(obj); } } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_mknode_latency.cc000066400000000000000000000240371473756622300236400ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "mknode_latency" #define TEST_NODE "test_node" #define FILE_COUNT 100000 #define LOOP_COUNT 1000000 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class MknodeEmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { gtest::GaneshaFSALBaseTest::SetUp(); } virtual void TearDown() { gtest::GaneshaFSALBaseTest::TearDown(); } }; class MknodeFullLatencyTest : public MknodeEmptyLatencyTest { protected: virtual void SetUp() { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *obj; struct fsal_attrlist attrs_out; MknodeEmptyLatencyTest::SetUp(); /* create a bunch of dirents */ for (int i = 0; i < FILE_COUNT; ++i) { fsal_prepare_attrs(&attrs_out, 0); sprintf(fname, "f-%08x", i); status = fsal_create(test_root, fname, REGULAR_FILE, &attrs, NULL, &obj, &attrs_out, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(obj, nullptr); fsal_release_attrs(&attrs_out); obj->obj_ops->put_ref(obj); } } virtual void TearDown() { fsal_status_t status; char fname[NAMELEN]; for (int i = 0; i < FILE_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = fsal_remove(test_root, fname, NULL, NULL); EXPECT_EQ(status.major, 0); } MknodeEmptyLatencyTest::TearDown(); } }; } /* namespace */ TEST_F(MknodeEmptyLatencyTest, SIMPLE) { fsal_status_t status; struct fsal_obj_handle *mknode; struct fsal_obj_handle *lookup; status = test_root->obj_ops->mknode(test_root, TEST_NODE, SOCKET_FILE, &attrs, &mknode, NULL, nullptr, nullptr); EXPECT_EQ(status.major, 0); test_root->obj_ops->lookup(test_root, TEST_NODE, &lookup, NULL); EXPECT_EQ(lookup, mknode); mknode->obj_ops->put_ref(mknode); lookup->obj_ops->put_ref(lookup); /* Remove node created while running test */ status = fsal_remove(test_root, TEST_NODE, NULL, NULL); ASSERT_EQ(status.major, 0); } TEST_F(MknodeEmptyLatencyTest, SIMPLE_BYPASS) { fsal_status_t status; struct fsal_obj_handle *sub_hdl; struct fsal_obj_handle *mknode; struct fsal_obj_handle *lookup; sub_hdl = mdcdb_get_sub_handle(test_root); ASSERT_NE(sub_hdl, nullptr); status = nfs_export_get_root_entry(a_export, &sub_hdl); ASSERT_EQ(status.major, 0); status = sub_hdl->obj_ops->mknode(sub_hdl, TEST_NODE, SOCKET_FILE, &attrs, &mknode, NULL, nullptr, nullptr); EXPECT_EQ(status.major, 0); sub_hdl->obj_ops->lookup(sub_hdl, TEST_NODE, &lookup, NULL); EXPECT_EQ(lookup, mknode); mknode->obj_ops->put_ref(mknode); lookup->obj_ops->put_ref(lookup); /* Remove node created while running test */ status = fsal_remove(sub_hdl, TEST_NODE, NULL, NULL); ASSERT_EQ(status.major, 0); } TEST_F(MknodeEmptyLatencyTest, LOOP) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *obj; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "d-%08x", i); status = test_root->obj_ops->mknode(test_root, fname, SOCKET_FILE, &attrs, &obj, NULL, nullptr, nullptr); EXPECT_EQ(status.major, 0); obj->obj_ops->put_ref(obj); } now(&e_time); fprintf(stderr, "Average time per mknode: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove nodes created while running test */ for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "d-%08x", i); status = fsal_remove(test_root, fname, NULL, NULL); ASSERT_EQ(status.major, 0); } } TEST_F(MknodeEmptyLatencyTest, FSALCREATE) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *obj; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "d-%08x", i); status = fsal_create(test_root, fname, SOCKET_FILE, &attrs, NULL, &obj, NULL, nullptr, nullptr); EXPECT_EQ(status.major, 0); obj->obj_ops->put_ref(obj); } now(&e_time); fprintf(stderr, "Average time per fsal_create: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove nodes created while running test */ for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "d-%08x", i); status = fsal_remove(test_root, fname, NULL, NULL); ASSERT_EQ(status.major, 0); } } TEST_F(MknodeFullLatencyTest, BIG) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *obj; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "d-%08x", i); status = test_root->obj_ops->mknode(test_root, fname, SOCKET_FILE, &attrs, &obj, NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0) << " failed to mknnode " << fname; obj->obj_ops->put_ref(obj); } now(&e_time); fprintf(stderr, "Average time per mknode: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove nodes created while running test */ for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "d-%08x", i); status = fsal_remove(test_root, fname, NULL, NULL); ASSERT_EQ(status.major, 0); } } TEST_F(MknodeFullLatencyTest, BIG_BYPASS) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *sub_hdl; struct fsal_obj_handle *obj; struct timespec s_time, e_time; sub_hdl = mdcdb_get_sub_handle(test_root); ASSERT_NE(sub_hdl, nullptr); status = nfs_export_get_root_entry(a_export, &sub_hdl); ASSERT_EQ(status.major, 0); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "d-%08x", i); status = sub_hdl->obj_ops->mknode(sub_hdl, fname, SOCKET_FILE, &attrs, &obj, NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0) << " failed to mknode " << fname; obj->obj_ops->put_ref(obj); } now(&e_time); fprintf(stderr, "Average time per mknode: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove nodes created while running test */ for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "d-%08x", i); status = fsal_remove(sub_hdl, fname, NULL, NULL); ASSERT_EQ(status.major, 0); } } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_open2_latency.cc000066400000000000000000000261301473756622300234020ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "open2_latency" #define TEST_FILE "open2_latency_file" #define LOOP_COUNT 100000 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class Open2EmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { gtest::GaneshaFSALBaseTest::SetUp(); fsal_prepare_attrs(&attrs_in, 0); } virtual void TearDown() { fsal_release_attrs(&attrs_in); gtest::GaneshaFSALBaseTest::TearDown(); } struct fsal_attrlist attrs_in; }; class Open2LoopLatencyTest : public Open2EmptyLatencyTest { protected: virtual void SetUp() { Open2EmptyLatencyTest::SetUp(); for (int i = 0; i < LOOP_COUNT; ++i) { file_state[i] = op_ctx->fsal_export->exp_ops.alloc_state( op_ctx->fsal_export, STATE_TYPE_SHARE, NULL); ASSERT_NE(file_state[i], nullptr); } } virtual void TearDown() { for (int i = 0; i < LOOP_COUNT; ++i) { free_state(file_state[i]); } Open2EmptyLatencyTest::TearDown(); } struct fsal_obj_handle *obj[LOOP_COUNT]; struct state_t *file_state[LOOP_COUNT]; }; } /* namespace */ TEST_F(Open2EmptyLatencyTest, SIMPLE) { fsal_status_t status; struct fsal_obj_handle *obj; bool caller_perm_check = false; struct state_t *file_state; file_state = op_ctx->fsal_export->exp_ops.alloc_state( op_ctx->fsal_export, STATE_TYPE_SHARE, NULL); ASSERT_NE(file_state, nullptr); // create and open a file for test status = test_root->obj_ops->open2(test_root, file_state, FSAL_O_RDWR, FSAL_UNCHECKED, TEST_FILE, &attrs_in, NULL, &obj, NULL, &caller_perm_check, nullptr, nullptr); ASSERT_EQ(status.major, 0); // close and delete the file created for test status = obj->obj_ops->close2(obj, file_state); EXPECT_EQ(status.major, 0); status = fsal_remove(test_root, TEST_FILE, NULL, NULL); ASSERT_EQ(status.major, 0); obj->obj_ops->put_ref(obj); free_state(file_state); } TEST_F(Open2EmptyLatencyTest, SIMPLE_BYPASS) { fsal_status_t status; struct fsal_obj_handle *obj; bool caller_perm_check = false; struct state_t *file_state; struct fsal_obj_handle *sub_hdl; file_state = op_ctx->fsal_export->exp_ops.alloc_state( op_ctx->fsal_export, STATE_TYPE_SHARE, NULL); ASSERT_NE(file_state, nullptr); sub_hdl = mdcdb_get_sub_handle(test_root); ASSERT_NE(sub_hdl, nullptr); status = nfs_export_get_root_entry(a_export, &sub_hdl); ASSERT_EQ(status.major, 0); // create and open a file for test status = sub_hdl->obj_ops->open2(sub_hdl, file_state, FSAL_O_RDWR, FSAL_UNCHECKED, TEST_FILE, &attrs_in, NULL, &obj, NULL, &caller_perm_check, nullptr, nullptr); ASSERT_EQ(status.major, 0); // close and delete the file created for test status = obj->obj_ops->close2(obj, file_state); EXPECT_EQ(status.major, 0); status = fsal_remove(sub_hdl, TEST_FILE, NULL, NULL); ASSERT_EQ(status.major, 0); obj->obj_ops->put_ref(obj); free_state(file_state); } TEST_F(Open2LoopLatencyTest, FSAL_OPEN2) { fsal_status_t status; char fname[NAMELEN]; struct timespec s_time, e_time; now(&s_time); // create and open a files for test for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = fsal_open2(test_root, file_state[i], FSAL_O_RDWR, FSAL_UNCHECKED, fname, &attrs_in, NULL, &obj[i], NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per fsal_open2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); // close and delete the files created for test for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = fsal_close(obj[i]); EXPECT_EQ(status.major, 0); status = fsal_remove(test_root, fname, NULL, NULL); ASSERT_EQ(status.major, 0); obj[i]->obj_ops->put_ref(obj[i]); } } TEST_F(Open2LoopLatencyTest, LOOP) { fsal_status_t status; char fname[NAMELEN]; bool caller_perm_check = false; struct timespec s_time, e_time; now(&s_time); // create and open a files for test for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = test_root->obj_ops->open2( test_root, file_state[i], FSAL_O_RDWR, FSAL_UNCHECKED, fname, &attrs_in, NULL, &obj[i], NULL, &caller_perm_check, nullptr, nullptr); ASSERT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per open2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); // close and delete the files created for test for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = obj[i]->obj_ops->close2(obj[i], file_state[i]); EXPECT_EQ(status.major, 0); status = fsal_remove(test_root, fname, NULL, NULL); ASSERT_EQ(status.major, 0); obj[i]->obj_ops->put_ref(obj[i]); } } TEST_F(Open2LoopLatencyTest, LOOP_BYPASS) { fsal_status_t status; struct fsal_obj_handle *sub_hdl; char fname[NAMELEN]; bool caller_perm_check = false; struct timespec s_time, e_time; sub_hdl = mdcdb_get_sub_handle(test_root); ASSERT_NE(sub_hdl, nullptr); status = nfs_export_get_root_entry(a_export, &sub_hdl); ASSERT_EQ(status.major, 0); now(&s_time); // create and open a files for test for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = sub_hdl->obj_ops->open2( sub_hdl, file_state[i], FSAL_O_RDWR, FSAL_UNCHECKED, fname, &attrs_in, NULL, &obj[i], NULL, &caller_perm_check, nullptr, nullptr); ASSERT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per open2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); // close and delete the files created for test for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = obj[i]->obj_ops->close2(obj[i], file_state[i]); EXPECT_EQ(status.major, 0); status = fsal_remove(sub_hdl, fname, NULL, NULL); ASSERT_EQ(status.major, 0); obj[i]->obj_ops->put_ref(obj[i]); } } TEST_F(Open2LoopLatencyTest, OPEN_ONLY) { fsal_status_t status; char fname[NAMELEN]; bool caller_perm_check = false; struct timespec s_time, e_time; // create files for test for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = fsal_create(root_entry, fname, REGULAR_FILE, &attrs, NULL, &obj[i], NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(obj[i], nullptr); obj[i]->obj_ops->put_ref(obj[i]); } now(&s_time); // open a files for test for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = test_root->obj_ops->open2( test_root, file_state[i], FSAL_O_RDWR, FSAL_UNCHECKED, fname, &attrs_in, NULL, &obj[i], NULL, &caller_perm_check, nullptr, nullptr); ASSERT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per open2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); // close and delete the files created for test for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = obj[i]->obj_ops->close2(obj[i], file_state[i]); EXPECT_EQ(status.major, 0); status = fsal_remove(test_root, fname, NULL, NULL); ASSERT_EQ(status.major, 0); obj[i]->obj_ops->put_ref(obj[i]); } } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_read2_latency.cc000066400000000000000000000333721473756622300233620ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "read2_latency" #define TEST_FILE "read2_latency_file" #define LOOP_COUNT 1000000 #define OFFSET 0 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; class Read2EmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { fsal_status_t status; bool caller_perm_check = false; gtest::GaneshaFSALBaseTest::SetUp(); test_file_state = op_ctx->fsal_export->exp_ops.alloc_state( op_ctx->fsal_export, STATE_TYPE_SHARE, NULL); ASSERT_NE(test_file_state, nullptr); status = test_root->obj_ops->open2( test_root, test_file_state, FSAL_O_RDWR, FSAL_UNCHECKED, TEST_FILE, &attrs, NULL, &test_file, NULL, &caller_perm_check, nullptr, nullptr); ASSERT_EQ(status.major, 0); } virtual void TearDown() { fsal_status_t status; status = test_file->obj_ops->close2(test_file, test_file_state); EXPECT_EQ(0, status.major); free_state(test_file_state); status = fsal_remove(test_root, TEST_FILE, NULL, NULL); EXPECT_EQ(status.major, 0); test_file->obj_ops->put_ref(test_file); test_file = NULL; gtest::GaneshaFSALBaseTest::TearDown(); } struct fsal_obj_handle *test_file = nullptr; struct state_t *test_file_state; }; } /* namespace */ TEST_F(Read2EmptyLatencyTest, SIMPLE) { char *w_databuffer; char *r_databuffer; struct fsal_io_arg *write_arg; struct fsal_io_arg *read_arg; struct async_process_data io_data; int bytes = 64; int ret = -1; w_databuffer = (char *)malloc(bytes); memset(w_databuffer, 'a', bytes); write_arg = (struct fsal_io_arg *)alloca(sizeof(struct fsal_io_arg) + sizeof(struct iovec)); write_arg->info = NULL; write_arg->state = NULL; write_arg->offset = OFFSET; write_arg->io_request = bytes; write_arg->iov_count = 1; write_arg->iov[0].iov_len = bytes; write_arg->iov[0].iov_base = w_databuffer; write_arg->io_amount = 0; write_arg->fsal_stable = false; io_data.ret.major = ERR_FSAL_NO_ERROR; io_data.ret.minor = 0; io_data.done = false; io_data.fsa_cond = &cond; io_data.fsa_mutex = &mutex; fsal_write(test_file, true, write_arg, &io_data); EXPECT_EQ(io_data.ret.major, 0); r_databuffer = (char *)malloc(bytes); read_arg = (struct fsal_io_arg *)alloca(sizeof(struct fsal_io_arg) + sizeof(struct iovec)); read_arg->info = NULL; read_arg->state = NULL; read_arg->offset = OFFSET; read_arg->iov_count = 1; read_arg->iov[0].iov_len = bytes; read_arg->iov[0].iov_base = r_databuffer; read_arg->io_amount = 0; io_data.ret.major = ERR_FSAL_NO_ERROR; io_data.ret.minor = 0; io_data.done = false; io_data.fsa_cond = &cond; io_data.fsa_mutex = &mutex; fsal_read(test_file, true, read_arg, &io_data); EXPECT_EQ(io_data.ret.major, 0); ret = memcmp(r_databuffer, w_databuffer, bytes); EXPECT_EQ(ret, 0); free(w_databuffer); free(r_databuffer); } TEST_F(Read2EmptyLatencyTest, SIMPLE_BYPASS) { struct fsal_obj_handle *sub_hdl; char *w_databuffer; char *r_databuffer; struct fsal_io_arg *write_arg; struct fsal_io_arg *read_arg; struct async_process_data io_data; int bytes = 64; w_databuffer = (char *)malloc(bytes); memset(w_databuffer, 'a', bytes); write_arg = (struct fsal_io_arg *)alloca(sizeof(struct fsal_io_arg) + sizeof(struct iovec)); write_arg->info = NULL; write_arg->state = NULL; write_arg->offset = OFFSET; write_arg->io_request = bytes; write_arg->iov_count = 1; write_arg->iov[0].iov_len = bytes; write_arg->iov[0].iov_base = w_databuffer; write_arg->io_amount = 0; write_arg->fsal_stable = false; sub_hdl = mdcdb_get_sub_handle(test_file); ASSERT_NE(sub_hdl, nullptr); io_data.ret.major = ERR_FSAL_NO_ERROR; io_data.ret.minor = 0; io_data.done = false; io_data.fsa_cond = &cond; io_data.fsa_mutex = &mutex; fsal_write(sub_hdl, true, write_arg, &io_data); EXPECT_EQ(io_data.ret.major, 0); r_databuffer = (char *)malloc(bytes); read_arg = (struct fsal_io_arg *)alloca(sizeof(struct fsal_io_arg) + sizeof(struct iovec)); read_arg->info = NULL; read_arg->state = NULL; read_arg->offset = OFFSET; read_arg->iov_count = 1; read_arg->iov[0].iov_len = bytes; read_arg->iov[0].iov_base = r_databuffer; read_arg->io_amount = 0; io_data.ret.major = ERR_FSAL_NO_ERROR; io_data.ret.minor = 0; io_data.done = false; io_data.fsa_cond = &cond; io_data.fsa_mutex = &mutex; fsal_read(sub_hdl, true, read_arg, &io_data); EXPECT_EQ(io_data.ret.major, 0); free(w_databuffer); free(r_databuffer); } TEST_F(Read2EmptyLatencyTest, LARGE_DATA_READ) { char *w_databuffer; char *r_databuffer; struct fsal_io_arg *write_arg; struct fsal_io_arg *read_arg; struct async_process_data io_data; int bytes = (2 * 1024 * 1024); int ret = -1; w_databuffer = (char *)malloc(bytes); memset(w_databuffer, 'a', bytes); write_arg = (struct fsal_io_arg *)alloca(sizeof(struct fsal_io_arg) + sizeof(struct iovec)); write_arg->info = NULL; write_arg->state = NULL; write_arg->offset = OFFSET; write_arg->io_request = bytes; write_arg->iov_count = 1; write_arg->iov[0].iov_len = bytes; write_arg->iov[0].iov_base = w_databuffer; write_arg->io_amount = 0; write_arg->fsal_stable = false; io_data.ret.major = ERR_FSAL_NO_ERROR; io_data.ret.minor = 0; io_data.done = false; io_data.fsa_cond = &cond; io_data.fsa_mutex = &mutex; fsal_write(test_file, true, write_arg, &io_data); EXPECT_EQ(io_data.ret.major, 0); r_databuffer = (char *)malloc(bytes); read_arg = (struct fsal_io_arg *)alloca(sizeof(struct fsal_io_arg) + sizeof(struct iovec)); read_arg->info = NULL; read_arg->state = NULL; read_arg->offset = OFFSET; read_arg->iov_count = 1; read_arg->iov[0].iov_len = bytes; read_arg->iov[0].iov_base = r_databuffer; read_arg->io_amount = 0; io_data.ret.major = ERR_FSAL_NO_ERROR; io_data.ret.minor = 0; io_data.done = false; io_data.fsa_cond = &cond; io_data.fsa_mutex = &mutex; fsal_read(test_file, true, read_arg, &io_data); EXPECT_EQ(io_data.ret.major, 0); ret = memcmp(r_databuffer, w_databuffer, bytes); EXPECT_EQ(ret, 0); free(w_databuffer); free(r_databuffer); } TEST_F(Read2EmptyLatencyTest, LOOP) { char *w_databuffer; char *r_databuffer; struct fsal_io_arg *write_arg; struct fsal_io_arg *read_arg; struct async_process_data io_data; int bytes = 64 * LOOP_COUNT; struct timespec s_time, e_time; w_databuffer = (char *)malloc(bytes); memset(w_databuffer, 'a', bytes); write_arg = (struct fsal_io_arg *)alloca(sizeof(struct fsal_io_arg) + sizeof(struct iovec)); write_arg->info = NULL; write_arg->state = NULL; write_arg->offset = OFFSET; write_arg->io_request = bytes; write_arg->iov_count = 1; write_arg->iov[0].iov_len = bytes; write_arg->iov[0].iov_base = w_databuffer; write_arg->io_amount = 0; write_arg->fsal_stable = false; io_data.ret.major = ERR_FSAL_NO_ERROR; io_data.ret.minor = 0; io_data.done = false; io_data.fsa_cond = &cond; io_data.fsa_mutex = &mutex; fsal_write(test_file, true, write_arg, &io_data); EXPECT_EQ(io_data.ret.major, 0); bytes = 64; r_databuffer = (char *)malloc(bytes); read_arg = (struct fsal_io_arg *)alloca(sizeof(struct fsal_io_arg) + sizeof(struct iovec)); read_arg->info = NULL; read_arg->state = NULL; read_arg->offset = OFFSET; read_arg->iov_count = 1; read_arg->iov[0].iov_len = bytes; read_arg->iov[0].iov_base = r_databuffer; read_arg->io_amount = 0; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i, read_arg->offset += 64) { io_data.ret.major = ERR_FSAL_NO_ERROR; io_data.ret.minor = 0; io_data.done = false; io_data.fsa_cond = &cond; io_data.fsa_mutex = &mutex; fsal_read(test_file, true, read_arg, &io_data); EXPECT_EQ(io_data.ret.major, 0); } now(&e_time); fprintf(stderr, "Average time per read2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); free(w_databuffer); free(r_databuffer); } TEST_F(Read2EmptyLatencyTest, LOOP_BYPASS) { struct fsal_obj_handle *sub_hdl; char *w_databuffer; char *r_databuffer; struct fsal_io_arg *write_arg; struct fsal_io_arg *read_arg; struct async_process_data io_data; int bytes = 64 * LOOP_COUNT; struct timespec s_time, e_time; w_databuffer = (char *)malloc(bytes); memset(w_databuffer, 'a', bytes); write_arg = (struct fsal_io_arg *)alloca(sizeof(struct fsal_io_arg) + sizeof(struct iovec)); write_arg->info = NULL; write_arg->state = NULL; write_arg->offset = OFFSET; write_arg->io_request = bytes; write_arg->iov_count = 1; write_arg->iov[0].iov_len = bytes; write_arg->iov[0].iov_base = w_databuffer; write_arg->io_amount = 0; write_arg->fsal_stable = false; sub_hdl = mdcdb_get_sub_handle(test_file); ASSERT_NE(sub_hdl, nullptr); io_data.ret.major = ERR_FSAL_NO_ERROR; io_data.ret.minor = 0; io_data.done = false; io_data.fsa_cond = &cond; io_data.fsa_mutex = &mutex; fsal_write(sub_hdl, true, write_arg, &io_data); EXPECT_EQ(io_data.ret.major, 0); bytes = 64; r_databuffer = (char *)malloc(bytes); read_arg = (struct fsal_io_arg *)alloca(sizeof(struct fsal_io_arg) + sizeof(struct iovec)); read_arg->info = NULL; read_arg->state = NULL; read_arg->offset = OFFSET; read_arg->iov_count = 1; read_arg->iov[0].iov_len = bytes; read_arg->iov[0].iov_base = r_databuffer; read_arg->io_amount = 0; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i, read_arg->offset += 64) { io_data.ret.major = ERR_FSAL_NO_ERROR; io_data.ret.minor = 0; io_data.done = false; io_data.fsa_cond = &cond; io_data.fsa_mutex = &mutex; fsal_read(sub_hdl, true, read_arg, &io_data); EXPECT_EQ(io_data.ret.major, 0); } now(&e_time); fprintf(stderr, "Average time per read2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); free(w_databuffer); free(r_databuffer); } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_readdir_correctness.cc000066400000000000000000000204131473756622300246620ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "readdir_correctness" #define TEST_DIR "test_directory" #define DIR_COUNT 100000 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; typedef struct { struct gsh_buffdesc *keys; bool *hdl_found; std::string *names; } rd_state_t; class ReaddirEmptyCorrectnessTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { fsal_status_t status; struct fsal_attrlist attrs_out; gtest::GaneshaFSALBaseTest::SetUp(); status = fsal_create(test_root, TEST_DIR, DIRECTORY, &attrs, NULL, &test_dir, &attrs_out, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(test_dir, nullptr); fsal_release_attrs(&attrs_out); } virtual void TearDown() { fsal_status_t status; status = test_root->obj_ops->unlink(test_root, test_dir, TEST_DIR, nullptr, nullptr); EXPECT_EQ(0, status.major); test_dir->obj_ops->put_ref(test_dir); test_dir = NULL; gtest::GaneshaFSALBaseTest::TearDown(); } struct fsal_obj_handle *test_dir = nullptr; }; class ReaddirFullCorrectnessTest : public ReaddirEmptyCorrectnessTest { protected: virtual void SetUp() { struct fsal_obj_handle *dir_hdls[DIR_COUNT]; ReaddirEmptyCorrectnessTest::SetUp(); create_and_prime_many(DIR_COUNT, dir_hdls, test_dir); for (int i = 0; i < DIR_COUNT; i++) { struct gsh_buffdesc fh_desc; std::stringstream sstr; dir_hdls[i]->obj_ops->handle_to_key(dir_hdls[i], &fh_desc); keyDup(&keys[i], &fh_desc); sstr << "f-" << std::setfill('0') << std::setw(8) << std::hex << i; names[i] = sstr.str(); sstr.str(""); sstr.clear(); dir_hdls[i]->obj_ops->put_ref(dir_hdls[i]); } /* Clean up extra entries */ mdcache_lru_release_entries(-1); } virtual void TearDown() { remove_many(DIR_COUNT, NULL, test_dir); for (int i = 0; i < DIR_COUNT; i++) { gsh_free(keys[i].addr); } ReaddirEmptyCorrectnessTest::TearDown(); } void keyDup(struct gsh_buffdesc *dest, struct gsh_buffdesc *src) { dest->len = src->len; dest->addr = gsh_malloc(src->len); memcpy(dest->addr, src->addr, src->len); } struct gsh_buffdesc keys[DIR_COUNT]; std::string names[DIR_COUNT]; bool hdl_found[DIR_COUNT] = { false }; }; bool keyEQ(struct gsh_buffdesc *key1, struct gsh_buffdesc *key2) { if (key1->len != key2->len) return false; return !memcmp(key1->addr, key2->addr, key1->len); } enum fsal_dir_result trc_populate_dirent(const char *name, struct fsal_obj_handle *obj, struct fsal_attrlist *attrs, void *dir_state, fsal_cookie_t cookie) { rd_state_t *st = (rd_state_t *)dir_state; struct gsh_buffdesc fh_desc; obj->obj_ops->handle_to_key(obj, &fh_desc); for (int i = 0; i < DIR_COUNT; i++) { if (keyEQ(&st->keys[i], &fh_desc)) { EXPECT_EQ(false, st->hdl_found[i]) << st->names[i]; st->hdl_found[i] = true; break; } } obj->obj_ops->put_ref(obj); return DIR_CONTINUE; } } /* namespace */ TEST_F(ReaddirFullCorrectnessTest, BIG) { fsal_status_t status; uint64_t whence = 0; bool eod = false; rd_state_t st; st.keys = keys; st.hdl_found = hdl_found; st.names = names; status = test_dir->obj_ops->readdir(test_dir, &whence, &st, trc_populate_dirent, 0, &eod); ASSERT_EQ(status.major, 0); for (int i = 0; i < DIR_COUNT; i++) { ASSERT_EQ(true, hdl_found[i]) << names[i]; hdl_found[i] = false; } status = test_dir->obj_ops->readdir(test_dir, &whence, &st, trc_populate_dirent, 0, &eod); ASSERT_EQ(status.major, 0); for (int i = 0; i < DIR_COUNT; i++) { ASSERT_EQ(true, hdl_found[i]) << names[i]; hdl_found[i] = false; } } #if 0 /* bypass won't work right now, because of the obj pointer comparisons */ TEST_F(ReaddirFullCorrectnessTest, BIG_BYPASS) { fsal_status_t status; uint64_t whence = 0; bool eod = false; struct fsal_obj_handle *sub_hdl; rd_state_t st; st.dir_hdls = dir_hdls; st.hdl_found = hdl_found; sub_hdl = mdcdb_get_sub_handle(test_dir); ASSERT_NE(sub_hdl, nullptr); status = sub_hdl->obj_ops->readdir(sub_hdl, &whence, &st, trc_populate_dirent, 0, &eod); ASSERT_EQ(status.major, 0); for (int i = 0; i < DIR_COUNT; i++) { ASSERT_EQ(true, hdl_found[i]); } } #endif int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_readdir_latency.cc000066400000000000000000000205551473756622300237760ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "readdir_latency" #define TEST_DIR "test_directory" #define DIR_COUNT 100000 #define EMPTY_LOOP_COUNT 1000000 #define FULL_LOOP_COUNT 1000 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class ReaddirEmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { fsal_status_t status; struct fsal_attrlist attrs_out; gtest::GaneshaFSALBaseTest::SetUp(); status = fsal_create(test_root, TEST_DIR, DIRECTORY, &attrs, NULL, &test_dir, &attrs_out, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(test_dir, nullptr); fsal_release_attrs(&attrs_out); } virtual void TearDown() { fsal_status_t status; status = test_root->obj_ops->unlink(test_root, test_dir, TEST_DIR, nullptr, nullptr); EXPECT_EQ(0, status.major); test_dir->obj_ops->put_ref(test_dir); test_dir = NULL; gtest::GaneshaFSALBaseTest::TearDown(); } struct fsal_obj_handle *test_dir = nullptr; }; class ReaddirFullLatencyTest : public ReaddirEmptyLatencyTest { protected: virtual void SetUp() { ReaddirEmptyLatencyTest::SetUp(); create_and_prime_many(DIR_COUNT, NULL, test_dir); } virtual void TearDown() { remove_many(DIR_COUNT, NULL, test_dir); ReaddirEmptyLatencyTest::TearDown(); } }; static enum fsal_dir_result populate_dirent(const char *name, struct fsal_obj_handle *obj, struct fsal_attrlist *attrs, void *dir_state, fsal_cookie_t cookie) { obj->obj_ops->put_ref(obj); return DIR_CONTINUE; } struct cb_data { uint8_t *cursor; unsigned int count; unsigned int max; }; } /* namespace */ TEST_F(ReaddirEmptyLatencyTest, SIMPLE) { fsal_status_t status; uint64_t whence = 0; bool eod = false; status = test_dir->obj_ops->readdir(test_dir, &whence, NULL, populate_dirent, 0, &eod); EXPECT_EQ(status.major, 0); } TEST_F(ReaddirEmptyLatencyTest, SIMPLE_BYPASS) { fsal_status_t status; uint64_t whence = 0; bool eod = false; struct fsal_obj_handle *sub_hdl; sub_hdl = mdcdb_get_sub_handle(test_dir); ASSERT_NE(sub_hdl, nullptr); status = sub_hdl->obj_ops->readdir(sub_hdl, &whence, NULL, populate_dirent, 0, &eod); EXPECT_EQ(status.major, 0); } TEST_F(ReaddirEmptyLatencyTest, FSALREADDIR) { fsal_status_t status; uint64_t cookie = 0LL; unsigned int num_entries = 0; bool eod_met = false; struct cb_data tracker = { 0 }; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < EMPTY_LOOP_COUNT; ++i) { status = fsal_readdir(test_dir, cookie, &num_entries, &eod_met, 0, readdir_callback, &tracker); EXPECT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per fsal_readdir: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / EMPTY_LOOP_COUNT); } TEST_F(ReaddirEmptyLatencyTest, LOOP) { fsal_status_t status; uint64_t whence = 0; bool eod = false; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < EMPTY_LOOP_COUNT; ++i) { status = test_dir->obj_ops->readdir(test_dir, &whence, NULL, populate_dirent, 0, &eod); EXPECT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per readdir: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / EMPTY_LOOP_COUNT); } TEST_F(ReaddirFullLatencyTest, BIG) { fsal_status_t status; uint64_t whence = 0; bool eod = false; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < FULL_LOOP_COUNT; ++i) { status = test_dir->obj_ops->readdir(test_dir, &whence, NULL, populate_dirent, 0, &eod); ASSERT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per readdir: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / FULL_LOOP_COUNT); } TEST_F(ReaddirFullLatencyTest, BIG_BYPASS) { fsal_status_t status; uint64_t whence = 0; bool eod = false; struct fsal_obj_handle *sub_hdl; struct timespec s_time, e_time; sub_hdl = mdcdb_get_sub_handle(test_dir); ASSERT_NE(sub_hdl, nullptr); now(&s_time); for (int i = 0; i < FULL_LOOP_COUNT; ++i) { status = sub_hdl->obj_ops->readdir(sub_hdl, &whence, NULL, populate_dirent, 0, &eod); ASSERT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per readdir: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / FULL_LOOP_COUNT); } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_readlink_latency.cc000066400000000000000000000211531473756622300241500ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "readlink_latency" #define TEST_ROOT_LINK "symlink_to_readlink_latency" #define FILE_COUNT 100000 #define LOOP_COUNT 1000000 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class ReadlinkEmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { fsal_status_t status; struct fsal_attrlist attrs_out; gtest::GaneshaFSALBaseTest::SetUp(); status = fsal_create(root_entry, TEST_ROOT_LINK, SYMBOLIC_LINK, &attrs, TEST_ROOT, &symlink_test_root, &attrs_out, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(symlink_test_root, nullptr); status = fsal_readlink(symlink_test_root, &bfr_content); ASSERT_EQ(status.major, 0); fsal_release_attrs(&attrs_out); } virtual void TearDown() { fsal_status_t status; gsh_free(bfr_content.addr); status = symlink_test_root->obj_ops->unlink(root_entry, symlink_test_root, TEST_ROOT_LINK, nullptr, nullptr); EXPECT_EQ(0, status.major); symlink_test_root->obj_ops->put_ref(symlink_test_root); symlink_test_root = NULL; gtest::GaneshaFSALBaseTest::TearDown(); } struct fsal_obj_handle *symlink_test_root = nullptr; struct gsh_buffdesc bfr_content; }; class ReadlinkFullLatencyTest : public ReadlinkEmptyLatencyTest { protected: virtual void SetUp() { ReadlinkEmptyLatencyTest::SetUp(); create_and_prime_many(FILE_COUNT, NULL); } virtual void TearDown() { remove_many(FILE_COUNT, NULL); ReadlinkEmptyLatencyTest::TearDown(); } }; } /* namespace */ TEST_F(ReadlinkEmptyLatencyTest, SIMPLE) { fsal_status_t status; struct gsh_buffdesc link_content; int ret = -1; status = symlink_test_root->obj_ops->readlink(symlink_test_root, &link_content, false); EXPECT_EQ(status.major, 0); if (link_content.len == bfr_content.len) ret = memcmp(link_content.addr, bfr_content.addr, link_content.len); EXPECT_EQ(ret, 0); gsh_free(link_content.addr); } TEST_F(ReadlinkEmptyLatencyTest, SIMPLE_BYPASS) { fsal_status_t status; struct fsal_obj_handle *sub_hdl; struct gsh_buffdesc link_content; int ret = -1; sub_hdl = mdcdb_get_sub_handle(symlink_test_root); ASSERT_NE(sub_hdl, nullptr); status = sub_hdl->obj_ops->readlink(sub_hdl, &link_content, false); EXPECT_EQ(status.major, 0); if (link_content.len == bfr_content.len) ret = memcmp(link_content.addr, bfr_content.addr, link_content.len); EXPECT_EQ(ret, 0); gsh_free(link_content.addr); } TEST_F(ReadlinkEmptyLatencyTest, LOOP) { fsal_status_t status; struct gsh_buffdesc link_content; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = symlink_test_root->obj_ops->readlink( symlink_test_root, &link_content, false); EXPECT_EQ(status.major, 0); gsh_free(link_content.addr); } now(&e_time); fprintf(stderr, "Average time per readlink: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(ReadlinkEmptyLatencyTest, FSALREADLINK) { fsal_status_t status; struct gsh_buffdesc link_content; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = fsal_readlink(symlink_test_root, &link_content); EXPECT_EQ(status.major, 0); gsh_free(link_content.addr); } now(&e_time); fprintf(stderr, "Average time per fsal_readlink: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(ReadlinkFullLatencyTest, BIG) { fsal_status_t status; struct gsh_buffdesc link_content; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = symlink_test_root->obj_ops->readlink( symlink_test_root, &link_content, false); ASSERT_EQ(status.major, 0) << " failed to readlink " << TEST_ROOT_LINK; gsh_free(link_content.addr); } now(&e_time); fprintf(stderr, "Average time per readlink: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(ReadlinkFullLatencyTest, BIG_BYPASS) { fsal_status_t status; struct fsal_obj_handle *sub_hdl; struct gsh_buffdesc link_content; struct timespec s_time, e_time; sub_hdl = mdcdb_get_sub_handle(symlink_test_root); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = sub_hdl->obj_ops->readlink(sub_hdl, &link_content, false); ASSERT_EQ(status.major, 0) << " failed to readlink " << TEST_ROOT_LINK; gsh_free(link_content.addr); } now(&e_time); fprintf(stderr, "Average time per readlink: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_release_latency.cc000066400000000000000000000116611473756622300240020ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "release_latency" #define TEST_NODE "test_node" #define LOOP_COUNT 1000000 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class ReleaseEmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { gtest::GaneshaFSALBaseTest::SetUp(); } virtual void TearDown() { gtest::GaneshaFSALBaseTest::TearDown(); } }; } /* namespace */ TEST_F(ReleaseEmptyLatencyTest, SIMPLE) { test_root->obj_ops->release(test_root); } TEST_F(ReleaseEmptyLatencyTest, LOOP) { struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { test_root->obj_ops->release(test_root); } now(&e_time); fprintf(stderr, "Average time per release: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } /* Can't bypass release */ int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_rename_latency.cc000066400000000000000000000254541473756622300236360ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "test_root" #define TEST_FILE "original_name" #define TEST_FILE_NEW "new_name" #define FILE_COUNT 100000 #define LOOP_COUNT 1000000 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class RenameEmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { gtest::GaneshaFSALBaseTest::SetUp(); } virtual void TearDown() { gtest::GaneshaFSALBaseTest::TearDown(); } }; class RenameFullLatencyTest : public RenameEmptyLatencyTest { protected: virtual void SetUp() { RenameEmptyLatencyTest::SetUp(); create_and_prime_many(FILE_COUNT, NULL); } virtual void TearDown() { remove_many(FILE_COUNT, NULL); RenameEmptyLatencyTest::TearDown(); } }; } /* namespace */ TEST_F(RenameEmptyLatencyTest, SIMPLE) { fsal_status_t status; struct fsal_obj_handle *obj = nullptr; struct fsal_obj_handle *lookup = nullptr; /* Create file for the the test */ status = fsal_create(test_root, TEST_FILE, REGULAR_FILE, &attrs, NULL, &obj, NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(obj, nullptr); status = test_root->obj_ops->rename(obj, test_root, TEST_FILE, test_root, TEST_FILE_NEW, nullptr, nullptr, nullptr, nullptr); EXPECT_EQ(status.major, 0); test_root->obj_ops->lookup(test_root, TEST_FILE_NEW, &lookup, NULL); EXPECT_EQ(lookup, obj); lookup->obj_ops->put_ref(lookup); obj->obj_ops->put_ref(obj); /* Delete file created for the test */ status = fsal_remove(test_root, TEST_FILE_NEW, NULL, NULL); ASSERT_EQ(status.major, 0); } TEST_F(RenameEmptyLatencyTest, SIMPLE_BYPASS) { fsal_status_t status; struct fsal_obj_handle *obj = nullptr; struct fsal_obj_handle *sub_hdl = nullptr; struct fsal_obj_handle *sub_hdl_obj = nullptr; struct fsal_obj_handle *lookup = nullptr; /* Create file for the the test */ status = fsal_create(test_root, TEST_FILE, REGULAR_FILE, &attrs, NULL, &obj, NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(obj, nullptr); sub_hdl = mdcdb_get_sub_handle(test_root); ASSERT_NE(sub_hdl, nullptr); sub_hdl_obj = mdcdb_get_sub_handle(obj); ASSERT_NE(sub_hdl_obj, nullptr); status = sub_hdl_obj->obj_ops->rename(sub_hdl_obj, sub_hdl, TEST_FILE, sub_hdl, TEST_FILE_NEW, NULL, NULL, NULL, NULL); EXPECT_EQ(status.major, 0); sub_hdl->obj_ops->lookup(sub_hdl, TEST_FILE_NEW, &lookup, NULL); EXPECT_EQ(lookup, sub_hdl_obj); lookup->obj_ops->put_ref(lookup); obj->obj_ops->put_ref(obj); /* Delete file created for the test */ status = fsal_remove(test_root, TEST_FILE_NEW, NULL, NULL); ASSERT_EQ(status.major, 0); } TEST_F(RenameEmptyLatencyTest, LOOP) { fsal_status_t status; struct fsal_obj_handle *obj; char fname[NAMELEN] = TEST_FILE; char fname_new[NAMELEN]; struct timespec s_time, e_time; /* Create file for the the test */ status = fsal_create(test_root, TEST_FILE, REGULAR_FILE, &attrs, NULL, &obj, NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(obj, nullptr); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname_new, "nf-%08x", i); status = test_root->obj_ops->rename(obj, test_root, fname, test_root, fname_new, nullptr, nullptr, nullptr, nullptr); EXPECT_EQ(status.major, 0); strncpy(fname, fname_new, NAMELEN); } now(&e_time); fprintf(stderr, "Average time per rename: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); obj->obj_ops->put_ref(obj); /* Delete file created for the test */ status = fsal_remove(test_root, fname, nullptr, nullptr); ASSERT_EQ(status.major, 0); } TEST_F(RenameEmptyLatencyTest, FSALRENAME) { fsal_status_t status; struct fsal_obj_handle *obj; char fname[NAMELEN] = TEST_FILE; char fname_new[NAMELEN]; struct timespec s_time, e_time; /* Create file for the the test */ status = fsal_create(test_root, TEST_FILE, REGULAR_FILE, &attrs, NULL, &obj, NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(obj, nullptr); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname_new, "nf-%08x", i); status = fsal_rename(test_root, fname, test_root, fname_new, NULL, NULL, NULL, NULL); EXPECT_EQ(status.major, 0); strncpy(fname, fname_new, NAMELEN); } now(&e_time); fprintf(stderr, "Average time per fsal_rename: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); obj->obj_ops->put_ref(obj); /* Delete file created for the test */ status = fsal_remove(test_root, fname, nullptr, nullptr); ASSERT_EQ(status.major, 0); } TEST_F(RenameFullLatencyTest, BIG) { fsal_status_t status; struct fsal_obj_handle *obj; char fname[NAMELEN] = TEST_FILE; char fname_new[NAMELEN]; struct timespec s_time, e_time; /* Create file for the the test */ status = fsal_create(test_root, TEST_FILE, REGULAR_FILE, &attrs, NULL, &obj, NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(obj, nullptr); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname_new, "nf-%08x", i); status = test_root->obj_ops->rename(obj, test_root, fname, test_root, fname_new, nullptr, nullptr, nullptr, nullptr); ASSERT_EQ(status.major, 0) << " failed to rename " << fname; strncpy(fname, fname_new, NAMELEN); } now(&e_time); fprintf(stderr, "Average time per rename: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); obj->obj_ops->put_ref(obj); /* Delete file created for the test */ status = fsal_remove(test_root, fname, nullptr, nullptr); ASSERT_EQ(status.major, 0); } TEST_F(RenameFullLatencyTest, BIG_BYPASS) { fsal_status_t status; struct fsal_obj_handle *obj; char fname[NAMELEN] = TEST_FILE; char fname_new[NAMELEN]; struct fsal_obj_handle *sub_hdl; struct fsal_obj_handle *sub_hdl_obj; struct timespec s_time, e_time; /* Create file for the the test */ status = fsal_create(test_root, TEST_FILE, REGULAR_FILE, &attrs, NULL, &obj, NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(obj, nullptr); sub_hdl = mdcdb_get_sub_handle(test_root); ASSERT_NE(sub_hdl, nullptr); sub_hdl_obj = mdcdb_get_sub_handle(obj); ASSERT_NE(sub_hdl_obj, nullptr); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname_new, "nf-%08x", i); status = sub_hdl->obj_ops->rename(sub_hdl_obj, sub_hdl, fname, sub_hdl, fname_new, NULL, NULL, NULL, NULL); ASSERT_EQ(status.major, 0) << " failed to rename " << fname; strncpy(fname, fname_new, NAMELEN); } now(&e_time); fprintf(stderr, "Average time per rename: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); obj->obj_ops->put_ref(obj); /* Delete file created for the test */ status = fsal_remove(test_root, fname, NULL, NULL); ASSERT_EQ(status.major, 0); } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_reopen2_latency.cc000066400000000000000000000171101473756622300237270ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "reopen2_latency" #define TEST_FILE "test_file" #define LOOP_COUNT 1000000 #define OFFSET 0 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class Reopen2EmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { fsal_status_t status; bool caller_perm_check = false; gtest::GaneshaFSALBaseTest::SetUp(); test_file_state = op_ctx->fsal_export->exp_ops.alloc_state( op_ctx->fsal_export, STATE_TYPE_SHARE, NULL); ASSERT_NE(test_file_state, nullptr); status = test_root->obj_ops->open2( test_root, test_file_state, FSAL_O_RDWR, FSAL_UNCHECKED, TEST_FILE, NULL, NULL, &test_file, NULL, &caller_perm_check, nullptr, nullptr); ASSERT_EQ(status.major, 0); } virtual void TearDown() { fsal_status_t status; status = test_file->obj_ops->close2(test_file, test_file_state); EXPECT_EQ(0, status.major); free_state(test_file_state); status = fsal_remove(test_root, TEST_FILE, NULL, NULL); EXPECT_EQ(status.major, 0); test_file->obj_ops->put_ref(test_file); test_file = NULL; gtest::GaneshaFSALBaseTest::TearDown(); } struct fsal_obj_handle *test_file = nullptr; struct state_t *test_file_state; }; } /* namespace */ TEST_F(Reopen2EmptyLatencyTest, SIMPLE) { fsal_status_t status; status = test_file->obj_ops->reopen2(test_file, test_file_state, FSAL_O_READ); ASSERT_EQ(status.major, 0); } TEST_F(Reopen2EmptyLatencyTest, SIMPLE_BYPASS) { fsal_status_t status; struct fsal_obj_handle *sub_hdl; sub_hdl = mdcdb_get_sub_handle(test_file); ASSERT_NE(sub_hdl, nullptr); status = sub_hdl->obj_ops->reopen2(sub_hdl, test_file_state, FSAL_O_READ); ASSERT_EQ(status.major, 0); } TEST_F(Reopen2EmptyLatencyTest, FSAL_REOPEN2) { fsal_status_t status; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { if (i % 2 == 0) { status = fsal_reopen2(test_file, test_file_state, FSAL_O_READ, false); ASSERT_EQ(status.major, 0); } else { status = fsal_reopen2(test_file, test_file_state, FSAL_O_WRITE, false); ASSERT_EQ(status.major, 0); } } now(&e_time); fprintf(stderr, "Average time per fsal_reopen2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(Reopen2EmptyLatencyTest, LOOP) { fsal_status_t status; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { if (i % 2 == 0) { status = test_file->obj_ops->reopen2( test_file, test_file_state, FSAL_O_READ); ASSERT_EQ(status.major, 0); } else { status = test_file->obj_ops->reopen2( test_file, test_file_state, FSAL_O_WRITE); ASSERT_EQ(status.major, 0); } } now(&e_time); fprintf(stderr, "Average time per reopen2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(Reopen2EmptyLatencyTest, LOOP_BYPASS) { fsal_status_t status; struct fsal_obj_handle *sub_hdl; struct timespec s_time, e_time; sub_hdl = mdcdb_get_sub_handle(test_file); ASSERT_NE(sub_hdl, nullptr); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { if (i % 2 == 0) { status = sub_hdl->obj_ops->reopen2( sub_hdl, test_file_state, FSAL_O_READ); ASSERT_EQ(status.major, 0); } else { status = sub_hdl->obj_ops->reopen2( sub_hdl, test_file_state, FSAL_O_WRITE); ASSERT_EQ(status.major, 0); } } now(&e_time); fprintf(stderr, "Average time per reopen2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_setattr2_latency.cc000066400000000000000000000212221473756622300241240ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "setattr2_latency" #define TEST_FILE "setattr2_test_file" #define DIR_COUNT 100000 #define LOOP_COUNT 1000000 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class Setattr2EmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { fsal_status_t status; struct fsal_attrlist attrs_out; gtest::GaneshaFSALBaseTest::SetUp(); status = fsal_create(test_root, TEST_FILE, REGULAR_FILE, &attrs, NULL, &test_file, &attrs_out, nullptr, nullptr); ASSERT_EQ(status.major, 0); fsal_release_attrs(&attrs_out); } virtual void TearDown() { fsal_status_t status; status = fsal_remove(test_root, TEST_FILE, NULL, NULL); EXPECT_EQ(status.major, 0); test_file->obj_ops->put_ref(test_file); test_file = NULL; gtest::GaneshaFSALBaseTest::TearDown(); } struct fsal_obj_handle *test_file = nullptr; }; class Setattr2FullLatencyTest : public Setattr2EmptyLatencyTest { protected: virtual void SetUp() { Setattr2EmptyLatencyTest::SetUp(); create_and_prime_many(DIR_COUNT, NULL); } virtual void TearDown() { remove_many(DIR_COUNT, NULL); Setattr2EmptyLatencyTest::TearDown(); } }; } /* namespace */ TEST_F(Setattr2EmptyLatencyTest, SIMPLE) { fsal_status_t status; status = test_file->obj_ops->setattr2(test_file, false, NULL, &attrs); EXPECT_EQ(status.major, 0); } TEST_F(Setattr2EmptyLatencyTest, SIMPLE_BYPASS) { fsal_status_t status; struct fsal_obj_handle *sub_hdl; sub_hdl = mdcdb_get_sub_handle(test_file); ASSERT_NE(sub_hdl, nullptr); status = sub_hdl->obj_ops->setattr2(sub_hdl, false, NULL, &attrs); EXPECT_EQ(status.major, 0); } TEST_F(Setattr2EmptyLatencyTest, FSAL_SETATTR) { fsal_status_t status; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = fsal_setattr(test_file, false, NULL, &attrs); EXPECT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per fsal_setattr: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(Setattr2FullLatencyTest, BIG_CACHED) { fsal_status_t status; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = test_file->obj_ops->setattr2(test_file, false, NULL, &attrs); ASSERT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per setattr2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(Setattr2FullLatencyTest, BIG_UNCACHED) { fsal_status_t status; struct fsal_obj_handle *obj[DIR_COUNT]; char fname[NAMELEN]; struct timespec s_time, e_time; for (int i = 0; i < DIR_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = test_root->obj_ops->lookup(test_root, fname, &obj[i], NULL); ASSERT_EQ(status.major, 0); } now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = obj[i % DIR_COUNT]->obj_ops->setattr2( obj[i % DIR_COUNT], false, NULL, &attrs); ASSERT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per setattr2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); for (int i = 0; i < DIR_COUNT; ++i) { obj[i]->obj_ops->put_ref(obj[i]); } } TEST_F(Setattr2FullLatencyTest, BIG_BYPASS_CACHED) { fsal_status_t status; struct fsal_obj_handle *sub_hdl; struct timespec s_time, e_time; sub_hdl = mdcdb_get_sub_handle(test_file); ASSERT_NE(sub_hdl, nullptr); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = sub_hdl->obj_ops->setattr2(sub_hdl, false, NULL, &attrs); ASSERT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per setattr2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(Setattr2FullLatencyTest, BIG_BYPASS_UNCACHED) { fsal_status_t status; struct fsal_obj_handle *obj[DIR_COUNT]; struct fsal_obj_handle *sub_hdl[DIR_COUNT]; char fname[NAMELEN]; struct timespec s_time, e_time; for (int i = 0; i < DIR_COUNT; ++i) { sprintf(fname, "f-%08x", i); status = test_root->obj_ops->lookup(test_root, fname, &obj[i], NULL); ASSERT_EQ(status.major, 0); sub_hdl[i] = mdcdb_get_sub_handle(obj[i]); ASSERT_NE(sub_hdl[i], nullptr); } now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { status = sub_hdl[i % DIR_COUNT]->obj_ops->setattr2( sub_hdl[i % DIR_COUNT], false, NULL, &attrs); ASSERT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per setattr2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); for (int i = 0; i < DIR_COUNT; ++i) { obj[i]->obj_ops->put_ref(obj[i]); } } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_symlink_latency.cc000066400000000000000000000256641473756622300240600ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "symlink_latency" #define TEST_ROOT_LINK "symlink_to_symlink_latency" #define TEST_SYMLINK "test_symlink" #define FILE_COUNT 100000 #define LOOP_COUNT 1000000 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class SymlinkEmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { fsal_status_t status; struct fsal_attrlist attrs_out; gtest::GaneshaFSALBaseTest::SetUp(); status = fsal_create(root_entry, TEST_SYMLINK, SYMBOLIC_LINK, &attrs, TEST_ROOT, &test_symlink, &attrs_out, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(test_symlink, nullptr); status = fsal_readlink(test_symlink, &bfr_content); ASSERT_EQ(status.major, 0); fsal_release_attrs(&attrs_out); } virtual void TearDown() { fsal_status_t status; gsh_free(bfr_content.addr); status = root_entry->obj_ops->unlink(root_entry, test_symlink, TEST_SYMLINK, nullptr, nullptr); EXPECT_EQ(0, status.major); test_symlink->obj_ops->put_ref(test_symlink); test_symlink = NULL; gtest::GaneshaFSALBaseTest::TearDown(); } struct fsal_obj_handle *test_symlink = nullptr; struct gsh_buffdesc bfr_content; }; class SymlinkFullLatencyTest : public SymlinkEmptyLatencyTest { protected: virtual void SetUp() { SymlinkEmptyLatencyTest::SetUp(); create_and_prime_many(FILE_COUNT, NULL); } virtual void TearDown() { remove_many(FILE_COUNT, NULL); SymlinkEmptyLatencyTest::TearDown(); } }; } /* namespace */ TEST_F(SymlinkEmptyLatencyTest, SIMPLE) { fsal_status_t status; struct fsal_obj_handle *symlink; struct fsal_obj_handle *lookup; struct gsh_buffdesc link_content; int ret = -1; status = root_entry->obj_ops->symlink(root_entry, TEST_ROOT_LINK, TEST_ROOT, &attrs, &symlink, NULL, nullptr, nullptr); EXPECT_EQ(status.major, 0); root_entry->obj_ops->lookup(root_entry, TEST_ROOT_LINK, &lookup, NULL); EXPECT_EQ(lookup, symlink); status = symlink->obj_ops->readlink(symlink, &link_content, false); EXPECT_EQ(status.major, 0); if (link_content.len == bfr_content.len) ret = memcmp(link_content.addr, bfr_content.addr, link_content.len); EXPECT_EQ(ret, 0); gsh_free(link_content.addr); symlink->obj_ops->put_ref(symlink); lookup->obj_ops->put_ref(lookup); /* Remove symlink created while running test */ status = fsal_remove(root_entry, TEST_ROOT_LINK, NULL, NULL); ASSERT_EQ(status.major, 0); } TEST_F(SymlinkEmptyLatencyTest, SIMPLE_BYPASS) { fsal_status_t status; struct fsal_obj_handle *sub_hdl; struct fsal_obj_handle *symlink; struct fsal_obj_handle *lookup; struct gsh_buffdesc link_content; int ret = -1; sub_hdl = mdcdb_get_sub_handle(root_entry); ASSERT_NE(sub_hdl, nullptr); status = nfs_export_get_root_entry(a_export, &sub_hdl); ASSERT_EQ(status.major, 0); status = sub_hdl->obj_ops->symlink(sub_hdl, TEST_ROOT_LINK, TEST_ROOT, &attrs, &symlink, NULL, nullptr, nullptr); EXPECT_EQ(status.major, 0); root_entry->obj_ops->lookup(root_entry, TEST_ROOT_LINK, &lookup, NULL); EXPECT_EQ(lookup, symlink); status = symlink->obj_ops->readlink(symlink, &link_content, false); EXPECT_EQ(status.major, 0); if (link_content.len == bfr_content.len) ret = memcmp(link_content.addr, bfr_content.addr, link_content.len); EXPECT_EQ(ret, 0); gsh_free(link_content.addr); symlink->obj_ops->put_ref(symlink); lookup->obj_ops->put_ref(lookup); /* Remove symlink created while running test */ status = fsal_remove(root_entry, TEST_ROOT_LINK, NULL, NULL); ASSERT_EQ(status.major, 0); } TEST_F(SymlinkEmptyLatencyTest, LOOP) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *obj; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "s-%08x", i); status = root_entry->obj_ops->symlink(root_entry, fname, TEST_ROOT, &attrs, &obj, NULL, nullptr, nullptr); EXPECT_EQ(status.major, 0); obj->obj_ops->put_ref(obj); } now(&e_time); fprintf(stderr, "Average time per symlink: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove symlink created while running test */ for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "s-%08x", i); status = fsal_remove(root_entry, fname, NULL, NULL); ASSERT_EQ(status.major, 0); } } TEST_F(SymlinkEmptyLatencyTest, FSALCREATE) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *obj; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "s-%08x", i); status = fsal_create(root_entry, fname, SYMBOLIC_LINK, &attrs, TEST_ROOT, &obj, NULL, nullptr, nullptr); EXPECT_EQ(status.major, 0); obj->obj_ops->put_ref(obj); } now(&e_time); fprintf(stderr, "Average time per fsal_create: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove symlink created while running test */ for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "s-%08x", i); status = fsal_remove(root_entry, fname, NULL, NULL); ASSERT_EQ(status.major, 0); } } TEST_F(SymlinkFullLatencyTest, BIG) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *obj; struct timespec s_time, e_time; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "s-%08x", i); status = root_entry->obj_ops->symlink(root_entry, fname, TEST_ROOT, &attrs, &obj, NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0) << " failed to symlink " << fname; obj->obj_ops->put_ref(obj); } now(&e_time); fprintf(stderr, "Average time per symlink: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove symlink created while running test */ for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "s-%08x", i); status = fsal_remove(root_entry, fname, NULL, NULL); ASSERT_EQ(status.major, 0); } } TEST_F(SymlinkFullLatencyTest, BIG_BYPASS) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *sub_hdl; struct fsal_obj_handle *obj; struct timespec s_time, e_time; sub_hdl = mdcdb_get_sub_handle(root_entry); ASSERT_NE(sub_hdl, nullptr); status = nfs_export_get_root_entry(a_export, &sub_hdl); ASSERT_EQ(status.major, 0); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "s-%08x", i); status = sub_hdl->obj_ops->symlink(sub_hdl, fname, TEST_ROOT, &attrs, &obj, NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0) << " failed to symlink " << fname; obj->obj_ops->put_ref(obj); } now(&e_time); fprintf(stderr, "Average time per symlink: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove symlink created while running test */ for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "s-%08x", i); status = fsal_remove(root_entry, fname, NULL, NULL); ASSERT_EQ(status.major, 0); } } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_unlink_latency.cc000066400000000000000000000233331473756622300236610ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "unlink_latency" #define TEST_FILE "unlink_test_file" #define DIR_COUNT 100000 #define LOOP_COUNT 1000000 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; class UnlinkEmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { gtest::GaneshaFSALBaseTest::SetUp(); } virtual void TearDown() { gtest::GaneshaFSALBaseTest::TearDown(); } }; class UnlinkFullLatencyTest : public UnlinkEmptyLatencyTest { protected: virtual void SetUp() { UnlinkEmptyLatencyTest::SetUp(); create_and_prime_many(DIR_COUNT, NULL); } virtual void TearDown() { remove_many(DIR_COUNT, NULL); UnlinkEmptyLatencyTest::TearDown(); } }; } /* namespace */ TEST_F(UnlinkEmptyLatencyTest, SIMPLE) { fsal_status_t status; struct fsal_obj_handle *obj = nullptr; struct fsal_obj_handle *lookup = nullptr; /* Create file to unlink for the test */ status = fsal_create(test_root, TEST_FILE, REGULAR_FILE, &attrs, NULL, &obj, NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(obj, nullptr); status = test_root->obj_ops->unlink(test_root, obj, TEST_FILE, nullptr, nullptr); EXPECT_EQ(status.major, 0); status = test_root->obj_ops->lookup(test_root, TEST_FILE, &lookup, NULL); EXPECT_EQ(status.major, ERR_FSAL_NOENT); EXPECT_EQ(lookup, nullptr); obj->obj_ops->put_ref(obj); } TEST_F(UnlinkEmptyLatencyTest, SIMPLE_BYPASS) { fsal_status_t status; bool caller_perm_check = false; struct fsal_obj_handle *sub_hdl = nullptr; struct fsal_obj_handle *sub_hdl_obj = nullptr; struct fsal_obj_handle *lookup = nullptr; sub_hdl = mdcdb_get_sub_handle(test_root); ASSERT_NE(sub_hdl, nullptr); gtws_subcall(status = sub_hdl->obj_ops->open2( sub_hdl, NULL, FSAL_O_RDWR, FSAL_UNCHECKED, TEST_FILE, NULL, NULL, &sub_hdl_obj, NULL, &caller_perm_check, nullptr, nullptr)); ASSERT_EQ(status.major, 0); ASSERT_NE(sub_hdl_obj, nullptr); status = sub_hdl_obj->obj_ops->close(sub_hdl_obj); EXPECT_EQ(status.major, 0); status = sub_hdl->obj_ops->unlink(sub_hdl, sub_hdl_obj, TEST_FILE, nullptr, nullptr); EXPECT_EQ(status.major, 0); status = sub_hdl->obj_ops->lookup(sub_hdl, TEST_FILE, &lookup, NULL); EXPECT_EQ(status.major, ERR_FSAL_NOENT); EXPECT_EQ(lookup, nullptr); sub_hdl_obj->obj_ops->put_ref(sub_hdl_obj); } TEST_F(UnlinkEmptyLatencyTest, FSALREMOVE) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *obj; struct timespec s_time, e_time; /* Create files to unlink for the test */ status = fsal_create(test_root, TEST_FILE, REGULAR_FILE, &attrs, NULL, &obj, NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(obj, nullptr); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "fl-%08x", i); status = test_root->obj_ops->link(obj, test_root, fname, nullptr, nullptr); ASSERT_EQ(status.major, 0); } now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "fl-%08x", i); status = fsal_remove(test_root, fname, NULL, NULL); EXPECT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per fsal_remove: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove file created for running the test */ status = fsal_remove(test_root, TEST_FILE, NULL, NULL); ASSERT_EQ(status.major, 0); obj->obj_ops->put_ref(obj); } TEST_F(UnlinkFullLatencyTest, BIG) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *obj; struct timespec s_time, e_time; /* Create files to unlink for the test */ status = fsal_create(test_root, TEST_FILE, REGULAR_FILE, &attrs, NULL, &obj, NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(obj, nullptr); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "fl-%08x", i); status = test_root->obj_ops->link(obj, test_root, fname, nullptr, nullptr); ASSERT_EQ(status.major, 0); } now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "fl-%08x", i); status = test_root->obj_ops->unlink(test_root, obj, fname, nullptr, nullptr); EXPECT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per unlink: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove file created for running the test */ status = fsal_remove(test_root, TEST_FILE, NULL, NULL); ASSERT_EQ(status.major, 0); obj->obj_ops->put_ref(obj); } TEST_F(UnlinkFullLatencyTest, BIG_BYPASS) { fsal_status_t status; char fname[NAMELEN]; struct fsal_obj_handle *obj; struct fsal_obj_handle *sub_root; struct fsal_obj_handle *sub_obj; struct timespec s_time, e_time; /* Create files to unlink for the test */ status = fsal_create(test_root, TEST_FILE, REGULAR_FILE, &attrs, NULL, &obj, NULL, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(obj, nullptr); sub_root = mdcdb_get_sub_handle(test_root); ASSERT_NE(sub_root, nullptr); sub_obj = mdcdb_get_sub_handle(obj); ASSERT_NE(sub_obj, nullptr); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "fl-%08x", i); status = sub_root->obj_ops->link(sub_obj, sub_root, fname, nullptr, nullptr); ASSERT_EQ(status.major, 0); } now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { sprintf(fname, "fl-%08x", i); status = sub_root->obj_ops->unlink(sub_root, sub_obj, fname, nullptr, nullptr); EXPECT_EQ(status.major, 0); } now(&e_time); fprintf(stderr, "Average time per unlink: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove file created for running the test */ status = fsal_remove(test_root, TEST_FILE, NULL, NULL); ASSERT_EQ(status.major, 0); obj->obj_ops->put_ref(obj); } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/fsal_api/test_write2_latency.cc000066400000000000000000000300171473756622300235720ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Girjesh Rajoria * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include #include "gtest.hh" extern "C" { /* Manually forward this, as 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" #include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "write2_latency" #define TEST_FILE "test_file" #define LOOP_COUNT 1000000 #define OFFSET 0 namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; char *event_list = nullptr; char *profile_out = nullptr; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; class Write2EmptyLatencyTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { fsal_status_t status; bool caller_perm_check = false; gtest::GaneshaFSALBaseTest::SetUp(); test_file_state = op_ctx->fsal_export->exp_ops.alloc_state( op_ctx->fsal_export, STATE_TYPE_SHARE, NULL); ASSERT_NE(test_file_state, nullptr); status = test_root->obj_ops->open2( test_root, test_file_state, FSAL_O_RDWR, FSAL_UNCHECKED, TEST_FILE, NULL, NULL, &test_file, NULL, &caller_perm_check, nullptr, nullptr); ASSERT_EQ(status.major, 0); } virtual void TearDown() { fsal_status_t status; status = test_file->obj_ops->close2(test_file, test_file_state); EXPECT_EQ(0, status.major); free_state(test_file_state); status = fsal_remove(test_root, TEST_FILE, NULL, NULL); EXPECT_EQ(status.major, 0); test_file->obj_ops->put_ref(test_file); test_file = NULL; gtest::GaneshaFSALBaseTest::TearDown(); } struct fsal_obj_handle *test_file = nullptr; struct state_t *test_file_state; }; } /* namespace */ TEST_F(Write2EmptyLatencyTest, SIMPLE) { char *databuffer; struct fsal_io_arg *write_arg; struct async_process_data io_data; int bytes = 64; databuffer = (char *)malloc(bytes); memset(databuffer, 'a', bytes); write_arg = (struct fsal_io_arg *)alloca(sizeof(struct fsal_io_arg) + sizeof(struct iovec)); write_arg->info = NULL; write_arg->state = NULL; write_arg->offset = OFFSET; write_arg->io_request = bytes; write_arg->iov_count = 1; write_arg->iov[0].iov_len = bytes; write_arg->iov[0].iov_base = databuffer; write_arg->io_amount = 0; write_arg->fsal_stable = false; io_data.ret.major = ERR_FSAL_NO_ERROR; io_data.ret.minor = 0; io_data.done = false; io_data.fsa_cond = &cond; io_data.fsa_mutex = &mutex; fsal_write(test_file, true, write_arg, &io_data); EXPECT_EQ(io_data.ret.major, 0); free(databuffer); } TEST_F(Write2EmptyLatencyTest, SIMPLE_BYPASS) { struct fsal_obj_handle *sub_hdl; char *databuffer; struct fsal_io_arg *write_arg; struct async_process_data io_data; int bytes = 64; databuffer = (char *)malloc(bytes); memset(databuffer, 'a', bytes); write_arg = (struct fsal_io_arg *)alloca(sizeof(struct fsal_io_arg) + sizeof(struct iovec)); write_arg->info = NULL; write_arg->state = NULL; write_arg->offset = OFFSET; write_arg->io_request = bytes; write_arg->iov_count = 1; write_arg->iov[0].iov_len = bytes; write_arg->iov[0].iov_base = databuffer; write_arg->io_amount = 0; write_arg->fsal_stable = false; sub_hdl = mdcdb_get_sub_handle(test_file); ASSERT_NE(sub_hdl, nullptr); io_data.ret.major = ERR_FSAL_NO_ERROR; io_data.ret.minor = 0; io_data.done = false; io_data.fsa_cond = &cond; io_data.fsa_mutex = &mutex; fsal_write(sub_hdl, true, write_arg, &io_data); EXPECT_EQ(io_data.ret.major, 0); free(databuffer); } TEST_F(Write2EmptyLatencyTest, SMALL_STABLE_WRITE) { char *databuffer; struct fsal_io_arg *write_arg; struct async_process_data io_data; int bytes = 64; databuffer = (char *)malloc(bytes); memset(databuffer, 'a', bytes); write_arg = (struct fsal_io_arg *)alloca(sizeof(struct fsal_io_arg) + sizeof(struct iovec)); write_arg->info = NULL; write_arg->state = NULL; write_arg->offset = OFFSET; write_arg->io_request = bytes; write_arg->iov_count = 1; write_arg->iov[0].iov_len = bytes; write_arg->iov[0].iov_base = databuffer; write_arg->io_amount = 0; write_arg->fsal_stable = true; io_data.ret.major = ERR_FSAL_NO_ERROR; io_data.ret.minor = 0; io_data.done = false; io_data.fsa_cond = &cond; io_data.fsa_mutex = &mutex; fsal_write(test_file, true, write_arg, &io_data); EXPECT_EQ(io_data.ret.major, 0); free(databuffer); } TEST_F(Write2EmptyLatencyTest, LARGE_UNSTABLE_WRITE) { char *databuffer; struct fsal_io_arg *write_arg; struct async_process_data io_data; int bytes = (2 * 1024 * 1024); databuffer = (char *)malloc(bytes); memset(databuffer, 'a', bytes); write_arg = (struct fsal_io_arg *)alloca(sizeof(struct fsal_io_arg) + sizeof(struct iovec)); write_arg->info = NULL; write_arg->state = NULL; write_arg->offset = OFFSET; write_arg->io_request = bytes; write_arg->iov_count = 1; write_arg->iov[0].iov_len = bytes; write_arg->iov[0].iov_base = databuffer; write_arg->io_amount = 0; write_arg->fsal_stable = false; io_data.ret.major = ERR_FSAL_NO_ERROR; io_data.ret.minor = 0; io_data.done = false; io_data.fsa_cond = &cond; io_data.fsa_mutex = &mutex; fsal_write(test_file, true, write_arg, &io_data); EXPECT_EQ(io_data.ret.major, 0); free(databuffer); } TEST_F(Write2EmptyLatencyTest, LARGE_STABLE_WRITE) { char *databuffer; struct fsal_io_arg *write_arg; struct async_process_data io_data; int bytes = (2 * 1024 * 1024); databuffer = (char *)malloc(bytes); memset(databuffer, 'a', bytes); write_arg = (struct fsal_io_arg *)alloca(sizeof(struct fsal_io_arg) + sizeof(struct iovec)); write_arg->info = NULL; write_arg->state = NULL; write_arg->offset = OFFSET; write_arg->io_request = bytes; write_arg->iov_count = 1; write_arg->iov[0].iov_len = bytes; write_arg->iov[0].iov_base = databuffer; write_arg->io_amount = 0; write_arg->fsal_stable = true; io_data.ret.major = ERR_FSAL_NO_ERROR; io_data.ret.minor = 0; io_data.done = false; io_data.fsa_cond = &cond; io_data.fsa_mutex = &mutex; fsal_write(test_file, true, write_arg, &io_data); EXPECT_EQ(io_data.ret.major, 0); free(databuffer); } TEST_F(Write2EmptyLatencyTest, LOOP) { char *databuffer; struct fsal_io_arg *write_arg; struct async_process_data io_data; struct timespec s_time, e_time; int bytes = 64; databuffer = (char *)malloc(bytes); memset(databuffer, 'a', bytes); write_arg = (struct fsal_io_arg *)alloca(sizeof(struct fsal_io_arg) + sizeof(struct iovec)); write_arg->info = NULL; write_arg->state = NULL; write_arg->offset = OFFSET; write_arg->io_request = bytes; write_arg->iov_count = 1; write_arg->iov[0].iov_len = bytes; write_arg->iov[0].iov_base = databuffer; write_arg->io_amount = 0; write_arg->fsal_stable = false; now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i, write_arg->offset += 64) { io_data.ret.major = ERR_FSAL_NO_ERROR; io_data.ret.minor = 0; io_data.done = false; io_data.fsa_cond = &cond; io_data.fsa_mutex = &mutex; fsal_write(test_file, true, write_arg, &io_data); EXPECT_EQ(io_data.ret.major, 0); } now(&e_time); fprintf(stderr, "Average time per write2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); free(databuffer); } TEST_F(Write2EmptyLatencyTest, LOOP_BYPASS) { struct fsal_obj_handle *sub_hdl; char *databuffer; struct fsal_io_arg *write_arg; struct async_process_data io_data; struct timespec s_time, e_time; int bytes = 64; databuffer = (char *)malloc(bytes); memset(databuffer, 'a', bytes); write_arg = (struct fsal_io_arg *)alloca(sizeof(struct fsal_io_arg) + sizeof(struct iovec)); write_arg->info = NULL; write_arg->state = NULL; write_arg->offset = OFFSET; write_arg->io_request = bytes; write_arg->iov_count = 1; write_arg->iov[0].iov_len = bytes; write_arg->iov[0].iov_base = databuffer; write_arg->io_amount = 0; write_arg->fsal_stable = false; sub_hdl = mdcdb_get_sub_handle(test_file); ASSERT_NE(sub_hdl, nullptr); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i, write_arg->offset += 64) { io_data.ret.major = ERR_FSAL_NO_ERROR; io_data.ret.minor = 0; io_data.done = false; io_data.fsa_cond = &cond; io_data.fsa_mutex = &mutex; fsal_write(sub_hdl, true, write_arg, &io_data); EXPECT_EQ(io_data.ret.major, 0); } now(&e_time); fprintf(stderr, "Average time per write2: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); free(databuffer); } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/gtest.hh000066400000000000000000000214501473756622300171630ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Daniel Gryniewicz * * This program 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 3 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 * 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 * * ------------- */ #include "gtest/gtest.h" /* Make sure urcu-bp.h is included as C++ */ #include extern "C" { /* Manually forward this, an 9P is not C++ safe */ void admin_halt(void); /* Don't include rpcent.h; it has C++ issues, and is unneeded */ #define _RPC_RPCENT_H /* Ganesha headers */ #include "nfs_lib.h" #include "fsal.h" #include "export_mgr.h" #include "nfs_exports.h" /* LTTng headers */ #include /* gperf headers */ #include } #ifndef GTEST_GTEST_HH #define GTEST_GTEST_HH #define NAMELEN 24 #define gtws_subcall(call) do { \ struct fsal_export *_saveexp = op_ctx->fsal_export; \ op_ctx->fsal_export = _saveexp->sub_export; \ call; \ op_ctx->fsal_export = _saveexp; \ } while (0) namespace gtest { class Environment* env; class Environment : public ::testing::Environment { public: Environment() : Environment(NULL, NULL, -1, NULL) {} Environment(char* ganesha_conf, char* lpath, int dlevel, char *_ses_name, const char *_test_root_name = nullptr, uint16_t _export_id = 77) : ganesha(nfs_libmain, ganesha_conf, lpath, dlevel), session_name(_ses_name), test_root_name(_test_root_name), export_id(_export_id), handle(NULL) { std::this_thread::sleep_for(std::chrono::seconds(5)); } virtual ~Environment() { admin_halt(); ganesha.join(); } virtual void SetUp() { struct lttng_domain dom; if (!session_name) { /* Don't setup LTTng */ return; } /* Set up LTTng */ memset(&dom, 0, sizeof(dom)); dom.type = LTTNG_DOMAIN_UST; dom.buf_type = LTTNG_BUFFER_PER_UID; handle = lttng_create_handle(session_name, &dom); } virtual void TearDown() { if (handle) { lttng_destroy_handle(handle); handle = NULL; } } struct lttng_handle* getLTTng() { return handle; } const char *get_test_root_name() { return test_root_name; } uint16_t get_export_id() { return export_id; } std::thread ganesha; char *session_name; const char *test_root_name; uint16_t export_id; struct lttng_handle* handle; }; class GaneshaBaseTest : public ::testing::Test { protected: virtual void enableEvents(char *event_list) { struct lttng_event ev; int ret; if (!env->getLTTng()) { /* No LTTng this run */ return; } memset(&ev, 0, sizeof(ev)); ev.type = LTTNG_EVENT_TRACEPOINT; ev.loglevel_type = LTTNG_EVENT_LOGLEVEL_ALL; ev.loglevel = -1; if (!event_list) { /* Do them all */ strcpy(ev.name, "*"); ret = lttng_enable_event_with_exclusions(env->getLTTng(), &ev, NULL, NULL, 0, NULL); ASSERT_GE(ret, 0); } else { char *event_name; event_name = strtok(event_list, ","); while (event_name != NULL) { /* Copy name and type of the event */ strncpy(ev.name, event_name, LTTNG_SYMBOL_NAME_LEN); ev.name[sizeof(ev.name) - 1] = '\0'; ret = lttng_enable_event_with_exclusions(env->getLTTng(), &ev, NULL, NULL, 0, NULL); ASSERT_GE(ret, 0); /* Next event */ event_name = strtok(NULL, ","); } } } virtual void disableEvents(char *event_list) { struct lttng_event ev; int ret; if (!env->getLTTng()) { /* No LTTng this run */ return; } memset(&ev, 0, sizeof(ev)); ev.type = LTTNG_EVENT_ALL; ev.loglevel = -1; if (!event_list) { /* Do them all */ ret = lttng_disable_event_ext(env->getLTTng(), &ev, NULL, NULL); ASSERT_GE(ret, 0); } else { char *event_name; event_name = strtok(event_list, ","); while (event_name != NULL) { /* Copy name and type of the event */ strncpy(ev.name, event_name, LTTNG_SYMBOL_NAME_LEN); ev.name[sizeof(ev.name) - 1] = '\0'; ret = lttng_disable_event_ext(env->getLTTng(), &ev, NULL, NULL); ASSERT_GE(ret, 0); /* Next event */ event_name = strtok(NULL, ","); } } } virtual void SetUp() { } virtual void TearDown() { } }; class GaneshaFSALBaseTest : public gtest::GaneshaBaseTest { protected: static fsal_errors_t readdir_callback(void *opaque, struct fsal_obj_handle *obj, const struct fsal_attrlist *attr, uint64_t mounted_on_fileid, uint64_t cookie, enum cb_state cb_state) { return ERR_FSAL_NO_ERROR; } virtual void SetUp() { fsal_status_t status; struct fsal_attrlist attrs_out; gtest::GaneshaBaseTest::SetUp(); a_export = get_gsh_export(env->get_export_id()); ASSERT_NE(a_export, nullptr); status = nfs_export_get_root_entry(a_export, &root_entry); ASSERT_EQ(status.major, 0); ASSERT_NE(root_entry, nullptr); /* Ganesha call paths need real or forged context info */ init_op_context_simple(&op_context, a_export, a_export->fsal_export); // create root directory for test FSAL_SET_MASK(attrs.valid_mask, ATTR_MODE | ATTR_OWNER | ATTR_GROUP); attrs.mode = 0777; /* XXX */ attrs.owner = 667; attrs.group = 766; fsal_prepare_attrs(&attrs_out, 0); status = fsal_create(root_entry, env->get_test_root_name(), DIRECTORY, &attrs, NULL, &test_root, &attrs_out, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(test_root, nullptr); fsal_release_attrs(&attrs_out); } virtual void TearDown() { fsal_status_t status; status = test_root->obj_ops->unlink(root_entry, test_root, env->get_test_root_name(), nullptr, nullptr); EXPECT_EQ(0, status.major); test_root->obj_ops->put_ref(test_root); test_root = NULL; root_entry->obj_ops->put_ref(root_entry); root_entry = NULL; a_export = NULL; release_op_context(); gtest::GaneshaBaseTest::TearDown(); } void create_and_prime_many(int count, struct fsal_obj_handle **objs = NULL, struct fsal_obj_handle *directory = NULL) { fsal_status_t status; char fname[NAMELEN]; struct fsal_attrlist attrs_out; unsigned int num_entries; bool eod_met; attrmask_t attrmask = 0; uint32_t tracker; struct fsal_obj_handle *obj; if (directory == NULL) directory = test_root; /* create a bunch of dirents */ for (int i = 0; i < count; ++i) { fsal_prepare_attrs(&attrs_out, 0); sprintf(fname, "f-%08x", i); status = fsal_create(directory, fname, REGULAR_FILE, &attrs, NULL, &obj, &attrs_out, nullptr, nullptr); ASSERT_EQ(status.major, 0); ASSERT_NE(obj, nullptr); fsal_release_attrs(&attrs_out); if (objs != NULL) objs[i] = obj; else obj->obj_ops->put_ref(obj); } /* Prime the cache */ status = fsal_readdir(directory, 0, &num_entries, &eod_met, attrmask, readdir_callback, &tracker); } void remove_many(int count, struct fsal_obj_handle **objs = NULL, struct fsal_obj_handle *directory = NULL) { fsal_status_t status; char fname[NAMELEN]; if (directory == NULL) directory = test_root; for (int i = 0; i < count; ++i) { sprintf(fname, "f-%08x", i); if (objs != NULL) objs[i]->obj_ops->put_ref(objs[i]); status = fsal_remove(directory, fname, NULL, NULL); EXPECT_EQ(status.major, 0); } } struct req_op_context op_context; struct fsal_attrlist attrs; struct gsh_export* a_export = nullptr; struct fsal_obj_handle *root_entry = nullptr; struct fsal_obj_handle *test_root = nullptr; }; } // namespase gtest #endif /* GTEST_GTEST_HH */ nfs-ganesha-6.5/src/gtest/gtest_nfs4.hh000066400000000000000000000152631473756622300201220ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Frank Filz * * This program 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 3 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 * 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 * * ------------- */ #include "gtest.hh" extern "C" { /* Don't include rpcent.h; it has C++ issues, and is unneeded */ #define _RPC_RPCENT_H /* Ganesha headers */ #include "nfs_lib.h" #include "nfs_file_handle.h" #include "nfs_proto_functions.h" } #ifndef GTEST_GTEST_NFS4_HH #define GTEST_GTEST_NFS4_HH namespace gtest { class GaeshaNFS4BaseTest : public gtest::GaneshaFSALBaseTest { protected: virtual void SetUp() { gtest::GaneshaFSALBaseTest::SetUp(); data = (compound_data_t *) gsh_calloc(1, sizeof(*data)); memset(&arg, 0, sizeof(nfs_arg_t)); memset(&resp, 0, sizeof(struct nfs_resop4)); ops = (struct nfs_argop4 *) gsh_calloc(1, sizeof(struct nfs_argop4)); arg.arg_compound4.argarray.argarray_len = 1; arg.arg_compound4.argarray.argarray_val = ops; /* Setup some basic stuff (that will be overrode) so TearDown works. */ data->minorversion = 0; ops[0].argop = NFS4_OP_PUTROOTFH; } virtual void TearDown() { bool rc; set_current_entry(data, nullptr); nfs4_Compound_FreeOne(&resp); /* Free the compound data and response */ compound_data_Free(data); /* Free the args structure. */ rc = xdr_free((xdrproc_t) xdr_COMPOUND4args, &arg); EXPECT_EQ(rc, true); gtest::GaneshaFSALBaseTest::TearDown(); } void setCurrentFH(struct fsal_obj_handle *entry) { bool fhres; /* Convert root_obj to a file handle in the args */ fhres = nfs4_FSALToFhandle(data->currentFH.nfs_fh4_val == NULL, &data->currentFH, entry, op_ctx->ctx_export); EXPECT_EQ(fhres, true); set_current_entry(data, entry); } void setSavedFH(struct fsal_obj_handle *entry) { bool fhres; /* Convert root_obj to a file handle in the args */ fhres = nfs4_FSALToFhandle(data->savedFH.nfs_fh4_val == NULL, &data->savedFH, entry, op_ctx->ctx_export); EXPECT_EQ(fhres, true); set_saved_entry(data, entry); } void set_saved_export(void) { /* Set saved export from op_ctx */ if (data->saved_export != NULL) put_gsh_export(data->saved_export); /* Save the export information and take reference. */ get_gsh_export_ref(op_ctx->ctx_export); data->saved_export = op_ctx->ctx_export; data->saved_export_perms = op_ctx->export_perms; } void setup_lookup(int pos, const char *name) { gsh_free(ops[pos].nfs_argop4_u.oplookup.objname.utf8string_val); ops[pos].argop = NFS4_OP_LOOKUP; ops[pos].nfs_argop4_u.oplookup.objname.utf8string_len = strlen(name); ops[pos].nfs_argop4_u.oplookup.objname.utf8string_val = gsh_strdup(name); } void cleanup_lookup(int pos) { gsh_free(ops[pos].nfs_argop4_u.oplookup.objname.utf8string_val); ops[pos].nfs_argop4_u.oplookup.objname.utf8string_len = 0; ops[pos].nfs_argop4_u.oplookup.objname.utf8string_val = nullptr; } void setup_putfh(int pos, struct fsal_obj_handle *entry) { bool fhres; gsh_free(ops[pos].nfs_argop4_u.opputfh.object.nfs_fh4_val); ops[pos].argop = NFS4_OP_PUTFH; /* Convert root_obj to a file handle in the args */ fhres = nfs4_FSALToFhandle(true, &ops[pos].nfs_argop4_u.opputfh.object, entry, op_ctx->ctx_export); EXPECT_EQ(fhres, true); } void cleanup_putfh(int pos) { gsh_free(ops[pos].nfs_argop4_u.opputfh.object.nfs_fh4_val); ops[pos].nfs_argop4_u.opputfh.object.nfs_fh4_len = 0; ops[pos].nfs_argop4_u.opputfh.object.nfs_fh4_val = nullptr; } void setup_rename(int pos, const char *oldname, const char *newname) { gsh_free(ops[pos].nfs_argop4_u.oprename.oldname.utf8string_val); gsh_free(ops[pos].nfs_argop4_u.oprename.newname.utf8string_val); ops[pos].argop = NFS4_OP_RENAME; ops[pos].nfs_argop4_u.oprename.oldname.utf8string_len = strlen(oldname); ops[pos].nfs_argop4_u.oprename.oldname.utf8string_val = gsh_strdup(oldname); ops[pos].nfs_argop4_u.oprename.newname.utf8string_len = strlen(newname); ops[pos].nfs_argop4_u.oprename.newname.utf8string_val = gsh_strdup(newname); } void swap_rename(int pos) { component4 temp = ops[pos].nfs_argop4_u.oprename.newname; ops[pos].nfs_argop4_u.oprename.newname = ops[pos].nfs_argop4_u.oprename.oldname; ops[pos].nfs_argop4_u.oprename.oldname = temp; } void cleanup_rename(int pos) { gsh_free(ops[pos].nfs_argop4_u.oprename.oldname.utf8string_val); gsh_free(ops[pos].nfs_argop4_u.oprename.newname.utf8string_val); ops[pos].nfs_argop4_u.oprename.oldname.utf8string_len = 0; ops[pos].nfs_argop4_u.oprename.oldname.utf8string_val = nullptr; ops[pos].nfs_argop4_u.oprename.newname.utf8string_len = 0; ops[pos].nfs_argop4_u.oprename.newname.utf8string_val = nullptr; } void setup_link(int pos, const char *newname) { gsh_free(ops[pos].nfs_argop4_u.oplink.newname.utf8string_val); ops[pos].argop = NFS4_OP_LINK; ops[pos].nfs_argop4_u.oplink.newname.utf8string_len = strlen(newname); ops[pos].nfs_argop4_u.oplink.newname.utf8string_val = gsh_strdup(newname); } void cleanup_link(int pos) { gsh_free(ops[pos].nfs_argop4_u.oplink.newname.utf8string_val); ops[pos].nfs_argop4_u.oplink.newname.utf8string_len = 0; ops[pos].nfs_argop4_u.oplink.newname.utf8string_val = nullptr; } compound_data_t *data; struct nfs_argop4 *ops; nfs_arg_t arg; struct nfs_resop4 resp; }; } // namespase gtest #endif /* GTEST_GTEST_NFS4_HH */ nfs-ganesha-6.5/src/gtest/nfs4/000077500000000000000000000000001473756622300163645ustar00rootroot00000000000000nfs-ganesha-6.5/src/gtest/nfs4/CMakeLists.txt000066400000000000000000000054561473756622300211360ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- set(test_nfs4_lookup_latency_SRCS test_nfs4_lookup_latency.cc ) add_executable(test_nfs4_lookup_latency ${test_nfs4_lookup_latency_SRCS}) add_sanitizers(test_nfs4_lookup_latency) target_link_libraries(test_nfs4_lookup_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_nfs4_lookup_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_nfs4_putfh_latency_SRCS test_nfs4_putfh_latency.cc ) add_executable(test_nfs4_putfh_latency ${test_nfs4_putfh_latency_SRCS}) add_sanitizers(test_nfs4_putfh_latency) target_link_libraries(test_nfs4_putfh_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_nfs4_putfh_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_nfs4_rename_latency_SRCS test_nfs4_rename_latency.cc ) add_executable(test_nfs4_rename_latency ${test_nfs4_rename_latency_SRCS}) add_sanitizers(test_nfs4_rename_latency) target_link_libraries(test_nfs4_rename_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_nfs4_rename_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") set(test_nfs4_link_latency_SRCS test_nfs4_link_latency.cc ) add_executable(test_nfs4_link_latency ${test_nfs4_link_latency_SRCS}) add_sanitizers(test_nfs4_link_latency) target_link_libraries(test_nfs4_link_latency ganesha_nfsd ${LIBTIRPC_LIBRARIES} ${UNITTEST_LIBS} ${LTTNG_LIBRARIES} ${LTTNG_CTL_LIBRARIES} ${GPERFTOOLS_LIBRARIES} ) set_target_properties(test_nfs4_link_latency PROPERTIES COMPILE_FLAGS "${UNITTEST_CXX_FLAGS}") nfs-ganesha-6.5/src/gtest/nfs4/test_nfs4_link_latency.cc000066400000000000000000000142121473756622300233400ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Frank Filz * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest_nfs4.hh" extern "C" { /* Manually forward this, an 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ //#include "sal_data.h" //#include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "nfs4_link_latency" #define DIR_COUNT 100000 #define LOOP_COUNT 1000000 namespace { char *event_list = nullptr; char *profile_out = nullptr; class LinkFullLatencyTest : public gtest::GaeshaNFS4BaseTest { protected: virtual void SetUp() { GaeshaNFS4BaseTest::SetUp(); create_and_prime_many(DIR_COUNT, objs); set_saved_export(); } virtual void TearDown() { remove_many(DIR_COUNT, objs); GaeshaNFS4BaseTest::TearDown(); } struct fsal_obj_handle *objs[DIR_COUNT]; }; } /* namespace */ TEST_F(LinkFullLatencyTest, BIG_SINGLE) { int rc; fsal_status_t status; char fname[NAMELEN]; struct timespec s_time, e_time; enableEvents(event_list); now(&s_time); sprintf(fname, "d-%08x-%08x", DIR_COUNT / 5, 1); setup_link(0, fname); setCurrentFH(test_root); setSavedFH(objs[DIR_COUNT / 5]); rc = nfs4_op_link(&ops[0], data, &resp); EXPECT_EQ(rc, NFS4_OK); now(&e_time); disableEvents(event_list); fprintf(stderr, "Average time per link: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time)); /* Remove the created link */ status = fsal_remove(test_root, fname, NULL, NULL); EXPECT_EQ(status.major, 0); } TEST_F(LinkFullLatencyTest, BIG) { int rc; fsal_status_t status; char fname[NAMELEN]; struct timespec s_time, e_time; enableEvents(event_list); if (profile_out) ProfilerStart(profile_out); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { int n = i % DIR_COUNT; int r = i / DIR_COUNT; sprintf(fname, "d-%08x-%08x", n, r + 1); setup_link(0, fname); setCurrentFH(test_root); setSavedFH(objs[n]); rc = nfs4_op_link(&ops[0], data, &resp); EXPECT_EQ(rc, NFS4_OK); cleanup_link(0); } now(&e_time); if (profile_out) ProfilerStop(); disableEvents(event_list); fprintf(stderr, "Average time per link: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); /* Remove the created links */ for (int i = 0; i < LOOP_COUNT; ++i) { int n = i % DIR_COUNT; int r = i / DIR_COUNT; sprintf(fname, "d-%08x-%08x", n, r + 1); status = fsal_remove(test_root, fname, NULL, NULL); EXPECT_EQ(status.major, 0); } } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/nfs4/test_nfs4_lookup_latency.cc000066400000000000000000000150721473756622300237210ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Frank Filz * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest_nfs4.hh" extern "C" { /* Manually forward this, an 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ //#include "sal_data.h" //#include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "nfs4_lookup_latency" #define FILE_COUNT 100000 #define LOOP_COUNT 1000000 namespace { char *event_list = nullptr; char *profile_out = nullptr; class LookupEmptyLatencyTest : public gtest::GaeshaNFS4BaseTest {}; class LookupFullLatencyTest : public gtest::GaeshaNFS4BaseTest { protected: virtual void SetUp() { GaeshaNFS4BaseTest::SetUp(); create_and_prime_many(FILE_COUNT, objs); } virtual void TearDown() { remove_many(FILE_COUNT, objs); GaeshaNFS4BaseTest::TearDown(); } struct fsal_obj_handle *objs[FILE_COUNT]; }; } /* namespace */ TEST_F(LookupEmptyLatencyTest, SIMPLE) { int rc; setCurrentFH(root_entry); setup_lookup(0, TEST_ROOT); enableEvents(event_list); rc = nfs4_op_lookup(&ops[0], data, &resp); EXPECT_EQ(rc, NFS4_OK); EXPECT_EQ(test_root, data->current_obj); disableEvents(event_list); } TEST_F(LookupEmptyLatencyTest, LOOP) { int rc; struct timespec s_time, e_time; setup_lookup(0, TEST_ROOT); enableEvents(event_list); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { setCurrentFH(root_entry); rc = nfs4_op_lookup(&ops[0], data, &resp); EXPECT_EQ(rc, NFS4_OK); EXPECT_EQ(test_root, data->current_obj); } now(&e_time); disableEvents(event_list); fprintf(stderr, "Average time per lookup: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(LookupFullLatencyTest, BIG_SINGLE) { int rc; char fname[NAMELEN]; struct timespec s_time, e_time; enableEvents(event_list); now(&s_time); sprintf(fname, "f-%08x", FILE_COUNT / 5); setup_lookup(0, fname); setCurrentFH(test_root); rc = nfs4_op_lookup(&ops[0], data, &resp); EXPECT_EQ(rc, NFS4_OK); EXPECT_EQ(objs[FILE_COUNT / 5], data->current_obj); now(&e_time); disableEvents(event_list); fprintf(stderr, "Average time per lookup: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time)); } TEST_F(LookupFullLatencyTest, BIG) { int rc; char fname[NAMELEN]; struct timespec s_time, e_time; enableEvents(event_list); if (profile_out) ProfilerStart(profile_out); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { int n = i % FILE_COUNT; sprintf(fname, "f-%08x", n); setup_lookup(0, fname); setCurrentFH(test_root); rc = nfs4_op_lookup(&ops[0], data, &resp); EXPECT_EQ(rc, NFS4_OK); EXPECT_EQ(objs[n], data->current_obj); cleanup_lookup(0); } now(&e_time); if (profile_out) ProfilerStop(); disableEvents(event_list); fprintf(stderr, "Average time per lookup: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/nfs4/test_nfs4_putfh_latency.cc000066400000000000000000000145241473756622300235370ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Frank Filz * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest_nfs4.hh" extern "C" { /* Manually forward this, an 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ //#include "sal_data.h" //#include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "nfs4_putfh_latency" #define FILE_COUNT 100000 #define LOOP_COUNT 1000000 namespace { char *event_list = nullptr; char *profile_out = nullptr; class PutfhEmptyLatencyTest : public gtest::GaeshaNFS4BaseTest {}; class PutfhFullLatencyTest : public gtest::GaeshaNFS4BaseTest { protected: virtual void SetUp() { GaeshaNFS4BaseTest::SetUp(); create_and_prime_many(FILE_COUNT, objs); } virtual void TearDown() { remove_many(FILE_COUNT, objs); GaeshaNFS4BaseTest::TearDown(); } struct fsal_obj_handle *objs[FILE_COUNT]; }; } /* namespace */ TEST_F(PutfhEmptyLatencyTest, SIMPLE) { int rc; setup_putfh(0, test_root); enableEvents(event_list); rc = nfs4_op_putfh(&ops[0], data, &resp); EXPECT_EQ(rc, NFS4_OK); EXPECT_EQ(test_root, data->current_obj); disableEvents(event_list); } TEST_F(PutfhEmptyLatencyTest, LOOP) { int rc; struct timespec s_time, e_time; setup_putfh(0, test_root); enableEvents(event_list); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { rc = nfs4_op_putfh(&ops[0], data, &resp); EXPECT_EQ(rc, NFS4_OK); EXPECT_EQ(test_root, data->current_obj); } now(&e_time); disableEvents(event_list); fprintf(stderr, "Average time per putfh: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(PutfhFullLatencyTest, BIG_SINGLE) { int rc; struct timespec s_time, e_time; enableEvents(event_list); now(&s_time); setup_putfh(0, objs[FILE_COUNT / 5]); rc = nfs4_op_putfh(&ops[0], data, &resp); EXPECT_EQ(rc, NFS4_OK); EXPECT_EQ(objs[FILE_COUNT / 5], data->current_obj); now(&e_time); disableEvents(event_list); fprintf(stderr, "Average time per putfh: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time)); } TEST_F(PutfhFullLatencyTest, BIG) { int rc; struct timespec s_time, e_time; enableEvents(event_list); if (profile_out) ProfilerStart(profile_out); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { int n = i % FILE_COUNT; setup_putfh(0, objs[n]); rc = nfs4_op_putfh(&ops[0], data, &resp); EXPECT_EQ(rc, NFS4_OK); EXPECT_EQ(objs[n], data->current_obj); cleanup_putfh(0); } now(&e_time); if (profile_out) ProfilerStop(); disableEvents(event_list); fprintf(stderr, "Average time per putfh: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/nfs4/test_nfs4_rename_latency.cc000066400000000000000000000163061473756622300236600ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Frank Filz * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include #include #include #include "gtest_nfs4.hh" extern "C" { /* Manually forward this, an 9P is not C++ safe */ void admin_halt(void); /* Ganesha headers */ //#include "sal_data.h" //#include "common_utils.h" /* For MDCACHE bypass. Use with care */ #include "../FSAL/Stackable_FSALs/FSAL_MDCACHE/mdcache_debug.h" } #define TEST_ROOT "nfs4_rename_latency" #define TEST_ROOT2 "nfs4_rename_latency2" #define FILE_COUNT 100000 #define LOOP_COUNT 1000000 namespace { char *event_list = nullptr; char *profile_out = nullptr; class RenameEmptyLatencyTest : public gtest::GaeshaNFS4BaseTest {}; class RenameFullLatencyTest : public gtest::GaeshaNFS4BaseTest { protected: virtual void SetUp() { GaeshaNFS4BaseTest::SetUp(); create_and_prime_many(FILE_COUNT, objs); set_saved_export(); } virtual void TearDown() { remove_many(FILE_COUNT, objs); GaeshaNFS4BaseTest::TearDown(); } struct fsal_obj_handle *objs[FILE_COUNT]; }; } /* namespace */ TEST_F(RenameEmptyLatencyTest, SIMPLE) { int rc; setCurrentFH(root_entry); setSavedFH(root_entry); setup_rename(0, TEST_ROOT, TEST_ROOT2); enableEvents(event_list); rc = nfs4_op_rename(&ops[0], data, &resp); EXPECT_EQ(rc, NFS4_OK); disableEvents(event_list); swap_rename(0); rc = nfs4_op_rename(&ops[0], data, &resp); EXPECT_EQ(rc, NFS4_OK); } TEST_F(RenameEmptyLatencyTest, LOOP) { int rc; struct timespec s_time, e_time; setCurrentFH(root_entry); setSavedFH(root_entry); setup_rename(0, TEST_ROOT, TEST_ROOT2); enableEvents(event_list); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { rc = nfs4_op_rename(&ops[0], data, &resp); EXPECT_EQ(rc, NFS4_OK); /* Set up so next time, we rename back... Even loop count value assures * that the file ends up having the original name. */ swap_rename(0); } now(&e_time); disableEvents(event_list); fprintf(stderr, "Average time per rename: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } TEST_F(RenameFullLatencyTest, BIG_SINGLE) { int rc; char fname[NAMELEN], fname2[NAMELEN]; struct timespec s_time, e_time; enableEvents(event_list); now(&s_time); sprintf(fname, "f-%08x", FILE_COUNT / 5); sprintf(fname2, "r-%08x", FILE_COUNT / 5); setup_rename(0, fname, fname2); setCurrentFH(test_root); setSavedFH(test_root); rc = nfs4_op_rename(&ops[0], data, &resp); EXPECT_EQ(rc, NFS4_OK); now(&e_time); disableEvents(event_list); fprintf(stderr, "Average time per rename: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time)); swap_rename(0); rc = nfs4_op_rename(&ops[0], data, &resp); EXPECT_EQ(rc, NFS4_OK); } TEST_F(RenameFullLatencyTest, BIG) { int rc; char fname[NAMELEN], fname2[NAMELEN]; struct timespec s_time, e_time; enableEvents(event_list); if (profile_out) ProfilerStart(profile_out); now(&s_time); for (int i = 0; i < LOOP_COUNT; ++i) { int n = i % FILE_COUNT; sprintf(fname, "f-%08x", n); sprintf(fname2, "r-%08x", n); if ((int)(i / FILE_COUNT) % 2 == 0) { /* On odd cycles, rename from original */ setup_rename(0, fname, fname2); } else { /* On even cycles, rename back to original */ setup_rename(0, fname2, fname); } setCurrentFH(test_root); setSavedFH(test_root); rc = nfs4_op_rename(&ops[0], data, &resp); EXPECT_EQ(rc, NFS4_OK); cleanup_rename(0); } now(&e_time); if (profile_out) ProfilerStop(); disableEvents(event_list); fprintf(stderr, "Average time per rename: %" PRIu64 " ns\n", timespec_diff(&s_time, &e_time) / LOOP_COUNT); } int main(int argc, char *argv[]) { int code = 0; char *session_name = NULL; char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); opts.add_options()("session", po::value(), "LTTng session name"); opts.add_options()("event-list", po::value(), "LTTng event list, comma separated"); opts.add_options()("profile", po::value(), "Enable profiling and set output file."); po::variables_map::iterator vm_iter; po::command_line_parser parser{ argc, argv }; parser.options(opts).allow_unregistered(); po::store(parser.run(), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } vm_iter = vm.find("session"); if (vm_iter != vm.end()) { session_name = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("event-list"); if (vm_iter != vm.end()) { event_list = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("profile"); if (vm_iter != vm.end()) { profile_out = (char *)vm_iter->second.as() .c_str(); } ::testing::InitGoogleTest(&argc, argv); gtest::env = new gtest::Environment(ganesha_conf, lpath, dlevel, session_name, TEST_ROOT, export_id); ::testing::AddGlobalTestEnvironment(gtest::env); code = RUN_ALL_TESTS(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/test_ci_hash_dist1.cc000066400000000000000000000111541473756622300215640ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2015 Red Hat, Inc. * Contributor : Matt Benjamin * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include "gtest/gtest.h" #include #include #include /* Make sure urcu-bp.h is included as C++ */ #include extern "C" { /* Don't include rpcent.h; it has C++ issues, and is unneeded */ #define _RPC_RPCENT_H /* Ganesha headers */ #include "nfs_lib.h" #include "export_mgr.h" #include "nfs_exports.h" #include "sal_data.h" #include "fsal.h" } namespace bf = boost::filesystem; namespace { char *ganesha_conf = nullptr; char *lpath = nullptr; int dlevel = -1; uint16_t export_id = 77; struct req_op_context req_ctx; struct fsal_attrlist object_attributes; struct gsh_export *a_export = nullptr; struct fsal_obj_handle *root_entry = nullptr; struct fsal_obj_handle *test_root = nullptr; #if 0 std::uniform_int_distribution uint_dist; std::mt19937 rng; p->cksum = XXH64(p->data, 65536, 8675309); #endif int ganesha_server() { /* XXX */ return nfs_libmain(ganesha_conf, lpath, dlevel); } } /* namespace */ TEST(CI_HASH_DIST1, INIT) { fsal_status_t status; a_export = get_gsh_export(export_id); ASSERT_NE(a_export, nullptr); status = nfs_export_get_root_entry(a_export, &root_entry); ASSERT_NE(root_entry, nullptr); /* Ganesha call paths need real or forged context info */ init_op_context_simple(&req_ctx, a_export, a_export->fsal_export); memset(&object_attributes, 0, sizeof(object_attributes)); } TEST(CI_HASH_DIST1, CREATE_ROOT) { fsal_status_t status; struct fsal_attrlist *attrs_out = nullptr; // create root directory for test FSAL_SET_MASK(object_attributes.request_mask, ATTR_MODE | ATTR_OWNER | ATTR_GROUP); object_attributes.mode = 777; /* XXX */ object_attributes.owner = 667; object_attributes.group = 766; status = root_entry->obj_ops->mkdir(root_entry, "ci_hash_dist1", &object_attributes, &test_root, attrs_out, nullptr, nullptr); ASSERT_NE(test_root, nullptr); } int main(int argc, char *argv[]) { int code = 0; using namespace std; namespace po = boost::program_options; po::options_description opts("program options"); po::variables_map vm; try { opts.add_options()("config", po::value(), "path to Ganesha conf file"); opts.add_options()("logfile", po::value(), "log to the provided file path"); opts.add_options()( "export", po::value(), "id of export on which to operate (must exist)"); opts.add_options()("debug", po::value(), "ganesha debug level"); po::variables_map::iterator vm_iter; po::store(po::parse_command_line(argc, argv, opts), vm); po::notify(vm); // use config vars--leaves them on the stack vm_iter = vm.find("config"); if (vm_iter != vm.end()) { ganesha_conf = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("logfile"); if (vm_iter != vm.end()) { lpath = (char *)vm_iter->second.as() .c_str(); } vm_iter = vm.find("debug"); if (vm_iter != vm.end()) { dlevel = ReturnLevelAscii( (char *)vm_iter->second.as() .c_str()); } vm_iter = vm.find("export"); if (vm_iter != vm.end()) { export_id = vm_iter->second.as(); } ::testing::InitGoogleTest(&argc, argv); std::thread ganesha(ganesha_server); std::this_thread::sleep_for(std::chrono::seconds(5)); code = RUN_ALL_TESTS(); ganesha.join(); } catch (po::error &e) { cout << "Error parsing opts " << e.what() << endl; } catch (...) { cout << "Unhandled exception in main()" << endl; } return code; } nfs-ganesha-6.5/src/gtest/test_example.cc000066400000000000000000000025251473756622300205170ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2015 Red Hat, Inc. * Contributor : Matt Benjamin * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include "gtest/gtest.h" extern "C" { /* Ganesha headers */ } namespace { bool global_decls = false; } /* namespace */ TEST(EXAMPLE, INIT) { ASSERT_EQ(0, 0); } int main(int argc, char *argv[]) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } nfs-ganesha-6.5/src/gtest/test_rbt.cc000066400000000000000000000101421473756622300176450ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Copyright (C) 2018 Red Hat, Inc. * Contributor : Matt Benjamin * * This program 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 3 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 * 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 * * ------------- */ #include #include #include #include #include #include #include #include "gtest/gtest.h" #include #include #include /* Make sure urcu-bp.h is included as C++ */ #include extern "C" { /* Don't include rpcent.h; it has C++ issues, and is unneeded */ #define _RPC_RPCENT_H #include #include #include #include "nfs_core.h" /* LTTng headers */ #include /* gperf headers */ #include struct rbt_item { struct opr_rbtree_node xid_node; uint32_t xid; /* defeat some caching */ char pad[65536]; }; int rbt_item_xid_cmpf(const struct opr_rbtree_node *lhs, const struct opr_rbtree_node *rhs) { struct rbt_item *lk, *rk; lk = opr_containerof(lhs, struct rbt_item, xid_node); rk = opr_containerof(rhs, struct rbt_item, xid_node); if (lk->xid < rk->xid) return (-1); if (lk->xid == rk->xid) return (0); return (1); } } /* extern "C" */ namespace { char *profile_out = nullptr; //"/tmp/profile.out"; struct opr_rbtree call_replies; struct rbt_item *rbt_arr1; bool verbose = false; static constexpr uint32_t item_wsize = 100000; static constexpr uint32_t num_calls = 1000000; uint32_t xid_ix; class RBTLatency1 : public ::testing::Test { virtual void SetUp() { rbt_arr1 = new rbt_item[item_wsize]; opr_rbtree_init(&call_replies, rbt_item_xid_cmpf); /* fill window */ for (xid_ix = 0; xid_ix < item_wsize; ++xid_ix) { rbt_item *item = &rbt_arr1[xid_ix]; if (verbose) { std::cout << "INIT" << " insert next_xid: " << xid_ix << std::endl; } item->xid = xid_ix; // yes, don't usually have xid 0 opr_rbtree_insert(&call_replies, &item->xid_node); } } virtual void TearDown() { delete[] rbt_arr1; } }; } /* namespace */ TEST_F(RBTLatency1, RUN1) { rbt_item *item; struct opr_rbtree_node *nv; struct rbt_item item_k; struct timespec s_time, e_time; if (profile_out) ProfilerStart(profile_out); now(&s_time); uint32_t prev_xid = 0; uint32_t next_xid = xid_ix; for (uint32_t call_ctr = 0; call_ctr < num_calls; ++call_ctr) { /* delete 1 and insert 1 */ if (verbose) { std::cout << " remove prev_xid: " << prev_xid << " insert next_xid: " << next_xid << std::endl; } /* lookup at oldest position */ item_k.xid = prev_xid; nv = opr_rbtree_lookup(&call_replies, &item_k.xid_node); item = opr_containerof(nv, struct rbt_item, xid_node); /* remove it */ opr_rbtree_remove(&call_replies, &item->xid_node); /* reinsert at highest position */ item->xid = next_xid; opr_rbtree_insert(&call_replies, &item->xid_node); ++next_xid; ++prev_xid; } now(&e_time); if (profile_out) ProfilerStop(); uint64_t dt = timespec_diff(&s_time, &e_time); uint64_t reqs_s = num_calls / (double(dt) / 1000000000); fprintf(stderr, "total run time: %" PRIu64 " (" PRIu64 " reqs %" PRIu64 " reqs/s) \n", dt, reqs_s); } int main(int argc, char *argv[]) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } nfs-ganesha-6.5/src/hashtable/000077500000000000000000000000001473756622300163175ustar00rootroot00000000000000nfs-ganesha-6.5/src/hashtable/CMakeLists.txt000066400000000000000000000026421473756622300210630ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- ########### next target ############### SET(hashtable_STAT_SRCS hashtable.c ) add_library(hashtable OBJECT ${hashtable_STAT_SRCS}) add_sanitizers(hashtable) set_target_properties(hashtable PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(hashtable gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) ########### install files ############### nfs-ganesha-6.5/src/hashtable/hashtable.c000066400000000000000000000746031473756622300204300ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @addtogroup hashtable * @{ */ /** * @file hashtable.c * @brief Implement an RBTree-based partitioned hash lookup * * This file implements a partitioned, tree-based, concurrent * hash-lookup structure. For every key, two values are derived that * determine its location within the structure: an index, which * determines which of the partitions (each containing a tree and each * separately locked), and a hash which acts as the key within an * individual Red-Black Tree. */ #include "config.h" #include #include #include #include "hashtable.h" #include "log.h" #include "abstract_atomic.h" #include "common_utils.h" #include /** * @brief Total size of the cache page configured for a table * * This function returns the size of the cache page for the given hash * table, based on the configured entry count. * * @param[in] ht The hash table to query * * @return The cache page size */ static inline size_t cache_page_size(const hash_table_t *ht) { return (ht->parameter.cache_entry_count) * sizeof(struct rbt_node *); } /** * @brief Offset of a pointer in the cache * * This function returns the offset into a cache array of the given * hash value. * * @param[in] ht The hash table to query * @param[in] rbthash The hash value to look up * * @return the offset into the cache at which the hash value might be * found */ static inline int cache_offsetof(struct hash_table *ht, uint64_t rbthash) { return rbthash % ht->parameter.cache_entry_count; } /** * @brief Return an error string for an error code * * This function returns an error string corresponding to the supplied * error code. * * @param[in] err The error code to look up * * @return An error string or "UNKNOWN HASH TABLE ERROR" */ const char *hash_table_err_to_str(hash_error_t err) { switch (err) { case HASHTABLE_SUCCESS: return "HASHTABLE_SUCCESS"; case HASHTABLE_UNKNOWN_HASH_TYPE: return "HASHTABLE_UNKNOWN_HASH_TYPE"; case HASHTABLE_ERROR_NO_SUCH_KEY: return "HASHTABLE_ERROR_NO_SUCH_KEY"; case HASHTABLE_ERROR_KEY_ALREADY_EXISTS: return "HASHTABLE_ERROR_KEY_ALREADY_EXISTS"; case HASHTABLE_ERROR_INVALID_ARGUMENT: return "HASHTABLE_ERROR_INVALID_ARGUMENT"; case HASHTABLE_ERROR_DELALL_FAIL: return "HASHTABLE_ERROR_DELALL_FAIL"; case HASHTABLE_NOT_DELETED: return "HASHTABLE_NOT_DELETED"; case HASHTABLE_OVERWRITTEN: return "HASHTABLE_OVERWRITTEN"; } return "UNKNOWN HASH TABLE ERROR"; } /** * @brief Locate a key within a partition * * This function traverses the red-black tree within a hash table * partition and returns, if one exists, a pointer to a node matching * the supplied key. * * @param[in] ht The hashtable to be used * @param[in] key The key to look up * @param[in] index Index into RBT array * @param[in] rbthash Hash in red-black tree * @param[out] node On success, the found node, NULL otherwise * * @retval HASHTABLE_SUCCESS if successful * @retval HASHTABLE_NO_SUCH_KEY if key was not found */ static hash_error_t key_locate(struct hash_table *ht, const struct gsh_buffdesc *key, uint32_t index, uint64_t rbthash, struct rbt_node **node) { /* The current partition */ struct hash_partition *partition = &(ht->partitions[index]); /* The root of the red black tree matching this index */ struct rbt_head *root = NULL; /* A pair of buffer descriptors locating key and value for this entry */ struct hash_data *data = NULL; /* The node in the red-black tree currently being traversed */ struct rbt_node *cursor = NULL; /* true if we have located the key */ int found = false; *node = NULL; if (partition->cache) { void **cache_slot = (void **)&( partition->cache[cache_offsetof(ht, rbthash)]); cursor = atomic_fetch_voidptr(cache_slot); LogFullDebug(COMPONENT_HASHTABLE_CACHE, "hash %s index %" PRIu32 " slot %d", (cursor) ? "hit" : "miss", index, cache_offsetof(ht, rbthash)); if (cursor) { data = RBT_OPAQ(cursor); if (ht->parameter.compare_key( (struct gsh_buffdesc *)key, &(data->key)) == 0) { goto out; } } } root = &(ht->partitions[index].rbt); /* The lefmost occurrence of the value is the one from which we may start iteration to visit all nodes containing a value. */ RBT_FIND_LEFT(root, cursor, rbthash); if (cursor == NULL) { if (isFullDebug(COMPONENT_HASHTABLE) && isFullDebug(ht->parameter.ht_log_component)) LogFullDebug(ht->parameter.ht_log_component, "Key not found: rbthash = %" PRIu64, rbthash); return HASHTABLE_ERROR_NO_SUCH_KEY; } while ((cursor != NULL) && (RBT_VALUE(cursor) == rbthash)) { data = RBT_OPAQ(cursor); if (ht->parameter.compare_key((struct gsh_buffdesc *)key, &(data->key)) == 0) { if (partition->cache) { void **cache_slot = (void **)&partition ->cache[cache_offsetof( ht, rbthash)]; atomic_store_voidptr(cache_slot, cursor); } found = true; break; } RBT_INCREMENT(cursor); } if (!found) { if (isFullDebug(COMPONENT_HASHTABLE) && isFullDebug(ht->parameter.ht_log_component)) LogFullDebug( ht->parameter.ht_log_component, "Matching hash found, but no matching key."); return HASHTABLE_ERROR_NO_SUCH_KEY; } out: *node = cursor; return HASHTABLE_SUCCESS; } /** * @brief Compute the values to search a hash store * * This function computes the index and RBT hash values for the * specified key. * * @param[in] ht The hash table whose parameters determine computation * @param[in] key The key from which to compute the values * @param[out] index The partition index * @param[out] rbt_hash The hash in the Red-Black tree * * @retval HASHTABLE_SUCCESS if values computed * @retval HASHTABLE_ERROR_INVALID_ARGUMENT if the supplied function * fails */ static inline hash_error_t compute(struct hash_table *ht, const struct gsh_buffdesc *key, uint32_t *index, uint64_t *rbt_hash) { /* Compute the partition index and red-black tree hash */ if (ht->parameter.hash_func_both) { if (!(*(ht->parameter.hash_func_both))( &ht->parameter, (struct gsh_buffdesc *)key, index, rbt_hash)) return HASHTABLE_ERROR_INVALID_ARGUMENT; } else { *index = (*(ht->parameter.hash_func_key))( &ht->parameter, (struct gsh_buffdesc *)key); *rbt_hash = (*(ht->parameter.hash_func_rbt))( &ht->parameter, (struct gsh_buffdesc *)key); } /* At the suggestion of Jim Lieb, die if a hash function sends us off the end of the array. */ assert(*index < ht->parameter.index_size); return HASHTABLE_SUCCESS; } /* The following are the hash table primitives implementing the actual functionality. */ /** * @brief Initialize a new hash table * * This function initializes and allocates storage for a hash table. * * @param[in] hparam Parameters to determine the hash table's * behaviour * * @return Pointer to the new hash table, NULL on failure * */ struct hash_table *hashtable_init(struct hash_param *hparam) { /* The hash table being constructed */ struct hash_table *ht = NULL; /* The index for initializing each partition */ uint32_t index = 0; /* Hash partition */ struct hash_partition *partition = NULL; /* The number of fully initialized partitions */ uint32_t completed = 0; ht = gsh_calloc(1, sizeof(struct hash_table) + (sizeof(struct hash_partition) * hparam->index_size)); /* Fixup entry size */ if (hparam->flags & HT_FLAG_CACHE) { if (!hparam->cache_entry_count) /* works fine with a good hash algo */ hparam->cache_entry_count = 32767; } /* We need to save copy of the parameters in the table. */ ht->parameter = *hparam; for (index = 0; index < hparam->index_size; ++index) { partition = (&ht->partitions[index]); RBT_HEAD_INIT(&(partition->rbt)); PTHREAD_RWLOCK_init(&partition->ht_lock, NULL); /* Allocate a cache if requested */ if (hparam->flags & HT_FLAG_CACHE) partition->cache = gsh_calloc(1, cache_page_size(ht)); completed++; } ht->node_pool = pool_basic_init(NULL, sizeof(rbt_node_t)); ht->data_pool = pool_basic_init(NULL, sizeof(struct hash_data)); return ht; } /** * @brief Dispose of a hash table * * This function deletes all the entries from the given hash table and * then destroys the hash table. * * @param[in,out] ht Pointer to the hash table. After calling * this function, the memory pointed to by ht * must not be accessed in any way. * @param[in] free_func Function to free entries as they are * deleted * * @return HASHTABLE_SUCCESS on success, other things on failure */ hash_error_t hashtable_destroy(struct hash_table *ht, int (*free_func)(struct gsh_buffdesc, struct gsh_buffdesc)) { size_t index = 0; hash_error_t hrc = HASHTABLE_SUCCESS; hrc = hashtable_delall(ht, free_func); if (hrc != HASHTABLE_SUCCESS) goto out; for (index = 0; index < ht->parameter.index_size; ++index) { if (ht->partitions[index].cache) { gsh_free(ht->partitions[index].cache); ht->partitions[index].cache = NULL; } PTHREAD_RWLOCK_destroy(&(ht->partitions[index].ht_lock)); } pool_destroy(ht->node_pool); pool_destroy(ht->data_pool); gsh_free(ht); out: return hrc; } /** * @brief acquire the partition lock for the given key * * This function acquires the partition write mode lock corresponding to * the given key. * * @brief[in] ht The hash table to search * @brief[in] key The key for which to acquire the partition lock * @brief[out] latch Opaque structure holding partition information * * @retval HASHTABLE_SUCCESS, the partition lock is acquired. * @retval Others, failure, the partition lock is not acquired * * NOTES: fast path if the caller just needs to lock the partition and * doesn't need to look for the entry. The lock needs to be released * with hashtable_releaselatched() */ hash_error_t hashtable_acquire_latch(struct hash_table *ht, const struct gsh_buffdesc *key, struct hash_latch *latch) { uint32_t index; uint64_t rbt_hash; hash_error_t rc = HASHTABLE_SUCCESS; memset(latch, 0, sizeof(struct hash_latch)); rc = compute(ht, key, &index, &rbt_hash); if (rc != HASHTABLE_SUCCESS) return rc; latch->index = index; PTHREAD_RWLOCK_wrlock(&(ht->partitions[index].ht_lock)); return HASHTABLE_SUCCESS; } /** * @brief Look up an entry, latching the table * * This function looks up an entry in the hash table and latches the * partition in which that entry would belong in preparation for other * activities. This function is a primitive and is intended more for * use building other access functions than for client code itself. * * @brief[in] ht The hash table to search * @brief[in] key The key for which to search * @brief[out] val The value found * @brief[in] may_write This must be true if the followup call might * mutate the hash table (set or delete) * @brief[out] latch Opaque structure holding information on the * table. * * @retval HASHTABLE_SUCCESS The entry was found, the table is * latched. * @retval HASHTABLE_ERROR_NOT_FOUND The entry was not found, the * table is latched. * @retval Others, failure, the table is not latched. */ hash_error_t hashtable_getlatch(struct hash_table *ht, const struct gsh_buffdesc *key, struct gsh_buffdesc *val, bool may_write, struct hash_latch *latch) { /* The index specifying the partition to search */ uint32_t index = 0; /* The node found for the key */ struct rbt_node *locator = NULL; /* The buffer descriptors for the key and value for the found entry */ struct hash_data *data = NULL; /* The hash value to be searched for within the Red-Black tree */ uint64_t rbt_hash = 0; /* Stored error return */ hash_error_t rc = HASHTABLE_SUCCESS; rc = compute(ht, key, &index, &rbt_hash); if (rc != HASHTABLE_SUCCESS) return rc; /* Acquire mutex */ if (may_write) PTHREAD_RWLOCK_wrlock(&(ht->partitions[index].ht_lock)); else PTHREAD_RWLOCK_rdlock(&(ht->partitions[index].ht_lock)); rc = key_locate(ht, key, index, rbt_hash, &locator); if (rc == HASHTABLE_SUCCESS) { /* Key was found */ data = RBT_OPAQ(locator); if (val) { val->addr = data->val.addr; val->len = data->val.len; } if (isDebug(COMPONENT_HASHTABLE) && isFullDebug(ht->parameter.ht_log_component)) { char dispval[HASHTABLE_DISPLAY_STRLEN]; struct display_buffer valbuf = { sizeof(dispval), dispval, dispval }; if (ht->parameter.display_val != NULL) ht->parameter.display_val(&valbuf, &data->val); else dispval[0] = '\0'; LogFullDebug(ht->parameter.ht_log_component, "Get %s returning Value=%p {%s}", ht->parameter.ht_name, data->val.addr, dispval); } } if (((rc == HASHTABLE_SUCCESS) || (rc == HASHTABLE_ERROR_NO_SUCH_KEY)) && (latch != NULL)) { latch->index = index; latch->rbt_hash = rbt_hash; latch->locator = locator; } else { PTHREAD_RWLOCK_unlock(&ht->partitions[index].ht_lock); } if (rc != HASHTABLE_SUCCESS && isDebug(COMPONENT_HASHTABLE) && isFullDebug(ht->parameter.ht_log_component)) LogFullDebug(ht->parameter.ht_log_component, "Get %s returning failure %s", ht->parameter.ht_name, hash_table_err_to_str(rc)); return rc; } /** * * @brief Release lock held on hash table * * This function releases the lock on the hash partition acquired and * retained by a call to hashtable_getlatch. This function must be * used to free any acquired lock but ONLY if the lock was not already * freed by some other means (hashtable_setlatched or * HashTable_DelLatched). * * @param[in] ht The hash table with the lock to be released * @param[in] latch The latch structure holding retained state */ void hashtable_releaselatched(struct hash_table *ht, struct hash_latch *latch) { if (latch) { PTHREAD_RWLOCK_unlock(&ht->partitions[latch->index].ht_lock); memset(latch, 0, sizeof(struct hash_latch)); } } /** * @brief Set a value in a table following a previous GetLatch * * This function sets a value in a hash table following a previous * call to the hashtable_getlatch function. It must only be used * after such a call made with the may_write parameter set to true. * In all cases, the lock on the hash table is released. * * @param[in,out] ht The hash store to be modified * @param[in] key A buffer descriptor locating the key to set * @param[in] val A buffer descriptor locating the value to insert * @param[in] latch A pointer to a structure filled by a previous * call to hashtable_getlatched. * @param[in] overwrite If true, overwrite a preexisting key, * otherwise return error on collision. * @param[out] stored_key If non-NULL, a buffer descriptor for an * overwritten key as stored. * @param[out] stored_val If non-NULL, a buffer descriptor for an * overwritten value as stored. * * @retval HASHTABLE_SUCCESS on non-colliding insert * @retval HASHTABLE_ERROR_KEY_ALREADY_EXISTS if overwrite disabled * @retval HASHTABLE_OVERWRITTEN on successful overwrite * @retval Other errors on failure */ hash_error_t hashtable_setlatched(struct hash_table *ht, struct gsh_buffdesc *key, struct gsh_buffdesc *val, struct hash_latch *latch, int overwrite, struct gsh_buffdesc *stored_key, struct gsh_buffdesc *stored_val) { /* Stored error return */ hash_error_t rc = HASHTABLE_SUCCESS; /* The pair of buffer descriptors locating both key and value for this object, what actually gets stored. */ struct hash_data *descriptors = NULL; /* Node giving the location to insert new node */ struct rbt_node *locator = NULL; /* New node for the case of non-overwrite */ struct rbt_node *mutator = NULL; if (isDebug(COMPONENT_HASHTABLE) && isFullDebug(ht->parameter.ht_log_component)) { char dispkey[HASHTABLE_DISPLAY_STRLEN]; char dispval[HASHTABLE_DISPLAY_STRLEN]; struct display_buffer keybuf = { sizeof(dispkey), dispkey, dispkey }; struct display_buffer valbuf = { sizeof(dispval), dispval, dispval }; if (ht->parameter.display_key != NULL) ht->parameter.display_key(&keybuf, key); else dispkey[0] = '\0'; if (ht->parameter.display_val != NULL) ht->parameter.display_val(&valbuf, val); else dispval[0] = '\0'; LogFullDebug(ht->parameter.ht_log_component, "Set %s Key=%p {%s} Value=%p {%s} index=%" PRIu32 " rbt_hash=%" PRIu64, ht->parameter.ht_name, key->addr, dispkey, val->addr, dispval, latch->index, latch->rbt_hash); } /* In the case of collision */ if (latch->locator) { if (!overwrite) { rc = HASHTABLE_ERROR_KEY_ALREADY_EXISTS; goto out; } descriptors = RBT_OPAQ(latch->locator); if (isDebug(COMPONENT_HASHTABLE) && isFullDebug(ht->parameter.ht_log_component)) { char dispkey[HASHTABLE_DISPLAY_STRLEN]; char dispval[HASHTABLE_DISPLAY_STRLEN]; struct display_buffer keybuf = { sizeof(dispkey), dispkey, dispkey }; struct display_buffer valbuf = { sizeof(dispval), dispval, dispval }; if (ht->parameter.display_key != NULL) ht->parameter.display_key(&keybuf, &descriptors->key); else dispkey[0] = '\0'; if (ht->parameter.display_val != NULL) ht->parameter.display_val(&valbuf, &descriptors->val); else dispval[0] = '\0'; LogFullDebug( ht->parameter.ht_log_component, "Set %s Key=%p {%s} Value=%p {%s} index=%" PRIu32 " rbt_hash=%" PRIu64 " was replaced", ht->parameter.ht_name, descriptors->key.addr, dispkey, descriptors->val.addr, dispval, latch->index, latch->rbt_hash); } if (stored_key) *stored_key = descriptors->key; if (stored_val) *stored_val = descriptors->val; descriptors->key = *key; descriptors->val = *val; rc = HASHTABLE_OVERWRITTEN; goto out; } /* We have no collision, so go about creating and inserting a new node. */ RBT_FIND(&ht->partitions[latch->index].rbt, locator, latch->rbt_hash); mutator = pool_alloc(ht->node_pool); descriptors = pool_alloc(ht->data_pool); RBT_OPAQ(mutator) = descriptors; RBT_VALUE(mutator) = latch->rbt_hash; RBT_INSERT(&ht->partitions[latch->index].rbt, mutator, locator); descriptors->key.addr = key->addr; descriptors->key.len = key->len; descriptors->val.addr = val->addr; descriptors->val.len = val->len; /* Only in the non-overwrite case */ ++ht->partitions[latch->index].count; rc = HASHTABLE_SUCCESS; out: hashtable_releaselatched(ht, latch); if (rc != HASHTABLE_SUCCESS && isDebug(COMPONENT_HASHTABLE) && isFullDebug(ht->parameter.ht_log_component)) LogFullDebug(ht->parameter.ht_log_component, "Set %s returning failure %s", ht->parameter.ht_name, hash_table_err_to_str(rc)); return rc; } /** * @brief Delete a value from the store following a previous GetLatch * * This function removes a value from the a hash store, the value * already having been looked up with GetLatched. In all cases, the * lock is retained. hashtable_getlatch must have been called with * may_write true. * * @param[in,out] ht The hash store to be modified * @param[in] key A buffer descriptore locating the key to remove * @param[in] latch A pointer to a structure filled by a previous * call to hashtable_getlatched. * @param[out] stored_key If non-NULL, a buffer descriptor the * removed key as stored. * @param[out] stored_val If non-NULL, a buffer descriptor for the * removed value as stored. * */ void hashtable_deletelatched(struct hash_table *ht, const struct gsh_buffdesc *key, struct hash_latch *latch, struct gsh_buffdesc *stored_key, struct gsh_buffdesc *stored_val) { /* The pair of buffer descriptors comprising the stored entry */ struct hash_data *data; /* Its partition */ struct hash_partition *partition = &ht->partitions[latch->index]; data = RBT_OPAQ(latch->locator); if (isDebug(COMPONENT_HASHTABLE) && isFullDebug(ht->parameter.ht_log_component)) { char dispkey[HASHTABLE_DISPLAY_STRLEN]; char dispval[HASHTABLE_DISPLAY_STRLEN]; struct display_buffer keybuf = { sizeof(dispkey), dispkey, dispkey }; struct display_buffer valbuf = { sizeof(dispval), dispval, dispval }; if (ht->parameter.display_key != NULL) ht->parameter.display_key(&keybuf, &data->key); else dispkey[0] = '\0'; if (ht->parameter.display_val != NULL) ht->parameter.display_val(&valbuf, &data->val); else dispval[0] = '\0'; LogFullDebug( ht->parameter.ht_log_component, "Delete %s Key=%p {%s} Value=%p {%s} index=%" PRIu32 " rbt_hash=%" PRIu64 " was removed", ht->parameter.ht_name, data->key.addr, dispkey, data->val.addr, dispval, latch->index, latch->rbt_hash); } if (stored_key) *stored_key = data->key; if (stored_val) *stored_val = data->val; /* Clear cache */ if (partition->cache) { uint32_t offset = cache_offsetof(ht, latch->rbt_hash); struct rbt_node *cnode = partition->cache[offset]; if (cnode) { #if COMPARE_BEFORE_CLEAR_CACHE struct hash_data *data1 = RBT_OPAQ(cnode); struct hash_data *data2 = RBT_OPAQ(latch->locator); if (ht->parameter.compare_key(&data1->key, &data2->key) == 0) { LogFullDebug(COMPONENT_HASHTABLE_CACHE, "hash clear index %d slot %" PRIu64 latch->index, offset); partition->cache[offset] = NULL; } #else LogFullDebug(COMPONENT_HASHTABLE_CACHE, "hash clear slot %d", offset); partition->cache[offset] = NULL; #endif } } /* Now remove the entry */ RBT_UNLINK(&partition->rbt, latch->locator); pool_free(ht->data_pool, data); pool_free(ht->node_pool, latch->locator); --ht->partitions[latch->index].count; /* Some callers re-use the latch to insert a record after this call, * so reset latch locator to avoid hashtable_setlatched() using the * invalid latch->locator */ latch->locator = NULL; } /** * @brief Remove and free all (key,val) couples from the hash store * * This function removes all (key,val) couples from the hashtable and * frees the stored data using the supplied function * * @param[in,out] ht The hashtable to be cleared of all entries * @param[in] free_func The function with which to free the contents * of each entry * * @return HASHTABLE_SUCCESS or errors */ hash_error_t hashtable_delall(struct hash_table *ht, int (*free_func)(struct gsh_buffdesc, struct gsh_buffdesc)) { /* Successive partition numbers */ uint32_t index = 0; for (index = 0; index < ht->parameter.index_size; index++) { /* The root of each successive partition */ struct rbt_head *root = &ht->partitions[index].rbt; /* Pointer to node in tree for removal */ struct rbt_node *cursor = NULL; PTHREAD_RWLOCK_wrlock(&ht->partitions[index].ht_lock); /* Continue until there are no more entries in the red-black tree */ while ((cursor = RBT_LEFTMOST(root)) != NULL) { /* Pointer to the key and value descriptors for each successive entry */ struct hash_data *data = NULL; /* Aliased pointer to node, for freeing buffers after removal from tree */ struct rbt_node *holder = cursor; /* Buffer descriptor for key, as stored */ struct gsh_buffdesc key; /* Buffer descriptor for value, as stored */ struct gsh_buffdesc val; /* Return code from the free function. Zero on failure */ int rc = 0; RBT_UNLINK(root, cursor); data = RBT_OPAQ(holder); key = data->key; val = data->val; pool_free(ht->data_pool, data); pool_free(ht->node_pool, holder); --ht->partitions[index].count; rc = free_func(key, val); if (rc == 0) { PTHREAD_RWLOCK_unlock( &ht->partitions[index].ht_lock); return HASHTABLE_ERROR_DELALL_FAIL; } } PTHREAD_RWLOCK_unlock(&ht->partitions[index].ht_lock); } return HASHTABLE_SUCCESS; } /** * @brief Log information about the hashtable * * This debugging function prints information about the hash table to * the log. * * @param[in] component The component debugging config to use. * @param[in] ht The hashtable to be used. */ void hashtable_log(log_components_t component, struct hash_table *ht) { /* The current position in the hash table */ struct rbt_node *it = NULL; /* The root of the tree currently being inspected */ struct rbt_head *root; /* Buffer descriptors for the key and value */ struct hash_data *data = NULL; /* String representation of the key */ char dispkey[HASHTABLE_DISPLAY_STRLEN]; /* String representation of the stored value */ char dispval[HASHTABLE_DISPLAY_STRLEN]; struct display_buffer keybuf = { sizeof(dispkey), dispkey, dispkey }; struct display_buffer valbuf = { sizeof(dispval), dispval, dispval }; /* Index for traversing the partitions */ uint32_t i = 0; /* Running count of entries */ size_t nb_entries = 0; /* Recomputed partitionindex */ uint32_t index = 0; /* Recomputed hash for Red-Black tree */ uint64_t rbt_hash = 0; LogFullDebug(component, "The hash is partitioned into %d trees", ht->parameter.index_size); for (i = 0; i < ht->parameter.index_size; i++) nb_entries += ht->partitions[i].count; LogFullDebug(component, "The hash contains %zd entries", nb_entries); for (i = 0; i < ht->parameter.index_size; i++) { root = &ht->partitions[i].rbt; LogFullDebug(component, "The partition in position %" PRIu32 "contains: %u entries", i, root->rbt_num_node); PTHREAD_RWLOCK_rdlock(&ht->partitions[i].ht_lock); RBT_LOOP(root, it) { data = it->rbt_opaq; if (ht->parameter.display_key != NULL) ht->parameter.display_key(&keybuf, &data->key); else dispkey[0] = '\0'; if (ht->parameter.display_val != NULL) ht->parameter.display_val(&valbuf, &data->val); else dispval[0] = '\0'; if (compute(ht, &data->key, &index, &rbt_hash) != HASHTABLE_SUCCESS) { LogCrit(component, "Possible implementation error in hash_func_both"); index = 0; rbt_hash = 0; } LogFullDebug(component, "%s => %s; index=%" PRIu32 " rbt_hash=%" PRIu64, dispkey, dispval, index, rbt_hash); RBT_INCREMENT(it); } PTHREAD_RWLOCK_unlock(&ht->partitions[i].ht_lock); } } /** * @brief Set a pair (key,value) into the Hash Table * * Depending on the value of 'how', this function sets a value into * the hash table or tests that the hash table contains that value. * * This function is deprecated. * * @param[in,out] ht The hashtable to test or alter * @param[in] key The key to be set * @param[in] val The value to be stored * @param[in] how A value determining whether this is a test, a set * with overwrite, or a set without overwrite. * * @retval HASHTABLE_SUCCESS if successful. */ hash_error_t hashtable_test_and_set(struct hash_table *ht, struct gsh_buffdesc *key, struct gsh_buffdesc *val, hash_set_how_t how) { /* structure to hold retained state */ struct hash_latch latch; /* Stored return code */ hash_error_t rc = 0; rc = hashtable_getlatch(ht, key, NULL, (how != HASHTABLE_SET_HOW_TEST_ONLY), &latch); if ((rc != HASHTABLE_SUCCESS) && (rc != HASHTABLE_ERROR_NO_SUCH_KEY)) return rc; if (how == HASHTABLE_SET_HOW_TEST_ONLY) { hashtable_releaselatched(ht, &latch); return rc; } /* No point in calling hashtable_setlatched when we know it will error. */ if ((how == HASHTABLE_SET_HOW_SET_NO_OVERWRITE) && (rc == HASHTABLE_SUCCESS)) { hashtable_releaselatched(ht, &latch); return HASHTABLE_ERROR_KEY_ALREADY_EXISTS; } rc = hashtable_setlatched(ht, key, val, &latch, (how == HASHTABLE_SET_HOW_SET_OVERWRITE), NULL, NULL); if (rc == HASHTABLE_OVERWRITTEN) rc = HASHTABLE_SUCCESS; return rc; } /** * @brief Look up a value and take a reference * * This function attempts to locate a key in the hash store and return * the associated value. It also calls the supplied function to take * a reference before releasing the partition lock. It is implemented * as a wrapper around hashtable_getlatched. * * @param[in] ht The hash store to be searched * @param[in] key A buffer descriptore locating the key to find * @param[out] val A buffer descriptor locating the value found * @param[in] get_ref A function to take a reference on the supplied * value * * @return HASHTABLE_SUCCESS or errors */ hash_error_t hashtable_getref(hash_table_t *ht, struct gsh_buffdesc *key, struct gsh_buffdesc *val, void (*get_ref)(struct gsh_buffdesc *)) { /* structure to hold retained state */ struct hash_latch latch; /* Stored return code */ hash_error_t rc = 0; rc = hashtable_getlatch(ht, key, val, false, &latch); switch (rc) { case HASHTABLE_SUCCESS: if (get_ref != NULL) get_ref(val); /* FALLTHROUGH */ case HASHTABLE_ERROR_NO_SUCH_KEY: hashtable_releaselatched(ht, &latch); break; default: break; } return rc; } void hashtable_for_each(hash_table_t *ht, ht_for_each_cb_t callback, void *arg) { uint32_t i; struct rbt_head *head_rbt; struct rbt_node *pn; /* For each bucket of the requested hashtable */ for (i = 0; i < ht->parameter.index_size; i++) { head_rbt = &ht->partitions[i].rbt; PTHREAD_RWLOCK_rdlock(&ht->partitions[i].ht_lock); RBT_LOOP(head_rbt, pn) { callback(pn, arg); RBT_INCREMENT(pn); } PTHREAD_RWLOCK_unlock(&ht->partitions[i].ht_lock); } } /** @} */ nfs-ganesha-6.5/src/idmapper/000077500000000000000000000000001473756622300161655ustar00rootroot00000000000000nfs-ganesha-6.5/src/idmapper/.gitignore000066400000000000000000000000161473756622300201520ustar00rootroot00000000000000test_idmapper nfs-ganesha-6.5/src/idmapper/CMakeLists.txt000066400000000000000000000032121473756622300207230ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- if(_MSPAC_SUPPORT) include_directories( ${WBCLIENT_INCLUDE_DIR} ) endif(_MSPAC_SUPPORT) if(USE_DBUS) include_directories( ${DBUS_INCLUDE_DIRS} ) endif(USE_DBUS) ########### next target ############### SET(idmap_STAT_SRCS idmapper.c idmapper_cache.c idmapper_negative_cache.c idmapper_monitoring.c ) add_library(idmap OBJECT ${idmap_STAT_SRCS}) add_sanitizers(idmap) set_target_properties(idmap PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(idmap gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) ########### install files ############### nfs-ganesha-6.5/src/idmapper/idmapper.c000066400000000000000000001204011473756622300201300ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @addtogroup idmapper * @{ */ /** * @file idmapper.c * @brief Id mapping functions */ #include "config.h" #include /* for using gethostname */ #include /* for using exit */ #include #include #include #include #include #include #include #ifdef USE_NFSIDMAP #include #include "nfs_exports.h" #endif /* USE_NFSIDMAP */ #ifdef _MSPAC_SUPPORT #include #endif #include "common_utils.h" #include "gsh_rpc.h" #include "gsh_types.h" #include "gsh_list.h" #ifdef USE_DBUS #include "gsh_dbus.h" #endif #include "nfs_core.h" #include "idmapper.h" #include "server_stats_private.h" #include "idmapper_monitoring.h" struct owner_domain_holder { struct gsh_buffdesc domain; /* Lock to synchronise reads and writes to owner domain */ pthread_rwlock_t lock; }; static struct owner_domain_holder owner_domain; /* winbind auth stats information */ struct auth_stats winbind_auth_stats; pthread_rwlock_t winbind_auth_lock; /*group cache auth stats information */ struct auth_stats gc_auth_stats; pthread_rwlock_t gc_auth_lock; /*DNS auth stats information */ struct auth_stats dns_auth_stats; pthread_rwlock_t dns_auth_lock; /* Cleanup on shutdown */ struct cleanup_list_element idmapper_cleanup_element; /* Struct representing threads that reap idmapper caches */ static struct fridgethr *cache_reaper_fridge; /* Switch to enable or disable idmapping */ bool idmapping_enabled = true; /* Mutex to protect set/reset of idmapping status */ static mutex_t idmapping_status_lock = MUTEX_INITIALIZER; /** * @brief Set the ID Mapper's owner-domain * * @return true on success, false on failure */ static bool idmapper_set_owner_domain(void) { char domain_addr[NFS4_MAX_DOMAIN_LEN + 1] = {}; #ifdef USE_NFSIDMAP if (!nfs_param.nfsv4_param.use_getpwnam) { /* Note: The libnfsidmap function `nfs4_init_name_mapping` has * no effect if called after it has been previously called once * during the lifetime of the process. Any subsequent call to * override libnfsidmap's global state must be preceded by * clearing the earlier state within libnfsidmap, which is * currently not possible. */ if (nfs4_init_name_mapping(nfs_param.nfsv4_param.idmapconf) != 0) { LogCrit(COMPONENT_IDMAPPER, "Failed to init idmapping via nfsidmap"); return false; } if (nfs4_get_default_domain(NULL, domain_addr, NFS4_MAX_DOMAIN_LEN) != 0) { LogCrit(COMPONENT_IDMAPPER, "Failed to get default domain via nfsidmap"); return false; } } #endif /* USE_NFSIDMAP */ if (nfs_param.nfsv4_param.use_getpwnam) strlcpy(domain_addr, nfs_param.directory_services_param.domainname, NFS4_MAX_DOMAIN_LEN + 1); /* Return false if domain was not initialised through above * conditions */ if (domain_addr[0] == '\0') { LogCrit(COMPONENT_IDMAPPER, "Owner domain was not found or initialised"); return false; } PTHREAD_RWLOCK_wrlock(&owner_domain.lock); owner_domain.domain.addr = gsh_strdup(domain_addr); owner_domain.domain.len = strlen(owner_domain.domain.addr); PTHREAD_RWLOCK_unlock(&owner_domain.lock); return true; } /** * @brief Remove and free the ID Mapper's owner-domain */ static void idmapper_clear_owner_domain(void) { PTHREAD_RWLOCK_wrlock(&owner_domain.lock); if (owner_domain.domain.len == 0) { PTHREAD_RWLOCK_unlock(&owner_domain.lock); return; } gsh_free(owner_domain.domain.addr); owner_domain.domain.addr = NULL; owner_domain.domain.len = 0; PTHREAD_RWLOCK_unlock(&owner_domain.lock); } /** * This function sets the idmapping status within Ganesha. * If the status is OFF, it performs existing data cleanup in uid2grp.c and * idmapper.c */ bool set_idmapping_status(bool status_enabled) { bool rc; /* Acquire mutex to prevent interference by another invocation */ mutex_lock(&idmapping_status_lock); if (idmapping_enabled == status_enabled) { mutex_unlock(&idmapping_status_lock); LogInfo(COMPONENT_IDMAPPER, "Idmapping status is already set to %d", status_enabled); return true; } if (status_enabled) { /* Set the domainname for idmapping */ rc = idmapper_set_owner_domain(); if (!rc) { mutex_unlock(&idmapping_status_lock); LogWarn(COMPONENT_IDMAPPER, "Could not set owner-domain while enabling Idmapping"); return false; } idmapping_enabled = true; mutex_unlock(&idmapping_status_lock); LogInfo(COMPONENT_IDMAPPER, "Idmapping is now enabled"); return true; } idmapping_enabled = false; /* Clear idmapper data */ idmapper_clear_cache(); idmapper_clear_owner_domain(); idmapper_negative_cache_clear(); /* Clear uid2grp data */ uid2grp_clear_cache(); mutex_unlock(&idmapping_status_lock); LogInfo(COMPONENT_IDMAPPER, "Idmapping is now disabled"); return true; } /** * @brief Cleanup idmapper on shutdown * * This should happen only once in the process lifetime, during shutdown. */ void idmapper_cleanup(void) { if (cache_reaper_fridge != NULL) { fridgethr_destroy(cache_reaper_fridge); cache_reaper_fridge = NULL; } idmapper_clear_owner_domain(); idmapper_destroy_cache(); idmapper_negative_cache_destroy(); PTHREAD_RWLOCK_destroy(&owner_domain.lock); PTHREAD_RWLOCK_destroy(&winbind_auth_lock); PTHREAD_RWLOCK_destroy(&gc_auth_lock); PTHREAD_RWLOCK_destroy(&dns_auth_lock); } /** * @brief Reaps the positive and negative idmapper cache entries. */ static void cache_reaper_run(struct fridgethr_context *unused_ctx) { idmapper_cache_reap(); idmapper_negative_cache_reap(); uid2grp_cache_reap(); } /** * @brief Initialise the reaper thread for reaping expired cache entries */ static void idmapper_reaper_init(void) { struct fridgethr_params thread_params; int rc; int reaping_interval = nfs_param.directory_services_param.cache_reaping_interval; if (reaping_interval == 0) { LogInfo(COMPONENT_IDMAPPER, "Idmapper cache reaper is disabled"); return; } memset(&thread_params, 0, sizeof(struct fridgethr_params)); thread_params.thr_max = 1; thread_params.thr_min = 1; thread_params.thread_delay = reaping_interval; thread_params.flavor = fridgethr_flavor_looper; assert(cache_reaper_fridge == NULL); rc = fridgethr_init(&cache_reaper_fridge, "idmapper_reaper", &thread_params); if (rc != 0) { LogCrit(COMPONENT_IDMAPPER, "Idmapper reaper fridge init failed. Error: %d", rc); return; } rc = fridgethr_submit(cache_reaper_fridge, cache_reaper_run, NULL); if (rc != 0) { LogCrit(COMPONENT_IDMAPPER, "Unable to start reaper for idmapper. Error: %d.", rc); fridgethr_destroy(cache_reaper_fridge); cache_reaper_fridge = NULL; return; } LogInfo(COMPONENT_IDMAPPER, "Idmapper reaper initialized"); } /** * @brief Initialize the ID Mapper * * This should happen only once in the process lifetime, during startup. * * @return true on success, false on failure */ bool idmapper_init(void) { bool rc; PTHREAD_RWLOCK_init(&winbind_auth_lock, NULL); PTHREAD_RWLOCK_init(&gc_auth_lock, NULL); PTHREAD_RWLOCK_init(&dns_auth_lock, NULL); PTHREAD_RWLOCK_init(&owner_domain.lock, NULL); rc = idmapper_set_owner_domain(); if (!rc) { LogWarn(COMPONENT_IDMAPPER, "Unable to set owner-domain required for idmapping."); return false; } idmapper_cache_init(); idmapper_negative_cache_init(); idmapper_reaper_init(); idmapper_cleanup_element.clean = idmapper_cleanup; RegisterCleanup(&idmapper_cleanup_element); idmapper_monitoring__init(); return true; } /** * @brief Add user to the idmapper-user cache * * @param[in] name user name * @param[in] uid user ID * @param[in] gid user GID * @param[in] gss_princ true if the name is a gss principal */ static void add_user_to_cache(const struct gsh_buffdesc *name, uid_t uid, const gid_t *gid, bool gss_princ) { bool success; if (!idmapping_enabled) { LogWarn(COMPONENT_IDMAPPER, "Idmapping is disabled. Add user(uid: %u) skipped.", uid); return; } PTHREAD_RWLOCK_wrlock(&idmapper_user_lock); /* Recheck after obtaining the lock */ if (!idmapping_enabled) { PTHREAD_RWLOCK_unlock(&idmapper_user_lock); LogWarn(COMPONENT_IDMAPPER, "Idmapping is disabled. Add user(uid: %u) skipped.", uid); return; } success = idmapper_add_user(name, uid, gid, gss_princ); PTHREAD_RWLOCK_unlock(&idmapper_user_lock); if (unlikely(!success)) { LogMajor(COMPONENT_IDMAPPER, "idmapper_add_user (uid: %u) failed.", uid); } } /** * @brief Add user to the idmapper-user negative cache * * @param[in] name user name */ static void add_user_to_negative_cache(const struct gsh_buffdesc *name) { if (!idmapping_enabled) { LogWarn(COMPONENT_IDMAPPER, "Idmapping is disabled. Add user to negative cache skipped."); return; } PTHREAD_RWLOCK_wrlock(&idmapper_negative_cache_user_lock); /* Recheck after obtaining the lock */ if (!idmapping_enabled) { PTHREAD_RWLOCK_unlock(&idmapper_negative_cache_user_lock); LogWarn(COMPONENT_IDMAPPER, "Idmapping is disabled. Add user to negative cache skipped."); return; } idmapper_negative_cache_add_user_by_name(name); PTHREAD_RWLOCK_unlock(&idmapper_negative_cache_user_lock); } /** * @brief Add group to the idmapper-group cache * * @param[in] name group name * @param[in] gid group ID */ static void add_group_to_cache(const struct gsh_buffdesc *name, gid_t gid) { bool success; if (!idmapping_enabled) { LogWarn(COMPONENT_IDMAPPER, "Idmapping is disabled. Add group(gid: %u) skipped.", gid); return; } PTHREAD_RWLOCK_wrlock(&idmapper_group_lock); /* Recheck after obtaining the lock */ if (!idmapping_enabled) { PTHREAD_RWLOCK_unlock(&idmapper_group_lock); LogWarn(COMPONENT_IDMAPPER, "Idmapping is disabled. Add group(gid: %u) skipped.", gid); return; } success = idmapper_add_group(name, gid); PTHREAD_RWLOCK_unlock(&idmapper_group_lock); if (unlikely(!success)) { LogMajor(COMPONENT_IDMAPPER, "idmapper_add_group (gid: %u) failed.", gid); } } /** * @brief Add group to the idmapper-group negative cache * * @param[in] name group name */ static void add_group_to_negative_cache(const struct gsh_buffdesc *name) { if (!idmapping_enabled) { LogWarn(COMPONENT_IDMAPPER, "Idmapping is disabled. Add group to negative cache skipped."); return; } PTHREAD_RWLOCK_wrlock(&idmapper_negative_cache_group_lock); /* Recheck after obtaining the lock */ if (!idmapping_enabled) { PTHREAD_RWLOCK_unlock(&idmapper_negative_cache_group_lock); LogWarn(COMPONENT_IDMAPPER, "Idmapping is disabled. Add group to negative cache skipped."); return; } idmapper_negative_cache_add_group_by_name(name); PTHREAD_RWLOCK_unlock(&idmapper_negative_cache_group_lock); } /** * @brief Encode a UID or GID as a string * * @param[in,out] xdrs XDR stream to which to encode * @param[in] id UID or GID * @param[in] group True if this is a GID, false for a UID * * @retval true on success. * @retval false on failure. */ static bool xdr_encode_nfs4_princ(XDR *xdrs, uint32_t id, bool group) { const struct gsh_buffdesc *found; uint32_t not_a_size_t; bool success = false; if (nfs_param.nfsv4_param.only_numeric_owners) { /* 2**32 is 10 digits long in decimal */ struct gsh_buffdesc name; char namebuf[11]; name.addr = namebuf; name.len = sprintf(namebuf, "%" PRIu32, id); not_a_size_t = name.len; return inline_xdr_bytes(xdrs, (char **)&name.addr, ¬_a_size_t, UINT32_MAX); } if (!idmapping_enabled) { LogWarn(COMPONENT_IDMAPPER, "Idmapping is disabled, encode-nfs4-principal skipped"); return false; } PTHREAD_RWLOCK_rdlock(group ? &idmapper_group_lock : &idmapper_user_lock); if (group) success = idmapper_lookup_by_gid(id, &found); else success = idmapper_lookup_by_uid(id, &found, NULL); if (likely(success)) { not_a_size_t = found->len; /* Fully qualified owners are always stored in the hash table, no matter what our lookup method. */ success = inline_xdr_bytes(xdrs, (char **)&found->addr, ¬_a_size_t, UINT32_MAX); PTHREAD_RWLOCK_unlock(group ? &idmapper_group_lock : &idmapper_user_lock); return success; } else { PTHREAD_RWLOCK_unlock(group ? &idmapper_group_lock : &idmapper_user_lock); int rc; size_t size; bool looked_up = false; char *namebuff = NULL; struct gsh_buffdesc new_name; struct timespec s_time, e_time; /* We copy owner_domain to a static buffer to: * 1. Avoid holding owner_domain read lock during network calls, * and avoid possible writes starvation (when using libnfsidmap) * 2. Avoid inconsistencies across the usage points of * owner_domain, if owner_domain gets updated in the meantime */ PTHREAD_RWLOCK_rdlock(&owner_domain.lock); size_t owner_domain_len = owner_domain.domain.len; char owner_domain_addr[owner_domain_len + 1]; memcpy(owner_domain_addr, owner_domain.domain.addr, owner_domain_len); owner_domain_addr[owner_domain_len] = '\0'; PTHREAD_RWLOCK_unlock(&owner_domain.lock); new_name.len = 0; if (nfs_param.nfsv4_param.use_getpwnam) { if (group) size = sysconf(_SC_GETGR_R_SIZE_MAX); else size = sysconf(_SC_GETPW_R_SIZE_MAX); if (size == -1) size = PWENT_BEST_GUESS_LEN; if (nfs_param.directory_services_param .pwutils_use_fully_qualified_names) { size += NFS4_MAX_DOMAIN_LEN + 1; /* new_name should include domain length */ new_name.len = size; } else { /* new_name should not include domain length */ new_name.len = size; if (owner_domain_len == 0) { LogInfo(COMPONENT_IDMAPPER, "owner_domain.domain is NULL, cannot encode nfs4 principal"); return false; } size += owner_domain_len + 2; } } else { size = NFS4_MAX_DOMAIN_LEN + 2; } namebuff = alloca(size); new_name.addr = namebuff; if (nfs_param.nfsv4_param.use_getpwnam) { bool nulled; if (group) { struct group g; struct group *gres; now_mono(&s_time); rc = getgrgid_r(id, &g, namebuff, new_name.len, &gres); now_mono(&e_time); idmapper_monitoring__external_request( IDMAPPING_GID_TO_GROUP, IDMAPPING_PWUTILS, rc == 0, &s_time, &e_time); nulled = (gres == NULL); } else { struct passwd p; struct passwd *pres; now_mono(&s_time); rc = getpwuid_r(id, &p, namebuff, new_name.len, &pres); now_mono(&e_time); idmapper_monitoring__external_request( IDMAPPING_UID_TO_UIDGID, IDMAPPING_PWUTILS, rc == 0, &s_time, &e_time); nulled = (pres == NULL); } if ((rc == 0) && !nulled) { new_name.len = strlen(namebuff); if (!nfs_param.directory_services_param .pwutils_use_fully_qualified_names) { char *cursor = namebuff + new_name.len; *(cursor++) = '@'; ++new_name.len; if (owner_domain_len == 0) { LogInfo(COMPONENT_IDMAPPER, "owner_domain.domain is NULL, cannot encode nfs4 principal"); return false; } memcpy(cursor, owner_domain_addr, owner_domain_len); new_name.len += owner_domain_len; *(cursor + owner_domain_len) = '\0'; } looked_up = true; } else { LogInfo(COMPONENT_IDMAPPER, "%s failed with code %d.", (group ? "getgrgid_r" : "getpwuid_r"), rc); } } else { #ifdef USE_NFSIDMAP now_mono(&s_time); if (group) { rc = nfs4_gid_to_name(id, owner_domain_addr, namebuff, NFS4_MAX_DOMAIN_LEN + 1); } else { rc = nfs4_uid_to_name(id, owner_domain_addr, namebuff, NFS4_MAX_DOMAIN_LEN + 1); } now_mono(&e_time); idmapper_monitoring__external_request( group ? IDMAPPING_GID_TO_GROUP : IDMAPPING_UID_TO_UIDGID, IDMAPPING_NFSIDMAP, rc == 0, &s_time, &e_time); if (rc == 0) { new_name.len = strlen(namebuff); looked_up = true; } else { LogInfo(COMPONENT_IDMAPPER, "%s failed with code %d.", (group ? "nfs4_gid_to_name" : "nfs4_uid_to_name"), rc); } #else /* USE_NFSIDMAP */ looked_up = false; #endif /* !USE_NFSIDMAP */ } if (!looked_up) { if (nfs_param.nfsv4_param.allow_numeric_owners) { LogInfo(COMPONENT_IDMAPPER, "Lookup for %d failed, using numeric %s", id, (group ? "group" : "owner")); /* 2**32 is 10 digits long in decimal */ new_name.len = sprintf(namebuff, "%" PRIu32, id); } else { LogInfo(COMPONENT_IDMAPPER, "Lookup for %d failed, using nobody.", id); memcpy(new_name.addr, "nobody", 6); new_name.len = 6; } } /* Add to the cache and encode the result. */ if (group) add_group_to_cache(&new_name, id); else add_user_to_cache(&new_name, id, NULL, false); not_a_size_t = new_name.len; return inline_xdr_bytes(xdrs, (char **)&new_name.addr, ¬_a_size_t, UINT32_MAX); } } /** * @brief Encode a UID as a string * * @param[in,out] xdrs XDR stream to which to encode * @param[in] uid UID * * @retval true on success. * @retval false on failure. */ bool xdr_encode_nfs4_owner(XDR *xdrs, uid_t uid) { return xdr_encode_nfs4_princ(xdrs, uid, false); } /** * @brief Encode a GID as a string * * @param[in,out] xdrs XDR stream to which to encode * @param[in] gid GID * * @retval true on success. * @retval false on failure. */ bool xdr_encode_nfs4_group(XDR *xdrs, gid_t gid) { return xdr_encode_nfs4_princ(xdrs, gid, true); } /** * @brief Handle unqualified names * * @param[in] name C string of name * @param[in] len Length of name * @param[out] id ID found * @param[in] anon ID to use in case of nobody * * @return true on success, false on just phoning it in. */ static bool atless2id(char *name, size_t len, uint32_t *id, const uint32_t anon) { if ((len == 6) && (!memcmp(name, "nobody", 6))) { *id = anon; return true; } else if (nfs_param.nfsv4_param.allow_numeric_owners) { char *end = NULL; *id = strtol(name, &end, 10); if (!(end && *end != '\0')) return true; } /* Nothing else without an @ is allowed. */ return false; } /** * @brief Return gid given a group name * * @param[in] name group name * @param[out] gid address for gid to be filled in * * @return 0 on success and errno on failure and -1 if idmapping is disabled. * * NOTE: If a group name doesn't exist, getgrnam_r returns 0 with the * result pointer set to NULL. We turn that into ENOENT error! Also, * getgrnam_r fails with ERANGE if there is a group with a large number * of users that it can't fill all those users into the supplied buffer. * This need not be the group we are asking for! ERANGE is handled here, * so this function never ends up returning ERANGE back to the caller. */ static int name_to_gid(const char *name, gid_t *gid) { struct group g; struct group *gres = NULL; char *buf; size_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX); struct timespec s_time, e_time; int err; if (buflen == -1) buflen = PWENT_BEST_GUESS_LEN; if (!idmapping_enabled) { LogWarn(COMPONENT_IDMAPPER, "Idmapping is disabled. name-to-gid skipped."); /* Return -1 as the pw-functions return >= 0 return codes */ return -1; } do { buf = gsh_malloc(buflen); now_mono(&s_time); err = getgrnam_r(name, &g, buf, buflen, &gres); now_mono(&e_time); idmapper_monitoring__external_request( IDMAPPING_GROUPNAME_TO_GROUP, IDMAPPING_PWUTILS, err == 0, &s_time, &e_time); if (err == ERANGE) { buflen *= 16; gsh_free(buf); } } while (buflen <= GROUP_MAX_SIZE && err == ERANGE); if (err == 0) { if (gres == NULL) err = ENOENT; else *gid = gres->gr_gid; } if (err != ERANGE) gsh_free(buf); return err; } /** * @brief Populate uid and gid given a user name * * @param[in] name user name * @param[out] uid address for uid to be filled in * @param[out] gid address for gid to be filled in * * @return 0 on success and errno on failure and -1 if idmapping is disabled. * * NOTE: If a user name doesn't exist, getpwnam_r returns 0 with the * result pointer set to NULL. We turn that into ENOENT error! Also, * getpwnam_r fails with ERANGE if it can't fill all user passwd fields * into the supplied buffer. ERANGE is handled here, * so this function never ends up returning ERANGE back to the caller. */ static int name_to_uid(const char *name, uint32_t *uid, gid_t *gid) { struct passwd p; struct passwd *pres = NULL; char *buf; size_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX); struct timespec s_time, e_time; int err = ERANGE; if (!idmapping_enabled) { LogWarn(COMPONENT_IDMAPPER, "Idmapping is disabled. name-to-uid skipped."); /* Return -1 as the pw-functions return >= 0 return codes */ return -1; } if (buflen == -1) buflen = PWENT_BEST_GUESS_LEN; while (buflen <= PWENT_MAX_SIZE) { buf = gsh_malloc(buflen); now_mono(&s_time); err = getpwnam_r(name, &p, buf, buflen, &pres); now_mono(&e_time); idmapper_monitoring__external_request( IDMAPPING_USERNAME_TO_UIDGID, IDMAPPING_PWUTILS, err == 0, &s_time, &e_time); /* We don't use any strings from the buffer, so free it */ gsh_free(buf); if (err != ERANGE) break; buflen *= 16; } if (err == 0) { if (pres == NULL) err = ENOENT; else { *uid = pres->pw_uid; *gid = pres->pw_gid; } } return err; } /** * @brief Lookup a name using PAM * * @param[in] name C string of name * @param[out] id ID found * @param[in] group Whether this a group lookup * @param[out] gss_gid Found GID * @param[out] gss_uid Found UID * @apram[out] gotgss_gid Found a GID. * @param[in] at Location of the @ * * @return true on success, false not making the grade */ static bool pwentname2id(char *name, uint32_t *id, bool group, gid_t *gid, bool *got_gid, char *at) { int err; if (nfs_param.directory_services_param .pwutils_use_fully_qualified_names) { if (at == NULL) { LogWarn(COMPONENT_IDMAPPER, "The input name: %s must contain a domain", name); return false; } } else { /* Validate and strip off the domain from the name */ if (at != NULL) { PTHREAD_RWLOCK_rdlock(&owner_domain.lock); if (owner_domain.domain.len == 0) { PTHREAD_RWLOCK_unlock(&owner_domain.lock); LogWarn(COMPONENT_IDMAPPER, "owner_domain.domain is NULL, cannot validate the input domain"); return false; } if (strcmp(at + 1, owner_domain.domain.addr) != 0) { PTHREAD_RWLOCK_unlock(&owner_domain.lock); /* We won't map what isn't in right domain */ return false; } PTHREAD_RWLOCK_unlock(&owner_domain.lock); *at = '\0'; } } if (group) { err = name_to_gid(name, id); if (err == 0) return true; if (err != ENOENT) { LogWarn(COMPONENT_IDMAPPER, "getgrnam_r %s failed, error: %d", name, err); return false; } #ifndef USE_NFSIDMAP char *end = NULL; gid_t gid; gid = strtol(name, &end, 10); if (end && *end != '\0') return false; *id = gid; return true; #endif } else { err = name_to_uid(name, id, gid); if (err == 0) { *got_gid = true; return true; } if (err != ENOENT) { LogWarn(COMPONENT_IDMAPPER, "getpwnam_r %s failed, error: %d", name, err); return false; } #ifndef USE_NFSIDMAP char *end = NULL; uid_t uid; uid = strtol(name, &end, 10); if (end && *end != '\0') return false; *id = uid; *got_gid = false; return true; #endif } return false; } /** * @brief Lookup a name NFS ID Mapper * * @param[in] name C string of name * @param[in] len Length of name * @param[out] id ID found * @param[in] anon ID to use in case of nobody * @param[in] group Whether this a group lookup * @param[out] gss_gid Found GID * @param[out] gss_uid Found UID * @apram[out] gotgss_gid Found a GID. * @param[in] at Location of the @ * * @return true on success, false not making the grade */ static bool idmapname2id(char *name, size_t len, uint32_t *id, const uint32_t anon, bool group, gid_t *gid, bool *got_gid, char *at) { #ifdef USE_NFSIDMAP int rc; struct timespec s_time, e_time; if (!idmapping_enabled) { LogWarn(COMPONENT_IDMAPPER, "Idmapping is disabled. idmap-name-to-id skipped."); return false; } if (group) { now_mono(&s_time); rc = nfs4_name_to_gid(name, id); now_mono(&e_time); } else { now_mono(&s_time); rc = nfs4_name_to_uid(name, id); now_mono(&e_time); } idmapper_monitoring__external_request( group ? IDMAPPING_GROUPNAME_TO_GROUP : IDMAPPING_USERNAME_TO_UIDGID, IDMAPPING_NFSIDMAP, rc == 0, &s_time, &e_time); if (rc == 0) { return true; } else { LogInfo(COMPONENT_IDMAPPER, "%s %s failed with %d, using anonymous.", (group ? "nfs4_name_to_gid" : "nfs4_name_to_uid"), name, -rc); return false; } #else /* USE_NFSIDMAP */ return false; #endif /* USE_NFSIDMAP */ } /** * @brief Convert a name to an ID * * @param[in] name The name of the user * @param[out] id The resulting id * @param[in] group True if this is a group name * @param[in] anon ID to return if look up fails * * @return true if successful, false otherwise */ static bool name2id(const struct gsh_buffdesc *name, uint32_t *id, bool group, const uint32_t anon) { bool success; gid_t gid; char *namebuff; char *at; bool got_gid = false; bool looked_up = false; PTHREAD_RWLOCK_rdlock(group ? &idmapper_group_lock : &idmapper_user_lock); if (group) success = idmapper_lookup_by_gname(name, id); else success = idmapper_lookup_by_uname(name, id, NULL, false); PTHREAD_RWLOCK_unlock(group ? &idmapper_group_lock : &idmapper_user_lock); if (success) return true; /* Lookup negative cache */ PTHREAD_RWLOCK_rdlock(group ? &idmapper_negative_cache_group_lock : &idmapper_negative_cache_user_lock); if (group) success = idmapper_negative_cache_lookup_group_by_name(name); else success = idmapper_negative_cache_lookup_user_by_name(name); PTHREAD_RWLOCK_unlock(group ? &idmapper_negative_cache_group_lock : &idmapper_negative_cache_user_lock); if (success) { *id = anon; return true; } /* Something we can mutate and count on as terminated */ namebuff = alloca(name->len + 1); memcpy(namebuff, name->addr, name->len); *(namebuff + name->len) = '\0'; at = memchr(namebuff, '@', name->len); if (at == NULL) { if (pwentname2id(namebuff, id, group, &gid, &got_gid, NULL)) looked_up = true; else if (atless2id(namebuff, name->len, id, anon)) looked_up = true; else return false; } else if (nfs_param.nfsv4_param.use_getpwnam) { looked_up = pwentname2id(namebuff, id, group, &gid, &got_gid, at); } else { looked_up = idmapname2id(namebuff, name->len, id, anon, group, &gid, &got_gid, at); } if (!looked_up) { LogInfo(COMPONENT_IDMAPPER, "All lookups failed for %s, using anonymous.", namebuff); *id = anon; /* Add to negative cache */ if (group) add_group_to_negative_cache(name); else add_user_to_negative_cache(name); return true; } if (group) add_group_to_cache(name, *id); else add_user_to_cache(name, *id, got_gid ? &gid : NULL, false); return true; } /** * @brief Convert a name to a uid * * @param[in] name The name of the user * @param[out] uid The resulting UID * @param[in] anon The anonymous UID for this export * * @return true if successful, false otherwise * */ bool name2uid(const struct gsh_buffdesc *name, uid_t *uid, const uid_t anon) { return name2id(name, uid, false, anon); } /** * @brief Convert a name to a GID * * @param[in] name The name of the user * @param[out] gid The resulting GID * @param[in] anon The anonymous GID for this export * * @return true if successful, false otherwise */ bool name2gid(const struct gsh_buffdesc *name, gid_t *gid, const gid_t anon) { return name2id(name, gid, true, anon); } void winbind_stats_update(struct timespec *s_time, struct timespec *e_time) { nsecs_elapsed_t resp_time; resp_time = timespec_diff(s_time, e_time); PTHREAD_RWLOCK_wrlock(&winbind_auth_lock); (void)atomic_inc_uint64_t(&winbind_auth_stats.total); (void)atomic_add_uint64_t(&winbind_auth_stats.latency, resp_time); if (winbind_auth_stats.max < resp_time) winbind_auth_stats.max = resp_time; if (winbind_auth_stats.min == 0 || winbind_auth_stats.min > resp_time) winbind_auth_stats.min = resp_time; PTHREAD_RWLOCK_unlock(&winbind_auth_lock); } void gc_stats_update(struct timespec *s_time, struct timespec *e_time) { nsecs_elapsed_t resp_time; resp_time = timespec_diff(s_time, e_time); PTHREAD_RWLOCK_wrlock(&gc_auth_lock); (void)atomic_inc_uint64_t(&gc_auth_stats.total); (void)atomic_add_uint64_t(&gc_auth_stats.latency, resp_time); if (gc_auth_stats.max < resp_time) gc_auth_stats.max = resp_time; if (gc_auth_stats.min == 0 || gc_auth_stats.min > resp_time) gc_auth_stats.min = resp_time; PTHREAD_RWLOCK_unlock(&gc_auth_lock); } void dns_stats_update(struct timespec *s_time, struct timespec *e_time) { nsecs_elapsed_t resp_time; resp_time = timespec_diff(s_time, e_time); PTHREAD_RWLOCK_wrlock(&dns_auth_lock); (void)atomic_inc_uint64_t(&dns_auth_stats.total); (void)atomic_add_uint64_t(&dns_auth_stats.latency, resp_time); if (dns_auth_stats.max < resp_time) dns_auth_stats.max = resp_time; if (dns_auth_stats.min == 0 || dns_auth_stats.min > resp_time) dns_auth_stats.min = resp_time; PTHREAD_RWLOCK_unlock(&dns_auth_lock); } void reset_auth_stats(void) { PTHREAD_RWLOCK_wrlock(&winbind_auth_lock); winbind_auth_stats.total = 0; winbind_auth_stats.latency = 0; winbind_auth_stats.max = 0; winbind_auth_stats.min = 0; PTHREAD_RWLOCK_unlock(&winbind_auth_lock); PTHREAD_RWLOCK_wrlock(&gc_auth_lock); gc_auth_stats.total = 0; gc_auth_stats.latency = 0; gc_auth_stats.max = 0; gc_auth_stats.min = 0; PTHREAD_RWLOCK_unlock(&gc_auth_lock); PTHREAD_RWLOCK_wrlock(&dns_auth_lock); dns_auth_stats.total = 0; dns_auth_stats.latency = 0; dns_auth_stats.max = 0; dns_auth_stats.min = 0; PTHREAD_RWLOCK_unlock(&dns_auth_lock); } /* NFSv4 specific features: RPCSEC_GSS will * provide user like * * nfs/ * root/ * host/ * choice is made to map them to root based on config setting */ bool is_root_principal(struct gsh_buffdesc *princbuff) { static const char *KERBEROS_PRINCIPAL_NFS = "nfs/"; static const char *KERBEROS_PRINCIPAL_ROOT = "root/"; static const char *KERBEROS_PRINCIPAL_HOST = "host/"; const uint32_t root_kerberos_principal = nfs_param.directory_services_param.root_kerberos_principal; if (root_kerberos_principal & ROOT_KERBEROS_PRINCIPAL_NONE) return false; if (princbuff->len < 4) return false; if (root_kerberos_principal & ROOT_KERBEROS_PRINCIPAL_NFS && !memcmp(princbuff->addr, KERBEROS_PRINCIPAL_NFS, 4)) { return true; } if (princbuff->len < 5) return false; if (root_kerberos_principal & ROOT_KERBEROS_PRINCIPAL_ROOT && !memcmp(princbuff->addr, KERBEROS_PRINCIPAL_ROOT, 5)) { return true; } if (root_kerberos_principal & ROOT_KERBEROS_PRINCIPAL_HOST && !memcmp(princbuff->addr, KERBEROS_PRINCIPAL_HOST, 5)) { return true; } return false; } #ifdef _HAVE_GSSAPI #ifdef _MSPAC_SUPPORT /** * @brief Convert a principal (as returned by @c gss_display_name) to a UID * * @param[in] name The principal of the user * @param[out] uid The resulting UID * @param[in,out] gd GSS data * * @return true if successful, false otherwise */ bool principal2uid(char *principal, uid_t *uid, gid_t *gid, struct svc_rpc_gss_data *gd) #else /** * @brief Convert a principal (as returned by @c gss_display_name) to a UID * * @param[in] name The principal of the user * @param[out] uid The resulting UID * * @return true if successful, false otherwise */ bool principal2uid(char *principal, uid_t *uid, gid_t *gid) #endif { uid_t gss_uid = -1; gid_t gss_gid = -1; const gid_t *gss_gidres = NULL; bool success; uint principal_len = strlen(principal); struct gsh_buffdesc princbuff = { .addr = principal, .len = principal_len }; if (!idmapping_enabled) { LogWarn(COMPONENT_IDMAPPER, "Idmapping is disabled. principal-to-uid skipped."); return false; } PTHREAD_RWLOCK_rdlock(&idmapper_user_lock); success = idmapper_lookup_by_uname(&princbuff, &gss_uid, &gss_gidres, true); /* We do need uid and gid. If gid is not in the cache, treat it as a * failure. */ if (success && (gss_gidres != NULL)) { gss_gid = *gss_gidres; PTHREAD_RWLOCK_unlock(&idmapper_user_lock); goto out; } PTHREAD_RWLOCK_unlock(&idmapper_user_lock); /* Lookup negative cache */ PTHREAD_RWLOCK_rdlock(&idmapper_negative_cache_user_lock); success = idmapper_negative_cache_lookup_user_by_name(&princbuff); PTHREAD_RWLOCK_unlock(&idmapper_negative_cache_user_lock); if (success) return false; if (is_root_principal(&princbuff)) { /* This is a "root" request made from the * hostbased nfs principal, use root */ *uid = 0; *gid = 0; return true; } if (nfs_param.nfsv4_param.use_getpwnam) { bool got_gid = false; char *at = strchr(principal, '@'); LogDebug(COMPONENT_IDMAPPER, "Get uid for %s using pw func", principal); if (nfs_param.directory_services_param .pwutils_use_fully_qualified_names) { success = pwentname2id(principal, &gss_uid, false, &gss_gid, &got_gid, at); } else { char *uname; int uname_len = principal_len; /* Strip off realm from principal for pwnam lookup */ if (at != NULL) uname_len = at - principal; uname = alloca(uname_len + 1); memcpy(uname, principal, uname_len); uname[uname_len] = '\0'; success = pwentname2id(uname, &gss_uid, false, &gss_gid, &got_gid, NULL); } if (!success) { idmapper_monitoring__failure( IDMAPPING_PRINCIPAL_TO_UIDGID, IDMAPPING_PWUTILS); goto principal_not_found; } if (!got_gid) { LogWarn(COMPONENT_IDMAPPER, "Gid resolution failed for %s", principal); goto principal_not_found; } goto principal_found; } else { #ifdef USE_NFSIDMAP struct timespec s_time, e_time; int err; LogDebug(COMPONENT_IDMAPPER, "Get uid for %s using nfsidmap", principal); /* nfs4_gss_princ_to_ids required to extract uid/gid from gss creds */ now_mono(&s_time); err = nfs4_gss_princ_to_ids("krb5", principal, &gss_uid, &gss_gid); now_mono(&e_time); idmapper_monitoring__external_request( IDMAPPING_PRINCIPAL_TO_UIDGID, IDMAPPING_NFSIDMAP, err == 0, &s_time, &e_time); if (err) { LogWarn(COMPONENT_IDMAPPER, "Could not resolve %s to uid using nfsidmap, err: %d", principal, err); idmapper_monitoring__failure( IDMAPPING_PRINCIPAL_TO_UIDGID, IDMAPPING_NFSIDMAP); #ifdef _MSPAC_SUPPORT bool stats = nfs_param.core_param.enable_AUTHSTATS; struct passwd *pwd; if (gd->flags & SVC_RPC_GSS_FLAG_MSPAC) { struct wbcAuthUserParams params; wbcErr wbc_err; struct wbcAuthUserInfo *info; struct wbcAuthErrorInfo *error = NULL; memset(¶ms, 0, sizeof(params)); params.level = WBC_AUTH_USER_LEVEL_PAC; params.password.pac.data = (uint8_t *)gd->pac.ms_pac.value; params.password.pac.length = gd->pac.ms_pac.length; now(&s_time); wbc_err = wbcAuthenticateUserEx(¶ms, &info, &error); now(&e_time); idmapper_monitoring__external_request( IDMAPPING_MSPAC_TO_SID, IDMAPPING_WINBIND, WBC_ERROR_IS_OK(wbc_err), &s_time, &e_time); if (stats) winbind_stats_update(&s_time, &e_time); if (!WBC_ERROR_IS_OK(wbc_err)) { LogInfo(COMPONENT_IDMAPPER, "wbcAuthenticateUserEx returned %s", wbcErrorString(wbc_err)); goto principal_not_found; } if (error) { LogInfo(COMPONENT_IDMAPPER, "nt_status: %s, display_string %s", error->nt_string, error->display_string); wbcFreeMemory(error); goto principal_not_found; } now(&s_time); wbc_err = wbcGetpwsid(&info->sids[0].sid, &pwd); now(&e_time); idmapper_monitoring__external_request( IDMAPPING_SID_TO_UIDGID, IDMAPPING_WINBIND, WBC_ERROR_IS_OK(wbc_err), &s_time, &e_time); if (stats) winbind_stats_update(&s_time, &e_time); if (!WBC_ERROR_IS_OK(wbc_err)) { LogInfo(COMPONENT_IDMAPPER, "wbcGetpwsid returned %s", wbcErrorString(wbc_err)); wbcFreeMemory(info); goto principal_not_found; } gss_uid = pwd->pw_uid; gss_gid = pwd->pw_gid; wbcFreeMemory(info); goto principal_found; } #endif /* _MSPAC_SUPPORT */ goto principal_not_found; } goto principal_found; #else /* !USE_NFSIDMAP */ assert(!"prohibited by idmapping configuration"); #endif } principal_found: add_user_to_cache(&princbuff, gss_uid, &gss_gid, true); out: *uid = gss_uid; *gid = gss_gid; return true; principal_not_found: add_user_to_negative_cache(&princbuff); return false; } #ifdef USE_DBUS /** * DBUS method to collect Auth stats for group cache and winbind */ static bool all_auth_stats(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { bool success = true, stats_exist = false; char *errormsg = "OK"; DBusMessageIter iter, struct_iter; double res = 0.0; dbus_message_iter_init_append(reply, &iter); if (!nfs_param.core_param.enable_AUTHSTATS) { success = false; errormsg = "auth related stats disabled"; gsh_dbus_status_reply(&iter, success, errormsg); return true; } gsh_dbus_status_reply(&iter, success, errormsg); gsh_dbus_append_timestamp(&iter, &auth_stats_time); dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); /* group cache stats */ PTHREAD_RWLOCK_rdlock(&gc_auth_lock); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &gc_auth_stats.total); if (gc_auth_stats.total > 0) { stats_exist = true; res = (double)gc_auth_stats.latency / gc_auth_stats.total * 0.000001; } dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_DOUBLE, &res); if (stats_exist) res = (double)gc_auth_stats.max * 0.000001; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_DOUBLE, &res); if (stats_exist) res = (double)gc_auth_stats.min * 0.000001; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_DOUBLE, &res); PTHREAD_RWLOCK_unlock(&gc_auth_lock); stats_exist = false; res = 0.0; /* winbind stats */ PTHREAD_RWLOCK_rdlock(&winbind_auth_lock); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &winbind_auth_stats.total); if (winbind_auth_stats.total > 0) { stats_exist = true; res = (double)winbind_auth_stats.latency / winbind_auth_stats.total * 0.000001; } dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_DOUBLE, &res); if (stats_exist) res = (double)winbind_auth_stats.max * 0.000001; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_DOUBLE, &res); if (stats_exist) res = (double)winbind_auth_stats.min * 0.000001; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_DOUBLE, &res); PTHREAD_RWLOCK_unlock(&winbind_auth_lock); stats_exist = false; res = 0.0; /* DNS stats */ PTHREAD_RWLOCK_rdlock(&dns_auth_lock); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &dns_auth_stats.total); if (dns_auth_stats.total > 0) { stats_exist = true; res = (double)dns_auth_stats.latency / dns_auth_stats.total * 0.000001; } dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_DOUBLE, &res); if (stats_exist) res = (double)dns_auth_stats.max * 0.000001; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_DOUBLE, &res); if (stats_exist) res = (double)dns_auth_stats.min * 0.000001; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_DOUBLE, &res); PTHREAD_RWLOCK_unlock(&dns_auth_lock); dbus_message_iter_close_container(&iter, &struct_iter); return true; } struct gsh_dbus_method auth_statistics = { .name = "GetAuthStats", .method = all_auth_stats, .args = { STATUS_REPLY, TIMESTAMP_REPLY, AUTH_REPLY, END_ARG_LIST } }; #endif #endif /** @} */ nfs-ganesha-6.5/src/idmapper/idmapper_cache.c000066400000000000000000000626561473756622300212740ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @addtogroup idmapper * @{ */ /** * @file idmapper_cache.c * @brief Id mapping cache functions */ #include "config.h" #include "log.h" #include "config_parsing.h" #include #include #include #include "gsh_intrinsic.h" #include "gsh_types.h" #include "gsh_list.h" #ifdef USE_DBUS #include "gsh_dbus.h" #endif #include "common_utils.h" #include "avltree.h" #include "idmapper.h" #include "nfs_core.h" #include "abstract_atomic.h" #include "server_stats_private.h" #include "idmapper_monitoring.h" /** * @brief User entry in the IDMapper cache */ struct cache_user { struct gsh_buffdesc uname; /*< Username */ uid_t uid; /*< Corresponding UID */ gid_t gid; /*< Corresponding GID */ bool gid_set; /*< if the GID has been set */ struct avltree_node uname_node; /*< Node in the name tree */ struct avltree_node uid_node; /*< Node in the UID tree */ bool in_uidtree; /* true iff this is in uid_tree */ time_t epoch; TAILQ_ENTRY(cache_user) queue_entry; /* Node in user-fifo-queue */ }; #define user_expired(user) \ (time(NULL) - (user)->epoch > \ nfs_param.directory_services_param.idmapped_user_time_validity) /** * @brief Group entry in the IDMapper cache */ struct cache_group { struct gsh_buffdesc gname; /*< Group name */ gid_t gid; /*< Group ID */ struct avltree_node gname_node; /*< Node in the name tree */ struct avltree_node gid_node; /*< Node in the GID tree */ TAILQ_ENTRY(cache_group) queue_entry; /* Node in group-fifo-queue */ time_t epoch; }; #define group_expired(group) \ (time(NULL) - (group)->epoch > \ nfs_param.directory_services_param.idmapped_group_time_validity) /** * @brief Number of entries in the UID cache, should be prime. */ #define id_cache_size 1009 /** * @brief A user fifo queue ordered by insertion timestamp * * This fifo queue also mimics the order of expiration time of the cache * entries, since the expiration time is a linear function of the insertion * time. * * Expiration_time = Insertion_time + Cache_time_validity (constant) * * The head of the queue contains the entry with least time-validity. * The tail of the queue contains the entry with most time-validity. * The eviction happens from the head, and insertion happens at the tail. */ static TAILQ_HEAD(, cache_user) user_fifo_queue; /* Similar to above, this is the fifo queue containing group entries */ static TAILQ_HEAD(, cache_group) group_fifo_queue; /** * @brief UID cache, may only be accessed with idmapper_user_lock * held. If idmapper_user_lock is held for read, it must be accessed * atomically. (For a write, normal fetch/store is sufficient since * others are kept out.) */ static struct avltree_node *uid_cache[id_cache_size]; /** * @brief GID cache, may only be accessed with idmapper_group_lock * held. If idmapper_group_lock is held for read, it must be accessed * atomically. (For a write, normal fetch/store is sufficient since * others are kept out.) */ static struct avltree_node *gid_cache[id_cache_size]; /** * @brief Lock that protects the idmapper user cache */ pthread_rwlock_t idmapper_user_lock; /** * @brief Lock that protects the idmapper group cache */ pthread_rwlock_t idmapper_group_lock; /** * @brief Tree of users, by name */ static struct avltree uname_tree; /** * @brief Tree of users, by ID */ static struct avltree uid_tree; /** * @brief Tree of groups, by name */ static struct avltree gname_tree; /** * @brief Tree of groups, by ID */ static struct avltree gid_tree; /** * @brief Comparison for user names * * @param[in] node1 A node * @param[in] nodea Another node * * @retval -1 if node1 is less than nodea * @retval 0 if node1 and nodea are equal * @retval 1 if node1 is greater than nodea */ static int uname_comparator(const struct avltree_node *node1, const struct avltree_node *nodea) { struct cache_user *user1 = avltree_container_of(node1, struct cache_user, uname_node); struct cache_user *usera = avltree_container_of(nodea, struct cache_user, uname_node); return gsh_buffdesc_comparator(&user1->uname, &usera->uname); } /** * @brief Comparison for UIDs * * @param[in] node1 A node * @param[in] nodea Another node * * @retval -1 if node1 is less than nodea * @retval 0 if node1 and nodea are equal * @retval 1 if node1 is greater than nodea */ static int uid_comparator(const struct avltree_node *node1, const struct avltree_node *nodea) { struct cache_user *user1 = avltree_container_of(node1, struct cache_user, uid_node); struct cache_user *usera = avltree_container_of(nodea, struct cache_user, uid_node); if (user1->uid < usera->uid) return -1; else if (user1->uid > usera->uid) return 1; else return 0; } /** * @brief Comparison for group names * * @param[in] node1 A node * @param[in] nodea Another node * * @retval -1 if node1 is less than nodea * @retval 0 if node1 and nodea are equal * @retval 1 if node1 is greater than nodea */ static inline int gname_comparator(const struct avltree_node *node1, const struct avltree_node *nodea) { struct cache_group *group1 = avltree_container_of(node1, struct cache_group, gname_node); struct cache_group *groupa = avltree_container_of(nodea, struct cache_group, gname_node); return gsh_buffdesc_comparator(&group1->gname, &groupa->gname); } /** * @brief Comparison for GIDs * * @param[in] node1 A node * @param[in] nodea Another node * * @retval -1 if node1 is less than nodea * @retval 0 if node1 and nodea are equal * @retval 1 if node1 is greater than nodea */ static int gid_comparator(const struct avltree_node *node1, const struct avltree_node *nodea) { struct cache_group *group1 = avltree_container_of(node1, struct cache_group, gid_node); struct cache_group *groupa = avltree_container_of(nodea, struct cache_group, gid_node); if (group1->gid < groupa->gid) return -1; else if (group1->gid > groupa->gid) return 1; else return 0; } /** * @brief Remove user entry from all user cache data structures * * @note The caller must hold idmapper_user_lock for write. */ static void remove_cache_user(struct cache_user *user) { avltree_remove(&user->uname_node, &uname_tree); if (user->in_uidtree) { uid_cache[user->uid % id_cache_size] = NULL; avltree_remove(&user->uid_node, &uid_tree); } /* Remove from users fifo queue */ TAILQ_REMOVE(&user_fifo_queue, user, queue_entry); gsh_free(user); } /** * @brief Remove group entry from all group cache data structures * * @note The caller must hold idmapper_group_lock for write. */ static void remove_cache_group(struct cache_group *group) { gid_cache[group->gid % id_cache_size] = NULL; avltree_remove(&group->gid_node, &gid_tree); avltree_remove(&group->gname_node, &gname_tree); /* Remove from groups fifo queue */ TAILQ_REMOVE(&group_fifo_queue, group, queue_entry); gsh_free(group); } /** * @brief Reaps the cache user entries * * Since the user-fifo queue stores entries in increasing order of time * validity, the reaper reaps from the queue head in the same order. It * stops when it first encounters a non-expired entry. */ static void reap_users_cache(void) { struct cache_user *user; LogFullDebug(COMPONENT_IDMAPPER, "Idmapper user-cache reaper run started"); PTHREAD_RWLOCK_wrlock(&idmapper_user_lock); for (user = TAILQ_FIRST(&user_fifo_queue); user != NULL;) { if (!user_expired(user)) break; remove_cache_user(user); user = TAILQ_FIRST(&user_fifo_queue); } PTHREAD_RWLOCK_unlock(&idmapper_user_lock); LogFullDebug(COMPONENT_IDMAPPER, "Idmapper user-cache reaper run ended"); } /** * @brief Reaps the cache group entries * * Since the group-fifo queue stores entries in increasing order of time * validity, the reaper reaps from the queue head in the same order. It * stops when it first encounters a non-expired entry. */ static void reap_groups_cache(void) { struct cache_group *group; LogFullDebug(COMPONENT_IDMAPPER, "Idmapper group-cache reap run started"); PTHREAD_RWLOCK_wrlock(&idmapper_group_lock); for (group = TAILQ_FIRST(&group_fifo_queue); group != NULL;) { if (!group_expired(group)) break; remove_cache_group(group); group = TAILQ_FIRST(&group_fifo_queue); } PTHREAD_RWLOCK_unlock(&idmapper_group_lock); LogFullDebug(COMPONENT_IDMAPPER, "Idmapper group-cache reaper run ended"); } /** * @brief Reaps the cache user, group entries */ void idmapper_cache_reap(void) { reap_users_cache(); reap_groups_cache(); } /** * @brief Initialize the IDMapper cache */ void idmapper_cache_init(void) { PTHREAD_RWLOCK_init(&idmapper_user_lock, NULL); PTHREAD_RWLOCK_init(&idmapper_group_lock, NULL); avltree_init(&uname_tree, uname_comparator, 0); avltree_init(&uid_tree, uid_comparator, 0); memset(uid_cache, 0, id_cache_size * sizeof(struct avltree_node *)); avltree_init(&gname_tree, gname_comparator, 0); avltree_init(&gid_tree, gid_comparator, 0); memset(gid_cache, 0, id_cache_size * sizeof(struct avltree_node *)); TAILQ_INIT(&user_fifo_queue); TAILQ_INIT(&group_fifo_queue); } /** * @brief Add a user entry to the cache * * @note The caller must hold idmapper_user_lock for write. * * @param[in] name The user name * @param[in] uid The user ID * @param[in] gid Optional. Set to NULL if no gid is known. * @param[in] gss_princ true when name is gss principal. * The uid to name map is not added for gss principals. * * @retval true on success. * @retval false if our reach exceeds our grasp. */ bool idmapper_add_user(const struct gsh_buffdesc *name, uid_t uid, const gid_t *gid, bool gss_princ) { struct avltree_node *found_name; struct avltree_node *found_id; struct cache_user *old; struct cache_user *new; struct cache_user *user_fifo_queue_head_node; new = gsh_malloc(sizeof(struct cache_user) + name->len); new->epoch = time(NULL); new->uname.addr = (char *)new + sizeof(struct cache_user); new->uname.len = name->len; new->uid = uid; memcpy(new->uname.addr, name->addr, name->len); if (gid) { new->gid = *gid; new->gid_set = true; } else { new->gid = -1; new->gid_set = false; } new->in_uidtree = (gss_princ) ? false : true; /* * There are 3 cases why we find an existing cache entry. * * Case 1: * The threads that lookup by-name or by-id use the read lock. * If they don't find an entry, then they release the read lock, * acquire the write lock and then add the entry. So it is * possible that multiple threads may fail to find an entry at * one point and they all try to add. In this case, we will be * trying to insert same name,id mapping. * * Case 2: * It is also possible that name got a different id or an id got * a different name causing us to find an existing entry when we * are trying to add an entry. This case calls for removing the * stale entry and update with this new entry. * * Case 3: * The username to id mapping could be from plain nfs idmapping * in which case we will not have a valid gid. If this is for a * kerberos principal mapping, we will have uid and gid but we * will not have "uid to name" cache entry (the reverse * mapping). This case requires us to combine the old entry and * the new entry! * * Note that the 3rd case happens if and only if IDMAPD_DOMAIN * and LOCAL_REALMS are set to the same value! */ found_name = avltree_insert(&new->uname_node, &uname_tree); if (unlikely(found_name)) { old = avltree_container_of(found_name, struct cache_user, uname_node); /* Combine non-expired old into new if uid's match */ if ((old->uid == new->uid) && !user_expired(old)) { if (!new->gid_set && old->gid_set) { new->gid = old->gid; new->gid_set = true; } if (!new->in_uidtree && old->in_uidtree) new->in_uidtree = true; } /* Remove the old and insert the new */ remove_cache_user(old); found_name = avltree_insert(&new->uname_node, &uname_tree); assert(found_name == NULL); } if (!new->in_uidtree) /* all done */ goto add_to_queue; found_id = avltree_insert(&new->uid_node, &uid_tree); if (unlikely(found_id)) { old = avltree_container_of(found_id, struct cache_user, uid_node); remove_cache_user(old); found_id = avltree_insert(&new->uid_node, &uid_tree); assert(found_id == NULL); } uid_cache[uid % id_cache_size] = &new->uid_node; add_to_queue: TAILQ_INSERT_TAIL(&user_fifo_queue, new, queue_entry); /* If we breach max-cache capacity, remove the user queue's head node */ if (avltree_size(&uname_tree) > nfs_param.directory_services_param.cache_users_max_count) { LogDebug( COMPONENT_IDMAPPER, "Cache size limit violated, removing user with least time validity"); user_fifo_queue_head_node = TAILQ_FIRST(&user_fifo_queue); const time_t cached_duration = time(NULL) - user_fifo_queue_head_node->epoch; remove_cache_user(user_fifo_queue_head_node); idmapper_monitoring__evicted_cache_entity( IDMAPPING_CACHE_ENTITY_USER, cached_duration); } return true; } /** * @brief Add a group entry to the cache * * @note The caller must hold idmapper_group_lock for write. * * @param[in] name The user name * @param[in] gid The group id * * @retval true on success. * @retval false if our reach exceeds our grasp. */ bool idmapper_add_group(const struct gsh_buffdesc *name, const gid_t gid) { struct avltree_node *found_name; struct avltree_node *found_id; struct cache_group *tmp; struct cache_group *new; struct cache_group *group_fifo_queue_head_node; new = gsh_malloc(sizeof(struct cache_group) + name->len); new->epoch = time(NULL); new->gname.addr = (char *)new + sizeof(struct cache_group); new->gname.len = name->len; new->gid = gid; memcpy(new->gname.addr, name->addr, name->len); /* * The threads that lookup by-name or by-id use the read lock. If * they don't find an entry, then they release the read lock, * acquire the write lock and then add the entry. So it is * possible that multiple threads may fail to find an entry at * one point and they all try to add. In this case, we will be * trying to insert same name,id mapping. It is also possible * that name got a different id or an id got a different name * causing us to find an existing entry when we are trying to * add an entry! * * If we find an existing entry, we remove it from both the name * and the id AVL trees, and then add the new entry. */ found_name = avltree_insert(&new->gname_node, &gname_tree); if (unlikely(found_name)) { tmp = avltree_container_of(found_name, struct cache_group, gname_node); remove_cache_group(tmp); found_name = avltree_insert(&new->gname_node, &gname_tree); assert(found_name == NULL); } found_id = avltree_insert(&new->gid_node, &gid_tree); if (unlikely(found_id)) { tmp = avltree_container_of(found_id, struct cache_group, gid_node); remove_cache_group(tmp); found_id = avltree_insert(&new->gid_node, &gid_tree); assert(found_id == NULL); } gid_cache[gid % id_cache_size] = &new->gid_node; TAILQ_INSERT_TAIL(&group_fifo_queue, new, queue_entry); /* If we breach max-cache capacity, remove the user queue's head node */ if (avltree_size(&gname_tree) > nfs_param.directory_services_param.cache_groups_max_count) { LogDebug( COMPONENT_IDMAPPER, "Cache size limit violated, removing group with least time validity"); group_fifo_queue_head_node = TAILQ_FIRST(&group_fifo_queue); const time_t cached_duration = time(NULL) - group_fifo_queue_head_node->epoch; remove_cache_group(group_fifo_queue_head_node); idmapper_monitoring__evicted_cache_entity( IDMAPPING_CACHE_ENTITY_GROUP, cached_duration); } return true; } /** * @brief Look up a user by name * * @note The caller must hold idmapper_user_lock for read. * * @param[in] name The user name to look up. * @param[out] uid The user ID found. May be NULL if the caller * isn't interested in the UID. (This seems * unlikely.) * @param[out] gid The GID for the user, or NULL if there is * none. The caller may specify NULL if it isn't * interested. * * @retval true on success. * @retval false if we need to try, try again. */ bool idmapper_lookup_by_uname(const struct gsh_buffdesc *name, uid_t *uid, const gid_t **gid, bool gss_princ) { struct cache_user prototype = { .uname = *name }; struct avltree_node *found_node = avltree_lookup(&prototype.uname_node, &uname_tree); struct cache_user *found_user; void **cache_slot; if (unlikely(!found_node)) return false; found_user = avltree_container_of(found_node, struct cache_user, uname_node); if (!gss_princ) { /* I assume that if someone likes this user enough to look it up by name, they'll like it enough to look it up by ID later. If the name is gss principal it does not have entry in uid tree */ cache_slot = (void **)&uid_cache[found_user->uid % id_cache_size]; atomic_store_voidptr(cache_slot, &found_user->uid_node); } if (likely(uid)) *uid = found_user->uid; if (unlikely(gid)) *gid = (found_user->gid_set ? &found_user->gid : NULL); return user_expired(found_user) ? false : true; } /** * @brief Look up a user by ID * * @note The caller must hold idmapper_user_lock for read. * * @param[in] uid The user ID to look up. * @param[out] name The user name to look up. (May be NULL if the user * doesn't care about the name.) * @param[out] gid The GID for the user, or NULL if there is * none. The caller may specify NULL if it isn't * interested. * * @retval true on success. * @retval false if we weren't so successful. */ bool idmapper_lookup_by_uid(const uid_t uid, const struct gsh_buffdesc **name, const gid_t **gid) { struct cache_user prototype = { .uid = uid }; void **cache_slot = (void **)&uid_cache[uid % id_cache_size]; struct avltree_node *found_node = atomic_fetch_voidptr(cache_slot); struct cache_user *found_user; bool found = false; if (likely(found_node)) { found_user = avltree_container_of(found_node, struct cache_user, uid_node); if (found_user->uid == uid) found = true; } if (unlikely(!found)) { found_node = avltree_lookup(&prototype.uid_node, &uid_tree); if (unlikely(!found_node)) return false; atomic_store_voidptr(cache_slot, found_node); found_user = avltree_container_of(found_node, struct cache_user, uid_node); } if (likely(name)) *name = &found_user->uname; if (gid) *gid = (found_user->gid_set ? &found_user->gid : NULL); return user_expired(found_user) ? false : true; } /** * @brief Lookup a group by name * * @note The caller must hold idmapper_group_lock for read. * * @param[in] name The user name to look up. * @param[out] gid The group ID found. May be NULL if the caller * isn't interested in the GID. (This seems * unlikely, since you can't get anything else from * this function.) * * @retval true on success. * @retval false if we need to try, try again. */ bool idmapper_lookup_by_gname(const struct gsh_buffdesc *name, uid_t *gid) { struct cache_group prototype = { .gname = *name }; struct avltree_node *found_node = avltree_lookup(&prototype.gname_node, &gname_tree); struct cache_group *found_group; void **cache_slot; if (unlikely(!found_node)) return false; found_group = avltree_container_of(found_node, struct cache_group, gname_node); /* I assume that if someone likes this group enough to look it up by name, they'll like it enough to look it up by ID later. */ cache_slot = (void **)&gid_cache[found_group->gid % id_cache_size]; atomic_store_voidptr(cache_slot, &found_group->gid_node); if (likely(gid)) *gid = found_group->gid; else LogDebug(COMPONENT_IDMAPPER, "Caller is being weird."); return group_expired(found_group) ? false : true; } /** * @brief Look up a group by ID * * @note The caller must hold idmapper_group_lock for read. * * @param[in] gid The group ID to look up. * @param[out] name The user name to look up. (May be NULL if the user * doesn't care about the name, which would be weird.) * * @retval true on success. * @retval false if we're most unfortunate. */ bool idmapper_lookup_by_gid(const gid_t gid, const struct gsh_buffdesc **name) { struct cache_group prototype = { .gid = gid }; void **cache_slot = (void **)&gid_cache[gid % id_cache_size]; struct avltree_node *found_node = atomic_fetch_voidptr(cache_slot); struct cache_group *found_group; bool found = false; if (likely(found_node)) { found_group = avltree_container_of( found_node, struct cache_group, gid_node); if (found_group->gid == gid) found = true; } if (unlikely(!found)) { found_node = avltree_lookup(&prototype.gid_node, &gid_tree); if (unlikely(!found_node)) return false; atomic_store_voidptr(cache_slot, found_node); found_group = avltree_container_of( found_node, struct cache_group, gid_node); } if (likely(name)) *name = &found_group->gname; else LogDebug(COMPONENT_IDMAPPER, "Caller is being weird."); return group_expired(found_group) ? false : true; } /** * @brief Wipe out the idmapper cache */ void idmapper_clear_cache(void) { struct avltree_node *node; int removed_uname_entries = 0; int removed_uid_entries = 0; int removed_group_entries = 0; PTHREAD_RWLOCK_wrlock(&idmapper_user_lock); PTHREAD_RWLOCK_wrlock(&idmapper_group_lock); memset(uid_cache, 0, id_cache_size * sizeof(struct avltree_node *)); memset(gid_cache, 0, id_cache_size * sizeof(struct avltree_node *)); for (node = avltree_first(&uname_tree); node != NULL; node = avltree_first(&uname_tree)) { struct cache_user *user; user = avltree_container_of(node, struct cache_user, uname_node); removed_uid_entries += (user->in_uidtree ? 1 : 0); removed_uname_entries++; remove_cache_user(user); } assert(avltree_first(&uid_tree) == NULL); for (node = avltree_first(&gname_tree); node != NULL; node = avltree_first(&gname_tree)) { struct cache_group *group; group = avltree_container_of(node, struct cache_group, gname_node); removed_group_entries++; remove_cache_group(group); } assert(avltree_first(&gid_tree) == NULL); PTHREAD_RWLOCK_unlock(&idmapper_group_lock); PTHREAD_RWLOCK_unlock(&idmapper_user_lock); LogInfo(COMPONENT_IDMAPPER, "Total entries removed per cache: " "uname cache: %d uid cache: %d group cache: %d", removed_uname_entries, removed_uid_entries, removed_group_entries); } /** * @brief Destroy the IDMapper cache * * This function clears the cache, and destroys its locks. */ void idmapper_destroy_cache(void) { idmapper_clear_cache(); PTHREAD_RWLOCK_destroy(&idmapper_user_lock); PTHREAD_RWLOCK_destroy(&idmapper_group_lock); } #ifdef USE_DBUS /** *@brief Dbus method for showing idmapper cache * *@param[in] args *@param[out] reply */ static bool show_idmapper(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct timespec timestamp; struct avltree_node *node; uint32_t val; DBusMessageIter iter, sub_iter, id_iter; char *namebuff = gsh_malloc(1024); dbus_bool_t gid_set; dbus_message_iter_init_append(reply, &iter); now(×tamp); gsh_dbus_append_timestamp(&iter, ×tamp); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(subu)", &sub_iter); PTHREAD_RWLOCK_rdlock(&idmapper_user_lock); /* Traverse idmapper cache */ for (node = avltree_first(&uname_tree); node != NULL; node = avltree_next(node)) { struct cache_user *user; user = avltree_container_of(node, struct cache_user, uname_node); dbus_message_iter_open_container(&sub_iter, DBUS_TYPE_STRUCT, NULL, &id_iter); memcpy(namebuff, user->uname.addr, user->uname.len); if (user->uname.len > 255) /*Truncate the name */ *(namebuff + 255) = '\0'; else *(namebuff + user->uname.len) = '\0'; dbus_message_iter_append_basic(&id_iter, DBUS_TYPE_STRING, &namebuff); val = user->uid; dbus_message_iter_append_basic(&id_iter, DBUS_TYPE_UINT32, &val); if (user->gid_set) { val = user->gid; gid_set = true; } else { val = 0; gid_set = false; } dbus_message_iter_append_basic(&id_iter, DBUS_TYPE_BOOLEAN, &gid_set); dbus_message_iter_append_basic(&id_iter, DBUS_TYPE_UINT32, &val); dbus_message_iter_close_container(&sub_iter, &id_iter); } PTHREAD_RWLOCK_unlock(&idmapper_user_lock); free(namebuff); dbus_message_iter_close_container(&iter, &sub_iter); return true; } struct gsh_dbus_method cachemgr_show_idmapper = { .name = "showidmapper", .method = show_idmapper, .args = { TIMESTAMP_REPLY, { .name = "ids", .type = "a(subu)", .direction = "out" }, END_ARG_LIST } }; #endif /** @} */ nfs-ganesha-6.5/src/idmapper/idmapper_monitoring.c000066400000000000000000000226351473756622300224070ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2024 Google LLC * Contributor : Dipit Grover dipit@google.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @addtogroup idmapper * @{ */ /** * @file idmapper_monitoring.c * @brief ID mapping monitoring functions */ #include #include "idmapper_monitoring.h" #include "monitoring.h" typedef enum idmapping_status { IDMAPPING_STATUS_SUCCESS = 0, IDMAPPING_STATUS_FAILURE, IDMAPPING_STATUS_COUNT, } idmapping_status_t; /* ID Mapping metrics */ static histogram_metric_handle_t idmapping_user_groups_total; static histogram_metric_handle_t idmapping_external_request_latency [IDMAPPING_OP_COUNT][IDMAPPING_UTILITY_COUNT][IDMAPPING_STATUS_COUNT]; static counter_metric_handle_t idmapping_cache_uses_total[IDMAPPING_CACHE_COUNT] [IDMAPPING_STATUS_COUNT]; static counter_metric_handle_t idmapping_failures_total[IDMAPPING_OP_COUNT][IDMAPPING_UTILITY_COUNT]; /* Distribution of cached-duration of the cache-evicted entries */ static histogram_metric_handle_t evicted_entries_cached_duration[IDMAPPING_CACHE_ENTITY_COUNT]; /* 8 buckets in increasing powers of 2 */ static const int64_t groups_buckets[] = { 0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 }; /* Get status string */ static const char *get_status_name(idmapping_status_t status) { switch (status) { case IDMAPPING_STATUS_SUCCESS: return "success"; case IDMAPPING_STATUS_FAILURE: return "failure"; default: LogFatal(COMPONENT_IDMAPPER, "Unsupported idmapping operation status"); } } /* Get idmapping_op_t string */ static const char *get_op_name(idmapping_op_t op) { switch (op) { case IDMAPPING_UID_TO_UIDGID: return "uid_to_uidgid"; case IDMAPPING_UID_TO_GROUPLIST: return "uid_to_grouplist"; case IDMAPPING_USERNAME_TO_UIDGID: return "username_to_uidgid"; case IDMAPPING_USERNAME_TO_GROUPLIST: return "username_to_grouplist"; case IDMAPPING_PRINCIPAL_TO_UIDGID: return "principal_to_uidgid"; case IDMAPPING_PRINCIPAL_TO_GROUPLIST: return "principal_to_grouplist"; case IDMAPPING_MSPAC_TO_SID: return "mspac_to_sid"; case IDMAPPING_SID_TO_UIDGID: return "sid_to_uidgid"; case IDMAPPING_GID_TO_GROUP: return "gid_to_group"; case IDMAPPING_GROUPNAME_TO_GROUP: return "groupname_to_group"; default: LogFatal(COMPONENT_IDMAPPER, "Unsupported idmapping op"); } } /* Get idmapping_utility_t string */ static const char *get_utility_name(idmapping_utility_t utility) { switch (utility) { case IDMAPPING_PWUTILS: return "pwutils"; case IDMAPPING_NFSIDMAP: return "nfsidmap"; case IDMAPPING_WINBIND: return "winbind"; default: LogFatal(COMPONENT_IDMAPPER, "Unsupported idmapping utility"); } } /* Get idmapping_cache_t string */ static const char *get_cache_name(idmapping_cache_t idmapping_cache) { switch (idmapping_cache) { case IDMAPPING_UID_TO_USER_CACHE: return "uid_to_user"; case IDMAPPING_USERNAME_TO_USER_CACHE: return "username_to_user"; case IDMAPPING_GSSPRINC_TO_USER_CACHE: return "gssprinc_to_user"; case IDMAPPING_GID_TO_GROUP_CACHE: return "gid_to_group"; case IDMAPPING_GROUPNAME_TO_GROUP_CACHE: return "groupname_to_group"; case IDMAPPING_UID_TO_GROUPLIST_CACHE: return "uid_to_grouplist"; case IDMAPPING_USERNAME_TO_GROUPLIST_CACHE: return "username_to_grouplist"; default: LogFatal(COMPONENT_IDMAPPER, "Unsupported idmapping cache"); } } /* Get idmapping_cache_entity_t string */ static const char *get_cache_entity_name(idmapping_cache_entity_t cache_entity) { switch (cache_entity) { case IDMAPPING_CACHE_ENTITY_USER: return "USER"; case IDMAPPING_CACHE_ENTITY_GROUP: return "GROUP"; case IDMAPPING_CACHE_ENTITY_USER_GROUPS: return "USER_GROUPS"; case IDMAPPING_CACHE_ENTITY_NEGATIVE_USER: return "NEGATIVE_USER"; case IDMAPPING_CACHE_ENTITY_NEGATIVE_GROUP: return "NEGATIVE_GROUP"; default: LogFatal(COMPONENT_IDMAPPER, "Unsupported idmapping cache entity: %d", cache_entity); } } static void register_user_groups_metric(void) { const histogram_buckets_t histogram_buckets = (histogram_buckets_t){ .buckets = groups_buckets, .count = ARRAY_SIZE(groups_buckets) }; const metric_label_t empty_labels[] = {}; idmapping_user_groups_total = monitoring__register_histogram( "idmapping__user_groups_total", METRIC_METADATA("Total groups per user", METRIC_UNIT_NONE), empty_labels, ARRAY_SIZE(empty_labels), histogram_buckets); } static void register_external_request_latency_metric(idmapping_op_t op, idmapping_utility_t utility, idmapping_status_t status) { const metric_label_t labels[] = { METRIC_LABEL("op", get_op_name(op)), METRIC_LABEL("utility", get_utility_name(utility)), METRIC_LABEL("status", get_status_name(status)) }; idmapping_external_request_latency[op][utility][status] = monitoring__register_histogram( "idmapping__external_request_latency", METRIC_METADATA("Idmapping external request latency", METRIC_UNIT_MILLISECOND), labels, ARRAY_SIZE(labels), monitoring__buckets_exp2_compact()); } static void register_external_request_latency_metrics(void) { for (int i = 0; i < IDMAPPING_OP_COUNT; i++) { for (int j = 0; j < IDMAPPING_UTILITY_COUNT; j++) { for (int k = 0; k < IDMAPPING_STATUS_COUNT; k++) { register_external_request_latency_metric(i, j, k); } } } } static void register_cache_uses_total_metrics(void) { for (int i = 0; i < IDMAPPING_CACHE_COUNT; i++) { for (int j = 0; j < IDMAPPING_STATUS_COUNT; j++) { const metric_label_t labels[] = { METRIC_LABEL("cache", get_cache_name(i)), METRIC_LABEL("hit", get_status_name(j)) }; idmapping_cache_uses_total[i][j] = monitoring__register_counter( "idmapping__cache_uses_total", METRIC_METADATA( "Total idmapping-cache uses", METRIC_UNIT_NONE), labels, ARRAY_SIZE(labels)); } } } static void register_failure_total_metrics(void) { for (int i = 0; i < IDMAPPING_OP_COUNT; i++) { for (int j = 0; j < IDMAPPING_UTILITY_COUNT; j++) { const metric_label_t labels[] = { METRIC_LABEL("op", get_op_name(i)), METRIC_LABEL("utility", get_utility_name(j)) }; idmapping_failures_total[i][j] = monitoring__register_counter( "idmapping__failures_total", METRIC_METADATA( "Total idmapping failures", METRIC_UNIT_NONE), labels, ARRAY_SIZE(labels)); } } } static void register_evicted_entries_cache_duration_metrics(void) { for (int i = 0; i < IDMAPPING_CACHE_ENTITY_COUNT; i++) { const metric_label_t labels[] = { METRIC_LABEL( "cache_entity", get_cache_entity_name(i)) }; evicted_entries_cached_duration[i] = monitoring__register_histogram( "idmapping__evicted_entries_cached_duration", METRIC_METADATA( "Distribution of the time duration " "that evicted entries were stored in " "the cache", METRIC_UNIT_MINUTE), labels, ARRAY_SIZE(labels), monitoring__buckets_exp2_compact()); } } void idmapper_monitoring__init(void) { register_user_groups_metric(); register_external_request_latency_metrics(); register_cache_uses_total_metrics(); register_failure_total_metrics(); register_evicted_entries_cache_duration_metrics(); } void idmapper_monitoring__cache_usage(idmapping_cache_t idmapping_cache, bool is_cache_hit) { const idmapping_status_t idmapping_status = is_cache_hit ? IDMAPPING_STATUS_SUCCESS : IDMAPPING_STATUS_FAILURE; monitoring__counter_inc( idmapping_cache_uses_total[idmapping_cache][idmapping_status], 1); } void idmapper_monitoring__external_request( idmapping_op_t idmapping_op, idmapping_utility_t idmapping_utility, bool is_success, const struct timespec *start, const struct timespec *end) { const nsecs_elapsed_t resp_time_ns = timespec_diff(start, end); const idmapping_status_t idmapping_status = is_success ? IDMAPPING_STATUS_SUCCESS : IDMAPPING_STATUS_FAILURE; const int64_t resp_time_ms = resp_time_ns / NS_PER_MSEC; monitoring__histogram_observe( idmapping_external_request_latency[idmapping_op] [idmapping_utility] [idmapping_status], resp_time_ms); } void idmapper_monitoring__evicted_cache_entity( idmapping_cache_entity_t idmapping_cache_entity, time_t cached_duration_in_sec) { const time_t cached_duration_in_min = cached_duration_in_sec / 60; monitoring__histogram_observe( evicted_entries_cached_duration[idmapping_cache_entity], cached_duration_in_min); } void idmapper_monitoring__failure(idmapping_op_t idmapping_op, idmapping_utility_t idmapping_utility) { monitoring__counter_inc( idmapping_failures_total[idmapping_op][idmapping_utility], 1); } void idmapper_monitoring__user_groups(int num_groups) { monitoring__histogram_observe(idmapping_user_groups_total, num_groups); } /** @} */ nfs-ganesha-6.5/src/idmapper/idmapper_negative_cache.c000066400000000000000000000316771473756622300231550ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2023 Google LLC * Contributor : Dipit Grover dipit@google.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @addtogroup idmapper * @{ */ /** * @file idmapper_negative_cache.c * @brief Negative cache for entities that failed idmapping */ #include #include #include "common_utils.h" #include "avltree.h" #include "idmapper.h" #include "nfs_core.h" #include #include "idmapper_monitoring.h" /* Struct representing the negative cache entity */ typedef enum negative_cache_entity_type { USER, GROUP } negative_cache_entity_type_t; /* Struct representing a user or group entry in the negative cache */ typedef struct negative_cache_entity { struct gsh_buffdesc name; /*< Entity name */ struct avltree_node name_node; /*< Entity tree node */ time_t epoch; /*< Entity creation timestamp */ TAILQ_ENTRY(negative_cache_entity) queue_entry; /*< Entity queue node */ char name_buffer[]; /*< Entity name buffer */ } negative_cache_entity_t; /* Lock that protects the idmapper negative user cache */ pthread_rwlock_t idmapper_negative_cache_user_lock; /* Lock that protects the idmapper negative group cache */ pthread_rwlock_t idmapper_negative_cache_group_lock; /** @brief The fifo queue for storing negative users * * A fifo queue mimics the order of expiration time of the cache entries, * since the expiration time is a linear function of the insertion time. * * Expiration_time = Insertion_time + Cache_expiration_time (constant) * * The head of the queue contains the entry with least time-validity. * The tail of the queue contains the entry with most time-validity. * The eviction happens from the head, and insertion happens into the tail. */ static TAILQ_HEAD(idmapping_negative_cache_queue, negative_cache_entity) negative_user_fifo_queue; /* The fifo queue (similar to above) for storing negative groups */ struct idmapping_negative_cache_queue negative_group_fifo_queue; /* AVL-Tree cache for storing negative cache user name node */ static struct avltree uname_tree; /* AVL-Tree cache for storing negative cache group name node */ static struct avltree gname_tree; /** * @brief Checks if a negative user/group entry is expired * * @return true if expired, false otherwise */ static bool is_negative_cache_entity_expired(negative_cache_entity_t *entity) { return (time(NULL) - entity->epoch) > nfs_param.directory_services_param.negative_cache_time_validity; } /** * @brief Remove negative entity entry from all its cache data structures * * @note The caller must hold the corresponding entity lock for write. */ static void remove_negative_cache_entity(negative_cache_entity_t *entity, negative_cache_entity_type_t entity_type) { struct avltree *cache_tree; struct idmapping_negative_cache_queue *cache_queue; switch (entity_type) { case USER: cache_tree = &uname_tree; cache_queue = &negative_user_fifo_queue; break; case GROUP: cache_tree = &gname_tree; cache_queue = &negative_group_fifo_queue; break; default: LogFatal(COMPONENT_IDMAPPER, "Unknown negative cache entity type: %d", entity_type); } avltree_remove(&entity->name_node, cache_tree); TAILQ_REMOVE(cache_queue, entity, queue_entry); gsh_free(entity); } /** * @brief Reaps the negative cache entities * * Since the entity fifo queue stores entries in increasing order of time * validity, the reaper reaps from the queue head in the same order. It stops * when it first encounters a non-expired entry. */ static void reap_negative_cache_entities(negative_cache_entity_type_t entity_type) { struct negative_cache_entity *entity; struct idmapping_negative_cache_queue *cache_queue; pthread_rwlock_t *entity_lock; switch (entity_type) { case USER: cache_queue = &negative_user_fifo_queue; entity_lock = &idmapper_negative_cache_user_lock; break; case GROUP: cache_queue = &negative_group_fifo_queue; entity_lock = &idmapper_negative_cache_group_lock; break; default: LogFatal(COMPONENT_IDMAPPER, "Unknown negative cache entity type: %d", entity_type); } PTHREAD_RWLOCK_wrlock(entity_lock); for (entity = TAILQ_FIRST(cache_queue); entity != NULL;) { if (!is_negative_cache_entity_expired(entity)) break; remove_negative_cache_entity(entity, entity_type); entity = TAILQ_FIRST(cache_queue); } PTHREAD_RWLOCK_unlock(entity_lock); } /** * @brief Reaps the negative cache user and group entries */ void idmapper_negative_cache_reap(void) { LogFullDebug(COMPONENT_IDMAPPER, "Idmapper negative-cache reaper run started"); reap_negative_cache_entities(USER); reap_negative_cache_entities(GROUP); LogFullDebug(COMPONENT_IDMAPPER, "Idmapper negative-cache reaper run ended"); } /* * @brief Comparison function for negative-cache-entity nodes * * @return -1 if @arg node1 is less than @arg node2 * @return 0 if @arg node1 and @arg node2 are equal * @return 1 if @arg node1 is greater than @arg node2 */ static int node_comparator(const struct avltree_node *node1, const struct avltree_node *node2) { negative_cache_entity_t *entity1 = avltree_container_of(node1, negative_cache_entity_t, name_node); negative_cache_entity_t *entity2 = avltree_container_of(node2, negative_cache_entity_t, name_node); return gsh_buffdesc_comparator(&entity1->name, &entity2->name); } /** * @brief Initialise the idmapper negative cache */ void idmapper_negative_cache_init(void) { PTHREAD_RWLOCK_init(&idmapper_negative_cache_user_lock, NULL); PTHREAD_RWLOCK_init(&idmapper_negative_cache_group_lock, NULL); avltree_init(&uname_tree, node_comparator, 0); avltree_init(&gname_tree, node_comparator, 0); TAILQ_INIT(&negative_user_fifo_queue); TAILQ_INIT(&negative_group_fifo_queue); } /** * @brief Add an entity to the negative cache by name * * @note The caller must hold the corresponding entity lock for write * * @param[in] name The entity's name for insertion * @param[in] entity_type The entity's type for insertion */ static void idmapper_negative_cache_add_entity_by_name( const struct gsh_buffdesc *name, negative_cache_entity_type_t entity_type) { struct avltree *cache_tree; struct avltree_node *old_node; struct idmapping_negative_cache_queue *cache_queue; negative_cache_entity_t *old_entity, *new_entity, *cache_queue_head; uint32_t max_cache_entities; char *entity_type_string; idmapping_cache_entity_t cache_entity; new_entity = gsh_malloc(sizeof(negative_cache_entity_t) + name->len); new_entity->name.addr = new_entity->name_buffer; new_entity->name.len = name->len; memcpy(new_entity->name.addr, name->addr, name->len); new_entity->epoch = time(NULL); switch (entity_type) { case USER: cache_tree = &uname_tree; cache_queue = &negative_user_fifo_queue; max_cache_entities = nfs_param.directory_services_param .negative_cache_users_max_count; entity_type_string = (char *)"user"; cache_entity = IDMAPPING_CACHE_ENTITY_NEGATIVE_USER; break; case GROUP: cache_tree = &gname_tree; cache_queue = &negative_group_fifo_queue; max_cache_entities = nfs_param.directory_services_param .negative_cache_groups_max_count; entity_type_string = (char *)"group"; cache_entity = IDMAPPING_CACHE_ENTITY_NEGATIVE_GROUP; break; default: LogFatal(COMPONENT_IDMAPPER, "Unknown negative cache entity type: %d", entity_type); } old_node = avltree_insert(&new_entity->name_node, cache_tree); /* Unlikely that the node already exists. If it does, we update it */ if (unlikely(old_node)) { old_entity = avltree_container_of( old_node, negative_cache_entity_t, name_node); old_entity->epoch = time(NULL); /* Move entity to the tail of the queue */ TAILQ_REMOVE(cache_queue, old_entity, queue_entry); TAILQ_INSERT_TAIL(cache_queue, old_entity, queue_entry); gsh_free(new_entity); return; } TAILQ_INSERT_TAIL(cache_queue, new_entity, queue_entry); /* If we breach max-cache capacity, remove entity queue's head node */ if (avltree_size(cache_tree) > max_cache_entities) { LogInfo(COMPONENT_IDMAPPER, "Cache size limit violated, removing %s with least time validity", entity_type_string); cache_queue_head = TAILQ_FIRST(cache_queue); const time_t cached_duration = time(NULL) - cache_queue_head->epoch; remove_negative_cache_entity(cache_queue_head, entity_type); idmapper_monitoring__evicted_cache_entity(cache_entity, cached_duration); } } /** * @brief Add a user entry to the negative cache by name * * @note The caller must hold idmapper_negative_cache_user_lock for write * * @param[in] name The user's name for insertion */ void idmapper_negative_cache_add_user_by_name(const struct gsh_buffdesc *name) { idmapper_negative_cache_add_entity_by_name(name, USER); } /** * @brief Add a group entry to the negative cache by name * * @note The caller must hold idmapper_negative_cache_group_lock for write * * @param[in] name The group name */ void idmapper_negative_cache_add_group_by_name(const struct gsh_buffdesc *name) { idmapper_negative_cache_add_entity_by_name(name, GROUP); } /** * @brief Look up an entity by name in negative cache * * @note The caller must hold the corresponding entity lock for read * * @param[in] name The entity name to look up. * @param[in] entity_type The entity type to look up. * * @return true on success, false otherwise. */ static bool idmapper_negative_cache_lookup_entity_by_name( const struct gsh_buffdesc *name, negative_cache_entity_type_t entity_type) { struct avltree *cache_tree; struct avltree_node *cache_node; negative_cache_entity_t *cache_entity; negative_cache_entity_t prototype = { .name = *name }; switch (entity_type) { case USER: cache_tree = &uname_tree; break; case GROUP: cache_tree = &gname_tree; break; default: LogFatal(COMPONENT_IDMAPPER, "Unknown negative cache entity type: %d", entity_type); } cache_node = avltree_lookup(&prototype.name_node, cache_tree); if (!cache_node) return false; cache_entity = avltree_container_of(cache_node, negative_cache_entity_t, name_node); return is_negative_cache_entity_expired(cache_entity) ? false : true; } /** * @brief Look up a user by name in negative cache * * @note The caller must hold idmapper_negative_cache_user_lock for read * * @param[in] name The user name to look up. * * @return true on success, false otherwise. */ bool idmapper_negative_cache_lookup_user_by_name(const struct gsh_buffdesc *name) { return idmapper_negative_cache_lookup_entity_by_name(name, USER); } /** * @brief Look up a group by name in negative cache * * @note The caller must hold idmapper_negative_cache_group_lock for read * * @param[in] name The group name to look up. * * @return true on success, false otherwise. */ bool idmapper_negative_cache_lookup_group_by_name( const struct gsh_buffdesc *name) { return idmapper_negative_cache_lookup_entity_by_name(name, GROUP); } /** * @brief Remove all negative cache entities of input @arg entity_type */ static void remove_all_negative_cache_entities(negative_cache_entity_type_t entity_type) { struct avltree *cache_tree; pthread_rwlock_t *entity_lock; struct avltree_node *node; negative_cache_entity_t *entity; switch (entity_type) { case USER: cache_tree = &uname_tree; entity_lock = &idmapper_negative_cache_user_lock; break; case GROUP: cache_tree = &gname_tree; entity_lock = &idmapper_negative_cache_group_lock; break; default: LogFatal(COMPONENT_IDMAPPER, "Unknown negative cache entity type: %d", entity_type); } PTHREAD_RWLOCK_wrlock(entity_lock); for (node = avltree_first(cache_tree); node != NULL; node = avltree_first(cache_tree)) { entity = avltree_container_of(node, negative_cache_entity_t, name_node); remove_negative_cache_entity(entity, entity_type); } assert(avltree_first(cache_tree) == NULL); PTHREAD_RWLOCK_unlock(entity_lock); } /** * @brief Clear the idmapper negative cache */ void idmapper_negative_cache_clear(void) { remove_all_negative_cache_entities(USER); remove_all_negative_cache_entities(GROUP); } /** * @brief Clean up the idmapper negative cache */ void idmapper_negative_cache_destroy(void) { idmapper_negative_cache_clear(); PTHREAD_RWLOCK_destroy(&idmapper_negative_cache_user_lock); PTHREAD_RWLOCK_destroy(&idmapper_negative_cache_group_lock); } /** @} */ nfs-ganesha-6.5/src/include/000077500000000000000000000000001473756622300160075ustar00rootroot00000000000000nfs-ganesha-6.5/src/include/.gitignore000066400000000000000000000000461473756622300177770ustar00rootroot00000000000000config.h* !config-h.in.cmake stamp-h1 nfs-ganesha-6.5/src/include/9p.h000066400000000000000000000573001473756622300165150ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2011) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /* * Copied from 2.6.38-rc2 kernel, taken from diod sources * ( http://code.google.com/p/diod/ ) then adapted to ganesha */ #ifndef _9P_H #define _9P_H #include #include #include #include #include #include #include "9p_types.h" #include "fsal_types.h" #include "sal_data.h" #ifdef _USE_9P_RDMA #include #endif #define NB_PREALLOC_HASH_9P 100 #define NB_PREALLOC_FID_9P 100 #define PRIME_9P 17 #define _9P_LOCK_CLIENT_LEN 64 #define _9P_FID_PER_CONN 1024 /* _9P_MSG_SIZE: maximum message size for 9P/TCP */ #define _9P_MSG_SIZE 70000 #define _9P_HDR_SIZE 4 #define _9P_TYPE_SIZE 1 #define _9P_TAG_SIZE 2 #define _9P_STD_HDR_SIZE (_9P_HDR_SIZE + _9P_TYPE_SIZE + _9P_TAG_SIZE) /* _9P_BLK_SIZE: (fake) filesystem block size that we return in getattr() */ #define _9P_BLK_SIZE 4096 #define _9P_IOUNIT 0 /** * enum _9p_msg_t - 9P message types * @_9P_TLERROR: not used * @_9P_RLERROR: response for any failed request for 9P2000.L * @_9P_TSTATFS: file system status request * @_9P_RSTATFS: file system status response * @_9P_TSYMLINK: make symlink request * @_9P_RSYMLINK: make symlink response * @_9P_TMKNOD: create a special file object request * @_9P_RMKNOD: create a special file object response * @_9P_TLCREATE: prepare a handle for I/O on an new file for 9P2000.L * @_9P_RLCREATE: response with file access information for 9P2000.L * @_9P_TRENAME: rename request * @_9P_RRENAME: rename response * @_9P_TMKDIR: create a directory request * @_9P_RMKDIR: create a directory response * @_9P_TVERSION: version handshake request * @_9P_RVERSION: version handshake response * @_9P_TAUTH: request to establish authentication channel * @_9P_RAUTH: response with authentication information * @_9P_TATTACH: establish user access to file service * @_9P_RATTACH: response with top level handle to file hierarchy * @_9P_TERROR: not used * @_9P_RERROR: response for any failed request * @_9P_TFLUSH: request to abort a previous request * @_9P_RFLUSH: response when previous request has been cancelled * @_9P_TWALK: descend a directory hierarchy * @_9P_RWALK: response with new handle for position within hierarchy * @_9P_TOPEN: prepare a handle for I/O on an existing file * @_9P_ROPEN: response with file access information * @_9P_TCREATE: prepare a handle for I/O on a new file * @_9P_RCREATE: response with file access information * @_9P_TREAD: request to transfer data from a file or directory * @_9P_RREAD: response with data requested * @_9P_TWRITE: request to transfer data to a file * @_9P_RWRITE: response with out much data was transferred to file * @_9P_TCLUNK: forget about a handle to an entity within the file system * @_9P_RCLUNK: response when server has forgotten about the handle * @_9P_TREMOVE: request to remove an entity from the hierarchy * @_9P_RREMOVE: response when server has removed the entity * @_9P_TSTAT: request file entity attributes * @_9P_RSTAT: response with file entity attributes * @_9P_TWSTAT: request to update file entity attributes * @_9P_RWSTAT: response when file entity attributes are updated * * There are 14 basic operations in 9P2000, paired as * requests and responses. The one special case is ERROR * as there is no @_9P_TERROR request for clients to transmit to * the server, but the server may respond to any other request * with an @_9P_RERROR. * * See Also: http://plan9.bell-labs.com/sys/man/5/INDEX.html */ enum _9p_msg_t { _9P_TLERROR = 6, _9P_RLERROR, _9P_TSTATFS = 8, _9P_RSTATFS, _9P_TLOPEN = 12, _9P_RLOPEN, _9P_TLCREATE = 14, _9P_RLCREATE, _9P_TSYMLINK = 16, _9P_RSYMLINK, _9P_TMKNOD = 18, _9P_RMKNOD, _9P_TRENAME = 20, _9P_RRENAME, _9P_TREADLINK = 22, _9P_RREADLINK, _9P_TGETATTR = 24, _9P_RGETATTR, _9P_TSETATTR = 26, _9P_RSETATTR, _9P_TXATTRWALK = 30, _9P_RXATTRWALK, _9P_TXATTRCREATE = 32, _9P_RXATTRCREATE, _9P_TREADDIR = 40, _9P_RREADDIR, _9P_TFSYNC = 50, _9P_RFSYNC, _9P_TLOCK = 52, _9P_RLOCK, _9P_TGETLOCK = 54, _9P_RGETLOCK, _9P_TLINK = 70, _9P_RLINK, _9P_TMKDIR = 72, _9P_RMKDIR, _9P_TRENAMEAT = 74, _9P_RRENAMEAT, _9P_TUNLINKAT = 76, _9P_RUNLINKAT, _9P_TVERSION = 100, _9P_RVERSION, _9P_TAUTH = 102, _9P_RAUTH, _9P_TATTACH = 104, _9P_RATTACH, _9P_TERROR = 106, _9P_RERROR, _9P_TFLUSH = 108, _9P_RFLUSH, _9P_TWALK = 110, _9P_RWALK, _9P_TOPEN = 112, _9P_ROPEN, _9P_TCREATE = 114, _9P_RCREATE, _9P_TREAD = 116, _9P_RREAD, _9P_TWRITE = 118, _9P_RWRITE, _9P_TCLUNK = 120, _9P_RCLUNK, _9P_TREMOVE = 122, _9P_RREMOVE, _9P_TSTAT = 124, _9P_RSTAT, _9P_TWSTAT = 126, _9P_RWSTAT, }; /* Arbitrary max xattr size, 64k is the limit for VFS given in man xattr(7) */ #define _9P_XATTR_MAX_SIZE 65535 /** * 9p internal flags for xattrs: set as guard for read/write and actual * setxattr "flush" call */ enum _9p_xattr_write { _9P_XATTR_READ_ONLY, _9P_XATTR_CAN_WRITE, _9P_XATTR_DID_WRITE }; /** * enum _9p_qid_t - QID types * @_9P_QTDIR: directory * @_9P_QTAPPEND: append-only * @_9P_QTEXCL: exclusive use (only one open handle allowed) * @_9P_QTMOUNT: mount points * @_9P_QTAUTH: authentication file * @_9P_QTTMP: non-backed-up files * @_9P_QTSYMLINK: symbolic links (9P2000.u) * @_9P_QTLINK: hard-link (9P2000.u) * @_9P_QTFILE: normal files * * QID types are a subset of permissions - they are primarily * used to differentiate semantics for a file system entity via * a jump-table. Their value is also the most significant 16 bits * of the permission_t * * See Also: http://plan9.bell-labs.com/magic/man2html/2/stat */ enum _9p_qid__ { _9P_QTDIR = 0x80, _9P_QTAPPEND = 0x40, _9P_QTEXCL = 0x20, _9P_QTMOUNT = 0x10, _9P_QTAUTH = 0x08, _9P_QTTMP = 0x04, _9P_QTSYMLINK = 0x02, _9P_QTLINK = 0x01, _9P_QTFILE = 0x00, }; /* 9P Magic Numbers */ #define _9P_NOTAG ((u16)(~0)) #define _9P_NOFID ((u32)(~0)) #define _9P_NONUNAME ((u32)(~0)) #define _9P_MAXWELEM 16 /* Various header lengths to check message sizes: */ /* size[4] Rread tag[2] count[4] data[count] */ #define _9P_ROOM_RREAD (_9P_STD_HDR_SIZE + 4) /* size[4] Twrite tag[2] fid[4] offset[8] count[4] data[count] */ #define _9P_ROOM_TWRITE (_9P_STD_HDR_SIZE + 4 + 8 + 4) /* size[4] Rreaddir tag[2] count[4] data[count] */ #define _9P_ROOM_RREADDIR (_9P_STD_HDR_SIZE + 4) /** * @brief Length prefixed string type * * The protocol uses length prefixed strings for all * string data, so we replicate that for our internal * string members. */ struct _9p_str { u16 len; /*< Length of the string */ char *str; /*< The string */ }; /** * @brief file system entity information * * qids are /identifiers used by 9P servers to track file system * entities. The type is used to differentiate semantics for operations * on the entity (ie. read means something different on a directory than * on a file). The path provides a server unique index for an entity * (roughly analogous to an inode number), while the version is updated * every time a file is modified and can be used to maintain cache * coherency between clients and serves. * Servers will often differentiate purely synthetic entities by setting * their version to 0, signaling that they should never be cached and * should be accessed synchronously. * * See Also://plan9.bell-labs.com/magic/man2html/2/stat */ struct _9p_qid { u8 type; /*< Type */ u32 version; /*< Monotonically incrementing version number */ u64 path; /*< Per-server-unique ID * for a file system element */ }; /** * @brief Internal 9P structure containing client credentials. * * This structure wraps struct user_cred, adding a refcounter to know when it * should be released (it is shared between several op_ctx and fids). */ struct _9p_user_cred { struct user_cred creds; /*< Credentials. */ int64_t refcount; /*< Counter of references (to the container or the * creds field). */ }; /** * * @brief Internal 9P structure for xattr operations, linked in fid * * This structure is allocated as needed (xattrwalk/create) and freed * on clunk */ struct _9p_xattr_desc { char xattr_name[MAXNAMLEN + 1]; u64 xattr_size; u64 xattr_offset; enum _9p_xattr_write xattr_write; char xattr_content[]; }; struct _9p_fid { u32 fid; /** Ganesha export of the file (refcounted). */ struct gsh_export *fid_export; struct _9p_user_cred *ucred; /*< Client credentials (refcounted). */ struct group_data *gdata; struct fsal_obj_handle *pentry; struct _9p_qid qid; struct state_t *state; struct fsal_obj_handle *ppentry; char name[MAXNAMLEN + 1]; u32 opens; struct _9p_xattr_desc *xattr; }; enum _9p_trans_type { _9P_TCP, _9P_RDMA }; struct flush_condition; /* flush hook: * * We use this to insert the request in a list * so it can be found later during a TFLUSH. * The goal is to wait until a request has been fully * processed and the reply sent before we send a RFLUSH. * * When a TFLUSH arrives, its thread will fill `condition' * so we can wake it up later, after we have sent the reply * to the original request. */ struct _9p_flush_hook { int tag; struct flush_condition *condition; unsigned long sequence; struct glist_head list; }; struct _9p_flush_bucket { pthread_mutex_t flb_lock; struct glist_head list; }; #define FLUSH_BUCKETS 32 struct _9p_conn { union trans_data { long sockfd; #ifdef _USE_9P_RDMA msk_trans_t *rdma_trans; #endif } trans_data; enum _9p_trans_type trans_type; uint32_t refcount; struct gsh_client *client; struct timeval birth; /* This is useful if same sockfd is reused on socket's close/open */ struct _9p_fid *fids[_9P_FID_PER_CONN]; struct _9p_flush_bucket flush_buckets[FLUSH_BUCKETS]; unsigned long sequence; pthread_mutex_t sock_lock; sockaddr_t addrpeer; unsigned int msize; }; #ifdef _USE_9P_RDMA struct _9p_outqueue { msk_data_t *data; pthread_mutex_t oq_lock; pthread_cond_t oq_cond; }; struct _9p_rdma_priv_pernic { struct ibv_mr *outmr; struct ibv_mr *inmr; uint8_t *rdmabuf; msk_data_t *rdata; }; struct _9p_rdma_priv { struct _9p_conn *pconn; struct _9p_outqueue *outqueue; struct _9p_rdma_priv_pernic *pernic; }; #define _9p_rdma_priv_of(x) ((struct _9p_rdma_priv *)x->private_data) #endif struct _9p_request_data { struct glist_head req_q; /* chaining of pending requests */ char *_9pmsg; struct _9p_conn *pconn; #ifdef _USE_9P_RDMA msk_data_t *data; #endif struct _9p_flush_hook flush_hook; pthread_mutex_t *_9prq_mutex; pthread_cond_t *_9prq_cond; }; typedef int (*_9p_function_t)(struct _9p_request_data *req9p, u32 *plenout, char *preply); struct _9p_function_desc { _9p_function_t service_function; char *funcname; }; extern const struct _9p_function_desc _9pfuncdesc[]; #define _9p_getptr(__cursor, __pvar, __type) \ do { \ __pvar = (__type *)__cursor; \ __cursor += sizeof(__type); \ } while (0) #define _9p_getstr(__cursor, __len, __str) \ do { \ __len = (u16 *)__cursor; \ __cursor += sizeof(u16); \ __str = __cursor; \ __cursor += *__len; \ } while (0) #define _9p_setptr(__cursor, __pvar, __type) \ do { \ *((__type *)__cursor) = *__pvar; \ __cursor += sizeof(__type); \ } while (0) #define _9p_setvalue(__cursor, __var, __type) \ do { \ *((__type *)__cursor) = __var; \ __cursor += sizeof(__type); \ } while (0) #define _9p_savepos(__cursor, __savedpos, __type) \ do { \ __savedpos = __cursor; \ __cursor += sizeof(__type); \ } while (0) /* Insert a qid */ #define _9p_setqid(__cursor, __qid) \ do { \ *((u8 *)__cursor) = __qid.type; \ __cursor += sizeof(u8); \ *((u32 *)__cursor) = __qid.version; \ __cursor += sizeof(u32); \ *((u64 *)__cursor) = __qid.path; \ __cursor += sizeof(u64); \ } while (0) /* Insert a non-null terminated string */ #define _9p_setstr(__cursor, __len, __str) \ do { \ *((u16 *)__cursor) = __len; \ __cursor += sizeof(u16); \ memcpy(__cursor, __str, __len); \ __cursor += __len; \ } while (0) /* _9p_setbuffer: * Copy data from __buffer into the reply, * with a length u32 header. */ #define _9p_setbuffer(__cursor, __len, __buffer) \ do { \ *((u32 *)__cursor) = __len; \ __cursor += sizeof(u32); \ memcpy(__cursor, __buffer, __len); \ __cursor += __len; \ } while (0) /* _9p_setfilledbuffer: * Data has already been copied into the reply. * Only move the cursor and set the length. */ #define _9p_setfilledbuffer(__cursor, __len) \ do { \ *((u32 *)__cursor) = __len; \ __cursor += sizeof(u32) + __len; \ } while (0) /* _9p_getbuffertofill: * Get a pointer where to copy data in the reply. * This leaves room in the reply for a u32 len header */ #define _9p_getbuffertofill(__cursor) (((char *)(__cursor)) + sizeof(u32)) #define _9p_setinitptr(__cursor, __start, __reqtype) \ do { \ __cursor = __start + _9P_HDR_SIZE; \ *((u8 *)__cursor) = __reqtype; \ __cursor += sizeof(u8); \ } while (0) /* _9p_setendptr: * Calculate message size, and write this value in the * header of the 9p message. */ #define _9p_setendptr(__cursor, __start) \ (*((u32 *)__start) = (u32)(__cursor - __start)) /* _9p_checkbound: * Check that the message size is less than *__maxlen, * AND set *__maxlen to actual message size. */ #define _9p_checkbound(__cursor, __start, __maxlen) \ do { \ if ((u32)(__cursor - __start) > *__maxlen) \ return -1; \ else \ *__maxlen = (u32)(__cursor - __start); \ } while (0) /* Bit values for getattr valid field. */ #define _9P_GETATTR_MODE 0x00000001ULL #define _9P_GETATTR_NLINK 0x00000002ULL #define _9P_GETATTR_UID 0x00000004ULL #define _9P_GETATTR_GID 0x00000008ULL #define _9P_GETATTR_RDEV 0x00000010ULL #define _9P_GETATTR_ATIME 0x00000020ULL #define _9P_GETATTR_MTIME 0x00000040ULL #define _9P_GETATTR_CTIME 0x00000080ULL #define _9P_GETATTR_INO 0x00000100ULL #define _9P_GETATTR_SIZE 0x00000200ULL #define _9P_GETATTR_BLOCKS 0x00000400ULL #define _9P_GETATTR_BTIME 0x00000800ULL #define _9P_GETATTR_GEN 0x00001000ULL #define _9P_GETATTR_DATA_VERSION 0x00002000ULL #define _9P_GETATTR_BASIC \ 0x000007ffULL /* Mask for fields * up to BLOCKS */ #define _9P_GETATTR_ALL 0x00003fffULL /* Mask for all fields above */ /* Bit values for setattr valid field from . */ #define _9P_SETATTR_MODE 0x00000001UL #define _9P_SETATTR_UID 0x00000002UL #define _9P_SETATTR_GID 0x00000004UL #define _9P_SETATTR_SIZE 0x00000008UL #define _9P_SETATTR_ATIME 0x00000010UL #define _9P_SETATTR_MTIME 0x00000020UL #define _9P_SETATTR_CTIME 0x00000040UL #define _9P_SETATTR_ATIME_SET 0x00000080UL #define _9P_SETATTR_MTIME_SET 0x00000100UL /* Bit values for lock type. */ #define _9P_LOCK_TYPE_RDLCK 0 #define _9P_LOCK_TYPE_WRLCK 1 #define _9P_LOCK_TYPE_UNLCK 2 /* Bit values for lock status. */ #define _9P_LOCK_SUCCESS 0 #define _9P_LOCK_BLOCKED 1 #define _9P_LOCK_ERROR 2 #define _9P_LOCK_GRACE 3 /* Bit values for lock flags. */ #define _9P_LOCK_FLAGS_BLOCK 1 #define _9P_LOCK_FLAGS_RECLAIM 2 /** * @defgroup config_9p Structure and defaults for _9P * * @{ */ /** * @brief Default value for _9p_tcp_port */ #define _9P_TCP_PORT 564 /** * @brief Default value for _9p_rdma_port */ #define _9P_RDMA_PORT 5640 /** * @brief Default value for _9p_tcp_msize */ #define _9P_TCP_MSIZE 65536 /** * @brief Default value for _9p_rdma_msize */ #define _9P_RDMA_MSIZE 1048576 /** * @brief Default number of receive buffer per nic */ #define _9P_RDMA_INPOOL_SIZE 64 /** * @brief Default number of send buffer (total, not per nic) * * shared pool for sends - optimal when set oh-so-slightly * higher than the number of worker threads */ #define _9P_RDMA_OUTPOOL_SIZE 32 /** * @brief Default rdma connection backlog * (number of pending connection requests) */ #define _9P_RDMA_BACKLOG 10 /** * @brief 9p configuration */ struct _9p_param { /** Number of worker threads. Set to NB_WORKER_DEFAULT by default and changed with the Nb_Worker option. */ uint32_t nb_worker; /** TCP port for 9p operations. Defaults to _9P_TCP_PORT, settable by _9P_TCP_Port */ uint16_t _9p_tcp_port; /** RDMA port for 9p operations. Defaults to _9P_RDMA_PORT, settable by _9P_RDMA_Port */ uint16_t _9p_rdma_port; /** Msize for 9P operation on tcp. Defaults to _9P_TCP_MSIZE, settable by _9P_TCP_Msize */ uint32_t _9p_tcp_msize; /** Msize for 9P operation on rdma. Defaults to _9P_RDMA_MSIZE, settable by _9P_RDMA_Msize */ uint32_t _9p_rdma_msize; /** Backlog for 9P rdma connections. Defaults to _9P_RDMA_BACKLOG, settable by _9P_RDMA_Backlog */ uint16_t _9p_rdma_backlog; /** Input buffer pool size for 9P rdma connections. Defaults to _9P_RDMA_INPOOL_SIZE, settable by _9P_RDMA_Inpool_Size */ uint16_t _9p_rdma_inpool_size; /** Output buffer pool size for 9P rdma connections. Defaults to _9P_RDMA_OUTPOOL_SIZE, settable by _9P_RDMA_OutPool_Size */ uint16_t _9p_rdma_outpool_size; }; /** @} */ /* protocol parameter tables */ extern struct _9p_param _9p_param; extern struct config_block _9p_param_blk; /* service functions */ int _9p_init(void); /* Tools functions */ /** * @brief Increment the refcounter of a _9p_user_cred structure. * * @param creds Reference that is being copied. */ void get_9p_user_cred_ref(struct _9p_user_cred *creds); /** * @brief Release a reference to an _9p_user_cred structure. * * This function decrements the refcounter of the containing _9p_user_cred * structure. If this counter reaches 0, the structure is freed. * * @param creds The reference that is released. */ void release_9p_user_cred_ref(struct _9p_user_cred *creds); /** * @brief Initialize op_ctx for the current request. * * op_ctx must point to an allocated structure. * * @param pfid fid used to initialize the context. * @param req9p request date used to initialize export_perms. It can be NULL, * in this case export_perms will be uninitialized. */ void _9p_init_opctx(struct _9p_fid *pfid, struct _9p_request_data *req9p); /** * @brief Release resources taken by _9p_init_opctx. * * op_ctx contains several pointers to refcounted objects. This function * decrements these counters and sets the associated fields to NULL. */ void _9p_release_opctx(void); /** * @brief Free this fid after releasing its resources. * * This function can be used to free a partially allocated fid, when an error * occurs. To release a valid fid, use _9p_tools_clunk instead. * * @param[in,out] pfid pointer to fid entry. */ void free_fid(struct _9p_fid *pfid); int _9p_tools_get_req_context_by_uid(u32 uid, struct _9p_fid *pfid); int _9p_tools_get_req_context_by_name(int uname_len, char *uname_str, struct _9p_fid *pfid); int _9p_tools_errno(fsal_status_t fsal_status); void _9p_openflags2FSAL(u32 *inflags, fsal_openflags_t *outflags); int _9p_tools_clunk(struct _9p_fid *pfid); void _9p_cleanup_fids(struct _9p_conn *conn); static inline unsigned int _9p_openflags_to_share_access(u32 *inflags) { switch ((*inflags) & O_ACCMODE) { case O_RDONLY: return OPEN4_SHARE_ACCESS_READ; case O_WRONLY: return OPEN4_SHARE_ACCESS_WRITE; case O_RDWR: return OPEN4_SHARE_ACCESS_READ | OPEN4_SHARE_ACCESS_WRITE; default: return 0; } } #ifdef _USE_9P_RDMA /* 9P/RDMA callbacks */ void *_9p_rdma_handle_trans(void *arg); void _9p_rdma_callback_recv(msk_trans_t *trans, msk_data_t *pdata, void *arg); void _9p_rdma_callback_disconnect(msk_trans_t *trans); void _9p_rdma_callback_send(msk_trans_t *trans, msk_data_t *pdata, void *arg); void _9p_rdma_callback_recv_err(msk_trans_t *trans, msk_data_t *pdata, void *arg); void _9p_rdma_callback_send_err(msk_trans_t *trans, msk_data_t *pdata, void *arg); #endif void _9p_AddFlushHook(struct _9p_request_data *req, int tag, unsigned long sequence); void _9p_FlushFlushHook(struct _9p_conn *conn, int tag, unsigned long sequence); int _9p_LockAndTestFlushHook(struct _9p_request_data *req); void _9p_ReleaseFlushHook(struct _9p_request_data *req); void _9p_DiscardFlushHook(struct _9p_request_data *req); /* Protocol functions */ int _9p_not_2000L(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_clunk(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_attach(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_auth(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_lcreate(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_flush(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_getattr(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_getlock(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_link(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_lock(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_lopen(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_mkdir(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_mknod(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_read(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_readdir(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_readlink(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_setattr(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_symlink(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_remove(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_rename(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_renameat(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_statfs(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_fsync(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_unlinkat(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_version(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_walk(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_write(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_xattrcreate(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_xattrwalk(struct _9p_request_data *req9p, u32 *plenout, char *preply); int _9p_rerror(struct _9p_request_data *req9p, u16 *msgtag, u32 err, u32 *plenout, char *preply); /* Expects to already be size checked */ static inline void _9p_get_fname(char *name, int len, const char *str) { memcpy(name, str, len); name[len] = '\0'; } #endif /* _9P_H */ nfs-ganesha-6.5/src/include/9p_req_queue.h000066400000000000000000000055131473756622300205670ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2012, The Linux Box Corporation * Copyright (c) 2012-2017 Red Hat, Inc. and/or its affiliates. * Contributor : Matt Benjamin * * This program 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 3 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 * 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 * * ------------- */ /** * @file 9p_req_queue.h * @author Matt Benjamin * @brief 9P request queue package * * This module defines an infrastructure for classification and * dispatch of incoming protocol requests using a forward queueing * model, with priority and isolation partitions. */ #ifndef _9P_REQ_QUEUE_H #define _9P_REQ_QUEUE_H #include "gsh_list.h" #include "common_utils.h" #include "gsh_wait_queue.h" struct req_q { pthread_spinlock_t _9p_rq_spinlock; struct glist_head q; /* LIFO */ uint32_t size; uint32_t max; uint32_t waiters; }; struct req_q_pair { const char *s; GSH_CACHE_PAD(0); struct req_q producer; /* from decoder */ GSH_CACHE_PAD(1); struct req_q consumer; /* to executor */ GSH_CACHE_PAD(2); }; enum req_q_e { REQ_Q_LOW_LATENCY, /*< GETATTR, RENEW, etc */ N_REQ_QUEUES }; struct req_q_set { struct req_q_pair qset[N_REQ_QUEUES]; }; struct _9p_req_st { struct { uint32_t ctr; struct req_q_set _9p_request_q; uint64_t size; pthread_spinlock_t _9p_rq_st_spinlock; struct glist_head wait_list; uint32_t waiters; } reqs; GSH_CACHE_PAD(1); }; static inline void _9p_rpc_q_init(struct req_q *q) { glist_init(&q->q); PTHREAD_SPIN_init(&q->_9p_rq_spinlock, PTHREAD_PROCESS_PRIVATE); q->size = 0; q->waiters = 0; } static inline void _9p_rpc_q_destroy(struct req_q *q) { PTHREAD_SPIN_destroy(&q->_9p_rq_spinlock); } static inline void _9p_queue_awaken(void *arg) { struct _9p_req_st *st = arg; struct glist_head *g = NULL; struct glist_head *n = NULL; PTHREAD_SPIN_lock(&st->reqs._9p_rq_st_spinlock); glist_for_each_safe(g, n, &st->reqs.wait_list) { wait_q_entry_t *wqe = glist_entry(g, wait_q_entry_t, waitq); pthread_cond_signal(&wqe->lwe.wq_cv); pthread_cond_signal(&wqe->rwe.wq_cv); } PTHREAD_SPIN_unlock(&st->reqs._9p_rq_st_spinlock); } #endif /* _9P_REQ_QUEUE_H */ nfs-ganesha-6.5/src/include/9p_types.h000066400000000000000000000024141473756622300177350ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright  2014 CohortFS, LLC. * Author: William Allen Simpson * * This program 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 3 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 * 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 */ #ifndef _9P_TYPES_H #define _9P_TYPES_H #if 0 /* problematic redefinitions that may cause compiler complications */ typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; #else /* cleanly defined replacement */ #define u8 uint8_t #define u16 uint16_t #define u32 uint32_t #define u64 uint64_t #endif #endif /* _9P_TYPES_H */ nfs-ganesha-6.5/src/include/Connectathon_config_parsing.h000066400000000000000000000015331473756622300236550ustar00rootroot00000000000000/* SPDX-License-Identifier: unknown license... */ #ifndef _CONFIG_PARSING_H #define _CONFIG_PARSING_H enum test_number { ONE = 1, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE }; struct btest { enum test_number num; enum test_number num2; int levels; int files; int dirs; int count; int size; int blocksize; char *bigfile; char *fname; char *dname; char *nname; char *sname; struct btest *nextbtest; }; struct testparam { char *dirtest; char *logfile; struct btest *btest; }; void btest_init_defaults(struct btest *b); void testparam_init_defaults(struct testparam *t); void free_testparam(struct testparam *t); char *get_test_directory(struct testparam *t); char *get_log_file(struct testparam *t); struct btest *get_btest_args(struct testparam *param, enum test_number k); struct testparam *readin_config(char *fname); #endif nfs-ganesha-6.5/src/include/FSAL/000077500000000000000000000000001473756622300165345ustar00rootroot00000000000000nfs-ganesha-6.5/src/include/FSAL/access_check.h000066400000000000000000000043521473756622300213070ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * This program 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 3 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 * 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 * */ #ifndef _ACCESS_CHECK_H #define _ACCESS_CHECK_H /* A few headers required to have "struct stat" */ #include #include #include #include "config.h" #include "fsal_api.h" /* fsal_test_access * common (default) access check method for fsal_obj_handle objects. */ fsal_status_t fsal_test_access(struct fsal_obj_handle *obj_hdl, fsal_accessflags_t access_type, fsal_accessflags_t *allowed, fsal_accessflags_t *denied, bool owner_skip); int display_fsal_v4mask(struct display_buffer *dspbuf, fsal_aceperm_t v4mask, bool is_dir); #if GSH_CAN_HOST_LOCAL_FS void fsal_set_credentials(const struct user_cred *creds); void fsal_restore_ganesha_credentials(void); #endif bool fsal_set_credentials_only_one_user(const struct user_cred *creds); void fsal_save_ganesha_credentials(void); void fsal_print_ace_int(log_components_t component, log_levels_t debug, fsal_ace_t *ace, char *file, int line, char *function); #define fsal_print_ace(component, debug, ace) \ fsal_print_ace_int((component), (debug), (ace), (char *)__FILE__, \ __LINE__, (char *)__func__) void fsal_print_acl_int(log_components_t component, log_levels_t debug, fsal_acl_t *acl, char *file, int line, char *function); #define fsal_print_acl(component, debug, acl) \ fsal_print_acl_int((component), (debug), (acl), (char *)__FILE__, \ __LINE__, (char *)__func__) #endif nfs-ganesha-6.5/src/include/FSAL/fsal_commonlib.h000066400000000000000000000224011473756622300216700ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup FSAL File-System Abstraction Layer * @{ */ /** * @file fsal_commomnlib.h * @brief Miscellaneous FSAL common library routines */ #ifndef FSAL_COMMONLIB_H #define FSAL_COMMONLIB_H #include "fsal_api.h" #include "sal_data.h" #include "sal_functions.h" /* * fsal common utility functions */ /* fsal_module to fsal_export helpers */ int fsal_attach_export(struct fsal_module *fsal_hdl, struct glist_head *obj_link); void fsal_detach_export(struct fsal_module *fsal_hdl, struct glist_head *obj_link); /* fsal_export common methods */ void fsal_export_init(struct fsal_export *); void fsal_export_stack(struct fsal_export *sub_export, struct fsal_export *super_export); void free_export_ops(struct fsal_export *exp_hdl); /* fsal_obj_handle common methods */ void fsal_default_obj_ops_init(struct fsal_obj_ops *obj_ops); /* * @param[in] add_to_fsal_list Whether FSAL would like the handle to be in * the global list. FSAL which have no usecase of storing the obj handles * within fsal handlers, can pass this flag as false. */ void fsal_obj_handle_init(struct fsal_obj_handle *obj, struct fsal_export *exp, object_file_type_t type, bool add_to_fsal_handle); void fsal_obj_handle_fini(struct fsal_obj_handle *obj, bool added_to_fsal_handle); /** * @brief Test handle type * * This function tests that a handle is of the specified type. * * @retval true if it is. * @retval false if it isn't. */ static inline bool fsal_obj_handle_is(struct fsal_obj_handle *obj_hdl, object_file_type_t type) { return obj_hdl->type == type; } /* * pNFS DS Helpers */ void fsal_pnfs_ds_init(struct fsal_pnfs_ds *pds, struct fsal_module *fsal); void fsal_pnfs_ds_fini(struct fsal_pnfs_ds *pds); int encode_fsid(char *buf, int max, struct fsal_fsid__ *fsid, enum fsid_type fsid_type); int decode_fsid(char *buf, int max, struct fsal_fsid__ *fsid, enum fsid_type fsid_type); fsal_errors_t fsal_inherit_acls(struct fsal_attrlist *attrs, fsal_acl_t *sacl, fsal_aceflag_t inherit); fsal_status_t fsal_remove_access(struct fsal_obj_handle *dir_hdl, struct fsal_obj_handle *rem_hdl, bool isdir); fsal_status_t fsal_rename_access(struct fsal_obj_handle *old_dir_hdl, struct fsal_obj_handle *src_obj_hdl, struct fsal_obj_handle *new_dir_hdl, struct fsal_obj_handle *dst_obj_hdl, bool isdir); bool fsal_can_reuse_mode_to_acl(const fsal_acl_t *sacl); fsal_status_t fsal_mode_to_acl(struct fsal_attrlist *attrs, fsal_acl_t *sacl); fsal_status_t fsal_acl_to_mode(struct fsal_attrlist *attrs); void set_common_verifier(struct fsal_attrlist *attrs, fsal_verifier_t verifier, bool trunc_verif); void update_share_counters(struct fsal_share *share, fsal_openflags_t old_openflags, fsal_openflags_t new_openflags); static inline void update_share_counters_locked(struct fsal_obj_handle *obj_hdl, struct fsal_share *share, fsal_openflags_t old_openflags, fsal_openflags_t new_openflags) { PTHREAD_RWLOCK_wrlock(&obj_hdl->obj_lock); update_share_counters(share, old_openflags, new_openflags); PTHREAD_RWLOCK_unlock(&obj_hdl->obj_lock); } fsal_status_t check_share_conflict(struct fsal_share *share, fsal_openflags_t openflags, bool bypass); static inline fsal_status_t check_share_conflict_and_update(struct fsal_obj_handle *obj_hdl, struct fsal_share *share, fsal_openflags_t old_openflags, fsal_openflags_t new_openflags, bool bypass) { fsal_status_t status; status = check_share_conflict(share, new_openflags, bypass); if (!FSAL_IS_ERROR(status)) { /* Take the share reservation now by updating the counters. */ update_share_counters(share, old_openflags, new_openflags); } return status; } static inline fsal_status_t check_share_conflict_and_update_locked( struct fsal_obj_handle *obj_hdl, struct fsal_share *share, fsal_openflags_t old_openflags, fsal_openflags_t new_openflags, bool bypass) { fsal_status_t status; PTHREAD_RWLOCK_wrlock(&obj_hdl->obj_lock); status = check_share_conflict_and_update(obj_hdl, share, old_openflags, new_openflags, bypass); PTHREAD_RWLOCK_unlock(&obj_hdl->obj_lock); return status; } fsal_status_t merge_share(struct fsal_obj_handle *orig_hdl, struct fsal_share *orig_share, struct fsal_share *dupe_share); /** * @brief Function to open an fsal_obj_handle's global file descriptor. * * @param[in] obj_hdl File on which to operate * @param[in] openflags Mode for open * @param[out] fd File descriptor that is to be used * * @return FSAL status. */ typedef fsal_status_t (*fsal_open_func)(struct fsal_obj_handle *obj_hdl, fsal_openflags_t openflags, struct fsal_fd *fd); /** * @brief Function to close an fsal_obj_handle's global file descriptor. * * @param[in] obj_hdl File on which to operate * @param[in] fd File handle to close * * @return FSAL status. */ typedef fsal_status_t (*fsal_close_func)(struct fsal_obj_handle *obj_hdl, struct fsal_fd *fd); /** * @brief Function to close a fsal_fd. * * @param[in] obj_hdl File on which to operate * @param[in] fd File handle to close * * @return FSAL status. */ static inline fsal_status_t fsal_close_fd(struct fsal_obj_handle *obj_hdl, struct fsal_fd *fd) { return obj_hdl->obj_ops->close_func(obj_hdl, fd); } /** * @brief Function to open or reopen a fsal_fd. * * @param[in] obj_hdl File on which to operate * @param[in] openflags New mode for open * @param[out] fd File descriptor that is to be used * * @return FSAL status. */ static inline fsal_status_t fsal_reopen_fd(struct fsal_obj_handle *obj_hdl, fsal_openflags_t openflags, struct fsal_fd *fd) { return obj_hdl->obj_ops->reopen_func(obj_hdl, openflags, fd); } fsal_status_t close_fsal_fd(struct fsal_obj_handle *obj_hdl, struct fsal_fd *fsal_fd, bool is_reclaiming); fsal_status_t fsal_start_global_io(struct fsal_fd **out_fd, struct fsal_obj_handle *obj_hdl, struct fsal_fd *my_fd, struct fsal_fd *tmp_fd, fsal_openflags_t openflags, bool bypass, struct fsal_share *share); fsal_status_t fsal_start_io(struct fsal_fd **out_fd, struct fsal_obj_handle *obj_hdl, struct fsal_fd *obj_fd, struct fsal_fd *tmp_fd, struct state_t *state, fsal_openflags_t openflags, bool open_for_locks, bool *reusing_open_state_fd, bool bypass, struct fsal_share *share); fsal_status_t fsal_complete_io(struct fsal_obj_handle *obj_hdl, struct fsal_fd *fsal_fd); fsal_status_t fsal_start_fd_work(struct fsal_fd *fsal_fd, bool is_reclaiming); static inline void fsal_start_fd_work_no_reclaim(struct fsal_fd *fsal_fd) { fsal_status_t rc; rc = fsal_start_fd_work(fsal_fd, false); if (rc.major != ERR_FSAL_NO_ERROR) LogFatal(COMPONENT_FSAL, "Unexpected failure."); } void fsal_complete_fd_work(struct fsal_fd *fsal_fd); void insert_fd_lru(struct fsal_fd *fsal_fd); void bump_fd_lru(struct fsal_fd *fsal_fd); void remove_fd_lru(struct fsal_fd *fsal_fd); /** * @brief Initialize a state_t structure * * @param[in] state The state to initialize * @param[in] state_free An optional function to manage freeing state * @param[in] state_type Type of state to allocate * @param[in] related_state Related state if appropriate * * @returns the state structure for streamlined coding. * * NOTE: state_free MUST free the state. */ static inline struct state_t *init_state(struct state_t *state, state_free_t state_free, enum state_type state_type, struct state_t *related_state) { state->state_type = state_type; state->state_free = state_free; if (related_state) { memcpy(state->state_data.lock.openstate_key, related_state->stateid_other, OTHERSIZE); } return state; } bool check_verifier_stat(struct stat *st, fsal_verifier_t verifier, bool trunc_verif); bool check_verifier_attrlist(struct fsal_attrlist *attrs, fsal_verifier_t verifier, bool trunc_verif); bool fsal_common_is_referral(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs, bool cache_attrs); fsal_status_t update_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, struct fsal_export *original, struct fsal_module *updated_super); #endif /* FSAL_COMMONLIB_H */ nfs-ganesha-6.5/src/include/FSAL/fsal_config.h000066400000000000000000000030341473756622300211570ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * This program 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 3 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 * 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 * */ /* * configuration structure management functions */ bool fsal_supports(struct fsal_staticfsinfo_t *info, fsal_fsinfo_options_t option); uint64_t fsal_maxfilesize(struct fsal_staticfsinfo_t *info); uint32_t fsal_maxlink(struct fsal_staticfsinfo_t *info); uint32_t fsal_maxnamelen(struct fsal_staticfsinfo_t *info); uint32_t fsal_maxpathlen(struct fsal_staticfsinfo_t *info); fsal_aclsupp_t fsal_acl_support(struct fsal_staticfsinfo_t *info); attrmask_t fsal_supported_attrs(struct fsal_staticfsinfo_t *info); uint32_t fsal_maxread(struct fsal_staticfsinfo_t *info); uint32_t fsal_maxwrite(struct fsal_staticfsinfo_t *info); uint32_t fsal_umask(struct fsal_staticfsinfo_t *info); int32_t fsal_expiretimeparent(struct fsal_staticfsinfo_t *info); nfs-ganesha-6.5/src/include/FSAL/fsal_init.h000066400000000000000000000033571473756622300206650ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * This program 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 3 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 * 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 * */ /** * @file fsal_init.h * @author Jim Lieb * @brief Module initialization */ /** * @brief Initializer macro * * Every FSAL module has an initializer. any function labeled as * MODULE_INIT will be called in order after the module is loaded and * before dlopen returns. This is where you register your fsal. * * The initializer function should use register_fsal to initialize * public data and get the default operation vectors, then override * them with module-specific methods. */ #define MODULE_INIT __attribute__((constructor)) /** * @brief Finalizer macro * * Every FSAL module *must* have a destructor to free any resources. * the function should assert() that the module can be safely unloaded. * However, the core should do the same check prior to attempting an * unload. The function must be defined as void foo(void), i.e. no args * passed and no returns evaluated. */ #define MODULE_FINI __attribute__((destructor)) nfs-ganesha-6.5/src/include/FSAL/fsal_localfs.h000066400000000000000000000152641473756622300213450ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ #ifndef FSAL_LOCAL_FS_H #define FSAL_LOCAL_FS_H #include "fsal_api.h" #if !GSH_CAN_HOST_LOCAL_FS static inline void release_posix_file_systems(void) { } #ifdef USE_DBUS static inline void dbus_cache_init(void) { } #endif struct fsal_filesystem; #else /** * @brief Public structure for filesystem descriptions * * This structure is provided along with a general interface to support those * FSALs that map into a traditional file system model. Note that * fsal_obj_handles do not link to an fsal_filesystem, that linkage is reserved * for FSAL's private obj handle if appropriate. * */ struct fsal_filesystem { struct glist_head filesystems; /*< List of file systems */ struct glist_head children; /*< Child file systems */ struct glist_head siblings; /*< Entry in list of parent's child file systems */ struct fsal_filesystem *parent; /*< Parent file system */ struct fsal_module *fsal; /*< Link back to fsal module */ struct glist_head exports; /*< List of all the export maps */ void *private_data; /*< Private data for owning FSAL */ char *path; /*< Path to root of this file system */ char *device; /*< Path to block device */ char *type; /*< fs type */ unclaim_filesystem_cb unclaim; /*< Call back to unclaim this fs */ uint32_t pathlen; /*< Length of path */ uint32_t namelen; /*< Name length from statfs */ struct avltree_node avl_fsid; /*< AVL indexed by fsid */ struct avltree_node avl_dev; /*< AVL indexed by dev */ struct fsal_fsid__ fsid; /*< file system id */ fsal_dev_t dev; /*< device filesystem is on */ enum fsid_type fsid_type; /*< type of fsid present */ bool in_fsid_avl; /*< true if inserted in fsid avl */ bool in_dev_avl; /*< true if inserted in dev avl */ int claims[CLAIM_NUM]; /*< number of each type of claim */ bool trunc_verif; /*< true if the filesystem needs atime/mtime to be truncated to 31 bits when storing verifier. */ }; /* * Link fsal_filesystems and fsal_exports * Supports a many-to-many relationship */ struct fsal_filesystem_export_map { struct fsal_filesystem_export_map *parent_map; struct fsal_export *exp; struct fsal_filesystem *fs; struct glist_head child_maps; struct glist_head on_parent; struct glist_head on_exports; struct glist_head on_filesystems; enum claim_type claim_type; }; int open_dir_by_path_walk(int first_fd, const char *path, struct stat *stat); static inline int fsal_fs_compare_fsid(enum fsid_type left_fsid_type, const struct fsal_fsid__ *left_fsid, enum fsid_type right_fsid_type, const struct fsal_fsid__ *right_fsid) { if (left_fsid_type < right_fsid_type) return -1; if (left_fsid_type > right_fsid_type) return 1; if (left_fsid->major < right_fsid->major) return -1; if (left_fsid->major > right_fsid->major) return 1; /* No need to compare minors as they should be * zeros if the type is FSID_MAJOR_64 */ if (left_fsid_type == FSID_MAJOR_64) { assert(right_fsid_type == FSID_MAJOR_64); return 0; } if (left_fsid->minor < right_fsid->minor) return -1; if (left_fsid->minor > right_fsid->minor) return 1; return 0; } extern pthread_rwlock_t fs_lock; #define LogFilesystem(cmt, cmt2, fs) \ LogFullDebug(COMPONENT_FSAL, \ "%s%s FS %p %s parent %p %s children? %s siblings? %s " \ "FSAL %s exports? %s private %p " \ "claims ALL %d ROOT %d SUBTREE %d CHILD %d TEMP %d", \ (cmt), (cmt2), (fs), (fs)->path, (fs)->parent, \ (fs)->parent ? (fs)->parent->path : "NONE", \ glist_empty(&(fs)->children) ? "NO" : "YES", \ glist_null(&(fs)->siblings) ? "NO" : "YES", \ (fs)->fsal ? (fs)->fsal->name : "NONE", \ glist_empty(&(fs)->exports) ? "NO" : "YES", \ (fs)->private_data, (fs)->claims[CLAIM_ALL], \ (fs)->claims[CLAIM_ROOT], (fs)->claims[CLAIM_SUBTREE], \ (fs)->claims[CLAIM_CHILD], (fs)->claims[CLAIM_TEMP]) int populate_posix_file_systems(const char *path); int resolve_posix_filesystem(const char *path, struct fsal_module *fsal, struct fsal_export *exp, claim_filesystem_cb claimfs, unclaim_filesystem_cb unclaim, struct fsal_filesystem **root_fs); void release_posix_file_systems(void); enum release_claims { UNCLAIM_WARN, UNCLAIM_SKIP, }; bool release_posix_file_system(struct fsal_filesystem *fs, enum release_claims release_claims); int re_index_fs_fsid(struct fsal_filesystem *fs, enum fsid_type fsid_type, struct fsal_fsid__ *fsid); int re_index_fs_dev(struct fsal_filesystem *fs, struct fsal_dev__ *dev); int change_fsid_type(struct fsal_filesystem *fs, enum fsid_type fsid_type); struct fsal_filesystem *lookup_fsid_locked(struct fsal_fsid__ *fsid, enum fsid_type fsid_type); struct fsal_filesystem *lookup_dev_locked(struct fsal_dev__ *dev); struct fsal_filesystem *lookup_fsid(struct fsal_fsid__ *fsid, enum fsid_type fsid_type); struct fsal_filesystem *lookup_dev(struct fsal_dev__ *dev); int claim_posix_filesystems(const char *path, struct fsal_module *fsal, struct fsal_export *exp, claim_filesystem_cb claimfs, unclaim_filesystem_cb unclaim, struct fsal_filesystem **root_fs, struct stat *statbuf); bool is_filesystem_exported(struct fsal_filesystem *fs, struct fsal_export *exp); void unclaim_all_export_maps(struct fsal_export *exp); void get_fs_first_export_ref(struct fsal_filesystem *this, struct gsh_export **gsh_export, struct fsal_export **fsal_export); #ifdef USE_DBUS void dbus_cache_init(void); #endif #endif /* GSH_CAN_HOST_LOCAL_FS */ #endif /* FSAL_LOCAL_FS_H */ nfs-ganesha-6.5/src/include/abstract_atomic.h000066400000000000000000001363521473756622300213310ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright © 2012 Linux Box Corporation * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file abstract_atomic.h * @author Adam C. Emerson * @author Frank S. Filz * @brief Shim for compiler or library supplied atomic operations * * This file provides inline functions that provide atomic operations * appropriate to the compiler being used. (Someone can add support * for an appropriate library later on.) * * The types functions are provided for are: * * ptrdiff_t (fetch and store only) * time_t (fetch and store only) * void* (fetch and store only) * uintptr_t (fetch and store only) * int64_t * uint64_t * int32_t * uint32_t * int16_t * uint16_t * int8_t * uint8_t * size_t * * The functions provided are (using int64_t for example): * * int64_t atomic_add_int64_t(int64_t *augend, int64_t addend) * int64_t atomic_inc_int64_t(int64_t *var) * int64_t atomic_sub_int64_t(int64_t *minuend, int64_t subtrahend) * int64_t atomic_dec_int64_t(int64_t *var) * int64_t atomic_postadd_int64_t(int64_t *augend, int64_t addend) * int64_t atomic_postinc_int64_t(int64_t *var) * int64_t atomic_postsub_int64_t(int64_t *minuend, int64_t subtrahend) * int64_t atomic_postdec_int64_t(int64_t *var) * int64_t atomic_fetch_int64_t(int64_t *var) * void atomic_store_int64_t(int64_t *var, int64_t val) * * The following is provided for int64_t, uint64_t, int32_t, and uint32_t * * bool atomic_add_unless_int64_t(int64_t *var, int64_t addend, int64_t unless) * * The following is provided for int32_t * * int32_t atomic_inc_unless_0_int32_t(int32_t *var) * * The following bit mask operations are provided for * uint64_t, uint32_t, uint_16t, and uint8_t: * * uint64_t atomic_clear_uint64_t_bits(uint64_t *var, uint64_t bits) * uint64_t atomic_set_uint64_t_bits(uint64_t *var, uint64_t bits) * uint64_t atomic_postclear_uint64_t_bits(uint64_t *var, * uint64_t atomic_postset_uint64_t_bits(uint64_t *var, * */ #ifndef _ABSTRACT_ATOMIC_H #define _ABSTRACT_ATOMIC_H #include #include #include #include /* * Preaddition, presubtraction, preincrement, predecrement (return the * value after the operation, by analogy with the ++n preincrement * operator.) */ /** * @brief Atomically add to an int64_t * * This function atomically adds to the supplied value. * * @param[in,out] augend Number to be added to * @param[in] addend Number to add * * @return The value after addition. */ static inline int64_t atomic_add_int64_t(int64_t *augend, int64_t addend) { return __atomic_add_fetch(augend, addend, __ATOMIC_SEQ_CST); } /** * @brief Atomically increment an int64_t * * This function atomically adds 1 to the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value after increment. */ static inline int64_t atomic_inc_int64_t(int64_t *var) { return atomic_add_int64_t(var, 1); } /** * @brief Atomically subtract from an int64_t * * This function atomically subtracts from the supplied value. * * @param[in,out] minuend Number to be subtracted from * @param[in] subtrahend Number to subtract * * @return The value after subtraction. */ static inline int64_t atomic_sub_int64_t(int64_t *minuend, int64_t subtrahend) { return __atomic_sub_fetch(minuend, subtrahend, __ATOMIC_SEQ_CST); } /** * @brief Atomically decrement an int64_t * * This function atomically subtracts 1 from the supplied value. * * @param[in,out] var Pointer to the variable to modify */ static inline int64_t atomic_dec_int64_t(int64_t *var) { return atomic_sub_int64_t(var, 1); } /** * @brief Atomically add to an int64_t * * This function atomically adds to the supplied value. * * @param[in,out] augend Number to be added to * @param[in] addend Number to add * * @return The value after addition. */ static inline uint64_t atomic_add_uint64_t(uint64_t *augend, uint64_t addend) { return __atomic_add_fetch(augend, addend, __ATOMIC_SEQ_CST); } /** * @brief Atomically increment a uint64_t * * This function atomically adds 1 to the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value after increment. */ static inline uint64_t atomic_inc_uint64_t(uint64_t *var) { return atomic_add_uint64_t(var, 1); } /** * @brief Atomically subtract from a uint64_t * * This function atomically subtracts from the supplied value. * * @param[in,out] minuend Number to be subtracted from * @param[in] subtrahend Number to subtract * * @return The value after subtraction. */ static inline uint64_t atomic_sub_uint64_t(uint64_t *minuend, uint64_t subtrahend) { return __atomic_sub_fetch(minuend, subtrahend, __ATOMIC_SEQ_CST); } /** * @brief Atomically decrement a uint64_t * * This function atomically subtracts 1 from the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value after decrement. */ static inline uint64_t atomic_dec_uint64_t(uint64_t *var) { return atomic_sub_uint64_t(var, 1); } /** * @brief Atomically add to an int32_t * * This function atomically adds to the supplied value. * * @param[in,out] augend Number to be added to * @param[in] addend Number to add * * @return The value after addition. */ static inline int32_t atomic_add_int32_t(int32_t *augend, int32_t addend) { return __atomic_add_fetch(augend, addend, __ATOMIC_SEQ_CST); } /** * @brief Atomically increment an int32_t * * This function atomically adds 1 to the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value after increment. */ static inline int32_t atomic_inc_int32_t(int32_t *var) { return atomic_add_int32_t(var, 1); } /** * @brief Atomically subtract from an int32_t * * This function atomically subtracts from the supplied value. * * @param[in,out] minuend Number to be subtracted from * @param[in] subtrahend Number to subtract * * @return The value after subtraction. */ static inline int32_t atomic_sub_int32_t(int32_t *minuend, int32_t subtrahend) { return __atomic_sub_fetch(minuend, subtrahend, __ATOMIC_SEQ_CST); } /** * @brief Atomically decrement an int32_t * * This function atomically subtracts 1 from the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value after decrement. */ static inline int32_t atomic_dec_int32_t(int32_t *var) { return atomic_sub_int32_t(var, 1); } /** * @brief Atomically add to a uint32_t * * This function atomically adds to the supplied value. * * @param[in,out] augend Number to be added to * @param[in] addend Number to add * * @return The value after addition. */ static inline uint32_t atomic_add_uint32_t(uint32_t *augend, uint32_t addend) { return __atomic_add_fetch(augend, addend, __ATOMIC_SEQ_CST); } /** * @brief Atomically increment a uint32_t * * This function atomically adds 1 to the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value after increment. */ static inline uint32_t atomic_inc_uint32_t(uint32_t *var) { return atomic_add_uint32_t(var, 1); } /** * @brief Atomically subtract from a uint32_t * * This function atomically subtracts from the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value after subtraction. */ static inline uint32_t atomic_sub_uint32_t(uint32_t *var, uint32_t sub) { return __atomic_sub_fetch(var, sub, __ATOMIC_SEQ_CST); } /** * @brief Atomically decrement a uint32_t * * This function atomically subtracts 1 from the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value after decrement. */ static inline uint32_t atomic_dec_uint32_t(uint32_t *var) { return atomic_sub_uint32_t(var, 1); } /** * @brief Atomically add to an int16_t * * This function atomically adds to the supplied value. * * @param[in,out] augend Number to be added to * @param[in] addend Number to add * * @return The value after addition. */ static inline int16_t atomic_add_int16_t(int16_t *augend, int16_t addend) { return __atomic_add_fetch(augend, addend, __ATOMIC_SEQ_CST); } /** * @brief Atomically increment an int16_t * * This function atomically adds 1 to the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value after increment. */ static inline int16_t atomic_inc_int16_t(int16_t *var) { return atomic_add_int16_t(var, 1); } /** * @brief Atomically subtract from an int16_t * * This function atomically subtracts from the supplied value. * * @param[in,out] minuend Number to be subtracted from * @param[in] subtrahend Number to subtract * * @return The value after subtraction. */ static inline int16_t atomic_sub_int16_t(int16_t *minuend, int16_t subtrahend) { return __atomic_sub_fetch(minuend, subtrahend, __ATOMIC_SEQ_CST); } /** * @brief Atomically decrement an int16_t * * This function atomically subtracts 1 from the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value after decrement. */ static inline int16_t atomic_dec_int16_t(int16_t *var) { return atomic_sub_int16_t(var, 1); } /** * @brief Atomically add to a uint16_t * * This function atomically adds to the supplied value. * * @param[in,out] augend Number to be added to * @param[in] addend Number to add * * @return The value after addition. */ static inline uint16_t atomic_add_uint16_t(uint16_t *augend, uint16_t addend) { return __atomic_add_fetch(augend, addend, __ATOMIC_SEQ_CST); } /** * @brief Atomically increment a uint16_t * * This function atomically adds 1 to the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value after increment. */ static inline uint16_t atomic_inc_uint16_t(uint16_t *var) { return atomic_add_uint16_t(var, 1); } /** * @brief Atomically subtract from a uint16_t * * This function atomically subtracts from the supplied value. * * @param[in,out] minuend Number to be subtracted from * @param[in] subtrahend Number to subtract * * @return The value after subtraction. */ static inline uint16_t atomic_sub_uint16_t(uint16_t *minuend, uint16_t subtrahend) { return __atomic_sub_fetch(minuend, subtrahend, __ATOMIC_SEQ_CST); } /** * @brief Atomically decrement a uint16_t * * This function atomically subtracts 1 from the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value after decrement. */ static inline uint16_t atomic_dec_uint16_t(uint16_t *var) { return atomic_sub_uint16_t(var, 1); } /** * @brief Atomically add to an int8_t * * This function atomically adds to the supplied value. * * @param[in,out] augend Number to be added to * @param[in] addend Number to add * * @return The value after addition. */ static inline int8_t atomic_add_int8_t(int8_t *augend, int8_t addend) { return __atomic_add_fetch(augend, addend, __ATOMIC_SEQ_CST); } /** * @brief Atomically increment an int8_t * * This function atomically adds 1 to the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value after increment. */ static inline int8_t atomic_inc_int8_t(int8_t *var) { return atomic_add_int8_t(var, 1); } /** * @brief Atomically subtract from an int8_t * * This function atomically subtracts from the supplied value. * * @param[in,out] minuend Number to be subtracted from * @param[in] subtrahend Number to subtract * * @return The value after subtraction. */ static inline int8_t atomic_sub_int8_t(int8_t *minuend, int8_t subtrahend) { return __atomic_sub_fetch(minuend, subtrahend, __ATOMIC_SEQ_CST); } /** * @brief Atomically decrement an int8_t * * This function atomically subtracts 1 from the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value after decrement. */ static inline int8_t atomic_dec_int8_t(int8_t *var) { return atomic_sub_int8_t(var, 1); } /** * @brief Atomically add to a uint8_t * * This function atomically adds to the supplied value. * * @param[in,out] augend Number to be added to * @param[in] addend Number to add * * @return The value after addition. */ static inline uint8_t atomic_add_uint8_t(uint8_t *augend, int8_t addend) { return __atomic_add_fetch(augend, addend, __ATOMIC_SEQ_CST); } /** * @brief Atomically increment a uint8_t * * This function atomically adds 1 to the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value after increment. */ static inline uint8_t atomic_inc_uint8_t(uint8_t *var) { return atomic_add_uint8_t(var, 1); } /** * @brief Atomically subtract from a uint8_t * * This function atomically subtracts from the supplied value. * * @param[in,out] minuend Number to be subtracted from * @param[in] subtrahend Number to subtract * * @return The value after subtraction. */ static inline uint8_t atomic_sub_uint8_t(uint8_t *minuend, uint8_t subtrahend) { return __atomic_sub_fetch(minuend, subtrahend, __ATOMIC_SEQ_CST); } /** * @brief Atomically decrement a uint8_t * * This function atomically subtracts 1 from the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value after decrement. */ static inline uint8_t atomic_dec_uint8_t(uint8_t *var) { return atomic_sub_uint8_t(var, 1); } /** * @brief Atomically add to a size_t * * This function atomically adds to the supplied value. * * @param[in,out] augend Number to be added to * @param[in] addend Number to add * * @return The value after addition. */ static inline size_t atomic_add_size_t(size_t *augend, size_t addend) { return __atomic_add_fetch(augend, addend, __ATOMIC_SEQ_CST); } /** * @brief Atomically increment a size_t * * This function atomically adds 1 to the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value after increment. */ static inline size_t atomic_inc_size_t(size_t *var) { return atomic_add_size_t(var, 1); } /** * @brief Atomically subtract from a size_t * * This function atomically subtracts from the supplied value. * * @param[in,out] minuend Number to be subtracted from * @param[in] subtrahend Number to subtract * * @return The value after subtraction. */ static inline size_t atomic_sub_size_t(size_t *minuend, size_t subtrahend) { return __atomic_sub_fetch(minuend, subtrahend, __ATOMIC_SEQ_CST); } /** * @brief Atomically decrement a size_t * * This function atomically subtracts 1 from the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value after decrement. */ static inline size_t atomic_dec_size_t(size_t *var) { return atomic_sub_size_t(var, 1); } /* * Postaddition, postsubtraction, postincrement, postdecrement (return the * value before the operation, by analogy with the n++ postincrement * operator.) */ /** * @brief Atomically add to an int64_t * * This function atomically adds to the supplied value. * * @param[in,out] augend Number to be added to * @param[in] addend Number to add * * @return The value before addition. */ static inline int64_t atomic_postadd_int64_t(int64_t *augend, uint64_t addend) { return __atomic_fetch_add(augend, addend, __ATOMIC_SEQ_CST); } /** * @brief Atomically increment an int64_t * * This function atomically adds 1 to the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value before increment. */ static inline int64_t atomic_postinc_int64_t(int64_t *var) { return atomic_postadd_int64_t(var, 1); } /** * @brief Atomically subtract from an int64_t * * This function atomically subtracts from the supplied value. * * @param[in,out] minuend Number to be subtracted from * @param[in] subtrahend Number to subtract * * @return The value before subtraction. */ static inline int64_t atomic_postsub_int64_t(int64_t *minuend, int64_t subtrahend) { return __atomic_fetch_sub(minuend, subtrahend, __ATOMIC_SEQ_CST); } /** * @brief Atomically decrement an int64_t * * This function atomically subtracts 1 from the supplied value. * * @param[in,out] var Pointer to the variable to modify */ static inline int64_t atomic_postdec_int64_t(int64_t *var) { return atomic_postsub_int64_t(var, 1); } /** * @brief Atomically add to an int64_t * * This function atomically adds to the supplied value. * * @param[in,out] augend Number to be added to * @param[in] addend Number to add * * @return The value before addition. */ static inline uint64_t atomic_postadd_uint64_t(uint64_t *augend, uint64_t addend) { return __atomic_fetch_add(augend, addend, __ATOMIC_SEQ_CST); } /** * @brief Atomically increment a uint64_t * * This function atomically adds 1 to the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value before increment. */ static inline uint64_t atomic_postinc_uint64_t(uint64_t *var) { return atomic_postadd_uint64_t(var, 1); } /** * @brief Atomically subtract from a uint64_t * * This function atomically subtracts from the supplied value. * * @param[in,out] minuend Number to be subtracted from * @param[in] subtrahend Number to subtract * * @return The value before subtraction. */ static inline uint64_t atomic_postsub_uint64_t(uint64_t *minuend, uint64_t subtrahend) { return __atomic_fetch_sub(minuend, subtrahend, __ATOMIC_SEQ_CST); } /** * @brief Atomically decrement a uint64_t * * This function atomically subtracts 1 from the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value before decrement. */ static inline uint64_t atomic_postdec_uint64_t(uint64_t *var) { return atomic_postsub_uint64_t(var, 1); } /** * @brief Atomically add to an int32_t * * This function atomically adds to the supplied value. * * @param[in,out] augend Number to be added to * @param[in] addend Number to add * * @return The value before addition. */ static inline int32_t atomic_postadd_int32_t(int32_t *augend, int32_t addend) { return __atomic_fetch_add(augend, addend, __ATOMIC_SEQ_CST); } /** * @brief Atomically increment an int32_t * * This function atomically adds 1 to the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value before increment. */ static inline int32_t atomic_postinc_int32_t(int32_t *var) { return atomic_postadd_int32_t(var, 1); } /** * @brief Atomically subtract from an int32_t * * This function atomically subtracts from the supplied value. * * @param[in,out] minuend Number to be subtracted from * @param[in] subtrahend Number to subtract * * @return The value before subtraction. */ static inline int32_t atomic_postsub_int32_t(int32_t *minuend, int32_t subtrahend) { return __atomic_fetch_sub(minuend, subtrahend, __ATOMIC_SEQ_CST); } /** * @brief Atomically decrement an int32_t * * This function atomically subtracts 1 from the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value before decrement. */ static inline int32_t atomic_postdec_int32_t(int32_t *var) { return atomic_postsub_int32_t(var, 1); } /** * @brief Atomically add to a uint32_t * * This function atomically adds to the supplied value. * * @param[in,out] augend Number to be added to * @param[in] addend Number to add * * @return The value before addition. */ static inline uint32_t atomic_postadd_uint32_t(uint32_t *augend, uint32_t addend) { return __atomic_fetch_add(augend, addend, __ATOMIC_SEQ_CST); } /** * @brief Atomically increment a uint32_t * * This function atomically adds 1 to the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value before increment. */ static inline uint32_t atomic_postinc_uint32_t(uint32_t *var) { return atomic_postadd_uint32_t(var, 1); } /** * @brief Atomically subtract from a uint32_t * * This function atomically subtracts from the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value before subtraction. */ static inline uint32_t atomic_postsub_uint32_t(uint32_t *var, uint32_t sub) { return __atomic_fetch_sub(var, sub, __ATOMIC_SEQ_CST); } /** * @brief Atomically decrement a uint32_t * * This function atomically subtracts 1 from the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value before decrement. */ static inline uint32_t atomic_postdec_uint32_t(uint32_t *var) { return atomic_postsub_uint32_t(var, 1); } /** * @brief Atomically add to an int16_t * * This function atomically adds to the supplied value. * * @param[in,out] augend Number to be added to * @param[in] addend Number to add * * @return The value before addition. */ static inline int16_t atomic_postadd_int16_t(int16_t *augend, int16_t addend) { return __atomic_fetch_add(augend, addend, __ATOMIC_SEQ_CST); } /** * @brief Atomically increment an int16_t * * This function atomically adds 1 to the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value before increment. */ static inline int16_t atomic_postinc_int16_t(int16_t *var) { return atomic_postadd_int16_t(var, 1); } /** * @brief Atomically subtract from an int16_t * * This function atomically subtracts from the supplied value. * * @param[in,out] minuend Number to be subtracted from * @param[in] subtrahend Number to subtract * * @return The value before subtraction. */ static inline int16_t atomic_postsub_int16_t(int16_t *minuend, int16_t subtrahend) { return __atomic_fetch_sub(minuend, subtrahend, __ATOMIC_SEQ_CST); } /** * @brief Atomically decrement an int16_t * * This function atomically subtracts 1 from the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value before decrement. */ static inline int16_t atomic_postdec_int16_t(int16_t *var) { return atomic_postsub_int16_t(var, 1); } /** * @brief Atomically add to a uint16_t * * This function atomically adds to the supplied value. * * @param[in,out] augend Number to be added to * @param[in] addend Number to add * * @return The value before addition. */ static inline uint16_t atomic_postadd_uint16_t(uint16_t *augend, uint16_t addend) { return __atomic_fetch_add(augend, addend, __ATOMIC_SEQ_CST); } /** * @brief Atomically increment a uint16_t * * This function atomically adds 1 to the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value before increment. */ static inline uint16_t atomic_postinc_uint16_t(uint16_t *var) { return atomic_postadd_uint16_t(var, 1); } /** * @brief Atomically subtract from a uint16_t * * This function atomically subtracts from the supplied value. * * @param[in,out] minuend Number to be subtracted from * @param[in] subtrahend Number to subtract * * @return The value before subtraction. */ static inline uint16_t atomic_postsub_uint16_t(uint16_t *minuend, uint16_t subtrahend) { return __atomic_fetch_sub(minuend, subtrahend, __ATOMIC_SEQ_CST); } /** * @brief Atomically decrement a uint16_t * * This function atomically subtracts 1 from the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value before decrement. */ static inline uint16_t atomic_postdec_uint16_t(uint16_t *var) { return atomic_postsub_uint16_t(var, 1); } /** * @brief Atomically add to an int8_t * * This function atomically adds to the supplied value. * * @param[in,out] augend Number to be added to * @param[in] addend Number to add * * @return The value before addition. */ static inline int8_t atomic_postadd_int8_t(int8_t *augend, int8_t addend) { return __atomic_fetch_add(augend, addend, __ATOMIC_SEQ_CST); } /** * @brief Atomically increment an int8_t * * This function atomically adds 1 to the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value before increment. */ static inline int8_t atomic_postinc_int8_t(int8_t *var) { return atomic_postadd_int8_t(var, 1); } /** * @brief Atomically subtract from an int8_t * * This function atomically subtracts from the supplied value. * * @param[in,out] minuend Number to be subtracted from * @param[in] subtrahend Number to subtract * * @return The value before subtraction. */ static inline int8_t atomic_postsub_int8_t(int8_t *minuend, int8_t subtrahend) { return __atomic_fetch_sub(minuend, subtrahend, __ATOMIC_SEQ_CST); } /** * @brief Atomically decrement an int8_t * * This function atomically subtracts 1 from the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value before decrement. */ static inline int8_t atomic_postdec_int8_t(int8_t *var) { return atomic_postsub_int8_t(var, 1); } /** * @brief Atomically add to a uint8_t * * This function atomically adds to the supplied value. * * @param[in,out] augend Number to be added to * @param[in] addend Number to add * * @return The value before addition. */ static inline uint8_t atomic_postadd_uint8_t(uint8_t *augend, uint8_t addend) { return __atomic_fetch_add(augend, addend, __ATOMIC_SEQ_CST); } /** * @brief Atomically increment a uint8_t * * This function atomically adds 1 to the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value before increment. */ static inline uint8_t atomic_postinc_uint8_t(uint8_t *var) { return atomic_postadd_uint8_t(var, 1); } /** * @brief Atomically subtract from a uint8_t * * This function atomically subtracts from the supplied value. * * @param[in,out] minuend Number to be subtracted from * @param[in] subtrahend Number to subtract * * @return The value before subtraction. */ static inline uint8_t atomic_postsub_uint8_t(uint8_t *minuend, uint8_t subtrahend) { return __atomic_fetch_sub(minuend, subtrahend, __ATOMIC_SEQ_CST); } /** * @brief Atomically decrement a uint8_t * * This function atomically subtracts 1 from the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value before decrement. */ static inline uint8_t atomic_postdec_uint8_t(uint8_t *var) { return atomic_postsub_uint8_t(var, 1); } /** * @brief Atomically add to a size_t * * This function atomically adds to the supplied value. * * @param[in,out] augend Number to be added to * @param[in] addend Number to add * * @return The value before addition. */ static inline size_t atomic_postadd_size_t(size_t *augend, size_t addend) { return __atomic_fetch_add(augend, addend, __ATOMIC_SEQ_CST); } /** * @brief Atomically increment a size_t * * This function atomically adds 1 to the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value before increment. */ static inline size_t atomic_postinc_size_t(size_t *var) { return atomic_postadd_size_t(var, 1); } /** * @brief Atomically subtract from a size_t * * This function atomically subtracts from the supplied value. * * @param[in,out] minuend Number to be subtracted from * @param[in] subtrahend Number to subtract * * @return The value before subtraction. */ static inline size_t atomic_postsub_size_t(size_t *minuend, size_t subtrahend) { return __atomic_fetch_sub(minuend, subtrahend, __ATOMIC_SEQ_CST); } /** * @brief Atomically decrement a size_t * * This function atomically subtracts 1 from the supplied value. * * @param[in,out] var Pointer to the variable to modify * * @return The value before decrement. */ static inline size_t atomic_postdec_size_t(size_t *var) { return atomic_postsub_size_t(var, 1); } /* * Preclear and preset bits (return the value after the operation, by * analogy with the ++n preincrement operator.) */ /** * @brief Atomically clear bits in a uint64_t * * This function atomic clears the bits indicated. * * @param[in,out] var Pointer to the value to modify * @param[in] bits The bits to clear * * @return The value after clearing. */ static inline uint64_t atomic_clear_uint64_t_bits(uint64_t *var, uint64_t bits) { return __atomic_and_fetch(var, ~bits, __ATOMIC_SEQ_CST); } /** * @brief Atomically set bits in a uint64_t * * This function atomic clears the bits indicated. * * @param[in,out] var Pointer to the value to modify * @param[in] bits The bits to set * * @return The value after setting. */ static inline uint64_t atomic_set_uint64_t_bits(uint64_t *var, uint64_t bits) { return __atomic_or_fetch(var, bits, __ATOMIC_SEQ_CST); } /** * @brief Atomically clear bits in a uint32_t * * This function atomic clears the bits indicated. * * @param[in,out] var Pointer to the value to modify * @param[in] bits The bits to clear * * @return The value after clearing. */ static inline uint32_t atomic_clear_uint32_t_bits(uint32_t *var, uint32_t bits) { return __atomic_and_fetch(var, ~bits, __ATOMIC_SEQ_CST); } /** * @brief Atomically set bits in a uint32_t * * This function atomic clears the bits indicated. * * @param[in,out] var Pointer to the value to modify * @param[in] bits The bits to set * * @return The value after setting. */ static inline uint32_t atomic_set_uint32_t_bits(uint32_t *var, uint32_t bits) { return __atomic_or_fetch(var, bits, __ATOMIC_SEQ_CST); } /** * @brief Atomically clear bits in a uint16_t * * This function atomic clears the bits indicated. * * @param[in,out] var Pointer to the value to modify * @param[in] bits The bits to clear * * @return The value after clearing. */ static inline uint16_t atomic_clear_uint16_t_bits(uint16_t *var, uint16_t bits) { return __atomic_and_fetch(var, ~bits, __ATOMIC_SEQ_CST); } /** * @brief Atomically set bits in a uint16_t * * This function atomic clears the bits indicated. * * @param[in,out] var Pointer to the value to modify * @param[in] bits The bits to set * * @return The value after setting. */ static inline uint16_t atomic_set_uint16_t_bits(uint16_t *var, uint16_t bits) { return __atomic_or_fetch(var, bits, __ATOMIC_SEQ_CST); } /** * @brief Atomically clear bits in a uint8_t * * This function atomic clears the bits indicated. * * @param[in,out] var Pointer to the value to modify * @param[in] bits The bits to clear * * @return The value after clearing. */ static inline uint8_t atomic_clear_uint8_t_bits(uint8_t *var, uint8_t bits) { return __atomic_and_fetch(var, ~bits, __ATOMIC_SEQ_CST); } /** * @brief Atomically set bits in a uint8_t * * This function atomic clears the bits indicated. * * @param[in,out] var Pointer to the value to modify * @param[in] bits The bits to set * * @return The value after setting. */ static inline uint8_t atomic_set_uint8_t_bits(uint8_t *var, uint8_t bits) { return __atomic_or_fetch(var, bits, __ATOMIC_SEQ_CST); } /* * Postclear and postset bits (return the value before the operation, * by analogy with the n++ postincrement operator.) */ /** * @brief Atomically clear bits in a uint64_t * * This function atomic clears the bits indicated. * * @param[in,out] var Pointer to the value to modify * @param[in] bits The bits to clear * * @return The value before clearing. */ static inline uint64_t atomic_postclear_uint64_t_bits(uint64_t *var, uint64_t bits) { return __atomic_fetch_and(var, ~bits, __ATOMIC_SEQ_CST); } /** * @brief Atomically set bits in a uint64_t * * This function atomic clears the bits indicated. * * @param[in,out] var Pointer to the value to modify * @param[in] bits The bits to set * * @return The value before setting. */ static inline uint64_t atomic_postset_uint64_t_bits(uint64_t *var, uint64_t bits) { return __atomic_fetch_or(var, bits, __ATOMIC_SEQ_CST); } /** * @brief Atomically clear bits in a uint32_t * * This function atomic clears the bits indicated. * * @param[in,out] var Pointer to the value to modify * @param[in] bits The bits to clear * * @return The value before clearing. */ static inline uint32_t atomic_postclear_uint32_t_bits(uint32_t *var, uint32_t bits) { return __atomic_fetch_and(var, ~bits, __ATOMIC_SEQ_CST); } /** * @brief Atomically set bits in a uint32_t * * This function atomic clears the bits indicated. * * @param[in,out] var Pointer to the value to modify * @param[in] bits The bits to set * * @return The value before setting. */ static inline uint32_t atomic_postset_uint32_t_bits(uint32_t *var, uint32_t bits) { return __atomic_fetch_or(var, bits, __ATOMIC_SEQ_CST); } /** * @brief Atomically clear bits in a uint16_t * * This function atomic clears the bits indicated. * * @param[in,out] var Pointer to the value to modify * @param[in] bits The bits to clear * * @return The value before clearing. */ static inline uint16_t atomic_postclear_uint16_t_bits(uint16_t *var, uint16_t bits) { return __atomic_fetch_and(var, ~bits, __ATOMIC_SEQ_CST); } /** * @brief Atomically set bits in a uint16_t * * This function atomic clears the bits indicated. * * @param[in,out] var Pointer to the value to modify * @param[in] bits The bits to set * * @return The value before setting. */ static inline uint16_t atomic_postset_uint16_t_bits(uint16_t *var, uint16_t bits) { return __atomic_fetch_or(var, bits, __ATOMIC_SEQ_CST); } /** * @brief Atomically clear bits in a uint8_t * * This function atomic clears the bits indicated. * * @param[in,out] var Pointer to the value to modify * @param[in] bits The bits to clear * * @return The value before clearing. */ static inline uint8_t atomic_postclear_uint8_t_bits(uint8_t *var, uint8_t bits) { return __atomic_fetch_and(var, ~bits, __ATOMIC_SEQ_CST); } /** * @brief Atomically set bits in a uint8_t * * This function atomic clears the bits indicated. * * @param[in,out] var Pointer to the value to modify * @param[in] bits The bits to set * * @return The value before setting. */ static inline uint8_t atomic_postset_uint8_t_bits(uint8_t *var, uint8_t bits) { return __atomic_fetch_or(var, bits, __ATOMIC_SEQ_CST); } /* * Fetch and store */ /** * @brief Atomically fetch a size_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to fetch * * @return the value pointed to by var. */ static inline size_t atomic_fetch_size_t(size_t *var) { return __atomic_load_n(var, __ATOMIC_SEQ_CST); } /** * @brief Atomically store a size_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to modify * @param[in] val The value to store */ static inline void atomic_store_size_t(size_t *var, size_t val) { __atomic_store_n(var, val, __ATOMIC_SEQ_CST); } /** * @brief Atomically fetch a ptrdiff_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to fetch * * @return the value pointed to by var. */ static inline ptrdiff_t atomic_fetch_ptrdiff_t(ptrdiff_t *var) { return __atomic_load_n(var, __ATOMIC_SEQ_CST); } /** * @brief Atomically store a ptrdiff_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to modify * @param[in] val The value to store */ static inline void atomic_store_ptrdiff_t(ptrdiff_t *var, ptrdiff_t val) { __atomic_store_n(var, val, __ATOMIC_SEQ_CST); } /** * @brief Atomically fetch a time_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to fetch * * @return the value pointed to by var. */ static inline time_t atomic_fetch_time_t(time_t *var) { return __atomic_load_n(var, __ATOMIC_SEQ_CST); } /** * @brief Atomically store a time_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to modify * @param[in] val The value to store */ static inline void atomic_store_time_t(time_t *var, time_t val) { __atomic_store_n(var, val, __ATOMIC_SEQ_CST); } /** * @brief Atomically fetch a uintptr_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to fetch * * @return the value pointed to by var. */ static inline uintptr_t atomic_fetch_uintptr_t(uintptr_t *var) { return __atomic_load_n(var, __ATOMIC_SEQ_CST); } /** * @brief Atomically store a uintptr_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to modify * @param[in] val The value to store */ static inline void atomic_store_uintptr_t(uintptr_t *var, uintptr_t val) { __atomic_store_n(var, val, __ATOMIC_SEQ_CST); } /** * @brief Atomically fetch a void * * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to fetch * * @return the value pointed to by var. */ static inline void *atomic_fetch_voidptr(void **var) { return __atomic_load_n(var, __ATOMIC_SEQ_CST); } /** * @brief Atomically store a void * * * This function atomically stores the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to modify * @param[in] val The value to store */ static inline void atomic_store_voidptr(void **var, void *val) { __atomic_store_n(var, val, __ATOMIC_SEQ_CST); } /** * @brief Atomically fetch an int64_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to fetch * * @return the value pointed to by var. */ static inline int64_t atomic_fetch_int64_t(int64_t *var) { return __atomic_load_n(var, __ATOMIC_SEQ_CST); } /** * @brief Atomically add to an int64_t unless it has a flag value * * This function atomically adds to the supplied value unless the * variable is equal to the flag value. * * @param[in,out] var Number to be added to * @param[in] addend Number to add * @param[in] unless The flag value * * @return true if the addition occurred */ static inline bool atomic_add_unless_int64_t(int64_t *var, int64_t addend, int64_t unless) { int64_t cur, newv; bool changed; cur = atomic_fetch_int64_t(var); do { if (cur == unless) return false; newv = cur + addend; changed = __atomic_compare_exchange_n(var, &cur, newv, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); } while (!changed); return true; } /** * @brief Atomically store an int64_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to modify * @param[in] val The value to store */ static inline void atomic_store_int64_t(int64_t *var, int64_t val) { __atomic_store_n(var, val, __ATOMIC_SEQ_CST); } /** * @brief Atomically fetch a uint64_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to fetch * * @return the value pointed to by var. */ static inline uint64_t atomic_fetch_uint64_t(uint64_t *var) { return __atomic_load_n(var, __ATOMIC_SEQ_CST); } /** * @brief Atomically add to an uint64_t unless it has a flag value * * This function atomically adds to the supplied value unless the * variable is equal to the flag value. * * @param[in,out] var Number to be added to * @param[in] addend Number to add * @param[in] unless The flag value * * @return true if the addition occurred */ static inline bool atomic_add_unless_uint64_t(uint64_t *var, uint64_t addend, uint64_t unless) { uint64_t cur, newv; bool changed; cur = atomic_fetch_uint64_t(var); do { if (cur == unless) return false; newv = cur + addend; changed = __atomic_compare_exchange_n(var, &cur, newv, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); } while (!changed); return true; } /** * @brief Atomically store a uint64_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to modify * @param[in] val The value to store */ static inline void atomic_store_uint64_t(uint64_t *var, uint64_t val) { __atomic_store_n(var, val, __ATOMIC_SEQ_CST); } /** * @brief Atomically fetch an int32_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to fetch * * @return the value pointed to by var. */ static inline int32_t atomic_fetch_int32_t(int32_t *var) { return __atomic_load_n(var, __ATOMIC_SEQ_CST); } /** * @brief Atomically add to an int32_t unless it has a flag value * * This function atomically adds to the supplied value unless the * variable is equal to the flag value. * * @param[in,out] var Number to be added to * @param[in] addend Number to add * @param[in] unless The flag value * * @return true if the addition occurred */ static inline bool atomic_add_unless_int32_t(int32_t *var, int32_t addend, int32_t unless) { int32_t cur, newv; bool changed; cur = atomic_fetch_int32_t(var); do { if (cur == unless) return false; newv = cur + addend; changed = __atomic_compare_exchange_n(var, &cur, newv, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); } while (!changed); return true; } /** * @brief Atomically increment an int32_t unless it is 0 * * This function atomically adds to the supplied value unless the * variable is equal to the flag value. * * @param[in,out] var Number to be incremented * * @return the new value, if 0, then it failed */ static inline int32_t atomic_inc_unless_0_int32_t(int32_t *var) { int32_t cur, newv; bool changed; cur = atomic_fetch_int32_t(var); do { if (cur == 0) return 0; newv = cur + 1; changed = __atomic_compare_exchange_n(var, &cur, newv, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); } while (!changed); return newv; } /** * @brief Atomically store an int32_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to modify * @param[in] val The value to store */ static inline void atomic_store_int32_t(int32_t *var, int32_t val) { __atomic_store_n(var, val, __ATOMIC_SEQ_CST); } /** * @brief Atomically fetch a uint32_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to fetch * * @return the value pointed to by var. */ static inline uint32_t atomic_fetch_uint32_t(uint32_t *var) { return __atomic_load_n(var, __ATOMIC_SEQ_CST); } /** * @brief Atomically add to an uint32_t unless it has a flag value * * This function atomically adds to the supplied value unless the * variable is equal to the flag value. * * @param[in,out] var Number to be added to * @param[in] addend Number to add * @param[in] unless The flag value * * @return true if the addition occurred */ static inline bool atomic_add_unless_uint32_t(uint32_t *var, uint32_t addend, uint32_t unless) { uint32_t cur, newv; bool changed; cur = atomic_fetch_uint32_t(var); do { if (cur == unless) return false; newv = cur + addend; changed = __atomic_compare_exchange_n(var, &cur, newv, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); } while (!changed); return true; } /** * @brief Atomically store a uint32_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to modify * @param[in] val The value to store */ static inline void atomic_store_uint32_t(uint32_t *var, uint32_t val) { __atomic_store_n(var, val, __ATOMIC_SEQ_CST); } /** * @brief Atomically fetch an int16_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to fetch * * @return the value pointed to by var. */ static inline int16_t atomic_fetch_int16_t(int16_t *var) { return __atomic_load_n(var, __ATOMIC_SEQ_CST); } /** * @brief Atomically store an int16_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to modify * @param[in] val The value to store */ static inline void atomic_store_int16_t(int16_t *var, int16_t val) { __atomic_store_n(var, val, __ATOMIC_SEQ_CST); } /** * @brief Atomically fetch a uint16_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to fetch * * @return the value pointed to by var. */ static inline uint16_t atomic_fetch_uint16_t(uint16_t *var) { return __atomic_load_n(var, __ATOMIC_SEQ_CST); } /** * @brief Atomically store a uint16_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to modify * @param[in] val The value to store */ static inline void atomic_store_uint16_t(uint16_t *var, uint16_t val) { __atomic_store_n(var, val, __ATOMIC_SEQ_CST); } /** * @brief Atomically fetch a int8_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to fetch * * @return the value pointed to by var. */ static inline int8_t atomic_fetch_int8_t(int8_t *var) { return __atomic_load_n(var, __ATOMIC_SEQ_CST); } /** * @brief Atomically store a int8_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to modify * @param[in] val The value to store */ static inline void atomic_store_int8_t(int8_t *var, int8_t val) { __atomic_store_n(var, val, __ATOMIC_SEQ_CST); } /** * @brief Atomically fetch a uint8_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to fetch * * @return the value pointed to by var. */ static inline uint8_t atomic_fetch_uint8_t(uint8_t *var) { return __atomic_load_n(var, __ATOMIC_SEQ_CST); } /** * @brief Atomically store a uint8_t * * This function atomically fetches the value indicated by the * supplied pointer. * * @param[in,out] var Pointer to the variable to modify * @param[in] val The value to store */ static inline void atomic_store_uint8_t(uint8_t *var, uint8_t val) { __atomic_store_n(var, val, __ATOMIC_SEQ_CST); } #endif /* !_ABSTRACT_ATOMIC_H */ nfs-ganesha-6.5/src/include/abstract_mem.h000066400000000000000000000274101473756622300206250ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * Copyright © Linux box Corporation, 2012 * Author: Adam C. Emerson * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file abstract_mem.h * @author Adam C. Emerson * @brief Abstract memory shims to allow swapping out allocators * * This file's purpose is to allow us to easily replace the memory * allocator used by Ganesha. Further, it provides a pool abstraction * that may be implemented in terms of the normal allocator that may * be expanded at a later date. These are intended to be thin * wrappers, but conditionally compiled trace information could be * added. */ #ifndef ABSTRACT_MEM_H #define ABSTRACT_MEM_H #include #include #include #include "log.h" /** * @page GeneralAllocator General Allocator Shim * * These functions provide an interface akin to the standard libc * allocation functions. Currently they call the functions malloc, * free, and so forth, with changes in functionality being provided by * linking in alternate allocator libraries (tcmalloc and jemalloc, at * present.) So long as the interface remains the same, these * functions can be switched out using ifdef for versions that do more * memory tracking or that call allocators with other names. */ /** * @brief Allocate memory * * This function allocates a block of memory no less than the given * size. The block of memory allocated must be released with gsh_free. * * This function aborts if no memory is available. * * @param[in] n Number of bytes to allocate * @param[in] file Calling source file * @param[in] line Calling source line * @param[in] function Calling source function * * @return Pointer to a block of memory. */ static inline void *gsh_malloc__(size_t n, const char *file, int line, const char *function) { void *p = malloc(n); if (p == NULL) { LogMallocFailure(file, line, function, "gsh_malloc"); abort(); } return p; } #define gsh_malloc(n) \ ({ \ void *p_ = malloc(n); \ if (p_ == NULL) { \ abort(); \ } \ p_; \ }) /** * @brief Allocate aligned memory * * This function allocates a block of memory to the given alignment. * Failure may indicate either insufficient memory or an invalid * alignment. * * @param[in] a Block alignment * @param[in] n Number of bytes to allocate * @param[in] file Calling source file * @param[in] line Calling source line * @param[in] function Calling source function * * @return Pointer to a block of memory or NULL. */ static inline void *gsh_malloc_aligned__(size_t a, size_t n, const char *file, int line, const char *function) { void *p; #ifdef __APPLE__ p = valloc(n); #else if (posix_memalign(&p, a, n) != 0) p = NULL; #endif if (p == NULL) { LogMallocFailure(file, line, function, "gsh_malloc_aligned"); abort(); } return p; } #define gsh_malloc_aligned(a, n) \ ({ \ void *p_; \ if (posix_memalign(&p_, a, n) != 0) { \ abort(); \ } \ p_; \ }) /** * @brief Allocate zeroed memory * * This function allocates a block of memory that is guaranteed to be * zeroed. The block of memory allocated must be released with gsh_free. * * This function aborts if no memory is available. * * @param[in] n Number of objects in block * @param[in] s Size of object * * @return Pointer to a block of zeroed memory. */ static inline void *gsh_calloc__(size_t n, size_t s, const char *file, int line, const char *function) { void *p = calloc(n, s); if (p == NULL) { LogMallocFailure(file, line, function, "gsh_calloc"); abort(); } return p; } #define gsh_calloc(n, s) \ ({ \ void *p_ = calloc(n, s); \ if (p_ == NULL) { \ abort(); \ } \ p_; \ }) /** * @brief Resize a block of memory * * This function resizes the buffer indicated by the supplied pointer * to the given size. The block may be moved in this process. On * failure, the original block is retained at its original address. * * This function aborts if no memory is available to resize. * * @param[in] p Block of memory to resize * @param[in] n New size * @param[in] file Calling source file * @param[in] line Calling source line * @param[in] function Calling source function * * @return Pointer to the address of the resized block. */ static inline void *gsh_realloc__(void *p, size_t n, const char *file, int line, const char *function) { void *p2 = realloc(p, n); if (n != 0 && p2 == NULL) { LogMallocFailure(file, line, function, "gsh_realloc"); abort(); } return p2; } #define gsh_realloc(p, n) \ ({ \ void *p2_ = realloc(p, n); \ if (n != 0 && p2_ == NULL) { \ abort(); \ } \ p2_; \ }) #define gsh_strdup(s) \ ({ \ char *p_ = strdup(s); \ if (p_ == NULL) { \ abort(); \ } \ p_; \ }) #define gsh_strldup(s, l, n) \ ({ \ char *p_ = (char *)gsh_malloc(l + 1); \ memcpy(p_, s, l); \ p_[l] = '\0'; \ *n = l + 1; \ p_; \ }) #if defined(__GLIBC__) && defined(_GNU_SOURCE) #define gsh_strdupa(src) strdupa(src) #else #define gsh_strdupa(src) \ ({ \ char *dest = alloca(strlen(src) + 1); \ strcpy(dest, src); \ dest; \ }) #endif #define gsh_memdup(s, l) \ ({ \ void *p_ = gsh_malloc(l); \ memcpy(p_, s, l); \ p_; \ }) /** * @brief Free a block of memory * * This function frees a block of memory allocated with gsh_malloc, * gsh_malloc_aligned, gsh_calloc, gsh_realloc, or gsh_strdup. * * @param[in] p Block of memory to free. */ static inline void gsh_free(void *p) { free(p); } /** * @brief Free a block of memory with size * * This function exists to be passed to TIRPC when setting * allocators. It should not be used by anyone else. New shim layers * should not redefine it. * * @param[in] p Block of memory to free. * @param[in] n Size of block (unused) */ static inline void gsh_free_size(void *p, size_t n __attribute__((unused))) { free(p); } /** * @brief Type representing a pool * * This type represents a memory pool. it should be treated, by all * callers, as a completely abstract type. The pointer should only be * stored or passed to pool functions. The pointer should never be * referenced. No assumptions about the size of the pointed-to type * should be made. * * This allows for flexible growth in the future. */ typedef struct pool { char *name; /*< The name of the pool */ size_t object_size; /*< The size of the objects created */ } pool_t; /** * @brief Create a basic object pool * * This function creates a new object pool, given a name, object size, * constructor and destructor. * * This particular implementation throws the name away, but other * implementations that do tracking or keep counts of allocated or * de-allocated objects will likely wish to use it in log messages. * * This initializer function is expected to abort if it fails. * * @param[in] name The name of this pool * @param[in] object_size The size of objects to allocate * @param[in] file Calling source file * @param[in] line Calling source line * @param[in] function Calling source function * * @return A pointer to the pool object. This pointer must not be * dereferenced. It may be stored or supplied as an argument * to the other pool functions. It must not be supplied as an * argument to gsh_free, rather it must be disposed of with * pool_destroy. */ static inline pool_t *pool_basic_init(const char *name, size_t object_size) { pool_t *pool = (pool_t *)gsh_malloc(sizeof(pool_t)); pool->object_size = object_size; if (name) pool->name = gsh_strdup(name); else pool->name = NULL; return pool; } /** * @brief Destroy a memory pool * * This function destroys a memory pool. All objects must be returned * to the pool before this function is called. * * @param[in] pool The pool to be destroyed. */ static inline void pool_destroy(pool_t *pool) { gsh_free(pool->name); gsh_free(pool); } /** * @brief Allocate an object from a pool * * This function allocates a single object from the pool and returns a * pointer to it. If a constructor was specified at pool creation, it * is called on that pointer. This function must be thread safe. If * the underlying pool abstraction requires a lock, this function must * take and release it. * * This function returns void pointers. Programmers who wish for more * type safety can easily create static inline wrappers (alloc_client * or similar) to return pointers of a specific type (and omitting the * pool parameter). * * This function aborts if no memory is available. * * @param[in] pool The pool from which to allocate * @param[in] file Calling source file * @param[in] line Calling source line * @param[in] function Calling source function * * @return A pointer to the allocated pool item. */ #define pool_alloc(pool) gsh_calloc(1, (pool)->object_size) /** * @brief Return an entry to a pool * * This function returns a single object to the pool. If a destructor * was defined at pool creation time, it is called before the object * is freed. This function must be thread-safe. If the underlying * pool abstract requires a lock, this function must take and release * it. * * @param[in] pool Pool to which to return the object * @param[in] object The object to return. This is a void pointer. * Programmers wishing more type safety could create * a static inline wrapper taking an object of a * specific type (and omitting the pool parameter.) */ static inline void pool_free(pool_t *pool, void *object) { gsh_free(object); } static inline char *gsh_concat(const char *p1, const char *p2) { size_t len1 = strlen(p1); size_t len2 = strlen(p2); char *path = (char *)gsh_malloc(len1 + len2 + 1); memcpy(path, p1, len1); memcpy(path + len1, p2, len2 + 1); return path; } static inline char *gsh_concat_sep(const char *p1, char sep, const char *p2) { size_t len1 = strlen(p1); size_t len2 = strlen(p2); char *path = (char *)gsh_malloc(len1 + 1 + len2 + 1); memcpy(path, p1, len1); path[len1] = sep; memcpy(path + len1 + 1, p2, len2 + 1); return path; } #endif /* ABSTRACT_MEM_H */ nfs-ganesha-6.5/src/include/atomic_utils.h000066400000000000000000000061411473756622300206560ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright © 2012 Linux Box Corporation * * This program 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 3 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 * 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 * * --------------------------------------- */ #ifndef ATOMIC_UTILS_H #define ATOMIC_UTILS_H #include "common_utils.h" #include "abstract_atomic.h" /** * @brief Decrement an int64_t refcounter and take mutex if zero. * * @param[in,out] var The refcounter * @param[in,out] _mtx The mutex to acquire * * @return true if the counter was decremented to zero and the mutex locked. */ static inline bool PTHREAD_MUTEX_dec_int64_t_and_lock(int64_t *var, pthread_mutex_t *lock) { if (atomic_add_unless_int64_t(var, -1, 1)) return false; PTHREAD_MUTEX_lock(lock); if (atomic_add_int64_t(var, -1) == 0) return true; PTHREAD_MUTEX_unlock(lock); return false; } /** * @brief Decrement an uint64_t refcounter and take mutex if zero. * * @param[in,out] var The refcounter * @param[in,out] _mtx The mutex to acquire * * @return true if the counter was decremented to zero and the mutex locked. */ static inline bool PTHREAD_MUTEX_dec_uint64_t_and_lock(uint64_t *var, pthread_mutex_t *lock) { if (atomic_add_unless_uint64_t(var, -1, 1)) return false; PTHREAD_MUTEX_lock(lock); if (atomic_add_uint64_t(var, -1) == 0) return true; PTHREAD_MUTEX_unlock(lock); return false; } /** * @brief Decrement an int32_t refcounter and take mutex if zero. * * @param[in,out] var The refcounter * @param[in,out] _mtx The mutex to acquire * * @return true if the counter was decremented to zero and the mutex locked. */ static inline bool PTHREAD_MUTEX_dec_int32_t_and_lock(int32_t *var, pthread_mutex_t *lock) { if (atomic_add_unless_int32_t(var, -1, 1)) return false; PTHREAD_MUTEX_lock(lock); if (atomic_add_int32_t(var, -1) == 0) return true; PTHREAD_MUTEX_unlock(lock); return false; } /** * @brief Decrement an uint32_t refcounter and take mutex if zero. * * @param[in,out] var The refcounter * @param[in,out] _mtx The mutex to acquire * * @return true if the counter was decremented to zero and the mutex locked. */ static inline bool PTHREAD_MUTEX_dec_uint32_t_and_lock(uint32_t *var, pthread_mutex_t *lock) { if (atomic_add_unless_uint32_t(var, -1, 1)) return false; PTHREAD_MUTEX_lock(lock); if (atomic_add_uint32_t(var, -1) == 0) return true; PTHREAD_MUTEX_unlock(lock); return false; } #endif /* !ATOMIC_UTILS_H */ nfs-ganesha-6.5/src/include/avltree.h000066400000000000000000000236451473756622300176340ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * libtree.h - this file is part of Libtree. * * Copyright (C) 2010-2014 Franck Bui-Huu * * This file is part of libtree which 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 LICENSE file for license rights and limitations. */ #ifndef _LIBTREE_H #define _LIBTREE_H #include #include /* * The definition has been stolen from the Linux kernel. */ #ifdef __GNUC__ #define bstree_container_of(node, type, member) \ ({ \ const struct bstree_node *__mptr = (node); \ (type *)((char *)__mptr - offsetof(type, member)); \ }) #define rbtree_container_of(node, type, member) \ ({ \ const struct rbtree_node *__mptr = (node); \ (type *)((char *)__mptr - offsetof(type, member)); \ }) #define avltree_container_of(node, type, member) \ ({ \ const struct avltree_node *__mptr = (node); \ (type *)((char *)__mptr - offsetof(type, member)); \ }) #define splaytree_container_of(node, type, member) \ ({ \ const struct splaytree_node *__mptr = (node); \ (type *)((char *)__mptr - offsetof(type, member)); \ }) #else #define bstree_container_of(node, type, member) \ ((type *)((char *)(node) - offsetof(type, member))) #define rbtree_container_of(node, type, member) \ ((type *)((char *)(node) - offsetof(type, member))) #define avltree_container_of(node, type, member) \ ((type *)((char *)(node) - offsetof(type, member))) #define splaytree_container_of(node, type, member) \ ((type *)((char *)(node) - offsetof(type, member))) #endif /* __GNUC__ */ /* * Threaded binary search tree */ #ifdef UINTPTR_MAX struct bstree_node { uintptr_t left, right; } __attribute__((aligned(2))); #else struct bstree_node { struct bstree_node *left, *right; unsigned left_is_thread : 1; unsigned right_is_thread : 1; }; #endif /* UINTPTR_MAX */ typedef int (*bstree_cmp_fn_t)(const struct bstree_node *, const struct bstree_node *); struct bstree { struct bstree_node *root; bstree_cmp_fn_t cmp_fn; struct bstree_node *first, *last; uint64_t reserved[4]; }; struct bstree_node *bstree_first(const struct bstree *tree); struct bstree_node *bstree_last(const struct bstree *tree); struct bstree_node *bstree_next(const struct bstree_node *node); struct bstree_node *bstree_prev(const struct bstree_node *node); struct bstree_node *bstree_lookup(const struct bstree_node *key, const struct bstree *tree); struct bstree_node *bstree_insert(struct bstree_node *node, struct bstree *tree); void bstree_remove(struct bstree_node *node, struct bstree *tree); void bstree_replace(struct bstree_node *old, struct bstree_node *newe, struct bstree *tree); int bstree_init(struct bstree *tree, bstree_cmp_fn_t cmp, unsigned long flags); /* * Red-black tree */ enum rb_color { RB_BLACK, RB_RED, }; #ifdef UINTPTR_MAX struct rbtree_node { struct rbtree_node *left, *right; uintptr_t parent; } __attribute__((aligned(2))); #else struct rbtree_node { struct rbtree_node *left, *right; struct rbtree_node *parent; enum rb_color color; }; #endif /* UINTPTR_MAX */ typedef int (*rbtree_cmp_fn_t)(const struct rbtree_node *, const struct rbtree_node *); struct rbtree { struct rbtree_node *root; rbtree_cmp_fn_t cmp_fn; struct rbtree_node *first, *last; uint64_t reserved[4]; }; struct rbtree_node *rbtree_first(const struct rbtree *tree); struct rbtree_node *rbtree_last(const struct rbtree *tree); struct rbtree_node *rbtree_next(const struct rbtree_node *node); struct rbtree_node *rbtree_prev(const struct rbtree_node *node); struct rbtree_node *rbtree_lookup(const struct rbtree_node *key, const struct rbtree *tree); struct rbtree_node *rbtree_insert(struct rbtree_node *node, struct rbtree *tree); void rbtree_remove(struct rbtree_node *node, struct rbtree *tree); void rbtree_replace(struct rbtree_node *old, struct rbtree_node *newe, struct rbtree *tree); int rbtree_init(struct rbtree *tree, rbtree_cmp_fn_t cmp, unsigned long flags); /* * AVL tree */ #if defined UINTPTR_MAX && UINTPTR_MAX == UINT64_MAX struct avltree_node { struct avltree_node *left, *right; uintptr_t parent; /* balance factor [0:4] */ } __attribute__((aligned(8))); static inline signed int get_balance(struct avltree_node *node) { return (int)(node->parent & 7) - 2; } #else struct avltree_node { struct avltree_node *left, *right; struct avltree_node *parent; signed balance : 3; /* balance factor [-2:+2] */ }; static inline signed int get_balance(struct avltree_node *node) { return node->balance; } #endif typedef int (*avltree_cmp_fn_t)(const struct avltree_node *, const struct avltree_node *); struct avltree { struct avltree_node *root; avltree_cmp_fn_t cmp_fn; int height; struct avltree_node *first, *last; uint64_t size; #if 0 uint64_t reserved[4]; #endif }; /** * @brief Perform a lookup in an AVL tree, returning useful bits for * subsequent inser. * * 'pparent', 'unbalanced' and 'is_left' are only used for * insertions. Normally GCC will notice this and get rid of them for * lookups. * * @param[in] key Key to look for * @param[in] tree AVL tree to look in * @param[in,out] pparent Parent of key * @param[in,out] unbalanced Unbalanced parent * @param[in,out] is_left True if key would be to left of parent * @param[in] cmp_fn Comparison function to use * * @returns The node found if any */ static inline struct avltree_node * avltree_do_lookup(const struct avltree_node *key, const struct avltree *tree, struct avltree_node **pparent, struct avltree_node **unbalanced, int *is_left, avltree_cmp_fn_t cmp_fn) { struct avltree_node *node = tree->root; int res = 0; *pparent = NULL; *unbalanced = node; *is_left = 0; while (node) { if (get_balance(node) != 0) *unbalanced = node; res = cmp_fn(node, key); if (res == 0) return node; *pparent = node; *is_left = res > 0; if (*is_left) node = node->left; else node = node->right; } return NULL; } static inline struct avltree_node * avltree_inline_lookup(const struct avltree_node *key, const struct avltree *tree, avltree_cmp_fn_t cmp_fn) { struct avltree_node *parent, *unbalanced; int is_left; return avltree_do_lookup(key, tree, &parent, &unbalanced, &is_left, cmp_fn); } static inline struct avltree_node * avltree_lookup(const struct avltree_node *key, const struct avltree *tree) { return avltree_inline_lookup(key, tree, tree->cmp_fn); } void avltree_do_insert(struct avltree_node *node, struct avltree *tree, struct avltree_node *parent, struct avltree_node *unbalanced, int is_left); static inline struct avltree_node * avltree_inline_insert(struct avltree_node *node, struct avltree *tree, avltree_cmp_fn_t cmp_fn) { struct avltree_node *found, *parent, *unbalanced; int is_left; found = avltree_do_lookup(node, tree, &parent, &unbalanced, &is_left, cmp_fn); if (found) return found; avltree_do_insert(node, tree, parent, unbalanced, is_left); return NULL; } static inline struct avltree_node *avltree_insert(struct avltree_node *node, struct avltree *tree) { return avltree_inline_insert(node, tree, tree->cmp_fn); } static inline struct avltree_node *avltree_first(const struct avltree *tree) { return tree->first; } static inline struct avltree_node *avltree_last(const struct avltree *tree) { return tree->last; } struct avltree_node *avltree_next(const struct avltree_node *node); struct avltree_node *avltree_prev(const struct avltree_node *node); uint64_t avltree_size(const struct avltree *tree); struct avltree_node *avltree_inf(const struct avltree_node *key, const struct avltree *tree); struct avltree_node *avltree_sup(const struct avltree_node *key, const struct avltree *tree); void avltree_remove(struct avltree_node *node, struct avltree *tree); void avltree_replace(struct avltree_node *old, struct avltree_node *newe, struct avltree *tree); int avltree_init(struct avltree *tree, avltree_cmp_fn_t cmp, unsigned long flags); /* * Splay tree */ #ifdef UINTPTR_MAX struct splaytree_node { uintptr_t left, right; } __attribute__((aligned(2))); #else struct splaytree_node { struct splaytree_node *left, *right; unsigned left_is_thread : 1; unsigned right_is_thread : 1; }; #endif typedef int (*splaytree_cmp_fn_t)(const struct splaytree_node *, const struct splaytree_node *); struct splaytree { struct splaytree_node *root; struct splaytree_node *first, *last; splaytree_cmp_fn_t cmp_fn; uint64_t reserved[4]; }; struct splaytree_node *splaytree_first(const struct splaytree *tree); struct splaytree_node *splaytree_last(const struct splaytree *tree); struct splaytree_node *splaytree_next(const struct splaytree_node *node); struct splaytree_node *splaytree_prev(const struct splaytree_node *node); struct splaytree_node *splaytree_lookup(const struct splaytree_node *key, struct splaytree *tree); struct splaytree_node *splaytree_insert(struct splaytree_node *node, struct splaytree *tree); void splaytree_remove(struct splaytree_node *node, struct splaytree *tree); void splaytree_replace(struct splaytree_node *old, struct splaytree_node *newe, struct splaytree *tree); int splaytree_init(struct splaytree *tree, splaytree_cmp_fn_t cmp, unsigned long flags); #endif /* _LIBTREE_H */ nfs-ganesha-6.5/src/include/bsd-base64.h000066400000000000000000000051631473756622300200170ustar00rootroot00000000000000/* SPDX-License-Identifier: unknown 0BSD */ /* * Copyright (c) 1996 by Internet Software Consortium. * * Permission to use, copy, modify, and distribute this software for * any purpose with or without fee is hereby granted, provided that * the above copyright notice and this permission notice appear in all * copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT * SHALL INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS * SOFTWARE. */ /* * Portions Copyright (c) 1995 by International Business Machines, Inc. * * International Business Machines, Inc. (hereinafter called IBM) * grants permission under its copyrights to use, copy, modify, and * distribute this Software with or without fee, provided that the * above copyright notice and all paragraphs of this notice appear in * all copies, and that the name of IBM not be used in connection with * the marketing of any product incorporating the Software or * modifications thereof, without specific, written prior permission. * * To the extent it has a right to do so, IBM grants an immunity from * suit under its patents, if any, for the use, sale or manufacture of * products to the extent that such products are used for performing * Domain Name System dynamic updates in TCP/IP networks by means of * the Software. No immunity is granted for any product per se or for * any other function of any product. * * THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE, EVEN IF IBM IS APPRISED OF THE * POSSIBILITY OF SUCH DAMAGES. */ #ifndef _BSD_BASE64_H #define _BSD_BASE64_H #include int b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize); int b64_pton(char const *src, u_char *target, size_t targsize); int base64url_encode(u_char const *src, size_t srclength, char *target, size_t targsize); #define __b64_ntop b64_ntop #define __b64_pton b64_pton #endif /* _BSD_BINRESVPORT_H */ nfs-ganesha-6.5/src/include/cidr.h000066400000000000000000000107331473756622300171050ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (c) 2005, 2006 * Matthew D. Fuller * 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 THE AUTHOR(S) ``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 AUTHORS 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. * */ /* * libcidr.h - Header file for libCIDR */ #ifndef __LIBCIDR_H #define __LIBCIDR_H /* We need the fixed-size int types. See discussion below. */ #include /* We need the struct in[6]_addr defs */ #include /* CONSTANTS */ /* String forms (cidr_to_str()) */ #define CIDR_NOFLAGS (0) #define CIDR_NOCOMPACT (1) /* Don't do :: compaction */ #define CIDR_VERBOSE (1 << 1) /* Don't minimize leading zeros */ #define CIDR_USEV6 (1 << 2) /* Use v6 form for v4 addresses */ #define CIDR_USEV4COMPAT (1 << 3) /* Use v4-compat rather than v4-mapped */ #define CIDR_NETMASK (1 << 4) /* Show netmask instead of pflen */ #define CIDR_ONLYADDR (1 << 5) /* Only show the address */ #define CIDR_ONLYPFLEN (1 << 6) /* Only show the pf/mask */ #define CIDR_WILDCARD (1 << 7) /* Show wildcard-mask instead of netmask */ #define CIDR_FORCEV6 (1 << 8) /* Force treating as v6 address */ #define CIDR_FORCEV4 (1 << 9) /* Force treating as v4 address */ #define CIDR_REVERSE (1 << 10) /* Return a DNS PTR name */ /* Protocols */ #define CIDR_NOPROTO 0 #define CIDR_IPV4 1 #define CIDR_IPV6 2 /* Versioning info */ #define CIDR_VERSION "1.1" #define CIDR_RELEASE "release" #define CIDR_REVISION \ " (fullermd@over-yonder.net-20061125141312-f6mjjptgl4zqh6wt)" #define CIDR_VERSION_STR (CIDR_VERSION "-" CIDR_RELEASE CIDR_REVISION) /* DATA STRUCTURES */ /* * Discussion: * uint*_t are defined by POSIX and C99. We only probably NEED stdint.h * defines, since we don't need the various output stuff. However, for * now, we'll get all of inttypes.h because some older platforms only * have it, and define the uint*_t's in there (FreeBSD 4.x being the most * obvious one I care about). Revisit this down the line if necessary. * * Note that you should almost certainly not be messing with this * structure directly from external programs. Use the cidr_get_*() * functions to get a copy to work with. */ struct cidr_addr { int version; uint8_t addr[16]; uint8_t mask[16]; int proto; }; typedef struct cidr_addr CIDR; /* PROTOTYPES */ CIDR *cidr_addr_broadcast(const CIDR *); CIDR *cidr_addr_hostmax(const CIDR *); CIDR *cidr_addr_hostmin(const CIDR *); CIDR *cidr_addr_network(const CIDR *); CIDR *cidr_alloc(void); int cidr_contains(const CIDR *, const CIDR *); CIDR *cidr_dup(const CIDR *); int cidr_equals(const CIDR *, const CIDR *); void cidr_free(CIDR *); CIDR *cidr_from_inaddr(const struct in_addr *); CIDR *cidr_from_in6addr(const struct in6_addr *); CIDR *cidr_from_str(const char *); uint8_t *cidr_get_addr(const CIDR *); uint8_t *cidr_get_mask(const CIDR *); int cidr_get_pflen(const CIDR *); int cidr_get_proto(const CIDR *); int cidr_is_v4mapped(const CIDR *); CIDR **cidr_net_subnets(const CIDR *); CIDR *cidr_net_supernet(const CIDR *); const char *cidr_numaddr(const CIDR *); const char *cidr_numaddr_pflen(int); const char *cidr_numhost(const CIDR *); const char *cidr_numhost_pflen(int); struct in_addr *cidr_to_inaddr(const CIDR *, struct in_addr *); struct in6_addr *cidr_to_in6addr(const CIDR *, struct in6_addr *); char *cidr_to_str(const CIDR *, int); const char *cidr_version(void); #endif /* __LIBCIDR_H */ nfs-ganesha-6.5/src/include/city.h000066400000000000000000000064301473756622300171330ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ // city.h - cityhash-c // CityHash on C // Copyright (c) 2011-2012, Alexander Nusov // // - original copyright notice - // Copyright (c) 2011 Google, Inc. // // 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. // // CityHash, by Geoff Pike and Jyrki Alakuijala // // This file provides a few functions for hashing strings. On x86-64 // hardware in 2011, CityHash64() is faster than other high-quality // hash functions, such as Murmur. This is largely due to higher // instruction-level parallelism. CityHash64() and CityHash128() also perform // well on hash-quality tests. // // CityHash128() is optimized for relatively long strings and returns // a 128-bit hash. For strings more than about 2000 bytes it can be // faster than CityHash64(). // // Functions in the CityHash family are not suitable for cryptography. // // WARNING: This code has not been tested on big-endian platforms! // It is known to work well on little-endian platforms that have a small penalty // for unaligned reads, such as current Intel and AMD moderate-to-high-end CPUs. // // By the way, for some hash functions, given strings a and b, the hash // of a+b is easily derived from the hashes of a and b. This property // doesn't hold for any hash functions in this file. #ifndef CITY_HASH_H_ #define CITY_HASH_H_ #include #include typedef uint8_t uint8; typedef uint32_t uint32; typedef uint64_t uint64; typedef struct _uint128 uint128; struct _uint128 { uint64 first; uint64 second; }; #define Uint128Low64(x) (x).first #define Uint128High64(x) (x).second // Hash function for a byte array. uint64 CityHash64(const char *buf, size_t len); // Hash function for a byte array. For convenience, a 64-bit seed is also // hashed into the result. uint64 CityHash64WithSeed(const char *buf, size_t len, uint64 seed); // Hash function for a byte array. For convenience, two seeds are also // hashed into the result. uint64 CityHash64WithSeeds(const char *buf, size_t len, uint64 seed0, uint64 seed1); // Hash function for a byte array. uint128 CityHash128(const char *s, size_t len); // Hash function for a byte array. For convenience, a 128-bit seed is also // hashed into the result. uint128 CityHash128WithSeed(const char *s, size_t len, uint128 seed); #endif // CITY_HASH_H_ nfs-ganesha-6.5/src/include/citycrc.h000066400000000000000000000037431473756622300176270ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ // citycrc.h - cityhash-c // CityHash on C // Copyright (c) 2011-2012, Alexander Nusov // // - original copyright notice - // Copyright (c) 2011 Google, Inc. // // 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. // // CityHash, by Geoff Pike and Jyrki Alakuijala // // This file declares the subset of the CityHash functions that require // _mm_crc32_u64(). See the CityHash README for details. // // Functions in the CityHash family are not suitable for cryptography. #ifndef CITY_HASH_CRC_H_ #define CITY_HASH_CRC_H_ #include "city.h" // Hash function for a byte array. uint128 CityHashCrc128(const char *s, size_t len); // Hash function for a byte array. For convenience, a 128-bit seed is also // hashed into the result. uint128 CityHashCrc128WithSeed(const char *s, size_t len, uint128 seed); // Hash function for a byte array. Sets result[0] ... result[3]. void CityHashCrc256(const char *s, size_t len, uint64 *result); #endif // CITY_HASH_CRC_H_ nfs-ganesha-6.5/src/include/client_mgr.h000066400000000000000000000102471473756622300203070ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2013 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @defgroup Client host management * @{ */ /** * @file client_mgr.h * @author Jim Lieb * @brief Client manager */ #ifndef CLIENT_MGR_H #define CLIENT_MGR_H #include #include #include "avltree.h" #include "gsh_types.h" #include "gsh_rpc.h" #include "cidr.h" #include "sal_shared.h" #include "connection_manager.h" struct gsh_client { struct avltree_node node_k; pthread_rwlock_t client_lock; int64_t refcnt; struct timespec last_update; char hostaddr_str[SOCK_NAME_MAX]; sockaddr_t cl_addrbuf; uint64_t state_stats[STATE_TYPE_MAX]; /* state stats for this client */ connection_manager__client_t connection_manager; }; static inline int64_t inc_gsh_client_refcount(struct gsh_client *client) { return atomic_inc_int64_t(&client->refcnt); } static inline int64_t inc_gsh_client_state_stats(struct gsh_client *client, enum state_type state_type) { return atomic_inc_uint64_t(&client->state_stats[state_type]); } static inline int64_t dec_gsh_client_state_stats(struct gsh_client *client, enum state_type state_type) { return atomic_dec_uint64_t(&client->state_stats[state_type]); } void client_pkginit(void); #ifdef USE_DBUS void dbus_client_init(void); #endif struct gsh_client *get_gsh_client(sockaddr_t *client_ipaddr, bool lookup_only); void put_gsh_client(struct gsh_client *client); int foreach_gsh_client(bool (*cb)(struct gsh_client *cl, void *state), void *state); enum exportlist_client_type { PROTO_CLIENT = 0, NETWORK_CLIENT = 1, NETGROUP_CLIENT = 2, WILDCARDHOST_CLIENT = 3, GSSPRINCIPAL_CLIENT = 4, MATCH_ANY_CLIENT = 5, BAD_CLIENT = 6 }; struct base_client_entry { struct glist_head cle_list; enum exportlist_client_type type; union { struct { CIDR *cidr; } network; struct { char *netgroupname; } netgroup; struct { char *wildcard; } wildcard; struct { char *princname; } gssprinc; } client; }; int StrClient(struct display_buffer *dspbuf, struct base_client_entry *client); void LogClientListEntry(enum log_components component, log_levels_t level, int line, const char *func, const char *tag, struct base_client_entry *entry); #define LogMidDebug_ClientListEntry(component, tag, cli) \ LogClientListEntry(component, NIV_MID_DEBUG, __LINE__, \ (char *)__func__, tag, cli) typedef void(client_free_func)(struct base_client_entry *client); void FreeClientList(struct glist_head *clients, client_free_func free_func); struct base_client_entry *client_match(enum log_components component, const char *str, sockaddr_t *hostaddr, struct glist_head *clients); typedef void *(client_list_entry_allocator_t)(void); typedef void(client_list_entry_filler_t)(struct base_client_entry *client, void *private_data); int add_client(enum log_components component, struct glist_head *client_list, const char *client_tok, enum term_type type_hint, void *cnode, struct config_error_type *err_type, client_list_entry_allocator_t cle_allocator, client_list_entry_filler_t cle_filler, void *private_data); bool haproxy_match(SVCXPRT *xprt); #endif /* !CLIENT_MGR_H */ /** @} */ nfs-ganesha-6.5/src/include/common_utils.h000066400000000000000000001274111473756622300206760ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * @file common_utils.h * @brief Common tools for printing, parsing, .... * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ #ifndef COMMON_UTILS_H #define COMMON_UTILS_H #include #include #include #include #include #include #include #include #include #include "abstract_atomic.h" #include "gsh_types.h" #include "log.h" extern pthread_mutexattr_t default_mutex_attr; extern pthread_rwlockattr_t default_rwlock_attr; /** * BUILD_BUG_ON - break compile if a condition is true. * @condition: the condition which the compiler should know is false. * * If you have some code which relies on certain constants being equal, or * other compile-time-evaluated condition, you should use BUILD_BUG_ON to * detect if someone changes it. * * The implementation uses gcc's reluctance to create a negative array, but * gcc (as of 4.4) only emits that error for obvious cases (eg. not arguments * to inline functions). So as a fallback we use the optimizer; if it can't * prove the condition is false, it will cause a link error on the undefined * "__build_bug_on_failed". This error message can be harder to track down * though, hence the two different methods. * * Blatantly stolen from kernel source, include/linux/kernel.h:651 */ #ifndef __OPTIMIZE__ #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2 * !!(condition)])) #else extern int __build_bug_on_failed; #define BUILD_BUG_ON(condition) \ do { \ ((void)sizeof(char[1 - 2 * !!(condition)])); \ if (condition) \ __build_bug_on_failed = 1; \ } while (0) #endif /* Most machines scandir callback requires a const. But not all */ #define SCANDIR_CONST const /* Most machines have mntent.h. */ #define HAVE_MNTENT_H 1 /* String parsing functions */ #ifndef HAVE_STRLCPY extern size_t strlcpy(char *dst, const char *src, size_t siz); #endif #ifndef HAVE_STRNLEN #define strnlen(a, b) gsh_strnlen(a, b) /* prefix with gsh_ to prevent library conflict -- will fix properly with new build system */ extern size_t gsh_strnlen(const char *s, size_t max); #endif #if defined(__APPLE__) #define pthread_yield() pthread_yield_np() #undef SCANDIR_CONST #define SCANDIR_CONST #undef HAVE_MNTENT_H #endif #if defined(__FreeBSD__) #undef SCANDIR_CONST #define SCANDIR_CONST #endif #ifndef ARRAY_SIZE /* Assumes a is an array */ #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) #endif #ifndef CONCAT #define CONCAT_(A, B) A##B /** Macro-concatenate A and B (using ##, avoids macro expansion order issues) */ #define CONCAT(A, B) CONCAT_(A, B) #endif extern unsigned long PTHREAD_stack_size; static inline int PTHREAD_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg) { pthread_attr_t scratch; pthread_attr_t *pattr = (attr == NULL ? &scratch : attr); if (attr == NULL) pthread_attr_init(&scratch); /* * glibc's current default stacksize is 8M */ (void)pthread_attr_setstacksize(pattr, PTHREAD_stack_size); return pthread_create(thread, pattr, start_routine, arg); } /** * @brief Logging pthread attribute initialization * * @param[in,out] _attr The attributes to initialize */ #define PTHREAD_ATTR_init(_attr) \ do { \ int rc; \ \ rc = pthread_attr_init(_attr); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Init pthread attr %p (%s) at %s:%d", \ _attr, #_attr, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, pthread attr init %p (%s) " \ "at %s:%d", \ rc, _attr, #_attr, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging pthread attribute destruction * * @param[in,out] _attr The attributes to initialize */ #define PTHREAD_ATTR_destroy(_attr) \ do { \ int rc; \ \ rc = pthread_attr_destroy(_attr); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Destroy pthread attr %p (%s) at %s:%d", \ _attr, #_attr, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, pthread attr destroy %p (%s) " \ "at %s:%d", \ rc, _attr, #_attr, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging pthread attr set scope * * @param[in,out] _attr The attributes to update * @param[in] _scope The scope to set */ #define PTHREAD_ATTR_setscope(_attr, _scope) \ do { \ int rc; \ \ rc = pthread_attr_setscope(_attr, _scope); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "pthread_attr_setscope %p (%s) at %s:%d", \ _attr, #_attr, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, pthread_attr_setscope %p (%s) " \ "at %s:%d", \ rc, _attr, #_attr, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging pthread attr set detach state * * @param[in,out] _attr The attributes to update * @param[in] _detach The detach type to set */ #define PTHREAD_ATTR_setdetachstate(_attr, _detach) \ do { \ int rc; \ \ rc = pthread_attr_setdetachstate(_attr, _detach); \ if (rc == 0) { \ LogFullDebug( \ COMPONENT_RW_LOCK, \ "pthread_attr_setdetachstate %p (%s) at %s:%d", \ _attr, #_attr, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, pthread_attr_setdetachstate %p (%s) " \ "at %s:%d", \ rc, _attr, #_attr, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging pthread attr set stack size * * @param[in,out] _attr The attributes to update * @param[in] _detach The detach type to set */ #define PTHREAD_ATTR_setstacksize(_attr, _detach) \ do { \ int rc; \ \ rc = pthread_attr_setstacksize(_attr, _detach); \ if (rc == 0) { \ LogFullDebug( \ COMPONENT_RW_LOCK, \ "pthread_attr_setstacksize %p (%s) at %s:%d", \ _attr, #_attr, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, pthread_attr_setstacksize %p (%s) " \ "at %s:%d", \ rc, _attr, #_attr, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging pthread rwlock attribute initialization * * @param[in,out] _attr The attributes to initialize */ #define PTHREAD_RWLOCKATTR_init(_attr) \ do { \ int rc; \ \ rc = pthread_rwlockattr_init(_attr); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Init rwlockattr %p (%s) at %s:%d", \ _attr, #_attr, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, rwlockattr init %p (%s) " \ "at %s:%d", \ rc, _attr, #_attr, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging pthread rwlock set kind * * @param[in,out] _attr The attributes to update * @param[in] _kind The kind to set */ #if __GLIBC__ #define PTHREAD_RWLOCKATTR_setkind_np(_attr, _kind) \ do { \ int rc; \ \ rc = pthread_rwlockattr_setkind_np(_attr, _kind); \ if (rc == 0) { \ LogFullDebug( \ COMPONENT_RW_LOCK, \ "pthread_rwlockattr_setkind_np %p (%s) at %s:%d", \ _attr, #_attr, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, rwlockattr setkind_np %p (%s) " \ "at %s:%d", \ rc, _attr, #_attr, __FILE__, __LINE__); \ abort(); \ } \ } while (0) #else #define PTHREAD_RWLOCKATTR_setkind_np(_attr, _kind) /**/ #endif /** * @brief Logging pthread rwlock attribute destruction * * @param[in,out] _attr The attributes to initialize */ #define PTHREAD_RWLOCKATTR_destroy(_attr) \ do { \ int rc; \ \ rc = pthread_rwlockattr_destroy(_attr); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Destroy rwlockattr %p (%s) at %s:%d", \ _attr, #_attr, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, rwlockattr destroy %p (%s) " \ "at %s:%d", \ rc, _attr, #_attr, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging rwlock initialization * * @param[in,out] _lock The rwlock to initialize * @param[in,out] _attr The attributes used while initializing the lock */ #define PTHREAD_RWLOCK_init(_lock, _attr) \ do { \ int rc; \ pthread_rwlockattr_t *attr = _attr; \ \ if (attr == NULL) \ attr = &default_rwlock_attr; \ \ rc = pthread_rwlock_init(_lock, attr); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Init rwlock %p (%s) at %s:%d", _lock, \ #_lock, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, Init rwlock %p (%s) " \ "at %s:%d", \ rc, _lock, #_lock, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging rwlock destroy * * @param[in,out] _lock The rwlock to destroy */ #define PTHREAD_RWLOCK_destroy(_lock) \ do { \ int rc; \ \ rc = pthread_rwlock_destroy(_lock); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Destroy mutex %p (%s) at %s:%d", _lock, \ #_lock, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, Destroy mutex %p (%s) " \ "at %s:%d", \ rc, _lock, #_lock, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging write-lock * * @param[in,out] _lock Read-write lock */ #define PTHREAD_RWLOCK_wrlock(_lock) \ do { \ int rc; \ \ rc = pthread_rwlock_wrlock(_lock); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Got write lock on %p (%s) " \ "at %s:%d", \ _lock, #_lock, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, write locking %p (%s) " \ "at %s:%d", \ rc, _lock, #_lock, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging read-lock * * @param[in,out] _lock Read-write lock */ #define PTHREAD_RWLOCK_rdlock(_lock) \ do { \ int rc; \ \ rc = pthread_rwlock_rdlock(_lock); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Got read lock on %p (%s) " \ "at %s:%d", \ _lock, #_lock, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, read locking %p (%s) " \ "at %s:%d", \ rc, _lock, #_lock, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging read-write lock unlock * * @param[in,out] _lock Read-write lock */ #define PTHREAD_RWLOCK_unlock(_lock) \ do { \ int rc; \ \ rc = pthread_rwlock_unlock(_lock); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Unlocked %p (%s) at %s:%d", _lock, \ #_lock, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, unlocking %p (%s) at %s:%d", rc, \ _lock, #_lock, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging pthread mutex attribute initialization * * @param[in,out] _attr The attributes to initialize */ #define PTHREAD_MUTEXATTR_init(_attr) \ do { \ int rc; \ \ rc = pthread_mutexattr_init(_attr); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Init mutexattr %p (%s) at %s:%d", _attr, \ #_attr, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, mutexattr init %p (%s) " \ "at %s:%d", \ rc, _attr, #_attr, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging pthread mutex set type * * @param[in,out] _attr The attributes to update * @param[in] _type The type to set */ #define PTHREAD_MUTEXATTR_settype(_attr, _type) \ do { \ int rc; \ \ rc = pthread_mutexattr_settype(_attr, _type); \ if (rc == 0) { \ LogFullDebug( \ COMPONENT_RW_LOCK, \ "pthread_mutexattr_settype %p (%s) at %s:%d", \ _attr, #_attr, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, mutexattr settype %p (%s) " \ "at %s:%d", \ rc, _attr, #_attr, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging pthread mutex attribute destruction * * @param[in,out] _attr The attributes to initialize */ #define PTHREAD_MUTEXATTR_destroy(_attr) \ do { \ int rc; \ \ rc = pthread_mutexattr_destroy(_attr); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Destroy mutexattr %p (%s) at %s:%d", \ _attr, #_attr, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, mutexattr destroy %p (%s) " \ "at %s:%d", \ rc, _attr, #_attr, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging mutex lock * * @param[in,out] _mtx The mutex to acquire */ #define PTHREAD_MUTEX_lock(_mtx) \ do { \ int rc; \ \ rc = pthread_mutex_lock(_mtx); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Acquired mutex %p (%s) at %s:%d", _mtx, \ #_mtx, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, acquiring mutex %p (%s) " \ "at %s:%d", \ rc, _mtx, #_mtx, __FILE__, __LINE__); \ abort(); \ } \ } while (0) static inline int PTHREAD_mutex_trylock(pthread_mutex_t *mtx, const char *mtx_name) { int rc; rc = pthread_mutex_trylock(mtx); if (rc == 0) { LogFullDebug(COMPONENT_RW_LOCK, "Acquired mutex %p (%s) at %s:%d", mtx, mtx_name, __FILE__, __LINE__); } else if (rc == EBUSY) { LogFullDebug(COMPONENT_RW_LOCK, "Busy mutex %p (%s) at %s:%d", mtx, mtx_name, __FILE__, __LINE__); } else { LogCrit(COMPONENT_RW_LOCK, "Error %d, acquiring mutex %p (%s) at %s:%d", rc, mtx, mtx_name, __FILE__, __LINE__); abort(); } return rc; } #define PTHREAD_MUTEX_trylock(_mtx) PTHREAD_mutex_trylock(_mtx, #_mtx) /** * @brief Logging mutex unlock * * @param[in,out] _mtx The mutex to relinquish */ #define PTHREAD_MUTEX_unlock(_mtx) \ do { \ int rc; \ \ rc = pthread_mutex_unlock(_mtx); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Released mutex %p (%s) at %s:%d", _mtx, \ #_mtx, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, releasing mutex %p (%s) " \ "at %s:%d", \ rc, _mtx, #_mtx, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging mutex initialization * * @param[in,out] _mtx The mutex to initialize * @param[in,out] _attr The attributes used while initializing the mutex */ #define PTHREAD_MUTEX_init(_mtx, _attr) \ do { \ int rc; \ pthread_mutexattr_t *attr = _attr; \ \ if (attr == NULL) \ attr = &default_mutex_attr; \ \ rc = pthread_mutex_init(_mtx, attr); \ \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Init mutex %p (%s) at %s:%d", _mtx, \ #_mtx, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, Init mutex %p (%s) " \ "at %s:%d", \ rc, _mtx, #_mtx, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging mutex destroy * * @param[in,out] _mtx The mutex to destroy */ #define PTHREAD_MUTEX_destroy(_mtx) \ do { \ int rc; \ \ rc = pthread_mutex_destroy(_mtx); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Destroy mutex %p (%s) at %s:%d", _mtx, \ #_mtx, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, Destroy mutex %p (%s) " \ "at %s:%d", \ rc, _mtx, #_mtx, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging spin lock * * @param[in,out] _spin The spin lock to acquire */ #define PTHREAD_SPIN_lock(_spin) \ do { \ int rc; \ \ rc = pthread_spin_lock(_spin); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Acquired spin lock %p (%s) at %s:%d", \ _spin, #_spin, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, acquiring spin lock %p (%s) " \ "at %s:%d", \ rc, _spin, #_spin, __FILE__, __LINE__); \ abort(); \ } \ } while (0) #define PTHREAD_SPIN_unlock(_spin) \ do { \ int rc; \ \ rc = pthread_spin_unlock(_spin); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Released spin lock %p (%s) at %s:%d", \ _spin, #_spin, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, releasing spin lock %p (%s) " \ "at %s:%d", \ rc, _spin, #_spin, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging spin lock initialization * * @param[in,out] _spin The spin lock to initialize * @param[in,out] _pshared The sharing type for the spin lock */ #define PTHREAD_SPIN_init(_spin, _pshared) \ do { \ int rc; \ \ rc = pthread_spin_init(_spin, _pshared); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Init spin lock %p (%s) at %s:%d", _spin, \ #_spin, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, Init spin lock %p (%s) " \ "at %s:%d", \ rc, _spin, #_spin, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging spin lock destroy * * @param[in,out] _spin The spin lock to destroy */ #define PTHREAD_SPIN_destroy(_spin) \ do { \ int rc; \ \ rc = pthread_spin_destroy(_spin); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Destroy spin lock %p (%s) at %s:%d", \ _spin, #_spin, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, Destroy spin lock %p (%s) " \ "at %s:%d", \ rc, _spin, #_spin, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging condition variable initialization * * @param[in,out] _cond The condition variable to initialize * @param[in,out] _attr The attributes used while initializing the * condition variable */ #define PTHREAD_COND_init(_cond, _attr) \ do { \ int rc; \ \ rc = pthread_cond_init(_cond, _attr); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Init cond %p (%s) at %s:%d", _cond, \ #_cond, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, Init cond %p (%s) " \ "at %s:%d", \ rc, _cond, #_cond, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging condition variable destroy * * @param[in,out] _cond The condition variable to destroy */ #define PTHREAD_COND_destroy(_cond) \ do { \ int rc; \ \ rc = pthread_cond_destroy(_cond); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Destroy cond %p (%s) at %s:%d", _cond, \ #_cond, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, Destroy cond %p (%s) " \ "at %s:%d", \ rc, _cond, #_cond, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging condtion variable wait * * @param[in,out] _cond The condition variable to wait for * @param[in,out] _mutex The mutex associated with the condition variable */ #define PTHREAD_COND_wait(_cond, _mutex) \ do { \ int rc; \ \ rc = pthread_cond_wait(_cond, _mutex); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Wait cond %p (%s) at %s:%d", _cond, \ #_cond, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, Wait cond %p (%s) " \ "at %s:%d", \ rc, _cond, #_cond, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging condition variable signal * * Shall unblock at least one of the threads that are blocked on the specified * condition variable cond (if any threads are blocked on cond). * * @param[in,out] _cond The condition variable to signal */ #define PTHREAD_COND_signal(_cond) \ do { \ int rc; \ \ rc = pthread_cond_signal(_cond); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Signal cond %p (%s) at %s:%d", _cond, \ #_cond, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, Signal cond %p (%s) " \ "at %s:%d", \ rc, _cond, #_cond, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Logging condition variable broadcast * * Shall unblock all threads currently blocked on the specified condition * variable cond. * * @param[in,out] _cond The condition variable to broadcast */ #define PTHREAD_COND_broadcast(_cond) \ do { \ int rc; \ \ rc = pthread_cond_broadcast(_cond); \ if (rc == 0) { \ LogFullDebug(COMPONENT_RW_LOCK, \ "Broadcast cond %p (%s) at %s:%d", _cond, \ #_cond, __FILE__, __LINE__); \ } else { \ LogCrit(COMPONENT_RW_LOCK, \ "Error %d, Broadcast cond %p (%s) " \ "at %s:%d", \ rc, _cond, #_cond, __FILE__, __LINE__); \ abort(); \ } \ } while (0) /** * @brief Inline functions for timespec math * * This is for timespec math. If you want to * do the same kinds of math on timeval values, * See timeradd(3) in GLIBC. * * The primary purpose of nsecs_elapsed_t is for a compact * and quick way to handle time issues relative to server * start and server EPOCH (which is not quite the same thing * but too complicated to explain here). */ #ifdef __APPLE__ /* For accessing timespec values on 'struct stat' */ #define st_atim st_atimespec #define st_mtim st_mtimespec #define st_ctim st_ctimespec #endif /** * @brief Get the abs difference between two timespecs in nsecs * * useful for cheap time calculation. Works with Dr. Who... * * @param[in] start timespec of before end * @param[in] end timespec after start time * * @return Elapsed time in nsecs */ static inline nsecs_elapsed_t timespec_diff(const struct timespec *start, const struct timespec *end) { if ((end->tv_sec > start->tv_sec) || (end->tv_sec == start->tv_sec && end->tv_nsec >= start->tv_nsec)) { return (end->tv_sec - start->tv_sec) * NS_PER_SEC + (end->tv_nsec - start->tv_nsec); } else { return (start->tv_sec - end->tv_sec) * NS_PER_SEC + (start->tv_nsec - end->tv_nsec); } } /** * @brief update timespec fields atomically. * */ static inline void timespec_update(const struct timespec *dest, const struct timespec *src) { (void)atomic_store_uint64_t((uint64_t *)&dest->tv_sec, (uint64_t)src->tv_sec); (void)atomic_store_uint64_t((uint64_t *)&dest->tv_nsec, (uint64_t)src->tv_nsec); } /** * @brief Convert a timespec to an elapsed time interval * * This will work for wallclock time until 2554. */ static inline nsecs_elapsed_t timespec_to_nsecs(struct timespec *timespec) { return timespec->tv_sec * NS_PER_SEC + timespec->tv_nsec; } /** * @brief Convert an elapsed time interval to a timespec */ static inline void nsecs_to_timespec(nsecs_elapsed_t interval, struct timespec *timespec) { timespec->tv_sec = interval / NS_PER_SEC; timespec->tv_nsec = interval % NS_PER_SEC; } /** * @brief Add an interval to a timespec * * @param[in] interval Nanoseconds to add * @param[in,out] timespec Time */ static inline void timespec_add_nsecs(nsecs_elapsed_t interval, struct timespec *timespec) { timespec->tv_sec += (interval / NS_PER_SEC); timespec->tv_nsec += (interval % NS_PER_SEC); if ((nsecs_elapsed_t)timespec->tv_nsec > NS_PER_SEC) { timespec->tv_sec += (timespec->tv_nsec / NS_PER_SEC); timespec->tv_nsec = timespec->tv_nsec % NS_PER_SEC; } } /** * @brief Subtract an interval from a timespec * * @param[in] interval Nanoseconds to subtract * @param[in,out] timespec Time */ static inline void timespec_sub_nsecs(nsecs_elapsed_t interval, struct timespec *t) { struct timespec ts; nsecs_to_timespec(interval, &ts); if (ts.tv_nsec > t->tv_nsec) { t->tv_sec -= (ts.tv_sec + 1); t->tv_nsec = ts.tv_nsec - t->tv_nsec; } else { t->tv_sec -= ts.tv_sec; t->tv_nsec -= ts.tv_nsec; } } /** * @brief Compare two times * * Determine if @c t1 is less-than, equal-to, or greater-than @c t2. * * @param[in] t1 First time * @param[in] t2 Second time * * @retval -1 @c t1 is less-than @c t2 * @retval 0 @c t1 is equal-to @c t2 * @retval 1 @c t1 is greater-than @c t2 */ static inline int gsh_time_cmp(const struct timespec *t1, const struct timespec *t2) { if (t1->tv_sec < t2->tv_sec) { return -1; } else if (t1->tv_sec > t2->tv_sec) { return 1; } else { if (t1->tv_nsec < t2->tv_nsec) return -1; else if (t1->tv_nsec > t2->tv_nsec) return 1; } return 0; } /** * @brief Compare two buffers * * Handle the case where one buffer is a left sub-buffer of another * buffer by counting the longer one as larger. * * @param[in] buff1 A buffer * @param[in] buffa Another buffer * * @retval -1 if buff1 is less than buffa * @retval 0 if buff1 and buffa are equal * @retval 1 if buff1 is greater than buffa */ static inline int gsh_buffdesc_comparator(const struct gsh_buffdesc *buffa, const struct gsh_buffdesc *buff1) { int mr = memcmp(buff1->addr, buffa->addr, MIN(buff1->len, buffa->len)); if (unlikely(mr == 0)) { if (buff1->len < buffa->len) return -1; else if (buff1->len > buffa->len) return 1; else return 0; } else { return mr; } } /** * @brief Get the time right now as a timespec * * @param[out] ts Timespec struct */ static inline void now(struct timespec *ts) { int rc; rc = clock_gettime(CLOCK_REALTIME, ts); if (rc != 0) { LogCrit(COMPONENT_MAIN, "Failed to get timestamp"); assert(0); /* if this is broken, we are toast so die */ } } /** * @brief Get the monotonic time right now as a timespec * * CLOCK_MONOTONIC is more efficient than CLOCK_REALTIME. * * @param[out] ts Timespec struct */ static inline void now_mono(struct timespec *ts) { const int rc = clock_gettime(CLOCK_MONOTONIC, ts); if (rc != 0) { LogFatal(COMPONENT_MAIN, "Failed to get timestamp"); assert(0); /* if this is broken, we are toast so die */ } } /* The following definitions require some of the following */ #include "idmapper.h" /*wrapper for gethostname to capture auth stats, if required */ static inline int gsh_gethostname(char *name, size_t len, bool stats) { int ret; struct timespec s_time, e_time; if (stats) now(&s_time); ret = gethostname(name, len); if (!ret && stats) { now(&e_time); dns_stats_update(&s_time, &e_time); } return ret; } /*wrapper for getaddrinfo to capture auth stats, if required */ static inline int gsh_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res, bool stats) { int ret; struct timespec s_time, e_time; if (stats) now(&s_time); ret = getaddrinfo(node, service, hints, res); if (!ret && stats) { now(&e_time); dns_stats_update(&s_time, &e_time); } return ret; } /*wrapper for getnameinfo to capture auth stats, if required */ static inline int gsh_getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags, bool stats) { int ret; struct timespec s_time, e_time; if (stats) now(&s_time); ret = getnameinfo(addr, addrlen, host, hostlen, serv, servlen, flags); if (!ret && stats) { now(&e_time); dns_stats_update(&s_time, &e_time); } return ret; } #endif /* !COMMON_UTILS_H */ nfs-ganesha-6.5/src/include/conf_url.h000066400000000000000000000032131473756622300177660ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* ---------------------------------------------------------------------------- * Copyright (C) 2017, Red Hat, Inc. * contributeur : Matt Benjamin mbenjamin@redhat.com * * This program 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 3 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 * 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 * --------------------------------------- */ #ifndef CONF_URL_H #define CONF_URL_H #include #include "gsh_list.h" struct gsh_url_provider { struct glist_head link; const char *name; void (*url_init)(void); /* XXX needs config info */ void (*url_shutdown)(void); int (*url_fetch)(const char *url, FILE **f, char **fbuf); }; /** @brief package initializer */ void config_url_init(void); void config_url_shutdown(void); int register_url_provider(struct gsh_url_provider *nurl_p); int config_url_fetch(const char *url, FILE **f, char **fbuf); void config_url_release(FILE *f, char *fbuf); int gsh_rados_url_setup_watch(void); void gsh_rados_url_shutdown_watch(void); #endif /* CONF_URL_H */ nfs-ganesha-6.5/src/include/conf_url_rados.h000066400000000000000000000024311473756622300211570ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* ---------------------------------------------------------------------------- * Copyright (C) 2017, Red Hat, Inc. * contributeur : Matt Benjamin mbenjamin@redhat.com * * This program 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 3 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 * 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 * --------------------------------------- */ #ifndef CONF_URL_RADOS_H #define CONF_URL_RADOS_H #include "config.h" #ifdef RADOS_URLS #include #include "gsh_list.h" #include int gsh_rados_url_setup_watch(void); void gsh_rados_url_shutdown_watch(void); #endif /* RADOS_URLS */ #endif /* CONF_URL_RADOS_H */ nfs-ganesha-6.5/src/include/config-h.in.cmake000066400000000000000000000107361473756622300211170ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /* config.h file expanded by Cmake for build */ #ifndef CONFIG_H #define CONFIG_H #define GSH_CHECK_VERSION(major,minor,micro,libmajor,libminor,libmicro) \ ((libmajor) > (major) || \ ((libmajor) == (major) && (libminor) > (minor)) || \ ((libmajor) == (major) && (libminor) == (minor) && (libmicro) >= (micro))) #define GANESHA_VERSION_MAJOR @GANESHA_MAJOR_VERSION@ #define GANESHA_VERSION_MINOR @GANESHA_MINOR_VERSION@ #define GANESHA_EXTRA_VERSION @GANESHA_EXTRA_VERSION@ #define GANESHA_VERSION "@GANESHA_VERSION@" #define GANESHA_BUILD_RELEASE @GANESHA_BUILD_RELEASE@ #define VERSION GANESHA_VERSION #define VERSION_COMMENT "@VERSION_COMMENT@" #define _GIT_HEAD_COMMIT "@_GIT_HEAD_COMMIT@" #define _GIT_DESCRIBE "@_GIT_DESCRIBE@" #define BUILD_HOST "@BUILD_HOST_NAME@" #define FSAL_MODULE_LOC "@FSAL_DESTINATION@" /* Build controls */ #cmakedefine _MSPAC_SUPPORT 1 #cmakedefine USE_NFSIDMAP 1 #cmakedefine USE_DBUS 1 #cmakedefine USE_UNWIND 1 #cmakedefine _USE_CB_SIMULATOR 1 #cmakedefine USE_CAPS 1 #cmakedefine USE_BLKID 1 #cmakedefine PROXYV4_HANDLE_MAPPING 1 #cmakedefine _USE_9P 1 #cmakedefine _USE_9P_RDMA 1 #cmakedefine _USE_NFS_RDMA 1 #cmakedefine _USE_NFS3 1 #cmakedefine _USE_NLM 1 #cmakedefine _USE_RQUOTA 1 #cmakedefine USE_NFSACL3 1 #cmakedefine DEBUG_SAL 1 #cmakedefine _VALGRIND_MEMCHECK 1 #cmakedefine _NO_TCP_REGISTER 1 #cmakedefine RPCBIND 1 #cmakedefine HAVE_KRB5 1 #cmakedefine HAVE_HEIMDAL 1 #cmakedefine USE_GSS_KRB5_CCACHE_NAME 1 #cmakedefine LINUX 1 #cmakedefine FREEBSD 1 #cmakedefine BSDBASED 1 #cmakedefine DARWIN 1 #cmakedefine _HAVE_GSSAPI 1 #cmakedefine HAVE_STRING_H 1 #cmakedefine HAVE_STRNLEN 1 #cmakedefine LITTLEEND 1 #cmakedefine HAVE_DAEMON 1 #cmakedefine USE_LTTNG 1 #cmakedefine HAVE_ACL_GET_FD_NP 1 #cmakedefine HAVE_ACL_SET_FD_NP 1 #cmakedefine ENABLE_VFS_POSIX_ACL 1 #cmakedefine ENABLE_VFS_DEBUG_ACL 1 #cmakedefine ENABLE_RFC_ACL 1 #cmakedefine ENABLE_VFS_ACL 1 #cmakedefine CEPHFS_POSIX_ACL 1 #cmakedefine USE_GLUSTER_XREADDIRPLUS 1 #cmakedefine USE_GLUSTER_UPCALL_REGISTER 1 #cmakedefine USE_GLUSTER_DELEGATION 1 #cmakedefine GSH_CAN_HOST_LOCAL_FS 1 #cmakedefine USE_FSAL_CEPH_MKNOD 1 #cmakedefine USE_FSAL_CEPH_SETLK 1 #cmakedefine USE_FSAL_CEPH_LL_LOOKUP_ROOT 1 #cmakedefine USE_FSAL_CEPH_STATX 1 #cmakedefine USE_FSAL_CEPH_FS_NONBLOCKING_IO 1 #cmakedefine USE_FSAL_CEPH_FS_ZEROCOPY_IO 1 #cmakedefine USE_FSAL_CEPH_LL_DELEGATION 1 #cmakedefine USE_FSAL_CEPH_LL_SYNC_INODE 1 #cmakedefine USE_CEPH_LL_FALLOCATE 1 #cmakedefine USE_FSAL_CEPH_ABORT_CONN 1 #cmakedefine USE_FSAL_CEPH_RECLAIM_RESET 1 #cmakedefine USE_FSAL_CEPH_GET_FS_CID 1 #cmakedefine USE_FSAL_CEPH_REGISTER_CALLBACKS 1 #cmakedefine USE_FSAL_CEPH_LOOKUP_VINO 1 #cmakedefine USE_FSAL_RGW_MOUNT2 1 #cmakedefine USE_FSAL_RGW_XATTRS 1 #cmakedefine ENABLE_LOCKTRACE 1 #cmakedefine SANITIZE_ADDRESS 1 #cmakedefine DEBUG_MDCACHE 1 #cmakedefine USE_RADOS_RECOV 1 #cmakedefine RADOS_URLS 1 #cmakedefine USE_LLAPI 1 #cmakedefine USE_GLUSTER_STAT_FETCH_API 1 #cmakedefine HAVE_URCU_REF_GET_UNLESS_ZERO 1 #cmakedefine USE_BTRFSUTIL 1 #cmakedefine USE_MONITORING 1 #define NFS_GANESHA 1 #define GANESHA_CONFIG_PATH "@SYSCONFDIR@/ganesha/ganesha.conf" #define GANESHA_PIDFILE_PATH "@RUNTIMEDIR@/ganesha.pid" #define NFS_V4_RECOV_ROOT "@SYSSTATEDIR@/lib/nfs/ganesha" #define NFS_V4_RECOV_DIR "v4recov" #define NFS_V4_OLD_DIR "v4old" /** * @brief Default value for krb5_param.ccache_dir */ #define DEFAULT_NFS_CCACHE_DIR "@RUNTIMEDIR@" /* We're LGPL'd */ #define _LGPL_SOURCE 1 #endif /* CONFIG_H */ nfs-ganesha-6.5/src/include/config_parsing.h000066400000000000000000001102401473756622300211460ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* ---------------------------------------------------------------------------- * Copyright CEA/DAM/DIF (2007) * contributeur : Thomas LEIBOVICI thomas.leibovici@cea.fr * * This program 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 3 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 * 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 * * --------------------------------------- */ #ifndef _CONFIG_PARSING_H #define _CONFIG_PARSING_H #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif typedef struct config_root *config_file_t; typedef enum { CONFIG_ITEM_BLOCK = 1, CONFIG_ITEM_VAR } config_item_type; /** * @brief Data structures for config parse tree processing */ enum term_type { TERM_TOKEN = 1, TERM_REGEX, TERM_PATH, TERM_STRING, TERM_DQUOTE, TERM_SQUOTE, TERM_TRUE, TERM_FALSE, TERM_DECNUM, TERM_HEXNUM, TERM_OCTNUM, TERM_V4_ANY, TERM_V4ADDR, TERM_V4CIDR, TERM_V6ADDR, TERM_V6CIDR, TERM_FSID, TERM_NETGROUP }; enum config_type { CONFIG_NULL = 0, CONFIG_INT16, CONFIG_UINT16, CONFIG_INT32, CONFIG_UINT32, CONFIG_INT64, CONFIG_UINT64, CONFIG_ANON_ID, CONFIG_FSID, CONFIG_STRING, CONFIG_PATH, CONFIG_LIST, CONFIG_ENUM, CONFIG_TOKEN, CONFIG_BOOL, CONFIG_BOOLBIT, CONFIG_IP_ADDR, CONFIG_BLOCK, CONFIG_PROC, CONFIG_DEPRECATED, }; #define CONFIG_UNIQUE 0x001 /*< only one instance allowed */ #define CONFIG_MANDATORY 0x002 /*< param must be present */ #define CONFIG_MODE 0x004 /*< this param is octal "mode" */ #define CONFIG_RELAX \ 0x008 /*< this block has extra params * so don't complain about them */ #define CONFIG_MARK_SET 0x010 /*< Mark this param as set */ #define CONFIG_NO_DEFAULT \ 0x020 /*< This config block doesn't need a * default init if not present. */ /** * @brief Config file processing error type * * This is a better way than a bunch of mask bits... * Examination of the error type lets the calling code decide * just how bad and messed up the config file is. * * NOTE: If you add an error here, update err_type_str() and friends * as well. */ struct config_error_type { bool scan : 1; /*< lexer/scanner */ bool parse : 1; /*< parser rules */ bool init : 1; /*< block initialization */ bool fsal : 1; /*< fsal load failure */ bool cur_exp_create_err : 1; /*< current export create failure */ bool all_exp_create_err : 1; /*< all exports create failure */ bool resource : 1; /*< system resource */ bool unique : 1; /*< unique block/param */ bool invalid : 1; /*< invalid param value */ bool missing : 1; /*< missing mandatory parameter */ bool validate : 1; /*< commit param validation */ bool exists : 1; /*< block already exists */ bool internal : 1; /*< internal error */ bool bogus : 1; /*< bogus (deprecated?) param */ bool deprecated : 1; /*< A config item identified as deprecated */ bool dispose : 1; /*< Not actually an error, but we need to dispose of the config item anyway. */ uint32_t errors; /*< cumulative error count for parse+proc */ char *diag_buf; /*< buffer for scan+parse+processing msgs */ size_t diag_buf_size; /*< size of diag buffer used by memstream */ FILE *fp; /*< FILE * for memstream */ }; /** @brief Error detail decoders */ /** * @brief Test for errors that require us to exit the server */ static inline bool config_error_is_fatal(struct config_error_type *err_type) { return err_type->scan || err_type->parse || err_type->init || err_type->fsal || err_type->resource; } /** * @brief Test for errors that make the processed block unusable */ static inline bool config_error_is_crit(struct config_error_type *err_type) { return config_error_is_fatal(err_type) || err_type->internal || err_type->invalid || err_type->all_exp_create_err || err_type->missing; } /** * @brief Test for errors that will not cause problems */ static inline bool config_error_is_harmless(struct config_error_type *err_type) { return !(config_error_is_crit(err_type) || err_type->unique || err_type->exists || err_type->dispose); } /** * @brief Test for errors that make the processed block unusable for current * export */ static inline bool cur_exp_config_error_is_crit(struct config_error_type *err_type) { return config_error_is_fatal(err_type) || err_type->internal || err_type->invalid || err_type->cur_exp_create_err || err_type->missing; } /** * @brief Test for errors that will not cause problems */ static inline bool cur_exp_config_error_is_harmless(struct config_error_type *err_type) { return !(cur_exp_config_error_is_crit(err_type) || err_type->unique || err_type->exists || err_type->dispose); } /** * @brief Test that there are no errors at all * * NOTE: This is valid so long as sizeof(struct config_error_type) * == sizeof(uint16_t). Use uint32_t if this expands beyond 16 bools. * It could be a union here but that makes for messy code all * over the place. Handle with care and it won't bite you. */ static inline bool config_error_no_error(struct config_error_type *err_type) { return *(uint16_t *)err_type == 0; } /** * @brief Collect/combine errors */ static inline void config_error_comb_errors(struct config_error_type *err_type, struct config_error_type *more_errs) { *(uint16_t *)err_type |= *(uint16_t *)more_errs; } struct config_block; struct config_item; /** * @brief token list for CSV options */ struct config_item_list { const char *token; uint32_t value; }; #define CONFIG_LIST_TOK(_token_, _flags_) { .token = _token_, .value = _flags_ } #define CONFIG_LIST_EOL { .token = NULL, .value = 0 } /** * @brief A config file parameter * * These are structured as an initialized array with * CONFIG_EOL as the last initializer. * * The union wraps up minimum, maximum, and default values. * The type field is used to both validate the node type * and to switch the union. Each type has conversion functions * either inline or as separate functions. * * The CONFIG_BLOCK has special handling because it may have to * allocate memory for the structure and later link it to the * link_mem or other structures. Two functions provide this linkage. * * The following two parameters are opaque pointers to the config * parsing functions. They only make semantic sense to the 'init' * and 'commit' functions. * * link_mem * This is an opaque pointer to the data structure member in the * structure being filled by the enclosing block. This is typically * a glist_head, or in the simpler case, a struct pointer. * * self_struct * This is an opaque pointer the data structure that will be filled * by this block. * * init * The init function takes two void * arguments that are used as * follows: * * link_mem == NULL, self_struct != NULL * This call is during a do_block_init where the members of the * structure are being initialized to their defaults. For a * block, this may mean the initialization of things like glist * heads which can be done only once. The return self_struct on success * and NULL for errors. * * link_mem != NULL, self_struct == NULL * This call can potentially allocate space for the structure defined * by the parameter list. The link_mem argument is passed for reference. * Some data structures are related but not linked. For example, two * structures within an enclosing structure where a container_of the * link_mem references the enclosing which can now be used to dereference * the "self_struct" structure of interest. It should initialize any members * that are NOT initialized by the do_block_init pass. It should not * link the self_struct structure to the link_mem or initialize things like * mutexes or other linked lists. See commit. An example here are * non-settable FSAL parameters. It returns a pointer to the space. * * link_mem != NULL, self_struct != NULL * This call is to free or release any resources in this allocated * or referenced block. The link_mem argument is passed so that * dereferences as above are possible. It should not attempt to * change the link_mem such as doing glist removes. This is only * called on errors. return NULL; * * link_mem == NULL, self_struct == NULL * This is asserted as not possible. * * commit * The commit function has two functions. First, it (optionally) * validates the completed data structure. If the validation fails, * it returns non-zero error count. If the validation succeeds, it * can then do any structure specific linkage or state setting for * the structure. This state includes other linked lists and system * resources like mutexes. * * The node arg is provided for the case where the commit needs to reference * the parse tree. This is an opaque pointer that only the config_parse * know how to use. The link_mem is provided for cases where the link_mem * has as glist head that the self_struct is added to. It returns 0 to * indicate success. */ struct config_item { char *name; char *altname; enum config_type type; /* switches union */ int flags; union { struct { /* CONFIG_BOOL */ bool def; } b; struct { /* CONFIG_STRING | CONFIG_PATH */ int minsize; int maxsize; const char *def; } str; struct { /* CONFIG_IP_ADDR */ const char *def; } ip; struct { /* CONFIG_INT16 */ int16_t minval; int16_t maxval; int16_t def; bool zero_ok; } i16; struct { /* CONFIG_UINT16 */ uint16_t minval; uint16_t maxval; uint16_t def; bool zero_ok; } ui16; struct { /* CONFIG_INT32 */ int32_t minval; int32_t maxval; int32_t def; bool zero_ok; uint32_t bit; size_t set_off; } i32; struct { /* CONFIG_UINT32 */ uint32_t minval; uint32_t maxval; uint32_t def; bool zero_ok; } ui32; struct { /* CONFIG_INT64 */ int64_t minval; int64_t maxval; int64_t def; bool zero_ok; uint32_t bit; size_t set_off; } i64; struct { /* CONFIG_UINT64 */ uint64_t minval; uint64_t maxval; uint64_t def; bool zero_ok; uint32_t bit; size_t set_off; } ui64; struct { /* CONFIG_FSID */ int64_t def_maj; int64_t def_min; uint32_t bit; size_t set_off; } fsid; struct { /* CONFIG_LIST | CONFIG_ENUM | CONFIG_ENUM_SET | CONFIG_LIST_BITS | CONFIG_ENUM_BITS */ uint32_t def; uint32_t mask; struct config_item_list *tokens; size_t set_off; } lst; struct { /* CONFIG_BOOLBIT */ bool def; uint32_t bit; size_t set_off; } bit; struct { /* CONFIG_BLOCK */ void *(*init)(void *link_mem, void *self_struct); struct config_item *params; int (*commit)(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type); void (*display)(const char *step, void *node, void *link_mem, void *self_struct); bool (*check)(void *self_struct, struct config_error_type *err_type); } blk; struct { /* CONFIG_PROC */ size_t set_off; void *(*init)(void *link_mem, void *self_struct); int (*handler)(const char *token, enum term_type type_hint, struct config_item *item, void *param_addr, void *cnode, struct config_error_type *err_type); } proc; struct { /* CONFIG_DEPRECATED */ const char *message; } deprecated; } u; size_t off; /* offset into struct pointed to by opaque_dest */ }; /** * @brief Macros for defining arrays of config items. * * A config_item array is defined with one or more of the following * macros with the last entry being CONFIG_EOL which will supply * the necessary NULL pointer to terminate the walk. * * The naming has the form: * CONF__ * * where "something special" is: * * ITEM - generic entry * * MAND - This is a mandatory entry and will throw an error if there * is no config file entry for it. * * UNIQ - This is a unique entry. Multiple definitions are an error. * * RELAX - a block where unrecognized parameters are not reported errors. * * The "type" field is used for decoding and for storage. These match * the target structure members. This set defines what is currently used. * * NOOP - Used to indicate a parameter name is expected but that it is * used/processed elsewhere. * * FSID - A filesystem id, a uint64_t '.' uint64_t * * LIST - a comma separated list of bit flags * * ENUM - a single token and its enumerated type * * BLOCK - a sub-block. It points to another item list etc. * * BOOLBIT - Similar to a LIST but it is a boolean that sets flag bits * * BOOL - a boolean * * STR - A string that must have a size >= min and <= max size * * PATH - a string defining a filesystem path * * I - A signed integer of 'size' bits * * UI - an unsigned integer of 'size' bits * * MODE - an octal integer used as the 'mode' bits of an inode * * PROC - Calls a function to process the token value * * There are a few specialized item entries * * CONF_ITEM_IP_ADDR processes an IP (both v4 and v6) address specification * * CONF_ITEM_INET_PORT processes an unsigned 16 bit integer in * network byte order. * */ #define CONF_ITEM_NOOP(_name_) \ { \ .name = _name_, \ .type = CONFIG_NULL, \ } #define CONF_ITEM_FSID_SET(_name_, _def_maj_, _def_min_, _struct_, _mem_, \ _bit_, _set_) \ { .name = _name_, \ .type = CONFIG_FSID, \ .flags = CONFIG_MARK_SET | CONFIG_UNIQUE, \ .u.fsid.def_maj = _def_maj_, \ .u.fsid.def_min = _def_min_, \ .u.fsid.bit = _bit_, \ .u.fsid.set_off = offsetof(struct _struct_, _set_), \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_BLOCK_MULT(_name_, _params_, _init_, _commit_, _struct_, \ _mem_) \ { .name = _name_, \ .type = CONFIG_BLOCK, \ .u.blk.init = _init_, \ .u.blk.params = _params_, \ .u.blk.commit = _commit_, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_BLOCK(_name_, _params_, _init_, _commit_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_BLOCK, \ .flags = CONFIG_UNIQUE, \ .u.blk.init = _init_, \ .u.blk.params = _params_, \ .u.blk.commit = _commit_, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_RELAX_BLOCK(_name_, _params_, _init_, _commit_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_BLOCK, \ .flags = CONFIG_RELAX | CONFIG_UNIQUE, \ .u.blk.init = _init_, \ .u.blk.params = _params_, \ .u.blk.commit = _commit_, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_PROC_MULT(_name_, _init_, _handler_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_PROC, \ .u.proc.init = _init_, \ .u.proc.handler = _handler_, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_LIST(_name_, _def_, _tokens_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_LIST, \ .flags = CONFIG_UNIQUE, \ .u.lst.def = _def_, \ .u.lst.mask = UINT32_MAX, \ .u.lst.set_off = UINT32_MAX, \ .u.lst.tokens = _tokens_, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_LIST_BITS_SET(_name_, _def_, _mask_, _tokens_, _struct_, \ _mem_, _set_) \ { .name = _name_, \ .type = CONFIG_LIST, \ .flags = CONFIG_MARK_SET | CONFIG_UNIQUE, \ .u.lst.def = _def_, \ .u.lst.mask = _mask_, \ .u.lst.set_off = offsetof(struct _struct_, _set_), \ .u.lst.tokens = _tokens_, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_BOOLBIT_SET(_name_, _def_, _bit_, _struct_, _mem_, _set_) \ { .name = _name_, \ .type = CONFIG_BOOLBIT, \ .flags = CONFIG_MARK_SET | CONFIG_UNIQUE, \ .u.bit.def = _def_, \ .u.bit.bit = _bit_, \ .u.bit.set_off = offsetof(struct _struct_, _set_), \ .off = offsetof(struct _struct_, _mem_) } /* Use CONF_ITEM_TOKEN for a variable that is set to a single enum * value. The CONF_ITEM_ENUM_* macros are for setting one or more * bits within a field (I know, a bit confusing...). */ #define CONF_ITEM_ENUM_BITS(_name_, _def_, _mask_, _tokens_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_ENUM, \ .flags = CONFIG_UNIQUE, \ .u.lst.def = _def_, \ .u.lst.mask = _mask_, \ .u.lst.tokens = _tokens_, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_ENUM_BITS_SET(_name_, _def_, _mask_, _tokens_, _struct_, \ _mem_, _set_) \ { .name = _name_, \ .type = CONFIG_ENUM, \ .flags = CONFIG_MARK_SET | CONFIG_UNIQUE, \ .u.lst.def = _def_, \ .u.lst.mask = _mask_, \ .u.lst.set_off = offsetof(struct _struct_, _set_), \ .u.lst.tokens = _tokens_, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_TOKEN(_name_, _def_, _tokens_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_TOKEN, \ .flags = CONFIG_UNIQUE, \ .u.lst.def = _def_, \ .u.lst.tokens = _tokens_, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_INDEX_TOKEN(_name_, _def_, _tokens_, _idx_, _sizeof_) \ { .name = _name_, \ .type = CONFIG_TOKEN, \ .flags = CONFIG_UNIQUE, \ .u.lst.def = _def_, \ .u.lst.tokens = _tokens_, \ .off = (sizeof(_sizeof_) * _idx_) } #define CONF_ITEM_BOOL(_name_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_BOOL, \ .flags = CONFIG_UNIQUE, \ .u.b.def = _def_, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_STR(_name_, _minsize_, _maxsize_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_STRING, \ .flags = CONFIG_UNIQUE, \ .u.str.minsize = _minsize_, \ .u.str.maxsize = _maxsize_, \ .u.str.def = _def_, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_MAND_STR(_name_, _minsize_, _maxsize_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_STRING, \ .flags = CONFIG_UNIQUE | CONFIG_MANDATORY, \ .u.str.minsize = _minsize_, \ .u.str.maxsize = _maxsize_, \ .u.str.def = _def_, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_PATH(_name_, _minsize_, _maxsize_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_PATH, \ .flags = CONFIG_UNIQUE, \ .u.str.minsize = _minsize_, \ .u.str.maxsize = _maxsize_, \ .u.str.def = _def_, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_MAND_PATH(_name_, _minsize_, _maxsize_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_PATH, \ .flags = CONFIG_UNIQUE | CONFIG_MANDATORY, \ .u.str.minsize = _minsize_, \ .u.str.maxsize = _maxsize_, \ .u.str.def = _def_, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_IP_ADDR(_name_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_IP_ADDR, \ .flags = CONFIG_UNIQUE, \ .u.ip.def = _def_, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_MAND_IP_ADDR(_name_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_IP_ADDR, \ .flags = CONFIG_UNIQUE | CONFIG_MANDATORY, \ .u.ip.def = _def_, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_I16(_name_, _min_, _max_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_INT16, \ .flags = CONFIG_UNIQUE, \ .u.i16.minval = _min_, \ .u.i16.maxval = _max_, \ .u.i16.def = _def_, \ .u.i16.zero_ok = (_min_ <= 0), \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_UI16(_name_, _min_, _max_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_UINT16, \ .flags = CONFIG_UNIQUE, \ .u.ui16.minval = _min_, \ .u.ui16.maxval = _max_, \ .u.ui16.def = _def_, \ .u.ui16.zero_ok = (_min_ == 0), \ .off = offsetof(struct _struct_, _mem_) } #define CONF_MAND_UI16(_name_, _min_, _max_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_UINT16, \ .flags = CONFIG_UNIQUE | CONFIG_MANDATORY, \ .u.ui16.minval = _min_, \ .u.ui16.maxval = _max_, \ .u.ui16.def = _def_, \ .u.ui16.zero_ok = (_min_ == 0), \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_I32(_name_, _min_, _max_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_INT32, \ .flags = CONFIG_UNIQUE, \ .u.i32.minval = _min_, \ .u.i32.maxval = _max_, \ .u.i32.def = _def_, \ .u.i32.zero_ok = (_min_ <= 0), \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_I32_SET(_name_, _min_, _max_, _def_, _struct_, _mem_, _bit_, \ _set_) \ { .name = _name_, \ .type = CONFIG_INT32, \ .flags = CONFIG_MARK_SET | CONFIG_UNIQUE, \ .u.i32.minval = _min_, \ .u.i32.maxval = _max_, \ .u.i32.def = _def_, \ .u.i32.zero_ok = (_min_ <= 0), \ .u.i32.bit = _bit_, \ .u.i32.set_off = offsetof(struct _struct_, _set_), \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_ANON_ID_SET(_name_, _def_, _struct_, _mem_, _bit_, _set_) \ { .name = _name_, \ .type = CONFIG_ANON_ID, \ .flags = CONFIG_MARK_SET | CONFIG_UNIQUE, \ .u.i64.minval = INT32_MIN, \ .u.i64.maxval = UINT32_MAX, \ .u.i64.def = _def_, \ .u.i64.zero_ok = true, \ .u.i64.bit = _bit_, \ .u.i64.set_off = offsetof(struct _struct_, _set_), \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_UI32(_name_, _min_, _max_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_UINT32, \ .flags = CONFIG_UNIQUE, \ .u.ui32.minval = _min_, \ .u.ui32.maxval = _max_, \ .u.ui32.def = _def_, \ .u.ui32.zero_ok = (_min_ == 0), \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_UI32_ZERO(_name_, _min_, _max_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_UINT32, \ .flags = CONFIG_UNIQUE, \ .u.ui32.minval = _min_, \ .u.ui32.maxval = _max_, \ .u.ui32.def = _def_, \ .u.ui32.zero_ok = true, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_MAND_UI32(_name_, _min_, _max_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_UINT32, \ .flags = CONFIG_UNIQUE | CONFIG_MANDATORY, \ .u.ui32.minval = _min_, \ .u.ui32.maxval = _max_, \ .u.ui32.def = _def_, \ .u.ui32.zero_ok = (_min_ == 0), \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_MODE(_name_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_UINT32, \ .flags = CONFIG_MODE | CONFIG_UNIQUE, \ .u.ui32.minval = 0, \ .u.ui32.maxval = 0777, \ .u.ui32.def = _def_, \ .u.ui32.zero_ok = true, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_I64(_name_, _min_, _max_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_INT64, \ .flags = CONFIG_UNIQUE, \ .u.i64.minval = _min_, \ .u.i64.maxval = _max_, \ .u.i64.def = _def_, \ .u.i64.zero_ok = (_min_ <= 0), \ .off = offsetof(struct _struct_, _mem_) } /* The following exists because I64 is used for time_t which are likely * parameters to have a non-zero min but also allow 0. */ #define CONF_ITEM_I64_ZERO(_name_, _min_, _max_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_INT64, \ .flags = CONFIG_UNIQUE, \ .u.i64.minval = _min_, \ .u.i64.maxval = _max_, \ .u.i64.def = _def_, \ .u.i64.zero_ok = true, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_UI64(_name_, _min_, _max_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_UINT64, \ .flags = CONFIG_UNIQUE, \ .u.ui64.minval = _min_, \ .u.ui64.maxval = _max_, \ .u.ui64.def = _def_, \ .u.ui64.zero_ok = (_min_ == 0), \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_UI64_ZERO(_name_, _min_, _max_, _def_, _struct_, _mem_) \ { .name = _name_, \ .type = CONFIG_UINT64, \ .flags = CONFIG_UNIQUE, \ .u.ui64.minval = _min_, \ .u.ui64.maxval = _max_, \ .u.ui64.def = _def_, \ .u.ui64.zero_ok = true, \ .off = offsetof(struct _struct_, _mem_) } #define CONF_ITEM_UI64_SET(_name_, _min_, _max_, _def_, _struct_, _mem_, \ _bit_, _set_) \ { .name = _name_, \ .type = CONFIG_UINT64, \ .flags = CONFIG_MARK_SET | CONFIG_UNIQUE, \ .u.ui64.minval = _min_, \ .u.ui64.maxval = _max_, \ .u.ui64.def = _def_, \ .u.ui64.zero_ok = (_min_ == 0), \ .u.ui64.bit = _bit_, \ .u.ui64.set_off = offsetof(struct _struct_, _set_), \ .off = offsetof(struct _struct_, _mem_) } /** * Note that message can be NULL. */ #define CONF_ITEM_DEPRECATED(_name_, _message_) \ { \ .name = _name_, \ .type = CONFIG_DEPRECATED, \ .u.deprecated.message = _message_, \ } #define CONFIG_EOL { .name = NULL, .type = CONFIG_NULL } /** * @brief Configuration Block * * This is used for both config file parse tree processing * and DBus property settings. */ struct config_block { char *dbus_interface_name; struct config_item blk_desc; }; /** * @brief Check whether a given value is prime or not * * @param[in] v A given integer * * @return Whether it's prime or not. */ static inline bool is_prime(int v) { int i, m; if (v <= 1) return false; if (v == 2) return true; if (v % 2 == 0) return false; /* dont link with libm just for this */ #ifdef LINK_LIBM m = (int)sqrt(v); #else m = v - 1; #endif for (i = 3; i <= m; i += 2) { if (v % i == 0) return false; } return true; } /** * @brief Parse the content of a configuration file into a parse tree. * * @param file_path [IN] local path to the config file * @param err_type [OUT] Error type. Check this for success. * * @return pointer to parse tree. Must be freed if != NULL */ config_file_t config_ParseFile(char *file_path, struct config_error_type *err_type); /** * Return the first node in the global config block list with * name == block_name */ void *config_GetBlockNode(const char *block_name); /** * config_Print: * Print the content of the syntax tree * to a file. */ void config_Print(FILE *output, config_file_t config); /* Free the memory structure that store the configuration. */ void config_Free(config_file_t config); /* Find the root of the parse tree given a TYPE_BLOCK node */ config_file_t get_parse_root(void *node); /* Get generation of given config_root */ uint64_t get_config_generation(struct config_root *root); /* Get the generation of the config tree from config_node */ uint64_t get_parse_root_generation(void *node); struct config_node_list { void *tree_node; struct config_node_list *next; }; /* find a node in the parse tree using expression */ int find_config_nodes(config_file_t config, char *expr, struct config_node_list **node_list, struct config_error_type *err_type); /* fill configuration structure from parse tree */ int load_config_from_node(void *tree_node, struct config_block *conf_blk, void *param, bool unique, struct config_error_type *err_type); /* fill configuration structure from parse tree */ int load_config_from_parse(config_file_t config, struct config_block *conf_blk, void *param, bool unique, struct config_error_type *err_type); void find_unused_blocks(config_file_t config, struct config_error_type *err_type); /* translate err_type values to log/dbus error string*/ const char *config_term_name(enum term_type type); const char *config_term_desc(enum term_type type); char *err_type_str(struct config_error_type *err_type); bool init_error_type(struct config_error_type *err_type); void config_errs_to_log(char *err, void *, struct config_error_type *err_type); void config_proc_error(void *cnode, struct config_error_type *err_type, char *format, ...); int report_config_errors(struct config_error_type *err_type, void *dest, void (*logger)(char *msg, void *dest, struct config_error_type *err_type)); /** * @brief NOOP config initializer and commit functions. * Most config blocks refer to static structures that don't * need either allocation and sometimes validation/commit */ void *noop_conf_init(void *link_mem, void *self_struct); int noop_conf_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type); #ifdef __cplusplus } #endif #endif nfs-ganesha-6.5/src/include/connection_manager.h000066400000000000000000000254321473756622300220170ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2024 Google LLC * Contributor : Yoni Couriel yonic@google.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file connection_manager.h * @author Yoni Couriel * @brief Allows a client to be connected to a single Ganesha server at a time. * * This modules mitigates the Exactly-Once-Semantics issue when running * multiple Ganesha servers in a cluster. * * A client is all the connections from the same source IP address. * * The scenario is described here: * https://www.rfc-editor.org/rfc/rfc8881.html#section-2.10.6-6 * When applied to multiple Ganesha servers this scenario can happen since * Ganesha servers don't share their EOS-reply-cache with each other: * 1. The Client sends "WRITE A" to Server 1 * 2. Server 1 is slow to process the request (it might have too much load from * other clients) * 3. The Client connects to Server 2 (it might be due to load balancing) * 4. The Client establishes a session with Server 2, and sends "WRITE A" * 5. Server 2 executes the request and sends a success response * 6. The Client sends "WRITE B" to Server 2, and the server executes it * 7. Server 1 executes the old "WRITE A" request from step (1), overriding * "WRITE B" from step (6) * * Step (5) above won't happen if we had a cluster-wide EOS-reply-cache, or if * the client can't execute requests in Server 2 before all its requests * are completed in Server 1. This module implements the latter: * 1. When a client connects to a new Ganesha server, the server sends a "DRAIN" * request to all the other Ganesha servers in the cluster * 2. When a Ganesha server gets a "DRAIN" request, it closes and waits for the * connections of that client * 3. Only after a successful "DRAIN", the client is allowed to connect to the * new Ganesha server * * When an NFSv4 client connects to a new server, they are allowed to RECLAIM * their state. When using the Connection Manager, we need to extend the lease * time of the client state after draining, otherwise we are exposed to the * following (rare) scenario: * 1. The Client has a lock in Server 1 * 2. The Client connects to Server 2 (it might be due to load balancing) * 3. Server 2 starts draining all the other servers in the cluster * 4. Server 1 is successfully drained, and the lease timeout is set to * now + Lease_Lifetime * 5. Server 3, however, is very slow to respond, and delays the draining * 6. The lease times-out, and Server 1 releases the Client's lock * 7. Another client takes that lock * 8. Server 3 either: finishes the drain, or "kicked-out" from the cluster * 9. The Client is finally allowed to connect to Server 2, but it won't be able * to RECLAIM their lock (because of step 7) * The solution is to extend the lease time after draining, to be: * now + Lease_Lifetime + "Max time before kicking-out non-responsive servers" * * Usage: * 1. Set "Enable_Connection_Manager" in the config. * 2. Use connection_manager__callback_set to register a callback that * sends the "DRAIN" request to the other Ganesha servers in the cluster. The * callback is called each time a new client connects to the current Ganesha * server, and we block-wait until the callback finishes successfully before * we allow that client to issue requests. * 3. When receiving a "DRAIN" request, use * connection_manager__drain_and_disconnect_local to drain the current Ganesha * server. */ #ifndef CONNECTION_MANAGER_H #define CONNECTION_MANAGER_H #include "common_utils.h" #define CONNECTION_MANAGER__DRAIN_MAX_EXPECTED_ITERATIONS 15 enum connection_manager__drain_t { /* Drain was successful */ CONNECTION_MANAGER__DRAIN__SUCCESS = 0, /* Drain was vacuously successful, there were no active connections by * the client */ CONNECTION_MANAGER__DRAIN__SUCCESS_NO_CONNECTIONS, /* Drain failed, most likely due to a new incoming connection that * aborted the draining process, or because we were busy draining other * servers */ CONNECTION_MANAGER__DRAIN__FAILED, /* Drain failed due to timeout */ CONNECTION_MANAGER__DRAIN__FAILED_TIMEOUT, /* Drain failed due to a connection that did not drain */ CONNECTION_MANAGER__DRAIN__FAILED_STUCK, /* Number of drain results (for monitoring) */ CONNECTION_MANAGER__DRAIN__LAST, }; typedef enum connection_manager__drain_t (*connection_manager__callback_drain_t)( /* User provided context */ void *user_context, /* Client to drain */ const sockaddr_t *client_address, /* Client address string for logging/debugging */ const char *client_address_str, /* Timeout for the draining */ const struct timespec *timeout); typedef struct connection_manager__callback_context_t { /* User provided context */ void *user_context; /* Sends a "DRAIN" request to the other Ganesha servers in the * cluster */ connection_manager__callback_drain_t drain_and_disconnect_other_servers; } connection_manager__callback_context_t; /* A client steady state can be either DRAINED or ACTIVE. * The transition DRAINED -> ACTIVE is called ACTIVATING. * The transition ACTIVE -> DRAINED is called DRAINING. * If the transition fails, the state reverts back: * * +-----------+ +----------+ * +-----> DRAINED <---Success--+ DRAINING +-----+ * | +----+------+ +----^-----+ | * Failed | | | * | New connection Drain request | * | | | Failed * | +----v-------+ +----+-----+ | * +-----+ ACTIVATING +--Success--> ACTIVE <-----+ * +------------+ +----------+ * * Created with: asciiflow.com */ enum connection_manager__client_state_t { /* In this state, new connections will transition to ACTIVATING state * and try to drain other servers */ CONNECTION_MANAGER__CLIENT_STATE__DRAINED = 0, /* In this state, new connections will block-wait until the state is * changed */ CONNECTION_MANAGER__CLIENT_STATE__ACTIVATING, /* In this state, new connections are allowed immediately, without going * through the process of draining other servers */ CONNECTION_MANAGER__CLIENT_STATE__ACTIVE, /* In this state, new connections will abort the local draining process * and transition back to ACTIVE state */ CONNECTION_MANAGER__CLIENT_STATE__DRAINING, /* Number of client states (for monitoring) */ CONNECTION_MANAGER__CLIENT_STATE__LAST, }; enum connection_manager__connection_started_t { /* The new connection is allowed to be created and execute requests */ CONNECTION_MANAGER__CONNECTION_STARTED__ALLOW = 0, /* The draining process in other servers failed, the new connection * should be dropped */ CONNECTION_MANAGER__CONNECTION_STARTED__DROP, /* Number of connection started results (for monitoring) */ CONNECTION_MANAGER__CONNECTION_STARTED__LAST, }; typedef struct connection_manager__connection_t { /* When false, fields below are unused */ bool is_managed; /* We don't have ownership on XPRT, when the XPRT is destroyed it calls * connection_manager__connection_finished which destroys this struct */ SVCXPRT *xprt; /* We have ownership on gsh_client, and it should be released when this * struct is destroyed */ struct gsh_client *gsh_client; /* connections list in connection_manager__client_t */ struct glist_head node; /* This connection started draining and is no longer usable */ bool is_destroyed; time_t destroy_start; } connection_manager__connection_t; typedef struct connection_manager__client_t { enum connection_manager__client_state_t state; /* Protects this struct */ pthread_mutex_t mutex; /* Notified on state/connections change */ pthread_cond_t cond_change; /* List of connection_manager__connection_t */ struct glist_head connections; uint32_t connections_count; } connection_manager__client_t; /** * Sets the callbacks to be called on draining * Can be called only on init or after "clear" was called */ void connection_manager__callback_set(connection_manager__callback_context_t); /** * Clears the drain callbacks, and returns the last stored callbacks struct * Can be called only after "set" was called */ connection_manager__callback_context_t connection_manager__callback_clear(void); /** * Initialize the Connection Manager module */ void connection_manager__init(void); /** * Called from client_mgr when a new gsh_client is created. */ void connection_manager__client_init(connection_manager__client_t *); /** * Called from client_mgr when a gsh_client is destroyed. */ void connection_manager__client_fini(connection_manager__client_t *); /** * Called when a new connection is created. */ void connection_manager__connection_init(SVCXPRT *); /** * Called after connection_init and when client address is determined. * Client address could be obtained on connection establish, however, in case of * proxy protocol, client address can be obtained with the first request * received on the connection. * When this module is enabled, this method blocks until all the other Ganesha * servers in the cluster drain and close the connections by this client * Might fail when timeout is reached, and in that case the connection is * destroyed. * In case there are any draining processes in progress * (connection_manager__drain_and_disconnect_local), they are aborted so the new * connection gets the priority. */ enum connection_manager__connection_started_t connection_manager__connection_started(SVCXPRT *); /** * Called when a connection is closed * Updates the connection list, and potentially notifies the draining process */ void connection_manager__connection_finished(const SVCXPRT *); /** * Calls SVC_DESTROY on the client's connections, and block-waits until they * are closed, or until timeout. * The "drain_and_disconnect_other_servers" callback should send a "DRAIN" * request to the other Ganesha servers in the cluster. When a "DRAIN" request * is received, this method should be called. */ enum connection_manager__drain_t connection_manager__drain_and_disconnect_local(sockaddr_t *); void connection_manager__init(void); #endif /* CONNECTION_MANAGER_H */ nfs-ganesha-6.5/src/include/delayed_exec.h000066400000000000000000000037131473756622300205770ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright (c) 2013, The Linux Box Corporation * * Some portions copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup delayed Delayed Execution * * This provides a simple system allowing tasks to be submitted along * with a delay. * This is similar to the thread fridge, however there is a lot of * complication in the thread fridge that would make no sense here, * and the delay structure here is very different from the thread * fridge's task queue. Someone could merge the two together, but it * would make the internal logic rather snarly and the initialization * parameters even more recondite. * * @{ */ /** * @file delayed_exec.h * @author Adam C. Emerson * @brief Header for the delayed execution system */ #ifndef DELAYED_EXEC_H #define DELAYED_EXEC_H #include #include #include "gsh_types.h" void delayed_start(void); void delayed_shutdown(void); int delayed_submit(void (*)(void *), void *, nsecs_elapsed_t); #endif /* DELAYED_EXEC_H */ /** @} */ nfs-ganesha-6.5/src/include/display.h000066400000000000000000000166641473756622300176420ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright IBM Corporation, 2012 * Contributor: Frank Filz * * -------------------------- * * This program 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 3 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 * 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 * * */ /** * @defgroup Display display_buffer implementation * @{ */ /** * @file display.h * @author Frank Filz * @brief Implementation of a buffer for constructing string messages. * * This file provides a buffer descriptor for string messages that * contains a current position as well as the buffer pointer and size. * A variety of functions are provided to manipulate the buffer and * append various strings to the buffer. */ #ifndef _DISPLAY_H #define _DISPLAY_H #include #include #ifdef __cplusplus extern "C" { #endif /** * @page Display Safe display buffers * * A struct display_buffer describes a string buffer and the current position * within it so that a string can be built of various components. This is * especially useful for nested display functions for data types, where * the top level display function may call display functions for sub-data types. * * While building a complex string, users SHOULD check the return value from * each display function and exit if it is <= 0, however, continuing to call * display functions will be totally safe. * * The only things that MUST be done if building a new display primitive is * to call display_start at the beginning and display_finish at the end. A * display primitive is a function that uses a non-display function (such as * strcat, memcpy, sprintf) to copy bytes into the buffer. Such primitives must * assure that any such routines do not overflow the buffer, and then the * primitive must manage the b_current. display_finish will handle proper * indication of a full buffer or buffer overflow. * * A display function that is not a primitive (only uses display functions * themselves) SHOULD call display_start to make sure the buffer isn't already * full. It also assures the buffer will not wind up without a NUL terminator * should it not actually make any display calls. * * The core routines: * * display_start validate and prepare to start appending to the buffer. * display_finish wrap up after appending to the buffer. * display_reset_buffer reset a buffer for re-use to build a new string. * display_printf append to the string using printf formatting * display_opaque_value format an opaque value into the buffer * display_cat append a simple string to the buffer * * There are variants of these functions. */ /** * @brief Descriptor for display buffers. * * This structure defines a display buffer. * Buffer may be allocated global, on the stack, or by malloc. */ struct display_buffer { size_t b_size; /*< Size of the buffer, will hold b_size - 1 chars plus a '\0' */ char *b_current; /*< Current position in the buffer, where the next string will be appended */ char *b_start; /*< Start of the buffer */ }; int display_buffer_remain(struct display_buffer *dspbuf); int display_start(struct display_buffer *dspbuf); int display_finish(struct display_buffer *dspbuf); int display_force_overflow(struct display_buffer *dspbuf); /** * @brief Reset current position in buffer to start. * * @param[in,out] dspbuf The buffer. * */ static inline void display_reset_buffer(struct display_buffer *dspbuf) { /* To re-use a buffer, all we need to do is roll b_current back to * b_start and make it empty. */ dspbuf->b_current = dspbuf->b_start; *dspbuf->b_current = '\0'; } /** * @brief Compute the string length of the buffer. * * @param[in] dspbuf The buffer to finish up. * * @return the length. * * This function is more efficient than strlen if the buffer hasn't overflowed. * */ static inline size_t display_buffer_len(struct display_buffer *dspbuf) { size_t len = dspbuf->b_current - dspbuf->b_start; if (len == dspbuf->b_size) { /* Buffer has overflowed, due to forced overflow or partial * UTF-8 fixup, the actual string length might actually be less * than the full length of the buffer. Just use strlen. */ return strlen(dspbuf->b_start); } else { return len; } } int display_vprintf(struct display_buffer *dspbuf, const char *fmt, va_list args); /** * @brief Format a string into the buffer. * * @param[in,out] dspbuf The buffer. * @param[in] fmt the format string * @param[in] ... the args * * @return the bytes remaining in the buffer. * */ static inline int display_printf(struct display_buffer *dspbuf, const char *fmt, ...) { va_list args; int b_left; va_start(args, fmt); b_left = display_vprintf(dspbuf, fmt, args); va_end(args); return b_left; } #define OPAQUE_BYTES_SIZE(len) (MAX(len * 2 + 2 + 1, 32)) /* Indicate if use upper case (%02X) or lower case (%02x) */ #define OPAQUE_BYTES_UPPER 0x01 /* Indicate if to lead with 0x */ #define OPAQUE_BYTES_0x 0x02 /* Return -1 on invalid length */ #define OPAQUE_BYTES_INVALID_LEN 0x04 /* Return -1 on NULL pointer */ #define OPAQUE_BYTES_INVALID_NULL 0x08 /* Return -1 on EMPTTY target */ #define OPAQUE_BYTES_INVALID_EMPTY 0x10 int display_opaque_bytes_flags(struct display_buffer *dspbuf, void *value, int len, int flags); /** * @brief Display a number of opaque bytes as a hex string. * * @param[in,out] dspbuf The buffer. * @param[in] value The bytes to display * @param[in] len The number of bytes to display * * @return the bytes remaining in the buffer. * */ static inline int display_opaque_bytes(struct display_buffer *dspbuf, void *value, int len) { return display_opaque_bytes_flags(dspbuf, value, len, OPAQUE_BYTES_0x); } int display_opaque_value_max(struct display_buffer *dspbuf, void *value, int len, int max); /** * @brief Display a number of opaque bytes as a hex string. * * @param[in,out] dspbuf The buffer. * @param[in] value The bytes to display * @param[in] len The number of bytes in the opaque value * * @return the bytes remaining in the buffer. * * This routine just calls display_opaque_value_max with max = len. * */ static inline int display_opaque_value(struct display_buffer *dspbuf, void *value, int len) { return display_opaque_value_max(dspbuf, value, len, len); } int display_len_cat(struct display_buffer *dspbuf, const char *str, int len); /** * @brief Append a null delimited string to the buffer. * * @param[in,out] dspbuf The buffer. * @param[in] str The string * * @return the bytes remaining in the buffer. * */ static inline int display_cat(struct display_buffer *dspbuf, const char *str) { return display_len_cat(dspbuf, str, strlen(str)); } int display_cat_trunc(struct display_buffer *dspbuf, char *str, size_t max); /** @} */ #ifdef __cplusplus } #endif #endif /* _DISPLAY_H */ nfs-ganesha-6.5/src/include/err_inject.h000066400000000000000000000021401473756622300203010ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright IBM Corporation, 2011 * Contributor: Frank Filz * * -------------------------- * * This program 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 3 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 * 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 * * */ #ifndef ERR_INJECT_H #define ERR_INJECT_H #ifdef _ERROR_INJECTION extern int worker_delay_time; extern int next_worker_delay_time; int init_error_injector(void); #endif #endif /* ERR_INJECT_H */ nfs-ganesha-6.5/src/include/export_mgr.h000066400000000000000000000324541473756622300203560ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2013 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @defgroup Filesystem export management * @{ */ /** * @file export_mgr.h * @author Jim Lieb * @brief Export manager */ #include "gsh_list.h" #include "avltree.h" #include "abstract_atomic.h" #include "fsal.h" #ifndef EXPORT_MGR_H #define EXPORT_MGR_H extern pthread_mutex_t export_admin_mutex; /* The following counter is used to implement a seqlock protecting code that * needs to look at exports that are being changed up by an in progress update. * Such code should generally return an error causing the client to retry since * an export update may take way too much time to retry in line. * * Any code that modifies exports must increment this counter after taking the * above lock and again before releasing the above lock. */ extern uint64_t export_admin_counter; static inline void EXPORT_ADMIN_LOCK(void) { PTHREAD_MUTEX_lock(&export_admin_mutex); export_admin_counter++; } static inline void EXPORT_ADMIN_UNLOCK(void) { export_admin_counter++; PTHREAD_MUTEX_unlock(&export_admin_mutex); } static inline int EXPORT_ADMIN_TRYLOCK(void) { int rc = PTHREAD_MUTEX_trylock(&export_admin_mutex); if (rc == 0) export_admin_counter++; return rc; } /** * @brief Implement seqlock verification * * To use the export_admin_counter, a process that might get bad results due * to an in progress export update should save the export_admin_counter as * start_export_admin_counter before executing the code that could be confused. * The after the code is complete, the code can call * is_export_admin_counter_valid(start_export_admin_counter) to determine if * and export update might have upended things. * * Depending on how the code functions, it may only need to perform this check * if an unexpected result occurred. On the other hand the check is cheap, while * a false negative is possible, that still requires the code have been * executing parallel with an export update which are expected to be extremely * rare so even if the code catches a half-updated counter (due to NOT using * atomics) it just results in a false negative. */ static inline bool is_export_admin_counter_valid(uint64_t start_export_admin_counter) { return (start_export_admin_counter % 2) == 0 && start_export_admin_counter == export_admin_counter; } /** * @brief Simple check if an export update is in progress * * If code uses locks in a way that guarantee that an export update can not * upset their world while the code is executing then a simple check after * failure that an update is in progress (seqlock value is odd) is sufficient. * For example, code implementing a lookup in a pseudo fs where lookup holds a * lock that prevents the update from changing the pseudo fs while the lookup * in progress means that any update that will upset this lookups apple cart * can not start AND end while the lookup is in progress. */ static inline bool is_export_update_in_progress(void) { return (export_admin_counter % 2) != 0; } enum export_status { EXPORT_READY, /*< searchable, usable */ EXPORT_STALE, /*< export is no longer valid */ }; /** * @brief Represents an export. * * CFG: Identifies those fields that are associated with configuration. * */ struct gsh_export { /** List of all exports */ struct glist_head exp_list; /** gsh_exports are kept in an AVL tree by export_id */ struct avltree_node node_k; /** List of NFS v4 state belonging to this export */ struct glist_head exp_state_list; /** List of locks belonging to this export */ struct glist_head exp_lock_list; /** List of NLM shares belonging to this export */ struct glist_head exp_nlm_share_list; /** List of exports rooted on the same inode */ struct glist_head exp_root_list; /** List of exports to be mounted or cleaned up */ struct glist_head exp_work; /** List of exports mounted on this export */ struct glist_head mounted_exports_list; /** This export is a node in the list of mounted_exports */ struct glist_head mounted_exports_node; /** Entry for the root of this export, protected by lock */ struct fsal_obj_handle *exp_root_obj; /** CFG config_generation that last touched this export */ uint64_t config_gen; /** CFG Allowed clients - update protected by lock */ struct glist_head clients; /** Entry for the junction of this export. Protected by lock */ struct fsal_obj_handle *exp_junction_obj; /** The export this export sits on. Protected by lock */ struct gsh_export *exp_parent_exp; /** Pointer to the fsal_export associated with this export */ struct fsal_export *fsal_export; /** CFG: Exported path - static option */ struct gsh_refstr *fullpath; /** CFG: PseudoFS path for export - static option */ struct gsh_refstr *pseudopath; /** CFG: The following two strings are ONLY used during configuration * where they are guaranteed not to change. They can only be * changed while updating an export which can only happen while * the export_admin_mutex is held. Note that when doing an update, * the existing export is fetched, and it is safe to use these * strings from that export also. They will be safely updated as * part of the update. */ char *cfg_fullpath; char *cfg_pseudopath; /** CFG: Tag for direct NFS v3 mounting of export - static option */ char *FS_tag; /** Node id this is mounted on. Protected by lock */ uint64_t exp_mounted_on_file_id; /** CFG: Max Read for this entry - atomic changeable option */ uint64_t MaxRead; /** CFG: Max Write for this entry - atomic changeable option */ uint64_t MaxWrite; /** CFG: Preferred Read size - atomic changeable option */ uint64_t PrefRead; /** CFG: Preferred Write size - atomic changeable option */ uint64_t PrefWrite; /** CFG: Preferred Readdir size - atomic changeable option */ uint64_t PrefReaddir; /** CFG: Maximum Offset allowed for write - atomic changeable option */ uint64_t MaxOffsetWrite; /** CFG: Maximum Offset allowed for read - atomic changeable option */ uint64_t MaxOffsetRead; /** CFG: Filesystem ID for overriding fsid from FSAL - ????? */ fsal_fsid_t filesystem_id; /** CFG: Controls when to run permission check for read. */ uint32_t read_access_check_policy; /** References to this export */ int64_t refcnt; /** Read/Write lock protecting export */ pthread_rwlock_t exp_lock; /** CFG: available mount options - update protected by lock */ struct export_perms export_perms; /** The last time the export stats were updated */ struct timespec last_update; /** CFG: Export non-permission options - atomic changeable option */ uint32_t options; /** CFG: Export non-permission options set - atomic changeable option */ uint32_t options_set; /** CFG: Export_Id for this export - static option */ uint16_t export_id; uint8_t export_status; /*< current condition */ bool has_pnfs_ds; /*< id_servers matches export_id */ /** Inidcator if the export is mounted in the pseudofs */ bool is_mounted; /** Due to an update, during the prune phase, this export must be * unmounted. It will then be added to the mount work done during the * remount phase. This flag WILL be cleared during prune. */ bool update_prune_unmount; /** Due to an update, this export will need to be remounted. */ bool update_remount; }; /* Use macro to define this to get around include file order. */ #define ctx_export_path(ctx) \ ((nfs_param.core_param.mount_path_pseudo) ? CTX_PSEUDOPATH(ctx) : \ CTX_FULLPATH(ctx)) /* If op_ctx request is NFS_V4 always use pseudopath, otherwise use fullpath * for export. */ #define op_ctx_export_path(ctx) \ ((ctx->nfs_vers == NFS_V4) || \ (nfs_param.core_param.mount_path_pseudo) ? \ CTX_PSEUDOPATH(ctx) : \ CTX_FULLPATH(ctx)) /** * @brief Structure to make it easier to access the fullpath and pseudopath for * an export that isn't op_ctx->ctx_export or where an op context may not * be available. * * NOTE: This structure is not intended to be re-used and it is expected to * only be used for an actual export. */ struct tmp_export_paths { struct gsh_refstr *tmp_fullpath; struct gsh_refstr *tmp_pseudopath; }; #define TMP_PSEUDOPATH(tmp) ((tmp)->tmp_pseudopath->gr_val) #define TMP_FULLPATH(tmp) ((tmp)->tmp_fullpath->gr_val) #define tmp_export_path(tmp) \ ((nfs_param.core_param.mount_path_pseudo) ? TMP_PSEUDOPATH(tmp) : \ TMP_FULLPATH(tmp)) #define op_ctx_tmp_export_path(ctx, tmp) \ ((ctx->nfs_vers == NFS_V4) || \ (nfs_param.core_param.mount_path_pseudo) ? \ TMP_PSEUDOPATH(tmp) : \ TMP_FULLPATH(tmp)) static inline void tmp_get_exp_paths(struct tmp_export_paths *tmp, struct gsh_export *exp) { struct gsh_refstr *gr; rcu_read_lock(); gr = rcu_dereference(exp->fullpath); if (gr != NULL) tmp->tmp_fullpath = gsh_refstr_get(gr); else tmp->tmp_fullpath = gsh_refstr_dup(exp->cfg_fullpath); gr = rcu_dereference(exp->pseudopath); if (gr != NULL) tmp->tmp_pseudopath = gsh_refstr_get(gr); else if (exp->cfg_pseudopath != NULL) tmp->tmp_pseudopath = gsh_refstr_dup(exp->cfg_pseudopath); else tmp->tmp_pseudopath = gsh_refstr_get(no_export); rcu_read_unlock(); } static inline void tmp_put_exp_paths(struct tmp_export_paths *tmp) { gsh_refstr_put(tmp->tmp_fullpath); gsh_refstr_put(tmp->tmp_pseudopath); } static inline bool op_ctx_export_has_option(uint32_t option) { return atomic_fetch_uint32_t(&op_ctx->ctx_export->options) & option; } static inline bool op_ctx_export_has_option_set(uint32_t option) { return atomic_fetch_uint32_t(&op_ctx->ctx_export->options_set) & option; } void export_pkginit(void); #ifdef USE_DBUS void dbus_export_init(void); #endif struct gsh_export *alloc_export(void); bool insert_gsh_export(struct gsh_export *a_export); struct gsh_export *get_gsh_export(uint16_t export_id); struct gsh_export *get_gsh_export_by_path(char *path, bool exact_match); struct gsh_export *get_gsh_export_by_path_locked(char *path, bool exact_match); struct gsh_export *get_gsh_export_by_pseudo(char *path, bool exact_match); struct gsh_export *get_gsh_export_by_pseudo_locked(char *path, bool exact_match); struct gsh_export *get_gsh_export_by_tag(char *tag); bool mount_gsh_export(struct gsh_export *exp); void unmount_gsh_export(struct gsh_export *exp); void remove_gsh_export(uint16_t export_id); bool foreach_gsh_export(bool (*cb)(struct gsh_export *exp, void *state), bool wrlock, void *state); /** * @brief Advisory check of export readiness. * * This function does not guarantee the export is reachable at the point of * the test, it is just used to allow a function to take a shortcut if the * export has gone stale, usually when the function is about to take an * additional reference based on some object having a pointer and reference * to the export. * * @param[in] export The export to test for readiness. * * @retval true if the export is ready */ static inline bool export_ready(struct gsh_export *a_export) { return a_export->export_status == EXPORT_READY; } void _get_gsh_export_ref(struct gsh_export *a_export, char *file, int line, char *function); #define get_gsh_export_ref(a_export) \ _get_gsh_export_ref(a_export, (char *)__FILE__, __LINE__, \ (char *)__func__) void _put_gsh_export(struct gsh_export *a_export, bool config, char *file, int line, char *function); #define put_gsh_export(a_export) \ _put_gsh_export(a_export, false, (char *)__FILE__, __LINE__, \ (char *)__func__) #define put_gsh_export_config(a_export) \ _put_gsh_export(a_export, true, (char *)__FILE__, __LINE__, \ (char *)__func__) void export_revert(struct gsh_export *a_export); void export_add_to_mount_work(struct gsh_export *a_export); void export_add_to_unexport_work_locked(struct gsh_export *a_export); void export_add_to_unexport_work(struct gsh_export *a_export); struct gsh_export *export_take_mount_work(void); extern struct config_block add_export_param; extern struct config_block update_export_param; void prune_defunct_exports(uint64_t generation); void remove_all_exports(void); extern struct timespec nfs_stats_time; void nfs_init_stats_time(void); #endif /* !EXPORT_MGR_H */ /** @} */ nfs-ganesha-6.5/src/include/extended_types.h000066400000000000000000000035411473756622300212070ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file include/extended_types.h * @brief Extended types, platform dependent. * */ #ifndef _EXTENDED_TYPES_H #define _EXTENDED_TYPES_H #include "config.h" #include #ifdef LINUX #include #elif FREEBSD #include #endif /* Added extended types, often missing */ typedef long long longlong_t; typedef unsigned long long u_longlong_t; typedef unsigned int uint_t; /* conflict between sys/xattr.h and attr/xattr.h * this comes from xfs/linux.h. Very bad form but we are * stuck with it. If we didn't pick it up somewhere else * make is so here. * Danger Will Robinson!! this is an overlay of another errno... */ #ifndef ENOATTR /* ENOATTR is a BSD-ism, it does not exist on Linux. ENODATA is used instead */ #define ENOATTR ENODATA /* No such attribute */ #endif #endif /* _EXTENDED_TYPES_H */ nfs-ganesha-6.5/src/include/fridgethr.h000066400000000000000000000176511473756622300201500ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup fridgethr Thread Fridge * * The thread fridge provides a simple implementation of a thread pool * built atop POSIX threading capabilities. * * @{ */ /** * @file fridgethr.c * @brief Header for the thread fridge */ #ifndef FRIDGETHR_H #define FRIDGETHR_H #include #include #include #include "gsh_list.h" #include "gsh_wait_queue.h" struct fridgethr; /*< Decoder thread pool */ extern struct fridgethr *req_fridge; /** * @brief Per-worker data. Some of this will be destroyed. */ struct _9p_worker_data { wait_q_entry_t wqe; /*< Queue for coordinating with decoder */ unsigned int worker_index; /*< Index for log messages */ }; /** * @brief A given thread in the fridge */ struct fridgethr_entry { /** * @brief Thread context */ struct fridgethr_context { struct _9p_worker_data wd; /*< Work queue data */ pthread_mutex_t fre_mtx; /*< Mutex for fiddling this thread */ pthread_cond_t fre_cv; /*< Condition variable to wait for sync */ sigset_t sigmask; /*< This thread's signal mask */ void *thread_info; /*< Information belonging to the user and associated with the thread. Never modified by the fridgethr code. */ void (*func)(struct fridgethr_context *); /*< Function being executed */ void *arg; /*< Functions argument */ pthread_t id; /*< Thread ID */ uint32_t uflags; /*< Flags (for any use) */ bool woke; /*< Set to false on first run and if wait in fridgethr_freeze didn't time out. */ } ctx; uint32_t flags; /*< Thread-fridge flags (for handoff) */ bool frozen; /*< Thread is frozen */ struct timespec timeout; /*< Wait timeout */ struct glist_head thread_link; /*< Link in the list of all threads */ struct glist_head idle_link; /*< Link in the idle queue */ struct fridgethr *fr; /*< The fridge we belong to */ }; /** * @brief Fridge flavor, governing style of operation. */ typedef enum { fridgethr_flavor_worker = 0, /*< Take submitted jobs, do them, and then wait for more work to be submitted. */ fridgethr_flavor_looper = 1 /*< Each thread takes a single job and repeats it. */ } fridgethr_flavor_t; /** * @brief Enumeration governing requests when the fridge is full */ typedef enum { fridgethr_defer_fail = 0, /*< If the fridge is full, return an error immediately. This is the only allowable value for fridgethr_flavor_looper. */ fridgethr_defer_queue = 1, /*< If the fridge is full, queue requests for later and return immediately. */ } fridgethr_defer_t; /** * @brief Parameters set at fridgethr_init */ struct fridgethr_params { uint32_t thr_max; /*< Maximum number of threads */ uint32_t thr_min; /*< Low watermark for threads. Do not expire threads out if we have this many or fewer. */ time_t thread_delay; /*< Time frozen threads will wait after performing work. For fridgethr_flavor_worker fridges, threads exit if they are above the low water mark and no work is available after timeout. For fridgethr_flavor_looper fridges, sleep for this period before re-executing the supplied function. */ fridgethr_flavor_t flavor; /*< Execution flavor for this fridge. */ fridgethr_defer_t deferment; /*< Deferment strategy for this fridge */ /** * If non-NULL, run after every submitted job. */ void (*task_cleanup)(struct fridgethr_context *); /** * If non-NULL, called on thread creation just before we start * work, but after we set the function name (so it can be * overridden.) */ void (*thread_initialize)(struct fridgethr_context *); /** * If non-NULL, called on thread exit, just before the context * is freed. */ void (*thread_finalize)(struct fridgethr_context *); /** * Use this function to wake up all threads on a state * transition. Specifying this function implies that the * worker in a thread will wait for more work on its own. The * run function must be written either so that it exits after * any given piece of work or such that it calls @c * fridgethr_you_should_break before waiting. */ void (*wake_threads)(void *); /* Argument for wake_threads */ void *wake_threads_arg; }; /** * @brief Queued requests */ struct fridgethr_work { struct glist_head link; /*< Link in the work queue */ void (*func)(struct fridgethr_context *); /*< Function being executed */ void *arg; /*< Functions argument */ }; /** * @brief Commands a caller can issue */ typedef enum { fridgethr_comm_run, /*< Demand all threads execute */ fridgethr_comm_pause, /*< Demand all threads suspend */ fridgethr_comm_stop /*< Demand all threads exit */ } fridgethr_comm_t; /** * @brief Structure representing a group of threads */ struct fridgethr { char *s; /*< Name for this fridge */ struct fridgethr_params p; /*< Parameters */ pthread_mutex_t frt_mtx; /*< Mutex */ pthread_attr_t attr; /*< Creation attributes */ struct glist_head thread_list; /*< List of threads */ uint32_t nthreads; /*< Number of threads in fridge */ struct glist_head idle_q; /*< Idle threads */ uint32_t nidle; /*< Number of idle threads */ uint32_t flags; /*< Fridge-wide flags */ fridgethr_comm_t command; /*< Command state */ void (*cb_func)(void *); /*< Callback on command completion */ void *cb_arg; /*< Argument for completion callback */ pthread_mutex_t *cb_mtx; /*< Mutex for completion condition variable */ pthread_cond_t *cb_cv; /*< Condition variable, signalled on completion */ bool transitioning; /*< Changing state */ struct glist_head work_q; /*< Work queued */ }; #define fridgethr_flag_none 0x0000 /*< Null flag */ #define fridgethr_flag_available \ 0x0001 /*< I am available to be dispatched */ #define fridgethr_flag_dispatched 0x0002 /*< You have been dispatched */ int fridgethr_init(struct fridgethr **, const char *, const struct fridgethr_params *); void fridgethr_destroy(struct fridgethr *); int fridgethr_submit(struct fridgethr *, void (*)(struct fridgethr_context *), void *); int fridgethr_wake(struct fridgethr *); int fridgethr_pause(struct fridgethr *, pthread_mutex_t *, pthread_cond_t *, void (*)(void *), void *); int fridgethr_stop(struct fridgethr *, pthread_mutex_t *, pthread_cond_t *, void (*)(void *), void *); int fridgethr_start(struct fridgethr *, pthread_mutex_t *, pthread_cond_t *, void (*)(void *), void *); int fridgethr_sync_command(struct fridgethr *, fridgethr_comm_t, time_t); bool fridgethr_you_should_break(struct fridgethr_context *); int fridgethr_populate(struct fridgethr *, void (*)(struct fridgethr_context *), void *); void fridgethr_setwait(struct fridgethr_context *ctx, time_t thread_delay); time_t fridgethr_getwait(struct fridgethr_context *ctx); void fridgethr_cancel(struct fridgethr *fr); extern struct fridgethr *general_fridge; int general_fridge_init(void); int general_fridge_shutdown(void); #endif /* FRIDGETHR_H */ /** @} */ nfs-ganesha-6.5/src/include/fsal.h000066400000000000000000000556511473756622300171210ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup FSAL File-System Abstraction Layer * @{ */ /** * @file fsal.h * @brief Main FSAL externs and functions * @note not called by other header files. */ /** * @brief Thread Local Storage (TLS). * * TLS variables look like globals but since they are global only in the * context of a single thread, they do not require locks. This is true * of all thread either within or separate from a/the fridge. * * All thread local storage is declared extern here. The actual * storage declaration is in fridgethr.c. */ /** * @brief Operation context (op_ctx). * * This carries everything relevant to a protocol operation. * Space for the struct itself is allocated elsewhere. * Test/assert opctx != NULL first (or let the SEGV kill you) */ extern __thread struct req_op_context *op_ctx; #ifndef FSAL_H #define FSAL_H #include "fsal_api.h" #include "nfs23.h" #include "nfs4_acls.h" #include "nfs4_fs_locations.h" /** * @brief If we don't know how big a buffer we want for a link, use * this value. */ #define fsal_default_linksize (4096) /** * @brief Pointer to FSAL module by number. * This is actually defined in common_pnfs.c */ extern struct fsal_module *pnfs_fsal[]; /** * @brief Delegations types list for the Delegations parameter in FSAL. * This is actually defined in exports.c */ extern struct config_item_list deleg_types[]; /* Export permissions for root op context, defined in protocol layer */ extern uint32_t root_op_export_options; extern uint32_t root_op_export_set; /** * @brief node id used to construct recovery directory in * cluster implementation. */ extern int g_nodeid; void init_ctx_refstr(void); void destroy_ctx_refstr(void); void init_op_context(struct req_op_context *ctx, struct gsh_export *exp, struct fsal_export *fsal_exp, nfs_request_t *nfs_reqdata, sockaddr_t *caller_data, uint32_t nfs_vers, uint32_t nfs_minorvers, enum request_type req_type); static inline void init_op_context_simple(struct req_op_context *ctx, struct gsh_export *exp, struct fsal_export *fsal_exp) { init_op_context(ctx, exp, fsal_exp, NULL, NULL, 0, 0, UNKNOWN_REQUEST); } void release_op_context(void); void suspend_op_context(void); void resume_op_context(struct req_op_context *ctx); void set_op_context_export(struct gsh_export *exp); void clear_op_context_export(void); void save_op_context_export_and_clear(struct saved_export_context *saved); void save_op_context_export_and_set_export(struct saved_export_context *saved, struct gsh_export *exp); void restore_op_context_export(struct saved_export_context *saved); void discard_op_context_export(struct saved_export_context *saved); void set_op_context_pnfs_ds(struct fsal_pnfs_ds *pds); /****************************************************** * Structure used to define a fsal ******************************************************/ #include "FSAL/access_check.h" /* rethink where this should go */ /** * Global fsal manager functions * used by nfs_main to initialize fsal modules. */ /* Called only within MODULE_INIT and MODULE_FINI functions of a fsal * module */ /** * @brief Register a FSAL * * This function registers an FSAL with ganesha and initializes the * public portion of the FSAL data structure, including providing * default operation vectors. * * @param[in,out] fsal_hdl The FSAL module to register. * @param[in] name The FSAL's name * @param[in] major_version Major version fo the API against which * the FSAL was written * @param[in] minor_version Minor version of the API against which * the FSAL was written. * * @return 0 on success. * @return EINVAL on version mismatch. */ int register_fsal(struct fsal_module *fsal_hdl, const char *name, uint32_t major_version, uint32_t minor_version, uint8_t fsal_id); /** * @brief Unregister an FSAL * * This function unregisters an FSAL from Ganesha. It should be * called from the module finalizer as part of unloading. * * @param[in] fsal_hdl The FSAL to unregister * * @return 0 on success. * @return EBUSY if outstanding references or exports exist. */ int unregister_fsal(struct fsal_module *fsal_hdl); /** * @brief Find and take a reference on an FSAL * * This function finds an FSAL by name and increments its reference * count. It is used as part of export setup. The @c put method * should be used to release the reference before unloading. */ struct fsal_module *lookup_fsal(const char *name); int load_fsal(const char *name, struct fsal_module **fsal_hdl); int fsal_load_init(void *node, const char *name, struct fsal_module **fsal_hdl_p, struct config_error_type *err_type); struct fsal_args { char *name; }; void *fsal_init(void *link_mem, void *self_struct); struct subfsal_args { char *name; void *fsal_node; }; int subfsal_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type); void destroy_fsals(void); void emergency_cleanup_fsals(void); int start_fsals(config_file_t in_config, struct config_error_type *err_type); void display_fsinfo(struct fsal_module *fsal); int display_attrlist(struct display_buffer *dspbuf, struct fsal_attrlist *attr, bool is_obj); void log_attrlist(log_components_t component, log_levels_t level, const char *reason, struct fsal_attrlist *attr, bool is_obj, char *file, int line, char *function); #define LogAttrlist(component, level, reason, attr, is_obj) \ do { \ if (unlikely(isLevel(component, level))) \ log_attrlist(component, level, reason, attr, is_obj, \ (char *)__FILE__, __LINE__, \ (char *)__func__); \ } while (0) const char *msg_fsal_err(fsal_errors_t fsal_err); #define fsal_err_txt(s) msg_fsal_err((s).major) /* * FSAL helpers */ enum cb_state { CB_ORIGINAL, CB_JUNCTION, CB_PROBLEM, }; typedef fsal_errors_t (*helper_readdir_cb)(void *opaque, struct fsal_obj_handle *obj, const struct fsal_attrlist *attr, uint64_t mounted_on_fileid, uint64_t cookie, enum cb_state cb_state); /** * @brief Type of callback for fsal_readdir * * This callback provides the upper level protocol handling function * with one directory entry at a time. It may use the opaque to keep * track of the structure it is filling, space used, and so forth. * * This function should return true if the entry has been added to the * caller's response, or false if the structure is fulled and the * structure has not been added. */ struct fsal_readdir_cb_parms { void *opaque; /*< Protocol specific parms */ const char *name; /*< Dir entry name */ bool attr_allowed; /*< True if caller has perm to getattr */ bool in_result; /*< true if the entry has been added to the *< caller's response, or false if the *< structure is filled and the entry has not *< been added. */ }; fsal_status_t fsal_setattr(struct fsal_obj_handle *obj, bool bypass, struct state_t *state, struct fsal_attrlist *attr); /** * * @brief Checks the permissions on an object * * This function returns success if the supplied credentials possess * permission required to meet the specified access. * * @param[in] obj The object to be checked * @param[in] access_type The kind of access to be checked * * @return FSAL status * */ static inline fsal_status_t fsal_access(struct fsal_obj_handle *obj, fsal_accessflags_t access_type) { return obj->obj_ops->test_access(obj, access_type, NULL, NULL, false); } fsal_status_t fsal_link(struct fsal_obj_handle *obj, struct fsal_obj_handle *dest_dir, const char *name, struct fsal_attrlist *destdir_pre_attrs_out, struct fsal_attrlist *destdir_post_attrs_out); fsal_status_t fsal_readlink(struct fsal_obj_handle *obj, struct gsh_buffdesc *link_content); fsal_status_t fsal_lookup(struct fsal_obj_handle *parent, const char *name, struct fsal_obj_handle **obj, struct fsal_attrlist *attrs_out); fsal_status_t fsal_lookup_path(const char *path, struct fsal_obj_handle **dirobj); fsal_status_t fsal_lookupp(struct fsal_obj_handle *obj, struct fsal_obj_handle **parent, struct fsal_attrlist *attrs_out); fsal_status_t fsal_create(struct fsal_obj_handle *parent, const char *name, object_file_type_t type, struct fsal_attrlist *attrs, const char *link_content, struct fsal_obj_handle **obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out); void fsal_create_set_verifier(struct fsal_attrlist *sattr, uint32_t verf_hi, uint32_t verf_lo); bool fsal_create_verify(struct fsal_obj_handle *obj, uint32_t verf_hi, uint32_t verf_lo); fsal_status_t fsal_readdir(struct fsal_obj_handle *directory, uint64_t cookie, unsigned int *nbfound, bool *eod_met, attrmask_t attrmask, helper_readdir_cb cb, void *opaque); fsal_status_t fsal_remove(struct fsal_obj_handle *parent, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out); fsal_status_t fsal_rename(struct fsal_obj_handle *dir_src, const char *oldname, struct fsal_obj_handle *dir_dest, const char *newname, struct fsal_attrlist *olddir_pre_attrs_out, struct fsal_attrlist *olddir_post_attrs_out, struct fsal_attrlist *newdir_pre_attrs_out, struct fsal_attrlist *newdir_post_attrs_out); fsal_status_t fsal_open2(struct fsal_obj_handle *in_obj, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attr, fsal_verifier_t verifier, struct fsal_obj_handle **obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out); fsal_status_t fsal_reopen2(struct fsal_obj_handle *obj, struct state_t *state, fsal_openflags_t openflags, bool check_permission); fsal_status_t get_optional_attrs(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs_out); /** * @brief Close a file * * This handles both support_ex case and regular case (in case of * support_ex, close method is expected to manage whether file is * actually open or not, in old API case, close method should only * be closed if the file is open). * * In a change to the old way, non-regular files are just ignored. * * @param[in] obj File to close * @return FSAL status */ static inline fsal_status_t fsal_close(struct fsal_obj_handle *obj_hdl) { if (obj_hdl->type != REGULAR_FILE) { /* Can only close a regular file */ return fsalstat(ERR_FSAL_NO_ERROR, 0); } /* Return the result of close method. */ fsal_status_t status = obj_hdl->obj_ops->close(obj_hdl); if (status.major == ERR_FSAL_NOT_OPENED) { /* Wasn't open. Not an error, but shouldn't decrement */ status = fsalstat(ERR_FSAL_NO_ERROR, 0); } return status; } fsal_status_t fsal_statfs(struct fsal_obj_handle *obj, fsal_dynamicfsinfo_t *dynamicinfo); /** * @brief Commit a section of a file to storage * * @param[in] obj File to commit * @param[in] offset Offset for start of commit * @param[in] len Length of commit * @return FSAL status */ static inline fsal_status_t fsal_commit(struct fsal_obj_handle *obj, off_t offset, size_t len) { if ((uint64_t)len > ~(uint64_t)offset) return fsalstat(ERR_FSAL_INVAL, 0); return obj->obj_ops->commit2(obj, offset, len); } fsal_status_t fsal_verify2(struct fsal_obj_handle *obj, fsal_verifier_t verifier); fsal_status_t fsal_close2(struct fsal_obj_handle *obj); /** * @brief Prepare an fsal_attrlist for fetching attributes. * * @param[in,out] attrs The fsal_attrlist to work with * @param[in] The mask to use for the fetch * */ static inline void fsal_prepare_attrs(struct fsal_attrlist *attrs, attrmask_t request_mask) { memset(attrs, 0, sizeof(*attrs)); attrs->request_mask = request_mask; } /** * @brief Release any extra resources from an fsal_attrlist. * * @param[in] attrs The fsal_attrlist to work with * */ static inline void fsal_release_attrs(struct fsal_attrlist *attrs) { if (attrs->acl != NULL) { nfs4_acl_release_entry(attrs->acl); /* Poison the acl since we no longer hold a reference. */ attrs->acl = NULL; attrs->valid_mask &= ~ATTR_ACL; } if (attrs->fs_locations) { nfs4_fs_locations_release(attrs->fs_locations); attrs->fs_locations = NULL; attrs->valid_mask &= ~ATTR4_FS_LOCATIONS; } attrs->sec_label.slai_data.slai_data_len = 0; gsh_free(attrs->sec_label.slai_data.slai_data_val); attrs->sec_label.slai_data.slai_data_val = NULL; } /** * @brief Copy a set of attributes * * If ACL is requested in dest->request_mask, then ACL reference is acquired, * otherwise acl pointer is set to NULL. * * @param[in,out] dest The fsal_attrlist to receive the copy (mask must be * set) * @param[in] src The fsal_attrlist to make a copy of * @param[in] pass_refs If true, pass the ACL reference to dest. * */ static inline void fsal_copy_attrs(struct fsal_attrlist *dest, struct fsal_attrlist *src, bool pass_refs) { attrmask_t save_request_mask = dest->request_mask; if (dest == src || dest->acl != NULL) { LogCrit(COMPONENT_FSAL, "Invalid dest pointer, dest: %p, " "src: %p, ac: %p", dest, src, dest->acl); } /* Copy source to dest, but retain dest->request_mask */ *dest = *src; dest->request_mask = save_request_mask; if (pass_refs && ((save_request_mask & ATTR_ACL) != 0)) { /* Pass any ACL reference to the dest, so remove from * src without adjusting the refcount. */ src->acl = NULL; src->valid_mask &= ~ATTR_ACL; } else if (dest->acl != NULL && ((save_request_mask & ATTR_ACL) != 0)) { /* Take reference on ACL if necessary */ nfs4_acl_entry_inc_ref(dest->acl); } else { /* Make sure acl is NULL and don't pass a ref back (so * caller when calling fsal_release_attrs will not have to * release the ACL reference). * It might be that the src did include ATTR_ACL in the valid * mask but doesn't have a pointer to ACL as an indication * that ACL was not stored in the BE. In this case we don't * want to remove the ATTR_ACL from the valid_mask so a valid * response will be encoded in the reply. */ if (dest->acl != NULL) dest->valid_mask &= ~ATTR_ACL; dest->acl = NULL; } if (pass_refs && ((save_request_mask & ATTR4_FS_LOCATIONS) != 0)) { src->fs_locations = NULL; src->valid_mask &= ~ATTR4_FS_LOCATIONS; } else if (dest->fs_locations != NULL && ((save_request_mask & ATTR4_FS_LOCATIONS) != 0)) { nfs4_fs_locations_get_ref(dest->fs_locations); } else { dest->fs_locations = NULL; dest->valid_mask &= ~ATTR4_FS_LOCATIONS; } /* * Ditto for security label. Here though, we just make a copy if * needed. */ if (pass_refs && ((save_request_mask & ATTR4_SEC_LABEL) != 0)) { src->sec_label.slai_data.slai_data_len = 0; src->sec_label.slai_data.slai_data_val = NULL; src->valid_mask &= ~ATTR4_SEC_LABEL; } else if (dest->sec_label.slai_data.slai_data_val != NULL && ((save_request_mask & ATTR4_SEC_LABEL) != 0)) { dest->sec_label.slai_data.slai_data_val = (char *)gsh_memdup( dest->sec_label.slai_data.slai_data_val, dest->sec_label.slai_data.slai_data_len); } else { dest->sec_label.slai_data.slai_data_len = 0; dest->sec_label.slai_data.slai_data_val = NULL; dest->valid_mask &= ~ATTR4_SEC_LABEL; } } /** * @brief Return a changeid4 for this file. * * @param[in] obj The file to query. * * @return A changeid4 indicating the last modification of the file. */ static inline changeid4 fsal_get_changeid4(struct fsal_obj_handle *obj) { struct fsal_attrlist attrs; fsal_status_t status; changeid4 change; fsal_prepare_attrs(&attrs, ATTR_CHANGE); status = obj->obj_ops->getattrs(obj, &attrs); if (FSAL_IS_ERROR(status)) return 0; change = (changeid4)attrs.change; /* Done with the attrs */ fsal_release_attrs(&attrs); return change; } static inline enum fsal_create_mode nfs4_createmode_to_fsal(createmode4 createmode) { return (enum fsal_create_mode)(1 + (unsigned int)createmode); } static inline enum fsal_create_mode nfs3_createmode_to_fsal(createmode3 createmode) { return (enum fsal_create_mode)(1 + (unsigned int)createmode); } /** * @brief Determine if the openflags associated with an fd indicate it * is not open in a mode usable by the caller. * * The caller may pass FSAL_O_ANY to indicate any mode of open (RDONLY, * WRONLY, or RDWR is usable - often just to fetch attributes or something). * * @param[in] fd_openflags The openflags describing the fd * @param[in] to_openflags The openflags describing the desired mode */ static inline bool not_open_usable(fsal_openflags_t fd_openflags, fsal_openflags_t to_openflags) { /* 1. fd_openflags will NEVER be FSAL_O_ANY. * 2. If to_openflags == FSAL_O_ANY, the first half will be true if the * file is closed, and the second half MUST be true (per statement 1) * 3. If to_openflags is anything else, the first half will be true and * the second half will be true if fd_openflags does not include * the requested modes. */ return (to_openflags != FSAL_O_ANY || fd_openflags == FSAL_O_CLOSED) && ((fd_openflags & to_openflags) != to_openflags); } /** * @brief Determine if the openflags associated with an fd indicate it * is open in a mode usable by the caller. * * The caller may pass FSAL_O_ANY to indicate any mode of open (RDONLY, * WRONLY, or RDWR is usable - often just to fetch attributes or something). * * Note that this function is not just an inversion of the above function * because O_SYNC is not considered. * * @param[in] fd_openflags The openflags describing the fd * @param[in] to_openflags The openflags describing the desired mode */ static inline bool open_correct(fsal_openflags_t fd_openflags, fsal_openflags_t to_openflags) { return (to_openflags == FSAL_O_ANY && fd_openflags != FSAL_O_CLOSED) || (to_openflags != FSAL_O_ANY && (fd_openflags & to_openflags & FSAL_O_RDWR) == (to_openflags & FSAL_O_RDWR)); } /** * @brief "fsal_op_stats" struct useful for all the fsals which are going to * implement support for FSAL specific statistics */ struct fsal_op_stats { uint16_t op_code; uint64_t resp_time; uint64_t num_ops; uint64_t resp_time_max; uint64_t resp_time_min; }; struct fsal_stats { uint16_t total_ops; struct fsal_op_stats *op_stats; }; /* Async Processes that will be made synchronous */ struct async_process_data { /** Return from process */ fsal_status_t ret; /** Indicator callback is done. */ bool done; /** Mutex to protect done and condition variable. */ pthread_mutex_t *fsa_mutex; /** Condition variable to signal callback is done. */ pthread_cond_t *fsa_cond; }; extern void fsal_read2(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg); extern void fsal_read(struct fsal_obj_handle *obj_hdl, bool bypass, struct fsal_io_arg *arg, struct async_process_data *data); extern void fsal_write(struct fsal_obj_handle *obj_hdl, bool bypass, struct fsal_io_arg *arg, struct async_process_data *data); fsal_status_t fsal_listxattr_helper(const char *buf, size_t listlen, uint32_t maxbytes, nfs_cookie4 *lxa_cookie, bool_t *lxr_eof, xattrlist4 *lxr_names); /** * @brief Structure to hold FD LURU paramaters */ struct fd_lru_parameter { /** Base interval in seconds between runs of the LRU cleaner thread. Defaults to 90, settable with LRU_Run_Interval. */ uint32_t lru_run_interval; /** If Cache_FDs is false then FDs will remained cached till the LRU * reaper thread invokes and tries to close the FDs. * * If Cache_FDs is true (default) then FDs get cached and LRU reaper * thread on invocation will try to close the FDs only when the * currentopen >= fds_lowat (FD low watermark). */ bool Cache_FDs; /** * Whether to close files immediately after opening files and * using them for read/write/commit. Defaults to false, * settable with Close_Fast. */ bool close_fast; /** The percentage of the system-imposed maximum of file descriptors at which Ganesha will deny requests. Defaults to 99, settable with FD_Limit_Percent. */ uint32_t fd_limit_percent; /** The percentage of the system-imposed maximum of file descriptors above which Ganesha will make greater efforts at reaping. Defaults to 90, settable with FD_HWMark_Percent. */ uint32_t fd_hwmark_percent; /** The percentage of the system-imposed maximum of file descriptors below which Ganesha will not reap file descriptors. Defaults to 50, settable with FD_LWMark_Percent. */ uint32_t fd_lwmark_percent; /** Roughly, the amount of work to do on each pass through the thread under normal conditions. (Ideally, a multiple of the number of lanes.) Defaults to 1000, settable with Reaper_Work. */ uint32_t reaper_work; /** The amount of work for the reaper thread to do per-lane under normal conditions. Settable with Repaper_Work_Per_Thread */ uint32_t reaper_work_per_lane; /** The largest window (as a percentage of the system-imposed limit on FDs) of work that we will do in extremis. Defaults to 40, settable with Biggest_Window */ uint32_t biggest_window; /** Percentage of progress toward the high water mark required in a pass through the thread when in extremis. Defaults to 5, settable with Required_Progress. */ uint32_t required_progress; /** Number of failures to approach the high watermark before we disable caching, when in extremis. Defaults to 8, settable with Futility_Count */ uint32_t futility_count; uint32_t fd_fallback_limit; }; extern int32_t fsal_fd_global_counter; extern int32_t fsal_fd_state_counter; extern bool close_fast; void fsal_init_fds_limit(struct fd_lru_parameter *params); fsal_status_t fd_lru_pkginit(struct fd_lru_parameter *params); fsal_status_t fd_lru_pkgshutdown(void); #endif /* !FSAL_H */ /** @} */ nfs-ganesha-6.5/src/include/fsal_api.h000066400000000000000000003341321473756622300177440ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @defgroup FSAL File-System Abstraction Layer * @{ */ /** * @file fsal_api.h * @author Jim Lieb * @brief The object-oriented FSAL API */ #ifndef FSAL_API #define FSAL_API #include #include "fsal_types.h" #include "fsal_pnfs.h" #include "sal_shared.h" #include "config_parsing.h" #include "avltree.h" #include "abstract_atomic.h" #include "gsh_refstr.h" /** ** Forward declarations to resolve circular dependency conflicts */ struct gsh_client; struct gsh_export; struct fsal_up_vector; /* From fsal_up.h */ struct state_t; extern struct gsh_refstr *no_export; /** * @page newapi New FSAL API * * @section structs Public and Private Data Structures * * Shared FSAL data structures have two definitions, one that is * global and passed around by the core, the other private which * included the global definition within it. * * All these data structures are passed back to the core with the * global pointer and dereferenced with container_of within the FSAL * itself like so: * * @code{.c} * * struct private_obj_handle * { * [private stuff] * struct fsal_obj_handle *pub; * } * * fsal_getattr(struct fsal_obj_handle handle_pub) * { * struct private_obj_handle *handle; * * handle = container_of(handle_pub, * struct private_obj_handle, pub); * [ do stuff ] * } * @endcode * * The @c container_of macro takes the public pointer/handle @c * handle_pub which is indicated as the element @c pub of structure * type @c private_obj_handle. Throughout the function, where private * elements are dereferenced, the @c handle pointer is used. The @c * handle_pub pointer is used in the public case. * * @section usage Object usage * * Mutex locks and reference counts are used to manage both concurrent * usage and state. The reference counts are use to determine when * the object is "free". Current use is for managing ref counts and * lists. This will be expanded, though many cases are already * handled by the locks in MDCACHE. * * Since we cannot create objects out of thin air, there is an order * based on one object being the "context" in which the other is * created. In other words, a @c fsal_export is created from the @c * fsal_module that connects it to the backing store (filesystem). The * same applies to a @c fsal_obj_handle that only makes sense for a * specific 'fsal_export'. * * When an object is created, it is returned with a reference already * taken. The callee of the creating method must then either keep a * persistent reference to it or @c put it back. For example, a @c * fsal_export gets created for each export in the configuration. A * pointer to it gets saved in @c gsh_export and it has a reference * to reflect this. It is now safe to use it to do a @c lookup which * will return a @c fsal_obj_handle which can then be kept in a cache * inode entry. If we had done a @c put on the export, it could be * freed at any point and make a @c lookup using it unsafe. * * In addition to a reference count, object that create other objects * have a list of all the objects they create. This serves two * purposes. The obvious case is to keep the object "busy" until all * of its children are freed. Second, it provides a means to visit * all of the objects it creates. * * Every object has a pointer to its parent. This is used for such * things as managing the object list and for calling methods on the * parent. * * @section versioning Versioning * * One intent in this API is to be able to support fsals that are built * out-of-tree and possibly out of synch with the core of Ganesha. This * is managed by version numbers in this file that are validated at load * time for the fsal. There are major and minor version numbers which are * monotonically increasing numbers ( V1 < V2 means V2 is newer). * * API guarantee: * * * If major version numbers differ, the fsal will not be loaded because * the api has changed enough to make it unsafe. * * * If the major versions are equal, the minor version determines loadability. * * - A fsal that is older than the Ganesha core can safely load and run. * * - A fsal that is newer than the Ganesha core is not safe and will not * be loaded. * * * @section vector Operation Vectors * * Each structure carries with it an @c ops pointer. Default * operation vectors are created at FSAL module initialziation time, * and may be overridden there. Individual exports or handles may * have different operations vectors, but they should all be derived * from the module operations vector. * * This vector is used to access methods e.g.: * * @code{.c} * exp_hdl->exp_ops.lookup(exp_hdl, name, ...); * @endcode * * Note that exp_hdl is used to dereference the method and it is also * *always* the first argument to the method/function. Think of it as * the 'this' argument. * * @section Operation Context * * Protocol operations have lots of state such as user creds, the * export currently in use etc. Rather than pass all this down the * stack we take advantage of the design decision that a protocol * operation runs to completion in the thread that dequeued the * request from the RPC. All of the operation state (other than * some intermediate results passed as function args) are pointed * to by the thread local 'op_ctx'. This will always point to a * valid and initialized 'struct req_op_context'. * * Method code can reference through 'op_ctx' e.g. * * @code{.c} * if (op_ctx->req_type == 9P) { ... } * @endcode * */ /** * @page handles File-Handles and You * * Overview * ======== * * First off, there is the fsal_obj_handle which is usually extended by the * FSAL. This is a memory object that describes an instance of a file within * the Ganesha structure. It contains all the information the FSAL needs to * interact with the underlying file system for that file. * * Next are three forms of what are more traditionally considered a file * handle. The host handle, the handle-key, and the wire handle. * * The host handle is the set of components the underlying file system * needs to find a file object given only a handle and not path and file name. * This might be information such as file system id, inode number, inode * generation, and maybe even directory parent information. * * The wire handle is the handle form shared with NFS clients. The upper * protocol layers encapsulate this handle with additional information including * the export_id which allows the upper layer to find the FSAL that owns the * handle. The wire handle may be byte swapped or have other transformations * from the host handle. * * The final handle form is the handle-key or simply key. This is used by the * MDACHE stackable FSAL (which is in fact always present). This handle may have * less information than the wire or host handles for FSALs that have multiple * forms of the same handle (for example, if directory parent is part of the * host and wire handle, a moved or linked file might have different * directory parents but we need a single cache object to reference the file. * In this case, the key strips out that directory parent information. Other * FSALs may actually need to have a separate cache object per export and thus * ADD the export_id to the key. This does cause problems but at the moment is * the best solution for the CEPH FSAL. A FSAL that has a larger key than the * host handle MUST NOT make it larger by more than FSAL_KEY_EXTRA_BYTES. * * There are two methods that convert between these forms: * * wire_to_host converts a wire handle to a host handle. It would handle any * byte swapping. * * host_to_key converts a host handle into a handle-key. It is allowed to * return a larger key than the host handle, but it may only increase the * size by FSAL_KEY_EXTRA_BYTES defined below. NOTE that callers of * host_to_key MUST make room for at least this many bytes past the size of * the host handle. * * Note that there are no key_to_X methods. The mdcache (almost) always has a * reference to a fsal_obj_handle (after all, that's what it's caching...). The * exception is the dirent cache which caches host handles to provide a weak * link to the file system object rather than a strong link to a * fsal_obj_handle (if the object is in cache, a simple cache lookup will * resolve the object, host_to_key will be used during this lookup process). * * The host_to_X transformations are handled by the methods below, * handle_to_wire and handle_to_key. * * There are three methods that work to interface between handles and * fsal_obj_handles: * * create_handle takes a host handle and calls to the underlying file system * to instantiate a fsal_obj_handle for that object. * * handle_to_wire takes a fsal_obj_handle and produces a wire handle to be * passed to the protocol layer to be encapsulated and passed to a client. * * handle_to_key takes a fsal_obj_handle and produces a handle-key to allow * the object to be placed in the mdcache. Note that in some sense this does * the same conversion as host_to_key. * * The invariant to be maintained is that given an @c fsal_obj_handle, * obj_hdl, exp_ops.host_to_key(wire_to_host(handle_to_wire(obj_hdl))) * is equal to obj_ops->handle_to_key(obj_hdl). * * History and Details * =================== * * The terminology is confusing here. The old function names were * kept (up to a point), but the semantics differ in ways both subtle * and catastrophic. Making matters worse, that the first FSAL written * was VFS, where the internal @c file_handle for the syscalls is the * whole of the key, opaque, and syscall arg. This does not imply any * equivalence. * * In the old regime, the only place available to store _anything_ was * the handle array in @c cache_entry_t. People overloaded it with * all kinds of rubbish as a result, and the wire-handle, the * handle-key, and other stuff get mushed together. To sort things * out, * * 1. The wire-handle opaque _must_ be enough to re-acquire the cache * entry and its associated @c fsal_obj_handle. Other than that, * it doesn't matter a whit. The client treats the whole protocol * handle (including what is in the opaque) as an opaque token. * * 2. The purpose of the @c export_id in the protocol "handle" is to * locate the FSAL that knows what is inside the opaque. The @c * wire_to_host is an export method for that purpose. It should * be able to take the wire-handle opaque and translate it into * a host-handle. handle-key should be derived from @c host_to_key * that MDCACHE can use to find an entry. * * 3. The @c handle_to_key method, a @c fsal_obj_handle method, * generates a key for the MDCACHE hash table from the contents * of the @c fsal_obj_handle. It is an analogue of fsal export * @c host_to_key method. Note where it is called to see why it is * there. * * 4. The @c handle_to_wire method is similar in scope but it is the * inverse of @c wire_to_host. It's job is to fill in the opaque * part of a protocol handle. Note that it gets passed a @c gsh_buffdesc * that describes the full opaque storage in whatever protocol * specific structure is used. It's job is to put whatever it * takes into the opaque so the second and third items in this list * work. * * 5. Unlike the old API, a @c fsal_obj_handle is part of a FSAL * private structure for the object. Note that there is no handle * member of this public structure. The bits necessary to both * create a wire handle and use a filesystem handle go into this * private structure. You can put whatever is required into the * private part. Since both @c fsal_export and @c fsal_obj_handle * have private object storage, you could even do things like have * a container anchored in the export object that maps the * FSAL-external handle to the filesystem data needed to talk to * the filesystem. If you need more info to deal with handles * differing due to hard-links, this is where you would put * it. You would also have some other context in this private data * to do the right thing. Just make sure there is a way to * disambiguate the multiple cases. We do have to observe UNIX * semantics here. * * The upper layers don't care about the private handle data. All * they want is to be able to get something out from the object * (result of a lookup) so it can find the object again later. The * obvious case is what you describe in @c nfs[34]_FhandleToCache. These * various methods make that happen. * */ /** * @brief Major Version * * Increment this whenever any part of the existing API is changed, * e.g. the argument list changed or a method is removed. * * Technically this should also change if the libganesha_nfsd.so exported API * changes. */ #define FSAL_MAJOR_VERSION 13 /** * @brief Minor Version * * Increment this whenever a new method is appended to the m_ops vector. * The remainder of the API is unchanged. * * If the major version is incremented, reset the minor to 0 (zero). * * If new members are appended to struct req_op_context (following its own * rules), increment the minor version */ #define FSAL_MINOR_VERSION 0 /* Forward references for object methods */ struct fsal_module; struct fsal_export; struct fsal_obj_handle; struct fsal_filesystem; struct fsal_pnfs_ds; struct fsal_pnfs_ds_ops; struct fsal_ds_handle; struct fsal_dsh_ops; /* Allow extra space in handle-key for expansion beyond the size of the host * handle. Used by callers of host_to_key. */ #define FSAL_KEY_EXTRA_BYTES 8 #ifndef SEEK_SET #define SEEK_SET 0 #endif #ifndef SEEK_CUR #define SEEK_CUR 1 #endif #ifndef SEEK_END #define SEEK_END 2 #endif #ifndef SEEK_DATA #define SEEK_DATA 3 #endif #ifndef SEEK_HOLE #define SEEK_HOLE 4 #endif struct io_info { contents io_content; uint32_t io_advise; bool_t io_eof; }; struct io_hints { offset4 offset; length4 count; uint32_t hints; }; enum request_type { UNKNOWN_REQUEST, NFS_REQUEST, NFS_RELATED, #ifdef _USE_9P _9P_REQUEST, #endif /* _USE_9P */ }; /** * @brief request op context * * This is created early in the operation with the context of the * operation. The difference between "context" and request parameters * or arguments is that the context is derived information such as * the resolved credentials, socket (network and client host) data * and other bits of environment associated with the request. It gets * passed down the call chain only as far as it needs to go for the op * i.e. don't put it in the function/method proto "just because". * * The lifetime of this structure and all the data it points to is the * operation for V2,3 and the compound for V4+. All elements and what * they point to are invariant for the lifetime. * * If an op context is active, ctx_export MAY be NULL (very rare conditions) * but ctx_fullpath and ctx_pseudopath must always be valid. The function * init_op_context() assures this and init_op_context() and resum_op_context() * are the only functions that can ever set op_ctx to a non-NULL value, and * resume_op_context() must be setting it to point to an op context initialized * with init_op_context(). * * NOTE: This is an across-the-api shared structure. Changing it implies a * change in the FSAL API. * * NOTE: There is a set of functions in fsal.h to initialize and manage the * req_op_context. Please use them... * * NOTE: Usually a gsh_export is referenced by the req_op_context, it is * expected that a reference to that gsh_export will be held for the * duration of the time it's attached to the req_op_context or the life * of the req_op_context. To this extent, the responsibility to make the * put_gsh_export is now borne by the req_op_context management functions. * Specifically release_op_contextm, clear_op_context_export, * set_op_context_export, set_op_context_export_fsal, and * restore_op_context_export will all call put_gsh_export if there is an * attached export. * * The functions that manage a saved_export_context maintain a reference * to the gsh_export and thus discard_op_context_export will clean that * up since the saved req_op_context will not be restored. * * NOTE: In support of exports having fullpath and pseudopath changeable, * those strings are now accessed by gsh_refstr. In order to simplify * the bulk of access to those strings for op_cxt->ctx_export, whenever * ctx_export is changed, proper references to those strings are taken * and stored in the req_op_context. Since those references can not be * changed by any other thread, they are safe to access without RCU * protection. Further if for any reason, those strings are not available, * particularly when no export is attached, a reference to a "No Export" * string is taken, so it is ALWAYS safe to use these strings, there is * ALWAYS a valid reference which is safe to use in the context of the * thread owning the req_op_context. There are a set of functions that * make it easy to access these strings, and to access the "best" string * in the context of which string is used for NFS v3 MOUNT requests. * * NOTE: Any function that causes a saved op_context MUST not suspend the * op_context. This is because the suspend and resume can not rely on * utilizing saved_op_ctx. An op_context that relied on saved_op_ctx is * intended for doing some processing on an export other than the one * currently in use, or needs to do something with different credentials. */ struct req_op_context { struct req_op_context *saved_op_ctx; /* saved op_ctx */ struct user_cred creds; /*< resolved user creds from request */ struct user_cred original_creds; /*< Saved creds */ struct group_data *caller_gdata; gid_t *caller_garray_copy; /*< Copied garray from AUTH_SYS */ gid_t *managed_garray_copy; /*< Copied garray from managed gids */ int cred_flags; /* Various cred flags */ sockaddr_t *caller_addr; /*< IP connection info */ const uint64_t *clientid; /*< Client ID of caller, NULL if unknown/not applicable. */ uint32_t nfs_vers; /*< NFS protocol version of request */ uint32_t nfs_minorvers; /*< NFSv4 minor version */ enum request_type req_type; /*< request_type NFS | 9P */ struct gsh_client *client; /*< client host info including stats */ struct gsh_export *ctx_export; /*< current export, this MUST only be changed by one of the functions in commonlib.c. */ struct gsh_refstr *ctx_fullpath; /*< current fullpath */ struct gsh_refstr *ctx_pseudopath; /*< current pseudopath */ struct fsal_export *fsal_export; /*< current fsal export */ struct export_perms export_perms; /*< Effective export perms */ struct timespec start_time; /*< start time of this op/request */ void *fsal_private; /*< private for FSAL use */ void *proto_private; /*< private for protocol layer use */ bool is_rdma_buff_used; /*< Whether RDMA buffer is being used */ struct fsal_module *fsal_module; /*< current fsal module */ struct fsal_pnfs_ds *ctx_pnfs_ds; /*< current pNFS DS */ nfs_request_t * nfs_reqdata; /* Request data (if op is originated from a client request) */ uint32_t op_id; struct { bool pseudo_fsal_internal_lookup; } flags; }; /** * @brief Structure to save export context from op_context * * When we need to temporarily change the export in op_context, we can save * the context here. Use of this is cheaper than using set_op_context_export * to restore the op context since various references need not be retaken. */ struct saved_export_context { struct gsh_export *saved_export; struct gsh_refstr *saved_fullpath; /*< saved fullpath */ struct gsh_refstr *saved_pseudopath; /*< saved pseudopath */ struct fsal_export *saved_fsal_export; struct fsal_module *saved_fsal_module; struct fsal_pnfs_ds *saved_pnfs_ds; /*< saved pNFS DS */ struct export_perms saved_export_perms; }; /* Anything using these expects a valid op context and a valid op context * always had at least a reference to the no_export string. */ #define CTX_PSEUDOPATH(ctx) (ctx->ctx_pseudopath->gr_val) #define CTX_FULLPATH(ctx) (ctx->ctx_fullpath->gr_val) /** * @brief FSAL module methods */ struct fsal_ops { /**@{*/ /** * Base methods for loading and lifetime. */ /** * @brief Unload a module * * This function unloads the FSL module. It should not be overridden. * * @param[in] fsal_hdl The module to unload. * * @retval 0 On success. * @retval EBUSY If there are outstanding references or exports. */ int (*unload)(struct fsal_module *fsal_hdl); /**@}*/ /**@{*/ /** * Subclass/instance methods in each fsal */ /** * @brief Initialize the configuration * * Given the root of the Ganesha configuration structure, initialize * the FSAL parameters. * * @param[in] fsal_hdl The FSAL module * @param[in] config_struct Parsed ganesha configuration file * @param[out]err_type config error processing state * * @return FSAL status. */ fsal_status_t (*init_config)(struct fsal_module *fsal_hdl, config_file_t config_struct, struct config_error_type *err_type); /** * @brief Update the configuration * * Given the root of the Ganesha configuration structure, Update * the FSAL parameters. * * @param[in] fsal_hdl The FSAL module * @param[in] config_struct Parsed ganesha configuration file * @param[out]err_type config error processing state * * @return FSAL status. */ fsal_status_t (*update_config)(struct fsal_module *fsal_hdl, config_file_t config_struct, struct config_error_type *err_type); /** * @brief Dump configuration * * This function dumps a human readable representation of the FSAL * configuration. * * @param[in] fsal_hdl The FSAL module. * @param[in] log_fd File descriptor to which to output the dump */ void (*dump_config)(struct fsal_module *fsal_hdl, int log_fd); /** * @brief Create a new export * * This function creates a new export in the FSAL using the supplied * path and options. The function is expected to allocate its own * export (the full, private structure). It must then initialize the * public portion like so: * * @code{.c} * fsal_export_init(&private_export_handle->pub); * @endcode * * After doing other private initialization, it must attach the export * to the module, like so: * * * @code{.c} * fsal_attach_export(fsal_hdl, * &private_export->pub.exports); * * @endcode * * And create the parent link with: * * @code{.c} * private_export->pub.fsal = fsal_hdl; * @endcode * * @note This seems like something that fsal_attach_export should * do. -- ACE. * * @param[in] fsal_hdl FSAL module * @param[in] parse_node opaque pointer to parse tree node for * export options to be passed to * load_config_from_node * @param[out] err_type config processing error reporting * @param[in] up_ops Upcall ops * * @return FSAL status. */ fsal_status_t (*create_export)(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *up_ops); /** * @brief Update an existing export * * This will result in a temporary fsal_export being created, and built into * a stacked export. * * On entry, op_ctx has the original gsh_export and no fsal_export. * * The caller passes the original fsal_export, as well as the new super_export's * FSAL when there is a stacked export. This will allow the underlying export to * validate that the stacking has not changed. * * This function does not actually create a new fsal_export, the only purpose is * to validate and update the config. * * @param[in] fsal_hdl FSAL module * @param[in] parse_node opaque pointer to parse tree node for * export options to be passed to * load_config_from_node * @param[out] err_type config processing error reporting * @param[in] original The original export that is being updated * @param[in] updated_super The updated super_export's FSAL * * @return FSAL status. */ fsal_status_t (*update_export)(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, struct fsal_export *original, struct fsal_module *updated_super); /** * @brief Minimal emergency cleanup on error * * This method is called only in the event of a catastrophic * failure. Currently, it will be called if some detail of the orderly * shutdown fails, so that FSALs will have the opportunity to leave * their underlying filesystems in a consistent state. It may at some * later time be called in the event of a crash. The majority of FSALs * will have no need to implement this call and should not do so. * * This function should, if implemented: * * 1. Do the bare minimum necessary to allow access to the each * underlying filesystem it serves. (the equivalent of a clean * unmount, so that a future instance of Ganesha or other tool can * mount the filesystem without difficulty.) How the FSAL defines * 'underlying filesystem' is FSAL specific. The FSAL handle itself * has a list of attached exports and that can be traversed if * suitable. * * 2. It /must not/ take any mutexes, reader-writer locks, spinlocks, * sleep on any condition variables, or similar. Since other threads * may have crashed or been cancelled, locks may be left held, * overwritten with random garbage, or be similarly awful. The point * is to shut down cleanly, and you can't shut down cleanly if you're * hung. This does not create a race condition, since other threads in * Ganesha will have been cancelled by this point. * * 3. If it is at all possible to avoid, do not allocate memory on the * heap or use other services that require the user space to be in a * consistent state. If this is called from a crash handler, the Arena * may be corrupt. If you know that your FSAL *will* require memory, * you should either allocate it statically, or dynamically at * initialization time. */ void (*emergency_cleanup)(void); /** * pNFS functions */ /** * @brief Get information about a pNFS device * * When this function is called, the FSAL should write device * information to the @c da_addr_body stream. * * @param[in] fsal_hdl FSAL module * @param[out] da_addr_body An XDR stream to which the FSAL is to * write the layout type-specific information * corresponding to the deviceid. * @param[in] type The type of layout that specified the * device * @param[in] deviceid The device to look up * * @return Valid error codes in RFC 5661, p. 365. */ nfsstat4 (*getdeviceinfo)(struct fsal_module *fsal_hdl, XDR *da_addr_body, const layouttype4 type, const struct pnfs_deviceid *deviceid); /** * @brief Max Size of the buffer needed for da_addr_body in getdeviceinfo * * This function sets policy for XDR buffer allocation in getdeviceinfo. * If FSAL has a const size, just return it here. If it is dependent on * what the client can take return ~0UL. In any case the buffer allocated will * not be bigger than client's requested maximum. * * @param[in] exp_hdl Filesystem to interrogate * * @return Max size of the buffer needed for a da_addr_body */ size_t (*fs_da_addr_size)(struct fsal_module *fsal_hdl); /** * @brief Create a FSAL pNFS data server * * @param[in] fsal_hdl FSAL module * @param[in] parse_node opaque pointer to parse tree node for * export options to be passed to * load_config_from_node * @param[out] handle FSAL pNFS DS * * @return FSAL status. */ fsal_status_t (*create_fsal_pnfs_ds)(struct fsal_module *const fsal_hdl, void *parse_node, struct fsal_pnfs_ds **const handle); /** * @brief Initialize FSAL specific values for pNFS data server * * @param[in] ops FSAL pNFS Data Server operations vector */ void (*fsal_pnfs_ds_ops)(struct fsal_pnfs_ds_ops *ops); /** * @brief Provides function to extract FSAL stats * * @param[in] fsal_hdl FSAL module * @param[in] iter opaque pointer to DBusMessageIter */ void (*fsal_extract_stats)(struct fsal_module *const fsal_hdl, void *iter); /** * @brief FSAL function to reset FSAL stats * * @param[in] fsal_hdl FSAL module */ void (*fsal_reset_stats)(struct fsal_module *const fsal_hdl); /**@}*/ }; /** * @brief Export operations */ struct export_ops { /**@{*/ /** * Export information */ /** * @brief Get the name of the FSAL provisioning the export * * This function is used to find the name of the ultimate FSAL providing the * filesystem. If FSALs are stacked, then the super-FSAL may want to pass this * through to the sub-FSAL to get the name, or add the sub-FSAL's name onto it's * own name. * * @param[in] exp_hdl The export to query. * @return Name of FSAL provisioning export */ const char *(*get_name)(struct fsal_export *exp_hdl); /**@}*/ /**@{*/ /** * Export lifecycle management. */ /** * @brief Prepare an export to be unexported * * This function is called prior to unexporting an export. It should do any * preparation that the export requires prior to being removed. */ void (*prepare_unexport)(struct fsal_export *exp_hdl); /** * @brief Clean up an export when it's unexported * * This function is called when the export is unexported. It should release any * working data that is not necessary when unexported, but not free the export * itself, as there are still references to it. * * @param[in] exp_hdl The export to unexport. * @param[in] root_obj The root object of the export */ void (*unexport)(struct fsal_export *exp_hdl, struct fsal_obj_handle *root_obj); /** * @brief Handle the unmounting of an export. * * This function is called when the export is unmounted. The FSAL may need * to clean up references to the junction_obj and parent export. * * Specifically, mdcache must remove the export mapping and possibly schedule * the junction node for cleanup (which may be the same node as the unmounted * export's root node). * * The caller is expected to hold a reference to the junction_obj. * * @param[in] parent_exp_hdl The parent export of the mount. * @param[in] junction_obj The junction object the export was mounted on */ void (*unmount)(struct fsal_export *parent_exp_hdl, struct fsal_obj_handle *junction_obj); /** * @brief Finalize an export * * This function is called as part of cleanup when the last reference to * an export is released and it is no longer part of the list. It * should clean up all private resources and destroy the object. * * @param[in] exp_hdl The export to release. */ void (*release)(struct fsal_export *exp_hdl); /**@}*/ /**@{*/ /** * Create an object handles within this export */ /** * @brief Look up a path * * This function looks up a path within the export, it is now exclusively * used to get a handle for the root directory of the export. * * NOTE: This method will eventually be replaced by a method that simply * requests the root obj_handle be instantiated. The single caller * doesn't request attributes (nor did the two callers that were removed * in favor of calling fsal_lookup_path). * * The caller will set the request_mask in attrs_out to indicate the attributes * of interest. ATTR_ACL SHOULD NOT be requested and need not be provided. If * not all the requested attributes can be provided, this method MUST return * an error unless the ATTR_RDATTR_ERR bit was set in the request_mask. * * Since this method instantiates a new fsal_obj_handle, it will be forced * to fetch at least some attributes in order to even know what the object * type is (as well as it's fileid and fsid). For this reason, the operation * as a whole can be expected to fail if the attributes were not able to be * fetched. * * @param[in] exp_hdl The export in which to look up * @param[in] path The path to look up * @param[out] handle The object found * @param[in,out] attrs_out Optional attributes for newly created object * * @note On success, @a handle has been ref'd * * @return FSAL status. */ fsal_status_t (*lookup_path)(struct fsal_export *exp_hdl, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); /** * @brief Look up a junction * * This function returns a handle for the directory behind a junction * object. * * @deprecated This function is not implemented by any FSAL nor is it * called. It exists here as a placeholder for implementation in 2.1 * as part of the PseudoFSAL work. Its argument structure will almost * certainly change. * * @param[in] exp_hdl Export in which to look up * @param[in] junction The junction object * @param[out] handle The underlying directory handle * * @return FSAL status. */ fsal_status_t (*lookup_junction)(struct fsal_export *exp_hdl, struct fsal_obj_handle *junction, struct fsal_obj_handle **handle); /** * @brief Convert a wire handle to a host handle * * This function extracts a host handle from a wire handle. That * is, when given a handle as passed to a client, this method will * extract the handle to create objects. * * @param[in] exp_hdl Export handle * @param[in] in_type Protocol through which buffer was received. * @param[in] flags Flags to describe the wire handle. Example, if * the handle is a big endian handle. * @param[in,out] fh_desc Buffer descriptor. The address of the * buffer is given in @c fh_desc->buf and must * not be changed. @c fh_desc->len is the * length of the data contained in the buffer, * @c fh_desc->len must be updated to the correct * host handle size. * * @return FSAL type. */ fsal_status_t (*wire_to_host)(struct fsal_export *exp_hdl, fsal_digesttype_t in_type, struct gsh_buffdesc *fh_desc, int flags); /** * @brief extract "key" from a host handle * * This function extracts a "key" from a host handle. That is, when * given a handle that is extracted from wire_to_host() above, this * method will extract the unique bits used to index the inode cache. * * NOTE: Callers MUST make sure the passed in buffer has at least enough * room to hold the host handle PLUS FSAL_KEY_EXTRA_BYTES (defined * above). * * @param[in] exp_hdl Export handle * @param[in,out] fh_desc Buffer descriptor. The address of the * buffer is given in @c fh_desc->buf and must * not be changed. @c fh_desc->len is the length * of the data contained in the buffer, @c * fh_desc->len must be updated to the correct * size. In other words, the key has to be placed * at the beginning of the buffer! */ fsal_status_t (*host_to_key)(struct fsal_export *exp_hdl, struct gsh_buffdesc *fh_desc); /** * @brief Create a FSAL object handle from a host handle * * This function creates a FSAL object handle from a host handle * (when an object is no longer in cache but the client still remembers * the handle). * * The caller will set the request_mask in attrs_out to indicate the attributes * of interest. ATTR_ACL SHOULD NOT be requested and need not be provided. If * not all the requested attributes can be provided, this method MUST return * an error unless the ATTR_RDATTR_ERR bit was set in the request_mask. * * Since this method instantiates a new fsal_obj_handle, it will be forced * to fetch at least some attributes in order to even know what the object * type is (as well as it's fileid and fsid). For this reason, the operation * as a whole can be expected to fail if the attributes were not able to be * fetched. * * @param[in] exp_hdl The export in which to create the handle * @param[in] hdl_desc Buffer descriptor for the host handle * @param[out] handle FSAL object handle * @param[in,out] attrs_out Optional attributes for newly created object * * @note On success, @a handle has been ref'd * * @return FSAL status. */ fsal_status_t (*create_handle)(struct fsal_export *exp_hdl, struct gsh_buffdesc *fh_desc, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); /** * @brief Function to get the fasl_obj_handle that has fsal_fd as its global fd. * * @param[in] exp_hdl The export in which the handle exists * @param[in] fd File descriptor in question * @param[out] handle FSAL object handle * * @return the fsal_obj_handle. */ void (*get_fsal_obj_hdl)(struct fsal_export *exp_hdl, struct fsal_fd *fd, struct fsal_obj_handle **handle); /**@}*/ /**@{*/ /** * Statistics and configuration for this filesystem */ /** * @brief Get filesystem statistics * * This function gets information on inodes and space in use and free * for a filesystem. See @c fsal_dynamicinfo_t for details of what to * fill out. * * @param[in] exp_hdl Export handle to interrogate * @param[in] obj_hdl Directory * @param[out] info Buffer to fill with information * * @retval FSAL status. */ fsal_status_t (*get_fs_dynamic_info)(struct fsal_export *exp_hdl, struct fsal_obj_handle *obj_hdl, fsal_dynamicfsinfo_t *info); /** * @brief Export feature test * * This function checks whether a feature is supported on this * filesystem. The features that can be interrogated are given in the * @c fsal_fsinfo_options_t enumeration. * * @param[in] exp_hdl The export to interrogate * @param[in] option The feature to query * * @retval true if the feature is supported. * @retval false if the feature is unsupported or unknown. */ bool (*fs_supports)(struct fsal_export *exp_hdl, fsal_fsinfo_options_t option); /** * @brief Get the greatest file size supported * * @param[in] exp_hdl Filesystem to interrogate * * @return Greatest file size supported. */ uint64_t (*fs_maxfilesize)(struct fsal_export *exp_hdl); /** * @brief Get the greatest read size supported * * @param[in] exp_hdl Filesystem to interrogate * * @return Greatest read size supported. */ uint32_t (*fs_maxread)(struct fsal_export *exp_hdl); /** * @brief Get the greatest write size supported * * @param[in] exp_hdl Filesystem to interrogate * * @return Greatest write size supported. */ uint32_t (*fs_maxwrite)(struct fsal_export *exp_hdl); /** * @brief Get the greatest link count supported * * @param[in] exp_hdl Filesystem to interrogate * * @return Greatest link count supported. */ uint32_t (*fs_maxlink)(struct fsal_export *exp_hdl); /** * @brief Get the greatest name length supported * * @param[in] exp_hdl Filesystem to interrogate * * @return Greatest name length supported. */ uint32_t (*fs_maxnamelen)(struct fsal_export *exp_hdl); /** * @brief Get the greatest path length supported * * @param[in] exp_hdl Filesystem to interrogate * * @return Greatest path length supported. */ uint32_t (*fs_maxpathlen)(struct fsal_export *exp_hdl); /** * @brief Get supported ACL types * * This function returns a bitmask indicating whether it supports * ALLOW, DENY, neither, or both types of ACL. * * @note Could someone with more ACL support tell me if this is sane? * Is it legitimate for an FSAL supporting ACLs to support just ALLOW * or just DENY without supporting the other? It seems fishy to * me. -- ACE * * @param[in] exp_hdl Filesystem to interrogate * * @return supported ACL types. */ fsal_aclsupp_t (*fs_acl_support)(struct fsal_export *exp_hdl); /** * @brief Get supported attributes * * This function returns a list of all attributes that this FSAL will * support. Be aware that this is specifically the attributes in * struct fsal_attrlist, other NFS attributes (fileid and so forth) are * supported through other means. * * @param[in] exp_hdl Filesystem to interrogate * * @return supported attributes. */ attrmask_t (*fs_supported_attrs)(struct fsal_export *exp_hdl); /** * @brief Get umask applied to created files * * @note This seems fishy to me. Is this actually supported properly? * And is it something we want the FSAL being involved in? We already * have the functions in Protocol/NFS specifying a default mode. -- ACE * * @param[in] exp_hdl Filesystem to interrogate * * @return creation umask. */ uint32_t (*fs_umask)(struct fsal_export *exp_hdl); /**@}*/ /**@{*/ /** * Quotas are managed at the file system (export) level. Someone who * uses quotas, please look over these comments to check/expand them. */ /** * @brief Check if quotas allow an operation * * This function checks to see if a user has overrun a quota and * should be disallowed from performing an operation that would * consume blocks or inodes. * * @param[in] exp_hdl The export to interrogate * @param[in] filepath The path within the export to check * @param[in] quota_type Whether we are checking inodes or blocks * * @return FSAL types. */ fsal_status_t (*check_quota)(struct fsal_export *exp_hdl, const char *filepath, int quota_type); /** * @brief Get a user's quota * * This function retrieves a given user's quota. * * @param[in] exp_hdl The export to interrogate * @param[in] filepath The path within the export to check * @param[in] quota_type Whether we are checking inodes or blocks * @param[in] quota_id Id for which quota is set * @param[out] quota The user's quota * * @return FSAL types. */ fsal_status_t (*get_quota)(struct fsal_export *exp_hdl, const char *filepath, int quota_type, int quota_id, fsal_quota_t *quota); /** * @brief Set a user's quota * * This function sets a user's quota. * * @param[in] exp_hdl The export to interrogate * @param[in] filepath The path within the export to check * @param[in] quota_type Whether we are checking inodes or blocks * @param[in] quota_id Id for which quota is set * @param[in] quota The values to set for the quota * @param[out] resquota New values set (optional) * * @return FSAL types. */ fsal_status_t (*set_quota)(struct fsal_export *exp_hdl, const char *filepath, int quota_type, int quota_id, fsal_quota_t *quota, fsal_quota_t *resquota); /**@}*/ /**@{*/ /** * pNFS functions */ /** * @brief Get list of available devices * * This function should populate calls @c cb @c values representing the * low quad of deviceids it wishes to make the available to the * caller. it should continue calling @c cb until @c cb returns false * or it runs out of deviceids to make available. If @c cb returns * false, it should assume that @c cb has not stored the most recent * deviceid and set @c res->cookie to a value that will begin with the * most recently provided. * * If it wishes to return no deviceids, it may set @c res->eof to true * without calling @c cb at all. * * @param[in] exp_hdl Export handle * @param[in] type Type of layout to get devices for * @param[in] cb Function taking device ID halves * @param[in,out] res In/out and output arguments of the function * * @return Valid error codes in RFC 5661, pp. 365-6. */ nfsstat4 (*getdevicelist)(struct fsal_export *exp_hdl, layouttype4 type, void *opaque, bool (*cb)(void *opaque, const uint64_t id), struct fsal_getdevicelist_res *res); /** * @brief Get layout types supported by export * * This function is the handler of the NFS4.1 FATTR4_FS_LAYOUT_TYPES file * attribute. (See RFC) * * @param[in] exp_hdl Filesystem to interrogate * @param[out] count Number of layout types in array * @param[out] types Static array of layout types that must not be * freed or modified and must not be dereferenced * after export reference is relinquished */ void (*fs_layouttypes)(struct fsal_export *exp_hdl, int32_t *count, const layouttype4 **types); /** * @brief Get layout block size for export * * This function is the handler of the NFS4.1 FATTR4_LAYOUT_BLKSIZE f-attribute. * * This is the preferred read/write block size. Clients are requested * (but don't have to) read and write in multiples. * * NOTE: The linux client only asks for this in blocks-layout, where this is the * filesystem wide block-size. (Minimum write size and alignment) * * @param[in] exp_hdl Filesystem to interrogate * * @return The preferred layout block size. */ uint32_t (*fs_layout_blocksize)(struct fsal_export *exp_hdl); /** * @brief Maximum number of segments we will use * * This function returns the maximum number of segments that will be * used to construct the response to any single layoutget call. Bear * in mind that current clients only support 1 segment. * * @param[in] exp_hdl Filesystem to interrogate * * @return The Maximum number of layout segments in a campound layoutget. */ uint32_t (*fs_maximum_segments)(struct fsal_export *exp_hdl); /** * @brief Size of the buffer needed for loc_body at layoutget * * This function sets policy for XDR buffer allocation in layoutget vector * below. If FSAL has a const size, just return it here. If it is dependent on * what the client can take return ~0UL. In any case the buffer allocated will * not be bigger than client's requested maximum. * * @param[in] exp_hdl Filesystem to interrogate * * @return Max size of the buffer needed for a loc_body */ size_t (*fs_loc_body_size)(struct fsal_export *exp_hdl); /** * @brief Get write verifier * * This function is called by write and commit to match the commit verifier * with the one returned on write. * * @param[in] exp_hdl Export to query * @param[in,out] verf_desc Address and length of verifier */ void (*get_write_verifier)(struct fsal_export *exp_hdl, struct gsh_buffdesc *verf_desc); /**@}*/ /** * @brief Allocate a state_t structure * * Note that this is not expected to fail since memory allocation is * expected to abort on failure. * * @param[in] exp_hdl Export state_t will be associated with * @param[in] state_type Type of state to allocate * @param[in] related_state Related state if appropriate * * @returns a state structure. */ struct state_t *(*alloc_state)(struct fsal_export *exp_hdl, enum state_type state_type, struct state_t *related_state); /** * @brief Check to see if a user is superuser * * @param[in] exp_hdl Export state_t is associated with * @param[in] creds Credentials to check for superuser * * @returns NULL on failure otherwise a state structure. */ bool (*is_superuser)(struct fsal_export *exp_hdl, const struct user_cred *creds); /** * @brief Get the expiration time for parent handle. * * @param[in] exp_hdl Filesystem to interrogate * * @return Expiration time for parent handle */ int32_t (*fs_expiretimeparent)(struct fsal_export *exp_hdl); }; /** * @brief Filesystem operations */ typedef void (*fsal_async_cb)(struct fsal_obj_handle *obj, fsal_status_t ret, void *obj_data, void *caller_data); /** Types of filesystem claims, there can not be both CLAIM_ROOT and CLAIM_CHILD * for the same filesystem. */ enum claim_type { /** tyoe used only for counts of all claims */ CLAIM_ALL, /** Claim is the root of the file system */ CLAIM_ROOT, /** Claim is a subtree of the file system */ CLAIM_SUBTREE, /** Claim is due to the parent being claimed */ CLAIM_CHILD, /** Temporary claim */ CLAIM_TEMP, /** Number of claim types */ CLAIM_NUM }; /** @brief FSAL method to claim a filesystem * * @param(in) fs filesystem to claim * @param(in) exp export to claim for * @param(out) private_data private_data * * @retval errno or 0 */ typedef int (*claim_filesystem_cb)(struct fsal_filesystem *fs, struct fsal_export *exp, void **private_data); typedef void (*unclaim_filesystem_cb)(struct fsal_filesystem *fs); enum fsid_type { FSID_NO_TYPE, FSID_ONE_UINT64, FSID_MAJOR_64, FSID_TWO_UINT64, FSID_TWO_UINT32, FSID_DEVICE }; static inline uint64_t squash_fsid(const struct fsal_fsid__ *fsid) { return fsid->major ^ (fsid->minor << 32 | fsid->minor >> 32); } static inline int sizeof_fsid(enum fsid_type type) { switch (type) { case FSID_NO_TYPE: return 0; case FSID_ONE_UINT64: case FSID_MAJOR_64: return sizeof(uint64_t); case FSID_TWO_UINT64: return 2 * sizeof(uint64_t); case FSID_TWO_UINT32: case FSID_DEVICE: return 2 * sizeof(uint32_t); } return -1; } /** * @brief Directory cookie */ typedef uint64_t fsal_cookie_t; /* Cookie values 0, 1, and 2 are reserved by NFS: * 0 is "start from beginning" * 1 is the cookie associated with the "." entry * 2 is the cookie associated with the ".." entry * * FSALs that support compute_readdir_cookie that are for some reason unable * to compute the cookie for the very first entry (other than . and ..) * should return FIRST_COOKIE. Caching layers such as MDCACHE should treat an * insert of an entry with cookie 3 as inserting a new first entry, and then * compute a new cookie for the old first entry - they can safely assume the * sort order doesn't change which may allow for optimization of things like' * AVL trees. */ #define FIRST_COOKIE 3 enum fsal_dir_result { /** Continue readdir, call back with another dirent. */ DIR_CONTINUE, /** Continue supplying entries if readahead is supported, otherwise * stop providing entries. */ DIR_READAHEAD, /** Terminate readdir. */ DIR_TERMINATE, }; const char *fsal_dir_result_str(enum fsal_dir_result result); /** * @brief Callback to provide readdir caller with each directory entry * * The called function will indicate if readdir should continue, terminate, * terminate and mark cookie, or continue and mark cookie. In the last case, * the called function may also return a cookie if requested in the ret_cookie * parameter (which may be NULL if the caller doesn't need to mark cookies). * If ret_cookie is 0, the caller had no cookie to return. * * @param[in] name The name of the entry * @param[in] obj The fsal_obj_handle describing the entry * @param[in] attrs The requested attributes for the entry (see * readdir attrmask parameter) * @param[in] dir_state Opaque pointer to be passed to callback * @param[in] cookie An FSAL generated cookie for the entry * * @returns fsal_dir_result above */ typedef enum fsal_dir_result (*fsal_readdir_cb)(const char *name, struct fsal_obj_handle *obj, struct fsal_attrlist *attrs, void *dir_state, fsal_cookie_t cookie); /* Async FSAL support: * * With an async FSAL, a read2 or write2 call will dispatch I/O work to the * back end filesystem which will be expected to make a call back on * completion. Some back end filesystems may require additional back end * filesystem calls after completion that MUST NOT be made in the call back * context. One example is a need to close a file after I/O completion. * * The way this is accomplished is the FSAL requests the caller to make an * additional call to read2 or write2 with the same fsal_io_arg after the * caller has resumed the NFS or 9P request that triggered the read2 or write2 * call. The FSAL will then perform this additional operation, and make another * read/write done callback. * * The fsal_resume field in the fsal_io_arg is where the FSAL will put a * reason code that lets the caller know it needs to initiate this resume, and * allows the read2 or write2 implementation to know that it is being called * back on resume and why. */ /* FSAL resume_reason values, may be defined by specific FSALs also. Any * generic ones may be defined here. FSAL specific reasons should be defined * starting from values > 1000. */ #define FSAL_NORESUME 0 #define FSAL_CLOSEFD 1 /** * @brief Argument for read2/write2 and their callbacks * */ struct fsal_io_arg { void (*iov_release)(void *release_data); /**< function to release the iovec */ void *release_data; /**< pointer passed to release func */ size_t io_amount; /**< Total amount of I/O actually done */ struct io_info *info; /**< More info about data for read_plus */ void *cbi; /**< FSAL specific call back info */ int fsal_resume; /**< If non-zero, FSAL requests a resume callback, the specific value indicates the reason. */ union { bool end_of_file; /**< True if end-of-file reached */ bool fsal_stable; /**< requested/achieved stability */ }; struct state_t *state; /**< State to use for read (or NULL) */ uint64_t offset; /**< Offset into file to read */ size_t io_request; /**< requested length of I/O */ int iov_count; /**< Number of vectors in iov */ size_t *last_iov_buf_size; /**< Size of last iov buffer */ struct iovec *iov; /**< Vector of buffers to fill */ }; /** * @brief FSAL object operations vector */ struct fsal_obj_ops { /**@{*/ /** * Lifecycle management */ /** * @brief Get a reference to a handle * * Refcounting is required for all FSALs. An FSAL that will have FSAL_MDCACHE * stacked on top need not handle this as FSAL_MDCACHE will handle it. * * @param[in] obj_hdl Handle to release */ void (*get_ref)(struct fsal_obj_handle *obj_hdl); /** * @brief Put a reference to a handle * * Refcounting is required for all FSALs. An FSAL that will have FSAL_MDCACHE * stacked on top need not handle this as FSAL_MDCACHE will handle it. * * @param[in] obj_hdl Handle to release */ void (*put_ref)(struct fsal_obj_handle *obj_hdl); /** * @brief Clean up a filehandle * * This function cleans up private resources associated with a * filehandle and deallocates it. Implement this method or you will * leak. Refcount (if used) should be 1 * * @param[in] obj_hdl Handle to release */ void (*release)(struct fsal_obj_handle *obj_hdl); /** * @brief Merge a duplicate handle with an original handle * * This function is used if an upper layer detects that a duplicate * object handle has been created. It allows the FSAL to merge anything * from the duplicate back into the original. * * The caller must release the object (the caller may have to close * files if the merge is unsuccessful). * * @param[in] orig_hdl Original handle * @param[in] dupe_hdl Handle to merge into original * * @return FSAL status. * */ fsal_status_t (*merge)(struct fsal_obj_handle *orig_hdl, struct fsal_obj_handle *dupe_hdl); /**@}*/ /**@{*/ /** * Directory operations */ /** * @brief Look up a filename * * This function looks up the given name in the supplied directory. * * @note The old version of the FSAL had a special case for this * function, such that if the directory handle and path were both * NULL, a handle to the root of the export was returned. This * special case is no longer supported and should not be implemented. * * The caller will set the request_mask in attrs_out to indicate the attributes * of interest. ATTR_ACL SHOULD NOT be requested and need not be provided. If * not all the requested attributes can be provided, this method MUST return * an error unless the ATTR_RDATTR_ERR bit was set in the request_mask. * * Since this method instantiates a new fsal_obj_handle, it will be forced * to fetch at least some attributes in order to even know what the object * type is (as well as it's fileid and fsid). For this reason, the operation * as a whole can be expected to fail if the attributes were not able to be * fetched. * * @param[in] dir_hdl Directory to search * @param[in] path Name to look up * @param[out] handle Object found * @param[in,out] attrs_out Optional attributes for newly created object * * @note On success, @a handle has been ref'd * * @return FSAL status. */ fsal_status_t (*lookup)(struct fsal_obj_handle *dir_hdl, const char *path, struct fsal_obj_handle **handle, struct fsal_attrlist *attrs_out); /** * @brief Read a directory * * This function reads directory entries from the FSAL and supplies * them to a callback. * * @param[in] dir_hdl Directory to read * @param[in] whence Point at which to start reading. NULL to * start at beginning. * @param[in] dir_state Opaque pointer to be passed to callback * @param[in] cb Callback to receive names * @param[in] attrmask Indicate which attributes the caller is interested in * @param[out] eof true if the last entry was reached * * @return FSAL status. */ fsal_status_t (*readdir)(struct fsal_obj_handle *dir_hdl, fsal_cookie_t *whence, void *dir_state, fsal_readdir_cb cb, attrmask_t attrmask, bool *eof); /** * @brief Compute the readdir cookie for a given filename. * * Some FSALs are able to compute the cookie for a filename deterministically * from the filename. They also have a defined order of entries in a directory * based on the name (could be strcmp sort, could be strict alpha sort, could * be deterministic order based on cookie - in any case, the dirent_cmp method * will also be provided. * * The returned cookie is the cookie that can be passed as whence to FIND that * directory entry. This is different than the cookie passed in the readdir * callback (which is the cookie of the NEXT entry). * * @param[in] parent Directory file name belongs to. * @param[in] name File name to produce the cookie for. * * @retval 0 if not supported. * @returns The cookie value. */ fsal_cookie_t (*compute_readdir_cookie)(struct fsal_obj_handle *parent, const char *name); /** * @brief Help sort dirents. * * For FSALs that are able to compute the cookie for a filename * deterministically from the filename, there must also be a defined order of * entries in a directory based on the name (could be strcmp sort, could be * strict alpha sort, could be deterministic order based on cookie). * * Although the cookies could be computed, the caller will already have them * and thus will provide them to save compute time. * * @param[in] parent Directory entries belong to. * @param[in] name1 File name of first dirent * @param[in] cookie1 Cookie of first dirent * @param[in] name2 File name of second dirent * @param[in] cookie2 Cookie of second dirent * * @retval < 0 if name1 sorts before name2 * @retval == 0 if name1 sorts the same as name2 * @retval >0 if name1 sorts after name2 */ int (*dirent_cmp)(struct fsal_obj_handle *parent, const char *name1, fsal_cookie_t cookie1, const char *name2, fsal_cookie_t cookie2); /**@}*/ /**@{*/ /** * Creation operations */ /** * @brief Create a directory * * This function creates a new directory. * * For support_ex, this method will handle attribute setting. The caller * MUST include the mode attribute and SHOULD NOT include the owner or * group attributes if they are the same as the op_ctx->cred. * * The caller is expected to invoke fsal_release_attrs to release any * resources held by the set attributes. The FSAL layer MAY have added an * inherited ACL. * * The caller will set the request_mask in attrs_out to indicate the attributes * of interest. ATTR_ACL SHOULD NOT be requested and need not be provided. If * not all the requested attributes can be provided, this method MUST return * an error unless the ATTR_RDATTR_ERR bit was set in the request_mask. * * Since this method instantiates a new fsal_obj_handle, it will be forced * to fetch at least some attributes in order to even know what the object * type is (as well as it's fileid and fsid). For this reason, the operation * as a whole can be expected to fail if the attributes were not able to be * fetched. * * @param[in] dir_hdl Directory in which to create the * directory * @param[in] name Name of directory to create * @param[in] attrs_in Attributes to set on newly created * object * @param[out] new_obj Newly created object * @param[in,out] attrs_out Optional attributes for newly created * object * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @note On success, @a new_obj has been ref'd * * @return FSAL status. */ fsal_status_t (*mkdir)(struct fsal_obj_handle *dir_hdl, const char *name, struct fsal_attrlist *attrs_in, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out); /** * @brief Create a special file * * This function creates a new special file. * * For support_ex, this method will handle attribute setting. The caller * MUST include the mode attribute and SHOULD NOT include the owner or * group attributes if they are the same as the op_ctx->cred. * * If the node type has rawdev info, then @a attrs_in MUST have the rawdev field * set. * * The caller is expected to invoke fsal_release_attrs to release any * resources held by the set attributes. The FSAL layer MAY have added an * inherited ACL. * * The caller will set the request_mask in attrs_out to indicate the attributes * of interest. ATTR_ACL SHOULD NOT be requested and need not be provided. If * not all the requested attributes can be provided, this method MUST return * an error unless the ATTR_RDATTR_ERR bit was set in the request_mask. * * Since this method instantiates a new fsal_obj_handle, it will be forced * to fetch at least some attributes in order to even know what the object * type is (as well as it's fileid and fsid). For this reason, the operation * as a whole can be expected to fail if the attributes were not able to be * fetched. * * @param[in] dir_hdl Directory in which to create the object * @param[in] name Name of object to create * @param[in] nodetype Type of special file to create * @param[in] attrs_in Attributes to set on newly created * object * @param[out] new_obj Newly created object * @param[in,out] attrs_out Optional attributes for newly created * object * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @note On success, @a new_obj has been ref'd * * @return FSAL status. */ fsal_status_t (*mknode)(struct fsal_obj_handle *dir_hdl, const char *name, object_file_type_t nodetype, struct fsal_attrlist *attrs_in, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out); /** * @brief Create a symbolic link * * This function creates a new symbolic link. * * For support_ex, this method will handle attribute setting. The caller * MUST include the mode attribute and SHOULD NOT include the owner or * group attributes if they are the same as the op_ctx->cred. * * The caller is expected to invoke fsal_release_attrs to release any * resources held by the set attributes. The FSAL layer MAY have added an * inherited ACL. * * The caller will set the request_mask in attrs_out to indicate the attributes * of interest. ATTR_ACL SHOULD NOT be requested and need not be provided. If * not all the requested attributes can be provided, this method MUST return * an error unless the ATTR_RDATTR_ERR bit was set in the request_mask. * * Since this method instantiates a new fsal_obj_handle, it will be forced * to fetch at least some attributes in order to even know what the object * type is (as well as it's fileid and fsid). For this reason, the operation * as a whole can be expected to fail if the attributes were not able to be * fetched. * * @param[in] dir_hdl Directory in which to create the object * @param[in] name Name of object to create * @param[in] link_path Content of symbolic link * @param[in] attrs_in Attributes to set on newly created * object * @param[out] new_obj Newly created object * @param[in,out] attrs_out Optional attributes for newly created * object * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @note On success, @a new_obj has been ref'd * * @return FSAL status. */ fsal_status_t (*symlink)(struct fsal_obj_handle *dir_hdl, const char *name, const char *link_path, struct fsal_attrlist *attrs_in, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out); /**@}*/ /**@{*/ /** * File object operations */ /** * @brief Read the content of a link * * This function reads the content of a symbolic link. The FSAL will * allocate a buffer and store its address and the link length in the * link_content gsh_buffdesc. The caller *must* free this buffer with * gsh_free. * * The symlink content passed back *must* be null terminated and the * length indicated in the buffer description *must* include the * terminator. * * @param[in] obj_hdl Link to read * @param[out] link_content Buffdesc to which the FSAL will store * the address of the buffer holding the * link and the link length. * @param[out] refresh true if the content are to be retrieved * from the underlying filesystem rather * than cache * * @return FSAL status. */ fsal_status_t (*readlink)(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *link_content, bool refresh); /** * @brief Check access for a given user against a given object * * This function checks whether a given user is allowed to perform the * specified operations against the supplied file. The goal is to * allow filesystem specific semantics to be applied to cached * metadata. * * This method must read attributes and/or get them from a cache. * * @param[in] obj_hdl Handle to check * @param[in] access_type Access requested * @param[out] allowed Returned access that could be granted * @param[out] denied Returned access that would be granted * @param[in] owner_skip Skip test if op_ctx->creds is owner * * @return FSAL status. */ fsal_status_t (*test_access)(struct fsal_obj_handle *obj_hdl, fsal_accessflags_t access_type, fsal_accessflags_t *allowed, fsal_accessflags_t *denied, bool owner_skip); /** * @brief Get attributes * * This function fetches the attributes for the object. The attributes * requested in the mask are copied out (though other attributes might * be copied out). * * The caller will set the request_mask in attrs_out to indicate the attributes * of interest. ATTR_ACL SHOULD NOT be requested and need not be provided. If * not all the requested attributes can be provided, this method MUST return * an error unless the ATTR_RDATTR_ERR bit was set in the request_mask. * * The caller MUST call fsal_release_attrs when done with the copied * out attributes. This will release any attributes that might take * additional memory. * * @param[in] obj_hdl Object to query * @param[out] attrs_out Attribute list for file * * @return FSAL status. */ fsal_status_t (*getattrs)(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs_out); /** * @brief Create a new link * * This function creates a new name for an existing object. * * @param[in] obj_hdl Object to be linked to * @param[in] destdir_hdl Directory in which to create the link * @param[in] name Name for link * @param[in,out] destdir_pre_attrs_out Optional attributes for destdir dir * before the operation. Should be atomic. * @param[in,out] destdir_post_attrs_out Optional attributes for destdir dir * after the operation. Should be atomic. * * @return FSAL status */ fsal_status_t (*link)(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *destdir_hdl, const char *name, struct fsal_attrlist *destdir_pre_attrs_out, struct fsal_attrlist *destdir_post_attrs_out); /** * @brief Rename a file * * This function renames a file (technically it changes the name of * one link, which may be the only link to the file.) * * @param[in] olddir_hdl Old parent directory * @param[in] old_name Old name * @param[in] newdir_hdl New parent directory * @param[in] new_name New name * @param[in,out] olddir_pre_attrs_out Optional attributes for olddir dir * before the operation. Should be atomic. * @param[in,out] olddir_post_attrs_out Optional attributes for olddir dir * after the operation. Should be atomic. * @param[in,out] newdir_pre_attrs_out Optional attributes for newdir dir * before the operation. Should be atomic. * @param[in,out] newdir_post_attrs_out Optional attributes for newdir dir * after the operation. Should be atomic. * * @return FSAL status */ fsal_status_t (*rename)(struct fsal_obj_handle *obj_hdl, struct fsal_obj_handle *olddir_hdl, const char *old_name, struct fsal_obj_handle *newdir_hdl, const char *new_name, struct fsal_attrlist *olddir_pre_attrs_out, struct fsal_attrlist *olddir_post_attrs_out, struct fsal_attrlist *newdir_pre_attrs_out, struct fsal_attrlist *newdir_post_attrs_out); /** * @brief Remove a name from a directory * * This function removes a name from a directory and possibly deletes * the file so named. * * @param[in] dir_hdl The directory from which to remove the * name * @param[in] obj_hdl The object being removed * @param[in] name The name to remove * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @return FSAL status. */ fsal_status_t (*unlink)(struct fsal_obj_handle *dir_hdl, struct fsal_obj_handle *obj_hdl, const char *name, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out); /**@}*/ /**@{*/ /** * I/O management */ /** * @brief IO Advise * * This function give hints to fs. * * @param[in] obj_hdl File to be written * @param[in,out] info Information about the data * * @return FSAL status. */ fsal_status_t (*io_advise)(struct fsal_obj_handle *obj_hdl, struct io_hints *hints); /** * @brief Close a file * * This function closes a file. This should return ERR_FSAL_NOT_OPENED if * the global FD for this obj was not open. * * @param[in] obj_hdl File to close * * @return FSAL status. */ fsal_status_t (*close)(struct fsal_obj_handle *obj_hdl); /** * @brief Reserve/Deallocate space in a region of a file * * @param[in] obj_hdl File to which bytes should be allocated * @param[in] state open stateid under which to do the allocation * @param[in] offset offset at which to begin the allocation * @param[in] length length of the data to be allocated * @param[in] allocate Should space be allocated or deallocated? * * @return FSAL status. */ fsal_status_t (*fallocate)(struct fsal_obj_handle *obj_hdl, struct state_t *state, uint64_t offset, uint64_t length, bool allocate); /**@}*/ /**@{*/ /** * Extended attribute management */ /** * @brief List extended attributes on a file * * This function gets a list of attributes on a given file. * * @param[in] obj_hdl File to interrogate * @param[in] cookie Attribute at which to start * @param[out] xattrs_tab Array to which to write attributes * @param[in] xattrs_tabsize Size of array * @param[out] nb_returned Number of entries returned * * @return FSAL status. */ fsal_status_t (*list_ext_attrs)(struct fsal_obj_handle *obj_hdl, unsigned int cookie, struct fsal_xattrent *xattrs_tab, unsigned int xattrs_tabsize, unsigned int *nb_returned, int *end_of_list); /** * @brief Get a number for an attribute name * * This function returns an index for a given attribute specified by * name. * * @param[in] obj_hdl File to look up * @param[in] name Name to look up * @param[out] xattr_id Number uniquely identifying the attribute * within the scope of the file * * @return FSAL status. */ fsal_status_t (*getextattr_id_by_name)(struct fsal_obj_handle *obj_hdl, const char *xattr_name, unsigned int *xattr_id); /** * @brief Get content of an attribute by name * * This function returns the value of an extended attribute as * specified by name. * * As a special rule, because it is implemented that way in the linux * getxattr call, giving a buffer_size of 0 is allowed and should set * output_size appropriately to fit the xattr. * * Please note that the xattr could change between the query-size call * and that actual fetch, so this is not fail-proof. * * @param[in] obj_hdl File to interrogate * @param[in] xattr_name Name of attribute * @param[out] buffer_addr Buffer to store content * @param[in] buffer_size Buffer size * @param[out] output_size Size of content * * @return FSAL status. */ fsal_status_t (*getextattr_value_by_name)( struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, size_t *output_size); /** * @brief Get content of an attribute by id * * This function returns the value of an extended attribute as * specified by id. * * @param[in] obj_hdl File to interrogate * @param[in] xattr_id ID of attribute * @param[out] buffer_addr Buffer to store content * @param[in] buffer_size Buffer size * @param[out] output_size Size of content * * @return FSAL status. */ fsal_status_t (*getextattr_value_by_id)(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buffer_addr, size_t buffer_size, size_t *output_size); /** * @brief Set content of an attribute * * This function sets the value of an extended attribute. * * @param[in] obj_hdl File to modify * @param[in] xattr_name Name of attribute * @param[in] buffer_addr Content to set * @param[in] buffer_size Content size * @param[in] create true if attribute is to be created * * @return FSAL status. */ fsal_status_t (*setextattr_value)(struct fsal_obj_handle *obj_hdl, const char *xattr_name, void *buffer_addr, size_t buffer_size, int create); /** * @brief Set content of an attribute by id * * This function sets the value of an extended attribute by id. * * @param[in] obj_hdl File to modify * @param[in] xattr_id ID of attribute * @param[in] buffer_addr Content to set * @param[in] buffer_size Content size * * @return FSAL status. */ fsal_status_t (*setextattr_value_by_id)(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id, void *buffer_addr, size_t buffer_size); /** * @brief Remove an extended attribute by id * * This function removes an extended attribute as specified by ID. * * @param[in] obj_hdl File to modify * @param[in] xattr_id ID of attribute * * @return FSAL status. */ fsal_status_t (*remove_extattr_by_id)(struct fsal_obj_handle *obj_hdl, unsigned int xattr_id); /** * @brief Remove an extended attribute by name * * This function removes an extended attribute as specified by name. * * @param[in] obj_hdl File to modify * @param[in] xattr_name Name of attribute to remove * * @return FSAL status. */ fsal_status_t (*remove_extattr_by_name)(struct fsal_obj_handle *obj_hdl, const char *xattr_name); /**@}*/ /**@{*/ /** * Handle operations */ /** * @brief Write wire handle * * This function writes a "wire" handle or file ID to the given * buffer. * * @param[in] obj_hdl The handle to digest * @param[in] output_type The type of digest to write * @param[in,out] fh_desc Buffer descriptor to which to write * digest. Set fh_desc->len to final * output length. * * @return FSAL status */ fsal_status_t (*handle_to_wire)(const struct fsal_obj_handle *obj_hdl, fsal_digesttype_t output_type, struct gsh_buffdesc *fh_desc); /** * @brief Get key for handle * * Indicate the unique part of the handle that should be used for * hashing. * * @param[in] obj_hdl Handle whose key is to be got * @param[out] fh_desc Address and length giving sub-region of handle * to be used as key */ void (*handle_to_key)(struct fsal_obj_handle *obj_hdl, struct gsh_buffdesc *fh_desc); /** * @brief Compare two handles * * This function compares two handles to see if they reference the same file * * @param[in] obj_hdl1 The first handle to compare * @param[in] obj_hdl2 The second handle to compare * * @return True if match, false otherwise */ bool (*handle_cmp)(struct fsal_obj_handle *obj_hdl1, struct fsal_obj_handle *obj_hdl2); /**@}*/ /**@{*/ /** * pNFS functions */ /** * @brief Grant a layout segment. * * This function is called by nfs41_op_layoutget. It may be called * multiple times, to satisfy a request with multiple segments. The * FSAL may track state (what portion of the request has been or * remains to be satisfied or any other information it wishes) in the * bookkeeper member of res. Each segment may have FSAL-specific * information associated with it its segid. This segid will be * supplied to the FSAL when the segment is committed or returned. * When the granting the last segment it intends to grant, the FSAL * must set the last_segment flag in res. * * @param[in] obj_hdl The handle of the file on which the layout is * requested. * @param[out] loc_body An XDR stream to which the FSAL must encode * the layout specific portion of the granted * layout segment. * @param[in] arg Input arguments of the function * @param[in,out] res In/out and output arguments of the function * * @return Valid error codes in RFC 5661, pp. 366-7. */ nfsstat4 (*layoutget)(struct fsal_obj_handle *obj_hdl, XDR *loc_body, const struct fsal_layoutget_arg *arg, struct fsal_layoutget_res *res); /** * @brief Potentially return one layout segment * * This function is called once on each segment matching the IO mode * and intersecting the range specified in a LAYOUTRETURN operation or * for all layouts corresponding to a given stateid on last close, * lease expiry, or a layoutreturn with a return-type of FSID or ALL. * Whether it is called in the former or latter case is indicated by * the synthetic flag in the arg structure, with synthetic being true * in the case of last-close or lease expiry. * * If arg->dispose is true, all resources associated with the * layout must be freed. * * @param[in] obj_hdl The object on which a segment is to be returned * @param[in] lrf_body In the case of a non-synthetic return, this is * an XDR stream corresponding to the layout * type-specific argument to LAYOUTRETURN. In * the case of a synthetic or bulk return, * this is a NULL pointer. * @param[in] arg Input arguments of the function * * @return Valid error codes in RFC 5661, p. 367. */ nfsstat4 (*layoutreturn)(struct fsal_obj_handle *obj_hdl, XDR *lrf_body, const struct fsal_layoutreturn_arg *arg); /** * @brief Commit a segment of a layout * * This function is called once on every segment of a layout. The * FSAL may avoid being called again after it has finished all tasks * necessary for the commit by setting res->commit_done to true. * * The calling function does not inspect or act on the value of * size_supplied or new_size until after the last call to * FSAL_layoutcommit. * * @param[in] obj_hdl The object on which to commit * @param[in] lou_body An XDR stream containing the layout * type-specific portion of the LAYOUTCOMMIT * arguments. * @param[in] arg Input arguments of the function * @param[in,out] res In/out and output arguments of the function * * @return Valid error codes in RFC 5661, p. 366. */ nfsstat4 (*layoutcommit)(struct fsal_obj_handle *obj_hdl, XDR *lou_body, const struct fsal_layoutcommit_arg *arg, struct fsal_layoutcommit_res *res); /** * @brief Get Extended Attribute * * This function gets an extended attribute of an object. * * @param[in] obj_hdl Input object to query * @param[in] xa_name Input xattr name * @param[out] xa_value Output xattr value * * @return FSAL status. */ fsal_status_t (*getxattrs)(struct fsal_obj_handle *obj_hdl, xattrkey4 *xa_name, xattrvalue4 *xa_value); /** * @brief Set Extended Attribute * * This function sets an extended attribute of an object. * * @param[in] obj_hdl Input object to set * @param[in] xa_type Input xattr type * @param[in] xa_name Input xattr name to set * @param[in] xa_value Input xattr value to set * * @return FSAL status. */ fsal_status_t (*setxattrs)(struct fsal_obj_handle *obj_hdl, setxattr_option4 option, xattrkey4 *xa_name, xattrvalue4 *xa_value); /** * @brief Remove Extended Attribute * * This function remove an extended attribute of an object. * * @param[in] obj_hdl Input object to set * @param[in] xa_name Input xattr name to remove * * @return FSAL status. */ fsal_status_t (*removexattrs)(struct fsal_obj_handle *obj_hdl, xattrkey4 *xa_name); /** * @brief List Extended Attributes * * This function list the extended attributes of an object. * * @param[in] obj_hdl Input object to list * @param[in] la_maxcount Input maximum number of bytes for names * @param[in,out] la_cookie In/out cookie * @param[out] lr_eof Output eof set if no more extended attributes * @param[out] lr_names Output list of extended attribute names * this buffer size is double the size of * la_maxcount to allow for component4 overhead * * @return FSAL status. */ fsal_status_t (*listxattrs)(struct fsal_obj_handle *obj_hdl, count4 la_maxcount, nfs_cookie4 *la_cookie, bool_t *lr_eof, xattrlist4 *lr_names); /**@}*/ /**@{*/ /** * Extended API functions. * * With these new operations, the FSAL becomes responsible for managing * share reservations. The FSAL is also granted more control over the * state of a "file descriptor" and has more control of what a "file * descriptor" even is. Ultimately, it is whatever the FSAL needs in * order to manage the share reservations and lock state. * * The open2 method also allows atomic create/setattr/open (just like the * NFS v4 OPEN operation). * */ /** * @brief Open a file descriptor for read or write and possibly create * * This function opens a file for read or write, possibly creating it. * If the caller is passing a state, it must hold the state_lock * exclusive. * * state can be NULL which indicates a stateless open (such as via the * NFS v3 CREATE operation), in which case the FSAL must assure protection * of any resources. If the file is being created, such protection is * simple since no one else will have access to the object yet, however, * in the case of an exclusive create, the common resources may still need * protection. * * If Name is NULL, obj_hdl is the file itself, otherwise obj_hdl is the * parent directory. * * On an exclusive create, the upper layer may know the object handle * already, so it MAY call with name == NULL. In this case, the caller * expects just to check the verifier. * * On a call with an existing object handle for an UNCHECKED create, * we can set the size to 0. * * At least the mode attribute must be set if createmode is not FSAL_NO_CREATE. * Some FSALs may still have to pass a mode on a create call for exclusive, * and even with FSAL_NO_CREATE, and empty set of attributes MUST be passed. * * If an open by name succeeds and did not result in Ganesha creating a file, * the caller will need to do a subsequent permission check to confirm the * open. This is because the permission attributes were not available * beforehand. * * The caller is expected to invoke fsal_release_attrs to release any * resources held by the set attributes. The FSAL layer MAY have added an * inherited ACL. * * The caller will set the request_mask in attrs_out to indicate the attributes * of interest. ATTR_ACL SHOULD NOT be requested and need not be provided. If * not all the requested attributes can be provided, this method MUST return * an error unless the ATTR_RDATTR_ERR bit was set in the request_mask. * * Since this method may instantiate a new fsal_obj_handle, it will be forced * to fetch at least some attributes in order to even know what the object * type is (as well as it's fileid and fsid). For this reason, the operation * as a whole can be expected to fail if the attributes were not able to be * fetched. * * The attributes will not be returned if this is an open by object as * opposed to an open by name. * * @note If the file was created, @a new_obj has been ref'd * * @param[in] obj_hdl File to open or parent directory * @param[in,out] state state_t to use for this operation * @param[in] openflags Mode for open * @param[in] createmode Mode for create * @param[in] name Name for file if being created or opened * @param[in] attrs_in Attributes to set on created file * @param[in] verifier Verifier to use for exclusive create * @param[in,out] new_obj Newly created object * @param[in,out] attrs_out Optional attributes for newly created * object * @param[in,out] caller_perm_check The caller must do a permission check * @param[in,out] parent_pre_attrs_out Optional attributes for parent dir * before the operation. Should be atomic. * @param[in,out] parent_post_attrs_out Optional attributes for parent dir * after the operation. Should be atomic. * * @return FSAL status. */ fsal_status_t (*open2)( struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags, enum fsal_create_mode createmode, const char *name, struct fsal_attrlist *attrs_in, fsal_verifier_t verifier, struct fsal_obj_handle **new_obj, struct fsal_attrlist *attrs_out, bool *caller_perm_check, struct fsal_attrlist *parent_pre_attrs_out, struct fsal_attrlist *parent_post_attrs_out); /** * @brief Check the exclusive create verifier for a file. * * @param[in] obj_hdl File to check verifier * @param[in] verifier Verifier to use for exclusive create * * @retval true if verifier matches */ bool (*check_verifier)(struct fsal_obj_handle *obj_hdl, fsal_verifier_t verifier); /** * @brief Return open status of a state. * * This function returns open flags representing the current open * status for a state. The st_lock must be held. * * @param[in] obj_hdl File owning state * @param[in] state File state to interrogate * * @retval Flags representing current open status */ fsal_openflags_t (*status2)(struct fsal_obj_handle *obj_hdl, struct state_t *state); /** * @brief Re-open a file that may be already opened * * This function supports changing the access mode of a share reservation and * thus should only be called with a share state. The st_lock must be held. * * This MAY be used to open a file the first time if there is no need for * open by name or create semantics. One example would be 9P lopen. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] openflags Mode for re-open * * @return FSAL status. */ fsal_status_t (*reopen2)(struct fsal_obj_handle *obj_hdl, struct state_t *state, fsal_openflags_t openflags); /** * @brief Read data from a file * * This function reads data from the given file. The FSAL must be able to * perform the read whether a state is presented or not. This function also * is expected to handle properly bypassing or not share reservations. This is * an (optionally) asynchronous call. When the I/O is complete, the done * callback is called with the results. * * @param[in] obj_hdl File on which to operate * @param[in] bypass If state doesn't indicate a share reservation, * bypass any deny read * @param[in,out] done_cb Callback to call when I/O is done * @param[in,out] read_arg Info about read, passed back in callback * @param[in,out] caller_arg Opaque arg from the caller for callback * * @return Nothing; results are in callback */ void (*read2)(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *read_arg, void *caller_arg); /** * @brief Write data to a file * * This function writes data to a file. The FSAL must be able to * perform the write whether a state is presented or not. This function also * is expected to handle properly bypassing or not share reservations. Even * with bypass == true, it will enforce a mandatory (NFSv4) deny_write if * an appropriate state is not passed). * * The FSAL is expected to enforce sync if necessary. * * This is an (optionally) asynchronous call. When the I/O is complete, the @a * done_cb callback is called. * * @param[in] obj_hdl File on which to operate * @param[in] bypass If state doesn't indicate a share reservation, * bypass any non-mandatory deny write * @param[in,out] done_cb Callback to call when I/O is done * @param[in,out] write_arg Info about write, passed back in callback * @param[in,out] caller_arg Opaque arg from the caller for callback */ void (*write2)(struct fsal_obj_handle *obj_hdl, bool bypass, fsal_async_cb done_cb, struct fsal_io_arg *write_arg, void *caller_arg); /** * @brief Seek to data or hole * * This function seek to data or hole in a file. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in,out] info Information about the data * * @return FSAL status. */ fsal_status_t (*seek2)(struct fsal_obj_handle *obj_hdl, struct state_t *state, struct io_info *info); /** * @brief IO Advise * * This function give hints to fs. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in,out] info Information about the data * * @return FSAL status. */ fsal_status_t (*io_advise2)(struct fsal_obj_handle *obj_hdl, struct state_t *state, struct io_hints *hints); /** * @brief Commit written data * * This function flushes possibly buffered data to a file. This method * differs from commit due to the need to interact with share reservations * and the fact that the FSAL manages the state of "file descriptors". The * FSAL must be able to perform this operation without being passed a specific * state. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] offset Start of range to commit * @param[in] len Length of range to commit * * @return FSAL status. */ fsal_status_t (*commit2)(struct fsal_obj_handle *obj_hdl, off_t offset, size_t len); /** * @brief Perform a lock operation * * This function performs a lock operation (lock, unlock, test) on a * file. This method assumes the FSAL is able to support lock owners, * though it need not support asynchronous blocking locks. Passing the * lock state allows the FSAL to associate information with a specific * lock owner for each file (which may include use of a "file descriptor". * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] owner Lock owner * @param[in] lock_op Operation to perform * @param[in] request_lock Lock to take/release/test * @param[out] conflicting_lock Conflicting lock * * @return FSAL status. */ fsal_status_t (*lock_op2)(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *owner, fsal_lock_op_t lock_op, fsal_lock_param_t *request_lock, fsal_lock_param_t *conflicting_lock); /** * @brief Acquire or Release delegation * * This functions acquires/releases delegation/lease_lock. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * @param[in] owner Opaque state owner token * @param[in] deleg Requested delegation state * * @return FSAL status. */ fsal_status_t (*lease_op2)(struct fsal_obj_handle *obj_hdl, struct state_t *state, void *owner, fsal_deleg_t deleg); /** * @brief Set attributes on an object * * This function sets attributes on an object. Which attributes are * set is determined by attrib_set->mask. The FSAL must manage bypass * or not of share reservations, and a state may be passed. * * The caller is expected to invoke fsal_release_attrs to release any * resources held by the set attributes. The FSAL layer MAY have added an * inherited ACL. * * @param[in] obj_hdl File on which to operate * @param[in] bypass If state doesn't indicate a share reservation, * bypass any non-mandatory deny write * @param[in] state state_t to use for this operation * @param[in] attrib_set Attributes to set * * @return FSAL status. */ fsal_status_t (*setattr2)(struct fsal_obj_handle *obj_hdl, bool bypass, struct state_t *state, struct fsal_attrlist *attrib_set); /** * @brief Manage closing a file when a state is no longer needed. * * When the upper layers are ready to dispense with a state, this method is * called to allow the FSAL to close any file descriptors or release any other * resources associated with the state. A call to free_state should be assumed * to follow soon. * * @param[in] obj_hdl File on which to operate * @param[in] state state_t to use for this operation * * @return FSAL status. */ fsal_status_t (*close2)(struct fsal_obj_handle *obj_hdl, struct state_t *state); /**@}*/ /** * FSAL FD FUNCTIONS * * These functions are not to be called extrnal to FSAL, but instead are called * by the fsal_fd management functions. */ /** * @brief Function to close a fsal_fd. * * @param[in] obj_hdl File on which to operate * @param[in] fd File handle to close * * @return FSAL status. */ fsal_status_t (*close_func)(struct fsal_obj_handle *obj_hdl, struct fsal_fd *fd); /** * @brief Function to open or reopen a fsal_fd. * * @param[in] obj_hdl File on which to operate * @param[in] openflags New mode for open * @param[out] fd File descriptor that is to be used * * @return FSAL status. */ fsal_status_t (*reopen_func)(struct fsal_obj_handle *obj_hdl, fsal_openflags_t openflags, struct fsal_fd *fd); /**@}*/ /** * @brief Determine if the given handle is a referral point * * @param[in] obj_hdl Handle on which to operate * @param[in|out] attrs Attributes of the handle * @param[in] cache_attrs Cache the received attrs * * @return true if it is a referral point, false otherwise */ bool (*is_referral)(struct fsal_obj_handle *obj_hdl, struct fsal_attrlist *attrs, bool cache_attrs); /**@{*/ /** * ASYNC API functions. * * These are asynchronous versions of some of the API functions. FSALs are * expected to implement these, but the upper layers are not expected to call * them. Instead, they will be called by MDCACHE at the appropriate points. */ /**@}*/ }; /** * @brief FSAL pNFS Data Server operations vector */ struct fsal_pnfs_ds_ops { /**@{*/ /** * Lifecycle management. */ /** * @brief Clean up a server * * This function cleans up private resources associated with a * server and deallocates it. A default is supplied. * * This function should not be called directly. * * @param[in] pds FSAL pNFS DS to release */ void (*ds_release)(struct fsal_pnfs_ds *const pds); /** * @brief Initialize FSAL specific permissions per pNFS DS * * @param[in] pds FSAL pNFS DS * @param[in] req Incoming request. * * @return NFSv4.1 error codes: * NFS4_OK, NFS4ERR_ACCESS, NFS4ERR_WRONGSEC. */ nfsstat4 (*ds_permissions)(struct fsal_pnfs_ds *const pds, struct svc_req *req); /**@}*/ /**@{*/ /** * @brief Create a FSAL data server handle from a wire handle * * This function creates a FSAL data server handle from a client * supplied "wire" handle. * * @param[in] pds FSAL pNFS DS * @param[in] hdl_desc Buffer from which to create the struct * @param[out] handle FSAL DS handle * * @return NFSv4.1 error codes. */ nfsstat4 (*make_ds_handle)(struct fsal_pnfs_ds *const pds, const struct gsh_buffdesc *const hdl_desc, struct fsal_ds_handle **const handle, int flags); /** * DS handle Lifecycle management. */ /** * @brief Clean up a DS handle * * This function cleans up private resources associated with a * filehandle and deallocates it. Implement this method or you will * leak. This function should not be called directly. * * @param[in] ds_hdl Handle to release */ void (*dsh_release)(struct fsal_ds_handle *const ds_hdl); /**@}*/ /**@{*/ /** * DS handle I/O Functions */ /** * @brief Read from a data-server handle. * * NFSv4.1 data server handles are disjount from normal * filehandles (in Ganesha, there is a ds_flag in the filehandle_v4_t * structure) and do not get loaded into mdcache or processed the * normal way. * * @param[in] ds_hdl FSAL DS handle * @param[in] stateid The stateid supplied with the READ operation, * for validation * @param[in] offset The offset at which to read * @param[in] requested_length Length of read requested (and size of buffer) * @param[out] buffer The buffer to which to store read data * @param[out] supplied_length Length of data read * @param[out] eof true on end of file * * @return An NFSv4.1 status code. */ nfsstat4 (*dsh_read)(struct fsal_ds_handle *const ds_hdl, const stateid4 *stateid, const offset4 offset, const count4 requested_length, void *const buffer, count4 *const supplied_length, bool *const end_of_file); /** * @brief Read plus from a data-server handle. * * NFSv4.2 data server handles are disjount from normal * filehandles (in Ganesha, there is a ds_flag in the filehandle_v4_t * structure) and do not get loaded into mdcache or processed the * normal way. * * @param[in] ds_hdl FSAL DS handle * @param[in] stateid The stateid supplied with the READ operation, * for validation * @param[in] offset The offset at which to read * @param[in] requested_length Length of read requested (and size of buffer) * @param[out] buffer The buffer to which to store read data * @param[out] supplied_length Length of data read * @param[out] eof true on end of file * @param[out] info IO info * * @return An NFSv4.2 status code. */ nfsstat4 (*dsh_read_plus)(struct fsal_ds_handle *const ds_hdl, const stateid4 *stateid, const offset4 offset, const count4 requested_length, void *const buffer, const count4 supplied_length, bool *const end_of_file, struct io_info *info); /** * * @brief Write to a data-server handle. * * NFSv4.1 data server filehandles are disjount from normal * filehandles (in Ganesha, there is a ds_flag in the filehandle_v4_t * structure) and do not get loaded into mdcache or processed the * normal way. * * @param[in] ds_hdl FSAL DS handle * @param[in] stateid The stateid supplied with the READ operation, * for validation * @param[in] offset The offset at which to read * @param[in] write_length Length of write requested (and size of buffer) * @param[out] buffer The buffer to which to store read data * @param[in] stability wanted Stability of write * @param[out] written_length Length of data written * @param[out] writeverf Write verifier * @param[out] stability_got Stability used for write (must be as * or more stable than request) * * @return An NFSv4.1 status code. */ nfsstat4 (*dsh_write)(struct fsal_ds_handle *const ds_hdl, const stateid4 *stateid, const offset4 offset, const count4 write_length, const void *buffer, const stable_how4 stability_wanted, count4 *const written_length, verifier4 *const writeverf, stable_how4 *const stability_got); /** * @brief Commit a byte range to a DS handle. * * NFSv4.1 data server filehandles are disjount from normal * filehandles (in Ganesha, there is a ds_flag in the filehandle_v4_t * structure) and do not get loaded into mdcache or processed the * normal way. * * @param[in] ds_hdl FSAL DS handle * @param[in] offset Start of commit window * @param[in] count Length of commit window * @param[out] writeverf Write verifier * * @return An NFSv4.1 status code. */ nfsstat4 (*dsh_commit)(struct fsal_ds_handle *const ds_hdl, const offset4 offset, const count4 count, verifier4 *const writeverf); /**@}*/ }; /** * @brief FSAL object definition * * This structure is the base FSAL instance definition, providing the * public face to a single, loaded FSAL. */ struct fsal_module { struct glist_head fsals; /*< link in list of loaded fsals */ struct glist_head exports; /*< Head of list of exports from this FSAL */ struct glist_head handles; /*< Head of list of object handles */ struct glist_head servers; /*< Head of list of Data Servers */ char *path; /*< Path to .so file */ char *name; /*< Name set from .so and/or config */ void *dl_handle; /*< Handle to the dlopen()d shared library. NULL if statically linked */ struct fsal_ops m_ops; /*< FSAL module methods vector */ bool is_configured; /* was fsal configured*/ pthread_rwlock_t fsm_lock; /*< Lock to be held when manipulating its lists (above). */ int32_t refcount; /*< Reference count */ struct fsal_stats *stats; /*< for storing the FSAL specific stats */ struct fsal_staticfsinfo_t fs_info; /*< for storing FSAL static info */ }; /** * @brief Get a reference to a module * * @param[in] fsal_hdl FSAL on which to acquire reference. */ static inline void fsal_get(struct fsal_module *fsal_hdl) { (void)atomic_inc_int32_t(&fsal_hdl->refcount); assert(fsal_hdl->refcount > 0); } /** * @brief Relinquish a reference to the module * * This function relinquishes one reference to the FSAL. After the * reference count falls to zero, the FSAL may be freed and unloaded. * * @param[in] fsal_hdl FSAL on which to release reference. */ static inline void fsal_put(struct fsal_module *fsal_hdl) { int32_t refcount; refcount = atomic_dec_int32_t(&fsal_hdl->refcount); assert(refcount >= 0); if (refcount == 0) { LogInfo(COMPONENT_FSAL, "FSAL %s now unused", fsal_hdl->name); } } /** * @brief Export object * * This structure is created by the @c create_export method on the * FSAL module. It is stored as part of the export list and is used * to manage individual exports, interrogate properties of the * filesystem, and create individual file handle objects. */ struct fsal_export { struct glist_head exports; /*< Link in list of exports from the same FSAL. */ struct fsal_module *fsal; /*< Link back to the FSAL module */ const struct fsal_up_vector *up_ops; /*< Upcall operations */ struct export_ops exp_ops; /*< Vector of operations */ struct fsal_export *sub_export; /*< Sub export for stacking */ struct fsal_export *super_export; /*< Super export for stacking */ struct gsh_export *owning_export; /*< The gsh_export this belongs to */ struct fsal_filesystem *root_fs; struct glist_head filesystems; uint16_t export_id; /*< Export ID copied from gsh_export, initialized by fsal_export_init */ }; /** * @brief Public structure for filesystem objects * * This structure is used for files of all types including directories * and anything else that can be operated on via NFS. * * All functions that create a a new object handle should allocate * memory for the complete (public and private) handle and perform any * private initialization. They should fill the * @c fsal_obj_handle::attributes structure. They should also call the * @c fsal_obj_handle_init function with the public object handle, * object handle operations vector, public export, and file type. * * @note Do we actually need a lock and ref count on the fsal object * handle, since mdcache is managing life cycle and concurrency? * That is, do we expect fsal_obj_handle to have a reference count * that would be separate from that managed by mdcache_lru? */ struct fsal_obj_handle { struct glist_head handles; /*< Link in list of handles under the same FSAL. */ struct fsal_filesystem *fs; /*< Owning filesystem */ struct fsal_module *fsal; /*< Link back to fsal module */ struct fsal_obj_ops *obj_ops; /*< Operations vector */ pthread_rwlock_t obj_lock; /*< Lock on handle */ /* Static attributes */ object_file_type_t type; /*< Object file type */ fsal_fsid_t fsid; /*< Filesystem on which this object is stored */ uint64_t fileid; /*< Unique identifier for this object within the scope of the fsid, (e.g. inode number) */ struct state_hdl *state_hdl; /*< State related to this handle */ int32_t exp_refcnt; /*< ref count by export root nodes or export junction nodes*/ }; /** * @brief Public structure for pNFS Data Servers * * This structure is used for files of all types including directories * and anything else that can be operated on via NFS. Having an * independent reference count and lock here makes sense, since there * is no caching infrastructure overlaying this system. * */ /** * @brief PNFS Data Server * * This represents a Data Server for PNFS. It may be stand-alone, or may be * associated with an export (which represents an MDS). * * NOTE: While a fsal_pnfs_ds is stored in a lookup table, if it has an * mds_export attached, an export reference MUST be held. This is * accomplished by pnfs_ds_insert and pnfs_ds_remove. */ struct fsal_pnfs_ds { struct glist_head ds_list; /**< Entry in list of all DSs */ struct glist_head server; /**< Link in list of Data Servers under the same FSAL. */ struct fsal_module *fsal; /**< Link back to fsal module */ struct fsal_pnfs_ds_ops s_ops; /**< Operations vector */ struct gsh_export *mds_export; /**< related export */ struct fsal_export *mds_fsal_export; /**< related FSAL export (avoids MDS stacking) */ struct avltree_node ds_node; /**< Node in tree of all Data Servers */ int32_t ds_refcount; /**< Reference count */ uint16_t id_servers; /**< Identifier */ }; /** * @brief Public structure for DS file handles * * This structure is used for files of all types including directories * and anything else that can be operated on via NFS. Having an * independent reference count and lock here makes sense, since there * is no caching infrastructure overlaying this system. * */ struct fsal_ds_handle {}; /** * @brief Get a reference on a fsal object handle by export * * This function increments the reference count on export root object * or PseudoFS export junction nodes. * */ static inline void export_root_object_get(struct fsal_obj_handle *obj_hdl) { (void)atomic_inc_int32_t(&obj_hdl->exp_refcnt); } /** * @brief Put a reference on a fsal object handle by export * * This function releases the reference count on export root object * or PseudoFS export junction nodes. * */ static inline void export_root_object_put(struct fsal_obj_handle *obj_hdl) { int32_t __attribute__((unused)) ref = atomic_dec_int32_t(&obj_hdl->exp_refcnt); assert(ref >= 0); } /** ** Resolve forward declarations */ #include "client_mgr.h" #include "export_mgr.h" #include "fsal_up.h" #endif /* !FSAL_API */ /** @} */ nfs-ganesha-6.5/src/include/fsal_convert.h000066400000000000000000000064471473756622300206600ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup FSAL File-System Abstraction Layer * @{ */ /** * @file fsal_convert.h * @brief FSAL conversion function. */ #ifndef FSAL_CONVERT_H #define FSAL_CONVERT_H #include #include #include #include #include "fsal_types.h" /* convert error codes */ fsal_errors_t posix2fsal_error(int posix_errorcode); static inline fsal_status_t posix2fsal_status(int posix_errorcode) { return fsalstat(posix2fsal_error(posix_errorcode), posix_errorcode); } /** converts an fsal open flag to a POSIX open flag. */ void fsal2posix_openflags(fsal_openflags_t fsal_flags, int *p_posix_flags); /** converts an FSAL permission test to a Posix permission test. */ int fsal2posix_testperm(fsal_accessflags_t testperm); /** * Converts POSIX attributes (struct stat) to FSAL attributes * (fsal_attrib_list_t) */ void posix2fsal_attributes(const struct stat *buffstat, struct fsal_attrlist *fsalattr_out); void posix2fsal_attributes_all(const struct stat *buffstat, struct fsal_attrlist *fsalattr_out); /* mode bits are a uint16_t and chmod masks off type */ #define S_IALLUGO (~S_IFMT & 0xFFFF) /** * @brief Convert FSAL mode to POSIX mode * * @param[in] fsal_mode FSAL mode to be translated * * @return The POSIX mode associated to fsal_mode. */ static inline mode_t fsal2unix_mode(uint32_t fsal_mode) { return fsal_mode & S_IALLUGO; } /** * @brief Convert POSIX mode to FSAL mode * * @param[in] unix_mode POSIX mode to be translated * * @return FSAL mode associated with @c unix_mode */ static inline uint32_t unix2fsal_mode(mode_t unix_mode) { return unix_mode & S_IALLUGO; } /** converts POSIX object type to fsal object type. */ object_file_type_t posix2fsal_type(mode_t posix_type_in); /** converts posix fsid to fsal FSid. */ fsal_fsid_t posix2fsal_fsid(dev_t posix_devid); /** * posix2fsal_time: * Convert POSIX time structure (time_t) * to FSAL time type (now struct timespec). */ static inline struct timespec posix2fsal_time(time_t tsec, time_t nsec) { struct timespec ts = { .tv_sec = tsec, .tv_nsec = nsec }; return ts; } const char *object_file_type_to_str(object_file_type_t type); #define my_high32m(a) ((unsigned int)(a >> 32)) #define my_low32m(a) ((unsigned int)a) fsal_dev_t posix2fsal_devt(dev_t posix_devid); #endif /* !FSAL_CONVERT_H */ /** @} */ nfs-ganesha-6.5/src/include/fsal_handle_syscalls.h000066400000000000000000000052271473756622300223430ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) International Business Machines Corp., 2010 * Author(s): Aneesh Kumar K.V * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * @defgroup FSAL File-System Abstraction Layer * @{ */ /** * @file include/fsal_handle_syscalls.h * @brief System calls for the platform dependent handle calls * * @todo This file should be in FSAL_VFS, not in the top-level include * directory. */ #ifndef HANDLE_H #define HANDLE_H #include "config.h" #include #include #include #include #include #include #include #include #include #include /* For having offsetof defined */ #include /* For uint8_t */ #include #include "gsh_types.h" #define VFS_HANDLE_LEN 59 typedef struct vfs_file_handle { uint8_t handle_len; /* does not go on the wire */ uint8_t handle_data[VFS_HANDLE_LEN]; } vfs_file_handle_t; static inline bool vfs_handle_invalid(struct gsh_buffdesc *desc) { return desc->len > VFS_HANDLE_LEN; } #define vfs_alloc_handle(fh) \ do { \ (fh) = alloca(sizeof(struct vfs_file_handle)); \ memset((fh), 0, (sizeof(struct vfs_file_handle))); \ (fh)->handle_len = VFS_HANDLE_LEN; \ } while (0) #define vfs_malloc_handle(fh) \ do { \ (fh) = gsh_calloc(1, sizeof(struct vfs_file_handle)); \ (fh)->handle_len = VFS_HANDLE_LEN; \ } while (0) #ifdef LINUX #include "os/linux/fsal_handle_syscalls.h" #elif FREEBSD #include "os/freebsd/fsal_handle_syscalls.h" #else #error "No by-handle syscalls defined on this platform." #endif #ifndef AT_FDCWD #error "Very old kernel and/or glibc" #endif #endif /** @} */ nfs-ganesha-6.5/src/include/fsal_pnfs.h000066400000000000000000000270541473756622300201430ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * Copyright (C) 2011 Linux Box Corporation * Author: Adam C. Emerson * Boaz Harrosh * * This program 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 3 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 * 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 * --------------------------------------- */ /** * @defgroup FSAL File-System Abstraction Layer * @{ */ /** * @file fsal_pnfs.h * @brief pNFS functions and structures used at the FSAL level */ #ifndef FSAL_PNFS_H #define FSAL_PNFS_H #include "nfs4.h" /** * @page FSAL_PNFS How to pNFS enable your FSAL * * Meta-data server * ================ * * Your FSAL must indicate to callers that it supports pNFS. Ensure * that the @c fs_supports method returns @c true when queried with * @c fso_pnfs_mds_supported. * * You must implement @c getdeviceinfo on the export and may implement * @c getdevicelist, if you wish. To let clients know what layouts * they may request, be sure to implement @c fs_layouttypes. You * should implement @c fs_maximum_segments to inform the protocol * layer the maximum number of segments you will ever provide for a * single layoutget call. (The current Linux kernel only supports one * segment per LAYOUTGET, unfortunately, so that's a good maximum for * now.) Other hints for the protocol layer are @c fs_loc_body_size * (to determine how much space it will allocate for your loc_body XDR * stream) and @c fs_da_addr_size (the same thing for da_addr). * * On @c fsal_obj_handle, you should implement @c layoutget, @c * layoutreturn, and @c layoutcommit. If you want to be able to * recall layouts, you will need to send a request of the type * @c FSAL_UP_EVENT_LAYOUTRECALL with @c fsal_up_submit. For details, * see the documentation for the FSAL Upcall System. * * Data server * =========== * * This is only relevant if you are using the LAYOUT4_NFSV4_1_FILES * layouts. If you are using OSD or Object layouts, or plan to use an * spNFS-like configuration employing naíve data servers, you do not * need to worry about this. * * Your FSAL must indicate to callers that it supports pNFS DS * operations. Ensure that the @c fs_supports method returns @c true * when queried with @c fso_pnfs_ds_supported. * * You must implement the @c make_ds_handle method on the pNFS DS. * This must create an object of type @c fsal_ds_handle from the NFS * handle supplied as part of your layout. See the @c fsal_ds_handle * documentation for details. You must implement the @c release, @c * read, @c write, and @c commit methods. */ /****************************************************** * Basic in-memory types ******************************************************/ /** * @brief Represent a layout segment * * This structure not only represents segments granted by the FSAL or * being committed or returned, but also selectors as used in * LAYOUTRETURN4_FILE. */ struct pnfs_segment { /** The IO mode (must be read or write) */ layoutiomode4 io_mode; /** The offset of the segment */ uint64_t offset; /** The length of the segment */ uint64_t length; }; enum fsal_id { /** The following FSAL_ID implies no PNFS support. */ FSAL_ID_NO_PNFS = 0, /** The following ID is to be used by out of tree implementations * during an experimental phase before we are able to add an * official FSAL_ID. */ FSAL_ID_EXPERIMENTAL = 1, FSAL_ID_RETIRED = 2, FSAL_ID_GPFS = 3, FSAL_ID_CEPH = 4, FSAL_ID_GLUSTER = 6, FSAL_ID_VFS = 7, FSAL_ID_RGW = 8, FSAL_ID_LIZARDFS = 9, FSAL_ID_KVSFS = 10, FSAL_ID_SAUNAFS = 11, FSAL_ID_COUNT }; /** * @brief FSAL view of the NFSv4.1 deviceid4. * * Note that this will be encoded as an opaque, thus the byte order on the wire * will be host order NOT network order. */ struct pnfs_deviceid { /** FSAL_ID - to dispatch getdeviceinfo based on */ uint8_t fsal_id; /** Break up the remainder into useful chunks */ uint8_t device_id1; uint16_t device_id2; uint32_t device_id4; uint64_t devid; }; #define DEVICE_ID_INIT_ZERO(fsal_id) { fsal_id, 0, 0, 0, 0 } /****************************************************** * FSAL MDS function argument structs ******************************************************/ /** * @brief Input parameters to FSAL_layoutget */ struct fsal_layoutget_arg { /** The type of layout being requested */ layouttype4 type; /** The minimum length that must be granted if a layout is to be * granted at all. */ uint64_t minlength; /** This FSAL must use this value (in network byte order) as the * high quad of any deviceid4 it returns in the loc_body. */ uint64_t export_id; /** The maximum number of bytes the client is willing to accept in the response, including XDR overhead. */ uint32_t maxcount; }; /** * In/out and output parameters to FSAL_layoutget */ struct fsal_layoutget_res { /** As input, the offset, length, and iomode requested by the * caller. As output, the offset, length, and iomode of a given * segment granted by the FSAL. */ struct pnfs_segment segment; /** Whatever value the FSAL stores here is saved with the segment * and supplied to it on future calls to LAYOUTCOMMIT and * LAYOUTRETURN. Any memory allocated must be freed on layout * disposal. */ void *fsal_seg_data; /** Whether the layout should be returned on last close. Note * that this flag being set on one segment makes all layout * segments associated with the same stateid return_on_close. */ bool return_on_close; /** This pointer is NULL on the first call FSAL_layoutget. The * FSAL may store a pointer to any data it wishes, and this * pointer will be supplied to future calls to FSAL_layoutget * that serve the same LAYOUTGET operation. The FSAL must * de-allocate any memory it allocated when it sets the * last_segment flag */ void *context; /** The FSAL must set this to true when it has granted the last * segment to satisfy this operation. Currently, no production * clients support multiple segments granted by a single * LAYOUTGET operation, so FSALs should grant a single segment * and set this value on the first call. */ bool last_segment; /** On input, this field signifies a request by the client to be * signaled when a requested but unavailable layout becomes * available. In output, it signifies the FSAL's willingness to * make a callback when the layout becomes available. We do not * yet implement callbacks, so it should always be set to * false. */ bool signal_available; }; /** * @brief Circumstance that triggered the layoutreturn */ enum fsal_layoutreturn_circumstance { /** Return initiated by client call. */ circumstance_client, /** Indicates that the client is performing a return of a * layout it held prior to a server reboot. As such, * cur_segment is meaningless (no record of the layout having * been granted exists). */ circumstance_reclaim, /** This is a return following from the last close on a file with return_on_close layouts. */ circumstance_roc, /** The client has behaved badly and we are taking its layout away forcefully. */ circumstance_revoke, /** The client forgot this layout and requested a new layout on the same file without an layout stateid. */ circumstance_forgotten, /** This layoutrecall is a result of system shutdown */ circumstance_shutdown }; /** * Input parameters to FSAL_layoutreturn */ struct fsal_layoutreturn_arg { /** The type of layout being returned */ layouttype4 lo_type; /** The return type of the LAYOUTRETURN call. Meaningless if fsal_layoutreturn_synthetic is set. */ layoutreturn_type4 return_type; /** The circumstances under which the return was triggered. */ enum fsal_layoutreturn_circumstance circumstance; /** Layout for specified for return. This need not match any * actual granted layout. Offset and length are set to 0 and * NFS4_UINT64_MAX in the case of bulk or synthetic returns. * For synthetic returns, the io_mode is set to * LAYOUTIOMODE4_ANY. */ struct pnfs_segment spec_segment; /** The current segment in the return iteration which is to be * returned. */ struct pnfs_segment cur_segment; /** Pointer to layout specific data supplied by LAYOUTGET. If * dispose is true, any memory allocated for this value must be * freed. */ void *fsal_seg_data; /** If true, the FSAL must free all resources associated with * res.segment. */ bool dispose; /** After this return, there will be no more layouts associated * with this layout state (that is, there will be no more * layouts for this (clientid, handle, layout type) triple. */ bool last_segment; /** Count of recall tokens. 0 if no LAYOUTRECALLs are * satisfied. */ size_t ncookies; /** Array of pointers to layout specific data supplied by * LAYOUTRECALL. If this LAYOUTRETURN completely satisfies * one or more invoked LAYOUTRECALLs, the tokens of the * recalls will be supplied. */ const void *recall_cookies[1]; }; /** * Input parameters to FSAL_layoutcommit */ struct fsal_layoutcommit_arg { /** The type of the layout being committed */ layouttype4 type; /** The segment being committed on this call */ struct pnfs_segment segment; /** Pointer to layout specific data supplied by LAYOUTGET. */ void *fsal_seg_data; /** True if this is a reclaim commit */ bool reclaim; /** True if the client has suggested a new offset */ bool new_offset; /** The offset of the last byte written, if new_offset if set, * otherwise undefined. */ uint64_t last_write; /** True if the client provided a new value for mtime */ bool time_changed; /** If new_time is true, the client-supplied modification time * for the file. otherwise, undefined. */ nfstime4 new_time; }; /** * In/out and output parameters to FSAL_layoutcommit */ struct fsal_layoutcommit_res { /** A pointer, NULL on the first call to FSAL_layoutcommit. The * FSAL may store whatever it wishes in this field and it will * be supplied on all subsequent calls. If the FSAL has * allocated any memory, this memory must be freed if * commit_done is set. */ void *context; /** True if the FSAL is returning a new file size */ bool size_supplied; /** The new file size returned by the FSAL */ uint64_t new_size; /** The FSAL has completed the LAYOUTCOMMIT operation and * FSAL_layoutcommit need not be called again, even if more * segments are left in the layout. */ bool commit_done; }; /** * In/out and output parameters to FSAL_getdevicelist */ struct fsal_getdevicelist_res { /** Input, cookie indicating position in device list from which * to begin. Output, cookie that may be supplied to get the * entry after the alst one returned. Undefined if EOF is * set. */ uint64_t cookie; /** For any non-zero cookie, this must be the verifier returned * from a previous call to getdevicelist. The FSAL may use this * value to verify that the cookie is not out of date. A cookie * verifier may be supplied by the FSAL on output. */ uint64_t cookieverf; /** True if the last deviceid has been returned. */ bool eof; }; #endif /* !FSAL_PNFS_H */ /** @} */ nfs-ganesha-6.5/src/include/fsal_types.h000066400000000000000000001012771473756622300203410ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup FSAL File-System Abstraction Layer * @{ */ /** * @file include/fsal_types.h */ #ifndef _FSAL_TYPES_H #define _FSAL_TYPES_H #include #include #include #include /* for MAXNAMLEN */ #include "uid2grp.h" #include "nfsv41.h" typedef struct nfs_request nfs_request_t; /* Cookie to be used in FSAL_ListXAttrs() to bypass RO xattr */ #define FSAL_XATTR_RW_COOKIE (~0) /** * @brief Object file type within the system */ typedef enum { NO_FILE_TYPE = 0, /* sanity check to ignore type */ REGULAR_FILE = 1, CHARACTER_FILE = 2, BLOCK_FILE = 3, SYMBOLIC_LINK = 4, SOCKET_FILE = 5, FIFO_FILE = 6, DIRECTORY = 7, EXTENDED_ATTR = 8 } object_file_type_t; /* --------------- * FS dependent : * --------------*/ /* export object * Created by fsal and referenced by the export list */ /* handle descriptor * used primarily to extract the bits of a file object handle from * protocol buffers and for calculating hashes. * This points into a buffer allocated and passed by the caller. * len is set to the buffer size when passed. It is updated to * the actual copy length on return. */ /** object name. */ /* Used to record the uid and gid of the client that made a request. */ struct user_cred { uid_t caller_uid; gid_t caller_gid; unsigned int caller_glen; gid_t *caller_garray; }; struct export_perms { /** root uid when no root access is available uid when access is * available but all users are being squashed. */ uid_t anonymous_uid; /** root gid when no root access is available gid when access is * available but all users are being squashed. */ gid_t anonymous_gid; /** Expiration time interval in seconds for attributes. Settable with Attr_Expiration_Time (should never be set for client export_perms. */ int32_t expire_time_attr; /** available export options */ uint32_t options; /** Permission Options that have been set */ uint32_t set; }; /* Define bit values for cred_flags */ #define CREDS_LOADED 0x01 #define CREDS_ANON 0x02 #define UID_SQUASHED 0x04 #define GID_SQUASHED 0x08 #define GARRAY_SQUASHED 0x10 #define MANAGED_GIDS 0x20 /** filesystem identifier */ typedef struct fsal_fsid__ { uint64_t major; uint64_t minor; } fsal_fsid_t; /** raw device spec */ typedef struct fsal_dev__ { uint64_t major; uint64_t minor; } fsal_dev_t; /* constants for specifying which ACL types are supported */ typedef uint16_t fsal_aclsupp_t; #define FSAL_ACLSUPPORT_ALLOW 0x01 #define FSAL_ACLSUPPORT_DENY 0x02 /** ACE types */ typedef uint32_t fsal_acetype_t; #define FSAL_ACE_TYPE_ALLOW 0 #define FSAL_ACE_TYPE_DENY 1 #define FSAL_ACE_TYPE_AUDIT 2 #define FSAL_ACE_TYPE_ALARM 3 #define FSAL_ACE_TYPE_MAX 4 /** ACE flag */ typedef uint32_t fsal_aceflag_t; #define FSAL_ACE_FLAG_FILE_INHERIT 0x00000001 #define FSAL_ACE_FLAG_DIR_INHERIT 0x00000002 #define FSAL_ACE_FLAG_NO_PROPAGATE 0x00000004 #define FSAL_ACE_FLAG_INHERIT_ONLY 0x00000008 #define FSAL_ACE_FLAG_SUCCESSFUL 0x00000010 #define FSAL_ACE_FLAG_FAILED 0x00000020 #define FSAL_ACE_FLAG_GROUP_ID 0x00000040 #define FSAL_ACE_FLAG_INHERITED 0x00000080 #define FSAL_ACE_FLAG_MASK_READ_DENY 0x00000100 #define FSAL_ACE_FLAG_MASK_WRITE_DENY 0x00000200 #define FSAL_ACE_FLAG_MASK_EXECUTE_DENY 0x00000400 /** ACE internal flags */ #define FSAL_ACE_IFLAG_EXCLUDE_FILES 0x40000000 #define FSAL_ACE_IFLAG_EXCLUDE_DIRS 0x20000000 #define FSAL_ACE_IFLAG_SPECIAL_ID 0x80000000 #define FSAL_ACE_FLAG_INHERIT \ (FSAL_ACE_FLAG_FILE_INHERIT | FSAL_ACE_FLAG_DIR_INHERIT | \ FSAL_ACE_FLAG_INHERIT_ONLY) /** ACE permissions */ typedef uint32_t fsal_aceperm_t; #define FSAL_ACE_PERM_READ_DATA 0x00000001 #define FSAL_ACE_PERM_LIST_DIR 0x00000001 #define FSAL_ACE_PERM_WRITE_DATA 0x00000002 #define FSAL_ACE_PERM_ADD_FILE 0x00000002 #define FSAL_ACE_PERM_APPEND_DATA 0x00000004 #define FSAL_ACE_PERM_ADD_SUBDIRECTORY 0x00000004 #define FSAL_ACE_PERM_READ_NAMED_ATTR 0x00000008 #define FSAL_ACE_PERM_WRITE_NAMED_ATTR 0x00000010 #define FSAL_ACE_PERM_EXECUTE 0x00000020 #define FSAL_ACE_PERM_DELETE_CHILD 0x00000040 #define FSAL_ACE_PERM_READ_ATTR 0x00000080 #define FSAL_ACE_PERM_WRITE_ATTR 0x00000100 #define FSAL_ACE_PERM_DELETE 0x00010000 #define FSAL_ACE_PERM_READ_ACL 0x00020000 #define FSAL_ACE_PERM_WRITE_ACL 0x00040000 #define FSAL_ACE_PERM_WRITE_OWNER 0x00080000 #define FSAL_ACE_PERM_SYNCHRONIZE 0x00100000 /** ACE who */ #define FSAL_ACE_NORMAL_WHO 0 #define FSAL_ACE_SPECIAL_OWNER 1 #define FSAL_ACE_SPECIAL_GROUP 2 #define FSAL_ACE_SPECIAL_EVERYONE 3 #define FSAL_ACE_SPECIAL_MASK 4 typedef struct fsal_ace__ { fsal_acetype_t type; fsal_aceperm_t perm; fsal_aceflag_t flag; fsal_aceflag_t iflag; /* Internal flags. */ union { uid_t uid; gid_t gid; } who; } fsal_ace_t; typedef struct fsal_acl__ { uint32_t naces; fsal_ace_t *aces; pthread_rwlock_t acl_lock; uint32_t ref; } fsal_acl_t; typedef struct fsal_acl_data__ { uint32_t naces; fsal_ace_t *aces; } fsal_acl_data_t; /* Macros for NFS4 ACE flags, masks, and special who values. */ #define GET_FSAL_ACE_TYPE(ACE) ((ACE).type) #define GET_FSAL_ACE_PERM(ACE) ((ACE).perm) #define GET_FSAL_ACE_FLAG(ACE) ((ACE).flag) #define GET_FSAL_ACE_IFLAG(ACE) ((ACE).iflag) #define GET_FSAL_ACE_USER(ACE) ((ACE).who.uid) #define GET_FSAL_ACE_GROUP(ACE) ((ACE).who.gid) #define IS_FSAL_ACE_BIT(WORD, BIT) (0 != ((WORD) & (BIT))) #define IS_FSAL_ACE_ALL_BITS(WORD, BITS) (BITS == ((WORD) & (BITS))) #define IS_FSAL_ACE_TYPE(ACE, VALUE) ((GET_FSAL_ACE_TYPE(ACE)) == (VALUE)) #define IS_FSAL_ACE_USER(ACE, VALUE) ((GET_FSAL_ACE_USER(ACE)) == (VALUE)) #define IS_FSAL_ACE_GROUP(ACE, VALUE) ((GET_FSAL_ACE_GROUP(ACE)) == (VALUE)) #define IS_FSAL_ACE_ALLOW(ACE) IS_FSAL_ACE_TYPE(ACE, FSAL_ACE_TYPE_ALLOW) #define IS_FSAL_ACE_DENY(ACE) IS_FSAL_ACE_TYPE(ACE, FSAL_ACE_TYPE_DENY) #define IS_FSAL_ACE_AUDIT(ACE) IS_FSAL_ACE_TYPE(ACE, FSAL_ACE_TYPE_AUDIT) #define IS_FSAL_ACE_ALRAM(ACE) IS_FSAL_ACE_TYPE(ACE, FSAL_ACE_TYPE_ALARM) #define IS_FSAL_ACE_PERM(ACE) (IS_FSAL_ACE_ALLOW(ACE) || IS_FSAL_ACE_DENY(ACE)) #define IS_FSAL_ACE_FLAG(ACE, BIT) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_FLAG(ACE), (BIT)) #define IS_FSAL_ACE_FILE_INHERIT(ACE) \ IS_FSAL_ACE_FLAG(ACE, FSAL_ACE_FLAG_FILE_INHERIT) #define IS_FSAL_ACE_DIR_INHERIT(ACE) \ IS_FSAL_ACE_FLAG(ACE, FSAL_ACE_FLAG_DIR_INHERIT) #define IS_FSAL_ACE_NO_PROPAGATE(ACE) \ IS_FSAL_ACE_FLAG(ACE, FSAL_ACE_FLAG_NO_PROPAGATE) #define IS_FSAL_ACE_INHERIT_ONLY(ACE) \ IS_FSAL_ACE_FLAG(ACE, FSAL_ACE_FLAG_INHERIT_ONLY) #define IS_FSAL_ACE_FLAG_SUCCESSFUL(ACE) \ IS_FSAL_ACE_FLAG(ACE, FSAL_ACE_FLAG_SUCCESSFUL) #define IS_FSAL_ACE_AUDIT_FAILURE(ACE) \ IS_FSAL_ACE_FLAG(ACE, FSAL_ACE_FLAG_FAILED) #define IS_FSAL_ACE_GROUP_ID(ACE) IS_FSAL_ACE_FLAG(ACE, FSAL_ACE_FLAG_GROUP_ID) #define IS_FSAL_ACE_INHERIT(ACE) IS_FSAL_ACE_FLAG(ACE, FSAL_ACE_FLAG_INHERIT) #define IS_FSAL_ACE_INHERTED(ACE) IS_FSAL_ACE_FLAG(ACE, FSAL_ACE_FLAG_INHERITED) #define GET_FSAL_ACE_WHO_TYPE(ACE) (IS_FSAL_ACE_GROUP_ID(ACE) ? "gid" : "uid") #define GET_FSAL_ACE_WHO(ACE) \ (IS_FSAL_ACE_GROUP_ID(ACE) ? (ACE).who.gid : (ACE).who.uid) #define IS_FSAL_ACE_SPECIAL_OWNER(ACE) \ IS_FSAL_ACE_USER(ACE, FSAL_ACE_SPECIAL_OWNER) #define IS_FSAL_ACE_SPECIAL_GROUP(ACE) \ IS_FSAL_ACE_USER(ACE, FSAL_ACE_SPECIAL_GROUP) #define IS_FSAL_ACE_SPECIAL_EVERYONE(ACE) \ IS_FSAL_ACE_USER(ACE, FSAL_ACE_SPECIAL_EVERYONE) #define IS_FSAL_ACE_SPECIAL_MASK(ACE) \ IS_FSAL_ACE_USER(ACE, FSAL_ACE_SPECIAL_MASK) /* Macros for internal NFS4 ACE flags. */ #define IS_FSAL_ACE_SPECIAL_ID(ACE) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_IFLAG(ACE), FSAL_ACE_IFLAG_SPECIAL_ID) #define IS_FSAL_FILE_APPLICABLE(ACE) \ (!IS_FSAL_ACE_BIT(GET_FSAL_ACE_IFLAG(ACE), \ FSAL_ACE_IFLAG_EXCLUDE_FILES)) #define IS_FSAL_DIR_APPLICABLE(ACE) \ (!IS_FSAL_ACE_BIT(GET_FSAL_ACE_IFLAG(ACE), FSAL_ACE_IFLAG_EXCLUDE_DIRS)) #define IS_FSAL_ACE_IFLAG(ACE, BIT) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_IFLAG(ACE), (BIT)) /* Macros for NFS4 ACE permissions. */ #define IS_FSAL_ACE_READ_DATA(ACE) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_PERM(ACE), FSAL_ACE_PERM_READ_DATA) #define IS_FSAL_ACE_LIST_DIR(ACE) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_PERM(ACE), FSAL_ACE_PERM_LIST_DIR) #define IS_FSAL_ACE_WRITE_DATA(ACE) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_PERM(ACE), FSAL_ACE_PERM_WRITE_DATA) #define IS_FSAL_ACE_ADD_FIILE(ACE) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_PERM(ACE), FSAL_ACE_PERM_ADD_FILE) #define IS_FSAL_ACE_APPEND_DATA(ACE) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_PERM(ACE), FSAL_ACE_PERM_APPEND_DATA) #define IS_FSAL_ACE_ADD_SUBDIRECTORY(ACE) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_PERM(ACE), FSAL_ACE_PERM_ADD_SUBDIRECTORY) #define IS_FSAL_ACE_READ_NAMED_ATTR(ACE) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_PERM(ACE), FSAL_ACE_PERM_READ_NAMED_ATTR) #define IS_FSAL_ACE_WRITE_NAMED_ATTR(ACE) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_PERM(ACE), FSAL_ACE_PERM_WRITE_NAMED_ATTR) #define IS_FSAL_ACE_EXECUTE(ACE) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_PERM(ACE), FSAL_ACE_PERM_EXECUTE) #define IS_FSAL_ACE_DELETE_CHILD(ACE) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_PERM(ACE), FSAL_ACE_PERM_DELETE_CHILD) #define IS_FSAL_ACE_READ_ATTR(ACE) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_PERM(ACE), FSAL_ACE_PERM_READ_ATTR) #define IS_FSAL_ACE_WRITE_ATTR(ACE) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_PERM(ACE), FSAL_ACE_PERM_WRITE_ATTR) #define IS_FSAL_ACE_DELETE(ACE) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_PERM(ACE), FSAL_ACE_PERM_DELETE) #define IS_FSAL_ACE_READ_ACL(ACE) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_PERM(ACE), FSAL_ACE_PERM_READ_ACL) #define IS_FSAL_ACE_WRITE_ACL(ACE) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_PERM(ACE), FSAL_ACE_PERM_WRITE_ACL) #define IS_FSAL_ACE_WRITE_OWNER(ACE) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_PERM(ACE), FSAL_ACE_PERM_WRITE_OWNER) #define IS_FSAL_ACE_SYNCHRONIZE(ACE) \ IS_FSAL_ACE_BIT(GET_FSAL_ACE_PERM(ACE), FSAL_ACE_PERM_SYNCHRONIZE) /** * Stores root and fs locations. fs locations format is as follows * * : */ typedef struct fsal_fs_locations { uint32_t ref; uint32_t nservers; /* size of server array */ pthread_rwlock_t fsloc_lock; char *fs_root; char *rootpath; utf8string *server; } fsal_fs_locations_t; /** * Defines an attribute mask. * * Do not just use OR and AND to test these, use the macros. */ typedef uint64_t attrmask_t; /** For stackable FSALs that just pass through dealings with attributes */ #define ALL_ATTRIBUTES UINT64_MAX /** * Attribute masks. */ /* supported attributes (Obsolete) #define ATTR_SUPPATTR 0x0000000000000001 */ /* file type */ #define ATTR_TYPE 0x0000000000000002LL /* file size */ #define ATTR_SIZE 0x0000000000000004LL /* filesystem id */ #define ATTR_FSID 0x0000000000000008LL /* file space reserve */ #define ATTR4_SPACE_RESERVED 0x0000000000000010LL /* ACL */ #define ATTR_ACL 0x0000000000000020LL /* file id */ #define ATTR_FILEID 0x0000000000000040LL /* Access permission flag */ #define ATTR_MODE 0x0000000000000080LL /* Number of hard links */ #define ATTR_NUMLINKS 0x0000000000000100LL /* owner ID */ #define ATTR_OWNER 0x0000000000000200LL /* group ID */ #define ATTR_GROUP 0x0000000000000400LL /* ID of device for block special or character special files*/ #define ATTR_RAWDEV 0x0000000000000800LL /* Access time */ #define ATTR_ATIME 0x0000000000001000LL /* Creation time */ #define ATTR_CREATION 0x0000000000002000LL /* Metadata modification time */ #define ATTR_CTIME 0x0000000000004000LL /* data modification time */ #define ATTR_MTIME 0x0000000000008000LL /* space used by this file. */ #define ATTR_SPACEUSED 0x0000000000010000LL /* This bit indicates that an error occurred during getting object attributes */ #define ATTR_RDATTR_ERR 0x8000000000000000LL /* Generation number */ #define ATTR_GENERATION 0x0000000000080000LL /* Change attribute */ #define ATTR_CHANGE 0x0000000000100000LL /* Set atime to server time */ #define ATTR_ATIME_SERVER 0x0000000000200000LL /* Set mtime to server time */ #define ATTR_MTIME_SERVER 0x0000000000400000LL /* Set fs locations */ #define ATTR4_FS_LOCATIONS 0x0000000000800000LL /* xattr supported */ #define ATTR4_XATTR 0x0000000001000000LL /* security labels supported */ #define ATTR4_SEC_LABEL 0x0000000002000000LL /* attributes that used for NFS v3 */ #define ATTRS_NFS3 \ (ATTR_MODE | ATTR_FILEID | ATTR_TYPE | ATTR_RAWDEV | ATTR_NUMLINKS | \ ATTR_OWNER | ATTR_GROUP | ATTR_SIZE | ATTR_ATIME | ATTR_MTIME | \ ATTR_CTIME | ATTR_SPACEUSED | ATTR_FSID) #define ATTRS_NFS3_ACL (ATTRS_NFS3 | ATTR_ACL) #define ATTRS_TIME (ATTR_ATIME | ATTR_MTIME | ATTR_CTIME) #define ATTRS_CREDS (ATTR_OWNER | ATTR_GROUP) #define CREATE_MASK_NON_REG_NFS3 (ATTRS_TIME) #define CREATE_MASK_NON_REG_NFS4 (ATTRS_TIME | ATTR_ACL) #define CREATE_MASK_REG_NFS3 (CREATE_MASK_NON_REG_NFS3 | ATTR_SIZE) #define CREATE_MASK_REG_NFS4 (CREATE_MASK_NON_REG_NFS4 | ATTR_SIZE) #define ATTRS_SET_TIME \ (ATTR_ATIME | ATTR_MTIME | ATTR_ATIME_SERVER | ATTR_MTIME_SERVER) /** Define the set of attributes contained or derived from struct stat that * are supplied by posix2fsal_attributes. */ #define ATTRS_POSIX \ (ATTR_TYPE | ATTR_SIZE | ATTR_FSID | ATTR_FILEID | ATTR_MODE | \ ATTR_NUMLINKS | ATTR_OWNER | ATTR_GROUP | ATTR_ATIME | ATTR_CTIME | \ ATTR_MTIME | ATTR_CHANGE | ATTR_SPACEUSED | ATTR_RAWDEV) /** * @brief A list of FS object's attributes. */ struct fsal_attrlist { attrmask_t request_mask; /*< Indicates the requested from the FSAL. */ attrmask_t valid_mask; /*< Indicates the attributes to be set or that have been filled in by the FSAL. */ attrmask_t supported; /*< Indicates which attributes the FSAL supports. */ object_file_type_t type; /*< Type of this object */ uint64_t filesize; /*< Logical size (amount of data that can be read) */ fsal_fsid_t fsid; /*< Filesystem on which this object is stored */ uint64_t fsid3; /*< Squashed fsid for NFS v3 */ fsal_acl_t *acl; /*< ACL for this object */ uint64_t fileid; /*< Unique identifier for this object within the scope of the fsid, (e.g. inode number) */ uint32_t mode; /*< POSIX access mode */ uint32_t numlinks; /*< Number of links to this file */ uint64_t owner; /*< Owner ID */ uint64_t group; /*< Group ID */ fsal_dev_t rawdev; /*< Major/minor device number (only meaningful for character/block special files.) */ struct timespec atime; /*< Time of last access */ struct timespec creation; /*< Creation time */ struct timespec ctime; /*< Inode modification time (a la stat. NOT creation.) */ struct timespec mtime; /*< Time of last modification */ uint64_t spaceused; /*< Space used on underlying filesystem */ uint64_t change; /*< A 'change id' */ uint64_t generation; /*< Generation number for this file */ int32_t expire_time_attr; /*< Expiration time interval in seconds for attributes. Settable by FSAL. */ fsal_fs_locations_t *fs_locations; /*< fs locations for this object if any */ struct sec_label4 sec_label; }; /****************************************************** * Attribute mask management. ******************************************************/ /** this macro tests if an attribute is set * example : * FSAL_TEST_MASK( attrib_list.mask, FSAL_ATTR_CREATION ) */ #define FSAL_TEST_MASK(_attrib_mask_, _attr_const_) \ ((_attrib_mask_) & (_attr_const_)) /** this macro sets an attribute * example : * FSAL_SET_MASK( attrib_list.mask, FSAL_ATTR_CREATION ) */ #define FSAL_SET_MASK(_attrib_mask_, _attr_const_) \ ((_attrib_mask_) |= (_attr_const_)) #define FSAL_UNSET_MASK(_attrib_mask_, _attr_const_) \ ((_attrib_mask_) &= ~(_attr_const_)) /** this macro clears the attribute mask * example : * FSAL_CLEAR_MASK( attrib_list.asked_attributes ) */ #define FSAL_CLEAR_MASK(_attrib_mask_) ((_attrib_mask_) = 0LL) /****************************************************** * FSAL extended attributes management. ******************************************************/ #define XATTR_NAME_SIZE (MAXNAMLEN + 1) /** An extended attribute entry */ typedef struct fsal_xattrent { uint64_t xattr_id; /*< xattr index */ uint64_t xattr_cookie; /*< cookie for the next entry */ char xattr_name[XATTR_NAME_SIZE]; /*< attribute name */ } fsal_xattrent_t; /* generic definitions for extended attributes */ #define XATTR_FOR_FILE 0x00000001 #define XATTR_FOR_DIR 0x00000002 #define XATTR_FOR_SYMLINK 0x00000004 #define XATTR_FOR_ALL 0x0000000F #define XATTR_RO 0x00000100 #define XATTR_RW 0x00000200 /* function for getting an attribute value */ #define XATTR_RW_COOKIE ~0 /* Flags representing if an FSAL supports read or write delegations */ #define FSAL_OPTION_FILE_READ_DELEG 0x00000001 /*< File read delegations */ #define FSAL_OPTION_FILE_WRITE_DELEG 0x00000002 /*< File write delegations */ #define FSAL_OPTION_FILE_DELEGATIONS \ (FSAL_OPTION_FILE_READ_DELEG | FSAL_OPTION_FILE_WRITE_DELEG) #define FSAL_OPTION_NO_DELEGATIONS 0 /** Mask for permission testing. Both mode and ace4 mask are encoded. */ typedef enum { FSAL_R_OK = 0x04000000, /*< Test for Read permission */ FSAL_W_OK = 0x02000000, /*< Test for Write permission */ FSAL_X_OK = 0x01000000, /*< Test for execute permission */ FSAL_ACCESS_OK = 0x00000000, /*< Allow */ FSAL_ACCESS_FLAG_BIT_MASK = 0x80000000, FSAL_MODE_BIT_MASK = 0x07000000, FSAL_ACE4_BIT_MASK = 0x50FFFFFF, FSAL_MODE_MASK_FLAG = 0x00000000, FSAL_ACE4_MASK_FLAG = 0x80000000, FSAL_ACE4_PERM_CONTINUE = 0x40000000, /*< Indicate ACL evaluation * should continue */ FSAL_ACE4_REQ_FLAG = 0x10000000, /*< Indicate required ACL allow */ } fsal_accessflags_t; static inline fsal_accessflags_t FSAL_MODE_MASK(fsal_accessflags_t access) { unsigned long acc = access & FSAL_MODE_BIT_MASK; return (fsal_accessflags_t)acc; } static inline fsal_accessflags_t FSAL_ACE4_MASK(fsal_accessflags_t access) { unsigned long acc = access & FSAL_ACE4_BIT_MASK; return (fsal_accessflags_t)acc; } #define FSAL_MODE_MASK_SET(access) (access | FSAL_MODE_MASK_FLAG) #define FSAL_ACE4_MASK_SET(access) (access | FSAL_ACE4_MASK_FLAG) #define IS_FSAL_MODE_MASK_VALID(access) \ ((access & FSAL_ACCESS_FLAG_BIT_MASK) == FSAL_MODE_MASK_FLAG) #define IS_FSAL_ACE4_MASK_VALID(access) \ ((access & FSAL_ACCESS_FLAG_BIT_MASK) == FSAL_ACE4_MASK_FLAG) #define IS_FSAL_ACE4_REQ(access) (access & FSAL_ACE4_REQ_FLAG) #define FSAL_WRITE_ACCESS \ (FSAL_MODE_MASK_SET(FSAL_W_OK) | \ FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_WRITE_DATA | \ FSAL_ACE_PERM_APPEND_DATA)) #define FSAL_READ_ACCESS \ (FSAL_MODE_MASK_SET(FSAL_R_OK) | \ FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_READ_DATA)) #define FSAL_EXECUTE_ACCESS \ (FSAL_MODE_MASK_SET(FSAL_X_OK) | \ FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_EXECUTE)) /** FSAL_open behavior. */ typedef uint16_t fsal_openflags_t; #define FSAL_O_CLOSED 0x0000 /* Closed */ #define FSAL_O_READ 0x0001 /* read */ #define FSAL_O_WRITE 0x0002 /* write */ #define FSAL_O_RDWR \ (FSAL_O_READ | FSAL_O_WRITE) /* read/write: both flags * explicitly or'd together * so that FSAL_O_RDWR can * be used as a mask */ #define FSAL_O_RECLAIM 0x0008 /* open reclaim */ #define FSAL_O_ANY 0x0020 /* any open file descriptor is usable */ #define FSAL_O_TRUNC 0x0040 /* Truncate file on open */ #define FSAL_O_DENY_READ 0x0100 #define FSAL_O_DENY_WRITE 0x0200 #define FSAL_O_DENY_WRITE_MAND 0x0400 /* Mandatory deny-write (i.e. NFSv4) */ #define FSAL_O_DENY_NONE 0x0000 /* NFS open flags */ #define FSAL_O_OPENFLAGS \ (FSAL_O_RDWR | FSAL_O_DENY_READ | FSAL_O_DENY_WRITE | \ FSAL_O_DENY_WRITE_MAND) /* Extract NFS open flags */ #define FSAL_O_NFS_FLAGS(flags) ((flags) & (FSAL_O_OPENFLAGS)) enum fsal_create_mode { FSAL_NO_CREATE = 0, FSAL_UNCHECKED = 1, FSAL_GUARDED = 2, FSAL_EXCLUSIVE = 3, FSAL_EXCLUSIVE_41 = 4, FSAL_EXCLUSIVE_9P, }; /** File system static info. */ /* enums for accessing * boolean fields of staticfsinfo */ typedef enum enum_fsal_fsinfo_options { fso_no_trunc, fso_chown_restricted, fso_case_insensitive, fso_case_preserving, fso_link_support, fso_symlink_support, fso_lock_support, fso_lock_support_async_block, fso_lock_full_control, fso_named_attr, fso_unique_handles, fso_cansettime, fso_homogenous, fso_auth_exportpath_xdev, fso_delegations_r, fso_delegations_w, fso_pnfs_ds_supported, fso_pnfs_mds_supported, fso_grace_method, fso_link_supports_permission_checks, fso_rename_changes_key, fso_compute_readdir_cookie, fso_whence_is_name, fso_readdir_plus, fso_compliant_eof_behavior, fso_xattr_support, fso_allocate_own_read_buffer, } fsal_fsinfo_options_t; /* The largest maxread and maxwrite value */ #define FSAL_MAXIOSIZE XDR_BYTES_MAXLEN_IO /* Default time_delta nanoseconds. * FSALs may set time_delta.tv_nsec to this value or override with a different * value or even fill in programmatically with the result from some call. */ #define FSAL_DEFAULT_TIME_DELTA_NSEC 100 typedef struct fsal_staticfsinfo_t { uint64_t maxfilesize; /*< maximum allowed filesize */ uint32_t maxlink; /*< maximum hard links on a file */ uint32_t maxnamelen; /*< maximum name length */ uint32_t maxpathlen; /*< maximum path length */ bool no_trunc; /*< is it erroneous when name>maxnamelen? */ bool chown_restricted; /*< is chown limited to super-user. */ bool case_insensitive; /*< case insensitive FS? */ bool case_preserving; /*< FS preserves case? */ bool link_support; /*< FS supports hardlinks? */ bool symlink_support; /*< FS supports symlinks? */ bool lock_support; /*< FS supports file locking? */ bool lock_support_async_block; /*< FS supports blocking locks? */ bool lock_full_control; /*< FS persists enough about file locking? FS checks strictly about whether to grant or reclaim lock. */ bool named_attr; /*< FS supports named attributes. */ bool unique_handles; /*< Handles are unique and persistent. */ fsal_aclsupp_t acl_support; /*< what type of ACLs are supported */ bool cansettime; /*< Is it possible to change file times using SETATTR. */ bool homogenous; /*< Are supported attributes the same for all objects of this filesystem. */ attrmask_t supported_attrs; /*< If the FS is homogenous, this indicates the set of supported attributes. */ uint64_t maxread; /*< Max read size */ uint64_t maxwrite; /*< Max write size */ uint32_t umask; /*< This mask is applied to the mode of created objects */ bool auth_exportpath_xdev; /*< This flag indicates weither it is possible to cross junctions for resolving an NFS export path. */ uint32_t delegations; /*< fsal supports delegations */ bool pnfs_mds; /*< fsal supports file pnfs MDS */ bool pnfs_ds; /*< fsal supports file pnfs DS */ bool fsal_trace; /*< fsal trace supports */ bool fsal_grace; /*< fsal will handle grace */ bool link_supports_permission_checks; bool rename_changes_key; /*< Handle key is changed across rename */ bool compute_readdir_cookie; bool whence_is_name; bool readdir_plus; /*< FSAL supports readdir_plus */ bool compliant_eof_behavior; /* FSAL compliant to end-of-file NFS v3 read behavior. */ bool xattr_support; /*< fsal can support xattrs */ bool allocate_own_read_buffer; /*< fsal will allocate its own read * buffers. */ int32_t expire_time_parent; /*< Expiration time interval in seconds for parent handle. If FS gives information about parent change for a directory with an upcall, set this to -1. Else set it to some positive value. Defaults to -1. */ } fsal_staticfsinfo_t; /** * @brief The return error values of FSAL calls. */ typedef enum fsal_errors_t { ERR_FSAL_NO_ERROR = 0, ERR_FSAL_PERM = 1, ERR_FSAL_NOENT = 2, ERR_FSAL_IO = 5, ERR_FSAL_NXIO = 6, ERR_FSAL_NOMEM = 12, ERR_FSAL_ACCESS = 13, ERR_FSAL_FAULT = 14, ERR_FSAL_STILL_IN_USE = 16, ERR_FSAL_EXIST = 17, ERR_FSAL_XDEV = 18, ERR_FSAL_NOTDIR = 20, ERR_FSAL_ISDIR = 21, ERR_FSAL_INVAL = 22, ERR_FSAL_FBIG = 27, ERR_FSAL_NOSPC = 28, ERR_FSAL_ROFS = 30, ERR_FSAL_MLINK = 31, ERR_FSAL_DQUOT = 49, ERR_FSAL_NO_DATA = 61, ERR_FSAL_NAMETOOLONG = 78, ERR_FSAL_NOTEMPTY = 93, ERR_FSAL_STALE = 151, ERR_FSAL_BADHANDLE = 10001, ERR_FSAL_BADCOOKIE = 10003, ERR_FSAL_NOTSUPP = 10004, ERR_FSAL_TOOSMALL = 10005, ERR_FSAL_SERVERFAULT = 10006, ERR_FSAL_BADTYPE = 10007, ERR_FSAL_DELAY = 10008, ERR_FSAL_LOCKED = 10012, ERR_FSAL_FHEXPIRED = 10014, ERR_FSAL_SHARE_DENIED = 10015, ERR_FSAL_SYMLINK = 10029, ERR_FSAL_ATTRNOTSUPP = 10032, ERR_FSAL_BAD_RANGE = 10042, ERR_FSAL_NOT_INIT = 20001, ERR_FSAL_ALREADY_INIT = 20002, ERR_FSAL_BAD_INIT = 20003, ERR_FSAL_SEC = 20004, ERR_FSAL_NO_QUOTA = 20005, ERR_FSAL_NOT_OPENED = 20010, ERR_FSAL_DEADLOCK = 20011, ERR_FSAL_OVERFLOW = 20012, ERR_FSAL_INTERRUPT = 20013, ERR_FSAL_BLOCKED = 20014, ERR_FSAL_TIMEOUT = 20015, ERR_FSAL_FILE_OPEN = 10046, ERR_FSAL_UNION_NOTSUPP = 10090, ERR_FSAL_IN_GRACE = 10095, ERR_FSAL_NO_ACE = 10096, ERR_FSAL_CROSS_JUNCTION = 10097, ERR_FSAL_BADNAME = 10098, ERR_FSAL_NOXATTR = 10099, ERR_FSAL_XATTR2BIG = 10100, } fsal_errors_t; /** * @brief The return status of FSAL calls. */ typedef struct fsal_status__ { fsal_errors_t major; /*< FSAL status code */ int minor; /*< Other error code (usually POSIX) */ } fsal_status_t; /****************************************************** * FSAL Returns macros ******************************************************/ /** * fsalstat (was ReturnCode) : * Macro for returning a fsal_status_t without trace nor stats increment. */ static inline fsal_status_t fsalstat(fsal_errors_t major, int minor) { fsal_status_t status = { major, minor }; return status; } /****************************************************** * FSAL Errors handling. ******************************************************/ /** Tests whether the returned status is erroneous. * Example : * if (FSAL_IS_ERROR(status = FSAL_call(...))) { * printf("ERROR status = %d, %d\n", status.major,status.minor); * } */ #define FSAL_IS_SUCCESS(_status_) ((_status_).major == ERR_FSAL_NO_ERROR) #define FSAL_IS_ERROR(_status_) (!FSAL_IS_SUCCESS(_status_)) /** * @brief File system dynamic info. */ typedef struct fsal_dynamicfsinfo__ { uint64_t total_bytes; uint64_t free_bytes; uint64_t avail_bytes; uint64_t total_files; uint64_t free_files; uint64_t avail_files; uint64_t maxread; uint64_t maxwrite; struct timespec time_delta; } fsal_dynamicfsinfo_t; /** * Status of FSAL operations */ /* quotas */ typedef struct fsal_quota__ { uint64_t bhardlimit; uint64_t bsoftlimit; uint64_t curblocks; uint64_t fhardlimit; uint64_t fsoftlimit; uint64_t curfiles; uint64_t btimeleft; uint64_t ftimeleft; uint64_t bsize; } fsal_quota_t; typedef enum { FSAL_QUOTA_BLOCKS = 1, FSAL_QUOTA_INODES = 2 } fsal_quota_type_t; /** * @brief Digest types */ typedef enum fsal_digesttype_t { /* NFS handles */ FSAL_DIGEST_NFSV3, FSAL_DIGEST_NFSV4, } fsal_digesttype_t; typedef enum { FSAL_OP_LOCKT, /*< test if this lock may be applied */ FSAL_OP_LOCK, /*< request a non-blocking lock */ FSAL_OP_LOCKB, /*< request a blocking lock (NEW) */ FSAL_OP_UNLOCK, /*< release a lock */ FSAL_OP_CANCEL /*< cancel a blocking lock (NEW) */ } fsal_lock_op_t; typedef enum { FSAL_LOCK_R, FSAL_LOCK_W, FSAL_NO_LOCK } fsal_lock_t; enum fsal_sle_type { FSAL_POSIX_LOCK, FSAL_LEASE_LOCK }; typedef struct fsal_lock_param_t { enum fsal_sle_type lock_sle_type; fsal_lock_t lock_type; uint64_t lock_start; uint64_t lock_length; bool lock_reclaim; } fsal_lock_param_t; typedef struct fsal_share_param_t { uint32_t share_access; uint32_t share_deny; bool share_reclaim; } fsal_share_param_t; typedef enum { FSAL_DELEG_NONE, FSAL_DELEG_RD, FSAL_DELEG_WR } fsal_deleg_t; typedef char fsal_verifier_t[NFS4_VERIFIER_SIZE]; enum fd_states { FD_LOW, FD_MIDDLE, FD_HIGH, FD_LIMIT, }; struct fd_lru_state { uint32_t fds_system_imposed; uint32_t fds_hard_limit; uint32_t fds_hiwat; uint32_t fds_lowat; /** This is the actual counter of 'futile' attempts at reaping made in a given time period. When it reaches the futility count, we turn off caching of file descriptors. */ uint32_t futility; uint32_t biggest_window; /* defaults to 40% of fd_limit */ uint64_t prev_fd_count; /* previous # of open fds */ time_t prev_time; /* previous time the gc thread was run. */ uint32_t fd_state; uint32_t fd_fallback_limit; }; enum fsal_fd_type { /** @todo FSF - to be removed when old style is deprecated */ FSAL_FD_OLD_STYLE, FSAL_FD_GLOBAL, FSAL_FD_STATE, FSAL_FD_TEMP, }; extern struct fd_lru_state fd_lru_state; struct fsal_export; /** * @brief Generic file handle. */ struct fsal_fd { /** The open and share mode etc. This MUST be first in every * file descriptor structure. */ fsal_openflags_t openflags; int32_t fd_work; int32_t io_work; int32_t want_read; int32_t want_write; struct fsal_export *fsal_export; /** LRU for open global fd **/ struct glist_head fd_lru; /** work_mutex protects fd work */ pthread_mutex_t work_mutex; /** condition to signal when io work may commence */ pthread_cond_t io_work_cond; /** condition to signal when fd work may commence */ pthread_cond_t fd_work_cond; /** Indicate if should be closed on complete. */ bool close_on_complete; /** Indicate if LRU reclaim wants to operate on this fd */ uint32_t lru_reclaim; /** Type of fd */ enum fsal_fd_type fd_type; }; /* FSAL_FD_TEMP initializer, the work_mutex, io_work_cond, and fd_work_cond are * not initialized as these are never used with a FSAL_FD_TEMP. */ #define FSAL_FD_INIT \ { .openflags = FSAL_O_CLOSED, \ .fd_work = 0, \ .io_work = 0, \ .want_read = 0, \ .want_write = 0, \ .fsal_export = op_ctx->fsal_export, \ .fd_lru = { NULL, NULL }, /* work_mutex unused for FSAL_FD_TEMP */ \ /* io_work_cond unused for FSAL_FD_TEMP */ /* fd_work_cond unused for FSAL_FD_TEMP */ \ .close_on_complete = false, \ .lru_reclaim = 0, \ .fd_type = FSAL_FD_TEMP } static inline void init_fsal_fd(struct fsal_fd *fsal_fd, enum fsal_fd_type fd_type, struct fsal_export *fsal_export) { memset(fsal_fd, 0, sizeof(*fsal_fd)); if (fd_type != FSAL_FD_TEMP) { /* Only need to initialize work_mutex and work_cond if not * a FSAL_FD_TEMP. They are not used for FSAL_FD_TEMP. */ PTHREAD_MUTEX_init(&fsal_fd->work_mutex, NULL); PTHREAD_COND_init(&fsal_fd->fd_work_cond, NULL); PTHREAD_COND_init(&fsal_fd->io_work_cond, NULL); } fsal_fd->fd_type = fd_type; fsal_fd->fsal_export = fsal_export; } static inline void destroy_fsal_fd(struct fsal_fd *fsal_fd) { if (fsal_fd->fd_type != FSAL_FD_TEMP) { /* Only need to destroy work_mutex, io_work_cond, and * fd_work_cond if not a FSAL_FD_TEMP. They are not used for * FSAL_FD_TEMP. */ PTHREAD_MUTEX_destroy(&fsal_fd->work_mutex); PTHREAD_COND_destroy(&fsal_fd->fd_work_cond); PTHREAD_COND_destroy(&fsal_fd->io_work_cond); } } /** * @brief The ref counted share reservation state. * * Each field represents the count of instances of that flag being present * in a share reservation. * * There is a separate count of mandatory deny write flags so that they can be * enforced against all writes (non-mandatory deny write is only enforced * against indicated operations). */ struct fsal_share { unsigned int share_access_read; unsigned int share_access_write; unsigned int share_deny_read; unsigned int share_deny_write; /**< Count of mandatory share deny write */ unsigned int share_deny_write_mand; }; #endif /* _FSAL_TYPES_H */ /** @} */ nfs-ganesha-6.5/src/include/fsal_up.h000066400000000000000000000266521473756622300176240ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * This program 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 3 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 * 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 * *--------------------------------------- */ /** * @defgroup fsal_up Upcalls for the FSAL * @{ * * These functions allow a filesystem realization to modify the cache * and trigger recalls without it having to gain personal, intimate * knowledge of the rest of Ganesha. * * These calls are *synchronous*, meaning they immediately do whatever * they're going to do and return to the caller. They are intended to * be called from a notification or other thread. Specifically, this * means that you *must not* call layoutrecall from within layoutget. * * If you need to call one of these methods from within an FSAL * method, use the delayed executor interface in delayed_exec.h with a * delay of 0. If you don't want to share, you could have the FSAL * spawn a thread fridge of its own. * * If people find themselves needing it, generally, I'll rebuild the * asynchronous upcall interface on top of this synchronous one. */ /** * @file fsal_up.h * @brief Definitions for FSAL upcalls */ #ifndef FSAL_UP_H #define FSAL_UP_H #include "gsh_status.h" #include "fsal_api.h" #include "sal_data.h" enum { /* empty flags */ fsal_up_update_null = 0x0000, /* Update the filesize only if the new size is greater * than that currently set */ fsal_up_update_filesize_inc = 0x0001, /* Update the atime only if the new time is later than * the currently set time. */ fsal_up_update_atime_inc = 0x0002, /* Update the creation time only if the new time is * later than the currently set time. */ fsal_up_update_creation_inc = 0x0004, /* Update the ctime only if the new time is later * than that currently * set */ fsal_up_update_ctime_inc = 0x0008, /* Update the mtime only if the new time is later * than that currently set. */ fsal_up_update_mtime_inc = 0x0010, /* Update the spaceused only if the new size is greater * than that currently set. */ fsal_up_update_spaceused_inc = 0x0040, /* The file link count is zero. */ fsal_up_nlink = 0x0080, }; /** * @brief Optional stuff for layoutreturn * @{ */ enum layoutrecall_howspec { layoutrecall_howspec_exactly, layoutrecall_howspec_complement, layoutrecall_not_specced }; struct layoutrecall_spec { enum layoutrecall_howspec how; union { clientid4 client; } u; }; /** @} */ static const uint32_t FSAL_UP_INVALIDATE_ATTRS = 0x01; static const uint32_t FSAL_UP_INVALIDATE_ACL = 0x02; static const uint32_t FSAL_UP_INVALIDATE_CONTENT = 0x04; static const uint32_t FSAL_UP_INVALIDATE_DIR_POPULATED = 0x08; static const uint32_t FSAL_UP_INVALIDATE_DIR_CHUNKS = 0x10; static const uint32_t FSAL_UP_INVALIDATE_CLOSE = 0x100; static const uint32_t FSAL_UP_INVALIDATE_FS_LOCATIONS = 0x200; static const uint32_t FSAL_UP_INVALIDATE_SEC_LABEL = 0x400; static const uint32_t FSAL_UP_INVALIDATE_PARENT = 0x800; #define FSAL_UP_INVALIDATE_CACHE \ (FSAL_UP_INVALIDATE_ATTRS | FSAL_UP_INVALIDATE_ACL | \ FSAL_UP_INVALIDATE_CONTENT | FSAL_UP_INVALIDATE_DIR_POPULATED | \ FSAL_UP_INVALIDATE_DIR_CHUNKS | FSAL_UP_INVALIDATE_FS_LOCATIONS | \ FSAL_UP_INVALIDATE_SEC_LABEL | FSAL_UP_INVALIDATE_PARENT) /** * @brief Possible upcall functions * * This structure holds pointers to upcall functions. Each FSAL * should call through the vector in its export. * * For FSAL stacking, the 'higher' FSAL should copy its vector, * over-ride whatever methods it wishes, and pass the new vector to * the lower FSAL. It may then pass through, surround, or override as * it wishes. * * Note that all these functions take keys, not fsal object handles. * This is because the FSAL will always, by definition, be able to * know the key by which it identifies an object, but cannot know the * address of the handle stored in the cache. */ struct fsal_up_vector { /** The gsh_export this vector lives in */ struct gsh_export *up_gsh_export; /** The fsal_export this vector lives in */ struct fsal_export *up_fsal_export; /** ready to take upcalls condition */ bool up_ready; bool up_cancel; pthread_mutex_t up_mutex; pthread_cond_t up_cond; /** Invalidate some or all of a cache entry * * @param[in] vec Up ops vector * @param[in] obj The file to invalidate * @param[in] flags FSAL_UP_INVALIDATE_* * * @return FSAL status * */ fsal_status_t (*invalidate)(const struct fsal_up_vector *vec, struct gsh_buffdesc *obj, uint32_t flags); /** Update cached attributes * * @param[in] vec Up ops vector * @param[in] obj The file to update * @param[in] attr List of attributes to update. Note that the * @c type, @c fsid, @c fileid, @c rawdev, and * @c generation fields must not be updated and * the corresponding bits in the mask must not * be set, nor may the ATTR_RDATA_ERR bit be set. * @param[in] flags Flags requesting special update handling * */ fsal_status_t (*update)(const struct fsal_up_vector *vec, struct gsh_buffdesc *obj, struct fsal_attrlist *attr, uint32_t flags); /** Grant a lock to a client * * @param[in] vec Up ops vector * @param[in] file The file in question * @param[in] owner The lock owner * @param[in] lock_param A description of the lock * */ state_status_t (*lock_grant)(const struct fsal_up_vector *vec, struct gsh_buffdesc *file, void *owner, fsal_lock_param_t *lock_param); /** Signal lock availability * * @param[in] vec Up ops vector * @param[in] file The file in question * @param[in] owner The lock owner * @param[in] lock_param A description of the lock * */ state_status_t (*lock_avail)(const struct fsal_up_vector *vec, struct gsh_buffdesc *file, void *owner, fsal_lock_param_t *lock_param); /** Perform a layoutrecall on a single file * * @param[in] vec Up ops vector * @param[in] handle Handle on which the layout is held * @param[in] layout_type The type of layout to recall * @param[in] changed Whether the layout has changed and the * client ought to finish writes through MDS * @param[in] segment Segment to recall * @param[in] cookie A cookie returned with the return that * completely satisfies a recall * @param[in] spec Lets us be fussy about what clients we send * to. May be NULL. * */ state_status_t (*layoutrecall)(const struct fsal_up_vector *vec, struct gsh_buffdesc *handle, layouttype4 layout_type, bool changed, const struct pnfs_segment *segment, void *cookie, struct layoutrecall_spec *spec); /** Remove or change a deviceid * * @param[in] notify_type Change or remove * @param[in] layout_type The layout type affected * @param[in] devid The deviceid * @param[in] immediate Whether the change is immediate * */ state_status_t (*notify_device)(notify_deviceid_type4 notify_type, layouttype4 layout_type, struct pnfs_deviceid devid, bool immediate); /** Recall a delegation * * @param[in] vec Up ops vector * @param[in] handle Handle on which the delegation is held */ state_status_t (*delegrecall)(const struct fsal_up_vector *vec, struct gsh_buffdesc *handle); /** Invalidate some or all of a cache entry and close if open * * This version should NOT be used if an FSAL supports extended * operations, instead, the FSAL may directly close the file as * necessary. * * @param[in] vec Up ops vector * @param[in] obj The file to invalidate * @param[in] flags Flags governing invalidation * * @return FSAL status * */ fsal_status_t (*invalidate_close)(const struct fsal_up_vector *vec, struct gsh_buffdesc *obj, uint32_t flags); /** Remove a cache entry, if otherwise unused * * Attempt to shrink this entry from the cache immediately. If the * refcount indicates that it's not otherwise in use (either by an * active call or state held against it), then remove the entry from * the hash and put the sentinel reference. * * @param[in] vec Up ops vector * @param[in] obj The object to remove * @param[in] flags Unused, for future expansion * * @return FSAL status. ERR_FSAL_NO_ERROR means that an entry was * released. Any other error means that one wasn't. */ fsal_status_t (*try_release)(const struct fsal_up_vector *vec, struct gsh_buffdesc *obj, uint32_t flags); }; extern struct fsal_up_vector fsal_up_top; /** * @{ * @brief Asynchronous upcall wrappers */ fsal_status_t up_async_invalidate(struct fridgethr *fr, const struct fsal_up_vector *vec, struct gsh_buffdesc *obj, uint32_t flags, void (*cb)(void *, fsal_status_t), void *cb_arg); fsal_status_t up_async_update(struct fridgethr *fr, const struct fsal_up_vector *vec, struct gsh_buffdesc *obj, struct fsal_attrlist *attr, uint32_t flags, void (*cb)(void *, fsal_status_t), void *cb_arg); fsal_status_t up_async_lock_grant(struct fridgethr *fr, const struct fsal_up_vector *vec, struct gsh_buffdesc *file, void *owner, fsal_lock_param_t *lock_param, void (*cb)(void *, state_status_t), void *cb_arg); fsal_status_t up_async_lock_avail(struct fridgethr *fr, const struct fsal_up_vector *vec, struct gsh_buffdesc *file, void *owner, fsal_lock_param_t *lock_param, void (*cb)(void *, state_status_t), void *cb_arg); fsal_status_t up_async_layoutrecall(struct fridgethr *fr, const struct fsal_up_vector *vec, struct gsh_buffdesc *handle, layouttype4 layout_type, bool changed, const struct pnfs_segment *segment, void *cookie, struct layoutrecall_spec *spec, void (*cb)(void *, state_status_t), void *cb_arg); fsal_status_t up_async_notify_device(struct fridgethr *fr, const struct fsal_up_vector *vec, notify_deviceid_type4 notify_type, layouttype4 layout_type, struct pnfs_deviceid *devid, bool immediate, void (*cb)(void *, state_status_t), void *cb_arg); fsal_status_t up_async_delegrecall(struct fridgethr *fr, const struct fsal_up_vector *vec, struct gsh_buffdesc *handle, void (*cb)(void *, state_status_t), void *cb_arg); /** @} */ int async_delegrecall(struct fridgethr *fr, struct fsal_obj_handle *obj); int async_cbgetattr(struct fridgethr *fr, struct fsal_obj_handle *obj, nfs_client_id_t *client); void up_ready_init(struct fsal_up_vector *up_ops); void up_ready_destroy(struct fsal_up_vector *up_ops); void up_ready_set(struct fsal_up_vector *up_ops); void up_ready_wait(struct fsal_up_vector *up_ops); void up_ready_cancel(struct fsal_up_vector *up_ops); #endif /* FSAL_UP_H */ /** @} */ nfs-ganesha-6.5/src/include/gsh_config.h000066400000000000000000000576751473756622300203120ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup config Ganesha Configuration * * Ganesha configuration is contained in a global structure that is * populated with defaults, then modified from a configuration file. * This structure informs all behaviors of the daemon. * * @{ */ /** * @file nfs_core.h * @brief Configuration structure and defaults for NFS Ganesha */ #ifndef GSH_CONFIG_H #define GSH_CONFIG_H #include #include #include "nfs4.h" #include "gsh_recovery.h" #include "gsh_rpc.h" /** * @brief An enumeration of protocols in the NFS family */ typedef enum protos { P_NFS, /*< NFS, of course. */ #ifdef _USE_NFS3 P_MNT, /*< Mount (for v3) */ #endif #ifdef _USE_NLM P_NLM, /*< NLM (for v3) */ #endif #ifdef _USE_RQUOTA P_RQUOTA, /*< RQUOTA (for v3) */ #endif #ifdef USE_NFSACL3 P_NFSACL, /*< NFSACL (for v3) */ #endif #ifdef RPC_VSOCK P_NFS_VSOCK, /*< NFS over vmware, qemu vmci sockets */ #endif #ifdef _USE_NFS_RDMA P_NFS_RDMA, /*< NFS over RPC/RDMA */ #endif P_COUNT /*< Number of protocols */ } protos; /** * @defgroup config_core Structure and defaults for NFS_Core_Param * * @{ */ /** * @brief Default NFS Port. */ #define NFS_PORT 2049 /** * @brief Default RQUOTA port. */ #define RQUOTA_PORT 875 /** * @brief Default NFS Over RDMA Port. */ #define NFS_RDMA_PORT 20049 /** * @brief Default value for _9p_param.nb_worker */ #define NB_WORKER_THREAD_DEFAULT 256 /** * @brief Default value for core_param.drc.recycle_hiwat */ #define DRC_RECYCLE_HIWAT 1024 /** * @brief Default value for core_param.drc.tcp.npart */ #define DRC_TCP_NPART 1 /** * @brief Default value for core_param.drc.tcp.size */ #define DRC_TCP_SIZE 1024 /** * @brief Default value for core_param.drc.tcp.cachesz */ #define DRC_TCP_CACHESZ 127 /* make prime */ /** * @brief Default value for core_param.drc.tcp.hiwat */ #define DRC_TCP_HIWAT 64 /* 1/2(size) */ /** * @brief Default value for core_param.drc.tcp.recycle_npart */ #define DRC_TCP_RECYCLE_NPART 7 /** * @brief Default value for core_param.drc.tcp.expire_s */ #define DRC_TCP_RECYCLE_EXPIRE_S 600 /* 10m */ /** * @brief Default value for core_param.drc.tcp.checksum */ #define DRC_TCP_CHECKSUM true /** * @brief Default value for core_param.drc.udp.npart */ #define DRC_UDP_NPART 7 /** * @brief Default value for core_param.drc.udp.size */ #define DRC_UDP_SIZE 32768 /** * @brief Default value for core_param.drc.udp.cachesz */ #define DRC_UDP_CACHESZ 599 /* make prime */ /** * @brief Default value for core_param.drc.udp.hiwat */ #define DRC_UDP_HIWAT 16384 /* 1/2(size) */ /** * @brief Default value for core_param.drc.udp.checksum */ #define DRC_UDP_CHECKSUM true /** * Default value for core_param.rpc.max_send_buffer_size */ #define NFS_DEFAULT_SEND_BUFFER_SIZE 1048576 /** * Default value for core_param.rpc.max_recv_buffer_size */ #define NFS_DEFAULT_RECV_BUFFER_SIZE 1048576 /** * @brief Default Monitoring Port. */ #define MONITORING_PORT 9587 /** * @brief Turn off all protocols */ #define CORE_OPTION_NONE 0x00000000 /*< No operations are supported */ /** * @brief Support NFSv3 */ #define CORE_OPTION_NFSV3 0x00000001 /*< NFSv3 operations are supported */ /** * @brief Support NFSv4 */ #define CORE_OPTION_NFSV4 0x00000002 /*< NFSv4 operations are supported */ /** * @brief Support 9p */ #define CORE_OPTION_9P 0x00000004 /*< 9P operations are supported */ /** * @brief NFS AF_VSOCK */ #define CORE_OPTION_NFS_VSOCK 0x00000008 /*< AF_VSOCK NFS listener */ /** * @brief Support RPC/RDMA v1 */ #define CORE_OPTION_NFS_RDMA 0x00000010 /*< RPC/RDMA v1 NFS listener */ /** * @brief Support NFSv3 and NFSv4. */ #ifdef _USE_NFS3 #define CORE_OPTION_ALL_NFS_VERS (CORE_OPTION_NFSV3 | CORE_OPTION_NFSV4) #else #define CORE_OPTION_ALL_NFS_VERS CORE_OPTION_NFSV4 #endif #define UDP_LISTENER_NONE 0 #define UDP_LISTENER_ALL 0x00000001 #define UDP_LISTENER_MOUNT 0x00000002 #define UDP_LISTENER_MASK (UDP_LISTENER_ALL | UDP_LISTENER_MOUNT) #define ROOT_KERBEROS_PRINCIPAL_NONE (1 << 0) #define ROOT_KERBEROS_PRINCIPAL_NFS (1 << 1) #define ROOT_KERBEROS_PRINCIPAL_ROOT (1 << 2) #define ROOT_KERBEROS_PRINCIPAL_HOST (1 << 3) #define ROOT_KERBEROS_PRINCIPAL_ALL \ (ROOT_KERBEROS_PRINCIPAL_NFS | ROOT_KERBEROS_PRINCIPAL_ROOT | \ ROOT_KERBEROS_PRINCIPAL_HOST) #define ROOT_KERBEROS_PRINCIPAL_DEFAULT ROOT_KERBEROS_PRINCIPAL_ALL #ifdef _USE_NFS_RDMA #define NFS_RDMA_ENABLE_FOR_NONE 0 #define NFS_RDMA_ENABLE_FOR_NFSV3 0x00000001 /* Validations for V4.x happens based on below values being 2 << x */ #define NFS_RDMA_ENABLE_FOR_NFSV40 0x00000002 #define NFS_RDMA_ENABLE_FOR_NFSV41 0x00000004 #define NFS_RDMA_ENABLE_FOR_NFSV42 0x00000008 #define NFS_RDMA_ENABLE_FOR_ALL \ (NFS_RDMA_ENABLE_FOR_NFSV3 | NFS_RDMA_ENABLE_FOR_NFSV40) #define NFS_RDMA_ENABLE_BY_DEFAULT NFS_RDMA_ENABLE_FOR_NFSV40 #endif typedef struct nfs_core_param { /** The list of hosts allowed to use the HAProxy protocol. These are * the hosts running HAProxy, acting as load balancing/proxy. Actual * end clients are handled in EXPORT CLIENT lists. */ struct glist_head haproxy_hosts; /** The IPv4 or IPv6 address to which to bind for our listening port. Set by the Bind_Addr option. Must be 8-byte aligned (see sockaddr_t). */ sockaddr_t bind_addr; /** An array of port numbers, one for each protocol. Set by the NFS_Port, MNT_Port, NLM_Port, and Rquota_Port options. */ uint16_t port[P_COUNT]; /** An array of RPC program numbers. The correct values, by default, they may be set to incorrect values with the NFS_Program, MNT_Program, NLM_Program, and Rquota_Program. It is debatable whether this is a worthwhile option to have. */ uint32_t program[P_COUNT]; /** For NFSv3, whether to drop rather than reply to requests yielding I/O errors. True by default and settable with Drop_IO_Errors. As this generally results in client retry, this seems like a dubious idea. */ bool drop_io_errors; /** For NFSv3, whether to drop rather than reply to requests yielding invalid argument errors. False by default and settable with Drop_Inval_Errors. As this generally results in client retry, this seems like a really awful idea. */ bool drop_inval_errors; /** For NFSv3, whether to drop rather than reply to requests yielding delay errors. True by default and settable with Drop_Delay_Errors. As this generally results in client retry and there is no NFSERR_DELAY, this seems like an excellent idea. */ bool drop_delay_errors; /** Parameters controlling the Duplicate Request Cache. */ struct { /** Whether to disable the DRC entirely. Defaults to false, settable by DRC_Disabled. */ bool disabled; /** High water mark for len of recycle queue for DRCs */ uint32_t recycle_hiwat; /* Parameters controlling TCP specific DRC behavior. */ struct { /** Number of partitions in the tree for the TCP DRC. Defaults to DRC_TCP_NPART, settable by DRC_TCP_Npart. */ uint32_t npart; /** Maximum number of requests in a transport's DRC. Defaults to DRC_TCP_SIZE and settable by DRC_TCP_Size. */ uint32_t size; /** Number of entries in the O(1) front-end cache to a TCP Duplicate Request Cache. Defaults to DRC_TCP_CACHESZ and settable by DRC_TCP_Cachesz. */ uint32_t cachesz; /** High water mark for a TCP connection's DRC at which to start retiring entries if we can. Defaults to DRC_TCP_HIWAT and settable by DRC_TCP_Hiwat. */ uint32_t hiwat; /** Number of partitions in the recycle tree that holds per-connection DRCs so they can be used on reconnection (or recycled.) Defaults to DRC_TCP_RECYCLE_NPART and settable by DRC_TCP_Recycle_Npart. */ uint32_t recycle_npart; /** How long to wait (in seconds) before freeing the DRC of a disconnected client. Defaults to DRC_TCP_RECYCLE_EXPIRE_S and settable by DRC_TCP_Recycle_Expire_S. */ uint32_t recycle_expire_s; /** Whether to use a checksum to match requests as well as the XID. Defaults to DRC_TCP_CHECKSUM and settable by DRC_TCP_Checksum. */ bool checksum; } tcp; /** Parameters controlling UDP DRC behavior. */ struct { /** Number of partitions in the tree for the UDP DRC. Defaults to DRC_UDP_NPART, settable by DRC_UDP_Npart. */ uint32_t npart; /** Maximum number of requests in the UDP DRC. Defaults to DRC_UDP_SIZE and settable by DRC_UDP_Size. */ uint32_t size; /** Number of entries in the O(1) front-end cache to the UDP Duplicate Request Cache. Defaults to DRC_UDP_CACHESZ and settable by DRC_UDP_Cachesz. */ uint32_t cachesz; /** High water mark for the UDP DRC at which to start retiring entries if we can. Defaults to DRC_UDP_HIWAT and settable by DRC_UDP_Hiwat. */ uint32_t hiwat; /** Whether to use a checksum to match requests as well as the XID. Defaults to DRC_UDP_CHECKSUM and settable by DRC_UDP_Checksum. */ bool checksum; } udp; } drc; /** Parameters affecting the relation with TIRPC. */ struct { /** Maximum number of connections for TIRPC. Defaults to 1024 and settable by RPC_Max_Connections. */ uint32_t max_connections; /** Size of RPC send buffer. Defaults to NFS_DEFAULT_SEND_BUFFER_SIZE and is settable by MaxRPCSendBufferSize. */ uint32_t max_send_buffer_size; /** Size of RPC receive buffer. Defaults to NFS_DEFAULT_RECV_BUFFER_SIZE and is settable by MaxRPCRecvBufferSize. */ uint32_t max_recv_buffer_size; /** Idle timeout (seconds). Defaults to 5m */ uint32_t idle_timeout_s; /** TIRPC ioq min simultaneous io threads. Defaults to 2 and settable by rpc_ioq_thrdmin. */ uint32_t ioq_thrd_min; /** TIRPC ioq max simultaneous io threads. Defaults to 200 and settable by RPC_Ioq_ThrdMax. */ uint32_t ioq_thrd_max; #ifdef _USE_NFS_RDMA /** RDMA credits */ uint32_t rdma_credits; /** Maximum number of RDMA connections for TIRPC. Defaults to 64 and settable by RPC_Max_RDMA_Connections. */ uint32_t max_rdma_connections; #endif struct { /** Partitions in GSS ctx cache table (default 13). */ uint32_t ctx_hash_partitions; /** Max GSS contexts in cache (i.e., * max GSS clients, default 16K) */ uint32_t max_ctx; /** Max entries to expire in one idle * check (default 200) */ uint32_t max_gc; } gss; } rpc; /** Polling interval for blocked lock polling thread. */ int64_t blocked_lock_poller_interval; /** Protocols to support. Should probably be renamed. Defaults to CORE_OPTION_ALL_VERS and is settable with NFS_Protocols (as a comma-separated list of 3 and 4.) */ unsigned int core_options; /** Whether this Ganesha is part of a cluster of Ganeshas. This is somewhat vendor-specific and should probably be moved somewhere else. Settable with Clustered. */ bool clustered; #ifdef _USE_NLM /** Whether to support the Network Lock Manager protocol. Defaults to true and is settable with Enable_NLM. */ bool enable_NLM; /** Whether to disable NLM_SHARE and NLM_UNSHARE */ bool disable_NLM_SHARE; /** Whether to use the supplied name rather than the IP address in NSM operations. Settable with NSM_Use_Caller_Name. */ bool nsm_use_caller_name; #endif #ifdef _USE_RQUOTA /** Whether to support the Remote Quota protocol. Defaults to true and is settable with Enable_RQUOTA. */ bool enable_RQUOTA; #endif #ifdef USE_NFSACL3 /* Whether to support the POSIX ACL. Defaults to false. */ bool enable_NFSACL; #endif /** Whether to collect NFS stats. Defaults to true. */ bool enable_NFSSTATS; /** Whether to use fast stats. Defaults to false. */ bool enable_FASTSTATS; /** Whether to collect FSAL stats. Defaults to false. */ bool enable_FSALSTATS; #ifdef _USE_NFS3 /** Whether to collect NFSv3 Detailed stats. Defaults to false. */ bool enable_FULLV3STATS; #endif /** Whether to collect NFSv4 Detailed stats. Defaults to false. */ bool enable_FULLV4STATS; /** Whether to collect Auth related stats. Defaults to false. */ bool enable_AUTHSTATS; /** Whether to collect client all ops stats. Defaults to false. */ bool enable_CLNTALLSTATS; /** Whether tcp sockets should use SO_KEEPALIVE */ bool enable_tcp_keepalive; /** Maximum number of TCP probes before dropping the connection */ uint32_t tcp_keepcnt; /** Idle time before TCP starts to send keepalive probes */ uint32_t tcp_keepidle; /** Time between each keepalive probe */ uint32_t tcp_keepintvl; /** Whether to use short NFS file handle to accommodate VMware NFS client. Enable this if you have a VMware NFSv3 client. VMware NFSv3 client has a max limit of 56 byte file handles! Defaults to false. */ bool short_file_handle; /** How long the server will trust information it got by calling getgroups() when "Manage_Gids = TRUE" is used in a export entry. */ int64_t manage_gids_expiration; /** Path to the directory containing server specific modules. In particular, this is where FSALs live. */ char *ganesha_modules_loc; /** Frequency of dbus health heartbeat in ms. Set to 0 to disable */ uint32_t heartbeat_freq; /** Whether to use device major/minor for fsid. Defaults to false. */ bool fsid_device; /** How many times to attempt retry of stat while resolving POSIX * filesystems */ uint32_t resolve_fs_retries; /** Delay (in mili-seconds) between stat calls when trying to resolve * POSIX filesystems */ uint32_t resolve_fs_delay; /** Whether to use Pseudo (true) or Path (false) for NFS v3 and 9P mounts. */ bool mount_path_pseudo; /** Whether to disable UDP listeners */ uint32_t enable_UDP; /** DBus name prefix. Required if one wants to run multiple ganesha instances on single host. The prefix should be different for every ganesha instance. If this is set, dbus name will be .org.ganesha.nfsd */ char *dbus_name_prefix; /** Max parallel queries to Directory Server when Manage_Gids=True. Required if one does not want to overwhelm the directory server. The value limits the number of concurrent uid2grp requests. Useful when dealing with a slow Directory Service provider in an environment where users are part of large number of groups. */ uint32_t max_uid_to_grp_reqs; /** Enable v3 filehandle to be used for v4 */ bool enable_v3_fh_for_v4; /** Readdir response size, default is 64M (limited by maxcount from * nfs request. range 4K-64M */ uint32_t readdir_res_size; /** Readdir max entries count, default is 1M (limited by dircount from * nfs request). range 32-1M */ uint32_t readdir_max_count; /** Whether to call getattrs in nfs4_complete_read and nfs3_complete_read. Defaults to true and settable by Getattrs_In_Complete_Read. */ bool getattrs_in_complete_read; /** Enable malloc trim */ bool malloc_trim; /** Minimum threshold value to call malloc_trim. The malloc_trim * will be called once memory allocation exceeds minimum value. * Size in MB's. Note, this setting has no effect when * Enable_malloc_trim is set to false. */ uint32_t malloc_trim_minthreshold; #ifdef USE_MONITORING /** Monitoring port number. */ uint16_t monitoring_port; /** Enable creating metrics labels on the fly based on client-ip, * export name, etc. * Provides more debugging information, but significantly reduces * performance. */ bool enable_dynamic_metrics; #endif /** if Manage_Gids=True and group resolution fails, * then use gid data from rpc request */ bool enable_rpc_cred_fallback; /** unique server id, if 0 will use start time **/ uint32_t unique_server_id; /** When enabled, a client (from the same source IP address), is * allowed to be connected to a single Ganesha server at a specific * point in time. * See details in connection_manager.h */ bool enable_connection_manager; /** Timeout for waiting until client is fully disconnected from other * Ganesha servers. */ uint32_t connection_manager_timeout_sec; #ifdef _USE_NFS_RDMA /** NFS Versions to supported for NFSoRDMA. Defaults to NFS_RDMA_ENABLE_BY_DEFAULT and is settable with NFS_RDMA_Protocol_Versions (as a comma-separated list of 3,4.0,4.1,4.2) */ unsigned int nfs_rdma_supported_protocol_versions; #endif /** In linux, we need to set PR_SET_IO_FLUSHER to avoid a potential * deadlock when mounting ganesha locally in a system that nears out * memory. This is not possible due to lacking permissions in some * environments (specifically unprivileged containers). If you are * running in an unprivileged environment and you are sure your use * case doesn't require setting PR_SET_IO_FLUSHER, you can enable this * option and Ganesha will not fail on startup if it can't set it. * For more info, see: * https://git.kernel.org/torvalds/p/8d19f1c8e1937baf74e1962aae9f90fa3aeab463 */ bool allow_set_io_flusher_fail; } nfs_core_parameter_t; /** @} */ /** * @defgroup config_nfsv4 Structure and defaults for NFSv4 * * @{ */ /** * @brief Default value for lease_lifetime */ #define LEASE_LIFETIME_DEFAULT 60 /** * @brief Default value for grace period */ #define GRACE_PERIOD_DEFAULT 90 /** * @brief Default value of domainname. */ #define DOMAINNAME_DEFAULT "localdomain" /** * @brief Default value of idmapconf. */ #define IDMAPCONF_DEFAULT "/etc/idmapd.conf" /** * @brief Default value of deleg_recall_retry_delay. */ #define DELEG_RECALL_RETRY_DELAY_DEFAULT 1 /** * @brief NFSv4 minor versions */ #define NFSV4_MINOR_VERSION_ZERO (1 << 0) #define NFSV4_MINOR_VERSION_ONE (1 << 1) #define NFSV4_MINOR_VERSION_TWO (1 << 2) #define NFSV4_MINOR_VERSION_ALL \ (NFSV4_MINOR_VERSION_ZERO | NFSV4_MINOR_VERSION_ONE | \ NFSV4_MINOR_VERSION_TWO) typedef struct nfs_version4_parameter { /** Whether to disable sticky grace. Defaults to true and can be disabled with sticky_grace*/ bool sticky_grace; /** Whether to disable the NFSv4 grace period. Defaults to false and settable with Graceless. */ bool graceless; /** The NFSv4 lease lifetime. Defaults to LEASE_LIFETIME_DEFAULT and is settable with Lease_Lifetime. */ uint32_t lease_lifetime; /** The NFS grace period. Defaults to GRACE_PERIOD_DEFAULT and is settable with Grace_Period. */ uint32_t grace_period; /** The eir_server_scope for lock recovery. Defaults to NULL and is settable with server_scope. */ char *server_scope; /** The eir_server_owner. Defaults to NULL and is settable with server_owner. */ char *server_owner; /** This config param is deprecated. Use `domainname` defined in `directory_services_param` struct. */ char *domainname; /** Path to the idmap configuration file. Defaults to IDMAPCONF_DEFAULT, settable with IdMapConf */ char *idmapconf; /** Full path to recovery root directory */ char *recov_root; /** Name of recovery directory */ char *recov_dir; /** Name of recovery old dir (for legacy recovery_fs only */ char *recov_old_dir; /** Whether to use local password (PAM, on Linux) rather than nfsidmap. Defaults to false if nfsidmap support is compiled in and true if it isn't. Settable with UseGetpwnam. */ bool use_getpwnam; /** Whether to allow bare numeric IDs in NFSv4 owner and group identifiers. Defaults to true and is settable with Allow_Numeric_Owners. */ bool allow_numeric_owners; /** Whether to ONLY use bare numeric IDs in NFSv4 owner and group identifiers. Defaults to false and is settable with Only_Numeric_Owners. NB., this is permissible for a server implementation (RFC 5661). */ bool only_numeric_owners; /** Whether to allow delegations. Defaults to false and settable with Delegations */ bool allow_delegations; /** Delay after which server will retry a recall in case of failures */ uint32_t deleg_recall_retry_delay; /** Whether this a pNFS MDS server. Defaults to false */ bool pnfs_mds; /** Whether this a pNFS DS server. Defaults to false */ bool pnfs_ds; /** Recovery backend */ enum recovery_backend recovery_backend; /** List of supported NFSV4 minor versions */ unsigned int minor_versions; /** Number of allowed slots in the 4.1 slot table */ uint32_t nb_slots; /** whether to skip utf8 validation. defaults to false and settable with enforce_utf8_validation. */ bool enforce_utf8_vld; /** Max number of Client IDs allowed on the system */ uint32_t max_client_ids; /** Max number of files that could be opened by a client. Beyond this * limit, client gets denied if it tries to open too many files. */ uint32_t max_open_states_per_client; /** Threshold for number of expired clients to reach, * in order to start with the actual expiration */ uint32_t expired_client_threshold; /** Number of open files that an unresponsive client could have, * beyond which Ganesha need not keep them in memory or expire it. */ uint32_t max_open_files_for_expired_client; /** Max amount of time till which to keep the unresponsive client * in memory, beyond which Ganesha would start reaping & expire it off. */ uint64_t max_alive_time_for_expired_client; } nfs_version4_parameter_t; typedef struct directory_services_param { /** Domain to use if we aren't using the nfsidmap. Defaults to NULL and is set with DomainName. */ char *domainname; /** Whether to enable idmapping. Defaults to true. */ bool idmapping_active; /** Cache validity in seconds for idmapped user entries */ int64_t idmapped_user_time_validity; /** Cache validity in seconds for idmapped group entries */ int64_t idmapped_group_time_validity; /** Controls principals that will be assigned root privilege */ uint32_t root_kerberos_principal; /** Max number of cached idmapped users */ uint32_t cache_users_max_count; /** Max number of cached idmapped groups */ uint32_t cache_groups_max_count; /** Max number of cached user-groups entries */ uint32_t cache_user_groups_max_count; /** Cache validity in seconds for negative entries */ int64_t negative_cache_time_validity; /** Max number of negative cache users (that failed idmapping) */ uint32_t negative_cache_users_max_count; /** Max number of negative cache groups (that failed idmapping) */ uint32_t negative_cache_groups_max_count; /** Cache reaping interval in seconds for idmapped users and groups */ int64_t cache_reaping_interval; /** Whether to use fully qualified names for idmapping with pw-utils. Defaults to false. */ bool pwutils_use_fully_qualified_names; } directory_services_param_t; /** @} */ typedef struct nfs_param { /** NFS Core parameters, settable in the NFS_Core_Param stanza. */ nfs_core_parameter_t core_param; /** NFSv4 specific parameters, settable in the NFSv4 stanza. */ nfs_version4_parameter_t nfsv4_param; #ifdef _HAVE_GSSAPI /** kerberos configuration. Settable in the NFS_KRB5 stanza. */ nfs_krb5_parameter_t krb5_param; #endif /* _HAVE_GSSAPI */ /** Directory_services configuration, settable in the DIRECTORY_SERVICES stanza. */ directory_services_param_t directory_services_param; } nfs_parameter_t; extern nfs_parameter_t nfs_param; #endif /* GSH_CONFIG_H */ /** @} */ nfs-ganesha-6.5/src/include/gsh_dbus.h000066400000000000000000000122631473756622300177620ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2012, The Linux Box Corporation * Contributor : Matt Benjamin * * Some portions Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #ifndef GSH_DBUS_H #define GSH_DBUS_H #include "config.h" #include #include "log.h" #ifdef _USE_9P #include "9p_types.h" #endif /** * * \file gsh_dbus.h * \author Matt Benjamin and Lee Dobryden * \brief Low-level DBUS message server and callout framework. * * \section DESCRIPTION * * This module implements a very simple service provider interface for a * shared DBUS event loop. * * To use the service, a client implements the gsh_dbus_method_t interface, * then registers its callout routine(s) with gsh_dbus_register_method. */ /** * @brief Introspection contents and method dispatches. * * An interface array is passed when a path is registered by * a facility inside the server. A NULL interface pointer * terminates the list. * * Each interface has NULL terminated arrays of properties, methods, * and signals. */ #define HEARTBEAT_NAME "heartbeat" #define DBUS_PATH "/org/ganesha/nfsd/" #define DBUS_ADMIN_IFACE "org.ganesha.nfsd.admin" #define HEARTBEAT_ARG { .name = "isHealthy", .type = "b", .direction = "out" } #define STATUS_REPLY \ { .name = "status", .type = "b", .direction = "out" }, \ { .name = "error", .type = "s", .direction = "out" } #define MESSAGE_REPLY { .name = "message", .type = "s", .direction = "out" } #define END_ARG_LIST { NULL, NULL, NULL } #define IPADDR_ARG { .name = "ipaddr", .type = "s", .direction = "in" } #define ID_ARG { .name = "id", .type = "q", .direction = "in" } #define PATH_ARG { .name = "path", .type = "s", .direction = "in" } #define EXPR_ARG { .name = "expr", .type = "s", .direction = "in" } #define FSAL_ARG { .name = "fsal", .type = "s", .direction = "in" } #define STAT_TYPE_ARG { .name = "stat_type", .type = "s", .direction = "in" } /* Properties list helper macros */ #define END_ARG_LIST { NULL, NULL, NULL } #define END_PROPS_LIST { NULL, DBUS_PROP_READ, "", NULL, NULL } typedef enum { DBUS_PROP_READ = 0, DBUS_PROP_WRITE, DBUS_PROP_READWRITE } dbus_prop_access_t; struct gsh_dbus_prop { const char *name; dbus_prop_access_t access; const char *type; bool (*get)(DBusMessageIter *reply); bool (*set)(DBusMessageIter *args); }; struct gsh_dbus_arg { const char *name; const char *type; const char *direction; /* not used for signals */ }; struct gsh_dbus_method { const char *name; bool (*method)(DBusMessageIter *args, DBusMessage *reply, DBusError *error); struct gsh_dbus_arg args[]; }; struct gsh_dbus_signal { const char *name; bool (*signal)(DBusMessageIter *args, DBusMessage *reply); struct gsh_dbus_arg args[]; }; struct gsh_dbus_interface { const char *name; bool signal_props; struct gsh_dbus_prop **props; struct gsh_dbus_method **methods; struct gsh_dbus_signal **signals; }; /** * @brief Default value for heartbeat frequency in ms */ #define HEARTBEAT_FREQ_DEFAULT 1000 #define BCAST_FOREVER -1 #define BCAST_STATUS_OK 0x00 #define BCAST_STATUS_WARN 0x01 #define BCAST_STATUS_FATAL 0x02 typedef int (*dbus_bcast_callback)(void *); struct dbus_bcast_item { struct timespec next_bcast_time; uint32_t bcast_interval; uint32_t count; void *bcast_arg; dbus_bcast_callback bcast_callback; struct glist_head dbus_bcast_q; }; struct dbus_bcast_item *add_dbus_broadcast(dbus_bcast_callback bcast_callback, void *bcast_arg, uint32_t bcast_interval, int count); void del_dbus_broadcast(struct dbus_bcast_item *to_remove); /* heartbeat function call back */ int dbus_heartbeat_cb(void *arg); void init_heartbeat(void); void gsh_dbus_pkginit(void); void gsh_dbus_pkgshutdown(void); void *gsh_dbus_thread(void *arg); /* callout method */ void gsh_dbus_append_timestamp(DBusMessageIter *iterp, struct timespec *ts); void gsh_dbus_status_reply(DBusMessageIter *iter, bool success, char *errormsg); int32_t gsh_dbus_register_path(const char *name, struct gsh_dbus_interface **interfaces); int gsh_dbus_broadcast(char *obj_name, char *int_name, char *sig_name, int type, ...); /* more to come */ #ifdef _USE_9P bool arg_9p_op(DBusMessageIter *args, u8 *opcode, char **errormsg); #endif #endif /* GSH_DBUS_H */ nfs-ganesha-6.5/src/include/gsh_intrinsic.h000066400000000000000000000030251473756622300210230ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright (C) 2010, Linux Box Corporation * All Rights Reserved * * Contributor: Matt Benjamin * * This program 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 3 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 * 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 * * ------------- */ /** * * \file gsh_intrinsic.h * \author Matt Benjamin * \brief Compiler intrinsics * * \section DESCRIPTION * * Compiler intrinsics. */ #ifndef _GSH_INTRINSIC_H #define _GSH_INTRINSIC_H #if __GLIBC__ #ifndef likely #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) #endif #else #ifndef likely #define likely(x) (x) #define unlikely(x) (x) #endif #endif #if defined(__PPC64__) #define GSH_CACHE_LINE_SIZE 128 #else /* __x86_64__, __i386__ and others */ #define GSH_CACHE_LINE_SIZE 64 #endif #define GSH_CACHE_PAD(_n) char __pad##_n[GSH_CACHE_LINE_SIZE] #endif /* _GSH_INTRINSIC_H */ nfs-ganesha-6.5/src/include/gsh_list.h000066400000000000000000000211351473756622300177760ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright IBM Corporation, 2010 * Contributor: Aneesh Kumar K.v * * * This software is a server that implements the NFS protocol. * * This program 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 3 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 * 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 * * --------------------------------------- * * */ #ifndef _GANESHA_LIST_H #define _GANESHA_LIST_H #include #ifdef __cplusplus extern "C" { #endif struct glist_head { struct glist_head *next; struct glist_head *prev; }; /* * @brief Compare routine that is used by glist_insert_sorted * * This routine can be defined by the calling function * to enable a sorted insert * * @param struct glist_head *: The first element to compare * @param struct glist_head *: The second element to compare * * @return negative if the 1st element should appear before the 2nd element * 0 if the 1st and 2nd element are equal * positive if the 1st element should appear after the 2nd element */ typedef int (*glist_compare)(struct glist_head *, struct glist_head *); /** * @brief List head initialization * * These macros and functions are only for list heads, * not nodes. The head always points to something and * if the list is empty, it points to itself. */ #define GLIST_HEAD_INIT(name) { &(name), &(name) } #define GLIST_HEAD(name) struct glist_head name = GLIST_HEAD_INIT(name) static inline void glist_init(struct glist_head *head) { /* XXX glist_init? */ head->next = head; head->prev = head; } /* Add the new element between left and right */ static inline void __glist_add(struct glist_head *left, struct glist_head *right, struct glist_head *elt) { elt->prev = left; elt->next = right; left->next = elt; right->prev = elt; } static inline void glist_add_tail(struct glist_head *head, struct glist_head *elt) { __glist_add(head->prev, head, elt); } /* add after the specified entry*/ static inline void glist_add(struct glist_head *head, struct glist_head *elt) { __glist_add(head, head->next, elt); } static inline void glist_del(struct glist_head *node) { struct glist_head *left = node->prev; struct glist_head *right = node->next; if (left != NULL) left->next = right; if (right != NULL) right->prev = left; node->next = NULL; node->prev = NULL; } /* * @brief Move the node to list tail * * This function would help to move node to the tail of list. * Calling `glist_del` and `glist_add_tail` would do extra * operations when the node is already tail. * This function does pre-check for the situation that node is * already tail so that would reduce some operations. * * @param struct glist_head *head: The first element of list * @param struct glist_head *node: The element that want to move to tail * * @return Nothing; The operation is done w/o return value. */ static inline void glist_move_tail(struct glist_head *head, struct glist_head *node) { /* already tail */ if (node == head->prev) return; glist_del(node); __glist_add(head->prev, head, node); } /** * @brief Test if the list in this head is empty */ static inline int glist_empty(struct glist_head *head) { return head->next == head; } /** * @brief Test if this node is not on a list. * * NOT to be confused with glist_empty which is just * for heads. We poison with NULL for disconnected nodes. */ static inline int glist_null(struct glist_head *head) { return (head->next == NULL) && (head->prev == NULL); } static inline void glist_add_list_tail(struct glist_head *list, struct glist_head *elt) { struct glist_head *first = elt->next; struct glist_head *last = elt->prev; if (glist_empty(elt)) { /* nothing to add */ return; } first->prev = list->prev; list->prev->next = first; last->next = list; list->prev = last; } /* Move all of src onto the tail of tgt. Clears src. */ static inline void glist_splice_tail(struct glist_head *tgt, struct glist_head *src) { if (glist_empty(src)) return; src->next->prev = tgt->prev; tgt->prev->next = src->next; src->prev->next = tgt; tgt->prev = src->prev; glist_init(src); } static inline void glist_swap_lists(struct glist_head *l1, struct glist_head *l2) { struct glist_head temp; if (glist_empty(l1)) { /* l1 was empty, so splice tail will accomplish swap. */ glist_splice_tail(l1, l2); return; } if (glist_empty(l2)) { /* l2 was empty, so reverse splice tail will accomplish swap. */ glist_splice_tail(l2, l1); return; } /* Both lists are non-empty */ /* First swap the list pointers. */ temp = *l1; *l1 = *l2; *l2 = temp; /* Then fixup first entry in each list prev to point to it's new head */ l1->next->prev = l1; l2->next->prev = l2; /* And fixup the last entry in each list next to point to it's new head */ l1->prev->next = l1; l2->prev->next = l2; } /** * @brief Split list list1 into list2 at element. * * @note list2 is expected to be empty. list1 is expected to be non-empty (i.e. * element is NOT list1). * * @param[in,out] list1 Source list. * @param[in,out] list2 Destination list. * @param[in,out] element List element to become first element in list2. * */ static inline void glist_split(struct glist_head *list1, struct glist_head *list2, struct glist_head *element) { /* Set up list2 to contain element to the end. */ list2->next = element; list2->prev = list1->prev; /* Fixup the last element of list1 to be the last element of list2, even * if it was element. */ list2->prev->next = list2; /* Now fixup list1 even if element was first element of list1. */ list1->prev = element->prev; /* Now fixup prev of element, even if element was first element of * list1. */ element->prev->next = list1; /* Now fixup element */ element->prev = list2; } #define glist_for_each(node, head) \ for (node = (head)->next; node != head; node = node->next) #define glist_for_each_next(start, node, head) \ for (node = (start)->next; node != head; node = node->next) static inline size_t glist_length(struct glist_head *head) { size_t length = 0; struct glist_head *dummy = NULL; glist_for_each(dummy, head) { ++length; } return length; } #define container_of(addr, type, member) \ ({ \ const typeof(((type *)0)->member) *__mptr = (addr); \ (type *)((char *)__mptr - offsetof(type, member)); \ }) #define glist_first_entry(head, type, member) \ ((head)->next != (head) ? container_of((head)->next, type, member) : \ NULL) #define glist_last_entry(head, type, member) \ ((head)->prev != (head) ? container_of((head)->prev, type, member) : \ NULL) #define glist_entry(node, type, member) container_of(node, type, member) #define glist_for_each_safe(node, noden, head) \ for (node = (head)->next, noden = node->next; node != (head); \ node = noden, noden = node->next) #define glist_for_each_next_safe(start, node, noden, head) \ for (node = (start)->next, noden = node->next; node != (head); \ node = noden, noden = node->next) /* Return the next entry in the list after node if any. */ #define glist_next_entry(head, type, member, node) \ ((node)->next != (head) ? container_of((node)->next, type, member) : \ NULL) /* Return the previous entry in the list after node if any. */ #define glist_prev_entry(head, type, member, node) \ ((node)->prev != (head) ? container_of((node)->prev, type, member) : \ NULL) static inline void glist_insert_sorted(struct glist_head *head, struct glist_head *elt, glist_compare compare) { struct glist_head *next = NULL; if (glist_empty(head)) { glist_add_tail(head, elt); return; } glist_for_each(next, head) { if (compare(next, elt) > 0) break; } __glist_add(next->prev, next, elt); } #ifdef __cplusplus } #endif #endif /* _GANESHA_LIST_H */ nfs-ganesha-6.5/src/include/gsh_lttng/000077500000000000000000000000001473756622300200005ustar00rootroot00000000000000nfs-ganesha-6.5/src/include/gsh_lttng/gsh_lttng.h000066400000000000000000000111161473756622300221420ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2024 Google LLC * contributeur : Shahar Hochma shaharhoch@google.com * * * This program 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 3 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 * 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 * * --------------------------------------- */ #ifndef __GSH_LTTNG_H__ #define __GSH_LTTNG_H__ #ifdef USE_LTTNG #define __S1(x) #x #define __S2(x) __S1(x) #define LINE_AS_STRING __S2(__LINE__) #include "gsh_config.h" #include "lttng_generator.h" #include extern __thread struct req_op_context *op_ctx; /* Note that __func__ is not a string literal (see http://shortn/_GQDGGpmvUd) * and so, unfortunately, cannot be efficiently saved at compile time, so we * don't include it in the trace line. * To circumvent this, the lttng generator adds the function to the format * string itself. */ /* We define temp variable for op_id so that it has a representative key name * generated. */ #define GSH_AUTO_TRACEPOINT(prov_name, event_name, log_level, format, ...) \ do { \ const uint32_t _server_id_ = \ nfs_param.core_param.unique_server_id; \ \ uint32_t _op_id_ = op_ctx != NULL ? op_ctx->op_id : 0; \ AUTO_TRACEPOINT( \ prov_name, event_name, log_level, \ __FILE__ \ ":" LINE_AS_STRING \ " | {fnc} | server_id={} | op_id={} | " format, \ _server_id_, _op_id_, ##__VA_ARGS__); \ } while (0) #define GSH_UNIQUE_AUTO_TRACEPOINT(prov_name, event_name, log_level, format, \ ...) \ do { \ const uint32_t _server_id_ = \ nfs_param.core_param.unique_server_id; \ \ uint32_t _op_id_ = op_ctx != NULL ? op_ctx->op_id : 0; \ UNIQUE_AUTO_TRACEPOINT( \ prov_name, event_name, log_level, \ __FILE__ \ ":" LINE_AS_STRING \ " | {fnc} | server_id={} | op_id={} | " format, \ _server_id_, _op_id_, ##__VA_ARGS__); \ } while (0) #else /* USE_LTTNG */ /* We call the empty function with the variable args to avoid unused variables * warning when LTTNG traces are disabled */ static inline void gsh_empty_function(const char *unused, ...) { } #define GSH_AUTO_TRACEPOINT(prov_name, event_name, log_level, ...) \ gsh_empty_function("unused", ##__VA_ARGS__) #define GSH_UNIQUE_AUTO_TRACEPOINT(prov_name, event_name, log_level, ...) \ gsh_empty_function("unused", ##__VA_ARGS__) /* Define array macros for when lttng generator doesn't exist */ #ifndef TP_INT_ARR #define TP_INT_ARR(_data, _len) (_data), (_len) #define TP_UINT_ARR(_data, _len) (_data), (_len) #define TP_BYTE_ARR(_data, _len) (_data), (_len) #define TP_VAR_STR_ARR(_data, _len) (_data), (_len) #define TP_STR(_str) (_str) #endif /* TP_INT_ARR */ #endif /* USE_LTTNG */ #define TP_SESSION(byte_array) TP_BYTE_ARR((byte_array), NFS4_SESSIONID_SIZE) #define TP_VERIFIER(byte_array) TP_BYTE_ARR((byte_array), NFS4_VERIFIER_SIZE) #define TP_CHARS_LIMIT 16 /* Avoid copying too many chars */ #define TP_UTF8STR_TRUNCATED(utf8str) \ TP_VAR_STR_ARR((utf8str).utf8string_val, \ MIN((utf8str).utf8string_len, TP_CHARS_LIMIT)) #define TP_BYTE_ARR_TRUNCATED(data, len) \ TP_BYTE_ARR((data), MIN((len), TP_CHARS_LIMIT)) #define TP_CINFO_FORMAT "atomic={} before={} after={}" #define TP_CINFO_ARGS_EXPAND(cinfo) \ (cinfo).atomic, (cinfo).before, (cinfo).after #endif /* __GSH_LTTNG_H__ */ nfs-ganesha-6.5/src/include/gsh_recovery.h000066400000000000000000000023651473756622300206650ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright IBM Corporation, 2023 * Contributor: Frank Filz * * -------------------------- * * This program 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 3 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 * 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 * * */ #ifndef GSH_RECOVERY_H #define GSH_RECOVERY_H enum recovery_backend { RECOVERY_BACKEND_FS, RECOVERY_BACKEND_FS_NG, RECOVERY_BACKEND_RADOS_KV, RECOVERY_BACKEND_RADOS_NG, RECOVERY_BACKEND_RADOS_CLUSTER, }; /** * @brief Default value of recovery_backend. */ #define RECOVERY_BACKEND_DEFAULT RECOVERY_BACKEND_FS #endif /* GSH_RECOVERY_H */ nfs-ganesha-6.5/src/include/gsh_refstr.h000066400000000000000000000102461473756622300203310ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /** * Copyright (c) 2018 Jeff Layton * * This program 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 3 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 * 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 */ #ifndef _GSH_REFSTR_H #define _GSH_REFSTR_H #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * @brief Refcounted strings * * This struct contains an atomic refcount and a flexarray intended to hold a * NULL terminated string. They are allocated via gsh_refstr_alloc, and then * users can acquire and release references to them via gsh_refstr_get and * gsh_refstr_put. */ struct gsh_refstr { struct urcu_ref gr_ref; /* refcount */ char gr_val[]; /* buffer */ }; /** * @brief allocate a new gsh_refstr * * Allocate a new gsh_refstr with a gr_val buffer of the given length. * * Note that if allocating for a string, ensure that you pass in a length that * includes the NULL byte. * * @param[in] len Length of the embedded buffer */ struct gsh_refstr *gsh_refstr_alloc(size_t len); /** * @brief create a new gsh_refstr by duplicating an existing string * * Allocate a new gsh_refstr with a gr_val buffer to contain the duplicate. * * @param[in] str The string to be duplicated in the new gsh_refstr */ static inline struct gsh_refstr *gsh_refstr_dup(const char *str) { size_t len = strlen(str) + 1; struct gsh_refstr *refstr = gsh_refstr_alloc(len); memcpy(refstr->gr_val, str, len); return refstr; } /** * @brief free the given gsh_refstr * * A callback function that the refcounting code can use to free a gsh_refstr. * * @param[in] pointer to the gr_ref field in the structure */ void gsh_refstr_release(struct urcu_ref *ref); /** * @brief acquire a reference to the given gsh_refstr * * This is only safe to use when we know that the refcount is not zero. The * typical use it to use rcu_dereference to fetch an rcu-managed pointer * and use this function to take a reference to it inside of the rcu_read_lock. * * Returns the same pointer passed in (for convenience). * * @param[in] gr Pointer to gsh_refstr */ #ifdef HAVE_URCU_REF_GET_UNLESS_ZERO static inline struct gsh_refstr *gsh_refstr_get(struct gsh_refstr *gr) { /* * The assumption is that the persistent reference to the object is * only put after an RCU grace period has settled. */ if (!urcu_ref_get_unless_zero(&gr->gr_ref)) abort(); return gr; } #else /* HAVE_URCU_REF_GET_UNLESS_ZERO */ /* * Older versions of liburcu do not have urcu_ref_get_unless_zero, so we open * code it here for now. */ static inline struct gsh_refstr *gsh_refstr_get(struct gsh_refstr *gr) { struct urcu_ref *ref = &gr->gr_ref; long cur; /* * The assumption is that the persistent reference to the object is * only put after an RCU grace period has settled. So, we abort if * it's already zero or if it looks like the counter will wrap to 0. */ cur = uatomic_read(&ref->refcount); for (;;) { long new_val, old_val = cur; old_val = cur; if (old_val == 0 || old_val == LONG_MAX) abort(); new_val = old_val + 1; cur = uatomic_cmpxchg(&ref->refcount, old_val, new_val); if (cur == old_val) break; } return gr; } #endif /* HAVE_URCU_REF_GET_UNLESS_ZERO */ /** * @brief release a gsh_refstr reference * * Use this to release a gsh_refstr reference. * * @param[in] gr Pointer to gsh_refstr */ static inline void gsh_refstr_put(struct gsh_refstr *gr) { return urcu_ref_put(&gr->gr_ref, gsh_refstr_release); } #ifdef __cplusplus } #endif #endif /* _GSH_REFSTR_H */ nfs-ganesha-6.5/src/include/gsh_rpc.h000066400000000000000000000201371473756622300176100ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * @file common_utils.h * @brief Common tools for printing, parsing, .... * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /* This is a central clearing house for RPC definitions. Nothing * should included anything related to RPC except this file */ #ifndef GSH_RPC_H #define GSH_RPC_H #include "config.h" #include /* Ganesha project has abstract_atomic.h file and tirpc also has a * similar header with the same name. We want to include ganesha * project's header here, so include it here before including any * RPC headers. */ #include "abstract_atomic.h" #ifdef __cplusplus extern "C" { #endif #include #ifdef __cplusplus } #endif #include #include #include #include #include #ifdef _HAVE_GSSAPI #include #include /* XXX */ #endif #include #include #include "common_utils.h" #include "abstract_mem.h" #include "gsh_list.h" #include "log.h" #include "fridgethr.h" #ifdef __cplusplus extern "C" { #endif #define NFS_LOOKAHEAD_NONE 0x0000 #define NFS_LOOKAHEAD_MOUNT 0x0001 #define NFS_LOOKAHEAD_OPEN 0x0002 #define NFS_LOOKAHEAD_CLOSE 0x0004 #define NFS_LOOKAHEAD_READ 0x0008 #define NFS_LOOKAHEAD_WRITE 0x0010 #define NFS_LOOKAHEAD_COMMIT 0x0020 #define NFS_LOOKAHEAD_CREATE 0x0040 #define NFS_LOOKAHEAD_REMOVE 0x0080 #define NFS_LOOKAHEAD_RENAME 0x0100 #define NFS_LOOKAHEAD_LOCK 0x0200 /* !_U types */ #define NFS_LOOKAHEAD_READDIR 0x0400 #define NFS_LOOKAHEAD_LAYOUTCOMMIT 0x0040 #define NFS_LOOKAHEAD_SETATTR 0x0080 #define NFS_LOOKAHEAD_SETCLIENTID 0x0100 #define NFS_LOOKAHEAD_SETCLIENTID_CONFIRM 0x0200 #define NFS_LOOKAHEAD_LOOKUP 0x0400 #define NFS_LOOKAHEAD_READLINK 0x0800 /* ... */ struct nfs_request_lookahead { uint32_t flags; uint16_t read; uint16_t write; }; #define NFS_LOOKAHEAD_HIGH_LATENCY(lkhd) \ (((lkhd).flags & \ (NFS_LOOKAHEAD_READ | NFS_LOOKAHEAD_WRITE | NFS_LOOKAHEAD_COMMIT | \ NFS_LOOKAHEAD_LAYOUTCOMMIT | NFS_LOOKAHEAD_READDIR))) #define XDR_ARRAY_MAXLEN 1024 #define XDR_BYTES_MAXLEN (1024 * 1024) #define XDR_BYTES_MAXLEN_IO (64 * 1024 * 1024) #define XDR_STRING_MAXLEN (8 * 1024) typedef struct sockaddr_storage sockaddr_t; /* Allow much more space than we really need for a sock name. An IPV4 address * embedded in IPv6 could use 45 bytes and then if we add a port, that would be * an additional 6 bytes (:65535) for a total of 51, and then one more for NUL * termination. We could use 64 instead of 128. */ #define SOCK_NAME_MAX 128 struct netconfig *getnetconfigent(const char *); void freenetconfigent(struct netconfig *); /** * @addtogroup config * * @{ */ /** * @defgroup config_krb5 Structure and defaults for NFS_KRB5 * @brief Constants and csturctures for KRB5 configuration * * @{ */ /** * @brief Default value for krb5_param.gss.principal */ #define DEFAULT_NFS_PRINCIPAL "nfs" /** * @brief default value for krb5_param.keytab */ #define DEFAULT_NFS_KEYTAB "/etc/krb5.keytab" #ifdef _HAVE_GSSAPI /** * @brief Kerberos 5 parameters */ typedef struct nfs_krb5_param { /** Kerberos keytab. Defaults to DEFAULT_NFS_KEYTAB, settable with KeytabPath. */ char *keytab; /** The ganesha credential cache. Defaults to DEFAULT_NFS_CCACHE_DIR, unsettable by user. */ char *ccache_dir; /** * @note representation of GSSAPI service, independent of GSSRPC or * TI-RPC global variables. Initially, use it just for * callbacks. */ struct { /** Principal used in callbacks, set to DEFAULT_NFS_PRINCIPAL and unsettable by user. */ char *principal; /** Expanded gss name from principal, equal to principal/host\@domain. Unsettable by user. */ gss_name_t gss_name; } svc; /** Whether to activate Kerberos 5. Defaults to true (if Kerberos support is compiled in) and settable with Active_krb5 */ bool active_krb5; } nfs_krb5_parameter_t; /** @} */ /** @} */ void log_sperror_gss(char *, OM_uint32, OM_uint32); const char *str_gc_proc(rpc_gss_proc_t); #endif /* _HAVE_GSSAPI */ void copy_xprt_addr(sockaddr_t *, SVCXPRT *); int display_sockaddr_port(struct display_buffer *dspbuf, const sockaddr_t *addr, bool ignore_port); static inline int display_sockaddr(struct display_buffer *dspbuf, const sockaddr_t *addr) { return display_sockaddr_port(dspbuf, addr, false); } static inline int display_sockip(struct display_buffer *dspbuf, const sockaddr_t *addr) { return display_sockaddr_port(dspbuf, addr, true); } /* Displays xprt's remote address if non-empty. Otherwise displays * local address. */ static inline int display_xprt_sockaddr(struct display_buffer *db, const SVCXPRT *xprt) { const sockaddr_t *addr; const struct netbuf *hostaddr = svc_getcaller_netbuf(xprt); if (hostaddr->len == 0) hostaddr = svc_getlocal_netbuf(xprt); assert(hostaddr->len <= sizeof(sockaddr_t) && hostaddr->buf != NULL); addr = (const sockaddr_t *)hostaddr->buf; return display_sockaddr(db, addr); } static inline void *socket_addr(sockaddr_t *addr) { switch (addr->ss_family) { case AF_INET: return &(((struct sockaddr_in *)addr)->sin_addr); case AF_INET6: return &(((struct sockaddr_in6 *)addr)->sin6_addr); #ifdef RPC_VSOCK case AF_VSOCK: return &(((struct sockaddr_vm *)addr)->svm_cid); #endif /* VSOCK */ default: return addr; } } static inline size_t socket_addr_len(sockaddr_t *addr) { switch (addr->ss_family) { case AF_INET: return sizeof(struct sockaddr_in); case AF_INET6: return sizeof(struct sockaddr_in6); #ifdef RPC_VSOCK case AF_VSOCK: return sizeof(((struct sockaddr_vm *)addr)->svm_cid); #endif /* VSOCK */ default: return sizeof(sockaddr_t); } } static inline bool sprint_sockip(sockaddr_t *addr, char *buf, int len) { #ifdef RPC_VSOCK if (addr->ss_family == AF_VSOCK) { int rc = snprintf(buf, len, "%d", ((struct sockaddr_vm *)addr)->svm_cid)); return rc >= 0 && rc < len; } #endif /* VSOCK */ if (addr->ss_family != AF_INET && addr->ss_family != AF_INET6) return false; return inet_ntop(addr->ss_family, socket_addr(addr), buf, len) != NULL; } const char *xprt_type_to_str(xprt_type_t); int cmp_sockaddr(sockaddr_t *, sockaddr_t *, bool); int sockaddr_cmpf(sockaddr_t *, sockaddr_t *, bool); uint64_t hash_sockaddr(sockaddr_t *, bool); int get_port(sockaddr_t *); struct READ4resok; struct xdr_uio *xdr_READ4res_uio_setup(struct READ4resok *objp); extern tirpc_pkg_params ntirpc_pp; sockaddr_t *convert_ipv6_to_ipv4(sockaddr_t *ipv6, sockaddr_t *ipv4); bool is_loopback(sockaddr_t *addr); struct io_data { /** Release function if needed. */ void (*release)(void *); /** Data hook for release function to use if needed */ void *release_data; /** Total length of iovec data */ u_int data_len; /** Count of iovec structures in the io vector */ u_int iovcnt; /** Size of last iov buffer */ size_t last_iov_buf_size; /** The actual io vector */ struct iovec *iov; }; typedef struct io_data io_data; bool xdr_io_data(XDR *xdrs, io_data *objp); /** * API to get the buffer to fill the IO Payloads */ void *get_buffer_for_io_response(uint64_t size, size_t *buffer_size); #ifdef __cplusplus } #endif #endif /* GSH_RPC_H */ nfs-ganesha-6.5/src/include/gsh_status.h000066400000000000000000000045321473756622300203500ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright © 2014 CohortFS, LLC. * Author: William Allen Simpson * * This program 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 3 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 * 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 */ /** * @file gsh_status.h * @author William Allen Simpson * @author Daniel Gryniewicz * @date Mon Nov 17 14:11:17 2014 * * @brief Ganesha Unified Status */ #ifndef GSH_STATUS_H #define GSH_STATUS_H /** * @brief Possible Errors from SAL Code * * @note A lot of these errors don't make sense in the context of the * SAL and ought to be pruned. */ typedef enum state_status { STATE_SUCCESS, STATE_MALLOC_ERROR, STATE_POOL_MUTEX_INIT_ERROR, STATE_GET_NEW_LRU_ENTRY, STATE_INIT_ENTRY_FAILED, STATE_FSAL_ERROR, STATE_LRU_ERROR, STATE_HASH_SET_ERROR, STATE_NOT_A_DIRECTORY, STATE_INCONSISTENT_ENTRY, STATE_BAD_TYPE, STATE_ENTRY_EXISTS, STATE_DIR_NOT_EMPTY, STATE_NOT_FOUND, STATE_INVALID_ARGUMENT, STATE_INSERT_ERROR, STATE_HASH_TABLE_ERROR, STATE_FSAL_EACCESS, STATE_IS_A_DIRECTORY, STATE_FSAL_EPERM, STATE_NO_SPACE_LEFT, STATE_READ_ONLY_FS, STATE_IO_ERROR, STATE_ESTALE, STATE_FSAL_ERR_SEC, STATE_LOCKED, STATE_QUOTA_EXCEEDED, STATE_ASYNC_POST_ERROR, STATE_NOT_SUPPORTED, STATE_STATE_ERROR, STATE_FSAL_DELAY, STATE_NAME_TOO_LONG, STATE_LOCK_CONFLICT, STATE_LOCK_BLOCKED, STATE_LOCK_DEADLOCK, STATE_BAD_COOKIE, STATE_FILE_BIG, STATE_GRACE_PERIOD, STATE_SIGNAL_ERROR, STATE_FILE_OPEN, STATE_MLINK, STATE_SERVERFAULT, STATE_TOOSMALL, STATE_XDEV, STATE_SHARE_DENIED, STATE_IN_GRACE, STATE_BADHANDLE, STATE_BAD_RANGE, } state_status_t; #define STATE_FSAL_ESTALE STATE_ESTALE #endif /* !GSH_STATUS_H */ nfs-ganesha-6.5/src/include/gsh_types.h000066400000000000000000000036451473756622300201750ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright © CohortFS, LLC. * Author: Adam C. Emerson * * This program 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 3 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 * 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 */ /** * @file gsh_types.h * @author Adam C. Emerson * @date Mon Jul 9 16:59:11 2012 * * @brief Miscelalneous types used throughout Ganesha. * * This file contains miscellaneous types used through multiple layers * in Ganesha. */ #ifndef GSH_TYPES_H #define GSH_TYPES_H #include #include #ifdef __cplusplus extern "C" { #endif /* An elapsed time in nanosecs works because an unsigned * 64 bit can hold ~584 years of nanosecs. If any code I have * ever written stays up that long, I would be amazed (and dead * a very long time...) */ typedef uint64_t nsecs_elapsed_t; #define NS_PER_USEC ((nsecs_elapsed_t)1000) #define NS_PER_MSEC ((nsecs_elapsed_t)1000000) #define NS_PER_SEC ((nsecs_elapsed_t)1000000000) /** * @brief Buffer descriptor * * This structure is used to describe a counted buffer as an * address/length pair. */ struct gsh_buffdesc { void *addr; /*< First octet/byte of the buffer */ size_t len; /*< Length of the buffer */ }; #ifdef __cplusplus } #endif #endif /* !GSH_TYPES_H */ nfs-ganesha-6.5/src/include/gsh_wait_queue.h000066400000000000000000000043411473756622300211730ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2012, The Linux Box Corporation * Contributor : Matt Benjamin * * This program 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 3 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 * 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 * * ------------- */ /** * * @file wait_queue.h * @author Matt Benjamin * @brief Pthreads-based wait queue package * * @section DESCRIPTION * * This module provides simple wait queues using pthreads primitives. */ #ifndef GSH_WAIT_QUEUE_H #define GSH_WAIT_QUEUE_H #include #include #include "gsh_list.h" #include "common_utils.h" typedef struct wait_entry { pthread_mutex_t wq_mtx; pthread_cond_t wq_cv; } wait_entry_t; #define Wqe_LFlag_None 0x0000 #define Wqe_LFlag_WaitSync 0x0001 #define Wqe_LFlag_SyncDone 0x0002 /* thread wait queue */ typedef struct wait_q_entry { uint32_t flags; uint32_t waiters; wait_entry_t lwe; /* left */ wait_entry_t rwe; /* right */ struct glist_head waitq; } wait_q_entry_t; static inline void init_wait_entry(wait_entry_t *we) { PTHREAD_MUTEX_init(&we->wq_mtx, NULL); PTHREAD_COND_init(&we->wq_cv, NULL); } static inline void init_wait_q_entry(wait_q_entry_t *wqe) { glist_init(&wqe->waitq); init_wait_entry(&wqe->lwe); init_wait_entry(&wqe->rwe); } static inline void destroy_wait_entry(wait_entry_t *we) { PTHREAD_MUTEX_destroy(&we->wq_mtx); PTHREAD_COND_destroy(&we->wq_cv); } static inline void destroy_wait_q_entry(wait_q_entry_t *wqe) { destroy_wait_entry(&wqe->lwe); destroy_wait_entry(&wqe->rwe); } #endif /* GSH_WAIT_QUEUE_H */ nfs-ganesha-6.5/src/include/gsh_xprt_tracepoint.h000066400000000000000000000031511473756622300222460ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2024 Google LLC * contributeur : Shahar Hochma shaharhoch@google.com * * * This program 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 3 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 * 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 * * --------------------------------------- */ #include "gsh_lttng/gsh_lttng.h" #include "rpc/svc.h" /* We can not directly use the similar macros defined in libntirpc because we * can't call NTIRPC_AUTO_TRACEPOINT directly from Ganesha. */ #define GSH_XPRT_AUTO_TRACEPOINT(prov, event, log_level, _xprt, format, ...) \ GSH_AUTO_TRACEPOINT(prov, event, log_level, XPRT_FMT " | " format, \ XPRT_VARS(_xprt), ##__VA_ARGS__) #define GSH_XPRT_UNIQUE_AUTO_TRACEPOINT(prov, event, log_level, _xprt, format, \ ...) \ GSH_UNIQUE_AUTO_TRACEPOINT(prov, event, log_level, \ XPRT_FMT " | " format, XPRT_VARS(_xprt), \ ##__VA_ARGS__) nfs-ganesha-6.5/src/include/gss_credcache.h000066400000000000000000000040451473756622300207400ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ /* Copyright (c) 2004 The Regents of the University of Michigan. 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. 3. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED ``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 REGENTS 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. */ #ifndef GSS_CREDCACHE_H #define GSS_CREDCACHE_H #include #include #include #include #include #include #include struct gssd_k5_kt_princ; extern char *ccachesearch[]; void gssd_init_cred_cache(void); void gssd_shutdown_cred_cache(void); void gssd_clear_cred_cache(void); int gssd_check_mechs(void); int gssd_refresh_krb5_machine_credential(char *, struct gssd_k5_kt_princ *, char *); #endif /* !GSS_CREDCACHE */ nfs-ganesha-6.5/src/include/hashtable.h000066400000000000000000000251341473756622300201200ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup hashtable A non-intrusive, partitioned hash-keyed tree * @{ */ /** * @file HashTable.h * @brief Header for hash functionality * * This file declares the functions and data structures for use with * the Ganesha red-black tree based, concurrent hash store. */ #ifndef HASHTABLE_H #define HASHTABLE_H #include #include #include #include #include "log.h" #include "display.h" #include "abstract_mem.h" #include "gsh_types.h" /** * @brief A pair of buffer descriptors * * This is used internally to represent a single hash datum within the * table. */ struct hash_data { struct gsh_buffdesc key; /*< The lookup key */ struct gsh_buffdesc val; /*< The stored value */ }; /* Forward declaration */ typedef struct hash_param hash_parameter_t; typedef uint32_t (*index_function_t)(struct hash_param *, struct gsh_buffdesc *); typedef uint64_t (*rbthash_function_t)(struct hash_param *, struct gsh_buffdesc *); typedef int (*both_function_t)(struct hash_param *, struct gsh_buffdesc *, uint32_t *, uint64_t *); typedef int (*hash_comparator_t)(struct gsh_buffdesc *, struct gsh_buffdesc *); typedef int (*hash_display_function_t)(struct display_buffer *dspbuf, struct gsh_buffdesc *); #define HT_FLAG_NONE 0x0000 /*< Null hash table flags */ #define HT_FLAG_CACHE \ 0x0001 /*< Indicates that caching should be enabled */ /** * @brief Hash parameters * * This structure defines parameters determining the behaviour of a * given hash table. */ struct hash_param { uint32_t flags; /*< Create flags */ uint32_t cache_entry_count; /*< 2^10 <= Power of 2 <= 2^15 */ uint32_t index_size; /*< Number of partition trees, this MUST be a prime number. */ index_function_t hash_func_key; /*< Partition function, returns an integer from 0 to (index_size - 1). This should be something fairly simple and fast with a uniform distribution. */ rbthash_function_t hash_func_rbt; /*< The actual hash value, termining location within the partition tree. This should be a high quality hash function such as 64 bit Lookup3 or Murmur. */ both_function_t hash_func_both; /*< Index and partition calculator. Returns false on failure. A single function may replace the partition and hash functions. */ hash_comparator_t compare_key; /*< Function to compare two keys. This function returns 0 on equality. */ hash_display_function_t display_key; /*< Function to display a key */ hash_display_function_t display_val; /*< Function to display a value */ char *ht_name; /*< Name of this hash table. */ log_components_t ht_log_component; /*< Log component to use for this hash table */ }; /** * @brief Hash stats * * This structure defines various statistics for a hash table. */ typedef struct hash_stat { size_t entries; /*< Number of entries in the hash table */ size_t min_rbt_num_node; /*< Minimum size (in number of nodes) of the rbt used. */ size_t max_rbt_num_node; /*< Maximum size (in number of nodes) of the rbt used. */ size_t average_rbt_num_node; /*< Average size (in number of nodes) of the rbt used. */ } hash_stat_t; /** * @brief Represents an individual partition * * This structure holds the per-subtree data making up each partition in * a hash table. */ struct hash_partition { size_t count; /*< Number of entries in this partition */ struct rbt_head rbt; /*< The red-black tree */ pthread_rwlock_t ht_lock; /*< Lock for this partition */ struct rbt_node **cache; /*< Expected entry cache */ }; /** * @brief A hash table * * This structure defines an entire hash table. */ typedef struct hash_table { struct hash_param parameter; /*< Definitive parameter for the HashTable */ pool_t *node_pool; /*< Pool of RBT nodes */ pool_t *data_pool; /*< Pool of buffer pairs */ struct hash_partition partitions[]; /*< Parameter.index_size partitions of the hash table. */ } hash_table_t; /** * @brief A 'latching' lock * * This structure defines a 'latching' lock for subsequent operations * on a hash table after an initial lookup. */ struct hash_latch { struct rbt_node *locator; /*< Saved location in the tree */ uint64_t rbt_hash; /*< Saved red-black hash */ uint32_t index; /*< Saved partition index */ }; typedef enum hash_set_how { HASHTABLE_SET_HOW_TEST_ONLY = 1, HASHTABLE_SET_HOW_SET_OVERWRITE = 2, HASHTABLE_SET_HOW_SET_NO_OVERWRITE = 3 } hash_set_how_t; /* How many character used to display a key or value */ #define HASHTABLE_DISPLAY_STRLEN 512 /* Possible errors */ typedef enum hash_error { HASHTABLE_SUCCESS, HASHTABLE_UNKNOWN_HASH_TYPE, HASHTABLE_ERROR_NO_SUCH_KEY, HASHTABLE_ERROR_KEY_ALREADY_EXISTS, HASHTABLE_ERROR_INVALID_ARGUMENT, HASHTABLE_ERROR_DELALL_FAIL, HASHTABLE_NOT_DELETED, HASHTABLE_OVERWRITTEN, } hash_error_t; const char *hash_table_err_to_str(hash_error_t err); /* These are the primitives of the hash table */ struct hash_table *hashtable_init(struct hash_param *); hash_error_t hashtable_destroy(struct hash_table *, int (*)(struct gsh_buffdesc, struct gsh_buffdesc)); hash_error_t hashtable_getlatch(struct hash_table *, const struct gsh_buffdesc *, struct gsh_buffdesc *, bool, struct hash_latch *); hash_error_t hashtable_acquire_latch(struct hash_table *ht, const struct gsh_buffdesc *key, struct hash_latch *latch); void hashtable_releaselatched(struct hash_table *, struct hash_latch *); hash_error_t hashtable_setlatched(struct hash_table *, struct gsh_buffdesc *, struct gsh_buffdesc *, struct hash_latch *, int, struct gsh_buffdesc *, struct gsh_buffdesc *); void hashtable_deletelatched(struct hash_table *, const struct gsh_buffdesc *, struct hash_latch *, struct gsh_buffdesc *, struct gsh_buffdesc *); hash_error_t hashtable_delall(struct hash_table *, int (*)(struct gsh_buffdesc, struct gsh_buffdesc)); void hashtable_log(log_components_t, struct hash_table *); /* These are very simple wrappers around the primitives */ /** * @brief Look up a value * * This function attempts to locate a key in the hash store and return * the associated value. It is implemented as a wrapper around * the hashtable_getlatched function. * * @param[in] ht The hash store to be searched * @param[in] key A buffer descriptor locating the key to find * @param[out] val A buffer descriptor locating the value found * * @return Same possibilities as HahTable_GetLatch */ static inline hash_error_t HashTable_Get(struct hash_table *ht, const struct gsh_buffdesc *key, struct gsh_buffdesc *val) { return hashtable_getlatch(ht, key, val, false, NULL); } /** * @brief Set a pair (key,value) into the Hash Table * * This function sets a value into the hash table with no overwrite. * * The previous version of this function would overwrite, but having * overwrite as the only value for a function that doesn't return the * original buffers is a bad idea and can lead to leaks. * * @param[in,out] ht The hashtable to test or alter * @param[in] key The key to be set * @param[in] val The value to be stored * * @retval HASHTABLE_SUCCESS if successful * @retval HASHTABLE_KEY_ALREADY_EXISTS if the key already exists */ static inline hash_error_t HashTable_Set(struct hash_table *ht, struct gsh_buffdesc *key, struct gsh_buffdesc *val) { /* structure to hold retained state */ struct hash_latch latch; /* Stored return code */ hash_error_t rc = HASHTABLE_SUCCESS; rc = hashtable_getlatch(ht, key, NULL, true, &latch); if ((rc != HASHTABLE_SUCCESS) && (rc != HASHTABLE_ERROR_NO_SUCH_KEY)) return rc; rc = hashtable_setlatched(ht, key, val, &latch, false, NULL, NULL); return rc; } /* HashTable_Set */ /** * @brief Remove an entry from the hash table * * This function deletes an entry from the hash table. * * @param[in,out] ht The hashtable to be modified * @param[in] key The key corresponding to the entry to delete * @param[out] stored_key If non-NULL, a buffer descriptor * specifying the key as stored in the hash table * @param[out] stored_val If non-NULL, a buffer descriptor * specifying the key as stored in the hash table * * @retval HASHTABLE_SUCCESS on deletion */ static inline hash_error_t HashTable_Del(struct hash_table *ht, const struct gsh_buffdesc *key, struct gsh_buffdesc *stored_key, struct gsh_buffdesc *stored_val) { /* Structure to hold retained state */ struct hash_latch latch; /* Stored return code */ hash_error_t rc = HASHTABLE_SUCCESS; rc = hashtable_getlatch(ht, key, NULL, true, &latch); switch (rc) { case HASHTABLE_SUCCESS: hashtable_deletelatched(ht, key, &latch, stored_key, stored_val); /* Fall through to release latch */ case HASHTABLE_ERROR_NO_SUCH_KEY: hashtable_releaselatched(ht, &latch); /* Fall through to return */ default: return rc; } } /* These are the prototypes for large wrappers implementing more complex semantics on top of the primitives. */ hash_error_t hashtable_test_and_set(struct hash_table *, struct gsh_buffdesc *, struct gsh_buffdesc *, enum hash_set_how); hash_error_t hashtable_getref(struct hash_table *, struct gsh_buffdesc *, struct gsh_buffdesc *, void (*)(struct gsh_buffdesc *)); typedef void (*ht_for_each_cb_t)(struct rbt_node *pn, void *arg); void hashtable_for_each(struct hash_table *ht, ht_for_each_cb_t callback, void *arg); /** @} */ #endif /* HASHTABLE_H */ nfs-ganesha-6.5/src/include/idmapper.h000066400000000000000000000074461473756622300177740ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup idmapper ID Mapper * * The ID Mapper module provides mapping between numerical user and * group IDs and NFSv4 style owner and group strings. * * @{ */ /** * @file idmapper.c * @brief Id mapping functions */ #ifndef IDMAPPER_H #define IDMAPPER_H #include #include #include #include "gsh_rpc.h" #include "gsh_types.h" /* Arbitrary string buffer lengths */ #define PWENT_BEST_GUESS_LEN 1024 /* Maintain 64MB as the max buffer length for passwd and group entries */ #define PWENT_MAX_SIZE (64 * 1024 * 1024) #define GROUP_MAX_SIZE PWENT_MAX_SIZE /** * @brief Shared between idmapper.c and idmapper_cache.c. If you * aren't in idmapper.c, leave these symbols alone. * * @{ */ extern pthread_rwlock_t idmapper_user_lock; extern pthread_rwlock_t idmapper_group_lock; extern pthread_rwlock_t idmapper_negative_cache_user_lock; extern pthread_rwlock_t idmapper_negative_cache_group_lock; void idmapper_cache_init(void); void idmapper_cache_reap(void); bool idmapper_add_user(const struct gsh_buffdesc *, uid_t, const gid_t *, bool); bool idmapper_add_group(const struct gsh_buffdesc *, gid_t); bool idmapper_lookup_by_uname(const struct gsh_buffdesc *, uid_t *, const gid_t **, bool); bool idmapper_lookup_by_uid(const uid_t, const struct gsh_buffdesc **, const gid_t **); bool idmapper_lookup_by_gname(const struct gsh_buffdesc *, uid_t *); bool idmapper_lookup_by_gid(const gid_t, const struct gsh_buffdesc **); void idmapper_negative_cache_init(void); void idmapper_negative_cache_add_user_by_name(const struct gsh_buffdesc *); void idmapper_negative_cache_add_group_by_name(const struct gsh_buffdesc *); bool idmapper_negative_cache_lookup_user_by_name(const struct gsh_buffdesc *); bool idmapper_negative_cache_lookup_group_by_name(const struct gsh_buffdesc *); void idmapper_negative_cache_clear(void); void idmapper_negative_cache_destroy(void); void idmapper_negative_cache_reap(void); /** @} */ bool idmapper_init(void); void idmapper_clear_cache(void); void idmapper_destroy_cache(void); bool set_idmapping_status(bool status_enabled); bool xdr_encode_nfs4_owner(XDR *, uid_t); bool xdr_encode_nfs4_group(XDR *, gid_t); bool name2uid(const struct gsh_buffdesc *, uid_t *, const uid_t); bool name2gid(const struct gsh_buffdesc *, gid_t *, const gid_t); void winbind_stats_update(struct timespec *, struct timespec *); void gc_stats_update(struct timespec *, struct timespec *); void dns_stats_update(struct timespec *, struct timespec *); #ifdef _HAVE_GSSAPI #ifdef _MSPAC_SUPPORT bool principal2uid(char *, uid_t *, gid_t *, struct svc_rpc_gss_data *); #else bool principal2uid(char *, uid_t *, gid_t *); #endif #endif #ifdef USE_DBUS extern struct gsh_dbus_method cachemgr_show_idmapper; extern struct gsh_dbus_method auth_statistics; #endif #endif /* IDMAPPER_H */ /** @} */ nfs-ganesha-6.5/src/include/idmapper_monitoring.h000066400000000000000000000065611473756622300222360ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2024 Google LLC * Contributor : Dipit Grover dipit@google.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup idmapper ID Mapper * * The ID Mapper module provides mapping between numerical user and * group IDs and NFSv4 style owner and group strings. * * @{ */ /** * @file idmapper_monitoring.h * @brief ID mapping monitoring functions */ #ifndef IDMAPPER_MONITORING_H #define IDMAPPER_MONITORING_H #include #include #include "common_utils.h" typedef enum idmapping_utility { IDMAPPING_PWUTILS = 0, IDMAPPING_NFSIDMAP, IDMAPPING_WINBIND, IDMAPPING_UTILITY_COUNT, } idmapping_utility_t; typedef enum idmapping_op { IDMAPPING_UID_TO_UIDGID = 0, IDMAPPING_UID_TO_GROUPLIST, IDMAPPING_USERNAME_TO_UIDGID, IDMAPPING_USERNAME_TO_GROUPLIST, IDMAPPING_PRINCIPAL_TO_UIDGID, IDMAPPING_PRINCIPAL_TO_GROUPLIST, IDMAPPING_MSPAC_TO_SID, IDMAPPING_SID_TO_UIDGID, IDMAPPING_GID_TO_GROUP, IDMAPPING_GROUPNAME_TO_GROUP, IDMAPPING_OP_COUNT, } idmapping_op_t; typedef enum idmapping_cache { IDMAPPING_UID_TO_USER_CACHE = 0, IDMAPPING_USERNAME_TO_USER_CACHE, IDMAPPING_GSSPRINC_TO_USER_CACHE, IDMAPPING_GID_TO_GROUP_CACHE, IDMAPPING_GROUPNAME_TO_GROUP_CACHE, IDMAPPING_UID_TO_GROUPLIST_CACHE, IDMAPPING_USERNAME_TO_GROUPLIST_CACHE, IDMAPPING_CACHE_COUNT, } idmapping_cache_t; typedef enum idmapping_cache_entity { IDMAPPING_CACHE_ENTITY_USER = 0, IDMAPPING_CACHE_ENTITY_GROUP, IDMAPPING_CACHE_ENTITY_USER_GROUPS, IDMAPPING_CACHE_ENTITY_NEGATIVE_USER, IDMAPPING_CACHE_ENTITY_NEGATIVE_GROUP, IDMAPPING_CACHE_ENTITY_COUNT, } idmapping_cache_entity_t; /** * @brief Registers all idmapping metrics */ void idmapper_monitoring__init(void); /** * @brief Updates idmapping external latency metric */ void idmapper_monitoring__external_request(idmapping_op_t, idmapping_utility_t, bool is_success, const struct timespec *start, const struct timespec *end); /** * @brief Updates idmapping cache usage metric */ void idmapper_monitoring__cache_usage(idmapping_cache_t, bool is_cache_hit); /** * @brief Updates idmapping failure metric */ void idmapper_monitoring__failure(idmapping_op_t, idmapping_utility_t); /** * @brief Updates idmapping metric to count user groups */ void idmapper_monitoring__user_groups(int num_groups); /** * @brief Updates idmapping metric to record cached-duration of the * cache-evicted entries */ void idmapper_monitoring__evicted_cache_entity(idmapping_cache_entity_t, time_t cached_duration_in_sec); #endif /* IDMAPPER_MONITORING_H */ /** @} */ nfs-ganesha-6.5/src/include/log.h000066400000000000000000000434601473756622300167500ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * definition des codes d'error * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- * * */ #ifndef _LOGS_H #define _LOGS_H #include #include #include #include #include #include #ifndef LIBLOG_NO_THREAD #include #include #endif #include "gsh_intrinsic.h" #include "config_parsing.h" #include "display.h" #include "log_common.h" #ifdef __cplusplus extern "C" { #endif /* The maximum size of a log buffer */ #define LOG_BUFF_LEN 2048 /* previously at log_macros.h */ typedef void (*cleanup_function)(void); struct cleanup_list_element { struct cleanup_list_element *next; cleanup_function clean; }; /* Allocates buffer containing debug info to be printed. * Returned buffer needs to be freed. Returns number of * characters in size if size != NULL. */ char *get_debug_info(int *size); /* Function prototypes */ void SetNamePgm(const char *nom); void SetNameHost(const char *nom); void SetNameFunction(const char *nom); /* thread safe */ void SetClientIP(char *ip_str); void init_logging(const char *log_path, const int debug_level); int ReturnLevelAscii(const char *LevelInAscii); char *ReturnLevelInt(int level); /* previously at log_macros.h */ void RegisterCleanup(struct cleanup_list_element *clean); void Cleanup(void); void Fatal(void); /* This function is primarily for setting log level from config, it will * not override log level set from environment. */ void SetComponentLogLevel(log_components_t component, int level_to_set); void display_log_component_level(log_components_t component, const char *file, int line, const char *function, log_levels_t level, const char *format, va_list arguments); void DisplayLogComponentLevel(log_components_t component, const char *file, int line, const char *function, log_levels_t level, const char *format, ...) __attribute__((format(printf, 6, 7))); /* 6=format 7=params */ void LogMallocFailure(const char *file, int line, const char *function, const char *allocator); int read_log_config(config_file_t in_config, struct config_error_type *err_type); #ifdef USE_UNWIND void gsh_libunwind(void); #endif void gsh_backtrace(void); /* These functions display a timeval or timespec into the display buffer * in the same format used for logging timestamp. */ int display_timeval(struct display_buffer *dspbuf, struct timeval *tv); int display_timespec(struct display_buffer *dspbuf, struct timespec *ts); typedef enum log_type { SYSLOG = 0, FILELOG, STDERRLOG, STDOUTLOG, TESTLOG } log_type_t; typedef enum log_header_t { LH_NONE, LH_COMPONENT, LH_ALL, NB_LH_TYPES } log_header_t; /** * @brief Prototype for special log facility logging functions */ typedef int(lf_function_t)(log_header_t headers, void *priv, log_levels_t level, struct display_buffer *buffer, char *compstr, char *message); int create_log_facility(const char *name, lf_function_t *log_func, log_levels_t max_level, log_header_t header, void *private_data); void release_log_facility(const char *name); int enable_log_facility(const char *name); int disable_log_facility(const char *name); int set_log_destination(const char *name, char *dest); int set_log_level(const char *name, log_levels_t max_level); void set_const_log_str(void); struct log_component_info { const char *comp_name; /* component name */ const char *comp_str; /* shorter, more useful name */ }; extern log_levels_t *component_log_level; extern log_levels_t original_log_level; extern log_levels_t default_log_level; extern struct log_component_info LogComponents[COMPONENT_COUNT]; #define LogAlways(component, format, args...) \ DisplayLogComponentLevel(component, __FILE__, __LINE__, __func__, \ NIV_NULL, format, ##args) #define LogTest(format, args...) \ DisplayLogComponentLevel(COMPONENT_ALL, __FILE__, __LINE__, __func__, \ NIV_NULL, format, ##args) #define LogFatal(component, format, args...) \ do { \ DisplayLogComponentLevel(component, __FILE__, __LINE__, \ __func__, NIV_FATAL, format, ##args); \ abort(); \ } while (0) #define LogMajor(component, format, args...) \ do { \ if (likely(component_log_level[component] >= NIV_MAJ)) \ DisplayLogComponentLevel(component, __FILE__, \ __LINE__, __func__, NIV_MAJ, \ format, ##args); \ } while (0) #define LogCrit(component, format, args...) \ do { \ if (likely(component_log_level[component] >= NIV_CRIT)) \ DisplayLogComponentLevel(component, __FILE__, \ __LINE__, __func__, NIV_CRIT, \ format, ##args); \ } while (0) #define LogWarn(component, format, args...) \ do { \ if (likely(component_log_level[component] >= NIV_WARN)) \ DisplayLogComponentLevel(component, __FILE__, \ __LINE__, __func__, NIV_WARN, \ format, ##args); \ } while (0) #define LogWarnOnce(component, format, args...) \ do { \ static bool warned; \ if (unlikely(!warned) && \ likely(component_log_level[component] >= NIV_WARN)) { \ warned = true; \ DisplayLogComponentLevel(component, __FILE__, \ __LINE__, __func__, NIV_WARN, \ format, ##args); \ } \ } while (0) #define LogEvent(component, format, args...) \ do { \ if (likely(component_log_level[component] >= NIV_EVENT)) \ DisplayLogComponentLevel(component, __FILE__, \ __LINE__, __func__, \ NIV_EVENT, format, ##args); \ } while (0) #define LogInfo(component, format, args...) \ do { \ if (unlikely(component_log_level[component] >= NIV_INFO)) \ DisplayLogComponentLevel(component, __FILE__, \ __LINE__, __func__, NIV_INFO, \ format, ##args); \ } while (0) #define LogDebug(component, format, args...) \ do { \ if (unlikely(component_log_level[component] >= NIV_DEBUG)) \ DisplayLogComponentLevel(component, __FILE__, \ __LINE__, __func__, \ NIV_DEBUG, format, ##args); \ } while (0) #define LogMidDebug(component, format, args...) \ do { \ if (unlikely(component_log_level[component] >= NIV_MID_DEBUG)) \ DisplayLogComponentLevel(component, __FILE__, \ __LINE__, __func__, \ NIV_MID_DEBUG, format, \ ##args); \ } while (0) #define LogFullDebug(component, format, args...) \ do { \ if (unlikely(component_log_level[component] >= \ NIV_FULL_DEBUG)) \ DisplayLogComponentLevel(component, __FILE__, \ __LINE__, __func__, \ NIV_FULL_DEBUG, format, \ ##args); \ } while (0) #define LogFullDebugOpaque(component, format, buf_size, value, length, \ args...) \ do { \ if (unlikely(component_log_level[component] >= \ NIV_FULL_DEBUG)) { \ char buf[buf_size]; \ struct display_buffer dspbuf = { buf_size, buf, buf }; \ \ (void)display_opaque_value(&dspbuf, value, length); \ \ DisplayLogComponentLevel(component, __FILE__, \ __LINE__, __func__, \ NIV_FULL_DEBUG, format, buf, \ ##args); \ } \ } while (0) #define LogFullDebugBytes(component, format, buf_size, value, length, args...) \ do { \ if (unlikely(component_log_level[component] >= \ NIV_FULL_DEBUG)) { \ char buf[buf_size]; \ struct display_buffer dspbuf = { buf_size, buf, buf }; \ \ (void)display_opaque_bytes(&dspbuf, value, length); \ \ DisplayLogComponentLevel(component, __FILE__, \ __LINE__, __func__, \ NIV_FULL_DEBUG, format, buf, \ ##args); \ } \ } while (0) #define LogAtLevel(component, level, format, args...) \ do { \ if (unlikely(component_log_level[component] >= (level))) \ DisplayLogComponentLevel(component, __FILE__, \ __LINE__, __func__, level, \ format, ##args); \ } while (0) #define isLevel(component, level) \ (unlikely(component_log_level[component] >= level)) #define isInfo(component) (unlikely(component_log_level[component] >= NIV_INFO)) #define isDebug(component) \ (unlikely(component_log_level[component] >= NIV_DEBUG)) #define isMidDebug(component) \ (unlikely(component_log_level[component] >= NIV_MID_DEBUG)) #define isFullDebug(component) \ (unlikely(component_log_level[component] >= NIV_FULL_DEBUG)) /* Use either the first component, or if it is not at least at level, * use the second component. */ #define LogEventAlt(comp1, comp2, format, args...) \ do { \ if (unlikely(component_log_level[comp1] >= NIV_EVENT) || \ unlikely(component_log_level[comp2] >= NIV_EVENT)) { \ log_components_t component = \ component_log_level[comp1] >= NIV_EVENT ? \ comp1 : \ comp2; \ \ DisplayLogComponentLevel(component, __FILE__, \ __LINE__, __func__, \ NIV_EVENT, format, ##args); \ } \ } while (0) #define LogInfoAlt(comp1, comp2, format, args...) \ do { \ if (unlikely(component_log_level[comp1] >= NIV_INFO) || \ unlikely(component_log_level[comp2] >= NIV_INFO)) { \ log_components_t component = \ component_log_level[comp1] >= NIV_INFO ? \ comp1 : \ comp2; \ \ DisplayLogComponentLevel(component, __FILE__, \ __LINE__, __func__, NIV_INFO, \ format, ##args); \ } \ } while (0) #define LogDebugAlt(comp1, comp2, format, args...) \ do { \ if (unlikely(component_log_level[comp1] >= NIV_DEBUG) || \ unlikely(component_log_level[comp2] >= NIV_DEBUG)) { \ log_components_t component = \ component_log_level[comp1] >= NIV_DEBUG ? \ comp1 : \ comp2; \ \ DisplayLogComponentLevel(component, __FILE__, \ __LINE__, __func__, \ NIV_DEBUG, format, ##args); \ } \ } while (0) #define LogMidDebugAlt(comp1, comp2, format, args...) \ do { \ if (unlikely(component_log_level[comp1] >= NIV_MID_DEBUG) || \ unlikely(component_log_level[comp2] >= NIV_MID_DEBUG)) { \ log_components_t component = \ component_log_level[comp1] >= NIV_MID_DEBUG ? \ comp1 : \ comp2; \ \ DisplayLogComponentLevel(component, __FILE__, \ __LINE__, __func__, \ NIV_MID_DEBUG, format, \ ##args); \ } \ } while (0) #define LogFullDebugAlt(comp1, comp2, format, args...) \ do { \ if (unlikely(component_log_level[comp1] >= NIV_FULL_DEBUG) || \ unlikely(component_log_level[comp2] >= NIV_FULL_DEBUG)) { \ log_components_t component = \ component_log_level[comp1] >= NIV_FULL_DEBUG ? \ comp1 : \ comp2; \ \ DisplayLogComponentLevel(component, __FILE__, \ __LINE__, __func__, \ NIV_FULL_DEBUG, format, \ ##args); \ } \ } while (0) /* * Re-export component logging to TI-RPC internal logging */ void rpc_warnx(/* const */ char *fmt, ...); #ifdef USE_DBUS extern struct gsh_dbus_interface log_interface; #endif /* Rate limited logging */ struct ratelimit_state { pthread_mutex_t mutex; int interval; int burst; int printed; int missed; time_t begin; }; bool _ratelimit(struct ratelimit_state *rs, int *missed); #define RATELIMIT_STATE_INIT(interval_init, burst_init) \ { \ .mutex = PTHREAD_MUTEX_INITIALIZER, \ .interval = interval_init, \ .burst = burst_init, \ } #define DEFINE_RATELIMIT_STATE(name, interval_init, burst_init) \ struct ratelimit_state name = \ RATELIMIT_STATE_INIT(interval_init, burst_init) #define DEFAULT_RATELIMIT_INTERVAL 30 /* 30 seconds */ #define DEFAULT_RATELIMIT_BURST 2 #define LogEventLimited(comp, fmt, args...) \ ({ \ int missed; \ \ static DEFINE_RATELIMIT_STATE(_rs, DEFAULT_RATELIMIT_INTERVAL, \ DEFAULT_RATELIMIT_BURST); \ if (_ratelimit(&(_rs), &missed)) { \ if (missed) \ LogEvent(comp, "message missed %d times", \ missed); \ LogEvent(comp, fmt, ##args); \ } \ }) #define LogWarnLimited(comp, fmt, args...) \ ({ \ int missed; \ \ static DEFINE_RATELIMIT_STATE(_rs, DEFAULT_RATELIMIT_INTERVAL, \ DEFAULT_RATELIMIT_BURST); \ if (_ratelimit(&(_rs), &missed)) { \ if (missed) \ LogWarn(comp, "message missed %d times", \ missed); \ LogWarn(comp, fmt, ##args); \ } \ }) #ifdef __cplusplus } #endif #endif nfs-ganesha-6.5/src/include/log_common.h000066400000000000000000000044101473756622300203100ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * definition des codes d'error * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- * * */ #ifndef _LOG_COMMON_H #define _LOG_COMMON_H /* * Log message severity constants */ typedef enum log_levels { NIV_NULL, NIV_FATAL, NIV_MAJ, NIV_CRIT, NIV_WARN, NIV_EVENT, NIV_INFO, NIV_DEBUG, NIV_MID_DEBUG, NIV_FULL_DEBUG, NB_LOG_LEVEL } log_levels_t; /* * Log components used throughout the code. */ typedef enum log_components { COMPONENT_ALL = 0, /* Used for changing logging for all * components */ COMPONENT_LOG, /* Keep this first, some code depends on it * being the first component */ COMPONENT_MEM_ALLOC, COMPONENT_MEMLEAKS, COMPONENT_FSAL, COMPONENT_NFSPROTO, COMPONENT_NFS_V4, COMPONENT_EXPORT, COMPONENT_FILEHANDLE, COMPONENT_DISPATCH, COMPONENT_MDCACHE, COMPONENT_MDCACHE_LRU, COMPONENT_HASHTABLE, COMPONENT_HASHTABLE_CACHE, COMPONENT_DUPREQ, COMPONENT_INIT, COMPONENT_MAIN, COMPONENT_IDMAPPER, COMPONENT_NFS_READDIR, COMPONENT_NFS_V4_LOCK, COMPONENT_CONFIG, COMPONENT_CLIENTID, COMPONENT_SESSIONS, COMPONENT_PNFS, COMPONENT_RW_LOCK, COMPONENT_NLM, COMPONENT_TIRPC, COMPONENT_NFS_CB, COMPONENT_THREAD, COMPONENT_NFS_V4_ACL, COMPONENT_STATE, COMPONENT_9P, COMPONENT_9P_DISPATCH, COMPONENT_FSAL_UP, COMPONENT_DBUS, COMPONENT_NFS_MSK, COMPONENT_XPRT, COMPONENT_COUNT } log_components_t; #endif nfs-ganesha-6.5/src/include/mdcache.h000066400000000000000000000036661473756622300175570ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2015-2016 Red Hat, Inc. and/or its affiliates. * Author: Daniel Gryniewicz * * This program 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 3 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 * 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 * */ /** * @file mdcache_int.h * @brief MDCache main internal interface. * * Main data structures and profiles for MDCache */ #ifndef MDCACHE_H #define MDCACHE_H #include "config.h" #include "fsal_types.h" #include "fsal_up.h" /* Create an MDCACHE instance at the top of a stack */ fsal_status_t mdcache_fsal_create_export(struct fsal_module *fsal_hdl, void *parse_node, struct config_error_type *err_type, const struct fsal_up_vector *super_up_ops); /* Update an MDCACHE instance at the top of a stack */ fsal_status_t mdcache_fsal_update_export(struct fsal_module *sub_fsal, void *parse_node, struct config_error_type *err_type, struct fsal_export *original); /* Clean up on init failure */ void mdcache_export_uninit(void); /* Initialize the MDCACHE package. */ fsal_status_t mdcache_pkginit(void); /* Parse mdcache config */ int mdcache_set_param_from_conf(config_file_t parse_tree, struct config_error_type *err_type); void init_fds_limit(void); #endif /* MDCACHE_H */ nfs-ganesha-6.5/src/include/mount.h000066400000000000000000000053201473756622300173220ustar00rootroot00000000000000/* SPDX-License-Identifier: unknown license... */ /* * Please do not edit this file. * It was generated using rpcgen. */ #ifndef _MOUNT_H_RPCGEN #define _MOUNT_H_RPCGEN #include "gsh_refstr.h" #ifdef __cplusplus extern "C" { #endif #define MNTPATHLEN 1024 #define MNTNAMLEN 255 #define NB_AUTH_FLAVOR 10 /* RPCSEC_GSS flavor, for MOUNT */ #define MNT_RPC_GSS_NONE 390003 #define MNT_RPC_GSS_INTEGRITY 390004 #define MNT_RPC_GSS_PRIVACY 390005 enum mountstat3 { MNT3_OK = 0, MNT3ERR_PERM = 1, MNT3ERR_NOENT = 2, MNT3ERR_IO = 5, MNT3ERR_ACCES = 13, MNT3ERR_NOTDIR = 20, MNT3ERR_INVAL = 22, MNT3ERR_NAMETOOLONG = 63, MNT3ERR_NOTSUPP = 10004, MNT3ERR_SERVERFAULT = 10006 }; typedef enum mountstat3 mountstat3; /** * @todo * Danger Will Robinson!! * this struct is overlaid with nfs_fh3 in nfs23.h!! * This needs to be fixed */ typedef struct { u_int fhandle3_len; char *fhandle3_val; } fhandle3; typedef char *mnt3_dirpath; typedef char *mnt3_name; typedef struct groupnode *mnt3_groups; struct groupnode { mnt3_name gr_name; mnt3_groups gr_next; }; typedef struct groupnode groupnode; typedef struct exportnode *mnt3_exports; struct exportnode { struct gsh_refstr *ex_refdir; mnt3_dirpath ex_dir; mnt3_groups ex_groups; mnt3_exports ex_next; }; typedef struct exportnode exportnode; typedef struct mountbody *mountlist; struct mountbody { mnt3_name ml_hostname; mnt3_dirpath ml_directory; mountlist ml_next; }; typedef struct mountbody mountbody; struct mountres3_ok { fhandle3 fhandle; struct { u_int auth_flavors_len; int *auth_flavors_val; } auth_flavors; }; typedef struct mountres3_ok mountres3_ok; struct mountres3 { mountstat3 fhs_status; union { mountres3_ok mountinfo; } mountres3_u; }; typedef struct mountres3 mountres3; #define MOUNTPROG 100005 #define MOUNT_V1 1 #define MOUNTPROC2_NULL 0 #define MOUNTPROC2_MNT 1 #define MOUNTPROC2_DUMP 2 #define MOUNTPROC2_UMNT 3 #define MOUNTPROC2_UMNTALL 4 #define MOUNTPROC2_EXPORT 5 #define MOUNT_V3 3 #define MOUNTPROC3_NULL 0 #define MOUNTPROC3_MNT 1 #define MOUNTPROC3_DUMP 2 #define MOUNTPROC3_UMNT 3 #define MOUNTPROC3_UMNTALL 4 #define MOUNTPROC3_EXPORT 5 /* Number of mount v3 commands. */ #define MNT_V3_NB_COMMAND (MOUNTPROC3_EXPORT + 1) /* the xdr functions */ extern bool xdr_mountstat3(XDR *, mountstat3 *); extern bool xdr_fhandle3(XDR *, fhandle3 *); extern bool xdr_dirpath(XDR *, mnt3_dirpath *); extern bool xdr_name(XDR *, mnt3_name *); extern bool xdr_groups(XDR *, mnt3_groups *); extern bool xdr_exports(XDR *, mnt3_exports *); extern bool xdr_mountlist(XDR *, mountlist *); extern bool xdr_mountres3_ok(XDR *, mountres3_ok *); extern bool xdr_mountres3(XDR *, mountres3 *); #ifdef __cplusplus } #endif #endif /* !_MOUNT_H_RPCGEN */ nfs-ganesha-6.5/src/include/murmur3.h000066400000000000000000000014271473756622300175760ustar00rootroot00000000000000// SPDX-License-Identifier: unknown license... //----------------------------------------------------------------------------- // MurmurHash3 was written by Austin Appleby, and is placed in the // public domain. The author hereby disclaims copyright to this source // code. #ifndef _MURMURHASH3_H_ #define _MURMURHASH3_H_ #include //----------------------------------------------------------------------------- void MurmurHash3_x86_32(const void *key, int len, uint32_t seed, void *out); void MurmurHash3_x86_128(const void *key, int len, uint32_t seed, void *out); void MurmurHash3_x64_128(const void *key, int len, uint32_t seed, void *out); //----------------------------------------------------------------------------- #endif // _MURMURHASH3_H_ nfs-ganesha-6.5/src/include/netgroup_cache.h000066400000000000000000000021711473756622300211470ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * This program 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 3 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 * 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 * * --------------------------------------- */ /* Returns true if the given host is in the given netgroup. Uses * innetgr() to accomplish the task, and also caches such results. */ #ifndef NETGROUP_CACHE_H #define NETGROUP_CACHE_H void ng_cache_init(void); void ng_clear_cache(void); bool ng_innetgr(const char *group, const char *host); #endif nfs-ganesha-6.5/src/include/nfs23.h000066400000000000000000000623521473756622300171230ustar00rootroot00000000000000/* SPDX-License-Identifier: unknown license... */ /* * The content of this file is a mix of rpcgen-generated * and hand-edited program text. It is not automatically * generated by, e.g., build processes. * * This file is under version control. */ #ifndef _NFS23_H_RPCGEN #define _NFS23_H_RPCGEN #include "gsh_rpc.h" #include "extended_types.h" #include "mount.h" #include "fsal_types.h" #ifdef __cplusplus extern "C" { #endif #define NFS2_MAXDATA 8192 #define NFS2_MAXPATHLEN 1024 #define NFS2_MAXNAMLEN 255 #define NFS2_COOKIESIZE 4 #define NFS2_FHSIZE 32 #define NFS2_MNTPATHLEN 1024 #define NFS2_MNTNAMLEN 255 #define NFS3_FHSIZE 64 #define NFS3_COOKIEVERFSIZE 8 #define NFS3_CREATEVERFSIZE 8 #define NFS3_WRITEVERFSIZE 8 #define NFS2_MAX_FILESIZE (2147483647) /* 0x7fffffff */ typedef char fhandle2[NFS2_FHSIZE]; struct fhstatus2 { u_int status; union { fhandle2 directory; } fhstatus2_u; }; typedef struct fhstatus2 fhstatus2; typedef char *nfspath2; typedef char *filename2; typedef struct { u_int nfsdata2_len; char *nfsdata2_val; } nfsdata2; enum nfsstat2 { NFS_OK = 0, NFSERR_PERM = 1, NFSERR_NOENT = 2, NFSERR_IO = 5, NFSERR_NXIO = 6, NFSERR_ACCES = 13, NFSERR_EXIST = 17, NFSERR_NODEV = 19, NFSERR_NOTDIR = 20, NFSERR_ISDIR = 21, NFSERR_FBIG = 27, NFSERR_NOSPC = 28, NFSERR_ROFS = 30, NFSERR_NAMETOOLONG = 63, NFSERR_NOTEMPTY = 66, NFSERR_DQUOT = 69, NFSERR_STALE = 70, NFSERR_WFLUSH = 99 }; typedef char nfscookie2[NFS2_COOKIESIZE]; typedef uint64_t nfs3_uint64; typedef int64_t nfs3_int64; typedef uint32_t nfs3_uint32; typedef int32_t nfs3_int32; typedef char *filename3; typedef char *nfspath3; typedef nfs3_uint64 fileid3; typedef nfs3_uint64 cookie3; typedef char cookieverf3[8]; typedef char createverf3[8]; typedef char writeverf3[8]; typedef nfs3_uint32 uid3; typedef nfs3_uint32 gid3; typedef nfs3_uint64 size3; typedef nfs3_uint64 offset3; typedef nfs3_uint32 mode3; typedef nfs3_uint32 count3; enum nfsstat3 { NFS3_OK = 0, NFS3ERR_PERM = 1, NFS3ERR_NOENT = 2, NFS3ERR_IO = 5, NFS3ERR_NXIO = 6, NFS3ERR_ACCES = 13, NFS3ERR_EXIST = 17, NFS3ERR_XDEV = 18, NFS3ERR_NODEV = 19, NFS3ERR_NOTDIR = 20, NFS3ERR_ISDIR = 21, NFS3ERR_INVAL = 22, NFS3ERR_FBIG = 27, NFS3ERR_NOSPC = 28, NFS3ERR_ROFS = 30, NFS3ERR_MLINK = 31, NFS3ERR_NAMETOOLONG = 63, NFS3ERR_NOTEMPTY = 66, NFS3ERR_DQUOT = 69, NFS3ERR_STALE = 70, NFS3ERR_REMOTE = 71, NFS3ERR_BADHANDLE = 10001, NFS3ERR_NOT_SYNC = 10002, NFS3ERR_BAD_COOKIE = 10003, NFS3ERR_NOTSUPP = 10004, NFS3ERR_TOOSMALL = 10005, NFS3ERR_SERVERFAULT = 10006, NFS3ERR_BADTYPE = 10007, NFS3ERR_JUKEBOX = 10008 }; typedef enum nfsstat3 nfsstat3; enum ftype3 { NF3REG = 1, NF3DIR = 2, NF3BLK = 3, NF3CHR = 4, NF3LNK = 5, NF3SOCK = 6, NF3FIFO = 7 }; typedef enum ftype3 ftype3; struct specdata3 { nfs3_uint32 specdata1; nfs3_uint32 specdata2; }; typedef struct specdata3 specdata3; /** * @todo Danger Will Robinson!! * this struct is overlaid with fhandle3 in mount.h!! * This needs to be fixed. */ struct nfs_fh3 { struct { u_int data_len; char *data_val; } data; }; typedef struct nfs_fh3 nfs_fh3; struct nfstime3 { nfs3_uint32 tv_sec; nfs3_uint32 tv_nsec; }; typedef struct nfstime3 nfstime3; /* This struct documents what actually is serialized in the XDR stream * for attributes, and sizeof(struct fattr3) properly accounts for the * space used. */ struct fattr3_wire { ftype3 type; mode3 mode; nfs3_uint32 nlink; uid3 uid; gid3 gid; size3 size; size3 used; specdata3 rdev; nfs3_uint64 fsid; fileid3 fileid; nfstime3 atime; nfstime3 mtime; nfstime3 ctime; }; /* We use the fsal_types.h struct fsal_attrlist to avoid copying */ typedef struct fsal_attrlist fattr3; struct post_op_attr { bool_t attributes_follow; union { fattr3 attributes; } post_op_attr_u; }; typedef struct post_op_attr post_op_attr; struct wcc_attr { size3 size; nfstime3 mtime; nfstime3 ctime; }; typedef struct wcc_attr wcc_attr; struct pre_op_attr { bool_t attributes_follow; union { wcc_attr attributes; } pre_op_attr_u; }; typedef struct pre_op_attr pre_op_attr; struct wcc_data { pre_op_attr before; post_op_attr after; }; typedef struct wcc_data wcc_data; struct post_op_fh3 { bool_t handle_follows; union { nfs_fh3 handle; } post_op_fh3_u; }; typedef struct post_op_fh3 post_op_fh3; enum time_how { DONT_CHANGE = 0, SET_TO_SERVER_TIME = 1, SET_TO_CLIENT_TIME = 2 }; typedef enum time_how time_how; struct set_mode3 { bool_t set_it; union { mode3 mode; } set_mode3_u; }; typedef struct set_mode3 set_mode3; struct set_uid3 { bool_t set_it; union { uid3 uid; } set_uid3_u; }; typedef struct set_uid3 set_uid3; struct set_gid3 { bool_t set_it; union { gid3 gid; } set_gid3_u; }; typedef struct set_gid3 set_gid3; struct set_size3 { bool_t set_it; union { size3 size; } set_size3_u; }; typedef struct set_size3 set_size3; struct set_atime { time_how set_it; union { nfstime3 atime; } set_atime_u; }; typedef struct set_atime set_atime; struct set_mtime { time_how set_it; union { nfstime3 mtime; } set_mtime_u; }; typedef struct set_mtime set_mtime; struct sattr3 { set_mode3 mode; set_uid3 uid; set_gid3 gid; set_size3 size; set_atime atime; set_mtime mtime; }; typedef struct sattr3 sattr3; struct diropargs3 { nfs_fh3 dir; filename3 name; }; typedef struct diropargs3 diropargs3; struct GETATTR3args { nfs_fh3 object; }; typedef struct GETATTR3args GETATTR3args; struct GETATTR3resok { fattr3 obj_attributes; }; typedef struct GETATTR3resok GETATTR3resok; struct GETATTR3res { nfsstat3 status; union { GETATTR3resok resok; } GETATTR3res_u; }; typedef struct GETATTR3res GETATTR3res; struct sattrguard3 { bool_t check; union { nfstime3 obj_ctime; } sattrguard3_u; }; typedef struct sattrguard3 sattrguard3; struct SETATTR3args { nfs_fh3 object; sattr3 new_attributes; sattrguard3 guard; }; typedef struct SETATTR3args SETATTR3args; struct SETATTR3resok { wcc_data obj_wcc; }; typedef struct SETATTR3resok SETATTR3resok; struct SETATTR3resfail { wcc_data obj_wcc; }; typedef struct SETATTR3resfail SETATTR3resfail; struct SETATTR3res { nfsstat3 status; union { SETATTR3resok resok; SETATTR3resfail resfail; } SETATTR3res_u; }; typedef struct SETATTR3res SETATTR3res; struct LOOKUP3args { diropargs3 what; }; typedef struct LOOKUP3args LOOKUP3args; struct LOOKUP3resok { nfs_fh3 object; post_op_attr obj_attributes; post_op_attr dir_attributes; }; typedef struct LOOKUP3resok LOOKUP3resok; struct LOOKUP3resfail { post_op_attr dir_attributes; }; typedef struct LOOKUP3resfail LOOKUP3resfail; struct LOOKUP3res { nfsstat3 status; union { LOOKUP3resok resok; LOOKUP3resfail resfail; } LOOKUP3res_u; }; typedef struct LOOKUP3res LOOKUP3res; #define ACCESS3_READ 0x0001 #define ACCESS3_LOOKUP 0x0002 #define ACCESS3_MODIFY 0x0004 #define ACCESS3_EXTEND 0x0008 #define ACCESS3_DELETE 0x0010 #define ACCESS3_EXECUTE 0x0020 struct ACCESS3args { nfs_fh3 object; nfs3_uint32 access; }; typedef struct ACCESS3args ACCESS3args; struct ACCESS3resok { post_op_attr obj_attributes; nfs3_uint32 access; }; typedef struct ACCESS3resok ACCESS3resok; struct ACCESS3resfail { post_op_attr obj_attributes; }; typedef struct ACCESS3resfail ACCESS3resfail; struct ACCESS3res { nfsstat3 status; union { ACCESS3resok resok; ACCESS3resfail resfail; } ACCESS3res_u; }; typedef struct ACCESS3res ACCESS3res; struct READLINK3args { nfs_fh3 symlink; }; typedef struct READLINK3args READLINK3args; struct READLINK3resok { post_op_attr symlink_attributes; nfspath3 data; }; typedef struct READLINK3resok READLINK3resok; struct READLINK3resfail { post_op_attr symlink_attributes; }; typedef struct READLINK3resfail READLINK3resfail; struct READLINK3res { nfsstat3 status; union { READLINK3resok resok; READLINK3resfail resfail; } READLINK3res_u; }; typedef struct READLINK3res READLINK3res; struct READ3args { nfs_fh3 file; offset3 offset; count3 count; }; typedef struct READ3args READ3args; struct READ3resok { post_op_attr file_attributes; count3 count; bool_t eof; struct iovec iov0; io_data data; }; typedef struct READ3resok READ3resok; struct READ3resfail { post_op_attr file_attributes; }; typedef struct READ3resfail READ3resfail; struct READ3res { nfsstat3 status; union { READ3resok resok; READ3resfail resfail; } READ3res_u; }; typedef struct READ3res READ3res; enum stable_how { UNSTABLE = 0, DATA_SYNC = 1, FILE_SYNC = 2 }; typedef enum stable_how stable_how; struct WRITE3args { nfs_fh3 file; offset3 offset; count3 count; stable_how stable; io_data data; }; typedef struct WRITE3args WRITE3args; struct WRITE3resok { wcc_data file_wcc; count3 count; stable_how committed; writeverf3 verf; }; typedef struct WRITE3resok WRITE3resok; struct WRITE3resfail { wcc_data file_wcc; }; typedef struct WRITE3resfail WRITE3resfail; struct WRITE3res { nfsstat3 status; union { WRITE3resok resok; WRITE3resfail resfail; } WRITE3res_u; }; typedef struct WRITE3res WRITE3res; enum createmode3 { UNCHECKED = 0, GUARDED = 1, EXCLUSIVE = 2 }; typedef enum createmode3 createmode3; struct createhow3 { createmode3 mode; union { sattr3 obj_attributes; createverf3 verf; } createhow3_u; }; typedef struct createhow3 createhow3; struct CREATE3args { diropargs3 where; createhow3 how; }; typedef struct CREATE3args CREATE3args; struct CREATE3resok { post_op_fh3 obj; post_op_attr obj_attributes; wcc_data dir_wcc; }; typedef struct CREATE3resok CREATE3resok; struct CREATE3resfail { wcc_data dir_wcc; }; typedef struct CREATE3resfail CREATE3resfail; struct CREATE3res { nfsstat3 status; union { CREATE3resok resok; CREATE3resfail resfail; } CREATE3res_u; }; typedef struct CREATE3res CREATE3res; struct MKDIR3args { diropargs3 where; sattr3 attributes; }; typedef struct MKDIR3args MKDIR3args; struct MKDIR3resok { post_op_fh3 obj; post_op_attr obj_attributes; wcc_data dir_wcc; }; typedef struct MKDIR3resok MKDIR3resok; struct MKDIR3resfail { wcc_data dir_wcc; }; typedef struct MKDIR3resfail MKDIR3resfail; struct MKDIR3res { nfsstat3 status; union { MKDIR3resok resok; MKDIR3resfail resfail; } MKDIR3res_u; }; typedef struct MKDIR3res MKDIR3res; struct symlinkdata3 { sattr3 symlink_attributes; nfspath3 symlink_data; }; typedef struct symlinkdata3 symlinkdata3; struct SYMLINK3args { diropargs3 where; symlinkdata3 symlink; }; typedef struct SYMLINK3args SYMLINK3args; struct SYMLINK3resok { post_op_fh3 obj; post_op_attr obj_attributes; wcc_data dir_wcc; }; typedef struct SYMLINK3resok SYMLINK3resok; struct SYMLINK3resfail { wcc_data dir_wcc; }; typedef struct SYMLINK3resfail SYMLINK3resfail; struct SYMLINK3res { nfsstat3 status; union { SYMLINK3resok resok; SYMLINK3resfail resfail; } SYMLINK3res_u; }; typedef struct SYMLINK3res SYMLINK3res; struct devicedata3 { sattr3 dev_attributes; specdata3 spec; }; typedef struct devicedata3 devicedata3; struct mknoddata3 { ftype3 type; union { devicedata3 device; sattr3 pipe_attributes; } mknoddata3_u; }; typedef struct mknoddata3 mknoddata3; struct MKNOD3args { diropargs3 where; mknoddata3 what; }; typedef struct MKNOD3args MKNOD3args; struct MKNOD3resok { post_op_fh3 obj; post_op_attr obj_attributes; wcc_data dir_wcc; }; typedef struct MKNOD3resok MKNOD3resok; struct MKNOD3resfail { wcc_data dir_wcc; }; typedef struct MKNOD3resfail MKNOD3resfail; struct MKNOD3res { nfsstat3 status; union { MKNOD3resok resok; MKNOD3resfail resfail; } MKNOD3res_u; }; typedef struct MKNOD3res MKNOD3res; struct REMOVE3args { diropargs3 object; }; typedef struct REMOVE3args REMOVE3args; struct REMOVE3resok { wcc_data dir_wcc; }; typedef struct REMOVE3resok REMOVE3resok; struct REMOVE3resfail { wcc_data dir_wcc; }; typedef struct REMOVE3resfail REMOVE3resfail; struct REMOVE3res { nfsstat3 status; union { REMOVE3resok resok; REMOVE3resfail resfail; } REMOVE3res_u; }; typedef struct REMOVE3res REMOVE3res; struct RMDIR3args { diropargs3 object; }; typedef struct RMDIR3args RMDIR3args; struct RMDIR3resok { wcc_data dir_wcc; }; typedef struct RMDIR3resok RMDIR3resok; struct RMDIR3resfail { wcc_data dir_wcc; }; typedef struct RMDIR3resfail RMDIR3resfail; struct RMDIR3res { nfsstat3 status; union { RMDIR3resok resok; RMDIR3resfail resfail; } RMDIR3res_u; }; typedef struct RMDIR3res RMDIR3res; struct RENAME3args { diropargs3 from; diropargs3 to; }; typedef struct RENAME3args RENAME3args; struct RENAME3resok { wcc_data fromdir_wcc; wcc_data todir_wcc; }; typedef struct RENAME3resok RENAME3resok; struct RENAME3resfail { wcc_data fromdir_wcc; wcc_data todir_wcc; }; typedef struct RENAME3resfail RENAME3resfail; struct RENAME3res { nfsstat3 status; union { RENAME3resok resok; RENAME3resfail resfail; } RENAME3res_u; }; typedef struct RENAME3res RENAME3res; struct LINK3args { nfs_fh3 file; diropargs3 link; }; typedef struct LINK3args LINK3args; struct LINK3resok { post_op_attr file_attributes; wcc_data linkdir_wcc; }; typedef struct LINK3resok LINK3resok; struct LINK3resfail { post_op_attr file_attributes; wcc_data linkdir_wcc; }; typedef struct LINK3resfail LINK3resfail; struct LINK3res { nfsstat3 status; union { LINK3resok resok; LINK3resfail resfail; } LINK3res_u; }; typedef struct LINK3res LINK3res; struct READDIR3args { nfs_fh3 dir; cookie3 cookie; cookieverf3 cookieverf; count3 count; }; typedef struct READDIR3args READDIR3args; struct entry3 { fileid3 fileid; filename3 name; cookie3 cookie; struct entry3 *nextentry; }; typedef struct entry3 entry3; struct dirlist3 { entry3 *entries; xdr_uio *uio; bool_t eof; }; typedef struct dirlist3 dirlist3; struct READDIR3resok { post_op_attr dir_attributes; cookieverf3 cookieverf; dirlist3 reply; }; typedef struct READDIR3resok READDIR3resok; struct READDIR3resfail { post_op_attr dir_attributes; }; typedef struct READDIR3resfail READDIR3resfail; struct READDIR3res { nfsstat3 status; union { READDIR3resok resok; READDIR3resfail resfail; } READDIR3res_u; }; typedef struct READDIR3res READDIR3res; struct READDIRPLUS3args { nfs_fh3 dir; cookie3 cookie; cookieverf3 cookieverf; count3 dircount; count3 maxcount; }; typedef struct READDIRPLUS3args READDIRPLUS3args; struct entryplus3 { fileid3 fileid; filename3 name; cookie3 cookie; post_op_attr name_attributes; post_op_fh3 name_handle; struct entryplus3 *nextentry; }; typedef struct entryplus3 entryplus3; struct dirlistplus3 { entryplus3 *entries; xdr_uio *uio; bool_t eof; }; typedef struct dirlistplus3 dirlistplus3; struct READDIRPLUS3resok { post_op_attr dir_attributes; cookieverf3 cookieverf; dirlistplus3 reply; }; typedef struct READDIRPLUS3resok READDIRPLUS3resok; struct READDIRPLUS3resfail { post_op_attr dir_attributes; }; typedef struct READDIRPLUS3resfail READDIRPLUS3resfail; struct READDIRPLUS3res { nfsstat3 status; union { READDIRPLUS3resok resok; READDIRPLUS3resfail resfail; } READDIRPLUS3res_u; }; typedef struct READDIRPLUS3res READDIRPLUS3res; struct FSSTAT3args { nfs_fh3 fsroot; }; typedef struct FSSTAT3args FSSTAT3args; struct FSSTAT3resok { post_op_attr obj_attributes; size3 tbytes; size3 fbytes; size3 abytes; size3 tfiles; size3 ffiles; size3 afiles; nfs3_uint32 invarsec; }; typedef struct FSSTAT3resok FSSTAT3resok; struct FSSTAT3resfail { post_op_attr obj_attributes; }; typedef struct FSSTAT3resfail FSSTAT3resfail; struct FSSTAT3res { nfsstat3 status; union { FSSTAT3resok resok; FSSTAT3resfail resfail; } FSSTAT3res_u; }; typedef struct FSSTAT3res FSSTAT3res; #define FSF3_LINK 0x0001 #define FSF3_SYMLINK 0x0002 #define FSF3_HOMOGENEOUS 0x0008 #define FSF3_CANSETTIME 0x0010 struct FSINFO3args { nfs_fh3 fsroot; }; typedef struct FSINFO3args FSINFO3args; struct FSINFO3resok { post_op_attr obj_attributes; nfs3_uint32 rtmax; nfs3_uint32 rtpref; nfs3_uint32 rtmult; nfs3_uint32 wtmax; nfs3_uint32 wtpref; nfs3_uint32 wtmult; nfs3_uint32 dtpref; size3 maxfilesize; nfstime3 time_delta; nfs3_uint32 properties; }; typedef struct FSINFO3resok FSINFO3resok; struct FSINFO3resfail { post_op_attr obj_attributes; }; typedef struct FSINFO3resfail FSINFO3resfail; struct FSINFO3res { nfsstat3 status; union { FSINFO3resok resok; FSINFO3resfail resfail; } FSINFO3res_u; }; typedef struct FSINFO3res FSINFO3res; struct PATHCONF3args { nfs_fh3 object; }; typedef struct PATHCONF3args PATHCONF3args; struct PATHCONF3resok { post_op_attr obj_attributes; nfs3_uint32 linkmax; nfs3_uint32 name_max; bool_t no_trunc; bool_t chown_restricted; bool_t case_insensitive; bool_t case_preserving; }; typedef struct PATHCONF3resok PATHCONF3resok; struct PATHCONF3resfail { post_op_attr obj_attributes; }; typedef struct PATHCONF3resfail PATHCONF3resfail; struct PATHCONF3res { nfsstat3 status; union { PATHCONF3resok resok; PATHCONF3resfail resfail; } PATHCONF3res_u; }; typedef struct PATHCONF3res PATHCONF3res; struct COMMIT3args { nfs_fh3 file; offset3 offset; count3 count; }; typedef struct COMMIT3args COMMIT3args; struct COMMIT3resok { wcc_data file_wcc; writeverf3 verf; }; typedef struct COMMIT3resok COMMIT3resok; struct COMMIT3resfail { wcc_data file_wcc; }; typedef struct COMMIT3resfail COMMIT3resfail; struct COMMIT3res { nfsstat3 status; union { COMMIT3resok resok; COMMIT3resfail resfail; } COMMIT3res_u; }; typedef struct COMMIT3res COMMIT3res; #define NFS_PROGRAM 100003 #define NFS_V2 2 #define NFSPROC_NULL 0 #define NFSPROC_GETATTR 1 #define NFSPROC_SETATTR 2 #define NFSPROC_ROOT 3 #define NFSPROC_LOOKUP 4 #define NFSPROC_READLINK 5 #define NFSPROC_READ 6 #define NFSPROC_WRITECACHE 7 #define NFSPROC_WRITE 8 #define NFSPROC_CREATE 9 #define NFSPROC_REMOVE 10 #define NFSPROC_RENAME 11 #define NFSPROC_LINK 12 #define NFSPROC_SYMLINK 13 #define NFSPROC_MKDIR 14 #define NFSPROC_RMDIR 15 #define NFSPROC_READDIR 16 #define NFSPROC_STATFS 17 #define NFS_V3 3 #define NFSPROC3_NULL 0 #define NFSPROC3_GETATTR 1 #define NFSPROC3_SETATTR 2 #define NFSPROC3_LOOKUP 3 #define NFSPROC3_ACCESS 4 #define NFSPROC3_READLINK 5 #define NFSPROC3_READ 6 #define NFSPROC3_WRITE 7 #define NFSPROC3_CREATE 8 #define NFSPROC3_MKDIR 9 #define NFSPROC3_SYMLINK 10 #define NFSPROC3_MKNOD 11 #define NFSPROC3_REMOVE 12 #define NFSPROC3_RMDIR 13 #define NFSPROC3_RENAME 14 #define NFSPROC3_LINK 15 #define NFSPROC3_READDIR 16 #define NFSPROC3_READDIRPLUS 17 #define NFSPROC3_FSSTAT 18 #define NFSPROC3_FSINFO 19 #define NFSPROC3_PATHCONF 20 #define NFSPROC3_COMMIT 21 /* Number of NFSv3 commands. */ #define NFS_V3_NB_COMMAND (NFSPROC3_COMMIT + 1) /* the xdr functions */ extern bool xdr_nfs3_uint64(XDR *, nfs3_uint64 *); extern bool xdr_nfs3_int64(XDR *, nfs3_int64 *); extern bool xdr_nfs3_uint32(XDR *, nfs3_uint32 *); extern bool xdr_nfs3_int32(XDR *, nfs3_int32 *); extern bool xdr_filename3(XDR *, filename3 *); extern bool xdr_nfspath3(XDR *, nfspath3 *); extern bool xdr_fileid3(XDR *, fileid3 *); extern bool xdr_cookie3(XDR *, cookie3 *); extern bool xdr_fhandle3(XDR *, fhandle3 *); extern bool xdr_cookieverf3(XDR *, cookieverf3); extern bool xdr_createverf3(XDR *, createverf3); extern bool xdr_writeverf3(XDR *, writeverf3); extern bool xdr_uid3(XDR *, uid3 *); extern bool xdr_gid3(XDR *, gid3 *); extern bool xdr_size3(XDR *, size3 *); extern bool xdr_offset3(XDR *, offset3 *); extern bool xdr_mode3(XDR *, mode3 *); extern bool xdr_count3(XDR *, count3 *); extern bool xdr_nfsstat3(XDR *, nfsstat3 *); extern bool xdr_ftype3(XDR *, ftype3 *); extern bool xdr_specdata3(XDR *, specdata3 *); extern bool xdr_nfs_fh3(XDR *, nfs_fh3 *); extern bool xdr_nfstime3(XDR *, nfstime3 *); extern bool xdr_fattr3(XDR *, fattr3 *); extern bool xdr_post_op_attr(XDR *, post_op_attr *); extern bool xdr_wcc_attr(XDR *, wcc_attr *); extern bool xdr_pre_op_attr(XDR *, pre_op_attr *); extern bool xdr_wcc_data(XDR *, wcc_data *); extern bool xdr_post_op_fh3(XDR *, post_op_fh3 *); extern bool xdr_time_how(XDR *, time_how *); extern bool xdr_set_mode3(XDR *, set_mode3 *); extern bool xdr_set_uid3(XDR *, set_uid3 *); extern bool xdr_set_gid3(XDR *, set_gid3 *); extern bool xdr_set_size3(XDR *, set_size3 *); extern bool xdr_set_atime(XDR *, set_atime *); extern bool xdr_set_mtime(XDR *, set_mtime *); extern bool xdr_sattr3(XDR *, sattr3 *); extern bool xdr_diropargs3(XDR *, diropargs3 *); extern bool xdr_GETATTR3args(XDR *, GETATTR3args *); extern bool xdr_GETATTR3resok(XDR *, GETATTR3resok *); extern bool xdr_GETATTR3res(XDR *, GETATTR3res *); extern bool xdr_sattrguard3(XDR *, sattrguard3 *); extern bool xdr_SETATTR3args(XDR *, SETATTR3args *); extern bool xdr_SETATTR3resok(XDR *, SETATTR3resok *); extern bool xdr_SETATTR3resfail(XDR *, SETATTR3resfail *); extern bool xdr_SETATTR3res(XDR *, SETATTR3res *); extern bool xdr_LOOKUP3args(XDR *, LOOKUP3args *); extern bool xdr_LOOKUP3resok(XDR *, LOOKUP3resok *); extern bool xdr_LOOKUP3resfail(XDR *, LOOKUP3resfail *); extern bool xdr_LOOKUP3res(XDR *, LOOKUP3res *); extern bool xdr_ACCESS3args(XDR *, ACCESS3args *); extern bool xdr_ACCESS3resok(XDR *, ACCESS3resok *); extern bool xdr_ACCESS3resfail(XDR *, ACCESS3resfail *); extern bool xdr_ACCESS3res(XDR *, ACCESS3res *); extern bool xdr_READLINK3args(XDR *, READLINK3args *); extern bool xdr_READLINK3resok(XDR *, READLINK3resok *); extern bool xdr_READLINK3resfail(XDR *, READLINK3resfail *); extern bool xdr_READLINK3res(XDR *, READLINK3res *); extern bool xdr_READ3args(XDR *, READ3args *); extern bool xdr_READ3resok(XDR *, READ3resok *); extern bool xdr_READ3resfail(XDR *, READ3resfail *); extern bool xdr_READ3res(XDR *, READ3res *); extern bool xdr_stable_how(XDR *, stable_how *); extern bool xdr_WRITE3args(XDR *, WRITE3args *); extern bool xdr_WRITE3resok(XDR *, WRITE3resok *); extern bool xdr_WRITE3resfail(XDR *, WRITE3resfail *); extern bool xdr_WRITE3res(XDR *, WRITE3res *); extern bool xdr_createmode3(XDR *, createmode3 *); extern bool xdr_createhow3(XDR *, createhow3 *); extern bool xdr_CREATE3args(XDR *, CREATE3args *); extern bool xdr_CREATE3resok(XDR *, CREATE3resok *); extern bool xdr_CREATE3resfail(XDR *, CREATE3resfail *); extern bool xdr_CREATE3res(XDR *, CREATE3res *); extern bool xdr_MKDIR3args(XDR *, MKDIR3args *); extern bool xdr_MKDIR3resok(XDR *, MKDIR3resok *); extern bool xdr_MKDIR3resfail(XDR *, MKDIR3resfail *); extern bool xdr_MKDIR3res(XDR *, MKDIR3res *); extern bool xdr_symlinkdata3(XDR *, symlinkdata3 *); extern bool xdr_SYMLINK3args(XDR *, SYMLINK3args *); extern bool xdr_SYMLINK3resok(XDR *, SYMLINK3resok *); extern bool xdr_SYMLINK3resfail(XDR *, SYMLINK3resfail *); extern bool xdr_SYMLINK3res(XDR *, SYMLINK3res *); extern bool xdr_devicedata3(XDR *, devicedata3 *); extern bool xdr_mknoddata3(XDR *, mknoddata3 *); extern bool xdr_MKNOD3args(XDR *, MKNOD3args *); extern bool xdr_MKNOD3resok(XDR *, MKNOD3resok *); extern bool xdr_MKNOD3resfail(XDR *, MKNOD3resfail *); extern bool xdr_MKNOD3res(XDR *, MKNOD3res *); extern bool xdr_REMOVE3args(XDR *, REMOVE3args *); extern bool xdr_REMOVE3resok(XDR *, REMOVE3resok *); extern bool xdr_REMOVE3resfail(XDR *, REMOVE3resfail *); extern bool xdr_REMOVE3res(XDR *, REMOVE3res *); extern bool xdr_RMDIR3args(XDR *, RMDIR3args *); extern bool xdr_RMDIR3resok(XDR *, RMDIR3resok *); extern bool xdr_RMDIR3resfail(XDR *, RMDIR3resfail *); extern bool xdr_RMDIR3res(XDR *, RMDIR3res *); extern bool xdr_RENAME3args(XDR *, RENAME3args *); extern bool xdr_RENAME3resok(XDR *, RENAME3resok *); extern bool xdr_RENAME3resfail(XDR *, RENAME3resfail *); extern bool xdr_RENAME3res(XDR *, RENAME3res *); extern bool xdr_LINK3args(XDR *, LINK3args *); extern bool xdr_LINK3resok(XDR *, LINK3resok *); extern bool xdr_LINK3resfail(XDR *, LINK3resfail *); extern bool xdr_LINK3res(XDR *, LINK3res *); extern bool xdr_READDIR3args(XDR *, READDIR3args *); extern bool xdr_entry3(XDR *, entry3 *); extern void xdr_dirlist3_uio_release(struct xdr_uio *, u_int); extern bool xdr_encode_entry3(XDR *xdrs, entry3 *objp); extern bool xdr_dirlist3(XDR *, dirlist3 *); extern bool xdr_READDIR3resok(XDR *, READDIR3resok *); extern bool xdr_READDIR3resfail(XDR *, READDIR3resfail *); extern bool xdr_READDIR3res(XDR *, READDIR3res *); extern bool xdr_READDIRPLUS3args(XDR *, READDIRPLUS3args *); extern bool xdr_entryplus3(XDR *, entryplus3 *); extern void xdr_dirlistplus3_uio_release(struct xdr_uio *, u_int); extern bool xdr_encode_entryplus3(XDR *, entryplus3 *, const fattr3 *); extern bool xdr_dirlistplus3(XDR *, dirlistplus3 *); extern bool xdr_READDIRPLUS3resok(XDR *, READDIRPLUS3resok *); extern bool xdr_READDIRPLUS3resfail(XDR *, READDIRPLUS3resfail *); extern bool xdr_READDIRPLUS3res(XDR *, READDIRPLUS3res *); extern bool xdr_FSSTAT3args(XDR *, FSSTAT3args *); extern bool xdr_FSSTAT3resok(XDR *, FSSTAT3resok *); extern bool xdr_FSSTAT3resfail(XDR *, FSSTAT3resfail *); extern bool xdr_FSSTAT3res(XDR *, FSSTAT3res *); extern bool xdr_FSINFO3args(XDR *, FSINFO3args *); extern bool xdr_FSINFO3resok(XDR *, FSINFO3resok *); extern bool xdr_FSINFO3resfail(XDR *, FSINFO3resfail *); extern bool xdr_FSINFO3res(XDR *, FSINFO3res *); extern bool xdr_PATHCONF3args(XDR *, PATHCONF3args *); extern bool xdr_PATHCONF3resok(XDR *, PATHCONF3resok *); extern bool xdr_PATHCONF3resfail(XDR *, PATHCONF3resfail *); extern bool xdr_PATHCONF3res(XDR *, PATHCONF3res *); extern bool xdr_COMMIT3args(XDR *, COMMIT3args *); extern bool xdr_COMMIT3resok(XDR *, COMMIT3resok *); extern bool xdr_COMMIT3resfail(XDR *, COMMIT3resfail *); extern bool xdr_COMMIT3res(XDR *, COMMIT3res *); extern bool xdr_fhandle2(XDR *, fhandle2); extern bool xdr_fhstatus2(XDR *, fhstatus2 *); #ifdef __cplusplus } #endif #endif /* !_NFS23_H_RPCGEN */ nfs-ganesha-6.5/src/include/nfs4.h000066400000000000000000000040671473756622300170410ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2012 IETF Trust and the persons identified * as authors of the code. All rights reserved. * * Redistribution and use in source and binary forms, with * or without modification, are permitted provided that the * following conditions are met: * * o Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * o 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. * * o Neither the name of Internet Society, IETF or IETF * Trust, nor the names of specific contributors, may be * used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS * 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 COPYRIGHT OWNER 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 code was derived from RFCTBD10. * Please reproduce this note if possible. */ /* * Local RPC definitions, especially the GSS switch and * compensating definitions if we don't have GSS. */ #include "gsh_rpc.h" /* Now the NFS stuff we're looking for */ #include "nfsv41.h" #ifndef NFS4_MAX_DOMAIN_LEN #define NFS4_MAX_DOMAIN_LEN 512 #endif nfs-ganesha-6.5/src/include/nfs4_acls.h000066400000000000000000000033051473756622300200350ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright IBM Corporation, 2012 * Contributor: Frank Filz * * -------------------------- * * This program 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 3 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 * 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 * * */ #ifndef _NFS4_ACLS_H #define _NFS4_ACLS_H #include "fsal_types.h" /* Define the return value of ACL operation. */ typedef int fsal_acl_status_t; #define NFS_V4_ACL_SUCCESS 0 #define NFS_V4_ACL_ERROR 1 #define NFS_V4_ACL_EXISTS 2 #define NFS_V4_ACL_INTERNAL_ERROR 3 #define NFS_V4_ACL_UNAPPROPRIATED_KEY 4 #define NFS_V4_ACL_HASH_SET_ERROR 5 #define NFS_V4_ACL_INIT_ENTRY_FAILED 6 #define NFS_V4_ACL_NOT_FOUND 7 fsal_acl_t *nfs4_acl_alloc(void); fsal_ace_t *nfs4_ace_alloc(int nace); void nfs4_acl_free(fsal_acl_t *acl); void nfs4_ace_free(fsal_ace_t *pace); void nfs4_acl_entry_inc_ref(fsal_acl_t *pacl); fsal_acl_t *nfs4_acl_new_entry(fsal_acl_data_t *pacldata, fsal_acl_status_t *pstatus); void nfs4_acl_release_entry(fsal_acl_t *pacl); int nfs4_acls_init(void); #endif /* _NFS4_ACLS_H */ nfs-ganesha-6.5/src/include/nfs4_fs_locations.h000066400000000000000000000024301473756622300215740ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright 2018 VMware, Inc. * Contributor: Sriram Patil * * -------------------------- * * This program 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 3 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 * 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 * * */ #ifndef _NFS4_FS_LOCATIONS_H #define _NFS4_FS_LOCATIONS_H #include "fsal_types.h" void nfs4_fs_locations_free(fsal_fs_locations_t *); void nfs4_fs_locations_get_ref(fsal_fs_locations_t *); void nfs4_fs_locations_release(fsal_fs_locations_t *); fsal_fs_locations_t *nfs4_fs_locations_new(const char *fs_root, const char *rootpath, const unsigned int count); #endif nfs-ganesha-6.5/src/include/nfs_convert.h000066400000000000000000000037471473756622300205210ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_convert.h * @brief Prototypes for miscellaneous conversion routines. * * nfs_convert.h : Prototypes for miscellaneous conversion routines. * * */ #ifndef _NFS_CONVERT_H #define _NFS_CONVERT_H #include "nfs23.h" #include "nfs4.h" #include "mount.h" #include "fsal.h" #ifdef __cplusplus extern "C" { #endif char *nfsstat3_to_str(nfsstat3 code); char *nfsstat4_to_str(nfsstat4 code); char *nfstype3_to_str(ftype3 code); const char *auth_stat2str(enum auth_stat); #ifdef _USE_NFS3 const char *nfsproc3_to_str(int nfsproc3); #endif const char *nfsop4_to_str(int nfsop4); uint64_t nfs_htonl64(uint64_t); uint64_t nfs_ntohl64(uint64_t); /* Error conversion routines */ nfsstat4 nfs4_Errno_verbose(fsal_status_t, const char *); #define nfs4_Errno_status(e) nfs4_Errno_verbose(e, __func__) #ifdef _USE_NFS3 nfsstat3 nfs3_Errno_verbose(fsal_status_t, const char *); #define nfs3_Errno_status(e) nfs3_Errno_verbose(e, __func__) #endif #ifdef __cplusplus } #endif #endif /* _NFS_CONVERT_H */ nfs-ganesha-6.5/src/include/nfs_core.h000066400000000000000000000113521473756622300177600ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_core.h * @brief Prototypes for the different threads in the nfs core */ #ifndef NFS_CORE_H #define NFS_CORE_H #include #include #include #include #include #include "sal_data.h" #include "gsh_config.h" #ifdef _USE_9P #include "9p.h" #endif #ifdef _ERROR_INJECTION #include "err_inject.h" #endif /* Delegation client cache limits */ #define DELEG_SPACE_LIMIT_FILESZ 102400 /* just 100K, revisit? */ #define DELEG_SPACE_LIMIT_BLOCKS 200 #define XATTR_BUFFERSIZE 4096 extern char *nfs_host_name; extern char cid_server_owner[MAXNAMLEN + 1]; /* max hostname length */ extern char *cid_server_scope; /* * Bind protocol family, pending a richer interface model. */ #define P_FAMILY AF_INET6 typedef struct __nfs4_compound { union { int type; struct { CB_COMPOUND4args args; CB_COMPOUND4res res; } v4; } v_u; } nfs4_compound_t; typedef struct _rpc_call rpc_call_t; typedef void (*rpc_call_func)(rpc_call_t *call); #ifdef _HAVE_GSSAPI extern gss_OID_desc krb5oid; #endif /* _HAVE_GSSAPI */ struct _rpc_call { struct clnt_req call_req; rpc_call_channel_t *chan; rpc_call_func call_hook; void *call_arg; void *call_user_data[2]; nfs4_compound_t cbt; uint32_t states; uint32_t flags; }; /* in nfs_init.c */ struct _nfs_health { uint64_t enqueued_reqs; uint64_t dequeued_reqs; }; enum evchan { UDP_UREG_CHAN, /*< Put UDP on a dedicated channel */ TCP_UREG_CHAN, /*< Accepts new TCP connections */ #ifdef _USE_NFS_RDMA RDMA_UREG_CHAN, /*< Accepts new RDMA connections */ #endif EVCHAN_SIZE }; extern struct _nfs_health nfs_health_; bool nfs_health(void); /* ServerEpoch is ServerBootTime unless overridden by -E command line option */ extern struct timespec nfs_ServerBootTime; extern time_t nfs_ServerEpoch; extern verifier4 NFS4_write_verifier; /*< NFS V4 write verifier */ extern writeverf3 NFS3_write_verifier; /*< NFS V3 write verifier */ extern char *nfs_config_path; extern char *nfs_pidfile_path; /* * Thread entry functions */ #ifdef _USE_9P void *_9p_dispatcher_thread(void *arg); void _9p_tcp_process_request(struct _9p_request_data *req9p); int _9p_process_buffer(struct _9p_request_data *req9p, char *replydata, u32 *poutlen); int _9p_worker_init(void); int _9p_worker_shutdown(void); void DispatchWork9P(struct _9p_request_data *req); #endif #ifdef _USE_9P_RDMA void *_9p_rdma_dispatcher_thread(void *arg); void _9p_rdma_process_request(struct _9p_request_data *req9p); void _9p_rdma_cleanup_conn(msk_trans_t *trans); #endif /* in nfs_rpc_dispatcher_thread.c */ void Clean_RPC(void); void nfs_Init_svc(void); void nfs_rpc_dispatch_stop(void); uint32_t nfs_get_evchannel_id(enum evchan); /* Config parsing routines */ extern config_file_t nfs_config_struct; extern struct config_block nfs_core; extern struct config_block nfs_ip_name; #ifdef _HAVE_GSSAPI extern struct config_block krb5_param; #endif extern struct config_block version4_param; extern struct config_block directory_services_param; /* in nfs_admin_thread.c */ extern bool admin_shutdown; void nfs_Init_admin_thread(void); void *admin_thread(void *UnusedArg); void admin_halt(void); /* Tools */ int compare_state_id(struct gsh_buffdesc *buff1, struct gsh_buffdesc *buff2); /* used in DBUS-api diagnostic functions (e.g., serialize sessionid) */ int b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize); int b64_pton(char const *src, u_char *target, size_t targsize); unsigned int nfs_core_select_worker_queue(unsigned int avoid_index); int nfs_Init_ip_name(void); void nfs_rpc_destroy_chan(rpc_call_channel_t *chan); void nfs_rpc_destroy_chan_no_lock(rpc_call_channel_t *chan); int reaper_init(void); void reaper_wake(void); int reaper_shutdown(void); #endif /* !NFS_CORE_H */ nfs-ganesha-6.5/src/include/nfs_creds.h000066400000000000000000000035321473756622300201310ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_creds.h * @brief Prototypes for the RPC credentials used in NFS. * * nfs_creds.h : Prototypes for the RPC credentials used in NFS. * * */ #ifndef _NFS_CREDS_H #define _NFS_CREDS_H #include #include #include #include "fsal_types.h" #include "sal_data.h" void init_credentials(void); void clean_credentials(void); void squash_setattr(struct fsal_attrlist *attr); nfsstat4 nfs_req_creds(struct svc_req *req); nfsstat4 nfs4_export_check_access(struct svc_req *req); fsal_status_t nfs_access_op(struct fsal_obj_handle *hdl, uint32_t requested_access, uint32_t *granted_access, uint32_t *supported_access); bool nfs_compare_clientcred(nfs_client_cred_t *cred1, nfs_client_cred_t *cred2); int nfs_rpc_req2client_cred(struct svc_req *req, nfs_client_cred_t *pcred); #endif /* _NFS_CREDS_H */ nfs-ganesha-6.5/src/include/nfs_dupreq.h000066400000000000000000000121221473756622300203240ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_dupreq.h * @brief Prototypes for duplicate request cache */ #ifndef NFS_DUPREQ_H #define NFS_DUPREQ_H #include #include #include "nfs_core.h" #include "nfs23.h" #include "nfs4.h" #include "nfs_core.h" #include #include enum drc_type { DRC_TCP_V4, /*< safe to use an XID-based, per-connection DRC */ DRC_TCP_V3, /*< a shared, checksummed DRC per address */ DRC_UDP_V234 /*< UDP is strongly discouraged in RFC 3530bis */ }; #define DRC_FLAG_RECYCLE 0x1 typedef struct drc { enum drc_type type; struct rbtree_x xt; /* Define the tail queue */ TAILQ_HEAD(drc_tailq, dupreq_entry) dupreq_q; pthread_mutex_t drc_mtx; uint32_t npart; uint32_t cachesz; uint32_t size; uint32_t maxsize; uint32_t hiwat; uint32_t flags; uint32_t refcnt; /* call path refs */ uint32_t retwnd; union { struct { sockaddr_t addr; struct opr_rbtree_node recycle_k; TAILQ_ENTRY(drc) recycle_q; /* XXX drc */ time_t recycle_time; uint64_t hk; /* hash key */ } tcp; } d_u; } drc_t; /* * The old code would drop any duplicate request while the original * request was still in progress, assuming that the response would be * sent. Unfortunately, if a TCP connection is broken while the request * is in progress, sending the response fails. The client never retries * and gets stuck. * * Now when this occurs, we queue up the request and suspend it (utlizing * the async infrastructure). When the original request processing * completes and calls nfs_dupreq_finish() we track if there was an error * sending the response. If so, we don't mark the DRC entry as complete * and instead resume the first retry to attempt to send the response. * * That resumed retry will call nfs_dupreq_finish() after it tries to * send the response, so if there is a queue of retries, there are more * opportunities to re-send a failed response. * * The same retry logic is followed when nfs_dupreq_delete() is called * if there are again queued duplicate requests, however, those retries * instead are re-submitted for a new attempt to process. This logic * occurs when there is an NFS_DROP result from a retryable error or * an auth error. * * Once the request is successfully completed, any additional queued * requests are dropped. * * We limit the queue to 3 duplicates. That should be more than enough * to get through an issue like this unless the server has severely * stalled out on the original request. */ #define DUPREQ_MAX_DUPES 3 struct dupreq_entry { struct opr_rbtree_node rbt_k; /* Define the tail queue */ TAILQ_ENTRY(dupreq_entry) fifo_q; /* Queued duplicate requests waiting for request completion. Limited * to DUPREQ_MAX_DUPES. */ TAILQ_HEAD(dupes, nfs_request) dupes; pthread_mutex_t dre_mtx; struct { sockaddr_t addr; struct { uint32_t rq_xid; uint64_t checksum; } tcp; uint32_t rq_prog; uint32_t rq_vers; uint32_t rq_proc; } hin; uint64_t hk; /* hash key */ bool complete; uint32_t refcnt; nfs_res_t *res; enum nfs_req_result rc; /* Count of duplicate requests fielded. This coundts ALL duplicate * requests whether queued while the request is completing and those * that arrive after completion. */ int dupe_cnt; }; typedef struct dupreq_entry dupreq_entry_t; extern pool_t *nfs_res_pool; static inline nfs_res_t *alloc_nfs_res(void) { return pool_alloc(nfs_res_pool); } static inline void free_nfs_res(nfs_res_t *res) { pool_free(nfs_res_pool, res); } static inline enum nfs_req_result nfs_dupreq_reply_rc(nfs_request_t *reqnfs) { dupreq_entry_t *dv = reqnfs->svc.rq_u1; return dv->rc; } typedef enum dupreq_status { DUPREQ_SUCCESS = 0, DUPREQ_BEING_PROCESSED, DUPREQ_EXISTS, DUPREQ_DROP, } dupreq_status_t; void dupreq2_pkginit(void); drc_t *drc_get_tcp_drc(struct svc_req *); void drc_release_tcp_drc(drc_t *); void nfs_dupreq_put_drc(drc_t *drc); dupreq_status_t nfs_dupreq_start(nfs_request_t *); void nfs_dupreq_finish(nfs_request_t *, enum nfs_req_result); void nfs_dupreq_delete(nfs_request_t *, enum nfs_req_result); void nfs_dupreq_rele(nfs_request_t *); #endif /* NFS_DUPREQ_H */ nfs-ganesha-6.5/src/include/nfs_exports.h000066400000000000000000000220451473756622300205350ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_exports.h * @brief Prototypes for what's related to export list management. * @note not called by other header files. * * This file contains prototypes and data structures for related to * export list management and the NFSv4 compound. */ #ifndef NFS_EXPORTS_H #define NFS_EXPORTS_H #include #include #ifdef _HAVE_GSSAPI #include #include #endif #include "config_parsing.h" #include "client_mgr.h" #include "export_mgr.h" #include "fsal_types.h" #include "log.h" /* * Export List structure */ #define EXPORT_KEY_SIZE 8 #define ANON_UID -2 #define ANON_GID -2 #define EXPORT_LINESIZE 1024 #define INPUT_SIZE 1024 struct global_export_perms { struct export_perms def; struct export_perms conf; struct glist_head clients; }; extern pthread_rwlock_t export_opt_lock; extern struct global_export_perms export_opt; #define GSS_DEFINE_LEN_TEMP 255 struct exportlist_client_entry { struct base_client_entry client_entry; struct export_perms client_perms; /*< Available mount options */ }; /* Constants for export options masks */ #define EXPORT_OPTION_FSID_SET 0x00000001 /* Set if Filesystem_id is set */ #define EXPORT_OPTION_USE_COOKIE_VERIFIER 0x00000002 /* Use cookie verifier */ /** Controls whether a directory's dirent cache is trusted for negative results. */ #define EXPORT_OPTION_TRUST_READIR_NEGATIVE_CACHE 0x00000008 #define EXPORT_OPTION_MAXREAD_SET 0x00000010 /* Set if MaxRead was specified */ #define EXPORT_OPTION_MAXWRITE_SET \ 0x00000020 /* Set if MaxWrite was specified */ #define EXPORT_OPTION_PREFREAD_SET \ 0x00000040 /* Set if PrefRead was specified */ #define EXPORT_OPTION_PREFWRITE_SET \ 0x00000080 /* Set if PrefWrite was specified */ #define EXPORT_OPTION_SECLABEL_SET \ 0x00000100 /* Set if export supports v4.2 security labels */ /* Constants for export permissions masks */ #define EXPORT_OPTION_ROOT 0 /*< Allow root access as root uid */ #define EXPORT_OPTION_ROOT_ID_SQUASH \ 0x00000001 /*< Disallow root access as root uid but preserve alt_groups */ #define EXPORT_OPTION_ROOT_SQUASH \ 0x00000002 /*< Disallow root access as root uid */ #define EXPORT_OPTION_ALL_ANONYMOUS \ 0x00000004 /*< all users are squashed to anonymous */ #define EXPORT_OPTION_SQUASH_TYPES \ (EXPORT_OPTION_ROOT_SQUASH | EXPORT_OPTION_ROOT_ID_SQUASH | \ EXPORT_OPTION_ALL_ANONYMOUS) /*< All squash types */ #define EXPORT_OPTION_ANON_UID_SET \ 0x00000008 /*< Indicates Anon_uid was set */ #define EXPORT_OPTION_ANON_GID_SET \ 0x00000010 /*< Indicates Anon_gid was set */ #define EXPORT_OPTION_READ_ACCESS \ 0x00000020 /*< R_Access= option specified */ #define EXPORT_OPTION_WRITE_ACCESS \ 0x00000040 /*< RW_Access= option specified */ #define EXPORT_OPTION_RW_ACCESS \ (EXPORT_OPTION_READ_ACCESS | EXPORT_OPTION_WRITE_ACCESS) #define EXPORT_OPTION_MD_READ_ACCESS \ 0x00000080 /*< MDONLY_RO_Access= option specified */ #define EXPORT_OPTION_MD_WRITE_ACCESS \ 0x00000100 /*< MDONLY_Access= option specified */ #define EXPORT_OPTION_MD_ACCESS \ (EXPORT_OPTION_MD_WRITE_ACCESS | EXPORT_OPTION_MD_READ_ACCESS) #define EXPORT_OPTION_MODIFY_ACCESS \ (EXPORT_OPTION_WRITE_ACCESS | EXPORT_OPTION_MD_WRITE_ACCESS) #define EXPORT_OPTION_ACCESS_MASK \ (EXPORT_OPTION_READ_ACCESS | EXPORT_OPTION_WRITE_ACCESS | \ EXPORT_OPTION_MD_WRITE_ACCESS | EXPORT_OPTION_MD_READ_ACCESS) #define EXPORT_OPTION_NO_ACCESS 0 /*< Access_Type = None */ #define EXPORT_OPTION_PRIVILEGED_PORT \ 0x00000200 /*< Clients use only privileged port */ #define EXPORT_OPTION_COMMIT 0x00000400 /*< NFS Commit writes */ #define EXPORT_OPTION_DISABLE_ACL 0x00000800 /*< ACL is disabled */ /* @todo BUGAZOMEU : Mettre au carre les flags des flavors */ #define EXPORT_OPTION_AUTH_NONE \ 0x00001000 /*< Auth None authentication supported */ #define EXPORT_OPTION_AUTH_UNIX \ 0x00002000 /*< Auth Unix authentication supported */ #define EXPORT_OPTION_RPCSEC_GSS_NONE \ 0x00004000 /*< RPCSEC_GSS_NONE supported */ #define EXPORT_OPTION_RPCSEC_GSS_INTG \ 0x00008000 /*< RPCSEC_GSS INTEGRITY supported */ #define EXPORT_OPTION_RPCSEC_GSS_PRIV \ 0x00010000 /*< RPCSEC_GSS PRIVACY supported */ #define EXPORT_OPTION_AUTH_TYPES \ (EXPORT_OPTION_AUTH_NONE | EXPORT_OPTION_AUTH_UNIX | \ EXPORT_OPTION_RPCSEC_GSS_NONE | EXPORT_OPTION_RPCSEC_GSS_INTG | \ EXPORT_OPTION_RPCSEC_GSS_PRIV) #define EXPORT_OPTION_AUTH_DEFAULTS \ (EXPORT_OPTION_AUTH_NONE | EXPORT_OPTION_AUTH_UNIX) #define EXPORT_OPTION_EXPIRE_SET 0x00080000 /*< Inode expire was set */ #define EXPORT_DEFAULT_CACHE_EXPIRY 60 /*< Default cache expiry */ /* Protocol flags */ #define EXPORT_OPTION_NFSV3 0x00100000 /*< NFSv3 operations are supported */ #define EXPORT_OPTION_NFSV4 0x00200000 /*< NFSv4 operations are supported */ #define EXPORT_OPTION_9P 0x00400000 /*< 9P operations are supported */ #define EXPORT_OPTION_UDP 0x01000000 /*< UDP protocol is supported */ #define EXPORT_OPTION_TCP 0x02000000 /*< TCP protocol is supported */ #define EXPORT_OPTION_RDMA 0x04000000 /*< RDMA protocol is supported */ #define EXPORT_OPTION_PROTOCOLS \ (EXPORT_OPTION_NFSV3 | EXPORT_OPTION_NFSV4 | EXPORT_OPTION_9P) #define EXPORT_OPTION_PROTO_DEFAULTS (EXPORT_OPTION_NFSV3 | EXPORT_OPTION_NFSV4) #define EXPORT_OPTION_TRANSPORTS \ (EXPORT_OPTION_UDP | EXPORT_OPTION_TCP | EXPORT_OPTION_RDMA) #define EXPORT_OPTION_XPORT_DEFAULTS (EXPORT_OPTION_UDP | EXPORT_OPTION_TCP) #define EXPORT_OPTION_READ_DELEG 0x10000000 /*< Enable read delegations */ #define EXPORT_OPTION_WRITE_DELEG 0x20000000 /*< Using write delegations */ #define EXPORT_OPTION_DELEGATIONS \ (EXPORT_OPTION_READ_DELEG | EXPORT_OPTION_WRITE_DELEG) #define EXPORT_OPTION_NO_DELEGATIONS 0 #define EXPORT_OPTION_MANAGE_GIDS \ 0x40000000 /*< Do not trust altgrp in AUTH_SYS creds */ #define EXPORT_OPTION_NO_READDIR_PLUS 0x80000000 /*< Disallow readdir plus */ #define EXPORT_OPTION_PERM_UNUSED 0x08860000 #define READ_ACCESS_CHECK_POLICY_PRE (1 << 0) #define READ_ACCESS_CHECK_POLICY_POST (1 << 1) #define READ_ACCESS_CHECK_POLICY_ALL \ (READ_ACCESS_CHECK_POLICY_PRE | READ_ACCESS_CHECK_POLICY_POST) /* Export list related functions */ uid_t get_anonymous_uid(void); gid_t get_anonymous_gid(void); void export_check_access(void); bool export_check_security(struct svc_req *req); int init_export_root(struct gsh_export *exp); fsal_status_t nfs_export_get_root_entry(struct gsh_export *exp, struct fsal_obj_handle **obj); void release_export(struct gsh_export *exp, bool config); /* XXX */ /*void kill_export_root_entry(cache_entry_t *entry);*/ /*void kill_export_junction_entry(cache_entry_t *entry);*/ int ReadExports(config_file_t in_config, struct config_error_type *err_type); int reread_exports(config_file_t in_config, struct config_error_type *err_type); void free_export_resources(struct gsh_export *exp, bool config); void exports_pkginit(void); struct log_exports_parms { log_levels_t level; const char *file; int line; const char *func; const char *tag; bool clients; }; bool log_an_export(struct gsh_export *exp, void *state); uint32_t export_check_options(struct gsh_export *exp); static inline bool export_can_be_mounted(struct gsh_export *exp) { uint32_t options = export_check_options(exp); return (options & EXPORT_OPTION_NFSV4) != 0 && exp->cfg_pseudopath != NULL && exp->export_id != 0 && exp->cfg_pseudopath[1] != '\0'; } #define LOG_EXPORT(level, tag, exp, clients) \ do { \ if (isLevel(COMPONENT_EXPORT, level)) { \ struct log_exports_parms lep = { level, __FILE__, \ __LINE__, __func__, \ tag, clients }; \ (void)log_an_export(exp, &lep); \ } \ } while (0) #endif /* !NFS_EXPORTS_H */ nfs-ganesha-6.5/src/include/nfs_fh.h000066400000000000000000000047041473756622300174300ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * Eshel Marc eshel@us.ibm.com * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_fh.h * @brief Prototypes for the file handle in v3 and v4 */ #ifndef NFS_FH_H #define NFS_FH_H #include /* * Structure of the filehandle * these structures must be naturally aligned. The xdr buffer from/to which * they come/go are 4 byte aligned. */ #define GANESHA_FH_VERSION 0x43 #define FILE_HANDLE_V4_FLAG_DS 0x01 /*< handle for a DS */ #define FH_FSAL_BIG_ENDIAN 0x40 /*< FSAL FH is big endian */ /** * @brief An NFSv3 handle * * This may be up to 64 bytes long, aligned on 32 bits */ typedef struct file_handle_v3 { uint8_t fhversion; /*< Set to GANESHA_FH_VERSION */ uint8_t fhflags1; /*< To replace things like ds_flag */ uint16_t exportid; /*< Must be correlated to exportlist_t::id */ uint8_t fs_len; /*< Actual length of opaque handle */ uint8_t fsopaque[]; /*< Persistent part of FSAL handle, <= 59 bytes */ } file_handle_v3_t; /** * @brief An NFSv4 filehandle * * This may be up to 128 bytes, aligned on 32 bits. */ typedef struct __attribute__((__packed__)) file_handle_v4 { uint8_t fhversion; /*< Set to 0x41 to separate from Linux knfsd */ uint8_t fhflags1; /*< To replace things like ds_flag */ union { uint16_t exports; /*< FSAL exports, export_by_id */ uint16_t servers; /*< FSAL servers, server_by_id */ } id; uint8_t fs_len; /*< Length of opaque handle */ uint8_t fsopaque[]; /*< FSAL handle */ } file_handle_v4_t; #endif /* NFS_FH_H */ nfs-ganesha-6.5/src/include/nfs_file_handle.h000066400000000000000000000275241473756622300212720ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_file_handle.h * @brief Prototypes for the file handle in v3 and v4 */ #ifndef NFS_FILE_HANDLE_H #define NFS_FILE_HANDLE_H #include #include #include "log.h" #include "display.h" #include "sal_data.h" #include "export_mgr.h" #include "nfs_fh.h" #include "nfs_proto_functions.h" /** * @brief Get the actual size of a v3 handle based on the sized fsopaque * * @return The filehandle size */ static inline size_t nfs3_sizeof_handle(struct file_handle_v3 *hdl) { int hsize; int aligned_hsize; hsize = offsetof(struct file_handle_v3, fsopaque) + hdl->fs_len; /* correct packet's fh length so it's divisible by 4 to trick dNFS into working. This is essentially sending the padding. */ aligned_hsize = roundup(hsize, 4); if (aligned_hsize <= NFS3_FHSIZE) hsize = aligned_hsize; return hsize; } /** * * @brief Allocates a buffer to be used for storing a NFSv4 filehandle. * * Allocates a buffer to be used for storing a NFSv3 filehandle. * * @param fh [INOUT] the filehandle to manage. * */ static inline void nfs3_AllocateFH(nfs_fh3 *fh) { /* Allocating the filehandle in memory */ fh->data.data_len = NFS3_FHSIZE; fh->data.data_val = (char *)gsh_calloc(1, NFS3_FHSIZE); } static inline void nfs3_freeFH(nfs_fh3 *fh) { fh->data.data_len = 0; gsh_free(fh->data.data_val); fh->data.data_val = NULL; } /** * * @brief Allocates a buffer to be used for storing a NFSv4 filehandle. * * Allocates a buffer to be used for storing a NFSv4 filehandle. * * @param fh [INOUT] the filehandle to manage. * */ static inline void nfs4_AllocateFH(nfs_fh4 *fh) { /* Allocating the filehandle in memory */ fh->nfs_fh4_len = NFS4_FHSIZE; fh->nfs_fh4_val = (char *)gsh_calloc(1, NFS4_FHSIZE); } static inline void nfs4_freeFH(nfs_fh4 *fh) { fh->nfs_fh4_len = 0; gsh_free(fh->nfs_fh4_val); fh->nfs_fh4_val = NULL; } /** * @brief Get the actual size of a v4 handle based on the sized fsopaque * * @return The filehandle size */ static inline size_t nfs4_sizeof_handle(struct file_handle_v4 *hdl) { return offsetof(struct file_handle_v4, fsopaque) + hdl->fs_len; } static inline size_t nfs4_sizeof_handle_padding(struct file_handle_v4 *hdl) { int hsize; int aligned_hsize; hsize = offsetof(struct file_handle_v4, fsopaque) + hdl->fs_len; /* align on 4 byte block boundary */ aligned_hsize = roundup(hsize, 4); if (aligned_hsize <= NFS4_FHSIZE) hsize = aligned_hsize; return hsize; } static inline bool valid_Fh4_Len(nfs_fh4 *fh, file_handle_v4_t *pfile_handle) { if (!nfs_param.core_param.enable_v3_fh_for_v4) { return fh->nfs_fh4_len == nfs4_sizeof_handle(pfile_handle); } else { return ((fh->nfs_fh4_len == nfs4_sizeof_handle(pfile_handle)) || (fh->nfs_fh4_len == nfs4_sizeof_handle_padding(pfile_handle))); } } #define LEN_FH_STR OPAQUE_BYTES_SIZE(NFS4_FHSIZE) /* File handle translation utility */ #ifdef _USE_NFS3 struct fsal_obj_handle *nfs3_FhandleToCache(nfs_fh3 *, nfsstat3 *, int *); #endif bool nfs4_FSALToFhandle(bool allocate, nfs_fh4 *fh4, const struct fsal_obj_handle *fsalhandle, struct gsh_export *exp); bool nfs3_FSALToFhandle(bool allocate, nfs_fh3 *fh3, const struct fsal_obj_handle *fsalhandle, struct gsh_export *exp); /* nfs3 validation */ int nfs3_Is_Fh_Invalid(nfs_fh3 *); /** * * nfs3_FhandleToExportId * * This routine extracts the export id from the file handle NFSv3 * * @param pfh3 [IN] file handle to manage. * * @return the export id. * */ static inline int nfs3_FhandleToExportId(nfs_fh3 *pfh3) { file_handle_v3_t *pfile_handle; if (nfs3_Is_Fh_Invalid(pfh3) != NFS4_OK) return -1; /* Badly formed argument */ pfile_handle = (file_handle_v3_t *)(pfh3->data.data_val); /*exportid is in network byte order in nfs_fh3*/ return ntohs(pfile_handle->exportid); } /* nfs3_FhandleToExportId */ static inline int nlm4_FhandleToExportId(netobj *pfh3) { nfs_fh3 fh3; if (pfh3 == NULL) return nfs3_FhandleToExportId(NULL); fh3.data.data_val = pfh3->n_bytes; fh3.data.data_len = pfh3->n_len; return nfs3_FhandleToExportId(&fh3); } /** * * @brief Test if an NFS v4 file handle is empty. * * This routine is used to test if a fh is empty (contains no data). * * @param pfh [IN] file handle to test. * * @return NFS4_OK if successful, NFS4ERR_NOFILEHANDLE is fh is empty. * */ static inline int nfs4_Is_Fh_Empty(nfs_fh4 *pfh) { if (pfh == NULL) { LogMajor(COMPONENT_FILEHANDLE, "INVALID HANDLE: pfh=NULL"); return NFS4ERR_NOFILEHANDLE; } if (pfh->nfs_fh4_len == 0) { LogInfo(COMPONENT_FILEHANDLE, "INVALID HANDLE: empty"); return NFS4ERR_NOFILEHANDLE; } return NFS4_OK; } /* nfs4_Is_Fh_Empty */ /* NFSv4 specific FH related functions */ int nfs4_Is_Fh_Invalid(nfs_fh4 *); int nfs4_Is_Fh_DSHandle(nfs_fh4 *); nfsstat4 nfs4_sanity_check_FH(compound_data_t *data, object_file_type_t required_type, bool ds_allowed); nfsstat4 nfs4_sanity_check_saved_FH(compound_data_t *data, int required_type, bool ds_allowed); /* File handle print function (mostly used for debugging) */ #define LogNFS3_Operation(component, req, fh, format, args...) \ do { \ if (unlikely(component_log_level[component] >= NIV_DEBUG)) { \ char str[LEN_FH_STR]; \ struct display_buffer dspbuf = { sizeof(str), str, \ str }; \ \ display_opaque_bytes(&dspbuf, (fh)->data.data_val, \ (fh)->data.data_len); \ \ DisplayLogComponentLevel( \ component, __FILE__, __LINE__, __func__, \ NIV_DEBUG, \ "REQUEST PROCESSING: Calling %s " \ "File Handle V3: Len=%u %s" format, \ nfs3_func_desc[req->rq_msg.cb_proc].funcname, \ (fh)->data.data_len, str, ##args); \ } \ } while (0) #define LogNFS3_Operation2(component, req, fh1, name1, fh2, name2) \ do { \ if (unlikely(component_log_level[component] >= NIV_DEBUG)) { \ char str1[LEN_FH_STR]; \ struct display_buffer dspbuf1 = { sizeof(str1), str1, \ str1 }; \ char str2[LEN_FH_STR]; \ struct display_buffer dspbuf2 = { sizeof(str2), str2, \ str2 }; \ \ display_opaque_bytes(&dspbuf1, (fh1)->data.data_val, \ (fh1)->data.data_len); \ display_opaque_bytes(&dspbuf2, (fh2)->data.data_val, \ (fh2)->data.data_len); \ \ DisplayLogComponentLevel( \ component, __FILE__, __LINE__, __func__, \ NIV_DEBUG, \ "REQUEST PROCESSING: Calling %s " \ "File Handle V3: Len=%u %s%s%s to " \ "File Handle V3: Len=%u %s name %s", \ nfs3_func_desc[req->rq_msg.cb_proc].funcname, \ (fh1)->data.data_len, str1, \ name1 ? " name " : "", name1 ? name1 : "", \ (fh2)->data.data_len, str2, name2); \ } \ } while (0) #define LogHandleNFS4(label, fh4) \ do { \ if (isFullDebug(COMPONENT_NFS_V4)) { \ char str[LEN_FH_STR]; \ struct display_buffer dspbuf = { sizeof(str), str, \ str }; \ \ display_opaque_bytes(&dspbuf, (fh4)->nfs_fh4_val, \ (fh4)->nfs_fh4_len); \ \ LogFullDebug(COMPONENT_NFS_V4, \ "%sFile Handle V4: Len=%u %s", label, \ (fh4)->nfs_fh4_len, str); \ } \ } while (0) #define LogCompoundFH(data) \ do { \ if (isFullDebug(COMPONENT_NFS_V4)) { \ char str[LEN_FH_STR]; \ struct display_buffer dspbuf = { sizeof(str), str, \ str }; \ \ display_opaque_bytes(&dspbuf, \ &data->currentFH.nfs_fh4_val, \ data->currentFH.nfs_fh4_len); \ \ LogFullDebug(COMPONENT_NFS_V4, \ "Current FH Len=%u %s", \ data->currentFH.nfs_fh4_len, str); \ \ display_reset_buffer(&dspbuf); \ \ display_opaque_bytes(&dspbuf, \ &data->savedFH.nfs_fh4_val, \ data->savedFH.nfs_fh4_len); \ \ LogFullDebug(COMPONENT_NFS_V4, \ "Saved FH Len=%u %s", \ data->savedFH.nfs_fh4_len, str); \ } \ } while (0) #define LogNFSACL_Operation(component, req, fh, format, args...) \ do { \ if (unlikely(component_log_level[component] >= NIV_DEBUG)) { \ char str[LEN_FH_STR]; \ struct display_buffer dspbuf = { sizeof(str), str, \ str }; \ \ display_opaque_bytes(&dspbuf, (fh)->data.data_val, \ (fh)->data.data_len); \ \ DisplayLogComponentLevel( \ component, __FILE__, __LINE__, __func__, \ NIV_DEBUG, \ "REQUEST PROCESSING: Calling %s " \ "File Handle V3: Len=%u %s" format, \ nfsacl_func_desc[req->rq_msg.cb_proc].funcname, \ (fh)->data.data_len, str, ##args); \ } \ } while (0) #endif /* NFS_FILE_HANDLE_H */ nfs-ganesha-6.5/src/include/nfs_init.h000066400000000000000000000074261473756622300200020ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_init.h * @brief NFSd initialization prototypes. */ #ifndef NFS_INIT_H #define NFS_INIT_H #include "log.h" #include "nfs_core.h" #include "gsh_rpc.h" typedef struct __nfs_start_info { int dump_default_config; int lw_mark_trigger; bool drop_caps; } nfs_start_info_t; struct nfs_init { pthread_mutex_t init_mutex; pthread_cond_t init_cond; bool init_complete; }; extern struct nfs_init nfs_init; extern pthread_t gsh_dbus_thrid; void nfs_prereq_init_mutexes(void); void nfs_init_init(void); void nfs_init_cleanup(void); void nfs_init_complete(void); void nfs_init_wait(void); int nfs_init_wait_timeout(int timeout); /** * nfs_prereq_init: * Initialize NFSd prerequisites: memory management, logging, ... */ void nfs_prereq_init(const char *program_name, const char *host_name, int debug_level, const char *log_path, bool dump_trace, unsigned long stack_size); void nfs_prereq_destroy(void); /** * nfs_set_param_from_conf: * Load parameters from config file. */ int nfs_set_param_from_conf(config_file_t config_struct, nfs_start_info_t *p_start_info, struct config_error_type *err_type); /** * Initialization that needs config file parse but must be done * before any services actually start (exports, net sockets...) */ int init_server_pkgs(void); /** * Initialise NFSv4 specific parameters. */ int nfsv4_init_params(void); /** * nfs_start: * start NFS service */ void nfs_start(nfs_start_info_t *p_start_info); /** * check for usable malloc implementation */ static inline void nfs_check_malloc(void) { /* Check malloc(0) - Ganesha assumes malloc(0) returns non-NULL pointer. * Note we use malloc and calloc directly here and not gsh_malloc and * gsh_calloc because we don't want those functions to abort(), we * want to log a descriptive message. */ void *p; p = malloc(0); if (p == NULL) LogFatal( COMPONENT_MAIN, "Ganesha's assumption that malloc(0) returns a non-NULL pointer is not true, Ganesha can not work with the memory allocator in use. Aborting."); free(p); p = calloc(0, 0); if (p == NULL) LogFatal( COMPONENT_MAIN, "Ganesha's assumption that calloc(0, 0) returns a non-NULL pointer is not true, Ganesha can not work with the memory allocator in use. Aborting."); free(p); } /* in nfs_worker_thread.c */ enum xprt_stat nfs_rpc_valid_NFS(struct svc_req *); #ifdef _USE_NLM enum xprt_stat nfs_rpc_valid_NLM(struct svc_req *); #endif #ifdef _USE_NFS3 enum xprt_stat nfs_rpc_valid_MNT(struct svc_req *); #endif #ifdef _USE_RQUOTA enum xprt_stat nfs_rpc_valid_RQUOTA(struct svc_req *); #endif #ifdef USE_NFSACL3 enum xprt_stat nfs_rpc_valid_NFSACL(struct svc_req *); #endif #ifdef _USE_NFS_RDMA enum xprt_stat nfs_rpc_valid_NFS_RDMA(struct svc_req *); #endif #endif /* !NFS_INIT_H */ nfs-ganesha-6.5/src/include/nfs_ip_stats.h000066400000000000000000000033131473756622300206540ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * @file common_utils.h * @brief Common tools for printing, parsing, .... * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ #ifndef _NFS_IP_STATS_H #define _NFS_IP_STATS_H #include #include #include "gsh_rpc.h" #include /* for having MAXHOSTNAMELEN */ #include "hashtable.h" /* IP/name cache error */ #define IP_NAME_SUCCESS 0 #define IP_NAME_INSERT_MALLOC_ERROR 1 #define IP_NAME_NOT_FOUND 2 #define IP_NAME_PREALLOC_SIZE 200 /* NFS IPaddr cache entry structure */ typedef struct nfs_ip_name__ { time_t timestamp; char hostname[]; } nfs_ip_name_t; int nfs_ip_name_get(sockaddr_t *ipaddr, char *hostname, size_t size); int nfs_ip_name_add(sockaddr_t *ipaddr, char *hostname, size_t size); int nfs_ip_name_remove(sockaddr_t *ipaddr); #endif nfs-ganesha-6.5/src/include/nfs_lib.h000066400000000000000000000024741473756622300176030ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_lib.h * @brief an minimal library interface to the ganesha server */ #ifndef NFS_LIB_H #define NFS_LIB_H extern char *nfs_config_path; extern int nfs_libmain(const char *config_path, const char *log_path, const int debug_level); extern bool reread_config(void); #endif /* !NFS_LIB_H */ /** @} */ nfs-ganesha-6.5/src/include/nfs_metrics.h000066400000000000000000000044111473756622300204740ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2024 Google LLC * Contributor : Yoni Couriel yonic@google.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_metrics.h * @brief NFS metrics functions */ #ifndef NFS_METRICS_H #define NFS_METRICS_H #include "monitoring.h" #include "nfsv41.h" #include "nfs23.h" void nfs_metrics__nfs4_op_completed(nfs_opnum4, nfsstat4, nsecs_elapsed_t); void nfs_metrics__gss_request_dropped(void); void nfs_metrics__nfs4_compound_completed(nfsstat4, nsecs_elapsed_t, int num_ops); void nfs_metrics__rpc_received(void); void nfs_metrics__rpc_completed(void); void nfs_metrics__rpcs_in_flight(int64_t value); void nfs_metrics__init(void); /* * The following two functions generate the following dynamic metrics, * exported both as total and per export. * * - Total request count. * - Total request count by success / failure status. * - Total bytes sent. * - Total bytes received. * - Request size in bytes as histogram. * - Response size in bytes as histogram. * - Latency in ms as histogram. */ void nfs_metrics__nfs3_request(const uint32_t proc, const nsecs_elapsed_t request_time, const nfsstat3 status, const export_id_t export_id, const char *client_ip); void nfs_metrics__nfs4_request(const uint32_t op, const nsecs_elapsed_t request_time, const nfsstat4 status, const export_id_t export_id, const char *client_ip); #endif /* !NFS_METRICS_H */ nfs-ganesha-6.5/src/include/nfs_proto_data.h000066400000000000000000000273351473756622300211740ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright (C) 2014 CohortFS, LLC. * Author: William Allen Simpson * * This program 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 3 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 * 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 */ /** * @defgroup NFS compound data * @{ */ /** * @file nfs_proto_data.h * @author William Allen Simpson * @date Wed Nov 19 14:11:19 2014 * * @brief NFS compound data, function arguments, and return status * * @note called only from sal_data.h */ #ifndef NFS_PROTO_DATA_H #define NFS_PROTO_DATA_H #include "fsal_api.h" #include "rquota.h" /* * mount was autogenerated, and requires several headers to compile; * rather than scattered through the code, consolidated in nfs23.h. * (Used by the mass compound unions here.) */ #include "nfs23.h" #include "nfs4.h" #include "nlm4.h" #include "nfsacl.h" /* ------------------------------ Typedefs and structs----------------------- */ typedef union nfs_arg__ { GETATTR3args arg_getattr3; SETATTR3args arg_setattr3; LOOKUP3args arg_lookup3; ACCESS3args arg_access3; READLINK3args arg_readlink3; READ3args arg_read3; WRITE3args arg_write3; CREATE3args arg_create3; MKDIR3args arg_mkdir3; SYMLINK3args arg_symlink3; MKNOD3args arg_mknod3; REMOVE3args arg_remove3; RMDIR3args arg_rmdir3; RENAME3args arg_rename3; LINK3args arg_link3; READDIR3args arg_readdir3; READDIRPLUS3args arg_readdirplus3; FSSTAT3args arg_fsstat3; FSINFO3args arg_fsinfo3; PATHCONF3args arg_pathconf3; COMMIT3args arg_commit3; COMPOUND4args arg_compound4; /* mnt */ mnt3_dirpath arg_mnt; /* nlm */ nlm4_testargs arg_nlm4_test; nlm4_lockargs arg_nlm4_lock; nlm4_cancargs arg_nlm4_cancel; nlm4_shareargs arg_nlm4_share; nlm4_unlockargs arg_nlm4_unlock; nlm4_sm_notifyargs arg_nlm4_sm_notify; nlm4_free_allargs arg_nlm4_free_allargs; nlm4_res arg_nlm4_res; /* Rquota */ getquota_args arg_rquota_getquota; getquota_args arg_rquota_getactivequota; setquota_args arg_rquota_setquota; setquota_args arg_rquota_setactivequota; /* Ext Rquota */ ext_getquota_args arg_ext_rquota_getquota; ext_getquota_args arg_ext_rquota_getactivequota; ext_setquota_args arg_ext_rquota_setquota; ext_setquota_args arg_ext_rquota_setactivequota; /* NFSACL */ getaclargs arg_getacl; setaclargs arg_setacl; } nfs_arg_t; struct COMPOUND4res_extended { COMPOUND4res res_compound4; int32_t res_refcnt; }; typedef union nfs_res__ { GETATTR3res res_getattr3; SETATTR3res res_setattr3; LOOKUP3res res_lookup3; ACCESS3res res_access3; READLINK3res res_readlink3; READ3res res_read3; WRITE3res res_write3; CREATE3res res_create3; MKDIR3res res_mkdir3; SYMLINK3res res_symlink3; MKNOD3res res_mknod3; REMOVE3res res_remove3; RMDIR3res res_rmdir3; RENAME3res res_rename3; LINK3res res_link3; READDIR3res res_readdir3; READDIRPLUS3res res_readdirplus3; FSSTAT3res res_fsstat3; FSINFO3res res_fsinfo3; PATHCONF3res res_pathconf3; COMMIT3res res_commit3; struct COMPOUND4res_extended *res_compound4_extended; /* mount */ fhstatus2 res_mnt1; mnt3_exports res_mntexport; mountres3 res_mnt3; mountlist res_dump; /* nlm4 */ nlm4_testres res_nlm4test; nlm4_res res_nlm4; nlm4_shareres res_nlm4share; /* Rquota */ getquota_rslt res_rquota_getquota; getquota_rslt res_rquota_getactivequota; setquota_rslt res_rquota_setquota; setquota_rslt res_rquota_setactivequota; /* Ext Rquota */ getquota_rslt res_ext_rquota_getquota; getquota_rslt res_ext_rquota_getactivequota; setquota_rslt res_ext_rquota_setquota; setquota_rslt res_ext_rquota_setactivequota; /* NFSACL */ getaclres res_getacl; setaclres res_setacl; } nfs_res_t; /* flags related to the behaviour of the requests * (to be stored in the dispatch behaviour field) */ #define NOTHING_SPECIAL \ 0x0000 /* Nothing to be done for this kind of request */ #define MAKES_WRITE \ 0x0001 /* The function modifyes the FSAL (not permitted for RO FS) */ #define NEEDS_CRED \ 0x0002 /* A credential is needed for this operation */ #define CAN_BE_DUP \ 0x0004 /* Handling of dup request can be done for this request */ #define SUPPORTS_GSS \ 0x0008 /* Request may be authenticated by RPCSEC_GSS */ #define MAKES_IO \ 0x0010 /* Request may do I/O (not allowed on MD ONLY exports */ enum nfs_req_result { NFS_REQ_OK, NFS_REQ_DROP, NFS_REQ_ERROR, NFS_REQ_REPLAY, NFS_REQ_ASYNC_WAIT, NFS_REQ_XPRT_DIED, NFS_REQ_AUTH_ERR, }; /* Async process synchronizations flags to be used with * atomic_postset_uint32_t_bits */ #define ASYNC_PROC_DONE 1 #define ASYNC_PROC_EXIT 2 typedef int (*nfs_protocol_function_t)(nfs_arg_t *, struct svc_req *, nfs_res_t *); typedef struct compound_data compound_data_t; typedef enum nfs_req_result (*nfs4_function_t)(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); typedef int (*nfsremote_protocol_function_t)(CLIENT *, nfs_arg_t *, nfs_res_t *); typedef void (*nfs_protocol_free_t)(nfs_res_t *); typedef struct nfs_function_desc__ { nfs_protocol_function_t service_function; nfs_protocol_free_t free_function; xdrproc_t xdr_decode_func; xdrproc_t xdr_encode_func; char *funcname; unsigned int dispatch_behaviour; } nfs_function_desc_t; typedef struct nfs_request { struct svc_req svc; struct nfs_request_lookahead lookahead; struct req_op_context op_context; nfs_arg_t arg_nfs; nfs_res_t *res_nfs; const nfs_function_desc_t *funcdesc; void *proc_data; /** This request may be queued up pending completion of the request * this is a dupreq of. */ TAILQ_ENTRY(nfs_request) dupes; } nfs_request_t; enum rpc_chan_type { RPC_CHAN_V40, RPC_CHAN_V41 }; typedef struct rpc_call_channel { enum rpc_chan_type type; pthread_mutex_t chan_mtx; uint32_t states; union { nfs_client_id_t *clientid; nfs41_session_t *session; } source; time_t last_called; CLIENT *clnt; AUTH *auth; #ifdef _HAVE_GSSAPI struct rpc_gss_sec gss_sec; #endif /* _HAVE_GSSAPI */ } rpc_call_channel_t; /** * @todo Matt: this is automatically redundant, but in fact upstream * TI-RPC is not up-to-date with RFC 5665, will fix (Matt) * * @copyright 2012-2017, Linux Box Corp */ enum rfc_5665_nc_type { _NC_ERR, _NC_TCP, _NC_TCP6, _NC_RDMA, _NC_RDMA6, _NC_SCTP, _NC_SCTP6, _NC_UDP, _NC_UDP6, }; typedef enum rfc_5665_nc_type nc_type; struct __netid_nc_table { const char *netid; /* nc_type */ const nc_type nc; int af; }; extern const struct __netid_nc_table netid_nc_table[9]; nc_type nfs_netid_to_nc(const char *netid); void nfs_set_client_location(nfs_client_id_t *pclientid, const clientaddr4 *addr4); /* end TI-RPC */ typedef struct gsh_addr { nc_type nc; sockaddr_t ss; uint32_t port; } gsh_addr_t; /* NFS4 specific structures */ typedef struct nfs_client_cred_gss { unsigned int svc; unsigned int qop; #ifdef _HAVE_GSSAPI struct svc_rpc_gss_data *gd; #endif } nfs_client_cred_gss_t; typedef struct nfs_client_cred__ { unsigned int flavor; unsigned int length; union { struct authunix_parms auth_unix; nfs_client_cred_gss_t auth_gss; } auth_union; } nfs_client_cred_t; /** * @brief NFS v4 Compound Data * * This structure contains the necessary stuff for keeping the state * of a V4 compound request. */ /** * @brief Compound data * * This structure contains the necessary stuff for keeping the state * of a V4 compound request. */ struct compound_data { nfs_fh4 currentFH; /*< Current filehandle */ nfs_fh4 savedFH; /*< Saved filehandle */ stateid4 current_stateid; /*< Current stateid */ bool current_stateid_valid; /*< Current stateid is valid */ stateid4 saved_stateid; /*< Saved stateid */ bool saved_stateid_valid; /*< Saved stateid is valid */ unsigned int minorversion; /*< NFSv4 minor version */ struct fsal_obj_handle *current_obj; /*< Current object handle */ struct fsal_obj_handle *saved_obj; /*< saved object handle */ struct fsal_ds_handle *current_ds; /*< current ds handle */ struct fsal_ds_handle *saved_ds; /*< Saved DS handle */ object_file_type_t current_filetype; /*< File type of current obj */ object_file_type_t saved_filetype; /*< File type of saved entry */ struct gsh_export *saved_export; /*< Export entry related to the savedFH */ struct fsal_pnfs_ds *saved_pnfs_ds; /*< DS related to the savedFH */ struct export_perms saved_export_perms; /*< Permissions for export for savedFH */ struct svc_req *req; /*< RPC Request related to the compound */ struct timespec op_start_time; nfs_argop4 *argarray; nfs_res_t *res; nfs_resop4 *resarray; uint32_t argarray_len; nfs_client_cred_t credential; /*< Raw RPC credentials */ nfs_client_id_t *preserved_clientid; /*< clientid that has lease reserved, if any */ struct nfs41_session_slot__ *slot; /*< NFv41: pointer to the session's slot */ nfsstat4 cached_result_status; /* cached_result->status for reply request */ bool sa_cachethis; /*< True if cachethis was specified in SEQUENCE op. */ nfs_opnum4 opcode; /*< Current NFS4 OP */ uint32_t oppos; /*< Position of the operation within the request processed */ const char *opname; /*< Name of the operation */ char *tagname; void *op_data; /*< operation specific data for resume */ nfs41_session_t *session; /*< Related session (found by OP_SEQUENCE) */ sequenceid4 sequence; /*< Sequence ID of the current compound (if applicable) */ slotid4 slotid; /*< Slot ID of the current compound (if applicable) */ uint32_t resp_size; /*< Running total response size. */ uint32_t op_resp_size; /*< Current op's response size. */ }; #define VARIABLE_RESP_SIZE (0) typedef int (*nfs4_op_function_t)(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); /** * @brief Set the current entry in the context * * This manages refcounting on the object being stored in data. This means it * takes a ref on a new object, and releases it's ref on any old object. If the * caller has it's own ref, it must release it itself. * * @param[in] data Compound data to set entry in * @param[in] obj Object to set */ static inline void set_current_entry(compound_data_t *data, struct fsal_obj_handle *obj) { /* Mark current_stateid as invalid */ data->current_stateid_valid = false; if (data->current_ds && data->current_ds != data->saved_ds) { /* Release the current_ds because it's different We don't * bother with refcounting because a ds handle has a limited * lifetime and it's either current_ds or saved_ds. So as long * as saved_ds is not the same one here, we can release since * there is no other reference. */ op_ctx->ctx_pnfs_ds->s_ops.dsh_release(data->current_ds); /* Clear out the current_ds */ data->current_ds = NULL; } if (data->current_obj) { /* Release ref on old object */ data->current_obj->obj_ops->put_ref(data->current_obj); } data->current_obj = obj; if (obj == NULL) { data->current_filetype = NO_FILE_TYPE; return; } /* Get our ref on the new object */ data->current_obj->obj_ops->get_ref(data->current_obj); /* Set the current file type */ data->current_filetype = obj->type; } void set_saved_entry(compound_data_t *data, struct fsal_obj_handle *obj); #endif /* NFS_PROTO_DATA_H */ /** @} */ nfs-ganesha-6.5/src/include/nfs_proto_functions.h000066400000000000000000000470431473756622300222710ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_proto_functions.h * @brief Prototypes for NFS protocol functions. * @note not called by other header files. */ #ifndef NFS_PROTO_FUNCTIONS_H #define NFS_PROTO_FUNCTIONS_H #include #include #include #include "nfs_core.h" #include "sal_data.h" #include "nfs_proto_data.h" static inline enum nfs_req_result nfsstat4_to_nfs_req_result(nfsstat4 stat) { return stat == NFS4_OK ? NFS_REQ_OK : NFS_REQ_ERROR; } void nfs_rpc_complete_async_request(nfs_request_t *reqdata, enum nfs_req_result rc); enum xprt_stat drc_resume(struct svc_req *req); #ifdef _USE_NFS3 extern const nfs_function_desc_t nfs3_func_desc[]; #endif extern const nfs_function_desc_t nfs4_func_desc[]; #ifdef _USE_NFS3 extern const nfs_function_desc_t mnt1_func_desc[]; extern const nfs_function_desc_t mnt3_func_desc[]; #endif #ifdef _USE_NLM extern const nfs_function_desc_t nlm4_func_desc[]; #endif #ifdef _USE_RQUOTA extern const nfs_function_desc_t rquota1_func_desc[]; extern const nfs_function_desc_t rquota2_func_desc[]; #endif #ifdef USE_NFSACL3 extern const nfs_function_desc_t nfsacl_func_desc[]; #endif #ifdef _USE_NFS3 int mnt_Null(nfs_arg_t *, struct svc_req *, nfs_res_t *); int mnt_Mnt(nfs_arg_t *, struct svc_req *, nfs_res_t *); int mnt_Dump(nfs_arg_t *, struct svc_req *, nfs_res_t *); int mnt_Umnt(nfs_arg_t *, struct svc_req *, nfs_res_t *); int mnt_UmntAll(nfs_arg_t *, struct svc_req *, nfs_res_t *); int mnt_Export(nfs_arg_t *, struct svc_req *, nfs_res_t *); /* @} * -- End of MNT protocol functions. -- */ #endif #ifdef _USE_NLM int nlm_Null(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nlm4_Test(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nlm4_Lock(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nlm4_Cancel(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nlm4_Unlock(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nlm4_Sm_Notify(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nlm4_Test_Message(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nlm4_Cancel_Message(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nlm4_Lock_Message(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nlm4_Unlock_Message(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nlm4_Granted_Res(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nlm4_Share(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nlm4_Unshare(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nlm4_Free_All(nfs_arg_t *, struct svc_req *, nfs_res_t *); /* @} * -- End of NLM protocol functions. -- */ #endif #ifdef _USE_RQUOTA int rquota_Null(nfs_arg_t *, struct svc_req *, nfs_res_t *); int rquota_getquota(nfs_arg_t *, struct svc_req *, nfs_res_t *); int rquota_getactivequota(nfs_arg_t *, struct svc_req *, nfs_res_t *); int rquota_setquota(nfs_arg_t *, struct svc_req *, nfs_res_t *); int rquota_setactivequota(nfs_arg_t *, struct svc_req *, nfs_res_t *); /* @} * * -- End of RQUOTA protocol functions. -- * */ #endif #ifdef USE_NFSACL3 int nfsacl_Null(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfsacl_getacl(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfsacl_setacl(nfs_arg_t *, struct svc_req *, nfs_res_t *); /* @} * * -- End of NFSACL protocol functions. -- * */ #endif int nfs_null(nfs_arg_t *, struct svc_req *, nfs_res_t *); #ifdef _USE_NFS3 int nfs3_getattr(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_setattr(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_lookup(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_readlink(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_read(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_write(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_create(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_remove(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_rename(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_link(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_symlink(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_mkdir(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_rmdir(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_readdir(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_fsstat(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_access(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_readdirplus(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_fsinfo(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_pathconf(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_commit(nfs_arg_t *, struct svc_req *, nfs_res_t *); int nfs3_mknod(nfs_arg_t *, struct svc_req *, nfs_res_t *); #endif /* Functions needed for nfs v4 */ int nfs4_Compound(nfs_arg_t *, struct svc_req *, nfs_res_t *); enum nfs_req_result nfs4_op_read_resume(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp); enum nfs_req_result nfs4_op_write_resume(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp); enum nfs_req_result nfs4_op_read_plus_resume(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp); enum nfs_req_result nfs4_op_access(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_close(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_commit(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_create(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_delegpurge(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_delegreturn(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_getattr(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_getfh(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_link(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_lock(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_lockt(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_locku(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_lookup(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_lookupp(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_nverify(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_open(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_open_confirm(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_open_downgrade(struct nfs_argop4 *, compound_data_t *data, struct nfs_resop4 *); enum nfs_req_result nfs4_op_openattr(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_putfh(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_putpubfh(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_putrootfh(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_read(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_readdir(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_remove(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_renew(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_rename(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_restorefh(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_readlink(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_savefh(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_secinfo(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_secinfo_no_name(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_setattr(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_setclientid(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_setclientid_confirm(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_verify(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_release_lockowner(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_illegal(struct nfs_argop4 *, compound_data_t *data, struct nfs_resop4 *); enum nfs_req_result nfs4_op_notsupp(struct nfs_argop4 *, compound_data_t *data, struct nfs_resop4 *); enum nfs_req_result nfs4_op_bind_conn(struct nfs_argop4 *op, compound_data_t *data, struct nfs_resop4 *resp); enum nfs_req_result nfs4_op_exchange_id(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_create_session(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_getdevicelist(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_free_stateid(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_getdeviceinfo(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_destroy_clientid(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_destroy_session(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_layoutget(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_layoutcommit(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_layoutreturn(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_reclaim_complete(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_sequence(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_set_ssv(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_test_stateid(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); enum nfs_req_result nfs4_op_write(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); /* NFSv4.2 */ enum nfs_req_result nfs4_op_write_same(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); void nfs4_op_write_same_Free(nfs_resop4 *resp); enum nfs_req_result nfs4_op_read_plus(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); void nfs4_op_read_plus_Free(nfs_resop4 *resp); enum nfs_req_result nfs4_op_allocate(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); void nfs4_op_allocate_Free(nfs_resop4 *resp); enum nfs_req_result nfs4_op_deallocate(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); void nfs4_op_deallocate_Free(nfs_resop4 *resp); enum nfs_req_result nfs4_op_seek(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); void nfs4_op_seek_Free(nfs_resop4 *resp); enum nfs_req_result nfs4_op_io_advise(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); void nfs4_op_io_advise_Free(nfs_resop4 *resp); enum nfs_req_result nfs4_op_layouterror(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); void nfs4_op_layouterror_Free(nfs_resop4 *resp); enum nfs_req_result nfs4_op_layoutstats(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); void nfs4_op_layoutstats_Free(nfs_resop4 *resp); /* NFSv4.3 */ enum nfs_req_result nfs4_op_getxattr(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); void nfs4_op_getxattr_Free(nfs_resop4 *resp); enum nfs_req_result nfs4_op_setxattr(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); void nfs4_op_setxattr_Free(nfs_resop4 *resp); enum nfs_req_result nfs4_op_listxattr(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); void nfs4_op_listxattr_Free(nfs_resop4 *resp); enum nfs_req_result nfs4_op_removexattr(struct nfs_argop4 *, compound_data_t *, struct nfs_resop4 *); void nfs4_op_removexattr_Free(nfs_resop4 *resp); /* @} * -- End of NFS protocols functions. -- */ /* Free functions */ #ifdef _USE_NFS3 void mnt1_Mnt_Free(nfs_res_t *); void mnt3_Mnt_Free(nfs_res_t *); void mnt_Dump_Free(nfs_res_t *); void mnt_Export_Free(nfs_res_t *); void mnt_Null_Free(nfs_res_t *); void mnt_Umnt_Free(nfs_res_t *); void mnt_UmntAll_Free(nfs_res_t *); #endif #ifdef _USE_NLM void nlm_Null_Free(nfs_res_t *); void nlm4_Test_Free(nfs_res_t *); void nlm4_Lock_Free(nfs_res_t *); void nlm4_NM_Lock_Free(nfs_res_t *); void nlm4_Share_Free(nfs_res_t *); void nlm4_Unshare_Free(nfs_res_t *); void nlm4_Cancel_Free(nfs_res_t *); void nlm4_Unlock_Free(nfs_res_t *); void nlm4_Sm_Notify_Free(nfs_res_t *); void nlm4_Granted_Res_Free(nfs_res_t *); void nlm4_Free_All_Free(nfs_res_t *); #endif #ifdef _USE_RQUOTA void rquota_Null_Free(nfs_res_t *); void rquota_getquota_Free(nfs_res_t *); void rquota_getactivequota_Free(nfs_res_t *); void rquota_setquota_Free(nfs_res_t *); void rquota_setactivequota_Free(nfs_res_t *); #endif #ifdef USE_NFSACL3 void nfsacl_Null_Free(nfs_res_t *); void nfsacl_getacl_Free(nfs_res_t *); void nfsacl_setacl_Free(nfs_res_t *); #endif void nfs_null_free(nfs_res_t *); #ifdef _USE_NFS3 void nfs3_getattr_free(nfs_res_t *); void nfs3_setattr_free(nfs_res_t *); void nfs3_lookup_free(nfs_res_t *); void nfs3_access_free(nfs_res_t *); void nfs3_readlink_free(nfs_res_t *); void nfs3_write_free(nfs_res_t *); void nfs3_create_free(nfs_res_t *); void nfs3_mkdir_free(nfs_res_t *); void nfs3_symlink_free(nfs_res_t *); void nfs3_mknod_free(nfs_res_t *); void nfs3_remove_free(nfs_res_t *); void nfs3_rmdir_free(nfs_res_t *); void nfs3_rename_free(nfs_res_t *); void nfs3_link_free(nfs_res_t *); void nfs3_readdir_free(nfs_res_t *); void nfs3_readdirplus_free(nfs_res_t *); void nfs3_fsstat_free(nfs_res_t *); void nfs3_fsinfo_free(nfs_res_t *); void nfs3_pathconf_free(nfs_res_t *); void nfs3_commit_free(nfs_res_t *); void nfs3_read_free(nfs_res_t *); #endif void nfs4_Compound_FreeOne(nfs_resop4 *); void release_nfs4_res_compound(struct COMPOUND4res_extended *res_compound4_ex); void nfs4_Compound_Free(nfs_res_t *); void nfs4_Compound_CopyResOne(nfs_resop4 *, nfs_resop4 *); void nfs4_op_access_Free(nfs_resop4 *); void nfs4_op_close_Free(nfs_resop4 *); void nfs4_op_commit_Free(nfs_resop4 *); void nfs4_op_create_Free(nfs_resop4 *); void nfs4_op_delegreturn_Free(nfs_resop4 *); void nfs4_op_delegpurge_Free(nfs_resop4 *); void nfs4_op_getattr_Free(nfs_resop4 *); void nfs4_op_getfh_Free(nfs_resop4 *); void nfs4_op_illegal_Free(nfs_resop4 *); void nfs4_op_notsupp_Free(nfs_resop4 *); void nfs4_op_link_Free(nfs_resop4 *); void nfs4_op_lock_Free(nfs_resop4 *); void nfs4_op_lockt_Free(nfs_resop4 *); void nfs4_op_locku_Free(nfs_resop4 *); void nfs4_op_lookup_Free(nfs_resop4 *); void nfs4_op_lookupp_Free(nfs_resop4 *); void nfs4_op_nverify_Free(nfs_resop4 *); void nfs4_op_open_Free(nfs_resop4 *); void nfs4_op_open_confirm_Free(nfs_resop4 *); void nfs4_op_open_downgrade_Free(nfs_resop4 *); void nfs4_op_openattr_Free(nfs_resop4 *); void nfs4_op_openattr_Free(nfs_resop4 *); void nfs4_op_putfh_Free(nfs_resop4 *); void nfs4_op_putpubfh_Free(nfs_resop4 *); void nfs4_op_putrootfh_Free(nfs_resop4 *); void nfs4_op_read_Free(nfs_resop4 *); void nfs4_op_readdir_Free(nfs_resop4 *); void nfs4_op_readlink_Free(nfs_resop4 *); void nfs4_op_release_lockowner_Free(nfs_resop4 *); void nfs4_op_rename_Free(nfs_resop4 *); void nfs4_op_remove_Free(nfs_resop4 *); void nfs4_op_renew_Free(nfs_resop4 *); void nfs4_op_restorefh_Free(nfs_resop4 *); void nfs4_op_savefh_Free(nfs_resop4 *); void nfs4_op_secinfo_Free(nfs_resop4 *); void nfs4_op_secinfo_no_name_Free(nfs_resop4 *); void nfs4_op_setattr_Free(nfs_resop4 *); void nfs4_op_setclientid_Free(nfs_resop4 *); void nfs4_op_setclientid_confirm_Free(nfs_resop4 *); void nfs4_op_verify_Free(nfs_resop4 *); void nfs4_op_write_Free(nfs_resop4 *); void nfs4_op_close_CopyRes(CLOSE4res *, CLOSE4res *); void nfs4_op_lock_CopyRes(LOCK4res *, LOCK4res *); void nfs4_op_locku_CopyRes(LOCKU4res *, LOCKU4res *); void nfs4_op_open_CopyRes(OPEN4res *, OPEN4res *); void nfs4_op_open_confirm_CopyRes(OPEN_CONFIRM4res *, OPEN_CONFIRM4res *); void nfs4_op_open_downgrade_CopyRes(OPEN_DOWNGRADE4res *, OPEN_DOWNGRADE4res *); void nfs4_op_nfs4_op_bind_conn_Free(nfs_resop4 *resp); void nfs4_op_exchange_id_Free(nfs_resop4 *); void nfs4_op_close_Free(nfs_resop4 *); void nfs4_op_create_session_Free(nfs_resop4 *); void nfs4_op_getdevicelist_Free(nfs_resop4 *); void nfs4_op_getdeviceinfo_Free(nfs_resop4 *); void nfs4_op_free_stateid_Free(nfs_resop4 *); void nfs4_op_destroy_session_Free(nfs_resop4 *); void nfs4_op_lock_Free(nfs_resop4 *); void nfs4_op_lockt_Free(nfs_resop4 *); void nfs4_op_locku_Free(nfs_resop4 *); void nfs4_op_read_Free(nfs_resop4 *); void nfs4_op_sequence_Free(nfs_resop4 *); void nfs4_op_set_ssv_Free(nfs_resop4 *); void nfs4_op_test_stateid_Free(nfs_resop4 *); void nfs4_op_write_Free(nfs_resop4 *); void nfs4_op_destroy_clientid_Free(nfs_resop4 *); void nfs4_op_reclaim_complete_Free(nfs_resop4 *); void compound_data_Free(compound_data_t *); uint32_t get_nfs4_opcodes(compound_data_t *data, nfs_opnum4 *opcodes, uint32_t opcodes_array_len); bool xdr_COMPOUND4res_extended(XDR *xdrs, struct COMPOUND4res_extended **objp); /* Pseudo FS functions */ bool pseudo_mount_export(struct gsh_export *exp); void create_pseudofs(void); void pseudo_unmount_export_tree(struct gsh_export *exp); void prune_pseudofs_subtree(struct gsh_export *exp, uint64_t generation, bool ancestor_is_defunct); /* Slot functions */ static inline void release_slot(nfs41_session_slot_t *slot) { if (slot->cached_result != NULL) { /* Release slot cache reference to the result. */ release_nfs4_res_compound(slot->cached_result); /* And empty the slot cache */ slot->cached_result = NULL; } } #endif /* NFS_PROTO_FUNCTIONS_H */ nfs-ganesha-6.5/src/include/nfs_proto_tools.h000066400000000000000000000254501473756622300214170ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- * * @file nfs_proto_tools.c * @brief A set of functions used to managed NFS. * * nfs_proto_tools.c - A set of functions used to managed NFS. * * */ #ifndef _NFS_PROTO_TOOLS_H #define _NFS_PROTO_TOOLS_H #include #include #include #include #include /* for having FNDELAY */ #include "hashtable.h" #include "log.h" #include "nfs_file_handle.h" #include "sal_data.h" #include "fsal.h" #ifdef USE_NFSACL3 #include "posix_acls.h" #include #endif /* USE_NFSACL3 */ /* Hard and soft limit for nfsv4 quotas */ #define NFS_V4_MAX_QUOTA_SOFT 4294967296LL /* 4 GB */ #define NFS_V4_MAX_QUOTA_HARD 17179869184LL /* 16 GB */ #define NFS_V4_MAX_QUOTA 34359738368LL /* 32 GB */ #define NFS4_ATTRVALS_BUFFLEN 1024 /* * Definition of an array for the characteristics of each GETATTR sub-operations */ #define FATTR4_ATTR_READ 0x00001 #define FATTR4_ATTR_WRITE 0x00010 #define FATTR4_ATTR_READ_WRITE 0x00011 typedef enum { FATTR_XDR_NOOP, FATTR_XDR_SUCCESS, FATTR_XDR_SUCCESS_EXP, FATTR_XDR_FAILED, FATTR_BADOWNER } fattr_xdr_result; struct xdr_attrs_args { struct fsal_attrlist *attrs; nfs_fh4 *hdl4; uint32_t rdattr_error; uint64_t mounted_on_fileid; /*< If this is the root directory of a filesystem, the fileid of the directory on which the filesystem is mounted. */ /* Static attributes */ object_file_type_t type; /*< Object file type */ fsal_fsid_t fsid; /*< Filesystem on which this object is stored */ uint64_t fileid; /*< Unique identifier for this object within the scope of the fsid, (e.g. inode number) */ int nfs_status; compound_data_t *data; bool statfscalled; fsal_dynamicfsinfo_t dynamicinfo; }; typedef struct fattr4_dent { char *name; /* The name of the operation */ unsigned int supported; /* Is this action supported? */ unsigned int encoded; /* Can we encode this attribute? */ unsigned int size_fattr4; /* The size of the dedicated attr subtype */ unsigned int access; /* The access type for this attributes */ attrmask_t attrmask; /* attr bit for decoding to attrs */ attrmask_t exp_attrmask; /* attr bit for decoding to attrs in case of exepction */ fattr_xdr_result (*encode)(XDR *xdr, struct xdr_attrs_args *args); fattr_xdr_result (*decode)(XDR *xdr, struct xdr_attrs_args *args); fattr_xdr_result (*compare)(XDR *xdr1, XDR *xdr2); } fattr4_dent_t; extern const struct fattr4_dent fattr4tab[]; #define WORD0_FATTR4_RDATTR_ERROR (1 << FATTR4_RDATTR_ERROR) #define WORD1_FATTR4_MOUNTED_ON_FILEID (1 << (FATTR4_MOUNTED_ON_FILEID - 32)) static inline int check_for_wrongsec_ok_attr(struct bitmap4 *attr_request) { if (attr_request->bitmap4_len < 1) return true; if ((attr_request->map[0] & ~WORD0_FATTR4_RDATTR_ERROR) != 0) return false; if (attr_request->bitmap4_len < 2) return true; if ((attr_request->map[1] & ~WORD1_FATTR4_MOUNTED_ON_FILEID) != 0) return false; if (attr_request->bitmap4_len < 3) return true; if (attr_request->map[2] != 0) return false; return true; } static inline int check_for_rdattr_error(struct bitmap4 *attr_request) { if (attr_request->bitmap4_len < 1) return false; if ((attr_request->map[0] & WORD0_FATTR4_RDATTR_ERROR) != 0) return true; return false; } /** * * Attribute bitmap decoders */ /* bitmap is up to 3 x uint32_t. * * Structure of the bitmap is as follows * * 0 1 2 * +-------+---------+----------+----------+ * | count | 31 .. 0 | 63 .. 32 | 95 .. 64 | * +-------+---------+----------+----------+ * * One bit is set for every possible attributes. The bits are packed together * in a uint32_T (XDR alignment reason probably) * As said in the RFC3530, the n-th bit is with the uint32_t #(n/32), * and its position with the uint32_t is n % 32 * * Example * 1st bit = FATTR4_TYPE = 1 * 2nd bit = FATTR4_LINK_SUPPORT = 5 * 3rd bit = FATTR4_SYMLINK_SUPPORT = 6 * * Juste one uint32_t is necessary: 2**1 + 2**5 + 2**6 = 2 + 32 + 64 = 98 * +---+----+ * | 1 | 98 | * +---+----+ * * Other Example * * 1st bit = FATTR4_LINK_SUPPORT = 5 * 2nd bit = FATTR4_SYMLINK_SUPPORT = 6 * 3rd bit = FATTR4_MODE = 33 * 4th bit = FATTR4_OWNER = 36 * * Two uint32_t will be necessary there: * #1 = 2**5 + 2**6 = 32 + 64 = 96 # #2 = 2**(33-32) + 2**(36-32) = 2**1 + 2**4 = 2 + 16 = 18 * +---+----+----+ * | 2 | 98 | 18 | * +---+----+----+ * */ static inline int next_attr_from_bitmap(struct bitmap4 *bits, int last_attr) { int offset, bit; for (offset = (last_attr + 1) / 32; offset >= 0 && offset < bits->bitmap4_len; offset++) { if ((bits->map[offset] & ((~(uint32_t)(0)) << ((last_attr + 1) % 32))) != 0) { for (bit = (last_attr + 1) % 32; bit < 32; bit++) { if (bits->map[offset] & ((uint32_t)1 << bit)) return offset * 32 + bit; } } last_attr = -1; } return -1; } static inline bool attribute_is_set(struct bitmap4 *bits, int attr) { int offset = attr / 32; if (offset >= bits->bitmap4_len) return false; return (bits->map[offset] & (1 << (attr % 32))) != 0; } static inline bool set_attribute_in_bitmap(struct bitmap4 *bits, int attr) { int offset = attr / 32; if (offset >= 3) return false; /* over upper bound */ if (offset >= bits->bitmap4_len) bits->bitmap4_len = offset + 1; /* roll into the next word */ bits->map[offset] |= ((uint32_t)1 << (attr % 32)); return true; } static inline bool clear_attribute_in_bitmap(struct bitmap4 *bits, int attr) { int offset = attr / 32; if (offset >= bits->bitmap4_len) return false; bits->map[offset] &= ~((uint32_t)1 << (attr % 32)); return true; } #ifdef _USE_NFS3 void nfs_SetWccData(const struct pre_op_attr *before_attr, struct fsal_obj_handle *entry, struct fsal_attrlist *post_attrs, wcc_data *pwcc_data); void nfs_SetPostOpAttr(struct fsal_obj_handle *entry, post_op_attr *attr, struct fsal_attrlist *attrs); void nfs_SetPreOpAttr(struct fsal_obj_handle *entry, pre_op_attr *attr); void nfs_PreOpAttrFromFsalAttr(struct fsal_attrlist *fsal_attrs, pre_op_attr *out_pre_attr); #endif bool nfs_RetryableError(fsal_errors_t fsal_errors); int nfs3_Sattr_To_FSAL_attr(struct fsal_attrlist *pFSALattr, sattr3 *psattr); void nfs4_Fattr_Free(fattr4 *fattr); nfsstat4 nfs4_return_one_state(struct fsal_obj_handle *obj, layoutreturn_type4 return_type, enum fsal_layoutreturn_circumstance circumstance, state_t *layout_state, struct pnfs_segment spec_segment, size_t body_len, const void *body_val, bool *deleted); #define UTF8_SCAN_NONE 0x00 /* do no validation other than size */ #define UTF8_SCAN_NOSLASH 0x01 /* disallow '/' */ #define UTF8_SCAN_NODOT 0x02 /* disallow '.' and '..' */ #define UTF8_SCAN_CKUTF8 0x04 /* validate utf8 */ #define UTF8_SCAN_PATH 0x10 /* validate path length */ /* Do UTF-8 checking if Enforce_UTF8_Validation is true */ #define UTF8_SCAN_STRICT \ (nfs_param.nfsv4_param.enforce_utf8_vld ? UTF8_SCAN_CKUTF8 : 0) /* Validate path components, with optional UTF-8 validation */ #define UTF8_SCAN_PATH_COMP \ (UTF8_SCAN_NOSLASH | UTF8_SCAN_NODOT | UTF8_SCAN_STRICT) nfsstat4 path_filter(const char *name, int scan); static inline nfsstat4 nfs4_utf8string_scan(const utf8string *input, int scan) { if (input->utf8string_val == NULL || input->utf8string_len == 0) return NFS4ERR_INVAL; if (((scan & UTF8_SCAN_PATH) && input->utf8string_len > MAXPATHLEN) || (!(scan & UTF8_SCAN_PATH) && input->utf8string_len > MAXNAMLEN)) return NFS4ERR_NAMETOOLONG; /* Nothing else to do */ if (scan == UTF8_SCAN_NONE || scan == UTF8_SCAN_PATH) return NFS4_OK; /* utf8strings are now NUL terminated, so it's ok to call the filter */ return path_filter(input->utf8string_val, scan); } int bitmap4_to_attrmask_t(bitmap4 *bitmap4, attrmask_t *mask); nfsstat4 file_To_Fattr(compound_data_t *data, attrmask_t mask, struct fsal_attrlist *attr, fattr4 *Fattr, struct bitmap4 *Bitmap); bool nfs4_Fattr_Check_Access(fattr4 *, int); bool nfs4_Fattr_Check_Access_Bitmap(struct bitmap4 *, int); bool nfs4_Fattr_Supported(fattr4 *); int nfs4_Fattr_cmp(fattr4 *, fattr4 *); bool nfs3_Fixup_FSALattr(struct fsal_obj_handle *obj, const struct fsal_attrlist *FSAL_attr); bool is_sticky_bit_set(struct fsal_obj_handle *obj, const struct fsal_attrlist *attr); bool nfs3_Sattr_To_FSALattr(struct fsal_attrlist *, sattr3 *); int nfs4_Fattr_To_FSAL_attr(struct fsal_attrlist *, fattr4 *, compound_data_t *); int nfs4_Fattr_To_fsinfo(fsal_dynamicfsinfo_t *, fattr4 *); int nfs4_Fattr_Fill_Error(compound_data_t *, fattr4 *, nfsstat4, struct bitmap4 *, struct xdr_attrs_args *args); bool xdr_nfs4_fattr_fill_error(XDR *xdrs, struct bitmap4 *req_attrmask, nfs_cookie4 cookie, component4 *name, struct xdr_attrs_args *args); bool xdr_encode_entry4(XDR *xdrs, struct xdr_attrs_args *args, struct bitmap4 *req_bitmap, nfs_cookie4 cookie, component4 *name); int nfs4_FSALattr_To_Fattr(struct xdr_attrs_args *, struct bitmap4 *, fattr4 *); void nfs4_bitmap4_Remove_Unsupported(struct bitmap4 *); enum nfs4_minor_vers { NFS4_MINOR_VERS_0, NFS4_MINOR_VERS_1, NFS4_MINOR_VERS_2 }; void nfs4_pathname4_alloc(pathname4 *, char *); void nfs4_pathname4_free(pathname4 *); uint32_t resp_room(compound_data_t *data); nfsstat4 check_resp_room(compound_data_t *data, uint32_t op_resp_size); void get_mounted_on_fileid(compound_data_t *data, uint64_t *mounted_on_fileid); #ifdef USE_NFSACL3 posix_acl *encode_posix_acl(const acl_t acl, uint32_t type, struct fsal_attrlist *attrs); acl_t decode_posix_acl(posix_acl *nfs3_acl, uint32_t type); int nfs3_acl_2_fsal_acl(struct fsal_attrlist *attr, nfs3_int32 mask, posix_acl *a_acl, posix_acl *d_acl, bool is_dir); #endif /* USE_NFSACL3 */ #endif /* _NFS_PROTO_TOOLS_H */ nfs-ganesha-6.5/src/include/nfs_rpc_callback.h000066400000000000000000000073301473756622300214310ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2012, The Linux Box Corporation * Copyright (c) 2012-2017 Red Hat, Inc. and/or its affiliates. * Contributor : Matt Benjamin * William Allen Simpson * * Some portions Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #ifndef NFS_RPC_CALLBACK_H #define NFS_RPC_CALLBACK_H #include "config.h" #include "log.h" #include "nfs_core.h" /** * @file nfs_rpc_callback.h * @author Matt Benjamin * @author Lee Dobryden * @brief RPC callback dispatch package * * This module implements APIs for submission, and dispatch of NFSv4.0 * and NFSv4.1 format callbacks. */ /* Definition in sal_data.h */ struct state_refer; /* XXX move? */ typedef struct nfs4_cb_tag { int32_t ix; char *val; int32_t len; } nfs4_cb_tag_t; /* CB compound tags */ #define NFS4_CB_TAG_DEFAULT 0 void cb_compound_init_v4(nfs4_compound_t *cbt, uint32_t n_ops, uint32_t minorversion, uint32_t ident, char *tag, uint32_t tag_len); void cb_compound_add_op(nfs4_compound_t *cbt, nfs_cb_argop4 *src); void cb_compound_free(nfs4_compound_t *cbt); #define NFS_CB_FLAG_NONE 0x0000 #define NFS_RPC_FLAG_NONE 0x0000 enum nfs_cb_call_states { NFS_CB_CALL_DISPATCH, NFS_CB_CALL_FINISHED, NFS_CB_CALL_ABORTED, }; rpc_call_t *alloc_rpc_call(void); void free_rpc_call(rpc_call_t *call); static inline nfs_cb_argop4 *alloc_cb_argop(uint32_t cnt) { return gsh_calloc(cnt, sizeof(nfs_cb_argop4)); } static inline nfs_cb_resop4 *alloc_cb_resop(uint32_t cnt) { return gsh_calloc(cnt, sizeof(nfs_cb_resop4)); } static inline void free_cb_argop(nfs_cb_argop4 *ptr) { gsh_free(ptr); } static inline void free_cb_resop(nfs_cb_resop4 *ptr) { gsh_free(ptr); } static inline bool get_cb_chan_down(struct nfs_client_id_t *clid) { return clid->cid_cb.v40.cb_chan_down; } static inline void set_cb_chan_down(struct nfs_client_id_t *clid, bool down) { clid->cid_cb.v40.cb_chan_down = down; } rpc_call_channel_t *nfs_rpc_get_chan(nfs_client_id_t *pclientid, uint32_t flags); void nfs_rpc_cb_pkginit(void); void nfs_rpc_cb_pkgshutdown(void); #ifdef _HAVE_GSSAPI void nfs_rpc_cb_set_gss_status(bool gss_enabled); #endif int nfs_rpc_create_chan_v40(nfs_client_id_t *pclientid, uint32_t flags); int nfs_rpc_create_chan_v41(SVCXPRT *xprt, nfs41_session_t *session, int num_sec_parms, callback_sec_parms4 *sec_parms); #define NFS_RPC_CALL_NONE 0x0000 enum clnt_stat nfs_rpc_call(rpc_call_t *call, uint32_t flags); int nfs_rpc_cb_single(nfs_client_id_t *clientid, nfs_cb_argop4 *op, struct state_refer *refer, void (*completion)(rpc_call_t *), void *completion_arg); void nfs41_release_single(rpc_call_t *call); enum clnt_stat nfs_test_cb_chan(nfs_client_id_t *); #endif /* !NFS_RPC_CALLBACK_H */ nfs-ganesha-6.5/src/include/nfs_rpc_callback_simulator.h000066400000000000000000000035111473756622300235250ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2012, The Linux Box Corporation * Contributor : Matt Benjamin * * Some portions Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #ifndef _NFS_RPC_CALLBACK_SIMULATOR_H #define _NFS_RPC_CALLBACK_SIMULATOR_H #include "config.h" #include "log.h" /** * * \file nfs_rpc_callback_simulator.h * \author Matt Benjamin and Lee Dobryden * \brief RPC callback dispatch package * * \section DESCRIPTION * * This module implements APIs for submission, and dispatch of NFSv4.0 * and (soon) NFSv4.1 format callbacks. * * Planned strategy is to deal with all backchannels from a small number of * service threads, initially 1, using non-blocking socket operations. This * may change, as NFSv4.1 bi-directional support is integrated. * */ void nfs_rpc_cbsim_pkginit(void); void nfs_rpc_cbsim_pkgshutdown(void); #endif /* _NFS_RPC_CALLBACK_SIMULATOR_H */ nfs-ganesha-6.5/src/include/nfsacl.h000066400000000000000000000044401473756622300174300ustar00rootroot00000000000000// SPDX-License-Identifier: unknown license... /* * The content of this file is a mix of rpcgen-generated * and hand-edited program text. It is not automatically * generated by, e.g., build processes. * * This file is under version control. */ #ifndef _NFSACL_H_RPCGEN #define _NFSACL_H_RPCGEN #include #define NFS_ACL 0x0001 #define NFS_ACLCNT 0x0002 #define NFS_DFACL 0x0004 #define NFS_DFACLCNT 0x0008 #define NFS_ACL_MASK 0x00FF #define NFS_DFACL_MASK 0x1000 struct attr3 { bool_t attributes_follow; union { fattr3 obj_attributes; } attr3_u; }; typedef struct attr3 attr3; struct posix_acl_entry { nfs3_uint32 e_tag; nfs3_uint32 e_id; nfs3_uint32 e_perm; }; typedef struct posix_acl_entry posix_acl_entry; struct posix_acl { nfs3_uint32 count; posix_acl_entry entries[0]; }; typedef struct posix_acl posix_acl; struct getaclargs { nfs_fh3 fhandle; nfs3_int32 mask; }; typedef struct getaclargs getaclargs; struct getaclresok { attr3 attr; nfs3_int32 mask; nfs3_uint32 acl_access_count; posix_acl *acl_access; nfs3_uint32 acl_default_count; posix_acl *acl_default; }; typedef struct getaclresok getaclresok; struct getaclres { nfsstat3 status; union { getaclresok resok; } getaclres_u; }; typedef struct getaclres getaclres; struct setaclargs { nfs_fh3 fhandle; nfs3_int32 mask; nfs3_uint32 acl_access_count; posix_acl *acl_access; nfs3_uint32 acl_default_count; posix_acl *acl_default; }; typedef struct setaclargs setaclargs; struct setaclresok { attr3 attr; }; typedef struct setaclresok setaclresok; struct setaclres { nfsstat3 status; union { setaclresok resok; } setaclres_u; }; typedef struct setaclres setaclres; #define NFSACLPROG 100227 #define NFSACL_V3 3 #define NFSACLPROC_NULL 0 #define NFSACLPROC_GETACL 1 #define NFSACLPROC_SETACL 2 /* the xdr functions */ extern bool_t xdr_attr3(XDR *, attr3 *); extern bool_t xdr_posix_acl_entry(XDR *, posix_acl_entry *); extern bool_t xdr_posix_acl(XDR *, posix_acl *); extern bool_t xdr_getaclargs(XDR *, getaclargs *); extern bool_t xdr_getaclresok(XDR *, getaclresok *); extern bool_t xdr_getaclres(XDR *, getaclres *); extern bool_t xdr_setaclargs(XDR *, setaclargs *); extern bool_t xdr_setaclresok(XDR *, setaclresok *); extern bool_t xdr_setaclres(XDR *, setaclres *); #endif /* !_NFSACL_H_RPCGEN */ nfs-ganesha-6.5/src/include/nfsv41.h000066400000000000000000006776541473756622300173320ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (c) 2012 IETF Trust and the persons identified * as authors of the code. All rights reserved. * * Redistribution and use in source and binary forms, with * or without modification, are permitted provided that the * following conditions are met: * * o Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * o 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. * * o Neither the name of Internet Society, IETF or IETF * Trust, nor the names of specific contributors, may be * used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS * 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 COPYRIGHT OWNER 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 code was derived from RFCTBD10. * Please reproduce this note if possible. */ /* * The content of this file is a mix of rpcgen-generated * and hand-edited program text. It is not automatically * generated by, e.g., build processes. * * This file is under version control. */ #ifndef _NFSV41_H_RPCGEN #define _NFSV41_H_RPCGEN #ifdef __cplusplus extern "C" { #endif #ifndef RPCSEC_GSS #define RPCSEC_GSS 6 #endif #ifndef _AUTH_SYS_DEFINE_FOR_NFSv41 #define _AUTH_SYS_DEFINE_FOR_NFSv41 #include "gsh_rpc.h" #include "nfs_fh.h" #include "log.h" #include "abstract_mem.h" typedef struct authsys_parms authsys_parms; #endif /* _AUTH_SYS_DEFINE_FOR_NFSv41 */ #define NFS4_FHSIZE 128 #define NFS4_VERIFIER_SIZE 8 #define NFS4_OPAQUE_LIMIT 1024 #define NFS4_SESSIONID_SIZE 16 #define NFS4_INT64_MAX 0x7fffffffffffffffLL #define NFS4_UINT64_MAX 0xffffffffffffffffLL #define NFS4_INT32_MAX 0x7fffffff #define NFS4_UINT32_MAX 0xffffffff #define NFS4_MAXFILELEN 0xffffffffffffffff #define NFS4_MAXFILEOFF 0xfffffffffffffffe #define BITMAP4_MAPLEN 3 enum nfs_ftype4 { NF4REG = 1, NF4DIR = 2, NF4BLK = 3, NF4CHR = 4, NF4LNK = 5, NF4SOCK = 6, NF4FIFO = 7, NF4ATTRDIR = 8, NF4NAMEDATTR = 9, }; typedef enum nfs_ftype4 nfs_ftype4; enum nfsstat4 { NFS4_OK = 0, NFS4ERR_PERM = 1, NFS4ERR_NOENT = 2, NFS4ERR_IO = 5, NFS4ERR_NXIO = 6, NFS4ERR_ACCESS = 13, NFS4ERR_EXIST = 17, NFS4ERR_XDEV = 18, NFS4ERR_NOTDIR = 20, NFS4ERR_ISDIR = 21, NFS4ERR_INVAL = 22, NFS4ERR_FBIG = 27, NFS4ERR_NOSPC = 28, NFS4ERR_ROFS = 30, NFS4ERR_MLINK = 31, NFS4ERR_NAMETOOLONG = 63, NFS4ERR_NOTEMPTY = 66, NFS4ERR_DQUOT = 69, NFS4ERR_STALE = 70, NFS4ERR_BADHANDLE = 10001, NFS4ERR_BAD_COOKIE = 10003, NFS4ERR_NOTSUPP = 10004, NFS4ERR_TOOSMALL = 10005, NFS4ERR_SERVERFAULT = 10006, NFS4ERR_BADTYPE = 10007, NFS4ERR_DELAY = 10008, NFS4ERR_SAME = 10009, NFS4ERR_DENIED = 10010, NFS4ERR_EXPIRED = 10011, NFS4ERR_LOCKED = 10012, NFS4ERR_GRACE = 10013, NFS4ERR_FHEXPIRED = 10014, NFS4ERR_SHARE_DENIED = 10015, NFS4ERR_WRONGSEC = 10016, NFS4ERR_CLID_INUSE = 10017, NFS4ERR_RESOURCE = 10018, NFS4ERR_MOVED = 10019, NFS4ERR_NOFILEHANDLE = 10020, NFS4ERR_MINOR_VERS_MISMATCH = 10021, NFS4ERR_STALE_CLIENTID = 10022, NFS4ERR_STALE_STATEID = 10023, NFS4ERR_OLD_STATEID = 10024, NFS4ERR_BAD_STATEID = 10025, NFS4ERR_BAD_SEQID = 10026, NFS4ERR_NOT_SAME = 10027, NFS4ERR_LOCK_RANGE = 10028, NFS4ERR_SYMLINK = 10029, NFS4ERR_RESTOREFH = 10030, NFS4ERR_LEASE_MOVED = 10031, NFS4ERR_ATTRNOTSUPP = 10032, NFS4ERR_NO_GRACE = 10033, NFS4ERR_RECLAIM_BAD = 10034, NFS4ERR_RECLAIM_CONFLICT = 10035, NFS4ERR_BADXDR = 10036, NFS4ERR_LOCKS_HELD = 10037, NFS4ERR_OPENMODE = 10038, NFS4ERR_BADOWNER = 10039, NFS4ERR_BADCHAR = 10040, NFS4ERR_BADNAME = 10041, NFS4ERR_BAD_RANGE = 10042, NFS4ERR_LOCK_NOTSUPP = 10043, NFS4ERR_OP_ILLEGAL = 10044, NFS4ERR_DEADLOCK = 10045, NFS4ERR_FILE_OPEN = 10046, NFS4ERR_ADMIN_REVOKED = 10047, NFS4ERR_CB_PATH_DOWN = 10048, NFS4ERR_BADIOMODE = 10049, NFS4ERR_BADLAYOUT = 10050, NFS4ERR_BAD_SESSION_DIGEST = 10051, NFS4ERR_BADSESSION = 10052, NFS4ERR_BADSLOT = 10053, NFS4ERR_COMPLETE_ALREADY = 10054, NFS4ERR_CONN_NOT_BOUND_TO_SESSION = 10055, NFS4ERR_DELEG_ALREADY_WANTED = 10056, NFS4ERR_BACK_CHAN_BUSY = 10057, NFS4ERR_LAYOUTTRYLATER = 10058, NFS4ERR_LAYOUTUNAVAILABLE = 10059, NFS4ERR_NOMATCHING_LAYOUT = 10060, NFS4ERR_RECALLCONFLICT = 10061, NFS4ERR_UNKNOWN_LAYOUTTYPE = 10062, NFS4ERR_SEQ_MISORDERED = 10063, NFS4ERR_SEQUENCE_POS = 10064, NFS4ERR_REQ_TOO_BIG = 10065, NFS4ERR_REP_TOO_BIG = 10066, NFS4ERR_REP_TOO_BIG_TO_CACHE = 10067, NFS4ERR_RETRY_UNCACHED_REP = 10068, NFS4ERR_UNSAFE_COMPOUND = 10069, NFS4ERR_TOO_MANY_OPS = 10070, NFS4ERR_OP_NOT_IN_SESSION = 10071, NFS4ERR_HASH_ALG_UNSUPP = 10072, NFS4ERR_CLIENTID_BUSY = 10074, NFS4ERR_PNFS_IO_HOLE = 10075, NFS4ERR_SEQ_FALSE_RETRY = 10076, NFS4ERR_BAD_HIGH_SLOT = 10077, NFS4ERR_DEADSESSION = 10078, NFS4ERR_ENCR_ALG_UNSUPP = 10079, NFS4ERR_PNFS_NO_LAYOUT = 10080, NFS4ERR_NOT_ONLY_OP = 10081, NFS4ERR_WRONG_CRED = 10082, NFS4ERR_WRONG_TYPE = 10083, NFS4ERR_DIRDELEG_UNAVAIL = 10084, NFS4ERR_REJECT_DELEG = 10085, NFS4ERR_RETURNCONFLICT = 10086, NFS4ERR_DELEG_REVOKED = 10087, /* NFSv4.2 */ NFS4ERR_PARTNER_NOTSUPP = 10088, NFS4ERR_PARTNER_NO_AUTH = 10089, NFS4ERR_UNION_NOTSUPP = 10090, NFS4ERR_OFFLOAD_DENIED = 10091, NFS4ERR_WRONG_LFS = 10092, NFS4ERR_BADLABEL = 10093, NFS4ERR_OFFLOAD_NO_REQS = 10094, /* RFC 8276 (NFSv4.3-ish) */ NFS4ERR_NOXATTR = 10095, NFS4ERR_XATTR2BIG = 10096, NFS4ERR_REPLAY = 11001, }; typedef enum nfsstat4 nfsstat4; typedef struct { u_int attrlist4_len; char *attrlist4_val; } attrlist4; struct bitmap4 { u_int bitmap4_len; uint32_t map[BITMAP4_MAPLEN]; }; typedef struct bitmap4 bitmap4; typedef uint64_t changeid4; typedef uint64_t clientid4; typedef uint32_t count4; typedef uint64_t length4; typedef uint32_t mode4; typedef uint64_t nfs_cookie4; typedef struct { u_int nfs_fh4_len; char *nfs_fh4_val; } nfs_fh4; typedef uint64_t offset4; typedef uint32_t qop4; typedef struct { u_int sec_oid4_len; char *sec_oid4_val; } sec_oid4; typedef uint32_t sequenceid4; typedef uint32_t seqid4; typedef char sessionid4[NFS4_SESSIONID_SIZE]; typedef uint32_t slotid4; typedef struct { u_int utf8string_len; char *utf8string_val; } utf8string; typedef utf8string utf8str_cis; typedef utf8string utf8str_cs; typedef utf8string utf8str_mixed; typedef utf8str_cs component4; typedef utf8str_cs linktext4; static inline utf8string *utf8string_dup(utf8string *d, const char *s, size_t l) { if (s == NULL || l == 0) { d->utf8string_val = 0; d->utf8string_len = 0; return d; } d->utf8string_val = (char *)gsh_malloc(l + 1); d->utf8string_len = l; memcpy(d->utf8string_val, s, l + 1); return d; } typedef struct { u_int pathname4_len; component4 *pathname4_val; } pathname4; typedef char verifier4[NFS4_VERIFIER_SIZE]; struct nfstime4 { int64_t seconds; uint32_t nseconds; }; typedef struct nfstime4 nfstime4; enum time_how4 { SET_TO_SERVER_TIME4 = 0, SET_TO_CLIENT_TIME4 = 1, }; typedef enum time_how4 time_how4; struct settime4 { time_how4 set_it; union { nfstime4 time; } settime4_u; }; typedef struct settime4 settime4; typedef uint32_t nfs_lease4; struct fsid4 { uint64_t major; uint64_t minor; }; typedef struct fsid4 fsid4; struct change_policy4 { uint64_t cp_major; uint64_t cp_minor; }; typedef struct change_policy4 change_policy4; struct fs_location4 { struct { u_int server_len; utf8str_cis *server_val; } server; pathname4 rootpath; }; typedef struct fs_location4 fs_location4; struct fs_locations4 { pathname4 fs_root; struct { u_int locations_len; fs_location4 *locations_val; } locations; }; typedef struct fs_locations4 fs_locations4; #define ACL4_SUPPORT_ALLOW_ACL 0x00000001 #define ACL4_SUPPORT_DENY_ACL 0x00000002 #define ACL4_SUPPORT_AUDIT_ACL 0x00000004 #define ACL4_SUPPORT_ALARM_ACL 0x00000008 typedef uint32_t acetype4; #define ACE4_ACCESS_ALLOWED_ACE_TYPE 0x00000000 #define ACE4_ACCESS_DENIED_ACE_TYPE 0x00000001 #define ACE4_SYSTEM_AUDIT_ACE_TYPE 0x00000002 #define ACE4_SYSTEM_ALARM_ACE_TYPE 0x00000003 typedef uint32_t aceflag4; #define ACE4_FILE_INHERIT_ACE 0x00000001 #define ACE4_DIRECTORY_INHERIT_ACE 0x00000002 #define ACE4_NO_PROPAGATE_INHERIT_ACE 0x00000004 #define ACE4_INHERIT_ONLY_ACE 0x00000008 #define ACE4_SUCCESSFUL_ACCESS_ACE_FLAG 0x00000010 #define ACE4_FAILED_ACCESS_ACE_FLAG 0x00000020 #define ACE4_IDENTIFIER_GROUP 0x00000040 #define ACE4_INHERITED_ACE 0x00000080 typedef uint32_t acemask4; #define ACE4_READ_DATA 0x00000001 #define ACE4_LIST_DIRECTORY 0x00000001 #define ACE4_WRITE_DATA 0x00000002 #define ACE4_ADD_FILE 0x00000002 #define ACE4_APPEND_DATA 0x00000004 #define ACE4_ADD_SUBDIRECTORY 0x00000004 #define ACE4_READ_NAMED_ATTRS 0x00000008 #define ACE4_WRITE_NAMED_ATTRS 0x00000010 #define ACE4_EXECUTE 0x00000020 #define ACE4_DELETE_CHILD 0x00000040 #define ACE4_READ_ATTRIBUTES 0x00000080 #define ACE4_WRITE_ATTRIBUTES 0x00000100 #define ACE4_WRITE_RETENTION 0x00000200 #define ACE4_WRITE_RETENTION_HOLD 0x00000400 #define ACE4_DELETE 0x00010000 #define ACE4_READ_ACL 0x00020000 #define ACE4_WRITE_ACL 0x00040000 #define ACE4_WRITE_OWNER 0x00080000 #define ACE4_SYNCHRONIZE 0x00100000 #define ACE4_GENERIC_READ 0x00120081 #define ACE4_GENERIC_WRITE 0x00160106 #define ACE4_GENERIC_EXECUTE 0x001200A0 #define ACE4_READ_XATTRS 0x00200000 #define ACE4_WRITE_XATTRS 0x00400000 struct nfsace4 { acetype4 type; aceflag4 flag; acemask4 access_mask; utf8str_mixed who; }; typedef struct nfsace4 nfsace4; typedef uint32_t aclflag4; #define ACL4_AUTO_INHERIT 0x00000001 #define ACL4_PROTECTED 0x00000002 #define ACL4_DEFAULTED 0x00000004 struct nfsacl41 { aclflag4 na41_flag; struct { u_int na41_aces_len; nfsace4 *na41_aces_val; } na41_aces; }; typedef struct nfsacl41 nfsacl41; #define MODE4_SUID 0x800 #define MODE4_SGID 0x400 #define MODE4_SVTX 0x200 #define MODE4_RUSR 0x100 #define MODE4_WUSR 0x080 #define MODE4_XUSR 0x040 #define MODE4_RGRP 0x020 #define MODE4_WGRP 0x010 #define MODE4_XGRP 0x008 #define MODE4_ROTH 0x004 #define MODE4_WOTH 0x002 #define MODE4_XOTH 0x001 struct mode_masked4 { mode4 mm_value_to_set; mode4 mm_mask_bits; }; typedef struct mode_masked4 mode_masked4; struct specdata4 { uint32_t specdata1; uint32_t specdata2; }; typedef struct specdata4 specdata4; #define FH4_PERSISTENT 0x00000000 #define FH4_NOEXPIRE_WITH_OPEN 0x00000001 #define FH4_VOLATILE_ANY 0x00000002 #define FH4_VOL_MIGRATION 0x00000004 #define FH4_VOL_RENAME 0x00000008 struct netaddr4 { char *r_netid; char *r_addr; }; typedef struct netaddr4 netaddr4; struct nfs_impl_id4 { utf8str_cis nii_domain; utf8str_cs nii_name; nfstime4 nii_date; }; typedef struct nfs_impl_id4 nfs_impl_id4; struct stateid4 { uint32_t seqid; char other[12]; }; typedef struct stateid4 stateid4; enum layouttype4 { LAYOUT4_NFSV4_1_FILES = 0x1, LAYOUT4_OSD2_OBJECTS = 0x2, LAYOUT4_BLOCK_VOLUME = 0x3, LAYOUT4_FLEX_FILES = 0x4, }; typedef enum layouttype4 layouttype4; struct layout_content4 { layouttype4 loc_type; struct { u_int loc_body_len; char *loc_body_val; } loc_body; }; typedef struct layout_content4 layout_content4; /* * LAYOUT4_OSD2_OBJECTS loc_body description * is in a separate .x file */ /* * LAYOUT4_BLOCK_VOLUME loc_body description * is in a separate .x file */ struct layouthint4 { layouttype4 loh_type; struct { u_int loh_body_len; char *loh_body_val; } loh_body; }; typedef struct layouthint4 layouthint4; enum layoutiomode4 { LAYOUTIOMODE4_READ = 1, LAYOUTIOMODE4_RW = 2, LAYOUTIOMODE4_ANY = 3, }; typedef enum layoutiomode4 layoutiomode4; struct layout4 { offset4 lo_offset; length4 lo_length; layoutiomode4 lo_iomode; layout_content4 lo_content; }; typedef struct layout4 layout4; #define NFS4_DEVICEID4_SIZE 16 typedef char deviceid4[NFS4_DEVICEID4_SIZE]; struct device_addr4 { layouttype4 da_layout_type; struct { u_int da_addr_body_len; char *da_addr_body_val; } da_addr_body; }; typedef struct device_addr4 device_addr4; struct layoutupdate4 { layouttype4 lou_type; struct { u_int lou_body_len; char *lou_body_val; } lou_body; }; typedef struct layoutupdate4 layoutupdate4; #define LAYOUT4_RET_REC_FILE 1 #define LAYOUT4_RET_REC_FSID 2 #define LAYOUT4_RET_REC_ALL 3 enum layoutreturn_type4 { LAYOUTRETURN4_FILE = LAYOUT4_RET_REC_FILE, LAYOUTRETURN4_FSID = LAYOUT4_RET_REC_FSID, LAYOUTRETURN4_ALL = LAYOUT4_RET_REC_ALL, }; typedef enum layoutreturn_type4 layoutreturn_type4; /* layouttype4 specific data */ struct layoutreturn_file4 { offset4 lrf_offset; length4 lrf_length; stateid4 lrf_stateid; struct { u_int lrf_body_len; char *lrf_body_val; } lrf_body; }; typedef struct layoutreturn_file4 layoutreturn_file4; struct layoutreturn4 { layoutreturn_type4 lr_returntype; union { layoutreturn_file4 lr_layout; } layoutreturn4_u; }; typedef struct layoutreturn4 layoutreturn4; enum fs4_status_type { STATUS4_FIXED = 1, STATUS4_UPDATED = 2, STATUS4_VERSIONED = 3, STATUS4_WRITABLE = 4, STATUS4_REFERRAL = 5, }; typedef enum fs4_status_type fs4_status_type; struct fs4_status { bool_t fss_absent; fs4_status_type fss_type; utf8str_cs fss_source; utf8str_cs fss_current; int32_t fss_age; nfstime4 fss_version; }; typedef struct fs4_status fs4_status; #define TH4_READ_SIZE 0 #define TH4_WRITE_SIZE 1 #define TH4_READ_IOSIZE 2 #define TH4_WRITE_IOSIZE 3 typedef length4 threshold4_read_size; typedef length4 threshold4_write_size; typedef length4 threshold4_read_iosize; typedef length4 threshold4_write_iosize; struct threshold_item4 { layouttype4 thi_layout_type; struct bitmap4 thi_hintset; struct { u_int thi_hintlist_len; char *thi_hintlist_val; } thi_hintlist; }; typedef struct threshold_item4 threshold_item4; struct mdsthreshold4 { struct { u_int mth_hints_len; threshold_item4 *mth_hints_val; } mth_hints; }; typedef struct mdsthreshold4 mdsthreshold4; #define RET4_DURATION_INFINITE 0xffffffffffffffff struct retention_get4 { uint64_t rg_duration; struct { u_int rg_begin_time_len; nfstime4 *rg_begin_time_val; } rg_begin_time; }; typedef struct retention_get4 retention_get4; struct retention_set4 { bool_t rs_enable; struct { u_int rs_duration_len; uint64_t *rs_duration_val; } rs_duration; }; typedef struct retention_set4 retention_set4; #define FSCHARSET_CAP4_CONTAINS_NON_UTF8 0x1 #define FSCHARSET_CAP4_ALLOWS_ONLY_UTF8 0x2 typedef uint32_t fs_charset_cap4; typedef struct bitmap4 fattr4_supported_attrs; typedef nfs_ftype4 fattr4_type; typedef uint32_t fattr4_fh_expire_type; typedef changeid4 fattr4_change; typedef uint64_t fattr4_size; typedef bool_t fattr4_link_support; typedef bool_t fattr4_symlink_support; typedef bool_t fattr4_named_attr; typedef fsid4 fattr4_fsid; typedef bool_t fattr4_unique_handles; typedef nfs_lease4 fattr4_lease_time; typedef nfsstat4 fattr4_rdattr_error; typedef struct { u_int fattr4_acl_len; nfsace4 *fattr4_acl_val; } fattr4_acl; typedef uint32_t fattr4_aclsupport; typedef bool_t fattr4_archive; typedef bool_t fattr4_cansettime; typedef bool_t fattr4_case_insensitive; typedef bool_t fattr4_case_preserving; typedef bool_t fattr4_chown_restricted; typedef uint64_t fattr4_fileid; typedef uint64_t fattr4_files_avail; typedef nfs_fh4 fattr4_filehandle; typedef uint64_t fattr4_files_free; typedef uint64_t fattr4_files_total; typedef fs_locations4 fattr4_fs_locations; typedef bool_t fattr4_hidden; typedef bool_t fattr4_homogeneous; typedef uint64_t fattr4_maxfilesize; typedef uint32_t fattr4_maxlink; typedef uint32_t fattr4_maxname; typedef uint64_t fattr4_maxread; typedef uint64_t fattr4_maxwrite; typedef utf8str_cs fattr4_mimetype; typedef mode4 fattr4_mode; typedef mode_masked4 fattr4_mode_set_masked; typedef uint64_t fattr4_mounted_on_fileid; typedef bool_t fattr4_no_trunc; typedef uint32_t fattr4_numlinks; typedef utf8str_mixed fattr4_owner; typedef utf8str_mixed fattr4_owner_group; typedef uint64_t fattr4_quota_avail_hard; typedef uint64_t fattr4_quota_avail_soft; typedef uint64_t fattr4_quota_used; typedef specdata4 fattr4_rawdev; typedef uint64_t fattr4_space_avail; typedef uint64_t fattr4_space_free; typedef uint64_t fattr4_space_total; typedef uint64_t fattr4_space_used; typedef bool_t fattr4_system; typedef nfstime4 fattr4_time_access; typedef settime4 fattr4_time_access_set; typedef nfstime4 fattr4_time_backup; typedef nfstime4 fattr4_time_create; typedef nfstime4 fattr4_time_delta; typedef nfstime4 fattr4_time_metadata; typedef nfstime4 fattr4_time_modify; typedef settime4 fattr4_time_modify_set; typedef struct bitmap4 fattr4_suppattr_exclcreat; typedef nfstime4 fattr4_dir_notif_delay; typedef nfstime4 fattr4_dirent_notif_delay; typedef struct { u_int fattr4_fs_layout_types_len; layouttype4 *fattr4_fs_layout_types_val; } fattr4_fs_layout_types; typedef fs4_status fattr4_fs_status; typedef fs_charset_cap4 fattr4_fs_charset_cap; typedef uint32_t fattr4_layout_alignment; typedef uint32_t fattr4_layout_blksize; typedef layouthint4 fattr4_layout_hint; typedef struct { u_int fattr4_layout_types_len; layouttype4 *fattr4_layout_types_val; } fattr4_layout_types; typedef mdsthreshold4 fattr4_mdsthreshold; typedef retention_get4 fattr4_retention_get; typedef retention_set4 fattr4_retention_set; typedef retention_get4 fattr4_retentevt_get; typedef retention_set4 fattr4_retentevt_set; typedef uint64_t fattr4_retention_hold; typedef nfsacl41 fattr4_dacl; typedef nfsacl41 fattr4_sacl; typedef change_policy4 fattr4_change_policy; typedef uint32_t policy4; struct labelformat_spec4 { policy4 lfs_lfs; policy4 lfs_pi; }; struct sec_label4 { struct labelformat_spec4 slai_lfs; struct { u_int slai_data_len; char *slai_data_val; } slai_data; }; typedef struct sec_label4 fattr4_sec_label; /* * REQUIRED Attributes */ #define FATTR4_SUPPORTED_ATTRS 0 #define FATTR4_TYPE 1 #define FATTR4_FH_EXPIRE_TYPE 2 #define FATTR4_CHANGE 3 #define FATTR4_SIZE 4 #define FATTR4_LINK_SUPPORT 5 #define FATTR4_SYMLINK_SUPPORT 6 #define FATTR4_NAMED_ATTR 7 #define FATTR4_FSID 8 #define FATTR4_UNIQUE_HANDLES 9 #define FATTR4_LEASE_TIME 10 #define FATTR4_RDATTR_ERROR 11 #define FATTR4_FILEHANDLE 19 /* new to NFSV4.1 */ #define FATTR4_SUPPATTR_EXCLCREAT 75 /* * RECOMMENDED Attributes */ #define FATTR4_ACL 12 #define FATTR4_ACLSUPPORT 13 #define FATTR4_ARCHIVE 14 #define FATTR4_CANSETTIME 15 #define FATTR4_CASE_INSENSITIVE 16 #define FATTR4_CASE_PRESERVING 17 #define FATTR4_CHOWN_RESTRICTED 18 #define FATTR4_FILEID 20 #define FATTR4_FILES_AVAIL 21 #define FATTR4_FILES_FREE 22 #define FATTR4_FILES_TOTAL 23 #define FATTR4_FS_LOCATIONS 24 #define FATTR4_HIDDEN 25 #define FATTR4_HOMOGENEOUS 26 #define FATTR4_MAXFILESIZE 27 #define FATTR4_MAXLINK 28 #define FATTR4_MAXNAME 29 #define FATTR4_MAXREAD 30 #define FATTR4_MAXWRITE 31 #define FATTR4_MIMETYPE 32 #define FATTR4_MODE 33 #define FATTR4_NO_TRUNC 34 #define FATTR4_NUMLINKS 35 #define FATTR4_OWNER 36 #define FATTR4_OWNER_GROUP 37 #define FATTR4_QUOTA_AVAIL_HARD 38 #define FATTR4_QUOTA_AVAIL_SOFT 39 #define FATTR4_QUOTA_USED 40 #define FATTR4_RAWDEV 41 #define FATTR4_SPACE_AVAIL 42 #define FATTR4_SPACE_FREE 43 #define FATTR4_SPACE_TOTAL 44 #define FATTR4_SPACE_USED 45 #define FATTR4_SYSTEM 46 #define FATTR4_TIME_ACCESS 47 #define FATTR4_TIME_ACCESS_SET 48 #define FATTR4_TIME_BACKUP 49 #define FATTR4_TIME_CREATE 50 #define FATTR4_TIME_DELTA 51 #define FATTR4_TIME_METADATA 52 #define FATTR4_TIME_MODIFY 53 #define FATTR4_TIME_MODIFY_SET 54 #define FATTR4_MOUNTED_ON_FILEID 55 /* new to NFSV4.1 */ #define FATTR4_DIR_NOTIF_DELAY 56 #define FATTR4_DIRENT_NOTIF_DELAY 57 #define FATTR4_DACL 58 #define FATTR4_SACL 59 #define FATTR4_CHANGE_POLICY 60 #define FATTR4_FS_STATUS 61 #define FATTR4_FS_LAYOUT_TYPES 62 #define FATTR4_LAYOUT_HINT 63 #define FATTR4_LAYOUT_TYPES 64 #define FATTR4_LAYOUT_BLKSIZE 65 #define FATTR4_LAYOUT_ALIGNMENT 66 #define FATTR4_FS_LOCATIONS_INFO 67 #define FATTR4_MDSTHRESHOLD 68 #define FATTR4_RETENTION_GET 69 #define FATTR4_RETENTION_SET 70 #define FATTR4_RETENTEVT_GET 71 #define FATTR4_RETENTEVT_SET 72 #define FATTR4_RETENTION_HOLD 73 #define FATTR4_MODE_SET_MASKED 74 #define FATTR4_FS_CHARSET_CAP 76 /* NFSv4.2 */ #define FATTR4_CLONE_BLKSIZE 77 #define FATTR4_SPACE_FREED 78 #define FATTR4_CHANGE_ATTR_TYPE 79 #define FATTR4_SEC_LABEL 80 /* NFSv4.3 */ #define FATTR4_XATTR_SUPPORT 82 /* Largest defined attribute index */ #define FATTR4_MAX_ATTR_INDEX FATTR4_XATTR_SUPPORT /* Restricted attrs on an absent FS */ #define FATTR4_RESTRICTED_ATTRS \ (FATTR4_MOUNTED_ON_FILEID | FATTR4_FSID | FATTR4_RDATTR_ERROR | \ FATTR4_FS_LOCATIONS) #define FATTR4_RESTRICTED_ATTRS_SIZE \ (fattr4tab[FATTR4_MOUNTED_ON_FILEID].size_fattr4 + \ fattr4tab[FATTR4_FSID].size_fattr4 + \ fattr4tab[FATTR4_RDATTR_ERROR].size_fattr4 + \ fattr4tab[FATTR4_FS_LOCATIONS].size_fattr4) struct fattr4 { struct bitmap4 attrmask; attrlist4 attr_vals; }; typedef struct fattr4 fattr4; struct change_info4 { bool_t atomic; changeid4 before; changeid4 after; }; typedef struct change_info4 change_info4; typedef netaddr4 clientaddr4; struct cb_client4 { uint32_t cb_program; netaddr4 cb_location; }; typedef struct cb_client4 cb_client4; struct nfs_client_id4 { verifier4 verifier; struct { u_int id_len; char *id_val; } id; }; typedef struct nfs_client_id4 nfs_client_id4; struct client_owner4 { verifier4 co_verifier; struct { u_int co_ownerid_len; char *co_ownerid_val; } co_ownerid; }; typedef struct client_owner4 client_owner4; struct server_owner4 { uint64_t so_minor_id; struct { u_int so_major_id_len; char *so_major_id_val; } so_major_id; }; typedef struct server_owner4 server_owner4; struct state_owner4 { clientid4 clientid; struct { u_int owner_len; char *owner_val; } owner; }; typedef struct state_owner4 state_owner4; typedef state_owner4 open_owner4; typedef state_owner4 lock_owner4; enum nfs_lock_type4 { READ_LT = 1, WRITE_LT = 2, READW_LT = 3, WRITEW_LT = 4, }; typedef enum nfs_lock_type4 nfs_lock_type4; /* Input for computing subkeys */ enum ssv_subkey4 { SSV4_SUBKEY_MIC_I2T = 1, SSV4_SUBKEY_MIC_T2I = 2, SSV4_SUBKEY_SEAL_I2T = 3, SSV4_SUBKEY_SEAL_T2I = 4, }; typedef enum ssv_subkey4 ssv_subkey4; /* Input for computing smt_hmac */ struct ssv_mic_plain_tkn4 { uint32_t smpt_ssv_seq; struct { u_int smpt_orig_plain_len; char *smpt_orig_plain_val; } smpt_orig_plain; }; typedef struct ssv_mic_plain_tkn4 ssv_mic_plain_tkn4; /* SSV GSS PerMsgToken token */ struct ssv_mic_tkn4 { uint32_t smt_ssv_seq; struct { u_int smt_hmac_len; char *smt_hmac_val; } smt_hmac; }; typedef struct ssv_mic_tkn4 ssv_mic_tkn4; /* Input for computing ssct_encr_data and ssct_hmac */ struct ssv_seal_plain_tkn4 { struct { u_int sspt_confounder_len; char *sspt_confounder_val; } sspt_confounder; uint32_t sspt_ssv_seq; struct { u_int sspt_orig_plain_len; char *sspt_orig_plain_val; } sspt_orig_plain; struct { u_int sspt_pad_len; char *sspt_pad_val; } sspt_pad; }; typedef struct ssv_seal_plain_tkn4 ssv_seal_plain_tkn4; /* SSV GSS SealedMessage token */ struct ssv_seal_cipher_tkn4 { uint32_t ssct_ssv_seq; struct { u_int ssct_iv_len; char *ssct_iv_val; } ssct_iv; struct { u_int ssct_encr_data_len; char *ssct_encr_data_val; } ssct_encr_data; struct { u_int ssct_hmac_len; char *ssct_hmac_val; } ssct_hmac; }; typedef struct ssv_seal_cipher_tkn4 ssv_seal_cipher_tkn4; struct fs_locations_server4 { int32_t fls_currency; struct { u_int fls_info_len; char *fls_info_val; } fls_info; utf8str_cis fls_server; }; typedef struct fs_locations_server4 fs_locations_server4; #define FSLI4BX_GFLAGS 0 #define FSLI4BX_TFLAGS 1 #define FSLI4BX_CLSIMUL 2 #define FSLI4BX_CLHANDLE 3 #define FSLI4BX_CLFILEID 4 #define FSLI4BX_CLWRITEVER 5 #define FSLI4BX_CLCHANGE 6 #define FSLI4BX_CLREADDIR 7 #define FSLI4BX_READRANK 8 #define FSLI4BX_WRITERANK 9 #define FSLI4BX_READORDER 10 #define FSLI4BX_WRITEORDER 11 #define FSLI4GF_WRITABLE 0x01 #define FSLI4GF_CUR_REQ 0x02 #define FSLI4GF_ABSENT 0x04 #define FSLI4GF_GOING 0x08 #define FSLI4GF_SPLIT 0x10 #define FSLI4TF_RDMA 0x01 struct fs_locations_item4 { struct { u_int fli_entries_len; fs_locations_server4 *fli_entries_val; } fli_entries; pathname4 fli_rootpath; }; typedef struct fs_locations_item4 fs_locations_item4; struct fs_locations_info4 { uint32_t fli_flags; int32_t fli_valid_for; pathname4 fli_fs_root; struct { u_int fli_items_len; fs_locations_item4 *fli_items_val; } fli_items; }; typedef struct fs_locations_info4 fs_locations_info4; #define FSLI4IF_VAR_SUB 0x00000001 typedef fs_locations_info4 fattr4_fs_locations_info; #define NFL4_UFLG_MASK 0x0000003F #define NFL4_UFLG_DENSE 0x00000001 #define NFL4_UFLG_COMMIT_THRU_MDS 0x00000002 #define NFL4_UFLG_STRIPE_UNIT_SIZE_MASK 0xFFFFFFC0 typedef uint32_t nfl_util4; enum filelayout_hint_care4 { NFLH4_CARE_DENSE = NFL4_UFLG_DENSE, NFLH4_CARE_COMMIT_THRU_MDS = NFL4_UFLG_COMMIT_THRU_MDS, NFLH4_CARE_STRIPE_UNIT_SIZE = 0x00000040, NFLH4_CARE_STRIPE_COUNT = 0x00000080, }; typedef enum filelayout_hint_care4 filelayout_hint_care4; /* Encoded in the loh_body field of data type layouthint4: */ struct nfsv4_1_file_layouthint4 { uint32_t nflh_care; nfl_util4 nflh_util; count4 nflh_stripe_count; }; typedef struct nfsv4_1_file_layouthint4 nfsv4_1_file_layouthint4; typedef struct { u_int multipath_list4_len; netaddr4 *multipath_list4_val; } multipath_list4; /* * Encoded in the da_addr_body field of * data type device_addr4: */ struct nfsv4_1_file_layout_ds_addr4 { struct { u_int nflda_stripe_indices_len; uint32_t *nflda_stripe_indices_val; } nflda_stripe_indices; struct { u_int nflda_multipath_ds_list_len; multipath_list4 *nflda_multipath_ds_list_val; } nflda_multipath_ds_list; }; typedef struct nfsv4_1_file_layout_ds_addr4 nfsv4_1_file_layout_ds_addr4; /* * Encoded in the loc_body field of * data type layout_content4: */ struct nfsv4_1_file_layout4 { deviceid4 nfl_deviceid; nfl_util4 nfl_util; uint32_t nfl_first_stripe_index; offset4 nfl_pattern_offset; struct { u_int nfl_fh_list_len; nfs_fh4 *nfl_fh_list_val; } nfl_fh_list; }; typedef struct nfsv4_1_file_layout4 nfsv4_1_file_layout4; /* * Encoded in the lou_body field of data type layoutupdate4: * Nothing. lou_body is a zero length array of bytes. */ /* * Encoded in the lrf_body field of * data type layoutreturn_file4: * Nothing. lrf_body is a zero length array of bytes. */ #define ACCESS4_READ 0x00000001 #define ACCESS4_LOOKUP 0x00000002 #define ACCESS4_MODIFY 0x00000004 #define ACCESS4_EXTEND 0x00000008 #define ACCESS4_DELETE 0x00000010 #define ACCESS4_EXECUTE 0x00000020 #define ACCESS4_XAREAD 0x00000040 #define ACCESS4_XAWRITE 0x00000080 #define ACCESS4_XALIST 0x00000100 struct ACCESS4args { uint32_t access; }; typedef struct ACCESS4args ACCESS4args; struct ACCESS4resok { uint32_t supported; uint32_t access; }; typedef struct ACCESS4resok ACCESS4resok; struct ACCESS4res { nfsstat4 status; union { ACCESS4resok resok4; } ACCESS4res_u; }; typedef struct ACCESS4res ACCESS4res; struct CLOSE4args { seqid4 seqid; stateid4 open_stateid; }; typedef struct CLOSE4args CLOSE4args; struct CLOSE4res { nfsstat4 status; union { stateid4 open_stateid; } CLOSE4res_u; }; typedef struct CLOSE4res CLOSE4res; struct COMMIT4args { offset4 offset; count4 count; }; typedef struct COMMIT4args COMMIT4args; struct COMMIT4resok { verifier4 writeverf; }; typedef struct COMMIT4resok COMMIT4resok; struct COMMIT4res { nfsstat4 status; union { COMMIT4resok resok4; } COMMIT4res_u; }; typedef struct COMMIT4res COMMIT4res; struct createtype4 { nfs_ftype4 type; union { linktext4 linkdata; specdata4 devdata; } createtype4_u; }; typedef struct createtype4 createtype4; struct CREATE4args { createtype4 objtype; component4 objname; fattr4 createattrs; }; typedef struct CREATE4args CREATE4args; struct CREATE4resok { change_info4 cinfo; struct bitmap4 attrset; }; typedef struct CREATE4resok CREATE4resok; struct CREATE4res { nfsstat4 status; union { CREATE4resok resok4; } CREATE4res_u; }; typedef struct CREATE4res CREATE4res; struct DELEGPURGE4args { clientid4 clientid; }; typedef struct DELEGPURGE4args DELEGPURGE4args; struct DELEGPURGE4res { nfsstat4 status; }; typedef struct DELEGPURGE4res DELEGPURGE4res; struct DELEGRETURN4args { stateid4 deleg_stateid; }; typedef struct DELEGRETURN4args DELEGRETURN4args; struct DELEGRETURN4res { nfsstat4 status; }; typedef struct DELEGRETURN4res DELEGRETURN4res; struct GETATTR4args { struct bitmap4 attr_request; }; typedef struct GETATTR4args GETATTR4args; struct GETATTR4resok { fattr4 obj_attributes; }; typedef struct GETATTR4resok GETATTR4resok; struct GETATTR4res { nfsstat4 status; union { GETATTR4resok resok4; } GETATTR4res_u; }; typedef struct GETATTR4res GETATTR4res; struct GETFH4resok { nfs_fh4 object; }; typedef struct GETFH4resok GETFH4resok; struct GETFH4res { nfsstat4 status; union { GETFH4resok resok4; } GETFH4res_u; }; typedef struct GETFH4res GETFH4res; struct LINK4args { component4 newname; }; typedef struct LINK4args LINK4args; struct LINK4resok { change_info4 cinfo; }; typedef struct LINK4resok LINK4resok; struct LINK4res { nfsstat4 status; union { LINK4resok resok4; } LINK4res_u; }; typedef struct LINK4res LINK4res; struct open_to_lock_owner4 { seqid4 open_seqid; stateid4 open_stateid; seqid4 lock_seqid; lock_owner4 lock_owner; }; typedef struct open_to_lock_owner4 open_to_lock_owner4; struct exist_lock_owner4 { stateid4 lock_stateid; seqid4 lock_seqid; }; typedef struct exist_lock_owner4 exist_lock_owner4; struct locker4 { bool_t new_lock_owner; union { open_to_lock_owner4 open_owner; exist_lock_owner4 lock_owner; } locker4_u; }; typedef struct locker4 locker4; struct LOCK4args { nfs_lock_type4 locktype; bool_t reclaim; offset4 offset; length4 length; locker4 locker; }; typedef struct LOCK4args LOCK4args; struct LOCK4denied { offset4 offset; length4 length; nfs_lock_type4 locktype; lock_owner4 owner; }; typedef struct LOCK4denied LOCK4denied; struct LOCK4resok { stateid4 lock_stateid; }; typedef struct LOCK4resok LOCK4resok; struct LOCK4res { nfsstat4 status; union { LOCK4resok resok4; LOCK4denied denied; } LOCK4res_u; }; typedef struct LOCK4res LOCK4res; struct LOCKT4args { nfs_lock_type4 locktype; offset4 offset; length4 length; lock_owner4 owner; }; typedef struct LOCKT4args LOCKT4args; struct LOCKT4res { nfsstat4 status; union { LOCK4denied denied; } LOCKT4res_u; }; typedef struct LOCKT4res LOCKT4res; struct LOCKU4args { nfs_lock_type4 locktype; seqid4 seqid; stateid4 lock_stateid; offset4 offset; length4 length; }; typedef struct LOCKU4args LOCKU4args; struct LOCKU4res { nfsstat4 status; union { stateid4 lock_stateid; } LOCKU4res_u; }; typedef struct LOCKU4res LOCKU4res; struct LOOKUP4args { component4 objname; }; typedef struct LOOKUP4args LOOKUP4args; struct LOOKUP4res { nfsstat4 status; }; typedef struct LOOKUP4res LOOKUP4res; struct LOOKUPP4res { nfsstat4 status; }; typedef struct LOOKUPP4res LOOKUPP4res; struct NVERIFY4args { fattr4 obj_attributes; }; typedef struct NVERIFY4args NVERIFY4args; struct NVERIFY4res { nfsstat4 status; }; typedef struct NVERIFY4res NVERIFY4res; enum createmode4 { UNCHECKED4 = 0, GUARDED4 = 1, EXCLUSIVE4 = 2, EXCLUSIVE4_1 = 3, }; typedef enum createmode4 createmode4; struct creatverfattr { verifier4 cva_verf; fattr4 cva_attrs; }; typedef struct creatverfattr creatverfattr; struct createhow4 { createmode4 mode; union { fattr4 createattrs; verifier4 createverf; creatverfattr ch_createboth; } createhow4_u; }; typedef struct createhow4 createhow4; enum opentype4 { OPEN4_NOCREATE = 0, OPEN4_CREATE = 1, }; typedef enum opentype4 opentype4; struct openflag4 { opentype4 opentype; union { createhow4 how; } openflag4_u; }; typedef struct openflag4 openflag4; enum limit_by4 { NFS_LIMIT_SIZE = 1, NFS_LIMIT_BLOCKS = 2, }; typedef enum limit_by4 limit_by4; struct nfs_modified_limit4 { uint32_t num_blocks; uint32_t bytes_per_block; }; typedef struct nfs_modified_limit4 nfs_modified_limit4; struct nfs_space_limit4 { limit_by4 limitby; union { uint64_t filesize; nfs_modified_limit4 mod_blocks; } nfs_space_limit4_u; }; typedef struct nfs_space_limit4 nfs_space_limit4; #define OPEN4_SHARE_ACCESS_READ 0x00000001 #define OPEN4_SHARE_ACCESS_WRITE 0x00000002 #define OPEN4_SHARE_ACCESS_BOTH 0x00000003 #define OPEN4_SHARE_ACCESS_ALL 0x00000004 #define OPEN4_SHARE_DENY_NONE 0x00000000 #define OPEN4_SHARE_DENY_READ 0x00000001 #define OPEN4_SHARE_DENY_WRITE 0x00000002 #define OPEN4_SHARE_DENY_BOTH 0x00000003 #define OPEN4_SHARE_DENY_ALL 0x00000004 #define OPEN4_SHARE_ACCESS_WANT_DELEG_MASK 0xFF00 #define OPEN4_SHARE_ACCESS_WANT_NO_PREFERENCE 0x0000 #define OPEN4_SHARE_ACCESS_WANT_READ_DELEG 0x0100 #define OPEN4_SHARE_ACCESS_WANT_WRITE_DELEG 0x0200 #define OPEN4_SHARE_ACCESS_WANT_ANY_DELEG 0x0300 #define OPEN4_SHARE_ACCESS_WANT_NO_DELEG 0x0400 #define OPEN4_SHARE_ACCESS_WANT_CANCEL 0x0500 #define OPEN4_SHARE_ACCESS_WANT_SIGNAL_DELEG_WHEN_RESRC_AVAIL 0x10000 #define OPEN4_SHARE_ACCESS_WANT_PUSH_DELEG_WHEN_UNCONTENDED 0x20000 enum open_delegation_type4 { OPEN_DELEGATE_NONE = 0, OPEN_DELEGATE_READ = 1, OPEN_DELEGATE_WRITE = 2, OPEN_DELEGATE_NONE_EXT = 3, }; typedef enum open_delegation_type4 open_delegation_type4; enum open_claim_type4 { CLAIM_NULL = 0, CLAIM_PREVIOUS = 1, CLAIM_DELEGATE_CUR = 2, CLAIM_DELEGATE_PREV = 3, CLAIM_FH = 4, CLAIM_DELEG_CUR_FH = 5, CLAIM_DELEG_PREV_FH = 6, }; typedef enum open_claim_type4 open_claim_type4; struct open_claim_delegate_cur4 { stateid4 delegate_stateid; component4 file; }; typedef struct open_claim_delegate_cur4 open_claim_delegate_cur4; struct open_claim4 { open_claim_type4 claim; union { component4 file; open_delegation_type4 delegate_type; open_claim_delegate_cur4 delegate_cur_info; component4 file_delegate_prev; stateid4 oc_delegate_stateid; } open_claim4_u; }; typedef struct open_claim4 open_claim4; struct OPEN4args { seqid4 seqid; uint32_t share_access; uint32_t share_deny; open_owner4 owner; openflag4 openhow; open_claim4 claim; }; typedef struct OPEN4args OPEN4args; struct open_read_delegation4 { stateid4 stateid; bool_t recall; nfsace4 permissions; }; typedef struct open_read_delegation4 open_read_delegation4; struct open_write_delegation4 { stateid4 stateid; bool_t recall; nfs_space_limit4 space_limit; nfsace4 permissions; }; typedef struct open_write_delegation4 open_write_delegation4; enum why_no_delegation4 { WND4_NOT_WANTED = 0, WND4_CONTENTION = 1, WND4_RESOURCE = 2, WND4_NOT_SUPP_FTYPE = 3, WND4_WRITE_DELEG_NOT_SUPP_FTYPE = 4, WND4_NOT_SUPP_UPGRADE = 5, WND4_NOT_SUPP_DOWNGRADE = 6, WND4_CANCELLED = 7, WND4_IS_DIR = 8, }; typedef enum why_no_delegation4 why_no_delegation4; struct open_none_delegation4 { why_no_delegation4 ond_why; union { bool_t ond_server_will_push_deleg; bool_t ond_server_will_signal; } open_none_delegation4_u; }; typedef struct open_none_delegation4 open_none_delegation4; struct open_delegation4 { open_delegation_type4 delegation_type; union { open_read_delegation4 read; open_write_delegation4 write; open_none_delegation4 od_whynone; } open_delegation4_u; }; typedef struct open_delegation4 open_delegation4; #define OPEN4_RESULT_CONFIRM 0x00000002 #define OPEN4_RESULT_LOCKTYPE_POSIX 0x00000004 #define OPEN4_RESULT_PRESERVE_UNLINKED 0x00000008 #define OPEN4_RESULT_MAY_NOTIFY_LOCK 0x00000020 struct OPEN4resok { stateid4 stateid; change_info4 cinfo; uint32_t rflags; struct bitmap4 attrset; open_delegation4 delegation; }; typedef struct OPEN4resok OPEN4resok; struct OPEN4res { nfsstat4 status; union { OPEN4resok resok4; } OPEN4res_u; }; typedef struct OPEN4res OPEN4res; struct OPENATTR4args { bool_t createdir; }; typedef struct OPENATTR4args OPENATTR4args; struct OPENATTR4res { nfsstat4 status; }; typedef struct OPENATTR4res OPENATTR4res; struct OPEN_CONFIRM4args { stateid4 open_stateid; seqid4 seqid; }; typedef struct OPEN_CONFIRM4args OPEN_CONFIRM4args; struct OPEN_CONFIRM4resok { stateid4 open_stateid; }; typedef struct OPEN_CONFIRM4resok OPEN_CONFIRM4resok; struct OPEN_CONFIRM4res { nfsstat4 status; union { OPEN_CONFIRM4resok resok4; } OPEN_CONFIRM4res_u; }; typedef struct OPEN_CONFIRM4res OPEN_CONFIRM4res; struct OPEN_DOWNGRADE4args { stateid4 open_stateid; seqid4 seqid; uint32_t share_access; uint32_t share_deny; }; typedef struct OPEN_DOWNGRADE4args OPEN_DOWNGRADE4args; struct OPEN_DOWNGRADE4resok { stateid4 open_stateid; }; typedef struct OPEN_DOWNGRADE4resok OPEN_DOWNGRADE4resok; struct OPEN_DOWNGRADE4res { nfsstat4 status; union { OPEN_DOWNGRADE4resok resok4; } OPEN_DOWNGRADE4res_u; }; typedef struct OPEN_DOWNGRADE4res OPEN_DOWNGRADE4res; struct PUTFH4args { nfs_fh4 object; }; typedef struct PUTFH4args PUTFH4args; struct PUTFH4res { nfsstat4 status; }; typedef struct PUTFH4res PUTFH4res; struct PUTPUBFH4res { nfsstat4 status; }; typedef struct PUTPUBFH4res PUTPUBFH4res; struct PUTROOTFH4res { nfsstat4 status; }; typedef struct PUTROOTFH4res PUTROOTFH4res; struct READ4args { stateid4 stateid; offset4 offset; count4 count; }; typedef struct READ4args READ4args; struct READ4resok { bool_t eof; struct iovec iov0; io_data data; }; typedef struct READ4resok READ4resok; struct READ4res { nfsstat4 status; union { READ4resok resok4; } READ4res_u; }; typedef struct READ4res READ4res; struct READDIR4args { nfs_cookie4 cookie; verifier4 cookieverf; count4 dircount; count4 maxcount; struct bitmap4 attr_request; }; typedef struct READDIR4args READDIR4args; struct entry4 { nfs_cookie4 cookie; component4 name; fattr4 attrs; struct entry4 *nextentry; }; typedef struct entry4 entry4; struct dirlist4 { entry4 *entries; xdr_uio *uio; bool_t eof; }; typedef struct dirlist4 dirlist4; struct READDIR4resok { verifier4 cookieverf; dirlist4 reply; }; typedef struct READDIR4resok READDIR4resok; struct READDIR4res { nfsstat4 status; union { READDIR4resok resok4; } READDIR4res_u; }; typedef struct READDIR4res READDIR4res; struct READLINK4resok { linktext4 link; }; typedef struct READLINK4resok READLINK4resok; struct READLINK4res { nfsstat4 status; union { READLINK4resok resok4; } READLINK4res_u; }; typedef struct READLINK4res READLINK4res; struct REMOVE4args { component4 target; }; typedef struct REMOVE4args REMOVE4args; struct REMOVE4resok { change_info4 cinfo; }; typedef struct REMOVE4resok REMOVE4resok; struct REMOVE4res { nfsstat4 status; union { REMOVE4resok resok4; } REMOVE4res_u; }; typedef struct REMOVE4res REMOVE4res; struct RENAME4args { component4 oldname; component4 newname; }; typedef struct RENAME4args RENAME4args; struct RENAME4resok { change_info4 source_cinfo; change_info4 target_cinfo; }; typedef struct RENAME4resok RENAME4resok; struct RENAME4res { nfsstat4 status; union { RENAME4resok resok4; } RENAME4res_u; }; typedef struct RENAME4res RENAME4res; struct RENEW4args { clientid4 clientid; }; typedef struct RENEW4args RENEW4args; struct RENEW4res { nfsstat4 status; }; typedef struct RENEW4res RENEW4res; struct RESTOREFH4res { nfsstat4 status; }; typedef struct RESTOREFH4res RESTOREFH4res; struct SAVEFH4res { nfsstat4 status; }; typedef struct SAVEFH4res SAVEFH4res; struct SECINFO4args { component4 name; }; typedef struct SECINFO4args SECINFO4args; #ifdef _HAVE_GSSAPI struct rpcsec_gss_info { sec_oid4 oid; qop4 qop; rpc_gss_svc_t service; }; typedef struct rpcsec_gss_info rpcsec_gss_info; #endif /* _HAVE_GSSAPI */ struct secinfo4 { uint32_t flavor; union { #ifdef _HAVE_GSSAPI rpcsec_gss_info flavor_info; #endif /* _HAVE_GSSAPI */ uint32_t unused; } secinfo4_u; }; typedef struct secinfo4 secinfo4; typedef struct { u_int SECINFO4resok_len; secinfo4 *SECINFO4resok_val; } SECINFO4resok; struct SECINFO4res { nfsstat4 status; union { SECINFO4resok resok4; } SECINFO4res_u; }; typedef struct SECINFO4res SECINFO4res; struct SETATTR4args { stateid4 stateid; fattr4 obj_attributes; }; typedef struct SETATTR4args SETATTR4args; struct SETATTR4res { nfsstat4 status; struct bitmap4 attrsset; }; typedef struct SETATTR4res SETATTR4res; struct SETCLIENTID4args { nfs_client_id4 client; cb_client4 callback; uint32_t callback_ident; }; typedef struct SETCLIENTID4args SETCLIENTID4args; struct SETCLIENTID4resok { clientid4 clientid; verifier4 setclientid_confirm; }; typedef struct SETCLIENTID4resok SETCLIENTID4resok; struct SETCLIENTID4res { nfsstat4 status; union { SETCLIENTID4resok resok4; clientaddr4 client_using; } SETCLIENTID4res_u; }; typedef struct SETCLIENTID4res SETCLIENTID4res; struct SETCLIENTID_CONFIRM4args { clientid4 clientid; verifier4 setclientid_confirm; }; typedef struct SETCLIENTID_CONFIRM4args SETCLIENTID_CONFIRM4args; struct SETCLIENTID_CONFIRM4res { nfsstat4 status; }; typedef struct SETCLIENTID_CONFIRM4res SETCLIENTID_CONFIRM4res; struct VERIFY4args { fattr4 obj_attributes; }; typedef struct VERIFY4args VERIFY4args; struct VERIFY4res { nfsstat4 status; }; typedef struct VERIFY4res VERIFY4res; enum stable_how4 { UNSTABLE4 = 0, DATA_SYNC4 = 1, FILE_SYNC4 = 2, }; typedef enum stable_how4 stable_how4; struct WRITE4args { stateid4 stateid; offset4 offset; stable_how4 stable; io_data data; }; typedef struct WRITE4args WRITE4args; struct WRITE4resok { count4 count; stable_how4 committed; verifier4 writeverf; }; typedef struct WRITE4resok WRITE4resok; struct WRITE4res { nfsstat4 status; union { WRITE4resok resok4; } WRITE4res_u; }; typedef struct WRITE4res WRITE4res; struct RELEASE_LOCKOWNER4args { lock_owner4 lock_owner; }; typedef struct RELEASE_LOCKOWNER4args RELEASE_LOCKOWNER4args; struct RELEASE_LOCKOWNER4res { nfsstat4 status; }; typedef struct RELEASE_LOCKOWNER4res RELEASE_LOCKOWNER4res; struct ILLEGAL4res { nfsstat4 status; }; typedef struct ILLEGAL4res ILLEGAL4res; typedef struct { u_int gsshandle4_t_len; char *gsshandle4_t_val; } gsshandle4_t; struct gss_cb_handles4 { #ifdef _HAVE_GSSAPI rpc_gss_svc_t gcbp_service; #endif /* _HAVE_GSSAPI */ gsshandle4_t gcbp_handle_from_server; gsshandle4_t gcbp_handle_from_client; }; typedef struct gss_cb_handles4 gss_cb_handles4; struct callback_sec_parms4 { uint32_t cb_secflavor; union { struct authunix_parms cbsp_sys_cred; gss_cb_handles4 cbsp_gss_handles; } callback_sec_parms4_u; }; typedef struct callback_sec_parms4 callback_sec_parms4; struct BACKCHANNEL_CTL4args { uint32_t bca_cb_program; struct { u_int bca_sec_parms_len; callback_sec_parms4 *bca_sec_parms_val; } bca_sec_parms; }; typedef struct BACKCHANNEL_CTL4args BACKCHANNEL_CTL4args; struct BACKCHANNEL_CTL4res { nfsstat4 bcr_status; }; typedef struct BACKCHANNEL_CTL4res BACKCHANNEL_CTL4res; enum channel_dir_from_client4 { CDFC4_FORE = 0x1, CDFC4_BACK = 0x2, CDFC4_FORE_OR_BOTH = 0x3, CDFC4_BACK_OR_BOTH = 0x7, }; typedef enum channel_dir_from_client4 channel_dir_from_client4; struct BIND_CONN_TO_SESSION4args { sessionid4 bctsa_sessid; channel_dir_from_client4 bctsa_dir; bool_t bctsa_use_conn_in_rdma_mode; }; typedef struct BIND_CONN_TO_SESSION4args BIND_CONN_TO_SESSION4args; enum channel_dir_from_server4 { CDFS4_FORE = 0x1, CDFS4_BACK = 0x2, CDFS4_BOTH = 0x3, }; typedef enum channel_dir_from_server4 channel_dir_from_server4; struct BIND_CONN_TO_SESSION4resok { sessionid4 bctsr_sessid; channel_dir_from_server4 bctsr_dir; bool_t bctsr_use_conn_in_rdma_mode; }; typedef struct BIND_CONN_TO_SESSION4resok BIND_CONN_TO_SESSION4resok; struct BIND_CONN_TO_SESSION4res { nfsstat4 bctsr_status; union { BIND_CONN_TO_SESSION4resok bctsr_resok4; } BIND_CONN_TO_SESSION4res_u; }; typedef struct BIND_CONN_TO_SESSION4res BIND_CONN_TO_SESSION4res; #define EXCHGID4_FLAG_SUPP_MOVED_REFER 0x00000001 #define EXCHGID4_FLAG_SUPP_MOVED_MIGR 0x00000002 #define EXCHGID4_FLAG_BIND_PRINC_STATEID 0x00000100 #define EXCHGID4_FLAG_USE_NON_PNFS 0x00010000 #define EXCHGID4_FLAG_USE_PNFS_MDS 0x00020000 #define EXCHGID4_FLAG_USE_PNFS_DS 0x00040000 #define EXCHGID4_FLAG_MASK_PNFS 0x00070000 #define EXCHGID4_FLAG_UPD_CONFIRMED_REC_A 0x40000000 #define EXCHGID4_FLAG_CONFIRMED_R 0x80000000 struct state_protect_ops4 { struct bitmap4 spo_must_enforce; struct bitmap4 spo_must_allow; }; typedef struct state_protect_ops4 state_protect_ops4; struct ssv_sp_parms4 { state_protect_ops4 ssp_ops; struct { u_int ssp_hash_algs_len; sec_oid4 *ssp_hash_algs_val; } ssp_hash_algs; struct { u_int ssp_encr_algs_len; sec_oid4 *ssp_encr_algs_val; } ssp_encr_algs; uint32_t ssp_window; uint32_t ssp_num_gss_handles; }; typedef struct ssv_sp_parms4 ssv_sp_parms4; enum state_protect_how4 { SP4_NONE = 0, SP4_MACH_CRED = 1, SP4_SSV = 2, SP4_COUNT = 3, }; typedef enum state_protect_how4 state_protect_how4; struct state_protect4_a { state_protect_how4 spa_how; union { state_protect_ops4 spa_mach_ops; ssv_sp_parms4 spa_ssv_parms; } state_protect4_a_u; }; typedef struct state_protect4_a state_protect4_a; struct EXCHANGE_ID4args { client_owner4 eia_clientowner; uint32_t eia_flags; state_protect4_a eia_state_protect; struct { u_int eia_client_impl_id_len; nfs_impl_id4 *eia_client_impl_id_val; } eia_client_impl_id; }; typedef struct EXCHANGE_ID4args EXCHANGE_ID4args; struct ssv_prot_info4 { state_protect_ops4 spi_ops; uint32_t spi_hash_alg; uint32_t spi_encr_alg; uint32_t spi_ssv_len; uint32_t spi_window; struct { u_int spi_handles_len; gsshandle4_t *spi_handles_val; } spi_handles; }; typedef struct ssv_prot_info4 ssv_prot_info4; struct state_protect4_r { state_protect_how4 spr_how; union { state_protect_ops4 spr_mach_ops; ssv_prot_info4 spr_ssv_info; } state_protect4_r_u; }; typedef struct state_protect4_r state_protect4_r; struct EXCHANGE_ID4resok { clientid4 eir_clientid; sequenceid4 eir_sequenceid; uint32_t eir_flags; state_protect4_r eir_state_protect; server_owner4 eir_server_owner; struct { u_int eir_server_scope_len; char *eir_server_scope_val; } eir_server_scope; struct { u_int eir_server_impl_id_len; nfs_impl_id4 *eir_server_impl_id_val; } eir_server_impl_id; }; typedef struct EXCHANGE_ID4resok EXCHANGE_ID4resok; struct EXCHANGE_ID4res { nfsstat4 eir_status; union { EXCHANGE_ID4resok eir_resok4; } EXCHANGE_ID4res_u; }; typedef struct EXCHANGE_ID4res EXCHANGE_ID4res; struct channel_attrs4 { count4 ca_headerpadsize; count4 ca_maxrequestsize; count4 ca_maxresponsesize; count4 ca_maxresponsesize_cached; count4 ca_maxoperations; count4 ca_maxrequests; struct { u_int ca_rdma_ird_len; uint32_t *ca_rdma_ird_val; } ca_rdma_ird; }; typedef struct channel_attrs4 channel_attrs4; #define CREATE_SESSION4_FLAG_PERSIST 0x00000001 #define CREATE_SESSION4_FLAG_CONN_BACK_CHAN 0x00000002 #define CREATE_SESSION4_FLAG_CONN_RDMA 0x00000004 struct CREATE_SESSION4args { clientid4 csa_clientid; sequenceid4 csa_sequence; uint32_t csa_flags; channel_attrs4 csa_fore_chan_attrs; channel_attrs4 csa_back_chan_attrs; uint32_t csa_cb_program; struct { u_int csa_sec_parms_len; callback_sec_parms4 *csa_sec_parms_val; } csa_sec_parms; }; typedef struct CREATE_SESSION4args CREATE_SESSION4args; struct CREATE_SESSION4resok { sessionid4 csr_sessionid; sequenceid4 csr_sequence; uint32_t csr_flags; channel_attrs4 csr_fore_chan_attrs; channel_attrs4 csr_back_chan_attrs; }; typedef struct CREATE_SESSION4resok CREATE_SESSION4resok; struct CREATE_SESSION4res { nfsstat4 csr_status; union { CREATE_SESSION4resok csr_resok4; } CREATE_SESSION4res_u; }; typedef struct CREATE_SESSION4res CREATE_SESSION4res; struct DESTROY_SESSION4args { sessionid4 dsa_sessionid; }; typedef struct DESTROY_SESSION4args DESTROY_SESSION4args; struct DESTROY_SESSION4res { nfsstat4 dsr_status; }; typedef struct DESTROY_SESSION4res DESTROY_SESSION4res; struct FREE_STATEID4args { stateid4 fsa_stateid; }; typedef struct FREE_STATEID4args FREE_STATEID4args; struct FREE_STATEID4res { nfsstat4 fsr_status; }; typedef struct FREE_STATEID4res FREE_STATEID4res; typedef nfstime4 attr_notice4; struct GET_DIR_DELEGATION4args { bool_t gdda_signal_deleg_avail; struct bitmap4 gdda_notification_types; attr_notice4 gdda_child_attr_delay; attr_notice4 gdda_dir_attr_delay; struct bitmap4 gdda_child_attributes; struct bitmap4 gdda_dir_attributes; }; typedef struct GET_DIR_DELEGATION4args GET_DIR_DELEGATION4args; struct GET_DIR_DELEGATION4resok { verifier4 gddr_cookieverf; stateid4 gddr_stateid; struct bitmap4 gddr_notification; struct bitmap4 gddr_child_attributes; struct bitmap4 gddr_dir_attributes; }; typedef struct GET_DIR_DELEGATION4resok GET_DIR_DELEGATION4resok; enum gddrnf4_status { GDD4_OK = 0, GDD4_UNAVAIL = 1, }; typedef enum gddrnf4_status gddrnf4_status; struct GET_DIR_DELEGATION4res_non_fatal { gddrnf4_status gddrnf_status; union { GET_DIR_DELEGATION4resok gddrnf_resok4; bool_t gddrnf_signal; } GET_DIR_DELEGATION4res_non_fatal_u; }; typedef struct GET_DIR_DELEGATION4res_non_fatal GET_DIR_DELEGATION4res_non_fatal; struct GET_DIR_DELEGATION4res { nfsstat4 gddr_status; union { GET_DIR_DELEGATION4res_non_fatal gddr_res_non_fatal4; } GET_DIR_DELEGATION4res_u; }; typedef struct GET_DIR_DELEGATION4res GET_DIR_DELEGATION4res; struct GETDEVICEINFO4args { deviceid4 gdia_device_id; layouttype4 gdia_layout_type; count4 gdia_maxcount; struct bitmap4 gdia_notify_types; }; typedef struct GETDEVICEINFO4args GETDEVICEINFO4args; struct GETDEVICEINFO4resok { device_addr4 gdir_device_addr; struct bitmap4 gdir_notification; }; typedef struct GETDEVICEINFO4resok GETDEVICEINFO4resok; struct GETDEVICEINFO4res { nfsstat4 gdir_status; union { GETDEVICEINFO4resok gdir_resok4; count4 gdir_mincount; } GETDEVICEINFO4res_u; }; typedef struct GETDEVICEINFO4res GETDEVICEINFO4res; struct GETDEVICELIST4args { layouttype4 gdla_layout_type; count4 gdla_maxdevices; nfs_cookie4 gdla_cookie; verifier4 gdla_cookieverf; }; typedef struct GETDEVICELIST4args GETDEVICELIST4args; struct GETDEVICELIST4resok { nfs_cookie4 gdlr_cookie; verifier4 gdlr_cookieverf; struct { u_int gdlr_deviceid_list_len; deviceid4 *gdlr_deviceid_list_val; } gdlr_deviceid_list; bool_t gdlr_eof; }; typedef struct GETDEVICELIST4resok GETDEVICELIST4resok; struct GETDEVICELIST4res { nfsstat4 gdlr_status; union { GETDEVICELIST4resok gdlr_resok4; } GETDEVICELIST4res_u; }; typedef struct GETDEVICELIST4res GETDEVICELIST4res; struct newtime4 { bool_t nt_timechanged; union { nfstime4 nt_time; } newtime4_u; }; typedef struct newtime4 newtime4; struct newoffset4 { bool_t no_newoffset; union { offset4 no_offset; } newoffset4_u; }; typedef struct newoffset4 newoffset4; struct LAYOUTCOMMIT4args { offset4 loca_offset; length4 loca_length; bool_t loca_reclaim; stateid4 loca_stateid; newoffset4 loca_last_write_offset; newtime4 loca_time_modify; layoutupdate4 loca_layoutupdate; }; typedef struct LAYOUTCOMMIT4args LAYOUTCOMMIT4args; struct newsize4 { bool_t ns_sizechanged; union { length4 ns_size; } newsize4_u; }; typedef struct newsize4 newsize4; struct LAYOUTCOMMIT4resok { newsize4 locr_newsize; }; typedef struct LAYOUTCOMMIT4resok LAYOUTCOMMIT4resok; struct LAYOUTCOMMIT4res { nfsstat4 locr_status; union { LAYOUTCOMMIT4resok locr_resok4; } LAYOUTCOMMIT4res_u; }; typedef struct LAYOUTCOMMIT4res LAYOUTCOMMIT4res; struct LAYOUTGET4args { bool_t loga_signal_layout_avail; layouttype4 loga_layout_type; layoutiomode4 loga_iomode; offset4 loga_offset; length4 loga_length; length4 loga_minlength; stateid4 loga_stateid; count4 loga_maxcount; }; typedef struct LAYOUTGET4args LAYOUTGET4args; struct LAYOUTGET4resok { bool_t logr_return_on_close; stateid4 logr_stateid; struct { u_int logr_layout_len; layout4 *logr_layout_val; } logr_layout; }; typedef struct LAYOUTGET4resok LAYOUTGET4resok; struct LAYOUTGET4res { nfsstat4 logr_status; union { LAYOUTGET4resok logr_resok4; bool_t logr_will_signal_layout_avail; } LAYOUTGET4res_u; }; typedef struct LAYOUTGET4res LAYOUTGET4res; struct LAYOUTRETURN4args { bool_t lora_reclaim; layouttype4 lora_layout_type; layoutiomode4 lora_iomode; layoutreturn4 lora_layoutreturn; }; typedef struct LAYOUTRETURN4args LAYOUTRETURN4args; struct layoutreturn_stateid { bool_t lrs_present; union { stateid4 lrs_stateid; } layoutreturn_stateid_u; }; typedef struct layoutreturn_stateid layoutreturn_stateid; struct LAYOUTRETURN4res { nfsstat4 lorr_status; union { layoutreturn_stateid lorr_stateid; } LAYOUTRETURN4res_u; }; typedef struct LAYOUTRETURN4res LAYOUTRETURN4res; enum secinfo_style4 { SECINFO_STYLE4_CURRENT_FH = 0, SECINFO_STYLE4_PARENT = 1, }; typedef enum secinfo_style4 secinfo_style4; typedef secinfo_style4 SECINFO_NO_NAME4args; typedef SECINFO4res SECINFO_NO_NAME4res; struct SEQUENCE4args { sessionid4 sa_sessionid; sequenceid4 sa_sequenceid; slotid4 sa_slotid; slotid4 sa_highest_slotid; bool_t sa_cachethis; }; typedef struct SEQUENCE4args SEQUENCE4args; #define SEQ4_STATUS_CB_PATH_DOWN 0x00000001 #define SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRING 0x00000002 #define SEQ4_STATUS_CB_GSS_CONTEXTS_EXPIRED 0x00000004 #define SEQ4_STATUS_EXPIRED_ALL_STATE_REVOKED 0x00000008 #define SEQ4_STATUS_EXPIRED_SOME_STATE_REVOKED 0x00000010 #define SEQ4_STATUS_ADMIN_STATE_REVOKED 0x00000020 #define SEQ4_STATUS_RECALLABLE_STATE_REVOKED 0x00000040 #define SEQ4_STATUS_LEASE_MOVED 0x00000080 #define SEQ4_STATUS_RESTART_RECLAIM_NEEDED 0x00000100 #define SEQ4_STATUS_CB_PATH_DOWN_SESSION 0x00000200 #define SEQ4_STATUS_BACKCHANNEL_FAULT 0x00000400 #define SEQ4_STATUS_DEVID_CHANGED 0x00000800 #define SEQ4_STATUS_DEVID_DELETED 0x00001000 struct SEQUENCE4resok { sessionid4 sr_sessionid; sequenceid4 sr_sequenceid; slotid4 sr_slotid; slotid4 sr_highest_slotid; slotid4 sr_target_highest_slotid; uint32_t sr_status_flags; }; typedef struct SEQUENCE4resok SEQUENCE4resok; struct SEQUENCE4res { nfsstat4 sr_status; union { SEQUENCE4resok sr_resok4; } SEQUENCE4res_u; }; typedef struct SEQUENCE4res SEQUENCE4res; struct ssa_digest_input4 { SEQUENCE4args sdi_seqargs; }; typedef struct ssa_digest_input4 ssa_digest_input4; struct SET_SSV4args { struct { u_int ssa_ssv_len; char *ssa_ssv_val; } ssa_ssv; struct { u_int ssa_digest_len; char *ssa_digest_val; } ssa_digest; }; typedef struct SET_SSV4args SET_SSV4args; struct ssr_digest_input4 { SEQUENCE4res sdi_seqres; }; typedef struct ssr_digest_input4 ssr_digest_input4; struct SET_SSV4resok { struct { u_int ssr_digest_len; char *ssr_digest_val; } ssr_digest; }; typedef struct SET_SSV4resok SET_SSV4resok; struct SET_SSV4res { nfsstat4 ssr_status; union { SET_SSV4resok ssr_resok4; } SET_SSV4res_u; }; typedef struct SET_SSV4res SET_SSV4res; struct TEST_STATEID4args { struct { u_int ts_stateids_len; stateid4 *ts_stateids_val; } ts_stateids; }; typedef struct TEST_STATEID4args TEST_STATEID4args; struct TEST_STATEID4resok { struct { u_int tsr_status_codes_len; nfsstat4 *tsr_status_codes_val; } tsr_status_codes; }; typedef struct TEST_STATEID4resok TEST_STATEID4resok; struct TEST_STATEID4res { nfsstat4 tsr_status; union { TEST_STATEID4resok tsr_resok4; } TEST_STATEID4res_u; }; typedef struct TEST_STATEID4res TEST_STATEID4res; struct deleg_claim4 { open_claim_type4 dc_claim; union { open_delegation_type4 dc_delegate_type; } deleg_claim4_u; }; typedef struct deleg_claim4 deleg_claim4; struct WANT_DELEGATION4args { uint32_t wda_want; deleg_claim4 wda_claim; }; typedef struct WANT_DELEGATION4args WANT_DELEGATION4args; struct WANT_DELEGATION4res { nfsstat4 wdr_status; union { open_delegation4 wdr_resok4; } WANT_DELEGATION4res_u; }; typedef struct WANT_DELEGATION4res WANT_DELEGATION4res; struct DESTROY_CLIENTID4args { clientid4 dca_clientid; }; typedef struct DESTROY_CLIENTID4args DESTROY_CLIENTID4args; struct DESTROY_CLIENTID4res { nfsstat4 dcr_status; }; typedef struct DESTROY_CLIENTID4res DESTROY_CLIENTID4res; struct RECLAIM_COMPLETE4args { bool_t rca_one_fs; }; typedef struct RECLAIM_COMPLETE4args RECLAIM_COMPLETE4args; struct RECLAIM_COMPLETE4res { nfsstat4 rcr_status; }; typedef struct RECLAIM_COMPLETE4res RECLAIM_COMPLETE4res; /* NFSv4.2 */ enum netloc_type4 { NL4_NAME = 0, NL4_URL = 1, NL4_NETADDR = 2 }; typedef enum netloc_type4 netloc_type4; enum data_content4 { NFS4_CONTENT_DATA = 0, NFS4_CONTENT_HOLE = 1, NFS4_CONTENT_ALLOCATE = 2, NFS4_CONTENT_DEALLOCATE = 4 }; typedef enum data_content4 data_content4; typedef struct { offset4 d_offset; struct { u_int data_len; char *data_val; } d_data; } data4; typedef struct { offset4 adb_offset; length4 adb_block_size; length4 adb_block_count; length4 adb_reloff_blocknum; count4 adb_block_num; length4 adb_reloff_pattern; struct { u_int data_len; char *data_val; } adb_data; } app_data_block4; typedef struct { offset4 di_offset; length4 di_length; } data_info4; typedef struct { count4 wr_ids; stateid4 wr_callback_id; length4 wr_count; stable_how4 wr_committed; verifier4 wr_writeverf; } write_response4; typedef struct { data_content4 what; union { data4 data; app_data_block4 adb; data_info4 hole; }; } contents; typedef struct { bool_t rpr_eof; count4 rpr_contents_count; contents rpr_contents; } read_plus_res4; typedef struct { bool_t sr_eof; offset4 sr_offset; } seek_res4; typedef struct OFFLOAD_STATUS4resok { length4 osr_bytes_copied; count4 osr_count_complete; nfsstat4 osr_complete; } OFFLOAD_STATUS4resok; struct COPY_NOTIFY4args { stateid4 cna_stateid; netloc_type4 cna_type; union { utf8str_cis cna_name; utf8str_cis cna_url; netaddr4 cna_addr; }; }; typedef struct COPY_NOTIFY4args COPY_NOTIFY4args; struct COPY_NOTIFY4res { nfstime4 cnr_time; netloc_type4 cna_type; union { utf8str_cis cnr_name; utf8str_cis cnr_url; netaddr4 cnr_addr; }; }; typedef struct COPY_NOTIFY4res COPY_NOTIFY4res; struct OFFLOAD_REVOKE4args { netloc_type4 ora_type; union { utf8str_cis ora_name; utf8str_cis ora_url; netaddr4 ora_addr; }; }; typedef struct OFFLOAD_REVOKE4args OFFLOAD_REVOKE4args; struct OFFLOAD_REVOKE4res { netloc_type4 orr_type; }; typedef struct OFFLOAD_REVOKE4res OFFLOAD_REVOKE4res; struct COPY4args { stateid4 ca_src_stateid; stateid4 ca_dst_stateid; offset4 ca_src_offset; offset4 ca_dst_offset; length4 ca_count; netloc_type4 ca_type; union { utf8str_cis ca_name; utf8str_cis ca_url; netaddr4 ca_addr; }; }; typedef struct COPY4args COPY4args; struct COPY4res { nfsstat4 cr_status; union { write_response4 cr_resok4; length4 cr_bytes_copied; } COPY4res_u; }; typedef struct COPY4res COPY4res; struct OFFLOAD_ABORT4args { stateid4 oaa_stateid; }; typedef struct OFFLOAD_ABORT4args OFFLOAD_ABORT4args; struct OFFLOAD_ABORT4res { stateid4 oar_stateid; }; typedef struct OFFLOAD_ABORT4res OFFLOAD_ABORT4res; struct OFFLOAD_STATUS4args { stateid4 osa_stateid; }; typedef struct OFFLOAD_STATUS4args OFFLOAD_STATUS4args; struct OFFLOAD_STATUS4res { nfsstat4 osr_status; union { OFFLOAD_STATUS4resok osr_resok4; } OFFLOAD_STATUS4res_u; }; typedef struct OFFLOAD_STATUS4res OFFLOAD_STATUS4res; struct WRITE_SAME4args { stateid4 wp_stateid; stable_how4 wp_stable; app_data_block4 wp_adb; }; typedef struct WRITE_SAME4args WRITE_SAME4args; struct WRITE_SAME4res { nfsstat4 wpr_status; union { write_response4 wpr_resok4; }; }; typedef struct WRITE_SAME4res WRITE_SAME4res; struct READ_PLUS4args { stateid4 rpa_stateid; offset4 rpa_offset; count4 rpa_count; }; typedef struct READ_PLUS4args READ_PLUS4args; struct READ_PLUS4res { nfsstat4 rpr_status; union { read_plus_res4 rpr_resok4; }; }; typedef struct READ_PLUS4res READ_PLUS4res; struct ALLOCATE4args { stateid4 aa_stateid; offset4 aa_offset; length4 aa_length; }; typedef struct ALLOCATE4args ALLOCATE4args; struct ALLOCATE4res { nfsstat4 ar_status; }; typedef struct ALLOCATE4res ALLOCATE4res; struct DEALLOCATE4args { stateid4 da_stateid; offset4 da_offset; length4 da_length; }; typedef struct DEALLOCATE4args DEALLOCATE4args; struct DEALLOCATE4res { nfsstat4 dr_status; }; typedef struct DEALLOCATE4res DEALLOCATE4res; struct SEEK4args { stateid4 sa_stateid; offset4 sa_offset; data_content4 sa_what; }; typedef struct SEEK4args SEEK4args; struct SEEK4res { nfsstat4 sr_status; union { seek_res4 sr_resok4; }; }; typedef struct SEEK4res SEEK4res; enum IO_ADVISE_type4 { IO_ADVISE4_NORMAL = 0, IO_ADVISE4_SEQUENTIAL = 1, IO_ADVISE4_SEQUENTIAL_BACKWARDS = 2, IO_ADVISE4_RANDOM = 3, IO_ADVISE4_WILLNEED = 4, IO_ADVISE4_WILLNEED_OPPORTUNISTIC = 5, IO_ADVISE4_DONTNEED = 6, IO_ADVISE4_NOREUSE = 7, IO_ADVISE4_READ = 8, IO_ADVISE4_WRITE = 9, IO_ADVISE4_INIT_PROXIMITY = 10 }; struct IO_ADVISE4args { stateid4 iaa_stateid; offset4 iaa_offset; length4 iaa_count; bitmap4 iaa_hints; }; typedef struct IO_ADVISE4args IO_ADVISE4args; struct IO_ADVISE4res { nfsstat4 iaa_status; union { bitmap4 iaa_hints; }; }; typedef struct IO_ADVISE4res IO_ADVISE4res; /* new operations for NFSv4.1 */ enum nfs_opnum4 { NFS4_OP_ACCESS = 3, NFS4_OP_CLOSE = 4, NFS4_OP_COMMIT = 5, NFS4_OP_CREATE = 6, NFS4_OP_DELEGPURGE = 7, NFS4_OP_DELEGRETURN = 8, NFS4_OP_GETATTR = 9, NFS4_OP_GETFH = 10, NFS4_OP_LINK = 11, NFS4_OP_LOCK = 12, NFS4_OP_LOCKT = 13, NFS4_OP_LOCKU = 14, NFS4_OP_LOOKUP = 15, NFS4_OP_LOOKUPP = 16, NFS4_OP_NVERIFY = 17, NFS4_OP_OPEN = 18, NFS4_OP_OPENATTR = 19, NFS4_OP_OPEN_CONFIRM = 20, NFS4_OP_OPEN_DOWNGRADE = 21, NFS4_OP_PUTFH = 22, NFS4_OP_PUTPUBFH = 23, NFS4_OP_PUTROOTFH = 24, NFS4_OP_READ = 25, NFS4_OP_READDIR = 26, NFS4_OP_READLINK = 27, NFS4_OP_REMOVE = 28, NFS4_OP_RENAME = 29, NFS4_OP_RENEW = 30, NFS4_OP_RESTOREFH = 31, NFS4_OP_SAVEFH = 32, NFS4_OP_SECINFO = 33, NFS4_OP_SETATTR = 34, NFS4_OP_SETCLIENTID = 35, NFS4_OP_SETCLIENTID_CONFIRM = 36, NFS4_OP_VERIFY = 37, NFS4_OP_WRITE = 38, NFS4_OP_RELEASE_LOCKOWNER = 39, /* NFSv4.1 */ NFS4_OP_BACKCHANNEL_CTL = 40, NFS4_OP_BIND_CONN_TO_SESSION = 41, NFS4_OP_EXCHANGE_ID = 42, NFS4_OP_CREATE_SESSION = 43, NFS4_OP_DESTROY_SESSION = 44, NFS4_OP_FREE_STATEID = 45, NFS4_OP_GET_DIR_DELEGATION = 46, NFS4_OP_GETDEVICEINFO = 47, NFS4_OP_GETDEVICELIST = 48, NFS4_OP_LAYOUTCOMMIT = 49, NFS4_OP_LAYOUTGET = 50, NFS4_OP_LAYOUTRETURN = 51, NFS4_OP_SECINFO_NO_NAME = 52, NFS4_OP_SEQUENCE = 53, NFS4_OP_SET_SSV = 54, NFS4_OP_TEST_STATEID = 55, NFS4_OP_WANT_DELEGATION = 56, NFS4_OP_DESTROY_CLIENTID = 57, NFS4_OP_RECLAIM_COMPLETE = 58, /* NFSv4.2 */ NFS4_OP_ALLOCATE = 59, NFS4_OP_COPY = 60, NFS4_OP_COPY_NOTIFY = 61, NFS4_OP_DEALLOCATE = 62, NFS4_OP_IO_ADVISE = 63, NFS4_OP_LAYOUTERROR = 64, NFS4_OP_LAYOUTSTATS = 65, NFS4_OP_OFFLOAD_CANCEL = 66, NFS4_OP_OFFLOAD_STATUS = 67, NFS4_OP_READ_PLUS = 68, NFS4_OP_SEEK = 69, NFS4_OP_WRITE_SAME = 70, NFS4_OP_CLONE = 71, /* NFSv4.3 */ NFS4_OP_GETXATTR = 72, NFS4_OP_SETXATTR = 73, NFS4_OP_LISTXATTR = 74, NFS4_OP_REMOVEXATTR = 75, NFS4_OP_LAST_ONE = 76, NFS4_OP_ILLEGAL = 10044, }; typedef enum nfs_opnum4 nfs_opnum4; /* Number of operations, per NFSv4 version. */ #define NFS_V40_NB_OPERATION (NFS4_OP_RELEASE_LOCKOWNER + 1) #define NFS_V41_NB_OPERATION (NFS4_OP_RECLAIM_COMPLETE + 1) #define NFS_V42_NB_OPERATION (NFS4_OP_CLONE + 1) #define NFS_V43_NB_OPERATION (NFS4_OP_REMOVEXATTR + 1) typedef component4 xattrkey4; /* * This is not universally a utf8string, but rather a variable-length * opaque sequence of bytes. We still call it a utf8string under the * hood however, as those routines work just as well for this. */ typedef utf8string xattrvalue4; enum setxattr_option4 { SETXATTR4_EITHER = 0, SETXATTR4_CREATE = 1, SETXATTR4_REPLACE = 2, }; typedef enum setxattr_option4 setxattr_option4; struct xattrlist4 { count4 xl4_count; xattrkey4 *xl4_entries; }; typedef struct xattrlist4 xattrlist4; struct GETXATTR4args { xattrkey4 gxa_name; }; typedef struct GETXATTR4args GETXATTR4args; struct GETXATTR4resok { xattrvalue4 gxr_value; }; typedef struct GETXATTR4resok GETXATTR4resok; struct GETXATTR4res { nfsstat4 status; union { GETXATTR4resok resok4; } GETXATTR4res_u; }; typedef struct GETXATTR4res GETXATTR4res; struct SETXATTR4args { setxattr_option4 sxa_option; xattrkey4 sxa_key; xattrvalue4 sxa_value; }; typedef struct SETXATTR4args SETXATTR4args; struct SETXATTR4resok { change_info4 sxr_info; }; typedef struct SETXATTR4resok SETXATTR4resok; struct SETXATTR4res { nfsstat4 status; union { SETXATTR4resok resok4; } SETXATTR4res_u; }; typedef struct SETXATTR4res SETXATTR4res; struct LISTXATTR4args { nfs_cookie4 lxa_cookie; count4 lxa_maxcount; }; typedef struct LISTXATTR4args LISTXATTR4args; struct LISTXATTR4resok { nfs_cookie4 lxr_cookie; xattrlist4 lxr_names; bool_t lxr_eof; }; typedef struct LISTXATTR4resok LISTXATTR4resok; struct LISTXATTR4res { nfsstat4 status; union { LISTXATTR4resok resok4; } LISTXATTR4res_u; }; typedef struct LISTXATTR4res LISTXATTR4res; struct REMOVEXATTR4args { xattrkey4 rxa_name; }; typedef struct REMOVEXATTR4args REMOVEXATTR4args; struct REMOVEXATTR4resok { change_info4 rxr_info; }; typedef struct REMOVEXATTR4resok REMOVEXATTR4resok; struct REMOVEXATTR4res { nfsstat4 status; union { REMOVEXATTR4resok resok4; } REMOVEXATTR4res_u; }; typedef struct REMOVEXATTR4res REMOVEXATTR4res; typedef struct { deviceid4 de_deviceid; nfsstat4 de_status; nfs_opnum4 de_opnum; } device_error4; struct LAYOUTERROR4args { offset4 lea_offset; length4 lea_length; stateid4 lea_stateid; device_error4 lea_errors; }; typedef struct LAYOUTERROR4args LAYOUTERROR4args; struct LAYOUTERROR4res { nfsstat4 ler_status; }; typedef struct LAYOUTERROR4res LAYOUTERROR4res; struct io_info4 { uint32_t ii_count; uint64_t ii_bytes; }; typedef struct io_info4 io_info4; struct LAYOUTSTATS4args { offset4 lsa_offset; length4 lsa_length; stateid4 lsa_stateid; io_info4 lsa_read; io_info4 lsa_write; layoutupdate4 lsa_layoutupdate; }; typedef struct LAYOUTSTATS4args LAYOUTSTATS4args; struct LAYOUTSTATS4res { nfsstat4 lsr_status; }; typedef struct LAYOUTSTATS4res LAYOUTSTATS4res; struct ff_device_versions4 { uint32_t ffdv_version; uint32_t ffdv_minorversion; uint32_t ffdv_rsize; uint32_t ffdv_wsize; bool_t ffdv_tightly_coupled; }; typedef struct ff_device_versions4 ff_device_versions4; struct ff_device_addr4 { multipath_list4 ffda_netaddrs; struct { u_int ffda_versions_len; ff_device_versions4 *ffda_versions_val; } ffda_versions; }; typedef struct ff_device_addr4 ff_device_addr4; struct ff_data_server4 { deviceid4 ffds_deviceid; uint32_t ffds_efficiency; stateid4 ffds_stateid; struct { u_int ffds_fh_vers_len; nfs_fh4 *ffds_fh_vers_val; } ffds_fh_vers; fattr4_owner ffds_user; fattr4_owner_group ffds_group; }; typedef struct ff_data_server4 ff_data_server4; struct ff_mirror4 { struct { u_int ffm_data_servers_len; ff_data_server4 *ffm_data_servers_val; } ffm_data_servers; }; typedef struct ff_mirror4 ff_mirror4; enum ff_flags4 { FF_FLAGS_NO_LAYOUTCOMMIT = 0x00000001, FF_FLAGS_NO_IO_THRU_MDS = 0x00000002, FF_FLAGS_NO_READ_IO = 0x00000004, FF_FLAGS_WRITE_ONE_MIRROR = 0x00000008, }; typedef enum ff_flags4 ff_flags4; struct ff_layout4 { length4 ffl_stripe_unit; struct { u_int ffl_mirrors_len; ff_mirror4 *ffl_mirrors_val; } ffl_mirrors; ff_flags4 ffl_flags; uint32_t ffl_stats_collect_hint; }; typedef struct ff_layout4 ff_layout4; struct ff_ioerr4 { offset4 ffie_offset; length4 ffie_length; stateid4 ffie_stateid; struct { u_int ffie_errors_len; device_error4 *ffie_errors_val; } ffie_errors; }; typedef struct ff_ioerr4 ff_ioerr4; struct ff_io_latency4 { uint64_t ffil_ops_requested; uint64_t ffil_bytes_requested; uint64_t ffil_ops_completed; uint64_t ffil_bytes_completed; uint64_t ffil_bytes_not_delivered; nfstime4 ffil_total_busy_time; nfstime4 ffil_aggregate_completion_time; }; typedef struct ff_io_latency4 ff_io_latency4; struct ff_layoutupdate4 { netaddr4 ffl_addr; nfs_fh4 ffl_fhandle; ff_io_latency4 ffl_read; ff_io_latency4 ffl_write; nfstime4 ffl_duration; bool_t ffl_local; }; typedef struct ff_layoutupdate4 ff_layoutupdate4; struct ff_iostats4 { offset4 ffis_offset; length4 ffis_length; stateid4 ffis_stateid; io_info4 ffis_read; io_info4 ffis_write; deviceid4 ffis_deviceid; ff_layoutupdate4 ffis_layoutupdate; }; typedef struct ff_iostats4 ff_iostats4; struct ff_layoutreturn4 { struct { u_int fflr_ioerr_report_len; ff_ioerr4 *fflr_ioerr_report_val; } fflr_ioerr_report; struct { u_int fflr_iostats_report_len; ff_iostats4 *fflr_iostats_report_val; } fflr_iostats_report; }; typedef struct ff_layoutreturn4 ff_layoutreturn4; struct ff_mirrors_hint { bool_t ffmc_valid; union { uint32_t ffmc_mirrors; } ff_mirrors_hint_u; }; typedef struct ff_mirrors_hint ff_mirrors_hint; struct ff_layouthint4 { ff_mirrors_hint fflh_mirrors_hint; }; typedef struct ff_layouthint4 ff_layouthint4; enum ff_cb_recall_any_mask { FF_RCA4_TYPE_MASK_READ = -2, FF_RCA4_TYPE_MASK_RW = -1, }; typedef enum ff_cb_recall_any_mask ff_cb_recall_any_mask; struct nfs_argop4 { nfs_opnum4 argop; union { ACCESS4args opaccess; CLOSE4args opclose; COMMIT4args opcommit; CREATE4args opcreate; DELEGPURGE4args opdelegpurge; DELEGRETURN4args opdelegreturn; GETATTR4args opgetattr; LINK4args oplink; LOCK4args oplock; LOCKT4args oplockt; LOCKU4args oplocku; LOOKUP4args oplookup; NVERIFY4args opnverify; OPEN4args opopen; OPENATTR4args opopenattr; OPEN_CONFIRM4args opopen_confirm; OPEN_DOWNGRADE4args opopen_downgrade; PUTFH4args opputfh; READ4args opread; READDIR4args opreaddir; REMOVE4args opremove; RENAME4args oprename; RENEW4args oprenew; SECINFO4args opsecinfo; SETATTR4args opsetattr; SETCLIENTID4args opsetclientid; SETCLIENTID_CONFIRM4args opsetclientid_confirm; VERIFY4args opverify; WRITE4args opwrite; RELEASE_LOCKOWNER4args oprelease_lockowner; BACKCHANNEL_CTL4args opbackchannel_ctl; BIND_CONN_TO_SESSION4args opbind_conn_to_session; EXCHANGE_ID4args opexchange_id; CREATE_SESSION4args opcreate_session; DESTROY_SESSION4args opdestroy_session; FREE_STATEID4args opfree_stateid; GET_DIR_DELEGATION4args opget_dir_delegation; GETDEVICEINFO4args opgetdeviceinfo; GETDEVICELIST4args opgetdevicelist; LAYOUTCOMMIT4args oplayoutcommit; LAYOUTGET4args oplayoutget; LAYOUTRETURN4args oplayoutreturn; SECINFO_NO_NAME4args opsecinfo_no_name; SEQUENCE4args opsequence; SET_SSV4args opset_ssv; TEST_STATEID4args optest_stateid; WANT_DELEGATION4args opwant_delegation; DESTROY_CLIENTID4args opdestroy_clientid; RECLAIM_COMPLETE4args opreclaim_complete; /* NFSv4.2 */ COPY_NOTIFY4args opoffload_notify; OFFLOAD_REVOKE4args opcopy_revoke; COPY4args opcopy; OFFLOAD_ABORT4args opoffload_abort; OFFLOAD_STATUS4args opoffload_status; WRITE_SAME4args opwrite_same; ALLOCATE4args opallocate; DEALLOCATE4args opdeallocate; READ_PLUS4args opread_plus; SEEK4args opseek; IO_ADVISE4args opio_advise; LAYOUTERROR4args oplayouterror; LAYOUTSTATS4args oplayoutstats; /* NFSv4.3 */ GETXATTR4args opgetxattr; SETXATTR4args opsetxattr; LISTXATTR4args oplistxattr; REMOVEXATTR4args opremovexattr; } nfs_argop4_u; }; typedef struct nfs_argop4 nfs_argop4; struct nfs_resop4 { nfs_opnum4 resop; union { ACCESS4res opaccess; CLOSE4res opclose; COMMIT4res opcommit; CREATE4res opcreate; DELEGPURGE4res opdelegpurge; DELEGRETURN4res opdelegreturn; GETATTR4res opgetattr; GETFH4res opgetfh; LINK4res oplink; LOCK4res oplock; LOCKT4res oplockt; LOCKU4res oplocku; LOOKUP4res oplookup; LOOKUPP4res oplookupp; NVERIFY4res opnverify; OPEN4res opopen; OPENATTR4res opopenattr; OPEN_CONFIRM4res opopen_confirm; OPEN_DOWNGRADE4res opopen_downgrade; PUTFH4res opputfh; PUTPUBFH4res opputpubfh; PUTROOTFH4res opputrootfh; READ4res opread; READDIR4res opreaddir; READLINK4res opreadlink; REMOVE4res opremove; RENAME4res oprename; RENEW4res oprenew; RESTOREFH4res oprestorefh; SAVEFH4res opsavefh; SECINFO4res opsecinfo; SETATTR4res opsetattr; SETCLIENTID4res opsetclientid; SETCLIENTID_CONFIRM4res opsetclientid_confirm; VERIFY4res opverify; WRITE4res opwrite; RELEASE_LOCKOWNER4res oprelease_lockowner; BACKCHANNEL_CTL4res opbackchannel_ctl; BIND_CONN_TO_SESSION4res opbind_conn_to_session; EXCHANGE_ID4res opexchange_id; CREATE_SESSION4res opcreate_session; DESTROY_SESSION4res opdestroy_session; FREE_STATEID4res opfree_stateid; GET_DIR_DELEGATION4res opget_dir_delegation; GETDEVICEINFO4res opgetdeviceinfo; GETDEVICELIST4res opgetdevicelist; LAYOUTCOMMIT4res oplayoutcommit; LAYOUTGET4res oplayoutget; LAYOUTRETURN4res oplayoutreturn; SECINFO_NO_NAME4res opsecinfo_no_name; SEQUENCE4res opsequence; SET_SSV4res opset_ssv; TEST_STATEID4res optest_stateid; WANT_DELEGATION4res opwant_delegation; DESTROY_CLIENTID4res opdestroy_clientid; RECLAIM_COMPLETE4res opreclaim_complete; /* NFSv4.2 */ COPY_NOTIFY4res opoffload_notify; OFFLOAD_REVOKE4res opcopy_revoke; COPY4res opcopy; OFFLOAD_ABORT4res opoffload_abort; OFFLOAD_STATUS4res opoffload_status; WRITE_SAME4res opwrite_same; ALLOCATE4res opallocate; DEALLOCATE4res opdeallocate; READ_PLUS4res opread_plus; SEEK4res opseek; IO_ADVISE4res opio_advise; LAYOUTERROR4res oplayouterror; LAYOUTSTATS4res oplayoutstats; /* NFSv4.3 */ GETXATTR4res opgetxattr; SETXATTR4res opsetxattr; LISTXATTR4res oplistxattr; REMOVEXATTR4res opremovexattr; ILLEGAL4res opillegal; } nfs_resop4_u; }; typedef struct nfs_resop4 nfs_resop4; struct COMPOUND4args { utf8str_cs tag; uint32_t minorversion; struct { u_int argarray_len; nfs_argop4 *argarray_val; } argarray; }; typedef struct COMPOUND4args COMPOUND4args; struct COMPOUND4res { nfsstat4 status; utf8str_cs tag; struct { u_int resarray_len; nfs_resop4 *resarray_val; } resarray; }; typedef struct COMPOUND4res COMPOUND4res; struct CB_GETATTR4args { nfs_fh4 fh; struct bitmap4 attr_request; }; typedef struct CB_GETATTR4args CB_GETATTR4args; struct CB_GETATTR4resok { fattr4 obj_attributes; }; typedef struct CB_GETATTR4resok CB_GETATTR4resok; struct CB_GETATTR4res { nfsstat4 status; union { CB_GETATTR4resok resok4; } CB_GETATTR4res_u; }; typedef struct CB_GETATTR4res CB_GETATTR4res; struct CB_RECALL4args { stateid4 stateid; bool_t truncate; nfs_fh4 fh; }; typedef struct CB_RECALL4args CB_RECALL4args; struct CB_RECALL4res { nfsstat4 status; }; typedef struct CB_RECALL4res CB_RECALL4res; struct CB_ILLEGAL4res { nfsstat4 status; }; typedef struct CB_ILLEGAL4res CB_ILLEGAL4res; enum layoutrecall_type4 { LAYOUTRECALL4_FILE = LAYOUT4_RET_REC_FILE, LAYOUTRECALL4_FSID = LAYOUT4_RET_REC_FSID, LAYOUTRECALL4_ALL = LAYOUT4_RET_REC_ALL, }; typedef enum layoutrecall_type4 layoutrecall_type4; struct layoutrecall_file4 { nfs_fh4 lor_fh; offset4 lor_offset; length4 lor_length; stateid4 lor_stateid; }; typedef struct layoutrecall_file4 layoutrecall_file4; struct layoutrecall4 { layoutrecall_type4 lor_recalltype; union { layoutrecall_file4 lor_layout; fsid4 lor_fsid; } layoutrecall4_u; }; typedef struct layoutrecall4 layoutrecall4; struct CB_LAYOUTRECALL4args { layouttype4 clora_type; layoutiomode4 clora_iomode; bool_t clora_changed; layoutrecall4 clora_recall; }; typedef struct CB_LAYOUTRECALL4args CB_LAYOUTRECALL4args; struct CB_LAYOUTRECALL4res { nfsstat4 clorr_status; }; typedef struct CB_LAYOUTRECALL4res CB_LAYOUTRECALL4res; enum notify_type4 { NOTIFY4_CHANGE_CHILD_ATTRS = 0, NOTIFY4_CHANGE_DIR_ATTRS = 1, NOTIFY4_REMOVE_ENTRY = 2, NOTIFY4_ADD_ENTRY = 3, NOTIFY4_RENAME_ENTRY = 4, NOTIFY4_CHANGE_COOKIE_VERIFIER = 5, }; typedef enum notify_type4 notify_type4; struct notify_entry4 { component4 ne_file; fattr4 ne_attrs; }; typedef struct notify_entry4 notify_entry4; struct prev_entry4 { notify_entry4 pe_prev_entry; nfs_cookie4 pe_prev_entry_cookie; }; typedef struct prev_entry4 prev_entry4; struct notify_remove4 { notify_entry4 nrm_old_entry; nfs_cookie4 nrm_old_entry_cookie; }; typedef struct notify_remove4 notify_remove4; struct notify_add4 { struct { u_int nad_old_entry_len; notify_remove4 *nad_old_entry_val; } nad_old_entry; notify_entry4 nad_new_entry; struct { u_int nad_new_entry_cookie_len; nfs_cookie4 *nad_new_entry_cookie_val; } nad_new_entry_cookie; struct { u_int nad_prev_entry_len; prev_entry4 *nad_prev_entry_val; } nad_prev_entry; bool_t nad_last_entry; }; typedef struct notify_add4 notify_add4; struct notify_attr4 { notify_entry4 na_changed_entry; }; typedef struct notify_attr4 notify_attr4; struct notify_rename4 { notify_remove4 nrn_old_entry; notify_add4 nrn_new_entry; }; typedef struct notify_rename4 notify_rename4; struct notify_verifier4 { verifier4 nv_old_cookieverf; verifier4 nv_new_cookieverf; }; typedef struct notify_verifier4 notify_verifier4; typedef struct { u_int notifylist4_len; char *notifylist4_val; } notifylist4; struct notify4 { struct bitmap4 notify_mask; notifylist4 notify_vals; }; typedef struct notify4 notify4; struct CB_NOTIFY4args { stateid4 cna_stateid; nfs_fh4 cna_fh; struct { u_int cna_changes_len; notify4 *cna_changes_val; } cna_changes; }; typedef struct CB_NOTIFY4args CB_NOTIFY4args; struct CB_NOTIFY4res { nfsstat4 cnr_status; }; typedef struct CB_NOTIFY4res CB_NOTIFY4res; struct CB_PUSH_DELEG4args { nfs_fh4 cpda_fh; open_delegation4 cpda_delegation; }; typedef struct CB_PUSH_DELEG4args CB_PUSH_DELEG4args; struct CB_PUSH_DELEG4res { nfsstat4 cpdr_status; }; typedef struct CB_PUSH_DELEG4res CB_PUSH_DELEG4res; #define RCA4_TYPE_MASK_RDATA_DLG 0 #define RCA4_TYPE_MASK_WDATA_DLG 1 #define RCA4_TYPE_MASK_DIR_DLG 2 #define RCA4_TYPE_MASK_FILE_LAYOUT 3 #define RCA4_TYPE_MASK_BLK_LAYOUT 4 #define RCA4_TYPE_MASK_OBJ_LAYOUT_MIN 8 #define RCA4_TYPE_MASK_OBJ_LAYOUT_MAX 9 #define RCA4_TYPE_MASK_OTHER_LAYOUT_MIN 12 #define RCA4_TYPE_MASK_OTHER_LAYOUT_MAX 15 #define RCA4_TYPE_MASK_FF_LAYOUT_MIN 16 #define RCA4_TYPE_MASK_FF_LAYOUT_MAX 17 struct CB_RECALL_ANY4args { uint32_t craa_objects_to_keep; struct bitmap4 craa_type_mask; }; typedef struct CB_RECALL_ANY4args CB_RECALL_ANY4args; struct CB_RECALL_ANY4res { nfsstat4 crar_status; }; typedef struct CB_RECALL_ANY4res CB_RECALL_ANY4res; typedef CB_RECALL_ANY4args CB_RECALLABLE_OBJ_AVAIL4args; struct CB_RECALLABLE_OBJ_AVAIL4res { nfsstat4 croa_status; }; typedef struct CB_RECALLABLE_OBJ_AVAIL4res CB_RECALLABLE_OBJ_AVAIL4res; struct CB_RECALL_SLOT4args { slotid4 rsa_target_highest_slotid; }; typedef struct CB_RECALL_SLOT4args CB_RECALL_SLOT4args; struct CB_RECALL_SLOT4res { nfsstat4 rsr_status; }; typedef struct CB_RECALL_SLOT4res CB_RECALL_SLOT4res; struct referring_call4 { sequenceid4 rc_sequenceid; slotid4 rc_slotid; }; typedef struct referring_call4 referring_call4; struct referring_call_list4 { sessionid4 rcl_sessionid; struct { u_int rcl_referring_calls_len; referring_call4 *rcl_referring_calls_val; } rcl_referring_calls; }; typedef struct referring_call_list4 referring_call_list4; struct CB_SEQUENCE4args { sessionid4 csa_sessionid; sequenceid4 csa_sequenceid; slotid4 csa_slotid; slotid4 csa_highest_slotid; bool_t csa_cachethis; struct { u_int csarcl_len; referring_call_list4 *csarcl_val; } csa_referring_call_lists; }; typedef struct CB_SEQUENCE4args CB_SEQUENCE4args; struct CB_SEQUENCE4resok { sessionid4 csr_sessionid; sequenceid4 csr_sequenceid; slotid4 csr_slotid; slotid4 csr_highest_slotid; slotid4 csr_target_highest_slotid; }; typedef struct CB_SEQUENCE4resok CB_SEQUENCE4resok; struct CB_SEQUENCE4res { nfsstat4 csr_status; union { CB_SEQUENCE4resok csr_resok4; } CB_SEQUENCE4res_u; }; typedef struct CB_SEQUENCE4res CB_SEQUENCE4res; struct CB_WANTS_CANCELLED4args { bool_t cwca_contended_wants_cancelled; bool_t cwca_resourced_wants_cancelled; }; typedef struct CB_WANTS_CANCELLED4args CB_WANTS_CANCELLED4args; struct CB_WANTS_CANCELLED4res { nfsstat4 cwcr_status; }; typedef struct CB_WANTS_CANCELLED4res CB_WANTS_CANCELLED4res; struct CB_NOTIFY_LOCK4args { nfs_fh4 cnla_fh; lock_owner4 cnla_lock_owner; }; typedef struct CB_NOTIFY_LOCK4args CB_NOTIFY_LOCK4args; struct CB_NOTIFY_LOCK4res { nfsstat4 cnlr_status; }; typedef struct CB_NOTIFY_LOCK4res CB_NOTIFY_LOCK4res; enum notify_deviceid_type4 { NOTIFY_DEVICEID4_CHANGE = 1, NOTIFY_DEVICEID4_DELETE = 2, }; typedef enum notify_deviceid_type4 notify_deviceid_type4; #define NOTIFY_DEVICEID4_CHANGE_MASK (1 << NOTIFY_DEVICEID4_CHANGE) #define NOTIFY_DEVICEID4_DELETE_MASK (1 << NOTIFY_DEVICEID4_DELETE) struct notify_deviceid_delete4 { layouttype4 ndd_layouttype; deviceid4 ndd_deviceid; }; typedef struct notify_deviceid_delete4 notify_deviceid_delete4; struct notify_deviceid_change4 { layouttype4 ndc_layouttype; deviceid4 ndc_deviceid; bool_t ndc_immediate; }; typedef struct notify_deviceid_change4 notify_deviceid_change4; struct CB_NOTIFY_DEVICEID4args { struct { u_int cnda_changes_len; notify4 *cnda_changes_val; } cnda_changes; }; typedef struct CB_NOTIFY_DEVICEID4args CB_NOTIFY_DEVICEID4args; struct CB_NOTIFY_DEVICEID4res { nfsstat4 cndr_status; }; typedef struct CB_NOTIFY_DEVICEID4res CB_NOTIFY_DEVICEID4res; /* Callback operations new to NFSv4.1 */ enum nfs_cb_opnum4 { NFS4_OP_CB_GETATTR = 3, NFS4_OP_CB_RECALL = 4, NFS4_OP_CB_LAYOUTRECALL = 5, NFS4_OP_CB_NOTIFY = 6, NFS4_OP_CB_PUSH_DELEG = 7, NFS4_OP_CB_RECALL_ANY = 8, NFS4_OP_CB_RECALLABLE_OBJ_AVAIL = 9, NFS4_OP_CB_RECALL_SLOT = 10, NFS4_OP_CB_SEQUENCE = 11, NFS4_OP_CB_WANTS_CANCELLED = 12, NFS4_OP_CB_NOTIFY_LOCK = 13, NFS4_OP_CB_NOTIFY_DEVICEID = 14, NFS4_OP_CB_ILLEGAL = 10044, }; typedef enum nfs_cb_opnum4 nfs_cb_opnum4; struct nfs_cb_argop4 { u_int argop; union { CB_GETATTR4args opcbgetattr; CB_RECALL4args opcbrecall; CB_LAYOUTRECALL4args opcblayoutrecall; CB_NOTIFY4args opcbnotify; CB_PUSH_DELEG4args opcbpush_deleg; CB_RECALL_ANY4args opcbrecall_any; CB_RECALLABLE_OBJ_AVAIL4args opcbrecallable_obj_avail; CB_RECALL_SLOT4args opcbrecall_slot; CB_SEQUENCE4args opcbsequence; CB_WANTS_CANCELLED4args opcbwants_cancelled; CB_NOTIFY_LOCK4args opcbnotify_lock; CB_NOTIFY_DEVICEID4args opcbnotify_deviceid; } nfs_cb_argop4_u; }; typedef struct nfs_cb_argop4 nfs_cb_argop4; struct nfs_cb_resop4 { u_int resop; union { CB_GETATTR4res opcbgetattr; CB_RECALL4res opcbrecall; CB_LAYOUTRECALL4res opcblayoutrecall; CB_NOTIFY4res opcbnotify; CB_PUSH_DELEG4res opcbpush_deleg; CB_RECALL_ANY4res opcbrecall_any; CB_RECALLABLE_OBJ_AVAIL4res opcbrecallable_obj_avail; CB_RECALL_SLOT4res opcbrecall_slot; CB_SEQUENCE4res opcbsequence; CB_WANTS_CANCELLED4res opcbwants_cancelled; CB_NOTIFY_LOCK4res opcbnotify_lock; CB_NOTIFY_DEVICEID4res opcbnotify_deviceid; CB_ILLEGAL4res opcbillegal; } nfs_cb_resop4_u; }; typedef struct nfs_cb_resop4 nfs_cb_resop4; struct CB_COMPOUND4args { utf8str_cs tag; uint32_t minorversion; uint32_t callback_ident; struct { u_int argarray_len; nfs_cb_argop4 *argarray_val; } argarray; }; typedef struct CB_COMPOUND4args CB_COMPOUND4args; struct CB_COMPOUND4res { nfsstat4 status; utf8str_cs tag; struct { u_int resarray_len; nfs_cb_resop4 *resarray_val; } resarray; }; typedef struct CB_COMPOUND4res CB_COMPOUND4res; #define NFS4_PROGRAM 100003 #define NFS_V4 4 #define NFSPROC4_NULL 0 extern void *nfsproc4_null_4(void *, CLIENT *); extern void *nfsproc4_null_4_svc(void *, struct svc_req *); #define NFSPROC4_COMPOUND 1 extern COMPOUND4res *nfsproc4_compound_4(COMPOUND4args *, CLIENT *); extern COMPOUND4res *nfsproc4_compound_4_svc(COMPOUND4args *, struct svc_req *); #define NFS4_CALLBACK 0x40000000 #define NFS_CB 1 #define CB_NULL 0 extern void *cb_null_1(void *, CLIENT *); extern void *cb_null_1_svc(void *, struct svc_req *); #define CB_COMPOUND 1 extern CB_COMPOUND4res *cb_compound_1(CB_COMPOUND4args *, CLIENT *); extern CB_COMPOUND4res *cb_compound_1_svc(CB_COMPOUND4args *, struct svc_req *); /* the xdr functions */ static inline bool xdr_nfs_ftype4(XDR *xdrs, nfs_ftype4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_nfsstat4(XDR *xdrs, nfsstat4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_attrlist4(XDR *xdrs, attrlist4 *objp) { if (!inline_xdr_bytes(xdrs, (char **)&objp->attrlist4_val, &objp->attrlist4_len, XDR_BYTES_MAXLEN)) return false; return true; } static inline bool xdr_bitmap4(XDR *xdrs, struct bitmap4 *objp) { u_int32_t *map = objp->map; u_int i, mapsize; /* short circuit the free pass (done at the end) because we don't * allocate an array in the "conventional" sense here. There is * nothing to free and trying to free here would crash - bail out. */ if (xdrs->x_op == XDR_FREE) return true; /* for the same reason, calling xdr_array doesn't work for us (we need * to accept bitmaps bigger than BITMAP4_MAPLEN, but throw the rest away * so manually do the looping and skip the end */ if (!inline_xdr_u_int(xdrs, &objp->bitmap4_len)) return false; mapsize = MIN(objp->bitmap4_len, BITMAP4_MAPLEN); for (i = 0; i < mapsize; i++) if (!inline_xdr_u_int32_t(xdrs, &map[i])) return false; /* skip any further elements and lie on bitmap len */ for (i = mapsize; i < objp->bitmap4_len; i++) { u_int crud = 0; if (!inline_xdr_u_int32_t(xdrs, &crud)) return false; } objp->bitmap4_len = mapsize; return true; } static inline bool xdr_changeid4(XDR *xdrs, changeid4 *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_clientid4(XDR *xdrs, clientid4 *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_count4(XDR *xdrs, count4 *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_length4(XDR *xdrs, length4 *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_mode4(XDR *xdrs, mode4 *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_nfs_cookie4(XDR *xdrs, nfs_cookie4 *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_nfs_fh4(XDR *xdrs, nfs_fh4 *objp) { if (!inline_xdr_bytes(xdrs, (char **)&objp->nfs_fh4_val, &objp->nfs_fh4_len, NFS4_FHSIZE)) return false; return true; } static inline bool xdr_offset4(XDR *xdrs, offset4 *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_qop4(XDR *xdrs, qop4 *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_sec_oid4(XDR *xdrs, sec_oid4 *objp) { if (!inline_xdr_bytes(xdrs, (char **)&objp->sec_oid4_val, &objp->sec_oid4_len, XDR_BYTES_MAXLEN)) return false; return true; } static inline bool xdr_sequenceid4(XDR *xdrs, sequenceid4 *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_seqid4(XDR *xdrs, seqid4 *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_sessionid4(XDR *xdrs, sessionid4 objp) { if (!xdr_opaque(xdrs, objp, NFS4_SESSIONID_SIZE)) return false; return true; } static inline bool xdr_slotid4(XDR *xdrs, slotid4 *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_utf8string_decode(XDR *xdrs, utf8string *objp, u_int maxsize) { /* sp is the actual string pointer */ bool allocated_locally = false; char *sp = objp->utf8string_val; uint32_t size; bool ret; /* * first deal with the length since xdr bytes are counted */ if (!XDR_GETUINT32(xdrs, &size)) { LogDebug(COMPONENT_TIRPC, "%s:%u ERROR size", __func__, __LINE__); return false; } if (size >= maxsize) { LogDebug(COMPONENT_TIRPC, "%s:%u ERROR size %" PRIu32 " > max %u", __func__, __LINE__, size, maxsize); return false; } /* only valid size */ objp->utf8string_len = (u_int)size; /* * now deal with the actual bytes of the string */ if (!size) return true; if (!sp) { sp = (char *)gsh_malloc(size + 1); allocated_locally = true; } ret = xdr_opaque_decode(xdrs, sp, size); if (!ret) { if (allocated_locally) /* Only free if we allocated */ gsh_free(sp); return ret; } objp->utf8string_val = sp; /* only valid pointer */ sp[size] = '\0'; return ret; } static inline bool inline_xdr_utf8string(XDR *xdrs, utf8string *objp, u_int maxsize) { if (xdrs->x_op == XDR_DECODE) { return xdr_utf8string_decode(xdrs, objp, maxsize); } else { return inline_xdr_bytes(xdrs, &objp->utf8string_val, &objp->utf8string_len, maxsize); } } static inline bool xdr_utf8string(XDR *xdrs, utf8string *objp) { return inline_xdr_utf8string(xdrs, objp, XDR_STRING_MAXLEN); } static inline bool xdr_utf8str_cis(XDR *xdrs, utf8str_cis *objp) { if (!xdr_utf8string(xdrs, objp)) return false; return true; } static inline bool xdr_utf8str_cs(XDR *xdrs, utf8str_cs *objp) { if (!xdr_utf8string(xdrs, objp)) return false; return true; } static inline bool xdr_utf8str_mixed(XDR *xdrs, utf8str_mixed *objp) { if (!xdr_utf8string(xdrs, objp)) return false; return true; } static inline bool xdr_component4(XDR *xdrs, component4 *objp) { if (!xdr_utf8str_cs(xdrs, objp)) return false; return true; } static inline bool xdr_linktext4(XDR *xdrs, linktext4 *objp) { if (!xdr_utf8str_cs(xdrs, objp)) return false; return true; } static inline bool xdr_pathname4(XDR *xdrs, pathname4 *objp) { if (!xdr_array(xdrs, (char **)&objp->pathname4_val, &objp->pathname4_len, XDR_ARRAY_MAXLEN, sizeof(component4), (xdrproc_t)xdr_component4)) return false; return true; } static inline bool xdr_verifier4(XDR *xdrs, verifier4 objp) { if (!xdr_opaque(xdrs, objp, NFS4_VERIFIER_SIZE)) return false; return true; } static inline bool xdr_nfstime4(XDR *xdrs, nfstime4 *objp) { if (!xdr_int64_t(xdrs, &objp->seconds)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->nseconds)) return false; return true; } static inline bool xdr_time_how4(XDR *xdrs, time_how4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_settime4(XDR *xdrs, settime4 *objp) { if (!xdr_time_how4(xdrs, &objp->set_it)) return false; switch (objp->set_it) { case SET_TO_CLIENT_TIME4: if (!xdr_nfstime4(xdrs, &objp->settime4_u.time)) return false; break; default: break; } return true; } static inline bool xdr_nfs_lease4(XDR *xdrs, nfs_lease4 *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_fsid4(XDR *xdrs, fsid4 *objp) { if (!inline_xdr_u_int64_t(xdrs, &objp->major)) return false; if (!inline_xdr_u_int64_t(xdrs, &objp->minor)) return false; return true; } static inline bool xdr_change_policy4(XDR *xdrs, change_policy4 *objp) { if (!inline_xdr_u_int64_t(xdrs, &objp->cp_major)) return false; if (!inline_xdr_u_int64_t(xdrs, &objp->cp_minor)) return false; return true; } static inline bool xdr_sec_label4(XDR *xdrs, fattr4_sec_label *objp) { if (!inline_xdr_u_int32_t(xdrs, &objp->slai_lfs.lfs_lfs)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->slai_lfs.lfs_pi)) return false; if (!inline_xdr_bytes(xdrs, &objp->slai_data.slai_data_val, &objp->slai_data.slai_data_len, XDR_ARRAY_MAXLEN)) return false; return true; } static inline bool xdr_fs_location4(XDR *xdrs, fs_location4 *objp) { if (!xdr_array(xdrs, (char **)&objp->server.server_val, &objp->server.server_len, XDR_ARRAY_MAXLEN, sizeof(utf8str_cis), (xdrproc_t)xdr_utf8str_cis)) return false; if (!xdr_pathname4(xdrs, &objp->rootpath)) return false; return true; } static inline bool xdr_fs_locations4(XDR *xdrs, fs_locations4 *objp) { if (!xdr_pathname4(xdrs, &objp->fs_root)) return false; if (!xdr_array(xdrs, (char **)&objp->locations.locations_val, &objp->locations.locations_len, XDR_ARRAY_MAXLEN, sizeof(fs_location4), (xdrproc_t)xdr_fs_location4)) return false; return true; } static inline bool xdr_acetype4(XDR *xdrs, acetype4 *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_aceflag4(XDR *xdrs, aceflag4 *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_acemask4(XDR *xdrs, acemask4 *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_nfsace4(XDR *xdrs, nfsace4 *objp) { if (!xdr_acetype4(xdrs, &objp->type)) return false; if (!xdr_aceflag4(xdrs, &objp->flag)) return false; if (!xdr_acemask4(xdrs, &objp->access_mask)) return false; if (!xdr_utf8str_mixed(xdrs, &objp->who)) return false; return true; } static inline bool xdr_aclflag4(XDR *xdrs, aclflag4 *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_nfsacl41(XDR *xdrs, nfsacl41 *objp) { if (!xdr_aclflag4(xdrs, &objp->na41_flag)) return false; if (!xdr_array(xdrs, (char **)&objp->na41_aces.na41_aces_val, &objp->na41_aces.na41_aces_len, XDR_ARRAY_MAXLEN, sizeof(nfsace4), (xdrproc_t)xdr_nfsace4)) return false; return true; } static inline bool xdr_mode_masked4(XDR *xdrs, mode_masked4 *objp) { if (!xdr_mode4(xdrs, &objp->mm_value_to_set)) return false; if (!xdr_mode4(xdrs, &objp->mm_mask_bits)) return false; return true; } static inline bool xdr_specdata4(XDR *xdrs, specdata4 *objp) { if (!inline_xdr_u_int32_t(xdrs, &objp->specdata1)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->specdata2)) return false; return true; } static inline bool xdr_netaddr4(XDR *xdrs, netaddr4 *objp) { if (!inline_xdr_string(xdrs, &objp->r_netid, XDR_STRING_MAXLEN)) return false; if (!inline_xdr_string(xdrs, &objp->r_addr, XDR_STRING_MAXLEN)) return false; return true; } static inline bool xdr_nfs_impl_id4(XDR *xdrs, nfs_impl_id4 *objp) { if (!xdr_utf8str_cis(xdrs, &objp->nii_domain)) return false; if (!xdr_utf8str_cs(xdrs, &objp->nii_name)) return false; if (!xdr_nfstime4(xdrs, &objp->nii_date)) return false; return true; } static inline bool xdr_stateid4(XDR *xdrs, stateid4 *objp) { if (!inline_xdr_u_int32_t(xdrs, &objp->seqid)) return false; if (!xdr_opaque(xdrs, objp->other, 12)) return false; return true; } static inline bool xdr_layouttype4(XDR *xdrs, layouttype4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_layout_content4(XDR *xdrs, layout_content4 *objp) { if (!xdr_layouttype4(xdrs, &objp->loc_type)) return false; if (!inline_xdr_bytes(xdrs, (char **)&objp->loc_body.loc_body_val, &objp->loc_body.loc_body_len, XDR_BYTES_MAXLEN)) return false; return true; } /* * LAYOUT4_OSD2_OBJECTS loc_body description * is in a separate .x file */ /* * LAYOUT4_FLEX_FILES loc_body description * is in a separate .x file */ /* * LAYOUT4_BLOCK_VOLUME loc_body description * is in a separate .x file */ static inline bool xdr_layouthint4(XDR *xdrs, layouthint4 *objp) { if (!xdr_layouttype4(xdrs, &objp->loh_type)) return false; if (!inline_xdr_bytes(xdrs, (char **)&objp->loh_body.loh_body_val, &objp->loh_body.loh_body_len, XDR_BYTES_MAXLEN)) return false; return true; } static inline bool xdr_layoutiomode4(XDR *xdrs, layoutiomode4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_layout4(XDR *xdrs, layout4 *objp) { if (!xdr_offset4(xdrs, &objp->lo_offset)) return false; if (!xdr_length4(xdrs, &objp->lo_length)) return false; if (!xdr_layoutiomode4(xdrs, &objp->lo_iomode)) return false; if (!xdr_layout_content4(xdrs, &objp->lo_content)) return false; return true; } static inline bool xdr_deviceid4(XDR *xdrs, deviceid4 objp) { if (!xdr_opaque(xdrs, objp, NFS4_DEVICEID4_SIZE)) return false; return true; } static inline bool xdr_device_addr4(XDR *xdrs, device_addr4 *objp) { if (!xdr_layouttype4(xdrs, &objp->da_layout_type)) return false; if (!inline_xdr_bytes( xdrs, (char **)&objp->da_addr_body.da_addr_body_val, &objp->da_addr_body.da_addr_body_len, XDR_BYTES_MAXLEN)) return false; return true; } static inline bool xdr_layoutupdate4(XDR *xdrs, layoutupdate4 *objp) { if (!xdr_layouttype4(xdrs, &objp->lou_type)) return false; if (!inline_xdr_bytes(xdrs, (char **)&objp->lou_body.lou_body_val, &objp->lou_body.lou_body_len, XDR_BYTES_MAXLEN)) return false; return true; } static inline bool xdr_layoutreturn_type4(XDR *xdrs, layoutreturn_type4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } /* layouttype4 specific data */ static inline bool xdr_layoutreturn_file4(XDR *xdrs, layoutreturn_file4 *objp) { if (!xdr_offset4(xdrs, &objp->lrf_offset)) return false; if (!xdr_length4(xdrs, &objp->lrf_length)) return false; if (!xdr_stateid4(xdrs, &objp->lrf_stateid)) return false; if (!inline_xdr_bytes(xdrs, (char **)&objp->lrf_body.lrf_body_val, &objp->lrf_body.lrf_body_len, XDR_BYTES_MAXLEN)) return false; return true; } static inline bool xdr_layoutreturn4(XDR *xdrs, layoutreturn4 *objp) { if (!xdr_layoutreturn_type4(xdrs, &objp->lr_returntype)) return false; switch (objp->lr_returntype) { case LAYOUTRETURN4_FILE: if (!xdr_layoutreturn_file4(xdrs, &objp->layoutreturn4_u.lr_layout)) return false; break; default: break; } return true; } static inline bool xdr_fs4_status_type(XDR *xdrs, fs4_status_type *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_fs4_status(XDR *xdrs, fs4_status *objp) { if (!xdr_bool(xdrs, &objp->fss_absent)) return false; if (!xdr_fs4_status_type(xdrs, &objp->fss_type)) return false; if (!xdr_utf8str_cs(xdrs, &objp->fss_source)) return false; if (!xdr_utf8str_cs(xdrs, &objp->fss_current)) return false; if (!xdr_int32_t(xdrs, &objp->fss_age)) return false; if (!xdr_nfstime4(xdrs, &objp->fss_version)) return false; return true; } static inline bool xdr_threshold4_read_size(XDR *xdrs, threshold4_read_size *objp) { if (!xdr_length4(xdrs, objp)) return false; return true; } static inline bool xdr_threshold4_write_size(XDR *xdrs, threshold4_write_size *objp) { if (!xdr_length4(xdrs, objp)) return false; return true; } static inline bool xdr_threshold4_read_iosize(XDR *xdrs, threshold4_read_iosize *objp) { if (!xdr_length4(xdrs, objp)) return false; return true; } static inline bool xdr_threshold4_write_iosize(XDR *xdrs, threshold4_write_iosize *objp) { if (!xdr_length4(xdrs, objp)) return false; return true; } static inline bool xdr_threshold_item4(XDR *xdrs, threshold_item4 *objp) { if (!xdr_layouttype4(xdrs, &objp->thi_layout_type)) return false; if (!xdr_bitmap4(xdrs, &objp->thi_hintset)) return false; if (!inline_xdr_bytes( xdrs, (char **)&objp->thi_hintlist.thi_hintlist_val, &objp->thi_hintlist.thi_hintlist_len, XDR_BYTES_MAXLEN)) return false; return true; } static inline bool xdr_mdsthreshold4(XDR *xdrs, mdsthreshold4 *objp) { if (!xdr_array(xdrs, (char **)&objp->mth_hints.mth_hints_val, &objp->mth_hints.mth_hints_len, XDR_ARRAY_MAXLEN, sizeof(threshold_item4), (xdrproc_t)xdr_threshold_item4)) return false; return true; } static inline bool xdr_retention_get4(XDR *xdrs, retention_get4 *objp) { if (!inline_xdr_u_int64_t(xdrs, &objp->rg_duration)) return false; if (!xdr_array(xdrs, (char **)&objp->rg_begin_time.rg_begin_time_val, (u_int *)&objp->rg_begin_time.rg_begin_time_len, 1, sizeof(nfstime4), (xdrproc_t)xdr_nfstime4)) return false; return true; } static inline bool xdr_retention_set4(XDR *xdrs, retention_set4 *objp) { if (!inline_xdr_bool(xdrs, &objp->rs_enable)) return false; if (!xdr_array(xdrs, (char **)&objp->rs_duration.rs_duration_val, (u_int *)&objp->rs_duration.rs_duration_len, 1, sizeof(uint64_t), (xdrproc_t)inline_xdr_u_int64_t)) return false; return true; } static inline bool xdr_fs_charset_cap4(XDR *xdrs, fs_charset_cap4 *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_supported_attrs(XDR *xdrs, fattr4_supported_attrs *objp) { if (!xdr_bitmap4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_type(XDR *xdrs, fattr4_type *objp) { if (!xdr_nfs_ftype4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_fh_expire_type(XDR *xdrs, fattr4_fh_expire_type *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_change(XDR *xdrs, fattr4_change *objp) { if (!xdr_changeid4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_size(XDR *xdrs, fattr4_size *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_link_support(XDR *xdrs, fattr4_link_support *objp) { if (!inline_xdr_bool(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_symlink_support(XDR *xdrs, fattr4_symlink_support *objp) { if (!inline_xdr_bool(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_named_attr(XDR *xdrs, fattr4_named_attr *objp) { if (!inline_xdr_bool(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_fsid(XDR *xdrs, fattr4_fsid *objp) { if (!xdr_fsid4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_unique_handles(XDR *xdrs, fattr4_unique_handles *objp) { if (!inline_xdr_bool(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_lease_time(XDR *xdrs, fattr4_lease_time *objp) { if (!xdr_nfs_lease4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_rdattr_error(XDR *xdrs, fattr4_rdattr_error *objp) { if (!xdr_nfsstat4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_acl(XDR *xdrs, fattr4_acl *objp) { if (!xdr_array(xdrs, (char **)&objp->fattr4_acl_val, &objp->fattr4_acl_len, XDR_ARRAY_MAXLEN, sizeof(nfsace4), (xdrproc_t)xdr_nfsace4)) return false; return true; } static inline bool xdr_fattr4_aclsupport(XDR *xdrs, fattr4_aclsupport *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_archive(XDR *xdrs, fattr4_archive *objp) { if (!inline_xdr_bool(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_cansettime(XDR *xdrs, fattr4_cansettime *objp) { if (!inline_xdr_bool(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_case_insensitive(XDR *xdrs, fattr4_case_insensitive *objp) { if (!inline_xdr_bool(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_case_preserving(XDR *xdrs, fattr4_case_preserving *objp) { if (!inline_xdr_bool(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_chown_restricted(XDR *xdrs, fattr4_chown_restricted *objp) { if (!inline_xdr_bool(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_fileid(XDR *xdrs, fattr4_fileid *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_files_avail(XDR *xdrs, fattr4_files_avail *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_filehandle(XDR *xdrs, fattr4_filehandle *objp) { if (!xdr_nfs_fh4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_files_free(XDR *xdrs, fattr4_files_free *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_files_total(XDR *xdrs, fattr4_files_total *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_fs_locations(XDR *xdrs, fattr4_fs_locations *objp) { if (!xdr_fs_locations4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_hidden(XDR *xdrs, fattr4_hidden *objp) { if (!inline_xdr_bool(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_homogeneous(XDR *xdrs, fattr4_homogeneous *objp) { if (!inline_xdr_bool(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_maxfilesize(XDR *xdrs, fattr4_maxfilesize *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_maxlink(XDR *xdrs, fattr4_maxlink *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_maxname(XDR *xdrs, fattr4_maxname *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_maxread(XDR *xdrs, fattr4_maxread *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_maxwrite(XDR *xdrs, fattr4_maxwrite *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_mimetype(XDR *xdrs, fattr4_mimetype *objp) { if (!xdr_utf8str_cs(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_mode(XDR *xdrs, fattr4_mode *objp) { if (!xdr_mode4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_mode_set_masked(XDR *xdrs, fattr4_mode_set_masked *objp) { if (!xdr_mode_masked4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_mounted_on_fileid(XDR *xdrs, fattr4_mounted_on_fileid *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_no_trunc(XDR *xdrs, fattr4_no_trunc *objp) { if (!inline_xdr_bool(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_numlinks(XDR *xdrs, fattr4_numlinks *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_owner(XDR *xdrs, fattr4_owner *objp) { if (!xdr_utf8str_mixed(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_owner_group(XDR *xdrs, fattr4_owner_group *objp) { if (!xdr_utf8str_mixed(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_quota_avail_hard(XDR *xdrs, fattr4_quota_avail_hard *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_quota_avail_soft(XDR *xdrs, fattr4_quota_avail_soft *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_quota_used(XDR *xdrs, fattr4_quota_used *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_rawdev(XDR *xdrs, fattr4_rawdev *objp) { if (!xdr_specdata4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_space_avail(XDR *xdrs, fattr4_space_avail *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_space_free(XDR *xdrs, fattr4_space_free *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_space_total(XDR *xdrs, fattr4_space_total *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_space_used(XDR *xdrs, fattr4_space_used *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_system(XDR *xdrs, fattr4_system *objp) { if (!inline_xdr_bool(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_time_access(XDR *xdrs, fattr4_time_access *objp) { if (!xdr_nfstime4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_time_access_set(XDR *xdrs, fattr4_time_access_set *objp) { if (!xdr_settime4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_time_backup(XDR *xdrs, fattr4_time_backup *objp) { if (!xdr_nfstime4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_time_create(XDR *xdrs, fattr4_time_create *objp) { if (!xdr_nfstime4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_time_delta(XDR *xdrs, fattr4_time_delta *objp) { if (!xdr_nfstime4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_time_metadata(XDR *xdrs, fattr4_time_metadata *objp) { if (!xdr_nfstime4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_time_modify(XDR *xdrs, fattr4_time_modify *objp) { if (!xdr_nfstime4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_time_modify_set(XDR *xdrs, fattr4_time_modify_set *objp) { if (!xdr_settime4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_suppattr_exclcreat(XDR *xdrs, fattr4_suppattr_exclcreat *objp) { if (!xdr_bitmap4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_dir_notif_delay(XDR *xdrs, fattr4_dir_notif_delay *objp) { if (!xdr_nfstime4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_dirent_notif_delay(XDR *xdrs, fattr4_dirent_notif_delay *objp) { if (!xdr_nfstime4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_fs_layout_types(XDR *xdrs, fattr4_fs_layout_types *objp) { if (!xdr_array(xdrs, (char **)&objp->fattr4_fs_layout_types_val, &objp->fattr4_fs_layout_types_len, XDR_ARRAY_MAXLEN, sizeof(layouttype4), (xdrproc_t)xdr_layouttype4)) return false; return true; } static inline bool xdr_fattr4_fs_status(XDR *xdrs, fattr4_fs_status *objp) { if (!xdr_fs4_status(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_fs_charset_cap(XDR *xdrs, fattr4_fs_charset_cap *objp) { if (!xdr_fs_charset_cap4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_layout_alignment(XDR *xdrs, fattr4_layout_alignment *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_layout_blksize(XDR *xdrs, fattr4_layout_blksize *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_layout_hint(XDR *xdrs, fattr4_layout_hint *objp) { if (!xdr_layouthint4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_layout_types(XDR *xdrs, fattr4_layout_types *objp) { if (!xdr_array(xdrs, (char **)&objp->fattr4_layout_types_val, &objp->fattr4_layout_types_len, XDR_ARRAY_MAXLEN, sizeof(layouttype4), (xdrproc_t)xdr_layouttype4)) return false; return true; } static inline bool xdr_fattr4_mdsthreshold(XDR *xdrs, fattr4_mdsthreshold *objp) { if (!xdr_mdsthreshold4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_retention_get(XDR *xdrs, fattr4_retention_get *objp) { if (!xdr_retention_get4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_retention_set(XDR *xdrs, fattr4_retention_set *objp) { if (!xdr_retention_set4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_retentevt_get(XDR *xdrs, fattr4_retentevt_get *objp) { if (!xdr_retention_get4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_retentevt_set(XDR *xdrs, fattr4_retentevt_set *objp) { if (!xdr_retention_set4(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_retention_hold(XDR *xdrs, fattr4_retention_hold *objp) { if (!inline_xdr_u_int64_t(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_dacl(XDR *xdrs, fattr4_dacl *objp) { if (!xdr_nfsacl41(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_sacl(XDR *xdrs, fattr4_sacl *objp) { if (!xdr_nfsacl41(xdrs, objp)) return false; return true; } static inline bool xdr_fattr4_change_policy(XDR *xdrs, fattr4_change_policy *objp) { if (!xdr_change_policy4(xdrs, objp)) return false; return true; } /* * REQUIRED Attributes */ /* new to NFSV4.1 */ /* * RECOMMENDED Attributes */ /* new to NFSV4.1 */ static inline bool xdr_fattr4(XDR *xdrs, fattr4 *objp) { if (!xdr_bitmap4(xdrs, &objp->attrmask)) return false; if (!xdr_attrlist4(xdrs, &objp->attr_vals)) return false; return true; } static inline bool xdr_change_info4(XDR *xdrs, change_info4 *objp) { if (!inline_xdr_bool(xdrs, &objp->atomic)) return false; if (!xdr_changeid4(xdrs, &objp->before)) return false; if (!xdr_changeid4(xdrs, &objp->after)) return false; return true; } static inline bool xdr_clientaddr4(XDR *xdrs, clientaddr4 *objp) { if (!xdr_netaddr4(xdrs, objp)) return false; return true; } static inline bool xdr_cb_client4(XDR *xdrs, cb_client4 *objp) { if (!inline_xdr_u_int32_t(xdrs, &objp->cb_program)) return false; if (!xdr_netaddr4(xdrs, &objp->cb_location)) return false; return true; } static inline bool xdr_nfs_client_id4(XDR *xdrs, nfs_client_id4 *objp) { if (!xdr_verifier4(xdrs, objp->verifier)) return false; if (!inline_xdr_bytes(xdrs, (char **)&objp->id.id_val, (u_int *)&objp->id.id_len, NFS4_OPAQUE_LIMIT)) return false; return true; } static inline bool xdr_client_owner4(XDR *xdrs, client_owner4 *objp) { if (!xdr_verifier4(xdrs, objp->co_verifier)) return false; if (!inline_xdr_bytes(xdrs, (char **)&objp->co_ownerid.co_ownerid_val, (u_int *)&objp->co_ownerid.co_ownerid_len, NFS4_OPAQUE_LIMIT)) return false; return true; } static inline bool xdr_server_owner4(XDR *xdrs, server_owner4 *objp) { if (!inline_xdr_u_int64_t(xdrs, &objp->so_minor_id)) return false; if (!inline_xdr_bytes(xdrs, (char **)&objp->so_major_id.so_major_id_val, (u_int *)&objp->so_major_id.so_major_id_len, NFS4_OPAQUE_LIMIT)) return false; return true; } static inline bool xdr_state_owner4(XDR *xdrs, state_owner4 *objp) { if (!xdr_clientid4(xdrs, &objp->clientid)) return false; if (!inline_xdr_bytes(xdrs, (char **)&objp->owner.owner_val, (u_int *)&objp->owner.owner_len, NFS4_OPAQUE_LIMIT)) return false; return true; } static inline bool xdr_open_owner4(XDR *xdrs, open_owner4 *objp) { if (!xdr_state_owner4(xdrs, objp)) return false; return true; } static inline bool xdr_lock_owner4(XDR *xdrs, lock_owner4 *objp) { if (!xdr_state_owner4(xdrs, objp)) return false; return true; } static inline bool xdr_nfs_lock_type4(XDR *xdrs, nfs_lock_type4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } /* Input for computing subkeys */ static inline bool xdr_ssv_subkey4(XDR *xdrs, ssv_subkey4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } /* Input for computing smt_hmac */ static inline bool xdr_ssv_mic_plain_tkn4(XDR *xdrs, ssv_mic_plain_tkn4 *objp) { if (!inline_xdr_u_int32_t(xdrs, &objp->smpt_ssv_seq)) return false; if (!inline_xdr_bytes( xdrs, (char **)&objp->smpt_orig_plain.smpt_orig_plain_val, &objp->smpt_orig_plain.smpt_orig_plain_len, XDR_BYTES_MAXLEN)) return false; return true; } /* SSV GSS PerMsgToken token */ static inline bool xdr_ssv_mic_tkn4(XDR *xdrs, ssv_mic_tkn4 *objp) { if (!inline_xdr_u_int32_t(xdrs, &objp->smt_ssv_seq)) return false; if (!inline_xdr_bytes(xdrs, (char **)&objp->smt_hmac.smt_hmac_val, &objp->smt_hmac.smt_hmac_len, XDR_BYTES_MAXLEN)) return false; return true; } /* Input for computing ssct_encr_data and ssct_hmac */ static inline bool xdr_ssv_seal_plain_tkn4(XDR *xdrs, ssv_seal_plain_tkn4 *objp) { if (!inline_xdr_bytes( xdrs, (char **)&objp->sspt_confounder.sspt_confounder_val, &objp->sspt_confounder.sspt_confounder_len, XDR_BYTES_MAXLEN)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->sspt_ssv_seq)) return false; if (!inline_xdr_bytes( xdrs, (char **)&objp->sspt_orig_plain.sspt_orig_plain_val, &objp->sspt_orig_plain.sspt_orig_plain_len, XDR_BYTES_MAXLEN)) return false; if (!inline_xdr_bytes(xdrs, (char **)&objp->sspt_pad.sspt_pad_val, &objp->sspt_pad.sspt_pad_len, XDR_BYTES_MAXLEN)) return false; return true; } /* SSV GSS SealedMessage token */ static inline bool xdr_ssv_seal_cipher_tkn4(XDR *xdrs, ssv_seal_cipher_tkn4 *objp) { if (!inline_xdr_u_int32_t(xdrs, &objp->ssct_ssv_seq)) return false; if (!inline_xdr_bytes(xdrs, (char **)&objp->ssct_iv.ssct_iv_val, &objp->ssct_iv.ssct_iv_len, XDR_BYTES_MAXLEN)) return false; if (!inline_xdr_bytes( xdrs, (char **)&objp->ssct_encr_data.ssct_encr_data_val, &objp->ssct_encr_data.ssct_encr_data_len, XDR_BYTES_MAXLEN)) return false; if (!inline_xdr_bytes(xdrs, (char **)&objp->ssct_hmac.ssct_hmac_val, &objp->ssct_hmac.ssct_hmac_len, XDR_BYTES_MAXLEN)) return false; return true; } static inline bool xdr_fs_locations_server4(XDR *xdrs, fs_locations_server4 *objp) { if (!xdr_int32_t(xdrs, &objp->fls_currency)) return false; if (!inline_xdr_bytes(xdrs, (char **)&objp->fls_info.fls_info_val, &objp->fls_info.fls_info_len, XDR_BYTES_MAXLEN)) return false; if (!xdr_utf8str_cis(xdrs, &objp->fls_server)) return false; return true; } static inline bool xdr_fs_locations_item4(XDR *xdrs, fs_locations_item4 *objp) { if (!xdr_array(xdrs, (char **)&objp->fli_entries.fli_entries_val, &objp->fli_entries.fli_entries_len, XDR_ARRAY_MAXLEN, sizeof(fs_locations_server4), (xdrproc_t)xdr_fs_locations_server4)) return false; if (!xdr_pathname4(xdrs, &objp->fli_rootpath)) return false; return true; } static inline bool xdr_fs_locations_info4(XDR *xdrs, fs_locations_info4 *objp) { if (!inline_xdr_u_int32_t(xdrs, &objp->fli_flags)) return false; if (!xdr_int32_t(xdrs, &objp->fli_valid_for)) return false; if (!xdr_pathname4(xdrs, &objp->fli_fs_root)) return false; if (!xdr_array(xdrs, (char **)&objp->fli_items.fli_items_val, &objp->fli_items.fli_items_len, XDR_ARRAY_MAXLEN, sizeof(fs_locations_item4), (xdrproc_t)xdr_fs_locations_item4)) return false; return true; } static inline bool xdr_fattr4_fs_locations_info(XDR *xdrs, fattr4_fs_locations_info *objp) { if (!xdr_fs_locations_info4(xdrs, objp)) return false; return true; } static inline bool xdr_nfl_util4(XDR *xdrs, nfl_util4 *objp) { if (!inline_xdr_u_int32_t(xdrs, objp)) return false; return true; } static inline bool xdr_filelayout_hint_care4(XDR *xdrs, filelayout_hint_care4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } /* Encoded in the loh_body field of data type layouthint4: */ static inline bool xdr_nfsv4_1_file_layouthint4(XDR *xdrs, nfsv4_1_file_layouthint4 *objp) { if (!inline_xdr_u_int32_t(xdrs, &objp->nflh_care)) return false; if (!xdr_nfl_util4(xdrs, &objp->nflh_util)) return false; if (!xdr_count4(xdrs, &objp->nflh_stripe_count)) return false; return true; } static inline bool xdr_multipath_list4(XDR *xdrs, multipath_list4 *objp) { if (!xdr_array(xdrs, (char **)&objp->multipath_list4_val, &objp->multipath_list4_len, XDR_ARRAY_MAXLEN, sizeof(netaddr4), (xdrproc_t)xdr_netaddr4)) return false; return true; } /* * Encoded in the da_addr_body field of * data type device_addr4: */ static inline bool xdr_nfsv4_1_file_layout_ds_addr4(XDR *xdrs, nfsv4_1_file_layout_ds_addr4 *objp) { if (!xdr_array(xdrs, (char **)&objp->nflda_stripe_indices .nflda_stripe_indices_val, &objp->nflda_stripe_indices.nflda_stripe_indices_len, XDR_ARRAY_MAXLEN, sizeof(uint32_t), (xdrproc_t)xdr_uint32_t)) return false; if (!xdr_array( xdrs, (char **)&objp->nflda_multipath_ds_list .nflda_multipath_ds_list_val, &objp->nflda_multipath_ds_list.nflda_multipath_ds_list_len, XDR_ARRAY_MAXLEN, sizeof(multipath_list4), (xdrproc_t)xdr_multipath_list4)) return false; return true; } /* * Encoded in the loc_body field of * data type layout_content4: */ static inline bool xdr_nfsv4_1_file_layout4(XDR *xdrs, nfsv4_1_file_layout4 *objp) { if (!xdr_deviceid4(xdrs, objp->nfl_deviceid)) return false; if (!xdr_nfl_util4(xdrs, &objp->nfl_util)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->nfl_first_stripe_index)) return false; if (!xdr_offset4(xdrs, &objp->nfl_pattern_offset)) return false; if (!xdr_array(xdrs, (char **)&objp->nfl_fh_list.nfl_fh_list_val, &objp->nfl_fh_list.nfl_fh_list_len, XDR_ARRAY_MAXLEN, sizeof(nfs_fh4), (xdrproc_t)xdr_nfs_fh4)) return false; return true; } /* * Encoded in the lou_body field of data type layoutupdate4: * Nothing. lou_body is a zero length array of bytes. */ /* * Encoded in the lrf_body field of * data type layoutreturn_file4: * Nothing. lrf_body is a zero length array of bytes. */ static inline bool xdr_ACCESS4args(XDR *xdrs, ACCESS4args *objp) { if (!inline_xdr_u_int32_t(xdrs, &objp->access)) return false; return true; } static inline bool xdr_ACCESS4resok(XDR *xdrs, ACCESS4resok *objp) { if (!inline_xdr_u_int32_t(xdrs, &objp->supported)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->access)) return false; return true; } static inline bool xdr_ACCESS4res(XDR *xdrs, ACCESS4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_ACCESS4resok(xdrs, &objp->ACCESS4res_u.resok4)) return false; break; default: break; } return true; } static inline bool xdr_CLOSE4args(XDR *xdrs, CLOSE4args *objp) { if (!xdr_seqid4(xdrs, &objp->seqid)) return false; if (!xdr_stateid4(xdrs, &objp->open_stateid)) return false; return true; } static inline bool xdr_CLOSE4res(XDR *xdrs, CLOSE4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_stateid4(xdrs, &objp->CLOSE4res_u.open_stateid)) return false; break; default: break; } return true; } static inline bool xdr_COMMIT4args(XDR *xdrs, COMMIT4args *objp) { if (!xdr_offset4(xdrs, &objp->offset)) return false; if (!xdr_count4(xdrs, &objp->count)) return false; return true; } static inline bool xdr_COMMIT4resok(XDR *xdrs, COMMIT4resok *objp) { if (!xdr_verifier4(xdrs, objp->writeverf)) return false; return true; } static inline bool xdr_COMMIT4res(XDR *xdrs, COMMIT4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_COMMIT4resok(xdrs, &objp->COMMIT4res_u.resok4)) return false; break; default: break; } return true; } static inline bool xdr_createtype4(XDR *xdrs, createtype4 *objp) { if (!xdr_nfs_ftype4(xdrs, &objp->type)) return false; switch (objp->type) { case NF4LNK: if (!xdr_linktext4(xdrs, &objp->createtype4_u.linkdata)) return false; break; case NF4BLK: case NF4CHR: if (!xdr_specdata4(xdrs, &objp->createtype4_u.devdata)) return false; break; case NF4SOCK: case NF4FIFO: case NF4DIR: break; default: break; } return true; } static inline bool xdr_CREATE4args(XDR *xdrs, CREATE4args *objp) { if (!xdr_createtype4(xdrs, &objp->objtype)) return false; if (!xdr_component4(xdrs, &objp->objname)) return false; if (!xdr_fattr4(xdrs, &objp->createattrs)) return false; return true; } static inline bool xdr_CREATE4resok(XDR *xdrs, CREATE4resok *objp) { if (!xdr_change_info4(xdrs, &objp->cinfo)) return false; if (!xdr_bitmap4(xdrs, &objp->attrset)) return false; return true; } static inline bool xdr_CREATE4res(XDR *xdrs, CREATE4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_CREATE4resok(xdrs, &objp->CREATE4res_u.resok4)) return false; break; default: break; } return true; } static inline bool xdr_DELEGPURGE4args(XDR *xdrs, DELEGPURGE4args *objp) { if (!xdr_clientid4(xdrs, &objp->clientid)) return false; return true; } static inline bool xdr_DELEGPURGE4res(XDR *xdrs, DELEGPURGE4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; return true; } static inline bool xdr_DELEGRETURN4args(XDR *xdrs, DELEGRETURN4args *objp) { if (!xdr_stateid4(xdrs, &objp->deleg_stateid)) return false; return true; } static inline bool xdr_DELEGRETURN4res(XDR *xdrs, DELEGRETURN4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; return true; } /* NFSv4.2 */ static inline bool xdr_GETXATTR4args(XDR *xdrs, GETXATTR4args *objp) { if (!xdr_component4(xdrs, &objp->gxa_name)) return false; return true; } static inline bool xdr_GETXATTR4res(XDR *xdrs, GETXATTR4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_utf8string(xdrs, &objp->GETXATTR4res_u.resok4.gxr_value)) return false; break; default: break; } return true; } static inline bool xdr_SETXATTR4args(XDR *xdrs, SETXATTR4args *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)&objp->sxa_option)) return false; if (!xdr_component4(xdrs, &objp->sxa_key)) return false; if (!xdr_utf8string(xdrs, &objp->sxa_value)) return false; return true; } static inline bool xdr_SETXATTR4res(XDR *xdrs, SETXATTR4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_change_info4(xdrs, &objp->SETXATTR4res_u.resok4.sxr_info)) return false; break; default: break; } return true; } static inline bool xdr_LISTXATTR4args(XDR *xdrs, LISTXATTR4args *objp) { if (!xdr_nfs_cookie4(xdrs, &objp->lxa_cookie)) return false; if (!xdr_count4(xdrs, &objp->lxa_maxcount)) return false; return true; } static inline bool xdr_xattrlist4(XDR *xdrs, xattrlist4 *objp) { if (!xdr_array(xdrs, (char **)&objp->xl4_entries, &objp->xl4_count, XDR_ARRAY_MAXLEN, sizeof(xattrkey4), (xdrproc_t)xdr_component4)) return false; return true; } static inline bool xdr_LISTXATTR4resok(XDR *xdrs, LISTXATTR4resok *objp) { if (!xdr_nfs_cookie4(xdrs, &objp->lxr_cookie)) return false; if (!xdr_xattrlist4(xdrs, &objp->lxr_names)) return false; if (!inline_xdr_bool(xdrs, &objp->lxr_eof)) return false; return true; } static inline bool xdr_LISTXATTR4res(XDR *xdrs, LISTXATTR4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_LISTXATTR4resok(xdrs, &objp->LISTXATTR4res_u.resok4)) return false; break; default: break; } return true; } static inline bool xdr_REMOVEXATTR4args(XDR *xdrs, REMOVEXATTR4args *objp) { if (!xdr_component4(xdrs, &objp->rxa_name)) return false; return true; } static inline bool xdr_REMOVEXATTR4res(XDR *xdrs, REMOVEXATTR4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_change_info4(xdrs, &objp->REMOVEXATTR4res_u.resok4.rxr_info)) return false; break; default: break; } return true; } static inline bool xdr_GETATTR4args(XDR *xdrs, GETATTR4args *objp) { if (!xdr_bitmap4(xdrs, &objp->attr_request)) return false; return true; } static inline bool xdr_GETATTR4resok(XDR *xdrs, GETATTR4resok *objp) { if (!xdr_fattr4(xdrs, &objp->obj_attributes)) return false; return true; } static inline bool xdr_GETATTR4res(XDR *xdrs, GETATTR4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_GETATTR4resok(xdrs, &objp->GETATTR4res_u.resok4)) return false; break; default: break; } return true; } static inline bool xdr_GETFH4resok(XDR *xdrs, GETFH4resok *objp) { if (!xdr_nfs_fh4(xdrs, &objp->object)) return false; return true; } static inline bool xdr_GETFH4res(XDR *xdrs, GETFH4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_GETFH4resok(xdrs, &objp->GETFH4res_u.resok4)) return false; break; default: break; } return true; } static inline bool xdr_LINK4args(XDR *xdrs, LINK4args *objp) { if (!xdr_component4(xdrs, &objp->newname)) return false; return true; } static inline bool xdr_LINK4resok(XDR *xdrs, LINK4resok *objp) { if (!xdr_change_info4(xdrs, &objp->cinfo)) return false; return true; } static inline bool xdr_LINK4res(XDR *xdrs, LINK4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_LINK4resok(xdrs, &objp->LINK4res_u.resok4)) return false; break; default: break; } return true; } static inline bool xdr_open_to_lock_owner4(XDR *xdrs, open_to_lock_owner4 *objp) { if (!xdr_seqid4(xdrs, &objp->open_seqid)) return false; if (!xdr_stateid4(xdrs, &objp->open_stateid)) return false; if (!xdr_seqid4(xdrs, &objp->lock_seqid)) return false; if (!xdr_lock_owner4(xdrs, &objp->lock_owner)) return false; return true; } static inline bool xdr_exist_lock_owner4(XDR *xdrs, exist_lock_owner4 *objp) { if (!xdr_stateid4(xdrs, &objp->lock_stateid)) return false; if (!xdr_seqid4(xdrs, &objp->lock_seqid)) return false; return true; } static inline bool xdr_locker4(XDR *xdrs, locker4 *objp) { if (!inline_xdr_bool(xdrs, &objp->new_lock_owner)) return false; switch (objp->new_lock_owner) { case true: if (!xdr_open_to_lock_owner4(xdrs, &objp->locker4_u.open_owner)) return false; break; case false: if (!xdr_exist_lock_owner4(xdrs, &objp->locker4_u.lock_owner)) return false; break; default: return false; } return true; } static inline bool xdr_LOCK4args(XDR *xdrs, LOCK4args *objp) { if (!xdr_nfs_lock_type4(xdrs, &objp->locktype)) return false; if (!inline_xdr_bool(xdrs, &objp->reclaim)) return false; if (!xdr_offset4(xdrs, &objp->offset)) return false; if (!xdr_length4(xdrs, &objp->length)) return false; if (!xdr_locker4(xdrs, &objp->locker)) return false; return true; } static inline bool xdr_LOCK4denied(XDR *xdrs, LOCK4denied *objp) { if (!xdr_offset4(xdrs, &objp->offset)) return false; if (!xdr_length4(xdrs, &objp->length)) return false; if (!xdr_nfs_lock_type4(xdrs, &objp->locktype)) return false; if (!xdr_lock_owner4(xdrs, &objp->owner)) return false; return true; } static inline bool xdr_LOCK4resok(XDR *xdrs, LOCK4resok *objp) { if (!xdr_stateid4(xdrs, &objp->lock_stateid)) return false; return true; } static inline bool xdr_LOCK4res(XDR *xdrs, LOCK4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_LOCK4resok(xdrs, &objp->LOCK4res_u.resok4)) return false; break; case NFS4ERR_DENIED: if (!xdr_LOCK4denied(xdrs, &objp->LOCK4res_u.denied)) return false; break; default: break; } return true; } static inline bool xdr_LOCKT4args(XDR *xdrs, LOCKT4args *objp) { if (!xdr_nfs_lock_type4(xdrs, &objp->locktype)) return false; if (!xdr_offset4(xdrs, &objp->offset)) return false; if (!xdr_length4(xdrs, &objp->length)) return false; if (!xdr_lock_owner4(xdrs, &objp->owner)) return false; return true; } static inline bool xdr_LOCKT4res(XDR *xdrs, LOCKT4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4ERR_DENIED: if (!xdr_LOCK4denied(xdrs, &objp->LOCKT4res_u.denied)) return false; break; case NFS4_OK: break; default: break; } return true; } static inline bool xdr_LOCKU4args(XDR *xdrs, LOCKU4args *objp) { if (!xdr_nfs_lock_type4(xdrs, &objp->locktype)) return false; if (!xdr_seqid4(xdrs, &objp->seqid)) return false; if (!xdr_stateid4(xdrs, &objp->lock_stateid)) return false; if (!xdr_offset4(xdrs, &objp->offset)) return false; if (!xdr_length4(xdrs, &objp->length)) return false; return true; } static inline bool xdr_LOCKU4res(XDR *xdrs, LOCKU4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_stateid4(xdrs, &objp->LOCKU4res_u.lock_stateid)) return false; break; default: break; } return true; } static inline bool xdr_LOOKUP4args(XDR *xdrs, LOOKUP4args *objp) { if (!xdr_component4(xdrs, &objp->objname)) return false; return true; } static inline bool xdr_LOOKUP4res(XDR *xdrs, LOOKUP4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; return true; } static inline bool xdr_LOOKUPP4res(XDR *xdrs, LOOKUPP4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; return true; } static inline bool xdr_NVERIFY4args(XDR *xdrs, NVERIFY4args *objp) { if (!xdr_fattr4(xdrs, &objp->obj_attributes)) return false; return true; } static inline bool xdr_NVERIFY4res(XDR *xdrs, NVERIFY4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; return true; } static inline bool xdr_createmode4(XDR *xdrs, createmode4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_creatverfattr(XDR *xdrs, creatverfattr *objp) { if (!xdr_verifier4(xdrs, objp->cva_verf)) return false; if (!xdr_fattr4(xdrs, &objp->cva_attrs)) return false; return true; } static inline bool xdr_createhow4(XDR *xdrs, createhow4 *objp) { if (!xdr_createmode4(xdrs, &objp->mode)) return false; switch (objp->mode) { case UNCHECKED4: case GUARDED4: if (!xdr_fattr4(xdrs, &objp->createhow4_u.createattrs)) return false; break; case EXCLUSIVE4: if (!xdr_verifier4(xdrs, objp->createhow4_u.createverf)) return false; break; case EXCLUSIVE4_1: if (!xdr_creatverfattr(xdrs, &objp->createhow4_u.ch_createboth)) return false; break; default: return false; } return true; } static inline bool xdr_opentype4(XDR *xdrs, opentype4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_openflag4(XDR *xdrs, openflag4 *objp) { if (!xdr_opentype4(xdrs, &objp->opentype)) return false; switch (objp->opentype) { case OPEN4_CREATE: if (!xdr_createhow4(xdrs, &objp->openflag4_u.how)) return false; break; default: break; } return true; } static inline bool xdr_limit_by4(XDR *xdrs, limit_by4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_nfs_modified_limit4(XDR *xdrs, nfs_modified_limit4 *objp) { if (!inline_xdr_u_int32_t(xdrs, &objp->num_blocks)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->bytes_per_block)) return false; return true; } static inline bool xdr_nfs_space_limit4(XDR *xdrs, nfs_space_limit4 *objp) { if (!xdr_limit_by4(xdrs, &objp->limitby)) return false; switch (objp->limitby) { case NFS_LIMIT_SIZE: if (!inline_xdr_u_int64_t(xdrs, &objp->nfs_space_limit4_u.filesize)) return false; break; case NFS_LIMIT_BLOCKS: if (!xdr_nfs_modified_limit4( xdrs, &objp->nfs_space_limit4_u.mod_blocks)) return false; break; default: return false; } return true; } static inline bool xdr_open_delegation_type4(XDR *xdrs, open_delegation_type4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_open_claim_type4(XDR *xdrs, open_claim_type4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_open_claim_delegate_cur4(XDR *xdrs, open_claim_delegate_cur4 *objp) { if (!xdr_stateid4(xdrs, &objp->delegate_stateid)) return false; if (!xdr_component4(xdrs, &objp->file)) return false; return true; } static inline bool xdr_open_claim4(XDR *xdrs, open_claim4 *objp) { if (!xdr_open_claim_type4(xdrs, &objp->claim)) return false; switch (objp->claim) { case CLAIM_NULL: if (!xdr_component4(xdrs, &objp->open_claim4_u.file)) return false; break; case CLAIM_PREVIOUS: if (!xdr_open_delegation_type4( xdrs, &objp->open_claim4_u.delegate_type)) return false; break; case CLAIM_DELEGATE_CUR: if (!xdr_open_claim_delegate_cur4( xdrs, &objp->open_claim4_u.delegate_cur_info)) return false; break; case CLAIM_DELEGATE_PREV: if (!xdr_component4(xdrs, &objp->open_claim4_u.file_delegate_prev)) return false; break; case CLAIM_FH: break; case CLAIM_DELEG_PREV_FH: break; case CLAIM_DELEG_CUR_FH: if (!xdr_stateid4(xdrs, &objp->open_claim4_u.oc_delegate_stateid)) return false; break; default: return false; } return true; } static inline bool xdr_OPEN4args(XDR *xdrs, OPEN4args *objp) { if (!xdr_seqid4(xdrs, &objp->seqid)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->share_access)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->share_deny)) return false; if (!xdr_open_owner4(xdrs, &objp->owner)) return false; if (!xdr_openflag4(xdrs, &objp->openhow)) return false; if (!xdr_open_claim4(xdrs, &objp->claim)) return false; return true; } static inline bool xdr_open_read_delegation4(XDR *xdrs, open_read_delegation4 *objp) { if (!xdr_stateid4(xdrs, &objp->stateid)) return false; if (!inline_xdr_bool(xdrs, &objp->recall)) return false; if (!xdr_nfsace4(xdrs, &objp->permissions)) return false; return true; } static inline bool xdr_open_write_delegation4(XDR *xdrs, open_write_delegation4 *objp) { if (!xdr_stateid4(xdrs, &objp->stateid)) return false; if (!inline_xdr_bool(xdrs, &objp->recall)) return false; if (!xdr_nfs_space_limit4(xdrs, &objp->space_limit)) return false; if (!xdr_nfsace4(xdrs, &objp->permissions)) return false; return true; } static inline bool xdr_why_no_delegation4(XDR *xdrs, why_no_delegation4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_open_none_delegation4(XDR *xdrs, open_none_delegation4 *objp) { if (!xdr_why_no_delegation4(xdrs, &objp->ond_why)) return false; switch (objp->ond_why) { case WND4_CONTENTION: if (!inline_xdr_bool(xdrs, &objp->open_none_delegation4_u .ond_server_will_push_deleg)) return false; break; case WND4_RESOURCE: if (!inline_xdr_bool(xdrs, &objp->open_none_delegation4_u .ond_server_will_signal)) return false; break; default: break; } return true; } static inline bool xdr_open_delegation4(XDR *xdrs, open_delegation4 *objp) { if (!xdr_open_delegation_type4(xdrs, &objp->delegation_type)) return false; switch (objp->delegation_type) { case OPEN_DELEGATE_NONE: break; case OPEN_DELEGATE_READ: if (!xdr_open_read_delegation4(xdrs, &objp->open_delegation4_u.read)) return false; break; case OPEN_DELEGATE_WRITE: if (!xdr_open_write_delegation4( xdrs, &objp->open_delegation4_u.write)) return false; break; case OPEN_DELEGATE_NONE_EXT: if (!xdr_open_none_delegation4( xdrs, &objp->open_delegation4_u.od_whynone)) return false; break; default: return false; } return true; } static inline bool xdr_OPEN4resok(XDR *xdrs, OPEN4resok *objp) { if (!xdr_stateid4(xdrs, &objp->stateid)) return false; if (!xdr_change_info4(xdrs, &objp->cinfo)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->rflags)) return false; if (!xdr_bitmap4(xdrs, &objp->attrset)) return false; if (!xdr_open_delegation4(xdrs, &objp->delegation)) return false; return true; } static inline bool xdr_OPEN4res(XDR *xdrs, OPEN4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_OPEN4resok(xdrs, &objp->OPEN4res_u.resok4)) return false; break; default: break; } return true; } static inline bool xdr_OPENATTR4args(XDR *xdrs, OPENATTR4args *objp) { if (!inline_xdr_bool(xdrs, &objp->createdir)) return false; return true; } static inline bool xdr_OPENATTR4res(XDR *xdrs, OPENATTR4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; return true; } static inline bool xdr_OPEN_CONFIRM4args(XDR *xdrs, OPEN_CONFIRM4args *objp) { if (!xdr_stateid4(xdrs, &objp->open_stateid)) return false; if (!xdr_seqid4(xdrs, &objp->seqid)) return false; return true; } static inline bool xdr_OPEN_CONFIRM4resok(XDR *xdrs, OPEN_CONFIRM4resok *objp) { if (!xdr_stateid4(xdrs, &objp->open_stateid)) return false; return true; } static inline bool xdr_OPEN_CONFIRM4res(XDR *xdrs, OPEN_CONFIRM4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_OPEN_CONFIRM4resok(xdrs, &objp->OPEN_CONFIRM4res_u.resok4)) return false; break; default: break; } return true; } static inline bool xdr_OPEN_DOWNGRADE4args(XDR *xdrs, OPEN_DOWNGRADE4args *objp) { if (!xdr_stateid4(xdrs, &objp->open_stateid)) return false; if (!xdr_seqid4(xdrs, &objp->seqid)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->share_access)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->share_deny)) return false; return true; } static inline bool xdr_OPEN_DOWNGRADE4resok(XDR *xdrs, OPEN_DOWNGRADE4resok *objp) { if (!xdr_stateid4(xdrs, &objp->open_stateid)) return false; return true; } static inline bool xdr_OPEN_DOWNGRADE4res(XDR *xdrs, OPEN_DOWNGRADE4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_OPEN_DOWNGRADE4resok( xdrs, &objp->OPEN_DOWNGRADE4res_u.resok4)) return false; break; default: break; } return true; } static inline bool xdr_PUTFH4args(XDR *xdrs, PUTFH4args *objp) { if (!xdr_nfs_fh4(xdrs, &objp->object)) return false; return true; } static inline bool xdr_PUTFH4res(XDR *xdrs, PUTFH4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; return true; } static inline bool xdr_PUTPUBFH4res(XDR *xdrs, PUTPUBFH4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; return true; } static inline bool xdr_PUTROOTFH4res(XDR *xdrs, PUTROOTFH4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; return true; } static inline bool xdr_READ4args(XDR *xdrs, READ4args *objp) { if (!xdr_stateid4(xdrs, &objp->stateid)) return false; if (!xdr_offset4(xdrs, &objp->offset)) return false; if (!xdr_count4(xdrs, &objp->count)) return false; return true; } static inline bool xdr_READ4resok(XDR *xdrs, READ4resok *objp) { if (!inline_xdr_bool(xdrs, &objp->eof)) return false; if (!xdr_io_data(xdrs, &objp->data)) return false; return true; } static inline bool xdr_READ4res(XDR *xdrs, READ4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_READ4resok(xdrs, &objp->READ4res_u.resok4)) return false; break; default: break; } return true; } static inline bool xdr_READDIR4args(XDR *xdrs, READDIR4args *objp) { if (!xdr_nfs_cookie4(xdrs, &objp->cookie)) return false; if (!xdr_verifier4(xdrs, objp->cookieverf)) return false; if (!xdr_count4(xdrs, &objp->dircount)) return false; if (!xdr_count4(xdrs, &objp->maxcount)) return false; if (!xdr_bitmap4(xdrs, &objp->attr_request)) return false; return true; } static inline bool xdr_entry4(XDR *xdrs, entry4 *objp) { if (!xdr_nfs_cookie4(xdrs, &objp->cookie)) return false; if (!xdr_component4(xdrs, &objp->name)) return false; if (!xdr_fattr4(xdrs, &objp->attrs)) return false; if (!xdr_pointer(xdrs, (void **)&objp->nextentry, sizeof(entry4), (xdrproc_t)xdr_entry4)) return false; return true; } static inline bool xdr_dirlist4_encode(XDR *xdrs, dirlist4 *objp) { if (!xdr_putbufs(xdrs, objp->uio, UIO_FLAG_NONE)) { objp->uio->uio_release(objp->uio, UIO_FLAG_NONE); return false; } return true; } static inline bool xdr_dirlist4(XDR *xdrs, dirlist4 *objp) { if (objp->uio != NULL) return xdr_dirlist4_encode(xdrs, objp); if (!xdr_pointer(xdrs, (void **)&objp->entries, sizeof(entry4), (xdrproc_t)xdr_entry4)) return false; if (!inline_xdr_bool(xdrs, &objp->eof)) return false; return true; } static inline bool xdr_READDIR4resok(XDR *xdrs, READDIR4resok *objp) { if (!xdr_verifier4(xdrs, objp->cookieverf)) return false; if (!xdr_dirlist4(xdrs, &objp->reply)) return false; return true; } static inline bool xdr_READDIR4res(XDR *xdrs, READDIR4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_READDIR4resok(xdrs, &objp->READDIR4res_u.resok4)) return false; break; default: break; } return true; } static inline bool xdr_READLINK4resok(XDR *xdrs, READLINK4resok *objp) { if (!xdr_linktext4(xdrs, &objp->link)) return false; return true; } static inline bool xdr_READLINK4res(XDR *xdrs, READLINK4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_READLINK4resok(xdrs, &objp->READLINK4res_u.resok4)) return false; break; default: break; } return true; } static inline bool xdr_REMOVE4args(XDR *xdrs, REMOVE4args *objp) { if (!xdr_component4(xdrs, &objp->target)) return false; return true; } static inline bool xdr_REMOVE4resok(XDR *xdrs, REMOVE4resok *objp) { if (!xdr_change_info4(xdrs, &objp->cinfo)) return false; return true; } static inline bool xdr_REMOVE4res(XDR *xdrs, REMOVE4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_REMOVE4resok(xdrs, &objp->REMOVE4res_u.resok4)) return false; break; default: break; } return true; } static inline bool xdr_RENAME4args(XDR *xdrs, RENAME4args *objp) { if (!xdr_component4(xdrs, &objp->oldname)) return false; if (!xdr_component4(xdrs, &objp->newname)) return false; return true; } static inline bool xdr_RENAME4resok(XDR *xdrs, RENAME4resok *objp) { if (!xdr_change_info4(xdrs, &objp->source_cinfo)) return false; if (!xdr_change_info4(xdrs, &objp->target_cinfo)) return false; return true; } static inline bool xdr_RENAME4res(XDR *xdrs, RENAME4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_RENAME4resok(xdrs, &objp->RENAME4res_u.resok4)) return false; break; default: break; } return true; } static inline bool xdr_RENEW4args(XDR *xdrs, RENEW4args *objp) { if (!xdr_clientid4(xdrs, &objp->clientid)) return false; return true; } static inline bool xdr_RENEW4res(XDR *xdrs, RENEW4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; return true; } static inline bool xdr_RESTOREFH4res(XDR *xdrs, RESTOREFH4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; return true; } static inline bool xdr_SAVEFH4res(XDR *xdrs, SAVEFH4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; return true; } static inline bool xdr_SECINFO4args(XDR *xdrs, SECINFO4args *objp) { if (!xdr_component4(xdrs, &objp->name)) return false; return true; } #ifdef _HAVE_GSSAPI static inline bool xdr_rpc_gss_svc_t(XDR *xdrs, rpc_gss_svc_t *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_rpcsec_gss_info(XDR *xdrs, rpcsec_gss_info *objp) { if (!xdr_sec_oid4(xdrs, &objp->oid)) return false; if (!xdr_qop4(xdrs, &objp->qop)) return false; if (!xdr_rpc_gss_svc_t(xdrs, &objp->service)) return false; return true; } #endif /* _HAVE_GSSAPI */ static inline bool xdr_secinfo4(XDR *xdrs, secinfo4 *objp) { if (!inline_xdr_u_int32_t(xdrs, &objp->flavor)) return false; switch (objp->flavor) { #ifdef _HAVE_GSSAPI case RPCSEC_GSS: if (!xdr_rpcsec_gss_info(xdrs, &objp->secinfo4_u.flavor_info)) return false; break; default: #endif /* _HAVE_GSSAPI */ break; } return true; } static inline bool xdr_SECINFO4resok(XDR *xdrs, SECINFO4resok *objp) { if (!xdr_array(xdrs, (char **)&objp->SECINFO4resok_val, &objp->SECINFO4resok_len, XDR_ARRAY_MAXLEN, sizeof(secinfo4), (xdrproc_t)xdr_secinfo4)) return false; return true; } static inline bool xdr_SECINFO4res(XDR *xdrs, SECINFO4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_SECINFO4resok(xdrs, &objp->SECINFO4res_u.resok4)) return false; break; default: break; } return true; } static inline bool xdr_SETATTR4args(XDR *xdrs, SETATTR4args *objp) { if (!xdr_stateid4(xdrs, &objp->stateid)) return false; if (!xdr_fattr4(xdrs, &objp->obj_attributes)) return false; return true; } static inline bool xdr_SETATTR4res(XDR *xdrs, SETATTR4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; if (!xdr_bitmap4(xdrs, &objp->attrsset)) return false; return true; } static inline bool xdr_SETCLIENTID4args(XDR *xdrs, SETCLIENTID4args *objp) { if (!xdr_nfs_client_id4(xdrs, &objp->client)) return false; if (!xdr_cb_client4(xdrs, &objp->callback)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->callback_ident)) return false; return true; } static inline bool xdr_SETCLIENTID4resok(XDR *xdrs, SETCLIENTID4resok *objp) { if (!xdr_clientid4(xdrs, &objp->clientid)) return false; if (!xdr_verifier4(xdrs, objp->setclientid_confirm)) return false; return true; } static inline bool xdr_SETCLIENTID4res(XDR *xdrs, SETCLIENTID4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_SETCLIENTID4resok(xdrs, &objp->SETCLIENTID4res_u.resok4)) return false; break; case NFS4ERR_CLID_INUSE: if (!xdr_clientaddr4(xdrs, &objp->SETCLIENTID4res_u.client_using)) return false; break; default: break; } return true; } static inline bool xdr_SETCLIENTID_CONFIRM4args(XDR *xdrs, SETCLIENTID_CONFIRM4args *objp) { if (!xdr_clientid4(xdrs, &objp->clientid)) return false; if (!xdr_verifier4(xdrs, objp->setclientid_confirm)) return false; return true; } static inline bool xdr_SETCLIENTID_CONFIRM4res(XDR *xdrs, SETCLIENTID_CONFIRM4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; return true; } static inline bool xdr_VERIFY4args(XDR *xdrs, VERIFY4args *objp) { if (!xdr_fattr4(xdrs, &objp->obj_attributes)) return false; return true; } static inline bool xdr_VERIFY4res(XDR *xdrs, VERIFY4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; return true; } static inline bool xdr_stable_how4(XDR *xdrs, stable_how4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_WRITE4args(XDR *xdrs, WRITE4args *objp) { if (!xdr_stateid4(xdrs, &objp->stateid)) return false; if (!xdr_offset4(xdrs, &objp->offset)) return false; if (!xdr_stable_how4(xdrs, &objp->stable)) return false; if (!xdr_io_data(xdrs, &objp->data)) return false; return true; } static inline bool xdr_WRITE4resok(XDR *xdrs, WRITE4resok *objp) { if (!xdr_count4(xdrs, &objp->count)) return false; if (!xdr_stable_how4(xdrs, &objp->committed)) return false; if (!xdr_verifier4(xdrs, objp->writeverf)) return false; return true; } static inline bool xdr_WRITE4res(XDR *xdrs, WRITE4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_WRITE4resok(xdrs, &objp->WRITE4res_u.resok4)) return false; break; default: break; } return true; } static inline bool xdr_RELEASE_LOCKOWNER4args(XDR *xdrs, RELEASE_LOCKOWNER4args *objp) { if (!xdr_lock_owner4(xdrs, &objp->lock_owner)) return false; return true; } static inline bool xdr_RELEASE_LOCKOWNER4res(XDR *xdrs, RELEASE_LOCKOWNER4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; return true; } static inline bool xdr_ILLEGAL4res(XDR *xdrs, ILLEGAL4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; return true; } static inline bool xdr_gsshandle4_t(XDR *xdrs, gsshandle4_t *objp) { if (!inline_xdr_bytes(xdrs, (char **)&objp->gsshandle4_t_val, &objp->gsshandle4_t_len, XDR_BYTES_MAXLEN)) return false; return true; } #ifdef _HAVE_GSSAPI static inline bool xdr_gss_cb_handles4(XDR *xdrs, gss_cb_handles4 *objp) { if (!xdr_rpc_gss_svc_t(xdrs, &objp->gcbp_service)) return false; if (!xdr_gsshandle4_t(xdrs, &objp->gcbp_handle_from_server)) return false; if (!xdr_gsshandle4_t(xdrs, &objp->gcbp_handle_from_client)) return false; return true; } #endif /* _HAVE_GSSAPI */ static inline bool xdr_callback_sec_parms4(XDR *xdrs, callback_sec_parms4 *objp) { if (!inline_xdr_u_int32_t(xdrs, &objp->cb_secflavor)) return false; switch (objp->cb_secflavor) { case AUTH_NONE: break; case AUTH_SYS: if (!xdr_authunix_parms( xdrs, &objp->callback_sec_parms4_u.cbsp_sys_cred)) return false; break; #ifdef _HAVE_GSSAPI case RPCSEC_GSS: if (!xdr_gss_cb_handles4( xdrs, &objp->callback_sec_parms4_u.cbsp_gss_handles)) return false; break; #endif /* _HAVE_GSSAPI */ default: return false; } return true; } static inline bool xdr_BACKCHANNEL_CTL4args(XDR *xdrs, BACKCHANNEL_CTL4args *objp) { if (!inline_xdr_u_int32_t(xdrs, &objp->bca_cb_program)) return false; if (!xdr_array(xdrs, (char **)&objp->bca_sec_parms.bca_sec_parms_val, &objp->bca_sec_parms.bca_sec_parms_len, XDR_ARRAY_MAXLEN, sizeof(callback_sec_parms4), (xdrproc_t)xdr_callback_sec_parms4)) return false; return true; } static inline bool xdr_BACKCHANNEL_CTL4res(XDR *xdrs, BACKCHANNEL_CTL4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->bcr_status)) return false; return true; } static inline bool xdr_channel_dir_from_client4(XDR *xdrs, channel_dir_from_client4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_BIND_CONN_TO_SESSION4args(XDR *xdrs, BIND_CONN_TO_SESSION4args *objp) { if (!xdr_sessionid4(xdrs, objp->bctsa_sessid)) return false; if (!xdr_channel_dir_from_client4(xdrs, &objp->bctsa_dir)) return false; if (!inline_xdr_bool(xdrs, &objp->bctsa_use_conn_in_rdma_mode)) return false; return true; } static inline bool xdr_channel_dir_from_server4(XDR *xdrs, channel_dir_from_server4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_BIND_CONN_TO_SESSION4resok(XDR *xdrs, BIND_CONN_TO_SESSION4resok *objp) { if (!xdr_sessionid4(xdrs, objp->bctsr_sessid)) return false; if (!xdr_channel_dir_from_server4(xdrs, &objp->bctsr_dir)) return false; if (!inline_xdr_bool(xdrs, &objp->bctsr_use_conn_in_rdma_mode)) return false; return true; } static inline bool xdr_BIND_CONN_TO_SESSION4res(XDR *xdrs, BIND_CONN_TO_SESSION4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->bctsr_status)) return false; switch (objp->bctsr_status) { case NFS4_OK: if (!xdr_BIND_CONN_TO_SESSION4resok( xdrs, &objp->BIND_CONN_TO_SESSION4res_u.bctsr_resok4)) return false; break; default: break; } return true; } static inline bool xdr_state_protect_ops4(XDR *xdrs, state_protect_ops4 *objp) { if (!xdr_bitmap4(xdrs, &objp->spo_must_enforce)) return false; if (!xdr_bitmap4(xdrs, &objp->spo_must_allow)) return false; return true; } static inline bool xdr_ssv_sp_parms4(XDR *xdrs, ssv_sp_parms4 *objp) { if (!xdr_state_protect_ops4(xdrs, &objp->ssp_ops)) return false; if (!xdr_array(xdrs, (char **)&objp->ssp_hash_algs.ssp_hash_algs_val, &objp->ssp_hash_algs.ssp_hash_algs_len, XDR_ARRAY_MAXLEN, sizeof(sec_oid4), (xdrproc_t)xdr_sec_oid4)) return false; if (!xdr_array(xdrs, (char **)&objp->ssp_encr_algs.ssp_encr_algs_val, &objp->ssp_encr_algs.ssp_encr_algs_len, XDR_ARRAY_MAXLEN, sizeof(sec_oid4), (xdrproc_t)xdr_sec_oid4)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->ssp_window)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->ssp_num_gss_handles)) return false; return true; } static inline bool xdr_state_protect_how4(XDR *xdrs, state_protect_how4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_state_protect4_a(XDR *xdrs, state_protect4_a *objp) { if (!xdr_state_protect_how4(xdrs, &objp->spa_how)) return false; switch (objp->spa_how) { case SP4_NONE: break; case SP4_MACH_CRED: if (!xdr_state_protect_ops4( xdrs, &objp->state_protect4_a_u.spa_mach_ops)) return false; break; case SP4_SSV: if (!xdr_ssv_sp_parms4(xdrs, &objp->state_protect4_a_u.spa_ssv_parms)) return false; break; default: return false; } return true; } static inline bool xdr_EXCHANGE_ID4args(XDR *xdrs, EXCHANGE_ID4args *objp) { if (!xdr_client_owner4(xdrs, &objp->eia_clientowner)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->eia_flags)) return false; if (!xdr_state_protect4_a(xdrs, &objp->eia_state_protect)) return false; if (!xdr_array( xdrs, (char **)&objp->eia_client_impl_id.eia_client_impl_id_val, (u_int *)&objp->eia_client_impl_id.eia_client_impl_id_len, 1, sizeof(nfs_impl_id4), (xdrproc_t)xdr_nfs_impl_id4)) return false; return true; } static inline bool xdr_ssv_prot_info4(XDR *xdrs, ssv_prot_info4 *objp) { if (!xdr_state_protect_ops4(xdrs, &objp->spi_ops)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->spi_hash_alg)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->spi_encr_alg)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->spi_ssv_len)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->spi_window)) return false; if (!xdr_array(xdrs, (char **)&objp->spi_handles.spi_handles_val, &objp->spi_handles.spi_handles_len, XDR_ARRAY_MAXLEN, sizeof(gsshandle4_t), (xdrproc_t)xdr_gsshandle4_t)) return false; return true; } static inline bool xdr_state_protect4_r(XDR *xdrs, state_protect4_r *objp) { if (!xdr_state_protect_how4(xdrs, &objp->spr_how)) return false; switch (objp->spr_how) { case SP4_NONE: break; case SP4_MACH_CRED: if (!xdr_state_protect_ops4( xdrs, &objp->state_protect4_r_u.spr_mach_ops)) return false; break; case SP4_SSV: if (!xdr_ssv_prot_info4(xdrs, &objp->state_protect4_r_u.spr_ssv_info)) return false; break; default: return false; } return true; } static inline bool xdr_EXCHANGE_ID4resok(XDR *xdrs, EXCHANGE_ID4resok *objp) { if (!xdr_clientid4(xdrs, &objp->eir_clientid)) return false; if (!xdr_sequenceid4(xdrs, &objp->eir_sequenceid)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->eir_flags)) return false; if (!xdr_state_protect4_r(xdrs, &objp->eir_state_protect)) return false; if (!xdr_server_owner4(xdrs, &objp->eir_server_owner)) return false; if (!inline_xdr_bytes( xdrs, (char **)&objp->eir_server_scope.eir_server_scope_val, (u_int *)&objp->eir_server_scope.eir_server_scope_len, NFS4_OPAQUE_LIMIT)) return false; if (!xdr_array( xdrs, (char **)&objp->eir_server_impl_id.eir_server_impl_id_val, (u_int *)&objp->eir_server_impl_id.eir_server_impl_id_len, 1, sizeof(nfs_impl_id4), (xdrproc_t)xdr_nfs_impl_id4)) return false; return true; } static inline bool xdr_EXCHANGE_ID4res(XDR *xdrs, EXCHANGE_ID4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->eir_status)) return false; switch (objp->eir_status) { case NFS4_OK: if (!xdr_EXCHANGE_ID4resok(xdrs, &objp->EXCHANGE_ID4res_u.eir_resok4)) return false; break; default: break; } return true; } static inline bool xdr_channel_attrs4(XDR *xdrs, channel_attrs4 *objp) { if (!xdr_count4(xdrs, &objp->ca_headerpadsize)) return false; if (!xdr_count4(xdrs, &objp->ca_maxrequestsize)) return false; if (!xdr_count4(xdrs, &objp->ca_maxresponsesize)) return false; if (!xdr_count4(xdrs, &objp->ca_maxresponsesize_cached)) return false; if (!xdr_count4(xdrs, &objp->ca_maxoperations)) return false; if (!xdr_count4(xdrs, &objp->ca_maxrequests)) return false; if (!xdr_array(xdrs, (char **)&objp->ca_rdma_ird.ca_rdma_ird_val, (u_int *)&objp->ca_rdma_ird.ca_rdma_ird_len, 1, sizeof(uint32_t), (xdrproc_t)xdr_uint32_t)) return false; return true; } static inline bool xdr_CREATE_SESSION4args(XDR *xdrs, CREATE_SESSION4args *objp) { if (!xdr_clientid4(xdrs, &objp->csa_clientid)) return false; if (!xdr_sequenceid4(xdrs, &objp->csa_sequence)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->csa_flags)) return false; if (!xdr_channel_attrs4(xdrs, &objp->csa_fore_chan_attrs)) return false; if (!xdr_channel_attrs4(xdrs, &objp->csa_back_chan_attrs)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->csa_cb_program)) return false; if (!xdr_array(xdrs, (char **)&objp->csa_sec_parms.csa_sec_parms_val, &objp->csa_sec_parms.csa_sec_parms_len, XDR_ARRAY_MAXLEN, sizeof(callback_sec_parms4), (xdrproc_t)xdr_callback_sec_parms4)) return false; return true; } static inline bool xdr_CREATE_SESSION4resok(XDR *xdrs, CREATE_SESSION4resok *objp) { if (!xdr_sessionid4(xdrs, objp->csr_sessionid)) return false; if (!xdr_sequenceid4(xdrs, &objp->csr_sequence)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->csr_flags)) return false; if (!xdr_channel_attrs4(xdrs, &objp->csr_fore_chan_attrs)) return false; if (!xdr_channel_attrs4(xdrs, &objp->csr_back_chan_attrs)) return false; return true; } static inline bool xdr_CREATE_SESSION4res(XDR *xdrs, CREATE_SESSION4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->csr_status)) return false; switch (objp->csr_status) { case NFS4_OK: if (!xdr_CREATE_SESSION4resok( xdrs, &objp->CREATE_SESSION4res_u.csr_resok4)) return false; break; default: break; } return true; } static inline bool xdr_DESTROY_SESSION4args(XDR *xdrs, DESTROY_SESSION4args *objp) { if (!xdr_sessionid4(xdrs, objp->dsa_sessionid)) return false; return true; } static inline bool xdr_DESTROY_SESSION4res(XDR *xdrs, DESTROY_SESSION4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->dsr_status)) return false; return true; } static inline bool xdr_FREE_STATEID4args(XDR *xdrs, FREE_STATEID4args *objp) { if (!xdr_stateid4(xdrs, &objp->fsa_stateid)) return false; return true; } static inline bool xdr_FREE_STATEID4res(XDR *xdrs, FREE_STATEID4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->fsr_status)) return false; return true; } static inline bool xdr_attr_notice4(XDR *xdrs, attr_notice4 *objp) { if (!xdr_nfstime4(xdrs, objp)) return false; return true; } static inline bool xdr_GET_DIR_DELEGATION4args(XDR *xdrs, GET_DIR_DELEGATION4args *objp) { if (!inline_xdr_bool(xdrs, &objp->gdda_signal_deleg_avail)) return false; if (!xdr_bitmap4(xdrs, &objp->gdda_notification_types)) return false; if (!xdr_attr_notice4(xdrs, &objp->gdda_child_attr_delay)) return false; if (!xdr_attr_notice4(xdrs, &objp->gdda_dir_attr_delay)) return false; if (!xdr_bitmap4(xdrs, &objp->gdda_child_attributes)) return false; if (!xdr_bitmap4(xdrs, &objp->gdda_dir_attributes)) return false; return true; } static inline bool xdr_GET_DIR_DELEGATION4resok(XDR *xdrs, GET_DIR_DELEGATION4resok *objp) { if (!xdr_verifier4(xdrs, objp->gddr_cookieverf)) return false; if (!xdr_stateid4(xdrs, &objp->gddr_stateid)) return false; if (!xdr_bitmap4(xdrs, &objp->gddr_notification)) return false; if (!xdr_bitmap4(xdrs, &objp->gddr_child_attributes)) return false; if (!xdr_bitmap4(xdrs, &objp->gddr_dir_attributes)) return false; return true; } static inline bool xdr_gddrnf4_status(XDR *xdrs, gddrnf4_status *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_GET_DIR_DELEGATION4res_non_fatal(XDR *xdrs, GET_DIR_DELEGATION4res_non_fatal *objp) { if (!xdr_gddrnf4_status(xdrs, &objp->gddrnf_status)) return false; switch (objp->gddrnf_status) { case GDD4_OK: if (!xdr_GET_DIR_DELEGATION4resok( xdrs, &objp->GET_DIR_DELEGATION4res_non_fatal_u .gddrnf_resok4)) return false; break; case GDD4_UNAVAIL: if (!inline_xdr_bool(xdrs, &objp->GET_DIR_DELEGATION4res_non_fatal_u .gddrnf_signal)) return false; break; default: return false; } return true; } static inline bool xdr_GET_DIR_DELEGATION4res(XDR *xdrs, GET_DIR_DELEGATION4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->gddr_status)) return false; switch (objp->gddr_status) { case NFS4_OK: if (!xdr_GET_DIR_DELEGATION4res_non_fatal( xdrs, &objp->GET_DIR_DELEGATION4res_u.gddr_res_non_fatal4)) return false; break; default: break; } return true; } static inline bool xdr_GETDEVICEINFO4args(XDR *xdrs, GETDEVICEINFO4args *objp) { if (!xdr_deviceid4(xdrs, objp->gdia_device_id)) return false; if (!xdr_layouttype4(xdrs, &objp->gdia_layout_type)) return false; if (!xdr_count4(xdrs, &objp->gdia_maxcount)) return false; if (!xdr_bitmap4(xdrs, &objp->gdia_notify_types)) return false; return true; } static inline bool xdr_GETDEVICEINFO4resok(XDR *xdrs, GETDEVICEINFO4resok *objp) { if (!xdr_device_addr4(xdrs, &objp->gdir_device_addr)) return false; if (!xdr_bitmap4(xdrs, &objp->gdir_notification)) return false; return true; } static inline bool xdr_GETDEVICEINFO4res(XDR *xdrs, GETDEVICEINFO4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->gdir_status)) return false; switch (objp->gdir_status) { case NFS4_OK: if (!xdr_GETDEVICEINFO4resok( xdrs, &objp->GETDEVICEINFO4res_u.gdir_resok4)) return false; break; case NFS4ERR_TOOSMALL: if (!xdr_count4(xdrs, &objp->GETDEVICEINFO4res_u.gdir_mincount)) return false; break; default: break; } return true; } static inline bool xdr_GETDEVICELIST4args(XDR *xdrs, GETDEVICELIST4args *objp) { if (!xdr_layouttype4(xdrs, &objp->gdla_layout_type)) return false; if (!xdr_count4(xdrs, &objp->gdla_maxdevices)) return false; if (!xdr_nfs_cookie4(xdrs, &objp->gdla_cookie)) return false; if (!xdr_verifier4(xdrs, objp->gdla_cookieverf)) return false; return true; } static inline bool xdr_GETDEVICELIST4resok(XDR *xdrs, GETDEVICELIST4resok *objp) { if (!xdr_nfs_cookie4(xdrs, &objp->gdlr_cookie)) return false; if (!xdr_verifier4(xdrs, objp->gdlr_cookieverf)) return false; if (!xdr_array(xdrs, (char **)&objp->gdlr_deviceid_list.gdlr_deviceid_list_val, &objp->gdlr_deviceid_list.gdlr_deviceid_list_len, XDR_ARRAY_MAXLEN, sizeof(deviceid4), (xdrproc_t)xdr_deviceid4)) return false; if (!inline_xdr_bool(xdrs, &objp->gdlr_eof)) return false; return true; } static inline bool xdr_GETDEVICELIST4res(XDR *xdrs, GETDEVICELIST4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->gdlr_status)) return false; switch (objp->gdlr_status) { case NFS4_OK: if (!xdr_GETDEVICELIST4resok( xdrs, &objp->GETDEVICELIST4res_u.gdlr_resok4)) return false; break; default: break; } return true; } static inline bool xdr_newtime4(XDR *xdrs, newtime4 *objp) { if (!inline_xdr_bool(xdrs, &objp->nt_timechanged)) return false; switch (objp->nt_timechanged) { case true: if (!xdr_nfstime4(xdrs, &objp->newtime4_u.nt_time)) return false; break; case false: break; default: return false; } return true; } static inline bool xdr_newoffset4(XDR *xdrs, newoffset4 *objp) { if (!inline_xdr_bool(xdrs, &objp->no_newoffset)) return false; switch (objp->no_newoffset) { case true: if (!xdr_offset4(xdrs, &objp->newoffset4_u.no_offset)) return false; break; case false: break; default: return false; } return true; } static inline bool xdr_LAYOUTCOMMIT4args(XDR *xdrs, LAYOUTCOMMIT4args *objp) { if (!xdr_offset4(xdrs, &objp->loca_offset)) return false; if (!xdr_length4(xdrs, &objp->loca_length)) return false; if (!inline_xdr_bool(xdrs, &objp->loca_reclaim)) return false; if (!xdr_stateid4(xdrs, &objp->loca_stateid)) return false; if (!xdr_newoffset4(xdrs, &objp->loca_last_write_offset)) return false; if (!xdr_newtime4(xdrs, &objp->loca_time_modify)) return false; if (!xdr_layoutupdate4(xdrs, &objp->loca_layoutupdate)) return false; return true; } static inline bool xdr_newsize4(XDR *xdrs, newsize4 *objp) { if (!inline_xdr_bool(xdrs, &objp->ns_sizechanged)) return false; switch (objp->ns_sizechanged) { case true: if (!xdr_length4(xdrs, &objp->newsize4_u.ns_size)) return false; break; case false: break; default: return false; } return true; } static inline bool xdr_LAYOUTCOMMIT4resok(XDR *xdrs, LAYOUTCOMMIT4resok *objp) { if (!xdr_newsize4(xdrs, &objp->locr_newsize)) return false; return true; } static inline bool xdr_LAYOUTCOMMIT4res(XDR *xdrs, LAYOUTCOMMIT4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->locr_status)) return false; switch (objp->locr_status) { case NFS4_OK: if (!xdr_LAYOUTCOMMIT4resok( xdrs, &objp->LAYOUTCOMMIT4res_u.locr_resok4)) return false; break; default: break; } return true; } static inline bool xdr_LAYOUTGET4args(XDR *xdrs, LAYOUTGET4args *objp) { if (!inline_xdr_bool(xdrs, &objp->loga_signal_layout_avail)) return false; if (!xdr_layouttype4(xdrs, &objp->loga_layout_type)) return false; if (!xdr_layoutiomode4(xdrs, &objp->loga_iomode)) return false; if (!xdr_offset4(xdrs, &objp->loga_offset)) return false; if (!xdr_length4(xdrs, &objp->loga_length)) return false; if (!xdr_length4(xdrs, &objp->loga_minlength)) return false; if (!xdr_stateid4(xdrs, &objp->loga_stateid)) return false; if (!xdr_count4(xdrs, &objp->loga_maxcount)) return false; return true; } static inline bool xdr_LAYOUTGET4resok(XDR *xdrs, LAYOUTGET4resok *objp) { if (!inline_xdr_bool(xdrs, &objp->logr_return_on_close)) return false; if (!xdr_stateid4(xdrs, &objp->logr_stateid)) return false; if (!xdr_array(xdrs, (char **)&objp->logr_layout.logr_layout_val, &objp->logr_layout.logr_layout_len, XDR_ARRAY_MAXLEN, sizeof(layout4), (xdrproc_t)xdr_layout4)) return false; return true; } static inline bool xdr_LAYOUTGET4res(XDR *xdrs, LAYOUTGET4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->logr_status)) return false; switch (objp->logr_status) { case NFS4_OK: if (!xdr_LAYOUTGET4resok(xdrs, &objp->LAYOUTGET4res_u.logr_resok4)) return false; break; case NFS4ERR_LAYOUTTRYLATER: if (!inline_xdr_bool(xdrs, &objp->LAYOUTGET4res_u .logr_will_signal_layout_avail)) return false; break; default: break; } return true; } static inline bool xdr_LAYOUTRETURN4args(XDR *xdrs, LAYOUTRETURN4args *objp) { if (!inline_xdr_bool(xdrs, &objp->lora_reclaim)) return false; if (!xdr_layouttype4(xdrs, &objp->lora_layout_type)) return false; if (!xdr_layoutiomode4(xdrs, &objp->lora_iomode)) return false; if (!xdr_layoutreturn4(xdrs, &objp->lora_layoutreturn)) return false; return true; } static inline bool xdr_layoutreturn_stateid(XDR *xdrs, layoutreturn_stateid *objp) { if (!inline_xdr_bool(xdrs, &objp->lrs_present)) return false; switch (objp->lrs_present) { case true: if (!xdr_stateid4(xdrs, &objp->layoutreturn_stateid_u.lrs_stateid)) return false; break; case false: break; default: return false; } return true; } static inline bool xdr_LAYOUTRETURN4res(XDR *xdrs, LAYOUTRETURN4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->lorr_status)) return false; switch (objp->lorr_status) { case NFS4_OK: if (!xdr_layoutreturn_stateid( xdrs, &objp->LAYOUTRETURN4res_u.lorr_stateid)) return false; break; default: break; } return true; } static inline bool xdr_secinfo_style4(XDR *xdrs, secinfo_style4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_SECINFO_NO_NAME4args(XDR *xdrs, SECINFO_NO_NAME4args *objp) { if (!xdr_secinfo_style4(xdrs, objp)) return false; return true; } static inline bool xdr_SECINFO_NO_NAME4res(XDR *xdrs, SECINFO_NO_NAME4res *objp) { if (!xdr_SECINFO4res(xdrs, objp)) return false; return true; } static inline bool xdr_SEQUENCE4args(XDR *xdrs, SEQUENCE4args *objp) { if (!xdr_sessionid4(xdrs, objp->sa_sessionid)) return false; if (!xdr_sequenceid4(xdrs, &objp->sa_sequenceid)) return false; if (!xdr_slotid4(xdrs, &objp->sa_slotid)) return false; if (!xdr_slotid4(xdrs, &objp->sa_highest_slotid)) return false; if (!inline_xdr_bool(xdrs, &objp->sa_cachethis)) return false; return true; } static inline bool xdr_SEQUENCE4resok(XDR *xdrs, SEQUENCE4resok *objp) { if (!xdr_sessionid4(xdrs, objp->sr_sessionid)) return false; if (!xdr_sequenceid4(xdrs, &objp->sr_sequenceid)) return false; if (!xdr_slotid4(xdrs, &objp->sr_slotid)) return false; if (!xdr_slotid4(xdrs, &objp->sr_highest_slotid)) return false; if (!xdr_slotid4(xdrs, &objp->sr_target_highest_slotid)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->sr_status_flags)) return false; return true; } static inline bool xdr_SEQUENCE4res(XDR *xdrs, SEQUENCE4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->sr_status)) return false; switch (objp->sr_status) { case NFS4_OK: if (!xdr_SEQUENCE4resok(xdrs, &objp->SEQUENCE4res_u.sr_resok4)) return false; break; default: break; } return true; } static inline bool xdr_ssa_digest_input4(XDR *xdrs, ssa_digest_input4 *objp) { if (!xdr_SEQUENCE4args(xdrs, &objp->sdi_seqargs)) return false; return true; } static inline bool xdr_SET_SSV4args(XDR *xdrs, SET_SSV4args *objp) { if (!inline_xdr_bytes(xdrs, (char **)&objp->ssa_ssv.ssa_ssv_val, &objp->ssa_ssv.ssa_ssv_len, XDR_BYTES_MAXLEN)) return false; if (!inline_xdr_bytes(xdrs, (char **)&objp->ssa_digest.ssa_digest_val, &objp->ssa_digest.ssa_digest_len, XDR_BYTES_MAXLEN)) return false; return true; } static inline bool xdr_ssr_digest_input4(XDR *xdrs, ssr_digest_input4 *objp) { if (!xdr_SEQUENCE4res(xdrs, &objp->sdi_seqres)) return false; return true; } static inline bool xdr_SET_SSV4resok(XDR *xdrs, SET_SSV4resok *objp) { if (!inline_xdr_bytes(xdrs, (char **)&objp->ssr_digest.ssr_digest_val, &objp->ssr_digest.ssr_digest_len, XDR_BYTES_MAXLEN)) return false; return true; } static inline bool xdr_SET_SSV4res(XDR *xdrs, SET_SSV4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->ssr_status)) return false; switch (objp->ssr_status) { case NFS4_OK: if (!xdr_SET_SSV4resok(xdrs, &objp->SET_SSV4res_u.ssr_resok4)) return false; break; default: break; } return true; } static inline bool xdr_TEST_STATEID4args(XDR *xdrs, TEST_STATEID4args *objp) { if (!xdr_array(xdrs, (char **)&objp->ts_stateids.ts_stateids_val, &objp->ts_stateids.ts_stateids_len, XDR_ARRAY_MAXLEN, sizeof(stateid4), (xdrproc_t)xdr_stateid4)) return false; return true; } static inline bool xdr_TEST_STATEID4resok(XDR *xdrs, TEST_STATEID4resok *objp) { if (!xdr_array(xdrs, (char **)&objp->tsr_status_codes.tsr_status_codes_val, &objp->tsr_status_codes.tsr_status_codes_len, XDR_ARRAY_MAXLEN, sizeof(nfsstat4), (xdrproc_t)xdr_nfsstat4)) return false; return true; } static inline bool xdr_TEST_STATEID4res(XDR *xdrs, TEST_STATEID4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->tsr_status)) return false; switch (objp->tsr_status) { case NFS4_OK: if (!xdr_TEST_STATEID4resok( xdrs, &objp->TEST_STATEID4res_u.tsr_resok4)) return false; break; default: break; } return true; } static inline bool xdr_deleg_claim4(XDR *xdrs, deleg_claim4 *objp) { if (!xdr_open_claim_type4(xdrs, &objp->dc_claim)) return false; switch (objp->dc_claim) { case CLAIM_FH: break; case CLAIM_DELEG_PREV_FH: break; case CLAIM_PREVIOUS: if (!xdr_open_delegation_type4( xdrs, &objp->deleg_claim4_u.dc_delegate_type)) return false; break; default: return false; } return true; } static inline bool xdr_WANT_DELEGATION4args(XDR *xdrs, WANT_DELEGATION4args *objp) { if (!inline_xdr_u_int32_t(xdrs, &objp->wda_want)) return false; if (!xdr_deleg_claim4(xdrs, &objp->wda_claim)) return false; return true; } static inline bool xdr_WANT_DELEGATION4res(XDR *xdrs, WANT_DELEGATION4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->wdr_status)) return false; switch (objp->wdr_status) { case NFS4_OK: if (!xdr_open_delegation4( xdrs, &objp->WANT_DELEGATION4res_u.wdr_resok4)) return false; break; default: break; } return true; } static inline bool xdr_DESTROY_CLIENTID4args(XDR *xdrs, DESTROY_CLIENTID4args *objp) { if (!xdr_clientid4(xdrs, &objp->dca_clientid)) return false; return true; } static inline bool xdr_DESTROY_CLIENTID4res(XDR *xdrs, DESTROY_CLIENTID4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->dcr_status)) return false; return true; } static inline bool xdr_RECLAIM_COMPLETE4args(XDR *xdrs, RECLAIM_COMPLETE4args *objp) { if (!inline_xdr_bool(xdrs, &objp->rca_one_fs)) return false; return true; } static inline bool xdr_RECLAIM_COMPLETE4res(XDR *xdrs, RECLAIM_COMPLETE4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->rcr_status)) return false; return true; } /* NFSv4.2 */ static inline bool xdr_WRITE_SAME4args(XDR *xdrs, WRITE_SAME4args *objp) { if (!xdr_stateid4(xdrs, &objp->wp_stateid)) return false; if (!xdr_stable_how4(xdrs, &objp->wp_stable)) return false; if (!xdr_offset4(xdrs, &objp->wp_adb.adb_offset)) return false; if (!xdr_length4(xdrs, &objp->wp_adb.adb_block_size)) return false; if (!xdr_length4(xdrs, &objp->wp_adb.adb_block_count)) return false; if (!xdr_length4(xdrs, &objp->wp_adb.adb_reloff_blocknum)) return false; if (!xdr_count4(xdrs, &objp->wp_adb.adb_block_num)) return false; if (!xdr_length4(xdrs, &objp->wp_adb.adb_reloff_pattern)) return false; if (!inline_xdr_bytes(xdrs, (char **)&objp->wp_adb.adb_data.data_val, &objp->wp_adb.adb_data.data_len, XDR_BYTES_MAXLEN)) return false; return true; } static inline bool xdr_WRITE_SAME4resok(XDR *xdrs, write_response4 *objp) { if (!xdr_count4(xdrs, &objp->wr_ids)) return false; if (objp->wr_ids > 1) return false; if (objp->wr_ids == 1) if (!xdr_stateid4(xdrs, &objp->wr_callback_id)) return false; if (!xdr_length4(xdrs, &objp->wr_count)) return false; if (!xdr_stable_how4(xdrs, &objp->wr_committed)) return false; if (!xdr_verifier4(xdrs, objp->wr_writeverf)) return false; return true; } static inline bool xdr_READ_PLUS4args(XDR *xdrs, READ_PLUS4args *objp) { if (!xdr_stateid4(xdrs, &objp->rpa_stateid)) return false; if (!xdr_offset4(xdrs, &objp->rpa_offset)) return false; if (!xdr_count4(xdrs, &objp->rpa_count)) return false; return true; } static inline bool xdr_READ_PLUS4resok(XDR *xdrs, read_plus_res4 *objp) { if (!inline_xdr_bool(xdrs, &objp->rpr_eof)) return false; if (objp->rpr_contents_count != 1) /* an array of 1 for now */ return false; if (!xdr_count4(xdrs, &objp->rpr_contents_count)) return false; if (!inline_xdr_enum(xdrs, (enum_t *)&objp->rpr_contents.what)) return false; if (objp->rpr_contents.what == NFS4_CONTENT_DATA) { if (!xdr_offset4(xdrs, &objp->rpr_contents.data.d_offset)) return false; if (!inline_xdr_bytes( xdrs, (char **)&objp->rpr_contents.data.d_data.data_val, &objp->rpr_contents.data.d_data.data_len, XDR_BYTES_MAXLEN_IO)) return false; return true; } if (objp->rpr_contents.what == NFS4_CONTENT_HOLE) { if (!xdr_offset4(xdrs, &objp->rpr_contents.hole.di_offset)) return false; if (!xdr_length4(xdrs, &objp->rpr_contents.hole.di_length)) return false; return true; } else return false; } static inline bool xdr_READ_PLUS4res(XDR *xdrs, READ_PLUS4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->rpr_status)) return false; switch (objp->rpr_status) { case NFS4_OK: if (!xdr_READ_PLUS4resok(xdrs, &objp->rpr_resok4)) return false; break; default: break; } return true; } static inline bool xdr_WRITE_SAME4res(XDR *xdrs, WRITE_SAME4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->wpr_status)) return false; switch (objp->wpr_status) { case NFS4_OK: if (!xdr_WRITE_SAME4resok(xdrs, &objp->wpr_resok4)) return false; break; default: break; } return true; } static inline bool xdr_SEEK4args(XDR *xdrs, SEEK4args *objp) { if (!xdr_stateid4(xdrs, &objp->sa_stateid)) return false; if (!xdr_offset4(xdrs, &objp->sa_offset)) return false; if (!inline_xdr_enum(xdrs, (enum_t *)&objp->sa_what)) return false; return true; } static inline bool xdr_ALLOCATE4args(XDR *xdrs, ALLOCATE4args *objp) { if (!xdr_stateid4(xdrs, &objp->aa_stateid)) return false; if (!xdr_offset4(xdrs, &objp->aa_offset)) return false; if (!xdr_length4(xdrs, &objp->aa_length)) return false; return true; } static inline bool xdr_DEALLOCATE4args(XDR *xdrs, DEALLOCATE4args *objp) { if (!xdr_stateid4(xdrs, &objp->da_stateid)) return false; if (!xdr_offset4(xdrs, &objp->da_offset)) return false; if (!xdr_length4(xdrs, &objp->da_length)) return false; return true; } static inline bool xdr_data_contents(XDR *xdrs, contents *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)&objp->what)) return false; if (objp->what == NFS4_CONTENT_DATA) { if (!xdr_offset4(xdrs, &objp->hole.di_offset)) return false; if (!xdr_length4(xdrs, &objp->hole.di_length)) return false; return true; } if (objp->what == NFS4_CONTENT_HOLE) { if (!xdr_offset4(xdrs, &objp->hole.di_offset)) return false; if (!xdr_length4(xdrs, &objp->hole.di_length)) return false; return true; } else return false; } static inline bool xdr_SEEK4resok(XDR *xdrs, seek_res4 *objp) { if (!inline_xdr_bool(xdrs, &objp->sr_eof)) return false; if (!xdr_offset4(xdrs, &objp->sr_offset)) return false; return true; } static inline bool xdr_SEEK4res(XDR *xdrs, SEEK4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->sr_status)) return false; switch (objp->sr_status) { case NFS4_OK: if (!xdr_SEEK4resok(xdrs, &objp->sr_resok4)) return false; break; default: break; } return true; } static inline bool xdr_ALLOCATE4res(XDR *xdrs, ALLOCATE4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->ar_status)) return false; return true; } static inline bool xdr_DEALLOCATE4res(XDR *xdrs, DEALLOCATE4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->dr_status)) return false; return true; } static inline bool xdr_IO_ADVISE4args(XDR *xdrs, IO_ADVISE4args *objp) { if (!xdr_stateid4(xdrs, &objp->iaa_stateid)) return false; if (!xdr_offset4(xdrs, &objp->iaa_offset)) return false; if (!xdr_length4(xdrs, &objp->iaa_count)) return false; if (!xdr_bitmap4(xdrs, &objp->iaa_hints)) return false; return true; } static inline bool xdr_IO_ADVISE4res(XDR *xdrs, IO_ADVISE4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->iaa_status)) return false; switch (objp->iaa_status) { case NFS4_OK: if (!xdr_bitmap4(xdrs, &objp->iaa_hints)) return false; break; default: break; } return true; } static inline bool xdr_LAYOUTERROR4args(XDR *xdrs, LAYOUTERROR4args *objp) { if (!xdr_offset4(xdrs, &objp->lea_offset)) return false; if (!xdr_length4(xdrs, &objp->lea_length)) return false; if (!xdr_stateid4(xdrs, &objp->lea_stateid)) return false; if (!xdr_deviceid4(xdrs, objp->lea_errors.de_deviceid)) return false; if (!xdr_nfsstat4(xdrs, &objp->lea_errors.de_status)) return false; if (!inline_xdr_enum(xdrs, (enum_t *)&objp->lea_errors.de_opnum)) return false; return true; } static inline bool xdr_LAYOUTERROR4res(XDR *xdrs, LAYOUTERROR4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->ler_status)) return false; return true; } static inline bool xdr_LAYOUTSTATS4args(XDR *xdrs, LAYOUTSTATS4args *objp) { if (!xdr_offset4(xdrs, &objp->lsa_offset)) return false; if (!xdr_length4(xdrs, &objp->lsa_length)) return false; if (!xdr_stateid4(xdrs, &objp->lsa_stateid)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->lsa_read.ii_count)) return false; if (!inline_xdr_u_int64_t(xdrs, &objp->lsa_read.ii_bytes)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->lsa_write.ii_count)) return false; if (!inline_xdr_u_int64_t(xdrs, &objp->lsa_write.ii_bytes)) return false; if (!xdr_layoutupdate4(xdrs, &objp->lsa_layoutupdate)) return false; return true; } static inline bool xdr_LAYOUTSTATS4res(XDR *xdrs, LAYOUTSTATS4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->lsr_status)) return false; return true; } /* new operations for NFSv4.1 */ static inline bool xdr_nfs_opnum4(XDR *xdrs, nfs_opnum4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_nfs_argop4(XDR *xdrs, nfs_argop4 *objp) { struct nfs_request_lookahead slhd = { .flags = 0, .read = 0, .write = 0 }; struct nfs_request_lookahead *lkhd = xdrs->x_public ? (struct nfs_request_lookahead *)xdrs->x_public : &slhd; if (!xdr_nfs_opnum4(xdrs, &objp->argop)) return false; switch (objp->argop) { case NFS4_OP_ACCESS: if (!xdr_ACCESS4args(xdrs, &objp->nfs_argop4_u.opaccess)) return false; break; case NFS4_OP_CLOSE: if (!xdr_CLOSE4args(xdrs, &objp->nfs_argop4_u.opclose)) return false; lkhd->flags |= NFS_LOOKAHEAD_CLOSE; break; case NFS4_OP_COMMIT: if (!xdr_COMMIT4args(xdrs, &objp->nfs_argop4_u.opcommit)) return false; lkhd->flags |= NFS_LOOKAHEAD_COMMIT; break; case NFS4_OP_CREATE: if (!xdr_CREATE4args(xdrs, &objp->nfs_argop4_u.opcreate)) return false; lkhd->flags |= NFS_LOOKAHEAD_CREATE; break; case NFS4_OP_DELEGPURGE: if (!xdr_DELEGPURGE4args(xdrs, &objp->nfs_argop4_u.opdelegpurge)) return false; break; case NFS4_OP_DELEGRETURN: if (!xdr_DELEGRETURN4args(xdrs, &objp->nfs_argop4_u.opdelegreturn)) return false; break; case NFS4_OP_GETATTR: if (!xdr_GETATTR4args(xdrs, &objp->nfs_argop4_u.opgetattr)) return false; break; case NFS4_OP_GETFH: break; case NFS4_OP_LINK: if (!xdr_LINK4args(xdrs, &objp->nfs_argop4_u.oplink)) return false; break; case NFS4_OP_LOCK: if (!xdr_LOCK4args(xdrs, &objp->nfs_argop4_u.oplock)) return false; lkhd->flags |= NFS_LOOKAHEAD_LOCK; break; case NFS4_OP_LOCKT: if (!xdr_LOCKT4args(xdrs, &objp->nfs_argop4_u.oplockt)) return false; break; case NFS4_OP_LOCKU: if (!xdr_LOCKU4args(xdrs, &objp->nfs_argop4_u.oplocku)) return false; lkhd->flags |= NFS_LOOKAHEAD_LOCK; break; case NFS4_OP_LOOKUP: if (!xdr_LOOKUP4args(xdrs, &objp->nfs_argop4_u.oplookup)) return false; lkhd->flags |= NFS_LOOKAHEAD_LOOKUP; break; case NFS4_OP_LOOKUPP: lkhd->flags |= NFS_LOOKAHEAD_LOOKUP; break; case NFS4_OP_NVERIFY: if (!xdr_NVERIFY4args(xdrs, &objp->nfs_argop4_u.opnverify)) return false; break; case NFS4_OP_OPEN: if (!xdr_OPEN4args(xdrs, &objp->nfs_argop4_u.opopen)) return false; lkhd->flags |= NFS_LOOKAHEAD_OPEN; if (objp->nfs_argop4_u.opopen.openhow.opentype == OPEN4_CREATE) lkhd->flags |= NFS_LOOKAHEAD_CREATE; break; case NFS4_OP_OPENATTR: if (!xdr_OPENATTR4args(xdrs, &objp->nfs_argop4_u.opopenattr)) return false; break; case NFS4_OP_OPEN_CONFIRM: if (!xdr_OPEN_CONFIRM4args(xdrs, &objp->nfs_argop4_u.opopen_confirm)) return false; lkhd->flags |= NFS_LOOKAHEAD_OPEN; break; case NFS4_OP_OPEN_DOWNGRADE: if (!xdr_OPEN_DOWNGRADE4args( xdrs, &objp->nfs_argop4_u.opopen_downgrade)) return false; lkhd->flags |= NFS_LOOKAHEAD_OPEN; break; case NFS4_OP_PUTFH: if (!xdr_PUTFH4args(xdrs, &objp->nfs_argop4_u.opputfh)) return false; break; case NFS4_OP_PUTPUBFH: break; case NFS4_OP_PUTROOTFH: break; case NFS4_OP_READ: if (!xdr_READ4args(xdrs, &objp->nfs_argop4_u.opread)) return false; lkhd->flags |= NFS_LOOKAHEAD_READ; (lkhd->read)++; break; case NFS4_OP_READDIR: if (!xdr_READDIR4args(xdrs, &objp->nfs_argop4_u.opreaddir)) return false; lkhd->flags |= NFS_LOOKAHEAD_READDIR; break; case NFS4_OP_READLINK: lkhd->flags |= NFS_LOOKAHEAD_READLINK; break; case NFS4_OP_REMOVE: if (!xdr_REMOVE4args(xdrs, &objp->nfs_argop4_u.opremove)) return false; lkhd->flags |= NFS_LOOKAHEAD_REMOVE; break; case NFS4_OP_RENAME: if (!xdr_RENAME4args(xdrs, &objp->nfs_argop4_u.oprename)) return false; lkhd->flags |= NFS_LOOKAHEAD_RENAME; break; case NFS4_OP_RENEW: if (!xdr_RENEW4args(xdrs, &objp->nfs_argop4_u.oprenew)) return false; break; case NFS4_OP_RESTOREFH: break; case NFS4_OP_SAVEFH: break; case NFS4_OP_SECINFO: if (!xdr_SECINFO4args(xdrs, &objp->nfs_argop4_u.opsecinfo)) return false; break; case NFS4_OP_SETATTR: if (!xdr_SETATTR4args(xdrs, &objp->nfs_argop4_u.opsetattr)) return false; lkhd->flags |= NFS_LOOKAHEAD_SETATTR; break; case NFS4_OP_SETCLIENTID: if (!xdr_SETCLIENTID4args(xdrs, &objp->nfs_argop4_u.opsetclientid)) return false; lkhd->flags |= NFS_LOOKAHEAD_SETCLIENTID; break; case NFS4_OP_SETCLIENTID_CONFIRM: if (!xdr_SETCLIENTID_CONFIRM4args( xdrs, &objp->nfs_argop4_u.opsetclientid_confirm)) return false; lkhd->flags |= NFS_LOOKAHEAD_SETCLIENTID_CONFIRM; break; case NFS4_OP_VERIFY: if (!xdr_VERIFY4args(xdrs, &objp->nfs_argop4_u.opverify)) return false; break; case NFS4_OP_WRITE: if (!xdr_WRITE4args(xdrs, &objp->nfs_argop4_u.opwrite)) return false; lkhd->flags |= NFS_LOOKAHEAD_WRITE; (lkhd->write)++; break; case NFS4_OP_RELEASE_LOCKOWNER: if (!xdr_RELEASE_LOCKOWNER4args( xdrs, &objp->nfs_argop4_u.oprelease_lockowner)) return false; break; case NFS4_OP_BACKCHANNEL_CTL: if (!xdr_BACKCHANNEL_CTL4args( xdrs, &objp->nfs_argop4_u.opbackchannel_ctl)) return false; break; case NFS4_OP_BIND_CONN_TO_SESSION: if (!xdr_BIND_CONN_TO_SESSION4args( xdrs, &objp->nfs_argop4_u.opbind_conn_to_session)) return false; break; case NFS4_OP_EXCHANGE_ID: if (!xdr_EXCHANGE_ID4args(xdrs, &objp->nfs_argop4_u.opexchange_id)) return false; break; case NFS4_OP_CREATE_SESSION: if (!xdr_CREATE_SESSION4args( xdrs, &objp->nfs_argop4_u.opcreate_session)) return false; break; case NFS4_OP_DESTROY_SESSION: if (!xdr_DESTROY_SESSION4args( xdrs, &objp->nfs_argop4_u.opdestroy_session)) return false; break; case NFS4_OP_FREE_STATEID: if (!xdr_FREE_STATEID4args(xdrs, &objp->nfs_argop4_u.opfree_stateid)) return false; break; case NFS4_OP_GET_DIR_DELEGATION: if (!xdr_GET_DIR_DELEGATION4args( xdrs, &objp->nfs_argop4_u.opget_dir_delegation)) return false; break; case NFS4_OP_GETDEVICEINFO: if (!xdr_GETDEVICEINFO4args( xdrs, &objp->nfs_argop4_u.opgetdeviceinfo)) return false; break; case NFS4_OP_GETDEVICELIST: if (!xdr_GETDEVICELIST4args( xdrs, &objp->nfs_argop4_u.opgetdevicelist)) return false; break; case NFS4_OP_LAYOUTCOMMIT: if (!xdr_LAYOUTCOMMIT4args(xdrs, &objp->nfs_argop4_u.oplayoutcommit)) return false; lkhd->flags |= NFS_LOOKAHEAD_LAYOUTCOMMIT; break; case NFS4_OP_LAYOUTGET: if (!xdr_LAYOUTGET4args(xdrs, &objp->nfs_argop4_u.oplayoutget)) return false; break; case NFS4_OP_LAYOUTRETURN: if (!xdr_LAYOUTRETURN4args(xdrs, &objp->nfs_argop4_u.oplayoutreturn)) return false; break; case NFS4_OP_SECINFO_NO_NAME: if (!xdr_SECINFO_NO_NAME4args( xdrs, &objp->nfs_argop4_u.opsecinfo_no_name)) return false; break; case NFS4_OP_SEQUENCE: if (!xdr_SEQUENCE4args(xdrs, &objp->nfs_argop4_u.opsequence)) return false; break; case NFS4_OP_SET_SSV: if (!xdr_SET_SSV4args(xdrs, &objp->nfs_argop4_u.opset_ssv)) return false; break; case NFS4_OP_TEST_STATEID: if (!xdr_TEST_STATEID4args(xdrs, &objp->nfs_argop4_u.optest_stateid)) return false; break; case NFS4_OP_WANT_DELEGATION: if (!xdr_WANT_DELEGATION4args( xdrs, &objp->nfs_argop4_u.opwant_delegation)) return false; break; case NFS4_OP_DESTROY_CLIENTID: if (!xdr_DESTROY_CLIENTID4args( xdrs, &objp->nfs_argop4_u.opdestroy_clientid)) return false; break; case NFS4_OP_RECLAIM_COMPLETE: if (!xdr_RECLAIM_COMPLETE4args( xdrs, &objp->nfs_argop4_u.opreclaim_complete)) return false; break; /* NFSv4.2 */ case NFS4_OP_WRITE_SAME: if (!xdr_WRITE_SAME4args(xdrs, &objp->nfs_argop4_u.opwrite_same)) return false; lkhd->flags |= NFS_LOOKAHEAD_WRITE; (lkhd->write)++; break; case NFS4_OP_READ_PLUS: if (!xdr_READ_PLUS4args(xdrs, &objp->nfs_argop4_u.opread_plus)) return false; lkhd->flags |= NFS_LOOKAHEAD_READ; (lkhd->read)++; break; case NFS4_OP_SEEK: if (!xdr_SEEK4args(xdrs, &objp->nfs_argop4_u.opseek)) return false; break; case NFS4_OP_ALLOCATE: if (!xdr_ALLOCATE4args(xdrs, &objp->nfs_argop4_u.opallocate)) return false; break; case NFS4_OP_DEALLOCATE: if (!xdr_DEALLOCATE4args(xdrs, &objp->nfs_argop4_u.opdeallocate)) return false; break; case NFS4_OP_IO_ADVISE: if (!xdr_IO_ADVISE4args(xdrs, &objp->nfs_argop4_u.opio_advise)) return false; break; case NFS4_OP_LAYOUTERROR: if (!xdr_LAYOUTERROR4args(xdrs, &objp->nfs_argop4_u.oplayouterror)) return false; break; case NFS4_OP_LAYOUTSTATS: if (!xdr_LAYOUTSTATS4args(xdrs, &objp->nfs_argop4_u.oplayoutstats)) return false; break; case NFS4_OP_COPY: case NFS4_OP_COPY_NOTIFY: case NFS4_OP_OFFLOAD_CANCEL: case NFS4_OP_OFFLOAD_STATUS: case NFS4_OP_CLONE: break; /* NFSv4.3 */ case NFS4_OP_GETXATTR: if (!xdr_GETXATTR4args(xdrs, &objp->nfs_argop4_u.opgetxattr)) return false; break; case NFS4_OP_SETXATTR: if (!xdr_SETXATTR4args(xdrs, &objp->nfs_argop4_u.opsetxattr)) return false; break; case NFS4_OP_LISTXATTR: if (!xdr_LISTXATTR4args(xdrs, &objp->nfs_argop4_u.oplistxattr)) return false; break; case NFS4_OP_REMOVEXATTR: if (!xdr_REMOVEXATTR4args(xdrs, &objp->nfs_argop4_u.opremovexattr)) return false; break; case NFS4_OP_ILLEGAL: break; default: /* Avoid transforming unknown opcodes as RPC * decoder errors */ objp->argop = NFS4_OP_ILLEGAL; break; } return true; } static inline bool xdr_nfs_resop4(XDR *xdrs, nfs_resop4 *objp) { if (!xdr_nfs_opnum4(xdrs, &objp->resop)) return false; switch (objp->resop) { case NFS4_OP_ACCESS: if (!xdr_ACCESS4res(xdrs, &objp->nfs_resop4_u.opaccess)) return false; break; case NFS4_OP_CLOSE: if (!xdr_CLOSE4res(xdrs, &objp->nfs_resop4_u.opclose)) return false; break; case NFS4_OP_COMMIT: if (!xdr_COMMIT4res(xdrs, &objp->nfs_resop4_u.opcommit)) return false; break; case NFS4_OP_CREATE: if (!xdr_CREATE4res(xdrs, &objp->nfs_resop4_u.opcreate)) return false; break; case NFS4_OP_DELEGPURGE: if (!xdr_DELEGPURGE4res(xdrs, &objp->nfs_resop4_u.opdelegpurge)) return false; break; case NFS4_OP_DELEGRETURN: if (!xdr_DELEGRETURN4res(xdrs, &objp->nfs_resop4_u.opdelegreturn)) return false; break; case NFS4_OP_GETATTR: if (!xdr_GETATTR4res(xdrs, &objp->nfs_resop4_u.opgetattr)) return false; break; case NFS4_OP_GETFH: if (!xdr_GETFH4res(xdrs, &objp->nfs_resop4_u.opgetfh)) return false; break; case NFS4_OP_LINK: if (!xdr_LINK4res(xdrs, &objp->nfs_resop4_u.oplink)) return false; break; case NFS4_OP_LOCK: if (!xdr_LOCK4res(xdrs, &objp->nfs_resop4_u.oplock)) return false; break; case NFS4_OP_LOCKT: if (!xdr_LOCKT4res(xdrs, &objp->nfs_resop4_u.oplockt)) return false; break; case NFS4_OP_LOCKU: if (!xdr_LOCKU4res(xdrs, &objp->nfs_resop4_u.oplocku)) return false; break; case NFS4_OP_LOOKUP: if (!xdr_LOOKUP4res(xdrs, &objp->nfs_resop4_u.oplookup)) return false; break; case NFS4_OP_LOOKUPP: if (!xdr_LOOKUPP4res(xdrs, &objp->nfs_resop4_u.oplookupp)) return false; break; case NFS4_OP_NVERIFY: if (!xdr_NVERIFY4res(xdrs, &objp->nfs_resop4_u.opnverify)) return false; break; case NFS4_OP_OPEN: if (!xdr_OPEN4res(xdrs, &objp->nfs_resop4_u.opopen)) return false; break; case NFS4_OP_OPENATTR: if (!xdr_OPENATTR4res(xdrs, &objp->nfs_resop4_u.opopenattr)) return false; break; case NFS4_OP_OPEN_CONFIRM: if (!xdr_OPEN_CONFIRM4res(xdrs, &objp->nfs_resop4_u.opopen_confirm)) return false; break; case NFS4_OP_OPEN_DOWNGRADE: if (!xdr_OPEN_DOWNGRADE4res( xdrs, &objp->nfs_resop4_u.opopen_downgrade)) return false; break; case NFS4_OP_PUTFH: if (!xdr_PUTFH4res(xdrs, &objp->nfs_resop4_u.opputfh)) return false; break; case NFS4_OP_PUTPUBFH: if (!xdr_PUTPUBFH4res(xdrs, &objp->nfs_resop4_u.opputpubfh)) return false; break; case NFS4_OP_PUTROOTFH: if (!xdr_PUTROOTFH4res(xdrs, &objp->nfs_resop4_u.opputrootfh)) return false; break; case NFS4_OP_READ: if (!xdr_READ4res(xdrs, &objp->nfs_resop4_u.opread)) return false; break; case NFS4_OP_READDIR: if (!xdr_READDIR4res(xdrs, &objp->nfs_resop4_u.opreaddir)) return false; break; case NFS4_OP_READLINK: if (!xdr_READLINK4res(xdrs, &objp->nfs_resop4_u.opreadlink)) return false; break; case NFS4_OP_REMOVE: if (!xdr_REMOVE4res(xdrs, &objp->nfs_resop4_u.opremove)) return false; break; case NFS4_OP_RENAME: if (!xdr_RENAME4res(xdrs, &objp->nfs_resop4_u.oprename)) return false; break; case NFS4_OP_RENEW: if (!xdr_RENEW4res(xdrs, &objp->nfs_resop4_u.oprenew)) return false; break; case NFS4_OP_RESTOREFH: if (!xdr_RESTOREFH4res(xdrs, &objp->nfs_resop4_u.oprestorefh)) return false; break; case NFS4_OP_SAVEFH: if (!xdr_SAVEFH4res(xdrs, &objp->nfs_resop4_u.opsavefh)) return false; break; case NFS4_OP_SECINFO: if (!xdr_SECINFO4res(xdrs, &objp->nfs_resop4_u.opsecinfo)) return false; break; case NFS4_OP_SETATTR: if (!xdr_SETATTR4res(xdrs, &objp->nfs_resop4_u.opsetattr)) return false; break; case NFS4_OP_SETCLIENTID: if (!xdr_SETCLIENTID4res(xdrs, &objp->nfs_resop4_u.opsetclientid)) return false; break; case NFS4_OP_SETCLIENTID_CONFIRM: if (!xdr_SETCLIENTID_CONFIRM4res( xdrs, &objp->nfs_resop4_u.opsetclientid_confirm)) return false; break; case NFS4_OP_VERIFY: if (!xdr_VERIFY4res(xdrs, &objp->nfs_resop4_u.opverify)) return false; break; case NFS4_OP_WRITE: if (!xdr_WRITE4res(xdrs, &objp->nfs_resop4_u.opwrite)) return false; break; case NFS4_OP_RELEASE_LOCKOWNER: if (!xdr_RELEASE_LOCKOWNER4res( xdrs, &objp->nfs_resop4_u.oprelease_lockowner)) return false; break; case NFS4_OP_BACKCHANNEL_CTL: if (!xdr_BACKCHANNEL_CTL4res( xdrs, &objp->nfs_resop4_u.opbackchannel_ctl)) return false; break; case NFS4_OP_BIND_CONN_TO_SESSION: if (!xdr_BIND_CONN_TO_SESSION4res( xdrs, &objp->nfs_resop4_u.opbind_conn_to_session)) return false; break; case NFS4_OP_EXCHANGE_ID: if (!xdr_EXCHANGE_ID4res(xdrs, &objp->nfs_resop4_u.opexchange_id)) return false; break; case NFS4_OP_CREATE_SESSION: if (!xdr_CREATE_SESSION4res( xdrs, &objp->nfs_resop4_u.opcreate_session)) return false; break; case NFS4_OP_DESTROY_SESSION: if (!xdr_DESTROY_SESSION4res( xdrs, &objp->nfs_resop4_u.opdestroy_session)) return false; break; case NFS4_OP_FREE_STATEID: if (!xdr_FREE_STATEID4res(xdrs, &objp->nfs_resop4_u.opfree_stateid)) return false; break; case NFS4_OP_GET_DIR_DELEGATION: if (!xdr_GET_DIR_DELEGATION4res( xdrs, &objp->nfs_resop4_u.opget_dir_delegation)) return false; break; case NFS4_OP_GETDEVICEINFO: if (!xdr_GETDEVICEINFO4res(xdrs, &objp->nfs_resop4_u.opgetdeviceinfo)) return false; break; case NFS4_OP_GETDEVICELIST: if (!xdr_GETDEVICELIST4res(xdrs, &objp->nfs_resop4_u.opgetdevicelist)) return false; break; case NFS4_OP_LAYOUTCOMMIT: if (!xdr_LAYOUTCOMMIT4res(xdrs, &objp->nfs_resop4_u.oplayoutcommit)) return false; break; case NFS4_OP_LAYOUTGET: if (!xdr_LAYOUTGET4res(xdrs, &objp->nfs_resop4_u.oplayoutget)) return false; break; case NFS4_OP_LAYOUTRETURN: if (!xdr_LAYOUTRETURN4res(xdrs, &objp->nfs_resop4_u.oplayoutreturn)) return false; break; case NFS4_OP_SECINFO_NO_NAME: if (!xdr_SECINFO_NO_NAME4res( xdrs, &objp->nfs_resop4_u.opsecinfo_no_name)) return false; break; case NFS4_OP_SEQUENCE: if (!xdr_SEQUENCE4res(xdrs, &objp->nfs_resop4_u.opsequence)) return false; break; case NFS4_OP_SET_SSV: if (!xdr_SET_SSV4res(xdrs, &objp->nfs_resop4_u.opset_ssv)) return false; break; case NFS4_OP_TEST_STATEID: if (!xdr_TEST_STATEID4res(xdrs, &objp->nfs_resop4_u.optest_stateid)) return false; break; case NFS4_OP_WANT_DELEGATION: if (!xdr_WANT_DELEGATION4res( xdrs, &objp->nfs_resop4_u.opwant_delegation)) return false; break; case NFS4_OP_DESTROY_CLIENTID: if (!xdr_DESTROY_CLIENTID4res( xdrs, &objp->nfs_resop4_u.opdestroy_clientid)) return false; break; case NFS4_OP_RECLAIM_COMPLETE: if (!xdr_RECLAIM_COMPLETE4res( xdrs, &objp->nfs_resop4_u.opreclaim_complete)) return false; break; /* NFSv4.2 */ case NFS4_OP_WRITE_SAME: if (!xdr_WRITE_SAME4res(xdrs, &objp->nfs_resop4_u.opwrite_same)) return false; break; case NFS4_OP_READ_PLUS: if (!xdr_READ_PLUS4res(xdrs, &objp->nfs_resop4_u.opread_plus)) return false; break; case NFS4_OP_SEEK: if (!xdr_SEEK4res(xdrs, &objp->nfs_resop4_u.opseek)) return false; break; case NFS4_OP_ALLOCATE: if (!xdr_ALLOCATE4res(xdrs, &objp->nfs_resop4_u.opallocate)) return false; break; case NFS4_OP_DEALLOCATE: if (!xdr_DEALLOCATE4res(xdrs, &objp->nfs_resop4_u.opdeallocate)) return false; break; case NFS4_OP_IO_ADVISE: if (!xdr_IO_ADVISE4res(xdrs, &objp->nfs_resop4_u.opio_advise)) return false; break; case NFS4_OP_LAYOUTERROR: if (!xdr_LAYOUTERROR4res(xdrs, &objp->nfs_resop4_u.oplayouterror)) return false; break; case NFS4_OP_LAYOUTSTATS: if (!xdr_LAYOUTSTATS4res(xdrs, &objp->nfs_resop4_u.oplayoutstats)) return false; break; case NFS4_OP_COPY: case NFS4_OP_COPY_NOTIFY: case NFS4_OP_OFFLOAD_CANCEL: case NFS4_OP_OFFLOAD_STATUS: case NFS4_OP_CLONE: /* NFSv4.3 */ case NFS4_OP_GETXATTR: if (!xdr_GETXATTR4res(xdrs, &objp->nfs_resop4_u.opgetxattr)) return false; break; case NFS4_OP_SETXATTR: if (!xdr_SETXATTR4res(xdrs, &objp->nfs_resop4_u.opsetxattr)) return false; break; case NFS4_OP_LISTXATTR: if (!xdr_LISTXATTR4res(xdrs, &objp->nfs_resop4_u.oplistxattr)) return false; break; case NFS4_OP_REMOVEXATTR: if (!xdr_REMOVEXATTR4res(xdrs, &objp->nfs_resop4_u.opremovexattr)) return false; break; case NFS4_OP_ILLEGAL: if (!xdr_ILLEGAL4res(xdrs, &objp->nfs_resop4_u.opillegal)) return false; break; default: return false; } return true; } static inline bool xdr_COMPOUND4args(XDR *xdrs, COMPOUND4args *objp) { if (!xdr_utf8str_cs(xdrs, &objp->tag)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->minorversion)) return false; /* decoder hint */ if (objp->minorversion > 0) xdrs->x_flags &= ~XDR_FLAG_CKSUM; if (!xdr_array(xdrs, (char **)&objp->argarray.argarray_val, &objp->argarray.argarray_len, XDR_ARRAY_MAXLEN, sizeof(nfs_argop4), (xdrproc_t)xdr_nfs_argop4)) return false; return true; } static inline bool xdr_COMPOUND4res(XDR *xdrs, COMPOUND4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; if (!xdr_utf8str_cs(xdrs, &objp->tag)) return false; if (!xdr_array(xdrs, (char **)&objp->resarray.resarray_val, &objp->resarray.resarray_len, XDR_ARRAY_MAXLEN, sizeof(nfs_resop4), (xdrproc_t)xdr_nfs_resop4)) return false; return true; } static inline bool xdr_CB_GETATTR4args(XDR *xdrs, CB_GETATTR4args *objp) { if (!xdr_nfs_fh4(xdrs, &objp->fh)) return false; if (!xdr_bitmap4(xdrs, &objp->attr_request)) return false; return true; } static inline bool xdr_CB_GETATTR4resok(XDR *xdrs, CB_GETATTR4resok *objp) { if (!xdr_fattr4(xdrs, &objp->obj_attributes)) return false; return true; } static inline bool xdr_CB_GETATTR4res(XDR *xdrs, CB_GETATTR4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; switch (objp->status) { case NFS4_OK: if (!xdr_CB_GETATTR4resok(xdrs, &objp->CB_GETATTR4res_u.resok4)) return false; break; default: break; } return true; } static inline bool xdr_CB_RECALL4args(XDR *xdrs, CB_RECALL4args *objp) { if (!xdr_stateid4(xdrs, &objp->stateid)) return false; if (!inline_xdr_bool(xdrs, &objp->truncate)) return false; if (!xdr_nfs_fh4(xdrs, &objp->fh)) return false; return true; } static inline bool xdr_CB_RECALL4res(XDR *xdrs, CB_RECALL4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; return true; } static inline bool xdr_CB_ILLEGAL4res(XDR *xdrs, CB_ILLEGAL4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; return true; } static inline bool xdr_layoutrecall_type4(XDR *xdrs, layoutrecall_type4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_layoutrecall_file4(XDR *xdrs, layoutrecall_file4 *objp) { if (!xdr_nfs_fh4(xdrs, &objp->lor_fh)) return false; if (!xdr_offset4(xdrs, &objp->lor_offset)) return false; if (!xdr_length4(xdrs, &objp->lor_length)) return false; if (!xdr_stateid4(xdrs, &objp->lor_stateid)) return false; return true; } static inline bool xdr_layoutrecall4(XDR *xdrs, layoutrecall4 *objp) { if (!xdr_layoutrecall_type4(xdrs, &objp->lor_recalltype)) return false; switch (objp->lor_recalltype) { case LAYOUTRECALL4_FILE: if (!xdr_layoutrecall_file4(xdrs, &objp->layoutrecall4_u.lor_layout)) return false; break; case LAYOUTRECALL4_FSID: if (!xdr_fsid4(xdrs, &objp->layoutrecall4_u.lor_fsid)) return false; break; case LAYOUTRECALL4_ALL: break; default: return false; } return true; } static inline bool xdr_CB_LAYOUTRECALL4args(XDR *xdrs, CB_LAYOUTRECALL4args *objp) { if (!xdr_layouttype4(xdrs, &objp->clora_type)) return false; if (!xdr_layoutiomode4(xdrs, &objp->clora_iomode)) return false; if (!inline_xdr_bool(xdrs, &objp->clora_changed)) return false; if (!xdr_layoutrecall4(xdrs, &objp->clora_recall)) return false; return true; } static inline bool xdr_CB_LAYOUTRECALL4res(XDR *xdrs, CB_LAYOUTRECALL4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->clorr_status)) return false; return true; } static inline bool xdr_notify_type4(XDR *xdrs, notify_type4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_notify_entry4(XDR *xdrs, notify_entry4 *objp) { if (!xdr_component4(xdrs, &objp->ne_file)) return false; if (!xdr_fattr4(xdrs, &objp->ne_attrs)) return false; return true; } static inline bool xdr_prev_entry4(XDR *xdrs, prev_entry4 *objp) { if (!xdr_notify_entry4(xdrs, &objp->pe_prev_entry)) return false; if (!xdr_nfs_cookie4(xdrs, &objp->pe_prev_entry_cookie)) return false; return true; } static inline bool xdr_notify_remove4(XDR *xdrs, notify_remove4 *objp) { if (!xdr_notify_entry4(xdrs, &objp->nrm_old_entry)) return false; if (!xdr_nfs_cookie4(xdrs, &objp->nrm_old_entry_cookie)) return false; return true; } static inline bool xdr_notify_add4(XDR *xdrs, notify_add4 *objp) { if (!xdr_array(xdrs, (char **)&objp->nad_old_entry.nad_old_entry_val, (u_int *)&objp->nad_old_entry.nad_old_entry_len, 1, sizeof(notify_remove4), (xdrproc_t)xdr_notify_remove4)) return false; if (!xdr_notify_entry4(xdrs, &objp->nad_new_entry)) return false; if (!xdr_array(xdrs, (char **)&objp->nad_new_entry_cookie .nad_new_entry_cookie_val, (u_int *)&objp->nad_new_entry_cookie .nad_new_entry_cookie_len, 1, sizeof(nfs_cookie4), (xdrproc_t)xdr_nfs_cookie4)) return false; if (!xdr_array(xdrs, (char **)&objp->nad_prev_entry.nad_prev_entry_val, (u_int *)&objp->nad_prev_entry.nad_prev_entry_len, 1, sizeof(prev_entry4), (xdrproc_t)xdr_prev_entry4)) return false; if (!inline_xdr_bool(xdrs, &objp->nad_last_entry)) return false; return true; } static inline bool xdr_notify_attr4(XDR *xdrs, notify_attr4 *objp) { if (!xdr_notify_entry4(xdrs, &objp->na_changed_entry)) return false; return true; } static inline bool xdr_notify_rename4(XDR *xdrs, notify_rename4 *objp) { if (!xdr_notify_remove4(xdrs, &objp->nrn_old_entry)) return false; if (!xdr_notify_add4(xdrs, &objp->nrn_new_entry)) return false; return true; } static inline bool xdr_notify_verifier4(XDR *xdrs, notify_verifier4 *objp) { if (!xdr_verifier4(xdrs, objp->nv_old_cookieverf)) return false; if (!xdr_verifier4(xdrs, objp->nv_new_cookieverf)) return false; return true; } static inline bool xdr_notify_deviceid_delete4(XDR *xdrs, notify_deviceid_delete4 *objp) { if (!xdr_layouttype4(xdrs, &objp->ndd_layouttype)) return false; if (!xdr_deviceid4(xdrs, objp->ndd_deviceid)) return false; return true; } static inline bool xdr_notify_deviceid_change4(XDR *xdrs, notify_deviceid_change4 *objp) { if (!xdr_layouttype4(xdrs, &objp->ndc_layouttype)) return false; if (!xdr_deviceid4(xdrs, objp->ndc_deviceid)) return false; if (!inline_xdr_bool(xdrs, &objp->ndc_immediate)) return false; return true; } static inline bool xdr_notifylist4(XDR *xdrs, notifylist4 *objp) { if (!inline_xdr_bytes(xdrs, (char **)&objp->notifylist4_val, &objp->notifylist4_len, XDR_BYTES_MAXLEN)) return false; return true; } static inline bool xdr_notifylist_dev(XDR *xdrs, notifylist4 *objp, int type) { if (!xdr_count4(xdrs, &objp->notifylist4_len)) return false; if (type == NOTIFY_DEVICEID4_DELETE_MASK) { if (!xdr_notify_deviceid_delete4( xdrs, (notify_deviceid_delete4 *)objp->notifylist4_val)) return false; } else { if (!xdr_notify_deviceid_change4( xdrs, (notify_deviceid_change4 *)objp->notifylist4_val)) return false; } return true; } static inline bool xdr_notify4(XDR *xdrs, notify4 *objp) { if (!xdr_bitmap4(xdrs, &objp->notify_mask)) return false; if (!xdr_notifylist4(xdrs, &objp->notify_vals)) return false; return true; } static inline bool xdr_notify_dev(XDR *xdrs, notify4 *objp) { if (!xdr_bitmap4(xdrs, &objp->notify_mask)) return false; if (!xdr_notifylist_dev(xdrs, &objp->notify_vals, objp->notify_mask.map[0])) return false; return true; } static inline bool xdr_CB_NOTIFY4args(XDR *xdrs, CB_NOTIFY4args *objp) { if (!xdr_stateid4(xdrs, &objp->cna_stateid)) return false; if (!xdr_nfs_fh4(xdrs, &objp->cna_fh)) return false; if (!xdr_array(xdrs, (char **)&objp->cna_changes.cna_changes_val, &objp->cna_changes.cna_changes_len, XDR_ARRAY_MAXLEN, sizeof(notify4), (xdrproc_t)xdr_notify4)) return false; return true; } static inline bool xdr_CB_NOTIFY4res(XDR *xdrs, CB_NOTIFY4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->cnr_status)) return false; return true; } static inline bool xdr_CB_PUSH_DELEG4args(XDR *xdrs, CB_PUSH_DELEG4args *objp) { if (!xdr_nfs_fh4(xdrs, &objp->cpda_fh)) return false; if (!xdr_open_delegation4(xdrs, &objp->cpda_delegation)) return false; return true; } static inline bool xdr_CB_PUSH_DELEG4res(XDR *xdrs, CB_PUSH_DELEG4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->cpdr_status)) return false; return true; } static inline bool xdr_CB_RECALL_ANY4args(XDR *xdrs, CB_RECALL_ANY4args *objp) { if (!inline_xdr_u_int32_t(xdrs, &objp->craa_objects_to_keep)) return false; if (!xdr_bitmap4(xdrs, &objp->craa_type_mask)) return false; return true; } static inline bool xdr_CB_RECALL_ANY4res(XDR *xdrs, CB_RECALL_ANY4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->crar_status)) return false; return true; } static inline bool xdr_CB_RECALLABLE_OBJ_AVAIL4args(XDR *xdrs, CB_RECALLABLE_OBJ_AVAIL4args *objp) { if (!xdr_CB_RECALL_ANY4args(xdrs, objp)) return false; return true; } static inline bool xdr_CB_RECALLABLE_OBJ_AVAIL4res(XDR *xdrs, CB_RECALLABLE_OBJ_AVAIL4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->croa_status)) return false; return true; } static inline bool xdr_CB_RECALL_SLOT4args(XDR *xdrs, CB_RECALL_SLOT4args *objp) { if (!xdr_slotid4(xdrs, &objp->rsa_target_highest_slotid)) return false; return true; } static inline bool xdr_CB_RECALL_SLOT4res(XDR *xdrs, CB_RECALL_SLOT4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->rsr_status)) return false; return true; } static inline bool xdr_referring_call4(XDR *xdrs, referring_call4 *objp) { if (!xdr_sequenceid4(xdrs, &objp->rc_sequenceid)) return false; if (!xdr_slotid4(xdrs, &objp->rc_slotid)) return false; return true; } static inline bool xdr_referring_call_list4(XDR *xdrs, referring_call_list4 *objp) { if (!xdr_sessionid4(xdrs, objp->rcl_sessionid)) return false; if (!xdr_array( xdrs, (char **)&objp->rcl_referring_calls.rcl_referring_calls_val, &objp->rcl_referring_calls.rcl_referring_calls_len, XDR_ARRAY_MAXLEN, sizeof(referring_call4), (xdrproc_t)xdr_referring_call4)) return false; return true; } /* * XDR functions for FLEX_FILES loc_body * */ static inline bool xdr_ff_device_versions4(XDR *xdrs, ff_device_versions4 *objp) { if (!xdr_uint32_t(xdrs, &objp->ffdv_version)) return false; if (!xdr_uint32_t(xdrs, &objp->ffdv_minorversion)) return false; if (!xdr_uint32_t(xdrs, &objp->ffdv_rsize)) return false; if (!xdr_uint32_t(xdrs, &objp->ffdv_wsize)) return false; if (!xdr_bool(xdrs, &objp->ffdv_tightly_coupled)) return false; return true; } static inline bool xdr_ff_device_addr4(XDR *xdrs, ff_device_addr4 *objp) { if (!xdr_multipath_list4(xdrs, &objp->ffda_netaddrs)) return false; if (!xdr_array(xdrs, (char **)&objp->ffda_versions.ffda_versions_val, &objp->ffda_versions.ffda_versions_len, XDR_ARRAY_MAXLEN, sizeof(ff_device_versions4), (xdrproc_t)xdr_ff_device_versions4)) return false; return true; } static inline bool xdr_ff_data_server4(XDR *xdrs, ff_data_server4 *objp) { if (!xdr_deviceid4(xdrs, objp->ffds_deviceid)) return false; if (!xdr_uint32_t(xdrs, &objp->ffds_efficiency)) return false; if (!xdr_stateid4(xdrs, &objp->ffds_stateid)) return false; if (!xdr_array(xdrs, (char **)&objp->ffds_fh_vers.ffds_fh_vers_val, &objp->ffds_fh_vers.ffds_fh_vers_len, XDR_ARRAY_MAXLEN, sizeof(nfs_fh4), (xdrproc_t)xdr_nfs_fh4)) return false; if (!xdr_fattr4_owner(xdrs, &objp->ffds_user)) return false; if (!xdr_fattr4_owner_group(xdrs, &objp->ffds_group)) return false; return true; } static inline bool xdr_ff_mirror4(XDR *xdrs, ff_mirror4 *objp) { if (!xdr_array(xdrs, (char **)&objp->ffm_data_servers.ffm_data_servers_val, &objp->ffm_data_servers.ffm_data_servers_len, XDR_ARRAY_MAXLEN, sizeof(ff_data_server4), (xdrproc_t)xdr_ff_data_server4)) return false; return true; } static inline bool xdr_ff_flags(XDR *xdrs, ff_flags4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_ff_layout4(XDR *xdrs, ff_layout4 *objp) { if (!xdr_length4(xdrs, &objp->ffl_stripe_unit)) return false; if (!xdr_array(xdrs, (char **)&objp->ffl_mirrors.ffl_mirrors_val, &objp->ffl_mirrors.ffl_mirrors_len, XDR_ARRAY_MAXLEN, sizeof(ff_mirror4), (xdrproc_t)xdr_ff_mirror4)) return false; if (!xdr_ff_flags(xdrs, &objp->ffl_flags)) return false; return true; } static inline bool xdr_device_error4(XDR *xdrs, device_error4 *objp) { if (!xdr_deviceid4(xdrs, objp->de_deviceid)) return false; if (!xdr_nfsstat4(xdrs, &objp->de_status)) return false; if (!xdr_nfs_opnum4(xdrs, &objp->de_opnum)) return false; return true; } static inline bool xdr_ff_ioerr4(XDR *xdrs, ff_ioerr4 *objp) { if (!xdr_offset4(xdrs, &objp->ffie_offset)) return false; if (!xdr_length4(xdrs, &objp->ffie_length)) return false; if (!xdr_stateid4(xdrs, &objp->ffie_stateid)) return false; if (!xdr_array(xdrs, (char **)&objp->ffie_errors.ffie_errors_val, &objp->ffie_errors.ffie_errors_len, XDR_ARRAY_MAXLEN, sizeof(device_error4), (xdrproc_t)xdr_device_error4)) return false; return true; } static inline bool xdr_ff_io_latency4(XDR *xdrs, ff_io_latency4 *objp) { if (!xdr_uint64_t(xdrs, &objp->ffil_ops_requested)) return false; if (!xdr_uint64_t(xdrs, &objp->ffil_bytes_requested)) return false; if (!xdr_uint64_t(xdrs, &objp->ffil_ops_completed)) return false; if (!xdr_uint64_t(xdrs, &objp->ffil_bytes_completed)) return false; if (!xdr_uint64_t(xdrs, &objp->ffil_bytes_not_delivered)) return false; if (!xdr_nfstime4(xdrs, &objp->ffil_total_busy_time)) return false; if (!xdr_nfstime4(xdrs, &objp->ffil_aggregate_completion_time)) return false; return true; } static inline bool xdr_ff_layoutupdate4(XDR *xdrs, ff_layoutupdate4 *objp) { if (!xdr_netaddr4(xdrs, &objp->ffl_addr)) return false; if (!xdr_nfs_fh4(xdrs, &objp->ffl_fhandle)) return false; if (!xdr_ff_io_latency4(xdrs, &objp->ffl_read)) return false; if (!xdr_ff_io_latency4(xdrs, &objp->ffl_write)) return false; if (!xdr_nfstime4(xdrs, &objp->ffl_duration)) return false; if (!xdr_bool(xdrs, &objp->ffl_local)) return false; return true; } static inline bool xdr_io_info4(XDR *xdrs, io_info4 *objp) { if (!xdr_uint32_t(xdrs, &objp->ii_count)) return FALSE; if (!xdr_uint64_t(xdrs, &objp->ii_bytes)) return FALSE; return TRUE; } static inline bool xdr_ff_iostats4(XDR *xdrs, ff_iostats4 *objp) { if (!xdr_offset4(xdrs, &objp->ffis_offset)) return false; if (!xdr_length4(xdrs, &objp->ffis_length)) return false; if (!xdr_stateid4(xdrs, &objp->ffis_stateid)) return false; if (!xdr_io_info4(xdrs, &objp->ffis_read)) return false; if (!xdr_io_info4(xdrs, &objp->ffis_write)) return false; if (!xdr_deviceid4(xdrs, objp->ffis_deviceid)) return false; if (!xdr_ff_layoutupdate4(xdrs, &objp->ffis_layoutupdate)) return false; return true; } static inline bool xdr_ff_layoutreturn4(XDR *xdrs, ff_layoutreturn4 *objp) { if (!xdr_array(xdrs, (char **)&objp->fflr_ioerr_report.fflr_ioerr_report_val, &objp->fflr_ioerr_report.fflr_ioerr_report_len, XDR_ARRAY_MAXLEN, sizeof(ff_ioerr4), (xdrproc_t)xdr_ff_ioerr4)) return false; if (!xdr_array( xdrs, (char **)&objp->fflr_iostats_report.fflr_iostats_report_val, &objp->fflr_iostats_report.fflr_iostats_report_len, XDR_ARRAY_MAXLEN, sizeof(ff_iostats4), (xdrproc_t)xdr_ff_iostats4)) return false; return true; } static inline bool xdr_ff_mirrors_hint(XDR *xdrs, ff_mirrors_hint *objp) { if (!xdr_bool(xdrs, &objp->ffmc_valid)) return false; switch (objp->ffmc_valid) { case TRUE: if (!xdr_uint32_t(xdrs, &objp->ff_mirrors_hint_u.ffmc_mirrors)) return false; break; case FALSE: break; default: return false; } return true; } static inline bool xdr_ff_layouthint4(XDR *xdrs, ff_layouthint4 *objp) { if (!xdr_ff_mirrors_hint(xdrs, &objp->fflh_mirrors_hint)) return false; return true; } static inline bool xdr_ff_cb_recall_any_mask(XDR *xdrs, ff_cb_recall_any_mask *objp) { if (!xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_CB_SEQUENCE4args(XDR *xdrs, CB_SEQUENCE4args *objp) { if (!xdr_sessionid4(xdrs, objp->csa_sessionid)) return false; if (!xdr_sequenceid4(xdrs, &objp->csa_sequenceid)) return false; if (!xdr_slotid4(xdrs, &objp->csa_slotid)) return false; if (!xdr_slotid4(xdrs, &objp->csa_highest_slotid)) return false; if (!inline_xdr_bool(xdrs, &objp->csa_cachethis)) return false; if (!xdr_array(xdrs, (char **)&objp->csa_referring_call_lists.csarcl_val, &objp->csa_referring_call_lists.csarcl_len, XDR_ARRAY_MAXLEN, sizeof(referring_call_list4), (xdrproc_t)xdr_referring_call_list4)) return false; return true; } static inline bool xdr_CB_SEQUENCE4resok(XDR *xdrs, CB_SEQUENCE4resok *objp) { if (!xdr_sessionid4(xdrs, objp->csr_sessionid)) return false; if (!xdr_sequenceid4(xdrs, &objp->csr_sequenceid)) return false; if (!xdr_slotid4(xdrs, &objp->csr_slotid)) return false; if (!xdr_slotid4(xdrs, &objp->csr_highest_slotid)) return false; if (!xdr_slotid4(xdrs, &objp->csr_target_highest_slotid)) return false; return true; } static inline bool xdr_CB_SEQUENCE4res(XDR *xdrs, CB_SEQUENCE4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->csr_status)) return false; switch (objp->csr_status) { case NFS4_OK: if (!xdr_CB_SEQUENCE4resok(xdrs, &objp->CB_SEQUENCE4res_u.csr_resok4)) return false; break; default: break; } return true; } static inline bool xdr_CB_WANTS_CANCELLED4args(XDR *xdrs, CB_WANTS_CANCELLED4args *objp) { if (!inline_xdr_bool(xdrs, &objp->cwca_contended_wants_cancelled)) return false; if (!inline_xdr_bool(xdrs, &objp->cwca_resourced_wants_cancelled)) return false; return true; } static inline bool xdr_CB_WANTS_CANCELLED4res(XDR *xdrs, CB_WANTS_CANCELLED4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->cwcr_status)) return false; return true; } static inline bool xdr_CB_NOTIFY_LOCK4args(XDR *xdrs, CB_NOTIFY_LOCK4args *objp) { if (!xdr_nfs_fh4(xdrs, &objp->cnla_fh)) return false; if (!xdr_lock_owner4(xdrs, &objp->cnla_lock_owner)) return false; return true; } static inline bool xdr_CB_NOTIFY_LOCK4res(XDR *xdrs, CB_NOTIFY_LOCK4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->cnlr_status)) return false; return true; } static inline bool xdr_notify_deviceid_type4(XDR *xdrs, notify_deviceid_type4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_CB_NOTIFY_DEVICEID4args(XDR *xdrs, CB_NOTIFY_DEVICEID4args *objp) { if (!xdr_array(xdrs, (char **)&objp->cnda_changes.cnda_changes_val, &objp->cnda_changes.cnda_changes_len, XDR_ARRAY_MAXLEN, sizeof(notify4), (xdrproc_t)xdr_notify_dev)) return false; return true; } static inline bool xdr_CB_NOTIFY_DEVICEID4res(XDR *xdrs, CB_NOTIFY_DEVICEID4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->cndr_status)) return false; return true; } /* Callback operations new to NFSv4.1 */ static inline bool xdr_nfs_cb_opnum4(XDR *xdrs, nfs_cb_opnum4 *objp) { if (!inline_xdr_enum(xdrs, (enum_t *)objp)) return false; return true; } static inline bool xdr_nfs_cb_argop4(XDR *xdrs, nfs_cb_argop4 *objp) { if (!inline_xdr_u_int(xdrs, &objp->argop)) return false; switch (objp->argop) { case NFS4_OP_CB_GETATTR: if (!xdr_CB_GETATTR4args(xdrs, &objp->nfs_cb_argop4_u.opcbgetattr)) return false; break; case NFS4_OP_CB_RECALL: if (!xdr_CB_RECALL4args(xdrs, &objp->nfs_cb_argop4_u.opcbrecall)) return false; break; case NFS4_OP_CB_LAYOUTRECALL: if (!xdr_CB_LAYOUTRECALL4args( xdrs, &objp->nfs_cb_argop4_u.opcblayoutrecall)) return false; break; case NFS4_OP_CB_NOTIFY: if (!xdr_CB_NOTIFY4args(xdrs, &objp->nfs_cb_argop4_u.opcbnotify)) return false; break; case NFS4_OP_CB_PUSH_DELEG: if (!xdr_CB_PUSH_DELEG4args( xdrs, &objp->nfs_cb_argop4_u.opcbpush_deleg)) return false; break; case NFS4_OP_CB_RECALL_ANY: if (!xdr_CB_RECALL_ANY4args( xdrs, &objp->nfs_cb_argop4_u.opcbrecall_any)) return false; break; case NFS4_OP_CB_RECALLABLE_OBJ_AVAIL: if (!xdr_CB_RECALLABLE_OBJ_AVAIL4args( xdrs, &objp->nfs_cb_argop4_u.opcbrecallable_obj_avail)) return false; break; case NFS4_OP_CB_RECALL_SLOT: if (!xdr_CB_RECALL_SLOT4args( xdrs, &objp->nfs_cb_argop4_u.opcbrecall_slot)) return false; break; case NFS4_OP_CB_SEQUENCE: if (!xdr_CB_SEQUENCE4args(xdrs, &objp->nfs_cb_argop4_u.opcbsequence)) return false; break; case NFS4_OP_CB_WANTS_CANCELLED: if (!xdr_CB_WANTS_CANCELLED4args( xdrs, &objp->nfs_cb_argop4_u.opcbwants_cancelled)) return false; break; case NFS4_OP_CB_NOTIFY_LOCK: if (!xdr_CB_NOTIFY_LOCK4args( xdrs, &objp->nfs_cb_argop4_u.opcbnotify_lock)) return false; break; case NFS4_OP_CB_NOTIFY_DEVICEID: if (!xdr_CB_NOTIFY_DEVICEID4args( xdrs, &objp->nfs_cb_argop4_u.opcbnotify_deviceid)) return false; break; case NFS4_OP_CB_ILLEGAL: break; default: return false; } return true; } static inline bool xdr_nfs_cb_resop4(XDR *xdrs, nfs_cb_resop4 *objp) { if (!inline_xdr_u_int(xdrs, &objp->resop)) return false; switch (objp->resop) { case NFS4_OP_CB_GETATTR: if (!xdr_CB_GETATTR4res(xdrs, &objp->nfs_cb_resop4_u.opcbgetattr)) return false; break; case NFS4_OP_CB_RECALL: if (!xdr_CB_RECALL4res(xdrs, &objp->nfs_cb_resop4_u.opcbrecall)) return false; break; case NFS4_OP_CB_LAYOUTRECALL: if (!xdr_CB_LAYOUTRECALL4res( xdrs, &objp->nfs_cb_resop4_u.opcblayoutrecall)) return false; break; case NFS4_OP_CB_NOTIFY: if (!xdr_CB_NOTIFY4res(xdrs, &objp->nfs_cb_resop4_u.opcbnotify)) return false; break; case NFS4_OP_CB_PUSH_DELEG: if (!xdr_CB_PUSH_DELEG4res( xdrs, &objp->nfs_cb_resop4_u.opcbpush_deleg)) return false; break; case NFS4_OP_CB_RECALL_ANY: if (!xdr_CB_RECALL_ANY4res( xdrs, &objp->nfs_cb_resop4_u.opcbrecall_any)) return false; break; case NFS4_OP_CB_RECALLABLE_OBJ_AVAIL: if (!xdr_CB_RECALLABLE_OBJ_AVAIL4res( xdrs, &objp->nfs_cb_resop4_u.opcbrecallable_obj_avail)) return false; break; case NFS4_OP_CB_RECALL_SLOT: if (!xdr_CB_RECALL_SLOT4res( xdrs, &objp->nfs_cb_resop4_u.opcbrecall_slot)) return false; break; case NFS4_OP_CB_SEQUENCE: if (!xdr_CB_SEQUENCE4res(xdrs, &objp->nfs_cb_resop4_u.opcbsequence)) return false; break; case NFS4_OP_CB_WANTS_CANCELLED: if (!xdr_CB_WANTS_CANCELLED4res( xdrs, &objp->nfs_cb_resop4_u.opcbwants_cancelled)) return false; break; case NFS4_OP_CB_NOTIFY_LOCK: if (!xdr_CB_NOTIFY_LOCK4res( xdrs, &objp->nfs_cb_resop4_u.opcbnotify_lock)) return false; break; case NFS4_OP_CB_NOTIFY_DEVICEID: if (!xdr_CB_NOTIFY_DEVICEID4res( xdrs, &objp->nfs_cb_resop4_u.opcbnotify_deviceid)) return false; break; case NFS4_OP_CB_ILLEGAL: if (!xdr_CB_ILLEGAL4res(xdrs, &objp->nfs_cb_resop4_u.opcbillegal)) return false; break; default: return false; } return true; } static inline bool xdr_CB_COMPOUND4args(XDR *xdrs, CB_COMPOUND4args *objp) { if (!xdr_utf8str_cs(xdrs, &objp->tag)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->minorversion)) return false; if (!inline_xdr_u_int32_t(xdrs, &objp->callback_ident)) return false; if (!xdr_array(xdrs, (char **)&objp->argarray.argarray_val, &objp->argarray.argarray_len, XDR_ARRAY_MAXLEN, sizeof(nfs_cb_argop4), (xdrproc_t)xdr_nfs_cb_argop4)) return false; return true; } static inline bool xdr_CB_COMPOUND4res(XDR *xdrs, CB_COMPOUND4res *objp) { if (!xdr_nfsstat4(xdrs, &objp->status)) return false; if (!xdr_utf8str_cs(xdrs, &objp->tag)) return false; if (!xdr_array(xdrs, (char **)&objp->resarray.resarray_val, &objp->resarray.resarray_len, XDR_ARRAY_MAXLEN, sizeof(nfs_cb_resop4), (xdrproc_t)xdr_nfs_cb_resop4)) return false; return true; } #ifdef __cplusplus } #endif #endif /* !_NFSV41_H_RPCGEN */ nfs-ganesha-6.5/src/include/nlm4.h000066400000000000000000000250401473756622300170330ustar00rootroot00000000000000// SPDX-License-Identifier: unknown license... /* * Please do not edit this file. * It was generated using rpcgen. */ #ifndef _NLM4_H_RPCGEN #define _NLM4_H_RPCGEN #ifdef __cplusplus extern "C" { #endif #define LM_MAXSTRLEN 1024 #define LM_MAXNAMELEN 1025 #define MAXNETOBJ_SZ 1024 #define SM_MAXSTRLEN 1024 #define SM_PRIV_SZ 16 typedef int int32_t; typedef u_int uint32_t; typedef quad_t int64_t; typedef u_quad_t uint64_t; enum nlm4_stats { NLM4_GRANTED = 0, NLM4_DENIED = 1, NLM4_DENIED_NOLOCKS = 2, NLM4_BLOCKED = 3, NLM4_DENIED_GRACE_PERIOD = 4, NLM4_DEADLCK = 5, NLM4_ROFS = 6, NLM4_STALE_FH = 7, NLM4_FBIG = 8, NLM4_FAILED = 9, }; typedef enum nlm4_stats nlm4_stats; struct nlm4_stat { nlm4_stats stat; }; typedef struct nlm4_stat nlm4_stat; struct nlm4_res { netobj cookie; nlm4_stat stat; }; typedef struct nlm4_res nlm4_res; struct nlm4_holder { bool_t exclusive; int32_t svid; netobj oh; uint64_t l_offset; uint64_t l_len; }; typedef struct nlm4_holder nlm4_holder; struct nlm4_testrply { nlm4_stats stat; union { struct nlm4_holder holder; } nlm4_testrply_u; }; typedef struct nlm4_testrply nlm4_testrply; struct nlm4_testres { netobj cookie; nlm4_testrply test_stat; }; typedef struct nlm4_testres nlm4_testres; struct nlm4_lock { char *caller_name; netobj fh; netobj oh; int32_t svid; uint64_t l_offset; uint64_t l_len; }; typedef struct nlm4_lock nlm4_lock; struct nlm4_lockargs { netobj cookie; bool_t block; bool_t exclusive; struct nlm4_lock alock; bool_t reclaim; int32_t state; }; typedef struct nlm4_lockargs nlm4_lockargs; struct nlm4_cancargs { netobj cookie; bool_t block; bool_t exclusive; struct nlm4_lock alock; }; typedef struct nlm4_cancargs nlm4_cancargs; struct nlm4_testargs { netobj cookie; bool_t exclusive; struct nlm4_lock alock; }; typedef struct nlm4_testargs nlm4_testargs; struct nlm4_unlockargs { netobj cookie; struct nlm4_lock alock; }; typedef struct nlm4_unlockargs nlm4_unlockargs; enum fsh4_mode { fsm_DN = 0, fsm_DR = 1, fsm_DW = 2, fsm_DRW = 3, }; typedef enum fsh4_mode fsh4_mode; enum fsh4_access { fsa_NONE = 0, fsa_R = 1, fsa_W = 2, fsa_RW = 3, }; typedef enum fsh4_access fsh4_access; struct nlm4_share { char *caller_name; netobj fh; netobj oh; fsh4_mode mode; fsh4_access access; }; typedef struct nlm4_share nlm4_share; struct nlm4_shareargs { netobj cookie; nlm4_share share; bool_t reclaim; }; typedef struct nlm4_shareargs nlm4_shareargs; struct nlm4_shareres { netobj cookie; nlm4_stats stat; int32_t sequence; }; typedef struct nlm4_shareres nlm4_shareres; struct nlm4_free_allargs { char *name; uint32_t state; }; typedef struct nlm4_free_allargs nlm4_free_allargs; struct nlm4_sm_notifyargs { char *name; int32_t state; char priv[SM_PRIV_SZ]; }; typedef struct nlm4_sm_notifyargs nlm4_sm_notifyargs; extern void nlm_init(void); #define NLMPROG 100021 #define NLM4_VERS 4 #if defined(__STDC__) || defined(__cplusplus) #define NLMPROC4_NULL 0 extern void *nlmproc4_null_4(void *, CLIENT *); extern void *nlmproc4_null_4_svc(void *, struct svc_req *); #define NLMPROC4_TEST 1 extern nlm4_testres *nlmproc4_test_4(nlm4_testargs *, CLIENT *); extern nlm4_testres *nlmproc4_test_4_svc(nlm4_testargs *, struct svc_req *); #define NLMPROC4_LOCK 2 extern nlm4_res *nlmproc4_lock_4(nlm4_lockargs *, CLIENT *); extern nlm4_res *nlmproc4_lock_4_svc(nlm4_lockargs *, struct svc_req *); #define NLMPROC4_CANCEL 3 extern nlm4_res *nlmproc4_cancel_4(nlm4_cancargs *, CLIENT *); extern nlm4_res *nlmproc4_cancel_4_svc(nlm4_cancargs *, struct svc_req *); #define NLMPROC4_UNLOCK 4 extern nlm4_res *nlmproc4_unlock_4(nlm4_unlockargs *, CLIENT *); extern nlm4_res *nlmproc4_unlock_4_svc(nlm4_unlockargs *, struct svc_req *); #define NLMPROC4_GRANTED 5 extern nlm4_res *nlmproc4_granted_4(nlm4_testargs *, CLIENT *); extern nlm4_res *nlmproc4_granted_4_svc(nlm4_testargs *, struct svc_req *); #define NLMPROC4_TEST_MSG 6 extern void *nlmproc4_test_msg_4(nlm4_testargs *, CLIENT *); extern void *nlmproc4_test_msg_4_svc(nlm4_testargs *, struct svc_req *); #define NLMPROC4_LOCK_MSG 7 extern void *nlmproc4_lock_msg_4(nlm4_lockargs *, CLIENT *); extern void *nlmproc4_lock_msg_4_svc(nlm4_lockargs *, struct svc_req *); #define NLMPROC4_CANCEL_MSG 8 extern void *nlmproc4_cancel_msg_4(nlm4_cancargs *, CLIENT *); extern void *nlmproc4_cancel_msg_4_svc(nlm4_cancargs *, struct svc_req *); #define NLMPROC4_UNLOCK_MSG 9 extern void *nlmproc4_unlock_msg_4(nlm4_unlockargs *, CLIENT *); extern void *nlmproc4_unlock_msg_4_svc(nlm4_unlockargs *, struct svc_req *); #define NLMPROC4_GRANTED_MSG 10 extern void *nlmproc4_granted_msg_4(nlm4_testargs *, CLIENT *); extern void *nlmproc4_granted_msg_4_svc(nlm4_testargs *, struct svc_req *); #define NLMPROC4_TEST_RES 11 extern void *nlmproc4_test_res_4(nlm4_testres *, CLIENT *); extern void *nlmproc4_test_res_4_svc(nlm4_testres *, struct svc_req *); #define NLMPROC4_LOCK_RES 12 extern void *nlmproc4_lock_res_4(nlm4_res *, CLIENT *); extern void *nlmproc4_lock_res_4_svc(nlm4_res *, struct svc_req *); #define NLMPROC4_CANCEL_RES 13 extern void *nlmproc4_cancel_res_4(nlm4_res *, CLIENT *); extern void *nlmproc4_cancel_res_4_svc(nlm4_res *, struct svc_req *); #define NLMPROC4_UNLOCK_RES 14 extern void *nlmproc4_unlock_res_4(nlm4_res *, CLIENT *); extern void *nlmproc4_unlock_res_4_svc(nlm4_res *, struct svc_req *); #define NLMPROC4_GRANTED_RES 15 extern void *nlmproc4_granted_res_4(nlm4_res *, CLIENT *); extern void *nlmproc4_granted_res_4_svc(nlm4_res *, struct svc_req *); #define NLMPROC4_SM_NOTIFY 16 extern void *nlmproc4_sm_notify_4(nlm4_sm_notifyargs *, CLIENT *); extern void *nlmproc4_sm_notify_4_svc(nlm4_sm_notifyargs *, struct svc_req *); #define NLMPROC4_SHARE 20 extern nlm4_shareres *nlmproc4_share_4(nlm4_shareargs *, CLIENT *); extern nlm4_shareres *nlmproc4_share_4_svc(nlm4_shareargs *, struct svc_req *); #define NLMPROC4_UNSHARE 21 extern nlm4_shareres *nlmproc4_unshare_4(nlm4_shareargs *, CLIENT *); extern nlm4_shareres *nlmproc4_unshare_4_svc(nlm4_shareargs *, struct svc_req *); #define NLMPROC4_NM_LOCK 22 extern nlm4_res *nlmproc4_nm_lock_4(nlm4_lockargs *, CLIENT *); extern nlm4_res *nlmproc4_nm_lock_4_svc(nlm4_lockargs *, struct svc_req *); #define NLMPROC4_FREE_ALL 23 extern void *nlmproc4_free_all_4(nlm4_free_allargs *, CLIENT *); extern void *nlmproc4_free_all_4_svc(nlm4_free_allargs *, struct svc_req *); extern int nlmprog_4_freeresult(SVCXPRT *, xdrproc_t, void *); #else /* K&R C */ #define NLMPROC4_NULL 0 extern void *nlmproc4_null_4(); extern void *nlmproc4_null_4_svc(); #define NLMPROC4_TEST 1 extern nlm4_testres *nlmproc4_test_4(); extern nlm4_testres *nlmproc4_test_4_svc(); #define NLMPROC4_LOCK 2 extern nlm4_res *nlmproc4_lock_4(); extern nlm4_res *nlmproc4_lock_4_svc(); #define NLMPROC4_CANCEL 3 extern nlm4_res *nlmproc4_cancel_4(); extern nlm4_res *nlmproc4_cancel_4_svc(); #define NLMPROC4_UNLOCK 4 extern nlm4_res *nlmproc4_unlock_4(); extern nlm4_res *nlmproc4_unlock_4_svc(); #define NLMPROC4_GRANTED 5 extern nlm4_res *nlmproc4_granted_4(); extern nlm4_res *nlmproc4_granted_4_svc(); #define NLMPROC4_TEST_MSG 6 extern void *nlmproc4_test_msg_4(); extern void *nlmproc4_test_msg_4_svc(); #define NLMPROC4_LOCK_MSG 7 extern void *nlmproc4_lock_msg_4(); extern void *nlmproc4_lock_msg_4_svc(); #define NLMPROC4_CANCEL_MSG 8 extern void *nlmproc4_cancel_msg_4(); extern void *nlmproc4_cancel_msg_4_svc(); #define NLMPROC4_UNLOCK_MSG 9 extern void *nlmproc4_unlock_msg_4(); extern void *nlmproc4_unlock_msg_4_svc(); #define NLMPROC4_GRANTED_MSG 10 extern void *nlmproc4_granted_msg_4(); extern void *nlmproc4_granted_msg_4_svc(); #define NLMPROC4_TEST_RES 11 extern void *nlmproc4_test_res_4(); extern void *nlmproc4_test_res_4_svc(); #define NLMPROC4_LOCK_RES 12 extern void *nlmproc4_lock_res_4(); extern void *nlmproc4_lock_res_4_svc(); #define NLMPROC4_CANCEL_RES 13 extern void *nlmproc4_cancel_res_4(); extern void *nlmproc4_cancel_res_4_svc(); #define NLMPROC4_UNLOCK_RES 14 extern void *nlmproc4_unlock_res_4(); extern void *nlmproc4_unlock_res_4_svc(); #define NLMPROC4_GRANTED_RES 15 extern void *nlmproc4_granted_res_4(); extern void *nlmproc4_granted_res_4_svc(); #define NLMPROC4_SM_NOTIFY 16 extern void *nlmproc4_sm_notify_4(); extern void *nlmproc4_sm_notify_4_svc(); #define NLMPROC4_SHARE 20 extern nlm4_shareres *nlmproc4_share_4(); extern nlm4_shareres *nlmproc4_share_4_svc(); #define NLMPROC4_UNSHARE 21 extern nlm4_shareres *nlmproc4_unshare_4(); extern nlm4_shareres *nlmproc4_unshare_4_svc(); #define NLMPROC4_NM_LOCK 22 extern nlm4_res *nlmproc4_nm_lock_4(); extern nlm4_res *nlmproc4_nm_lock_4_svc(); #define NLMPROC4_FREE_ALL 23 extern void *nlmproc4_free_all_4(); extern void *nlmproc4_free_all_4_svc(); extern int nlmprog_4_freeresult(); #endif /* K&R C */ /* Number of nlm v4 operations. */ #define NLM_V4_NB_OPERATION (NLMPROC4_FREE_ALL + 1) /* the xdr functions */ #if defined(__STDC__) || defined(__cplusplus) extern bool xdr_int64_t(XDR *, int64_t *); extern bool xdr_uint64_t(XDR *, uint64_t *); extern bool xdr_nlm4_stats(XDR *, nlm4_stats *); extern bool xdr_nlm4_stat(XDR *, nlm4_stat *); extern bool xdr_nlm4_res(XDR *, nlm4_res *); extern bool xdr_nlm4_holder(XDR *, nlm4_holder *); extern bool xdr_nlm4_testrply(XDR *, nlm4_testrply *); extern bool xdr_nlm4_testres(XDR *, nlm4_testres *); extern bool xdr_nlm4_lock(XDR *, nlm4_lock *); extern bool xdr_nlm4_lockargs(XDR *, nlm4_lockargs *); extern bool xdr_nlm4_cancargs(XDR *, nlm4_cancargs *); extern bool xdr_nlm4_testargs(XDR *, nlm4_testargs *); extern bool xdr_nlm4_unlockargs(XDR *, nlm4_unlockargs *); extern bool xdr_fsh4_mode(XDR *, fsh4_mode *); extern bool xdr_fsh4_access(XDR *, fsh4_access *); extern bool xdr_nlm4_share(XDR *, nlm4_share *); extern bool xdr_nlm4_shareargs(XDR *, nlm4_shareargs *); extern bool xdr_nlm4_shareres(XDR *, nlm4_shareres *); extern bool xdr_nlm4_free_allargs(XDR *, nlm4_free_allargs *); extern bool xdr_nlm4_sm_notifyargs(XDR *, nlm4_sm_notifyargs *); #else /* K&R C */ extern bool xdr_int64_t(); extern bool xdr_uint64_t(); extern bool xdr_nlm4_stats(); extern bool xdr_nlm4_stat(); extern bool xdr_nlm4_res(); extern bool xdr_nlm4_holder(); extern bool xdr_nlm4_testrply(); extern bool xdr_nlm4_testres(); extern bool xdr_nlm4_lock(); extern bool xdr_nlm4_lockargs(); extern bool xdr_nlm4_cancargs(); extern bool xdr_nlm4_testargs(); extern bool xdr_nlm4_unlockargs(); extern bool xdr_fsh4_mode(); extern bool xdr_fsh4_access(); extern bool xdr_nlm4_share(); extern bool xdr_nlm4_shareargs(); extern bool xdr_nlm4_shareres(); extern bool xdr_nlm4_free_allargs(); extern bool xdr_nlm4_sm_notifyargs(); #endif /* K&R C */ #ifdef __cplusplus } #endif #endif /* !_NLM4_H_RPCGEN */ nfs-ganesha-6.5/src/include/nlm_async.h000066400000000000000000000031051473756622300201420ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright IBM Corporation, 2010 * Contributor: Aneesh Kumar K.v * * -------------------------- * * This program 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 3 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 * 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 * * */ #ifndef NLM_ASYNC_H #define NLM_ASYNC_H #include #include "sal_data.h" extern pthread_mutex_t nlm_async_resp_mutex; extern pthread_cond_t nlm_async_resp_cond; int nlm_async_callback_init(void); int nlm_send_async_res_nlm4(state_nlm_client_t *host, state_async_func_t func, nfs_res_t *pres); int nlm_send_async_res_nlm4test(state_nlm_client_t *host, state_async_func_t func, nfs_res_t *pres); /* Client routine to send the asynchronous response, key is used to wait for * a response */ int nlm_send_async(int proc, state_nlm_client_t *host, void *inarg, void *key); void nlm_signal_async_resp(void *key); #endif /* NLM_ASYNC_H */ nfs-ganesha-6.5/src/include/nlm_util.h000066400000000000000000000062461473756622300200130ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright IBM Corporation, 2010 * Contributor: Aneesh Kumar K.v * * * This program 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 3 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 * 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 * * --------------------------------------- * * */ #ifndef NLM_UTIL_H #define NLM_UTIL_H #include "gsh_list.h" #include "nlm4.h" #include "sal_data.h" extern const char *lock_result_str(int rc); extern void copy_netobj(netobj *dst, netobj *src); extern void netobj_free(netobj *obj); extern void netobj_to_string(netobj *obj, char *buffer, int maxlen); /** * process_nlm_parameters: Process NLM parameters * * Returns -1 for a request that needs processing, otherwise returns an NLM * status * * preq: passed in so interface doesn't need to change when NLM Client * uses IP address * exclusive: TRUE if lock is a write lock * alock: nlm4_lock request structure * plock: cache_lock_desc_t to fill in from alock * ppobj: FSAL obj pointer to fill in * care: TRUE if this caller cares if an owner is found (otherwise * return NLM4_GRANTED * because the caller will have nothing to do) * ppnsm_client NSM Client to fill in, returns a reference to the client * ppnlm_client: NLM Client to fill in, returns a reference to the client * ppowner: NLM Owner to fill in, returns a reference to the owner * block_data: Data required to make a call back to the client to grant a * blocked lock * nsm_state: nsm_state value * state: state_t to fill in */ int nlm_process_parameters(struct svc_req *req, bool exclusive, nlm4_lock *alock, fsal_lock_param_t *plock, struct fsal_obj_handle **ppobj, care_t care, state_nsm_client_t **ppnsm_client, state_nlm_client_t **ppnlm_client, state_owner_t **ppowner, state_block_data_t **block_data, int32_t nsm_state, state_t **state); int nlm_process_share_parms(struct svc_req *req, nlm4_share *share, struct fsal_export *exp_hdl, struct fsal_obj_handle **ppobj, care_t care, state_nsm_client_t **ppnsm_client, state_nlm_client_t **ppnlm_client, state_owner_t **ppowner, state_t **state); void nlm_process_conflict(nlm4_holder *nlm_holder, state_owner_t *holder, fsal_lock_param_t *conflict); nlm4_stats nlm_convert_state_error(state_status_t status); state_status_t nlm_granted_callback(struct fsal_obj_handle *obj, state_lock_entry_t *lock_entry); #endif /* NLM_UTIL_H */ nfs-ganesha-6.5/src/include/nsm.h000066400000000000000000000034301473756622300167550ustar00rootroot00000000000000// SPDX-License-Identifier: unknown license... /* * Please do not edit this file. * It was generated using rpcgen. */ #ifndef _NSM_H_RPCGEN #define _NSM_H_RPCGEN #include "config.h" #include "gsh_rpc.h" #include "sal_data.h" #ifdef __cplusplus extern "C" { #endif #define SM_MAXSTRLEN 1024 #define SM_PROG 100024 #define SM_VERS 1 #define SM_MON 2 #define SM_UNMON 3 #define SM_UNMON_ALL 4 #define SM_NOTIFY 6 enum res { STAT_SUCC = 0, STAT_FAIL = 1, }; typedef enum res res; struct sm_stat_res { res res_stat; int state; }; typedef struct sm_stat_res sm_stat_res; struct sm_stat { int state; }; typedef struct sm_stat sm_stat; struct my_id { char *my_name; int my_prog; int my_vers; int my_proc; }; typedef struct my_id my_id; struct mon_id { char *mon_name; struct my_id my_id; }; typedef struct mon_id mon_id; struct mon { struct mon_id mon_id; char priv[16]; }; typedef struct mon mon; struct notify { char *my_name; int state; }; typedef struct notify notify; extern bool nsm_monitor(state_nsm_client_t *host); extern bool nsm_unmonitor(state_nsm_client_t *host); extern void nsm_unmonitor_all(void); extern int nsm_notify(char *host, int state); /* the xdr functions */ #if defined(__STDC__) || defined(__cplusplus) extern bool xdr_res(XDR *, res *); extern bool xdr_sm_stat_res(XDR *, sm_stat_res *); extern bool xdr_sm_stat(XDR *, sm_stat *); extern bool xdr_my_id(XDR *, my_id *); extern bool xdr_mon_id(XDR *, mon_id *); extern bool xdr_mon(XDR *, mon *); extern bool xdr_notify(XDR *, notify *); #else /* K&R C */ extern bool xdr_res(); extern bool xdr_sm_stat_res(); extern bool xdr_sm_stat(); extern bool xdr_my_id(); extern bool xdr_mon_id(); extern bool xdr_mon(); extern bool xdr_notify(); #endif /* K&R C */ #ifdef __cplusplus } #endif #endif /* !_NSM_H_RPCGEN */ nfs-ganesha-6.5/src/include/os/000077500000000000000000000000001473756622300164305ustar00rootroot00000000000000nfs-ganesha-6.5/src/include/os/acl.h000066400000000000000000000015411473756622300173410ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright: DataDirect Networks, 2022 * Author: Martin Schwenke * * This program 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 3 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 * Lesser General Public License for more details. */ /** * @file os/acl.h * @brief Non-standard POSIX ACL functions */ #ifndef _ACL_OS_H #define _ACL_OS_H #include "config.h" #ifdef LINUX #include #endif #endif /* _ACL_OS_H */ nfs-ganesha-6.5/src/include/os/darwin/000077500000000000000000000000001473756622300177145ustar00rootroot00000000000000nfs-ganesha-6.5/src/include/os/darwin/sys_resource.h000066400000000000000000000020141473756622300226070ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * This program 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 3 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 * 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 * */ #ifndef DARWIN_SYS_RESOURCE_H_ #define DARWIN_SYS_RESOURCE_H_ #include /* A portable wrapper for getrlimit(RLIMIT_NOFILE, rlim). */ int get_open_file_limit(struct rlimit *rlim); #endif /* DARWIN_SYS_RESOURCE_H_ */ nfs-ganesha-6.5/src/include/os/freebsd/000077500000000000000000000000001473756622300200425ustar00rootroot00000000000000nfs-ganesha-6.5/src/include/os/freebsd/extended_types.h000066400000000000000000000020521473756622300232360ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file os/freebsd/extended_types.h * @brief Extended type, FreeBSD version. */ #ifndef _EXTENDED_TYPES_FREEBSD_H #define _EXTENDED_TYPES_FREEBSD_H #include #endif /* _EXTENDED_TYPES_FREEBSD_H */ nfs-ganesha-6.5/src/include/os/freebsd/fsal_handle_syscalls.h000066400000000000000000000051021473756622300243660ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) Panasas, Inc. 2011 * Author(s): Brent Welch Sachin Bhamare * * 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 can be distributed with a BSD license as well, just * ask. * * 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., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ /** * @file include/os/freebsd/fsal_handle_syscalls.h * @brief System calls for the FreeBSD handle calls */ #ifndef HANDLE_FREEBSD_H #define HANDLE_FREEBSD_H #include #include #include "syscalls.h" #ifndef O_PATH #define O_PATH 0 #endif #ifndef O_DIRECTORY #define O_DIRECTORY 0 #endif #ifndef O_NOACCESS #define O_NOACCESS 0 #endif #ifndef AT_EMPTY_PATH #define AT_EMPTY_PATH 0x1000 #endif #define HANDLE_DUMMY 0x20 struct v_fid { u_short fid_len; /* length of data in bytes */ u_short fid_reserved; /* force longword alignment */ char fid_data[MAXFIDSZ]; /* data (variable length) */ }; struct v_fhandle { uint8_t fh_flags; /* Handle flags */ fsid_t fh_fsid; /* Filesystem id of mount point */ struct v_fid fh_fid; /* Filesys specific id */ }; #define v_to_fhandle(hdl) \ ((struct fhandle *)((char *)hdl + offsetof(struct v_fhandle, fh_fsid))) static inline int vfs_stat_by_handle(int mountfd, struct stat *buf) { int ret; /* BSD doesn't (yet) have AT_EMPTY_PATH support, so just use fstat() */ ret = fstat(mountfd, buf); return ret; } static inline int vfs_link_by_handle(vfs_file_handle_t *fh, int srcfd, int destdirfd, const char *dname) { struct fhandle *handle = v_to_fhandle(fh->handle_data); return fhlink(handle, destdirfd, dname); } static inline int vfs_readlink_by_handle(vfs_file_handle_t *fh, int srcfd, const char *sname, char *buf, size_t bufsize) { struct fhandle *handle = v_to_fhandle(fh->handle_data); return fhreadlink(handle, buf, bufsize); } #endif /* HANDLE_FREEBSD_H */ /** @} */ nfs-ganesha-6.5/src/include/os/freebsd/memstream.h000066400000000000000000000024771473756622300222170ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Contributeur: Sachin Bhamare sbhamare@panasas.com * * This program 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 3 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 * 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 * * ------------- */ /** * @file os/freebsd/memstream.h * @brief Set of functions to provide platform dependent memstream APIs */ #ifndef MEMSTREAM_FREEBSD_H #define MEMSTREAM_FREEBSD_H #include #include #include #include #include struct memstream { char **cp; size_t *lenp; size_t offset; }; FILE *open_memstream(char **cp, size_t *lenp); #endif /* MEMSTREAM_FREEBSD_H */ nfs-ganesha-6.5/src/include/os/freebsd/mntent.h000066400000000000000000000036521473756622300215260ustar00rootroot00000000000000/* SPDX-License-Identifier: BSD-2-Clause */ /* * mntent * mntent.h - compatibility header for FreeBSD * * Copyright (c) 2001 David Rufino * 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 THE REGENTS 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 REGENTS 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. */ #ifndef _MNTENT_FREEBSD_H #define _MNTENT_FREEBSD_H #include #define MOUNTED "dummy" #define MNTTYPE_NFS "nfs" struct mntent { char *mnt_fsname; char *mnt_dir; char *mnt_type; char *mnt_opts; int mnt_freq; int mnt_passno; }; #define setmntent(x, y) ((FILE *)0x1) extern struct mntent *getmntent __P((FILE * fp)); char *hasmntopt __P((const struct mntent *mnt, const char *option)); #define endmntent(x) ((void)(int)1) #endif /* _MNTENT_FREEBSD_H */ nfs-ganesha-6.5/src/include/os/freebsd/quota.h000066400000000000000000000035721473756622300213530ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) Panasas, Inc. 2011 * Author(s): Sachin Bhamare * * 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 can be distributed with a BSD license as well, just * ask. * * 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., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ #ifndef QUOTA_FREEBSD_H #define QUOTA_FREEBSD_H #include #define QUOTACTL(cmd, path, id, addr) quotactl(path, (cmd), id, (void *)addr) /* * kludge to account for differently named member variable * (dqb_curspace Vs dqb_curblocks) in struct dqblk on Linux and * FreeBSD platforms */ struct dqblk_os { u_int64_t dqb_bhardlimit; /* absolute limit on disk blks alloc */ u_int64_t dqb_bsoftlimit; /* preferred limit on disk blks */ u_int64_t dqb_curspace; /* current block count */ u_int64_t dqb_ihardlimit; /* maximum # allocated inodes + 1 */ u_int64_t dqb_isoftlimit; /* preferred inode limit */ u_int64_t dqb_curinodes; /* current # allocated inodes */ int64_t dqb_btime; /* time limit for excessive disk use */ int64_t dqb_itime; /* time limit for excessive files */ }; #if __FreeBSD_cc_version >= 800001 #undef dqblk #endif #define dqblk dqblk_os #endif /* QUOTA_FREEBSD_H */ nfs-ganesha-6.5/src/include/os/freebsd/sys_resource.h000066400000000000000000000017471473756622300227510ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * This program 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 3 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 * 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 * */ #ifndef FREEBSD_SYS_RESOURCE_H_ #define FREEBSD_SYS_RESOURCE_H_ #include #define get_open_file_limit(rlim) getrlimit(RLIMIT_NOFILE, (rlim)) #endif /* FREEBSD_SYS_RESOURCE_H_ */ nfs-ganesha-6.5/src/include/os/freebsd/syscalls.h000066400000000000000000000056761473756622300220660ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Sachin Bhamare sbhamare@panasas.com * * This program 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 3 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 * 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 * */ /** * @file syscalls.h * @brief platform dependent syscalls */ #ifndef _SYSCALLS_FREEBSD_H #define _SYSCALLS_FREEBSD_H #include #include #ifndef AT_FDCWD #define AT_FDCWD -100 #define AT_SYMLINK_NOFOLLOW 0x200 /* Do not follow symbolic links */ #define AT_SYMLINK_FOLLOW 0x400 /* Follow symbolic link */ #define AT_REMOVEDIR 0x800 /* Remove directory instead of file */ #endif /* AT_FDCWD */ #if __FreeBSD_cc_version >= 800001 /* getfhat() is not implemented in FreeBSD kernel yet */ int getfhat(int fd, char *path, fhandle_t *fhp, int flag); int fhlink(struct fhandle *fhp, int tofd, const char *to); int fhreadlink(struct fhandle *fhp, char *buf, size_t bufsize); #endif /* __FreeBSD_cc_version */ #ifndef SYS_openat int openat(int dir_fd, const char *file, int oflag, mode_t mode); int fchownat(int dir_fd, const char *file, uid_t owner, gid_t group, int flag); int futimesat(int dir_fd, char *filename, struct timeval *utimes); int fstatat(int dir_fd, const char *file, struct stat *st, int flag); int getfhat(int dir_fd, char *fname, struct fhandle *fhp, int flag); int fhopenat(int dir_fd, const struct fhandle *u_fhp, int flags); int fchmodat(int dir_fd, const char *filename, mode_t mode, int flags); int faccessat(int dir_fd, char *filename, int mode, int flags); int linkat(int fromfd, const char *from, int tofd, const char *to, int flags); int mkdirat(int dir_fd, const char *file, mode_t mode); int mkfifoat(int dir_fd, char *file, mode_t mode); int mknodat(int dir_fd, const char *file, mode_t mode, dev_t dev); int unlinkat(int dir_fd, const char *file, int flag); int readlinkat(int fd, const char *path, char *buf, size_t len); int symlinkat(const char *from, int tofd, const char *to); int renameat(int oldfd, const char *old, int newfd, const char *new); int utimensat(int dir_fd, char *path, struct timespec *times, int flags); int fhlink(struct fhandle *fhp, int tofd, const char *to); int fhreadlink(struct fhandle *fhp, char *buf, size_t bufsize); #endif /* SYS_openat */ #endif /* _SYSCALLS_FREEBSD_H */ nfs-ganesha-6.5/src/include/os/freebsd/xattr.h000066400000000000000000000027731473756622300213660ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Sachin Bhamare sbhamare@panasas.com * * This program 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 3 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 * 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 * * ------------- */ /** * @file os/freebsd/xattr.h * @brief Platform dependent utils for xattr support on FreeBSD * */ #ifndef _XATTR_FREEBSD_H #define _XATTR_FREEBSD_H #include #include #define XATTR_CREATE 0x1 #define XATTR_REPLACE 0x2 extern ssize_t fgetxattr(int fd, const char *name, void *value, size_t size); extern ssize_t fsetxattr(int fd, const char *name, void *value, size_t size, int flags); extern ssize_t flistxattr(int fd, const char *list, size_t size); extern ssize_t fremovexattr(int fd, const char *name); #endif /* _XATTR_FREEBSD_H */ nfs-ganesha-6.5/src/include/os/linux/000077500000000000000000000000001473756622300175675ustar00rootroot00000000000000nfs-ganesha-6.5/src/include/os/linux/acl.h000066400000000000000000000020551473756622300205010ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright: DataDirect Networks, 2022 * Author: Martin Schwenke * * This program 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 3 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 * Lesser General Public License for more details. */ /** * @file os/linux/acl.h * @brief Non-standard POSIX ACL functions */ #ifndef _ACL_LINUX_H #define _ACL_LINUX_H #include "config.h" #include #include #include #ifndef HAVE_ACL_GET_FD_NP acl_t acl_get_fd_np(int fd, acl_type_t type); #endif #ifndef HAVE_ACL_SET_FD_NP int acl_set_fd_np(int fd, acl_t acl, acl_type_t type); #endif #endif /* _ACL_LINUX_H */ nfs-ganesha-6.5/src/include/os/linux/extended_types.h000066400000000000000000000023321473756622300227640ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file os/linux/extended_types.h * @brief Extended type, Linux version. */ #ifndef _EXTENDED_TYPES_LINUX_H #define _EXTENDED_TYPES_LINUX_H #include #include #endif /* _EXTENDED_TYPES_LINUX_H */ nfs-ganesha-6.5/src/include/os/linux/fsal_handle_syscalls.h000066400000000000000000000070441473756622300241220ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) International Business Machines Corp., 2010 * Author(s): Aneesh Kumar K.V * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * @file include/os/linux/fsal_handle_syscalls.h * @brief System calls for the Linux handle calls * */ #ifndef HANDLE_LINUX_H #define HANDLE_LINUX_H #ifndef AT_EMPTY_PATH #define AT_EMPTY_PATH 0x1000 #endif /* * This is the Linux-specific variation for the by-handle or "at" syscalls. * The vfs_file_handle_t is a redefinition of struct file_handle so * the code here just overlays the two structures on top of each other. */ #ifndef MAX_HANDLE_SZ /* syscalls introduced in 2.6.39 and enabled in glibc 2.14 * if we are not building against 2.14, create our own versions * as inlines. Glibc versions are externs to glibc... */ #define MAX_HANDLE_SZ 128 typedef unsigned int __u32; struct file_handle { __u32 handle_bytes; int handle_type; /* file identifier */ unsigned char f_handle[0]; }; #if defined(__aarch64__) #define __NR_name_to_handle_at 264 #define __NR_open_by_handle_at 265 #elif defined(__i386__) #define __NR_name_to_handle_at 341 #define __NR_open_by_handle_at 342 #elif defined(__x86_64__) #define __NR_name_to_handle_at 303 #define __NR_open_by_handle_at 304 #elif defined(__PPC64__) #define __NR_name_to_handle_at 345 #define __NR_open_by_handle_at 346 #endif static inline int name_to_handle_at(int mdirfd, const char *name, struct file_handle *handle, int *mnt_id, int flags) { return syscall(__NR_name_to_handle_at, mdirfd, name, handle, mnt_id, flags); } static inline int open_by_handle_at(int mdirfd, struct file_handle *handle, int flags) { return syscall(__NR_open_by_handle_at, mdirfd, handle, flags); } #endif /* MAX_HANDLE_SZ */ #ifndef O_PATH #define O_PATH 010000000 #endif #ifndef AT_EACCESS #define AT_EACCESS 0x200 #endif #ifndef O_NOACCESS #define O_NOACCESS O_ACCMODE #endif static inline int vfs_stat_by_handle(int mountfd, struct stat *buf) { /* Must use fstatat() even though fstat() seems like it might * work, the Linux version rejects the file descriptor we've * obtained with the O_NOACCESS flag */ return fstatat(mountfd, "", buf, AT_EMPTY_PATH); } static inline int vfs_link_by_handle(vfs_file_handle_t *fh, int srcfd, int destdirfd, const char *dname) { return linkat(srcfd, "", destdirfd, dname, AT_EMPTY_PATH); } static inline int vfs_readlink_by_handle(vfs_file_handle_t *fh, int srcfd, const char *sname, char *buf, size_t bufsize) { return readlinkat(srcfd, sname, buf, bufsize); } /* If not otherwise defined, define OFD locks */ #ifndef F_OFD_GETLK #define F_OFD_GETLK 36 #endif #ifndef F_OFD_SETLK #define F_OFD_SETLK 37 #endif #ifndef F_OFD_SETLKW #define F_OFD_SETLKW 38 #endif #endif /* HANDLE_LINUX_H */ /** @} */ nfs-ganesha-6.5/src/include/os/linux/quota.h000066400000000000000000000022201473756622300210650ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) Panasas, Inc. 2011 * Author(s): Sachin Bhamare * * 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 can be distributed with a BSD license as well, just * ask. * * 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., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ #ifndef QUOTA_LINUX_H #define QUOTA_LINUX_H #include #define QUOTACTL(cmd, path, id, addr) quotactl((cmd), path, id, addr) #endif /* QUOTA_LINUX_H */ nfs-ganesha-6.5/src/include/os/linux/sys_resource.h000066400000000000000000000017411473756622300224700ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * This program 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 3 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 * 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 * */ #ifndef LINUX_SYS_RESOURCE_H_ #define LINUX_SYS_RESOURCE_H_ #include #define get_open_file_limit(rlim) getrlimit(RLIMIT_NOFILE, (rlim)) #endif /* LINUX_SYS_RESOURCE_H_ */ nfs-ganesha-6.5/src/include/os/memstream.h000066400000000000000000000021701473756622300205730ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Contributeur: Sachin Bhamare sbhamare@panasas.com * * This program 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 3 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 * 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 */ /** * @file os/memstream.h * @brief Set of functions to provide platform dependent memstream APIs */ #ifndef _OS_MEMSTREAM_H #define _OS_MEMSTREAM_H #ifdef FREEBSD #include #endif #endif /* _OS_MEMSTREAM_H */ nfs-ganesha-6.5/src/include/os/mntent.h000066400000000000000000000022041473756622300201040ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * contributeur : Sachin Bhamare sbhamare@panasas.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file os/mntent.h * @brief Platform dependent utilities for reading/writing fstab, mtab, etc.. */ #ifndef MNTENT_H #define MNTENT_H #ifdef LINUX #include #elif FREEBSD #include #endif #endif /* MNTENT_H */ nfs-ganesha-6.5/src/include/os/quota.h000066400000000000000000000022101473756622300177250ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) Panasas, Inc. 2011 * Author(s): Sachin Bhamare * * 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 can be distributed with a BSD license as well, just * ask. * * 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., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ #ifndef _QUOTA_OS_H #define _QUOTA_OS_H #ifdef LINUX #include #elif FREEBSD #include #endif #endif /* _QUOTA_OS_H */ nfs-ganesha-6.5/src/include/os/subr.h000066400000000000000000000035261473756622300175620ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * contributeur : Sachin Bhamare sbhamare@panasas.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file subr.h * @author Sachin Bhamare * @brief platform dependent subroutine type definitions */ #ifndef SUBR_OS_H #define SUBR_OS_H #include #include #include #ifndef UTIME_NOW #define UTIME_NOW -1 #define UTIME_OMIT -2 #endif int vfs_utimesat(int fd, const char *path, const struct timespec times[2], int flags); int vfs_utimes(int fd, const struct timespec *times); struct vfs_dirent { uint64_t vd_ino; uint32_t vd_reclen; uint32_t vd_type; off_t vd_offset; char *vd_name; }; int vfs_readents(int fd, char *buf, unsigned int bcount, off_t *basepp); bool to_vfs_dirent(char *buf, int bpos, struct vfs_dirent *vd, off_t base); void setuser(uid_t uid); void setgroup(gid_t gid); int set_threadgroups(size_t size, const gid_t *list); /* Define these aliases for all OSs for consistency with setuser/setgroup */ #define getuser geteuid #define getgroup getegid #endif /* SUBR_OS_H */ nfs-ganesha-6.5/src/include/os/xattr.h000066400000000000000000000022431473756622300177440ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * contributeur : Sachin Bhamare sbhamare@panasas.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file os/xattr.h * @author Sachin Bhamare * @brief Platform dependent utils for xattr support */ #ifndef _XATTR_OS_H #define _XATTR_OS_H #ifdef LINUX #include #elif FREEBSD #include #endif #endif /* _XATTR_OS_H */ nfs-ganesha-6.5/src/include/pnfs_utils.h000066400000000000000000000173061473756622300203550ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * Copyright (C) 2011 Linux Box Corporation * Author: Adam C. Emerson * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file pnfs_utils.h * @brief Common utility functions for pNFS * * pNFS utility functions used all over Ganesha. */ #ifndef PNFS_UTILS_H #define PNFS_UTILS_H #include #include "nfs4.h" #include "fsal_pnfs.h" #include "fsal_api.h" /* The next 3 line are mandatory for proper autotools based management */ #include "config.h" /****************************************************** * Utility functions for ranges ******************************************************/ /** * @brief Test for overlap and compatible io_mode of segments * * @param segment1 [IN] A layout segment * @param segmenta [IN] A layout segment * * @return True if there is one or more byte contained in both * segments and the io_modes are compatible. */ static inline bool pnfs_segments_overlap(const struct pnfs_segment *segment1, const struct pnfs_segment *segmenta) { if (!(segment1->io_mode & segmenta->io_mode)) { return false; } else if ((segment1->length == 0) || (segmenta->length == 0)) { return false; } else if (segment1->offset < segmenta->offset) { if (segment1->length == NFS4_UINT64_MAX) { return true; } else if (segment1->offset + segment1->length < segmenta->offset) { return false; } else { return true; } } else if (segmenta->offset < segment1->offset) { if (segmenta->length == NFS4_UINT64_MAX) { return true; } else if ((segmenta->offset + segmenta->length) < segment1->offset) { return false; } else { return true; } } else { return true; } } /** * @brief Check if one segment contains the other * * This function checks whether segment2 is subsegment (not * necessarily proper) of segment1. * * @param segment1 [IN] The putative supersegment * @param segment2 [IN] The putative subsugment * * @return True if segment2 is completely contained within segment1 */ static inline bool pnfs_segment_contains(const struct pnfs_segment *segment1, const struct pnfs_segment *segment2) { if (!(segment1->io_mode & segment2->io_mode)) { return false; } else if (segment1->length == 0) { return false; } else if (segment1->offset <= segment2->offset) { if (segment1->length == NFS4_UINT64_MAX) { return true; } else if (segment2->length == NFS4_UINT64_MAX) { return false; } else if ((segment2->offset + segment2->length) <= (segment1->offset + segment1->length)) { return true; } else { return false; } } else { return false; } } /** * @brief Subtract the second segment from the first * * In the case that the subtrahend completely contains the minuend, * the return value has a length and offset of 0. If the IO modes of * the two arguments are incompatible, the minuend is returned * unchanged. If the subtrahend is a proper subset of the minuend, * the minuend is returned unchanged. This is incorrect, but to * handle splitting a segment, we need to add split and merge support * to FSALs. * * @param minuend [IN] The putative supersegment * @param subtrahend [IN] The putative subsugment * * @return A layout segment that is the difference between the two * segments. */ static inline struct pnfs_segment pnfs_segment_difference(const struct pnfs_segment *minuend, const struct pnfs_segment *subtrahend) { if (!(minuend->io_mode & subtrahend->io_mode)) { return *minuend; } else if (pnfs_segment_contains(subtrahend, minuend)) { struct pnfs_segment null = { .io_mode = minuend->io_mode, .offset = 0, .length = 0 }; return null; } else if (!(pnfs_segments_overlap(minuend, subtrahend))) { return *minuend; } else if (minuend->offset <= subtrahend->offset) { if (minuend->length == NFS4_UINT64_MAX) { if (subtrahend->length == NFS4_UINT64_MAX) { struct pnfs_segment difference = { .io_mode = minuend->io_mode, .offset = minuend->offset, .length = (subtrahend->offset - minuend->offset) }; return difference; } else { return *minuend; } } else { if ((minuend->length + minuend->offset) > (subtrahend->length + subtrahend->offset)) { return *minuend; } else { struct pnfs_segment difference = { .io_mode = minuend->io_mode, .offset = minuend->offset, .length = (minuend->offset - subtrahend->offset) }; return difference; } } } else { struct pnfs_segment difference = { .io_mode = minuend->io_mode, .offset = subtrahend->offset + subtrahend->length - 1, .length = minuend->length }; return difference; } } /****************************************************** * Common functions for every pNFS implementation ******************************************************/ /* ** in FSAL/common_pnfs.c */ bool xdr_fsal_deviceid(XDR *xdrs, struct pnfs_deviceid *deviceid); nfsstat4 FSAL_encode_ipv4_netaddr(XDR *xdrs, uint16_t proto, uint32_t addr, uint16_t port); /** * This type exists soleley so arrays of hosts can be passed to * FSAL_encode_multipath_list. */ typedef struct fsal_multipath_member { uint16_t proto; /*< Protocool number */ uint32_t addr; /*< IPv4 address */ uint16_t port; /*< Port */ } fsal_multipath_member_t; nfsstat4 FSAL_encode_file_layout(XDR *xdrs, const struct pnfs_deviceid *deviceid, nfl_util4 util, const uint32_t first_idx, const offset4 ptrn_ofst, const uint16_t *ds_ids, const uint32_t num_fhs, const struct gsh_buffdesc *fhs, const bool_t same_fh); nfsstat4 FSAL_encode_v4_multipath(XDR *xdrs, const uint32_t num_hosts, const fsal_multipath_member_t *hosts); nfsstat4 FSAL_encode_flex_file_layout( XDR *xdrs, const struct pnfs_deviceid *deviceid, const uint64_t ffl_stripe_unit, const uint32_t ffl_mirrors_len, u_int stripes, const uint32_t num_fhs, const uint16_t *ds_ids, const struct gsh_buffdesc *fhs, const uint32_t ffds_efficiency, const fattr4_owner ffds_user, const fattr4_owner_group ffds_group, const ff_flags4 ffl_flags, const uint32_t ffl_stats_collect_hint); nfsstat4 FSAL_encode_ff_device_versions4( XDR *xdrs, const u_int multipath_list4_len, const u_int ffda_versions_len, const fsal_multipath_member_t *hosts, const uint32_t ffdv_version, const uint32_t ffdv_minorversion, const uint32_t ffdv_rsize, const uint32_t ffdv_wsize, const bool_t ffdv_tightly_coupled); nfsstat4 posix2nfs4_error(int posix_errorcode); /* ** in support/ds.c */ struct fsal_pnfs_ds *pnfs_ds_alloc(void); void pnfs_ds_free(struct fsal_pnfs_ds *pds); bool pnfs_ds_insert(struct fsal_pnfs_ds *pds); struct fsal_pnfs_ds *pnfs_ds_get(uint16_t id_servers); static inline void pnfs_ds_get_ref(struct fsal_pnfs_ds *pds) { (void)atomic_inc_int32_t(&pds->ds_refcount); } void pnfs_ds_put(struct fsal_pnfs_ds *pds); void pnfs_ds_remove(uint16_t id_servers); int ReadDataServers(config_file_t in_config, struct config_error_type *err_type); void remove_all_dss(void); void server_pkginit(void); #endif /* PNFS_UTILS_H */ nfs-ganesha-6.5/src/include/posix_acls.h000066400000000000000000000067251473756622300203360ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * posix_acls.c * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Red Hat Inc., 2015 * Author: Niels de Vos * Jiffin Tony Thottan * * This program 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 3 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 * Lesser General Public License for more details. * * Conversion routines for fsal_acl <-> POSIX ACl * * Routines based on the description from an Internet Draft that has also been * used for the implementation of the conversion in the Linux kernel * NFS-server. * * Title: Mapping Between NFSv4 and Posix Draft ACLs * Authors: Marius Aamodt Eriksen & J. Bruce Fields * URL: http://tools.ietf.org/html/draft-ietf-nfsv4-acl-mapping-05 */ #ifndef _POSIX_ACLS_H #define _POSIX_ACLS_H #include #include "nfs4_acls.h" #include #include "fsal_types.h" /* inheritance flags checks */ #define IS_FSAL_ACE_HAS_INHERITANCE_FLAGS(ACE) \ (IS_FSAL_ACE_FILE_INHERIT(ACE) | IS_FSAL_ACE_DIR_INHERIT(ACE) | \ IS_FSAL_ACE_NO_PROPAGATE(ACE) | IS_FSAL_ACE_INHERIT_ONLY(ACE)) #define IS_FSAL_ACE_APPLICABLE_FOR_BOTH_ACL(ACE) \ ((IS_FSAL_ACE_FILE_INHERIT(ACE) | IS_FSAL_ACE_DIR_INHERIT(ACE)) & \ !IS_FSAL_ACE_APPLICABLE_ONLY_FOR_INHERITED_ACL(ACE)) #define IS_FSAL_ACE_APPLICABLE_ONLY_FOR_INHERITED_ACL(ACE) \ ((IS_FSAL_ACE_FILE_INHERIT(ACE) | IS_FSAL_ACE_DIR_INHERIT(ACE)) & \ IS_FSAL_ACE_INHERIT_ONLY(ACE)) /* permission set for ACE's */ #define FSAL_ACE_PERM_SET_DEFAULT \ (FSAL_ACE_PERM_READ_ACL | FSAL_ACE_PERM_READ_ATTR | \ FSAL_ACE_PERM_SYNCHRONIZE) #define FSAL_ACE_PERM_SET_DEFAULT_WRITE \ (FSAL_ACE_PERM_WRITE_DATA | FSAL_ACE_PERM_APPEND_DATA) #define FSAL_ACE_PERM_SET_OWNER_WRITE \ (FSAL_ACE_PERM_WRITE_ACL | FSAL_ACE_PERM_WRITE_ATTR) #define ACL_EA_VERSION 0x0002 #define ACL_EA_ACCESS "system.posix_acl_access" #define ACL_EA_DEFAULT "system.posix_acl_default" #define ACL_FOR_V4 true #define ACL_FOR_V3 false struct acl_ea_entry { u_int16_t e_tag; u_int16_t e_perm; u_int32_t e_id; }; struct acl_ea_header { u_int32_t a_version; struct acl_ea_entry a_entries[0]; }; int posix_acl_2_fsal_acl(acl_t p_posixacl, bool is_dir, bool is_inherit, bool for_v4, fsal_ace_t **p_falacl); acl_t fsal_acl_2_posix_acl(fsal_acl_t *p_fsalacl, acl_type_t type); acl_entry_t find_entry(acl_t acl, acl_tag_t tag, unsigned int id); acl_entry_t get_entry(acl_t acl, acl_tag_t tag, unsigned int id); int ace_count(acl_t acl); size_t posix_acl_xattr_size(int count); int posix_acl_entries_count(size_t size); acl_t xattr_2_posix_acl(const struct acl_ea_header *ea_header, size_t size); int posix_acl_2_xattr(acl_t acl, void *buf, size_t size); static inline uid_t posix_acl_get_uid(acl_entry_t entry_d) { void *q = acl_get_qualifier(entry_d); uid_t u = *(uid_t *)q; acl_free(q); return u; } static inline gid_t posix_acl_get_gid(acl_entry_t entry_d) { void *q = acl_get_qualifier(entry_d); gid_t g = *(gid_t *)q; acl_free(q); return g; } #endif /* _POSIX_ACLS_H */ nfs-ganesha-6.5/src/include/rados_grace.h000066400000000000000000000067531473756622300204440ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2018 Red Hat, Inc. and/or its affiliates. * Author: Jeff Layton * * This program 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 3 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 * 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 */ #ifndef _RADOS_GRACE_H #define _RADOS_GRACE_H #include #include #define DEFAULT_RADOS_GRACE_POOL "nfs-ganesha" #define DEFAULT_RADOS_GRACE_OID "grace" int rados_grace_create(rados_ioctx_t io_ctx, const char *oid); int rados_grace_dump(rados_ioctx_t io_ctx, const char *oid, FILE *stream); int rados_grace_epochs(rados_ioctx_t io_ctx, const char *oid, uint64_t *cur, uint64_t *rec); int rados_grace_enforcing_toggle(rados_ioctx_t io_ctx, const char *oid, int nodes, const char *const *nodeids, uint64_t *pcur, uint64_t *prec, bool start); int rados_grace_enforcing_check(rados_ioctx_t io_ctx, const char *oid, const char *nodeid); int rados_grace_join_bulk(rados_ioctx_t io_ctx, const char *oid, int nodes, const char *const *nodeids, uint64_t *pcur, uint64_t *prec, bool start); int rados_grace_lift_bulk(rados_ioctx_t io_ctx, const char *oid, int nodes, const char *const *nodeids, uint64_t *pcur, uint64_t *prec, bool remove); int rados_grace_add(rados_ioctx_t io_ctx, const char *oid, int nodes, const char *const *nodeids); int rados_grace_member_bulk(rados_ioctx_t io_ctx, const char *oid, int nodes, const char *const *nodeids); static inline int rados_grace_enforcing_on(rados_ioctx_t io_ctx, const char *oid, const char *nodeid, uint64_t *pcur, uint64_t *prec) { const char *nodeids[1]; nodeids[0] = nodeid; return rados_grace_enforcing_toggle(io_ctx, oid, 1, nodeids, pcur, prec, true); } static inline int rados_grace_enforcing_off(rados_ioctx_t io_ctx, const char *oid, const char *nodeid, uint64_t *pcur, uint64_t *prec) { const char *nodeids[1]; nodeids[0] = nodeid; return rados_grace_enforcing_toggle(io_ctx, oid, 1, nodeids, pcur, prec, false); } static inline int rados_grace_join(rados_ioctx_t io_ctx, const char *oid, const char *nodeid, uint64_t *pcur, uint64_t *prec, bool start) { const char *nodeids[1]; nodeids[0] = nodeid; return rados_grace_join_bulk(io_ctx, oid, 1, nodeids, pcur, prec, start); } static inline int rados_grace_lift(rados_ioctx_t io_ctx, const char *oid, const char *nodeid, uint64_t *pcur, uint64_t *prec) { const char *nodeids[1]; nodeids[0] = nodeid; return rados_grace_lift_bulk(io_ctx, oid, 1, nodeids, pcur, prec, false); } static inline int rados_grace_member(rados_ioctx_t io_ctx, const char *oid, const char *nodeid) { const char *nodeids[1]; nodeids[0] = nodeid; return rados_grace_member_bulk(io_ctx, oid, 1, nodeids); } #endif /* _RADOS_GRACE_H */ nfs-ganesha-6.5/src/include/rbt_node.h000066400000000000000000000120451473756622300177560ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later WITH special exception */ /** * @addtogroup hashtable * @{ */ /** * @file rbt_node.h * @brief Red-black tree node structure */ /* * Implementation of RedBlack trees * Definitions and structures */ /* * This implementation of RedBlack trees was copied from * the STL library and adapted to a C environment. */ /* * RB tree implementation -*- C++ -*- * Copyright (C) 2001 Free Software Foundation, Inc. * * This file is part of the GNU ISO C++ Library. This library 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, 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 General Public License for more details. * You should have received a copy of the GNU General Public License along * with this library; see the file COPYING. If not, write to the Free * Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. * As a special exception, you may use this file as part of a free software * library without restriction. Specifically, if other files instantiate * templates or use macros or inline functions from this file, or you compile * this file and link it with other files to produce an executable, this * file does not by itself cause the resulting executable to be covered by * the GNU General Public License. This exception does not however * invalidate any other reasons why the executable file might be covered by * the GNU General Public License. */ /* * * Copyright (c) 1996,1997 * Silicon Graphics Computer Systems, Inc. * * Permission to use, copy, modify, distribute and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appear in all copies and * that both that copyright notice and this permission notice appear * in supporting documentation. Silicon Graphics makes no * representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied warranty. * * * Copyright (c) 1994 * Hewlett-Packard Company * * Permission to use, copy, modify, distribute and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appear in all copies and * that both that copyright notice and this permission notice appear * in supporting documentation. Hewlett-Packard Company makes no * representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied warranty. * * */ /* NOTE: This is an internal header file, included by other STL headers. * You should not attempt to use it directly. */ /* Red-black tree class, designed for use in implementing STL associative containers (set, multiset, map, and multimap). The insertion and deletion algorithms are based on those in Cormen, Leiserson, and Rivest, Introduction to Algorithms (MIT Press, 1990), except that (1) the header cell is maintained with links not only to the root but also to the leftmost node of the tree, to enable constant time begin(), and to the rightmost node of the tree, to enable linear time performance when used with the generic set algorithms (set_union, etc.); (2) when a node being deleted has two children its successor node is relinked into its place, rather than copied, so that the only iterators invalidated are those referring to the deleted node. */ #ifndef RBT_NODE_H #define RBT_NODE_H #include /* * allocation parameters */ #define RBT_NUM 16 /* allocate nodes RBT_NUM at a time */ /* * rbt_head is the head structure. * There is one rbt_head structure per tree. */ struct rbt_head { struct rbt_node *root; /* root node */ struct rbt_node *leftmost; /* leftmost node */ struct rbt_node *rightmost; /* rightmost nodei */ unsigned int rbt_num_node; /* number of nodes */ }; /* * rbt_node is the node structure. * There is one rbt_tree structure per node. * * Usually, rbt_node is part of a bigger structure. * In this case, rbt_opaq point to this bigger structure. * * The field anchor is never NULL. It points to a pointer * containing the rbt_node address. This pointer is either * - the field left in the parent node * - the field next in the parent node * - the field root in the rbt_head structure (for the root node) */ typedef struct rbt_node { unsigned int rbt_flags; struct rbt_node **anchor; /* anchor for this node */ struct rbt_node *parent; /* parent node or NULL for root */ struct rbt_node *left; /* left node */ struct rbt_node *next; /* "right" node */ uint64_t rbt_value; /* used for order */ void *rbt_opaq; /* pointer for external object */ } rbt_node_t; /* * flags for rbt_node */ #define RBT_RED 01 /* red node */ #endif /* RBT_NODE_H */ /** @} */ nfs-ganesha-6.5/src/include/rbt_tree.h000066400000000000000000000747511473756622300200040ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0-or-later WITH special exception */ /** * @addtogroup hashtable * @{ */ /** * @file rbt_tree.h * @brief Red-Black Tree */ /* * Implementation of RedBlack trees * Macros and algorithms */ /* * This implementation of RedBlack trees was copied from * the STL library and adapted to a C environment. */ /* * RB tree implementation -*- C++ -*- * Copyright (C) 2001 Free Software Foundation, Inc. * * This file is part of the GNU ISO C++ Library. This library 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, 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 General Public License for more details. * You should have received a copy of the GNU General Public License along * with this library; see the file COPYING. If not, write to the Free * Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. * As a special exception, you may use this file as part of a free software * library without restriction. Specifically, if other files instantiate * templates or use macros or inline functions from this file, or you compile * this file and link it with other files to produce an executable, this * file does not by itself cause the resulting executable to be covered by * the GNU General Public License. This exception does not however * invalidate any other reasons why the executable file might be covered by * the GNU General Public License. */ /* * * Copyright (c) 1996,1997 * Silicon Graphics Computer Systems, Inc. * * Permission to use, copy, modify, distribute and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appear in all copies and * that both that copyright notice and this permission notice appear * in supporting documentation. Silicon Graphics makes no * representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied warranty. * * * Copyright (c) 1994 * Hewlett-Packard Company * * Permission to use, copy, modify, distribute and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appear in all copies and * that both that copyright notice and this permission notice appear * in supporting documentation. Hewlett-Packard Company makes no * representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied warranty. * * */ /* NOTE: This is an internal header file, included by other STL headers. * You should not attempt to use it directly. */ /* Red-black tree class, designed for use in implementing STL associative containers (set, multiset, map, and multimap). The insertion and deletion algorithms are based on those in Cormen, Leiserson, and Rivest, Introduction to Algorithms (MIT Press, 1990), except that (1) the header cell is maintained with links not only to the root but also to the leftmost node of the tree, to enable constant time begin(), and to the rightmost node of the tree, to enable linear time performance when used with the generic set algorithms (set_union, etc.); (2) when a node being deleted has two children its successor node is relinked into its place, rather than copied, so that the only iterators invalidated are those referring to the deleted node. */ #ifndef RBT_TREE_H #define RBT_TREE_H /* * For RBT_HEAD_INIT, RBT_COUNT, RBT_RIGHTMOST and RBT_LEFTMOST : * __header is the header */ #define RBT_HEAD_INIT(__header) \ ((__header)->root = 0, (__header)->leftmost = 0, \ (__header)->rightmost = 0, (__header)->rbt_num_node = 0) #define RBT_COUNT(__header) ((__header)->rbt_num_node) #define RBT_RIGHTMOST(__header) ((__header)->rightmost) #define RBT_LEFTMOST(__header) ((__header)->leftmost) /* * For RBT_VALUE : * __node is any node */ #define RBT_VALUE(__node) ((__node)->rbt_value) /* * For RBT_OPAQ : * __node is any node */ #define RBT_OPAQ(__node) ((__node)->rbt_opaq) /* * For RBT_INCREMENT and RBT_DECREMENT : * __node is the starting node * __x is a temporary variable * __node is modified to point to the next/previous node */ #define RBT_INCREMENT(__node) \ ({ \ if ((__node)->next) { \ __node = (__node)->next; \ while ((__node)->left) \ (__node) = (__node)->left; \ } else { \ struct rbt_node *__x; \ do { \ __x = (__node); \ } while ((((__node) = (__node)->parent)) && \ ((__node)->next == __x)); \ } \ }) #define RBT_DECREMENT(__node) \ ({ \ if ((__node)->left) { \ __node = (__node)->left; \ while ((__node)->next) \ (__node) = (__node)->next; \ } else { \ struct rbt_node *__x; \ do { \ __x = (__node); \ } while ((((__node) = (__node)->parent)) && \ ((__node)->left == __x)); \ } \ }) /* * For RBT_LOOP and RBT_LOOP_REVERSE : * __header is the header * __it is the iterator (type rbt_node *) * * These macros must be used with, respectively, * RBT_INCREMENT and RBT_DECREMENT. */ #define RBT_LOOP(__header, __it) for ((__it) = (__header)->leftmost; (__it);) #define RBT_LOOP_REVERSE(__header, __it) \ for ((__it) = (__header)->rightmost; (__it);) /* * For RBT_ROTATE_LEFT and RBT_ROTATE_RIGHT : * __xx is pointer to the pivotal node * __yy is a temporary variable * the pivotal node is not modified except its links in the tree * For RBT_ROTATE_LEFT, (__xx)->next must not be zero. * For RBT_ROTATE_RIGHT, (__xx)->left must not be zero. */ #define RBT_ROTATE_LEFT(__xx) \ ({ \ struct rbt_node *__yy; \ __yy = (__xx)->next; \ (__xx)->next = __yy->left; \ if (((__xx)->next)) { \ __yy->left->parent = (__xx); \ __yy->left->anchor = &(__xx)->next; \ } \ __yy->parent = (__xx)->parent; \ __yy->left = (__xx); \ __yy->anchor = (__xx)->anchor; \ (__xx)->parent = __yy; \ (__xx)->anchor = &__yy->left; \ *__yy->anchor = __yy; \ }) #define RBT_ROTATE_RIGHT(__xx) \ ({ \ struct rbt_node *__yy; \ __yy = (__xx)->left; \ (__xx)->left = __yy->next; \ if ((__xx)->left) { \ __yy->next->parent = (__xx); \ __yy->next->anchor = &(__xx)->left; \ } \ __yy->parent = (__xx)->parent; \ __yy->next = (__xx); \ __yy->anchor = (__xx)->anchor; \ (__xx)->parent = __yy; \ (__xx)->anchor = &__yy->next; \ *__yy->anchor = __yy; \ }) /* * For RBT_INSERT : * __node is the new node to be inserted * __par is the node which will be the first parent of __node * __node and __par are not modified * *__node and *__par are modified * __header is the header node * __x and __y are temporary variables * * __par must have been returned by RBT_FIND (successful or not). * If RBT_FIND was not successful, __par cannot have two children : * - If __node->rbt_value > __par->rbt_value, then __par->next is NULL. * __node will be installed at __par->next. * - If __node->rbt_value < __par->rbt_value, then __par->left is NULL. * __node will be installed at __par->next. * If RBT_FIND was successful : * - If __par has two children, search the previous node and replace * __par by this previous node. Then insert __node at __par->next. * - If __par->left is free, install __node at __par->left. * - Otherwise, install __node at __par->next. * * If this insertion unbalances the tree, __node may end in a different * position. */ #define RBT_INSERT(__header, __node, __par) \ ({ \ struct rbt_node *__x, *__y; \ (__header)->rbt_num_node++; \ __y = (__par); \ if (__y == 0) { \ (__node)->anchor = &(__header)->root; \ (__header)->root = (__node); \ (__header)->rightmost = (__node); \ (__header)->leftmost = (__node); \ } else if (((__node)->rbt_value == __y->rbt_value) && \ __y->next && __y->left) { \ __y = __y->left; \ while (__y->next) \ __y = __y->next; \ __y->next = (__node); \ (__node)->anchor = &__y->next; \ } else if (((__node)->rbt_value > __y->rbt_value) || \ (((__node)->rbt_value == __y->rbt_value) && \ __y->left)) { \ __y->next = (__node); \ (__node)->anchor = &__y->next; \ if (__y == (__header)->rightmost) { \ (__header)->rightmost = (__node); \ } \ } else { \ __y->left = (__node); \ (__node)->anchor = &__y->left; \ if (__y == (__header)->leftmost) { \ (__header)->leftmost = (__node); \ } \ } \ (__node)->rbt_flags = 0; \ (__node)->parent = __y; \ (__node)->left = 0; \ (__node)->next = 0; \ __x = (__node); \ while (__x->parent) { \ __x->rbt_flags |= RBT_RED; \ if ((__x->parent->rbt_flags & RBT_RED) == 0) \ break; \ if (__x->parent == __x->parent->parent->left) { \ __y = __x->parent->parent->next; \ if ((__y == 0) || \ ((__y->rbt_flags & RBT_RED) == 0)) { \ if (__x == __x->parent->next) { \ __x = __x->parent; \ RBT_ROTATE_LEFT(__x); \ } \ __x->parent->rbt_flags &= ~RBT_RED; \ __x = __x->parent->parent; \ __x->rbt_flags |= RBT_RED; \ RBT_ROTATE_RIGHT(__x); \ break; \ } \ } else { \ __y = __x->parent->parent->left; \ if ((__y == 0) || \ ((__y->rbt_flags & RBT_RED) == 0)) { \ if (__x == __x->parent->left) { \ __x = __x->parent; \ RBT_ROTATE_RIGHT(__x); \ } \ __x->parent->rbt_flags &= ~RBT_RED; \ __x = __x->parent->parent; \ __x->rbt_flags |= RBT_RED; \ RBT_ROTATE_LEFT(__x); \ break; \ } \ } \ __x->parent->rbt_flags &= ~RBT_RED; \ __y->rbt_flags &= ~RBT_RED; \ __x = __x->parent->parent; \ } \ }) /* * For RBT_UNLINK : * __node is the node to unlink * __header is the header node * __x, __z and __y are temporary variables * __node->rbt_flags may be modified * otherwise, __node is not modified */ #define RBT_UNLINK(__header, __node) \ ({ \ struct rbt_node *__x, *__y, *__z; \ (__header)->rbt_num_node--; \ if ((__node)->left && (__node)->next) { \ __y = (__node)->next; \ while (__y->left) \ __y = __y->left; \ if (((__node)->rbt_flags & RBT_RED) != \ (__y->rbt_flags & RBT_RED)) { \ (__node)->rbt_flags ^= RBT_RED; \ __y->rbt_flags ^= RBT_RED; \ } \ __x = __y->next; \ (__node)->left->parent = __y; \ (__node)->left->anchor = &__y->left; \ __y->left = (__node)->left; \ if (__y == (__node)->next) { \ __z = __y; \ } else { \ __z = __y->parent; \ if (__x) { \ __x->parent = __z; \ __x->anchor = &__z->left; \ } \ __z->left = __x; /* __y was a child of left */ \ __y->next = (__node)->next; \ (__node)->next->parent = __y; \ (__node)->next->anchor = &__y->next; \ } \ __y->parent = (__node)->parent; \ __y->anchor = (__node)->anchor; \ *(__node)->anchor = __y; \ } else { \ __z = (__node)->parent; \ __x = (__node)->next; /* __x might be NULL */ \ if (__x == 0) \ __x = (__node)->left; \ if (__x) { \ __x->parent = __z; \ __x->anchor = (__node)->anchor; \ } \ if ((__header)->leftmost == (__node)) { \ if (__x) { \ __y = __x; \ while (__y->left) \ __y = __y->left; \ (__header)->leftmost = __y; \ } else { \ (__header)->leftmost = __z; \ } \ } \ if ((__header)->rightmost == (__node)) { \ if (__x) { \ __y = __x; \ while (__y->next) \ __y = __y->next; \ (__header)->rightmost = __y; \ } else { \ (__header)->rightmost = __z; \ } \ } \ *(__node)->anchor = __x; \ } \ if (!((__node)->rbt_flags & RBT_RED)) { \ while ((__z) && \ ((__x == 0) || !(__x->rbt_flags & RBT_RED))) { \ if (__x == __z->left) { \ __y = __z->next; \ if (__y->rbt_flags & RBT_RED) { \ __y->rbt_flags &= ~RBT_RED; \ __z->rbt_flags |= RBT_RED; \ RBT_ROTATE_LEFT(__z); \ __y = __z->next; \ } \ if ((__y->left == 0 || \ !(__y->left->rbt_flags & \ RBT_RED)) && \ (__y->next == 0 || \ !(__y->next->rbt_flags & \ RBT_RED))) { \ __y->rbt_flags |= RBT_RED; \ __x = __z; \ __z = __z->parent; \ } else { \ if (__y->next == 0 || \ !(__y->next->rbt_flags & \ RBT_RED)) { \ if (__y->left) \ __y->left \ ->rbt_flags &= \ ~RBT_RED; \ __y->rbt_flags |= \ RBT_RED; \ RBT_ROTATE_RIGHT(__y); \ __y = __z->next; \ } \ __y->rbt_flags &= ~RBT_RED; \ __y->rbt_flags |= \ __z->rbt_flags & \ RBT_RED; \ __z->rbt_flags &= ~RBT_RED; \ if (__y->next) \ __y->next->rbt_flags &= \ ~RBT_RED; \ RBT_ROTATE_LEFT(__z); \ break; \ } \ } else { \ __y = __z->left; \ if (__y->rbt_flags & RBT_RED) { \ __y->rbt_flags &= ~RBT_RED; \ __z->rbt_flags |= RBT_RED; \ RBT_ROTATE_RIGHT(__z); \ __y = __z->left; \ } \ if ((__y->left == 0 || \ !(__y->left->rbt_flags & \ RBT_RED)) && \ (__y->next == 0 || \ !(__y->next->rbt_flags & \ RBT_RED))) { \ __y->rbt_flags |= RBT_RED; \ __x = __z; \ __z = __z->parent; \ } else { \ if (__y->left == 0 || \ !(__y->left->rbt_flags & \ RBT_RED)) { \ if (__y->next) \ __y->next \ ->rbt_flags &= \ ~RBT_RED; \ __y->rbt_flags |= \ RBT_RED; \ RBT_ROTATE_LEFT(__y); \ __y = __z->left; \ } \ __y->rbt_flags &= ~RBT_RED; \ __y->rbt_flags |= \ __z->rbt_flags & \ RBT_RED; \ __z->rbt_flags &= ~RBT_RED; \ if (__y->left) \ __y->left->rbt_flags &= \ ~RBT_RED; \ RBT_ROTATE_RIGHT(__z); \ break; \ } \ } \ } \ if (__x) \ __x->rbt_flags &= ~RBT_RED; \ } \ }) /* * For RBT_FIND * __header is the header node * __node will contain the found node * __val is a uint64_t and contains the value to search * __x is a temporary variable * No nodes are modified * __node is modified * * When RBT_FIND returns, __node points to the node whose value is __val. * If multiple nodes have the value __val, only one is returned. * If no node has the value __val, __node points to the preceding * or the following node and __node cannot have two children. * After the call, if __node is NULL, the tree is empty. * To check for success : * if (((__node) != 0) && (RBT_VALUE(__node) == (__val))) { * -- node found -- * ... * } * * RBT_FIND must be called before inserting a node using RBT_INSERT. */ #define RBT_FIND(__header, __node, __val) \ ({ \ struct rbt_node *__x; \ (__node) = (__header)->root; \ __x = (__header)->root; \ while (__x) { \ (__node) = __x; \ if (__x->rbt_value > (__val)) { \ __x = __x->left; \ } else if (__x->rbt_value < (__val)) { \ __x = __x->next; \ } else { \ break; \ } \ } \ }) /* * For RBT_FIND_LEFT * __header is the header node * __node will contain the found node * __val is a uint64_t and contains the value to search * __x is a temporary variable * No nodes are modified * __node is modified * * When RBT_FIND_LEFT returns, __node points to the leftmost node * whose value is __val. * If multiple nodes have the value __val, only one is returned. * If no node has the value __val, __node is NULL. * This is different from RBT_FIND. RBT_FIND_LEFT cannot be used * to insert a new node. * To check for success : * if ((__node) != 0) { * -- node found -- * ... * } */ #define RBT_FIND_LEFT(__header, __node, __val) \ ({ \ struct rbt_node *__x; \ (__node) = 0; \ __x = (__header)->root; \ while (__x) { \ if (__x->rbt_value > (__val)) { \ __x = __x->left; \ } else if (__x->rbt_value < (__val)) { \ __x = __x->next; \ } else { \ (__node) = __x; \ while (__x) { \ while ((__x = __x->left)) { \ if (__x->rbt_value < (__val)) \ break; \ (__node) = __x; \ } \ if (__x == 0) \ break; \ while ((__x = __x->next)) { \ if (__x->rbt_value == \ (__val)) { \ (__node) = __x; \ break; \ } \ } \ } \ break; \ } \ } \ }) /* * RBT_BLACK_COUNT counts the number of black nodes in the parents of a node */ #define RBT_BLACK_COUNT(__node, __sum) \ ({ \ for ((__sum) = 0; (__node); (__node) = (__node)->parent) { \ if (!((__node)->rbt_flags & RBT_RED)) \ ++(__sum); \ } \ }) #define RBT_VERIFY(__header, __it, __error) \ ({ \ int __len, __num, __sum; \ struct rbt_node *__L, *__R; \ (__error) = 0; \ if ((__header)->rbt_num_node == 0) { \ if (((__header)->leftmost) || \ ((__header)->rightmost) || ((__header)->root)) { \ (__error) = 1; \ (__it) = 0; \ } \ } else { \ __L = (__header)->leftmost; \ RBT_BLACK_COUNT(__L, __len) \ __num = 0; \ RBT_LOOP((__header), (__it)) \ { \ if ((__it)->parent == 0) { \ if (((__it) != (__header)->root) || \ ((__it)->anchor != \ &(__header)->root)) { \ (__error) = 2; \ break; \ } \ } else { \ if ((((__it) == \ (__it)->parent->next) && \ ((__it)->anchor != \ &(__it)->parent->next)) || \ (((__it) == \ (__it)->parent->left) && \ ((__it)->anchor != \ &(__it)->parent->left))) { \ (__error) = 2; \ break; \ } \ } \ __L = (__it)->left; \ __R = (__it)->next; \ if (((__it)->rbt_flags & RBT_RED) && \ ((__L && (__L->rbt_flags & RBT_RED)) || \ (__R && (__R->rbt_flags & RBT_RED)))) { \ (__error) = 3; \ break; \ } \ if (__L && \ (__L->rbt_value > (__it)->rbt_value)) { \ (__error) = 4; \ break; \ } \ if (__R && \ (__R->rbt_value < (__it)->rbt_value)) { \ (__error) = 5; \ break; \ } \ if (!__L && !__R) { \ __L = (__it); \ RBT_BLACK_COUNT(__L, __sum) \ if (__sum != __len) { \ (__error) = 6; \ break; \ } \ } \ __num++; \ RBT_INCREMENT(__it) \ } \ if (((__error) == 0) && \ (__num != (__header)->rbt_num_node)) { \ (__error) = 7; \ (__it) = 0; \ } \ /* test RBT_DECREMENT */ \ __num = 0; \ RBT_LOOP_REVERSE(__header, __it) \ { \ __num++; \ RBT_DECREMENT(__it) \ } \ if (((__error) == 0) && \ (__num != (__header)->rbt_num_node)) { \ (__error) = 8; \ (__it) = 0; \ } \ if ((__error) == 0) { \ __L = (__header)->root; \ while ((__L)->left) \ (__L) = (__L)->left; \ __R = (__header)->root; \ while ((__R)->next) \ (__R) = (__R)->next; \ if ((__L != (__header)->leftmost) || \ (__R != (__header)->rightmost)) { \ (__error) = 9; \ (__it) = 0; \ } \ } \ if (((__error) == 0) && ((__header)->root) && \ !((__header)->root->parent == 0)) { \ (__error) = 10; \ (__it) = 0; \ } \ } \ }) #endif /* RBT_TREE_H */ /** @{ */ nfs-ganesha-6.5/src/include/rquota.h000066400000000000000000000107541473756622300175020ustar00rootroot00000000000000// SPDX-License-Identifier: unknown license... /* * The content of this file is a mix of rpcgen-generated * and hand-edited program text. It is not automatically * generated by, e.g., build processes. * * This file is under version control. */ #ifndef _RQUOTA_H_RPCGEN #define _RQUOTA_H_RPCGEN #include "gsh_rpc.h" #include "extended_types.h" #ifdef __cplusplus extern "C" { #endif #define RQ_PATHLEN 1024 struct sq_dqblk { u_int rq_bhardlimit; u_int rq_bsoftlimit; u_int rq_curblocks; u_int rq_fhardlimit; u_int rq_fsoftlimit; u_int rq_curfiles; u_int rq_btimeleft; u_int rq_ftimeleft; }; typedef struct sq_dqblk sq_dqblk; struct getquota_args { char *gqa_pathp; int gqa_uid; }; typedef struct getquota_args getquota_args; struct setquota_args { int sqa_qcmd; char *sqa_pathp; int sqa_id; sq_dqblk sqa_dqblk; }; typedef struct setquota_args setquota_args; struct ext_getquota_args { char *gqa_pathp; int gqa_type; int gqa_id; }; typedef struct ext_getquota_args ext_getquota_args; struct ext_setquota_args { int sqa_qcmd; char *sqa_pathp; int sqa_id; int sqa_type; sq_dqblk sqa_dqblk; }; typedef struct ext_setquota_args ext_setquota_args; struct rquota { int rq_bsize; bool_t rq_active; u_int rq_bhardlimit; u_int rq_bsoftlimit; u_int rq_curblocks; u_int rq_fhardlimit; u_int rq_fsoftlimit; u_int rq_curfiles; u_int rq_btimeleft; u_int rq_ftimeleft; }; typedef struct rquota rquota; enum qr_status { Q_OK = 1, Q_NOQUOTA = 2, Q_EPERM = 3, }; typedef enum qr_status qr_status; struct getquota_rslt { qr_status status; union { rquota gqr_rquota; } getquota_rslt_u; }; typedef struct getquota_rslt getquota_rslt; struct setquota_rslt { qr_status status; union { rquota sqr_rquota; } setquota_rslt_u; }; typedef struct setquota_rslt setquota_rslt; #define RQUOTAPROG 100011 #define RQUOTAVERS 1 #if defined(__STDC__) || defined(__cplusplus) #define RQUOTAPROC_GETQUOTA 1 extern getquota_rslt *rquotaproc_getquota_1(getquota_args *, CLIENT *); extern getquota_rslt *rquotaproc_getquota_1_svc(getquota_args *, struct svc_req *); #define RQUOTAPROC_GETACTIVEQUOTA 2 extern getquota_rslt *rquotaproc_getactivequota_1(getquota_args *, CLIENT *); extern getquota_rslt *rquotaproc_getactivequota_1_svc(getquota_args *, struct svc_req *); #define RQUOTAPROC_SETQUOTA 3 extern setquota_rslt *rquotaproc_setquota_1(setquota_args *, CLIENT *); extern setquota_rslt *rquotaproc_setquota_1_svc(setquota_args *, struct svc_req *); #define RQUOTAPROC_SETACTIVEQUOTA 4 extern setquota_rslt *rquotaproc_setactivequota_1(setquota_args *, CLIENT *); extern setquota_rslt *rquotaproc_setactivequota_1_svc(setquota_args *, struct svc_req *); extern int rquotaprog_1_freeresult(SVCXPRT *, xdrproc_t, void *); extern char *check_handle_lead_slash(char *, char *, size_t); #else /* K&R C */ #define RQUOTAPROC_GETQUOTA 1 extern getquota_rslt *rquotaproc_getquota_1(); extern getquota_rslt *rquotaproc_getquota_1_svc(); #define RQUOTAPROC_GETACTIVEQUOTA 2 extern getquota_rslt *rquotaproc_getactivequota_1(); extern getquota_rslt *rquotaproc_getactivequota_1_svc(); #define RQUOTAPROC_SETQUOTA 3 extern setquota_rslt *rquotaproc_setquota_1(); extern setquota_rslt *rquotaproc_setquota_1_svc(); #define RQUOTAPROC_SETACTIVEQUOTA 4 extern setquota_rslt *rquotaproc_setactivequota_1(); extern setquota_rslt *rquotaproc_setactivequota_1_svc(); extern int rquotaprog_1_freeresult(); extern char *check_handle_lead_slash(); #endif /* K&R C */ /* Number of rquota commands. */ #define RQUOTA_NB_COMMAND (RQUOTAPROC_SETACTIVEQUOTA + 1) #define EXT_RQUOTAVERS 2 /* the xdr functions */ #if defined(__STDC__) || defined(__cplusplus) extern bool xdr_sq_dqblk(XDR *, sq_dqblk *); extern bool xdr_getquota_args(XDR *, getquota_args *); extern bool xdr_setquota_args(XDR *, setquota_args *); extern bool xdr_ext_getquota_args(XDR *, ext_getquota_args *); extern bool xdr_ext_setquota_args(XDR *, ext_setquota_args *); extern bool xdr_rquota(XDR *, rquota *); extern bool xdr_qr_status(XDR *, qr_status *); extern bool xdr_getquota_rslt(XDR *, getquota_rslt *); extern bool xdr_setquota_rslt(XDR *, setquota_rslt *); #else /* K&R C */ extern bool_t xdr_sq_dqblk(); extern bool_t xdr_getquota_args(); extern bool_t xdr_setquota_args(); extern bool_t xdr_ext_getquota_args(); extern bool_t xdr_ext_setquota_args(); extern bool_t xdr_rquota(); extern bool_t xdr_qr_status(); extern bool_t xdr_getquota_rslt(); extern bool_t xdr_setquota_rslt(); #endif /* K&R C */ #ifdef __cplusplus } #endif #endif /* !_RQUOTA_H_RPCGEN */ nfs-ganesha-6.5/src/include/sal_data.h000066400000000000000000001135071473756622300177370ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file sal_data.h * @brief Data structures for state management. */ #ifndef SAL_DATA_H #define SAL_DATA_H #include #include #include #include #include #include /* For having MAXNAMLEN */ #include "abstract_atomic.h" #include "abstract_mem.h" #include "hashtable.h" #include "fsal_pnfs.h" #include "config_parsing.h" #include "gsh_lttng/gsh_lttng.h" #ifdef _USE_9P /* define u32 and related types independent of SAL and 9P */ #include "9p_types.h" #endif /* _USE_9P */ /** ** Forward declarations to avoid circular dependency conflicts */ #include "gsh_status.h" typedef struct nfs_client_id_t nfs_client_id_t; typedef struct nfs41_session nfs41_session_t; /** ** Consolidated circular dependencies */ #include "nfs_proto_data.h" /** ** Forward references to types */ typedef struct nfs_argop4_state nfs_argop4_state; typedef struct nfs_client_record_t nfs_client_record_t; typedef struct state_async_queue_t state_async_queue_t; typedef struct state_block_data_t state_block_data_t; typedef struct state_cookie_entry_t state_cookie_entry_t; typedef struct state_lock_entry_t state_lock_entry_t; typedef struct state_nfs4_owner_t state_nfs4_owner_t; typedef struct state_nlm_client_t state_nlm_client_t; typedef struct state_owner_t state_owner_t; typedef struct state_t state_t; /** * @brief Number of errors before giving up on recovery * * We set a maximum because the recovery routines need to terminate at * some point. */ #define STATE_ERR_MAX 100 /** * @brief Maximum number of operation in a compound request * * We cap the number of operation to this value for all V4 versions */ #define NFS4_MAX_OPERATIONS 100 /** * @brief Indicate that lock extends to the entire range of the file * * This is true no matter what the beginning of the lock range is. */ #define STATE_LOCK_OFFSET_EOF 0xFFFFFFFFFFFFFFFFLL extern struct fridgethr *state_async_fridge; /***************************************************************************** * * NFSv4.1 Session data * *****************************************************************************/ extern pool_t *nfs41_session_pool; /** * @param Session ID hash */ extern hash_table_t *ht_session_id; /** * @brief Default number of forechannel slots in a session * * This is also the maximum number of backchannel slots we'll use, * even if the client offers more. */ #define NFS41_NB_SLOTS_DEF 64 /** * @brief Default string length of all operations in one compound */ #define NFS4_COMPOUND_OPERATIONS_STR_LEN 256 /** * @brief the simple info of last request to save in slot */ struct nfs41_cached_req { nfs_opnum4 opcodes[NFS4_MAX_OPERATIONS]; /*< opcodes of compound */ uint32_t opcode_num; /*< number of opcode in one compound */ uint32_t xid; /*< Xid of request */ sequenceid4 seq_id; /*< Sequence id of request */ uint64_t finish_time_ms; /*< Finish time of request in ms */ }; /** * @brief Members in the slot table */ typedef struct nfs41_session_slot__ { sequenceid4 sequence; /*< Sequence number of this operation */ pthread_mutex_t slot_lock; /*< Lock on the slot */ struct COMPOUND4res_extended *cached_result; /*< NFv41: pointer to cached RPC result in a session's slot */ struct nfs41_cached_req last_req; /*< Last request of the slot */ } nfs41_session_slot_t; /** * @brief Bookkeeping for callback slots on the client */ typedef struct nfs41_cb_session_slot { sequenceid4 sequence; /*< Sequence number of last call */ bool in_use; /*< Set if a call if in progress on this slot */ } nfs41_cb_session_slot_t; /** * Flag indicating that the backchannel is up */ enum { session_bc_up = 0x01, session_bc_fault = 0x02, /* not actually used anywhere */ }; /** * @brief minimum values for channel attributes */ #define NFS41_MIN_REQUEST_SIZE 256 #define NFS41_MIN_RESPONSE_SIZE 256 #define NFS41_MIN_OPERATIONS 2 #define NFS41_MAX_CONNECTIONS 16 /** * @brief buffer size for displaying session-id */ #define NFS4_SESSIONID_BUFFER_SIZE 128 /** * @brief Structure representing a connection-xprt entry in nfs41_session's * connection_xprts */ typedef struct connection_xprt { SVCXPRT *xprt; struct glist_head node; } connection_xprt_t; /** * @brief Structure representing an NFSv4.1 session */ struct nfs41_session { char session_id[NFS4_SESSIONID_SIZE]; /*< Session ID */ struct glist_head session_link; /*< Link in the list of sessions for this clientid */ channel_attrs4 fore_channel_attrs; /*< Fore-channel attributes */ channel_attrs4 back_channel_attrs; /*< Back-channel attributes */ struct rpc_call_channel cb_chan; /*< Back channel */ pthread_mutex_t cb_mutex; /*< Protects the cb slot table, when searching for a free slot */ pthread_cond_t cb_cond; /*< Condition variable on which we wait if the slot table is full and on which we signal when we free an entry. */ pthread_rwlock_t conn_lock; int num_conn; struct glist_head connection_xprts; nfs_client_id_t *clientid_record; /*< Client record corresponding to ID */ clientid4 clientid; /*< Clientid owning this session */ uint32_t cb_program; /*< Callback program ID */ struct { u_int sec_parms_len; callback_sec_parms4 *sec_parms_val; } cb_sec_parms; /*< Callback security params */ uint32_t flags; /*< Flags pertaining to this session */ int32_t refcount; uint32_t nb_slots; /**< Number of slots in this session */ nfs41_session_slot_t *fc_slots; /**< Forechannel slot table*/ nfs41_cb_session_slot_t *bc_slots; /**< Backchannel slot table */ }; /** * @brief Track the call that creates a state, for NFSv4.1. */ struct state_refer { sessionid4 session; /*< The session of the creating call. */ sequenceid4 sequence; /*< The sequence ID of the creating call. */ slotid4 slot; /*< The slot ID of the creating call. */ }; /****************************************************************************** * * NFSv4 Recovery data * *****************************************************************************/ /** * @brief Revoked filehandle list */ typedef struct rdel_fh { struct glist_head rdfh_list; char *rdfh_handle_str; } rdel_fh_t; /** * @brief A client entry */ typedef struct clid_entry { struct glist_head cl_list; /*< Link in the list */ struct glist_head cl_rfh_list; char cl_name[PATH_MAX]; /*< Client name */ } clid_entry_t; extern char v4_old_dir[PATH_MAX]; extern char v4_recov_dir[PATH_MAX]; /****************************************************************************** * * NFSv4 State data * *****************************************************************************/ extern hash_table_t *ht_state_id; #include "sal_shared.h" /** * @brief Data for an NFS v4 share reservation/open */ struct state_share { struct glist_head share_lockstates; /*< Lock states for this open state This field MUST be first */ unsigned int share_access; /*< The NFSv4 Share Access state */ unsigned int share_deny; /*< The NFSv4 Share Deny state */ unsigned int share_access_prev; /*< Previous share access state */ unsigned int share_deny_prev; /*< Previous share deny state */ }; /** * @brief Data for an NLM share reservation/open */ struct state_nlm_share { /** Put this share state on a list per client. This field MUST be first */ struct glist_head share_perclient; /** The NLM Share Access state */ unsigned int share_access; /** The NLM Share Deny state */ unsigned int share_deny; /** Counts of each share access state */ unsigned int share_access_counts[fsa_RW + 1]; /** Counts of each share deny state */ unsigned int share_deny_counts[fsm_DRW + 1]; }; /** * @brief The number of bytes in the stateid.other * * The value 12 is fixed by RFC 3530/RFC 5661. */ #define OTHERSIZE 12 extern char all_zero[OTHERSIZE]; extern char all_ones[OTHERSIZE]; /** * @brief Data for a set of locks */ struct state_lock { struct glist_head state_locklist; /*< List of locks owned by this stateid This field MUST be first */ struct glist_head state_sharelist; /*< List of states related to a share */ char openstate_key[OTHERSIZE]; /*< The hash key of related open-state */ }; /** * @brief Data for a 9p fid */ struct state_9p_fid { struct glist_head state_locklist; /*< List of locks owned by this fid This field MUST be first */ unsigned int share_access; /*< The 9p Access state */ unsigned int share_deny; /*< Will always be 0 */ }; /** * @brief Stats for client-file delegation heuristics */ /* @brief Per client, per file stats */ struct cf_deleg_stats { time_t cfd_rs_time; /* time when the client responded NFS4_OK for a recall. */ time_t cfd_r_time; /* time of the recall attempt */ }; /** * @brief States of a delegation * * Different states a delegation can be in during its lifetime */ enum deleg_state { DELEG_GRANTED = 1, /* Granted */ DELEG_RECALL_WIP, /* Recall in progress */ }; /** * @brief Data for a delegation */ struct state_deleg { open_delegation_type4 sd_type; enum deleg_state sd_state; struct cf_deleg_stats sd_clfile_stats; /* client specific */ uint32_t share_access; /*< The NFSv4 Share Access state */ uint32_t share_deny; /*< The NFSv4 Share Deny state */ }; /** * @brief Data for a set of layouts of a given type */ struct state_layout { struct glist_head state_segments; /*< List of segments */ layouttype4 state_layout_type; /*< The type of layout this state represents */ uint32_t granting; /*< Number of LAYOUTGETs in progress */ bool state_return_on_close; /*< Whether this layout should be returned on last close. */ }; /** * @brief Type specific state data */ union state_data { struct state_share share; struct state_nlm_share nlm_share; struct state_lock lock; struct state_deleg deleg; struct state_layout layout; struct state_9p_fid fid; uint32_t io_advise; }; /** * @brief Structure representing a single NFSv4 state * * Each state is identified by stateid and represents some resource or * set of resources. * * The lists are protected by the st_lock */ typedef void(state_free_t)(struct state_t *state); struct state_t { struct glist_head state_list; /**< List of states on a file */ struct glist_head state_owner_list; /**< List of states for an owner */ struct glist_head state_export_list; /**< List of states on the same export */ #ifdef DEBUG_SAL struct glist_head state_list_all; /**< Global list of all stateids */ #endif pthread_mutex_t state_mutex; /**< Mutex protecting following pointers */ state_free_t *state_free; /**< function to free if default gsh_free doesn't work. */ struct gsh_export *state_export; /**< Export this entry belongs to */ /* Don't re-order or move these next two. They are used for hashing */ state_owner_t *state_owner; /**< State Owner related to state */ struct fsal_obj_handle *state_obj; /**< owning object */ union state_data state_data; enum state_type state_type; u_int32_t state_seqid; /**< The NFSv4 Sequence id */ int32_t state_refcount; /**< Refcount for state_t objects */ char stateid_other[OTHERSIZE]; /**< "Other" part of state id, used as hash key */ struct state_refer state_refer; /**< For NFSv4.1, track the call that created a state. */ }; static inline void free_state(struct state_t *state) { if (state->state_free != NULL) state->state_free(state); else gsh_free(state); } /* Macros to compare and copy state_t to a struct stateid4 */ #define SAME_STATEID(id4, state) \ ((id4)->seqid == (state)->state_seqid && \ memcmp((id4)->other, (state)->stateid_other, OTHERSIZE) == 0) #define COPY_STATEID(id4, state) \ do { \ (id4)->seqid = (state)->state_seqid; \ (void)memcpy((id4)->other, (state)->stateid_other, OTHERSIZE); \ } while (0) /***************************************************************************** * * NFS Owner data * *****************************************************************************/ typedef void(state_owner_init_t)(state_owner_t *powner); extern hash_table_t *ht_nlm_owner; #ifdef _USE_9P extern hash_table_t *ht_9p_owner; #endif extern hash_table_t *ht_nfs4_owner; extern struct glist_head cached_open_owners; extern pthread_mutex_t cached_open_owners_lock; /** * @brief A structure identifying the owner of an NFSv4 open or lock state * * This serves as the key to the NFSv4 owner table, and is generated * from either an open or lock owner. */ typedef struct state_nfs4_owner_name_t { unsigned int son_owner_len; char *son_owner_val; } state_nfs4_owner_name_t; /** * @brief The type of entity responsible for a state * * For NLM and 9P, one kind of owner owns every kind of state. For * NFSv4.1 open owners and lock owners are in disjoint spaces, and all * non-open and non-lock states are associated with a given client. */ typedef enum state_owner_type_t { STATE_LOCK_OWNER_UNKNOWN, /*< Unknown */ #ifdef _USE_NLM STATE_LOCK_OWNER_NLM, /*< An NLM client */ #endif /* _USE_NLM */ #ifdef _USE_9P STATE_LOCK_OWNER_9P, /*< A 9P client */ #endif STATE_OPEN_OWNER_NFSV4, /*< An NFSv4 owner of an open */ STATE_LOCK_OWNER_NFSV4, /*< An NFSv4 owner of a set of locks */ STATE_CLIENTID_OWNER_NFSV4 /*< An NFSv4 client, owns all states but opens and locks. */ } state_owner_type_t; /** * @brief Specifies interest in a client for NLN/NSM. */ typedef enum care_t { CARE_NOT, /*< Do not care about client's status */ CARE_OWNER, /*< Don't care about nsm state but do need an owner. */ CARE_ALWAYS, /*< Always care about client's status */ CARE_NO_MONITOR, /*< Care, but will not actively monitor */ CARE_MONITOR /*< Will actively monitor client status */ } care_t; #ifdef _USE_NLM extern hash_table_t *ht_nsm_client; extern hash_table_t *ht_nlm_client; extern pthread_mutex_t granted_mutex; extern pthread_mutex_t nlm_async_resp_mutex; extern pthread_cond_t nlm_async_resp_cond; extern pthread_mutex_t nsm_mutex; #endif extern pthread_mutex_t cached_open_owners_lock; #ifdef DEBUG_SAL extern pthread_mutex_t all_state_owners_mutex; extern pthread_mutex_t all_state_v4_mutex; #endif /** * @brief NSM (rpc.statd) state for a given client. * * All nsm clients have a caller name. * * When nsm_use_caller_name is true, the caller name comes from the NLM * requests. * * When nsm_use_caller_name is false, the caller name is the string form of * the IP address. If the IP address is IPv4 encapsulated in IPv6, the name * will be the simple IPv4 version of the address. */ typedef struct state_nsm_client_t { pthread_mutex_t ssc_mutex; /*< Mutex protecting this structure */ struct glist_head ssc_lock_list; /*< All locks held by client */ struct glist_head ssc_share_list; /*< All share reservations */ struct gsh_client *ssc_client; /*< The client involved */ int32_t ssc_refcount; /*< Reference count to protect structure */ int32_t ssc_monitored; /*< If this client is actively monitored */ int32_t ssc_nlm_caller_name_len; /*< Length of identifier */ char *ssc_nlm_caller_name; /*< Client identifier */ } state_nsm_client_t; /** * @brief Represent a single client accessing us through NLM */ struct state_nlm_client_t { state_nsm_client_t *slc_nsm_client; /*< Related NSM client */ xprt_type_t slc_client_type; /*< The transport type to this client */ int32_t slc_refcount; /*< Reference count for disposal */ sockaddr_t slc_server_addr; /*< local addr when request is made */ int32_t slc_nlm_caller_name_len; /*< Length of client name */ char *slc_nlm_caller_name; /*< Client name */ CLIENT *slc_callback_clnt; /*< Callback for blocking locks */ AUTH *slc_callback_auth; /*< Authentication for callback */ }; /** * @brief Owner of an NLM lock or share. */ typedef struct state_nlm_owner_t { state_nlm_client_t *so_client; /*< Structure for this client */ int32_t so_nlm_svid; /*< Owner within client */ struct glist_head so_nlm_shares; /*< Share reservations */ } state_nlm_owner_t; /** * @brief 9P lock owner */ #ifdef _USE_9P typedef struct state_9p_owner_t { u32 proc_id; /*< PID on the client */ sockaddr_t client_addr; /*< Network address of client */ } state_9p_owner_t; #endif /* _USE_9P */ /** * @brief Share and lock operations for NFSv4.0 * * This structure saves the arguments to the most recent operation on * an open or lock owner associated with an NFSv4.0 client. This is * part of the At-Most Once semantics and not used under NFSv4.1. */ struct nfs_argop4_state { nfs_opnum4 argop; /*< Operation being saved */ union { CLOSE4args opclose; /*< CLOSE */ LOCK4args oplock; /*< LOCK */ LOCKU4args oplocku; /*< LOCKU */ OPEN4args opopen; /*< OPEN */ OPEN_CONFIRM4args opopen_confirm; /*< OPEN_CONFIRM */ OPEN_DOWNGRADE4args opopen_downgrade; /*< OPEN_DOWNGRADE */ } nfs_argop4_u; }; /** * @brief A structure supporting all NFSv4 owners. */ struct state_nfs4_owner_t { clientid4 so_clientid; /*< Owning clientid */ nfs_client_id_t *so_clientrec; /*< Owning client id record */ bool so_confirmed; /*< Confirmation (NFSv4.0 only) */ seqid4 so_seqid; /*< Seqid for serialization of operations on owner (NFSv4.0 only) */ nfs_argop4_state so_args; /*< Saved args */ void *so_last_entry; /*< Last file operated on by this state owner * we don't keep a reference so this is a * void * to prevent it's dereferencing because * the pointer might become invalid if cache * inode flushes the entry out. But it sufices * for the purposes of detecting replayed * operations. */ nfs_resop4 so_resp; /*< Saved response */ state_owner_t *so_related_owner; /*< For lock-owners, the open-owner under which the lock owner was created */ struct glist_head so_state_list; /*< This is the head of a list of states owned by this owner. so_mutex MUST be held when accessing this field. */ struct glist_head so_perclient; /*< open owner entry to be linked to client */ struct glist_head so_cache_entry; /*< Entry on cached_open_owners */ time_t so_cache_expire; /* time cached OPEN owner will expire. If non-zero, so_cache_entry is in cached_open_owners list. cached_open_owners_lock MUST be held when accessing this field.*/ }; /** * @brief General state owner * * This structure encodes the owner of any state, protocol specific * information is contained within the union. */ struct state_owner_t { state_owner_type_t so_type; /*< Owner type */ struct glist_head so_lock_list; /*< Locks for this owner */ #ifdef DEBUG_SAL struct glist_head so_all_owners; /**< Global list of all state owners */ #endif /* _DEBUG_MEMLEAKS */ pthread_mutex_t so_mutex; /*< Mutex on this owner */ int32_t so_refcount; /*< Reference count for lifecyce management */ int so_owner_len; /*< Length of owner name */ char *so_owner_val; /*< Owner name */ union { state_nfs4_owner_t so_nfs4_owner; /*< All NFSv4 state owners */ state_nlm_owner_t so_nlm_owner; /*< NLM lock and share owners */ #ifdef _USE_9P state_9p_owner_t so_9p_owner; /*< 9P lock owners */ #endif } so_owner; }; /* Test if the lock owner type is 9P */ #ifdef _USE_9P #define LOCK_OWNER_9P(owner) ((owner)->so_type == STATE_LOCK_OWNER_9P) #else #define LOCK_OWNER_9P(owner) (0) #endif extern state_owner_t unknown_owner; /****************************************************************************** * * NFSv4 Clientid data * *****************************************************************************/ /** * @brief State of a clientid record. */ typedef enum nfs_clientid_confirm_state__ { UNCONFIRMED_CLIENT_ID, CONFIRMED_CLIENT_ID, EXPIRED_CLIENT_ID, STALE_CLIENT_ID } nfs_clientid_confirm_state_t; /** * @brief Errors from the clientid functions */ typedef enum clientid_status { CLIENT_ID_SUCCESS = 0, /*< Success */ CLIENT_ID_INSERT_MALLOC_ERROR, /*< Unable to allocate memory */ CLIENT_ID_INVALID_ARGUMENT, /*< Invalid argument */ CLIENT_ID_EXPIRED, /*< requested client id expired */ CLIENT_ID_STALE /*< requested client id stale */ } clientid_status_t; /** * @brief Record associated with a clientid * * This record holds Ganesha's state on an NFSv4.0 client. */ struct nfs_client_id_t { clientid4 cid_clientid; /*< The clientid */ verifier4 cid_verifier; /*< Known verifier */ verifier4 cid_incoming_verifier; /*< Most recently supplied verifier */ time_t cid_last_renew; /*< Time of last renewal */ nfs_clientid_confirm_state_t cid_confirmed; /*< Confirm/expire state */ bool cid_allow_reclaim; /*< Can still reclaim state? */ nfs_client_cred_t cid_credential; /*< Client credential */ char *cid_recov_tag; /*< Recovery tag */ nfs_client_record_t *cid_client_record; /*< Record for managing confirmation and replacement */ struct glist_head cid_openowners; /*< All open owners */ struct glist_head cid_lockowners; /*< All lock owners */ pthread_mutex_t cid_mutex; /*< Mutex for this client */ union { struct { /** Callback channel */ struct rpc_call_channel cb_chan; /** Decoded address */ gsh_addr_t cb_addr; /** Callback identifier */ uint32_t cb_callback_ident; /** Universal address */ char cb_client_r_addr[SOCK_NAME_MAX]; /** Callback program */ uint32_t cb_program; bool cb_chan_down; /* Callback channel state */ } v40; /*< v4.0 callback information */ struct { bool cid_reclaim_complete; /*< reclaim complete indication */ /** All sessions */ struct glist_head cb_session_list; } v41; /*< v4.1 callback information */ } cid_cb; /*< Version specific callback information */ time_t first_path_down_resp_time; /* Time when the server first sent NFS4ERR_CB_PATH_DOWN */ /* Since Ganesha only supports SP4_NONE, we do not store state * protection data required for other state protection mechanisms */ state_protect_how4 cid_state_protect_how; /* State protection type */ unsigned int cid_nb_session; /*< Number of sessions stored */ uint32_t cid_create_session_sequence; /*< Sequence number for session creation. */ CREATE_SESSION4res cid_create_session_slot; /*< Cached response to last CREATE_SESSION */ state_owner_t cid_owner; /*< Owner for per-client state */ int32_t cid_refcount; /*< Reference count for lifecycle */ int cid_lease_reservations; /*< Counted lease reservations, to spare this clientid from the reaper */ uint32_t cid_minorversion; uint32_t cid_stateid_counter; uint32_t curr_deleg_grants; /* current num of delegations owned by this client */ uint32_t num_revokes; /* Num revokes for the client */ struct gsh_client *gsh_client; /* for client specific statistics. */ uint32_t cid_open_state_counter; /* Num of files opened by client */ struct glist_head expired_client; /* List entry for expired clients */ bool marked_for_delayed_cleanup; /* Flag marked to state that entry is intended to be cleaned up from the delayed cleanup list */ }; #define GSH_CLIENT_ID_AUTO_TRACEPOINT(prov, event, log_level, _client_id, \ format, ...) \ do { \ const int32_t __ref_count = \ atomic_fetch_int32_t(&_client_id->cid_refcount); \ GSH_AUTO_TRACEPOINT( \ prov, event, log_level, \ "client_id={},confirmed={},ref_count={} | " format, \ _client_id->cid_clientid, _client_id->cid_confirmed, \ __ref_count, ##__VA_ARGS__); \ } while (0) /** * @brief Client owner record for verification and replacement * * @note The cr_mutex should never be acquired while holding a * cid_mutex. */ struct nfs_client_record_t { int32_t cr_refcount; /*< Reference count for lifecycle */ pthread_mutex_t cr_mutex; /*< Mutex protecting this structure */ nfs_client_id_t *cr_confirmed_rec; /*< The confirmed record associated with this owner (if there is one.) */ nfs_client_id_t *cr_unconfirmed_rec; /*< The unconfirmed record associated with this client name, if there is one. */ uint32_t cr_server_addr; /*< Server IP address the client connected to */ uint32_t cr_pnfs_flags; /*< pNFS flags. RFC 5661 allows us to treat identical co_owners with different pNFS flags as disjoint. Linux client stupidity forces us to do so. */ int cr_client_val_len; /*< Length of owner */ char cr_client_val[]; /*< Supplied co_owner */ }; extern pool_t *client_id_pool; extern hash_table_t *ht_client_record; extern hash_table_t *ht_confirmed_client_id; extern hash_table_t *ht_unconfirmed_client_id; /****************************************************************************** * * Lock Data * *****************************************************************************/ /** * @brief Blocking lock type and state */ typedef enum state_blocking_t { STATE_NON_BLOCKING, STATE_BLOCKING, STATE_AVAILABLE, STATE_CANCELED } state_blocking_t; /** * @brief Lock protocol type */ typedef enum lock_protocol_t { LOCK_NLM, LOCK_NFSv4, LOCK_9P } lock_protocol_t; /** * @brief Grant callback * * The granted call back is responsible for acquiring a reference to * the lock entry if needed. */ typedef state_status_t (*granted_callback_t)(struct fsal_obj_handle *obj, state_lock_entry_t *lock_entry); /** * @brief NLM specific Blocking lock data */ typedef struct state_nlm_block_data_t { netobj sbd_nlm_fh; /*< Filehandle */ char sbd_nlm_fh_buf[NFS3_FHSIZE]; /*< Statically allocated FH buffer */ } state_nlm_block_data_t; /** * @brief NFSv4 specific Blocking lock data */ typedef struct state_nfsv4_block_data_t { time_t snbd_notified_eligible_time; /* Only valid for available locks */ time_t snbd_last_poll_time; /* Only valid for blocked locks */ } state_nfsv4_block_data_t; extern struct glist_head state_blocked_locks; /** * @brief Grant types */ typedef enum state_grant_type_t { STATE_GRANT_NONE, /*< No grant */ STATE_GRANT_INTERNAL, /*< Grant generated by SAL */ STATE_GRANT_FSAL, /*< FSAL granting lock */ STATE_GRANT_FSAL_AVAILABLE, /*< FSAL signalling lock availability */ STATE_GRANT_POLL, /*< Poll this lock */ } state_grant_type_t; typedef enum state_block_type_t { /** Not blocking */ STATE_BLOCK_NONE, /** Lock is blocked by an internal lock. */ STATE_BLOCK_INTERNAL, /** Lock is blocked and FSAL supports async blocking locks. */ STATE_BLOCK_ASYNC, /** Lock is blocked and SAL will poll the lock. */ STATE_BLOCK_POLL, } state_block_type_t; /** * @brief Blocking lock data */ struct state_block_data_t { struct glist_head sbd_list; /*< Lost of blocking locks */ state_grant_type_t sbd_grant_type; /*< Type of grant */ state_block_type_t sbd_block_type; /*< Type of block */ granted_callback_t sbd_granted_callback; /*< Callback for grant */ state_cookie_entry_t *sbd_blocked_cookie; /*< Blocking lock cookie */ state_lock_entry_t *sbd_lock_entry; /*< Details of lock */ union { state_nlm_block_data_t sbd_nlm; /*< NLM block data */ state_nfsv4_block_data_t sbd_v4; /*< NFSv4 block data */ } sbd_prot; }; struct state_lock_entry_t { struct glist_head sle_list; /*< Locks on this file */ struct glist_head sle_owner_locks; /*< Link on the owner lock list */ struct glist_head sle_client_locks; /*< Locks on this client */ struct glist_head sle_state_locks; /*< Locks on this state */ #ifdef DEBUG_SAL struct glist_head sle_all_locks; /*< Link on the global lock list */ #endif /* DEBUG_SAL */ struct glist_head sle_export_locks; /*< Link on the export lock list */ struct gsh_export *sle_export; struct fsal_obj_handle *sle_obj; /*< File being locked */ state_block_data_t *sle_block_data; /*< Blocking lock data */ state_owner_t *sle_owner; /* Lock owner */ state_t *sle_state; /*< Associated lock state */ state_blocking_t sle_blocked; /*< Blocking status */ lock_protocol_t sle_protocol; /*< Protocol type */ int32_t sle_ref_count; /*< Reference count */ fsal_lock_param_t sle_lock; /*< Lock description */ pthread_mutex_t sle_mutex; /*< Mutex to protect the structure */ }; /** * @brief Stats for file-specific and client-file delegation heuristics */ struct file_deleg_stats { uint32_t fds_curr_delegations; /* number of delegations on file */ open_delegation_type4 fds_deleg_type; /* delegation type */ uint32_t fds_delegation_count; /* times file has been delegated */ uint32_t fds_recall_count; /* times file has been recalled */ time_t fds_avg_hold; /* avg amount of time deleg held */ time_t fds_last_delegation; time_t fds_last_recall; uint32_t fds_num_opens; /* total num of opens so far. */ time_t fds_first_open; /* time that we started recording num_opens */ uint32_t fds_num_write_opens; /* total num of opens in write mode incremented irrespective of whether or not delegation was offered for the file */ }; enum cbgetattr_state { CB_GETATTR_NONE = 0, /* initial state or reset to as and when finished processing response */ CB_GETATTR_WIP, /* when req sent */ CB_GETATTR_RSP_OK, /* successful response */ CB_GETATTR_FAILED /* for any failure */ }; struct cbgetattr_rsp { enum cbgetattr_state state; uint64_t change; uint64_t filesize; bool modified; }; typedef struct cbgetattr_rsp cbgetattr_t; /** * @brief Per-file state lists * * To be used by FSALs */ struct state_file { /** File owning state */ struct fsal_obj_handle *obj; /** NFSv4 states on this file. Protected by st_lock */ struct glist_head list_of_states; /** Layout recalls on this file. Protected by st_lock */ struct glist_head layoutrecall_list; /** Pointers for lock list. Protected by st_lock */ struct glist_head lock_list; /** Pointers for NLM share list. Protected by st_lock */ struct glist_head nlm_share_list; /** true iff write delegated. Protected by st_lock */ bool write_delegated; /** client holding write_deleg. Protected by st_lock */ nfs_client_id_t *write_deleg_client; /** Delegation statistics. Protected by st_lock */ struct file_deleg_stats fdeleg_stats; /* cbgetattr stats. Protected by st_lock */ cbgetattr_t cbgetattr; uint32_t anon_ops; /* number of anonymous operations * happening at the moment which * prevents delegations from being * granted */ }; /** * @brief Per-directory state lists * * To be used by FSALs/SAL */ struct state_dir { /** If this is a junction, the export this node points to. * Protected by jct_lock. */ struct gsh_export *junction_export; /** gsh_refstr to stash copy of the export's pseudopath */ struct gsh_refstr *jct_pseudopath; /** List of exports that have this object as their root. * Protected by jct_lock */ struct glist_head export_roots; /** There is one export root reference counted for each export for which this entry is a root for. This field is used with the atomic inc/dec/fetch routines. */ int32_t exp_root_refcount; }; /** * @brief Associate lock state or export junction information with an object. * * LOCK ORDERING * * The state handle has two overlapping locks for different purposes. * * The st_lock is used to protect byte range locks, opens, and such for regular * files. It is a mutex since there is no parallelism benefit to it being a * rwlock. * * The jct_lock is used to protect export junction information for directories. * It is a rwlock since most of the time junctions are being looked at not * modified. * * Both of these locks are often used in conjunction with the export->exp_lock, * but the rules of lock order are different. * * For st_lock, the export->exp_lock MUST NOT be held, the export->exp_lock is * often taken while already holding st_lock, so the order is: * * st_lock THEN export->exp_lock * * For jct_lock, there is one place where the export->exp_lock is already held, * so it makes sense for the order to be: * * export->exp_lock THEN jct_lock. * */ struct state_hdl { union { /** Lock protecting state */ pthread_mutex_t st_lock; /** Lock protecting export junctions */ pthread_rwlock_t jct_lock; }; bool no_cleanup; union { /** Details about the state held for a regular file. */ struct state_file file; /** Details about an export junction. */ struct state_dir dir; }; }; #define JCT_PSEUDOPATH(shdl) \ (shdl->dir.jct_pseudopath ? shdl->dir.jct_pseudopath->gr_val : NULL) /** * @brief Description of a layout segment */ typedef struct state_layout_segment { struct glist_head sls_state_segments; /*< Link on the per-layout-state segment list */ state_t *sls_state; /*< Associated layout state */ struct pnfs_segment sls_segment; /*< Segment descriptor */ void *sls_fsal_data; /*< FSAL data */ } state_layout_segment_t; /** * @brief An entry in a list of states affected by a recall * * We make a non-intrusive list so that a state can be affected by any * number of recalls. */ struct recall_state_list { struct glist_head link; /*< Link to the next layout state in the queue */ state_t *state; /*< State on which to recall */ }; /** * @brief Processing for a LAYOUTRECALL from the FSAL. */ struct state_layout_recall_file { struct glist_head entry_link; /*< List of recalls on a file */ struct fsal_obj_handle *obj; /*< Related file */ layouttype4 type; /*< Type of layout being recalled */ struct pnfs_segment segment; /*< Segment to recall */ struct glist_head state_list; /*< List of states affected by this layout */ void *recall_cookie; /*< Cookie returned to FSAL on return of last segment satisfying the layout */ }; /** * @brief Blocking lock cookie entry * * Management of lce_refcount * ========================== * * * state_add_grant_cookie * * creates a reference. * * * state_find_grant * * gets a reference * * * state_complete_grant * * always releases 1 reference it releases a 2nd reference when the * call instance is the first to actually try and complete the grant * * * state_release_grant * * always releases 1 reference it releases a 2nd reference when the * call instance is the first to actually try and release the grant * * * state_cancel_grant * * calls cancel_blocked_lock, which will release the initial * reference * * * cancel_blocked_lock * * releases 1 reference if cookie exists called by * state_cancel_grant also called by unlock, cancel, sm_notify */ struct state_cookie_entry_t { struct fsal_obj_handle *sce_obj; /*< Associated file */ state_lock_entry_t *sce_lock_entry; /*< Associated lock */ void *sce_cookie; /*< Cookie data */ int sce_cookie_size; /*< Length of cookie */ }; /* * Structures for state async processing * */ /** * @brief Asynchronous state function */ typedef void(state_async_func_t)(state_async_queue_t *arg); /** * @brief Data for asynchronous NLM calls */ typedef struct state_nlm_async_data_t { state_nlm_client_t *nlm_async_host; /*< The client */ void *nlm_async_key; /*< Identifying key */ union { nfs_res_t nlm_async_res; /*< Asynchronous response */ nlm4_testargs nlm_async_grant; /*< Arguments for grant */ } nlm_async_args; } state_nlm_async_data_t; /** * @brief Asynchronous blocking lock data */ typedef struct state_async_block_data_t { state_lock_entry_t *state_async_lock_entry; /*< Associated lock */ } state_async_block_data_t; /** * @brief Queue of asynchronous events */ struct state_async_queue_t { struct glist_head state_async_glist; /*< List of events */ state_async_func_t *state_async_func; /*< Processing function */ union { state_nlm_async_data_t state_nlm_async_data; /*< Data for operation */ void *state_no_data; /*< Dummy pointer */ } state_async_data; }; /** * @brief Start of grace period * * @note This looks specific to SONAS and ought not to be in the top * level SAL files. (The SAL needs more A to support this kind * of thing.) */ #define EVENT_JUST_GRACE 0 /* Just start grace period */ #define EVENT_CLEAR_BLOCKED \ 1 /* Start grace period, and clear blocked locks */ #define EVENT_RELEASE_IP \ 2 /* Start grace period, clear blocked locks, and release all locks */ #define EVENT_UPDATE_CLIENTS \ 3 /* Start grace period, clear blocked locks, release all locks, and update clients list. */ #define EVENT_TAKE_NODEID \ 4 /* Start grace period, clear blocked locks, release all locks, and update clients using node id. */ #define EVENT_TAKE_IP \ 5 /* Start grace period, clear blocked locks, release all locks, and update clients using IP address. */ typedef struct nfs_grace_start { int event; /*< Reason for grace period, see EVENT_nnn */ int nodeid; /*< Node from which we are taking over */ char *ipaddr; /*< IP of failed node */ } nfs_grace_start_t; /* Memory pools */ extern pool_t *state_owner_pool; /*< Pool for NFSv4 files's open owner */ extern int g_total_num_files_delegated; extern int g_max_files_delegatable; #ifdef DEBUG_SAL extern struct glist_head state_v4_all; extern struct glist_head state_owners_all; #endif #endif /* SAL_DATA_H */ /** @} */ nfs-ganesha-6.5/src/include/sal_functions.h000066400000000000000000001022221473756622300210260ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file sal_functions.h * @brief Routines in the state abstraction layer * @note not called by other header files. */ #ifndef SAL_FUNCTIONS_H #define SAL_FUNCTIONS_H #include #include "sal_data.h" #include "fsal.h" #include "gsh_recovery.h" /** * @brief Divisions in state and clientid tables. */ #define PRIME_STATE 17 /***************************************************************************** * * Misc functions * *****************************************************************************/ const char *state_err_str(state_status_t err); state_status_t state_error_convert(fsal_status_t fsal_status); nfsstat4 nfs4_Errno_state(state_status_t error); nfsstat3 nfs3_Errno_state(state_status_t error); const char *state_owner_type_to_str(state_owner_type_t type); bool different_owners(state_owner_t *owner1, state_owner_t *owner2); int display_owner(struct display_buffer *dspbuf, state_owner_t *owner); void inc_state_owner_ref(state_owner_t *owner); bool hold_state_owner_ref(state_owner_t *owner); void dec_state_owner_ref(state_owner_t *owner); void free_state_owner(state_owner_t *owner); #define LogStateOwner(note, owner) \ do { \ if (isFullDebug(COMPONENT_STATE)) { \ char str[LOG_BUFF_LEN]; \ struct display_buffer dspbuf = { sizeof(str), str, \ str }; \ display_owner(&dspbuf, owner); \ LogFullDebug(COMPONENT_STATE, "%s%s", note, str); \ } \ } while (0) /** * @brief Acquire exclusive st_lock and set no_cleanup=true * * @param[in,out] obj the object whose state_hdl->st_lock is to be * acquired and state_hdl->no_cleanup needs to be set */ #define STATELOCK_lock(obj) \ do { \ PTHREAD_MUTEX_lock(&(obj)->state_hdl->st_lock); \ (obj)->state_hdl->no_cleanup = true; \ } while (0) /** * @brief Drop st_lock and set no_cleanup=false * * @param[in,out] obj the object whose state_hdl->st_lock is to be * dropped and state_hdl->no_cleanup needs to be cleared */ #define STATELOCK_unlock(obj) \ do { \ (obj)->state_hdl->no_cleanup = false; \ PTHREAD_MUTEX_unlock(&(obj)->state_hdl->st_lock); \ } while (0) state_owner_t *get_state_owner(care_t care, state_owner_t *pkey, state_owner_init_t init_owner, bool_t *isnew); void state_wipe_file(struct fsal_obj_handle *obj); #ifdef DEBUG_SAL void dump_all_owners(void); #endif void state_release_export(struct gsh_export *exp); uint64_t get_unique_server_id(void); bool state_unlock_err_ok(state_status_t status); /** * @brief Initialize a state handle * * @param[in,out] ostate State handle to initialize * @param[in] type Type of handle * @param[in] obj Object owning handle */ static inline void state_hdl_init(struct state_hdl *ostate, object_file_type_t type, struct fsal_obj_handle *obj) { memset(ostate, 0, sizeof(*ostate)); switch (type) { case REGULAR_FILE: PTHREAD_MUTEX_init(&ostate->st_lock, NULL); glist_init(&ostate->file.list_of_states); glist_init(&ostate->file.layoutrecall_list); glist_init(&ostate->file.lock_list); glist_init(&ostate->file.nlm_share_list); ostate->file.obj = obj; break; case DIRECTORY: PTHREAD_RWLOCK_init(&ostate->jct_lock, NULL); glist_init(&ostate->dir.export_roots); break; default: break; } } /** * @brief Clean up a state handle * * @param[in] state_hdl State handle to clean up */ static inline void state_hdl_cleanup(struct state_hdl *state_hdl, object_file_type_t type) { switch (type) { case REGULAR_FILE: PTHREAD_MUTEX_destroy(&state_hdl->st_lock); break; case DIRECTORY: PTHREAD_RWLOCK_destroy(&state_hdl->jct_lock); break; default: break; } } /***************************************************************************** * * 9P State functions * *****************************************************************************/ #ifdef _USE_9P int compare_9p_owner(state_owner_t *owner1, state_owner_t *owner2); int display_9p_owner(struct display_buffer *dspbuf, state_owner_t *owner); state_owner_t *get_9p_owner(sockaddr_t *client_addr, uint32_t proc_id); int Init_9p_hash(void); #endif /****************************************************************************** * * NLM Owner functions * ******************************************************************************/ void free_nsm_client(state_nsm_client_t *client); /* These refcount functions must not be called holding the ssc_mutex */ void _inc_nsm_client_ref(state_nsm_client_t *client, char *file, int line, char *function); #define inc_nsm_client_ref(client) \ _inc_nsm_client_ref(client, (char *)__FILE__, __LINE__, \ (char *)__func__) void _dec_nsm_client_ref(state_nsm_client_t *client, char *file, int line, char *function); #define dec_nsm_client_ref(client) \ _dec_nsm_client_ref(client, (char *)__FILE__, __LINE__, \ (char *)__func__) int display_nsm_client(struct display_buffer *dspbuf, state_nsm_client_t *key); state_nsm_client_t *get_nsm_client(care_t care, char *caller_name); void inc_nlm_client_ref(state_nlm_client_t *client); void dec_nlm_client_ref(state_nlm_client_t *client); state_nlm_client_t *get_nlm_client(care_t care, SVCXPRT *xprt, state_nsm_client_t *nsm_client, char *caller_name); void free_nlm_owner(state_owner_t *powner); int display_nlm_owner(struct display_buffer *dspbuf, state_owner_t *owner); int compare_nlm_owner(state_owner_t *owner1, state_owner_t *owner2); state_owner_t *get_nlm_owner(care_t care, state_nlm_client_t *client, netobj *oh, uint32_t svid); int Init_nlm_hash(void); /****************************************************************************** * * NLM State functions * ******************************************************************************/ int display_nlm_state(struct display_buffer *dspbuf, state_t *key); int compare_nlm_state(state_t *state1, state_t *state2); int Init_nlm_state_hash(void); void dec_nlm_state_ref(state_t *state); int get_nlm_state(enum state_type state_type, struct fsal_obj_handle *state_obj, state_owner_t *state_owner, care_t care, uint32_t nsm_state, state_t **pstate); /****************************************************************************** * * NFS4 Client ID functions * ******************************************************************************/ nfsstat4 clientid_error_to_nfsstat(clientid_status_t err); static inline nfsstat4 clientid_error_to_nfsstat_no_expire(clientid_status_t err) { nfsstat4 rc = clientid_error_to_nfsstat(err); if (rc == NFS4ERR_EXPIRED) rc = NFS4ERR_STALE_CLIENTID; return rc; } const char *clientid_error_to_str(clientid_status_t err); int nfs_Init_client_id(void); uint64_t get_total_count_of_open_states(void); clientid_status_t nfs_client_id_get_unconfirmed(clientid4 clientid, nfs_client_id_t **pclient_rec); clientid_status_t nfs_client_id_get_confirmed(clientid4 clientid, nfs_client_id_t **client_rec); nfs_client_id_t *create_client_id(clientid4 clientid, nfs_client_record_t *client_record, nfs_client_cred_t *credential, uint32_t minorversion); clientid_status_t nfs_client_id_insert(nfs_client_id_t *clientid); int remove_confirmed_client_id(nfs_client_id_t *clientid); int remove_unconfirmed_client_id(nfs_client_id_t *clientid); clientid_status_t nfs_client_id_confirm(nfs_client_id_t *clientid, log_components_t component); bool clientid_has_state(nfs_client_id_t *clientid); bool nfs_client_id_expire(nfs_client_id_t *clientid, bool make_stale, bool force_expire); extern uint32_t num_of_curr_expired_clients; int reap_expired_client_list(nfs_client_id_t *clientid); void remove_client_from_expired_client_list(nfs_client_id_t *active_clientid); #define DISPLAY_CLIENTID_SIZE 37 /* 36 chars + null terminator */ int display_clientid(struct display_buffer *dspbuf, clientid4 clientid); clientid4 new_clientid(void); void new_clientid_verifier(char *verf); int display_client_id_rec(struct display_buffer *dspbuf, nfs_client_id_t *clientid); #define CLIENTNAME_BUFSIZE (NFS4_OPAQUE_LIMIT * 2 + 1) int display_clientid_name(struct display_buffer *dspbuf, nfs_client_id_t *clientid); void free_client_id(nfs_client_id_t *clientid); void nfs41_foreach_client_callback(bool (*cb)(nfs_client_id_t *cl, void *state), void *state); int destroy_all_client_connections(const struct gsh_client *); bool client_id_has_state(nfs_client_id_t *clientid); int32_t inc_client_id_ref(nfs_client_id_t *clientid); int32_t dec_client_id_ref(nfs_client_id_t *clientid); int display_client_record(struct display_buffer *dspbuf, nfs_client_record_t *record); void free_client_record(nfs_client_record_t *record); int32_t inc_client_record_ref(nfs_client_record_t *record); int32_t dec_client_record_ref(nfs_client_record_t *record); nfs_client_record_t *get_client_record(const char *const value, const size_t len, const uint32_t pnfs_flags, const uint32_t server_addr); /****************************************************************************** * * NFS4.1 Session ID functions * ******************************************************************************/ int display_session_id(struct display_buffer *dspbuf, char *session_id); int display_session(struct display_buffer *dspbuf, nfs41_session_t *session); int display_nfs4_operations(struct display_buffer *dspbuf, nfs_opnum4 *opcodes, uint32_t opcode_num); int32_t _inc_session_ref(nfs41_session_t *session, const char *func, int line); #define inc_session_ref(s) _inc_session_ref(s, __func__, __LINE__) int32_t _dec_session_ref(nfs41_session_t *session, const char *func, int line); #define dec_session_ref(s) _dec_session_ref(s, __func__, __LINE__) int nfs41_Init_session_id(void); int nfs41_Session_Set(nfs41_session_t *session_data); int nfs41_Session_Get_Pointer(char sessionid[NFS4_SESSIONID_SIZE], nfs41_session_t **session_data); int nfs41_Session_Del(nfs41_session_t *session); void nfs41_Build_sessionid(clientid4 *clientid, char *sessionid); void nfs41_Session_PrintAll(void); bool check_session_conn(nfs41_session_t *session, compound_data_t *data, bool can_associate); void nfs41_Session_Add_Connection(nfs41_session_t *, SVCXPRT *); void nfs41_Session_Remove_Connection(nfs41_session_t *, SVCXPRT *); void nfs41_Session_Destroy_Backchannel_For_Xprt(nfs41_session_t *, SVCXPRT *); int nfs41_Session_Destroy_All_Connections(nfs41_session_t *); /****************************************************************************** * * NFSv4 Stateid functions * ******************************************************************************/ #define DISPLAY_STATEID_OTHER_SIZE (DISPLAY_CLIENTID_SIZE + 72) int display_stateid_other(struct display_buffer *dspbuf, char *other); int display_stateid(struct display_buffer *dspbuf, state_t *state); /* 17 is 7 for " seqid=" and 10 for MAX_UINT32 digits */ #define DISPLAY_STATEID4_SIZE (DISPLAY_STATEID_OTHER_SIZE + 17) int display_stateid4(struct display_buffer *dspbuf, stateid4 *stateid); void nfs4_BuildStateId_Other(nfs_client_id_t *clientid, char *other); #define STATEID_NO_SPECIAL 0 /*< No special stateids */ #define STATEID_SPECIAL_ALL_0 2 /*< Allow anonymous */ #define STATEID_SPECIAL_ALL_1 4 /*< Allow read-bypass */ #define STATEID_SPECIAL_CURRENT 8 /*< Allow current */ /* The following flag tells nfs4_Check_Stateid this is a close call * and to ignore stateid that have valid clientid portion, but the * counter portion doesn't reference a currently open file. */ #define STATEID_SPECIAL_CLOSE_40 0x40 #define STATEID_SPECIAL_CLOSE_41 0x80 #define STATEID_SPECIAL_ANY 0x3F #define STATEID_SPECIAL_FOR_LOCK (STATEID_SPECIAL_CURRENT) #define STATEID_SPECIAL_FOR_CLOSE_40 (STATEID_SPECIAL_CLOSE_40) #define STATEID_SPECIAL_FOR_CLOSE_41 \ (STATEID_SPECIAL_CLOSE_41 | STATEID_SPECIAL_CURRENT) nfsstat4 nfs4_Check_Stateid(stateid4 *stateid, struct fsal_obj_handle *fsal_obj, state_t **state, compound_data_t *data, int flags, seqid4 owner_seqid, bool check_seqid, const char *tag); void update_stateid(state_t *state, stateid4 *stateid, compound_data_t *data, const char *tag); int nfs4_Init_state_id(void); /** * @brief Take a reference on state_t * * @param[in] state The state_t to ref */ static inline void inc_state_t_ref(struct state_t *state) { int32_t refcount = atomic_inc_int32_t(&state->state_refcount); LogFullDebug(COMPONENT_STATE, "State %p state_refcount now %" PRIi32, state, refcount); } void dec_nfs4_state_ref(struct state_t *state); /** * @brief Relinquish a reference on any State * * @param[in] state The NLM State to release */ static inline void dec_state_t_ref(struct state_t *state) { #ifdef _USE_NLM if (state->state_type == STATE_TYPE_NLM_LOCK || state->state_type == STATE_TYPE_NLM_SHARE) dec_nlm_state_ref(state); else #endif /* _USE_NLM */ dec_nfs4_state_ref(state); } state_status_t nfs4_State_Set(state_t *state_data); struct state_t *nfs4_State_Get_Pointer(char *other); bool nfs4_State_Del(state_t *state); void nfs_State_PrintAll(void); struct state_t *nfs4_State_Get_Obj(struct fsal_obj_handle *obj, state_owner_t *owner); /****************************************************************************** * * NFSv4 Lease functions * ******************************************************************************/ int reserve_lease(nfs_client_id_t *clientid); bool reserve_lease_or_expire(nfs_client_id_t *clientid, bool update, state_owner_t **owner); bool update_lease(nfs_client_id_t *clientid); static inline void update_lease_simple(nfs_client_id_t *clientid) { bool unexpire; PTHREAD_MUTEX_lock(&clientid->cid_mutex); unexpire = update_lease(clientid); PTHREAD_MUTEX_unlock(&clientid->cid_mutex); if (unexpire) remove_client_from_expired_client_list(clientid); } bool valid_lease(nfs_client_id_t *clientid, bool is_from_reaper); /****************************************************************************** * * NFSv4 Owner functions * ******************************************************************************/ void uncache_nfs4_owner(struct state_nfs4_owner_t *nfs4_owner); void free_nfs4_owner(state_owner_t *owner); int display_nfs4_owner(struct display_buffer *dspbuf, state_owner_t *owner); int compare_nfs4_owner(state_owner_t *owner1, state_owner_t *owner2); /** * @brief Convert an open_owner to an owner name * * @param[in] nfsowner Open owner as specified in NFS * @param[out] name_owner Name used as key in owner table */ static inline void convert_nfs4_open_owner(open_owner4 *nfsowner, state_nfs4_owner_name_t *name_owner) { name_owner->son_owner_len = nfsowner->owner.owner_len; name_owner->son_owner_val = nfsowner->owner.owner_val; } /** * @brief Convert a lock_owner to an owner name * * @param[in] nfsowner Open owner as specified in NFS * @param[out] name_owner Name used as key in owner table */ static inline void convert_nfs4_lock_owner(lock_owner4 *nfsowner, state_nfs4_owner_name_t *name_owner) { name_owner->son_owner_len = nfsowner->owner.owner_len; name_owner->son_owner_val = nfsowner->owner.owner_val; } void nfs4_owner_PrintAll(void); state_owner_t *create_nfs4_owner(state_nfs4_owner_name_t *name, nfs_client_id_t *clientid, state_owner_type_t type, state_owner_t *related_owner, unsigned int init_seqid, bool_t *pisnew, care_t care, bool_t confirm); int Init_nfs4_owner(void); nfsstat4 Process_nfs4_conflict(LOCK4denied *denied, state_owner_t *holder, fsal_lock_param_t *conflict, compound_data_t *data); void Release_nfs4_denied(LOCK4denied *denied); void Copy_nfs4_denied(LOCK4denied *denied_dst, LOCK4denied *denied_src); void Copy_nfs4_state_req(state_owner_t *owner, seqid4 seqid, nfs_argop4 *args, struct fsal_obj_handle *obj, nfs_resop4 *resp, const char *tag); bool Check_nfs4_seqid(state_owner_t *owner, seqid4 seqid, nfs_argop4 *args, struct fsal_obj_handle *obj, nfs_resop4 *resp, const char *tag); /** * @brief Determine if an NFS v4 owner has state associated with it * * Note that this function is racy and is only suitable for calling * from places that should not have other activity pending against * the owner. Currently it is only called from setclientid which should * be fine. * * @param[in] owner The owner of interest * * @retval true if the owner has state */ static inline bool owner_has_state(state_owner_t *owner) { bool live_state; struct state_nfs4_owner_t *nfs4_owner = &owner->so_owner.so_nfs4_owner; /* If the owner is on the cached owners list, there can't be * active state. */ if (atomic_fetch_time_t(&nfs4_owner->so_cache_expire) != 0) return false; PTHREAD_MUTEX_lock(&owner->so_mutex); live_state = !glist_empty(&nfs4_owner->so_state_list); PTHREAD_MUTEX_unlock(&owner->so_mutex); return live_state; } /****************************************************************************** * * Lock functions * ******************************************************************************/ state_status_t state_lock_init(void); void log_lock(log_components_t component, log_levels_t debug, const char *reason, struct fsal_obj_handle *obj, state_owner_t *owner, fsal_lock_param_t *lock, char *file, int line, char *function); #define LogLock(component, debug, reason, obj, owner, lock) \ log_lock(component, debug, reason, obj, owner, lock, (char *)__FILE__, \ __LINE__, (char *)__func__) #define LOCK_RQST_FMT \ "lock: [ptr = {},id = {},type = {},start = {},length = {}]" #define LOCK_RQST_VARS(_lock, _handle) \ (_lock), (uint64_t)(_handle)->fileid, (_lock)->lock_type, \ (_lock)->lock_start, (_lock)->lock_length #define LOCK__REQUEST_AUTO_TRACEPOINT(_lock, _handle, event, log_level, \ format, ...) \ GSH_AUTO_TRACEPOINT(lock, event, log_level, \ LOCK_RQST_FMT " | " format, \ LOCK_RQST_VARS(_lock, _handle), ##__VA_ARGS__) #define LOCK_FMT \ "lock: [fileid = {},type = {},start = {},length = {}, blocked={}]" #define LOCK_VARS(_lock) \ ((uint64_t)_lock->sle_obj->fileid), _lock->sle_lock.lock_type, \ _lock->sle_lock.lock_start, (_lock)->sle_lock.lock_length, \ (_lock->sle_block_data ? \ _lock->sle_block_data->sbd_block_type : \ STATE_BLOCK_NONE) #define LOCK_AUTO_TRACEPOINT(_lock, event, log_level, format, ...) \ GSH_AUTO_TRACEPOINT(lock, event, log_level, LOCK_FMT " | " format, \ LOCK_VARS((_lock)), ##__VA_ARGS__) void dump_all_locks(const char *label); state_status_t state_add_grant_cookie(struct fsal_obj_handle *obj, void *cookie, int cookie_size, state_lock_entry_t *lock_entry, state_cookie_entry_t **cookie_entry); state_status_t state_find_grant(void *cookie, int cookie_size, state_cookie_entry_t **cookie_entry); void state_complete_grant(state_cookie_entry_t *cookie_entry); state_status_t state_cancel_grant(state_cookie_entry_t *cookie_entry); state_status_t state_release_grant(state_cookie_entry_t *cookie_entry); state_status_t state_test(struct fsal_obj_handle *obj, state_t *state, state_owner_t *owner, fsal_lock_param_t *lock, /* owner that holds conflicting lock */ state_owner_t **holder, /* description of conflicting lock */ fsal_lock_param_t *conflict); state_status_t state_lock(struct fsal_obj_handle *obj, state_owner_t *owner, state_t *state, state_blocking_t blocking, lock_protocol_t protocol, state_block_data_t **block_data, fsal_lock_param_t *lock, /* owner that holds conflicting lock */ state_owner_t **holder, /* description of conflicting lock */ fsal_lock_param_t *conflict); state_status_t state_unlock(struct fsal_obj_handle *obj, state_t *state, state_owner_t *owner, bool state_applies, int32_t nsm_state, fsal_lock_param_t *lock); state_status_t state_cancel(struct fsal_obj_handle *obj, state_owner_t *owner, fsal_lock_param_t *lock); state_status_t state_nlm_notify(state_nsm_client_t *nsmclient, bool state_applies, int32_t state); void state_unlock_all(struct fsal_obj_handle *obj, state_t *state); void state_nfs4_owner_unlock_all(state_owner_t *owner); void state_export_unlock_all(void); void state_lock_wipe(struct state_hdl *hstate); void cancel_all_nlm_blocked(void); state_status_t state_cancel_blocked(state_lock_entry_t *lock_entry); void lock_entry_dec_ref(state_lock_entry_t *); /****************************************************************************** * * NFSv4 State Management functions * ******************************************************************************/ #define state_add_impl(o, t, d, i, s, r) \ _state_add_impl(o, t, d, i, s, r, __func__, __LINE__) state_status_t _state_add_impl(struct fsal_obj_handle *obj, enum state_type state_type, union state_data *state_data, state_owner_t *owner_input, state_t **state, struct state_refer *refer, const char *func, int line); #define state_add(o, t, d, i, s, r) \ _state_add(o, t, d, i, s, r, __func__, __LINE__) state_status_t _state_add(struct fsal_obj_handle *obj, enum state_type state_type, union state_data *state_data, state_owner_t *owner_input, state_t **state, struct state_refer *refer, const char *func, int line); state_status_t state_set(state_t *state); #define state_del_locked(s) _state_del_locked(s, __func__, __LINE__) void _state_del_locked(state_t *state, const char *func, int line); void state_del(state_t *state); /** * @brief Get a reference to the obj owning a state * * @note state_mutex MUST be held * * @param[in] state State to get from * @return obj handle on success */ static inline struct fsal_obj_handle *get_state_obj_ref_locked(state_t *state) { if (state->state_obj) { state->state_obj->obj_ops->get_ref(state->state_obj); } return state->state_obj; } /** * @brief Get a reference to the obj owning a state * * Takes state_mutex, so it should not be held. * * @param[in] state State to get from * @return obj handle on success */ static inline struct fsal_obj_handle *get_state_obj_ref(state_t *state) { struct fsal_obj_handle *obj; PTHREAD_MUTEX_lock(&state->state_mutex); obj = get_state_obj_ref_locked(state); PTHREAD_MUTEX_unlock(&state->state_mutex); return obj; } static inline bool state_same_export(state_t *state, struct gsh_export *state_export) { bool same = false; PTHREAD_MUTEX_lock(&state->state_mutex); if (state->state_export != NULL) same = state->state_export == state_export; PTHREAD_MUTEX_unlock(&state->state_mutex); return same; } static inline uint16_t state_export_id(state_t *state) { uint16_t export_id = UINT16_MAX; PTHREAD_MUTEX_lock(&state->state_mutex); if (state->state_export != NULL) export_id = state->state_export->export_id; PTHREAD_MUTEX_unlock(&state->state_mutex); return export_id; } static inline state_owner_t *get_state_owner_ref(state_t *state) { state_owner_t *owner = NULL; PTHREAD_MUTEX_lock(&state->state_mutex); if (state->state_owner != NULL) { owner = state->state_owner; inc_state_owner_ref(owner); } PTHREAD_MUTEX_unlock(&state->state_mutex); return owner; } static inline bool state_owner_confirmed(state_t *state) { bool confirmed = false; PTHREAD_MUTEX_lock(&state->state_mutex); if (state->state_owner != NULL) { confirmed = state->state_owner->so_owner.so_nfs4_owner.so_confirmed; } PTHREAD_MUTEX_unlock(&state->state_mutex); return confirmed; } static inline bool state_same_owner(state_t *state, state_owner_t *owner) { bool same = false; PTHREAD_MUTEX_lock(&state->state_mutex); if (state->state_owner != NULL) same = state->state_owner == owner; PTHREAD_MUTEX_unlock(&state->state_mutex); return same; } bool get_state_obj_export_owner_refs(state_t *state, struct fsal_obj_handle **obj, struct gsh_export **state_export, state_owner_t **owner); void state_nfs4_state_wipe(struct state_hdl *ostate); enum nfsstat4 release_lock_owner(state_owner_t *owner); void release_openstate(state_owner_t *owner); void state_export_release_nfs4_state(void); void revoke_owner_delegs(state_owner_t *client_owner); #ifdef DEBUG_SAL void dump_all_states(void); #endif bool check_and_remove_conflicting_client(struct state_hdl *file_state_hdl); /****************************************************************************** * * Delegations functions * ******************************************************************************/ state_status_t acquire_lease_lock(struct state_hdl *ostate, state_owner_t *owner, state_t *state); state_status_t release_lease_lock(struct fsal_obj_handle *obj, state_t *state); bool init_deleg_heuristics(struct fsal_obj_handle *obj); bool deleg_supported(struct fsal_obj_handle *obj, struct fsal_export *fsal_export, struct export_perms *export_perms, uint32_t share_access); bool can_we_grant_deleg(struct state_hdl *ostate, state_t *open_state); bool should_we_grant_deleg(struct state_hdl *ostate, nfs_client_id_t *client, state_t *open_state, OPEN4args *args, OPEN4resok *resok, state_owner_t *owner, bool *prerecall); void init_new_deleg_state(union state_data *deleg_state, open_delegation_type4 sd_type, nfs_client_id_t *clientid); void deleg_heuristics_recall(struct fsal_obj_handle *obj, state_owner_t *owner, struct state_t *deleg); void get_deleg_perm(nfsace4 *permissions, open_delegation_type4 type); void update_delegation_stats(struct state_hdl *ostate, state_owner_t *owner); state_status_t delegrecall_impl(struct fsal_obj_handle *obj); nfsstat4 deleg_revoke(struct fsal_obj_handle *obj, struct state_t *deleg_state); void state_deleg_revoke(struct fsal_obj_handle *obj, state_t *state); bool state_deleg_conflict(struct fsal_obj_handle *obj, bool write); bool state_deleg_conflict_impl(struct fsal_obj_handle *obj, bool write); bool is_write_delegated(struct fsal_obj_handle *obj, nfs_client_id_t **client); void reset_cbgetattr_stats(struct fsal_obj_handle *obj); nfsstat4 handle_deleg_getattr(struct fsal_obj_handle *obj, nfs_client_id_t *client); int cbgetattr_impl(struct fsal_obj_handle *obj, nfs_client_id_t *client, struct gsh_export *ctx_exp); /** * @brief Decrement g_total_num_files_delegated if the file has no delegations * @note st_lock is held */ #define DEC_G_Total_Num_Files_Delegated(curr_delegations) \ do { \ if ((curr_delegations == 0)) { \ int32_t new_total_num_files_delegated = \ atomic_dec_int32_t( \ &g_total_num_files_delegated); \ LogFullDebug( \ COMPONENT_STATE, \ "num_files_delegated decremented to %" PRId32, \ new_total_num_files_delegated); \ } \ } while (0) /****************************************************************************** * * Layout functions * ******************************************************************************/ state_status_t state_add_segment(state_t *state, struct pnfs_segment *segment, void *fsal_data, bool return_on_close); state_status_t state_delete_segment(state_layout_segment_t *segment); state_status_t state_lookup_layout_state(struct fsal_obj_handle *obj, state_owner_t *owner, layouttype4 type, state_t **state); void revoke_owner_layouts(state_owner_t *client_owner); /****************************************************************************** * * Share functions * ******************************************************************************/ #define OPEN4_SHARE_ACCESS_NONE 0 enum share_bypass_modes { SHARE_BYPASS_NONE, SHARE_BYPASS_READ, SHARE_BYPASS_V3_WRITE }; state_status_t state_nlm_share(struct fsal_obj_handle *obj, int share_access, int share_deny, state_owner_t *owner, state_t *state, bool reclaim, bool unshare); void state_share_wipe(struct state_hdl *ostate); void state_export_unshare_all(void); /****************************************************************************** * * Async functions * ******************************************************************************/ /* Schedule Async Work */ state_status_t state_async_schedule(state_async_queue_t *arg); /* Schedule lock notifications */ state_status_t state_block_schedule(state_lock_entry_t *lock_entry); /* Schedule unlock for non polled lock */ state_status_t state_block_cancel_schedule(state_lock_entry_t *lock_entry); /* Schedule lock eligibility test */ state_status_t test_blocking_lock_eligibility_schedule(state_lock_entry_t *lock_entry); /* Signal Async Work */ void signal_async_work(void); state_status_t state_async_init(void); state_status_t state_async_shutdown(void); void grant_blocked_lock_upcall(struct fsal_obj_handle *obj, void *owner, fsal_lock_param_t *lock); void available_blocked_lock_upcall(struct fsal_obj_handle *obj, void *owner, fsal_lock_param_t *lock); void process_blocked_lock_upcall(state_lock_entry_t *lock_entry); void blocked_lock_polling(struct fridgethr_context *ctx); /****************************************************************************** * * NFSv4 Recovery functions * ******************************************************************************/ /* Grace period handling */ extern int32_t reclaim_completes; /* atomic */ int nfs_start_grace(nfs_grace_start_t *gsp); void nfs_end_grace(void); bool nfs_in_grace(void); bool nfs_get_grace_status(bool want_grace); void nfs_put_grace_status(void); void nfs_maybe_start_grace(void); bool nfs_grace_is_member(void); int nfs_recovery_get_nodeid(char **pnodeid); void nfs_try_lift_grace(void); void nfs_wait_for_grace_enforcement(void); void nfs_notify_grace_waiters(void); void nfs_wait_for_grace_norefs(void); void nfs_notify_grace_norefs_waiters(void); /* v4 Client stable-storage database management */ void nfs4_add_clid(nfs_client_id_t *); void nfs4_rm_clid(nfs_client_id_t *); void nfs4_chk_clid(nfs_client_id_t *); /* Delegation revocation tracking */ bool nfs4_check_deleg_reclaim(nfs_client_id_t *, nfs_fh4 *); void nfs4_record_revoke(nfs_client_id_t *, nfs_fh4 *); /* Recovery backend management */ const char *recovery_backend_str(enum recovery_backend recovery_backend); int nfs4_recovery_init(void); void nfs4_recovery_shutdown(void); /** * @brief Check to see if an object is a junction * * @param[in] obj Object to check * @return true if junction, false otherwise */ static inline bool obj_is_junction(struct fsal_obj_handle *obj) { bool res = false; if (obj->type != DIRECTORY) return false; PTHREAD_RWLOCK_rdlock(&obj->state_hdl->jct_lock); if ((obj->state_hdl->dir.junction_export != NULL || atomic_fetch_int32_t(&obj->state_hdl->dir.exp_root_refcount) != 0)) res = true; PTHREAD_RWLOCK_unlock(&obj->state_hdl->jct_lock); return res; } typedef clid_entry_t *(*add_clid_entry_hook)(char *); typedef rdel_fh_t *(*add_rfh_entry_hook)(clid_entry_t *, char *); struct nfs4_recovery_backend { int (*recovery_init)(void); void (*recovery_shutdown)(void); void (*recovery_read_clids)(nfs_grace_start_t *gsp, add_clid_entry_hook add_clid, add_rfh_entry_hook add_rfh); void (*add_clid)(nfs_client_id_t *); void (*rm_clid)(nfs_client_id_t *); void (*add_revoke_fh)(nfs_client_id_t *, nfs_fh4 *); void (*end_grace)(void); void (*maybe_start_grace)(void); bool (*try_lift_grace)(void); void (*set_enforcing)(void); bool (*grace_enforcing)(void); bool (*is_member)(void); int (*get_nodeid)(char **pnodeid); }; void fs_backend_init(struct nfs4_recovery_backend **); void fs_ng_backend_init(struct nfs4_recovery_backend **); int load_recovery_param_from_conf(config_file_t, struct config_error_type *); #endif /* SAL_FUNCTIONS_H */ /** @} */ nfs-ganesha-6.5/src/include/sal_metrics.h000066400000000000000000000043401473756622300204660ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2024 Google LLC * Contributor : Yoni Couriel yonic@google.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file sal_metrics.h * @brief SAL metrics module */ #ifndef SAL_METRICS_H #define SAL_METRICS_H #include "monitoring.h" #include "nfsv41.h" #include "xprt_handler.h" typedef enum sal_metrics__lock_type { SAL_METRICS_HOLDERS = 0, SAL_METRICS_WAITERS, SAL_METRICS_LOCK_TYPE_COUNT, } sal_metrics__lock_type; /* Total Number of Confirmed Clients */ void sal_metrics__confirmed_clients(int64_t num); /* Total Number of Clients Lease Expired Events */ void sal_metrics__lease_expire(void); /* Total number of clients per state-protection type */ void sal_metrics__client_state_protection(state_protect_how4); /* Total Number of Locks Record Count */ void sal_metrics__locks_inc(sal_metrics__lock_type); void sal_metrics__locks_dec(sal_metrics__lock_type); /* Distribution of number of session-connections across sessions */ void sal_metrics__session_connections(int64_t num); /* Total number of denied session-and-xprt associations across sessions */ void sal_metrics__xprt_association_denied(void); /* Total number of xprts per custom-data status */ void sal_metrics__xprt_custom_data_status(xprt_custom_data_status_t); /* Distribution of number of sessions associated with each xprt */ void sal_metrics__xprt_sessions(int64_t num); void sal_metrics__init(void); #endif /* !SAL_METRICS_H */ nfs-ganesha-6.5/src/include/sal_shared.h000066400000000000000000000027201473756622300202660ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup SAL State abstraction layer * @{ */ /** * @file sal_shared.h * @brief Data structures for state management. */ #ifndef SAL_SHARED_H #define SAL_SHARED_H /** * @brief Type of state */ enum state_type { STATE_TYPE_NONE = 0, STATE_TYPE_SHARE = 1, STATE_TYPE_DELEG = 2, STATE_TYPE_LOCK = 3, STATE_TYPE_LAYOUT = 4, STATE_TYPE_NLM_LOCK = 5, STATE_TYPE_NLM_SHARE = 6, STATE_TYPE_9P_FID = 7, STATE_TYPE_MAX = 8, }; #endif /* SAL_SHARED_H */ /** @} */ nfs-ganesha-6.5/src/include/server_stats.h000066400000000000000000000042051473756622300207050ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2013 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @defgroup Server statistics management * @{ */ /** * @file server_stats.h * @author Jim Lieb * @brief Server statistics */ #ifndef SERVER_STATS_H #define SERVER_STATS_H #include void server_stats_nfs_done(nfs_request_t *reqdata, int rc, bool dup); #ifdef _USE_9P void server_stats_9p_done(u8 msgtype, struct _9p_request_data *req9p); #endif void server_stats_io_done(size_t requested, size_t transferred, bool success, bool is_write); void server_stats_compound_done(int num_ops, int status); void server_stats_nfsv4_op_done(int proto_op, struct timespec *start_time, int status); void server_stats_transport_done(struct gsh_client *client, uint64_t rx_bytes, uint64_t rx_pkt, uint64_t rx_err, uint64_t tx_bytes, uint64_t tx_pkt, uint64_t tx_err); /* For delegations */ void inc_grants(struct gsh_client *client); void dec_grants(struct gsh_client *client); void inc_revokes(struct gsh_client *client); void inc_recalls(struct gsh_client *client); void inc_failed_recalls(struct gsh_client *client); #endif /* !SERVER_STATS_H */ /** @} */ nfs-ganesha-6.5/src/include/server_stats_private.h000066400000000000000000000346141473756622300224460ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2013 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @defgroup Server statistics management * @{ */ /** * @file server_stats.h * @author Jim Lieb * @brief Server statistics private interfaces */ #ifndef SERVER_STATS_PRIVATE_H #define SERVER_STATS_PRIVATE_H #include "sal_data.h" /** * @brief Server request statistics * * These are the stats we keep */ /* Forward references to build pointers to private defs. */ #ifdef _USE_NFS3 struct nfsv3_stats; struct mnt_stats; #endif #ifdef _USE_NLM struct nlmv4_stats; #endif #ifdef _USE_RQUOTA struct rquota_stats; #endif struct nfsv40_stats; struct nfsv41_stats; struct nfsv42_stats; struct deleg_stats; #ifdef _USE_9P struct _9p_stats; #endif #ifdef _USE_NFS3 struct clnt_allops_v3_stats; #endif struct clnt_allops_v4_stats; #ifdef _USE_NLM struct clnt_allops_nlm4_stats; #endif struct gsh_stats { #ifdef _USE_NFS3 struct nfsv3_stats *nfsv3; struct mnt_stats *mnt; #endif #ifdef _USE_NLM struct nlmv4_stats *nlm4; #endif #ifdef _USE_RQUOTA struct rquota_stats *rquota; #endif struct nfsv40_stats *nfsv40; struct nfsv41_stats *nfsv41; struct nfsv41_stats *nfsv42; struct deleg_stats *deleg; #ifdef _USE_9P struct _9p_stats *_9p; #endif }; struct gsh_clnt_allops_stats { #ifdef _USE_NFS3 struct clnt_allops_v3_stats *nfsv3; #endif struct clnt_allops_v4_stats *nfsv4; #ifdef _USE_NLM struct clnt_allops_nlm_stats *nlm4; #endif }; /** * @brief Server by client IP statistics * * toplevel structure for statistics gathering. This is * only shared between client_mgr.c and server_stats.c * client_mgr.c needs to know about it to properly size * the allocation. * * NOTE: see below. The client member must be the * last because the gsh_client structure has * a variable length array at the end (for the * key). */ struct server_stats { struct gsh_stats st; struct gsh_clnt_allops_stats c_all; /* for all ops stats */ struct gsh_client client; /* must be last element! */ }; /** * @brief Server by export id statistics * * Top level structure only shared between export_mgr.c and * server_stats.c */ struct export_stats { struct gsh_stats st; struct gsh_export export; }; /** * @brief Auth stats information */ struct auth_stats { uint64_t total; uint64_t latency; uint64_t max; uint64_t min; }; #ifdef USE_DBUS /* * Common definitions */ #define TYPE_ID "q" #define TYPE_STRING "s" /* * Bits for exports/clients related info * * We may like the protocols related info like following: * * { * string * bool * } * . * . * . */ #ifdef _USE_NFS3 #define STAT_TYPE_NFSV3 "(sb)" #define STAT_TYPE_MNT "(sb)" #else #define STAT_TYPE_NFSV3 "" #define STAT_TYPE_MNT "" #endif #ifdef _USE_NLM #define STAT_TYPE_NLM "(sb)" #else #define STAT_TYPE_NLM "" #endif #ifdef _USE_RQUOTA #define STAT_TYPE_RQUOTA "(sb)" #else #define STAT_TYPE_RQUOTA "" #endif #define STAT_TYPE_NFSV40 "(sb)" #define STAT_TYPE_NFSV41 "(sb)" #define STAT_TYPE_NFSV42 "(sb)" #ifdef _USE_9P #define STAT_TYPE_9P "(sb)" #else #define STAT_TYPE_9P "" #endif #define PROTOCOLS_CONTAINER \ "(" STAT_TYPE_NFSV3 STAT_TYPE_MNT STAT_TYPE_NLM STAT_TYPE_RQUOTA \ STAT_TYPE_NFSV40 STAT_TYPE_NFSV41 STAT_TYPE_NFSV42 STAT_TYPE_9P \ ")" #define EXPORT_CONTAINER "(" TYPE_ID TYPE_STRING PROTOCOLS_CONTAINER "(tt))" #define STATE_STATS_REPLY "(ststst)" #define CLIENT_CONTAINER \ "(" TYPE_STRING PROTOCOLS_CONTAINER STATE_STATS_REPLY "(tt))" #define EXPORTS_REPLY \ { .name = "exports", .type = "a" EXPORT_CONTAINER, .direction = "out" } #define CLIENTS_REPLY \ { .name = "clients", .type = "a" CLIENT_CONTAINER, .direction = "out" } #define TOTAL_DESTROYED_CONNECTIONS_REPLY \ { .name = "connections_destroyed", .type = "i", .direction = "out" } /* Bits for introspect arg structures */ #define EXPORT_ID_ARG { .name = "exp_id", .type = "q", .direction = "in" } #define TIMESTAMP_REPLY { .name = "time", .type = "(tt)", .direction = "out" } #define IOSTATS_REPLY \ { .name = "read", .type = "(tttttt)", .direction = "out" }, \ { .name = "write", .type = "(tttttt)", .direction = "out" } #define CEIOSTATS_REPLY \ { .name = "read", .type = "(ttdt)", .direction = "out" }, \ { .name = "write", .type = "(ttdt)", .direction = "out" }, \ { .name = "other", .type = "(ttd)", .direction = "out" } #define CELOSTATS_REPLY \ { .name = "layout", .type = "(ttt)", .direction = "out" } #ifdef _USE_NFS3 #define CE_STATS_REPLY \ { .name = "clnt_v3", .type = "b", .direction = "out" }, \ CEIOSTATS_REPLY, \ { .name = "clnt_v40", .type = "b", .direction = "out" }, \ CEIOSTATS_REPLY, \ { .name = "clnt_v41", .type = "b", .direction = "out" }, \ CEIOSTATS_REPLY, CELOSTATS_REPLY, \ { .name = "clnt_v42", .type = "b", .direction = "out" }, \ CEIOSTATS_REPLY, CELOSTATS_REPLY #else #define CE_STATS_REPLY \ CEIOSTATS_REPLY, \ { .name = "clnt_v40", .type = "b", .direction = "out" }, \ CEIOSTATS_REPLY, \ { .name = "clnt_v41", .type = "b", .direction = "out" }, \ CEIOSTATS_REPLY, CELOSTATS_REPLY, \ { .name = "clnt_v42", .type = "b", .direction = "out" }, \ CEIOSTATS_REPLY, CELOSTATS_REPLY #endif #ifdef _USE_NFS3 #define CLNT_V3NLM_OPS_REPLY \ { .name = "clnt_v3nlm_ops_stats", \ .type = "a(sttt)", \ .direction = "out" } #endif #define CLNT_V4_OPS_REPLY \ { .name = "clnt_v4_ops_stats", .type = "a(stt)", .direction = "out" } #define CLNT_CMP_OPS_REPLY \ { .name = "clnt_cmp_ops_stats", .type = "ttt", .direction = "out" } #define TRANSPORT_REPLY \ { .name = "rx_bytes", .type = "(t)", .direction = "out" }, \ { .name = "rx_pkt", .type = "(t)", .direction = "out" }, \ { .name = "rx_err", .type = "(t)", .direction = "out" }, \ { .name = "tx_bytes", .type = "(t)", .direction = "out" }, \ { .name = "tx_pkt", .type = "(t)", .direction = "out" }, \ { .name = "tx_err", .type = "(t)", .direction = "out" } #define TOTAL_OPS_REPLY { .name = "op", .type = "a(st)", .direction = "out" } /* We are passing back FSAL name so that ganesha_stats can show it as per * the FSAL name * The fsal_stats is an array with below items in it * OP_NAME, NUMBER_OF_OP, AVG_RES_TIME, MIN_RES_TIME & MAX_RES_TIME */ #define FSAL_OPS_REPLY \ { .name = "fsal_name", .type = "s", .direction = "out" }, \ { .name = "fsal_stats", \ .type = "a(stddd)", \ .direction = "out" } #ifdef _USE_NFS3 #define STATS_STATUS_REPLY \ { .name = "nfs_status", .type = "b(tt)", .direction = "out" }, \ { .name = "fsal_status", \ .type = "b(tt)", \ .direction = "out" }, \ { .name = "v3_full_status", \ .type = "b(tt)", \ .direction = "out" }, \ { .name = "v4_full_status", \ .type = "b(tt)", \ .direction = "out" }, \ { .name = "auth_status", \ .type = "b(tt)", \ .direction = "out" }, \ { .name = "clnt_allops_status", \ .type = "b(tt)", \ .direction = "out" } #else #define STATS_STATUS_REPLY \ { .name = "nfs_status", .type = "b(tt)", .direction = "out" }, \ { .name = "fsal_status", \ .type = "b(tt)", \ .direction = "out" }, \ { .name = "v4_full_status", \ .type = "b(tt)", \ .direction = "out" }, \ { .name = "auth_status", \ .type = "b(tt)", \ .direction = "out" }, \ { .name = "clnt_allops_status", \ .type = "b(tt)", \ .direction = "out" } #endif #ifdef _USE_NFS3 #define V3_FULL_REPLY \ { .name = "v3_full_stats", .type = "a(stttddd)", .direction = "out" } #endif #define V4_FULL_REPLY \ { .name = "v4_full_stats", .type = "a(sttddd)", .direction = "out" } #define AUTH_REPLY \ { .name = "auth", .type = "a(tdddtdddtddd)", .direction = "out" } #define LAYOUTS_REPLY \ { .name = "getdevinfo", .type = "(ttt)", .direction = "out" }, \ { .name = "layout_get", .type = "(ttt)", .direction = "out" }, \ { .name = "layout_commit", \ .type = "(ttt)", \ .direction = "out" }, \ { .name = "layout_return", \ .type = "(ttt)", \ .direction = "out" }, \ { .name = "layout_recall", \ .type = "(ttt)", \ .direction = "out" } /* number of delegations, number of sent recalls, * number of failed recalls, number of revokes */ #define DELEG_REPLY \ { .name = "delegation_stats", .type = "(tttt)", .direction = "out" } #define NFS_ALL_IO_REPLY_ARRAY_TYPE "(qs(tttttt)(tttttt))" #define NFS_ALL_IO_REPLY \ { .name = "iostats", \ .type = DBUS_TYPE_ARRAY_AS_STRING NFS_ALL_IO_REPLY_ARRAY_TYPE, \ .direction = "out" } #ifdef _USE_9P #define _9P_OP_ARG { .name = "_9p_opname", .type = "s", .direction = "in" } #endif #define OP_STATS_REPLY \ { .name = "op_stats", .type = "(tt)", .direction = "out" } #define LRU_UTILIZATION_REPLY \ { .name = "lru_data_utilization", \ .type = "stsussstst", \ .direction = "out" } #define FD_USAGE_SUMM_REPLY \ { .name = "fd_usage_summary", \ .type = "sususususssusust", \ .direction = "out" } extern struct timespec auth_stats_time; #ifdef _USE_NFS3 extern struct timespec v3_full_stats_time; #endif extern struct timespec v4_full_stats_time; void server_stats_summary(DBusMessageIter *iter, struct gsh_stats *st); void server_dbus_client_io_ops(DBusMessageIter *iter, struct gsh_client *client); void server_dbus_client_all_ops(DBusMessageIter *iter, struct gsh_client *client); void server_dbus_export_details(DBusMessageIter *iter, struct gsh_export *g_export); #ifdef _USE_NFS3 void server_dbus_v3_iostats(struct nfsv3_stats *v3p, DBusMessageIter *iter); #endif void server_dbus_v40_iostats(struct nfsv40_stats *v40p, DBusMessageIter *iter); void server_dbus_v41_iostats(struct nfsv41_stats *v41p, DBusMessageIter *iter); void server_dbus_v41_layouts(struct nfsv41_stats *v41p, DBusMessageIter *iter); void server_dbus_v42_iostats(struct nfsv41_stats *v42p, DBusMessageIter *iter); void server_dbus_v42_layouts(struct nfsv41_stats *v42p, DBusMessageIter *iter); void server_dbus_nfsmon_iostats(struct export_stats *export_st, DBusMessageIter *iter); void server_dbus_delegations(struct deleg_stats *ds, DBusMessageIter *iter); void server_dbus_all_iostats(struct export_stats *export_statistics, DBusMessageIter *iter); void server_dbus_total_ops(struct export_stats *export_st, DBusMessageIter *iter); void global_dbus_total_ops(DBusMessageIter *iter); void server_dbus_fast_ops(DBusMessageIter *iter); void mdcache_dbus_show(DBusMessageIter *iter); void mdcache_utilization(DBusMessageIter *iter); #ifdef _USE_NFS3 void server_dbus_v3_full_stats(DBusMessageIter *iter); #endif void server_dbus_v4_full_stats(DBusMessageIter *iter); void reset_server_stats(void); void reset_export_stats(void); void reset_client_stats(void); void reset_gsh_stats(struct gsh_stats *st); void reset_gsh_allops_stats(struct gsh_clnt_allops_stats *st); #ifdef _USE_NFS3 void reset_v3_full_stats(void); #endif void reset_v4_full_stats(void); void reset_auth_stats(void); void reset_clnt_allops_stats(void); void fd_usage_summarize_dbus(DBusMessageIter *iter); #ifdef _USE_9P void server_dbus_9p_iostats(struct _9p_stats *_9pp, DBusMessageIter *iter); void server_dbus_9p_transstats(struct _9p_stats *_9pp, DBusMessageIter *iter); void server_dbus_9p_tcpstats(struct _9p_stats *_9pp, DBusMessageIter *iter); void server_dbus_9p_rdmastats(struct _9p_stats *_9pp, DBusMessageIter *iter); void server_dbus_9p_opstats(struct _9p_stats *_9pp, u8 opcode, DBusMessageIter *iter); #endif extern struct glist_head fsal_list; #endif /* USE_DBUS */ void server_stats_free(struct gsh_stats *statsp); void server_stats_allops_free(struct gsh_clnt_allops_stats *statsp); void server_stats_init(void); #endif /* !SERVER_STATS_PRIVATE_H */ /** @} */ nfs-ganesha-6.5/src/include/uid2grp.h000066400000000000000000000053241473756622300175400ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @defgroup idmapper ID Mapper * * The ID Mapper module provides mapping between numerical user and * group IDs and NFSv4 style owner and group strings. * * @{ */ /** * @file idmapper.c * @brief Id mapping functions */ #ifndef UID2GRP_H #define UID2GRP_H #include #include #include #include #include "gsh_rpc.h" #include "gsh_types.h" /** * @brief Shared between idmapper.c and uid2grp_cache.c. If you * aren't in idmapper.c, leave these symbols alone. * * @{ */ typedef struct group_data { uid_t uid; struct gsh_buffdesc uname; gid_t gid; time_t epoch; int nbgroups; unsigned int refcount; pthread_mutex_t gd_lock; gid_t *groups; } group_data_t; extern pthread_rwlock_t uid2grp_user_lock; extern sem_t uid2grp_sem; void uid2grp_cache_init(void); void uid2grp_add_user(struct group_data *); bool uid2grp_lookup_by_uname(const struct gsh_buffdesc *, uid_t *, struct group_data **); bool uid2grp_lookup_by_uid(const uid_t, struct group_data **); void uid2grp_remove_by_uname(const struct gsh_buffdesc *); void uid2grp_remove_expired_by_uname(const struct gsh_buffdesc *); void uid2grp_remove_by_uid(const uid_t); void uid2grp_remove_expired_by_uid(const uid_t); void uid2grp_clear_cache(void); void uid2grp_cache_reap(void); bool uid2grp(uid_t uid, struct group_data **); bool name2grp(const struct gsh_buffdesc *name, struct group_data **gdata); bool principal2grp(char *principal, struct group_data **, const uid_t, const gid_t); void uid2grp_unref(struct group_data *gdata); void uid2grp_hold_group_data(struct group_data *); void uid2grp_release_group_data(struct group_data *); bool uid2grp_is_group_data_expired(struct group_data *); #endif /* UID2GRP_H */ /** @} */ nfs-ganesha-6.5/src/include/xprt_handler.h000066400000000000000000000042031473756622300206510ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2023 Google LLC * Contributor : Dipit Grover dipit@google.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file xprt_handler.h * @brief Functionality related to service transport. */ #ifndef XPRT_HANDLER_H #define XPRT_HANDLER_H #include "gsh_rpc.h" #include "sal_data.h" #include "connection_manager.h" typedef struct nfs41_session_list_entry { nfs41_session_t *session; struct glist_head node; } nfs41_session_list_entry_t; typedef struct nfs41_sessions_holder { pthread_rwlock_t sessions_lock; struct glist_head sessions; uint8_t num_sessions; } nfs41_sessions_holder_t; typedef enum xprt_custom_data_status { ASSOCIATED_TO_XPRT = 0, DISSOCIATED_FROM_XPRT, DESTROYED, XPRT_CUSTOM_DATA_STATUS_COUNT, } xprt_custom_data_status_t; /* Represents miscellaneous data related to the svc-xprt */ typedef struct xprt_custom_data { nfs41_sessions_holder_t nfs41_sessions_holder; xprt_custom_data_status_t status; connection_manager__connection_t managed_connection; } xprt_custom_data_t; void init_custom_data_for_xprt(SVCXPRT *); void destroy_custom_data_for_destroyed_xprt(SVCXPRT *); void dissociate_custom_data_from_xprt(SVCXPRT *); bool add_nfs41_session_to_xprt(SVCXPRT *, nfs41_session_t *); void remove_nfs41_session_from_xprt(SVCXPRT *, nfs41_session_t *); #endif /* XPRT_HANDLER_H */ nfs-ganesha-6.5/src/libganeshaNFS.pc.cmake000066400000000000000000000005211473756622300204310ustar00rootroot00000000000000prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=${prefix} libdir=${exec_prefix}/lib@LIB_SUFFIX@ includedir=${prefix}/include Name: libganeshaNFS Description: NFS-GANESHA's "fuselike" library Requires: Version: @GANESHA_MAJOR_VERSION@@GANESHA_MINOR_VERSION@ Libs: -L${exec_prefix}/lib@LIB_SUFFIX@ -lganeshaNFS Cflags: -I@INCLUDE_INSTALL_DIR@ nfs-ganesha-6.5/src/libntirpc/000077500000000000000000000000001473756622300163525ustar00rootroot00000000000000nfs-ganesha-6.5/src/log/000077500000000000000000000000001473756622300151455ustar00rootroot00000000000000nfs-ganesha-6.5/src/log/.gitignore000066400000000000000000000000141473756622300171300ustar00rootroot00000000000000test_liblog nfs-ganesha-6.5/src/log/CMakeLists.txt000066400000000000000000000032541473756622300177110ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- add_definitions( -D__USE_GNU ) if(USE_DBUS) include_directories( ${DBUS_INCLUDE_DIRS} ) endif(USE_DBUS) if(USE_LTTNG) include_directories( ${LTTNG_INCLUDE_DIR} ) endif(USE_LTTNG) ########### next target ############### SET(log_STAT_SRCS display.c log_functions.c ) add_library(log OBJECT ${log_STAT_SRCS}) add_sanitizers(log) set_target_properties(log PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(log gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) ########### next target ############### SET(test_liblog_SRCS test_display.c ) ########### install files ############### nfs-ganesha-6.5/src/log/display.c000066400000000000000000000374221473756622300167660ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2012 * Contributor: Frank Filz * * -------------------------- * * This program 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 3 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 * 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 * * */ /** * @defgroup Display display_buffer implementation * @{ */ /** * @file display.c * @author Frank Filz * @brief Implementation of a buffer for constructing string messages. * * A variety of functions are provided to manipulate display buffers and * append various strings to the buffer. */ #include #include #include #include #include #include "display.h" /** * @brief Internal routine to compute the bytes remaining in a buffer. * * @param[in] dspbuf The buffer. * * @return the number of bytes remaining in the buffer. */ static inline int _display_buffer_remain(struct display_buffer *dspbuf) { /* Compute number of bytes remaining in buffer * (including space for null). */ return dspbuf->b_size - (dspbuf->b_current - dspbuf->b_start); } /** * @brief Compute the bytes remaining in a buffer. * * @param[in,out] dspbuf The buffer. * * @retval -1 if there is some problem rendering the buffer unusable. * @retval 0 if the buffer has overflowed. * @retval >0 indicates the bytes remaining (including one byte for '\0'). */ int display_buffer_remain(struct display_buffer *dspbuf) { /* If no buffer, indicate problem. */ if (dspbuf == NULL || dspbuf->b_start == NULL || dspbuf->b_size == 0) { errno = EFAULT; return -1; } /* If b_current is invalid, set it to b_start */ if (dspbuf->b_current == NULL || dspbuf->b_current < dspbuf->b_start || dspbuf->b_current > (dspbuf->b_start + dspbuf->b_size)) dspbuf->b_current = dspbuf->b_start; /* Buffer is too small, just make it an empty * string and mark the buffer as overrun. */ if (dspbuf->b_size < 4) { dspbuf->b_start[0] = '\0'; dspbuf->b_current = dspbuf->b_start + dspbuf->b_size; return 0; } /* Compute number of bytes remaining in buffer * (including space for null). */ return _display_buffer_remain(dspbuf); } /** * @brief Finish up a buffer after overflowing it. * * @param[in,out] dspbuf The buffer. * @param[in] ptr Proposed position in the buffer for the "..." string. * * This routine will validate the final character that will remain in the buffer * prior to the "..." to make sure it is not a partial UTF-8 character. If so, * it will place the "..." such that it replaces the partial UTF-8 character. * The result will be a proper UTF-8 string (assuming the rest of the string is * valid UTF-8...). * * Caller will make sure sufficient room is available in buffer for the "..." * string. This leaves it up to the caller whether prt must be backed up * from b_current, or if the caller knows the next item won't fit in the buffer * but that there is room for the "..." string. */ void _display_complete_overflow(struct display_buffer *dspbuf, char *ptr) { int utf8len; char *end; /* ptr argument points after last byte that we will retain * set 'end' to that and 'ptr' to inspect previous byte if possible */ end = ptr; if (ptr > dspbuf->b_start) ptr--; /* Now ptr points to last byte that will remain part of string. * Next we need to check if this byte is the end of a valid UTF-8 * character. */ while ((ptr > dspbuf->b_start) && ((*ptr & 0xc0) == 0x80)) ptr--; /* Now ptr points to the start of a valid UTF-8 character or the string * was rather corrupt, there is no valid start of a UTF-8 character. */ /* Compute the length of the last UTF-8 character */ utf8len = end - ptr; /* Check if last character is valid UTF-8, for multibyte characters the * first byte is a string of 1 bits followed by a 0 bit. So for example, * a 2 byte character leads off with 110xxxxxxx, so we mask with * 11100000 (0xe0) and test for 11000000 (0xc0). */ if ((((*ptr & 0x80) == 0x00) && (utf8len == 1)) || (((*ptr & 0xe0) == 0xc0) && (utf8len == 2)) || (((*ptr & 0xf0) == 0xe0) && (utf8len == 3)) || (((*ptr & 0xf8) == 0xf0) && (utf8len == 4)) || (((*ptr & 0xfc) == 0xf8) && (utf8len == 5)) || (((*ptr & 0xfe) == 0xfc) && (utf8len == 6))) { /* Last character before end is valid, increment ptr past it. */ ptr = end; } /* else last character is not valid, leave ptr to strip it. */ /* Now we know where to place the ellipsis... */ strcpy(ptr, "..."); } /** * @brief Prepare to append to buffer. * * @param[in,out] dspbuf The buffer. * * @return the bytes remaining in the buffer. * * This routine validates the buffer, then checks if the buffer is already full * in which case it will mark the buffer as overflowed and finish up the buffer. * */ int display_start(struct display_buffer *dspbuf) { int b_left = display_buffer_remain(dspbuf); /* If buffer has already overflowed, or is invalid, return that. */ if (b_left <= 0) return b_left; /* If buffer is already full, indicate overflow now, and indicate * no space is left (so caller doesn't bother to do anything. */ if (b_left == 1) { /* Increment past end and finish buffer. */ dspbuf->b_current++; b_left--; /* Back up 4 bytes before last byte (note that b_current * points PAST the last byte of the buffer since the * buffer has overflowed). */ _display_complete_overflow(dspbuf, dspbuf->b_current - 4); } else { /* Some display functions might not put anything in the * buffer... */ *dspbuf->b_current = '\0'; } /* Indicate buffer is ok by returning b_left. */ return b_left; } /** * @brief Finish up a buffer after appending to it. * * @param[in,out] dspbuf The buffer. * * @return the bytes remaining in the buffer. * * After a buffer has been appended to, check for overflow. * * This should be called by every routine that actually copies bytes into a * display_buffer. It must not be called by routines that use other display * routines to build a buffer (since the last such routine executed will * have called this routine). * */ int display_finish(struct display_buffer *dspbuf) { /* display_buffer_remain will return the current number of bytes left in * the buffer. If this is 0, and we just appended to the buffer (i.e. * display_buffer_remain was NOT 0 before appending), then the last * append just overflowed the buffer (note that if it exactly filled the * buffer, display_buffer_remain would have returned 1). Since the * buffer just overflowed, the overflow will be indicated by truncating * the string to allow space for a three character "..." sequence. */ int b_left = display_buffer_remain(dspbuf); if (b_left != 0) return b_left; /* We validated above that buffer is at least 4 bytes... */ /* Back up 4 bytes before last byte (note that b_current points * PAST the last byte of the buffer since the buffer has overflowed). */ _display_complete_overflow(dspbuf, dspbuf->b_current - 4); return 0; } /** * @brief Force overflow on a buffer after appending to it. * * @param[in,out] dspbuf The buffer. * * @return the bytes remaining in the buffer. * * After a buffer has been appended to, check for overflow. * */ int display_force_overflow(struct display_buffer *dspbuf) { int b_left = display_buffer_remain(dspbuf); if (b_left <= 0) return b_left; if (b_left < 4) { /* There aren't at least 4 characters left, back up to allow for * them. If there aren't room for 3 more non-0 bytes in the * buffer, then (baring multi-byte UTF-8 charts), the "..." will * always be at the very end of the buffer, that is determined * by b_start + b_size, b_current is currently * b_start + b_size - b_left so instead of using b_current, we * just back up 4 bytes from the end of the buffer to make the * space. _display_complete_overflow will deal with the * possibility that a UTF-8 character ended up truncated as a * result. */ _display_complete_overflow(dspbuf, dspbuf->b_start + dspbuf->b_size - 4); } else { /* Otherwise just put the "..." at b_current */ _display_complete_overflow(dspbuf, dspbuf->b_current); } /* Mark buffer as overflowed. */ dspbuf->b_current = dspbuf->b_start + dspbuf->b_size; return 0; } /** * @brief Format a string into the buffer. * * @param[in,out] dspbuf The buffer. * @param[in] fmt The format string * @param[in] args The va_list args * * @return the bytes remaining in the buffer. * */ int display_vprintf(struct display_buffer *dspbuf, const char *fmt, va_list args) { int len; int b_left = display_start(dspbuf); if (b_left <= 0) return b_left; /* snprintf into the buffer no more than b_left bytes. snprintf assures * the buffer is null terminated (so will copy at most b_left * characters). */ len = vsnprintf(dspbuf->b_current, b_left, fmt, args); if (len >= b_left) { /* snprintf indicated that if the full string was printed, it * would have overflowed. By incrementing b_current by b_left, * b_current now points beyond the buffer and clearly marks the * buffer as full. */ dspbuf->b_current += b_left; } else { /* No overflow, move b_current to the end of the printf. */ dspbuf->b_current += len; } /* Finish up */ return display_finish(dspbuf); } /** * @brief Display a number of opaque bytes as a hex string. * * @param[in,out] dspbuf The buffer. * @param[in] value The bytes to display * @param[in] len The number of bytes to display * @param[in] flags Flags indicating options for display * * @return the bytes remaining in the buffer. * */ int display_opaque_bytes_flags(struct display_buffer *dspbuf, void *value, int len, int flags) { unsigned int i = 0; int b_left = display_start(dspbuf); const char *fmt; if (b_left <= 0) return b_left; /* Check that the length is ok */ if (len < 0) { if (flags & OPAQUE_BYTES_INVALID_LEN) return -1; else return display_printf(dspbuf, "(invalid len=%d)", len); } /* If the value is NULL, display NULL value. */ if (value == NULL) { if (flags & OPAQUE_BYTES_INVALID_NULL) return -1; else return display_cat(dspbuf, "(NULL)"); } /* If the value is empty, display EMPTY value. */ if (len == 0) { if (flags & OPAQUE_BYTES_INVALID_EMPTY) return -1; else return display_cat(dspbuf, "(EMPTY)"); } if (flags & OPAQUE_BYTES_0x) { /* Indicate the value is a hex string. */ b_left = display_cat(dspbuf, "0x"); } if (flags & OPAQUE_BYTES_UPPER) fmt = "%02X"; else fmt = "%02x"; /* Display the value one hex byte at a time. */ for (i = 0; i < len && b_left > 0; i++) b_left = display_printf(dspbuf, fmt, ((unsigned char *)value)[i]); /* Finish up */ return display_finish(dspbuf); } /** * @brief Display a number of opaque bytes as a hex string, limiting the number * of bytes used from the opaque value. * * @param[in,out] dspbuf The buffer. * @param[in] value The bytes to display * @param[in] len The number of bytes to display * @param[in] max Max number of bytes from the opaque value to display * * @return the bytes remaining in the buffer. * * This routine also attempts to detect a printable value and if so, displays * that instead of converting value to a hex string. It uses min(len,max) as * the number of bytes to use from the opaque value. * */ int display_opaque_value_max(struct display_buffer *dspbuf, void *value, int len, int max) { unsigned int i = 0; int b_left = display_start(dspbuf); int cpy = len; if (b_left <= 0) return b_left; /* Check that the length is ok */ if (len < 0) return display_printf(dspbuf, "(invalid len=%d)", len); /* If the value is NULL, display NULL value. */ if (value == NULL) return display_cat(dspbuf, "(NULL)"); /* If the value is empty, display EMPTY value. */ if (len == 0) return display_cat(dspbuf, "(EMPTY)"); /* Display the length of the value. */ b_left = display_printf(dspbuf, "(%d:", len); if (b_left <= 0) return b_left; if (len > max) cpy = max; /* Determine if the value is entirely printable characters. */ for (i = 0; i < len; i++) if (!isprint(((char *)value)[i])) break; if (i == len) { /* Entirely printable character, so we will just copy the * characters into the buffer (to the extent there is room * for them). */ b_left = display_len_cat(dspbuf, value, cpy); } else { b_left = display_opaque_bytes(dspbuf, value, cpy); } if (b_left <= 0) return b_left; if (len > max) return display_cat(dspbuf, "...)"); else return display_cat(dspbuf, ")"); } /** * @brief Append a length delimited string to the buffer. * * @param[in,out] dspbuf The buffer. * @param[in] str The string * @param[in] len The length of the string * * @return the bytes remaining in the buffer. * */ int display_len_cat(struct display_buffer *dspbuf, const char *str, int len) { int b_left = display_start(dspbuf); int cpy; if (b_left <= 0) return b_left; /* Check if string would overflow dspbuf. */ if (len >= b_left) { /* Don't copy more bytes than will fit. */ cpy = b_left - 1; } else { /* Copy the entire string including null. */ cpy = len; } /* Copy characters and null terminate. */ memcpy(dspbuf->b_current, str, cpy); dspbuf->b_current[cpy] = '\0'; if (len >= b_left) { /* Overflow, indicate by moving b_current past end of buffer. */ dspbuf->b_current += b_left; } else { /* Didn't overflow, just increment b_current. */ dspbuf->b_current += len; } return display_finish(dspbuf); } /** * @brief Append a null delimited string to the buffer, truncating it. * * @param[in,out] dspbuf The buffer. * @param[in] str The string * @param[in] max Truncate the string to this maximum length * * @return the bytes remaining in the buffer. * * This routine is useful when the caller wishes to append a string to * the buffer, but rather than truncating the string at the end of the buffer, * the caller desires the string to be truncated to some shorter length (max). * * If the string is truncated, that will be indicated with "..." characters. * Basically this routine makes a sub-display buffer of max+1 bytes and uses * display_cat to achieve the truncation. * */ int display_cat_trunc(struct display_buffer *dspbuf, char *str, size_t max) { struct display_buffer catbuf; int b_left = display_start(dspbuf); if (b_left <= 0) return b_left; /* If there isn't room left in dspbuf after max, then just use * display_cat so that dspbuf will be properly finished. */ if ((max + 1) >= b_left) return display_cat(dspbuf, str); /* Make a sub-buffer so we can properly truncate the string. */ catbuf.b_current = dspbuf->b_current; catbuf.b_start = dspbuf->b_current; catbuf.b_size = max + 1; b_left = display_cat(&catbuf, str); /* Did we overflow the catbuf? * NOTE b_left can't be -1 because catbuf is declared on the stack. */ if (b_left == 0) { /* Overflowed catbuf, catbuf.b_current points past the * terminating null. Roll back to point at the null. */ dspbuf->b_current = catbuf.b_current - 1; } else { /* Update dspbuf */ dspbuf->b_current = catbuf.b_current; } /* we know dspbuf itself can not have overflowed * so just return the new remaining buffer space. */ return _display_buffer_remain(dspbuf); } /** @} */ nfs-ganesha-6.5/src/log/log_functions.c000066400000000000000000002223411473756622300201660ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- * * All the display functions and error handling. * */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_UNWIND #define UNW_LOCAL_ONLY #include #endif #include "log.h" #include "gsh_list.h" #include "gsh_rpc.h" #include "common_utils.h" #include "abstract_mem.h" #ifdef USE_DBUS #include "gsh_dbus.h" #endif #include "nfs_core.h" #include "config_parsing.h" #include "sal_functions.h" /* * The usual PTHREAD_RWLOCK_xxx macros log messages for tracing if FULL * DEBUG is enabled. If such a macro is called from this logging file as * part of logging a message, it generates endless loop of lock tracing * messages. The following code redefines these lock macros to avoid the * loop. */ #ifdef PTHREAD_RWLOCK_wrlock #undef PTHREAD_RWLOCK_wrlock #endif #define PTHREAD_RWLOCK_wrlock(_lock) \ do { \ if (pthread_rwlock_wrlock(_lock) != 0) \ assert(0); \ } while (0) #ifdef PTHREAD_RWLOCK_rdlock #undef PTHREAD_RWLOCK_rdlock #endif #define PTHREAD_RWLOCK_rdlock(_lock) \ do { \ if (pthread_rwlock_rdlock(_lock) != 0) \ assert(0); \ } while (0) #ifdef PTHREAD_RWLOCK_unlock #undef PTHREAD_RWLOCK_unlock #endif #define PTHREAD_RWLOCK_unlock(_lock) \ do { \ if (pthread_rwlock_unlock(_lock) != 0) \ assert(0); \ } while (0) pthread_rwlock_t log_rwlock; /* Variables to control log fields */ /** * @brief Define an index each of the log fields that are configurable. * * Ganesha log messages have several "header" fields used in every * message. Some of those fields may be configured (mostly display or * not display). * */ enum log_flag_index_t { LF_DATE, /*< Date field. */ LF_TIME, /*< Time field. */ LF_EPOCH, /*< Server Epoch field (distinguishes server instance. */ LF_CLIENTIP, /* next = cleanup_list; cleanup_list = clean; } void Cleanup(void) { struct cleanup_list_element *c = cleanup_list; while (c != NULL) { c->clean(); c = c->next; } PTHREAD_RWLOCK_destroy(&log_rwlock); #ifdef _DONT_HAVE_LOCALTIME_R PTHREAD_MUTEX_destroy(&mutex_localtime); #endif } void Fatal(void) { gsh_backtrace(); _exit(2); } /* * Convert a numeral log level in ascii to * the numeral value. */ int ReturnLevelAscii(const char *LevelInAscii) { int i = 0; for (i = 0; i < ARRAY_SIZE(tabLogLevel); i++) if (tabLogLevel[i].str != NULL && (!strcasecmp(tabLogLevel[i].str, LevelInAscii) || !strcasecmp(tabLogLevel[i].str + 4, LevelInAscii) || !strcasecmp(tabLogLevel[i].short_str, LevelInAscii))) return i; /* If nothing found, return -1 */ return -1; } /* ReturnLevelAscii */ char *ReturnLevelInt(int level) { if (level >= 0 && level < NB_LOG_LEVEL) return tabLogLevel[level].str; /* If nothing is found, return NULL. */ return NULL; } /* ReturnLevelInt */ /* * Set the name of this program. */ void SetNamePgm(const char *nom) { /* This function isn't thread-safe because the name of the program * is common among all the threads. */ if (strlcpy(program_name, nom, sizeof(program_name)) >= sizeof(program_name)) LogFatal(COMPONENT_LOG, "Program name %s too long", nom); } /* * Set the hostname. */ void SetNameHost(const char *name) { if (strlcpy(hostname, name, sizeof(hostname)) >= sizeof(hostname)) LogFatal(COMPONENT_LOG, "Host name %s too long", name); } /* Set the function name in progress. */ void SetNameFunction(const char *nom) { if (strlcpy(thread_name, nom, sizeof(thread_name)) >= sizeof(thread_name)) { LogWarn(COMPONENT_LOG, "Thread name %s too long truncated to %s", nom, thread_name); } clientip = NULL; } /* * Sets the IP of the Client for this thread. * Make sure ip_str is valid for the duration of the thread */ void SetClientIP(char *ip_str) { clientip = ip_str; } static void SetLevelDebug(int level_to_set) { int i; if (level_to_set < NIV_NULL) level_to_set = NIV_NULL; if (level_to_set >= NB_LOG_LEVEL) level_to_set = NB_LOG_LEVEL - 1; /* COMPONENT_ALL is a pseudo component, handle it separately */ component_log_level[COMPONENT_ALL] = level_to_set; for (i = COMPONENT_ALL + 1; i < COMPONENT_COUNT; i++) { SetComponentLogLevel(i, level_to_set); } } /* _SetLevelDebug */ uint32_t rpc_debug_flags = TIRPC_DEBUG_FLAG_ERROR | TIRPC_DEBUG_FLAG_WARN | TIRPC_DEBUG_FLAG_EVENT; static void SetNTIRPCLogLevel(int level_to_set) { uint32_t old = ntirpc_pp.debug_flags; switch (level_to_set) { case NIV_NULL: case NIV_FATAL: ntirpc_pp.debug_flags = 0; /* disable all flags */ break; case NIV_CRIT: case NIV_MAJ: ntirpc_pp.debug_flags = TIRPC_DEBUG_FLAG_ERROR; break; case NIV_WARN: ntirpc_pp.debug_flags = TIRPC_DEBUG_FLAG_ERROR | TIRPC_DEBUG_FLAG_WARN; break; case NIV_EVENT: case NIV_INFO: ntirpc_pp.debug_flags = TIRPC_DEBUG_FLAG_ERROR | TIRPC_DEBUG_FLAG_WARN | TIRPC_DEBUG_FLAG_EVENT; break; case NIV_DEBUG: case NIV_MID_DEBUG: /* set by log_conf_commit() */ ntirpc_pp.debug_flags = rpc_debug_flags; break; case NIV_FULL_DEBUG: ntirpc_pp.debug_flags = 0xFFFFFFFF; /* enable all flags */ break; default: ntirpc_pp.debug_flags = TIRPC_DEBUG_FLAG_DEFAULT; break; } if (!tirpc_control(TIRPC_SET_DEBUG_FLAGS, &ntirpc_pp.debug_flags)) LogCrit(COMPONENT_CONFIG, "Setting nTI-RPC debug_flags failed"); else if (old != ntirpc_pp.debug_flags) LogChanges("Changed RPC_Debug_Flags from %" PRIx32 " to %" PRIx32, old, ntirpc_pp.debug_flags); } void SetComponentLogLevel(log_components_t component, int level_to_set) { assert(level_to_set >= NIV_NULL); assert(level_to_set < NB_LOG_LEVEL); assert(component != COMPONENT_ALL); if (component_log_level[component] == level_to_set) return; LogChanges("Changing log level of %s from %s to %s", LogComponents[component].comp_name, ReturnLevelInt(component_log_level[component]), ReturnLevelInt(level_to_set)); component_log_level[component] = level_to_set; if (component == COMPONENT_TIRPC) SetNTIRPCLogLevel(level_to_set); } void set_const_log_str(void) { struct display_buffer dspbuf = { sizeof(const_log_str), const_log_str, const_log_str }; struct display_buffer tdfbuf = { sizeof(date_time_fmt), date_time_fmt, date_time_fmt }; int b_left = display_start(&dspbuf); const_log_str[0] = '\0'; if (b_left > 0 && logfields->disp_epoch) b_left = display_printf(&dspbuf, ": epoch %08lx ", get_unique_server_id()); if (b_left > 0 && logfields->disp_host) b_left = display_printf(&dspbuf, ": %s ", hostname); if (b_left > 0 && logfields->disp_prog) b_left = display_printf(&dspbuf, ": %s", program_name); if (b_left > 0 && logfields->disp_prog && logfields->disp_pid) b_left = display_cat(&dspbuf, "-"); if (b_left > 0 && logfields->disp_pid) b_left = display_printf(&dspbuf, "%d", getpid()); if (b_left > 0 && (logfields->disp_prog || logfields->disp_pid) && !logfields->disp_threadname) (void)display_cat(&dspbuf, " "); b_left = display_start(&tdfbuf); if (b_left <= 0) return; if (logfields->datefmt == TD_LOCAL && logfields->timefmt == TD_LOCAL) { b_left = display_cat(&tdfbuf, "%c "); } else { switch (logfields->datefmt) { case TD_GANESHA: b_left = display_cat(&tdfbuf, "%d/%m/%Y "); break; case TD_8601: b_left = display_cat(&tdfbuf, "%F "); break; case TD_LOCAL: b_left = display_cat(&tdfbuf, "%x "); break; case TD_SYSLOG: b_left = display_cat(&tdfbuf, "%b %e "); break; case TD_SYSLOG_USEC: if (logfields->timefmt == TD_SYSLOG_USEC) b_left = display_cat(&tdfbuf, "%F"); else b_left = display_cat(&tdfbuf, "%F "); break; case TD_USER: b_left = display_printf(&tdfbuf, "%s ", logfields->user_date_fmt); break; case TD_NONE: default: break; } if (b_left <= 0) return; switch (logfields->timefmt) { case TD_GANESHA: b_left = display_cat(&tdfbuf, "%H:%M:%S "); break; case TD_SYSLOG: case TD_8601: case TD_LOCAL: b_left = display_cat(&tdfbuf, "%X "); break; case TD_SYSLOG_USEC: b_left = display_cat(&tdfbuf, "T%H:%M:%S.%%06u%z "); break; case TD_USER: b_left = display_printf(&tdfbuf, "%s ", logfields->user_time_fmt); break; case TD_NONE: default: break; } } /* Trim trailing blank from date time format. */ if (date_time_fmt[0] != '\0' && date_time_fmt[strlen(date_time_fmt) - 1] == ' ') date_time_fmt[strlen(date_time_fmt) - 1] = '\0'; } /** * * @brief Finds a log facility by name * * Must be called under the rwlock * * @param[in] name The name of the facility to be found * * @retval NULL No facility by that name * @retval non-NULL Pointer to the facility structure * */ static struct log_facility *find_log_facility(const char *name) { struct glist_head *glist; struct log_facility *facility; glist_for_each(glist, &facility_list) { facility = glist_entry(glist, struct log_facility, lf_list); if (!strcasecmp(name, facility->lf_name)) return facility; } return NULL; } /** * @brief Create a logging facility * * A logging facility outputs log messages using the helper function * log_func. See below for enabling/disabling. * * @param name [IN] the name of the new logger * @param log_func [IN] function pointer to the helper * @param max_level [IN] maximum message level this logger will handle. * @param header [IN] detail level for header part of messages * @param private [IN] logger specific argument. * * @return 0 on success, -errno for failure */ int create_log_facility(const char *name, lf_function_t *log_func, log_levels_t max_level, log_header_t header, void *private) { struct log_facility *facility; if (name == NULL || *name == '\0') return -EINVAL; if (max_level < NIV_NULL || max_level >= NB_LOG_LEVEL) return -EINVAL; if (log_func == log_to_file && private != NULL) { char *dir; int rc; if (*(char *)private == '\0' || strlen(private) >= MAXPATHLEN) { LogCrit(COMPONENT_LOG, "New log file path empty or too long"); return -EINVAL; } dir = gsh_strdupa(private); dir = dirname(dir); rc = access(dir, W_OK); if (rc != 0) { rc = errno; LogCrit(COMPONENT_LOG, "Cannot create new log file (%s), because: %s", (char *)private, strerror(rc)); return -rc; } } PTHREAD_RWLOCK_wrlock(&log_rwlock); facility = find_log_facility(name); if (facility != NULL) { PTHREAD_RWLOCK_unlock(&log_rwlock); LogInfo(COMPONENT_LOG, "Facility %s already exists", name); return -EEXIST; } facility = gsh_calloc(1, sizeof(*facility)); facility->lf_name = gsh_strdup(name); facility->lf_func = log_func; facility->lf_max_level = max_level; facility->lf_headers = header; if (log_func == log_to_file && private != NULL) facility->lf_private = gsh_strdup(private); else facility->lf_private = private; glist_add_tail(&facility_list, &facility->lf_list); PTHREAD_RWLOCK_unlock(&log_rwlock); LogInfo(COMPONENT_LOG, "Created log facility %s", facility->lf_name); return 0; } /** * @brief Release a logger facility * * Release the named facility and all its resources. * disable it first if it is active. It will refuse to * release the default logger because that could leave the server * with no place to send messages. * * @param name [IN] name of soon to be deceased logger * * @returns always. The logger is not disabled or released on errors */ void release_log_facility(const char *name) { struct log_facility *facility; PTHREAD_RWLOCK_wrlock(&log_rwlock); facility = find_log_facility(name); if (facility == NULL) { PTHREAD_RWLOCK_unlock(&log_rwlock); LogCrit(COMPONENT_LOG, "Attempting release of non-existent log facility (%s)", name); return; } if (facility == default_facility) { PTHREAD_RWLOCK_unlock(&log_rwlock); LogCrit(COMPONENT_LOG, "Attempting to release default log facility (%s)", name); return; } if (!glist_null(&facility->lf_active)) glist_del(&facility->lf_active); glist_del(&facility->lf_list); PTHREAD_RWLOCK_unlock(&log_rwlock); if (facility->lf_func == log_to_file && facility->lf_private != NULL) gsh_free(facility->lf_private); gsh_free(facility->lf_name); gsh_free(facility); } /** * @brief Enable the named logger * * Enabling a logger adds it to the list of facilities that will be * used to report messages. * * @param name [IN] the name of the logger to enable * * @return 0 on success, -errno on errors. */ int enable_log_facility(const char *name) { struct log_facility *facility; if (name == NULL || *name == '\0') return -EINVAL; PTHREAD_RWLOCK_wrlock(&log_rwlock); facility = find_log_facility(name); if (facility == NULL) { PTHREAD_RWLOCK_unlock(&log_rwlock); LogInfo(COMPONENT_LOG, "Facility %s does not exist", name); return -ENOENT; } if (glist_null(&facility->lf_active)) glist_add_tail(&active_facility_list, &facility->lf_active); if (facility->lf_headers > max_headers) max_headers = facility->lf_headers; PTHREAD_RWLOCK_unlock(&log_rwlock); return 0; } /** * @brief Disable the named logger * * Disabling a logger ends logging output to that facility. * Disabling the default logger is not allowed. Another facility * must be set instead. Loggers can be re-enabled at any time. * * @param name [IN] the name of the logger to enable * * @return 0 on success, -errno on errors. */ int disable_log_facility(const char *name) { struct log_facility *facility; if (name == NULL || *name == '\0') return -EINVAL; PTHREAD_RWLOCK_wrlock(&log_rwlock); facility = find_log_facility(name); if (facility == NULL) { PTHREAD_RWLOCK_unlock(&log_rwlock); LogInfo(COMPONENT_LOG, "Facility %s does not exist", name); return -ENOENT; } if (glist_null(&facility->lf_active)) { PTHREAD_RWLOCK_unlock(&log_rwlock); LogDebug(COMPONENT_LOG, "Log facility (%s) is already disabled", name); return 0; } if (facility == default_facility) { PTHREAD_RWLOCK_unlock(&log_rwlock); LogCrit(COMPONENT_LOG, "Cannot disable the default logger (%s)", default_facility->lf_name); return -EPERM; } glist_del(&facility->lf_active); if (facility->lf_headers == max_headers) { struct glist_head *glist; struct log_facility *found; max_headers = LH_NONE; glist_for_each(glist, &active_facility_list) { found = glist_entry(glist, struct log_facility, lf_active); if (found->lf_headers > max_headers) max_headers = found->lf_headers; } } PTHREAD_RWLOCK_unlock(&log_rwlock); return 0; } /** * @brief Set the named logger as the default logger * * The default logger can not be released sp we set another one as * the default instead. The previous default logger is disabled. * * @param name [IN] the name of the logger to enable * * @return 0 on success, -errno on errors. */ static int set_default_log_facility(const char *name) { struct log_facility *facility; if (name == NULL || *name == '\0') return -EINVAL; PTHREAD_RWLOCK_wrlock(&log_rwlock); facility = find_log_facility(name); if (facility == NULL) { PTHREAD_RWLOCK_unlock(&log_rwlock); LogCrit(COMPONENT_LOG, "Facility %s does not exist", name); return -ENOENT; } if (facility == default_facility) goto out; if (glist_null(&facility->lf_active)) glist_add_tail(&active_facility_list, &facility->lf_active); if (default_facility != NULL) { assert(!glist_null(&default_facility->lf_active)); glist_del(&default_facility->lf_active); if (facility->lf_headers != max_headers) { struct glist_head *glist; struct log_facility *found; max_headers = LH_NONE; glist_for_each(glist, &active_facility_list) { found = glist_entry(glist, struct log_facility, lf_active); if (found->lf_headers > max_headers) max_headers = found->lf_headers; } } } else if (facility->lf_headers > max_headers) max_headers = facility->lf_headers; default_facility = facility; out: PTHREAD_RWLOCK_unlock(&log_rwlock); return 0; } /** * @brief Set the destination for logger * * This function only works if the facility outputs to files. * * @param name [IN] the name of the facility * @param dest [IN] "stdout", "stderr", "syslog", or a file path * * @return 0 on success, -errno on errors */ int set_log_destination(const char *name, char *dest) { struct log_facility *facility; int rc; if (name == NULL || *name == '\0') return -EINVAL; if (dest == NULL || *dest == '\0' || strlen(dest) >= MAXPATHLEN) { LogCrit(COMPONENT_LOG, "New log file path empty or too long"); return -EINVAL; } PTHREAD_RWLOCK_wrlock(&log_rwlock); facility = find_log_facility(name); if (facility == NULL) { PTHREAD_RWLOCK_unlock(&log_rwlock); LogCrit(COMPONENT_LOG, "No such log facility (%s)", name); return -ENOENT; } if (facility->lf_func == log_to_file) { char *logfile, *dir; dir = gsh_strdupa(dest); dir = dirname(dir); rc = access(dir, W_OK); if (rc != 0) { PTHREAD_RWLOCK_unlock(&log_rwlock); LogCrit(COMPONENT_LOG, "Cannot create new log file (%s), because: %s", dest, strerror(errno)); return -errno; } logfile = gsh_strdup(dest); gsh_free(facility->lf_private); facility->lf_private = logfile; } else if (facility->lf_func == log_to_stream) { FILE *out; if (strcasecmp(dest, "stdout") == 0) { out = stdout; } else if (strcasecmp(dest, "stderr") == 0) { out = stderr; } else { PTHREAD_RWLOCK_unlock(&log_rwlock); LogCrit(COMPONENT_LOG, "Expected STDERR or STDOUT, not (%s)", dest); return -EINVAL; } facility->lf_private = out; } else { PTHREAD_RWLOCK_unlock(&log_rwlock); LogCrit(COMPONENT_LOG, "Log facility %s destination is not changeable", facility->lf_name); return -EINVAL; } PTHREAD_RWLOCK_unlock(&log_rwlock); return 0; } /** * @brief Set maximum logging level for a facilty * * @param name [IN] the name of the facility * @param max_level [IN] Maximum level * * * @return 0 on success, -errno on errors */ int set_log_level(const char *name, log_levels_t max_level) { struct log_facility *facility; if (name == NULL || *name == '\0') return -EINVAL; if (max_level < NIV_NULL || max_level >= NB_LOG_LEVEL) return -EINVAL; PTHREAD_RWLOCK_wrlock(&log_rwlock); facility = find_log_facility(name); if (facility == NULL) { PTHREAD_RWLOCK_unlock(&log_rwlock); LogCrit(COMPONENT_LOG, "No such log facility (%s)", name); return -ENOENT; } facility->lf_max_level = max_level; PTHREAD_RWLOCK_unlock(&log_rwlock); return 0; } /** * @brief Initialize Logging * * Called very early in server init to make logging available as * soon as possible. Create a logger to stderr first and make it * the default. We are forced to fprintf to stderr by hand until * this happens. Once this is up, the logger is working. * We then get stdout and syslog loggers init'd. * If log_path (passed in via -L on the command line), we get a * FILE logger going and make it our default logger. Otherwise, * we use syslog as the default. * * @param log_path [IN] optarg from -L, otherwise NULL * @param debug_level [IN] global debug level from -N optarg */ void init_logging(const char *log_path, const int debug_level) { int rc; /* Finish initialization of and register log facilities. */ PTHREAD_RWLOCK_init(&log_rwlock, NULL); #ifdef _DONT_HAVE_LOCALTIME_R PTHREAD_MUTEX_init(&mutex_localtime, NULL); #endif glist_init(&facility_list); glist_init(&active_facility_list); /* Initialize const_log_str to defaults. Ganesha can start logging * before the LOG config is processed (in fact, LOG config can itself * issue log messages to indicate errors. */ set_const_log_str(); rc = create_log_facility("STDERR", log_to_stream, NIV_FULL_DEBUG, LH_ALL, stderr); if (rc != 0) { fprintf(stderr, "Create error (%s) for STDERR log facility!", strerror(-rc)); Fatal(); } rc = set_default_log_facility("STDERR"); if (rc != 0) { fprintf(stderr, "Enable error (%s) for STDERR log facility!", strerror(-rc)); Fatal(); } rc = create_log_facility("STDOUT", log_to_stream, NIV_FULL_DEBUG, LH_ALL, stdout); if (rc != 0) LogFatal(COMPONENT_LOG, "Create error (%s) for STDOUT log facility!", strerror(-rc)); rc = create_log_facility("SYSLOG", log_to_syslog, NIV_FULL_DEBUG, LH_COMPONENT, NULL); if (rc != 0) LogFatal(COMPONENT_LOG, "Create error (%s) for SYSLOG log facility!", strerror(-rc)); if (log_path) { if ((strcmp(log_path, "STDERR") == 0) || (strcmp(log_path, "SYSLOG") == 0) || (strcmp(log_path, "STDOUT") == 0)) { rc = set_default_log_facility(log_path); if (rc != 0) LogFatal(COMPONENT_LOG, "Enable error (%s) for %s logging!", strerror(-rc), log_path); } else { rc = create_log_facility("FILE", log_to_file, NIV_FULL_DEBUG, LH_ALL, (void *)log_path); if (rc != 0) LogFatal( COMPONENT_LOG, "Create error (%s) for FILE (%s) logging!", strerror(-rc), log_path); rc = set_default_log_facility("FILE"); if (rc != 0) LogFatal( COMPONENT_LOG, "Enable error (%s) for FILE (%s) logging!", strerror(-rc), log_path); } } else { /* Fall back to SYSLOG as the first default facility */ rc = set_default_log_facility("SYSLOG"); if (rc != 0) LogFatal(COMPONENT_LOG, "Enable error (%s) for SYSLOG logging!", strerror(-rc)); } if (debug_level >= 0) { LogChanges("Setting log level for all components to %s", ReturnLevelInt(debug_level)); SetLevelDebug(debug_level); original_log_level = debug_level; } } /* * Routines for managing error messages */ static int log_to_syslog(log_header_t headers, void *private, log_levels_t level, struct display_buffer *buffer, char *compstr, char *message) { if (!syslog_opened) { openlog("nfs-ganesha", LOG_PID, LOG_USER); syslog_opened = 1; } /* Writing to syslog. */ syslog(tabLogLevel[level].syslog_level, "%s", compstr); return 0; } static int log_to_file(log_header_t headers, void *private, log_levels_t level, struct display_buffer *buffer, char *compstr, char *message) { int fd, my_status, len, rc = 0; char *path = private; len = display_buffer_len(buffer); /* Add newline to end of buffer, this is why LOG_BUF_EXTRA is 1 */ buffer->b_start[len] = '\n'; buffer->b_start[len + 1] = '\0'; fd = open(path, O_WRONLY | O_APPEND | O_CREAT, log_mask); if (fd != -1) { rc = write(fd, buffer->b_start, len + 1); if (rc < (len + 1)) { if (rc >= 0) my_status = ENOSPC; else my_status = errno; (void)close(fd); goto error; } rc = close(fd); if (rc == 0) goto out; } my_status = errno; error: fprintf(stderr, "Error: couldn't complete write to the log file %s status=%d (%s) message was:\n%s", path, my_status, strerror(my_status), buffer->b_start); out: /* Remove newline from buffer */ buffer->b_start[len] = '\0'; return rc; } static int log_to_stream(log_header_t headers, void *private, log_levels_t level, struct display_buffer *buffer, char *compstr, char *message) { FILE *stream = private; int rc; char *msg = buffer->b_start; int len; len = display_buffer_len(buffer); /* Add newline to end of buffer, this is why LOG_BUF_EXTRA is 1 */ buffer->b_start[len] = '\n'; buffer->b_start[len + 1] = '\0'; switch (headers) { case LH_NONE: msg = message; break; case LH_COMPONENT: msg = compstr; break; case LH_ALL: msg = buffer->b_start; break; default: msg = "Somehow header level got messed up!!"; } rc = fputs(msg, stream); if (rc != EOF) rc = fflush(stream); /* Remove newline from buffer */ buffer->b_start[len] = '\0'; if (rc == EOF) return -1; else return 0; } int display_timeval(struct display_buffer *dspbuf, struct timeval *tv) { char *fmt = date_time_fmt; int b_left = display_start(dspbuf); struct tm the_date; char tbuf[MAX_TD_FMT_LEN]; time_t tm = tv->tv_sec; if (b_left <= 0) return b_left; if (logfields->datefmt == TD_NONE && logfields->timefmt == TD_NONE) fmt = "%c "; if (disp_utc_timestamp) gmtime_r(&tm, &the_date); else Localtime_r(&tm, &the_date); /* Earlier we build the date/time format string in * date_time_fmt, now use that to format the time and/or date. * If time format is TD_SYSLOG_USEC, then we need an additional * step to add the microseconds (since strftime just takes a * struct tm which was filled in from a time_t and thus does not * have microseconds. */ if (strftime(tbuf, sizeof(tbuf), fmt, &the_date) != 0) { if (logfields->timefmt == TD_SYSLOG_USEC) b_left = display_printf(dspbuf, tbuf, tv->tv_usec); else b_left = display_cat(dspbuf, tbuf); } return b_left; } int display_timespec(struct display_buffer *dspbuf, struct timespec *ts) { char *fmt = date_time_fmt; int b_left = display_start(dspbuf); struct tm the_date; char tbuf[MAX_TD_FMT_LEN]; time_t tm = ts->tv_sec; if (b_left <= 0) return b_left; if (logfields->datefmt == TD_NONE && logfields->timefmt == TD_NONE) fmt = "%c "; Localtime_r(&tm, &the_date); /* Earlier we build the date/time format string in * date_time_fmt, now use that to format the time and/or date. * If time format is TD_SYSLOG_USEC, then we need an additional * step to add the microseconds (since strftime just takes a * struct tm which was filled in from a time_t and thus does not * have microseconds. */ if (strftime(tbuf, sizeof(tbuf), fmt, &the_date) != 0) { if (logfields->timefmt == TD_SYSLOG_USEC) b_left = display_printf(dspbuf, tbuf, ts->tv_nsec); else b_left = display_cat(dspbuf, tbuf); } return b_left; } static int display_log_header(struct display_buffer *dsp_log) { int b_left = display_start(dsp_log); if (b_left <= 0 || max_headers < LH_ALL) return b_left; /* Print date and/or time if either flag is enabled. */ if (b_left > 0 && (logfields->datefmt != TD_NONE || logfields->timefmt != TD_NONE)) { struct timeval tv; if (logfields->timefmt == TD_SYSLOG_USEC) { gettimeofday(&tv, NULL); } else { tv.tv_sec = time(NULL); tv.tv_usec = 0; } b_left = display_timeval(dsp_log, &tv); if (b_left > 0) b_left = display_cat(dsp_log, " "); } if (b_left > 0 && const_log_str[0] != '\0') b_left = display_cat(dsp_log, const_log_str); /* If thread name will not follow, need a : separator */ if (b_left > 0 && !logfields->disp_threadname) b_left = display_cat(dsp_log, ": "); /* If we overflowed the buffer with the header, just skip it. */ if (b_left == 0) { display_reset_buffer(dsp_log); b_left = display_start(dsp_log); } /* The message will now start at dsp_log.b_current */ return b_left; } static int display_log_component(struct display_buffer *dsp_log, log_components_t component, const char *file, int line, const char *function, int level) { int b_left = display_start(dsp_log); if (b_left <= 0 || max_headers < LH_COMPONENT) return b_left; if (b_left > 0 && logfields->disp_clientip) { if (clientip) b_left = display_printf(dsp_log, "[%s] ", clientip); else b_left = display_printf(dsp_log, "[none] "); } if (b_left > 0 && logfields->disp_threadname) { if (thread_name[0] != '\0') b_left = display_printf(dsp_log, "[%s] ", thread_name); else b_left = display_printf(dsp_log, "[%p] ", thread_name); } if (b_left > 0 && logfields->disp_filename) { if (logfields->disp_linenum) b_left = display_printf(dsp_log, "%s:", file); else b_left = display_printf(dsp_log, "%s :", file); } if (b_left > 0 && logfields->disp_linenum) b_left = display_printf(dsp_log, "%d :", line); if (b_left > 0 && logfields->disp_funct) b_left = display_printf(dsp_log, "%s :", function); if (b_left > 0 && logfields->disp_comp) b_left = display_printf( dsp_log, "%s :", LogComponents[component].comp_str); if (b_left > 0 && logfields->disp_level) b_left = display_printf(dsp_log, "%s :", tabLogLevel[level].short_str); if (b_left > 0 && logfields->disp_op_id) { if (op_ctx) b_left = display_printf(dsp_log, "op_id=%u :", op_ctx->op_id); else b_left = display_printf(dsp_log, "op_id=none :"); } if (b_left > 0 && logfields->disp_client_req_xid) { if (op_ctx && op_ctx->nfs_reqdata) b_left = display_printf( dsp_log, "xid=%X :", op_ctx->nfs_reqdata->svc.rq_msg.rm_xid); } /* If we overflowed the buffer with the header, just skip it. */ if (b_left == 0) { display_reset_buffer(dsp_log); b_left = display_start(dsp_log); } return b_left; } void display_log_component_level(log_components_t component, const char *file, int line, const char *function, log_levels_t level, const char *format, va_list arguments) { char *compstr; char *message; int b_left; struct glist_head *glist; struct log_facility *facility; struct display_buffer dsp_log = { LOG_BUF_USE, log_buffer, log_buffer }; /* Build up the message and capture the various positions in it. */ b_left = display_log_header(&dsp_log); if (b_left > 0) compstr = dsp_log.b_current; else compstr = dsp_log.b_start; if (b_left > 0) b_left = display_log_component(&dsp_log, component, file, line, function, level); if (b_left > 0) message = dsp_log.b_current; else message = dsp_log.b_start; if (b_left > 0) b_left = display_vprintf(&dsp_log, format, arguments); PTHREAD_RWLOCK_rdlock(&log_rwlock); glist_for_each(glist, &active_facility_list) { facility = glist_entry(glist, struct log_facility, lf_active); if (level <= facility->lf_max_level && facility->lf_func != NULL) facility->lf_func(facility->lf_headers, facility->lf_private, level, &dsp_log, compstr, message); } PTHREAD_RWLOCK_unlock(&log_rwlock); if (level == NIV_FATAL) Fatal(); } /** * @brief Default logging levels * * These are for early initialization and whenever we * have to fall back to something that will at least work... */ static log_levels_t default_log_levels[] = { [COMPONENT_ALL] = NIV_NULL, [COMPONENT_LOG] = NIV_EVENT, [COMPONENT_MEM_ALLOC] = NIV_EVENT, [COMPONENT_MEMLEAKS] = NIV_EVENT, [COMPONENT_FSAL] = NIV_EVENT, [COMPONENT_NFSPROTO] = NIV_EVENT, [COMPONENT_NFS_V4] = NIV_EVENT, [COMPONENT_EXPORT] = NIV_EVENT, [COMPONENT_FILEHANDLE] = NIV_EVENT, [COMPONENT_DISPATCH] = NIV_EVENT, [COMPONENT_MDCACHE] = NIV_EVENT, [COMPONENT_MDCACHE_LRU] = NIV_EVENT, [COMPONENT_HASHTABLE] = NIV_EVENT, [COMPONENT_HASHTABLE_CACHE] = NIV_EVENT, [COMPONENT_DUPREQ] = NIV_EVENT, [COMPONENT_INIT] = NIV_EVENT, [COMPONENT_MAIN] = NIV_EVENT, [COMPONENT_IDMAPPER] = NIV_EVENT, [COMPONENT_NFS_READDIR] = NIV_EVENT, [COMPONENT_NFS_V4_LOCK] = NIV_EVENT, [COMPONENT_CONFIG] = NIV_EVENT, [COMPONENT_CLIENTID] = NIV_EVENT, [COMPONENT_SESSIONS] = NIV_EVENT, [COMPONENT_PNFS] = NIV_EVENT, [COMPONENT_RW_LOCK] = NIV_EVENT, [COMPONENT_NLM] = NIV_EVENT, [COMPONENT_TIRPC] = NIV_EVENT, [COMPONENT_NFS_CB] = NIV_EVENT, [COMPONENT_THREAD] = NIV_EVENT, [COMPONENT_NFS_V4_ACL] = NIV_EVENT, [COMPONENT_STATE] = NIV_EVENT, [COMPONENT_9P] = NIV_EVENT, [COMPONENT_9P_DISPATCH] = NIV_EVENT, [COMPONENT_FSAL_UP] = NIV_EVENT, [COMPONENT_DBUS] = NIV_EVENT, [COMPONENT_NFS_MSK] = NIV_EVENT, [COMPONENT_XPRT] = NIV_EVENT, }; /* Active set of log levels */ log_levels_t *component_log_level = default_log_levels; /* Original log level set by -N or otherwise code default */ log_levels_t original_log_level = NIV_EVENT; /* Default log level setby LOG { default_log_level }, setting to NB_LOG_LEVEL * indicates it has not been specified in the config (in which case we fall * back to original_log_level. */ log_levels_t default_log_level = NB_LOG_LEVEL; struct log_component_info LogComponents[COMPONENT_COUNT] = { [COMPONENT_ALL] = { .comp_name = "COMPONENT_ALL", .comp_str = "",}, [COMPONENT_LOG] = { .comp_name = "COMPONENT_LOG", .comp_str = "LOG",}, [COMPONENT_MEM_ALLOC] = { .comp_name = "COMPONENT_MEM_ALLOC", .comp_str = "MEM ALLOC",}, [COMPONENT_MEMLEAKS] = { .comp_name = "COMPONENT_MEMLEAKS", .comp_str = "LEAKS",}, [COMPONENT_FSAL] = { .comp_name = "COMPONENT_FSAL", .comp_str = "FSAL",}, [COMPONENT_NFSPROTO] = { .comp_name = "COMPONENT_NFSPROTO", .comp_str = "NFS3",}, [COMPONENT_NFS_V4] = { .comp_name = "COMPONENT_NFS_V4", .comp_str = "NFS4",}, [COMPONENT_EXPORT] = { .comp_name = "COMPONENT_EXPORT", .comp_str = "EXPORT",}, [COMPONENT_FILEHANDLE] = { .comp_name = "COMPONENT_FILEHANDLE", .comp_str = "FH",}, [COMPONENT_DISPATCH] = { .comp_name = "COMPONENT_DISPATCH", .comp_str = "DISP",}, [COMPONENT_MDCACHE] = { .comp_name = "COMPONENT_MDCACHE", .comp_str = "MDCACHE",}, [COMPONENT_MDCACHE_LRU] = { .comp_name = "COMPONENT_MDCACHE_LRU", .comp_str = "MDCACHE LRU",}, [COMPONENT_HASHTABLE] = { .comp_name = "COMPONENT_HASHTABLE", .comp_str = "HT",}, [COMPONENT_HASHTABLE_CACHE] = { .comp_name = "COMPONENT_HASHTABLE_CACHE", .comp_str = "HT CACHE",}, [COMPONENT_DUPREQ] = { .comp_name = "COMPONENT_DUPREQ", .comp_str = "DUPREQ",}, [COMPONENT_INIT] = { .comp_name = "COMPONENT_INIT", .comp_str = "NFS STARTUP",}, [COMPONENT_MAIN] = { .comp_name = "COMPONENT_MAIN", .comp_str = "MAIN",}, [COMPONENT_IDMAPPER] = { .comp_name = "COMPONENT_IDMAPPER", .comp_str = "ID MAPPER",}, [COMPONENT_NFS_READDIR] = { .comp_name = "COMPONENT_NFS_READDIR", .comp_str = "NFS READDIR",}, [COMPONENT_NFS_V4_LOCK] = { .comp_name = "COMPONENT_NFS_V4_LOCK", .comp_str = "NFS4 LOCK",}, [COMPONENT_CONFIG] = { .comp_name = "COMPONENT_CONFIG", .comp_str = "CONFIG",}, [COMPONENT_CLIENTID] = { .comp_name = "COMPONENT_CLIENTID", .comp_str = "CLIENT ID",}, [COMPONENT_SESSIONS] = { .comp_name = "COMPONENT_SESSIONS", .comp_str = "SESSIONS",}, [COMPONENT_PNFS] = { .comp_name = "COMPONENT_PNFS", .comp_str = "PNFS",}, [COMPONENT_RW_LOCK] = { .comp_name = "COMPONENT_RW_LOCK", .comp_str = "RW LOCK",}, [COMPONENT_NLM] = { .comp_name = "COMPONENT_NLM", .comp_str = "NLM",}, [COMPONENT_TIRPC] = { .comp_name = "COMPONENT_TIRPC", .comp_str = "TIRPC",}, [COMPONENT_NFS_CB] = { .comp_name = "COMPONENT_NFS_CB", .comp_str = "NFS CB",}, [COMPONENT_THREAD] = { .comp_name = "COMPONENT_THREAD", .comp_str = "THREAD",}, [COMPONENT_NFS_V4_ACL] = { .comp_name = "COMPONENT_NFS_V4_ACL", .comp_str = "NFS4 ACL",}, [COMPONENT_STATE] = { .comp_name = "COMPONENT_STATE", .comp_str = "STATE",}, [COMPONENT_9P] = { .comp_name = "COMPONENT_9P", .comp_str = "9P",}, [COMPONENT_9P_DISPATCH] = { .comp_name = "COMPONENT_9P_DISPATCH", .comp_str = "9P DISP",}, [COMPONENT_FSAL_UP] = { .comp_name = "COMPONENT_FSAL_UP", .comp_str = "FSAL_UP",}, [COMPONENT_DBUS] = { .comp_name = "COMPONENT_DBUS", .comp_str = "DBUS",}, [COMPONENT_NFS_MSK] = { .comp_name = "COMPONENT_NFS_MSK", .comp_str = "NFS_MSK",}, [COMPONENT_XPRT] = { .comp_name = "COMPONENT_XPRT", .comp_str = "XPRT",}, }; void DisplayLogComponentLevel(log_components_t component, const char *file, int line, const char *function, log_levels_t level, const char *format, ...) { va_list arguments; va_start(arguments, format); display_log_component_level(component, file, line, function, level, format, arguments); va_end(arguments); } void LogMallocFailure(const char *file, int line, const char *function, const char *allocator) { DisplayLogComponentLevel(COMPONENT_MEM_ALLOC, (char *)file, line, (char *)function, NIV_NULL, "Aborting %s due to out of memory", allocator); } /* * Re-export component logging to TI-RPC internal logging */ void rpc_warnx(char *fmt, ...) { va_list ap; if (component_log_level[COMPONENT_TIRPC] <= NIV_FATAL) return; va_start(ap, fmt); display_log_component_level(COMPONENT_TIRPC, "", 0, "rpc", component_log_level[COMPONENT_TIRPC], fmt, ap); va_end(ap); } #ifdef USE_DBUS static bool dbus_prop_get(log_components_t component, DBusMessageIter *reply) { char *level_code; level_code = ReturnLevelInt(component_log_level[component]); if (level_code == NULL) return false; if (!dbus_message_iter_append_basic(reply, DBUS_TYPE_STRING, &level_code)) return false; return true; } static bool dbus_prop_set(log_components_t component, DBusMessageIter *arg) { char *level_code; int log_level; if (dbus_message_iter_get_arg_type(arg) != DBUS_TYPE_STRING) return false; dbus_message_iter_get_basic(arg, &level_code); log_level = ReturnLevelAscii(level_code); if (log_level == -1) { LogDebug(COMPONENT_DBUS, "Invalid log level: '%s' given for component %s", level_code, LogComponents[component].comp_name); return false; } if (component == COMPONENT_ALL) { LogChanges("Dbus setting log level for all components to %s", level_code); SetLevelDebug(log_level); } else { LogChanges("Dbus set log level for %s from %s to %s.", LogComponents[component].comp_name, ReturnLevelInt(component_log_level[component]), ReturnLevelInt(log_level)); SetComponentLogLevel(component, log_level); } return true; } /* Macros to make mapping properties table to components enum etc. easier * expands to table entries and shim functions. */ #define HANDLE_PROP(component) \ static bool dbus_prop_get_COMPONENT_##component( \ DBusMessageIter *reply) \ { \ return dbus_prop_get(COMPONENT_##component, reply); \ } \ \ static bool dbus_prop_set_COMPONENT_##component(DBusMessageIter *args) \ { \ return dbus_prop_set(COMPONENT_##component, args); \ } \ \ static struct gsh_dbus_prop COMPONENT_##component##_prop = { \ .name = "COMPONENT_" #component, \ .access = DBUS_PROP_READWRITE, \ .type = "s", \ .get = dbus_prop_get_COMPONENT_##component, \ .set = dbus_prop_set_COMPONENT_##component \ } #define LOG_PROPERTY_ITEM(component) (&COMPONENT_##component##_prop) /** * @brief Log property handlers. * * Expands to get/set functions that match dbus_prop_get/set protos * and call common handler with component enum as arg. * There is one line per log_components_t enum. * These must also match LOG_PROPERTY_ITEM */ HANDLE_PROP(ALL); HANDLE_PROP(LOG); HANDLE_PROP(MEM_ALLOC); HANDLE_PROP(MEMLEAKS); HANDLE_PROP(FSAL); HANDLE_PROP(NFSPROTO); HANDLE_PROP(NFS_V4); HANDLE_PROP(EXPORT); HANDLE_PROP(FILEHANDLE); HANDLE_PROP(DISPATCH); HANDLE_PROP(MDCACHE); HANDLE_PROP(MDCACHE_LRU); HANDLE_PROP(HASHTABLE); HANDLE_PROP(HASHTABLE_CACHE); HANDLE_PROP(DUPREQ); HANDLE_PROP(INIT); HANDLE_PROP(MAIN); HANDLE_PROP(IDMAPPER); HANDLE_PROP(NFS_READDIR); HANDLE_PROP(NFS_V4_LOCK); HANDLE_PROP(CONFIG); HANDLE_PROP(CLIENTID); HANDLE_PROP(SESSIONS); HANDLE_PROP(PNFS); HANDLE_PROP(RW_LOCK); HANDLE_PROP(NLM); HANDLE_PROP(TIRPC); HANDLE_PROP(NFS_CB); HANDLE_PROP(THREAD); HANDLE_PROP(NFS_V4_ACL); HANDLE_PROP(STATE); HANDLE_PROP(9P); HANDLE_PROP(9P_DISPATCH); HANDLE_PROP(FSAL_UP); HANDLE_PROP(DBUS); HANDLE_PROP(NFS_MSK); HANDLE_PROP(XPRT); static struct gsh_dbus_prop *log_props[] = { LOG_PROPERTY_ITEM(ALL), LOG_PROPERTY_ITEM(LOG), LOG_PROPERTY_ITEM(MEM_ALLOC), LOG_PROPERTY_ITEM(MEMLEAKS), LOG_PROPERTY_ITEM(FSAL), LOG_PROPERTY_ITEM(NFSPROTO), LOG_PROPERTY_ITEM(NFS_V4), LOG_PROPERTY_ITEM(EXPORT), LOG_PROPERTY_ITEM(FILEHANDLE), LOG_PROPERTY_ITEM(DISPATCH), LOG_PROPERTY_ITEM(MDCACHE), LOG_PROPERTY_ITEM(MDCACHE_LRU), LOG_PROPERTY_ITEM(HASHTABLE), LOG_PROPERTY_ITEM(HASHTABLE_CACHE), LOG_PROPERTY_ITEM(DUPREQ), LOG_PROPERTY_ITEM(INIT), LOG_PROPERTY_ITEM(MAIN), LOG_PROPERTY_ITEM(IDMAPPER), LOG_PROPERTY_ITEM(NFS_READDIR), LOG_PROPERTY_ITEM(NFS_V4_LOCK), LOG_PROPERTY_ITEM(CONFIG), LOG_PROPERTY_ITEM(CLIENTID), LOG_PROPERTY_ITEM(SESSIONS), LOG_PROPERTY_ITEM(PNFS), LOG_PROPERTY_ITEM(RW_LOCK), LOG_PROPERTY_ITEM(NLM), LOG_PROPERTY_ITEM(TIRPC), LOG_PROPERTY_ITEM(NFS_CB), LOG_PROPERTY_ITEM(THREAD), LOG_PROPERTY_ITEM(NFS_V4_ACL), LOG_PROPERTY_ITEM(STATE), LOG_PROPERTY_ITEM(9P), LOG_PROPERTY_ITEM(9P_DISPATCH), LOG_PROPERTY_ITEM(FSAL_UP), LOG_PROPERTY_ITEM(DBUS), LOG_PROPERTY_ITEM(NFS_MSK), LOG_PROPERTY_ITEM(XPRT), NULL }; struct gsh_dbus_interface log_interface = { .name = "org.ganesha.nfsd.log.component", .signal_props = false, .props = log_props, .methods = NULL, .signals = NULL }; #endif /* USE_DBUS */ enum facility_state { FAC_IDLE, FAC_ACTIVE, FAC_DEFAULT }; struct facility_config { struct glist_head fac_list; char *facility_name; char *dest; enum facility_state state; lf_function_t *func; log_header_t headers; log_levels_t max_level; void *lf_private; }; /** * @brief Logger config block parameters */ struct logger_config { struct glist_head facility_list; struct logfields *logfields; log_levels_t *comp_log_level; log_levels_t default_log_level; uint32_t rpc_debug_flags; bool disp_utc_timestamp; }; /** * @brief Enumerated time and date format parameters */ static struct config_item_list timeformats[] = { CONFIG_LIST_TOK("ganesha", TD_GANESHA), CONFIG_LIST_TOK("true", TD_GANESHA), CONFIG_LIST_TOK("local", TD_LOCAL), CONFIG_LIST_TOK("8601", TD_8601), CONFIG_LIST_TOK("ISO-8601", TD_8601), CONFIG_LIST_TOK("ISO 8601", TD_8601), CONFIG_LIST_TOK("ISO", TD_8601), CONFIG_LIST_TOK("syslog", TD_SYSLOG), CONFIG_LIST_TOK("syslog_usec", TD_SYSLOG_USEC), CONFIG_LIST_TOK("false", TD_NONE), CONFIG_LIST_TOK("none", TD_NONE), CONFIG_LIST_TOK("user_defined", TD_USER), CONFIG_LIST_EOL }; /** * @brief Logging format parameters */ static struct config_item format_options[] = { CONF_ITEM_TOKEN("date_format", TD_GANESHA, timeformats, logfields, datefmt), CONF_ITEM_TOKEN("time_format", TD_GANESHA, timeformats, logfields, timefmt), CONF_ITEM_STR("user_date_format", 1, MAX_TD_FMT_LEN, NULL, logfields, user_date_fmt), CONF_ITEM_STR("user_time_format", 1, MAX_TD_FMT_LEN, NULL, logfields, user_time_fmt), CONF_ITEM_BOOL("EPOCH", true, logfields, disp_epoch), CONF_ITEM_BOOL("CLIENTIP", false, logfields, disp_clientip), CONF_ITEM_BOOL("HOSTNAME", true, logfields, disp_host), CONF_ITEM_BOOL("PROGNAME", true, logfields, disp_prog), CONF_ITEM_BOOL("PID", true, logfields, disp_pid), CONF_ITEM_BOOL("THREAD_NAME", true, logfields, disp_threadname), CONF_ITEM_BOOL("FILE_NAME", true, logfields, disp_filename), CONF_ITEM_BOOL("LINE_NUM", true, logfields, disp_linenum), CONF_ITEM_BOOL("FUNCTION_NAME", true, logfields, disp_funct), CONF_ITEM_BOOL("COMPONENT", true, logfields, disp_comp), CONF_ITEM_BOOL("LEVEL", true, logfields, disp_level), CONF_ITEM_BOOL("OP_ID", false, logfields, disp_op_id), CONF_ITEM_BOOL("CLIENT_REQ_XID", false, logfields, disp_client_req_xid), CONFIG_EOL }; /** * @brief Initialize the log message format parameters */ static void *format_init(void *link_mem, void *self_struct) { assert(link_mem != NULL || self_struct != NULL); if (link_mem == NULL) return NULL; if (self_struct == NULL) return gsh_calloc(1, sizeof(struct logfields)); else { struct logfields *lf = self_struct; if (lf->user_date_fmt != NULL) gsh_free(lf->user_date_fmt); if (lf->user_time_fmt != NULL) gsh_free(lf->user_time_fmt); gsh_free(lf); return NULL; } } /** * @brief Commit the log format parameters * * I'd prefer that Date_format and Time_format be enums but they are not. * They are enums except when they are not and we do hope that whatever * that is can be digested by printf... */ static int format_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { struct logfields *log = (struct logfields *)self_struct; struct logfields **logp = link_mem; struct logger_config *logger; int errcnt = 0; if (log->datefmt == TD_USER && log->user_date_fmt == NULL) { LogCrit(COMPONENT_CONFIG, "Date is \"user_set\" with empty date format."); err_type->validate = true; errcnt++; } if (log->datefmt != TD_USER && log->user_date_fmt != NULL) { LogCrit(COMPONENT_CONFIG, "Set user date format (%s) but not \"user_set\" format", log->user_date_fmt); err_type->validate = true; errcnt++; } if (log->timefmt == TD_USER && log->user_time_fmt == NULL) { LogCrit(COMPONENT_CONFIG, "Time is \"user_set\" with empty time format."); err_type->validate = true; errcnt++; } if (log->timefmt != TD_USER && log->user_time_fmt != NULL) { LogCrit(COMPONENT_CONFIG, "Set time format string (%s) but not \"user_set\" format", log->user_time_fmt); err_type->validate = true; errcnt++; } if (errcnt == 0) { logger = container_of(logp, struct logger_config, logfields); logger->logfields = log; } return errcnt; } /** * @brief Log component levels */ static struct config_item_list log_levels[] = { CONFIG_LIST_TOK("NULL", NIV_NULL), CONFIG_LIST_TOK("FATAL", NIV_FATAL), CONFIG_LIST_TOK("MAJ", NIV_MAJ), CONFIG_LIST_TOK("CRIT", NIV_CRIT), CONFIG_LIST_TOK("WARN", NIV_WARN), CONFIG_LIST_TOK("EVENT", NIV_EVENT), CONFIG_LIST_TOK("INFO", NIV_INFO), CONFIG_LIST_TOK("DEBUG", NIV_DEBUG), CONFIG_LIST_TOK("MID_DEBUG", NIV_MID_DEBUG), CONFIG_LIST_TOK("M_DBG", NIV_MID_DEBUG), CONFIG_LIST_TOK("FULL_DEBUG", NIV_FULL_DEBUG), CONFIG_LIST_TOK("F_DBG", NIV_FULL_DEBUG), CONFIG_LIST_EOL }; /** * @brief Logging components */ static struct config_item component_levels[] = { CONF_INDEX_TOKEN("ALL", NB_LOG_LEVEL, log_levels, COMPONENT_ALL, int), CONF_INDEX_TOKEN("LOG", NB_LOG_LEVEL, log_levels, COMPONENT_LOG, int), CONF_INDEX_TOKEN("MEM_ALLOC", NB_LOG_LEVEL, log_levels, COMPONENT_MEM_ALLOC, int), CONF_INDEX_TOKEN("MEMLEAKS", NB_LOG_LEVEL, log_levels, COMPONENT_MEMLEAKS, int), CONF_INDEX_TOKEN("LEAKS", NB_LOG_LEVEL, log_levels, COMPONENT_MEMLEAKS, int), CONF_INDEX_TOKEN("FSAL", NB_LOG_LEVEL, log_levels, COMPONENT_FSAL, int), CONF_INDEX_TOKEN("NFSPROTO", NB_LOG_LEVEL, log_levels, COMPONENT_NFSPROTO, int), CONF_INDEX_TOKEN("NFS3", NB_LOG_LEVEL, log_levels, COMPONENT_NFSPROTO, int), CONF_INDEX_TOKEN("NFS_V4", NB_LOG_LEVEL, log_levels, COMPONENT_NFS_V4, int), CONF_INDEX_TOKEN("NFS4", NB_LOG_LEVEL, log_levels, COMPONENT_NFS_V4, int), CONF_INDEX_TOKEN("EXPORT", NB_LOG_LEVEL, log_levels, COMPONENT_EXPORT, int), CONF_INDEX_TOKEN("FILEHANDLE", NB_LOG_LEVEL, log_levels, COMPONENT_FILEHANDLE, int), CONF_INDEX_TOKEN("FH", NB_LOG_LEVEL, log_levels, COMPONENT_FILEHANDLE, int), CONF_INDEX_TOKEN("DISPATCH", NB_LOG_LEVEL, log_levels, COMPONENT_DISPATCH, int), CONF_INDEX_TOKEN("DISP", NB_LOG_LEVEL, log_levels, COMPONENT_DISPATCH, int), CONF_INDEX_TOKEN("CACHE_INODE", NB_LOG_LEVEL, log_levels, COMPONENT_MDCACHE, int), CONF_INDEX_TOKEN("INODE", NB_LOG_LEVEL, log_levels, COMPONENT_MDCACHE, int), CONF_INDEX_TOKEN("MDCACHE", NB_LOG_LEVEL, log_levels, COMPONENT_MDCACHE, int), CONF_INDEX_TOKEN("CACHE_INODE_LRU", NB_LOG_LEVEL, log_levels, COMPONENT_MDCACHE_LRU, int), CONF_INDEX_TOKEN("INODE_LRU", NB_LOG_LEVEL, log_levels, COMPONENT_MDCACHE_LRU, int), CONF_INDEX_TOKEN("MDCACHE_LRU", NB_LOG_LEVEL, log_levels, COMPONENT_MDCACHE_LRU, int), CONF_INDEX_TOKEN("HASHTABLE", NB_LOG_LEVEL, log_levels, COMPONENT_HASHTABLE, int), CONF_INDEX_TOKEN("HT", NB_LOG_LEVEL, log_levels, COMPONENT_HASHTABLE, int), CONF_INDEX_TOKEN("HASHTABLE_CACHE", NB_LOG_LEVEL, log_levels, COMPONENT_HASHTABLE_CACHE, int), CONF_INDEX_TOKEN("HT_CACHE", NB_LOG_LEVEL, log_levels, COMPONENT_HASHTABLE_CACHE, int), CONF_INDEX_TOKEN("DUPREQ", NB_LOG_LEVEL, log_levels, COMPONENT_DUPREQ, int), CONF_INDEX_TOKEN("INIT", NB_LOG_LEVEL, log_levels, COMPONENT_INIT, int), CONF_INDEX_TOKEN("NFS_STARTUP", NB_LOG_LEVEL, log_levels, COMPONENT_INIT, int), CONF_INDEX_TOKEN("MAIN", NB_LOG_LEVEL, log_levels, COMPONENT_MAIN, int), CONF_INDEX_TOKEN("IDMAPPER", NB_LOG_LEVEL, log_levels, COMPONENT_IDMAPPER, int), CONF_INDEX_TOKEN("NFS_READDIR", NB_LOG_LEVEL, log_levels, COMPONENT_NFS_READDIR, int), CONF_INDEX_TOKEN("NFS_V4_LOCK", NB_LOG_LEVEL, log_levels, COMPONENT_NFS_V4_LOCK, int), CONF_INDEX_TOKEN("NFS4_LOCK", NB_LOG_LEVEL, log_levels, COMPONENT_NFS_V4_LOCK, int), CONF_INDEX_TOKEN("CONFIG", NB_LOG_LEVEL, log_levels, COMPONENT_CONFIG, int), CONF_INDEX_TOKEN("CLIENTID", NB_LOG_LEVEL, log_levels, COMPONENT_CLIENTID, int), CONF_INDEX_TOKEN("SESSIONS", NB_LOG_LEVEL, log_levels, COMPONENT_SESSIONS, int), CONF_INDEX_TOKEN("PNFS", NB_LOG_LEVEL, log_levels, COMPONENT_PNFS, int), CONF_INDEX_TOKEN("RW_LOCK", NB_LOG_LEVEL, log_levels, COMPONENT_RW_LOCK, int), CONF_INDEX_TOKEN("NLM", NB_LOG_LEVEL, log_levels, COMPONENT_NLM, int), CONF_INDEX_TOKEN("TIRPC", NB_LOG_LEVEL, log_levels, COMPONENT_TIRPC, int), CONF_INDEX_TOKEN("NFS_CB", NB_LOG_LEVEL, log_levels, COMPONENT_NFS_CB, int), CONF_INDEX_TOKEN("THREAD", NB_LOG_LEVEL, log_levels, COMPONENT_THREAD, int), CONF_INDEX_TOKEN("NFS_V4_ACL", NB_LOG_LEVEL, log_levels, COMPONENT_NFS_V4_ACL, int), CONF_INDEX_TOKEN("NFS4_ACL", NB_LOG_LEVEL, log_levels, COMPONENT_NFS_V4_ACL, int), CONF_INDEX_TOKEN("STATE", NB_LOG_LEVEL, log_levels, COMPONENT_STATE, int), CONF_INDEX_TOKEN("_9P", NB_LOG_LEVEL, log_levels, COMPONENT_9P, int), CONF_INDEX_TOKEN("_9P_DISPATCH", NB_LOG_LEVEL, log_levels, COMPONENT_9P_DISPATCH, int), CONF_INDEX_TOKEN("_9P_DISP", NB_LOG_LEVEL, log_levels, COMPONENT_9P_DISPATCH, int), CONF_INDEX_TOKEN("FSAL_UP", NB_LOG_LEVEL, log_levels, COMPONENT_FSAL_UP, int), CONF_INDEX_TOKEN("DBUS", NB_LOG_LEVEL, log_levels, COMPONENT_DBUS, int), CONF_INDEX_TOKEN("NFS_MSK", NB_LOG_LEVEL, log_levels, COMPONENT_NFS_MSK, int), CONF_INDEX_TOKEN("XPRT", NB_LOG_LEVEL, log_levels, COMPONENT_XPRT, int), CONFIG_EOL }; /** * @brief Initialize the log level array * * We allocate an array here even for the global case so as to * preserve something that works (default_log_levels) during config * processing. If the parse errors out, we just throw it away... * */ static void *component_init(void *link_mem, void *self_struct) { assert(link_mem != NULL || self_struct != NULL); if (link_mem == NULL) return NULL; if (self_struct == NULL) return gsh_calloc(COMPONENT_COUNT, sizeof(log_levels_t)); else { gsh_free(self_struct); return NULL; } } /** * @brief Commit the component levels * * COMPONENT_ALL is a magic component. It gets statically initialized * to NIV_NULL (no output) but the initialize pass changes that to * NB_LOG_LEVEL which is +1 the last valid level. This is used to detect * if COMPONENT_ALL has been set. If ALL is set, it overrides all * components including any that were set in the block. * * We also set the default for all components to be NB_LOG_LEVELS which * gets changed to the LOG { default_log_level ...} or NIV_EVENT if it * was not changed by the config. */ static int component_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { log_levels_t **log_lvls = link_mem; struct logger_config *logger; log_levels_t *log_level = self_struct; /* Save the log levels in logger for later use if all is well */ logger = container_of(log_lvls, struct logger_config, comp_log_level); logger->comp_log_level = log_level; return 0; } static struct config_item_list header_options[] = { CONFIG_LIST_TOK("none", LH_NONE), CONFIG_LIST_TOK("component", LH_COMPONENT), CONFIG_LIST_TOK("all", LH_ALL), CONFIG_LIST_EOL }; static struct config_item_list enable_options[] = { CONFIG_LIST_TOK("idle", FAC_IDLE), CONFIG_LIST_TOK("active", FAC_ACTIVE), CONFIG_LIST_TOK("default", FAC_DEFAULT), CONFIG_LIST_EOL }; static struct config_item facility_params[] = { CONF_ITEM_STR("name", 1, 20, NULL, facility_config, facility_name), CONF_MAND_STR("destination", 1, MAXPATHLEN, NULL, facility_config, dest), CONF_ITEM_TOKEN("max_level", NB_LOG_LEVEL, log_levels, facility_config, max_level), CONF_ITEM_TOKEN("headers", NB_LH_TYPES, header_options, facility_config, headers), CONF_ITEM_TOKEN("enable", FAC_IDLE, enable_options, facility_config, state), CONFIG_EOL }; /** * @brief Initialize a Facility block. * * This block is allocated just to capture the fields. It's members * are used to create/modify a facility at which point it gets freed. */ static void *facility_init(void *link_mem, void *self_struct) { struct facility_config *facility; assert(link_mem != NULL || self_struct != NULL); if (link_mem == NULL) { struct glist_head *facility_list; struct logger_config *logger; facility_list = self_struct; logger = container_of(facility_list, struct logger_config, facility_list); glist_init(&logger->facility_list); return self_struct; } else if (self_struct == NULL) { facility = gsh_calloc(1, sizeof(struct facility_config)); return facility; } else { facility = self_struct; assert(glist_null(&facility->fac_list)); if (facility->facility_name != NULL) gsh_free(facility->facility_name); if (facility->dest != NULL) gsh_free(facility->dest); gsh_free(self_struct); } return NULL; } /** * @brief Commit a facility block * * It can create a stream, syslog, or file facility and modify any * existing one. Special loggers must be created elsewhere. * Note that you cannot use a log { facility {... }} to modify one * of these special loggers because log block parsing is done first * at server initialization. */ static int facility_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { struct facility_config *conf = self_struct; struct glist_head *fac_list; int errcnt = 0; if (conf->facility_name == NULL) { LogCrit(COMPONENT_LOG, "No facility name given"); err_type->missing = true; errcnt++; return errcnt; } if (conf->dest != NULL) { if (strcasecmp(conf->dest, "stderr") == 0) { conf->func = log_to_stream; conf->lf_private = stderr; if (conf->headers == NB_LH_TYPES) conf->headers = LH_ALL; } else if (strcasecmp(conf->dest, "stdout") == 0) { conf->func = log_to_stream; conf->lf_private = stdout; if (conf->headers == NB_LH_TYPES) conf->headers = LH_ALL; } else if (strcasecmp(conf->dest, "syslog") == 0) { conf->func = log_to_syslog; if (conf->headers == NB_LH_TYPES) conf->headers = LH_COMPONENT; } else { conf->func = log_to_file; conf->lf_private = conf->dest; if (conf->headers == NB_LH_TYPES) conf->headers = LH_ALL; } } else { LogCrit(COMPONENT_LOG, "No facility destination given for (%s)", conf->facility_name); err_type->missing = true; errcnt++; return errcnt; } if (conf->func != log_to_syslog && conf->headers < LH_ALL) LogWarn(COMPONENT_CONFIG, "Headers setting for %s could drop some format fields!", conf->facility_name); if (conf->max_level == NB_LOG_LEVEL) conf->max_level = NIV_FULL_DEBUG; fac_list = link_mem; glist_add_tail(fac_list, &conf->fac_list); return 0; } static void *log_conf_init(void *link_mem, void *self_struct) { struct logger_config *logger = self_struct; assert(link_mem != NULL || self_struct != NULL); if (link_mem == NULL) return self_struct; else if (self_struct == NULL) return link_mem; else { if (logger->comp_log_level) { (void)component_init(&logger->comp_log_level, logger->comp_log_level); logger->comp_log_level = NULL; } if (!glist_empty(&logger->facility_list)) { struct glist_head *glist, *glistn; glist_for_each_safe(glist, glistn, &logger->facility_list) { struct facility_config *conf; conf = glist_entry(glist, struct facility_config, fac_list); glist_del(&conf->fac_list); (void)facility_init(&logger->facility_list, conf); } } if (logger->logfields != NULL) { (void)format_init(&logger->logfields, logger->logfields); logger->logfields = NULL; } } return NULL; } static void apply_logger_config_levels(struct logger_config *logger) { enum log_components comp; bool has_levels = logger->comp_log_level != NULL; log_levels_t log_level_all = has_levels ? logger->comp_log_level[COMPONENT_ALL] : NB_LOG_LEVEL; /* Handle Default_Log_Level */ if (logger->default_log_level != default_log_level) { /* Default log level has changed */ LogChanges("Changing Default_Log_Level from %s to %s", ReturnLevelInt(default_log_level), ReturnLevelInt(logger->default_log_level)); default_log_level = logger->default_log_level; } for (comp = COMPONENT_LOG; comp < COMPONENT_COUNT; comp++) { log_levels_t level; if (log_level_all != NB_LOG_LEVEL) { /* COMPONENT { ALL } was set, so use that to override * all log levels. */ level = log_level_all; } else if (has_levels && logger->comp_log_level[comp] != NB_LOG_LEVEL) { /* Individual component level was set, use it */ level = logger->comp_log_level[comp]; } else if (default_log_level != NB_LOG_LEVEL) { /* No COMPONENT was set and Default_Log_Level is set use * it. */ level = default_log_level; } else { /* Nothing has been set, revert to original log level * from -N command line option or code default. */ level = original_log_level; } SetComponentLogLevel(comp, level); } } static int log_conf_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { struct logger_config *logger = self_struct; struct glist_head *glist, *glistn; int errcnt = 0; int rc; glist_for_each_safe(glist, glistn, &logger->facility_list) { struct facility_config *conf; bool facility_exists; conf = glist_entry(glist, struct facility_config, fac_list); glist_del(&conf->fac_list); if (errcnt) { LogEvent(COMPONENT_CONFIG, "Skipping facility (%s) due to errors", conf->facility_name); goto done; } rc = create_log_facility(conf->facility_name, conf->func, conf->max_level, conf->headers, conf->lf_private); if (rc != 0 && rc != -EEXIST) { LogCrit(COMPONENT_CONFIG, "Failed to create facility (%s), (%s)", conf->facility_name, strerror(-rc)); err_type->resource = true; errcnt++; goto done; } facility_exists = (rc == -EEXIST); if (facility_exists && conf->dest != NULL) { rc = set_log_destination(conf->facility_name, conf->dest); if (rc < 0) { LogCrit(COMPONENT_LOG, "Could not set destination for (%s) because (%s)", conf->facility_name, strerror(-rc)); err_type->resource = true; errcnt++; goto done; } } if (facility_exists && conf->max_level != NB_LOG_LEVEL) { rc = set_log_level(conf->facility_name, conf->max_level); if (rc < 0) { LogCrit(COMPONENT_LOG, "Could not set severity level for (%s) because (%s)", conf->facility_name, strerror(-rc)); err_type->resource = true; errcnt++; goto done; } } if (conf->state == FAC_ACTIVE) { rc = enable_log_facility(conf->facility_name); if (rc != 0) { LogCrit(COMPONENT_CONFIG, "Could not enable (%s) because (%s)", conf->facility_name, strerror(-rc)); err_type->resource = true; errcnt++; } } else if (conf->state == FAC_DEFAULT) { struct log_facility *old_def = default_facility; rc = set_default_log_facility(conf->facility_name); if (rc != 0) { LogCrit(COMPONENT_CONFIG, "Could not make (%s) the default because (%s)", conf->facility_name, strerror(-rc)); err_type->resource = true; errcnt++; } else if (old_def != default_facility) LogEvent( COMPONENT_CONFIG, "Switched default logger from %s to %s", old_def->lf_name, default_facility->lf_name); } if (errcnt > 0 && !facility_exists) { LogCrit(COMPONENT_CONFIG, "Releasing new logger (%s) because of errors", conf->facility_name); release_log_facility(conf->facility_name); } done: (void)facility_init(&logger->facility_list, conf); } if (errcnt == 0) { if (logger->logfields != NULL) { LogEvent(COMPONENT_CONFIG, "Changing definition of log fields"); if (logfields != &default_logfields) { if (logfields->user_date_fmt != NULL) gsh_free(logfields->user_date_fmt); if (logfields->user_time_fmt != NULL) gsh_free(logfields->user_time_fmt); gsh_free(logfields); } logfields = logger->logfields; /* rebuild const_log_str with new format params. */ set_const_log_str(); } /* Apply any changes to Default_Log_Level or COMPONENTS */ apply_logger_config_levels(logger); if (ntirpc_pp.debug_flags != logger->rpc_debug_flags) LogChanges( "Changing custom RPC_Debug_Flags from %" PRIx32 " to %" PRIx32, rpc_debug_flags, logger->rpc_debug_flags); disp_utc_timestamp = logger->disp_utc_timestamp; rpc_debug_flags = logger->rpc_debug_flags; SetNTIRPCLogLevel(component_log_level[COMPONENT_TIRPC]); } else { if (logger->logfields != NULL) { struct logfields *lf = logger->logfields; if (lf->user_date_fmt != NULL) gsh_free(lf->user_date_fmt); if (lf->user_time_fmt != NULL) gsh_free(lf->user_time_fmt); gsh_free(lf); } } if (logger->comp_log_level != NULL) gsh_free(logger->comp_log_level); logger->logfields = NULL; logger->comp_log_level = NULL; return errcnt; } static struct config_item logging_params[] = { CONF_ITEM_TOKEN("Default_Log_Level", NB_LOG_LEVEL, log_levels, logger_config, default_log_level), CONF_ITEM_UI32("RPC_Debug_Flags", 0, UINT32_MAX, TIRPC_DEBUG_FLAG_DEFAULT, logger_config, rpc_debug_flags), CONF_ITEM_BLOCK_MULT("Facility", facility_params, facility_init, facility_commit, logger_config, facility_list), CONF_ITEM_BLOCK("Format", format_options, format_init, format_commit, logger_config, logfields), CONF_ITEM_BLOCK("Components", component_levels, component_init, component_commit, logger_config, comp_log_level), CONF_ITEM_BOOL("Display_UTC_Timestamp", false, logger_config, disp_utc_timestamp), CONFIG_EOL }; struct config_block logging_param = { .dbus_interface_name = "org.ganesha.nfsd.config.log", .blk_desc.name = "LOG", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = log_conf_init, .blk_desc.u.blk.params = logging_params, .blk_desc.u.blk.commit = log_conf_commit }; /** * * @brief Process the config parse tree for the logging component. * * Switch from the default component levels only if we found one * @param in_config [IN] configuration file handle * * @return 0 if ok, -1 if failed, * */ int read_log_config(config_file_t in_config, struct config_error_type *err_type) { struct logger_config logger; memset(&logger, 0, sizeof(struct logger_config)); (void)load_config_from_parse(in_config, &logging_param, &logger, true, err_type); if (config_error_is_harmless(err_type)) return 0; else return -1; } /* read_log_config */ #ifdef USE_UNWIND void gsh_libunwind(void) { unw_cursor_t cursor; unw_context_t uc; unsigned int i = 0; char procname[256]; unw_word_t ip, sp, off; struct log_facility *facility; struct glist_head *glist; int fd = -1; char buffer[256]; int n; procname[sizeof(procname) - 1] = '\0'; if (unw_getcontext(&uc) != 0) goto libunwind_failed; if (unw_init_local(&cursor, &uc) != 0) goto libunwind_failed; /* Find an active log facility */ PTHREAD_RWLOCK_rdlock(&log_rwlock); glist_for_each(glist, &active_facility_list) { facility = glist_entry(glist, struct log_facility, lf_active); if (facility->lf_func == log_to_file) { fd = open((char *)facility->lf_private, O_WRONLY | O_APPEND | O_CREAT, log_mask); break; } } LogMajor(COMPONENT_INIT, "BACKTRACE:"); do { n = 0; ip = sp = 0; unw_get_reg(&cursor, UNW_REG_IP, &ip); unw_get_reg(&cursor, UNW_REG_SP, &sp); switch (unw_get_proc_name(&cursor, procname, sizeof(procname) - 1, &off)) { case 0: /* Name found. */ case -UNW_ENOMEM: /* Name truncated. */ if (fd != -1) { n = snprintf( buffer, sizeof(buffer), " #%u %s + %#llx [ip=%#llx] [sp=%#llx]\n", i, procname, (long long)off, (long long)ip, (long long)sp); if (n > 0) write(fd, buffer, n); } else { LogMajor( COMPONENT_INIT, " #%u %s + %#llx [ip=%#llx] [sp=%#llx]", i, procname, (long long)off, (long long)ip, (long long)sp); } break; default: /* case -UNW_ENOINFO: */ /* case -UNW_EUNSPEC: */ if (fd != -1) { n = snprintf(buffer, sizeof(buffer), " #%u %s [ip=%#llx] [sp=%#llx]\n", i, "", (long long)ip, (long long)sp); if (n > 0) write(fd, buffer, n); } else { LogMajor(COMPONENT_INIT, " #%u %s [ip=%#llx] [sp=%#llx]", i, "", (long long)ip, (long long)sp); } } ++i; } while (unw_step(&cursor) > 0); PTHREAD_RWLOCK_unlock(&log_rwlock); return; libunwind_failed: LogCrit(COMPONENT_INIT, "unable to produce a stack trace with libunwind"); } #endif void gsh_backtrace(void) { #define MAX_STACK_DEPTH 32 /* enough ? */ void *buffer[MAX_STACK_DEPTH]; struct log_facility *facility; struct glist_head *glist; int fd = -1; char **traces; int i, nlines; nlines = backtrace(buffer, MAX_STACK_DEPTH); /* Find an active log facility that is file based to * log the backtrace symbols. */ PTHREAD_RWLOCK_rdlock(&log_rwlock); glist_for_each(glist, &active_facility_list) { facility = glist_entry(glist, struct log_facility, lf_active); if (facility->lf_func == log_to_file) { fd = open((char *)facility->lf_private, O_WRONLY | O_APPEND | O_CREAT, log_mask); break; } } if (fd != -1) { LogMajor(COMPONENT_INIT, "stack backtrace follows:"); backtrace_symbols_fd(buffer, nlines, fd); close(fd); } else { /* No file based logging, hope malloc call inside * backtrace_symbols() doesn't hang! */ traces = backtrace_symbols(buffer, nlines); if (traces) { for (i = 0; i < nlines; i++) { LogMajor(COMPONENT_INIT, "%s", traces[i]); } free(traces); } } PTHREAD_RWLOCK_unlock(&log_rwlock); } bool _ratelimit(struct ratelimit_state *rs, int *missed) { bool ret; time_t now; /* If we fail to acquire the mutex, then we are already busy, * so don't log message (aka return false) */ if (pthread_mutex_trylock(&rs->mutex)) return false; now = time(NULL); if (now > rs->begin + rs->interval) { /* new interval */ *missed = rs->missed; rs->begin = now; rs->printed = 0; rs->missed = 0; } else { *missed = 0; } if (rs->burst > rs->printed) { rs->printed++; ret = true; } else { rs->missed++; ret = false; } (void)pthread_mutex_unlock(&rs->mutex); return ret; } nfs-ganesha-6.5/src/log/maketest.conf000066400000000000000000000022471473756622300176360ustar00rootroot00000000000000# # Fichier de configuration de test # # $Header: /cea/home/cvs/cvs/SHERPA/BaseCvs/GANESHA/src/Log/maketest.conf,v 1.4 2005/02/17 12:58:28 leibovic Exp $ # # $Log: maketest.conf,v $ # Revision 1.4 2005/02/17 12:58:28 leibovic # Added multhithread test. # # Revision 1.3 2004/11/18 10:36:20 deniel # Dependence corrected in the GNUmakefiles # # Revision 1.2 2004/10/22 09:24:30 deniel # No more dynamic libraries compiled # # Revision 1.1 2004/09/30 14:08:42 deniel # Ajout des log en anglais (a partir des logs aglae) # # # Test Test_liblog_Standard { Product = Static version of the log library Command = ./test_liblog_STD.sh Comment = Check for correctness of the log Failure BadStatus { STATUS != 0 } Success TestOk { STDOUT =~ /PASSED/ AND STATUS == 0 } } Test Test_liblog_Multithread { Product = Static version of the log library Command = ./test_liblog_MT.sh Comment = Check for correctness of the log Failure BadStatus { STATUS != 0 } Success TestOk { STATUS == 0 } } nfs-ganesha-6.5/src/log/test_display.c000066400000000000000000000104111473756622300200120ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2012 * Contributor: Frank Filz * * -------------------------- * * This program 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 3 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 * 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 * */ #include "display.h" void show_display_buffer(struct display_buffer *dspbuf, char *cmt) { printf("%s size=%z len=%z buffer=%s\n", cmt, dspbuf->b_size, strlen(dspbuf->b_start), dspbuf->b_start); } int main(int argc, char **argv) { char *opaque1 = "this-is-opaque"; char *opaque2 = "\3\4\012\65\0"; char *opaque3 = "\3\4\012\65\0\55\66"; char *opaque4 = "aaa\012\0"; char buffer[10]; struct display_buffer display = { sizeof(buffer), buffer, buffer }; char buffer2[200]; struct display_buffer display2 = { sizeof(buffer2), buffer2, buffer2 }; char buffer3[14]; struct display_buffer display3 = { sizeof(buffer3), buffer3, buffer3 }; (void)display_printf(&display, "%s", "foo"); (void)display_printf(&display, "%s", "foo"); show_display_buffer(&display, "first test (foo, foo)"); display_reset_buffer(&display); (void)display_printf(&display, "%s", "foo"); (void)display_printf(&display, "%s", "foo"); (void)display_printf(&display, "%s", "food"); (void)display_printf(&display, "%s", "foo"); show_display_buffer(&display, "second test (foo, foo, food, foo)"); display_reset_buffer(&display); (void)display_printf(&display, "%s", "foo"); (void)display_printf(&display, "%s", "foo"); (void)display_printf(&display, "%s", "foo"); show_display_buffer(&display, "third test (foo, foo, foo)"); display_reset_buffer(&display); display_reset_buffer(&display); (void)display_printf(&display, "%s", "foo"); (void)display_printf(&display, "%s", "foo"); (void)display_printf(&display, "%s", "foo"); (void)display_printf(&display, "%s", "f"); show_display_buffer(&display, "fourth test (foo, foo, foo, f)"); display_reset_buffer(&display); (void)display_printf(&display, "%d %d", 5, 50000000); (void)display_printf(&display2, "%d %d", 5, 50000000); show_display_buffer(&display, "fifth test (%d %d)"); show_display_buffer(&display2, "fifth test (%d %d)"); display_reset_buffer(&display); display_reset_buffer(&display2); (void)display_opaque_value(&display, opaque1, strlen(opaque1)); show_display_buffer(&display, "opaque1"); display_reset_buffer(&display); (void)display_opaque_value(&display, opaque2, strlen(opaque2)); show_display_buffer(&display, "opaque2"); display_reset_buffer(&display); (void)display_opaque_value(&display, opaque3, strlen(opaque3) + 3); show_display_buffer(&display, "opaque3"); display_reset_buffer(&display2); (void)display_opaque_value(&display2, opaque1, strlen(opaque1)); show_display_buffer(&display2, "opaque1"); display_reset_buffer(&display2); (void)display_opaque_value(&display2, opaque2, strlen(opaque2)); show_display_buffer(&display2, "opaque2"); display_reset_buffer(&display2); (void)display_opaque_value(&display2, opaque3, strlen(opaque3) + 3); show_display_buffer(&display2, "opaque3"); display_reset_buffer(&display2); (void)display_opaque_value(&display2, opaque4, strlen(opaque4)); show_display_buffer(&display2, "opaque4"); display_reset_buffer(&display2); display_reset_buffer(&display3); (void)display_opaque_value(&display3, opaque1, strlen(opaque1)); show_display_buffer(&display3, "opaque1"); display_reset_buffer(&display3); (void)display_opaque_value(&display3, opaque2, strlen(opaque2)); show_display_buffer(&display3, "opaque2"); display_reset_buffer(&display3); (void)display_opaque_value(&display3, opaque3, strlen(opaque3) + 3); show_display_buffer(&display3, "opaque3"); display_reset_buffer(&display3); return 0; } nfs-ganesha-6.5/src/monitoring/000077500000000000000000000000001473756622300165515ustar00rootroot00000000000000nfs-ganesha-6.5/src/monitoring/CMakeLists.txt000066400000000000000000000010701473756622300213070ustar00rootroot00000000000000########### next target ############### set(CMAKE_CXX_STANDARD 17) SET(gmonitoring_SRCS exposer.cc monitoring.cc ) add_library(gmonitoring SHARED ${gmonitoring_SRCS}) add_sanitizers(gmonitoring) set_target_properties(gmonitoring PROPERTIES COMPILE_FLAGS "-fPIC") target_include_directories(gmonitoring PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/prometheus-cpp-lite/core/include) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pedantic-errors -Werror -Wall") install(TARGETS gmonitoring LIBRARY DESTINATION ${LIB_INSTALL_DIR}) ########### install files ############### nfs-ganesha-6.5/src/monitoring/exposer.cc000066400000000000000000000170351473756622300205530ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2024 Google LLC * Contributor : Yoni Couriel yonic@google.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file exposer.cc * @author Yoni Couriel * @brief Prometheus client that exposes HTTP interface for metrics scraping. */ #include #include #include #include #include #include "prometheus/histogram.h" #include "prometheus/text_serializer.h" #include "exposer.h" #define PERROR(MESSAGE) \ fprintf(stderr, "[%s:%d] %s: %s\n", __FILE__, __LINE__, (MESSAGE), \ strerror(errno)) #define PFATAL(MESSAGE) (PERROR(MESSAGE), abort()) static const char kStatus[] = "status"; static const char kSuccess[] = "success"; static const char kFailure[] = "failure"; namespace ganesha_monitoring { /* streambuf wrapper for sending into a socket */ template class SocketStreambuf : public std::streambuf { public: explicit SocketStreambuf(int socket_fd) : socket_fd_(socket_fd) { setp(buffer_.data(), buffer_.data() + buffer_.size()); } bool was_aborted() { return aborted_; } protected: /* Flushes buffer to socket */ int overflow(int ch) override { if (pptr() == epptr()) { /* Buffer is full, flush it */ if (sync()) return traits_type::eof(); } if (ch != traits_type::eof()) { /* Store incoming character */ *pptr() = static_cast(ch); pbump(1); } return ch; } /* Sends buffer to socket (blocking) and clears it */ int sync() override { if (aborted_) return -1; const std::size_t bytes_count = pptr() - pbase(); if (bytes_count > 0) { /* Try to send buffer */ std::size_t bytes_sent = 0; while (bytes_sent < bytes_count) { const ssize_t result = TEMP_FAILURE_RETRY( send(socket_fd_, pbase() + bytes_sent, bytes_count - bytes_sent, 0)); if (result < 0) { PERROR("Could not send metrics, aborting"); aborted_ = true; return -1; } bytes_sent += result; } } /* Clear buffer */ pbump(-bytes_count); return 0; } private: const int socket_fd_; bool aborted_ = false; std::array buffer_{}; // Delete copy/move constructor/assignment SocketStreambuf(const SocketStreambuf &) = delete; SocketStreambuf &operator=(const SocketStreambuf &) = delete; SocketStreambuf(SocketStreambuf &&) = delete; SocketStreambuf &operator=(SocketStreambuf &&) = delete; }; static bool is_metric_empty(prometheus::Metric::Type type, prometheus::ClientMetric &metric) { switch (type) { case prometheus::Metric::Type::Counter: return metric.counter.value == 0.0; case prometheus::Metric::Type::Summary: return metric.summary.sample_count == 0; case prometheus::Metric::Type::Histogram: return metric.histogram.sample_count == 0; default: return false; } } // Removes empty metrics from family // Most metrics are empty or rarly used (for example consider // nfsv4__op_latency_bucket{op="REMOVEXATTR",status="NFS4ERR_REPLAY"}) // Significantly reduces the amount of data transferred to the Prometheus // server from MBs to KBs static void compact_family(prometheus::MetricFamily &family) { auto first_element_to_remove = std::remove_if( family.metric.begin(), family.metric.end(), [&family](auto metric) { return is_metric_empty(family.type, metric); }); // Keep at least one metric even if it's empty so it's easier to query if (first_element_to_remove == family.metric.begin()) first_element_to_remove++; family.metric.erase(first_element_to_remove, family.metric.end()); } static inline HistogramInt::BucketBoundaries getBoundries() { return { 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216 }; } Exposer::Exposer(prometheus::Registry ®istry) : registry_(registry) , scrapingLatencies_( prometheus::Builder() .Name("monitoring__scraping_latencies") .Help("Time duration of entire registry scraping [ms].") .Register(registry)) , successLatencies_(scrapingLatencies_.Add({ { kStatus, kSuccess } }, getBoundries())) , failureLatencies_(scrapingLatencies_.Add({ { kStatus, kFailure } }, getBoundries())) { } Exposer::~Exposer() { stop(); } void Exposer::start(uint16_t port) { const std::lock_guard lock(mutex_); if (running_) PFATAL("Already running"); server_fd_ = socket(AF_INET, SOCK_STREAM, 0); if (server_fd_ == -1) PFATAL("Failed to create socket"); const int opt = 1; if (setsockopt(server_fd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) PFATAL("Failed to set socket options"); struct sockaddr_in address; address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(port); if (bind(server_fd_, (struct sockaddr *)&address, sizeof(address))) PFATAL("Failed to bind socket"); if (listen(server_fd_, 3)) PFATAL("Failed to listen on socket"); running_ = true; thread_id_ = std::thread{ server_thread, this }; } void Exposer::stop() { const std::lock_guard lock(mutex_); if (running_) { running_ = false; shutdown(server_fd_, SHUT_RDWR); // Wakes up the thread thread_id_.join(); close(server_fd_); server_fd_ = INVALID_FD; } } static inline uint64_t now_mono_ns(void) { struct timespec ts; const int rc = clock_gettime(CLOCK_MONOTONIC, &ts); if (rc == 0) { return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec; } else { PERROR("Could not get the time"); return 0; } } static inline int64_t get_elapsed_ms(uint64_t start_time_ns) { return (now_mono_ns() - start_time_ns) / 1000000LL; } void *Exposer::server_thread(void *arg) { Exposer *const exposer = (Exposer *)arg; char buffer[1024]; while (exposer->running_) { const int client_fd = TEMP_FAILURE_RETRY( accept4(exposer->server_fd_, NULL, NULL, SOCK_CLOEXEC)); if (client_fd < 0) { if (exposer->running_) PERROR("Failed to accept connection"); continue; } const uint64_t start_time = now_mono_ns(); recv(client_fd, buffer, sizeof(buffer), 0); auto families = exposer->registry_.Collect(); for (auto &family : families) { compact_family(family); } SocketStreambuf socket_streambuf(client_fd); std::ostream socket_ostream(&socket_streambuf); socket_ostream << "HTTP/1.1 200 OK\r\n\r\n"; prometheus::TextSerializer::Serialize(socket_ostream, families); socket_ostream.flush(); close(client_fd); const int64_t elapsed_ms = get_elapsed_ms(start_time); if (socket_streambuf.was_aborted()) exposer->failureLatencies_.Observe(elapsed_ms); else exposer->successLatencies_.Observe(elapsed_ms); } return NULL; } } // namespace ganesha_monitoring nfs-ganesha-6.5/src/monitoring/include/000077500000000000000000000000001473756622300201745ustar00rootroot00000000000000nfs-ganesha-6.5/src/monitoring/include/exposer.h000066400000000000000000000037301473756622300220350ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2024 Google LLC * Contributor : Yoni Couriel yonic@google.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file exposer.h * @author Yoni Couriel * @brief Prometheus client that exposes HTTP interface for metrics scraping. */ #include #include "prometheus/registry.h" namespace ganesha_monitoring { using HistogramInt = prometheus::Histogram; class Exposer { public: explicit Exposer(prometheus::Registry ®istry); ~Exposer(); void start(uint16_t port); void stop(void); private: prometheus::Registry ®istry_; HistogramInt::Family &scrapingLatencies_; prometheus::Histogram &successLatencies_; prometheus::Histogram &failureLatencies_; static constexpr int INVALID_FD = -1; int server_fd_ = INVALID_FD; bool running_ = false; std::thread thread_id_; std::mutex mutex_; // Delete copy/move constructor/assignment Exposer(const Exposer &) = delete; Exposer &operator=(const Exposer &) = delete; Exposer(Exposer &&) = delete; Exposer &operator=(Exposer &&) = delete; static void *server_thread(void *arg); }; } // namespace ganesha_monitoring nfs-ganesha-6.5/src/monitoring/include/monitoring.h000066400000000000000000000263241473756622300225410ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Google Inc., 2021 * Author: Bjorn Leffler leffler@google.com * * 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 3 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * @brief Monitoring library for NFS Ganesha. * * Monitoring must fail gracefully. * Monitoring problems should not affect serving. * * This file contains two types of metrics: * 1. Static metrics - metric definition is known at init time * 2. Dynamic metrics - metrics that create new labels during running time, for * example, metrics that have a Client IP Address label. * * Static metrics (1) are preferable, since the Dynamic metrics (2) affect * performance. * The Dynamic metrics can be disabled by unsetting Enable_Dynamic_Metrics. * * We avoid using float/double values since updating them *atomically* also * affects performance. * * Usage: * - Create a static metric during init time: * my_handle = monitoring__register_counter(...); * - Update the metric during running time: * monitoring__counter_inc(my_handle, 1); * * Naming convention: * For new metrics, please use "__", for example: * "clients__lease_expire_count" * * See more: * - https://prometheus.io/docs/concepts/data_model/ * - https://prometheus.io/docs/concepts/metric_types/ */ #ifndef GANESHA_MONITORING_H #define GANESHA_MONITORING_H #include #include #include #include #include "config.h" #include "gsh_types.h" #ifdef __cplusplus extern "C" { #endif typedef uint16_t export_id_t; /* Metric value units. */ #define METRIC_UNIT_NONE (NULL) #define METRIC_UNIT_MINUTE ("minute") #define METRIC_UNIT_SECOND ("sec") #define METRIC_UNIT_MILLISECOND ("ms") #define METRIC_UNIT_MICROSECOND ("us") #define METRIC_UNIT_NANOSECOND ("ns") #define METRIC_METADATA(DESCRIPTION, UNIT) \ ((metric_metadata_t){ .description = (DESCRIPTION), .unit = (UNIT) }) /* Metric help description */ typedef struct metric_metadata { const char *description; /* Helper message */ const char *unit; /* Units like: second, byte */ } metric_metadata_t; /* Label is a dimension in the metric family, for example "operation=GETATTR" */ typedef struct metric_label { const char *key; const char *value; } metric_label_t; #define METRIC_LABEL(KEY, VALUE) \ ((metric_label_t){ .key = (KEY), .value = (VALUE) }) /* Buckets of (a,b,c) mean boundaries of: (-INF,a) [a,b) [b,c) [c, INF) */ typedef struct histogram_buckets { const int64_t *buckets; uint16_t count; } histogram_buckets_t; /* C wrapper for prometheus::Counter pointer */ typedef struct counter_metric_handle { void *metric; } counter_metric_handle_t; /* C wrapper for prometheus::Gauge pointer */ typedef struct gauge_metric_handle { void *metric; } gauge_metric_handle_t; /* C wrapper for prometheus::Histogram pointer */ typedef struct histogram_metric_handle { void *metric; } histogram_metric_handle_t; #ifdef USE_MONITORING /* Registers and initializes a new static counter metric. */ counter_metric_handle_t monitoring__register_counter(const char *name, metric_metadata_t metadata, const metric_label_t *labels, uint16_t num_labels); /* Registers and initializes a new static gauge metric. */ gauge_metric_handle_t monitoring__register_gauge(const char *name, metric_metadata_t metadata, const metric_label_t *labels, uint16_t num_labels); /* Registers and initializes a new static histogram metric. */ histogram_metric_handle_t monitoring__register_histogram(const char *name, metric_metadata_t metadata, const metric_label_t *labels, uint16_t num_labels, histogram_buckets_t buckets); /* Increments counter metric by value. */ void monitoring__counter_inc(counter_metric_handle_t, int64_t val); /* Increments gauge metric by value. */ void monitoring__gauge_inc(gauge_metric_handle_t, int64_t val); /* Decrements gauge metric by value. */ void monitoring__gauge_dec(gauge_metric_handle_t, int64_t val); /* Sets gauge metric value. */ void monitoring__gauge_set(gauge_metric_handle_t, int64_t val); /* Observes a histogram metric value */ void monitoring__histogram_observe(histogram_metric_handle_t, int64_t val); /* Returns default exp2 histogram buckets. */ histogram_buckets_t monitoring__buckets_exp2(void); /* Returns compact exp2 histogram buckets (fewer compared to default). */ histogram_buckets_t monitoring__buckets_exp2_compact(void); /* Allow FSALs to register a human readable label used for per-export metrics. * The default label (if the FSAL doesn't set one) is "exportid=". */ void monitoring_register_export_label(export_id_t export_id, const char *label); /* Inits monitoring module and exposes a Prometheus-format HTTP endpoint. */ void monitoring__init(uint16_t port, bool enable_dynamic_metrics); /* * The following two functions generate the following metrics, * exported both as total and per export. * * - Total request count. * - Total request count by success / failure status. * - Total bytes sent. * - Total bytes received. * - Request size in bytes as histogram. * - Response size in bytes as histogram. * - Latency in ms as histogram. */ void monitoring__dynamic_observe_nfs_request(const char *operation, nsecs_elapsed_t request_time, const char *version, const char *status_label, export_id_t export_id, const char *client_ip); void monitoring__dynamic_observe_nfs_io(size_t bytes_requested, size_t bytes_transferred, bool success, bool is_write, export_id_t export_id, const char *client_ip); /* MDCache hit rates. */ void monitoring__dynamic_mdcache_cache_hit(const char *operation, export_id_t export_id); void monitoring__dynamic_mdcache_cache_miss(const char *operation, export_id_t export_id); #else /* USE_MONITORING */ /** The empty implementations below enable using monitoring functions * conveniently without wrapping with USE_MONITORING condition each time. */ /* Wraps an expression to avoid *-unused warnings */ #define UNUSED_EXPR(x) ((void)(x)) #define monitoring__register_counter(name, metadata, labels, num_labels) \ ({ \ UNUSED_EXPR(name); \ UNUSED_EXPR(metadata); \ UNUSED_EXPR(labels); \ UNUSED_EXPR(num_labels); \ (counter_metric_handle_t){ 0 }; \ }) #define monitoring__register_gauge(name, metadata, labels, num_labels) \ ({ \ UNUSED_EXPR(name); \ UNUSED_EXPR(metadata); \ UNUSED_EXPR(labels); \ UNUSED_EXPR(num_labels); \ (gauge_metric_handle_t){ 0 }; \ }) #define monitoring__register_histogram(name, metadata, labels, num_labels, \ buckets) \ ({ \ UNUSED_EXPR(name); \ UNUSED_EXPR(metadata); \ UNUSED_EXPR(labels); \ UNUSED_EXPR(num_labels); \ UNUSED_EXPR(buckets); \ (histogram_metric_handle_t){ 0 }; \ }) #define monitoring__counter_inc(metric, value) \ ({ \ UNUSED_EXPR(metric); \ UNUSED_EXPR(value); \ }) #define monitoring__gauge_inc(metric, value) \ ({ \ UNUSED_EXPR(metric); \ UNUSED_EXPR(value); \ }) #define monitoring__gauge_dec(metric, value) \ ({ \ UNUSED_EXPR(metric); \ UNUSED_EXPR(value); \ }) #define monitoring__gauge_set(metric, value) \ ({ \ UNUSED_EXPR(metric); \ UNUSED_EXPR(value); \ }) #define monitoring__histogram_observe(metric, value) \ ({ \ UNUSED_EXPR(metric); \ UNUSED_EXPR(value); \ }) #define monitoring__buckets_exp2() ((histogram_buckets_t){ 0 }) #define monitoring__buckets_exp2_compact() ((histogram_buckets_t){ 0 }) #define monitoring__init(port) ({ UNUSED_EXPR(port); }) #define monitoring_register_export_label(export_id, label) \ ({ \ UNUSED_EXPR(export_id); \ UNUSED_EXPR(label); \ }) #define monitoring__dynamic_observe_nfs_request( \ operation, request_time, version, status_label, export_id, client_ip) \ ({ \ UNUSED_EXPR(operation); \ UNUSED_EXPR(request_time); \ UNUSED_EXPR(version); \ UNUSED_EXPR(status_label); \ UNUSED_EXPR(export_id); \ UNUSED_EXPR(client_ip); \ }) #define monitoring__dynamic_observe_nfs_io(bytes_requested, bytes_transferred, \ success, is_write, export_id, \ client_ip) \ ({ \ UNUSED_EXPR(bytes_requested); \ UNUSED_EXPR(bytes_transferred); \ UNUSED_EXPR(success); \ UNUSED_EXPR(is_write); \ UNUSED_EXPR(export_id); \ UNUSED_EXPR(client_ip); \ }) #define monitoring__dynamic_mdcache_cache_hit(operation, export_id) \ ({ \ UNUSED_EXPR(operation); \ UNUSED_EXPR(export_id); \ }) #define monitoring__dynamic_mdcache_cache_miss(operation, export_id) \ ({ \ UNUSED_EXPR(operation); \ UNUSED_EXPR(export_id); \ }) #endif /* USE_MONITORING */ #ifdef __cplusplus } #endif #endif /* GANESHA_MONITORING_H */ nfs-ganesha-6.5/src/monitoring/monitoring.cc000066400000000000000000000454421473756622300212560ustar00rootroot00000000000000/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Google Inc., 2022 * Author: Bjorn Leffler leffler@google.com * * 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 3 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include "prometheus/counter.h" #include "prometheus/gauge.h" #include "prometheus/histogram.h" #include "monitoring.h" #include "exposer.h" /* * This file contains the C++ monitoring implementation for Ganesha. */ static const char kClient[] = "client"; static const char kExport[] = "export"; static const char kOperation[] = "operation"; static const char kStatus[] = "status"; static const char kVersion[] = "version"; namespace ganesha_monitoring { using CounterInt = prometheus::Counter; using GaugeInt = prometheus::Gauge; using HistogramInt = prometheus::Histogram; using HistogramDouble = prometheus::Histogram; using LabelsMap = std::map; static prometheus::Registry registry; static Exposer exposer(registry); // 24 size buckets: 2 bytes to 16 MB as powers of 2. static const HistogramInt::BucketBoundaries requestSizeBuckets = { 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216 }; // 30 time buckets: 0.1 ms to 12 seconds. Generated with 50% increases. static const HistogramDouble::BucketBoundaries latencyBuckets = { 0.1, 0.15, 0.225, 0.337, 0.506, 0.759, 1.13, 1.70, 2.56, 3.84, 5.76, 8.64, 12.9, 19.4, 29.1, 43.7, 65.6, 98.5, 147, 221, 332, 498, 748, 1122, 1683, 2525, 3787, 5681, 8522, 12783 }; class DynamicMetrics { public: DynamicMetrics(prometheus::Registry ®istry); // Counters CounterInt::Family &mdcacheCacheHitsTotal; CounterInt::Family &mdcacheCacheMissesTotal; CounterInt::Family &mdcacheCacheHitsByExportTotal; CounterInt::Family &mdcacheCacheMissesByExportTotal; CounterInt::Family &rpcsReceivedTotal; CounterInt::Family &rpcsCompletedTotal; CounterInt::Family &errorsByVersionOperationStatus; // Per client metrics. // Only track request and throughput rates to reduce memory overhead. // NFS request metrics below also generate latency percentiles, etc. CounterInt::Family &clientRequestsTotal; CounterInt::Family &clientBytesReceivedTotal; CounterInt::Family &clientBytesSentTotal; // Gauges GaugeInt::Family &rpcsInFlight; GaugeInt::Family &lastClientUpdate; // Per {operation} NFS request metrics. CounterInt::Family &requestsTotalByOperation; CounterInt::Family &bytesReceivedTotalByOperation; CounterInt::Family &bytesSentTotalByOperation; HistogramInt::Family &requestSizeByOperation; HistogramInt::Family &responseSizeByOperation; HistogramDouble::Family &latencyByOperation; // Per {operation, export_id} NFS request metrics. CounterInt::Family &requestsTotalByOperationExport; CounterInt::Family &bytesReceivedTotalByOperationExport; CounterInt::Family &bytesSentTotalByOperationExport; HistogramInt::Family &requestSizeByOperationExport; HistogramInt::Family &responseSizeByOperationExport; HistogramDouble::Family &latencyByOperationExport; }; DynamicMetrics::DynamicMetrics(prometheus::Registry ®istry) : // Counters mdcacheCacheHitsTotal( prometheus::Builder() .Name("mdcache_cache_hits_total") .Help("Counter for total cache hits in mdcache.") .Register(registry)) , mdcacheCacheMissesTotal( prometheus::Builder() .Name("mdcache_cache_misses_total") .Help("Counter for total cache misses in mdcache.") .Register(registry)) , mdcacheCacheHitsByExportTotal( prometheus::Builder() .Name("mdcache_cache_hits_by_export_total") .Help("Counter for total cache hits in mdcache, by export.") .Register(registry)) , mdcacheCacheMissesByExportTotal( prometheus::Builder() .Name("mdcache_cache_misses_by_export_total") .Help("Counter for total cache misses in mdcache, by export.") .Register(registry)) , rpcsReceivedTotal(prometheus::Builder() .Name("rpcs_received_total") .Help("Counter for total RPCs received.") .Register(registry)) , rpcsCompletedTotal(prometheus::Builder() .Name("rpcs_completed_total") .Help("Counter for total RPCs completed.") .Register(registry)) , errorsByVersionOperationStatus( prometheus::Builder() .Name("nfs_errors_total") .Help("Error count by version, operation and status.") .Register(registry)) , // Per client metrics. clientRequestsTotal(prometheus::Builder() .Name("client_requests_total") .Help("Total requests by client.") .Register(registry)) , clientBytesReceivedTotal( prometheus::Builder() .Name("client_bytes_received_total") .Help("Total request bytes by client.") .Register(registry)) , clientBytesSentTotal( prometheus::Builder() .Name("client_bytes_sent_total") .Help("Total response bytes sent by client.") .Register(registry)) , // Gauges rpcsInFlight( prometheus::Builder() .Name("rpcs_in_flight") .Help("Number of NFS requests received or in flight.") .Register(registry)) , lastClientUpdate(prometheus::Builder() .Name("last_client_update") .Help("Last update timestamp, per client.") .Register(registry)) , // Per {operation} NFS request metrics. requestsTotalByOperation(prometheus::Builder() .Name("nfs_requests_total") .Help("Total requests.") .Register(registry)) , bytesReceivedTotalByOperation( prometheus::Builder() .Name("nfs_bytes_received_total") .Help("Total request bytes.") .Register(registry)) , bytesSentTotalByOperation(prometheus::Builder() .Name("nfs_bytes_sent_total") .Help("Total response bytes.") .Register(registry)) , requestSizeByOperation(prometheus::Builder() .Name("nfs_request_size_bytes") .Help("Request size in bytes.") .Register(registry)) , responseSizeByOperation(prometheus::Builder() .Name("nfs_response_size_bytes") .Help("Response size in bytes.") .Register(registry)) , latencyByOperation(prometheus::Builder() .Name("nfs_latency_ms") .Help("Request latency in ms.") .Register(registry)) , // Per {operation, export_id} NFS request metrics. requestsTotalByOperationExport( prometheus::Builder() .Name("nfs_requests_by_export_total") .Help("Total requests by export.") .Register(registry)) , bytesReceivedTotalByOperationExport( prometheus::Builder() .Name("nfs_bytes_received_by_export_total") .Help("Total request bytes by export.") .Register(registry)) , bytesSentTotalByOperationExport( prometheus::Builder() .Name("nfs_bytes_sent_by_export_total") .Help("Total response bytes by export.") .Register(registry)) , requestSizeByOperationExport( prometheus::Builder() .Name("nfs_request_size_by_export_bytes") .Help("Request size by export in bytes.") .Register(registry)) , responseSizeByOperationExport( prometheus::Builder() .Name("nfs_response_size_by_export_bytes") .Help("Response size by export in bytes.") .Register(registry)) , latencyByOperationExport( prometheus::Builder() .Name("nfs_latency_ms_by_export") .Help("Request latency by export in ms.") .Register(registry)) { } static std::unique_ptr dynamic_metrics; static std::string trimIPv6Prefix(const std::string input) { const std::string prefix("::ffff:"); if (input.find(prefix) == 0) { return input.substr(prefix.size()); } return input; } // SimpleMap is a simple thread-safe wrapper of std::map. template class SimpleMap { public: SimpleMap(std::function get_value) : get_value_(get_value) { } T GetOrInsert(const K &k) { std::shared_lock rlock(mutex_); auto iter = map_.find(k); if (iter != map_.end()) { return iter->second; } rlock.unlock(); std::unique_lock wlock(mutex_); iter = map_.find(k); if (iter != map_.end()) { return iter->second; } auto v = get_value_(k); map_.emplace(k, v); return v; } // @ret whether the key already exists bool InsertOrUpdate(const K &k, const T &v) { std::unique_lock wlock(mutex_); bool exist = map_.find(k) != map_.end(); map_[k] = v; return exist; } private: std::function get_value_; std::shared_mutex mutex_; std::map map_; }; static SimpleMap exportLabels([](const export_id_t &export_id) { std::ostringstream ss; ss << "export_id=" << export_id; return ss.str(); }); static std::string GetExportLabel(export_id_t export_id) { return exportLabels.GetOrInsert(export_id); } static void toLowerCase(std::string &s) { std::transform(s.begin(), s.end(), s.begin(), ::tolower); } /** * @brief Formats full description from metadata into output buffer. * * @note description character array needs to be provided by the caller, * the function populates a string into the character array. */ static const std::string get_description(const metric_metadata_t &metadata) { std::ostringstream description; description << metadata.description; if (metadata.unit != METRIC_UNIT_NONE) { description << " [" << metadata.unit << "]"; } return description.str(); } static const LabelsMap get_labels(const metric_label_t *labels, uint16_t num_labels) { LabelsMap labels_map; for (uint16_t i = 0; i < num_labels; i++) { labels_map.emplace(labels[i].key, labels[i].value); } return labels_map; } template static X convert_to_handle(Y *metric) { void *const ptr = static_cast(metric); return { ptr }; } template static X *convert_from_handle(Y handle) { void *const ptr = handle.metric; return static_cast(ptr); } /* * Functions used from NFS Ganesha below. */ extern "C" { histogram_buckets_t monitoring__buckets_exp2(void) { static const int64_t buckets[] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824 }; return { buckets, sizeof(buckets) / sizeof(*buckets) }; } histogram_buckets_t monitoring__buckets_exp2_compact(void) { static const int64_t buckets[] = { 10, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120, 10240, 20480, 40960, 81920, 163840, 327680 }; return { buckets, sizeof(buckets) / sizeof(*buckets) }; } counter_metric_handle_t monitoring__register_counter(const char *name, metric_metadata_t metadata, const metric_label_t *labels, uint16_t num_labels) { auto &counter = prometheus::Builder() .Name(name) .Help(get_description(metadata)) .Register(registry) .Add(get_labels(labels, num_labels)); return convert_to_handle(&counter); } gauge_metric_handle_t monitoring__register_gauge(const char *name, metric_metadata_t metadata, const metric_label_t *labels, uint16_t num_labels) { auto &gauge = prometheus::Builder() .Name(name) .Help(get_description(metadata)) .Register(registry) .Add(get_labels(labels, num_labels)); return convert_to_handle(&gauge); } histogram_metric_handle_t monitoring__register_histogram(const char *name, metric_metadata_t metadata, const metric_label_t *labels, uint16_t num_labels, histogram_buckets_t buckets) { const auto &buckets_vector = HistogramInt::BucketBoundaries( buckets.buckets, buckets.buckets + buckets.count); auto &histogram = prometheus::Builder() .Name(name) .Help(get_description(metadata)) .Register(registry) .Add(get_labels(labels, num_labels), buckets_vector); return convert_to_handle(&histogram); } void monitoring__counter_inc(counter_metric_handle_t handle, int64_t value) { convert_from_handle(handle)->Increment(value); } void monitoring__gauge_inc(gauge_metric_handle_t handle, int64_t value) { convert_from_handle(handle)->Increment(value); } void monitoring__gauge_dec(gauge_metric_handle_t handle, int64_t value) { convert_from_handle(handle)->Decrement(value); } void monitoring__gauge_set(gauge_metric_handle_t handle, int64_t value) { convert_from_handle(handle)->Set(value); } void monitoring__histogram_observe(histogram_metric_handle_t handle, int64_t value) { convert_from_handle(handle)->Observe(value); } void monitoring_register_export_label(const export_id_t export_id, const char *label) { exportLabels.InsertOrUpdate(export_id, std::string(label)); } void monitoring__init(uint16_t port, bool enable_dynamic_metrics) { static bool initialized = false; if (initialized) return; if (enable_dynamic_metrics) dynamic_metrics = std::make_unique(registry); exposer.start(port); initialized = true; } void monitoring__dynamic_observe_nfs_request(const char *operation, nsecs_elapsed_t request_time, const char *version, const char *status_label, export_id_t export_id, const char *client_ip) { if (!dynamic_metrics) return; const int64_t latency_ms = request_time / NS_PER_MSEC; std::string operationLowerCase = std::string(operation); toLowerCase(operationLowerCase); if (client_ip != NULL) { std::string client(client_ip); int64_t epoch = std::chrono::duration_cast( std::chrono::system_clock::now() .time_since_epoch()) .count(); client = trimIPv6Prefix(client); dynamic_metrics->clientRequestsTotal .Add({ { kClient, client }, { kOperation, operationLowerCase } }) .Increment(); dynamic_metrics->lastClientUpdate.Add({ { kClient, client } }) .Set(epoch); } dynamic_metrics->errorsByVersionOperationStatus .Add({ { kVersion, version }, { kOperation, operationLowerCase }, { kStatus, status_label } }) .Increment(); // Observe metrics. dynamic_metrics->requestsTotalByOperation .Add({ { kOperation, operationLowerCase } }) .Increment(); dynamic_metrics->latencyByOperation .Add({ { kOperation, operationLowerCase } }, latencyBuckets) .Observe(latency_ms); if (export_id == 0) { return; } // Observe metrics, by export. const std::string exportLabel = GetExportLabel(export_id); dynamic_metrics->requestsTotalByOperationExport .Add({ { kOperation, operationLowerCase }, { kExport, exportLabel } }) .Increment(); dynamic_metrics->latencyByOperationExport .Add({ { kOperation, operationLowerCase }, { kExport, exportLabel } }, latencyBuckets) .Observe(latency_ms); } void monitoring__dynamic_observe_nfs_io(size_t bytes_requested, size_t bytes_transferred, bool success, bool is_write, export_id_t export_id, const char *client_ip) { if (!dynamic_metrics) return; const std::string operation(is_write ? "write" : "read"); const size_t bytes_received = (is_write ? 0 : bytes_transferred); const size_t bytes_sent = (is_write ? bytes_transferred : 0); if (client_ip != NULL) { std::string client(client_ip); client = trimIPv6Prefix(client); dynamic_metrics->clientBytesReceivedTotal .Add({ { kClient, client }, { kOperation, operation } }) .Increment(bytes_received); dynamic_metrics->clientBytesSentTotal .Add({ { kClient, client }, { kOperation, operation } }) .Increment(bytes_sent); } // Observe metrics. dynamic_metrics->bytesReceivedTotalByOperation .Add({ { kOperation, operation } }) .Increment(bytes_received); dynamic_metrics->bytesSentTotalByOperation .Add({ { kOperation, operation } }) .Increment(bytes_sent); dynamic_metrics->requestSizeByOperation .Add({ { kOperation, operation } }, requestSizeBuckets) .Observe(bytes_requested); dynamic_metrics->responseSizeByOperation .Add({ { kOperation, operation } }, requestSizeBuckets) .Observe(bytes_sent); // Ignore export id 0. It's never used for actual exports, but can happen // during the setup phase, or when the export id is unknown. if (export_id == 0) return; // Observe by export metrics. const std::string exportLabel = GetExportLabel(export_id); dynamic_metrics->bytesReceivedTotalByOperationExport .Add({ { kOperation, operation }, { kExport, exportLabel } }) .Increment(bytes_received); dynamic_metrics->bytesSentTotalByOperationExport .Add({ { kOperation, operation }, { kExport, exportLabel } }) .Increment(bytes_sent); dynamic_metrics->requestSizeByOperationExport .Add({ { kOperation, operation }, { kExport, exportLabel } }, requestSizeBuckets) .Observe(bytes_requested); dynamic_metrics->responseSizeByOperationExport .Add({ { kOperation, operation }, { kExport, exportLabel } }, requestSizeBuckets) .Observe(bytes_sent); } void monitoring__dynamic_mdcache_cache_hit(const char *operation, export_id_t export_id) { if (!dynamic_metrics) return; dynamic_metrics->mdcacheCacheHitsTotal .Add({ { kOperation, operation } }) .Increment(); if (export_id != 0) { const std::string exportLabel = GetExportLabel(export_id); dynamic_metrics->mdcacheCacheHitsByExportTotal .Add({ { kExport, exportLabel }, { kOperation, operation } }) .Increment(); } } void monitoring__dynamic_mdcache_cache_miss(const char *operation, export_id_t export_id) { if (!dynamic_metrics) return; dynamic_metrics->mdcacheCacheMissesTotal .Add({ { kOperation, operation } }) .Increment(); if (export_id != 0) { dynamic_metrics->mdcacheCacheMissesByExportTotal .Add({ { kExport, GetExportLabel(export_id) }, { kOperation, operation } }) .Increment(); } } } // extern "C" } // namespace ganesha_monitoring nfs-ganesha-6.5/src/monitoring/prometheus-cpp-lite/000077500000000000000000000000001473756622300224575ustar00rootroot00000000000000nfs-ganesha-6.5/src/nfs-ganesha.spec-in.cmake000066400000000000000000000625061473756622300211260ustar00rootroot00000000000000%define __arch_install_post /usr/lib/rpm/check-rpaths /usr/lib/rpm/check-buildroot %define _unpackaged_files_terminate_build 0 %if ( 0%{?suse_version} ) BuildRequires: distribution-release %if ( ! 0%{?is_opensuse} ) BuildRequires: sles-release >= 12 Requires: sles-release >= 12 %else BuildRequires: openSUSE-release Requires: openSUSE-release %endif %endif # Conditionally enable some FSALs, disable others. # # 1. rpmbuild accepts these options (gpfs as example): # --with gpfs # --without gpfs %define on_off_switch() %%{?with_%1:ON}%%{!?with_%1:OFF} # A few explanation about % bcond_with and % bcond_without # /!\ be careful: this syntax can be quite messy # % bcond_with means you add a "--with" option, default = without this feature # % bcond_without adds a"--without" so the feature is enabled by default @BCOND_NULLFS@ nullfs %global use_fsal_null %{on_off_switch nullfs} @BCOND_MEM@ mem %global use_fsal_mem %{on_off_switch mem} @BCOND_GPFS@ gpfs %global use_fsal_gpfs %{on_off_switch gpfs} @BCOND_XFS@ xfs %global use_fsal_xfs %{on_off_switch xfs} @BCOND_LUSTRE@ lustre %global use_fsal_lustre %{on_off_switch lustre} @BCOND_CEPH@ ceph %global use_fsal_ceph %{on_off_switch ceph} @BCOND_RGW@ rgw %global use_fsal_rgw %{on_off_switch rgw} @BCOND_GLUSTER@ gluster %global use_fsal_gluster %{on_off_switch gluster} @BCOND_KVSFS@ kvsfs %global use_fsal_kvsfs %{on_off_switch kvsfs} @BCOND_RDMA@ rdma %global use_rdma %{on_off_switch rdma} @BCOND_9P@ 9P %global use_9P %{on_off_switch 9P} @BCOND_JEMALLOC@ jemalloc @BCOND_TCMALLOC@ tcmalloc @BCOND_LTTNG@ lttng %global use_lttng %{on_off_switch lttng} @BCOND_UTILS@ utils %global use_utils %{on_off_switch utils} @BCOND_GUI_UTILS@ gui_utils %global use_gui_utils %{on_off_switch gui_utils} @BCOND_NTIRPC@ system_ntirpc %global use_system_ntirpc %{on_off_switch system_ntirpc} @BCOND_MAN_PAGE@ man_page %global use_man_page %{on_off_switch man_page} @BCOND_RADOS_RECOV@ rados_recov %global use_rados_recov %{on_off_switch rados_recov} @BCOND_RADOS_URLS@ rados_urls %global use_rados_urls %{on_off_switch rados_urls} @BCOND_RPCBIND@ rpcbind %global use_rpcbind %{on_off_switch rpcbind} @BCOND_UNWIND@ unwind %global use_unwind %{on_off_switch unwind} @BCOND_MSPAC_SUPPORT@ mspac_support %global use_mspac_support %{on_off_switch mspac_support} @BCOND_SANITIZE_ADDRESS@ sanitize_address %global use_sanitize_address %{on_off_switch sanitize_address} @BCOND_LEGACY_PYTHON_INSTALL@ legacy_python_install %global use_legacy_python_install %{on_off_switch legacy_python_install} @BCOND_MONITORING@ monitoring %global use_monitoring %{on_off_switch monitoring} %global dev_version %{lua: s = string.gsub('@GANESHA_EXTRA_VERSION@', '^%-', ''); s2 = string.gsub(s, '%-', '.'); print((s2 ~= nil and s2 ~= '') and s2 or "0.1") } %define sourcename @CPACK_SOURCE_PACKAGE_FILE_NAME@ Name: nfs-ganesha Version: @GANESHA_BASE_VERSION@ Release: %{dev_version}%{?dist} Summary: NFS-Ganesha is a NFS Server running in user space Group: Applications/System License: LGPLv3+ Url: https://github.com/nfs-ganesha/nfs-ganesha/wiki Source: %{sourcename}.tar.gz BuildRequires: cmake3 BuildRequires: bison BuildRequires: flex BuildRequires: pkgconfig BuildRequires: userspace-rcu-devel BuildRequires: krb5-devel %if %{with rados_recov} || %{with rados_urls} BuildRequires: librados-devel >= 0.61 %endif %if ( 0%{?suse_version} >= 1330 ) BuildRequires: libnsl-devel %else %if ( 0%{?fedora} >= 28 || 0%{?rhel} >= 8 ) BuildRequires: libnsl2-devel %endif %endif %if ( 0%{?suse_version} ) BuildRequires: dbus-1-devel Requires: dbus-1 %else BuildRequires: dbus-devel Requires: dbus %endif %if ( 0%{?suse_version} ) BuildRequires: systemd-rpm-macros %endif BuildRequires: libcap-devel BuildRequires: libblkid-devel BuildRequires: libuuid-devel %if %{with mspac_support} BuildRequires: libwbclient-devel %endif BuildRequires: gcc-c++ %if %{with system_ntirpc} BuildRequires: libntirpc-devel >= @NTIRPC_MIN_VERSION@ %else Requires: libntirpc = @NTIRPC_VERSION_EMBED@ %endif %if %{with sanitize_address} BuildRequires: libasan %endif Requires: nfs-utils %if %{with monitoring} Requires: gmonitoring %endif %if ( 0%{?with_rpcbind} ) %if ( 0%{?fedora} ) || ( 0%{?rhel} && 0%{?rhel} >= 6 ) || ( 0%{?suse_version} ) Requires: rpcbind %else Requires: portmap %endif %endif %if ( 0%{?suse_version} ) BuildRequires: nfsidmap-devel %else BuildRequires: libnfsidmap-devel %endif %if %{with rdma} BuildRequires: libmooshika-devel >= 0.6-0 %endif %if %{with jemalloc} BuildRequires: jemalloc-devel %endif %if %{with tcmalloc} BuildRequires: gperftools-devel %endif BuildRequires: systemd Requires(post): systemd Requires(preun): systemd Requires(postun): systemd %if %{with man_page} %if ( 0%{?rhel} && 0%{?rhel} < 8 ) BuildRequires: python-sphinx %else %if ( 0%{?suse_version} ) BuildRequires: python3-Sphinx %else BuildRequires: python3-sphinx %endif %endif %endif Requires(post): psmisc Requires(pre): /usr/sbin/useradd Requires(pre): /usr/sbin/groupadd %if ( 0%{?fedora} >= 30 || 0%{?rhel} >= 8 ) Requires: nfs-ganesha-selinux = %{version}-%{release} %endif # Use CMake variables %description nfs-ganesha : NFS-GANESHA is a NFS Server running in user space. It comes with various back-end modules (called FSALs) provided as shared objects to support different file systems and name-spaces. %if %{with 9P} %package mount-9P Summary: a 9p mount helper Group: Applications/System %description mount-9P This package contains the mount.9P script that clients can use to simplify mounting to NFS-GANESHA. This is a 9p mount helper. %endif %if %{with monitoring} %package -n gmonitoring Summary: The NFS-GANESHA Monitoring module Group: Applications/System Provides: libgmonitoring.so %description -n gmonitoring The monitoring module contains metrics collectors and HTTP exposer in Prometheus format. %endif %package vfs Summary: The NFS-GANESHA VFS FSAL Group: Applications/System BuildRequires: libattr-devel Requires: nfs-ganesha = %{version}-%{release} %description vfs This package contains a FSAL shared object to be used with NFS-Ganesha to support VFS based filesystems %package proxy-v4 Summary: The NFS-GANESHA PROXY_V4 FSAL Group: Applications/System BuildRequires: libattr-devel Requires: nfs-ganesha = %{version}-%{release} %description proxy-v4 This package contains a FSAL shared object to be used with NFS-Ganesha to support PROXY_V4 based filesystems %package proxy-v3 Summary: The NFS-GANESHA PROXY_V3 FSAL Group: Applications/System BuildRequires: libattr-devel Requires: nfs-ganesha = %{version}-%{release} %description proxy-v3 This package contains a FSAL shared object to be used with NFS-Ganesha to support PROXY_V3 based filesystems %if %{with utils} %package utils Summary: The NFS-GANESHA util scripts Group: Applications/System %if ( 0%{?rhel} && 0%{?rhel} < 8 ) Requires: dbus-python, pygobject2, pyparsing BuildRequires: python-devel %else Requires: python3-gobject, python3-pyparsing BuildRequires: python3-devel %if ( 0%{?suse_version} ) Requires: dbus-1-python %else Requires: python3-dbus %endif %if ( ! 0%{?with_legacy_python_install} ) BuildRequires: python3-wheel BuildRequires: python3-build BuildRequires: python3-installer %endif %endif %if %{with unwind} Requires: glibc-devel %endif %if %{with gui_utils} %if ( 0%{?suse_version} ) BuildRequires: python-qt5-devel %else %if ( 0%{?fedora} >= 31 || 0%{?rhel} >= 8 ) BuildRequires: PyQt5-devel %else BuildRequires: PyQt4-devel %endif %endif %endif %description utils This package contains utility scripts for managing the NFS-GANESHA server %endif %if %{with lttng} %package lttng Summary: The NFS-GANESHA library for use with LTTng Group: Applications/System BuildRequires: lttng-ust-devel >= 2.3 BuildRequires: lttng-tools-devel >= 2.3 Requires: nfs-ganesha = %{version}-%{release}, lttng-tools >= 2.3, lttng-ust >= 2.3 %description lttng This package contains the libganesha_trace.so library. When preloaded to the ganesha.nfsd server, it makes it possible to trace using LTTng. %endif %if %{with rados_recov} %package rados-grace Summary: The NFS-GANESHA command for managing the RADOS grace database Group: Applications/System Requires: nfs-ganesha = %{version}-%{release} %description rados-grace This package contains the ganesha-rados-grace tool for interacting with the database used by the rados_cluster recovery backend and the libganesha_rados_grace shared library for using RADOS storage for recovery state. %endif %if %{with rados_urls} %package rados-urls Summary: The NFS-GANESHA library for use with RADOS URLs Group: Applications/System Requires: nfs-ganesha = %{version}-%{release} %description rados-urls This package contains the libganesha_rados_urls library used for handling RADOS URL configurations. %endif # Option packages start here. use "rpmbuild --with gpfs" (or equivalent) # for activating this part of the spec file # NULL %if %{with nullfs} %package nullfs Summary: The NFS-GANESHA NULLFS Stackable FSAL Group: Applications/System Requires: nfs-ganesha = %{version}-%{release} %description nullfs This package contains a Stackable FSAL shared object to be used with NFS-Ganesha. This is mostly a template for future (more sophisticated) stackable FSALs %endif # MEM %if %{with mem} %package mem Summary: The NFS-GANESHA Memory backed testing FSAL Group: Applications/System Requires: nfs-ganesha = %{version}-%{release} %description mem This package contains a FSAL shared object to be used with NFS-Ganesha. This is used for speed and latency testing. %endif # GPFS %if %{with gpfs} %package gpfs Summary: The NFS-GANESHA GPFS FSAL Group: Applications/System Requires: nfs-ganesha = %{version}-%{release} %description gpfs This package contains a FSAL shared object to be used with NFS-Ganesha to support GPFS backend %endif # CEPH %if %{with ceph} %package ceph Summary: The NFS-GANESHA CephFS FSAL Group: Applications/System Requires: nfs-ganesha = %{version}-%{release} BuildRequires: libcephfs-devel >= 10.2.0 BuildRequires: libacl-devel %description ceph This package contains a FSAL shared object to be used with NFS-Ganesha to support CephFS %endif # RGW %if %{with rgw} %package rgw Summary: The NFS-GANESHA Ceph RGW FSAL Group: Applications/System Requires: nfs-ganesha = %{version}-%{release} BuildRequires: librgw-devel >= 10.2.0 %description rgw This package contains a FSAL shared object to be used with NFS-Ganesha to support Ceph RGW %endif # XFS %if %{with xfs} %package xfs Summary: The NFS-GANESHA XFS FSAL Group: Applications/System Requires: nfs-ganesha = %{version}-%{release} BuildRequires: libattr-devel xfsprogs-devel %description xfs This package contains a shared object to be used with FSAL_VFS to support XFS correctly %endif #LUSTRE %if %{with lustre} %package lustre Summary: The NFS-GANESHA LUSTRE FSAL Group: Applications/System BuildRequires: libattr-devel BuildRequires: lustre-client Requires: nfs-ganesha = %{version}-%{release} Requires: lustre-client %description lustre This package contains a FSAL shared object to be used with NFS-Ganesha to support LUSTRE based filesystems %endif #KVSFS %if %{with kvsfs} %package kvsfs Summary: The NFS-GANESHA KVSFS FSAL Group: Applications/System Requires: nfs-ganesha = %{version}-%{release} Requires: libkvsns >= 1.2.0 BuildRequires: libkvsns-devel >= 1.2.0 %description kvsfs This package contains a FSAL shared object to be used with NFS-Ganesha to support KVSFS/libkvsns %endif # GLUSTER %if %{with gluster} %package gluster Summary: The NFS-GANESHA GLUSTER FSAL Group: Applications/System Requires: nfs-ganesha = %{version}-%{release} BuildRequires: libgfapi-devel >= 7.0 BuildRequires: libattr-devel, libacl-devel %description gluster This package contains a FSAL shared object to be used with NFS-Ganesha to support Gluster %endif # SELINUX %if ( 0%{?fedora} >= 30 || 0%{?rhel} >= 8 ) %package selinux Summary: The NFS-GANESHA SELINUX targeted policy Group: Applications/System BuildArch: noarch Requires: nfs-ganesha = %{version}-%{release} BuildRequires: selinux-policy-devel %{?selinux_requires} %description selinux This package contains an selinux policy for running ganesha.nfsd %post selinux %selinux_modules_install %{_selinux_store_path}/packages/ganesha.pp.bz2 %pre selinux %selinux_relabel_pre %postun selinux if [ $1 -eq 0 ]; then %selinux_modules_uninstall ganesha fi %posttrans %selinux_relabel_post %endif # NTIRPC (if built-in) %if ! %{with system_ntirpc} %package -n libntirpc Summary: New Transport Independent RPC Library Group: System Environment/Libraries License: BSD Version: @NTIRPC_VERSION_EMBED@ Url: https://github.com/nfs-ganesha/ntirpc # libtirpc has /etc/netconfig, most machines probably have it anyway # for NFS client Requires: libtirpc %if %{with monitoring} Requires: gmonitoring %endif %description -n libntirpc This package contains a new implementation of the original libtirpc, transport-independent RPC (TI-RPC) library for NFS-Ganesha. It has the following features not found in libtirpc: 1. Bi-directional operation 2. Full-duplex operation on the TCP (vc) transport 3. Thread-safe operating modes 3.1 new locking primitives and lock callouts (interface change) 3.2 stateless send/recv on the TCP transport (interface change) 4. Flexible server integration support 5. Event channels (remove static arrays of xprt handles, new EPOLL/KEVENT integration) %package -n libntirpc-devel Summary: Development headers for libntirpc Requires: libntirpc = @NTIRPC_VERSION_EMBED@ Group: System Environment/Libraries License: BSD Version: @NTIRPC_VERSION_EMBED@ Url: https://github.com/nfs-ganesha/ntirpc %description -n libntirpc-devel Development headers and auxiliary files for developing with %{name}. %endif %prep %setup -q -n %{sourcename} %build cmake3 . -DCMAKE_BUILD_TYPE=Debug \ -DBUILD_CONFIG=rpmbuild \ -DUSE_FSAL_NULL=%{use_fsal_null} \ -DUSE_FSAL_MEM=%{use_fsal_mem} \ -DUSE_FSAL_XFS=%{use_fsal_xfs} \ -DUSE_FSAL_LUSTRE=%{use_fsal_lustre} \ -DUSE_FSAL_CEPH=%{use_fsal_ceph} \ -DUSE_FSAL_RGW=%{use_fsal_rgw} \ -DUSE_FSAL_GPFS=%{use_fsal_gpfs} \ -DUSE_FSAL_KVSFS=%{use_fsal_kvsfs} \ -DUSE_FSAL_GLUSTER=%{use_fsal_gluster} \ -DUSE_SYSTEM_NTIRPC=%{use_system_ntirpc} \ -DUSE_9P_RDMA=%{use_rdma} \ -DUSE_LTTNG=%{use_lttng} \ -DUSE_UNWIND=%{use_unwind} \ -DUSE_ADMIN_TOOLS=%{use_utils} \ -DUSE_GUI_ADMIN_TOOLS=%{use_gui_utils} \ -DUSE_RADOS_RECOV=%{use_rados_recov} \ -DRADOS_URLS=%{use_rados_urls} \ -DUSE_FSAL_VFS=ON \ -DUSE_FSAL_PROXY_V4=ON \ -DUSE_DBUS=ON \ -DUSE_MONITORING=%{use_monitoring} \ -DUSE_9P=%{use_9P} \ -DDISTNAME_HAS_GIT_DATA=OFF \ -DUSE_MAN_PAGE=%{use_man_page} \ -DRPCBIND=%{use_rpcbind} \ -D_MSPAC_SUPPORT=%{use_mspac_support} \ -DSANITIZE_ADDRESS=%{use_sanitize_address} \ -DUSE_LEGACY_PYTHON_INSTALL=%{use_legacy_python_install} \ %if %{with jemalloc} -DALLOCATOR=jemalloc %endif %if %{with tcmalloc} -DALLOCATOR=tcmalloc %endif make %{?_smp_mflags} || make %{?_smp_mflags} || make %if ( 0%{?fedora} >= 30 || 0%{?rhel} >= 8 ) make -C selinux -f /usr/share/selinux/devel/Makefile ganesha.pp pushd selinux && bzip2 -9 ganesha.pp && popd %endif %install mkdir -p %{buildroot}%{_sysconfdir}/ganesha/ mkdir -p %{buildroot}%{_sysconfdir}/dbus-1/system.d mkdir -p %{buildroot}%{_sysconfdir}/sysconfig mkdir -p %{buildroot}%{_sysconfdir}/logrotate.d mkdir -p %{buildroot}%{_bindir} mkdir -p %{buildroot}%{_sbindir} mkdir -p %{buildroot}%{_libdir}/ganesha mkdir -p %{buildroot}%{_localstatedir}/log/ganesha mkdir -p %{buildroot}%{_libexecdir}/ganesha install -m 644 config_samples/logrotate_ganesha %{buildroot}%{_sysconfdir}/logrotate.d/ganesha install -m 644 scripts/ganeshactl/org.ganesha.nfsd.conf %{buildroot}%{_sysconfdir}/dbus-1/system.d install -m 755 scripts/nfs-ganesha-config.sh %{buildroot}%{_libexecdir}/ganesha install -m 755 scripts/gen_ctdb_epoch.py %{buildroot}%{_libexecdir}/ganesha/ %if %{with 9P} install -m 755 tools/mount.9P %{buildroot}%{_sbindir}/mount.9P %endif install -m 644 config_samples/vfs.conf %{buildroot}%{_sysconfdir}/ganesha mkdir -p %{buildroot}%{_unitdir} %if ( 0%{?fedora} ) || ( 0%{?rhel} && 0%{?rhel} >= 8 ) mkdir -p %{buildroot}%{_sysconfdir}/systemd/system/nfs-ganesha-lock.service.d %endif install -m 644 scripts/systemd/nfs-ganesha.service.el7 %{buildroot}%{_unitdir}/nfs-ganesha.service %if ( 0%{?fedora} ) || ( 0%{?rhel} && 0%{?rhel} >= 8 ) install -m 644 scripts/systemd/nfs-ganesha-lock.service.el8 %{buildroot}%{_unitdir}/nfs-ganesha-lock.service install -m 644 scripts/systemd/rpc-statd.conf.el8 %{buildroot}%{_sysconfdir}/systemd/system/nfs-ganesha-lock.service.d/rpc-statd.conf %else install -m 644 scripts/systemd/nfs-ganesha-lock.service.el7 %{buildroot}%{_unitdir}/nfs-ganesha-lock.service %endif install -m 644 scripts/systemd/nfs-ganesha-config.service %{buildroot}%{_unitdir}/nfs-ganesha-config.service install -m 644 scripts/systemd/sysconfig/nfs-ganesha %{buildroot}%{_sysconfdir}/sysconfig/ganesha %if %{with pt} install -m 644 config_samples/pt.conf %{buildroot}%{_sysconfdir}/ganesha %endif %if %{with lustre} install -m 644 config_samples/lustre.conf %{buildroot}%{_sysconfdir}/ganesha %endif %if %{with xfs} install -m 644 config_samples/xfs.conf %{buildroot}%{_sysconfdir}/ganesha %endif %if %{with ceph} install -m 644 config_samples/ceph.conf %{buildroot}%{_sysconfdir}/ganesha %endif %if %{with rgw} install -m 644 config_samples/rgw.conf %{buildroot}%{_sysconfdir}/ganesha install -m 644 config_samples/rgw_bucket.conf %{buildroot}%{_sysconfdir}/ganesha %endif %if %{with gluster} install -m 644 config_samples/logrotate_fsal_gluster %{buildroot}%{_sysconfdir}/logrotate.d/ganesha-gfapi %endif %if %{with gpfs} install -m 644 config_samples/gpfs.conf %{buildroot}%{_sysconfdir}/ganesha install -m 644 config_samples/gpfs.ganesha.nfsd.conf %{buildroot}%{_sysconfdir}/ganesha install -m 644 config_samples/gpfs.ganesha.main.conf %{buildroot}%{_sysconfdir}/ganesha install -m 644 config_samples/gpfs.ganesha.log.conf %{buildroot}%{_sysconfdir}/ganesha install -m 644 config_samples/gpfs.ganesha.exports.conf %{buildroot}%{_sysconfdir}/ganesha %endif make DESTDIR=%{buildroot} install %if ( 0%{?fedora} >= 30 || 0%{?rhel} >= 8 ) install -d %{buildroot}%{_selinux_store_path}/packages install -d -p %{buildroot}%{_selinux_store_path}/devel/include/contrib install -p -m 644 selinux/ganesha.if %{buildroot}%{_selinux_store_path}/devel/include/contrib install -m 0644 selinux/ganesha.pp.bz2 %{buildroot}%{_selinux_store_path}/packages %endif %if ( ! 0%{?with_legacy_python_install} ) %if ( 0%{?with_gpfs} ) mv %{buildroot}/usr/bin/gpfs-epoch %{buildroot}/usr/libexec/ganesha/ %endif %endif %if ( 0%{?rhel} && 0%{?rhel} < 8 ) rm -rf %{buildroot}/%{python_sitelib}/gpfs* rm -f %{buildroot}/%{python_sitelib}/__init__.* %else rm -rf %{buildroot}/%{python3_sitelib}/gpfs* rm -rf %{buildroot}/%{python3_sitelib}/ganeshactl* rm -f %{buildroot}/%{python3_sitelib}/__init__.* rm -rf %{buildroot}/%{python3_sitelib}/__pycache__ rm -f %{buildroot}/%{python3_sitelib}/Ganesha/__init__.* rm -f %{buildroot}/%{python3_sitelib}/Ganesha/QtUI/__init__.* rm -rf %{buildroot}/%{python3_sitelib}/Ganesha/QtUI/__pycache__ %endif %post %if ( 0%{?suse_version} ) %service_add_post nfs-ganesha.service nfs-ganesha-lock.service nfs-ganesha-config.service %else %if ( 0%{?fedora} || ( 0%{?rhel} && 0%{?rhel} > 6 ) ) semanage fcontext -a -t ganesha_var_log_t %{_localstatedir}/log/ganesha > /dev/null 2>&1 || : semanage fcontext -a -t ganesha_var_log_t %{_localstatedir}/log/ganesha/ganesha.log > /dev/null 2>&1 || : %if %{with gluster} semanage fcontext -a -t ganesha_var_log_t %{_localstatedir}/log/ganesha/ganesha-gfapi.log > /dev/null 2>&1 || : %endif restorecon %{_localstatedir}/log/ganesha %endif %systemd_post nfs-ganesha.service %systemd_post nfs-ganesha-lock.service %systemd_post nfs-ganesha-config.service %endif killall -SIGHUP dbus-daemon >/dev/null 2>&1 || : %pre getent group ganesha > /dev/null || groupadd -r ganesha getent passwd ganesha > /dev/null || useradd -r -g ganesha -d %{_rundir}/ganesha -s /sbin/nologin -c "NFS-Ganesha Daemon" ganesha exit 0 %preun %if ( 0%{?suse_version} ) %service_del_preun nfs-ganesha-lock.service %else %systemd_preun nfs-ganesha-lock.service %endif %postun %if ( 0%{?suse_version} ) %service_del_postun nfs-ganesha-lock.service %debug_package %else %systemd_postun_with_restart nfs-ganesha-lock.service %endif %files %{_bindir}/ganesha.nfsd %{_libdir}/libganesha_nfsd.so* %config %{_sysconfdir}/dbus-1/system.d/org.ganesha.nfsd.conf %config(noreplace) %{_sysconfdir}/sysconfig/ganesha %config(noreplace) %{_sysconfdir}/logrotate.d/ganesha %dir %{_sysconfdir}/ganesha/ %config(noreplace) %{_sysconfdir}/ganesha/ganesha.conf %dir %{_libexecdir}/ganesha %{_libexecdir}/ganesha/nfs-ganesha-config.sh %{_libexecdir}/ganesha/gen_ctdb_epoch.py %dir %attr(0775,ganesha,root) %{_localstatedir}/log/ganesha %{_unitdir}/nfs-ganesha.service %{_unitdir}/nfs-ganesha-lock.service %{_unitdir}/nfs-ganesha-config.service %if ( 0%{?fedora} ) || ( 0%{?rhel} && 0%{?rhel} >= 8 ) %{_sysconfdir}/systemd/system/nfs-ganesha-lock.service.d/rpc-statd.conf %endif %if %{with man_page} %{_mandir}/*/ganesha-config.8.gz %{_mandir}/*/ganesha-core-config.8.gz %{_mandir}/*/ganesha-export-config.8.gz %{_mandir}/*/ganesha-cache-config.8.gz %{_mandir}/*/ganesha-log-config.8.gz %endif %if %{with rados_recov} %files rados-grace %{_bindir}/ganesha-rados-grace %{_libdir}/libganesha_rados_recov.so* %if %{with man_page} %{_mandir}/*/ganesha-rados-grace.8.gz %{_mandir}/*/ganesha-rados-cluster-design.8.gz %endif %endif %if %{with rados_urls} %files rados-urls %{_libdir}/libganesha_rados_urls.so* %endif %if %{with 9P} %files mount-9P %{_sbindir}/mount.9P %if %{with man_page} %{_mandir}/*/ganesha-9p-config.8.gz %endif %endif %if %{with monitoring} %files -n gmonitoring %{_libdir}/libgmonitoring* %endif %files vfs %{_libdir}/ganesha/libfsalvfs* %config(noreplace) %{_sysconfdir}/ganesha/vfs.conf %if %{with man_page} %{_mandir}/*/ganesha-vfs-config.8.gz %endif %files proxy-v4 %{_libdir}/ganesha/libfsalproxy_v4* %if %{with man_page} %{_mandir}/*/ganesha-proxy-v4-config.8.gz %endif %files proxy-v3 %{_libdir}/ganesha/libfsalproxy_v3* %if %{with man_page} %{_mandir}/*/ganesha-proxy-v3-config.8.gz %endif # Optional packages %if %{with lustre} %files lustre %{_libdir}/ganesha/libfsallustre* %config(noreplace) %{_sysconfdir}/ganesha/lustre.conf %if %{with man_page} %{_mandir}/*/ganesha-lustre-config.8.gz %endif %endif %if %{with nullfs} %files nullfs %{_libdir}/ganesha/libfsalnull* %endif %if %{with mem} %files mem %{_libdir}/ganesha/libfsalmem* %endif %if %{with gpfs} %files gpfs %{_libdir}/ganesha/libfsalgpfs* %config(noreplace) %{_sysconfdir}/ganesha/gpfs.conf %config(noreplace) %{_sysconfdir}/ganesha/gpfs.ganesha.nfsd.conf %config(noreplace) %{_sysconfdir}/ganesha/gpfs.ganesha.main.conf %config(noreplace) %{_sysconfdir}/ganesha/gpfs.ganesha.log.conf %config(noreplace) %{_sysconfdir}/ganesha/gpfs.ganesha.exports.conf %if %{with utils} %{_libexecdir}/ganesha/gpfs-epoch %endif %if %{with man_page} %{_mandir}/*/ganesha-gpfs-config.8.gz %endif %endif %if %{with xfs} %files xfs %{_libdir}/ganesha/libfsalxfs* %config(noreplace) %{_sysconfdir}/ganesha/xfs.conf %if %{with man_page} %{_mandir}/*/ganesha-xfs-config.8.gz %endif %endif %if %{with ceph} %files ceph %{_libdir}/ganesha/libfsalceph* %config(noreplace) %{_sysconfdir}/ganesha/ceph.conf %if %{with man_page} %{_mandir}/*/ganesha-ceph-config.8.gz %endif %endif %if %{with rgw} %files rgw %{_libdir}/ganesha/libfsalrgw* %config(noreplace) %{_sysconfdir}/ganesha/rgw.conf %config(noreplace) %{_sysconfdir}/ganesha/rgw_bucket.conf %if %{with man_page} %{_mandir}/*/ganesha-rgw-config.8.gz %endif %endif %if %{with gluster} %files gluster %config(noreplace) %{_sysconfdir}/logrotate.d/ganesha-gfapi %{_libdir}/ganesha/libfsalgluster* %if %{with man_page} %{_mandir}/*/ganesha-gluster-config.8.gz %endif %endif %if ( 0%{?fedora} >= 30 || 0%{?rhel} >= 8 ) %files selinux %attr(0644,root,root) %{_selinux_store_path}/packages/ganesha.pp.bz2 %attr(0644,root,root) %{_selinux_store_path}/devel/include/contrib/ganesha.if %endif %if ! %{with system_ntirpc} %files -n libntirpc %{_libdir}/libntirpc.so.@NTIRPC_VERSION_EMBED@ %{_libdir}/libntirpc.so.@NTIRPC_ABI_EMBED@ %{_libdir}/libntirpc.so %{!?_licensedir:%global license %%doc} %license libntirpc/COPYING %doc libntirpc/NEWS libntirpc/README %files -n libntirpc-devel %{_libdir}/pkgconfig/libntirpc.pc %dir %{_includedir}/ntirpc %{_includedir}/ntirpc/* %endif %if %{with kvsfs} %files kvsfs %{_libdir}/ganesha/libfsalkvsfs* %endif %if %{with pt} %files pt %{_libdir}/ganesha/libfsalpt* %config(noreplace) %{_sysconfdir}/ganesha/pt.conf %endif %if %{with lttng} %files lttng %{_libdir}/libganesha_trace* %if ! %{with system_ntirpc} %{_libdir}/libntirpc_tracepoints.so %endif %endif %if %{with utils} %files utils %if ( 0%{?rhel} && 0%{?rhel} < 8 ) %{python_sitelib}/Ganesha/* %{python_sitelib}/ganeshactl-*-info %else %{python3_sitelib}/Ganesha/* %if ( ! 0%{?with_legacy_python_install} ) %{python3_sitelib}/ganeshactl-*-info %{python3_sitelib}/ganesha_top-*-info %endif %endif %if %{with gui_utils} %{_bindir}/ganesha-admin %{_bindir}/manage_clients %{_bindir}/manage_exports %{_bindir}/manage_logger %{_bindir}/ganeshactl %if %{with 9P} %{_bindir}/client_stats_9pOps %{_bindir}/export_stats_9pOps %else %exclude %{_bindir}/client_stats_9pOps %exclude %{_bindir}/export_stats_9pOps %endif %endif %{_bindir}/fake_recall %{_bindir}/get_clientids %{_bindir}/grace_period %{_bindir}/ganesha_stats %{_bindir}/sm_notify.ganesha %{_bindir}/ganesha_mgr %{_bindir}/ganesha_logrotate_mgr %{_bindir}/ganesha_conf %{_bindir}/ganesha-top %{_mandir}/*/ganesha_conf.8.gz %endif %changelog nfs-ganesha-6.5/src/os/000077500000000000000000000000001473756622300150055ustar00rootroot00000000000000nfs-ganesha-6.5/src/os/CMakeLists.txt000066400000000000000000000032601473756622300175460ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # All we need to do here is control the # build of chosen platform if(FREEBSD) SET(gos_STAT_SRCS freebsd/atsyscalls.c freebsd/mntent_compat.c freebsd/subr.c freebsd/xattr.c ) endif(FREEBSD) if(DARWIN) SET(gos_STAT_SRCS darwin/sys_resource.c ) endif(DARWIN) if(LINUX) SET(gos_STAT_SRCS linux/acl.c linux/subr.c ) endif(LINUX) add_library(gos OBJECT ${gos_STAT_SRCS}) add_sanitizers(gos) set_target_properties(gos PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(gos gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) ########### install files ############### nfs-ganesha-6.5/src/os/darwin/000077500000000000000000000000001473756622300162715ustar00rootroot00000000000000nfs-ganesha-6.5/src/os/darwin/sys_resource.c000066400000000000000000000024071473756622300211650ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * This program 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 3 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 * 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 * */ #include "sys_resource.h" #include int get_open_file_limit(struct rlimit *rlim) { if (getrlimit(RLIMIT_NOFILE, rlim) != 0) return -1; /* * macOS has unusual semantics for the RLIMIT_NOFILE hard limit. See * the COMPATIBILITY section of the getrlimit man page. * https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/getrlimit.2.html */ if (rlim->rlim_max > OPEN_MAX) rlim->rlim_max = OPEN_MAX; return 0; } nfs-ganesha-6.5/src/os/freebsd/000077500000000000000000000000001473756622300164175ustar00rootroot00000000000000nfs-ganesha-6.5/src/os/freebsd/atsyscalls.c000066400000000000000000000132071473756622300207500ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Sachin Bhamare sbhamare@panasas.com * * This program 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 3 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 * 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 * */ /** * @file atsyscalls.c * @brief platform dependent syscalls */ #include #include #include #include #include "syscalls.h" #include #include #if __FreeBSD_cc_version >= 800001 /* Fllowing syscalls are not yet implemented in vanilla FreeBSD kernels */ int getfhat(int dir_fd, char *fname, struct fhandle *fhp, int flag) { int mod, err; int syscall_num; struct module_stat stat; stat.version = sizeof(stat); /* modstat will retrieve the module_stat structure for our module named * syscall (see the SYSCALL_MODULE macro which sets the name to syscall) */ mod = modfind("sys/getfhat"); if (mod == -1) return errno; err = modstat(mod, &stat); if (err) return errno; /* extract the slot (syscall) number */ syscall_num = stat.data.intval; /* int getfhat(int fd, char *path, struct fhandle *fhp, int flag); */ return syscall(syscall_num, dir_fd, fname, fhp, flag); } int fhlink(struct fhandle *fhp, int tofd, const char *to) { int mod, err; int syscall_num; struct module_stat stat; stat.version = sizeof(stat); /* modstat will retrieve the module_stat structure for our module named * syscall (see the SYSCALL_MODULE macro which sets the name to syscall) */ mod = modfind("sys/fhlink"); if (mod == -1) return errno; err = modstat(mod, &stat); if (err) return errno; /* extract the slot (syscall) number */ syscall_num = stat.data.intval; /* int fhlink(struct fhandle *fhp, int tofd, const char *to); */ return syscall(syscall_num, fhp, tofd, to); } int fhreadlink(struct fhandle *fhp, char *buf, size_t bufsize) { int mod, err; int syscall_num; struct module_stat stat; stat.version = sizeof(stat); /* modstat will retrieve the module_stat structure for our module named * syscall (see the SYSCALL_MODULE macro which sets the name to syscall) */ mod = modfind("sys/fhreadlink"); if (mod == -1) return errno; err = modstat(mod, &stat); if (err) return errno; /* extract the slot (syscall) number */ syscall_num = stat.data.intval; /* int fhreadlink(struct fhandle *fhp, char *buf, size_t bufsize); */ return syscall(syscall_num, fhp, buf, bufsize); } #endif #ifndef SYS_openat /* * Allow compilation (only) on FreeBSD versions without these syscalls * These numbers match the modified FreeBSD 10.1 used by Panasas * */ #define SYS_faccessat 512 #define SYS_fchmodat 513 #define SYS_fchownat 514 #define SYS_fstatat 515 #define SYS_futimesat 516 #define SYS_linkat 517 #define SYS_mkdirat 518 #define SYS_mkfifoat 519 #define SYS_mknodat 520 #define SYS_openat 521 #define SYS_readlinkat 522 #define SYS_renameat 523 #define SYS_symlinkat 524 #define SYS_unlinkat 525 #define SYS_getfhat 526 #define SYS_fhlink 527 #define SYS_fhreadlink 528 int openat(int dir_fd, const char *file, int oflag, mode_t mode) { return syscall(SYS_openat, dir_fd, file, oflag, mode); } int mkdirat(int dir_fd, const char *file, mode_t mode) { return syscall(SYS_mkdirat, dir_fd, file, mode); } int mknodat(int dir_fd, const char *file, mode_t mode, dev_t dev) { return syscall(SYS_mknodat, dir_fd, file, mode, dev); } int fchownat(int dir_fd, const char *file, uid_t owner, gid_t group, int flag) { return syscall(SYS_fchownat, dir_fd, file, owner, group, flag); } int futimesat(int dir_fd, char *filename, struct timeval *utimes) { return syscall(SYS_futimesat, dir_fd, filename, utimes); } int fstatat(int dir_fd, const char *file, struct stat *st, int flag) { return syscall(SYS_fstatat, dir_fd, file, st, flag); } int unlinkat(int dir_fd, const char *file, int flag) { return syscall(SYS_unlinkat, dir_fd, file, flag); } int renameat(int oldfd, const char *old, int newfd, const char *new) { return syscall(SYS_renameat, oldfd, old, newfd, new); } int linkat(int fromfd, const char *from, int tofd, const char *to, int flags) { return syscall(SYS_linkat, fromfd, from, tofd, to, flags); } int symlinkat(const char *from, int tofd, const char *to) { return syscall(SYS_symlinkat, from, tofd, to); } int readlinkat(int fd, const char *path, char *buf, size_t len) { return syscall(SYS_readlinkat, fd, path, buf, len); } int fchmodat(int dir_fd, const char *filename, mode_t mode, int flags) { return syscall(SYS_fchmodat, dir_fd, filename, mode, flags); } int faccessat(int dir_fd, char *filename, int mode, int flags) { return syscall(SYS_faccessat, dir_fd, filename, mode, flags); } int getfhat(int dir_fd, char *fname, struct fhandle *fhp, int flag) { return syscall(SYS_getfhat, dir_fd, fname, fhp, flag); } int fhlink(struct fhandle *fhp, int tofd, const char *to) { return syscall(SYS_fhlink, fhp, tofd, to); } int fhreadlink(struct fhandle *fhp, char *buf, size_t bufsize) { return syscall(SYS_fhreadlink, fhp, buf, bufsize); } #endif /* SYS_openat */ nfs-ganesha-6.5/src/os/freebsd/memstream.c000066400000000000000000000067741473756622300205730ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Contributeur: Sachin Bhamare sbhamare@panasas.com * * This program 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 3 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 * 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 * * ------------- */ /** * @file memstream.c * @brief Set of function to provide open_memstream() API on FreeBSD * * Following set of function facilitate open_memstream API which * really is a wrapper around funopen() call on FreeBSD * platform. These are taken from the implementation at * http://people.freebsd.org/~jhb/mcelog/memstream.c and all the * relevant licences apply. */ #include #include #include #include #include #include "abstract_mem.h" struct memstream { char **cp; size_t *lenp; size_t offset; }; static void memstream_grow(struct memstream *ms, size_t newsize) { char *buf; if (newsize > *ms->lenp) { buf = gsh_realloc(*ms->cp, newsize + 1); #ifdef DEBUG fprintf(stderr, "MS: %p growing from %zd to %zd\n", ms, *ms->lenp, newsize); #endif memset(buf + *ms->lenp + 1, 0, newsize - *ms->lenp); *ms->cp = buf; *ms->lenp = newsize; } } static int memstream_read(void *cookie, char *buf, int len) { struct memstream *ms; int tocopy; ms = cookie; memstream_grow(ms, ms->offset + len); tocopy = *ms->lenp - ms->offset; if (len < tocopy) tocopy = len; memcpy(buf, *ms->cp + ms->offset, tocopy); ms->offset += tocopy; #ifdef DEBUG fprintf(stderr, "MS: read(%p, %d) = %d\n", ms, len, tocopy); #endif return tocopy; } static int memstream_write(void *cookie, const char *buf, int len) { struct memstream *ms; int tocopy; ms = cookie; memstream_grow(ms, ms->offset + len); tocopy = *ms->lenp - ms->offset; if (len < tocopy) tocopy = len; memcpy(*ms->cp + ms->offset, buf, tocopy); ms->offset += tocopy; #ifdef DEBUG fprintf(stderr, "MS: write(%p, %d) = %d\n", ms, len, tocopy); #endif return tocopy; } static fpos_t memstream_seek(void *cookie, fpos_t pos, int whence) { struct memstream *ms; #ifdef DEBUG size_t old; #endif ms = cookie; #ifdef DEBUG old = ms->offset; #endif switch (whence) { case SEEK_SET: ms->offset = pos; break; case SEEK_CUR: ms->offset += pos; break; case SEEK_END: ms->offset = *ms->lenp + pos; break; } #ifdef DEBUG fprintf(stderr, "MS: seek(%p, %zd, %d) %zd -> %zd\n", ms, pos, whence, old, ms->offset); #endif return ms->offset; } static int memstream_close(void *cookie) { gsh_free(cookie); return 0; } FILE *open_memstream(char **cp, size_t *lenp) { struct memstream *ms; int save_errno; FILE *fp; *cp = NULL; *lenp = 0; ms = gsh_malloc(sizeof(*ms)); ms->cp = cp; ms->lenp = lenp; ms->offset = 0; fp = funopen(ms, memstream_read, memstream_write, memstream_seek, memstream_close); if (fp == NULL) { save_errno = errno; gsh_free(ms); errno = save_errno; } return fp; } nfs-ganesha-6.5/src/os/freebsd/mntent_compat.c000066400000000000000000000112371473756622300214370ustar00rootroot00000000000000// SPDX-License-Identifier: BSD-4-Clause /* * Copyright (c) 1980, 1989, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2001 * David Rufino * Copyright (c) 2006 * Stanislav Sedov * * 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. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. */ /* most of this was ripped from the mount(3) source */ #include "config.h" #include #include #include #include #include #include #include #include "abstract_mem.h" #include "log.h" static int pos = -1; static int mntsize = -1; static struct statfs *_mntbuf; static struct mntent _mntent; struct { int m_flag; const char *m_option; } mntoptions[] = { { MNT_ASYNC, "async" }, { MNT_NOATIME, "noatime" }, { MNT_NOEXEC, "noexec" }, { MNT_NOSUID, "nosuid" }, { MNT_NOSYMFOLLOW, "nosymfollow" }, { MNT_SYNCHRONOUS, "sync" }, { MNT_UNION, "union" }, { MNT_NOCLUSTERR, "noclusterr" }, { MNT_NOCLUSTERW, "noclusterw" }, { MNT_SUIDDIR, "suiddir" }, #ifdef MNT_SNAPSHOT { MNT_SNAPSHOT, "snapshot" }, #endif #ifdef MNT_MULTILABEL { MNT_MULTILABEL, "multilabel" }, #endif #ifdef MNT_ACLS { MNT_ACLS, "acls" }, #endif #ifdef MNT_NODEV { MNT_NODEV, "nodev" }, #endif }; #define N_OPTS (sizeof(mntoptions) / sizeof(*mntoptions)) char *hasmntopt(const struct mntent *mnt, const char *option) { int found; char *opt, *optbuf; optbuf = gsh_strdup(mnt->mnt_opts); found = 0; for (opt = optbuf; (opt = strtok(opt, " ")) != NULL; opt = NULL) { if (!strcasecmp(opt, option)) { opt = opt - optbuf + mnt->mnt_opts; gsh_free(optbuf); return opt; } } gsh_free(optbuf); return NULL; } static void catopt(char *buf, int *pos, size_t size, const char *s1) { size_t newlen; if (s1 == NULL || *s1 == '\0') return; newlen = strlen(s1); if (*pos != 0 && (*pos + 1 + newlen) < size) { buf[*pos] = ' '; *pos = *pos + 1; } if ((*pos + newlen) < size) { memcpy(buf + *pos, s1, newlen + 1); *pos = *pos + newlen; } } static void flags2opts(int flags, char *buf, size_t size) { char *res = NULL; int i, pos = 0; catopt(res, &pos, size, (flags & MNT_RDONLY) ? "ro" : "rw"); for (i = 0; i < N_OPTS; i++) if (flags & mntoptions[i].m_flag) catopt(res, &pos, size, mntoptions[i].m_option); } static struct mntent *statfs_to_mntent(struct statfs *mntbuf) { static char opts_buf[40]; _mntent.mnt_fsname = mntbuf->f_mntfromname; _mntent.mnt_dir = mntbuf->f_mntonname; _mntent.mnt_type = mntbuf->f_fstypename; flags2opts(mntbuf->f_flags, opts_buf, sizeof(opts_buf)); _mntent.mnt_opts = opts_buf; _mntent.mnt_freq = _mntent.mnt_passno = 0; return &_mntent; } struct mntent *getmntent(FILE *fp) { int i; if (pos == -1 || mntsize == -1) mntsize = getmntinfo(&_mntbuf, MNT_WAIT); for (i = 0; i < mntsize; i++) LogFullDebug(COMPONENT_FSAL, "%s", _mntbuf[i].f_mntfromname); ++pos; if (pos == mntsize) { pos = mntsize = -1; return NULL; } return statfs_to_mntent(&_mntbuf[pos]); } nfs-ganesha-6.5/src/os/freebsd/subr.c000066400000000000000000000135661473756622300175510ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Sachin Bhamare sbhamare@panasas.com * * This program 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 3 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 * 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 * * ------------- */ /** * @file os/freebsd/subr.c * @author Sachin Bhamare * @brief Platform dependent subroutines for FreeBSD */ #include #include #include #include #include #include #include #include "syscalls.h" #include /** * @brief Read system directory entries into the buffer * * @param[in] fd File descriptor of open directory * @param[in] buf The buffer * @param[in] bcount Buffer size * @param[in,out] basepp Offset into "file" after this read */ int vfs_readents(int fd, char *buf, unsigned int bcount, off_t *basepp) { return getdirentries(fd, buf, bcount, basepp); } /** * @brief Mash a FreeBSD directory entry into the generic form * * @param[in] buf Pointer into buffer read by vfs_readents * @param[in] bpos Offset into buffer * @param[in] vd Pointer to the generic struct * @param[in] base File offset for this entry in buffer. * * @return true if entry valid, false if not (empty) */ bool to_vfs_dirent(char *buf, int bpos, struct vfs_dirent *vd, off_t base) { struct dirent *dp = (struct dirent *)(buf + bpos); vd->vd_ino = dp->d_fileno; vd->vd_reclen = dp->d_reclen; vd->vd_type = dp->d_type; #ifdef HAS_DOFF vd->vd_offset = dp->d_off; #else vd->vd_offset = base + bpos + dp->d_reclen; #endif vd->vd_name = dp->d_name; return (dp->d_fileno != 0); } /** * @brief Following functions exist to compensate for lack of *times() APIs with * nanosecond granularity on FreeBSD platform. These functions also take care of * timespec <--> timeval conversion. */ #define TIMEVAL_TO_TIMESPEC(tv, ts) \ do { \ (ts)->tv_sec = (tv)->tv_sec; \ (ts)->tv_nsec = (tv)->tv_usec * 1000; \ } while (0) #define TIMESPEC_TO_TIMEVAL(tv, ts) \ do { \ (tv)->tv_sec = (ts)->tv_sec; \ (tv)->tv_usec = (ts)->tv_nsec / 1000; \ } while (0) int vfs_utimesat(int fd, const char *path, const struct timespec ts[2], int flags) { struct timeval tv[2]; if (ts[0].tv_nsec == UTIME_OMIT || ts[1].tv_nsec == UTIME_OMIT) { /* nothing to do */ return 0; } else if (ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW) { /* set to the current timestamp. achieve this by passing NULL * timeval to kernel */ return futimesat(fd, (char *)path, NULL); } TIMESPEC_TO_TIMEVAL(&tv[0], &ts[0]); TIMESPEC_TO_TIMEVAL(&tv[1], &ts[1]); return futimesat(fd, (char *)path, tv); } int vfs_utimes(int fd, const struct timespec *ts) { struct timeval tv[2]; if (ts[0].tv_nsec == UTIME_OMIT || ts[1].tv_nsec == UTIME_OMIT) { /* nothing to do */ return 0; } else if (ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW) { /* set to the current timestamp. achieve this by passing NULL * timeval to kernel */ return futimes(fd, NULL); } TIMESPEC_TO_TIMEVAL(&tv[0], &ts[0]); TIMESPEC_TO_TIMEVAL(&tv[1], &ts[1]); return futimes(fd, tv); } static int setthreaduid(uid_t uid) { int mod, err; int syscall_num; struct module_stat stat; stat.version = sizeof(stat); /* modstat will retrieve the module_stat structure for our module named * syscall (see the SYSCALL_MODULE macro which sets the name to syscall) */ mod = modfind("sys/setthreaduid"); if (mod == -1) return errno; err = modstat(mod, &stat); if (err) return errno; /* extract the slot (syscall) number */ syscall_num = stat.data.intval; return syscall(syscall_num, uid); } static int setthreadgid(gid_t gid) { int mod, err; int syscall_num; struct module_stat stat; stat.version = sizeof(stat); /* modstat will retrieve the module_stat structure for our module named * syscall (see the SYSCALL_MODULE macro which sets the name to syscall) */ mod = modfind("sys/setthreadgid"); if (mod == -1) return errno; err = modstat(mod, &stat); if (err) return errno; /* extract the slot (syscall) number */ syscall_num = stat.data.intval; return syscall(syscall_num, gid); } static int setthreadgroups(size_t size, const gid_t *list) { int mod, err; int syscall_num; struct module_stat stat; stat.version = sizeof(stat); /* modstat will retrieve the module_stat structure for our module named * syscall (see the SYSCALL_MODULE macro which sets the name to syscall) */ mod = modfind("sys/setthreadgroups"); if (mod == -1) return errno; err = modstat(mod, &stat); if (err) return errno; /* extract the slot (syscall) number */ syscall_num = stat.data.intval; return syscall(syscall_num, size, list); } void setuser(uid_t uid) { int rc = setthreaduid(uid); if (rc != 0) LogCrit(COMPONENT_FSAL, "Could not set user identity %s (%d)", strerror(errno), errno); } void setgroup(gid_t gid) { int rc = setthreadgid(gid); if (rc != 0) LogCrit(COMPONENT_FSAL, "Could not set group identity %s (%d)", strerror(errno), errno); } int set_threadgroups(size_t size, const gid_t *list) { return setthreadgroups(size, list); } nfs-ganesha-6.5/src/os/freebsd/xattr.c000066400000000000000000000040511473756622300177250ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2011 * Author: Sachin Bhamare sbhamare@panasas.com * * This program 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 3 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 * 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 * */ /* xattr.c * VFS FSAL xattr support on FreeBSD platform */ #include #include #include #ifndef EXTATTR_MAXNAMELEN #define EXTATTR_MAXNAMELEN 255 #endif ssize_t fgetxattr(int fd, const char *name, void *value, size_t size) { return extattr_get_fd(fd, EXTATTR_NAMESPACE_SYSTEM, name, value, size); } ssize_t fsetxattr(int fd, const char *name, void *value, size_t size, int flags) { char buff[EXTATTR_MAXNAMELEN]; ssize_t attr_size = 0; errno = 0; attr_size = extattr_get_fd(fd, EXTATTR_NAMESPACE_SYSTEM, name, buff, size); if (attr_size != size && errno == ENOATTR) { /* attr we are trying to set doesn't exist. check if * XATTR_REPLACE was set */ if (flags & XATTR_REPLACE) return ENOATTR; } else { if (flags & XATTR_CREATE) return EEXIST; } return extattr_set_fd(fd, EXTATTR_NAMESPACE_SYSTEM, name, value, size); } ssize_t flistxattr(int fd, const char *list, size_t size) { return extattr_list_fd(fd, EXTATTR_NAMESPACE_SYSTEM, (void *)list, size); } ssize_t fremovexattr(int fd, const char *name) { return extattr_delete_fd(fd, EXTATTR_NAMESPACE_SYSTEM, name); } nfs-ganesha-6.5/src/os/linux/000077500000000000000000000000001473756622300161445ustar00rootroot00000000000000nfs-ganesha-6.5/src/os/linux/acl.c000066400000000000000000000040641473756622300170530ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright: DataDirect Networks, 2022 * Author: Martin Schwenke * * This program 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 3 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 * Lesser General Public License for more details. */ /** * @file os/linux/acl.c * @brief Non-standard POSIX ACL functions */ #include #include #include "os/acl.h" #ifndef HAVE_ACL_GET_FD_NP /* * @brief Get POSIX ACL, including default ACL, via file descriptor * * @param[in] fd File descriptor * @param[in] type ACL type - ACL_TYPE_ACCESS, ACL_TYPE_DEFAULT * * @return Posix ACL on success, NULL on failure. */ acl_t acl_get_fd_np(int fd, acl_type_t type) { char fname[PATH_MAX]; int num; if (type == ACL_TYPE_ACCESS) return acl_get_fd(fd); if (fd < 0) { errno = EINVAL; return NULL; } num = snprintf(fname, sizeof(fname), "/proc/self/fd/%d", fd); if (num >= sizeof(fname)) { errno = EINVAL; return NULL; } return acl_get_file(fname, type); } #endif #ifndef HAVE_ACL_SET_FD_NP /* * @brief Set POSIX ACL, including default ACL, via file descriptor * * @param[in] fd Extented attribute * @param[in] acl Posix ACL * @param[in] type ACL type - ACL_TYPE_ACCESS, ACL_TYPE_DEFAULT * * @return 0 on success, non-zero on failure, with errno set. */ int acl_set_fd_np(int fd, acl_t acl, acl_type_t type) { char fname[PATH_MAX]; int num; if (type == ACL_TYPE_ACCESS) return acl_set_fd(fd, acl); if (fd < 0) { errno = EINVAL; return -1; } num = snprintf(fname, sizeof(fname), "/proc/self/fd/%d", fd); if (num >= sizeof(fname)) { errno = EINVAL; return -1; } return acl_set_file(fname, type, acl); } #endif nfs-ganesha-6.5/src/os/linux/subr.c000066400000000000000000000067011473756622300172670ustar00rootroot00000000000000// SPDX-License-Identifier: LGPL-3.0-or-later /* * contributeur : Sachin Bhamare sbhamare@panasas.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file os/linux/subr.c * @author Sachin Bhamare * @brief Platform dependent subroutines for Linux * */ #include "fsal.h" #include #include #include #include #include #include "os/subr.h" /** * @brief Read system directory entries into the buffer * * @param[in] fd File descriptor of open directory * @param[in] buf The buffer * @param[in] bcount Buffer size * @param[in,out] basepp Offset into "file" after this read */ int vfs_readents(int fd, char *buf, unsigned int bcount, off_t *basepp) { int retval = 0; retval = syscall(SYS_getdents64, fd, buf, bcount); if (retval >= 0) *basepp += retval; return retval; } /** * @brief Mash a FreeBSD directory entry into the generic form * * @param buf [in] pointer into buffer read by vfs_readents * @param bpos [in] byte offset into buf to decode * @param vd [in] pointer to the generic struct * @param base [in] base file offset for this buffer - not used * * @return true. Linux entries are never empty. */ bool to_vfs_dirent(char *buf, int bpos, struct vfs_dirent *vd, off_t base) { struct dirent64 *dp = (struct dirent64 *)(buf + bpos); char type; vd->vd_ino = dp->d_ino; vd->vd_reclen = dp->d_reclen; type = buf[dp->d_reclen - 1]; vd->vd_type = type; vd->vd_offset = dp->d_off; vd->vd_name = dp->d_name; return true; } /** * @brief Platform specific wrapper for utimensat(). * * @param[in] fd File descriptor * @param[in] path Name of a file * @param[in] ts Array of struct timespec * @param[in] flags Set of flags for utimesat * * @return 0 on success, -1 on error (errno set to indicate the error). */ int vfs_utimesat(int fd, const char *path, const struct timespec ts[2], int flags) { return utimensat(fd, path, ts, flags); } /** * @brief Platform specific wrapper for futimens(). * * @param[in] fd File descriptor * @param[in] ts Array of struct timespec * * @return 0 on success, -1 on error (errno set to indicate the error). */ int vfs_utimes(int fd, const struct timespec *ts) { return futimens(fd, ts); } void setuser(uid_t uid) { int rc = syscall(SYS_setresuid, -1, uid, -1); if (rc != 0) LogCrit(COMPONENT_FSAL, "Could not set user identity %s (%d)", strerror(errno), errno); } void setgroup(gid_t gid) { int rc = syscall(SYS_setresgid, -1, gid, -1); if (rc != 0) LogCrit(COMPONENT_FSAL, "Could not set group identity %s (%d)", strerror(errno), errno); } int set_threadgroups(size_t size, const gid_t *list) { return syscall(__NR_setgroups, size, list); } nfs-ganesha-6.5/src/scripts/000077500000000000000000000000001473756622300160535ustar00rootroot00000000000000nfs-ganesha-6.5/src/scripts/CMakeLists.txt000066400000000000000000000021721473756622300206150ustar00rootroot00000000000000# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- if(USE_ADMIN_TOOLS) add_subdirectory(ganeshactl) add_subdirectory(gpfs-epoch) add_subdirectory(ganesha-top) endif(USE_ADMIN_TOOLS) nfs-ganesha-6.5/src/scripts/checkpatch.conf000066400000000000000000000053461473756622300210270ustar00rootroot00000000000000# This isn't actually a Linux kernel tree --no-tree # Errors that make no sense if we aren't the Linux kernel --ignore MODIFIED_INCLUDE_ASM --ignore LO_MACRO --ignore HI_MACRO --ignore CSYNC --ignore SSYNC --ignore UAPI_INCLUDE --ignore IN_ATOMIC --ignore LOCKDEP # Warnings specitifc to the Linux kernel --ignore USE_RELATIVE_PATH --ignore CONFIG_DESCRIPTION --ignore CONFIG_EXPERIMENTAL --ignore DEPRECATED_VARIABLE --ignore NETWORKING_BLOCK_COMMENT_STYLE --ignore HOTPLUG_SECTION --ignore EXPORT_SYMBOL --ignore DEFINE_PCI_DEVICE_TABLE --ignore LINUX_VERSION_CODE --ignore PRINTK_RATELIMITED --ignore PRINTK_WITHOUT_KERN_LEVEL --ignore PREFER_PR_LEVEL --ignore USE_NEGATIVE_ERRNO --ignore INCLUDE_LINUX --ignore MISSING_VMLINUX --ignore MSLEEP --ignore PREFER_PACKED --ignore PREFER_ALIGNED --ignore PREFER_PRINTF --ignore PREFER_SCANF --ignore USE_SPINLOCK_T --ignore PREFER_SEQ_PUTS --ignore USLEEP_RANGE --ignore KREALLOC_ARG_REUSE --ignore YIELD --ignore CONSIDER_KSTRTO --ignore USE_DEVICE_INITCALL --ignore NR_CPUS --ignore PRINTF_L --ignore EXPORTED_WORLD_WRITABLE --ignore FILE_PATH_CHANGES # Checks specitifc to the Linux kernel --ignore ARCH_INCLUDE_LINUX --ignore UNCOMMENTED_DEFINITION --ignore UNCOMMENTED_BARRIER --ignore ARCH_DEFINES --ignore UNDOCUMENTED_SETUP --ignore NEW_TYPEDEFS # Don't flag this, since we have LogThis and FSAL_that. --ignore CAMELCASE # Do not consider gerrit changeid as an error --ignore GERRIT_CHANGE_ID # Not being in the kernel, we don't have ARRAY_SIZE macro so some places # define it. --ignore ARRAY_SIZE # our coding style has too many if-then-else that have return in the middle # and thus create unnecessary else --ignore UNNECESSARY_ELSE # Allow janitor patches to cleanup checkpatch errors --ignore EMAIL_SUBJECT # We use full commit id when referencing other commits --ignore GIT_COMMIT_ID # We have the FSF address in a lot of our headers at the moment, # should clean that up one day but ignoring for now. --ignore FSF_MAILING_ADDRESS # We will no longer get grumpy about single line braces --ignore BRACES # We use #if 0 to preserve possibly interesting code --ignore IF_0 --ignore IF_1 --ignore PREFER_DEFINED_ATTRIBUTE_MACRO --ignore STRLCPY --ignore DATE_TIME # @todo FSF work on removing these ones if they make sense... --ignore BLOCK_COMMENT_STYLE #--ignore GLOBAL_INITIALISERS #--ignore UNSPECIFIED_INT #--ignore LONG_LINE #--ignore SUSPECT_CODE_INDENT #--ignore CONSTANT_COMPARISON #--ignore LINE_SPACING #--ignore TYPECAST_INT_CONSTANT # allow split strings --ignore SPLIT_STRING # we like symbolic permissions --ignore SYMBOLIC_PERMS # we are ok with function prototypes without identifier names --ignore FUNCTION_ARGUMENTS --ignore CONST_STRUCT #retain 80 column max line length --max-line-length=80 nfs-ganesha-6.5/src/scripts/checkpatch.pl000077500000000000000000007012761473756622300205250ustar00rootroot00000000000000#!/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_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_"; 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 --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, '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("codespell") ne "" && which("python") 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 = `python -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| ____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 $typeTypedefs = qr{(?x: $typeC99Typedefs\b| $typeOtherOSTypedefs\b| $typeKernelTypedefs\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 $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 @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", ); #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*\( )}; 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); 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 $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" . "$here\n" . $rawline); } if (!defined $lines[$linenr]) { WARN("BAD_SIGN_OFF", "Co-developed-by: must be immediately followed by Signed-off-by:\n" . "$here\n" . $rawline); } elsif ($rawlines[$linenr] !~ /^\s*signed-off-by:\s*(.*)/i) { WARN("BAD_SIGN_OFF", "Co-developed-by: must be immediately followed by Signed-off-by:\n" . "$here\n" . $rawline . "\n" .$rawlines[$linenr]); } elsif ($1 ne $email) { WARN("BAD_SIGN_OFF", "Co-developed-by and Signed-off-by: name/email do not match \n" . "$here\n" . $rawline . "\n" .$rawlines[$linenr]); } } } # 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:|$signature_tags)/i || # A Fixes: or Link: line or signature tag line $commit_log_possible_stack_dump)) { WARN("COMMIT_LOG_LONG_LINE", "Possible unwrapped commit description (prefer a maximum 75 chars per line)\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 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 ("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> (\"\")' - 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 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|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/@ && not $spdx_license =~ /GPL-2\.0.*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)/; } } } } } # check for embedded filenames if ($rawline =~ /^\+.*\Q$realfile\E/) { 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|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/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 comment styles # Networking with an initial /* if ($realfile =~ m@^(drivers/net/|net/)@ && $prevrawline =~ /^\+[ \t]*\/\*[ \t]*$/ && $rawline =~ /^\+[ \t]*\*/ && $realline > 3) { # Do not warn about the initial copyright comment block after SPDX-License-Identifier WARN("NETWORKING_BLOCK_COMMENT_STYLE", "networking block comments don't use an empty /* line, use /* Comment...\n" . $hereprev); } # 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/ || $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@; } } } # avoid BUG() or BUG_ON() if ($line =~ /\b(?:BUG|BUG_ON)\b/) { my $msg_level = \&WARN; $msg_level = \&CHK if ($file); &{$msg_level}("AVOID_BUG", "Avoid crashing the kernel - try using WARN_ON & recovery code rather than BUG() or BUG_ON()\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__)$/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); 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); } } } # 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"; } ERROR("TRAILING_STATEMENTS", "trailing statements should be on next line\n" . $herecurr . $stat_real); } } # 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 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{($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 !~ /^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 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 } 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 Documentation/timers/timers-howto.rst\n" . $herecurr); } if ($delay > 2000) { WARN("LONG_UDELAY", "long udelay - prefer mdelay; see arch/arm/include/asm/delay.h\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 Documentation/timers/timers-howto.rst\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 $ext_type = "Invalid"; my $use = ""; if ($bad_specifier =~ /p[Ff]/) { $use = " - use %pS instead"; $use =~ s/pS/ps/ if ($bad_specifier =~ /pf/); } WARN("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)/; # } # } # } # strlcpy uses that should likely be strscpy if ($line =~ /\bstrlcpy\s*\(/) { WARN("STRLCPY", "Prefer strscpy over strlcpy - see: https://lore.kernel.org/r/CAHk-=wgfRnXz0W3D37d01q3JFkr_i_uTL=V6A6G1oUZcprmknw\@mail.gmail.com/\n" . $herecurr); } # 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 Documentation/timers/timers-howto.rst\n" . "$here\n$stat\n"); } elsif ($min =~ /^\d+$/ && $max =~ /^\d+$/ && $min > $max) { WARN("USLEEP_RANGE", "usleep_range args reversed, use min then max; see Documentation/timers/timers-howto.rst\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 =~ /^.\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 k[mz]alloc with multiplies that could be kmalloc_array/kcalloc if ($perl_version_ok && defined $stat && $stat =~ /^\+\s*($Lval)\s*\=\s*(?:$balanced_parens)?\s*(k[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)\s*,/) { my $oldfunc = $3; my $a1 = $4; my $a2 = $10; my $newfunc = "kmalloc_array"; $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*(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_)?(?:kcalloc|kmalloc_array))\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'; } } # 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); } } # 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); } } # 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_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; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/const_structs.checkpatch������������������������������������������������0000664�0000000�0000000�00000000000�14737566223�0022775�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/docker/�����������������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0017322�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/docker/Dockerfile-in.cmake����������������������������������������������0000664�0000000�0000000�00000001076�14737566223�0023003�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# CEPH BASE IMAGE # CEPH VERSION: Hammer # CEPH VERSION DETAIL: 0.94.x FROM @DOCKER_DISTRO@:@DOCKER_DISTRO_VERSION@ MAINTAINER Daniel Gryniewicz "dang@redhat.com" #ENV ETCDCTL_VERSION v2.2.0 #ENV ETCDCTL_ARCH linux-amd64 #ENV CEPH_VERSION 10.0.3-2378.gd3db533 # Install prerequisites RUN dnf install -y tar redhat-lsb-core # Install deps RUN dnf install -y libcap libblkid libuuid dbus nfs-utils rpcbind libnfsidmap libattr #install ganesha ADD root/ / RUN mkdir -p @CMAKE_INSTALL_PREFIX@/var/log/ganesha ADD entrypoint.sh /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/docker/entrypoint.sh-in.cmake�������������������������������������������0000775�0000000�0000000�00000001324�14737566223�0023557�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/bash # # Entrypoint for the Ganesha docker container. If "shell" is given as the # argument, then a shell is run; otherwise, Ganseha is started. # These are the options for starting Ganesha. Set them in the environment. : ${GANESHA_LOGFILE:="@SYSSTATEDIR@/log/ganesha.log"} : ${GANESHA_CONFFILE:="@SYSCONFDIR@/ganesha/ganesha.conf"} : ${GANESHA_OPTIONS:="-N NIV_EVENT"} : ${GANESHA_EPOCH:=""} : ${GANESHA_LIBPATH:="@LIB_INSTALL_DIR@"} function rpc_init { rpcbind rpc.statd -L rpc.idmapd } if [ "$1" == "shell" ]; then /bin/bash else rpc_init LD_LIBRARY_PATH="${GANESHA_LIBPATH}" @CMAKE_INSTALL_PREFIX@/bin/ganesha.nfsd -F -L ${GANESHA_LOGFILE} -f ${GANESHA_CONFFILE} ${GANESHA_OPTIONS} ${GANESHA_EPOCH} fi ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganesha-top/������������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0020261�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganesha-top/CMakeLists.txt����������������������������������������������0000664�0000000�0000000�00000006554�14737566223�0023033�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright (c) SUSE, 2022 # Contributor: Vicente Cheng <vicente.cheng@suse.com> # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- if(USE_ADMIN_TOOLS) if(Python3_FOUND) set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in") set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py") set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/build/ganesha_top_timestamp") set(GANESHA_TOP_SRC ganesha-top.py) # Generate rules to copy command line scripts from src to build, # stripping .py along the way set(SCRIPTS) foreach(src_file ${GANESHA_TOP_SRC}) string(REPLACE ".py" "" script_file ${src_file}) add_custom_command( OUTPUT ${script_file} COMMAND mkdir -p build/lib COMMAND echo ${script_file} COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/${src_file}" ${script_file} DEPENDS ${src_file} ) list(APPEND SCRIPTS ${script_file}) endforeach() # Build up the string for the configure substitution in setup.py set(SCRIPTS_STRING) foreach(script_py ${GANESHA_TOP_SRC}) string(REPLACE ".py" "" script ${script_py}) if("${SCRIPTS_STRING}" STREQUAL "") set(SCRIPTS_STRING "'${script}'") else() set(SCRIPTS_STRING "${SCRIPTS_STRING}, '${script}'") endif() endforeach() configure_file(${SETUP_PY_IN} ${SETUP_PY}) if(USE_LEGACY_PYTHON_INSTALL) add_custom_command( OUTPUT ${OUTPUT} COMMAND ${Python3_EXECUTABLE} "${SETUP_PY}" build COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT} DEPENDS ${GANESHA_TOP_SRCS} ${SCRIPTS} ) else() add_custom_command( OUTPUT ${OUTPUT} COMMAND ${Python3_EXECUTABLE} -m build --wheel --no-isolation . COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT} DEPENDS ${GANESHA_TOP_SRCS} ${SCRIPTS} ) endif() add_custom_target(python_ganesha_top ALL DEPENDS ${OUTPUT}) if(USE_LEGACY_PYTHON_INSTALL) install( CODE "execute_process(WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${Python3_EXECUTABLE} ${SETUP_PY} install --skip-build --no-compile --prefix=\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX})" ) else() install( CODE "execute_process(WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${Python3_EXECUTABLE} -m installer --destdir \$ENV{DESTDIR} dist/ganesha-top-${GANESHA_MAJOR_VERSION}${GANESHA_MINOR_VERSION}-py3-none-any.whl)" ) endif() endif(Python3_FOUND) endif(USE_ADMIN_TOOLS) ����������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganesha-top/README.md���������������������������������������������������0000664�0000000�0000000�00000001536�14737566223�0021545�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Ganesha-Top - A tool for the information monitoring like top An idea that makes people easy to monitor information that included memory, CPU, MDCache, and Export/Client OP. (Now only support V4 Status) The whole screen could be split to three parts. - Header - ***Display the general information*** - Body - ***Display the goal content (like export/client information)*** - Footer - ***Display the simple help information and time*** # Header In this block we would show the build version/commit of ganesha. And the memory/CPU usage would be displayed here. In addition to the total ops (included V4.0/V4.1/V4.2) and MDCache status would also be displayed. # Body Show the full data which includes export status, client status, and NFSv4 op information. # Footer Show the useful information like `q` for quit, `h` for help and current time ������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganesha-top/ganesha-top.py����������������������������������������������0000664�0000000�0000000�00000047614�14737566223�0023055�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-3.0-or-later # # Copyright (C) SUSE, 2022 # Author: Vicente Cheng <vicente.cheng@suse.com> # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # import os import sys import curses import getopt import argparse import subprocess import psutil import dbus from datetime import datetime from Ganesha.ganesha_mgr_utils import AdminInterface from Ganesha.glib_dbus_stats import Export, Client GaneshaProcess = 'ganesha.nfsd' GaneshaService = 'org.ganesha.nfsd' DefaultInterval = 5 # seconds DEFAULT_CONTENT_POS_Y = 7 # default y position def enbale_all_stats(): try: subprocess.run(["ganesha_stats", "enable", "all"], stdout=subprocess.PIPE) except subprocess.CalledProcessError as e: sys.exit("Enable ALL stats Error {}".format(e)) def get_ganesha_pid(): try: return int(subprocess.run(["pidof", "-s", GaneshaProcess], stdout=subprocess.PIPE).stdout) except subprocess.CalledProcessError as e: sys.exit("NFS-Ganesha is not running, please check it again.\n {}" .format(e)) def setup_color(): curses.start_color() curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK) curses.init_pair(2, curses.COLOR_BLUE, curses.COLOR_BLACK) curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_BLACK) curses.init_pair(4, curses.COLOR_GREEN, curses.COLOR_BLACK) curses.init_pair(5, curses.COLOR_WHITE, curses.COLOR_BLACK) curses.init_pair(6, curses.COLOR_BLACK, curses.COLOR_MAGENTA) curses.init_pair(7, curses.COLOR_BLACK, curses.COLOR_GREEN) curses.init_pair(8, curses.COLOR_MAGENTA, curses.COLOR_BLACK) curses.init_pair(9, curses.COLOR_GREEN, curses.COLOR_BLACK) curses.init_pair(10, curses.COLOR_RED, curses.COLOR_BLACK) curses.init_pair(11, curses.COLOR_BLACK, curses.COLOR_WHITE) class SingleTon: _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance class ClientInterface(SingleTon): def __init__(self): self.dbus_clientstats_name = "org.ganesha.nfsd.clientstats" self.dbus_clientmgr_name = "org.ganesha.nfsd.clientmgr" self.client_interface = "/org/ganesha/nfsd/ClientMgr" self.dbus = dbus.SystemBus() self.mgr = self.dbus.get_object(GaneshaService, self.client_interface) def show_clients(self): dbus_op = self.mgr.get_dbus_method("ShowClients", self.dbus_clientmgr_name) return dbus_op def get_client_io_ops(self): dbus_op = self.mgr.get_dbus_method("GetClientIOops", self.dbus_clientstats_name) return dbus_op class ClientMgr(SingleTon): def __init__(self): self.interface = ClientInterface() self.clients = dict() self.client_io = dict() self.preload_client_info() @property def NumOfClients(self): return len(self.clients.keys()) def preload_client_info(self): stats_op = self.interface.show_clients() self.parse_client_stats(stats_op()) def parse_client_stats(self, stats): for client in stats[1]: clientaddr = client[0] self.clients[clientaddr] = Client(client) def get_total_client_ops(self): stats_op = self.interface.get_client_io_ops() for client_addr in self.clients.keys(): stats = stats_op(client_addr) if stats[1] != "OK": continue io_content = stats[3:] nfsver_cnt = 0 content = dict({'Read': 0, 'Write': 0, 'Others': 0}) # each protocol has 4 colume when is enable # io_content start from NFSv3, skip it. while nfsver_cnt < 4: if io_content[0]: if nfsver_cnt == 0: # mean v3 io_content = io_content[4:] continue else: content['Read'] = io_content[1][0] content['Write'] = io_content[2][0] content['Others'] = io_content[3][0] io_content = io_content[5:] else: io_content = io_content[1:] nfsver_cnt += 1 self.client_io[client_addr] = content return self.client_io class ExportInterface(SingleTon): def __init__(self): self.dbus_exportstats_name = "org.ganesha.nfsd.exportstats" self.dbus_exportmgr_name = "org.ganesha.nfsd.exportmgr" self.export_interface = "/org/ganesha/nfsd/ExportMgr" self.dbus = dbus.SystemBus() self.mgr = self.dbus.get_object(GaneshaService, self.export_interface) def show_exports(self): dbus_op = self.mgr.get_dbus_method("ShowExports", self.dbus_exportmgr_name) return dbus_op def get_global_ops(self): dbus_op = self.mgr.get_dbus_method("GetGlobalOPS", self.dbus_exportstats_name) return dbus_op def show_cache_inode(self): dbus_op = self.mgr.get_dbus_method("ShowMDCache", self.dbus_exportstats_name) return dbus_op def get_full_v4_stats(self): dbus_op = self.mgr.get_dbus_method("GetFULLV4Stats", self.dbus_exportstats_name) return dbus_op def get_total_ops(self): dbus_op = self.mgr.get_dbus_method("GetTotalOPS", self.dbus_exportstats_name) return dbus_op class ExportMgr(SingleTon): def __init__(self): self.interface = ExportInterface() self.exports = dict() self.export_stats = dict() self.global_ops = dict() self.cache_lru_info = dict() self.v4_detail_stats = dict() self.preload_export_info() self.preload_global_ops() self.preload_cache_lru() @property def NumOfExports(self): return len(self.exports.keys()) @property def GlobalV3OPS(self): return self.global_ops['v3'] @property def GlobalV40OPS(self): return self.global_ops['v4.0'] @property def GlobalV41OPS(self): return self.global_ops['v4.1'] @property def GlobalV42OPS(self): return self.global_ops['v4.2'] @property def FDUsage(self): return self.cache_lru_info['FDUsage'] @property def NumOfChunk(self): return self.cache_lru_info['chunks'] @property def NumOfOpenedFD(self): return self.cache_lru_info['openedFD'] @property def NumOfLRUEntry(self): return self.cache_lru_info['entries'] def preload_cache_lru(self): stats_op = self.interface.show_cache_inode() self.parse_cache_stats(stats_op()) def preload_export_info(self): stats_op = self.interface.show_exports() self.parse_export_stats(stats_op()) def preload_global_ops(self): stats_op = self.interface.get_global_ops() self.parse_global_ops(stats_op()) def parse_cache_stats(self, stats): self.cache_lru_info['FDUsage'] = stats[4][5] self.cache_lru_info['openedFD'] = stats[4][1] self.cache_lru_info['entries'] = stats[4][7] self.cache_lru_info['chunks'] = stats[4][9] def parse_export_stats(self, stats): for export in stats[1]: if export[0] == 0: # pseudo root should not be count continue exportid = export[0] self.exports[exportid] = Export(export) def parse_global_ops(self, stats): self.global_ops['v3'] = -1 self.global_ops['v4.0'] = -1 self.global_ops['v4.1'] = -1 self.global_ops['v4.2'] = -1 if stats[0]: self.global_ops['v3'] = stats[3][1] self.global_ops['v4.0'] = stats[3][3] self.global_ops['v4.1'] = stats[3][5] self.global_ops['v4.2'] = stats[3][7] def show_v4_full_stats(self): stats_op = self.interface.get_full_v4_stats() stats = stats_op() self.v4_detail_stats['Status'] = "" self.v4_detail_stats['Details'] = dict() if not stats[0]: self.v4_detail_stats['Status'] = "Unable to fetch NFSv4 stats." else: if stats[4] != "OK": self.v4_detail_stats['Status'] = \ "No stats available for display" else: self.v4_detail_stats['Status'] = "OK" cnt = 0 while cnt < len(stats[3]): content = dict() content['TotalOPs'] = stats[3][cnt][1] content['Error'] = stats[3][cnt][2] content['Max'] = "{:12.6f}".format(stats[3][cnt][5]) content['Min'] = "{:12.6f}".format(stats[3][cnt][4]) content['Avg'] = "{:12.6f}".format(stats[3][cnt][3]) self.v4_detail_stats['Details'][str(stats[3][cnt][0])] = \ content cnt += 1 return self.v4_detail_stats def get_total_export_stats(self): stats_op = self.interface.get_total_ops() for export_id in self.exports.keys(): if export_id == 0: continue self.export_stats[export_id] = stats_op(export_id) return self.export_stats class GaneshaPSInfo(SingleTon): def __init__(self): pid = get_ganesha_pid() self.ps = psutil.Process(pid) self.memory_info = self.ps.memory_full_info() @property def status(self): return "Running" if self.ps.is_running() else "Not Running" @property def rsize(self): return self.memory_info.rss/1024 @property def vsize(self): return self.memory_info.vms/1024 @property def swapsize(self): return self.memory_info.swap/1024 @property def cpu_usage(self): return self.ps.cpu_percent() # Default is KB def convert_memory_size(size): if size/1024 > 1024: # convert to GB return "{:5.2f} G".format(size/1024/1024) if size/1024 > 100: # convert to MB return "{:5.2f} M".format(size/1024) else: return "{} K".format(int(size)) def generate_version(): dbus_admin_name = "org.ganesha.nfsd.admin" admin_interface = "/org/ganesha/nfsd/admin" admin = AdminInterface(GaneshaService, admin_interface, dbus_admin_name) s, _, versions = admin.GetAll() if s: version = versions['VERSION_RELEASE'] git_hash = versions['VERSION_GIT_HEAD'] vers = "V{}({})".format(version, git_hash) return vers else: return "Unknown" def draw_header(stdscr, height, width): # Get header content ps_info = GaneshaPSInfo() exportmgr = ExportMgr() clientmgr = ClientMgr() version = generate_version() status = ps_info.status rsize = convert_memory_size(ps_info.rsize) vsize = convert_memory_size(ps_info.vsize) swapsize = convert_memory_size(ps_info.swapsize) cpu_usage = ps_info.cpu_usage num_of_exports = exportmgr.NumOfExports num_of_clients = clientmgr.NumOfClients global_v40_ops = exportmgr.GlobalV40OPS global_v41_ops = exportmgr.GlobalV41OPS global_v42_ops = exportmgr.GlobalV42OPS fd_usage = exportmgr.FDUsage num_of_opened_fds = exportmgr.NumOfOpenedFD num_of_lru_entry = exportmgr.NumOfLRUEntry num_of_chunk = exportmgr.NumOfChunk stdscr.attron(curses.color_pair(1)) stdscr.addstr(0, 0, "Version: {}".format(version)) stdscr.addstr(1, 0, "Status: {}".format(status)) stdscr.addstr(1, 20, "| RSIZE: {}".format(rsize)) stdscr.addstr(1, 40, "| VSIZE: {}".format(vsize)) stdscr.addstr(1, 60, "| SWAP: {}".format(swapsize)) stdscr.addstr(1, 80, "| CPU: {}%".format(cpu_usage)) stdscr.addstr(2, 0, "Exports: {}".format(num_of_exports)) stdscr.addstr(2, 20, "| Clients: {}".format(num_of_clients)) stdscr.addstr(2, 40, "| V4.0 OPs: {}".format(global_v40_ops)) stdscr.addstr(2, 60, "| V4.1 OPs: {}".format(global_v41_ops)) stdscr.addstr(2, 80, "| V4.2 OPs: {}".format(global_v42_ops)) stdscr.addstr(3, 0, "FD Status: {}".format(fd_usage)) stdscr.addstr(3, 40, "| Opened FD: {}".format(num_of_opened_fds)) stdscr.addstr(3, 60, "| LRU Entries: {}".format(num_of_lru_entry)) stdscr.addstr(3, 80, "| Dir Chunks: {}".format(num_of_chunk)) stdscr.attroff(curses.color_pair(1)) # generate divider divider = ("{}".format(" ")*width) stdscr.attron(curses.color_pair(11)) stdscr.addstr(5, 0, divider) stdscr.attroff(curses.color_pair(11)) def draw_footbar(stdscr, height, width, key): # content current_time = datetime.now() time_str = current_time.strftime("%Y-%m-%d %H:%M:%S") footbar_str = ("Press 'q' to exit | Press 'h' for help | {} | Last key: {}" .format(time_str, key)) # Rendering footbar stdscr.attron(curses.color_pair(11)) stdscr.addstr(height-1, 0, footbar_str) stdscr.addstr(height-1, len(footbar_str), " " * (width - len(footbar_str) - 1)) stdscr.attroff(curses.color_pair(11)) def draw_client_page(stdscr, screen_opt): stdscr.clear() height, width = stdscr.getmaxyx() draw_header(stdscr, height, width) draw_client_stats(stdscr) draw_footbar(stdscr, height, width, screen_opt) def draw_client_stats(stdscr): mgr = ClientMgr() client_stats = mgr.get_total_client_ops() pos_y = DEFAULT_CONTENT_POS_Y title = " ----- Client IO OPs (Only counted with NFSv4) ----- " stdscr.attron(curses.color_pair(5)) stdscr.attron(curses.A_BOLD) stdscr.addstr(pos_y, 0, title) stdscr.attroff(curses.color_pair(5)) stdscr.attroff(curses.A_BOLD) pos_y += 1 stdscr.attron(curses.color_pair(9)) stdscr.attron(curses.A_BOLD) for client_addr, stat in client_stats.items(): content = "" content = "Client Addr: {:>23}".format(client_addr) content += " | Read OPs: {:>12}".format(stat['Read']) content += " | Write OPs: {:>12}".format(stat['Write']) content += " | Others OPs: {:>12}".format(stat['Others']) stdscr.addstr(pos_y, 0, content) pos_y += 1 stdscr.attroff(curses.color_pair(9)) stdscr.attroff(curses.A_BOLD) def draw_export_page(stdscr, screen_opt): stdscr.clear() height, width = stdscr.getmaxyx() draw_header(stdscr, height, width) draw_export_stats(stdscr, width) draw_footbar(stdscr, height, width, screen_opt) def draw_export_stats(stdscr, width): mgr = ExportMgr() export_stats = mgr.get_total_export_stats() pos_y = DEFAULT_CONTENT_POS_Y title = " ----- Export OPs (Only counted with NFSv4) ----- " stdscr.attron(curses.color_pair(5)) stdscr.attron(curses.A_BOLD) stdscr.addstr(pos_y, 0, title) stdscr.attroff(curses.color_pair(5)) stdscr.attroff(curses.A_BOLD) pos_y += 1 stdscr.attron(curses.color_pair(9)) stdscr.attron(curses.A_BOLD) for exportid, stat in export_stats.items(): content = "" content = "EXPORTID: {:>10} ".format(exportid) content += "| NFSv4.0 OP: {:>10} ".format(stat[3][3]) content += "| NFSv4.1 OP: {:>10} ".format(stat[3][5]) content += "| NFSv4.2 OP: {:>10} ".format(stat[3][7]) stdscr.addstr(pos_y, 0, content) pos_y += 1 stdscr.attroff(curses.color_pair(9)) stdscr.attroff(curses.A_BOLD) def draw_help_page(stdscr, screen_opt): stdscr.clear() height, width = stdscr.getmaxyx() draw_header(stdscr, height, width) draw_help_content(stdscr) draw_footbar(stdscr, height, width, screen_opt) def draw_help_content(stdscr): content = "" content += "The following control keys are shown:\n" content += " 'c' - show all client simple info\n" content += " 'd' - show default page info (v4 full stats)\n" content += " 'e' - show all export simple info\n" content += " 'q' - exit\n" stdscr.attron(curses.color_pair(9)) stdscr.attron(curses.A_BOLD) stdscr.addstr(DEFAULT_CONTENT_POS_Y, 0, content) stdscr.attroff(curses.color_pair(9)) stdscr.attroff(curses.A_BOLD) def draw_v4_full_stats(stdscr): mgr = ExportMgr() v4_full_result = mgr.show_v4_full_stats() pos_y = DEFAULT_CONTENT_POS_Y result = "" if v4_full_result['Status'] != "OK": result += v4_full_result['Status'] else: title = " ----- NFSv4 Detailed statistics: ----- \n" stdscr.attron(curses.color_pair(5)) stdscr.attron(curses.A_BOLD) stdscr.addstr(pos_y, 0, title) stdscr.attroff(curses.color_pair(5)) stdscr.attroff(curses.A_BOLD) pos_y += 1 result += ("{:25} | {:^15} | {:^8} | {:^12} | {:^12} | {:^12}" .format("OP NAME", "Total OPs", "Errors", "Average", "Minimum", "Maxmum")) for op_name, content in v4_full_result['Details'].items(): result += "\n" result += "{:25} | ".format(op_name) result += "{:15} | ".format(content["TotalOPs"]) result += "{:8} | ".format(content["Error"]) result += "{:12} | ".format(content["Avg"]) result += "{:12} | ".format(content["Min"]) result += "{:12} | ".format(content["Max"]) stdscr.attron(curses.color_pair(9)) stdscr.attron(curses.A_BOLD) stdscr.addstr(pos_y, 0, result) stdscr.attroff(curses.color_pair(9)) stdscr.attroff(curses.A_BOLD) def draw_default_page(stdscr, screen_opt): stdscr.clear() height, width = stdscr.getmaxyx() draw_header(stdscr, height, width) draw_v4_full_stats(stdscr) draw_footbar(stdscr, height, width, screen_opt) def draw_menu(stdscr, interval): def opt_not_valid(opt): return opt != ord('q') and opt != ord('h') and opt != ord('e') and \ opt != ord('c') and opt != ord('d') screen_opt = 0 stdscr.clear() stdscr.refresh() # Start colors in curses setup_color() while (1): if screen_opt == ord('q'): break elif screen_opt == ord('h'): draw_help_page(stdscr, screen_opt) elif screen_opt == ord('c'): draw_client_page(stdscr, screen_opt) elif screen_opt == ord('d'): draw_default_page(stdscr, screen_opt) elif screen_opt == ord('e'): draw_export_page(stdscr, screen_opt) else: draw_default_page(stdscr, screen_opt) screen_opt_prev = screen_opt stdscr.refresh() stdscr.nodelay(1) stdscr.timeout(interval) # Wait for next input screen_opt = stdscr.getch() if screen_opt == -1 or opt_not_valid(screen_opt): screen_opt = screen_opt_prev def main(argv): interval = DefaultInterval parser = argparse.ArgumentParser() parser.add_argument("-i", "--interval", type=int, default=DefaultInterval, help="refresh interval in seconds (Default: 5)") args = parser.parse_args() interval = args.interval * 1000 # convert to seconds pid = get_ganesha_pid() enbale_all_stats() curses.wrapper(draw_menu, interval) if __name__ == "__main__": main(sys.argv[1:]) ��������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganesha-top/setup.py.in�������������������������������������������������0000664�0000000�0000000�00000000761�14737566223�0022404�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*-Python-*- from distutils.core import setup, Extension if __name__ == '__main__': setup(name='ganesha-top', version='${GANESHA_VERSION}', author = "Vicente Cheng", maintainer = "NFS-Ganesha Project", maintainer_email = "devel@lists.nfs-ganesha.org", description = "NFS Ganesha Administration tools", package_dir = { '': '${CMAKE_CURRENT_SOURCE_DIR}' }, scripts = [${SCRIPTS_STRING}]) ���������������nfs-ganesha-6.5/src/scripts/ganeshactl/�������������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0020164�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/CMakeLists.txt�����������������������������������������������0000664�0000000�0000000�00000013151�14737566223�0022725�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb <jlieb@panasas.com> # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # Building and packaging DBUS scripts for ganesha # Python3 was tested for in top level # PyQT based class modules set(GANESHA_BASE_SRCS Ganesha/__init__.py Ganesha/admin.py Ganesha/client_mgr.py Ganesha/QtUI/clients_table.py Ganesha/export_mgr.py Ganesha/QtUI/exports_table.py Ganesha/log_mgr.py Ganesha/QtUI/log_settings.py Ganesha/io_stats.py Ganesha/glib_dbus_stats.py Ganesha/ganesha_mgr_utils.py Ganesha/config_editor.py ) # Command line scripts set(SCRIPT_SRC fake_recall.py get_clientids.py grace_period.py ganesha_mgr.py ganesha_stats.py ganesha_conf.py ganesha_logrotate_mgr.py ) set(GUI_SCRIPT_SRC ganeshactl.py ganesha-admin.py manage_clients.py manage_logger.py manage_exports.py client_stats_9pOps.py export_stats_9pOps.py ) # Qt Designer files that get compiled into .py scripts set(UI_SRC Ganesha/QtUI/ui_main_window.ui Ganesha/QtUI/ui_log_dialog.ui ) if(Python3_FOUND) set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in") set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py") set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/build/ganeshactl_timestamp") # Generate rules for compiling .ui -> .py set(GANESHA_SRCS) set(UI_PY_FILES) if(USE_GUI_ADMIN_TOOLS) set(GANESHA_SRCS ${GANESHA_BASE_SRCS}) foreach(ui_file ${UI_SRC}) string(REPLACE ".ui" ".py" py_file ${ui_file}) add_custom_command( OUTPUT ${py_file} COMMAND mkdir -p build/lib/Ganesha/QtUI COMMAND ${PYUIC} "${CMAKE_CURRENT_SOURCE_DIR}/${ui_file}" -o "build/lib/${py_file}" DEPENDS ${ui_file} ) set(UI_PY_FILES ${UI_PY_FILES} "${py_file}") list(APPEND GANESHA_SRCS ${py_file}) endforeach() endif() # Generate rules to copy command line scripts from src to build, # stripping .py along the way set(SCRIPTS) foreach(src_file ${SCRIPT_SRC}) string(REPLACE ".py" "" script_file ${src_file}) add_custom_command( OUTPUT ${script_file} COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/${src_file}" ${script_file} DEPENDS ${src_file} ) list(APPEND SCRIPTS ${script_file}) endforeach() if(USE_GUI_ADMIN_TOOLS) foreach(src_file ${GUI_SCRIPT_SRC}) string(REPLACE ".py" "" script_file ${src_file}) add_custom_command( OUTPUT ${script_file} COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/${src_file}" ${script_file} DEPENDS ${src_file} ) list(APPEND SCRIPTS ${script_file}) endforeach() endif() # Build up the string for the configure substitution in setup.py set(SCRIPTS_STRING) foreach(script_py ${SCRIPT_SRC}) string(REPLACE ".py" "" script ${script_py}) if("${SCRIPTS_STRING}" STREQUAL "") set(SCRIPTS_STRING "'${script}'") else() set(SCRIPTS_STRING "${SCRIPTS_STRING}, '${script}'") endif() endforeach() if(USE_GUI_ADMIN_TOOLS) foreach(script_py ${GUI_SCRIPT_SRC}) string(REPLACE ".py" "" script ${script_py}) if("${SCRIPTS_STRING}" STREQUAL "") set(SCRIPTS_STRING "'${script}'") else() set(SCRIPTS_STRING "${SCRIPTS_STRING}, '${script}'") endif() endforeach() endif() configure_file(${SETUP_PY_IN} ${SETUP_PY}) if(USE_LEGACY_PYTHON_INSTALL) add_custom_command( OUTPUT ${OUTPUT} COMMAND ${Python3_EXECUTABLE} "${SETUP_PY}" build COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT} DEPENDS ${GANESHA_SRCS} ${UI_PY_FILES} ${SCRIPTS} ) else() add_custom_command( OUTPUT ${OUTPUT} COMMAND ${Python3_EXECUTABLE} -m build --wheel --no-isolation . COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT} DEPENDS ${GANESHA_SRCS} ${UI_PY_FILES} ${SCRIPTS} ) endif() add_custom_target(python_ganeshactl ALL DEPENDS ${OUTPUT}) if(USE_LEGACY_PYTHON_INSTALL) install( CODE "execute_process(WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${Python3_EXECUTABLE} ${SETUP_PY} install --skip-build --no-compile --prefix=\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX})" ) else() install( CODE "execute_process(WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${Python3_EXECUTABLE} -m installer --destdir \$ENV{DESTDIR} dist/ganeshactl-${GANESHA_MAJOR_VERSION}${GANESHA_MINOR_VERSION}-py3-none-any.whl)" ) endif() endif(Python3_FOUND) # Man page set(man8_file ganesha_conf.man) set(man8_page ganesha_conf.8) add_custom_command( OUTPUT ${man8_page} COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/${man8_file}" ${man8_page} DEPENDS ${man8_file} ) add_custom_target(man ALL DEPENDS ${man8_page}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${man8_page} DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man8/) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/Ganesha/�����������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0021532�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/Ganesha/QtUI/������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0022354�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/Ganesha/QtUI/__init__.py�������������������������������������0000664�0000000�0000000�00000001640�14737566223�0024466�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # # Copyright (C) 2014 Panasas # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Jim Lieb <jlieb@panasas.com> # # Ganesha Qt GUI classes __all__ = ["exports_table", "clients_table", "ui_log_dialog", "log_settings", "ui_main_window"] ������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/Ganesha/QtUI/clients_table.py��������������������������������0000664�0000000�0000000�00000011353�14737566223�0025541�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # # clients_table.py - ClientTableModel model class. # # Copyright (C) 2014 Panasas Inc. # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Jim Lieb <jlieb@panasas.com> #-*- coding: utf-8 -*- import sys, time from PyQt5.QtCore import * from PyQt5 import QtDBus, QtGui from PyQt5.QtGui import QColor class ClientTableModel(QAbstractTableModel): ''' Clients Table Model to match its table view ''' def __init__(self, clientmgr, parent=None): super(ClientTableModel, self).__init__(parent) self.header = ['Client IP', 'NFSv3', 'MNT', 'NLMv4', 'RQUOTA', 'NFSv4.0', 'NFSv4.1', '9P', 'Last Stats Update'] self.clientmgr = clientmgr self.clientmgr.show_clients.connect(self.FetchClients_done) self.clients = [] self.ts = (0, 0) # Fetch current clients def FetchClients(self): self.clientmgr.ShowClients() def FetchClients_done(self, ts, clients): if len(self.clients) != len(clients): if len(self.clients) > 0: self.removeRows(0, len(self.clients)) self.insertRows(0, len(clients)) for i in xrange(len(clients)): exp = clients[i] for j in xrange(len(exp)): if isinstance(exp[j], bool): if exp[j]: val = "yes" else: val = "no" elif isinstance(exp[j], tuple): val = time.ctime(exp[j][0]) else: val = str(exp[j]) self.setData(self.createIndex(i, j), val) # model abstract methods def setData(self, index, value, role=Qt.EditRole): if role == Qt.EditRole: row = index.row() col = index.column() t_ = self.clients[row] if row > self.rowCount() or col > self.columnCount(): return False t_[col] = value self.emit(SIGNAL('dataChanged'), index, index) return True return False def insertRow(self, row, parent=QModelIndex()): self.insertRows(self, row, 1, parent) def insertRows(self, row, count, parent=QModelIndex()): self.beginInsertRows(parent, row, row + count - 1) for i in xrange(count): self.clients.insert(row, ['',]*self.columnCount()) self.endInsertRows() return True def removeRow(self, row, parent=QModelIndex()): self.removeRows(self, row, 1, parent) def removeRows(self, row, count, parent=QModelIndex()): self.beginRemoveRows(parent, row, row + count -1) for i in reversed(xrange(count)): self.clients.pop(row + i) self.endRemoveRows() def rowCount(self, parent=QModelIndex()): return len(self.clients) def columnCount(self, parent=QModelIndex()): return len(self.header) def headerData(self, col, orientation, role): if orientation == Qt.Horizontal and role == Qt.DisplayRole: return QVariant(self.header[col]) return QVariant() def flags(self, index): return Qt.NoItemFlags def data(self, index, role): if not index.isValid(): return QVariant() elif role == Qt.DisplayRole: return QVariant(self.clients[index.row()][index.column()]) elif role == Qt.TextAlignmentRole: align = Qt.AlignVCenter if index.column() == 0: align = align + Qt.AlignRight elif index.column() == 9: align = align + Qt.AlignLeft else: align = align + Qt.AlignCenter return QVariant(align) elif role == Qt.BackgroundRole: if index.row() % 2 == 0: return QVariant(QColor(Qt.gray)) else: return QVariant(QColor(Qt.lightGray)) elif role == Qt.ForegroundRole: return QVariant(QColor(Qt.black)) return QVariant() �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/Ganesha/QtUI/exports_table.py��������������������������������0000664�0000000�0000000�00000011441�14737566223�0025602�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # # exports_table.py - ExportTableModel class. # # Copyright (C) 2014 Panasas Inc. # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Jim Lieb <jlieb@panasas.com> #-*- coding: utf-8 -*- import sys, time from PyQt5.QtCore import * from PyQt5 import QtDBus, QtGui from PyQt5.QtGui import QColor class ExportTableModel(QAbstractTableModel): ''' Exports Table Model to match its table view ''' def __init__(self, exportmgr, parent=None): super(ExportTableModel, self).__init__(parent) self.header = ['Export ID', 'Export Path', 'NFSv3', 'MNT', 'NLMv4', 'RQUOTA', 'NFSv4.0', 'NFSv4.1', '9P', 'Last Stats Update'] self.exportmgr = exportmgr self.exportmgr.show_exports.connect(self.FetchExports_done) self.exports = [] self.ts = (0, 0) # Fetch current exports def FetchExports(self): self.exportmgr.ShowExports() def FetchExports_done(self, ts, exports): if len(self.exports) != len(exports): if len(self.exports) > 0: self.removeRows(0, len(self.exports)) self.insertRows(0, len(exports)) for i in xrange(len(exports)): exp = exports[i] for j in xrange(len(exp)): if isinstance(exp[j], bool): if exp[j]: val = "yes" else: val = "no" elif isinstance(exp[j], tuple): val = time.ctime(exp[j][0]) else: val = str(exp[j]) self.setData(self.createIndex(i, j), val) # model abstract methods def setData(self, index, value, role=Qt.EditRole): if role == Qt.EditRole: row = index.row() col = index.column() t_ = self.exports[row] if row > self.rowCount() or col > self.columnCount(): return False t_[col] = value self.emit(SIGNAL('dataChanged'), index, index) return True return False def insertRow(self, row, parent=QModelIndex()): self.insertRows(self, row, 1, parent) def insertRows(self, row, count, parent=QModelIndex()): self.beginInsertRows(parent, row, row + count - 1) for i in xrange(count): self.exports.insert(row, ['',]*self.columnCount()) self.endInsertRows() return True def removeRow(self, row, parent=QModelIndex()): self.removeRows(self, row, 1, parent) def removeRows(self, row, count, parent=QModelIndex()): self.beginRemoveRows(parent, row, row + count -1) for i in reversed(xrange(count)): self.exports.pop(row + i) self.endRemoveRows() def rowCount(self, parent=QModelIndex()): return len(self.exports) def columnCount(self, parent=QModelIndex()): return len(self.header) def headerData(self, col, orientation, role): if orientation == Qt.Horizontal and role == Qt.DisplayRole: return QVariant(self.header[col]) return QVariant() def flags(self, index): return Qt.NoItemFlags def data(self, index, role): if not index.isValid(): return QVariant() elif role == Qt.DisplayRole: return QVariant(self.exports[index.row()][index.column()]) elif role == Qt.TextAlignmentRole: align = Qt.AlignVCenter if index.column() == 0: align = align + Qt.AlignRight elif index.column() == 1 or index.column() == 9: align = align + Qt.AlignLeft else: align = align + Qt.AlignCenter return QVariant(align) elif role == Qt.BackgroundRole: if index.row() % 2 == 0: return QVariant(QColor(Qt.gray)) else: return QVariant(QColor(Qt.lightGray)) elif role == Qt.ForegroundRole: return QVariant(QColor(Qt.black)) return QVariant() �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/Ganesha/QtUI/log_settings.py���������������������������������0000664�0000000�0000000�00000016234�14737566223�0025435�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # # log_settings.py - DebugLevelDelegate class. # # Copyright (C) 2014 Panasas Inc. # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Jim Lieb <jlieb@panasas.com> #-*- coding: utf-8 -*- from __future__ import print_function from PyQt5.QtCore import * from PyQt5 import QtGui from Ganesha.QtUI.ui_log_dialog import Ui_LogSettings class DebugLevelDelegate(QtGui.QItemDelegate): ''' Log level combo box editor ''' # Copied from log_levels_t in src/include/log.h:69 loglevels = ['NIV_NULL', 'NIV_FATAL', 'NIV_MAJ', 'NIV_CRIT', 'NIV_WARN', 'NIV_EVENT', 'NIV_INFO', 'NIV_DEBUG', 'NIV_MID_DEBUG', 'NIV_FULL_DEBUG'] def __init__(self, parent=None): super(DebugLevelDelegate, self).__init__(parent) def createEditor(self, parent, option, index): editor = QtGui.QComboBox(parent) editor.addItems(self.loglevels) editor.setCurrentIndex(0) editor.installEventFilter(self) return editor def setEditorData(self, editor, index): value = index.data(Qt.DisplayRole).toString() combo_index = editor.findText(value) editor.setCurrentIndex(combo_index) def setModelData(self, editor, model, index): text = editor.currentText() model.setData(index, text, Qt.EditRole) def updateEditorGeometry(self, editor, option, index): editor.setGeometry(option.rect) class LogSettingsModel(QAbstractTableModel): ''' Table of log settings as an editable form. Radio buttons are used for setting levels ''' def __init__(self, logmanager, parent=None): super(LogSettingsModel, self).__init__(parent) self.header = ['Log Component', 'Logging Level'] self.logmanager = logmanager self.logmanager.show_components.connect(self.getComponents_done) self.log_components = [] def getComponents(self): self.logmanager.GetAll() def getComponents_done(self, comp_dict): if len(self.log_components) != len(comp_dict): if len(self.log_components) > 0: self.removeRows(0, len(self.log_components)) self.insertRows(0, len(comp_dict)) comps = comp_dict.keys() comps.sort() # Populate the table in sorted order by hand. # in order to avoid the signal in setData which would # go back to ganesha and set what we just got... for i in xrange(len(comps)): self.log_components[i] = [comps[i], comp_dict[comps[i]]] def updateSetting(self, ULIndex, LRIndex): comp = self.log_components[ULIndex.row()][0] level = self.log_components[ULIndex.row()][1] self.logmanager.Set(comp, level) # Refresh the table because things like "All" whack everybody... self.logmanager.GetAll() # model abstract methods def setData(self, index, value, role=Qt.EditRole): if role == Qt.EditRole: row = index.row() col = index.column() comp = self.log_components[row] comp[col] = value self.dataChanged.emit(index, index) return True else: return False def insertRow(self, row, parent=QModelIndex()): self.insertRows(self, row, 1, parent) def insertRows(self, row, count, parent=QModelIndex()): self.beginInsertRows(parent, row, row + count -1) for row in xrange(count): self.log_components.insert(row, ['',]*self.columnCount()) self.endInsertRows() return True def removeRow(self, row, parent=QModelIndex()): self.removeRows(self, row, 1, parent) def removeRows(self, row, count, parent=QModelIndex()): self.beginRemoveRows(parent, row, count + count -1) for i in reversed(xrange(count)): self.log_comp_levels.pop(row + i) self.endRemoveRows() def rowCount(self, parent=QModelIndex()): return len(self.log_components) def columnCount(self, parent=QModelIndex()): return len(self.header) def headerData(self, section, orientation, role): if role == Qt.DisplayRole: if orientation == Qt.Horizontal: return QVariant(self.header[section]) else: return QVariant() def flags(self, index): if not index.isValid(): return Qt.ItemIsEnabled if index.column() == 1: return Qt.ItemFlags(QAbstractTableModel.flags(self, index) | Qt.ItemIsEditable) else: return Qt.ItemFlags(QAbstractTableModel.flags(self, index)) def data(self, index, role): if not index.isValid(): return QVariant() elif role == Qt.DisplayRole: if index.column() == 0: comp = self.log_components[index.row()][0] return QVariant(comp) else: return QVariant(self.log_components[index.row()][1]) elif role == Qt.EditRole: if index.column() == 0: comp = self.log_components[index.row()][0] print("edit partitioned comp", comp) return QVariant(comp) else: return QVariant(self.log_components[index.row()][1]) elif role == Qt.TextAlignmentRole: align = Qt.AlignCenter if index.column() == 0: align = Qt.AlignLeft return QVariant(align) class LogSetDialog(QtGui.QDialog): ''' Manage popup of log setting dialog ''' def __init__(self, log_mgr, parent=None): super(LogSetDialog, self).__init__(parent) self.log_ui = Ui_LogSettings() self.log_ui.setupUi(self) self.log_mgr = log_mgr self.log_setting_model = LogSettingsModel(self.log_mgr) self.level_edit_delegate = DebugLevelDelegate() self.log_ui.log_levels.setModel(self.log_setting_model) self.log_ui.log_levels.resizeColumnsToContents() self.log_ui.log_levels.verticalHeader().setVisible(False) self.log_ui.log_levels.setItemDelegateForColumn(1, self.level_edit_delegate) self.log_ui.log_done.clicked.connect(self.close_logsetting_dialog) self.log_setting_model.getComponents() self.log_setting_model.dataChanged.connect(self.log_setting_model.updateSetting) def show_logsetting_dialog(self): self.show() def close_logsetting_dialog(self): self.hide() ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/Ganesha/QtUI/ui_log_dialog.ui��������������������������������0000664�0000000�0000000�00000002147�14737566223�0025514�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>LogSettings</class> <widget class="QDialog" name="LogSettings"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>400</width> <height>300</height> </rect> </property> <property name="windowTitle"> <string>Log Settings</string> </property> <widget class="QWidget" name="verticalLayoutWidget"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>391</width> <height>241</height> </rect> </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QTableView" name="log_levels"> <property name="alternatingRowColors"> <bool>true</bool> </property> </widget> </item> </layout> </widget> <widget class="QPushButton" name="log_done"> <property name="geometry"> <rect> <x>280</x> <y>260</y> <width>100</width> <height>24</height> </rect> </property> <property name="text"> <string>Done</string> </property> </widget> </widget> <resources/> <connections/> </ui> �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/Ganesha/QtUI/ui_main_window.ui�������������������������������0000664�0000000�0000000�00000013566�14737566223�0025736�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>MainWindow</class> <widget class="QMainWindow" name="MainWindow"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>800</width> <height>600</height> </rect> </property> <property name="windowTitle"> <string>NFS Ganesha</string> </property> <widget class="QWidget" name="centralwidget"> <layout class="QGridLayout" name="gridLayout"> <item row="0" column="0"> <widget class="QTabWidget" name="tabWidget"> <property name="currentIndex"> <number>0</number> </property> <widget class="QWidget" name="exports_tab"> <attribute name="title"> <string>Exports</string> </attribute> <layout class="QGridLayout" name="gridLayout_2"> <item row="0" column="0"> <widget class="QScrollArea" name="scrollArea"> <property name="widgetResizable"> <bool>true</bool> </property> <widget class="QWidget" name="scrollAreaWidgetContents"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>770</width> <height>507</height> </rect> </property> <layout class="QGridLayout" name="gridLayout_5"> <item row="0" column="0"> <widget class="QTableView" name="exports"/> </item> </layout> </widget> </widget> </item> </layout> </widget> <widget class="QWidget" name="clients_tab"> <attribute name="title"> <string>Clients</string> </attribute> <layout class="QGridLayout" name="gridLayout_3"> <item row="0" column="0"> <widget class="QScrollArea" name="scrollArea_2"> <property name="widgetResizable"> <bool>true</bool> </property> <widget class="QWidget" name="scrollAreaWidgetContents_2"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>770</width> <height>507</height> </rect> </property> <layout class="QGridLayout" name="gridLayout_4"> <item row="0" column="0"> <widget class="QTableView" name="clients"/> </item> </layout> </widget> </widget> </item> </layout> </widget> </widget> </item> </layout> </widget> <widget class="QMenuBar" name="menubar"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>800</width> <height>21</height> </rect> </property> <widget class="QMenu" name="menuFile"> <property name="title"> <string>File</string> </property> <addaction name="actionDBus_connect"/> <addaction name="actionQuit"/> </widget> <widget class="QMenu" name="menuManager"> <property name="title"> <string>Manage</string> </property> <widget class="QMenu" name="menuClients"> <property name="title"> <string>Clients</string> </property> <addaction name="actionAdd_Client"/> <addaction name="actionRemove_Client"/> </widget> <widget class="QMenu" name="menuAdmin"> <property name="title"> <string>Admin</string> </property> <addaction name="actionReset_Grace"/> <addaction name="actionShutdown"/> <addaction name="actionReload"/> </widget> <addaction name="actionLog_Settings"/> <addaction name="menuClients"/> <addaction name="actionExports"/> <addaction name="menuAdmin"/> </widget> <widget class="QMenu" name="menuView"> <property name="title"> <string>View</string> </property> <addaction name="actionStatistics"/> <addaction name="actionViewExports"/> <addaction name="actionViewClients"/> </widget> <widget class="QMenu" name="menuHelp"> <property name="title"> <string>Help</string> </property> <addaction name="actionAbout"/> </widget> <addaction name="menuFile"/> <addaction name="menuManager"/> <addaction name="menuView"/> <addaction name="menuHelp"/> </widget> <widget class="QStatusBar" name="statusbar"/> <action name="actionDBus_connect"> <property name="text"> <string>Connect to Ganesha</string> </property> </action> <action name="actionQuit"> <property name="text"> <string>Quit</string> </property> </action> <action name="actionExports"> <property name="text"> <string>Exports</string> </property> </action> <action name="actionStatistics"> <property name="text"> <string>Statistics</string> </property> </action> <action name="actionLog_Levels"> <property name="text"> <string>Log Levels</string> </property> </action> <action name="actionViewExports"> <property name="text"> <string>Exports</string> </property> </action> <action name="actionAbout"> <property name="text"> <string>About</string> </property> </action> <action name="actionReset_Grace"> <property name="text"> <string>Reset Grace Period</string> </property> </action> <action name="actionShutdown"> <property name="text"> <string>Shutdown</string> </property> </action> <action name="actionReload"> <property name="text"> <string>Reload</string> </property> </action> <action name="actionViewClients"> <property name="text"> <string>Clients</string> </property> </action> <action name="actionAdd_Client"> <property name="text"> <string>Add Client</string> </property> </action> <action name="actionRemove_Client"> <property name="text"> <string>Remove Client</string> </property> </action> <action name="actionLog_Settings"> <property name="text"> <string>Log Settings</string> </property> </action> </widget> <resources/> <connections/> </ui> ������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/Ganesha/__init__.py������������������������������������������0000664�0000000�0000000�00000001613�14737566223�0023644�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # # Copyright (C) 2013 Panasas # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Jim Lieb <jlieb@panasas.com> # # Ganesha management classes __all__ = ["admin", "io_stats", "export_mgr", "client_mgr", "log_mgr"] ���������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/Ganesha/admin.py���������������������������������������������0000664�0000000�0000000�00000004471�14737566223�0023202�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # # admin.py - AdminInterface DBUS object class. # # Copyright (C) 2014 Panasas Inc. # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Jim Lieb <jlieb@panasas.com> #-*- coding: utf-8 -*- from PyQt5 import QtDBus class AdminInterface(QtDBus.QDBusAbstractInterface): ''' org.ganesha.nfsd.admin interface ''' def __init__(self, service, path, connection, show_status, parent=None): super(AdminInterface, self).__init__(service, path, 'org.ganesha.nfsd.admin', connection, parent) self.show_status = show_status def grace(self, ipaddr): _async = self.asyncCall("grace", ipaddr) status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.admin_done) def reload(self): _async = self.asyncCall("reload") status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.admin_done) def shutdown(self): _async = self.asyncCall("shutdown") status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.admin_done) # catch the reply and forward it to the UI def admin_done(self, call): reply = QtDBus.QDBusPendingReply(call) if reply.isError(): self.show_status.emit(False, "DBUS error:" + str(reply.error().message())) else: status = reply.argumentAt(0).toPyObject() msg = reply.argumentAt(1).toPyObject() self.show_status.emit(status, msg) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/Ganesha/client_mgr.py����������������������������������������0000664�0000000�0000000�00000012575�14737566223�0024241�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # # client_mgr.py - ClientMgr DBus object class. # # Copyright (C) 2014 Panasas Inc. # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Jim Lieb <jlieb@panasas.com> #-*- coding: utf-8 -*- from PyQt5 import QtCore, QtDBus from collections import namedtuple Client = namedtuple('Client', ['ClientIP', 'HasNFSv3', 'HasMNT', 'HasNLM4', 'HasRQUOTA', 'HasNFSv40', 'HasNFSv41', 'HasNFSv42', 'Has9P', 'LastTime']) class ClientMgr(QtDBus.QDBusAbstractInterface): ''' org.ganesha.nfsd.clientmgr ''' show_clients = QtCore.pyqtSignal(tuple, list) def __init__(self, service, path, connection, show_status, parent=None): super(ClientMgr, self).__init__(service, path, 'org.ganesha.nfsd.clientmgr', connection, parent) self.show_status = show_status def AddClient(self, ipaddr): _async = self.asyncCall("AddClient", ipaddr) status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.clientmgr_done) def RemoveClient(self, ipaddr): _async = self.asyncCall("RemoveClient", ipaddr) status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.clientmgr_done) def ShowClients(self): _async = self.asyncCall("ShowClients") status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.clientshow_done) # catch the reply and forward it to the UI def clientmgr_done(self, call): reply = QtDBus.QDBusPendingReply(call) if reply.isError(): self.show_status.emit(False, "DBUS error:" + str(reply.error().message())) else: status = reply.argumentAt(0).toPyObject() msg = reply.argumentAt(1).toPyObject() self.show_status.emit(status, msg) def clientshow_done(self, call): reply = QtDBus.QDBusPendingReply(call) if reply.isError(): self.show_status.emit(False, "DBUS error:" + str(reply.error().message())) else: ts_ = (reply.argumentAt(0).toPyObject()[0].toULongLong()[0], reply.argumentAt(0).toPyObject()[1].toULongLong()[0]) interval_nsecs = ts_[0] * 1000000000 + ts_[1] clients = [] for client in reply.argumentAt(1).toPyObject(): cl_ = client.toPyObject() lasttime = cl_[10].toPyObject() clt = Client(ClientIP=str(cl_[0].toString()), HasNFSv3=cl_[1].toBool(), HasMNT=cl_[2].toBool(), HasNLM4=cl_[3].toBool(), HasRQUOTA=cl_[4].toBool(), HasNFSv40=cl_[5].toBool(), HasNFSv41=cl_[6].toBool(), HasNFSv42=cl_[7].toBool(), Has9P=cl_[8].toBool(), LastTime=(lasttime[0].toPyObject(), lasttime[1].toPyObject())) clients.append(clt) self.show_clients.emit(ts_, clients) class ClientStats(QtDBus.QDBusAbstractInterface): ''' org.ganesha.nfsd.clientstats ''' def __init__(self, service, path, connection, status_handler, parent=None): super(ClientStats, self).__init__(service, path, 'org.ganesha.nfsd.clientstats', connection, parent) self.status_handler = status_handler def GetNFSv3IO(self, ipaddr): _async = self.asyncCall("GetNFSv3IO", ipaddr) status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.io_done) def GetNFSv40IO(self, ipaddr): _async = self.asyncCall("GetNFSv40IO", ipaddr) status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.io_done) def GetNFSv41IO(self, ipaddr): _async = self.asyncCall("GetNFSv41IO", ipaddr) status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.io_done) def GetNFSv41Layouts(self, ipaddr): _async = self.asyncCall("GetNFSv41Layouts", ipaddr) status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.layout_done) def io_done(self, call): pass def layout_done(self, call): pass �����������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/Ganesha/config_editor.py�������������������������������������0000664�0000000�0000000�00000027711�14737566223�0024727�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-3.0-or-later # # Copyright (C) 2017 IBM # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Malahal Naineni <malahal@us.ibm.com> import re, sys import pyparsing as pp import logging, pprint LBRACE = pp.Literal("{").suppress() RBRACE = pp.Literal("}").suppress() SEMICOLON = pp.Literal(";").suppress() EQUAL = pp.Literal("=").suppress() KEY = pp.Word(pp.alphas, pp.alphanums+"_") VALUE = pp.CharsNotIn(';') # doesn't skip whitespace! BLOCKNAME = pp.Word(pp.alphas, pp.alphanums+"_") KEYPAIR = KEY + EQUAL + VALUE + SEMICOLON # block is recursively defined. It starts with name, left brace, then a # list of "key value" pairs followed by a list of blocks, and finally # ends with a right brace. We assume that all "key value" pairs ,if any, # precede any sub-blocks. Ganesha daemon itself allows "key value" pairs # after sub-blocks or in between sub-blocks, but we don't allow for # simplification. # # We construct a 3 element list in python for every block! The first # element is the name of block itself, the second element is the list of # "key value" pairs, and the last element is the list of sub-block. The # first element must be the name of the block and the remaining two # elements could be empty lists! This recursive list is usually named as # r3. # block definition for pyparsing. ppblock = pp.Forward() KEYPAIR_GROUP = pp.Group(pp.ZeroOrMore(pp.Group(KEYPAIR))) SUBS_GROUP = pp.Group(pp.ZeroOrMore(pp.Group(ppblock))) ppblock << BLOCKNAME + LBRACE + KEYPAIR_GROUP + SUBS_GROUP + RBRACE class BLOCK(object): def __init__(self, blocknames): self.blocknames = blocknames def set_keys(self, s, opairs): validate_blocknames(self.blocknames) validate_opt_pairs(opairs) match = ppblock.parseWithTabs().scanString(s) block_found = False for ppr, start, end in match: if block_match(self.blocknames, ppr[0], ppr[1]): block_found = True break if block_found: begin_part = s[:start] end_part = s[end:] r3_ = ppr.asList() logging.debug("%s", pprint.pformat(r3_)) self.set_process(r3_, self.blocknames, opairs) text = r3_to_text(r3_, 0) logging.debug("%s", pprint.pformat(text)) assert text[-1] == "\n" if end_part[0] == "\n": text = text[:-1] # remove the last new line else: begin_part = s end_part = "" r3_ = make_r3(self.blocknames) self.set_process(r3_, self.blocknames, opairs) text = r3_to_text(r3_, 0) return begin_part + text + end_part def get_keys(self, s, opair): validate_blocknames(self.blocknames) match = ppblock.parseWithTabs().scanString(s) block_found = False text = "" for ppr, start, end in match: if block_match(self.blocknames, ppr[0], ppr[1]): match_text = dict(ppr[1].asList()) if len(opair) == 0: for key in match_text: text = text + "{:<20}".format(key) + "\t\t" + match_text[key] + "\n" block_found = True break; else: if match_text.has_key(opair[0]): text = match_text[opair[0]] block_found = True break; if False == block_found: text = self.blocknames[0] + " not configured" return text def del_keys(self, s, okeys): validate_blocknames(self.blocknames) validate_opt_keys(okeys) match = ppblock.parseWithTabs().scanString(s) block_found = False for ppr, start, end in match: if block_match(self.blocknames, ppr[0], ppr[1]): block_found = True break if block_found: begin_part = s[:start] end_part = s[end:] r3_ = ppr.asList() logging.debug("%s", pprint.pformat(r3_)) self.del_process(r3_, self.blocknames, okeys) text = r3_to_text(r3_, 0) logging.debug("%s", pprint.pformat(text)) # if we remove this entire block, remove the last new line # character associated with this block. # # @todo: should we remove other white space also? if end_part[0] == "\n": end_part = end_part[1:] else: logging.debug("block not found") sys.exit("block not found") return begin_part + text + end_part def set_process(self, r3_, blocknames, opairs): logging.debug("names: %s, r3: %s", pprint.pformat(blocknames), pprint.pformat(r3_)) name, pairs, subs = r3_[0], r3_[1], r3_[2] assert block_match(blocknames, name, pairs) # If last block, add given key value opairs subnames = next_subnames(blocknames) if not subnames: for key, value in opairs: key_found = False for idx, pair in enumerate(pairs): if key.lower() == pair[0].lower(): key_found = True pairs[idx] = [key, value] if not key_found: pairs.append([key, value]) return block_found = False for sub in subs: name2, pairs2, subs2 = sub[0], sub[1], sub[2] if block_match(subnames, name2, pairs2): block_found = True break if block_found: self.set_process(sub, subnames, opairs) else: new_r3 = make_r3(subnames) subs.append(new_r3) self.set_process(new_r3, subnames, opairs) def del_process(self, r3_, blocknames, okeys): logging.debug("names: %s, r3: %s", pprint.pformat(blocknames), pprint.pformat(r3_)) name, pairs, subs = r3_[0], r3_[1], r3_[2] assert block_match(blocknames, name, pairs) # If last block, delete given okeys subnames = next_subnames(blocknames) if not subnames: for key in okeys: key_found = False for pair in pairs[:]: if key.lower() == pair[0].lower(): key_found = True pairs.remove(pair) if not key_found: # @todo: exception to report sys.exit("key to delete is not found") # export and client blocks can't exist without some # key pairs identifying them. So remove the whole # block. @todo: shall we do this for regular blocks # also? if not pairs and (blocknames[0].lower() == "export" or blocknames[0].lower() == "client"): r3_[:] = [] if not okeys: # remove the whole block r3_[:] = [] return block_found = False for sub in subs: name, keypairs, subs2 = sub[0], sub[1], sub[2] if block_match(subnames, name, keypairs): block_found = True break if block_found: self.del_process(sub, subnames, okeys) else: logging.debug("block not found") sys.exit("block not found") # Given a block as recursive 3 element list, and the indentation level, # produce a corresponding text that can be written to config file! def r3_to_text(r3_, level): logging.debug("%s", pprint.pformat(r3_)) if not r3_: return "" name, keypairs, subs = r3_[0], r3_[1], r3_[2] indent = level * "\t" ss_ = indent + name + " {\n" for keypair in keypairs: key, value = keypair[0], keypair[1] ss_ += indent + "\t" + "%s = %s;\n" % (key, value.strip()) for sub in subs: ss_ += r3_to_text(sub, level+1) ss_ += indent + "}\n" return ss_ # Exception for arguments and options class ArgError(Exception): def __init__(self, error): self.error = error def validate_key(key): # We allow any identifier as a block name or a key name # except it can't start with an underscore! key_re = re.compile(r"^[a-zA-Z]\w*$") if not key_re.search(key): raise ArgError("'%s' is not a valid key" % key) def validate_value(value): # value should be any printable but should NOT contain a semicolon import string for char in value: if char not in string.printable: raise ArgError("'%s' has non printable characters" % value) if char == ';': raise ArgError("'%s' has semicolon which is not allowed" % value) def validate_opt_pairs(opairs): for key, value in opairs: validate_key(key) validate_value(value) def validate_opt_keys(okeys): for key in okeys: validate_key(key) def validate_blocknames(blocknames): if not blocknames: raise ArgError("no blocknames given") while blocknames: validate_blockname(blocknames) blocknames = next_subnames(blocknames) def validate_blockname(blocknames): name_re = re.compile(r"^[a-zA-Z]\w*$") if not name_re.search(blocknames[0]): raise ArgError("'%s' is not a valid blockname" % blocknames[0]) # export and client blocks require a key and a value to identify them if blocknames[0].lower() == "export" or blocknames[0].lower() == "client": if len(blocknames) < 3: err = "'%s' block requires 2 additional arguments" % blocknames[0] raise ArgError(err) key = blocknames[1] value = blocknames[2] if blocknames[0].lower() == "export": valid_keys = ["export_id", "pseudo", "path"] else: valid_keys = ["clients"] if key.lower() not in valid_keys: err = "'%s' is not in %s" % (key, pprint.pformat(valid_keys)) raise ArgError(err) validate_value(value) def next_subnames(blocknames): assert blocknames if blocknames[0].lower() == "export" or blocknames[0].lower() == "client": # export and client blocks require a key and a value to identify them return blocknames[3:] else: return blocknames[1:] def block_match(blocknames, name, pairs): logging.debug("names:%s, name:%s, pairs:%s", pprint.pformat(blocknames), name, pprint.pformat(pairs)) if blocknames[0].lower() == "export" or blocknames[0].lower() == "client": if blocknames[0].lower() != name.lower(): return False key = blocknames[1] value = blocknames[2] if blocknames[0].lower() == "export": valid_keys = ["export_id", "pseudo", "path"] else: valid_keys = ["clients"] assert key.lower() in valid_keys, "key:%s valid_keys:%s" % (key, pprint.pformat(valid_keys)) for pair in pairs: if pair[0].lower() == key.lower() and pair[1].strip() == value: return True return False # neither export nor client block return blocknames[0].lower() == name.lower() # Make a new r3 list from blockname def make_r3(blocknames): if blocknames[0].lower() == "export" or blocknames[0].lower() == "client": pairs = [[blocknames[1], blocknames[2]]] else: pairs = [] return [blocknames[0], pairs, []] �������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/Ganesha/export_mgr.py����������������������������������������0000664�0000000�0000000�00000015435�14737566223�0024302�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # # export_mgr.py - ExportMgr DBUS object class. # # Copyright (C) 2014 Panasas Inc. # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Jim Lieb <jlieb@panasas.com> #-*- coding: utf-8 -*- import sys, time from PyQt5.QtCore import * from PyQt5 import QtDBus, QtGui from collections import namedtuple Export = namedtuple('Export', ['ExportID', 'ExportPath', 'HasNFSv3', 'HasMNT', 'HasNLM4', 'HasRQUOTA', 'HasNFSv40', 'HasNFSv41', 'HasNFSv42', 'Has9P', 'LastTime']) class ExportMgr(QtDBus.QDBusAbstractInterface): ''' org.ganesha.nfsd.exportmgr ''' show_exports = pyqtSignal(tuple, list) display_export = pyqtSignal(int, str, str, str) def __init__(self, service, path, connection, show_status, parent=None): super(ExportMgr, self).__init__(service, path, 'org.ganesha.nfsd.exportmgr', connection, parent) self.show_status = show_status def AddExport(self, conf_path, exp_expr): _async = self.asyncCall("AddExport", conf_path, exp_expr) status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.exportadd_done) def UpdateExport(self, conf_path, exp_expr): _async = self.asyncCall("UpdateExport", conf_path, exp_expr) status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.exportadd_done) def RemoveExport(self, exp_id): _async = self.asyncCall("RemoveExport", int(exp_id)) status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.exportrm_done) def DisplayExport(self, exp_id): _async = self.asyncCall("DisplayExport", int(exp_id)) status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.exportdisplay_done) def ShowExports(self): _async = self.asyncCall("ShowExports") status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.exportshow_done) def exportadd_done(self, call): reply = QtDBus.QDBusPendingReply(call) if reply.isError(): self.show_status.emit(False, "Error:" + str(reply.error().message())) else: message = reply.argumentAt(0).toPyObject() self.show_status.emit(True, "Done: " + message) def exportrm_done(self, call): reply = QtDBus.QDBusPendingReply(call) if reply.isError(): self.show_status.emit(False, "Error:" + str(reply.error().message())) else: self.show_status.emit(True, "Done") def exportdisplay_done(self, call): reply = QtDBus.QDBusPendingReply(call) if reply.isError(): self.show_status.emit(False, "Error:" + str(reply.error().message())) else: id_ = reply.argumentAt(0).toPyObject() fullpath = reply.argumentAt(1).toPyObject() pseudopath = reply.argumentAt(2).toPyObject() tag = reply.argumentAt(3).toPyObject() self.display_export.emit(id_, fullpath, pseudopath, tag) def exportshow_done(self, call): reply = QtDBus.QDBusPendingReply(call) if reply.isError(): self.show_status.emit(False, "DBUS error:" + str(reply.error().message())) else: ts_ = (reply.argumentAt(0).toPyObject()[0].toULongLong()[0], reply.argumentAt(0).toPyObject()[1].toULongLong()[0]) exports = [] for export in reply.argumentAt(1).toPyObject(): ex = export.toPyObject() lasttime = ex[10].toPyObject() exp = Export(ExportID=ex[0].toInt()[0], ExportPath=str(ex[1].toString()), HasNFSv3=ex[2].toBool(), HasMNT=ex[3].toBool(), HasNLM4=ex[4].toBool(), HasRQUOTA=ex[5].toBool(), HasNFSv40=ex[6].toBool(), HasNFSv41=ex[7].toBool(), HasNFSv42=ex[8].toBool(), Has9P=ex[9].toBool(), LastTime=(lasttime[0].toPyObject(), lasttime[1].toPyObject())) exports.append(exp) self.show_exports.emit(ts_, exports) class ExportStats(QtDBus.QDBusAbstractInterface): ''' org.ganesha.nfsd.exportstats ''' def __init__(self, service, path, connection, stats_handler, parent=None): super(ExportStats, self).__init__(service, path, 'org.ganesha.nfsd.exportstats', connection, parent) self.stats_handler = stats_handler def GetNFSv3IO(self, exportid): _async = self.asyncCall("GetNFSv3IO", exportid) status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.io_done) def GetNFSv40IO(self, exportid): _async = self.asyncCall("GetNFSv40IO", exportid) status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.io_done) def GetNFSv41IO(self, exportid): _async = self.asyncCall("GetNFSv41IO", exportid) status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.io_done) def GetNFSv41Layouts(self, exportid): _async = self.asyncCall("GetNFSv41Layouts", exportid) status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.layout_done) def io_done(self, call): pass def layout_done(self, call): pass �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/Ganesha/ganesha_mgr_utils.py���������������������������������0000664�0000000�0000000�00000043052�14737566223�0025603�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: GPL-2.0-or-later # # ganesha_mgr_utils.py - commandline tool utils for managing nfs-ganesha. # # Copyright (C) 2014 IBM. # # 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, see <http://www.gnu.org/licenses/>. # # Author: Allison Henderson <achender@vnet.linux.ibm.com> #-*- coding: utf-8 -*- import sys import dbus import json from collections import namedtuple Client = namedtuple('Client', ['ClientIP', 'HasNFSv3', 'HasMNT', 'HasNLM4', 'HasRQUOTA', 'HasNFSv40', 'HasNFSv41', 'HasNFSv42', 'Has9P', 'LastTime']) class ClientMgr(): def __init__(self, service, path, interface): self.dbus_service_name = service self.dbus_path = path self.dbus_interface = interface self.bus = dbus.SystemBus() try: self.dbusobj = self.bus.get_object(self.dbus_service_name, self.dbus_path) except: sys.exit("Error: Can't talk to ganesha service on d-bus." \ " Looks like Ganesha is down") def AddClient(self, ipaddr): add_client_method = self.dbusobj.get_dbus_method("AddClient", self.dbus_interface) try: reply = add_client_method(ipaddr) except dbus.exceptions.DBusException as ex: return False, ex status = reply[0] msg = reply[1] return status, msg def RemoveClient(self, ipaddr): remove_client_method = self.dbusobj.get_dbus_method("RemoveClient", self.dbus_interface) try: reply = remove_client_method(ipaddr) except dbus.exceptions.DBusException as ex: return False, ex status = reply[0] msg = reply[1] return status, msg def ShowClients(self): show_client_method = self.dbusobj.get_dbus_method("ShowClients", self.dbus_interface) try: reply = show_client_method() except dbus.exceptions.DBusException as ex: return False, ex, [] time = reply[0] client_array = reply[1] ts_ = (time[0], time[1]) clients = [] for client in client_array: ''' return format of ShowClients [<client_ip>, [["NFSv3", <data>], ["MNT", <data>], ["NLMv4", <data>], ["RQUOTA", <data>], ["NFSv40", <data>], ["NFSv41", <data>], ["NFSv42", <data>], ["9P", <data>]], ["Open", <data>, "Lock", <data>, "Delegation", <data>], [<lastime>, <nsecs>]] convert index:1 to dict and use it ''' try: cl_str = json.dumps(client) data = json.loads(cl_str) except ValueError as e: return False, e, [] cl_ = dict(data[1]) lasttime = client[3] clt = Client(ClientIP=str(client[0]), HasNFSv3=cl_.get('NFSv3', 0), HasMNT=cl_.get('MNT', 0), HasNLM4=cl_.get('NLMv4', 0), HasRQUOTA=cl_.get('RQUOTA', 0), HasNFSv40=cl_.get('NFSv40', 0), HasNFSv41=cl_.get('NFSv41', 0), HasNFSv42=cl_.get('NFSv42', 0), Has9P=cl_.get('9P', 0), LastTime=(lasttime[0], lasttime[1])) clients.append(clt) return True, "Done", [ts_, clients] Export = namedtuple('Export', ['ExportID', 'ExportPath', 'HasNFSv3', 'HasMNT', 'HasNLM4', 'HasRQUOTA', 'HasNFSv40', 'HasNFSv41', 'HasNFSv42', 'Has9P', 'LastTime']) ExportClient = namedtuple('ExportClient', ['Client_type', 'CIDR_version', 'CIDR_address', 'CIDR_mask', 'CIDR_proto', 'Anonymous_uid', 'Anonymous_gid', 'Expire_time_attr', 'Options', 'Set']) class ExportMgr(): ''' org.ganesha.nfsd.exportmgr ''' def __init__(self, service, path, interface): self.dbus_service_name = service self.dbus_path = path self.dbus_interface = interface self.bus = dbus.SystemBus() try: self.dbusobj = self.bus.get_object(self.dbus_service_name, self.dbus_path) except: sys.exit("Error: Can't talk to ganesha service on d-bus." \ " Looks like Ganesha is down") def AddExport(self, conf_path, exp_expr): add_export_method = self.dbusobj.get_dbus_method("AddExport", self.dbus_interface) try: msg = add_export_method(conf_path, exp_expr) except dbus.exceptions.DBusException as ex: return False, ex return True, "Done: "+msg def UpdateExport(self, conf_path, exp_expr): update_export_method = self.dbusobj.get_dbus_method("UpdateExport", self.dbus_interface) try: msg = update_export_method(conf_path, exp_expr) except dbus.exceptions.DBusException as ex: return False, ex return True, "Done: "+msg def RemoveExport(self, exp_id): rm_export_method = self.dbusobj.get_dbus_method("RemoveExport", self.dbus_interface) try: rm_export_method(int(exp_id)) except dbus.exceptions.DBusException as ex: return False, ex return True, "Done" def DisplayExport(self, exp_id): display_export_method = self.dbusobj.get_dbus_method("DisplayExport", self.dbus_interface) try: id_, fullpath, pseudopath, tag, clients_array = \ display_export_method(int(exp_id)) except dbus.exceptions.DBusException as ex: return False, ex, [] export_clients = [] for client in clients_array: c_ = ExportClient(Client_type=client[0], CIDR_version=client[1], CIDR_address=client[2], CIDR_mask=client[3], CIDR_proto=client[4], Anonymous_uid=client[5], Anonymous_gid=client[6], Expire_time_attr=client[7], Options=client[8], Set=client[9]) export_clients.append(c_) return True, "Done", [id_, fullpath, pseudopath, tag, export_clients] def ShowExports(self): show_export_method = self.dbusobj.get_dbus_method("ShowExports", self.dbus_interface) try: reply = show_export_method() except dbus.exceptions.DBusException as ex: return False, ex, [] time = reply[0] export_array = reply[1] ts_ = (time[0], time[1]) exports = [] for export in export_array: ''' export format from ShowExports [exp_id, path, [["NFSv3", <data>], ["MNT", <data>], ["NLMv4", <data>], ["RQUOTA", <data>], ["NFSv40", <data>], ["NFSv41", <data>], ["NFSv42", <data>], ["9P", <data>]], [<lastime>, <nsecs>]] convert index:2 to dict and use it ''' try: exp_str = json.dumps(export) data = json.loads(exp_str) except ValueError as e: return False, e, [] exp_stat = dict(data[2]) lasttime = export[3] exp = Export(ExportID=export[0], ExportPath=str(export[1]), HasNFSv3=exp_stat.get('NFSv3', 0), HasMNT=exp_stat.get('MNT', 0), HasNLM4=exp_stat.get('NLMv4', 0), HasRQUOTA=exp_stat.get('RQUOTA', 0), HasNFSv40=exp_stat.get('NFSv40', 0), HasNFSv41=exp_stat.get('NFSv41', 0), HasNFSv42=exp_stat.get('NFSv42', 0), Has9P=exp_stat.get('9P', 0), LastTime=(lasttime[0], lasttime[1])) exports.append(exp) return True, "Done", [ts_, exports] class AdminInterface(): ''' org.ganesha.nfsd.admin interface ''' def __init__(self, service, path, interface): self.dbus_service_name = service self.dbus_path = path self.dbus_interface = interface self.bus = dbus.SystemBus() try: self.dbusobj = self.bus.get_object(self.dbus_service_name, self.dbus_path) except: sys.exit("Error: Can't talk to ganesha service on d-bus." \ " Looks like Ganesha is down") def grace(self, ipaddr): grace_method = self.dbusobj.get_dbus_method("grace", self.dbus_interface) try: reply = grace_method(ipaddr) except dbus.exceptions.DBusException as ex: return False, ex status = reply[0] msg = reply[1] return status, msg def shutdown(self): shutdown_method = self.dbusobj.get_dbus_method("shutdown", self.dbus_interface) try: reply = shutdown_method() except dbus.exceptions.DBusException as ex: return False, ex status = reply[0] msg = reply[1] return status, msg def purge_netgroups(self): method = self.dbusobj.get_dbus_method("purge_netgroups", self.dbus_interface) try: reply = method() except dbus.exceptions.DBusException as ex: return False, ex status = reply[0] msg = reply[1] return status, msg def purge_idmap(self): method = self.dbusobj.get_dbus_method("purge_idmapper_cache", self.dbus_interface) try: reply = method() except dbus.exceptions.DBusException as ex: return False, ex status = reply[0] msg = reply[1] return status, msg def purge_gids(self): method = self.dbusobj.get_dbus_method("purge_gids", self.dbus_interface) try: reply = method() except dbus.exceptions.DBusException as ex: return False, ex status = reply[0] msg = reply[1] return status, msg def trim_enable(self): method = self.dbusobj.get_dbus_method("trim_enable", self.dbus_interface) try: reply = method() except dbus.exceptions.DBusException as e: return False, e status = reply[0] msg = reply[1] return status, msg def trim_disable(self): method = self.dbusobj.get_dbus_method("trim_disable", self.dbus_interface) try: reply = method() except dbus.exceptions.DBusException as e: return False, e status = reply[0] msg = reply[1] return status, msg def trim_call(self): method = self.dbusobj.get_dbus_method("trim_call", self.dbus_interface) try: reply = method() except dbus.exceptions.DBusException as e: return False, e status = reply[0] msg = reply[1] return status, msg def trim_status(self): method = self.dbusobj.get_dbus_method("trim_status", self.dbus_interface) try: reply = method() except dbus.exceptions.DBusException as e: return False, e status = reply[0] msg = reply[1] return status, msg def GetAll(self): method = self.dbusobj.get_dbus_method("GetAll", "org.freedesktop.DBus.Properties") try: dictionary = method(self.dbus_interface) except dbus.exceptions.DBusException as ex: return False, ex, {} prop_dict = {} for key in dictionary.keys(): prop_dict[key] = dictionary[key] return True, "Done", prop_dict IDMapper = namedtuple('IDMapper', ['Name', 'UID', 'HasGID', 'GID']) FileSys = namedtuple('FileSys', ['Path', 'MajorDevId', 'MinorDevId']) class CacheMgr(): ''' org.ganesha.nfsd.cachemgr ''' def __init__(self, service, path, interface): self.dbus_service_name = service self.dbus_path = path self.dbus_interface = interface self.bus = dbus.SystemBus() try: self.dbusobj = self.bus.get_object(self.dbus_service_name, self.dbus_path) except: sys.exit("Error: Can't talk to ganesha service on d-bus." \ " Looks like Ganesha is down") def ShowFileSys(self): show_filesys_method = self.dbusobj.get_dbus_method("showfs", self.dbus_interface) try: reply = show_filesys_method() except dbus.exceptions.DBusException as ex: return False, ex, [] time = reply[0] fs_array = reply[1] ts_ = (time[0], time[1]) fss = [] for fs_ in fs_array: filesys1 = FileSys(Path=str(fs_[0]), MajorDevId=fs_[1], MinorDevId=fs_[2]) fss.append(filesys1) return True, "Done", [ts_, fss] def ShowIdmapper(self): show_id_method = self.dbusobj.get_dbus_method("showidmapper", self.dbus_interface) try: reply = show_id_method() except dbus.exceptions.DBusException as ex: return False, ex, [] time = reply[0] id_array = reply[1] ts_ = (time[0], time[1]) ids = [] for entry in id_array: entry1 = IDMapper(Name=str(entry[0]), UID=entry[1], HasGID=entry[2], GID=entry[3]) ids.append(entry1) return True, "Done", [ts_, ids] LOGGER_PROPS = 'org.ganesha.nfsd.log.component' class LogManager(): ''' org.ganesha.nfsd.log.component ''' def __init__(self, service, path, interface): self.dbus_service_name = service self.dbus_path = path self.dbus_interface = interface self.bus = dbus.SystemBus() try: self.dbusobj = self.bus.get_object(self.dbus_service_name, self.dbus_path) except: sys.exit("Error: Can't talk to ganesha service on d-bus. " \ " Looks like Ganesha is down") def GetAll(self): getall_method = self.dbusobj.get_dbus_method("GetAll", self.dbus_interface) try: dictionary = getall_method(LOGGER_PROPS) except dbus.exceptions.DBusException as ex: return False, ex, {} prop_dict = {} for key in dictionary.keys(): prop_dict[key] = dictionary[key] return True, "Done", prop_dict def Get(self, prop): get_method = self.dbusobj.get_dbus_method("Get", self.dbus_interface) try: level = get_method(LOGGER_PROPS, prop) except dbus.exceptions.DBusException as ex: return False, ex, 0 return True, "Done", level def Set(self, prop, setval): set_method = self.dbusobj.get_dbus_method("Set", self.dbus_interface) try: set_method(LOGGER_PROPS, prop, setval) except dbus.exceptions.DBusException as ex: return False, ex return True, "Done" ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/Ganesha/glib_dbus_stats.py�����������������������������������0000775�0000000�0000000�00000164321�14737566223�0025266�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-3.0-or-later # # Copyright (C) 2014 IBM # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Marc Eshel <eshel@us.ibm.com> from __future__ import print_function import sys import time import json # Create a system bus object. import dbus def dbus_to_std(v): ctor = None if (isinstance(v, dbus.UInt16) or isinstance(v, dbus.UInt32) or isinstance(v, dbus.UInt64) or isinstance(v, int)): ctor = int elif isinstance(v, dbus.Boolean): ctor = bool elif isinstance(v, dbus.Double): ctor = float elif isinstance(v, dbus.String): ctor = str assert ctor is not None, "{}".format(v.__class__.__name__) return ctor(v) def timestr(t): timestamp = t[0] + float(t[1]) / 1e9 return time.strftime('%FT%TZ', time.gmtime(timestamp)) class Report(): def __init__(self, result): self.result = result def json(self): output = self.report() return json.dumps(output) def report(self): report, success = self._header(self.result) if not success: return report self.fill_report(report) return report def fill_report(self, report): pass def _header(self, result): # Unfortunately, there is no established way to read the response # from the various endpoint. This tries to cover the most common # cases, if you are in a specific case, feel free to implement the # custom logic separately. def is_timestamp_struct(t): return (isinstance(result[2], dbus.Struct) and len(result[2]) == 2 and isinstance(result[2][0], dbus.UInt64) and isinstance(result[2][1], dbus.UInt64)) header = { 'status': {}, } if not isinstance(result[0], dbus.Boolean): # The header is missing the status field so we just return the time header['status']['time'] = timestr(result[0]) success = True return header, success status = result[0] success = status if not success: header['status']['error'] = result[1] elif result[2] and is_timestamp_struct(result[2]): header['status']['time'] = timestr(result[2]) return header, success def report_key_value(values, report): for i in range(int(len(values) / 2)): key = str(values[i*2 + 0]) value = values[i*2 + 1] report[key] = dbus_to_std(value) class RetrieveExportStats(): def __init__(self): self.dbus_service_name = "org.ganesha.nfsd" self.dbus_exportstats_name = "org.ganesha.nfsd.exportstats" self.dbus_exportmgr_name = "org.ganesha.nfsd.exportmgr" self.export_interface = "/org/ganesha/nfsd/ExportMgr" self.bus = dbus.SystemBus() self.exportmgrobj = self.bus.get_object(self.dbus_service_name, self.export_interface) # NFSv3/NFSv4/NLM/MNT/QUOTA stats over all exports def fast_stats(self): stats_op = self.exportmgrobj.get_dbus_method("GetFastOPS", self.dbus_exportstats_name) return FastStats(stats_op()) # NFSv3/NFSv40/NFSv41/NFSv42/NLM4/MNTv1/MNTv3/RQUOTA totalled over all exports def global_stats(self): stats_op = self.exportmgrobj.get_dbus_method("GetGlobalOPS", self.dbus_exportstats_name) return GlobalStats(stats_op()) # mdcache stats def inode_stats(self): stats_op = self.exportmgrobj.get_dbus_method("ShowMDCache", self.dbus_exportstats_name) return InodeStats(stats_op()) # list of all exports def export_stats(self): stats_op = self.exportmgrobj.get_dbus_method("ShowExports", self.dbus_exportmgr_name) return ExportStats(stats_op()) # NFSv3/NFSv4/NLM/MNT/QUOTA stats totalled for a single export def total_stats(self, export_id): stats_op = self.exportmgrobj.get_dbus_method("GetTotalOPS", self.dbus_exportstats_name) stats_dict = {} if export_id < 0: export_list = self.export_stats() for exportid in export_list.exportids(): stats_dict[exportid] = stats_op(exportid) else: stats_dict[export_id] = stats_op(int(export_id)) return TotalStats(stats_dict) def io_stats(self, stats_op, export_id): stats_dict = {} if export_id < 0: export_list = self.export_stats() for exportid in export_list.exportids(): stats_dict[exportid] = stats_op(exportid) return stats_dict else: stats_dict[export_id] = stats_op(export_id) return stats_dict def v3io_stats(self, export_id): stats_op = self.exportmgrobj.get_dbus_method("GetNFSv3IO", self.dbus_exportstats_name) return ExportIOv3Stats(self.io_stats(stats_op, export_id)) def v4io_stats(self, export_id): stats_op = self.exportmgrobj.get_dbus_method("GetNFSv40IO", self.dbus_exportstats_name) return ExportIOv4Stats(self.io_stats(stats_op, export_id)) def v41io_stats(self, export_id): stats_op = self.exportmgrobj.get_dbus_method("GetNFSv41IO", self.dbus_exportstats_name) return ExportIOv41Stats(self.io_stats(stats_op, export_id)) def v42io_stats(self, export_id): stats_op = self.exportmgrobj.get_dbus_method("GetNFSv42IO", self.dbus_exportstats_name) return ExportIOv42Stats(self.io_stats(stats_op, export_id)) def iomon_stats(self, export_id): stats_op = self.exportmgrobj.get_dbus_method("GetNFSIOMon", self.dbus_exportstats_name) return ExportIOMonStats(self.io_stats(stats_op, export_id)) def pnfs_stats(self, export_id): stats_op = self.exportmgrobj.get_dbus_method("GetNFSv41Layouts", self.dbus_exportstats_name) stats_dict = {} if export_id < 0: export_list = self.export_stats() for exportid in export_list.exportids(): stats_dict[exportid] = stats_op(exportid) return PNFSStats(stats_dict) else: stats_dict[export_id] = stats_op(int(export_id)) return PNFSStats(stats_dict) # Reset the statistics counters for all def reset_stats(self): stats_state = self.exportmgrobj.get_dbus_method("ResetStats", self.dbus_exportstats_name) return StatsReset(stats_state()) # fsal stats def fsal_stats(self, fsal): stats_op = self.exportmgrobj.get_dbus_method("GetFSALStats", self.dbus_exportstats_name) return DumpFSALStats(stats_op(fsal)) # enable stats def enable_stats(self, stat_type): stats_state = self.exportmgrobj.get_dbus_method("EnableStats", self.dbus_exportstats_name) return StatsEnable(stats_state(stat_type)) # disable stats def disable_stats(self, stat_type): stats_state = self.exportmgrobj.get_dbus_method("DisableStats", self.dbus_exportstats_name) return StatsDisable(stats_state(stat_type)) # status def status_stats(self): stats_state = self.exportmgrobj.get_dbus_method("StatusStats", self.dbus_exportstats_name) return StatsStatus(stats_state()) # v3_full def v3_full_stats(self): stats_state = self.exportmgrobj.get_dbus_method("GetFULLV3Stats", self.dbus_exportstats_name) return DumpFULLV3Stats(stats_state()) # v4_full def v4_full_stats(self): stats_state = self.exportmgrobj.get_dbus_method("GetFULLV4Stats", self.dbus_exportstats_name) return DumpFULLV4Stats(stats_state()) # authentication def auth_stats(self): stats_state = self.exportmgrobj.get_dbus_method("GetAuthStats", self.dbus_exportstats_name) return DumpAuth(stats_state()) # Export Details def export_details_stats(self, export_id): stats_op = self.exportmgrobj.get_dbus_method("GetExportDetails", self.dbus_exportstats_name) return ExportDetails(stats_op(export_id)) class RetrieveClientStats(): def __init__(self): self.dbus_service_name = "org.ganesha.nfsd" self.dbus_clientstats_name = "org.ganesha.nfsd.clientstats" self.dbus_clientmgr_name = "org.ganesha.nfsd.clientmgr" self.client_interface = "/org/ganesha/nfsd/ClientMgr" self.bus = dbus.SystemBus() self.clientmgrobj = self.bus.get_object(self.dbus_service_name, self.client_interface) # delegation stats related to a single client ip def deleg_stats(self, ip_): stats_op = self.clientmgrobj.get_dbus_method("GetDelegations", self.dbus_clientstats_name) return DelegStats(stats_op(ip_)) def list_clients(self): stats_op = self.clientmgrobj.get_dbus_method("ShowClients", self.dbus_clientmgr_name) return ClientStats(stats_op()) # Clients specific stats def client_io_ops_stats(self, ip): stats_op = self.clientmgrobj.get_dbus_method("GetClientIOops", self.dbus_clientstats_name) return ClientIOops(stats_op(ip)) def client_all_ops_stats(self, ip): stats_op = self.clientmgrobj.get_dbus_method("GetClientAllops", self.dbus_clientstats_name) return ClientAllops(stats_op(ip)) class ClientStats(Report): def __init__(self, stats): super().__init__(stats) self.stats = stats self.clients = {} for client in stats[1]: clientaddr = client[0] self.clients[clientaddr] = Client(client) def fill_report(self, report): report['clients'] = [] for addr, client in self.clients.items(): client_report = { 'addr': dbus_to_std(client.clientaddr), 'nfsv3_stats': dbus_to_std(client.nfsv3_stats_avail), 'nfsv40_stats': dbus_to_std(client.nfsv40_stats_avail), 'nfsv41_stats': dbus_to_std(client.nfsv41_stats_avail), 'nfsv42_stats': dbus_to_std(client.nfsv42_stats_avail), 'mnt_stats': dbus_to_std(client.mnt_stats_avail), 'nlmv4_stats': dbus_to_std(client.nlmv4_stats_avail), 'rquota_stats': dbus_to_std(client.rquota_stats_avail), '9p_stats': dbus_to_std(client._9p_stats_avail), } report['clients'].append(client_report) return report['clients'] def __str__(self): output = ("\nTimestamp: " + time.ctime(self.stats[0][0]) + str(self.stats[0][1]) + " nsecs" + "\nClient List:\n") for add, client in self.clients.items(): output += str(client) return output class ProtocolsStats(object): def __init__(self, protocols): # init default protocol stats self.protocols_stats = dict({'NFSv3': 0, 'NFSv40': 0, 'NFSv41': 0, 'NFSv42': 0, 'MNT': 0, 'NLMv4': 0, 'RQUOTA': 0, '9P': 0}) self._update_protocols_stats(protocols) def _update_protocols_stats(self, protocols): for protocol in protocols: name, enabled = protocol[0], protocol[1] self.protocols_stats[name] = enabled class Client(ProtocolsStats): def __init__(self, client): super().__init__(client[1]) self.clientaddr = client[0] self.nfsv3_stats_avail = self.protocols_stats['NFSv3'] self.nfsv40_stats_avail = self.protocols_stats['NFSv40'] self.nfsv41_stats_avail = self.protocols_stats['NFSv41'] self.nfsv42_stats_avail = self.protocols_stats['NFSv42'] self.mnt_stats_avail = self.protocols_stats['MNT'] self.nlmv4_stats_avail = self.protocols_stats['NLMv4'] self.rquota_stats_avail = self.protocols_stats['RQUOTA'] self._9p_stats_avail = self.protocols_stats['9P'] def __str__(self): return ("\nClient Address: " + str(self.clientaddr) + "\n\tNFSv3 stats available: " + str(self.nfsv3_stats_avail) + "\n\tNFSv4.0 stats available: " + str(self.nfsv40_stats_avail) + "\n\tNFSv4.1 stats available: " + str(self.nfsv41_stats_avail) + "\n\tNFSv4.2 stats available: " + str(self.nfsv42_stats_avail) + "\n\tMNT stats available: " + str(self.mnt_stats_avail) + "\n\tNLMv4 stats available: " + str(self.nlmv4_stats_avail) + "\n\tRQUOTA stats available: " + str(self.rquota_stats_avail) + "\n\t9P stats available: " + str(self._9p_stats_avail) + "\n") class DelegStats(Report): def __init__(self, stats): super().__init__(stats) self.curtime = time.time() self.status = stats[1] if stats[1] == "OK": self.timestamp = (stats[2][0], stats[2][1]) self.curr_deleg = stats[3][0] self.tot_recalls = stats[3][1] self.fail_recall = stats[3][2] self.num_revokes = stats[3][3] def fill_report(self, report): report['curr_deleg_grants'] = dbus_to_std(self.curr_deleg); report['tot_recalls'] = dbus_to_std(self.tot_recalls); report['failed_recalls'] = dbus_to_std(self.fail_recall); report['num_revokes'] = dbus_to_std(self.num_revokes); def __str__(self): if self.status != "OK": return "GANESHA RESPONSE STATUS: " + self.status self.starttime = self.timestamp[0] + self.timestamp[1] / 1e9 self.duration = self.curtime - self.starttime return ("GANESHA RESPONSE STATUS: " + self.status + "\nStats collected since: " + time.ctime(self.timestamp[0]) + str(self.timestamp[1]) + " nsecs \n" + "\nDuration: " + "%.10f" % self.duration + " seconds" + "\nCurrent Delegations: " + str(self.curr_deleg) + "\nCurrent Recalls: " + str(self.tot_recalls) + "\nCurrent Failed Recalls: " + str(self.fail_recall) + "\nCurrent Number of Revokes: " + str(self.num_revokes)) class ClientIOops(Report): def __init__(self, stats): super().__init__(stats) self.stats = stats self.status = stats[1] if stats[1] == "OK": self.timestamp = (stats[2][0], stats[2][1]) def fill_report(self, report): proto_table = [ 'nfsv3', 'nfsv40', 'nfsv41', 'nfsv42' ] op_table = [ 'read', 'write', 'other', 'layout' ] stats = self.result[3:] def op_stats(stats): counters = [ 'total', 'errors', 'transferred' ] result = {} for i in range(len(stats)): result[counters[i]] = dbus_to_std(stats[i]) return result i = 0 for proto in proto_table: # Checks that the current stat field is a boolean # indicating if stats are available for the given protocol. available = stats[i] i += 1 assert isinstance(available, dbus.Boolean) if not available: continue # Now greedily takes stats until we reach the next boolean (and so # the next protocol). ops = iter(op_table) found_stats = [] report[proto] = {} while i < len(stats) and not isinstance(stats[i], dbus.Boolean): op_name = next(ops) report[proto][op_name] = op_stats(stats[i]) i += 1 def __str__(self): output = "" cnt = 3 if self.status != "OK": return ("GANESHA RESPONSE STATUS: " + self.status) else: output += "\nClient last active at: " + time.ctime(self.timestamp[0]) + str(self.timestamp[1]) + " nsecs" j = 0 while j<4: if self.stats[cnt]: i = 0 if j==0: output += "\n\t\tNFSv3:" if j==1: output += "\n\t\tNFSv4.0:" if j==2: output += "\n\t\tNFSv4.1:" if j==3: output += "\n\t\tNFSv4.2:" output += "\n\t total \t errors transferred" while i<4: if i==0: output += "\nREAD :" if i==1: output += "\nWRITE:" if i==2: output += "\nOther:" if i==3: output += "\nLayout:" output += "%12d" % self.stats[cnt+i+1][0] output += " %12d" % self.stats[cnt+i+1][1] if i<2: output += " %12d" % self.stats[cnt+i+1][2] if (i==2 and j<2): i += 1 i += 1 if j<2: cnt += 4 else: cnt += 5 else: if j==0: output += "\n\tNo NFSv3 activity" if j==1: output += "\n\tNo NFSv4.0 activity" if j==2: output += "\n\tNo NFSv4.1 activity" if j==3: output += "\n\tNo NFSv4.2 activity" cnt += 1 j += 1 return output class ClientAllops(Report): def __init__(self, stats): super().__init__(stats) self.stats = stats self.status = stats[1] if stats[1] == "OK": self.timestamp = (stats[2][0], stats[2][1]) def fill_report(self, report): proto_table = iter([ 'nfsv3', 'nlmv4', 'nfsv4', 'nfsv4_compounds' ]) headers = iter([ ['total', 'errors', 'dups'], ['total', 'errors', 'dups'], ['total', 'errors'], ['total', 'errors', 'op_in_compounds'] ]) def named_ops_stats(stats, header, span): result = {} op_count = int(len(stats) / span) for op_idx in range(op_count): name = dbus_to_std(stats[op_idx * span + 0]) counters = {} for i in range(counter_count): counter_idx = op_idx * span + i + 1 counters[header[i]] = dbus_to_std( stats[counter_idx] ) result[name] = counters return result stats = iter(self.stats[3:]) for stats_available in stats: assert isinstance(stats_available, dbus.Boolean) proto = next(proto_table) header = next(headers) if stats_available: proto_stats = next(stats) result = {} counter_count = len(header) if isinstance(proto_stats[0], dbus.String): # the number of op is the number of stats field # for the protocol divided by the number of counter per op # plus the name of the op itself (the span). span = counter_count + 1 result = named_ops_stats( proto_stats, header, span ) else: for counter_idx, counter in enumerate(proto_stats): result[header[counter_idx]] = dbus_to_std(counter) report[proto] = result def __str__(self): output = "" cnt = 3 if self.status != "OK": return ("GANESHA RESPONSE STATUS: " + self.status) else: output += "\nClient last active at: " + time.ctime(self.timestamp[0]) + str(self.timestamp[1]) + " nsecs" if self.stats[cnt]: output += "\n\t\tNFSv3 Operations" output += "\nOp Name \t\t total \t errors dups" tot_len = len(self.stats[cnt+1]) i=0 while (i+4) <= tot_len: output += "\n" + (self.stats[cnt+1][i+0]).ljust(21) output += " %s" % (str(self.stats[cnt+1][i+1]).rjust(9)) output += " %s" % (str(self.stats[cnt+1][i+2]).rjust(9)) output += " %s" % (str(self.stats[cnt+1][i+3]).rjust(9)) i += 4 cnt += 2 else: output += "\n\tNo NFSv3 activity" cnt += 1 if self.stats[cnt]: output += "\n\t\tNLMv4 Operations" output += "\nOp Name \t\t total \t errors dups" tot_len = len(self.stats[cnt+1]) i=0 while (i+4) <= tot_len: output += "\n" + (self.stats[cnt+1][i+0]).ljust(21) output += " %s" % (str(self.stats[cnt+1][i+1]).rjust(9)) output += " %s" % (str(self.stats[cnt+1][i+2]).rjust(9)) output += " %s" % (str(self.stats[cnt+1][i+3]).rjust(9)) i += 4 cnt += 2 else: output += "\n\tNo NLMv4 activity" cnt += 1 if self.stats[cnt]: output += "\n\t\tNFSv4 Operations" output += "\nOp Name \t\t total \t errors" tot_len = len(self.stats[cnt+1]) i=0 while (i+3) <= tot_len: output += "\n" + (self.stats[cnt+1][i+0]).ljust(21) output += " %s" % (str(self.stats[cnt+1][i+1]).rjust(9)) output += " %s" % (str(self.stats[cnt+1][i+2]).rjust(9)) i += 3 cnt += 2 else: output += "\n\tNo NFSv4 activity" cnt += 1 if self.stats[cnt]: output += "\n\t\tNFSv4 Compound Operations" output += "\n total \t errors \t Ops in compound\n" output += " %s" % (str(self.stats[cnt+1][0]).rjust(9)) output += " %s" % (str(self.stats[cnt+1][1]).rjust(9)) output += " %s" % (str(self.stats[cnt+1][2]).rjust(9)) cnt += 2 else: output += "\n\tNo NFSv4 compound ops" cnt += 1 return output class Export(ProtocolsStats): def __init__(self, export): super().__init__(export[2]) self.exportid = export[0] self.path = export[1] self.nfsv3_stats_avail = self.protocols_stats['NFSv3'] self.nfsv40_stats_avail = self.protocols_stats['NFSv40'] self.nfsv41_stats_avail = self.protocols_stats['NFSv41'] self.nfsv42_stats_avail = self.protocols_stats['NFSv42'] self.mnt_stats_avail = self.protocols_stats['MNT'] self.nlmv4_stats_avail = self.protocols_stats['NLMv4'] self.rquota_stats_avail = self.protocols_stats['RQUOTA'] self._9p_stats_avail = self.protocols_stats['9P'] def __str__(self): return ("\nExport id: " + str(self.exportid) + "\n\tPath: " + self.path + "\n\tNFSv3 stats available: " + str(self.nfsv3_stats_avail) + "\n\tNFSv4.0 stats available: " + str(self.nfsv40_stats_avail) + "\n\tNFSv4.1 stats available: " + str(self.nfsv41_stats_avail) + "\n\tNFSv4.2 stats available: " + str(self.nfsv42_stats_avail) + "\n\tMNT stats available: " + str(self.mnt_stats_avail) + "\n\tNLMv4 stats available: " + str(self.nlmv4_stats_avail) + "\n\tRQUOTA stats available: " + str(self.rquota_stats_avail) + "\n\t9P stats available: " + str(self._9p_stats_avail) + "\n") class ExportStats(Report): def __init__(self, stats): super().__init__(stats) self.stats = stats self.curtime = time.time() self.timestamp = (stats[0][0], stats[0][1]) self.exports = {} for export in stats[1]: exportid = export[0] self.exports[exportid] = Export(export) def fill_report(self, report): report['exports'] = [] for exportid, export in self.exports.items(): export_report = { 'id': dbus_to_std(exportid), 'path': dbus_to_std(export.path), 'nfsv3_stats': dbus_to_std(export.nfsv3_stats_avail), 'nfsv40_stats': dbus_to_std(export.nfsv40_stats_avail), 'nfsv41_stats': dbus_to_std(export.nfsv41_stats_avail), 'nfsv42_stats': dbus_to_std(export.nfsv42_stats_avail), 'mnt_stats': dbus_to_std(export.mnt_stats_avail), 'nlmv4_stats': dbus_to_std(export.nlmv4_stats_avail), 'rquota_stats': dbus_to_std(export.rquota_stats_avail), '9p_stats': dbus_to_std(export._9p_stats_avail), } report['exports'].append(export_report) def __str__(self): self.starttime = self.timestamp[0] + self.timestamp[1] / 1e9 self.duration = self.curtime - self.starttime output = ("Export Stats \n" + "Stats collected since: " + time.ctime(self.timestamp[0]) + str(self.timestamp[1]) + " nsecs" + "\nDuration: " + "%.10f" % self.duration + " seconds") for exportid in self.exports: output += str(self.exports[exportid]) return output def exportids(self): return self.exports.keys() class ExportDetails(Report): def __init__(self, stats): super().__init__(stats) self.stats = stats self.status = stats[1] if stats[1] == "OK": self.timestamp = (stats[2][0], stats[2][1]) def fill_report(self, report): proto_table = [ 'nfsv3', 'nfsv40', 'nfsv41', 'nfsv42' ] op_table = [ 'read', 'write', 'other', 'layout' ] stats = self.result[3:] def op_stats(stats): counters = [ 'total', 'errors', 'latency', 'transferred' ] result = {} for i in range(len(stats)): result[counters[i]] = dbus_to_std(stats[i]) return result i = 0 for proto in proto_table: # Checks that the current stat field is a boolean # indicating if stats are available for the given protocol. available = stats[i] i += 1 assert isinstance(available, dbus.Boolean) if not available: continue # Now greedily takes stats until we reach the next boolean (and so # the next protocol). ops = iter(op_table) found_stats = [] report[proto] = {} while i < len(stats) and not isinstance(stats[i], dbus.Boolean): op_name = next(ops) report[proto][op_name] = op_stats(stats[i]) i += 1 def __str__(self): output = "" cnt = 3 if self.status != "OK": return ("GANESHA RESPONSE STATUS: " + self.status) else: output += "\nExport last accessed at: " + time.ctime(self.timestamp[0]) + str(self.timestamp[1]) + " nsecs" output += "\n Latency is in milliseconds for Read/Write/Other Operations" j = 0 while j<4: if self.stats[cnt]: i = 0 if j==0: output += "\n\t\tNFSv3:" if j==1: output += "\n\t\tNFSv4.0:" if j==2: output += "\n\t\tNFSv4.1:" if j==3: output += "\n\t\tNFSv4.2:" output += "\n\t total \t errors latency/delays transferred" while i<4: if i==0: output += "\nREAD :" if i==1: output += "\nWRITE:" if i==2: output += "\nOther:" if i==3: output += "\nLayout:" output += "%12d" % self.stats[cnt+i+1][0] output += " %12d" % self.stats[cnt+i+1][1] if (j>1 and i==3): output += " %12d" % self.stats[cnt+i+1][2] else: output += " %18.6f" % self.stats[cnt+i+1][2] if i<2: output += " %12d" % self.stats[cnt+i+1][3] if (i==2 and j<2): i += 1 i += 1 if j<2: cnt += 4 else: cnt += 5 else: if j==0: output += "\n\tNo NFSv3 activity" if j==1: output += "\n\tNo NFSv4.0 activity" if j==2: output += "\n\tNo NFSv4.1 activity" if j==3: output += "\n\tNo NFSv4.2 activity" cnt += 1 j += 1 return output class GlobalStats(Report): def __init__(self, stats): super().__init__(stats) self.curtime = time.time() self.success = stats[0] self.status = stats[1] if self.success: self.timestamp = (stats[2][0], stats[2][1]) self.nfsv3_total = stats[3][1] self.nfsv40_total = stats[3][3] self.nfsv41_total = stats[3][5] self.nfsv42_total = stats[3][7] def fill_report(self, report): report_key_value(self.result[3], report) def __str__(self): output = "" if not self.success: return "No NFS activity, GANESHA RESPONSE STATUS: " + self.status if self.status != "OK": output += self.status + "\n" self.starttime = self.timestamp[0] + self.timestamp[1] / 1e9 self.duration = self.curtime - self.starttime output += ("Global Stats \n" + "Stats collected since: " + time.ctime(self.timestamp[0]) + str(self.timestamp[1]) + " nsecs" + "\nDuration: " + "%.10f" % self.duration + " seconds" + "\nTotal NFSv3 ops: " + str(self.nfsv3_total) + "\nTotal NFSv4.0 ops: " + str(self.nfsv40_total) + "\nTotal NFSv4.1 ops: " + str(self.nfsv41_total) + "\nTotal NFSv4.2 ops: " + str(self.nfsv42_total)) return output class InodeStats(Report): def __init__(self, stats): super().__init__(stats) self.stats = stats def fill_report(self, report): values = self.result[3] + self.result[4] report_key_value(values, report) def __str__(self): output = "" if self.stats[1] != "OK": return "No NFS activity, GANESHA RESPONSE STATUS: " + self.stats[1] else: output += "\nTimestamp: " + time.ctime(self.stats[2][0]) + str(self.stats[2][1]) + " nsecs\n" output += "\nInode Cache statistics" output += "\n" + (self.stats[3][0]).ljust(25) + "%s" % (str(self.stats[3][1]).rjust(20)) output += "\n" + (self.stats[3][2]).ljust(25) + "%s" % (str(self.stats[3][3]).rjust(20)) output += "\n" + (self.stats[3][4]).ljust(25) + "%s" % (str(self.stats[3][5]).rjust(20)) output += "\n" + (self.stats[3][6]).ljust(25) + "%s" % (str(self.stats[3][7]).rjust(20)) output += "\n" + (self.stats[3][8]).ljust(25) + "%s" % (str(self.stats[3][9]).rjust(20)) output += "\n" + (self.stats[3][10]).ljust(25) + "%s" % (str(self.stats[3][11]).rjust(20)) output += "\n\nLRU Utilization Data" output += "\n" + (self.stats[4][0]).ljust(25) + "%s" % (str(self.stats[4][1]).rjust(20)) output += "\n" + (self.stats[4][2]).ljust(25) + "%s" % (str(self.stats[4][3]).rjust(20)) output += "\n" + (self.stats[4][4]).ljust(25) + (self.stats[4][5]).ljust(30) output += "\n" + (self.stats[4][6]).ljust(25) + "%s" % (str(self.stats[4][7]).rjust(20)) output += "\n" + (self.stats[4][8]).ljust(25) + "%s" % (str(self.stats[4][9]).rjust(20)) return output class FastStats(Report): def __init__(self, stats): super().__init__(stats) self.curtime = time.time() self.stats = stats def fill_report(self, report): stats = self.stats[3] current_category = None current_op = None for i in range(0, len(stats)): if FastStats.is_header_string(stats[i]): category = str(stats[i]) name = str(category)[:-1].strip() report[name] = {} current_category = name else: assert current_category is not None if FastStats.is_stat_value(stats[i]): assert current_op is not None report[current_category][current_op] = int(stats[i]) current_op = None else: current_op = str(stats[i]) def __str__(self): if not self.stats[0]: return "No NFS activity, GANESHA RESPONSE STATUS: " + self.stats[1] else: if self.stats[1] != "OK": output = self.stats[1]+ "\n" else: output = "" self.starttime = self.stats[2][0] + self.stats[2][1] / 1e9 self.duration = self.curtime - self.starttime output += ("Fast stats" + "\nStats collected since: " + time.ctime(self.stats[2][0]) + str(self.stats[2][1]) + " nsecs" + "\nDuration: " + "%.10f" % self.duration + " seconds" + "\nGlobal ops:\n") # NFSv3, NFSv4, NLM, MNT, QUOTA self.stats for i in range(len(self.stats[3])): if FastStats.is_header_string(self.stats[3][i]): output += self.stats[3][i] + "\n" elif FastStats.is_stat_value(self.stats[3][i]): output += "\t%s" % (str(self.stats[3][i]).rjust(8)) + "\n" else: output += "%s: " % (self.stats[3][i].ljust(20)) return output def is_header_string(x): return (isinstance(x, dbus.String) and x[-1] == ':') def is_stat_value(x): return isinstance(x, dbus.UInt64) class ExportIOv3Stats(Report): def __init__(self, stats): super().__init__(stats) self.stats = stats def report(self): return export_io_stats_report(self._header, self.result) def __str__(self): output = "" for key in self.stats: if not self.stats[key][0]: output += "EXPORT %s: %s\n" % (key, self.stats[key][1]) continue if self.stats[key][1] != "OK": output += self.stats[key][1] + "\n" output += ("\nEXPORT %s:" % (key) + "\n\t\trequested\ttransferred\t total\t errors\t latency" + "\nREADv3: ") for stat in self.stats[key][3]: output += "\t" + str(stat).rjust(8) output += "\nWRITEv3: " for stat in self.stats[key][4]: output += "\t" + str(stat).rjust(8) return output class ExportIOv4Stats(Report): def __init__(self, stats): super().__init__(stats) self.stats = stats def report(self): return export_io_stats_report(self._header, self.result) def __str__(self): output = "" for key in self.stats: if not self.stats[key][0]: output += "\nEXPORT %s: %s\n" % (key, self.stats[key][1]) continue if self.stats[key][1] != "OK": output += self.stats[key][1] + "\n" output += ("EXPORT %s:" % (key) + "\n\t\trequested\ttransferred\t total\t errors\t latency" + "\nREADv4: ") for stat in self.stats[key][3]: output += "\t" + str(stat).rjust(8) output += "\nWRITEv4: " for stat in self.stats[key][4]: output += "\t" + str(stat).rjust(8) output += "\n\n" return output class ExportIOv41Stats(Report): def __init__(self, stats): super().__init__(stats) self.stats = stats def report(self): return export_io_stats_report(self._header, self.result) def __str__(self): output = "" for key in self.stats: if not self.stats[key][0]: output += "\nEXPORT %s: %s\n" % (key, self.stats[key][1]) continue if self.stats[key][1] != "OK": output += self.stats[key][1] + "\n" output += ("EXPORT %s:" % (key) + "\n\t\trequested\ttransferred\t total\t errors\t latency" + "\nREADv41: ") for stat in self.stats[key][3]: output += "\t" + str(stat).rjust(8) output += "\nWRITEv41: " for stat in self.stats[key][4]: output += "\t" + str(stat).rjust(8) output += "\n\n" return output class ExportIOv42Stats(Report): def __init__(self, stats): super().__init__(stats) self.stats = stats def report(self): return export_io_stats_report(self._header, self.result) def __str__(self): output = "" for key in self.stats: if not self.stats[key][0]: output += "\nEXPORT %s: %s\n" % (key, self.stats[key][1]) continue if self.stats[key][1] != "OK": output += self.stats[key][1] + "\n" output += ("EXPORT %s:" % (key) + "\n\t\trequested\ttransferred\t total\t errors\t latency" + "\nREADv42: ") for stat in self.stats[key][3]: output += "\t" + str(stat).rjust(8) output += "\nWRITEv42: " for stat in self.stats[key][4]: output += "\t" + str(stat).rjust(8) output += "\n\n" return output class ExportIOMonStats(Report): def __init__(self, stats): super().__init__(stats) self.stats = stats def report(self): return export_io_stats_report(self._header, self.result) def __str__(self): output = "" for key in self.stats: if not self.stats[key][0]: output += "\nEXPORT %s: %s\n" % (key, self.stats[key][1]) continue if self.stats[key][1] != "OK": output += self.stats[key][1] + "\n" output += ("EXPORT %s:" % (key) + "\t BW(MB/s)\t" + "\nREAD: ") output += "\t\t" + str(self.stats[key][3][2]).rjust(8) output += "\nWRITE: " output += "\t\t" + str(self.stats[key][4][2]).rjust(8) output += "\nTotal: " output += "\t\t" + str(self.stats[key][3][2] + self.stats[key][4][2]).rjust(8) output += "\n\n" return output def export_io_stats_report(header, stats): reports = [] ops = [ 'read', 'write', ] counters = [ 'requested', 'transferred', 'total', 'errors', 'latency', ] for exportid, export_stats in stats.items(): report, success = header(export_stats) report['id'] = exportid if not success: reports.append(report) continue op_stat_start = 3 for i, op in enumerate(ops): ops_stats = export_stats[op_stat_start + i] result = {} for j, counter in enumerate(counters): result[counter] = dbus_to_std(ops_stats[j]) report[op] = result reports.append(report) return reports class TotalStats(Report): def __init__(self, stats): super().__init__(stats) self.curtime = time.time() self.stats = stats def report(self): reports = [] for exportid in self.result: export = self.result[exportid] report, success = super()._header(export) report['id'] = exportid reports.append(report) if not success: continue report_key_value(export[3], report) return reports def __str__(self): output = "" key = next(iter(self.stats)) self.starttime = self.stats[key][2][0] + self.stats[key][2][1] / 1e9 self.duration = self.curtime - self.starttime output += ("Total stats for export(s) \n" + "Stats collected since: " + time.ctime(self.stats[key][2][0]) + str(self.stats[key][2][1]) + " nsecs" + "\nDuration: " + "%.10f" % self.duration + " seconds ") for key in self.stats: if not self.stats[key][0]: return "No NFS activity, GANESHA RESPONSE STATUS: " + self.stats[key][1] if self.stats[key][1] != "OK": output += self.stats[key][1] + "\n" output += "\nExport id: " + str(key) for i in range(0, len(self.stats[key][3])-1, 2): output += "\n\t%s: %s" % (self.stats[key][3][i], self.stats[key][3][i+1]) return output class PNFSStats(Report): def __init__(self, stats): super().__init__(stats) self.curtime = time.time() self.stats = stats def report(self): reports = [] ops = [ 'layout_get', 'layout_commit', 'layout_return', 'recall' ] counters = [ 'total', 'errors', 'delays' ] for exportid, export_stats in self.stats.items(): report, success = self._header(export_stats) reports.append(report) if not success: continue stats_offset = 3 for i, op in enumerate(ops): result = {} report[op] = result stats = export_stats[stats_offset + i] for i_counter, counter in enumerate(counters): result[counter] = dbus_to_std(stats[i_counter]) return reports def __str__(self): output = "PNFS stats for export(s)" for key in self.stats: if self.stats[key][1] != "OK": return "No NFS activity, GANESHA RESPONSE STATUS: \n" + self.stats[key][1] self.starttime = self.stats[key][2][0] + self.stats[key][2][1] / 1e9 self.duration = self.curtime - self.starttime output += ("\nStats collected since: " + time.ctime(self.stats[key][2][0]) + str(self.stats[key][2][1]) + " nsecs" + "\nDuration: " + "%.10f" % self.duration + " seconds\n") output += "\nStatistics for export id: "+ str(key) output += "\n\t\ttotal\terrors\tdelays" output += "\ngetdevinfo " for stat in self.stats[key][3]: output += "\t" + str(stat) output += "\nlayout_get " for stat in self.stats[key][4]: output += "\t" + str(stat) output += "\nlayout_commit " for stat in self.stats[key][5]: output += "\t" + str(stat) output += "\nlayout_return " for stat in self.stats[key][6]: output += "\t" + str(stat) output += "\nrecall \t" for stat in self.stats[key][7]: output += "\t" + str(stat) return output class StatsReset(): def __init__(self, status): self.status = status def __str__(self): if self.status[1] != "OK": return "Failed to reset statistics, GANESHA RESPONSE STATUS: " + self.status[1] else: return "Successfully reset statistics counters" class StatsStatus(Report): def __init__(self, status): super().__init__(status) self.status = status def fill_report(self, report): proto_table = [ 'nfs', 'fsal', 'nfsv3', 'nfsv4', 'auth', 'client' ] for (i, status) in enumerate(self.result[2:]): enabled = dbus_to_std(status[0]) result = { "enabled": enabled } report[proto_table[i]] = result if enabled: result["since"] = timestr(status[1]) def __str__(self): output = "" if not self.status[0]: return "Unable to fetch current status of stats counting: " + self.status[1] else: if self.status[2][0]: output += "Stats counting for NFS server is enabled since: \n\t" output += time.ctime(self.status[2][1][0]) + str(self.status[2][1][1]) + " nsecs\n" else: output += "Stats counting for NFS server is currently disabled\n" if self.status[3][0]: output += "Stats counting for FSAL is enabled since: \n\t" output += time.ctime(self.status[3][1][0]) + str(self.status[3][1][1]) + " nsecs\n" else: output += "Stats counting for FSAL is currently disabled \n" if self.status[4][0]: output += "Stats counting for v3_full is enabled since: \n\t" output += time.ctime(self.status[4][1][0]) + str(self.status[4][1][1]) + " nsecs\n" else: output += "Stats counting for v3_full is currently disabled \n" if self.status[5][0]: output += "Stats counting for v4_full is enabled since: \n\t" output += time.ctime(self.status[5][1][0]) + str(self.status[5][1][1]) + " nsecs\n" else: output += "Stats counting for v4_full is currently disabled \n" if self.status[6][0]: output += "Stats counting for authentication is enabled since: \n\t" output += time.ctime(self.status[6][1][0]) + str(self.status[6][1][1]) + " nsecs\n" else: output += "Stats counting for authentication is currently disabled \n" if self.status[7][0]: output += "Stats counting of all ops for client is enabled since: \n\t" output += time.ctime(self.status[7][1][0]) + str(self.status[7][1][1]) + " nsecs\n" else: output += "Stats counting of all ops for client is currently disabled \n" return output class DumpFSALStats(Report): def __init__(self, stats): self.curtime = time.time() self.stats = stats def __str__(self): output = "" if not self.stats[0]: return "GANESHA RESPONSE STATUS: " + self.stats[1] else: if self.stats[3] == "GPFS": if self.stats[5] != "OK": output += "No stats available for display" return output self.starttime = self.stats[2][0] + self.stats[2][1] / 1e9 self.duration = self.curtime - self.starttime output += "FSAL stats for - GPFS \n" output += "Stats collected since: " + time.ctime(self.stats[2][0]) + str(self.stats[2][1]) + " nsecs\n" output += "Duration: " + "%.10f" % self.duration + " seconds\n" tot_len = len(self.stats[4]) output += "FSAL Stats (response time in milliseconds): \n" output += "\tOp-Name Total Res:Avg Min Max" i = 0 while (i+5) <= tot_len: output += "\n" + (self.stats[4][i+0]).ljust(20) output += " %s" % (str(self.stats[4][i+1]).rjust(8)) output += " %12.6f" % (self.stats[4][i+2]) output += " %12.6f" % (self.stats[4][i+3]) output += " %12.6f" % (self.stats[4][i+4]) i += 5 return output class StatsEnable(): def __init__(self, status): self.status = status def __str__(self): if self.status[1] != "OK": return "Failed to enable statistics counting, GANESHA RESPONSE STATUS: " + self.status[1] else: return "Successfully enabled statistics counting" class StatsDisable(): def __init__(self, status): self.status = status def __str__(self): if self.status[1] != "OK": return "Failed to disable statistics counting, GANESHA RESPONSE STATUS: " + self.status[1] else: return "Successfully disabled statistics counting" class DumpAuth(Report): def __init__(self, stats): super().__init__(stats) self.curtime = time.time() self.success = stats[0] self.status = stats[1] if self.success: self.timestamp = (stats[2][0], stats[2][1]) self.gctotal = stats[3][0] self.gclatency = stats[3][1] self.gcmax = stats[3][2] self.gcmin = stats[3][3] self.wbtotal = stats[3][4] self.wblatency = stats[3][5] self.wbmax = stats[3][6] self.wbmin = stats[3][7] self.dnstotal = stats[3][8] self.dnslatency = stats[3][9] self.dnsmax = stats[3][10] self.dnsmin = stats[3][11] def fill_report(self, report): report["gc"] = { "total": dbus_to_std(self.gctotal), "latency": dbus_to_std(self.gclatency), "max": dbus_to_std(self.gcmax), "min": dbus_to_std(self.gcmin), } report["wb"] = { "total": dbus_to_std(self.wbtotal), "latency": dbus_to_std(self.wblatency), "max": dbus_to_std(self.wbmax), "min": dbus_to_std(self.wbmin), } report["dns"] = { "total": dbus_to_std(self.dnstotal), "latency": dbus_to_std(self.dnslatency), "max": dbus_to_std(self.dnsmax), "min": dbus_to_std(self.dnsmin) } def __str__(self): output = "" if not self.success: return "No auth activity, GANESHA RESPONSE STATUS: " + self.status if self.status != "OK": output += self.status + "\n" self.starttime = self.timestamp[0] + self.timestamp[1] / 1e9 self.duration = self.curtime - self.starttime output += ("Authentication related stats" + "\nStats collected since: " + time.ctime(self.timestamp[0]) + str(self.timestamp[1]) + " nsecs"+ "\nDuration: " + "%.10f" % self.duration + " seconds\n" + "\n\nGroup Cache" + "\n\tTotal ops: " + str(self.gctotal) + "\n\tAve Latency: " + str(self.gclatency) + "\n\tMax Latency: " + str(self.gcmax) + "\n\tMin Latency: " + str(self.gcmin) + "\n\nWinbind" + "\n\tTotal ops: " + str(self.wbtotal) + "\n\tAve Latency: " + str(self.wblatency) + "\n\tMax Latency: " + str(self.wbmax) + "\n\tMin Latency: " + str(self.wbmin) + "\n\nDNS" + "\n\tTotal ops: " + str(self.dnstotal) + "\n\tAve Latency: " + str(self.dnslatency) + "\n\tMax Latency: " + str(self.dnsmax) + "\n\tMin Latency: " + str(self.dnsmin)) return output class DumpFULLV3Stats(Report): def __init__(self, status): super().__init__(status) self.curtime = time.time() self.stats = status def fill_report(self, report): if self.result[4] == 'None': return for op_stats in self.result[3]: name = dbus_to_std(op_stats[0]) report[name] = { "details": { "total": dbus_to_std(op_stats[1]), "error": dbus_to_std(op_stats[2]), "dups": dbus_to_std(op_stats[3]) }, "latency": { "average": dbus_to_std(op_stats[4]), "min": dbus_to_std(op_stats[5]), "max": dbus_to_std(op_stats[6]) } } def __str__(self): output = "" if not self.stats[0]: return "Unable to fetch Detailed NFSv3 stats - " + self.stats[1] else: if self.stats[4] != "OK": output += "\n No stats available for display" return output self.starttime = self.stats[2][0] + self.stats[2][1] / 1e9 self.duration = self.curtime - self.starttime output += "NFSv3 Detailed statistics \n" output += "Stats collected since: " + time.ctime(self.stats[2][0]) + str(self.stats[2][1]) + " nsecs\n" output += "Duration: " + "%.10f" % self.duration + " seconds\n" output += "\nOperation Details | Operation Latency (in milliseconds)" output += "\n==========================================|========================================" output += "\nName Total Error Dups | Avg Min Max " i = 0 tot_len = len(self.stats[3]) while i < tot_len: output += "\n" + (self.stats[3][i][0]).ljust(11) output += " %s" % (str(self.stats[3][i][1]).rjust(9)) output += " %s" % (str(self.stats[3][i][2]).rjust(9)) output += " %s |" % (str(self.stats[3][i][3]).rjust(9)) output += " %12.6f" % (self.stats[3][i][4]) output += " %12.6f" % (self.stats[3][i][5]) output += " %12.6f" % (self.stats[3][i][6]) i += 1 return output class DumpFULLV4Stats(Report): def __init__(self, status): super().__init__(status) self.curtime = time.time() self.stats = status def fill_report(self, report): if self.result[4] == 'None': return for op_stats in self.result[3]: name = dbus_to_std(op_stats[0]) report[name] = { "details": { "total": dbus_to_std(op_stats[1]), "error": dbus_to_std(op_stats[2]), }, "latency": { "average": dbus_to_std(op_stats[3]), "min": dbus_to_std(op_stats[4]), "max": dbus_to_std(op_stats[5]) } } def __str__(self): output = "" if not self.stats[0]: return "Unable to fetch Detailed NFSv4 stats - " + self.stats[1] else: if self.stats[4] != "OK": output += "\n No stats available for display" return output self.starttime = self.stats[2][0] + self.stats[2][1] / 1e9 self.duration = self.curtime - self.starttime output += "NFSv4 Detailed statistics \n" output += "Stats collected since: " + time.ctime(self.stats[2][0]) + str(self.stats[2][1]) + " nsecs\n" output += "Duration: " + "%.10f" % self.duration + " seconds\n" output += "\nOperation Details | Operation Latency (in milliseconds)" output += "\n=============================================|========================================" output += "\nName Total Error | Avg Min Max " i = 0 tot_len = len(self.stats[3]) while i < tot_len: output += "\n" + (self.stats[3][i][0]).ljust(23) output += " %s" % (str(self.stats[3][i][1]).rjust(9)) output += " %s |" % (str(self.stats[3][i][2]).rjust(9)) output += " %12.6f" % (self.stats[3][i][3]) output += " %12.6f" % (self.stats[3][i][4]) output += " %12.6f" % (self.stats[3][i][5]) i += 1 return output ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/Ganesha/io_stats.py������������������������������������������0000664�0000000�0000000�00000003466�14737566223�0023742�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # # io_stats.py - IOstat table object. # # Copyright (C) 2014 Panasas Inc. # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Jim Lieb <jlieb@panasas.com> #-*- coding: utf-8 -*- from collections import namedtuple # Basic I/O reply and io stats structs BasicIO = namedtuple('BasicIO', [requested, transferred, total_ops, errors, latency]) IOReply = namedtuple('IOReply', [status, errormsg, sampletime, read, write]) # pNFS layout stats reply and stats structs Layout = namedtuple('Layout', [total_ops, errors, delays]) pNFSReply = namedtuple('pNFSReply', [status, errormsg, sampletime, getdevinfo, layout_get, layout_commit, layout_return, layout_recall]) class IOstat(Object): def __init__(self, stat): pass ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/Ganesha/log_mgr.py�������������������������������������������0000664�0000000�0000000�00000007375�14737566223�0023546�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # # log_mgr.py - LogManager DBus object class. # # Copyright (C) 2014 Panasas Inc. # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Jim Lieb <jlieb@panasas.com> #-*- coding: utf-8 -*- from PyQt5 import QtCore, QtDBus ADMIN_OBJECT = '/org/ganesha/nfsd/admin' PROP_INTERFACE = 'org.freedesktop.DBus.Properties' LOGGER_PROPS = 'org.ganesha.nfsd.log.component' class LogManager(QtDBus.QDBusAbstractInterface): ''' org.ganesha.nfsd.log.component ''' show_components = QtCore.pyqtSignal(dict) show_level = QtCore.pyqtSignal(str) def __init__(self, service, connection, show_status, parent=None): super(LogManager, self).__init__(service, ADMIN_OBJECT, PROP_INTERFACE, connection, parent) self.show_status = show_status def GetAll(self): _async = self.asyncCall("GetAll", LOGGER_PROPS) status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.GetAll_done) def GetAll_done(self, call): reply = QtDBus.QDBusPendingReply(call) if reply.isError(): self.show_status.emit(False, "DBus error:" + str(reply.error().message())) else: # what follows is DBus+Qt magic. We get a Variant object back # which contains a "map", aka "dict" in python. Each item in # the map has a variant as a key and a variant as the value # first unwrap the top variant into d... # then walk d, unwrap the variant key to store the unwrapped # variant value into a string value. prop_dict = {} d = reply.value().toPyObject() for key in d.keys(): prop_dict[str(key.toString())] = str(d[key].toPyObject().toString()) self.show_components.emit(prop_dict) def Get(self, prop): _async = self.asyncCall("Get", LOGGER_PROPS, prop) status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.Get_done) def Get_done(self, call): reply = QtDBus.QDBusPendingReply(call) if reply.isError(): self.show_status.emit(False, "DBUS error:" + str(reply.error().message())) else: level = str(reply.value().toPyObject().toString()) self.show_level.emit(level) def Set(self, prop, setval): qval = QtDBus.QDBusVariant() qval.setVariant(str(str(setval))) _async = self.asyncCall("Set", LOGGER_PROPS, prop, qval) status = QtDBus.QDBusPendingCallWatcher(_async, self) status.finished.connect(self.Set_done) def Set_done(self, call): reply = QtDBus.QDBusPendingReply(call) if reply.isError(): self.show_status.emit(False, "DBUS error:" + str(reply.error().message() + str(reply.error().name()))) else: self.show_status.emit(True, "Done") �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/README.txt���������������������������������������������������0000664�0000000�0000000�00000011202�14737566223�0021656�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������This README is addressed to developers who plan to either improve this reference implementation of DBus control for Ganesha or to use it as a reference for their own implementation. Those who are looking for information about setting up DBus and Ganesha in a running system should consult the installation and administration pages of the project wiki. This directory contains a reference implementation of tools for managing NFS Ganesha through DBus. Everything here is in Python 2.7+ and PyQt 4.2+. Ganeshactl is a PyQt GUI application for managing NFS Ganesha through DBus. There are also python scripts that can be called from the shell to do most of the management functions. Under both is a set of python classes that implement the details of the DBus transactions. The GUI is a basic Qt application. I'm sure the usability people will be all over this thing complaining about my poor design taste and usability ignorance. Note two things. First, this is a reference implementation to have a functioning set of core classes. Second, see the next section, make it usable according to your designer sensibilities, and commit the resulting .ui file(s). Editing the UI -------------- The UI files (in python) are generated from XML files that are created by "designer-qt4". DO NOT edit the .py files directly. Only the *.ui files are committed to git and the .py files are overwritten by the build process. The process of changing/enhancing the UI follows some simple steps: 1. Use designer-qt4 to edit the .ui file. This tool will manage all of the details like buttons and dialog boxes. 2. Generate the corresponding .py file with the 'pyuic4' tool. This takes the XML that describes the UI and generates the .py. 3. Edit ganeshact.py to connect up logic to the UI. It helps here to examine the pyuic4 output. A good start is to have the new methods display something to the status bar. This ensures that your new GUI's events are wired properly. 4. If you add a new class for a new DBus interface, get an instance of it in the main window's __init__ method so it can be referenced properly. Script Runtime -------------- The python "distutils" packaging is now in place to make these scripts installable. However, there are some limitations that users/developers should be aware of. 1. The scripts work properly if they are invoked from src/scripts/ganeshactl 2. They will also work properly if installed by the RPM tools. 3. They will _not_ work if installed in /usr/local without intervention. The issue here is how Python manages how it finds its modules. The following script fragment illustrates the issue: from Ganesha.export_mgr import ExportMgr This tells the python interpreter to search for the module in its "known" locations in the filesystem. The python developers have made some infrastructure design decisions that make the mix of system packaging, such as RPM or Debian difficult. The built-in assumption in Python that its packages and the interpreter are in the same directory tree makes the mix of '/usr/local' and distribution packaging incompatible without intervention. The case for (1) works because python does have a search order, i.e. first search the directory tree where the script is located followed by the system location which, in the case of RPM would be /usr. The case for (2) works the same way because the Ganesha modules would be installed in the system location. The case for (3) breaks because python cannot find the modules after looking in the script directory tree and the system location. Note that one cannot invoke a script in /usr/local/bin by its full path. This is because python expects a different directory structure in the local directory case. This is confusing and broken but there has been lots of discussion on the topic over the years but this is one of the things that are baked-in deeply in the python ecosystem. For those users/developers who install NFS-Ganesha from source and install in /usr/local, some extra, manual effort is required. This is not really a satisfactory solution, as noted in the python forums and listserv archives, but it is a workaround for those who don't really want to carve up the python code or their python installation. Do the following in the shell or shell scripts: # export PYTHONPATH=/usr/local/lib/python2.7/site-packages # manage_exports display 79 This shell fragment sets up the environment to run these scripts located in /usr/local/bin. There are side-effects of setting PYTHONPATH in the environment and if one wants a further explanation of all this, do a search for "PYTHONPATH". There is a lot more detail in the forums than can be condensed into this README. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/client_stats_9pOps.py����������������������������������������0000775�0000000�0000000�00000005104�14737566223�0024327�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-3.0-or-later # # Copyright (C) 2014 Bull SAS # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Gregoire Pichon <gregoire.pichon@bull.net> from __future__ import print_function import sys # Create a system bus object. import dbus bus = dbus.SystemBus() # Create an object that will proxy for a particular remote object. try: admin = bus.get_object("org.ganesha.nfsd", "/org/ganesha/nfsd/ClientMgr") except: # catch *all* exceptions print("Error: Can't talk to ganesha service on d-bus. Looks like Ganesha is down") exit(1) # call method ganesha_9pOpstats = admin.get_dbus_method('Get9pOpStats', 'org.ganesha.nfsd.clientstats') # get parameters if len(sys.argv) != 2: print("Usage: %s client_ipaddr" % sys.argv[0]) exit(1) client_ipaddr = sys.argv[1] # for each 9p protocol operation OpNames = ("_9P_TSTATFS", "_9P_TLOPEN", "_9P_TLCREATE", "_9P_TSYMLINK", "_9P_TMKNOD", "_9P_TRENAME", "_9P_TREADLINK", "_9P_TGETATTR", "_9P_TSETATTR", "_9P_TXATTRWALK", "_9P_TXATTRCREATE", "_9P_TREADDIR", "_9P_TFSYNC", "_9P_TLOCK", "_9P_TGETLOCK", "_9P_TLINK", "_9P_TMKDIR", "_9P_TRENAMEAT", "_9P_TUNLINKAT", "_9P_TVERSION", "_9P_TAUTH", "_9P_TATTACH", "_9P_TFLUSH", "_9P_TWALK", "_9P_TOPEN", "_9P_TCREATE", "_9P_TREAD", "_9P_TWRITE", "_9P_TCLUNK", "_9P_TREMOVE", "_9P_TSTAT", "_9P_TWSTAT") for opname in OpNames: opstats = ganesha_9pOpstats(client_ipaddr, opname) status = opstats[0] errmsg = opstats[1] if not status: print(errmsg) break total = opstats[3][0] if total != 0: print("%-16s\t%ld" % (opname, total)) sys.exit(0) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/export_stats_9pOps.py����������������������������������������0000775�0000000�0000000�00000005175�14737566223�0024402�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-3.0-or-later # # Copyright (C) 2014 Bull SAS # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Gregoire Pichon <gregoire.pichon@bull.net> from __future__ import print_function import sys # Create a system bus object. import dbus bus = dbus.SystemBus() # Create an object that will proxy for a particular remote object. try: admin = bus.get_object("org.ganesha.nfsd", "/org/ganesha/nfsd/ExportMgr") except: # catch *all* exceptions print("Error: Can't talk to ganesha service on d-bus. Looks like Ganesha is down") exit(1) # call method ganesha_9pOpstats = admin.get_dbus_method('Get9pOpStats', 'org.ganesha.nfsd.exportstats') # get parameters if len(sys.argv) != 2 or not sys.argv[1].isdigit(): print("Usage: %s export_id" % sys.argv[0]) exit(1) export_id = dbus.UInt16(sys.argv[1]) # for each 9p protocol operation OpNames = ("_9P_TSTATFS", "_9P_TLOPEN", "_9P_TLCREATE", "_9P_TSYMLINK", "_9P_TMKNOD", "_9P_TRENAME", "_9P_TREADLINK", "_9P_TGETATTR", "_9P_TSETATTR", "_9P_TXATTRWALK", "_9P_TXATTRCREATE", "_9P_TREADDIR", "_9P_TFSYNC", "_9P_TLOCK", "_9P_TGETLOCK", "_9P_TLINK", "_9P_TMKDIR", "_9P_TRENAMEAT", "_9P_TUNLINKAT", "_9P_TVERSION", "_9P_TAUTH", "_9P_TATTACH", "_9P_TFLUSH", "_9P_TWALK", "_9P_TOPEN", "_9P_TCREATE", "_9P_TREAD", "_9P_TWRITE", "_9P_TCLUNK", "_9P_TREMOVE", "_9P_TSTAT", "_9P_TWSTAT") for opname in OpNames: opstats = ganesha_9pOpstats(export_id, opname) status = opstats[0] errmsg = opstats[1] if not status: print(errmsg) break total = opstats[3][0] if total != 0: print("%-16s\t%ld" % (opname, total)) sys.exit(0) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/fake_recall.py�����������������������������������������������0000775�0000000�0000000�00000003207�14737566223�0022773�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-3.0-or-later # # Copyright (C) 2012 Panasas Inc. # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Jim Lieb <jlieb@panasas.com> from __future__ import print_function import getopt, sys import dbus def usage(): print("fake_recall <clientid>") def main(): try: args = getopt.getopt(sys.argv[1:], "c", []) if len(args) < 1: usage() sys.exit(2) clientid = args[0] print(clientid) bus = dbus.SystemBus() cbsim = bus.get_object("org.ganesha.nfsd", "/org/ganesha/nfsd/CBSIM") print(cbsim.Introspect()) # call method fake_recall = cbsim.get_dbus_method('fake_recall', 'org.ganesha.nfsd.cbsim') print(fake_recall(dbus.UInt64(clientid))) except getopt.GetoptError as err: print(str(err)) # will print something like "option -a not recognized" usage() sys.exit(2) if __name__ == "__main__": main() �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/ganesha-admin.py���������������������������������������������0000775�0000000�0000000�00000004600�14737566223�0023235�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: GPL-3.0-or-later # # ganesha-admin.py - commandline tool for admin of nfs-ganesha. # # Copyright (C) 2014 Panasas Inc. # # 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Jim Lieb <jlieb@panasas.com> #-*- coding: utf-8 -*- from __future__ import print_function import sys from PyQt5 import QtCore, QtDBus from PyQt5.QtGui import QApplication from dbus.mainloop.qt import DBusQtMainLoop from Ganesha.admin import AdminInterface SERVICE = 'org.ganesha.nfsd' class ServerAdmin(QtCore.QObject): show_status = QtCore.pyqtSignal(bool, str) def __init__(self, sysbus, parent=None): super(ServerAdmin, self).__init__() self.admin = AdminInterface(SERVICE, '/org/ganesha/nfsd/admin', sysbus, self.show_status) self.show_status.connect(self.status_message) def shutdown(self): self.admin.shutdown() print("Shutting down server.") def reload(self): self.admin.reload() print("Reload server configuration.") def grace(self, ipaddr): self.admin.grace(ipaddr) print("Start grace period.") def status_message(self, status, errormsg): print("Returns: status = %s, %s" % (str(status), errormsg)) sys.exit() # Main if __name__ == '__main__': app = QApplication(sys.argv) loop = DBusQtMainLoop(set_as_default=True) sysbus = QtDBus.QDBusConnection.systemBus() ganesha = ServerAdmin(sysbus) if sys.argv[1] == "shutdown": ganesha.shutdown() elif sys.argv[1] == "reload": ganesha.reload() elif sys.argv[1] == "grace": ganesha.grace(sys.argv[2]) else: print("Unknown/missing command") sys.exit() app.exec_() ��������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/ganesha_conf.man���������������������������������������������0000664�0000000�0000000�00000012211�14737566223�0023271�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.\" This is a comment .\" This is a comment .TH ganesha_conf 8 "21 Mar 2017" .SH NAME ganesha_conf \- Ganesha configuration editor .SH SYNOPSIS .B ganesha_conf set .I <block-description> .B [--key1 value1 [--key2 value2]...] .br .B ganesha_conf del .I <block-description> .B [--key1 [--key2 ]...] .SH DESCRIPTION .PP The .B set command adds or changes the given key value parameters in the block. It also creates the block if the block corresponding to the given .I block description (see section .B BLOCK DESCRIPTION below) is not present in the configuration. .PP The .B del command deletes given keys from the ganesha configuration block described by the block description. It will delete the block itself if no keys are provided. .SH BLOCK DESCRIPTION: .PP .I Block description is a list of block names and possibly some key value parameters that uniquely identify a ganesha configuration block. .PP NFS Ganesha configuration file contains a list of blocks. Each block starts with a block .B name followed by a .B left brace, then a list of .B key = value; entries. The block may optionally have .B sub blocks (note the recursive definition!). Finally, the block ends with a .B right brace. (Note that .B key = value; entries can come after a sub block as well, but we don't allow this with ganesha_conf editor! All key value entries should come before any sub blocks.) An example of a ganesha configuration block: .PP .in +4n .nf nfs_core_param { Nb_Worker = 256; Clustered = TRUE; NFS_Protocols = 3,4; } .fi .in .PP Since there should be only one .B nfs_core_param block, we just need the name of the block to uniquely identify it. So "nfs_core_param" would be its block description! .PP An example of a ganesha configuration block with a couple sub blocks: .PP .in +4n .nf log { default_log_level = EVENT; format { date_format = ISO-8601; time_format = ISO-8601; thread_name = TRUE; } components { all = EVENT; } } .fi .in .PP Ganesha configuration should have only one .B log block as well, so "log" would identify the log block. To identify .B format sub block inside the .B log block, "log format" would be the block description for the above .B format sub block. Similarly "log components" would be the block description for the above .B components sub block. .PP An .B export block is special in that there can be many export blocks, one for each export. A .B client block is also special. There can be many .B client blocks and they are always sub blocks inside .B export blocks. .I export_id key value uniquely identifies an .B export block. .I clients key value uniquely identifies a .B client block inside a given .B export block. .PP Here are couple .B export blocks with couple .B client blocks in them: .PP .in +4n .nf export { export_id = 1; path = /fs1/export1; pseudo = /fs1/export1; manage_gids = true; client { clients = 192.168.122.31; access_type = RW; } client { clients = *; access_type = RO; } } export { export_id = 2; path = /fs1/export2; pseudo = /fs1/export2; manage_gids = true; client { clients = 192.168.122.31; access_type = RW; } client { clients = 192.168.122.32; access_type = RO; } } .fi .in .PP To identify the correct .B export block, we need to supply its .I export_id. For example "export export_id 2" identifies the second .B export block above. .B export blocks can be uniquely identified by .I pseudo or .I path keys in some environments. One could specify "export path /fs1/export2" to identify the second .B export block as well. Similarly, a .B client block needs additional .I clients key value to identify the correct .B client block. For example, "export export_id 2 client clients 192.168.122.31" identifies the first .B client block in the second .B export block above! .SH EXAMPLES: 1. To change number of ganesha worker threads: .PP ganesha_conf set nfs_core_param --nb_worker 256 .PP 2. To change the date and time format of ganesha log messages: .PP ganesha_conf set log format --date_format ISO-8601 --time_format ISO-8601 .PP 3. Create an export and allow client with IP address 192.168.122.31 to be able to do read write and all other clients to do read only: .PP ganesha_conf set export path /fs1/export2 --export_id 2 --pseudo /fs1/export2 --manage_gids true .br ganesha_conf set export path /fs1/export2 client clients 192.168.122.31 --access_type RW .br ganesha_conf set export path /fs1/export2 client clients 192.168.122.32 --access_type RO .SH NOTES: .B ganesha_conf by default uses /etc/ganesha/ganesha.conf file as the configuration file. If your environment uses a different file (or set of files), you can use .B CONFFILE environment variable to override the default configuration file. For example, "CONFFILE=/etc/ganesha/ganesha.main.conf ganesha_conf set nfs_core_param --nb_worker 256" will use /etc/ganesha/ganesha.main.conf file for changing the worker threads. .PP .B ganesha_conf can't handle comments within a block at this point. .PP Neither block descriptions nor key value parameters are verified to be valid ganesha configuration blocks or parameter values currently. ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/ganesha_conf.py����������������������������������������������0000775�0000000�0000000�00000010321�14737566223�0023151�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-3.0-or-later # # Copyright (C) 2017 IBM # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Malahal Naineni <malahal@us.ibm.com> import os, sys from Ganesha.config_editor import BLOCK, ArgError import logging, pprint # Modify the file with the given data atomically def modify_file(filename, data): from tempfile import NamedTemporaryFile tmp = NamedTemporaryFile(dir=os.path.dirname(filename), delete=False) tmp.write(data) tmp.flush() os.fsync(tmp.fileno()) # If filename exists, get its stats and apply them to the temp file try: stat = os.stat(filename) os.chown(tmp.name, stat.st_uid, stat.st_gid) os.chmod(tmp.name, stat.st_mode) except: pass os.rename(tmp.name, filename) # Get block names and key value options as separate lists def get_blocks(args): key_found = False for i, arg in enumerate(args): if arg.startswith("--"): key_found = True break if key_found: return (args[0:i], args[i:]) return (args, []) usage = """ Usage: %s set <block-descriptor> [--param value] %s del <block-descriptor> [--param] where <block-descriptor> is a list of blocknames with possible key value pair identifying the block. For example, %s set log --default_log_level DEBUG %s set log components --FSAL FULL_DEBUG %s set export export_id 14 --pseudo "/nfsroot/export1" %s set export export_id 14 client clients '*' --manage-gids true %s get mdcache %s get mdcache --Dir_Max """ % (8 * (sys.argv[0],)) if len(sys.argv) < 2: sys.exit(usage) FORMAT = "[%(filename)s:%(lineno)d-%(funcName)s()] %(message)s" #logging.basicConfig(format=FORMAT, level=logging.DEBUG) opcode = sys.argv[1] if opcode == "set": (names, pairs) = get_blocks(sys.argv[2:]) if not names: sys.exit("no block names") if len(pairs) % 2 != 0: sys.exit("odd number for key value pairs") for key in pairs[::2]: if not key.startswith("--"): sys.exit("some keys are not with -- prefix") # remove "--" prefix of keys keys = [key[2:] for key in pairs[::2]] values = [value for value in pairs[1::2]] opairs = zip(keys, values) block = BLOCK(names) elif opcode == "get": (names, pairs) = get_blocks(sys.argv[2:]) if not names: sys.exit("no block names") if len(pairs) > 1: sys.exit("only zero or one value for get operations") for key in pairs[::2]: if not key.startswith("--"): sys.exit("some keys are not with -- prefix") # remove "--" prefix of keys keys = [key[2:] for key in pairs[::2]] block = BLOCK(names) elif opcode == "del": (names, keys) = get_blocks(sys.argv[2:]) if not names: sys.exit("no block names") for key in keys: if not key.startswith("--"): sys.exit("some key not with -- prefix") # remove "--" prefix of keys keys = [key[2:] for key in keys] if not keys: keys = [] block = BLOCK(names) else: sys.exit(usage) conffile = os.environ.get("CONFFILE", "/etc/ganesha/ganesha.conf") old = open(conffile).read() try: if opcode == "set": logging.debug("opairs: %s", pprint.pformat(opairs)) new = block.set_keys(old, opairs) elif opcode == "get": logging.debug("keys: %s", pprint.pformat(keys)) value = block.get_keys(old, keys) sys.exit(value) else: logging.debug("keys: %s", pprint.pformat(keys)) new = block.del_keys(old, keys) except ArgError as e: sys.exit(e.error) modify_file(conffile, new) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/ganesha_logrotate_mgr.py�������������������������������������0000664�0000000�0000000�00000027701�14737566223�0025100�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright IBM Corporation, 2024 # Contributor: Prabhu Murugesan <prabhu.murugesan1@ibm.com> # # # This software is a server that implements the NFS protocol. # # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # # ganesha_logrotate_mgr - A script to manage log rotation for Ganesha logs. # # This script provides functionalities to enable, disable, list, and # change log rotation configurations for Ganesha logs. It also allows # setting a crontab entry to manage log rotation automatically. # The script supports specifying different FSAL types # to determine # the log file location and provides default values for size and # rotation settings. import os import sys import subprocess import platform import shutil # Constants for configuration files and default values LOGROTATE_CONFIG_FILE = "/etc/logrotate.d/ganesha" DEFAULT_LOGROTATE_CONFIG_FILE = "/etc/logrotate.d/ganesha.default" DEFAULT_CRONTAB_ENTRY = "*/1 * * * * /usr/sbin/logrotate /etc/logrotate.d/ganesha" CRONTAB_CMD = "crontab -l | grep -v '/etc/logrotate.d/ganesha' | crontab -" DEFAULT_SIZE_GB = 2 DEFAULT_ROTATE = 10 DEFAULT_FSAL_LOG_PATH = "/var/log/ganesha/nfs-ganesha.log" FSAL_LOG_PATHS = { "gpfs": "/var/log/ganesha.log", "nfs": "/var/log/ganesha/nfs-ganesha.log", # Add more FSAL types if needed } class LogRotateManager: """ Manages Ganesha log rotation: enabling, disabling, updating, and listing configs. """ def __init__(self, fsal=None): """ Initialize with FSAL type. :param fsal: FSAL type for log file path (default is None). """ self.os_type = self.get_os_type() self.fsal = fsal.lower() if fsal else None self.log_file_path = FSAL_LOG_PATHS.get(self.fsal, DEFAULT_FSAL_LOG_PATH) def get_os_type(self): """ Get OS type (Ubuntu, RHEL, or Other). :return: OS type as a string. """ try: # Check for /etc/os-release if os.path.isfile('/etc/os-release'): with open('/etc/os-release') as f: content = f.read().lower() if 'ubuntu' in content: return 'ubuntu' elif 'centos' in content or 'red hat' in content or 'rhel' in content: return 'rhel' # Check for /etc/*release files if /etc/os-release is not present for release_file in ['/etc/redhat-release', '/etc/centos-release', '/etc/lsb-release']: if os.path.isfile(release_file): with open(release_file) as f: content = f.read().lower() if 'ubuntu' in content: return 'ubuntu' elif 'centos' in content or 'red hat' in content or 'rhel' in content: return 'rhel' except Exception: pass return 'other' def is_crontab_entry_present(self): """ Check if the crontab entry exists. :return: True if present, otherwise False. """ result = subprocess.run(["crontab", "-l"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) return "/etc/logrotate.d/ganesha" in result.stdout def backup_default_config(self): """ Backup the current logrotate config. """ if os.path.exists(LOGROTATE_CONFIG_FILE): shutil.copy(LOGROTATE_CONFIG_FILE, DEFAULT_LOGROTATE_CONFIG_FILE) else: print(f"Warning: {LOGROTATE_CONFIG_FILE} not found. Cannot backup.") def restore_default_config(self): """ Restore the default logrotate config. """ if os.path.exists(DEFAULT_LOGROTATE_CONFIG_FILE): shutil.copy(DEFAULT_LOGROTATE_CONFIG_FILE, LOGROTATE_CONFIG_FILE) else: print(f"Warning: {DEFAULT_LOGROTATE_CONFIG_FILE} not found. Cannot restore.") def add_crontab_entry(self, entry): """ Add a crontab entry. :param entry: Crontab entry to add. """ subprocess.run(f'(crontab -l 2>/dev/null; echo "{entry}") | crontab -', shell=True) def remove_crontab_entry(self): """ Remove the crontab entry. """ subprocess.run(CRONTAB_CMD, shell=True) def list_config(self): """ List current logrotate config and crontab entry. """ if self.is_crontab_entry_present(): print(f"Ganesha Logrotate Configuration ({LOGROTATE_CONFIG_FILE}):") with open(LOGROTATE_CONFIG_FILE, 'r') as file: print(file.read()) print("\nGanesha Crontab Entry:") result = subprocess.run(["crontab", "-l"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) entries = [line for line in result.stdout.splitlines() if "/etc/logrotate.d/ganesha" in line] print("\n".join(entries) if entries else "No crontab entry found.") else: print("Ganesha logrotate is not enabled.") def generate_logrotate_config(self, size_gb, rotate_count): """ Generate a new logrotate config. :param size_gb: Log file size in GB. :param rotate_count: Number of rotations. """ config = f""" {self.log_file_path} {{ size {size_gb}G rotate {rotate_count} copytruncate dateformat -%Y%m%d%H%M%S compress missingok }} """ if self.os_type == "ubuntu": config = config.replace("{", "{\n su root syslog\n") with open(LOGROTATE_CONFIG_FILE, "w") as file: file.write(config) def update_logrotate_config(self, size_gb, rotate_count): """ Update existing logrotate config with new size and rotate values. :param size_gb: New log file size in GB. :param rotate_count: New number of rotations. """ if not os.path.exists(LOGROTATE_CONFIG_FILE): print(f"Error: {LOGROTATE_CONFIG_FILE} not found. Cannot update.") sys.exit(1) with open(LOGROTATE_CONFIG_FILE, 'r') as file: config_lines = file.readlines() new_config_lines = [] for line in config_lines: if line.strip().startswith("size"): new_config_lines.append(f" size {size_gb}G\n") elif line.strip().startswith("rotate"): new_config_lines.append(f" rotate {rotate_count}\n") else: new_config_lines.append(line) with open(LOGROTATE_CONFIG_FILE, 'w') as file: file.writelines(new_config_lines) def change_config(self, size=DEFAULT_SIZE_GB, rotate=DEFAULT_ROTATE): """ Change logrotate config if enabled. :param size: New size in GB. :param rotate: New rotate count. """ if not self.is_crontab_entry_present(): print("Error: Ganesha logrotate is not enabled. Enable it first.") sys.exit(1) self.update_logrotate_config(size, rotate) self.restart_cron_service() print("Ganesha logrotate configuration updated.") def restart_cron_service(self): """ Restart cron service based on OS type. """ if self.os_type == "ubuntu": subprocess.run(["systemctl", "restart", "cron"], check=True) else: subprocess.run(["systemctl", "restart", "crond"], check=True) def enable(self): """ Enable log rotation: backup config, generate new config, and add crontab entry. """ if not self.is_crontab_entry_present(): self.backup_default_config() self.generate_logrotate_config(DEFAULT_SIZE_GB, DEFAULT_ROTATE) self.add_crontab_entry(DEFAULT_CRONTAB_ENTRY) print("Ganesha logrotate enabled.") else: print("Ganesha logrotate is already enabled.") def disable(self): """ Disable log rotation: remove crontab entry and restore default config. """ if self.is_crontab_entry_present(): self.remove_crontab_entry() self.restore_default_config() print("Ganesha logrotate removed.") else: print("Ganesha logrotate is not enabled.") def set_crontab(self, new_entry): """ Set a new crontab entry and force logrotate. :param new_entry: New crontab entry. """ if self.is_crontab_entry_present(): self.remove_crontab_entry() self.add_crontab_entry(new_entry) subprocess.run("/usr/sbin/logrotate -f /etc/logrotate.d/ganesha", shell=True) print("Crontab entry updated and logrotate forced.") def show_help(): """ Show the help text for script usage. """ help_text = """ Usage: ganesha_logrotate_mgr <enable|disable|list|change|set-crontab> [options] Commands: enable Enable log rotation. Options: fsal=<type> FSAL type (optional, default uses /var/log/ganesha/nfs-ganesha.log). disable Disable log rotation. list List current log rotation configuration and crontab entry. change [options] Change log rotation configuration. Options: size=<int> Log file size in GB (default is 2GB). rotate=<int> Number of rotations (default is 10). set-crontab <entry> Set a new crontab entry. Options: <entry> Crontab entry to set. Examples: ganesha_logrotate_mgr enable ganesha_logrotate_mgr enable fsal=gpfs ganesha_logrotate_mgr disable ganesha_logrotate_mgr list ganesha_logrotate_mgr change size=5 rotate=10 ganesha_logrotate_mgr set-crontab "*/5 * * * * /usr/sbin/logrotate /etc/logrotate.d/ganesha" """ print(help_text) def main(): """ Main function to handle command-line arguments and invoke the appropriate actions. """ if len(sys.argv) < 2: show_help() sys.exit(1) action = sys.argv[1].lower() fsal = None if action == "enable": if len(sys.argv) > 2 and 'fsal=' in sys.argv[2]: fsal = sys.argv[2].split('=')[1] manager = LogRotateManager(fsal=fsal) manager.enable() elif action == "disable": manager = LogRotateManager() manager.disable() elif action == "list": manager = LogRotateManager() manager.list_config() elif action == "change": size = DEFAULT_SIZE_GB rotate = DEFAULT_ROTATE for arg in sys.argv[2:]: if arg.startswith("size="): size = int(arg.split('=')[1]) elif arg.startswith("rotate="): rotate = int(arg.split('=')[1]) manager = LogRotateManager() manager.change_config(size=size, rotate=rotate) elif action == "set-crontab": if len(sys.argv) < 3: print("Error: Crontab entry is required.") show_help() sys.exit(1) new_entry = sys.argv[2] manager = LogRotateManager() manager.set_crontab(new_entry) else: show_help() sys.exit(1) if __name__ == "__main__": main() ���������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/ganesha_mgr.py�����������������������������������������������0000664�0000000�0000000�00000054103�14737566223�0023014�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: GPL-2.0-or-later # # ganesha_mgr.py - commandline tool for managing nfs-ganesha. # # Copyright (C) 2014 IBM. # # 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, see <http://www.gnu.org/licenses/>. # # Author: Allison Henderson <achender@vnet.linux.ibm.com> #-*- coding: utf-8 -*- from __future__ import print_function import sys import time from Ganesha.ganesha_mgr_utils import ClientMgr from Ganesha.ganesha_mgr_utils import ExportMgr from Ganesha.ganesha_mgr_utils import AdminInterface from Ganesha.ganesha_mgr_utils import LogManager from Ganesha.ganesha_mgr_utils import CacheMgr SERVICE = 'org.ganesha.nfsd' class ManageClients(): def __init__(self, parent=None): self.clientmgr = ClientMgr(SERVICE, '/org/ganesha/nfsd/ClientMgr', 'org.ganesha.nfsd.clientmgr') def status_message(self, status, errormsg): print("Returns: status = %s, %s" % (str(status), errormsg)) def addclient(self, ipaddr): print("Add a client %s" % (ipaddr)) status, errormsg = self.clientmgr.AddClient(ipaddr) self.status_message(status, errormsg) def removeclient(self, ipaddr): print("Remove a client %s" % (ipaddr)) status, errormsg = self.clientmgr.RemoveClient(ipaddr) self.status_message(status, errormsg) def showclients(self): print("Show clients") status, errormsg, reply = self.clientmgr.ShowClients() if status == True: _ts = reply[0] clients = reply[1] self.proc_clients(_ts, clients) else: self.status_message(status, errormsg) def proc_clients(self, _ts, clients): print("Timestamp: ", time.ctime(_ts[0]), _ts[1], " nsecs") if len(clients) == 0: print("No clients") else: print("Clients:") print(" IP addr, nfsv3, mnt, nlm4, rquota,nfsv40, nfsv41, nfsv42, 9p, last") for client in clients: print(" %s, %s, %s, %s, %s, %s, %s, %s, %s, %s %d nsecs" % (client.ClientIP, client.HasNFSv3, client.HasMNT, client.HasNLM4, client.HasRQUOTA, client.HasNFSv40, client.HasNFSv41, client.HasNFSv42, client.Has9P, time.ctime(client.LastTime[0]), client.LastTime[1])) class ShowExports(): def __init__(self, parent=None): self.exportmgr = ExportMgr(SERVICE, '/org/ganesha/nfsd/ExportMgr', 'org.ganesha.nfsd.exportmgr') def showexports(self): print("Show exports") status, msg, reply = self.exportmgr.ShowExports() if status == True: _ts = reply[0] exports = reply[1] self.proc_exports(_ts, exports) else: self.status_message(status, msg) def addexport(self, conf_path, exp_expr): print("Add Export in %s" % conf_path) status, msg = self.exportmgr.AddExport(conf_path, exp_expr) self.status_message(status, msg) def removeexport(self, exp_id): print("Remove Export with id %d" % int(exp_id)) self.exportmgr.RemoveExport(exp_id) def updateexport(self, conf_path, exp_expr): print("Update Export in %s" % conf_path) status, msg = self.exportmgr.UpdateExport(conf_path, exp_expr) self.status_message(status, msg) def displayexport(self, exp_id): print("Display export with id %d" % int(exp_id)) status, msg, reply = self.exportmgr.DisplayExport(exp_id) if status == True: _id = reply[0] path = reply[1] pseudo = reply[2] tag = reply[3] clients = reply[4] self.proc_export(_id, path, pseudo, tag, clients) else: self.status_message(status, msg) def proc_export(self, _id, path, pseudo, tag, clients): print("export %d: path = %s, pseudo = %s, tag = %s" %\ (_id, path, pseudo, tag)) print(" Client type, CIDR version, CIDR address, CIDR mask, " +\ "CIDR proto, Anonymous UID, Anonymous GID, " +\ "Attribute timeout, Options, Set") for client in clients: print(" %s, %d, %d, %d, %d, %d, %d, %d, %d, %d" % (client.Client_type, client.CIDR_version, client.CIDR_address, client.CIDR_mask, client.CIDR_proto, client.Anonymous_uid, client.Anonymous_gid, client.Expire_time_attr, client.Options, client.Set)) def proc_exports(self, _ts, exports): print("Timestamp: ", time.ctime(_ts[0]), _ts[1], " nsecs") if len(exports) == 0: print("No exports") else: print("Exports:") print(" Id, path, nfsv3, mnt, nlm4, rquota,nfsv40, nfsv41, nfsv42, 9p, last") for export in exports: print(" %d, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %d nsecs" % (export.ExportID, export.ExportPath, export.HasNFSv3, export.HasMNT, export.HasNLM4, export.HasRQUOTA, export.HasNFSv40, export.HasNFSv41, export.HasNFSv42, export.Has9P, time.ctime(export.LastTime[0]), export.LastTime[1])) def status_message(self, status, errormsg): print("Returns: status = %s, %s" % (str(status), errormsg)) class ServerAdmin(): def __init__(self, parent=None): self.admin = AdminInterface(SERVICE, '/org/ganesha/nfsd/admin', 'org.ganesha.nfsd.admin') def shutdown(self): print("Shutting down server.") status, msg = self.admin.shutdown() self.status_message(status, msg) def grace(self, ipaddr): print("Start grace period.") status, msg = self.admin.grace(ipaddr) self.status_message(status, msg) def purge_netgroups(self): print("Purging netgroups cache") status, msg = self.admin.purge_netgroups() self.status_message(status, msg) def purge_idmap(self): print("Purging idmapper cache") status, msg = self.admin.purge_idmap() self.status_message(status, msg) def purge_gids(self): print("Purging gids cache") status, msg = self.admin.purge_gids() self.status_message(status, msg) def show_version(self): status, msg, versions = self.admin.GetAll() if status: print("NFS-Ganesha Release = V{}".format(versions['VERSION_RELEASE'])) try: print("ganesha compiled on {} at {}".format(versions['VERSION_COMPILE_DATE'], versions['VERSION_COMPILE_TIME'])) print("Release comment = {}".format(versions['VERSION_COMMENT'])) print("Git HEAD = {}".format(versions['VERSION_GIT_HEAD'])) print("Git Describe = {}".format(versions['VERSION_GIT_DESCRIBE'])) except KeyError: pass else: self.status_message(status, msg) def trim_enable(self): status, msg = self.admin.trim_enable() self.status_message(status, msg) def trim_disable(self): status, msg = self.admin.trim_disable() self.status_message(status, msg) def trim_call(self): status, msg = self.admin.trim_call() self.status_message(status, msg) def trim_status(self): status, msg = self.admin.trim_status() self.status_message(status, msg) def status_message(self, status, errormsg): print("Returns: status = %s, %s" % (str(status), errormsg)) class ManageCache(): def __init__(self, parent=None): self.cachemgr = CacheMgr(SERVICE, '/org/ganesha/nfsd/CacheMgr', 'org.ganesha.nfsd.cachemgr') def status_message(self, status, errormsg): print("Returns: status = %s, %s" % (str(status), errormsg)) def showfs(self): print("Show filesystems") status, errormsg, reply = self.cachemgr.ShowFileSys() if status == True: _ts = reply[0] fss = reply[1] self.proc_fs(_ts, fss) else: self.status_message(status, errormsg) def proc_fs(self, _ts, fss): print("Timestamp: ", time.ctime(_ts[0]), _ts[1], " nsecs") if len(fss) == 0: print("No filesystems") else: print("Filesystems:") print(" Path, MajorDevId, MinorDevId") for _fs in fss: print(" %s, %s, %s" % (_fs.Path, _fs.MajorDevId, _fs.MinorDevId)) def showidmapper(self): print("Show idmapper cache") status, errormsg, reply = self.cachemgr.ShowIdmapper() if status == True: _ts = reply[0] ids = reply[1] self.proc_id(_ts, ids) else: self.status_message(status, errormsg) def proc_id(self, _ts, ids): print("Timestamp: ", time.ctime(_ts[0]), _ts[1], " nsecs") if len(ids) == 0: print("No entries in idmapper cache") else: print("Idmapper cache:") print(" Name, UID, GID") for entry in ids: if entry.HasGID == True: print(" %s, %s, %s" % (entry.Name, entry.UID, entry.GID)) else: print(" %s, %s, -" % (entry.Name, entry.UID)) class ManageLogs(): def __init__(self, parent=None): self.logmgr = LogManager(SERVICE, '/org/ganesha/nfsd/admin', 'org.freedesktop.DBus.Properties') def set(self, prop, value): print("Set log %s to %s" % (prop, value)) status, msg = self.logmgr.Set(prop, value) self.status_message(status, msg) def get(self, prop): print("Get property %s" % (prop)) status, msg, level = self.logmgr.Get(prop) if status == True: self.show_loglevel(level) else: self.status_message(status, msg) def getall(self): print("Get all") status, msg, properties = self.logmgr.GetAll() if status == True: self.print_components(properties) else: self.status_message(status, msg) def show_loglevel(self, level): print("Log level: %s"% (str(level))) def status_message(self, status, errormsg): print("Returns: status = %s, %s" % (str(status), errormsg)) def print_components(self, properties): for prop in properties: print(str(prop)) # Main if __name__ == '__main__': exportmgr = ShowExports() clientmgr = ManageClients() ganesha = ServerAdmin() logmgr = ManageLogs() cachemgr = ManageCache() USAGE = \ "\nganesha_mgr.py command [OPTIONS]\n\n" \ "COMMANDS\n\n" \ " add: \n" \ " client ipaddr: Adds the client with the given IP\n" \ " export conf expr:\n" \ " Adds an export from the given config file that contains\n" \ " the given expression\n" \ " Example: \n" \ " add export /etc/ganesha/gpfs.conf \"EXPORT(Export_ID=77)\"\n\n" \ " remove: \n" \ " client ipaddr: Removes the client with the given IP\n" \ " export export_id: Removes the export with the given id \n\n" \ " update: \n" \ " export conf expr:\n" \ " Updates an export from the given config file that contains\n" \ " the given expression\n" \ " Example: \n" \ " update export /etc/ganesha/gpfs.conf \"EXPORT(Export_ID=77)\"\n\n"\ " display: \n" \ " export export_id: Displays the export with the given ID.\n" \ " export_id must be positive number\n" \ " Example: \n" \ " display export 10 \n\n"\ " purge: \n" \ " netgroups: Purges netgroups cache\n" \ " idmap: Purges idmapper cache\n" \ " gids: Purges gids cache\n\n" \ " show :\n" \ " clients: Displays the current clients\n" \ " version: Displays ganesha release information\n" \ " posix_fs: Displays the mounted POSIX filesystems\n" \ " exports: Displays all current exports\n" \ " idmap: Displays the idmapper cache\n\n" \ " grace: \n" \ " ipaddr: Begins grace for the given IP\n\n" \ " trim:\n" \ " enable: Enable malloc trim\n" \ " disable: Disable malloc trim\n" \ " call: Call malloc trim\n" \ " status: Get current malloc trim status\n\n" \ " get: \n" \ " log component: Gets the log level for the given component\n\n" \ " set: \n" \ " log component level: \n" \ " Sets the given log level to the given component\n\n" \ " getall: \n" \ " logs: Prints all log components\n\n" \ " shutdown: Shuts down the ganesha nfs server\n\n" if len(sys.argv) < 2: print("Too few arguments."\ " Try \"ganesha_mgr.py help\" for more info") sys.exit(1) # add elif sys.argv[1] == "add": if len(sys.argv) == 2: msg = 'add requires client or export option. ' msg += 'Try "ganesha_mgr.py help" for more info' sys.exit(msg) if sys.argv[2] == "client": if len(sys.argv) < 4: print("add client requires an IP."\ " Try \"ganesha_mgr.py help\" for more info") sys.exit(1) clientmgr.addclient(sys.argv[3]) elif sys.argv[2] == "export": if len(sys.argv) < 5: print("add export requires a config file and an expression."\ " Try \"ganesha_mgr.py help\" for more info") sys.exit(1) exportmgr.addexport(sys.argv[3], sys.argv[4]) else: msg = "Adding '%s' is not supported" % sys.argv[2] sys.exit(msg) # remove elif sys.argv[1] == "remove": if len(sys.argv) == 2: msg = 'remove requires client or export option. ' msg += 'Try "ganesha_mgr.py help" for more info' sys.exit(msg) if sys.argv[2] == "client": if len(sys.argv) < 4: print("remove client requires an IP."\ " Try \"ganesha_mgr.py help\" for more info") sys.exit(1) clientmgr.removeclient(sys.argv[3]) elif sys.argv[2] == "export": if len(sys.argv) < 4: print("remove export requires an export ID."\ " Try \"ganesha_mgr.py help\" for more info") sys.exit(1) exportmgr.removeexport(sys.argv[3]) else: msg = "Removing '%s' is not supported" % sys.argv[2] sys.exit(msg) # update elif sys.argv[1] == "update": if len(sys.argv) < 5: msg = 'update export requires a config file and an expression. ' msg += 'Try "ganesha_mgr.py help" for more info' sys.exit(msg) if sys.argv[2] == "export": exportmgr.updateexport(sys.argv[3], sys.argv[4]) else: msg = "Updating '%s' is not supported" % sys.argv[2] sys.exit(msg) # display elif sys.argv[1] == "display": if len(sys.argv) < 4: msg = 'display export requires an export ID. ' msg += 'Try "ganesha_mgr.py help" for more info' sys.exit(msg) elif sys.argv[3].isdigit() == False: msg = 'export ID must be positive number. ' msg += 'Try "ganesha_mgr.py help" for more info' sys.exit(msg) if sys.argv[2] == "export": exportmgr.displayexport(sys.argv[3]) else: msg = "Displaying '%s' is not supported" % sys.argv[2] sys.exit(msg) # purge elif sys.argv[1] == "purge": if len(sys.argv) < 3: msg = 'purge requires a cache name to purge, ' msg += 'Try "ganesha_mgr.py help" for more info' sys.exit(msg) if sys.argv[2] == "netgroups": ganesha.purge_netgroups() elif sys.argv[2] == "idmap": ganesha.purge_idmap() elif sys.argv[2] == "gids": ganesha.purge_gids() else: msg = "Purging '%s' is not supported" % sys.argv[2] sys.exit(msg) # show elif sys.argv[1] == "show": if len(sys.argv) < 3: msg = 'show requires an option, ' msg += 'Try "ganesha_mgr.py help" for more info' sys.exit(msg) if sys.argv[2] == "clients": clientmgr.showclients() elif sys.argv[2] == "version": ganesha.show_version() elif sys.argv[2] == "exports": exportmgr.showexports() elif sys.argv[2] == "posix_fs": cachemgr.showfs() elif sys.argv[2] == "idmap": cachemgr.showidmapper() else: msg = "Showing '%s' is not supported" % sys.argv[2] sys.exit(msg) # grace elif sys.argv[1] == "grace": if len(sys.argv) < 3: print("grace requires an IP."\ " Try \"ganesha_mgr.py help\" for more info") sys.exit(1) ganesha.grace(sys.argv[2]) #malloc trim elif sys.argv[1] == "trim": if len(sys.argv) < 3: print("trim requires enable/disable/call/status arg. " "Try \"ganesha_mgr.py help\" for more info") sys.exit(1) if sys.argv[2] == 'enable': ganesha.trim_enable() elif sys.argv[2] == 'disable': ganesha.trim_disable() elif sys.argv[2] == 'status': ganesha.trim_status() elif sys.argv[2] == 'call': ganesha.trim_call() else: msg = "trim '%s' is unknown" % sys.argv[2] sys.exit(msg) # set elif sys.argv[1] == "set": if len(sys.argv) < 5: msg = 'set log requires a component and a log level. ' msg += 'Try "ganesha_mgr.py help" for more info' sys.exit(msg) if sys.argv[2] == "log": logmgr.set(sys.argv[3], sys.argv[4]) else: msg = "Setting '%s' is not supported" % sys.argv[2] sys.exit(msg) # get elif sys.argv[1] == "get": if len(sys.argv) < 4: msg = 'get log requires a component. ' msg += 'Try "ganesha_mgr.py help" for more info' sys.exit(msg) if sys.argv[2] == "log": logmgr.get(sys.argv[3]) else: msg = "Getting '%s' is not supported" % sys.argv[2] sys.exit(msg) # getall elif sys.argv[1] == "getall": if len(sys.argv) < 3: msg = 'getall requires a component. ' msg += 'Try "ganesha_mgr.py help" for more info' sys.exit(msg) if sys.argv[2] == "logs": logmgr.getall() else: msg = "Getting all '%s' is not supported" % sys.argv[2] sys.exit(msg) # others elif sys.argv[1] == "shutdown": ganesha.shutdown() elif sys.argv[1] == "help": print(USAGE) else: print("Unknown/missing command."\ " Try \"ganesha_mgr.py help\" for more info") �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/ganesha_stats.py���������������������������������������������0000775�0000000�0000000�00000015532�14737566223�0023373�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-3.0-or-later # # Copyright (C) 2014 IBM # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Marc Eshel <eshel@us.ibm.com> # # This command receives statistics from Ganesha over DBus. The format # for a command is: # # ganesha_stats <subcommand> <args> # # ganesha_stats help # To get detailed help # from __future__ import print_function import sys import Ganesha.glib_dbus_stats import dbus def print_usage_exit(return_code): message = ( """ Usage: Command displays global stats by default. To display current status regarding stat counting use: {progname} status To display stat counters use: {progname} [list_clients | deleg <ip address> inode | iov3 [export id] | iov4 [export id] | iov41 [export id] | iov42 [export id] | iomon [export id] | export | total [export id] | fast | pnfs [export id] | fsal <fsal name> | v3_full | v4_full | auth | client_io_ops <ip address> | export_details <export id> | client_all_ops <ip address>] To display stat counters in json format use: {progname} json <command> To reset stat counters use: {progname} reset To enable/disable stat counters use: {progname} [enable | disable] [all | nfs | fsal | v3_full | v4_full | auth | client_all_ops] """ ) print(message.format(progname=sys.argv[0])) sys.exit(return_code) DEFAULT_COMMAND = 'global' output_json = False if (len(sys.argv) < 2): command = DEFAULT_COMMAND else: command = sys.argv[1] opts = sys.argv[2:] if command == 'json': output_json = True if len(opts) >= 1: command = opts[0] opts = opts[1:] else: command = DEFAULT_COMMAND json_not_available = ('fsal', 'reset', 'enable', 'disable') if output_json and command in json_not_available: sys.exit("{0} command is not available in json.".format(command)) # check arguments commands = ( 'help', 'list_clients', 'deleg', 'global', 'inode', 'iov3', 'iov4', 'iov41', 'iov42', 'iomon', 'export', 'total', 'fast', 'pnfs', 'fsal', 'reset', 'enable', 'disable', 'status', 'v3_full', 'v4_full', 'auth', 'client_io_ops', 'export_details', 'client_all_ops', 'json' ) if command not in commands: print("\nError: Option '%s' is not correct." % command) print_usage_exit(1) # requires an IP address elif command in ('deleg', 'client_io_ops', 'client_all_ops'): if not len(opts) == 1: print("\nError: Option '%s' must be followed by an ip address." % command) print_usage_exit(1) command_arg = opts[0] # requires an export id elif command == 'export_details': if not len(opts) == 1: print("\nError: Option '%s' must be followed by an export id." % command) print_usage_exit(1) if opts[0].isdigit(): command_arg = int(opts[0]) else: print("\nError: Argument '%s' must be numeric." % opts[0]) print_usage_exit(1) # optionally accepts an export id elif command in ('iov3', 'iov4', 'iov41', 'iov42', 'iomon', 'total', 'pnfs'): if (len(opts) == 0): command_arg = -1 elif (len(opts) == 1) and opts[0].isdigit(): command_arg = int(opts[0]) else: print_usage_exit(1) # requires fsal name elif command in ('fsal'): if not len(opts) == 1: print("\nError: Option '%s' must be followed by fsal name." % command) print_usage_exit(1) command_arg = opts[0] elif command in ('enable', 'disable'): if not len(opts) == 1: print("\nError: Option '%s' must be followed by all/nfs/fsal/v3_full/v4_full/auth/client_all_ops" % command) print_usage_exit(1) command_arg = opts[0] if command_arg not in ('all', 'nfs', 'fsal', 'v3_full', 'v4_full', 'auth', 'client_all_ops'): print("\nError: Option '%s' must be followed by all/nfs/fsal/v3_full/v4_full/auth/client_all_ops" % command) print_usage_exit(1) elif command == "help": print_usage_exit(0) # retrieve and print stats try: exp_interface = Ganesha.glib_dbus_stats.RetrieveExportStats() cl_interface = Ganesha.glib_dbus_stats.RetrieveClientStats() result = None if command == "global": result = exp_interface.global_stats() elif command == "export": result = exp_interface.export_stats() elif command == "inode": result = exp_interface.inode_stats() elif command == "fast": result = exp_interface.fast_stats() elif command == "list_clients": result = cl_interface.list_clients() elif command == "deleg": result = cl_interface.deleg_stats(command_arg) elif command == "client_io_ops": result = cl_interface.client_io_ops_stats(command_arg) elif command == "client_all_ops": result = cl_interface.client_all_ops_stats(command_arg) elif command == "iov3": result = exp_interface.v3io_stats(command_arg) elif command == "iov4": result = exp_interface.v4io_stats(command_arg) elif command == "iov41": result = exp_interface.v41io_stats(command_arg) elif command == "iov42": result = exp_interface.v42io_stats(command_arg) elif command == "iomon": result = exp_interface.iomon_stats(command_arg) elif command == "total": result = exp_interface.total_stats(command_arg) elif command == "export_details": result = exp_interface.export_details_stats(command_arg) elif command == "pnfs": result = exp_interface.pnfs_stats(command_arg) elif command == "reset": result = exp_interface.reset_stats() elif command == "fsal": result = exp_interface.fsal_stats(command_arg) elif command == "v3_full": result = exp_interface.v3_full_stats() elif command == "v4_full": result = exp_interface.v4_full_stats() elif command == "auth": result = exp_interface.auth_stats() elif command == "enable": result = exp_interface.enable_stats(command_arg) elif command == "disable": result = exp_interface.disable_stats(command_arg) elif command == "status": result = exp_interface.status_stats() print(result.json()) if output_json else print(result) except dbus.exceptions.DBusException: sys.exit("Error: Can't talk to ganesha service on d-bus. Looks like Ganesha is down") ����������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/ganeshactl.py������������������������������������������������0000664�0000000�0000000�00000015642�14737566223�0022657�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: GPL-3.0-or-later # # ganeshactl.py - PyQt4 GUI tool for admin of nfs-ganesha. # # Copyright (C) 2014 Panasas Inc. # # 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Jim Lieb <jlieb@panasas.com> #-*- coding: utf-8 -*- """ NFS Ganesha administration tool """ import sys from PyQt5 import QtCore, QtGui, QtDBus from Ganesha.QtUI.ui_main_window import Ui_MainWindow from Ganesha.admin import AdminInterface from Ganesha.export_mgr import ExportMgr from Ganesha.QtUI.exports_table import ExportTableModel from Ganesha.client_mgr import ClientMgr from Ganesha.QtUI.clients_table import ClientTableModel from Ganesha.log_mgr import LogManager from Ganesha.QtUI.log_settings import LogSetDialog SERVICE = 'org.ganesha.nfsd' class MainWindow(QtGui.QMainWindow): show_status = QtCore.pyqtSignal(bool, str) def __init__(self, sysbus, parent=None): QtGui.QWidget.__init__(self, parent) # Init ourself from the Designer UI self.ui = Ui_MainWindow() self.ui.setupUi(self) # light up the admin interface self.admin = AdminInterface(SERVICE, '/org/ganesha/nfsd/admin', sysbus, self.show_status) self.exportmgr = ExportMgr(SERVICE, '/org/ganesha/nfsd/ExportMgr', sysbus, self.show_status) self.clientmgr = ClientMgr(SERVICE, '/org/ganesha/nfsd/ClientMgr', sysbus, self.show_status) self.logmanager = LogManager(SERVICE, sysbus, self.show_status) self.logdialog = LogSetDialog(self.logmanager) self.show_status.connect(self.status_message) # Connect up the ui menubar #File self.ui.actionDBus_connect.triggered.connect(self.connect_gsh) self.ui.actionQuit.triggered.connect(self.quit) #Manage #Manage->Clients self.ui.actionAdd_Client.triggered.connect(self.add_client) self.ui.actionRemove_Client.triggered.connect(self.remove_client) #Manage->Exports self.ui.actionExports.triggered.connect(self.export_mgr) #Manage->Log Levels self.ui.actionLog_Settings.triggered.connect(self.logsettings) #Manage->Admin self.ui.actionReset_Grace.triggered.connect(self.reset_grace) self.ui.actionShutdown.triggered.connect(self.shutdown) self.ui.actionReload.triggered.connect(self.reload) #View self.ui.actionStatistics.triggered.connect(self.stats) self.ui.actionViewExports.triggered.connect(self.view_exports) self.ui.actionViewClients.triggered.connect(self.view_clients) #Help self.ui.actionAbout.triggered.connect(self.help) # Dbus data models self.exports_show_model = ExportTableModel(self.exportmgr) self.clients_show_model = ClientTableModel(self.clientmgr) # Tabs, tables, and views self.ui.exports.setModel(self.exports_show_model) self.ui.exports.resizeColumnsToContents() self.ui.exports.verticalHeader().setVisible(False) self.ui.clients.setModel(self.clients_show_model) self.ui.clients.resizeColumnsToContents() self.ui.clients.verticalHeader().setVisible(False) # actions to real work... def quit(self): self.statusBar().showMessage("Bye bye kiddies, quitting") quit() def connect_gsh(self): self.statusBar().showMessage("Connecting to nfs-ganesha...") def add_client(self): ipaddr, ok = QtGui.QInputDialog.getText(self, 'Add a Client', 'IP Address (N.N.N.N) of client: ') if ok: self.clientmgr.AddClient(ipaddr) def remove_client(self): ipaddr, ok = QtGui.QInputDialog.getText(self, 'Remove a Client', 'IP Address (N.N.N.N) of client: ') if ok: self.clientmgr.RemoveClient(ipaddr) def export_mgr(self): self.statusBar().showMessage("Export manager") def logsettings(self): self.logdialog.show_logsetting_dialog() def reset_grace(self): ipaddr, ok = QtGui.QInputDialog.getText(self, 'Grace Period', 'IP Address (N.N.N.N) of client: ') if ok: self.admin.grace(ipaddr) def shutdown(self): reply = QtGui.QMessageBox.question(self, 'Warning!!!', "Do you really want to shut down the server?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: self.admin.shutdown() def reload(self): reply = QtGui.QMessageBox.question(self, 'Warning!!!', "Do you really want to reload exports?", QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: self.admin.reload() def stats(self): self.statusBar().showMessage("stats go here") def view_exports(self): self.exports_show_model.FetchExports() def view_clients(self): self.clients_show_model.FetchClients() def help(self): self.statusBar().showMessage("Help! Help!!") def status_message(self, status, errormsg): if status: sstr = "Success: " else: sstr = "Failed: " self.statusBar().showMessage(sstr + errormsg) # Main if __name__ == '__main__': app = QtGui.QApplication(sys.argv) sysbus = QtDBus.QDBusConnection.systemBus() mw = MainWindow(sysbus) mw.show() sys.exit(app.exec_()) ����������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/get_clientids.py���������������������������������������������0000775�0000000�0000000�00000002534�14737566223�0023362�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-3.0-or-later # # Copyright (C) 2012 Panasas Inc. # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Jim Lieb <jlieb@panasas.com> from __future__ import print_function # Create a system dbu bus object. import dbus bus = dbus.SystemBus() # Create an object that will proxy for a particular remote object. cbsim = bus.get_object("org.ganesha.nfsd", "/org/ganesha/nfsd/CBSIM") print("introspection data") introspect = dbus.Interface( cbsim, dbus.INTROSPECTABLE_IFACE, ) print(introspect.Introspect()) # call method get_client_ids = cbsim.get_dbus_method('get_client_ids', 'org.ganesha.nfsd.cbsim') print("client ids:") print(get_client_ids()) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/grace_period.py����������������������������������������������0000775�0000000�0000000�00000002761�14737566223�0023172�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-3.0-or-later # # Copyright (C) 2013 IBM # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Marc Eshel <eshel@us.ibm.com> from __future__ import print_function import sys ipaddr = sys.argv[1] print('event:ip_addr=', ipaddr) # Create a system bus object. import dbus bus = dbus.SystemBus() # Create an object that will proxy for a particular remote object. try: admin = bus.get_object("org.ganesha.nfsd", "/org/ganesha/nfsd/admin") except dbus.exceptions.DBusException as e: sys.exit("Error: Can't talk to ganesha service on d-bus. " "Looks like Ganesha is down") # call method ganesha_grace = admin.get_dbus_method('grace', 'org.ganesha.nfsd.admin') print("Start grace period.") try: print(ganesha_grace(ipaddr)) except dbus.exceptions.DBusException as e: sys.exit("Error: Failed to start grace period") ���������������nfs-ganesha-6.5/src/scripts/ganeshactl/knfs2ganesha-exports.py��������������������������������������0000775�0000000�0000000�00000014527�14737566223�0024626�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: LGPL-3.0-or-later # # Copyright (C) 2018 IBM # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Malahal Naineni <malahal@us.ibm.com> # Convert given Linux kernel NFS server exports into Ganesha exports. # # Typical usage: # cat /etc/exports | knfs2ganesha-exports > /etc/ganesha/ganesha.conf # cat /etc/exports | knfs2ganesha-exports --fsal gpfs > /etc/ganesha/ganesha.conf # # NOTE: default options with DASH/HYPHEN are not implemented! from __future__ import print_function import sys, os import pyparsing as pp import subprocess # List of all export paths processed are kept in gan_paths dictionary gan_paths = {} export_id = 0 # increment before use LPAREN = pp.Literal("(").suppress() RPAREN = pp.Literal(")").suppress() # export path can be quoted to include white space! ppath = pp.QuotedString(quoteChar="'", escChar='\\', unquoteResults=False, convertWhitespaceEscapes=False) ppath = pp.Or([ppath, pp.QuotedString(quoteChar='"', escChar='\\', unquoteResults=False, convertWhitespaceEscapes=False)]) ppath = pp.Or([ppath, pp.Word(pp.alphanums+"_-/.+")]).setResultsName("path") phost = pp.Word(pp.alphanums+".@+*").setResultsName("host") phost_opts = (LPAREN + pp.CharsNotIn(')') + RPAREN).setResultsName("host_opts") phost_block = pp.Group(phost + phost_opts) pblock = ppath + pp.OneOrMore(phost_block) def process_exports(fsalname): for line in sys.stdin: line = line.strip() if not line or line.startswith("#"): continue p = pblock.ignore(pp.pythonStyleComment).parseString(line, parseAll=True) path = p["path"] host_blocks = p[1:] for host_block in host_blocks: host = host_block["host"] opts = host_block["host_opts"] if opts: # Not supposed as we don't support DASH based default options opts = opts[0] else: opts = "" #print(path, host, opts) pairs = process_opts(opts) create_client(path, host, pairs, fsalname) # Takes a knfs options string and converts to Ganesha # key,value pairs and returns a key,value list def process_opts(opts): # Set to defaults access_value = "ro" squash_value = "root_squash" sec_value = "sys" pairs = [] opts = opts.split(",") opts = [x.strip() for x in opts] for opt in opts: if opt in ["ro", "rw"]: access_value = opt elif opt in ["no_root_squash", "all_squash", "root_squash"]: squash_value = opt elif opt.startswith("sec"): auth = opt.split("=")[1] auth = auth.split(":") sec_value = ",".join(auth) elif opt in ["insecure", "sync", "no_subtree_check"]: pass elif opt == "async": sys.exit("async option is not supported in NFS-Ganesha, " "more over, you shouldn't be using it if you " "care about your data!\n" "Please remove this option and re-run the program") elif opt == "subtree_check": sys.exit("subtree_check option is not supported in NFS-Ganesha.\n" "Please remove this option and re-run the program") elif opt.startswith("fsid"): pass elif opt.startswith("anonuid"): uid = opt.split("=")[1].strip() pairs.append(("--Anonymous_uid", uid)) elif opt.startswith("anongid"): gid = opt.split("=")[1].strip() pairs.append(("--Anonymous_gid", gid)) else: sys.exit("Unknown option: %s, exiting" % opt) pairs.append(("--Access_Type", access_value)) pairs.append(("--Squash", squash_value)) pairs.append(("--SecType", sec_value)) return pairs def create_client(path, host, pairs, fsalname): global gan_paths global export_id # Create EXPORT{} block if needed if path not in gan_paths: gan_paths[path] = 1 export_id += 1 cmd = ["ganesha_conf", "set", "EXPORT", "Path", path] cmd += ["--Export_Id", str(export_id), "--Pseudo", path] subprocess.check_call(cmd) # Create EXPORT{FSAL{}} block cmd = ["ganesha_conf", "set", "EXPORT", "Path", path, "FSAL", "--name", fsalname] subprocess.check_call(cmd) # Create EXPORT{CLIENT{}} block with all key,value pairs cmd = ["ganesha_conf", "set", "EXPORT", "Path", path] cmd += ["CLIENT", "Clients", host] for key, value in pairs: cmd += [key, value] subprocess.check_call(cmd) def usage(msg=""): msg += "\n%s [--fsal <fsal-name>]" % sys.argv[0] sys.exit(msg) fsals = ["gpfs", "vfs", "lustre"] def main(): fsal = "vfs" argc = len(sys.argv) if argc == 1: pass elif argc == 2: if sys.argv[1].startswith("-h") or sys.argv[1].startswith("--help"): usage() else: msg = "Unknown parameter: %s" % sys.argv[1] usage(msg) elif argc == 3: if not sys.argv[1].startswith("--fsal"): msg = "Unknown parameter: %s" % sys.argv[2] usage(msg) fsal = sys.argv[2] if fsal not in fsals: msg = "Unknown fsal: %s" % fsal usage(msg) else: usage() import tempfile f = tempfile.NamedTemporaryFile() os.environ["CONFFILE"] = f.name process_exports(fsal) if export_id == 0: sys.exit("processed 0 exports!") else: # f.name is updated by subprocesses (ganesha_conf) and reading # "f" here gets nothing! Re-open does the trick! with open(f.name) as f2: for line in f2: sys.stdout.write(line) if __name__ == '__main__': main() �������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/manage_clients.py��������������������������������������������0000664�0000000�0000000�00000006475�14737566223�0023523�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: GPL-3.0-or-later # # manage_clients.py - commandline tool for managing clients of nfs-ganesha. # # Copyright (C) 2014 Panasas Inc. # # 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Jim Lieb <jlieb@panasas.com> #-*- coding: utf-8 -*- from __future__ import print_function import sys import time from PyQt5 import QtCore, QtDBus from PyQt5.QtGui import QApplication from dbus.mainloop.qt import DBusQtMainLoop from Ganesha.client_mgr import ClientMgr SERVICE = 'org.ganesha.nfsd' class ManageClients(QtCore.QObject): show_status = QtCore.pyqtSignal(bool, str) def __init__(self, sysbus, parent=None): super(ManageClients, self).__init__() self.clientmgr = ClientMgr(SERVICE, '/org/ganesha/nfsd/ClientMgr', sysbus, self.show_status) self.show_status.connect(self.status_message) self.clientmgr.show_clients.connect(self.proc_clients) def addclient(self, ipaddr): self.clientmgr.AddClient(ipaddr) print("Add a client %s" % (ipaddr)) def removeclient(self, ipaddr): self.clientmgr.RemoveClient(ipaddr) print("Remove a client %s" % (ipaddr)) def showclients(self): self.clientmgr.ShowClients() print("Show clients") def proc_clients(self, _ts, clients): print("Timestamp: ", time.ctime(_ts[0]), _ts[1], " nsecs") if len(clients) == 0: print("No clients") else: print("Clients:") print(" IP addr, nfsv3, mnt, nlm4, rquota,nfsv40, nfsv41, 9p, last") for client in clients: print(" %s, %s, %s, %s, %s, %s, %s, %s, %s %d nsecs" % (client.ClientIP, client.HasNFSv3, client.HasMNT, client.HasNLM4, client.HasRQUOTA, client.HasNFSv40, client.HasNFSv41, client.Has9P, time.ctime(client.LastTime[0]), client.LastTime[1])) sys.exit() def status_message(self, status, errormsg): print("Error: status = %s, %s" % (str(status), errormsg)) sys.exit() # Main if __name__ == '__main__': app = QApplication(sys.argv) loop = DBusQtMainLoop(set_as_default=True) sysbus = QtDBus.QDBusConnection.systemBus() clientmgr = ManageClients(sysbus) if sys.argv[1] == "add": clientmgr.addclient(sys.argv[2]) elif sys.argv[1] == "remove": clientmgr.removeclient(sys.argv[2]) elif sys.argv[1] == "show": clientmgr.showclients() else: print("unknown/missing command") sys.exit() app.exec_() ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/manage_exports.py��������������������������������������������0000775�0000000�0000000�00000010115�14737566223�0023553�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: GPL-3.0-or-later # # manage_exports.py - commandline tool for managing exports in nfs-ganesha. # # Copyright (C) 2014 Panasas Inc. # # 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Jim Lieb <jlieb@panasas.com> #-*- coding: utf-8 -*- from __future__ import print_function import sys import time from PyQt5 import QtCore, QtDBus from PyQt5.QtGui import QApplication from dbus.mainloop.qt import DBusQtMainLoop from Ganesha.export_mgr import ExportMgr SERVICE = 'org.ganesha.nfsd' class ShowExports(QtCore.QObject): show_status = QtCore.pyqtSignal(bool, str) def __init__(self, sysbus, parent=None): super(ShowExports, self).__init__() self.exportmgr = ExportMgr(SERVICE, '/org/ganesha/nfsd/ExportMgr', sysbus, self.show_status) self.show_status.connect(self.status_message) self.exportmgr.show_exports.connect(self.proc_exports) self.exportmgr.display_export.connect(self.proc_export) def showexports(self): self.exportmgr.ShowExports() print("Show exports") def addexport(self, conf_path, exp_expr): self.exportmgr.AddExport(conf_path, exp_expr) print("Add Export in %s" % conf_path) def updateexport(self, conf_path, exp_expr): self.exportmgr.UpdateExport(conf_path, exp_expr) print("Update Export in %s" % conf_path) def removeexport(self, exp_id): self.exportmgr.RemoveExport(exp_id) print("Remove Export with id %d" % int(exp_id)) def displayexport(self, exp_id): self.exportmgr.DisplayExport(exp_id) print("Display export with id %d" % int(exp_id)) def proc_export(self, _id, path, pseudo, tag): print("export %d: path = %s, pseudo = %s, tag = %s" % (_id, path, pseudo, tag)) sys.exit() def proc_exports(self, _ts, exports): print("Timestamp: ", time.ctime(_ts[0]), _ts[1], " nsecs") if len(exports) == 0: print("No exports") else: print("Exports:") print(" Id, path, nfsv3, mnt, nlm4, rquota,nfsv40, nfsv41, 9p, last") for export in exports: print(" %d, %s, %s, %s, %s, %s, %s, %s, %s, %s %d nsecs" % (export.ExportID, export.ExportPath, export.HasNFSv3, export.HasMNT, export.HasNLM4, export.HasRQUOTA, export.HasNFSv40, export.HasNFSv41, export.Has9P, time.ctime(export.LastTime[0]), export.LastTime[1])) sys.exit() def status_message(self, status, errormsg): print("Error: status = %s, %s" % (str(status), errormsg)) sys.exit() # Main if __name__ == '__main__': app = QApplication(sys.argv) loop = DBusQtMainLoop(set_as_default=True) sysbus = QtDBus.QDBusConnection.systemBus() exportmgr = ShowExports(sysbus) if sys.argv[1] == "add": exportmgr.addexport(sys.argv[2], sys.argv[3]) elif sys.argv[1] == "update": exportmgr.updateexport(sys.argv[2], sys.argv[3]) elif sys.argv[1] == "remove": exportmgr.removeexport(sys.argv[2]) elif sys.argv[1] == "display": exportmgr.displayexport(sys.argv[2]) elif sys.argv[1] == "show": exportmgr.showexports() else: print("Unknown/missing command") sys.exit() app.exec_() ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/manage_logger.py���������������������������������������������0000664�0000000�0000000�00000005523�14737566223�0023332�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # SPDX-License-Identifier: GPL-3.0-or-later # # manage_logger.py - commandline tool for setting log levels of nfs-ganesha. # # Copyright (C) 2014 Panasas Inc. # # 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 3 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, see <http://www.gnu.org/licenses/>. # # Author: Jim Lieb <jlieb@panasas.com> #-*- coding: utf-8 -*- from __future__ import print_function import sys from PyQt5 import QtCore, QtDBus from PyQt5.QtGui import QApplication from dbus.mainloop.qt import DBusQtMainLoop from Ganesha.log_mgr import LogManager SERVICE = 'org.ganesha.nfsd' class ManageLogger(QtCore.QObject): show_status = QtCore.pyqtSignal(bool, str) def __init__(self, sysbus, parent=None): super(ManageLogger, self).__init__() self.logmgr = LogManager(SERVICE, sysbus, self.show_status) self.show_status.connect(self.status_message) self.logmgr.show_level.connect(self.proc_level) self.logmgr.show_components.connect(self.proc_components) def get_level(self, component): self.logmgr.Get(component) print("Getting log level for %s" % (component)) def set_level(self, component, level): self.logmgr.Set(component, level) print("Setting log level for %s to %s" % (component, level)) def getall(self): self.logmgr.GetAll() print("Fetching component log levels") def proc_level(self, level): print("Level = %s" % (level)) sys.exit() def proc_components(self, components): print("dict of levels:") for comp in components.keys(): print("Component %s is at %s" % (comp, components[comp])) sys.exit() def status_message(self, status, errormsg): print("Error: status = %s, %s" % (str(status), errormsg)) sys.exit() # Main if __name__ == '__main__': app = QApplication(sys.argv) loop = DBusQtMainLoop(set_as_default=True) sysbus = QtDBus.QDBusConnection.systemBus() logger = ManageLogger(sysbus) if sys.argv[1] == "get": logger.get_level(sys.argv[2]) elif sys.argv[1] == "set": logger.set_level(sys.argv[2], sys.argv[3]) elif sys.argv[1] == "getall": logger.getall() else: print("unknown/missing command") sys.exit() app.exec_() �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/org.ganesha.nfsd.conf����������������������������������������0000664�0000000�0000000�00000001305�14737566223�0024157�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- --> <!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> <busconfig> <!-- Only root can own the service --> <policy user="root"> <allow own="org.ganesha.nfsd"/> <allow send_destination="org.ganesha.nfsd"/> <allow send_destination="org.ganesha.nfsd" send_interface="org.freedesktop.DBus.Introspectable"/> <allow send_destination="org.ganesha.nfsd" send_interface="org.ganesha.nfsd.CBSIM"/> <allow send_destination="org.ganesha.nfsd" send_interface="org.ganesha.nfsd.admin"/> </policy> </busconfig> ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/ganeshactl/setup.py.in��������������������������������������������������0000664�0000000�0000000�00000000742�14737566223�0022306�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*-Python-*- from distutils.core import setup, Extension if __name__ == '__main__': setup(name='ganeshactl', version='${GANESHA_VERSION}', author = "Jim Lieb", maintainer = "NFS-Ganesha Project", maintainer_email = "devel@lists.nfs-ganesha.org", description = "NFS Ganesha Administration tools", package_dir = { '': '${CMAKE_CURRENT_SOURCE_DIR}' }, packages = ['Ganesha', 'Ganesha.QtUI'], scripts = [${SCRIPTS_STRING}]) ������������������������������nfs-ganesha-6.5/src/scripts/gen_ctdb_epoch.py�������������������������������������������������������0000775�0000000�0000000�00000003135�14737566223�0021355�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # Usually ganesha daemon start up time is used as its epoch. This is # fine for a single node configurations, but in clustered environment, # ganesha daemon will have to distinguish itself from instances running # on other nodes. This is not possible if ganesha daemon is started at # the same time on multiple cluster nodes. # # The epoch will have to be a 32 bit number. We use nodeid in the most # significant 16 bits and a generation number in the least significant # 16 bits. Current generation number is written to seq_num_file. import os, sys, re, fcntl from pathlib import Path from subprocess import Popen, PIPE from ctypes import c_int, c_long, pointer, POINTER, Structure seq_num_file = "/var/lib/nfs/ganesha/seq_num" def main(): genid = get_genid() + 1 # next genid genid &= 0xFFFF # handle 16 bit overflow put_genid(genid) # store the genid nodeid = get_nodeid() epoch = nodeid << 16 | genid print(epoch) def get_genid(): try: with open(seq_num_file, "r",encoding='UTF-8') as f: genid = int(f.read()) except Exception: genid = 0 return genid def put_genid(genid): with open(seq_num_file, "w+", encoding='UTF-8') as f: f.write("%s" % genid) def get_nodeid(): child = Popen(['/usr/bin/ctdb', 'pnn'], encoding='UTF-8', stdout=PIPE, stderr=PIPE) (nodeid, err) = child.communicate() child.wait() return int(nodeid) if __name__ == "__main__": import traceback import syslog try: main() except: syslog.syslog(traceback.format_exc()) sys.exit(1) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/gerrit/�����������������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0017347�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/gerrit/checkpatch-to-gerrit-json.py�������������������������������������0000664�0000000�0000000�00000001720�14737566223�0024677�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python from __future__ import print_function import sys,json,re comments={} while 1: line = sys.stdin.readline() fileline = sys.stdin.readline() if not fileline.strip(): break while 1: newline = sys.stdin.readline() if not newline.strip(): break line += newline filere = re.search('FILE: (.*):([0-9]*):', fileline) if comments.has_key(filere.group(1)): comments[filere.group(1)] += [ { 'line': filere.group(2), 'message': line.strip()} ] else: comments[filere.group(1)] = [ { 'line': filere.group(2), 'message': line.strip()} ] if comments: #output = { 'comments': comments, 'message': line.strip(), 'labels': {'Code-Review': -1 }} output = { 'comments': comments, 'message': "Checkpatch %s" % (line.strip()) } else: #output = {'message': 'Checkpatch OK', 'labels': {'Code-Review': +1 }} output = {'message': 'Checkpatch OK'} print(json.dumps(output)) ������������������������������������������������nfs-ganesha-6.5/src/scripts/gerrit/gerrit-checkpatch.sh���������������������������������������������0000775�0000000�0000000�00000004055�14737566223�0023301�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/bash -u # configuration GERRIT_SERVER=review.gerrithub.io GERRIT_USER=ganesha-triggers GERRIT_KEYFILE=$HOME/.ssh/id_rsa_ganesha-triggers PROJECT=ffilz/nfs-ganesha # host box configuration needed: # - checkpatch.conf configured (.checkpatch.conf in homedir) # - current repository has a remote called 'gerrit' setup # - ssh configuration that said 'gerrit' remote doesn't need a password # - awk script for oneshot query requires awk >= 4 # internal variables SSH_GERRIT="ssh -p 29418 -i $GERRIT_KEYFILE -l $GERRIT_USER $GERRIT_SERVER" DRYRUN="" ONESHOT="" while getopts :nqch opt; do case "$opt" in n) DRYRUN=1 ;; q) ONESHOT=query ;; c) ONESHOT=cat ;; *) cat << EOF Usage: $0 [-n] [-o] -n dry-run mode, do not actually push reviews -q one-shot queries gerrit and tries to catch up on open patchset that were not commented -c one-shot cat, reads one line at a time "ref commit" e.g "refs/change/x/y/z 0123456789abcdef" EOF exit ;; esac done input_loop() { case "$ONESHOT" in "query") $SSH_GERRIT "gerrit query --comments --patch-sets \ is:open project:$PROJECT" | \ awk -f gerrit-query.awk ;; "cat") cat ;; *) while date 1>&2; do $SSH_GERRIT "gerrit stream-events" | \ python gerrit-stream-filter.py $PROJECT done ;; esac } commit_review() { if [[ -z "$DRYRUN" ]]; then tee >($SSH_GERRIT "gerrit review --json \ --project $PROJECT $COMMIT") 1>&2 else echo "Would have submit:" cat fi } input_loop | \ while read REF COMMIT; do echo "got ref $REF, commit $COMMIT" git fetch gerrit $REF >/dev/null 2>&1 git show --format=email $COMMIT | \ ../checkpatch.pl -q - | \ python checkpatch-to-gerrit-json.py | \ commit_review done �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/gerrit/gerrit-query.awk�������������������������������������������������0000664�0000000�0000000�00000001216�14737566223�0022512�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/awk -f # run with -v debug=1 for verbose printing /^change / { for (i in patchSets) { if (debug) for (j in patchSets[i]) { print i, j, patchSets[i][j] } if (!patchSets[i]["reviewed"]) { print patchSets[i]["ref"] " " patchSets[i]["commit"] } } delete patchSets; curSet=0; } /^ *username:/ { username=$2 } /^ *message: Patch Set/ { if (username == "ganesha-triggers") { sub(":", "", $4); patchSets[$4]["reviewed"] = "ok" } } /^ *number: / { curSet=$2 } /^ *ref: / { patchSets[curSet]["ref"]=$2 } /^ *revision: / { patchSets[curSet]["commit"]=$2 } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/gerrit/gerrit-stream-filter.py������������������������������������������0000664�0000000�0000000�00000001371�14737566223�0023773�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python from __future__ import print_function import json,sys if len(sys.argv) > 1: GERRIT_PROJECT = sys.argv[1] else: GERRIT_PROJECT = u'ffilz/nfs-ganesha' debug = False for line in sys.stdin: try: obj=json.loads(line) except ValueError: # silently skip what wasn't json continue if obj[u'type'] != 'patchset-created' or obj[u'change'][u'project'] != GERRIT_PROJECT: # skip events we don't care about continue if debug: print(json.dumps(obj, indent=4), file=sys.stderr) # format stuff so we can read it in shell easily print("%s %s" % (obj[u'patchSet'][u'ref'], obj[u'patchSet'][u'revision'])) # need to flush if printing to a pipe sys.stdout.flush() �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/git_hooks/��������������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0020041�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/git_hooks/commit-msg����������������������������������������������������0000775�0000000�0000000�00000011053�14737566223�0022043�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # SPDX-License-Identifier: Apache-2.0 # # From Gerrit Code Review 2.10-rc1-988-g333a9dd # Edited by Dominique Martinet to add Signed-off-by as we used to # # Part of Gerrit Code Review (http://code.google.com/p/gerrit/) # # Copyright (C) 2009 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # unset GREP_OPTIONS CHANGE_ID_AFTER="Bug|Issue" MSG="$1" # Check for, and add if missing, a unique Change-Id # add_ChangeId() { clean_message=`sed -e ' /^diff --git .*/{ s/// q } /^Signed-off-by:/d /^#/d ' "$MSG" | git stripspace` if test -z "$clean_message" then return fi if test "false" = "`git config --bool --get gerrit.createChangeId`" then return fi # Does Change-Id: already exist? if so, exit (no change). if grep -i '^Change-Id:' "$MSG" >/dev/null then return fi id=`_gen_ChangeId` T="$MSG.tmp.$$" AWK=awk if [ -x /usr/xpg4/bin/awk ]; then # Solaris AWK is just too broken AWK=/usr/xpg4/bin/awk fi # How this works: # - parse the commit message as (textLine+ blankLine*)* # - assume textLine+ to be a footer until proven otherwise # - exception: the first block is not footer (as it is the title) # - read textLine+ into a variable # - then count blankLines # - once the next textLine appears, print textLine+ blankLine* as these # aren't footer # - in END, the last textLine+ block is available for footer parsing $AWK ' BEGIN { # while we start with the assumption that textLine+ # is a footer, the first block is not. isFooter = 0 footerComment = 0 blankLines = 0 } # Skip lines starting with "#" without any spaces before it. /^#/ { next } # Skip the line starting with the diff command and everything after it, # up to the end of the file, assuming it is only patch data. # If more than one line before the diff was empty, strip all but one. /^diff --git / { blankLines = 0 while (getline) { } next } # Count blank lines outside footer comments /^$/ && (footerComment == 0) { blankLines++ next } # Catch footer comment /^\[[a-zA-Z0-9-]+:/ && (isFooter == 1) { footerComment = 1 } /]$/ && (footerComment == 1) { footerComment = 2 } # We have a non-blank line after blank lines. Handle this. (blankLines > 0) { print lines for (i = 0; i < blankLines; i++) { print "" } lines = "" blankLines = 0 isFooter = 1 footerComment = 0 } # Detect that the current block is not the footer (footerComment == 0) && (!/^\[?[a-zA-Z0-9-]+:/ || /^[a-zA-Z0-9-]+:\/\//) { isFooter = 0 } { # We need this information about the current last comment line if (footerComment == 2) { footerComment = 0 } if (lines != "") { lines = lines "\n"; } lines = lines $0 } # Footer handling: # If the last block is considered a footer, splice in the Change-Id at the # right place. # Look for the right place to inject Change-Id by considering # CHANGE_ID_AFTER. Keys listed in it (case insensitive) come first, # then Change-Id, then everything else (eg. Signed-off-by:). # # Otherwise just print the last block, a new line and the Change-Id as a # block of its own. END { unprinted = 1 if (isFooter == 0) { print lines "\n" lines = "" } changeIdAfter = "^(" tolower("'"$CHANGE_ID_AFTER"'") "):" numlines = split(lines, footer, "\n") for (line = 1; line <= numlines; line++) { if (unprinted && match(tolower(footer[line]), changeIdAfter) != 1) { unprinted = 0 print "Change-Id: I'"$id"'" } print footer[line] } if (unprinted) { print "Change-Id: I'"$id"'" } }' "$MSG" > "$T" && mv "$T" "$MSG" || rm -f "$T" } _gen_ChangeIdInput() { echo "tree `git write-tree`" if parent=`git rev-parse "HEAD^0" 2>/dev/null` then echo "parent $parent" fi echo "author `git var GIT_AUTHOR_IDENT`" echo "committer `git var GIT_COMMITTER_IDENT`" echo printf '%s' "$clean_message" } _gen_ChangeId() { _gen_ChangeIdInput | git hash-object -t commit --stdin } add_SignOffBy() { SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') grep -qs "^$SOB" "$MSG" || echo "$SOB" >> "$MSG" } add_ChangeId add_SignOffBy �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/git_hooks/install_git_hooks.sh������������������������������������������0000775�0000000�0000000�00000001063�14737566223�0024114�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/bash if [[ $OSTYPE == 'darwin'* ]]; then CURDIR=$(dirname "$(greadlink -m "$0")") TOPDIR=$(git rev-parse --show-toplevel) HOOKDIR=$TOPDIR/.git/hooks else CURDIR=$(dirname "$(readlink -m "$0")") TOPDIR=$(git rev-parse --show-toplevel) HOOKDIR=$TOPDIR/.git/hooks fi # Link checkpatch script configuration file to top level working # directory. ln -sf ./src/scripts/checkpatch.conf "$TOPDIR/.checkpatch.conf" cp -f "$CURDIR/pre-commit" "$HOOKDIR" chmod +x "$HOOKDIR/pre-commit" cp -f "$CURDIR/commit-msg" "$HOOKDIR" chmod +x "$HOOKDIR/commit-msg" �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/git_hooks/pre-commit����������������������������������������������������0000775�0000000�0000000�00000005256�14737566223�0022053�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/bash # Ganesha pre-commit hook # # 1. Run clang-format on the commit # 2. Check to see if a submodule is not being updated # define colors for use in output red='\033[0;31m' green='\033[0;32m' no_color='\033[0m' grey='\033[0;90m' ##################### # Clang-Format Hook ##################### function handle_unformatted_file() { set -u # If a rebase is in progress just show warning. git rebase --show-current-patch 1> /dev/null 2> /dev/null if [[ $? == 0 ]]; then echo "[git-pre-commit warning] clang-format found formatting missing in commit." echo "[git-pre-commit warning] In rebase so just printing the warning without failing the commit." else echo -e "${red}[git-pre-commit error]${no_color} clang-format found formatting missing in commit. commit aborted." exit 1 fi } function check_staged_files() { set -u local test_command="(cd src; clang-format -style=file --dry-run --Werror -)" local retval=0 for file in $(git diff --cached --name-only --diff-filter=d -- 'src/*.c' 'src/*.h' 'src/*.cpp' 'src/*.hpp' 'src/*.cc') do git show :${file} | eval "${test_command}" if [[ $? != 0 ]]; then echo "ERROR: Staged file ${file} is not formatted correctly, please use clang-format to format it" retval=1 fi done if [[ ${retval} != 0 ]]; then handle_unformatted_file fi } check_staged_files ############################################################### # Check whether any submodule is about to be updated with the # commit. Ask the user for confirmation. ############################################################### echo -e "Checking submodules ${grey}(pre-commit hook)${no_color} " # Jump to the current project's root directory (the one containing # .git/) ROOT_DIR=$(git rev-parse --show-cdup) SUBMODULES=$(grep path ${ROOT_DIR}.gitmodules | sed 's/^.*path = //') # Finding the submodules that have been modified MOD_SUBMODULES=$(git diff --cached --name-only | grep -F "$SUBMODULES") # If no modified submodules, exit with status code 0, else prompt the # user and exit accordingly if [[ -n "$MOD_SUBMODULES" ]]; then echo "Submodules to be committed:" echo " (use \"git reset HEAD <file>...\" to unstage)" echo for SUB in $MOD_SUBMODULES do echo -e "\t${green}modified:\t$SUB${no_color}" done echo echo -n -e "Continue with commit? ${grey}[N|y]${no_color} " read -n 1 reply </dev/tty echo if [[ "$reply" == "y" || "$reply" == "Y" ]]; then echo "Permitting submodules to be committed..." exit 0 else echo "Aborting commit due to submodule update." exit 1 fi fi exit 0 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/gpfs-epoch/�������������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0020106�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/gpfs-epoch/CMakeLists.txt�����������������������������������������������0000664�0000000�0000000�00000006761�14737566223�0022660�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb <jlieb@panasas.com> # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- if(USE_FSAL_GPFS) if(Python3_FOUND) set(SETUP_PY_IN "${CMAKE_CURRENT_SOURCE_DIR}/setup.py.in") set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py") set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/build/gpfs_epoch_timestamp") set(GPFS_EPOCH_SRCS gpfs-epoch.py) # Generate rules to copy command line scripts from src to build, # stripping .py along the way set(SCRIPTS) foreach(src_file ${GPFS_EPOCH_SRCS}) string(REPLACE ".py" "" script_file ${src_file}) add_custom_command( OUTPUT ${script_file} COMMAND mkdir -p build/lib COMMAND echo ${script_file} COMMAND cp "${CMAKE_CURRENT_SOURCE_DIR}/${src_file}" ${script_file} DEPENDS ${src_file} ) list(APPEND SCRIPTS ${script_file}) endforeach() # Build up the string for the configure substitution in setup.py set(SCRIPTS_STRING) foreach(script_py ${GPFS_EPOCH_SRCS}) string(REPLACE ".py" "" script ${script_py}) if("${SCRIPTS_STRING}" STREQUAL "") set(SCRIPTS_STRING "'${script}'") else() set(SCRIPTS_STRING "${SCRIPTS_STRING}, '${script}'") endif() endforeach() configure_file(${SETUP_PY_IN} ${SETUP_PY}) if(USE_LEGACY_PYTHON_INSTALL) add_custom_command( OUTPUT ${OUTPUT} COMMAND ${CMAKE_COMMAND} -E touch __init__.py COMMAND ${Python3_EXECUTABLE} "${SETUP_PY}" build COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT} DEPENDS ${GPFS_EPOCH_SRCS} ${SCRIPTS} ) else() add_custom_command( OUTPUT ${OUTPUT} COMMAND ${CMAKE_COMMAND} -E touch __init__.py COMMAND ${Python3_EXECUTABLE} -m build --wheel --no-isolation . COMMAND ${CMAKE_COMMAND} -E touch ${OUTPUT} DEPENDS ${GPFS_EPOCH_SRCS} ${SCRIPTS} ) endif() add_custom_target(python_gpfs_epoch ALL DEPENDS ${OUTPUT}) if (USE_LEGACY_PYTHON_INSTALL) install( CODE "execute_process(WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${Python3_EXECUTABLE} ${SETUP_PY} install --skip-build --no-compile --prefix=\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX} --install-scripts=\$ENV{DESTDIR}${LIBEXECDIR}/ganesha)" ) else() install( CODE "execute_process(WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${Python3_EXECUTABLE} -m installer --destdir \$ENV{DESTDIR} dist/gpfs-${GANESHA_MAJOR_VERSION}${GANESHA_MINOR_VERSION}-py3-none-any.whl)" ) endif() endif(Python3_FOUND) endif(USE_FSAL_GPFS) ���������������nfs-ganesha-6.5/src/scripts/gpfs-epoch/gpfs-epoch.py������������������������������������������������0000775�0000000�0000000�00000005356�14737566223�0022527�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python3 # Usually ganesha daemon start up time is used as its epoch. This is # fine for a single node configurations, but in clustered environment, # ganesha daemon will have to distinguish itself from instances running # on other nodes. This is not possible if ganesha daemon is started at # the same time on multiple cluster nodes. # # The epoch will have to be a 32 bit number. We use nodeid in the most # significant 16 bits and a generation number in the least significant # 16 bits. Current generation number is written to epoch_file. import os, sys, re, fcntl from subprocess import Popen, PIPE from ctypes import c_int, c_long, pointer, POINTER, Structure epoch_file = "/var/lib/nfs/ganesha/gpfs-epoch" def main(): genid = get_genid() + 1 # next genid genid &= 0xFFFF # handle 16 bit overflow put_genid(genid) # store the genid nodeid = get_nodeid() epoch = nodeid << 16 | genid print(epoch) def get_genid(): try: with open(epoch_file, "r") as f: genid = int(f.read()) except Exception: genid = 0 return genid def put_genid(genid): with open(epoch_file, "w+") as f: f.write("%s" % genid) def get_mount(): output = Popen("mount", shell=True, stdout=PIPE).communicate()[0].decode() for line in output.splitlines(): # the mount output line format is # # dev on mountpoint type fstype (options) # # Easier with split, but let us handle mount point names that # may include strange characters like space! Take anything from # "on" to "type gpfs" as a mount point! mo = re.search(r"\bon\b(.+)\btype gpfs\b", line) if mo: return mo.group(1).strip() return None # From gpfs headers! C code is probably less error prone for this! # # struct kxArgs { signed long arg1; signed long arg2 } # struct grace_period_arg { int mountdirfd, int grace_sec } # Note that struct grace_period_arg is used for GET_NODEID! class GracePeriodArg(Structure): _fields_ = [("mountdirfd", c_int), ("grace_sec", c_int)] class KxArgs(Structure): _fields_ = [("arg1", c_long), ("arg2", POINTER(GracePeriodArg))] def get_nodeid(): # GPFS FSAL constants GPFS_DEVNAMEX = "/dev/ss0" kGanesha = 140 OPENHANDLE_GET_NODEID = 125 gpfs_fd = os.open(GPFS_DEVNAMEX, os.O_RDONLY) gpfs_mount = get_mount() mountdirfd = os.open(gpfs_mount, os.O_RDONLY|os.O_DIRECTORY) gpa = GracePeriodArg(mountdirfd, 0) kxarg = KxArgs(c_long(OPENHANDLE_GET_NODEID), pointer(gpa)) return fcntl.ioctl(gpfs_fd, kGanesha, kxarg) if __name__ == "__main__": import traceback import syslog try: main() except: syslog.syslog(traceback.format_exc()) sys.exit(1) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/gpfs-epoch/setup.py.in��������������������������������������������������0000664�0000000�0000000�00000000721�14737566223�0022225�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*-Python-*- from distutils.core import setup, Extension if __name__ == '__main__': setup(name='gpfs', version='${GANESHA_VERSION}', author = "NFS-Ganesha Project", maintainer = "NFS-Ganesha Project", maintainer_email = "devel@lists.nfs-ganesha.org", description = "NFS Ganesha Administration tools", package_dir = { '': '${CMAKE_CURRENT_SOURCE_DIR}' }, packages = ['.'], scripts = [${SCRIPTS_STRING}]) �����������������������������������������������nfs-ganesha-6.5/src/scripts/init.d/�����������������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0017240�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/init.d/nfs-ganesha.debian8����������������������������������������������0000664�0000000�0000000�00000005040�14737566223�0022665�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/bash # # chkconfig: 2345 50 50 # description: GANESHA NFS Daemon # # processname: /usr/bin/ganesha.nfsd # config: /etc/ganesha/ganesha.nfsd.conf # pidfile: /var/run/ganesha.nfsd.pid # ### BEGIN INIT INFO # Provides: ganesha # Required-Start: $local_fs $named $time $network # Required-Stop: $local_fs $named $time $network # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: start and stop ganesha daemon # Description: NFS-GANESHA ### END INIT INFO # source function library #. /etc/rc.d/init.d/functions . /lib/lsb/init-functions PATHPROG=/usr/bin/ganesha.nfsd # Default Ganesha options LOGFILE=/var/log/ganesha/ganesha.log CONFFILE=/etc/ganesha/ganesha.conf prog=ganesha.nfsd PID_FILE=${PID_FILE:=/var/run/${prog}.pid} LOCK_FILE=${LOCK_FILE:=/var/lock/subsys/${prog}} [ -f /etc/sysconfig/ganesha ] && . /etc/sysconfig/ganesha [ -z "$NOFILE" ] && NOFILE=1048576 OPTIONS="-L $LOGFILE -f $CONFFILE -N NIV_EVENT -p $PID_FILE " RETVAL=0 start() { echo -n $"Starting $prog: " if [ $UID -ne 0 ]; then RETVAL=1 failure else mkdir -p "$(dirname "$PID_FILE")" ulimit -n $NOFILE log_daemon_msg "Starting nfs-ganesha daemon" nfs-ganesha if ! start-stop-daemon --start --oknodo --quiet --pidfile $PID_FILE --exec $PATHPROG -- $OPTIONS; then log_end_msg 1 exit 1 fi touch $LOCK_FILE log_end_msg 0 fi echo } stop() { echo -n $"Stopping $prog: " if [ $UID -ne 0 ]; then RETVAL=1 failure else log_daemon_msg "Stopping nfs-ganesha daemon" nfs-ganesha #start-stop-daemon --stop --quiet --pidfile killproc $PATHPROG RETVAL=$? if [ $RETVAL -eq 0 ]; then log_end_msg 0 rm -rf $LOCK_FILE else log_end_msg 1 fi fi echo } case "$1" in start) start ;; stop) stop ;; restart) if [ -f $LOCK_FILE ] ; then stop #avoid race sleep 3 fi start ;; reload) echo -n $"Reloading $prog: " if [ -f $LOCK_FILE ] ; then killproc -p $PID_FILE $prog -HUP RETVAL=0 else RETVAL=1 fi echo ;; force-reload) stop start ;; try-restart) if [ -f $LOCK_FILE ] ; then stop #avoid race sleep 3 start fi ;; status) if [ $RETVAL -eq 5 ] ; then RETVAL=3 else status -p $PID_FILE $PATHPROG RETVAL=$? fi ;; *) echo $"Usage: $0 {start|stop|restart|reload|try-restart|status}" RETVAL=1 esac exit $RETVAL ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/init.d/nfs-ganesha.el6��������������������������������������������������0000664�0000000�0000000�00000005001�14737566223�0022036�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/bash # # chkconfig: 2345 50 50 # description: GANESHA NFS Daemon # # processname: /usr/bin/ganesha.nfsd # config: /etc/ganesha/ganesha.nfsd.conf # pidfile: /var/run/ganesha.nfsd.pid # ### BEGIN INIT INFO # Provides: ganesha # Required-Start: $local_fs $named $time $network # Required-Stop: $local_fs $named $time $network # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: start and stop ganesha daemon # Description: NFS-GANESHA ### END INIT INFO # source function library . /etc/rc.d/init.d/functions PATHPROG=/usr/bin/ganesha.nfsd # Default Ganesha options LOGFILE=/var/log/ganesha/ganesha.log CONFFILE=/etc/ganesha/ganesha.conf prog=ganesha.nfsd PID_FILE=${PID_FILE:=/var/run/${prog}.pid} LOCK_FILE=${LOCK_FILE:=/var/lock/subsys/${prog}} # Generate and read config values [ -x /usr/libexec/ganesha/nfs-ganesha-config.sh ] && /usr/libexec/ganesha/nfs-ganesha-config.sh [ -f /run/sysconfig/ganesha ] && . /run/sysconfig/ganesha [ -z "$NOFILE" ] && NOFILE=1048576 OPTIONS="-L $LOGFILE -f $CONFFILE -N NIV_EVENT -p $PID_FILE $EPOCH" RETVAL=0 start() { echo -n $"Starting $prog: " if [ $UID -ne 0 ]; then RETVAL=1 failure else mkdir -p "$(dirname "$PID_FILE")" ulimit -n $NOFILE daemon --pidfile $PID_FILE $PATHPROG $OPTIONS RETVAL=$? if [ $RETVAL -eq 0 ]; then touch $LOCK_FILE else RETVAL=1 fi fi echo } stop() { echo -n $"Stopping $prog: " if [ $UID -ne 0 ]; then RETVAL=1 failure else killproc $PATHPROG RETVAL=$? if [ $RETVAL -eq 0 ]; then rm -f $LOCK_FILE success else failure fi fi echo return $RETVAL } case "$1" in start) start ;; stop) stop ;; restart) if [ -f $LOCK_FILE ] ; then stop #avoid race sleep 3 fi start ;; reload) echo -n $"Reloading $prog: " if [ -f $LOCK_FILE ] ; then killproc -p $PID_FILE $prog -HUP RETVAL=0 else RETVAL=1 fi echo ;; force-reload) stop start ;; try-restart) if [ -f $LOCK_FILE ] ; then stop #avoid race sleep 3 start fi ;; status) if [ $RETVAL -eq 5 ] ; then RETVAL=3 else status -p $PID_FILE $PATHPROG RETVAL=$? fi ;; *) echo $"Usage: $0 {start|stop|restart|reload|try-restart|status}" RETVAL=1 esac exit $RETVAL �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/init.d/sysconfig/�������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0021244�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/init.d/sysconfig/ganesha������������������������������������������������0000664�0000000�0000000�00000000146�14737566223�0022576�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������LOGFILE=/var/log/ganesha.log CONFFILE=/etc/ganesha/ganesha.conf EPOCH_EXEC=/bin/true #NODEID_EXEC="" ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/misc/�������������������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0017006�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/misc/pb_linkcount.ksh���������������������������������������������������0000664�0000000�0000000�00000000472�14737566223�0022207�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/ksh ##### LE TEST SUPPOSE QUE LE FILESYSTEM EST MONTE SUR LE REPERTOIRE /mnt ##### set -x trace TEST_REP=/mnt/dir.$$ # creation d'une respertoire vide mkdir $TEST_REP # on se place dedans cd $TEST_REP # on cree un nouveau repertoire mkdir toto # find gueule car le linkcount n'est pas bon find . -ls ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/misc/pb_stale_create.ksh������������������������������������������������0000664�0000000�0000000�00000002401�14737566223�0022626�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/ksh MNT_1=$1 if [[ ! -d $MNT_1 ]]; then echo "Usage $0 <mnt_dir>" exit 1 fi DIR_1="$MNT_1/TEST_STALE.$$" #creation du repertoire mkdir "$DIR_1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding mkdir($DIR_1)" exit 1 fi # creation fichier1 touch "$DIR_1/file.1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding touch($DIR_1/file.1)" exit 1 fi ls -l "$DIR_1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding ls ($DIR_1)" exit 1 fi ls -l "$MNT_1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding ls ($DIR_1)" exit 1 fi echo "###############################################" echo echo "Maintenant, supprimer le repertoire $DIR_1" echo "directement dans le filesystem (via scrub)" echo "(commande 'unlink TEST_STALE.$$ recurse top')" echo echo "Taper ensuite <enter> pour continuer" echo echo "###############################################" read input echo "On tente de creer un fichier dans le repertoire supprime" touch "$DIR_1/file.2" echo "Entree correspondante dans le parent" ls -l "$MNT_1" | grep "TEST_STALE.$$" echo "On attend 5s et on liste a nouveau" sleep 5 ls -l "$MNT_1" | grep "TEST_STALE.$$" ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/misc/pb_stale_rename1.ksh�����������������������������������������������0000664�0000000�0000000�00000003264�14737566223�0022723�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/ksh MNT_1=$1 if [[ ! -d $MNT_1 ]]; then echo "Usage $0 <mnt_dir>" exit 1 fi DIR_1="$MNT_1/TEST_STALE.$$" DIR_2="$MNT_1/TEST_STALE.$$_bis" #creation du repertoire 1 mkdir "$DIR_1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding mkdir($DIR_1)" exit 1 fi # creation fichier1 touch "$DIR_1/file.1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding touch($DIR_1/file.1)" exit 1 fi ls -l "$DIR_1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding ls ($DIR_1)" exit 1 fi ls -l "$MNT_1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding ls ($DIR_1)" exit 1 fi #creation du repertoire 2 mkdir "$DIR_2" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding mkdir($DIR_2)" exit 1 fi ls -l "$DIR_2" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding ls ($DIR_2)" exit 1 fi echo "###############################################" echo echo "Maintenant, supprimer le repertoire $DIR_2" echo "directement dans le filesystem (via scrub)" echo "(commande 'unlink TEST_STALE.$$_bis recurse top')" echo echo "Taper ensuite <enter> pour continuer" echo echo "###############################################" read input echo "On tente de deplacer un fichier dans le repertoire supprime" mv "$DIR_1/file.1" "$DIR_2/file.1" echo "L'entree source :" ls -l "$DIR_1/file.1" echo "L'entree destination :" ls -l "$DIR_2/file.1" echo "Entrees dans le repertoire parent" ls -l "$MNT_1" | grep "TEST_STALE.$$" echo "On attend 5s et on liste a nouveau" sleep 5 ls -l "$MNT_1" | grep "TEST_STALE.$$" ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/misc/pb_stale_rename2.ksh�����������������������������������������������0000664�0000000�0000000�00000003243�14737566223�0022721�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/ksh MNT_1=$1 if [[ ! -d $MNT_1 ]]; then echo "Usage $0 <mnt_dir>" exit 1 fi DIR_1="$MNT_1/TEST_STALE.$$" DIR_2="$MNT_1/TEST_STALE.$$_bis" #creation du repertoire 1 mkdir "$DIR_1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding mkdir($DIR_1)" exit 1 fi # creation fichier1 touch "$DIR_1/file.1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding touch($DIR_1/file.1)" exit 1 fi ls -l "$DIR_1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding ls ($DIR_1)" exit 1 fi ls -l "$MNT_1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding ls ($DIR_1)" exit 1 fi #creation du repertoire 2 mkdir "$DIR_2" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding mkdir($DIR_2)" exit 1 fi ls -l "$DIR_2" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding ls ($DIR_2)" exit 1 fi echo "###############################################" echo echo "Maintenant, supprimer le fichier $DIR_1/file.1" echo "directement dans le filesystem (via scrub)" echo "(commande 'unlink TEST_STALE.$$/file.1')" echo echo "Taper ensuite <enter> pour continuer" echo echo "###############################################" read input echo "On tente de deplacer un fichier source supprime" mv "$DIR_1/file.1" "$DIR_2/file.1" echo "L'entree source :" ls -l "$DIR_1/file.1" echo "L'entree destination :" ls -l "$DIR_2/file.1" echo "Entrees dans le repertoire parent" ls -l "$MNT_1" | grep "TEST_STALE.$$" echo "On attend 5s et on liste a nouveau" sleep 5 ls -l "$MNT_1" | grep "TEST_STALE.$$" �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/misc/pb_stale_setattr.ksh�����������������������������������������������0000664�0000000�0000000�00000002365�14737566223�0023062�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/ksh MNT_1=$1 if [[ ! -d $MNT_1 ]]; then echo "Usage $0 <mnt_dir>" exit 1 fi DIR_1="$MNT_1/TEST_STALE.$$" #creation du repertoire mkdir "$DIR_1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding mkdir($DIR_1)" exit 1 fi # creation fichier1 touch "$DIR_1/file.1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding touch($DIR_1/file.1)" exit 1 fi ls -l "$DIR_1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding ls ($DIR_1)" exit 1 fi ls -l "$MNT_1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding ls ($DIR_1)" exit 1 fi echo "###############################################" echo echo "Maintenant, supprimer le repertoire $DIR_1" echo "directement dans le filesystem (via scrub)" echo "(commande 'unlink TEST_STALE.$$ recurse top')" echo echo "Taper ensuite <enter> pour continuer" echo echo "###############################################" read input echo "On tente de faire un 'touch' le repertoire supprime" touch "$DIR_1" echo "Entree correspondante dans le parent" ls -l "$MNT_1" | grep "TEST_STALE.$$" echo "On attend 5s et on liste a nouveau" sleep 5 ls -l "$MNT_1" | grep "TEST_STALE.$$" ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/misc/pb_stale_unlink.ksh������������������������������������������������0000664�0000000�0000000�00000002711�14737566223�0022667�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/ksh MNT_1=$1 if [[ ! -d $MNT_1 ]]; then echo "Usage $0 <mnt_dir>" exit 1 fi DIR_1="$MNT_1/TEST_STALE.$$" #creation du repertoire mkdir "$DIR_1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding mkdir($DIR_1)" exit 1 fi # creation fichier touch "$DIR_1/file.1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding touch($DIR_1/file.1)" exit 1 fi touch "$DIR_1/file.2" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding touch($DIR_1/file.2)" exit 1 fi ls -l "$DIR_1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding ls ($DIR_1)" exit 1 fi ls -l "$MNT_1" > /dev/null status=$? if (( $status != 0 )); then echo "ERROR status proceeding ls ($DIR_1)" exit 1 fi echo "###############################################" echo echo "Maintenant, supprimer le repertoire $DIR_1" echo "directement dans le filesystem (via scrub)" echo "(commande 'unlink TEST_STALE.$$ recurse top')" echo echo "Taper ensuite <enter> pour continuer" echo echo "###############################################" read input echo "On tente de supprimer le repertoire STALE" rm -rf "$DIR_1" # listing dans le second repertoire echo "On tente de lister le repertoire STALE" ls -l "$DIR_1" echo "Entree dans le parent" ls -l "$MNT_1" | grep "TEST_STALE.$$" echo "On attend 5s et on liste a nouveau" sleep 5 ls -l "$MNT_1" | grep "TEST_STALE.$$" �������������������������������������������������������nfs-ganesha-6.5/src/scripts/nfs-ganesha-config.sh���������������������������������������������������0000664�0000000�0000000�00000002250�14737566223�0022043�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # # Extract configuration from /etc/sysconfig/ganesha and # copy or generate new environment variables to # /run/sysconfig/ganesha to be used by nfs-ganesha service # CONFIGFILE=/etc/sysconfig/ganesha RUNCONFIG=/run/sysconfig/ganesha if [ ! -e $(command dirname ${CONFIGFILE} 2>/dev/null) ]; then # Debian/Ubuntu CONFIGFILE=/etc/ganesha/nfs-ganesha RUNCONFIG=/etc/default/nfs-ganesha fi if [ -r ${CONFIGFILE} ]; then . ${CONFIGFILE} [ -x "${EPOCH_EXEC%% *}" ] && EPOCHVALUE=`${EPOCH_EXEC}` [ -x "${NODEID_EXEC%% *}" ] && NODEID=`${NODEID_EXEC}` NOFILE_CONF=/lib/systemd/system/nfs-ganesha.service.d/10-nofile.conf if [ -n "$NOFILE" ]; then mkdir -p $(command dirname ${NOFILE_CONF}) printf "[Service]\nLimitNOFILE=$NOFILE\n" > $NOFILE_CONF systemctl daemon-reload fi mkdir -p $(command dirname ${RUNCONFIG} 2>/dev/null) { cat ${CONFIGFILE} [ -n "${EPOCHVALUE}" ] && echo EPOCH=\"-E $EPOCHVALUE\" [ -n "${NODEID}" ] && echo GNODEID=\"-I $NODEID\" # Set NUMA options if numactl is present NUMACTL=$(command -v numactl 2>/dev/null) if [ -n "${NUMACTL}" ]; then echo NUMACTL=${NUMACTL} echo NUMAOPTS=--interleave=all fi } > ${RUNCONFIG} fi ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/podman/�����������������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0017331�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/podman/Containerfile����������������������������������������������������0000664�0000000�0000000�00000000705�14737566223�0022040�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ARG IMAGE FROM $IMAGE ARG USER_ID ARG GROUP_ID USER root COPY ["install-packages.sh", "/tmp"] RUN "/tmp/install-packages.sh" RUN { userdel ubuntu >/dev/null 2>&1 || true; } && \ { groupdel ubuntu >/dev/null 2>&1 || true; } && \ groupadd -g "$GROUP_ID" user && \ useradd -m -u "$USER_ID" -g "$GROUP_ID" -s /bin/bash user && \ mkdir -p /etc/sudoers.d && \ echo "user ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/container USER user �����������������������������������������������������������nfs-ganesha-6.5/src/scripts/podman/README.md��������������������������������������������������������0000664�0000000�0000000�00000005275�14737566223�0020621�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# ganesha-container - A script for creating containers for NFS Ganesha testing ## Motivation It can be hard to know if changes, particularly build system changes, will affect platforms other than a developer's main development platform. This script generates containers for a wide variety of Linux distributions. They include many dependencies required for building NFS Ganesha. The number of Linux distributions and the dependencies can be improved over time. The current directory is mounted into the container and used as the container's working directory. This allows at least the following use cases: * The current directory is a parent of the NFS Ganesha git repository, with build directories alongside. This allows those existing build directories to be used within the container. Build artifacts can then be examined outside the container. * The current directory is the NFS Ganesha git repository. This allows a build directory to be created in the container user's home directory, so it disappears when the container is stopped. ## Examples ### Run interactive Fedora 38 container $ ganesha-container fedora 38 [user@fedora38 nfs-ganesha]$ grep PRETTY_NAME /etc/os-release PRETTY_NAME="Fedora Linux 38 (Container Image)" [user@fedora38 nfs-ganesha]$ <control-d> exit $ Interactive containers are removed upon exit from the container's shell. ### Run interactive Debian (default version) container $ ganesha-container debian user@debian11:/home/martins$ grep PRETTY_NAME /etc/os-release PRETTY_NAME="Debian GNU/Linux 11 (bullseye)" user@debian11:/tmp$ exit exit $ ### Run a build test in all supported containers $ cmake_cmd="cmake -DUSE_FSAL_VFS=ON -DUSE_DBUS=ON -DENABLE_VFS_POSIX_ACL=YES -DUSE_ADMIN_TOOLS=YES ../nfs-ganesha/src" $ ganesha-container all sh -c "rm -rf build/* && cd build && ${cmake_cmd} && make -j" 2>&1 | tee build.wip ======================================= almalinux 8 (Tue 06 Jun 2023 08:56:51 AEST) CMake Deprecation Warning at CMakeLists.txt:26 (cmake_minimum_required): [...] The output from each container begins with a header indicating the distribution, version and current time (via the date(1) command). Given that the date always changes, the date will always appear in diffs. So, this makes it useful for comparing output from runs for different branches (e.g. next, wip). ### Generate all supported container images $ ganesha-container all </dev/null ======================================== almalinux 8 (Tue 06 Jun 2023 09:11:20 AEST) [...] This is useful for pre-generating all container images so the output from container image generation doesn't pollute the output of builds. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/podman/ganesha-container������������������������������������������������0000775�0000000�0000000�00000012375�14737566223�0022655�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # SPDX-License-Identifier: LGPL-3.0-or-later # # Copyright 2023, DataDirect Networks, Inc. All rights reserved. # Author: Martin Schwenke <mschwenke@ddn.com> # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Create a podman container with NFS-Ganesha build depencies # installed, for the given distro (and optional version). # # * If no further arguments (except perhaps --) are given, launch an # interactive bash session (if stdin is a tty). # # * If further arguments are given then they are passed to the # * container as a command. # # * "all" with a command runs the command in each container - to run a # shell pipeline use sh -l -c "<pipeline>". The "-l" is needed for # the SLE container to have a workable environment. # # * "all" with no further arguments can be used to construct all # supported containers. This can be useful for priming a test # environment. # # Please maintain this as a plain /bin/sh script that passes # ShellCheck. # # Formatted with: shfmt -w -p -i 0 -fn <file> # set -eu usage() { cat <<EOF usage: $0 { <distro> [ <version> ] | all | delete-all } [ [ -- ] <cmd>... ] If <cmd> is provided and <version> is omitted then -- must be used before <cmd>, otherwise -- is optional. supported distros: EOF list_versions exit 1 } # # Distros # list_versions() { cat <<EOF almalinux 8 almalinux 9 centos 8 centos 9 debian 10 debian 11 debian 12 fedora 37 fedora 38 fedora 39 fedora 40 rockylinux 8 rockylinux 9 sle 15.5 ubuntu 20.04 ubuntu 22.04 ubuntu 24.04 EOF } default_version() { distro="$1" case "$distro" in almalinux | centos | rockylinux) echo "9" ;; debian) echo "12" ;; fedora) echo "40" ;; sle) echo "15.5" ;; ubuntu) echo "24.04" ;; esac } validate_distro_version() { list_versions | { supported=false while read -r d v; do if [ "$distro" = "$d" ] && [ "$version" = "$v" ]; then supported=true break fi done if ! $supported; then usage fi } } # # Argument handling and validation # if [ $# -lt 1 ]; then usage fi distro="$1" shift if [ "$distro" != "all" ] && [ "$distro" != "delete-all" ]; then if [ $# -ge 1 ] && [ "$1" != "--" ]; then version="$1" shift else version=$(default_version "$distro") fi validate_distro_version fi container_image() { distro="$1" version="$2" case "$distro" in almalinux | debian | fedora | rockylinux | ubuntu) echo "docker.io/library/${distro}:${version}" ;; centos) case "$version" in 7) echo "docker.io/library/centos:${version}" ;; 8 | 9) echo "quay.io/centos/centos:stream${version}" ;; esac ;; sle) echo "registry.suse.com/suse/sle${version%%.*}" ;; esac } container_1() { distro="$1" version="$2" shift 2 image=$(container_image "$distro" "$version") tag="${distro}:${version}.ganesha" container="${distro}${version}.ganesha" cd "$(dirname "$(realpath "$0")")" if ! podman image inspect "$tag" >/dev/null 2>&1; then buildah bud \ -t "$tag" \ --build-arg IMAGE="$image" \ --build-arg USER_ID="$(id -u)" \ --build-arg GROUP_ID="$(id -g)" fi f='{{.State.Running}}' if running=$(podman container inspect \ -f "$f" "$container" >/dev/null 2>&1); then if [ "$running" = "true" ]; then echo "Container \"${container}\" already exists" exit 1 fi # Remove leftover non-running container podman container rm "$container" fi relabel="" selinux_mode=$(sestatus 2>/dev/null | sed -n -e 's|^Current mode:[[:space:]]*||p') if [ "$selinux_mode" = "enforcing" ]; then relabel=",relabel=shared" fi if [ $# -eq 0 ]; then if tty -s; then interactive="-ti" else interactive="-i" fi set -- "/bin/bash" "-l" else interactive="" fi mount="type=bind,source=${workdir},destination=${workdir}${relabel}" podman container run \ --name "$container" \ --hostname "$container" \ --rm=true \ --userns=keep-id \ --mount="$mount" \ --workdir="$workdir" \ $interactive \ "$tag" "$@" } container_foreach() { action="$1" shift list_versions | { rc=0 while read -r _distro _version; do echo "========================================" echo "${_distro} ${_version} ($(date))" case "$action" in all) # Redirect stops the container eating # the version list container_1 "$_distro" "$_version" \ "$@" </dev/null || rc=$? ;; delete-all) tag="${_distro}:${_version}.ganesha" if podman image exists "$tag"; then podman image rm "$tag" || rc=$? fi ;; esac done return $rc } } workdir="$PWD" if [ $# -ge 1 ] && [ "$1" = "--" ]; then shift fi case "$distro" in list) list_versions ;; all) container_foreach all "$@" ;; delete-all) container_foreach delete-all ;; *) container_1 "$distro" "$version" "$@" ;; esac �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/podman/install-packages.sh����������������������������������������������0000775�0000000�0000000�00000010537�14737566223�0023120�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # SPDX-License-Identifier: LGPL-3.0-or-later # # Copyright 2023, DataDirect Networks, Inc. All rights reserved. # Author: Martin Schwenke <mschwenke@ddn.com> # # This program 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 3 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, see <http://www.gnu.org/licenses/>. # # Install ganesha build dependencies for the given distributions. # This does not checked supported versions - that is done by # ganesha-container. This will obviously need continuous updates to # support new distro versions. # # Please maintain this as a plain /bin/sh script that passes # ShellCheck. # # The containers could be much lighter weight without the Python Qt # dependency. # # Formatted with: shfmt -w -p -i 0 -fn <file> # set -e install_debian() { export DEBIAN_FRONTEND="noninteractive" apt-get update libnsl_pkg="libnsl-dev" python3_distutils="python3-distutils" case "$ID" in debian) case "$VERSION_ID" in 10) libnsl_pkg="" ;; esac ;; ubuntu) case "$VERSION_ID" in 18.04 | 20.04) libnsl_pkg="" ;; 24.04) python3_distutils="" ;; esac ;; esac # shellcheck disable=SC2086 # variables may be intentionally empty apt-get install -y \ bison \ build-essential \ cmake \ debianutils \ doxygen \ flex \ g++ \ gcc \ git \ libacl1-dev \ libblkid-dev \ libcap-dev \ libdbus-1-dev \ libjemalloc-dev \ libkrb5-dev \ $libnsl_pkg \ liburcu-dev \ python3 \ $python3_distutils \ pyqt5-dev-tools \ rsync \ sudo \ uuid-dev } install_rh() { # Why can't the official images be fixed so they still work? :-o # Reference: https://stackoverflow.com/questions/70984003/centos-8-stream case "$ID" in centos) case "$VERSION_ID" in 8) mirror="http://mirror.centos.org" vault="http://vault.centos.org" sed -i \ -e 's|mirrorlist|#mirrorlist|g' \ -e "s|#baseurl=${mirror}|baseurl=${vault}|g" \ /etc/yum.repos.d/CentOS-* ;; esac ;; esac case "$ID" in fedora) : ;; *) yum install -y epel-release ;; esac python_pkg="python3" extra_repos="powertools" case "$ID" in almalinux) case "$VERSION_ID" in 9.*) extra_repos="crb" ;; esac ;; centos) case "$VERSION_ID" in 7) python_pkg="python36" extra_repos="" ;; 9) extra_repos="crb" ;; esac ;; fedora) case "$VERSION_ID" in *) extra_repos="" ;; esac ;; rocky) case "$VERSION_ID" in 9.*) extra_repos="devel" ;; esac ;; esac if [ -n "$extra_repos" ]; then yum install -y 'dnf-command(config-manager)' # shellcheck disable=SC2086 # $extra_repos can be multi-word yum config-manager --set-enabled -y $extra_repos fi yum install -y \ "@Development Tools" \ bison \ cmake \ dbus-devel \ doxygen \ flex \ gcc-c++ \ jemalloc-devel \ krb5-devel \ libacl-devel \ libasan \ libblkid-devel \ libcap-devel \ libnfsidmap-devel \ libnsl2-devel \ libuuid-devel \ "$python_pkg" \ "${python_pkg}-qt5-devel" \ rsync \ sudo \ userspace-rcu-devel \ which } install_suse() { zypper --non-interactive install --type pattern devel_basis zypper --non-interactive install \ bison \ cmake \ dbus-1-devel \ doxygen \ flex \ gcc-c++ \ git \ graphviz \ jemalloc-devel \ krb5-devel \ less \ libacl-devel \ libasan4 \ libblkid-devel \ libcap-devel \ libnsl-devel \ liburcu-devel \ libuuid-devel \ nfsidmap-devel \ python3 \ rsync \ sudo \ which } # # Get release family # . /etc/os-release case "$ID" in almalinux | centos | rhel | rocky | fedora) family="rh" ;; debian | ubuntu) family="debian" ;; sles | suse) family="suse" ;; *) echo "Unsupported distro: ${ID}" exit 1 ;; esac # # Run installation function for family # "install_${family}" �����������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/pycheckpatch������������������������������������������������������������0000775�0000000�0000000�00000005716�14737566223�0020460�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env python # Run checkpatch against commits specified. It just passes the arguments # to "git rev-list" command to get the list of commits except "-a" option. # # If "-a" option is given, it would run checkpatch against all commits. # Otherwise, it would stop at the very first commit that fails # checkpatch. # # Example: # pycheckpatch V2.2-dev-15..HEAD # pycheckpatch -a V2.2-dev-15..HEAD import os, sys, subprocess checkpatch = "./src/scripts/checkpatch.pl" if len(sys.argv) < 2: sys.exit('\tUsage: %s [-a] [rev-list-opts] {rev-list-args}' % sys.argv[0]) rev_list_args = sys.argv[1:] check_all = False if "-a" in rev_list_args: rev_list_args.remove("-a") check_all = True # Python 2.6 lacks check_output method. Following code copied from # Python2.7 source code to work with python2.6 or lower versions. if "check_output" not in dir(subprocess): def f(*popenargs, **kwargs): if 'stdout' in kwargs: raise ValueError('stdout argument not allowed, it will be overridden.') process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) output, unused_err = process.communicate() retcode = process.poll() if retcode: cmd = kwargs.get("args") if cmd is None: cmd = popenargs[0] error = subprocess.CalledProcessError(retcode, cmd) error.output = output raise error return output subprocess.check_output = f cmd = "git rev-parse --show-toplevel" output = subprocess.check_output(cmd, shell=True) # change the cwd to top level git directory for checkpatch config file. os.chdir(output.strip()) cmd = ['git', 'rev-list', '--no-merges'] + rev_list_args output = subprocess.check_output(cmd) commits = output.splitlines() if len(commits) == 0: sys.exit("'%s' resulted in zero commits to check" % " ".join(rev_list_args)) result = [] for commit in commits: cmd = "git show %s --format=email | %s -" % (commit, checkpatch) try: subprocess.check_output(cmd, shell=True) result.append((commit, True, "")) except subprocess.CalledProcessError as e: result.append((commit, False, e.output)) no_tree_error = "Must be run from the top-level dir. of a kernel tree" if no_tree_error in e.output: out = """ You don't have ganesha checkpatch config file placed in the top level git directory. "cp ./src/scripts/checkpatch.conf .checkpatch.conf" should fix the error! checkpatch.pl failed with the following error message: %s""" % e.output sys.exit(out) elif not check_all: break # check all statuses status = [s for c,s,o in result] if all(status): print("All commits passed checkpatch!") else: print("Not all commits passed checkpatch! :-(") for commit,status,out in result: s = "passed" if status else "failed" print("COMMIT %s %s" % (commit, s)) if not status: print(out) sys.exit(1) ��������������������������������������������������nfs-ganesha-6.5/src/scripts/reindenture�������������������������������������������������������������0000775�0000000�0000000�00000000767�14737566223�0020337�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh if [ $# -eq 0 ]; then echo usage: reindenture dir1 [dir2 ... dirn] 1>&2 exit 1 fi; while [ $# -gt 0 ]; do dir=$1 echo Reindenting files under $dir env VERSION_CONTROL=simple \ find $dir -type f \( -name \*.c -o -name \*.h \) -print0 | \ xargs -0 indent -nbad -bap -nbc -bbo -hnl -br -brs -c33 -cd33 \ -ncdb -ce -ci4 -cli0 -d0 -di1 -nfc1 -i8 -ip0 -l80 -lp -npcs \ -nprs -npsl -sai -saf -saw -ncs -nsc -sob -nfca -cp33 -ss -ts8 \ -il1 --ignore-newlines shift done ���������nfs-ganesha-6.5/src/scripts/runcp.sh����������������������������������������������������������������0000775�0000000�0000000�00000021712�14737566223�0017544�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # Copyright Panasas, 2013 # Contributor: Frank Filz <ffilzlnx@mindspring.com> # # # This software is a server that implements the NFS protocol. # # # This program 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 3 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 # 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 #------------------------------------------------------------------------------- # Find directory where runcp.sh lives for checkpatch will be there too # We keep that in CURDIR CURDIR=$(dirname $(readlink -m $0)) check_one_file() { FDIR=`echo $1 | sed -e "s@$DIR\(.*\)/.*@$ODIR\1@"` OUTFILE=`echo $1 | sed -e "s@$DIR\(.*\)@$ODIR\1.cp@"` #echo FIDR=$FIDR DIR=$DIR FILE=$1 OUTFILE=$OUTFILE if [ $ONEFILE -eq 1 ] then OUTFILE=$TEMP elif [ ! -d $FDIR ] then if [ -e $FDIR ] then echo "Oops, $FDIR is not a directory" return fi mkdir -p $FDIR fi EXTRA_OPT="" if [ $NO_SPACING -eq 1 ] then echo $1 > $OUTFILE egrep $NO_SPACING_FILES $OUTFILE 2>&1 >/dev/null RC=$? if [ $RC -eq 0 ] then EXTRA_OPT="$EXTRA_OPT --ignore SPACING" fi egrep $NO_MACRO_W_FLOW_FILES $OUTFILE 2>&1 >/dev/null RC=$? if [ $RC -eq 0 ] then EXTRA_OPT="$EXTRA_OPT --ignore MACRO_WITH_FLOW_CONTROL" fi egrep $NO_COMPLEX_MACRO_FILES $OUTFILE 2>&1 >/dev/null RC=$? if [ $RC -eq 0 ] then EXTRA_OPT="$EXTRA_OPT --ignore COMPLEX_MACRO" fi egrep $NO_BRACKET_SPACE_FILES $OUTFILE 2>&1 >/dev/null RC=$? if [ $RC -eq 0 ] then EXTRA_OPT="$EXTRA_OPT --ignore BRACKET_SPACE" fi egrep $NO_DATE_TIME_FILES $OUTFILE 2>&1 >/dev/null RC=$? if [ $RC -eq 0 ] then EXTRA_OPT="$EXTRA_OPT --ignore DATE_TIME" fi egrep $NO_STATIC_CONST_CHAR_ARRAY_FILES $OUTFILE 2>&1 >/dev/null RC=$? if [ $RC -eq 0 ] then EXTRA_OPT="$EXTRA_OPT --ignore STATIC_CONST_CHAR_ARRAY" fi egrep $NO_ENOSYS_FILES $OUTFILE 2>&1 >/dev/null RC=$? if [ $RC -eq 0 ] then EXTRA_OPT="$EXTRA_OPT --ignore ENOSYS" fi fi $CURDIR/checkpatch.pl $TYPEDEF $EXTRA_OPT \ --file $1 > $OUTFILE 2> $ERROR_FILE RESULT=`grep '^total:' $OUTFILE` if [ -n "$REPORT_FILT" ] then grep "$REPORT_FILT" $OUTFILE > /dev/null 2>&1 RC=$? else RC=1 fi if [ $RC -eq 1 ] || [ -s $ERROR_FILE ] then if [ $ONEFILE -eq 1 ] then echo "=========================================================================================================================" >>$REPORT_FILE if [ $NO_CRUFT -eq 1 ] then cat $OUTFILE | \ egrep -v "NOTE: Ignored message|If any of these errors|them to the maintainer" \ >>$REPORT_FILE else cat $OUTFILE >>$REPORT_FILE fi cat $ERROR_FILE >>$REPORT_FILE else echo $1 $RESULT >>$REPORT_FILE cat $ERROR_FILE | sed -e "s@\(.*\)@$1 \1@" >>$REPORT_FILE cat $ERROR_FILE >>$OUTFILE fi if [ $QUIET -eq 0 ] then echo $1 $RESULT cat $ERROR_FILE | sed -e "s@\(.*\)@$1 \1@" fi fi } check_files() { while [ -n "$1" ] do if [ -s "$1" ] then check_one_file $1 fi shift done } check_find() { check_files `find $DIR -name '*.[ch]' | egrep -v "$EXCLUDE" | sort` } check_git_files() { while [ -n "$1" ] do check_one_file $DIR/$1 shift done } check_git() { echo "diff --name-only $COMMIT | egrep -v $EXCLUDE" git diff --name-only $COMMIT | egrep -v "$EXCLUDE" check_git_files `git diff --name-only $COMMIT | egrep -v "$EXCLUDE" | sort` } show_help() { cat << ... Options: -c Don't show \"Clean\" files (files with no errors and no warnings) -w Don't show files that had no errors (even if they have warnings) -c prevails over -w -q Quiet, don't show report in progress -1 Put the results of each check into a separate file in the output dir for example, results for checkpatch of foo/test.c would show up in /tmp/checkpatch/foo/test.c.cp -d {dir} Directory to check (including sub-directories), defaults to current working directory: -x Exclude files egrep expression (libtirpc|libntirpc is prepended to the expression) -v turn off: --ignore SPACING on RPC program header files (like nfs23.h) and --ignore COMPLEX_MACRO on certain files --ignore BRACKET_SPACE in certain files --ignore DEEP_INDENTATION in certain files -i Include files agreed on to ignore (config_parsing|Protocols/XDR -e Include files from external prohects (murmur3|cidr|atomic_x86|city) -g Use git-diff --name-only instead of find (-d will be ignored) -k Specify commit for git-diff, default is HEAD -o {dir} Directory to direct output files to, defaults to /tmp/checkpatch -r Output report at end -t Ignore typedefs ... } CLEAN=0 DIR="." ALWAYS="libtirpc|libntirpc|CMakeFiles|tools/test_findlog.c|include/config.h" ALWAYS="$ALWAYS|nfsv41.h|nlm4.h|nsm.h|rquota.h|nfsacl.h" EXTERNAL="murmur3.h|cidr.h|cidr/|include/city|avltree.h" EXTERNAL="$EXTERNAL|avl/|FSAL/FSAL_GPFS/include" NO_EXTERNAL=0 IGNORE="config_parsing|Protocols/XDR" IGNORE="$IGNORE|include/gsh_intrinsic.h" NO_IGNORE=0 EXCLUDE="" QUIET=0 ODIR=/tmp/checkpatch NOWARN=0 ONEFILE=1 REPORT=0 FIND=check_find COMMIT=HEAD TYPEDEF="" NO_CRUFT=0 NO_SPACING_FILES="nfs23.h|nfsv41.h|nlm4.h|nsm.h|rquota.h|nfsacl.h" NO_COMPLEX_MACRO_FILES="include/ganesha_dbus.h|include/server_stats_private.h" NO_COMPLEX_MACRO_FILES="$NO_COMPLEX_MACRO_FILES|include/gsh_intrinsic.h" NO_COMPLEX_MACRO_FILES="$NO_COMPLEX_MACRO_FILES|support/exports.c" NO_COMPLEX_MACRO_FILES="$NO_COMPLEX_MACRO_FILES|support/export_mgr.c" NO_COMPLEX_MACRO_FILES="$NO_COMPLEX_MACRO_FILES|include/gsh_dbus.h" NO_MACRO_W_FLOW_FILES="FSAL/FSAL_PROXY_V4/handle_mapping/handle_mapping_db.c" NO_MACRO_W_FLOW_FILES="$NO_MACRO_W_FLOW_FILES|include/9p.h" NO_MACRO_W_FLOW_FILES="$NO_MACRO_W_FLOW_FILES|multilock/ml_functions.c" NO_BRACKET_SPACE_FILES="include/9p_req_queue.h" NO_DATE_TIME_FILES="MainNFSD/nfs_main.c" NO_STATIC_CONST_CHAR_ARRAY_FILES="FSAL_CEPH/main.c" NO_ENOSYS_FILES="FSAL_GPFS/fsal_up.c|FSAL_GPFS/gpfsext.c" NO_SPACING=1 while getopts "cd:x:qo:?w1rgk:tievK" OPT; do case $OPT in c) CLEAN=1 ;; w) NOWARN=1 ;; q) QUIET=1 ;; 1) ONEFILE=0 ;; d) DIR=$OPTARG ;; x) EXCLUDE="$OPTARG" ;; i) NO_IGNORE=1 ;; e) NO_EXTERNAL=1 ;; g) FIND=check_git ;; k) COMMIT=$OPTARG ;; o) ODIR=$OPTARG ;; r) REPORT=1 ;; t) TYPEDEF="--ignore NEW_TYPEDEFS" ;; v) NO_SPACING=0 ;; K) NO_CRUFT=1 ;; ?) show_help exit ;; esac done if [ $DIR = '.' ] then DIR=`pwd` fi if [ ! -d $ODIR ] then if [ -e $ODIR ] then echo "Oops, $ODIR is not a directory" return fi mkdir -p $ODIR fi if [ $FIND = check_git ] then DIR=`git rev-parse --show-toplevel` fi REPORT_FILE=$ODIR/results.cp TEMP=$ODIR/results.temp ERROR_FILE=$ODIR/results.err date > $REPORT_FILE if [ $FIND = check_git ] then echo "FILES: git-diff --name-only $COMMIT" >> $REPORT_FILE else echo "FILES: find $DIR -name '*.[ch]'" >> $REPORT_FILE fi echo "CLEAN=$CLEAN NOWARN=$NOWARN TYPEDEF=$TYPEDEF" >> $REPORT_FILE if [ $NO_SPACING -eq 1 ] then echo >> $REPORT_FILE echo "NO_SPACING_FILES=$NO_SPACING_FILES" >> $REPORT_FILE echo "NO_COMPLEX_MACRO_FILES=$NO_COMPLEX_MACRO_FILES" >> $REPORT_FILE echo "NO_MACRO_W_FLOW_FILES=$NO_MACRO_W_FLOW_FILES" >> $REPORT_FILE echo "NO_BRACKET_SPACE_FILES=$NO_BRACKET_SPACE_FILES" >> $REPORT_FILE echo "NO_DATE_TIME_FILES=$NO_DATE_TIME_FILES" >> $REPORT_FILE echo "NO_STATIC_CONST_CHAR_ARRAY_FILES=$NO_STATIC_CONST_CHAR_ARRAY_FILES" >> $REPORT_FILE echo "NO_ENOSYS_FILES=$NO_ENOSYS_FILES" >> $REPORT_FILE fi if [ -n "$EXCLUDE" ] then echo >> $REPORT_FILE echo "EXCLUDE=$EXCLUDE" >> $REPORT_FILE fi if [ $NO_IGNORE -eq 0 ] then if [ -n "$EXCLUDE" ] then EXCLUDE="$IGNORE|$EXCLUDE" else EXCLUDE="$IGNORE" fi echo >> $REPORT_FILE echo "IGNORE=$IGNORE" >> $REPORT_FILE fi if [ $NO_EXTERNAL -eq 0 ] then if [ -n "$EXCLUDE" ] then EXCLUDE="$EXTERNAL|$EXCLUDE" else EXCLUDE="$EXTERNAL" fi echo >> $REPORT_FILE echo "EXTERNAL=$EXTERNAL" >> $REPORT_FILE fi EXCLUDE="$ALWAYS|$EXCLUDE" echo >> $REPORT_FILE if [ $QUIET -eq 0 ] then cat $REPORT_FILE fi if [ $CLEAN -eq 1 ] then REPORT_FILT="^total: 0 errors, 0 warnings" elif [ $NOWARN -eq 1 ] then REPORT_FILT="^total: 0 errors" else REPORT_FILT="" fi $FIND if [ -e $TEMP ] then rm -f $TEMP rm -f $ERROR_FILE fi if [ $REPORT -eq 1 ] then cat $REPORT_FILE fi ������������������������������������������������������nfs-ganesha-6.5/src/scripts/spelling.txt������������������������������������������������������������0000664�0000000�0000000�00000051467�14737566223�0020446�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: GPL-2.0 # # Originally from Debian's Lintian tool. Various false positives have been # removed, and various additions have been made as they've been discovered # in the kernel source. # # License: GPLv2 # # The format of each line is: # mistake||correction # abandonning||abandoning abigious||ambiguous abitrate||arbitrate abov||above abreviated||abbreviated absense||absence absolut||absolute absoulte||absolute acccess||access acceleratoin||acceleration accelleration||acceleration accesing||accessing accesnt||accent accessable||accessible accesss||access accidentaly||accidentally accidentually||accidentally accoding||according accomodate||accommodate accomodates||accommodates accordign||according accoring||according accout||account accquire||acquire accquired||acquired acessable||accessible acess||access achitecture||architecture acient||ancient acitions||actions acitve||active acknowldegement||acknowldegement acknowledgement||acknowledgment ackowledge||acknowledge ackowledged||acknowledged acording||according activete||activate acumulating||accumulating adapater||adapter addional||additional additionaly||additionally addres||address addreses||addresses addresss||address aditional||additional aditionally||additionally aditionaly||additionally adminstrative||administrative adress||address adresses||addresses adviced||advised afecting||affecting agaist||against albumns||albums alegorical||allegorical algorith||algorithm algorithmical||algorithmically algoritm||algorithm algoritms||algorithms algorrithm||algorithm algorritm||algorithm allign||align allocatrd||allocated allocte||allocate allpication||application alocate||allocate alogirhtms||algorithms alogrithm||algorithm alot||a lot alow||allow alows||allows altough||although alue||value ambigious||ambiguous amoung||among amout||amount analysator||analyzer ang||and anniversery||anniversary annoucement||announcement anomolies||anomalies anomoly||anomaly anway||anyway aplication||application appearence||appearance applicaion||application appliction||application applictions||applications appplications||applications appropiate||appropriate appropriatly||appropriately approriate||appropriate approriately||appropriately aquainted||acquainted aquired||acquired arbitary||arbitrary architechture||architecture arguement||argument arguements||arguments aritmetic||arithmetic arne't||aren't arraival||arrival artifical||artificial artillary||artillery assiged||assigned assigment||assignment assigments||assignments assistent||assistant assocation||association associcated||associated assotiated||associated assum||assume assumtpion||assumption asuming||assuming asycronous||asynchronous asynchnous||asynchronous atomatically||automatically atomicly||atomically attachement||attachment attched||attached attemps||attempts attruibutes||attributes authentification||authentication automaticaly||automatically automaticly||automatically automatize||automate automatized||automated automatizes||automates autonymous||autonomous auxilliary||auxiliary avaiable||available avaible||available availabe||available availabled||available availablity||availability availale||available availavility||availability availble||available availiable||available avalable||available avaliable||available aysnc||async backgroud||background backword||backward backwords||backwards bahavior||behavior bakup||backup baloon||balloon baloons||balloons bandwith||bandwidth batery||battery beacuse||because becasue||because becomming||becoming becuase||because beeing||being befor||before begining||beginning beter||better betweeen||between bianries||binaries bitmast||bitmask boardcast||broadcast borad||board boundry||boundary brievely||briefly broadcat||broadcast cacluated||calculated caculation||calculation calender||calendar calle||called calucate||calculate calulate||calculate cancelation||cancellation capabilites||capabilities capabitilies||capabilities capatibilities||capabilities carefuly||carefully cariage||carriage catagory||category challange||challenge challanges||challenges chanell||channel changable||changeable channle||channel channnel||channel charachter||character charachters||characters charactor||character charater||character charaters||characters charcter||character checksuming||checksumming childern||children childs||children chiled||child chked||checked chnage||change chnages||changes chnnel||channel choosen||chosen chouse||chose circumvernt||circumvent claread||cleared clared||cleared closeing||closing clustred||clustered collapsable||collapsible colorfull||colorful comand||command comit||commit commerical||commercial comming||coming comminucation||communication commited||committed commiting||committing committ||commit commoditiy||commodity compability||compatibility compaibility||compatibility compatability||compatibility compatable||compatible compatibiliy||compatibility compatibilty||compatibility compilant||compliant compleatly||completely completly||completely complient||compliant componnents||components compres||compress compresion||compression comression||compression comunication||communication conbination||combination conditionaly||conditionally conected||connected configuratoin||configuration configuraton||configuration configuretion||configuration conider||consider conjuction||conjunction connectinos||connections connnection||connection connnections||connections consistancy||consistency consistant||consistent containes||contains containts||contains contaisn||contains contant||contact contence||contents continous||continuous continously||continuously continueing||continuing contraints||constraints controled||controlled controler||controller controll||control contruction||construction contry||country convertion||conversion convertor||converter convienient||convenient convinient||convenient corected||corrected correponding||corresponding correponds||corresponds correspoding||corresponding cotrol||control couter||counter coutner||counter cryptocraphic||cryptographic cunter||counter curently||currently dafault||default deafult||default deamon||daemon decompres||decompress decription||description defailt||default defferred||deferred definate||definite definately||definitely defintion||definition defualt||default defult||default deivce||device delared||declared delare||declare delares||declares delaring||declaring delemiter||delimiter dependancies||dependencies dependancy||dependency dependant||dependent depreacted||deprecated depreacte||deprecate desactivate||deactivate desciptors||descriptors descrition||description descritptor||descriptor desctiptor||descriptor desriptor||descriptor desriptors||descriptors destory||destroy destoryed||destroyed destorys||destroys destroied||destroyed detabase||database develope||develop developement||development developped||developed developpement||development developper||developer developpment||development deveolpment||development devided||divided deviece||device diable||disable dictionnary||dictionary diferent||different differrence||difference difinition||definition diplay||display direectly||directly disapear||disappear disapeared||disappeared disappared||disappeared disconnet||disconnect discontinous||discontinuous dispertion||dispersion dissapears||disappears distiction||distinction docuentation||documentation documantation||documentation documentaion||documentation documment||document dorp||drop dosen||doesn downlad||download downlads||downloads druing||during dynmaic||dynamic easilly||easily ecspecially||especially edditable||editable editting||editing efficently||efficiently ehther||ether eigth||eight eletronic||electronic enabledi||enabled enchanced||enhanced encorporating||incorporating encrupted||encrypted encrypiton||encryption endianess||endianness enhaced||enhanced enlightnment||enlightenment enocded||encoded enterily||entirely enviroiment||environment enviroment||environment environement||environment environent||environment eqivalent||equivalent equiped||equipped equivelant||equivalent equivilant||equivalent eror||error estbalishment||establishment etsablishment||establishment etsbalishment||establishment excecutable||executable exceded||exceeded excellant||excellent existance||existence existant||existent exixt||exist exlcude||exclude exlcusive||exclusive exmaple||example expecially||especially explicite||explicit explicitely||explicitly explict||explicit explictly||explicitly expresion||expression exprimental||experimental extened||extended extensability||extensibility extention||extension extracter||extractor faild||failed faill||fail failue||failure failuer||failure faireness||fairness faliure||failure familar||familiar fatser||faster feauture||feature feautures||features fetaure||feature fetaures||features fileystem||filesystem finanize||finalize findn||find finilizes||finalizes finsih||finish flusing||flushing folloing||following followign||following follwing||following forseeable||foreseeable forse||force fortan||fortran forwardig||forwarding framwork||framework frequncy||frequency frome||from fucntion||function fuction||function fuctions||functions funcion||function functionallity||functionality functionaly||functionally functionnality||functionality functonality||functionality funtion||function funtions||functions furthur||further futhermore||furthermore futrue||future gaurenteed||guaranteed generiously||generously genric||generic globel||global grabing||grabbing grahical||graphical grahpical||graphical grapic||graphic guage||gauge guarentee||guarantee halfs||halves hander||handler handfull||handful hanled||handled harware||hardware heirarchically||hierarchically helpfull||helpful hierachy||hierarchy hierarchie||hierarchy howver||however hsould||should hypter||hyper identidier||identifier imblance||imbalance immeadiately||immediately immedaite||immediate immediatelly||immediately immediatly||immediately immidiate||immediate impelentation||implementation impementated||implemented implemantation||implementation implemenation||implementation implementaiton||implementation implementated||implemented implemention||implementation implemetation||implementation implemntation||implementation implentation||implementation implmentation||implementation implmenting||implementing incomming||incoming incompatabilities||incompatibilities incompatable||incompatible inconsistant||inconsistent increas||increase incrment||increment indendation||indentation indended||intended independant||independent independantly||independently independed||independent indiate||indicate inexpect||inexpected infomation||information informatiom||information informations||information informtion||information infromation||information ingore||ignore inital||initial initalised||initialized initalise||initialize initalize||initialize initation||initiation initators||initiators initializiation||initialization initialzed||initialized initilization||initialization initilize||initialize inofficial||unofficial instal||install inteface||interface integreated||integrated integrety||integrity integrey||integrity intendet||intended intented||intended interanl||internal interchangable||interchangeable interferring||interfering interger||integer intermittant||intermittent internel||internal interoprability||interoperability interrface||interface interrrupt||interrupt interrup||interrupt interrups||interrupts interruptted||interrupted interupted||interrupted interupt||interrupt intial||initial intialized||initialized intialize||initialize intregral||integral intrrupt||interrupt intuative||intuitive invaid||invalid invalde||invald invalide||invalid invididual||individual invokation||invocation invokations||invocations irrelevent||irrelevant isssue||issue itslef||itself jave||java jeffies||jiffies juse||just jus||just kown||known langage||language langauage||language langauge||language langugage||language lauch||launch leightweight||lightweight lengh||length lenght||length lenth||length lesstiff||lesstif libaries||libraries libary||library librairies||libraries libraris||libraries licenceing||licencing loggging||logging loggin||login logile||logfile loosing||losing losted||lost machinary||machinery maintainance||maintenance maintainence||maintenance maintan||maintain makeing||making malplaced||misplaced malplace||misplace managable||manageable managment||management mangement||management manoeuvering||maneuvering mappping||mapping mathimatical||mathematical mathimatic||mathematic mathimatics||mathematics maxium||maximum mechamism||mechanism meetign||meeting ment||meant mergable||mergeable mesage||message messags||messages messgaes||messages messsage||message messsages||messages microprocesspr||microprocessor milliseonds||milliseconds minumum||minimum miscelleneous||miscellaneous misformed||malformed mispelled||misspelled mispelt||misspelt miximum||maximum mmnemonic||mnemonic mnay||many modeled||modelled modulues||modules monochorome||monochrome monochromo||monochrome monocrome||monochrome mopdule||module mroe||more mulitplied||multiplied multidimensionnal||multidimensional multple||multiple mumber||number muticast||multicast mutiple||multiple mutli||multi nams||names navagating||navigating nead||need neccecary||necessary neccesary||necessary neccessary||necessary necesary||necessary negaive||negative negoitation||negotiation negotation||negotiation nerver||never nescessary||necessary nessessary||necessary noticable||noticeable notications||notifications notifed||notified numebr||number numner||number obtaion||obtain occassionally||occasionally occationally||occasionally occurance||occurrence occurances||occurrences occured||occurred occurence||occurrence occure||occurred occuring||occurring offet||offset omitt||omit ommiting||omitting ommitted||omitted onself||oneself ony||only operatione||operation opertaions||operations optionnal||optional optmizations||optimizations orientatied||orientated orientied||oriented otherise||otherwise ouput||output overaall||overall overhread||overhead overlaping||overlapping overriden||overridden overun||overrun pacakge||package pachage||package packacge||package packege||package packge||package packtes||packets pakage||package pallette||palette paln||plan paramameters||parameters paramater||parameter parametes||parameters parametised||parametrised paramter||parameter paramters||parameters particuarly||particularly particularily||particularly pased||passed passin||passing pathes||paths pecularities||peculiarities peformance||performance peice||piece pendantic||pedantic peprocessor||preprocessor perfoming||performing permissons||permissions peroid||period persistance||persistence persistant||persistent platfrom||platform plattform||platform pleaes||please ploting||plotting plugable||pluggable poinnter||pointer poiter||pointer posible||possible positon||position possibilites||possibilities powerfull||powerful preceeded||preceded preceeding||preceding preceed||precede precendence||precedence precission||precision prefered||preferred prefferably||preferably premption||preemption prepaired||prepared pressre||pressure primative||primitive princliple||principle priorty||priority privilaged||privileged privilage||privilege priviledge||privilege priviledges||privileges probaly||probably procceed||proceed proccesors||processors procesed||processed proces||process processessing||processing processess||processes processpr||processor processsed||processed processsing||processing procteted||protected prodecure||procedure progams||programs progess||progress programers||programmers programm||program programms||programs progresss||progress promps||prompts pronnounced||pronounced prononciation||pronunciation pronouce||pronounce pronunce||pronounce propery||property propigate||propagate propigation||propagation propogate||propagate prosess||process protable||portable protcol||protocol protecion||protection protocoll||protocol psudo||pseudo psuedo||pseudo psychadelic||psychedelic pwoer||power quering||querying raoming||roaming reasearcher||researcher reasearchers||researchers reasearch||research recepient||recipient receving||receiving recieved||received recieve||receive reciever||receiver recieves||receives recogniced||recognised recognizeable||recognizable recommanded||recommended recyle||recycle redircet||redirect redirectrion||redirection refcounf||refcount refence||reference refered||referred referenace||reference refering||referring refernces||references refernnce||reference refrence||reference registerd||registered registeresd||registered registes||registers registraration||registration regster||register regualar||regular reguator||regulator regulamentations||regulations reigstration||registration releated||related relevent||relevant remoote||remote remore||remote removeable||removable repectively||respectively replacable||replaceable replacments||replacements replys||replies reponse||response representaion||representation reqeust||request requiere||require requirment||requirement requred||required requried||required requst||request reseting||resetting resizeable||resizable resouces||resources resoures||resources ressizes||resizes ressource||resource ressources||resources retransmited||retransmitted retreived||retrieved retreive||retrieve retrive||retrieve retuned||returned reuest||request reuqest||request reutnred||returned rmeoved||removed rmeove||remove rmeoves||removes rountine||routine routins||routines rquest||request runing||running runned||ran runnning||running runtine||runtime sacrifying||sacrificing safly||safely safty||safety savable||saveable scaned||scanned scaning||scanning scarch||search seach||search searchs||searches secquence||sequence secund||second segement||segment senarios||scenarios sentivite||sensitive separatly||separately sepcify||specify sepc||spec seperated||separated seperately||separately seperate||separate seperatly||separately seperator||separator sepperate||separate sequece||sequence sequencial||sequential serveral||several setts||sets settting||setting shotdown||shutdown shoud||should shoule||should shrinked||shrunk siginificantly||significantly signabl||signal similary||similarly similiar||similar simlar||similar simliar||similar simpified||simplified singaled||signaled singal||signal singed||signed sleeped||slept softwares||software speach||speech specfic||specific speciefied||specified specifc||specific specifed||specified specificatin||specification specificaton||specification specifing||specifying specifiying||specifying speficied||specified speicify||specify speling||spelling spinlcok||spinlock spinock||spinlock splitted||split spreaded||spread sructure||structure stablilization||stabilization staically||statically staion||station standardss||standards standartization||standardization standart||standard staticly||statically stoped||stopped stoppped||stopped straming||streaming struc||struct structres||structures stuct||struct sturcture||structure subdirectoires||subdirectories suble||subtle succesfully||successfully succesful||successful successfull||successful sucessfully||successfully sucess||success superflous||superfluous superseeded||superseded suplied||supplied suported||supported suport||support suppored||supported supportin||supporting suppoted||supported suppported||supported suppport||support supress||suppress surpresses||suppresses susbsystem||subsystem suspicously||suspiciously swaping||swapping switchs||switches symetric||symmetric synax||syntax synchonized||synchronized syncronize||synchronize syncronizing||synchronizing syncronus||synchronous syste||system sytem||system sythesis||synthesis taht||that targetted||targeted targetting||targeting teh||the temorary||temporary temproarily||temporarily thier||their threds||threads threshhold||threshold throught||through thses||these tiggered||triggered tipically||typically tmis||this torerable||tolerable tramsmitted||transmitted tramsmit||transmit tranfer||transfer transciever||transceiver transferd||transferrd transfered||transferred transfering||transferring transision||transition transmittd||transmitted transormed||transformed trasmission||transmission treshold||threshold trigerring||triggering trun||turn ture||true tyep||type udpate||update uesd||used unconditionaly||unconditionally underun||underrun unecessary||unnecessary unexecpted||unexpected unexpectd||unexpected unexpeted||unexpected unfortunatelly||unfortunately unifiy||unify unknonw||unknown unknow||unknown unkown||unknown unneedingly||unnecessarily unresgister||unregister unsinged||unsigned unstabel||unstable unsuccessfull||unsuccessful unsuported||unsupported untill||until unuseful||useless upate||update usefule||useful usefull||useful usege||usage usera||users usualy||usually utilites||utilities utillities||utilities utilties||utilities utiltity||utility utitity||utility utitlty||utility vaid||valid vaild||valid valide||valid variantions||variations varient||variant vaule||value verbse||verbose verisons||versions verison||version verson||version vicefersa||vice-versa virtal||virtual virtaul||virtual virtiual||virtual visiters||visitors vitual||virtual wating||waiting whataver||whatever whenver||whenever wheter||whether whe||when wierd||weird wiil||will wirte||write withing||within wnat||want workarould||workaround writeing||writing writting||writing zombe||zombie zomebie||zombie ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/systemd/����������������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0017543�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/systemd/nfs-ganesha-config.service-in.cmake�����������������������������0000664�0000000�0000000�00000000231�14737566223�0026241�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[Unit] Description=Process NFS-Ganesha configuration DefaultDependencies=no [Service] Type=oneshot ExecStart=@LIBEXECDIR@/ganesha/nfs-ganesha-config.sh �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/systemd/nfs-ganesha-lock.service.debian10�������������������������������0000664�0000000�0000000�00000001471�14737566223�0025632�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of nfs-ganesha. # # rpc.statd is started by the nfs-lock.service, but that also loads the 'lockd' # kernel module in 'ExecStartPre'. The 'lockd' kernel module will register # itself as 'nlockmgr' which conflicts with the nfs-ganesha locking # implementation. # # This unit includes all the nfs-lock.service settings and details, but # overrides the 'ExecStartPre' and 'ExecStartPost' options. # # When this unit is started, the original nfs-lock.service is stopped (due to # the 'Conflicts' directive). With stopping the nfs-lock.service, 'lockd' gets # instructed to unregister 'nlockmgr' from the portmapper. # # The nfs-ganesha.service depends on this unit. # [Unit] Before=nfs-ganesha.service Conflicts=nfs-lock.service [Service] ExecStartPre= ExecStopPost= [Install] WantedBy=nfs-ganesha.service �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/systemd/nfs-ganesha-lock.service.debian8��������������������������������0000664�0000000�0000000�00000001523�14737566223�0025557�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of nfs-ganesha. # # rpc.statd is started by the nfs-lock.service, but that also loads the 'lockd' # kernel module in 'ExecStartPre'. The 'lockd' kernel module will register # itself as 'nlockmgr' which conflicts with the nfs-ganesha locking # implementation. # # This unit includes all the nfs-lock.service settings and details, but # overrides the 'ExecStartPre' and 'ExecStartPost' options. # # When this unit is started, the original nfs-lock.service is stopped (due to # the 'Conflicts' directive). With stopping the nfs-lock.service, 'lockd' gets # instructed to unregister 'nlockmgr' from the portmapper. # # The nfs-ganesha.service depends on this unit. # .include /lib/systemd/system/rpc-statd.service [Unit] Before=nfs-ganesha.service Conflicts=nfs-lock.service rpc-statd.service [Service] ExecStartPre= ExecStopPost= �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/systemd/nfs-ganesha-lock.service.el7������������������������������������0000664�0000000�0000000�00000001527�14737566223�0024740�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of nfs-ganesha. # # rpc.statd is started by the nfs-lock.service, but that also loads the 'lockd' # kernel module in 'ExecStartPre'. The 'lockd' kernel module will register # itself as 'nlockmgr' which conflicts with the nfs-ganesha locking # implementation. # # This unit includes all the nfs-lock.service settings and details, but # overrides the 'ExecStartPre' and 'ExecStartPost' options. # # When this unit is started, the original nfs-lock.service is stopped (due to # the 'Conflicts' directive). With stopping the nfs-lock.service, 'lockd' gets # instructed to unregister 'nlockmgr' from the portmapper. # # The nfs-ganesha.service depends on this unit. # .include /usr/lib/systemd/system/rpc-statd.service [Unit] Before=nfs-ganesha.service Conflicts=nfs-lock.service rpc-statd.service [Service] ExecStartPre= ExecStopPost= �������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/systemd/nfs-ganesha-lock.service.el8������������������������������������0000664�0000000�0000000�00000001470�14737566223�0024736�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of nfs-ganesha. # # rpc.statd is started by the nfs-lock.service, but that also loads the 'lockd' # kernel module in 'ExecStartPre'. The 'lockd' kernel module will register # itself as 'nlockmgr' which conflicts with the nfs-ganesha locking # implementation. # # This unit includes all the nfs-lock.service settings and details, but # overrides the 'ExecStartPre' and 'ExecStartPost' options. # # When this unit is started, the original nfs-lock.service is stopped (due to # the 'Conflicts' directive). With stopping the nfs-lock.service, 'lockd' gets # instructed to unregister 'nlockmgr' from the portmapper. # # The nfs-ganesha.service depends on this unit. # [Unit] Before=nfs-ganesha.service Conflicts=nfs-lock.service [Service] ExecStartPre= ExecStopPost= [Install] WantedBy=nfs-ganesha.service ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/systemd/nfs-ganesha.service.debian8�������������������������������������0000664�0000000�0000000�00000002317�14737566223�0024633�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of nfs-ganesha. # # There can only be one NFS-server active on a system. When NFS-Ganesha is # started, the kernel NFS-server should have been stopped. This is achieved by # the 'Conflicts' directive in this unit. # # The Network Locking Manager (rpc.statd) is provided by the nfs-utils package. # NFS-Ganesha comes with its own nfs-ganesha-lock.service to resolve potential # conflicts in starting multiple rpc.statd processes. See the comments in the # nfs-ganesha-lock.service for more details. # [Unit] Description=NFS-Ganesha file server Documentation=http://github.com/nfs-ganesha/nfs-ganesha/wiki After=rpcbind.service nfs-ganesha-lock.service nfs-ganesha-config.service Wants=rpcbind.service nfs-ganesha-lock.service nfs-ganesha-config.service Conflicts=nfs.target [Service] Type=forking RuntimeDirectory=ganesha EnvironmentFile=-/etc/default/nfs-ganesha ExecStart=/bin/bash -c "${NUMACTL} ${NUMAOPTS} /usr/bin/ganesha.nfsd -C ${OPTIONS} ${EPOCH} ${GNODEID}" ExecReload=/bin/kill -HUP $MAINPID ExecStop=/usr/bin/dbus-send --system --dest=org.ganesha.nfsd --type=method_call /org/ganesha/nfsd/admin org.ganesha.nfsd.admin.shutdown [Install] WantedBy=multi-user.target Also=nfs-ganesha-lock.service �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/systemd/nfs-ganesha.service.el7�����������������������������������������0000664�0000000�0000000�00000002525�14737566223�0024011�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file is part of nfs-ganesha. # # There can only be one NFS-server active on a system. When NFS-Ganesha is # started, the kernel NFS-server should have been stopped. This is achieved by # the 'Conflicts' directive in this unit. # # The Network Locking Manager (rpc.statd) is provided by the nfs-utils package. # NFS-Ganesha comes with its own nfs-ganesha-lock.service to resolve potential # conflicts in starting multiple rpc.statd processes. See the comments in the # nfs-ganesha-lock.service for more details. # [Unit] Description=NFS-Ganesha file server Documentation=http://github.com/nfs-ganesha/nfs-ganesha/wiki After=rpcbind.service nfs-ganesha-lock.service nfs-ganesha-config.service Wants=rpcbind.service nfs-ganesha-lock.service nfs-ganesha-config.service Conflicts=nfs.target [Service] Type=forking # Let systemd create /run/ganesha, /var/log/ganesha and /var/lib/nfs/ganesha # directories RuntimeDirectory=ganesha LogsDirectory=ganesha StateDirectory=nfs/ganesha EnvironmentFile=-/run/sysconfig/ganesha ExecStart=/bin/bash -c "${NUMACTL} ${NUMAOPTS} /usr/bin/ganesha.nfsd -C ${OPTIONS} ${EPOCH} ${GNODEID}" ExecReload=/bin/kill -HUP $MAINPID ExecStop=/bin/dbus-send --system --dest=org.ganesha.nfsd --type=method_call /org/ganesha/nfsd/admin org.ganesha.nfsd.admin.shutdown [Install] WantedBy=multi-user.target Also=nfs-ganesha-lock.service ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/systemd/rpc-statd.conf.debian10�����������������������������������������0000664�0000000�0000000�00000000745�14737566223�0023703�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[Unit] Description=NFS status monitor for NFSv2/3 locking. DefaultDependencies=no Conflicts=umount.target Requires=nss-lookup.target rpcbind.socket After=network.target nss-lookup.target rpcbind.socket PartOf=nfs-utils.service Wants=nfs-config.service rpc-statd-notify.service After=nfs-config.service [Service] EnvironmentFile=-/run/sysconfig/nfs-utils Type=forking PIDFile=/run/rpc.statd.pid ExecStart=/sbin/rpc.statd --no-notify $STATDARGS [Install] WantedBy=nfs-server.service ���������������������������nfs-ganesha-6.5/src/scripts/systemd/rpc-statd.conf.el8����������������������������������������������0000664�0000000�0000000�00000000572�14737566223�0023006�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[Unit] Description=NFS status monitor for NFSv2/3 locking. DefaultDependencies=no Conflicts=umount.target Requires=nss-lookup.target rpcbind.socket Wants=network-online.target After=network-online.target nss-lookup.target rpcbind.socket PartOf=nfs-utils.service [Service] Environment=RPC_STATD_NO_NOTIFY=1 Type=forking PIDFile=/run/rpc.statd.pid ExecStart=/usr/sbin/rpc.statd ��������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/systemd/sysconfig/������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0021547�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/systemd/sysconfig/nfs-ganesha�������������������������������������������0000664�0000000�0000000�00000000173�14737566223�0023665�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������OPTIONS="-L /var/log/ganesha/ganesha.log -f /etc/ganesha/ganesha.conf -N NIV_EVENT" EPOCH_EXEC="/bin/true" #NODEID_EXEC="" �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_pynfs/�������������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0020251�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_pynfs/test.cd_junction.pynfs���������������������������������������0000664�0000000�0000000�00000000127�14737566223�0024607�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/ksh ../pynfs/nfs4client.py -u pinatubo1 << EOF cd /prot/test/root quit EOF �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_pynfs/test.proxy.pynfs���������������������������������������������0000775�0000000�0000000�00000001326�14737566223�0023476�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/ksh ../pynfs/nfs4client.py -u -p pinatubo1:2047/ << EOF attr_request = nfs4lib.list2attrmask([FATTR4_TIME_ACCESS,FATTR4_TIME_METADATA,FATTR4_TIME_MODIFY,FATTR4_FILEID,FATTR4_SIZE]) putrootfhop = c.ncl.putrootfh_op() lookup1op = c.ncl.lookup_op( "users" ) ; lookup2op = c.ncl.lookup_op( "thomas" ) ; lookup3op = c.ncl.lookup_op( "CL1" ) ; lookup4op = c.ncl.lookup_op( "connproxy" ) ; lookup5op = c.ncl.lookup_op( "fichier" ) ; getfhop = c.ncl.getfh_op() ; getattrop = c.ncl.getattr_op( attr_request ) ; res = c.ncl.compound([putrootfhop, getattrop, lookup1op, lookup2op, lookup3op, lookup4op, lookup5op, getfhop, getattrop]) nfs4lib.fattr2dict( res.resarray[-1].arm.arm.obj_attributes ) quit EOF ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_pynfs/test_get_root_entry_all_type_for_pseudofs.pynfs��������������0000664�0000000�0000000�00000005235�14737566223�0032110�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/ksh ../pynfs/nfs4client.py -u -p pinatubo1 << EOF putrootfhop = c.ncl.putrootfh_op() attr_request = nfs4lib.list2attrmask([FATTR4_TYPE,FATTR4_FH_EXPIRE_TYPE,FATTR4_CHANGE,FATTR4_LINK_SUPPORT,FATTR4_SYMLINK_SUPPORT,FATTR4_NAMED_ATTR,FATTR4_SIZE]) getattrop = c.ncl.getattr_op( attr_request ) res = c.ncl.compound([putrootfhop, getattrop]) nfs4lib.fattr2dict( res.resarray[-1].arm.arm.obj_attributes ) putrootfhop = c.ncl.putrootfh_op() attr_request = nfs4lib.list2attrmask([FATTR4_FSID,FATTR4_UNIQUE_HANDLES,FATTR4_LEASE_TIME,FATTR4_RDATTR_ERROR,FATTR4_ACL,FATTR4_ACLSUPPORT,FATTR4_ARCHIVE,FATTR4_CANSETTIME]) getattrop = c.ncl.getattr_op( attr_request ) res = c.ncl.compound([putrootfhop, getattrop]) nfs4lib.fattr2dict( res.resarray[-1].arm.arm.obj_attributes ) putrootfhop = c.ncl.putrootfh_op() attr_request = nfs4lib.list2attrmask([FATTR4_CASE_INSENSITIVE,FATTR4_CASE_PRESERVING,FATTR4_CHOWN_RESTRICTED,FATTR4_FILEID,FATTR4_FILES_AVAIL,FATTR4_FILES_FREE,FATTR4_FILES_TOTAL]) getattrop = c.ncl.getattr_op( attr_request ) res = c.ncl.compound([putrootfhop, getattrop]) nfs4lib.fattr2dict( res.resarray[-1].arm.arm.obj_attributes ) putrootfhop = c.ncl.putrootfh_op() attr_request = nfs4lib.list2attrmask([FATTR4_HIDDEN,FATTR4_HOMOGENEOUS,FATTR4_MAXFILESIZE,FATTR4_MAXLINK,FATTR4_MAXNAME,FATTR4_MAXREAD,FATTR4_MAXWRITE]) getattrop = c.ncl.getattr_op( attr_request ) res = c.ncl.compound([putrootfhop, getattrop]) nfs4lib.fattr2dict( res.resarray[-1].arm.arm.obj_attributes ) putrootfhop = c.ncl.putrootfh_op() attr_request = nfs4lib.list2attrmask([FATTR4_RAWDEV]) getattrop = c.ncl.getattr_op( attr_request ) res = c.ncl.compound([putrootfhop, getattrop]) nfs4lib.fattr2dict( res.resarray[-1].arm.arm.obj_attributes ) putrootfhop = c.ncl.putrootfh_op() attr_request = nfs4lib.list2attrmask([FATTR4_MODE,FATTR4_NO_TRUNC,FATTR4_NUMLINKS,FATTR4_OWNER,FATTR4_OWNER_GROUP]) getattrop = c.ncl.getattr_op( attr_request ) res = c.ncl.compound([putrootfhop, getattrop]) nfs4lib.fattr2dict( res.resarray[-1].arm.arm.obj_attributes ) putrootfhop = c.ncl.putrootfh_op() attr_request = nfs4lib.list2attrmask([FATTR4_SPACE_AVAIL,FATTR4_SPACE_FREE,FATTR4_SPACE_TOTAL,FATTR4_SPACE_USED,FATTR4_SYSTEM]) getattrop = c.ncl.getattr_op( attr_request ) res = c.ncl.compound([putrootfhop, getattrop]) nfs4lib.fattr2dict( res.resarray[-1].arm.arm.obj_attributes ) putrootfhop = c.ncl.putrootfh_op() attr_request = nfs4lib.list2attrmask([FATTR4_TIME_ACCESS,FATTR4_TIME_DELTA,FATTR4_TIME_METADATA,FATTR4_TIME_MODIFY,FATTR4_MOUNTED_ON_FILEID,FATTR4_FILEHANDLE]) getattrop = c.ncl.getattr_op( attr_request ) res = c.ncl.compound([putrootfhop, getattrop]) nfs4lib.fattr2dict( res.resarray[-1].arm.arm.obj_attributes ) quit EOF �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_pynfs/test_get_root_entry_rawdev.pynfs�����������������������������0000664�0000000�0000000�00000000531�14737566223�0027003�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/ksh ../pynfs/nfs4client.py -u -p pinatubo1 << EOF putrootfhop = c.ncl.putrootfh_op() attr_request = nfs4lib.list2attrmask([FATTR4_RAWDEV,FATTR4_TYPE,FATTR4_SPACE_AVAIL]) getattrop = c.ncl.getattr_op( attr_request ) res = c.ncl.compound([putrootfhop, getattrop]) nfs4lib.fattr2dict( res.resarray[-1].arm.arm.obj_attributes ) quit EOF �����������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_pynfs/test_get_root_entry_supported_attrs.pynfs��������������������0000664�0000000�0000000�00000000503�14737566223�0030754�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/ksh ../pynfs/nfs4client.py -u -p pinatubo1 << EOF putrootfhop = c.ncl.putrootfh_op() attr_request = nfs4lib.list2attrmask([FATTR4_SUPPORTED_ATTRS]) getattrop = c.ncl.getattr_op( attr_request ) res = c.ncl.compound([putrootfhop, getattrop]) nfs4lib.fattr2dict( res.resarray[-1].arm.arm.obj_attributes ) quit EOF ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_pynfs/test_get_root_entry_type.pynfs�������������������������������0000664�0000000�0000000�00000000470�14737566223�0026476�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/ksh ../pynfs/nfs4client.py -u -p pinatubo1 << EOF putrootfhop = c.ncl.putrootfh_op() attr_request = nfs4lib.list2attrmask([FATTR4_TYPE]) getattrop = c.ncl.getattr_op( attr_request ) res = c.ncl.compound([putrootfhop, getattrop]) nfs4lib.fattr2dict( res.resarray[-1].arm.arm.obj_attributes ) quit EOF ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_pynfs/test_nfs4_fs_localtions_request.pynfs������������������������0000664�0000000�0000000�00000000535�14737566223�0027735�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/ksh ../pynfs/nfs4client.py -u -p pinatubo1 << EOF putrootfhop = c.ncl.putrootfh_op() getfhop = c.ncl.getfh_op() ; attr_request = nfs4lib.list2attrmask([FATTR4_FS_LOCATIONS]) getattrop = c.ncl.getattr_op( attr_request ) res = c.ncl.compound([putrootfhop, getattrop]) nfs4lib.fattr2dict( res.resarray[-1].arm.arm.obj_attributes ) quit EOF �������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_pynfs/test_nfs4_mount_request.pynfs��������������������������������0000664�0000000�0000000�00000001040�14737566223�0026230�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/ksh ../pynfs/nfs4client.py -u -p pinatubo1 << EOF putrootfhop = c.ncl.putrootfh_op() getfhop = c.ncl.getfh_op() ; attr_request = nfs4lib.list2attrmask([FATTR4_TYPE,FATTR4_CHANGE,FATTR4_SIZE,FATTR4_FSID,FATTR4_FILEID,FATTR4_MODE,FATTR4_NUMLINKS,FATTR4_OWNER,FATTR4_OWNER_GROUP,FATTR4_RAWDEV,FATTR4_SPACE_USED,FATTR4_TIME_ACCESS,FATTR4_TIME_METADATA,FATTR4_TIME_MODIFY]) getattrop = c.ncl.getattr_op( attr_request ) res = c.ncl.compound([putrootfhop, getattrop]) nfs4lib.fattr2dict( res.resarray[-1].arm.arm.obj_attributes ) quit EOF ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_through_mountpoint/������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0023066�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_through_mountpoint/test_create.sh����������������������������������0000775�0000000�0000000�00000003074�14737566223�0025733�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # ce test cree plein d'entrees dans un repertoire # et les rename une par une TEST_DIR=$1 SUB_DIR1="create_rename-$$" FILENAME_1="HDep-n=Temps_u=s+n=NumSDom_g=0200x0203.v=f0000000000000000-v=f3f9f16208bfc1f8d" FILENAME_2="HDep-n=Temps_u=s+n=NumSDom_g=0184x0187.v=f0000000000000000-v=f3f74bbcde85767c5" NB_ENTREES=500 if [[ $TEST_DIR = "" ]]; then echo "usage : $0 <test_dir>" exit 1 fi if [[ ! -d $TEST_DIR ]]; then echo "$1 n'existe pas ou n'est pas un repertoire"; exit 1 fi #creation de l'arborescence initiale mkdir -p "$TEST_DIR/$SUB_DIR1" ERR=0 echo "creating files..." I=0 while (( $I < $NB_ENTREES )) ; do printf "#" touch "$TEST_DIR/$SUB_DIR1/$FILENAME_1-$I" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_1-$I" fi (( I = $I + 1 )) done echo echo "renaming files..." I=0 while (( $I < $NB_ENTREES )) ; do printf "#" mv "$TEST_DIR/$SUB_DIR1/$FILENAME_1-$I" "$TEST_DIR/$SUB_DIR1/$FILENAME_2-$I" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_1-$I" "$TEST_DIR/$SUB_DIR1/$FILENAME_2-$I" fi (( I = $I + 1 )) done echo echo "renaming files..." I=0 while (( $I < $NB_ENTREES )) ; do printf "#" mv "$TEST_DIR/$SUB_DIR1/$FILENAME_2-$I" "$TEST_DIR/$SUB_DIR1/$FILENAME_1-$I" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_2-$I" "$TEST_DIR/$SUB_DIR1/$FILENAME_1-$I" fi (( I = $I + 1 )) done echo echo "cleaning test directory..." rm -rf "$TEST_DIR/$SUB_DIR1" echo "Test termine. $ERR erreurs." ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_through_mountpoint/test_create_ls.sh�������������������������������0000775�0000000�0000000�00000002160�14737566223�0026424�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # ce test cree plein d'entrees dans un repertoire # et les rename une par une TEST_DIR=$1 SUB_DIR1="create_ls-$$" FILENAME_1="HDep-n=Temps_u=s+n=NumSDom_g=0200x0203.v=f0000000000000000-v=f3f9f16208bfc1f8d" FILENAME_2="HDep-n=Temps_u=s+n=NumSDom_g=0184x0187.v=f0000000000000000-v=f3f74bbcde85767c5" NB_ENTREES=100 if [[ $TEST_DIR = "" ]]; then echo "usage : $0 <test_dir>" exit 1 fi if [[ ! -d $TEST_DIR ]]; then echo "$1 n'existe pas ou n'est pas un repertoire"; exit 1 fi #creation de l'arborescence initiale mkdir -p "$TEST_DIR/$SUB_DIR1" ERR=0 echo "creating files..." I=0 while (( $I < $NB_ENTREES )) ; do printf "#" touch "$TEST_DIR/$SUB_DIR1/$FILENAME_1-$I" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_1-$I" fi (( I = $I + 1 )) done echo echo "ls" I=0 while (( $I < $NB_ENTREES )) ; do printf "#" ls "$TEST_DIR/$SUB_DIR1" >/dev/null if (( $? != 0 )); then ((ERR=$ERR+1)) fi sleep 10 (( I = $I + 1 )) done echo echo "cleaning test directory..." rm -rf "$TEST_DIR/$SUB_DIR1" echo "Test termine. $ERR erreurs." ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_through_mountpoint/test_create_rm.sh�������������������������������0000775�0000000�0000000�00000005206�14737566223�0026430�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # ce test cree plein d'entrees dans un repertoire # et les rename une par une TEST_DIR=$1 SUB_DIR1="touch_rm-$$" FILENAME_1="HDep-n=Temps_u=s+n=NumSDom_g=0200x0203.v=f0000000000000000-v=f3f9f16208bfc1f8d" FILENAME_2="HDep-n=Temps_u=s+n=NumSDom_g=0184x0187.v=f0000000000000000-v=f3f74bbcde85767c5" NB_LOOP=100 if [[ $TEST_DIR = "" ]]; then echo "usage : $0 <test_dir>" exit 1 fi if [[ ! -d $TEST_DIR ]]; then echo "$1 n'existe pas ou n'est pas un repertoire"; exit 1 fi #creation de l'arborescence initiale mkdir -p "$TEST_DIR/$SUB_DIR1" ERR=0 echo "touch/rm sequence... ($NB_LOOP times)" I=0 while (( $I < $NB_LOOP )) ; do printf "#" touch "$TEST_DIR/$SUB_DIR1/$FILENAME_1" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_1" fi rm "$TEST_DIR/$SUB_DIR1/$FILENAME_1" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_1" fi touch "$TEST_DIR/$SUB_DIR1/$FILENAME_2" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_2" fi rm "$TEST_DIR/$SUB_DIR1/$FILENAME_2" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_2" fi (( I = $I + 1 )) done echo echo "touch/mv/rm sequence... ($NB_LOOP times)" I=0 while (( $I < $NB_LOOP )) ; do printf "#" touch "$TEST_DIR/$SUB_DIR1/$FILENAME_1" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_1" fi mv "$TEST_DIR/$SUB_DIR1/$FILENAME_1" "$TEST_DIR/$SUB_DIR1/$FILENAME_2" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_1" "$TEST_DIR/$SUB_DIR1/$FILENAME_2" fi rm "$TEST_DIR/$SUB_DIR1/$FILENAME_2" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_2" fi (( I = $I + 1 )) done echo echo "touch/ls/mv/rm sequence... ($NB_LOOP times)" I=0 while (( $I < $NB_LOOP )) ; do printf "#" touch "$TEST_DIR/$SUB_DIR1/$FILENAME_1" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_1" fi ls -l "$TEST_DIR/$SUB_DIR1" >/dev/null if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1" fi mv "$TEST_DIR/$SUB_DIR1/$FILENAME_1" "$TEST_DIR/$SUB_DIR1/$FILENAME_2" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_1" "$TEST_DIR/$SUB_DIR1/$FILENAME_2" fi rm "$TEST_DIR/$SUB_DIR1/$FILENAME_2" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_2" fi (( I = $I + 1 )) done echo "cleaning test directory..." rm -rf "$TEST_DIR/$SUB_DIR1" echo "Test termine. $ERR erreurs." ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_through_mountpoint/test_createrenameunlink.sh����������������������0000775�0000000�0000000�00000007541�14737566223�0030347�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # ce test cree plein d'entrees dans un repertoire # et les rename une par une TEST_DIR=$1 SUB_DIR1="create_rename_unlink-$$" FILENAME1="kjdfslkjflskdjflsdkjflksdjflsdkjf" FILENAME2="mdslkmdf_lkdsjfksedf_9879" NB_ITER_1=100 NB_ITER_2=10 if [[ $TEST_DIR = "" ]]; then echo "usage : $0 <test_dir>" exit 1 fi if [[ ! -d $TEST_DIR ]]; then echo "$1 n'existe pas ou n'est pas un repertoire"; exit 1 fi #creation de l'arborescence initiale mkdir -p "$TEST_DIR/$SUB_DIR1" ERR=0 J=0 while (( $J < $NB_ITER_2 )) ; do echo "create/rename/unlink sequence..." I=0 while (( $I < $NB_ITER_1 )) ; do printf "#" # sequence 1 = create, rename unlink if [ -e "$TEST_DIR/$SUB_DIR1/$FILENAME1" ]; then echo "Error : $TEST_DIR/$SUB_DIR1/$FILENAME1 already exists" >&2 ((ERR=$ERR+1)) else touch "$TEST_DIR/$SUB_DIR1/$FILENAME1" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME1" fi fi if [ -e "$TEST_DIR/$SUB_DIR1/$FILENAME2" ]; then echo "Error : $TEST_DIR/$SUB_DIR1/$FILENAME2 already exists" >&2 ((ERR=$ERR+1)) else mv "$TEST_DIR/$SUB_DIR1/$FILENAME1" "$TEST_DIR/$SUB_DIR1/$FILENAME2" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME1" "$TEST_DIR/$SUB_DIR1/$FILENAME2" fi fi rm -f "$TEST_DIR/$SUB_DIR1/$FILENAME2" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME1" "$TEST_DIR/$SUB_DIR1/$FILENAME2" fi # sequence 2 = create, rename, rename, unlink if [ -e "$TEST_DIR/$SUB_DIR1/$FILENAME1" ]; then echo "Error : $TEST_DIR/$SUB_DIR1/$FILENAME1 already exists" >&2 ((ERR=$ERR+1)) else touch "$TEST_DIR/$SUB_DIR1/$FILENAME1" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME1" fi fi if [ -e "$TEST_DIR/$SUB_DIR1/$FILENAME2" ]; then echo "Error : $TEST_DIR/$SUB_DIR1/$FILENAME2 already exists" >&2 ((ERR=$ERR+1)) else mv "$TEST_DIR/$SUB_DIR1/$FILENAME1" "$TEST_DIR/$SUB_DIR1/$FILENAME2" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME1" "$TEST_DIR/$SUB_DIR1/$FILENAME2" fi fi if [ -e "$TEST_DIR/$SUB_DIR1/$FILENAME1" ]; then echo "Error : $TEST_DIR/$SUB_DIR1/$FILENAME1 already exists" >&2 ((ERR=$ERR+1)) else mv "$TEST_DIR/$SUB_DIR1/$FILENAME2" "$TEST_DIR/$SUB_DIR1/$FILENAME1" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME1" "$TEST_DIR/$SUB_DIR1/$FILENAME2" fi fi rm -f "$TEST_DIR/$SUB_DIR1/$FILENAME1" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME1" "$TEST_DIR/$SUB_DIR1/$FILENAME2" fi (( I = $I + 1 )) done printf "#" if [ -e "$TEST_DIR/$SUB_DIR1/$FILENAME2" ]; then echo "Error : $TEST_DIR/$SUB_DIR1/$FILENAME2 already exists" >&2 ((ERR=$ERR+1)) else touch "$TEST_DIR/$SUB_DIR1/$FILENAME2" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME2" fi fi echo echo "readdir/lookup/getattr sequence..." I=0 while (( $I < $NB_ITER_1 )) ; do printf "#" ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME2" > /dev/null if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME1" "$TEST_DIR/$SUB_DIR1/$FILENAME2" fi ls -li "$TEST_DIR/$SUB_DIR1" > /dev/null if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1" fi (( I = $I + 1 )) done echo echo "removing file..." rm -f "$TEST_DIR/$SUB_DIR1/$FILENAME2" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME2" fi (( J = $J + 1 )) done echo "Test termine. $ERR erreurs." ���������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_through_mountpoint/test_createunlink.sh����������������������������0000775�0000000�0000000�00000004030�14737566223�0027145�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # ce test cree plein d'entrees dans un repertoire # et les rename une par une TEST_DIR=$1 SUB_DIR1="create_unlink-$$" FILENAME="kjdfslkjflskdjflsdkjflksdjflsdkjf" NB_ITER_1=100 NB_ITER_2=10 if [[ $TEST_DIR = "" ]]; then echo "usage : $0 <test_dir>" exit 1 fi if [[ ! -d $TEST_DIR ]]; then echo "$1 n'existe pas ou n'est pas un repertoire"; exit 1 fi #creation de l'arborescence initiale mkdir -p "$TEST_DIR/$SUB_DIR1" ERR=0 J=0 while (( $J < $NB_ITER_2 )) ; do echo "create/unlink sequence..." I=0 while (( $I < $NB_ITER_1 )) ; do printf "#" if [ -e "$TEST_DIR/$SUB_DIR1/$FILENAME" ]; then echo "Error : $TEST_DIR/$SUB_DIR1/$FILENAME already exists" >&2 ((ERR=$ERR+1)) else touch "$TEST_DIR/$SUB_DIR1/$FILENAME" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME" fi fi rm -f "$TEST_DIR/$SUB_DIR1/$FILENAME" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME" fi (( I = $I + 1 )) done printf "#" if [ -e "$TEST_DIR/$SUB_DIR1/$FILENAME" ]; then echo "Error : $TEST_DIR/$SUB_DIR1/$FILENAME already exists" >&2 ((ERR=$ERR+1)) else touch "$TEST_DIR/$SUB_DIR1/$FILENAME" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME" fi fi echo echo "readdir/lookup/getattr sequence..." I=0 while (( $I < $NB_ITER_1 )) ; do printf "#" ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME" > /dev/null if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME" fi ls -li "$TEST_DIR/$SUB_DIR1" > /dev/null if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1" fi (( I = $I + 1 )) done echo echo "removing file..." rm -f "$TEST_DIR/$SUB_DIR1/$FILENAME" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME" fi (( J = $J + 1 )) done echo "Test termine. $ERR erreurs." ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_through_mountpoint/test_mkdircascade.sh����������������������������0000775�0000000�0000000�00000006531�14737566223�0027103�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # ce test cree plein d'entrees dans un repertoire # et les rename une par une TEST_DIR=$1 SUB_DIR1="mkdir_cascade-$$" NAME="TEST_ENTRY" NB_ITER=5000 ((SEED=1999*$$+`date +%s`)) ((RAND=((1999*$SEED + 857))%715827883)) LAST_VAL=0 if [[ $TEST_DIR = "" ]]; then echo "usage : $0 <test_dir>" exit 1 fi if [[ ! -d $TEST_DIR ]]; then echo "$1 n'existe pas ou n'est pas un repertoire"; exit 1 fi #creation de l'arborescence initiale mkdir -p "$TEST_DIR/$SUB_DIR1" function random { MODULUS=$1 ((RAND=((1999*$RAND + 857))%715827883)) ((value=$RAND%$MODULUS)) while (( $value < 0 )); do ((value=$value+$MODULUS)) done LAST_VAL=$value } function create_file { chemin=$1 touch "$chemin/$NAME" status=$? if (( $status != 0 )); then echo "Error $status creating file $chemin/$NAME" >&2 exit 1 fi } function create_dir { chemin=$1 mkdir "$chemin/$NAME" status=$? if (( $status != 0 )); then echo "Error $status creating subdirectory $chemin/$NAME" >&2 exit 1 fi } function remove_file { chemin=$1 rm -f "$chemin/$NAME" status=$? if (( $status != 0 )); then echo "Error $status removing file $chemin/$NAME" >&2 exit 1 fi } function remove_dir { chemin=$1 rmdir "$chemin/$NAME" status=$? if (( $status != 0 )); then echo "Error $status removing directory $chemin/$NAME" >&2 exit 1 fi } function go_up { chemin=$1 upper=`dirname $chemin` echo "$upper" } function go_in { chemin=$1 echo "$chemin/$NAME" } # on choisi aleatoirement une sequence des actions suivantes: # *si etat = vide : # - on peut creer un fichier => etat=fichier # - on peut creer un sous repertoire => etat=directory # - on peut remonter d'un cran (si depth > 0 ) # *si etat = fichier : # - on peut supprimer le fichier => etat = vide # *si etat = directory : # - on peut descendre dans ce repertoire => etat=vide # - on peut supprimer ce repertoire => etat=vide depth=0 path="$TEST_DIR/$SUB_DIR1" etat=vide I=0 while (( $I < $NB_ITER )) ; do case $etat in vide) random 4 case $LAST_VAL in 0) echo "Creating file $path/$NAME" create_file $path etat=fichier ;; 1) echo "Creating dir $path/$NAME" create_dir $path etat=directory ;; 2|3) # go up more often than down if (( $depth > 0 )); then path=`go_up $path` echo "Going back to $path" etat=directory ((depth=$depth - 1)) fi # sinon (depth=0) : on ne fait rien ;; esac ;; fichier) echo "Removing file $path/$NAME" remove_file $path etat=vide ;; directory) random 2 case $LAST_VAL in 0) path=`go_in $path` echo "Entering in $path" etat=vide ((depth=$depth + 1)) ;; 1) echo "Removing dir $path/$NAME" remove_dir $path etat=vide ;; esac ;; esac (( I = $I +1 )) done echo "Cleaning all..." rm -rf "$TEST_DIR/$SUB_DIR1" status=$? if (( $status != 0 )); then echo "Error $status removing $TEST_DIR/$SUB_DIR1" >&2 exit 1 fi exit 0 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_through_mountpoint/test_read_write.sh������������������������������0000775�0000000�0000000�00000002030�14737566223�0026604�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh COMMANDE_CONTENU="find /etc -ls" REPERTOIRE=$1 DATE=`date +"%d%m%Y"` if [[ ! -d $REPERTOIRE ]]; then echo "$REPERTOIRE N'EST PAS UN REPERTOIRE" exit 1 fi FICH_TEST="$REPERTOIRE/TEST_RW.$DATE" echo "Creation d'un fichier temoin..." TEMOIN="/tmp/TEST_RW.$DATE" $COMMANDE_CONTENU > $TEMOIN echo "Copie dans Ganesha (cp)..." cp $TEMOIN $FICH_TEST ls -l $TEMOIN $FICH_TEST echo "Comparaison du contenu..." diff $TEMOIN $FICH_TEST echo "Copie de Ganesha vers /tmp (cp)..." cp $FICH_TEST $TEMOIN.2 echo "Comparaison du temoin et du fichier exporte..." diff $TEMOIN $TEMOIN.2 echo "Suppression du fichier dans Ganesha..." rm $FICH_TEST echo "Copie dans Ganesha (cp -p)..." cp -p $TEMOIN $FICH_TEST ls -l $TEMOIN $FICH_TEST echo "Comparaison du contenu..." diff $TEMOIN $FICH_TEST echo "Copie de Ganesha vers /tmp (cp -p)..." cp -p $FICH_TEST $TEMOIN.2 echo "Comparaison du temoin et du fichier exporte..." diff $TEMOIN $TEMOIN.2 echo "Suppression des fichiers du test..." rm $FICH_TEST rm $TEMOIN rm $TEMOIN.2 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_through_mountpoint/test_rename.sh����������������������������������0000775�0000000�0000000�00000004753�14737566223�0025744�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # de test cree simplement deux fichiers dans un repertoire # et les rename TEST_DIR=$1 SUB_DIR1="hercule-$$/depouillement" SUB_DIR2="hercule-$$/protections" FILENAME_1="HDep-n=Temps_u=s+n=NumSDom_g=0200x0203.v=f0000000000000000-v=f3f9f16208bfc1f8d" FILENAME_2="HDep-n=Temps_u=s+n=NumSDom_g=0184x0187.v=f0000000000000000-v=f3f74bbcde85767c5" NB_LOOP_1=10 NB_LOOP_2=100 if [[ $TEST_DIR = "" ]]; then echo "usage : $0 <test_dir>" exit 1 fi if [[ ! -d $TEST_DIR ]]; then echo "$1 n'existe pas ou n'est pas un repertoire"; exit 1 fi #creation de l'arborescence initiale mkdir -p "$TEST_DIR/$SUB_DIR1" mkdir -p "$TEST_DIR/$SUB_DIR2" touch "$TEST_DIR/$SUB_DIR1/$FILENAME_1" I=0 ERR=0 while (( $I < $NB_LOOP_1 )) ; do J=0 echo "passe 1 - $I" while (( $J < $NB_LOOP_2 )); do mv "$TEST_DIR/$SUB_DIR1/$FILENAME_1" "$TEST_DIR/$SUB_DIR1/$FILENAME_2" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_1" "$TEST_DIR/$SUB_DIR1/$FILENAME_2" exit 1 fi mv "$TEST_DIR/$SUB_DIR1/$FILENAME_2" "$TEST_DIR/$SUB_DIR1/$FILENAME_1" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_2" "$TEST_DIR/$SUB_DIR1/$FILENAME_1" exit 1 fi (( J = $J + 1)) done echo "passe 2 - $I" mv "$TEST_DIR/$SUB_DIR1/$FILENAME_1" "$TEST_DIR/$SUB_DIR2/$FILENAME_1" if (( $? != 0 )); then ((ERR=$ERR+1)); fi J=0 while (( $J < $NB_LOOP_2 )); do mv "$TEST_DIR/$SUB_DIR2/$FILENAME_1" "$TEST_DIR/$SUB_DIR2/$FILENAME_2" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR2/$FILENAME_1" "$TEST_DIR/$SUB_DIR2/$FILENAME_2" exit 1 fi mv "$TEST_DIR/$SUB_DIR2/$FILENAME_2" "$TEST_DIR/$SUB_DIR1/$FILENAME_2" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR2/$FILENAME_2" "$TEST_DIR/$SUB_DIR1/$FILENAME_2" exit 1 fi mv "$TEST_DIR/$SUB_DIR1/$FILENAME_2" "$TEST_DIR/$SUB_DIR2/$FILENAME_1" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_2" "$TEST_DIR/$SUB_DIR2/$FILENAME_1" exit 1 fi (( J = $J + 1)) done mv "$TEST_DIR/$SUB_DIR2/$FILENAME_1" "$TEST_DIR/$SUB_DIR1/$FILENAME_1" if (( $? != 0 )); then ((ERR=$ERR+1)); fi (( I = $I + 1 )) done rm -rf "$TEST_DIR/$SUB_DIR1" rm -rf "$TEST_DIR/$SUB_DIR2" (( NB_RENAME = 5*$NB_LOOP_2 + 2 )) (( NB_RENAME = $NB_LOOP_1 * $NB_RENAME )) echo "Test termine. $NB_RENAME rename effectues. $ERR erreurs." ���������������������nfs-ganesha-6.5/src/scripts/test_through_mountpoint/test_rename2.sh���������������������������������0000775�0000000�0000000�00000005615�14737566223�0026024�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # de test cree simplement deux fichiers dans un repertoire # et les rename TEST_DIR=$1 SUB_DIR1="hercule-$$/depouillement" SUB_DIR2="hercule-$$/protections" FILENAME_1="HDep-n=Temps_u=s+n=NumSDom_g=0200x0203.v=f0000000000000000-v=f3f9f16208bfc1f8d" FILENAME_2="HDep-n=Temps_u=s+n=NumSDom_g=0184x0187.v=f0000000000000000-v=f3f74bbcde85767c5" NB_LOOP_1=10 NB_LOOP_2=100 NB_FILES=100 if [[ $TEST_DIR = "" ]]; then echo "usage : $0 <test_dir>" exit 1 fi if [[ ! -d $TEST_DIR ]]; then echo "$1 n'existe pas ou n'est pas un repertoire"; exit 1 fi #creation de l'arborescence initiale mkdir -p "$TEST_DIR/$SUB_DIR1" mkdir -p "$TEST_DIR/$SUB_DIR2" F=0 while (( $F < $NB_FILES )) ; do touch "$TEST_DIR/$SUB_DIR1/$FILENAME_1.$F" (( F = $F + 1 )) done I=0 ERR=0 while (( $I < $NB_LOOP_1 )) ; do echo "test 1" F=0 while (( $F < $NB_FILES )) ; do printf "." J=0 while (( $J < $NB_LOOP_2 )); do mv "$TEST_DIR/$SUB_DIR1/$FILENAME_1.$F" "$TEST_DIR/$SUB_DIR1/$FILENAME_2.$F" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_1.$F" "$TEST_DIR/$SUB_DIR1/$FILENAME_2.$F" exit 1 fi mv "$TEST_DIR/$SUB_DIR1/$FILENAME_2.$F" "$TEST_DIR/$SUB_DIR1/$FILENAME_1.$F" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_2.$F" "$TEST_DIR/$SUB_DIR1/$FILENAME_1.$F" exit 1 fi (( J = $J + 1)) done (( F = $F + 1 )) done echo echo "test 2" F=0 while (( $F < $NB_FILES )) ; do printf "." mv "$TEST_DIR/$SUB_DIR1/$FILENAME_1.$F" "$TEST_DIR/$SUB_DIR2/$FILENAME_1.$F" if (( $? != 0 )); then ((ERR=$ERR+1)); fi J=0 while (( $J < $NB_LOOP_2 )); do mv "$TEST_DIR/$SUB_DIR2/$FILENAME_1.$F" "$TEST_DIR/$SUB_DIR2/$FILENAME_2.$F" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR2/$FILENAME_1.$F" "$TEST_DIR/$SUB_DIR2/$FILENAME_2.$F" exit 1 fi mv "$TEST_DIR/$SUB_DIR2/$FILENAME_2.$F" "$TEST_DIR/$SUB_DIR1/$FILENAME_2.$F" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR2/$FILENAME_2.$F" "$TEST_DIR/$SUB_DIR1/$FILENAME_2.$F" exit 1 fi mv "$TEST_DIR/$SUB_DIR1/$FILENAME_2.$F" "$TEST_DIR/$SUB_DIR2/$FILENAME_1.$F" if (( $? != 0 )); then ((ERR=$ERR+1)) ls -li "$TEST_DIR/$SUB_DIR1/$FILENAME_2.$F" "$TEST_DIR/$SUB_DIR2/$FILENAME_1.$F" exit 1 fi (( J = $J + 1)) done mv "$TEST_DIR/$SUB_DIR2/$FILENAME_1.$F" "$TEST_DIR/$SUB_DIR1/$FILENAME_1.$F" if (( $? != 0 )); then ((ERR=$ERR+1)); fi (( F = $F + 1 )) done echo (( I = $I + 1 )) done rm -rf "$TEST_DIR/$SUB_DIR1" rm -rf "$TEST_DIR/$SUB_DIR2" (( NB_RENAME = 5*$NB_LOOP_2 + 2 )) (( NB_RENAME = $NB_LOOP_1 * $NB_RENAME * $NB_FILES )) echo "Test termine. $NB_RENAME rename effectues. $ERR erreurs." �������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/scripts/test_through_mountpoint/test_rm_stat_readdir.sh�������������������������0000775�0000000�0000000�00000011345�14737566223�0027633�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # This test create entries, check they are returned by readdir and getattr # then, it removes the entries and check getattr returns ENOENT and readdir don't display it TEST_DIR=$1 SUB_DIR1="TESTDIR_RMSTAT-$$" FILENAME_1="FILEabcdefghijklm" FILENAME_2="FILEsqklsjdlqsjll" NB_FILES=1000 if [[ $TEST_DIR = "" ]]; then echo "usage : $0 <test_dir>" exit 1 fi if [[ ! -d $TEST_DIR ]]; then echo "$1 does not exist or is not a directory"; exit 1 fi #creation de l'arborescence initiale mkdir -p "$TEST_DIR/$SUB_DIR1" ERR=0 echo "create/getattr/readdir sequence... ($NB_FILES files)" I=0 while (( $I < (($NB_FILES/2)) )) ; do printf "#" # create file PATH1="$TEST_DIR/$SUB_DIR1/${FILENAME_1}_$I" touch $PATH1 if (( $? != 0 )); then ((ERR=$ERR+1)) echo "Error creating file $PATH1" ls -li $PATH1 fi # first, getattr stat $PATH1 > /dev/null if (( $? != 0 )); then ((ERR=$ERR+1)) echo "Error getting file attributes for $PATH1" ls -li $PATH1 fi # then, check readdir ls $TEST_DIR/$SUB_DIR1 | egrep -e "^${FILENAME_1}_$I$" > /dev/null if (( $? != 0 )); then ((ERR=$ERR+1)) echo "Error: ${FILENAME_1}_$I does not appear in readdir($TEST_DIR/$SUB_DIR1)" ls $TEST_DIR/$SUB_DIR1 fi # create a second file PATH2="$TEST_DIR/$SUB_DIR1/${FILENAME_2}_$I" touch $PATH2 if (( $? != 0 )); then ((ERR=$ERR+1)) echo "Error creating file $PATH2" ls -li $PATH2 fi # for this file, we first check readdir ls $TEST_DIR/$SUB_DIR1 | egrep -e "^${FILENAME_2}_$I$" > /dev/null if (( $? != 0 )); then ((ERR=$ERR+1)) echo "Error: ${FILENAME_2}_$I does not appear in readdir($TEST_DIR/$SUB_DIR1)" ls $TEST_DIR/$SUB_DIR1 fi # then, getattr stat $PATH2 > /dev/null if (( $? != 0 )); then ((ERR=$ERR+1)) echo "Error getting file attributes for $PATH2" ls -li $PATH2 fi (( I = $I + 1 )) done echo echo "rm/stat/readdir sequence..." I=0 while (( $I < (($NB_FILES/2)) )) ; do printf "#" # remove file PATH1="$TEST_DIR/$SUB_DIR1/${FILENAME_1}_$I" rm $PATH1 if (( $? != 0 )); then ((ERR=$ERR+1)) echo "Error removing file $PATH1" ls -li $PATH1 fi # try to getattr ls -l $PATH1 > /dev/null 2>&1 val=$? if (( $val != 2 )); then ((ERR=$ERR+1)) echo "Error: getattr after rm should return ENOENT ($val returned)" ls -li $PATH1 fi # then, check readdir ls $TEST_DIR/$SUB_DIR1 | egrep -e "^${FILENAME_1}_$I$" > /dev/null if (( $? == 0 )); then ((ERR=$ERR+1)) echo "Error: ${FILENAME_1}_$I still appears in readdir($TEST_DIR/$SUB_DIR1)" ls $TEST_DIR/$SUB_DIR1 fi # remove file PATH2="$TEST_DIR/$SUB_DIR1/${FILENAME_2}_$I" rm $PATH2 if (( $? != 0 )); then ((ERR=$ERR+1)) echo "Error removing file $PATH2" ls -li $PATH2 fi # check readdir ls $TEST_DIR/$SUB_DIR1 | egrep -e "^${FILENAME_2}_$I$" > /dev/null if (( $? == 0 )); then ((ERR=$ERR+1)) echo "Error: ${FILENAME_2}_$I still appears in readdir($TEST_DIR/$SUB_DIR1)" ls $TEST_DIR/$SUB_DIR1 fi # then, try to getattr ls -l $PATH2 > /dev/null 2>&1 val=$? if (( $val != 2 )); then ((ERR=$ERR+1)) echo "Error: getattr after rm should return ENOENT ($val returned)" ls -li $PATH2 fi (( I = $I + 1 )) done echo echo "create/getattr/readdir sequence AGAIN... ($NB_FILES files)" I=0 while (( $I < (($NB_FILES/2)) )) ; do printf "#" # create file PATH1="$TEST_DIR/$SUB_DIR1/${FILENAME_1}_$I" touch $PATH1 if (( $? != 0 )); then ((ERR=$ERR+1)) echo "Error creating file $PATH1" ls -li $PATH1 fi # first, getattr stat $PATH1 > /dev/null if (( $? != 0 )); then ((ERR=$ERR+1)) echo "Error getting file attributes for $PATH1" ls -li $PATH1 fi # then, check readdir ls $TEST_DIR/$SUB_DIR1 | egrep -e "^${FILENAME_1}_$I$" > /dev/null if (( $? != 0 )); then ((ERR=$ERR+1)) echo "Error: ${FILENAME_1}_$I does not appear in readdir($TEST_DIR/$SUB_DIR1)" ls $TEST_DIR/$SUB_DIR1 fi # create a second file PATH2="$TEST_DIR/$SUB_DIR1/${FILENAME_2}_$I" touch $PATH2 if (( $? != 0 )); then ((ERR=$ERR+1)) echo "Error creating file $PATH2" ls -li $PATH2 fi # for this file, we first check readdir ls $TEST_DIR/$SUB_DIR1 | egrep -e "^${FILENAME_2}_$I$" > /dev/null if (( $? != 0 )); then ((ERR=$ERR+1)) echo "Error: ${FILENAME_2}_$I does not appear in readdir($TEST_DIR/$SUB_DIR1)" ls $TEST_DIR/$SUB_DIR1 fi # then, getattr stat $PATH2 > /dev/null if (( $? != 0 )); then ((ERR=$ERR+1)) echo "Error getting file attributes for $PATH2" ls -li $PATH2 fi (( I = $I + 1 )) done echo echo "cleaning test directory..." rm -rf "$TEST_DIR/$SUB_DIR1" echo "Test termine. $ERR erreurs." �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/selinux/������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0016053�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/selinux/ganesha.fc��������������������������������������������������������������0000664�0000000�0000000�00000001025�14737566223�0017771�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/usr/bin/ganesha.nfsd -- gen_context(system_u:object_r:ganesha_exec_t,s0) /usr/lib/systemd/system/nfs-ganesha-config.* -- gen_context(system_u:object_r:ganesha_unit_file_t,s0) /usr/lib/systemd/system/nfs-ganesha-lock.* -- gen_context(system_u:object_r:ganesha_unit_file_t,s0) /usr/lib/systemd/system/nfs-ganesha.*e -- gen_context(system_u:object_r:ganesha_unit_file_t,s0) /var/log/ganesha(/.*)? gen_context(system_u:object_r:ganesha_var_log_t,s0) /var/run/ganesha(/.*)? gen_context(system_u:object_r:ganesha_var_run_t,s0) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/selinux/ganesha.if��������������������������������������������������������������0000664�0000000�0000000�00000010216�14737566223�0020001�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## <summary>policy for ganesha</summary> ######################################## ## <summary> ## Execute ganesha_exec_t in the ganesha domain. ## </summary> ## <param name="domain"> ## <summary> ## Domain allowed to transition. ## </summary> ## </param> # interface(`ganesha_domtrans',` gen_require(` type ganesha_t, ganesha_exec_t; ') corecmd_search_bin($1) domtrans_pattern($1, ganesha_exec_t, ganesha_t) ') ###################################### ## <summary> ## Execute ganesha in the caller domain. ## </summary> ## <param name="domain"> ## <summary> ## Domain allowed access. ## </summary> ## </param> # interface(`ganesha_exec',` gen_require(` type ganesha_exec_t; ') corecmd_search_bin($1) can_exec($1, ganesha_exec_t) ') ######################################## ## <summary> ## Read ganesha PID files. ## </summary> ## <param name="domain"> ## <summary> ## Domain allowed access. ## </summary> ## </param> # interface(`ganesha_read_pid_files',` gen_require(` type ganesha_var_run_t; ') files_search_pids($1) read_files_pattern($1, ganesha_var_run_t, ganesha_var_run_t) ') ######################################## ## <summary> ## Execute ganesha server in the ganesha domain. ## </summary> ## <param name="domain"> ## <summary> ## Domain allowed to transition. ## </summary> ## </param> # interface(`ganesha_systemctl',` gen_require(` type ganesha_t; type ganesha_unit_file_t; ') systemd_exec_systemctl($1) systemd_read_fifo_file_passwd_run($1) allow $1 ganesha_unit_file_t:file read_file_perms; allow $1 ganesha_unit_file_t:service manage_service_perms; ps_process_pattern($1, ganesha_t) ') ######################################## ## <summary> ## Send and receive messages from ## ganesha over dbus. ## </summary> ## <param name="domain"> ## <summary> ## Domain allowed access. ## </summary> ## </param> # interface(`ganesha_dbus_chat',` gen_require(` type ganesha_t; class dbus send_msg; ') allow $1 ganesha_t:dbus send_msg; allow ganesha_t $1:dbus send_msg; ') ######################################## ## <summary> ## All of the rules required to administrate ## an ganesha environment ## </summary> ## <param name="domain"> ## <summary> ## Domain allowed access. ## </summary> ## </param> ## <param name="role"> ## <summary> ## Role allowed access. ## </summary> ## </param> ## <rolecap/> # interface(`ganesha_admin',` gen_require(` type ganesha_t; type ganesha_var_run_t; type ganesha_unit_file_t; ') allow $1 ganesha_t:process { signal_perms }; ps_process_pattern($1, ganesha_t) tunable_policy(`deny_ptrace',`',` allow $1 ganesha_t:process ptrace; ') files_search_pids($1) admin_pattern($1, ganesha_var_run_t) ganesha_systemctl($1) admin_pattern($1, ganesha_unit_file_t) allow $1 ganesha_unit_file_t:service all_service_perms; optional_policy(` systemd_passwd_agent_exec($1) systemd_read_fifo_file_passwd_run($1) ') ') ####################################### ## <summary> ## Start or stop ganesha service ## </summary> ## <param name="domain"> ## <summary> ## Domain allowed access ## </summary> ## </param> # # Definition ensuring compatibility with systems that do not have this interface interface(`ganesha_service_start_stop',` gen_require(` type ganesha_unit_file_t; ') allow $1 ganesha_unit_file_t:service { start stop }; ') ####################################### ## <summary> ## Get status of ganesha service ## </summary> ## <param name="domain"> ## <summary> ## Domain allowed access ## </summary> ## </param> # # Definition ensuring compatibility with systems that do not have this interface interface(`ganesha_service_status',` gen_require(` type ganesha_unit_file_t; ') allow $1 ganesha_unit_file_t:service status; ') ####################################### ## <summary> ## Send and receive messages from glusterd over dbus. ## </summary> ## <param name="domain"> ## <summary> ## Domain allowed access ## </summary> ## </param> # # Definition ensuring compatibility with systems that do not have this interface ifndef(`glusterd_dbus_chat',` interface(`glusterd_dbus_chat',` gen_require(` type glusterd_t; ') allow $1 glusterd_t:dbus send_msg; allow glusterd_t $1:dbus send_msg; ') ') ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/selinux/ganesha.te��������������������������������������������������������������0000664�0000000�0000000�00000024074�14737566223�0020022�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������policy_module(ganesha, 1.0.0) require { type var_lib_nfs_t; type apm_bios_t; type autofs_device_t; type bpf_t; type clock_device_t; type configfs_t; type cpu_device_t; type debugfs_t; type default_t; type device_t; type devpts_t; type dri_device_t; type event_device_t; type fixed_disk_device_t; type framebuf_device_t; type fs_t; type hugetlbfs_t; type initctl_t; type kmsg_device_t; type kvm_device_t; type lvm_control_t; type memory_device_t; type mouse_device_t; type netcontrol_device_t; type nvram_device_t; type portmap_port_t; type proc_kcore_t; type proc_t; type pstore_t; type ptmx_t; type removable_device_t; type rpc_pipefs_t; type scsi_generic_device_t; type sound_device_t; type sysfs_t; type tmpfs_t; type usb_device_t; type usbmon_device_t; type virtio_device_t; type watchdog_device_t; type wireless_device_t; type xserver_misc_device_t; type rpcbind_t; type cgroup_t; type usr_t; type cluster_t; type ceph_t; type ceph_log_t; class dbus send_msg; } # Note: gluster*_t types are still, at the time of this writing, all # defined in the Fedora/CentOS/RHEL base selinux policy (because # gluster never got their act together and rolled their own -selinux # package. ######################################## # # Declarations # ## <desc> ## <p> ## Allow ganesha to read/write fuse files ## </p> ## </desc> gen_tunable(ganesha_use_fusefs, false) type ganesha_t; type ganesha_exec_t; init_daemon_domain(ganesha_t, ganesha_exec_t) type ganesha_var_log_t; logging_log_file(ganesha_var_log_t) type ganesha_var_run_t; files_pid_file(ganesha_var_run_t) type ganesha_tmp_t; files_tmp_file(ganesha_tmp_t) type ganesha_unit_file_t; systemd_unit_file(ganesha_unit_file_t) ######################################## # # ganesha local policy # dontaudit ganesha_t self:capability net_admin; allow ganesha_t self:capability { dac_read_search dac_override fowner setgid setuid }; allow ganesha_t self:capability2 block_suspend; allow ganesha_t self:process { setcap setrlimit }; allow ganesha_t self:fifo_file rw_fifo_file_perms; allow ganesha_t self:unix_stream_socket create_stream_socket_perms; allow ganesha_t self:tcp_socket { accept listen }; manage_dirs_pattern(ganesha_t, ganesha_var_run_t, ganesha_var_run_t) manage_files_pattern(ganesha_t, ganesha_var_run_t, ganesha_var_run_t) manage_lnk_files_pattern(ganesha_t, ganesha_var_run_t, ganesha_var_run_t) files_pid_filetrans(ganesha_t, ganesha_var_run_t, { dir file lnk_file }) manage_dirs_pattern(ganesha_t, ganesha_var_log_t, ganesha_var_log_t) manage_files_pattern(ganesha_t, ganesha_var_log_t, ganesha_var_log_t) logging_log_filetrans(ganesha_t, ganesha_var_log_t, { file dir }) manage_dirs_pattern(ganesha_t, ganesha_tmp_t, ganesha_tmp_t) manage_files_pattern(ganesha_t, ganesha_tmp_t, ganesha_tmp_t) files_tmp_filetrans(ganesha_t, ganesha_tmp_t, { file dir }) kernel_read_system_state(ganesha_t) kernel_search_network_sysctl(ganesha_t) kernel_read_net_sysctls(ganesha_t) auth_use_nsswitch(ganesha_t) corenet_tcp_bind_nfs_port(ganesha_t) corenet_tcp_connect_generic_port(ganesha_t) corenet_tcp_connect_gluster_port(ganesha_t) corenet_udp_bind_dey_keyneg_port(ganesha_t) corenet_tcp_bind_dey_keyneg_port(ganesha_t) corenet_udp_bind_nfs_port(ganesha_t) corenet_udp_bind_all_rpc_ports(ganesha_t) corenet_tcp_bind_all_rpc_ports(ganesha_t) corenet_tcp_bind_mountd_port(ganesha_t) corenet_udp_bind_mountd_port(ganesha_t) corenet_tcp_connect_virt_migration_port(ganesha_t) corenet_tcp_connect_all_rpc_ports(ganesha_t) corenet_tcp_connect_portmap_port(ganesha_t) dev_rw_infiniband_dev(ganesha_t) dev_read_gpfs(ganesha_t) dev_read_rand(ganesha_t) logging_send_syslog_msg(ganesha_t) sysnet_dns_name_resolve(ganesha_t) optional_policy(` dbus_system_bus_client(ganesha_t) dbus_connect_system_bus(ganesha_t) unconfined_dbus_chat(ganesha_t) ') ifdef(`glusterd_read_conf',` optional_policy(` glusterd_read_conf(ganesha_t) ') ') ifdef(`glusterd_read_lib_files',` optional_policy(` glusterd_read_lib_files(ganesha_t) ') ') ifdef(`glusterd_manage_pid',` optional_policy(` glusterd_manage_pid(ganesha_t) ') ') optional_policy(` kerberos_read_keytab(ganesha_t) ') optional_policy(` rpc_manage_nfs_state_data_dir(ganesha_t) rpc_read_nfs_state_data(ganesha_t) rpcbind_stream_connect(ganesha_t) ') tunable_policy(`ganesha_use_fusefs',` fs_manage_fusefs_dirs(ganesha_t) fs_manage_fusefs_files(ganesha_t) fs_read_fusefs_symlinks(ganesha_t) fs_getattr_fusefs(ganesha_t) fs_search_fusefs(ganesha_t) ') ######################################## # # ganesha local policy rhbz#1796160, FSAL_RGW # rhbz#1829804 # #============= ganesha_t ============== allow ganesha_t ceph_log_t:dir { add_name search write }; allow ganesha_t ceph_log_t:file { create open }; fs_read_cgroup_files(ganesha_t) #!!!! This avc can be allowed using the boolean 'domain_can_mmap_files' allow ganesha_t usr_t:file map; #============= init_t ============== allow init_t var_lib_nfs_t:dir { create setattr }; ######################################## # # ganesha local policy rhbz#1821612, FSAL_RGW # #!!!! This avc can be allowed using the boolean 'daemons_enable_cluster_mode' allow ganesha_t ceph_t:unix_stream_socket connectto; ######################################## # # ganesha local policy rhbz#1788541, FSAL_VFS # rhbz#1829804 # #============= ganesha_t ============== dontaudit ganesha_t apm_bios_t:chr_file getattr; dontaudit ganesha_t autofs_device_t:chr_file getattr; dontaudit ganesha_t bpf_t:dir getattr; dontaudit ganesha_t bpf_t:filesystem getattr; dontaudit ganesha_t cgroup_t:dir getattr; dontaudit ganesha_t cgroup_t:filesystem getattr; dontaudit ganesha_t clock_device_t:chr_file getattr; dontaudit ganesha_t configfs_t:dir getattr; dontaudit ganesha_t configfs_t:filesystem getattr; dontaudit ganesha_t cpu_device_t:chr_file getattr; dontaudit ganesha_t debugfs_t:filesystem getattr; dontaudit ganesha_t device_t:chr_file getattr; dontaudit ganesha_t device_t:filesystem getattr; dontaudit ganesha_t devpts_t:filesystem getattr; dontaudit ganesha_t dri_device_t:chr_file getattr; dontaudit ganesha_t event_device_t:chr_file getattr; allow ganesha_t fixed_disk_device_t:blk_file { getattr ioctl open read }; dontaudit ganesha_t fixed_disk_device_t:chr_file getattr; dontaudit ganesha_t framebuf_device_t:chr_file getattr; dontaudit ganesha_t fs_t:filesystem getattr; allow ganesha_t hugetlbfs_t:dir { getattr open read }; dontaudit ganesha_t hugetlbfs_t:filesystem getattr; dontaudit ganesha_t initctl_t:fifo_file getattr; #!!!! This avc can be allowed using the boolean 'domain_can_write_kmsg' dontaudit ganesha_t kmsg_device_t:chr_file getattr; dontaudit ganesha_t kvm_device_t:chr_file getattr; dontaudit ganesha_t lvm_control_t:chr_file getattr; dontaudit ganesha_t memory_device_t:chr_file getattr; dontaudit ganesha_t mouse_device_t:chr_file getattr; dontaudit ganesha_t netcontrol_device_t:chr_file getattr; dontaudit ganesha_t nvram_device_t:chr_file getattr; dontaudit ganesha_t proc_kcore_t:file getattr; dontaudit ganesha_t proc_t:filesystem getattr; dontaudit ganesha_t pstore_t:dir getattr; dontaudit ganesha_t pstore_t:filesystem getattr; dontaudit ganesha_t ptmx_t:chr_file getattr; dontaudit ganesha_t removable_device_t:blk_file getattr; dontaudit ganesha_t rpc_pipefs_t:dir getattr; dontaudit ganesha_t rpc_pipefs_t:filesystem getattr; dontaudit ganesha_t scsi_generic_device_t:chr_file getattr; dontaudit ganesha_t sound_device_t:chr_file getattr; allow ganesha_t sysfs_t:dir read; allow ganesha_t sysfs_t:file { getattr open read }; dontaudit ganesha_t sysfs_t:filesystem getattr; allow ganesha_t sysfs_t:lnk_file read; allow ganesha_t tmpfs_t:dir read; dontaudit ganesha_t tmpfs_t:filesystem getattr; dontaudit ganesha_t usb_device_t:chr_file getattr; dontaudit ganesha_t usbmon_device_t:chr_file getattr; dontaudit ganesha_t virtio_device_t:chr_file getattr; dontaudit ganesha_t watchdog_device_t:chr_file getattr; dontaudit ganesha_t wireless_device_t:chr_file getattr; dontaudit ganesha_t xserver_misc_device_t:chr_file getattr; #============= init_t ============== allow init_t ganesha_var_log_t:dir { read setattr }; allow init_t ganesha_var_log_t:file setattr; allow init_t var_lib_nfs_t:dir create; #============= rpcbind_t ============== #!!!! This avc can be allowed using the boolean 'nis_enabled' allow rpcbind_t unreserved_port_t:udp_socket name_bind; ######################################## # # ganesha local policy rhbz#1857170 allow ganesha_t portmap_port_t:tcp_socket name_connect; ######################################## # # ganesha local policy, nfs-ganesha-2.8.4 # allow ganesha_t default_t:dir { add_name getattr open read write remove_name search }; allow ganesha_t default_t:file { create getattr open read write unlink }; allow ganesha_t self:capability { fowner setgid setuid }; # if you want to export old gluster brick dirs directly, e.g. because # maybe you're no longer using gluster. selinux knows about gluster brick # volumes in /bricks/... # Needs to be used in an optional_policy block requiring type glusterd_brick_t # allow ganesha_t glusterd_brick_t:dir { add_name getattr open read write remove_name search }; # allow ganesha_t glusterd_brick_t:file { create getattr open read write unlink }; ######################################## # # ganesha local policy rhbz#1816501, rhbz#1822500, rhbz#1826101, # rhbz#1826627 optional_policy(` glusterd_dbus_chat(ganesha_t) # the following interfaces can only be safely used together with an interface # requiring glusterd_t type such as glusterd_dbus_chat ganesha_service_status(glusterd_t) ganesha_service_start_stop(glusterd_t) ') ######################################## # # ganesha local policy rhbz#1855350 ifdef(`ceph_read_lib_files',` optional_policy(` ceph_read_lib_files(ganesha_t) ') ') ifdef(`ceph_search_lib',` optional_policy(` ceph_search_lib(ganesha_t) ') ') ifdef(`ceph_read_pid_files',` optional_policy(` ceph_read_pid_files(ganesha_t) ') ') ######################################## # # ganesha local policy rhbz#1938050 allow ganesha_t cluster_t:dbus send_msg; allow init_t var_lib_nfs_t:lnk_file read; ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0016100�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/CMakeLists.txt����������������������������������������������������������0000664�0000000�0000000�00000006141�14737566223�0020642�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb <jlieb@panasas.com> # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- if(USE_DBUS) include_directories( ${DBUS_INCLUDE_DIRS} ) endif(USE_DBUS) # string utilities. This should eventually go into src/os # because BSD has them and Linux doesn't set(string_utils_STAT_SRCS strlcpy.c strnlen.c refstr.c ) add_library(string_utils OBJECT ${string_utils_STAT_SRCS}) add_sanitizers(string_utils) set_target_properties(string_utils PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(string_utils gsh_trace_header_generate) include("${CMAKE_BINARY_DIR}/gsh_lttng_generation_file_properties.cmake") endif (USE_LTTNG) # hash function libraries set(hash_SRCS murmur3.c city.c ) add_library(hash OBJECT ${hash_SRCS}) add_sanitizers(hash) set_target_properties(hash PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(hash gsh_trace_header_generate) endif (USE_LTTNG) # uid2grp mapper set( uid2grp_SRCS uid2grp.c uid2grp_cache.c ) add_library(uid2grp OBJECT ${uid2grp_SRCS}) add_sanitizers(uid2grp) set_target_properties(uid2grp PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(uid2grp gsh_trace_header_generate) endif (USE_LTTNG) # netgroup cache set(netgroup_cache_SRCS netgroup_cache.c ) add_library(netgroup_cache OBJECT ${netgroup_cache_SRCS}) add_sanitizers(netgroup_cache) set_target_properties(netgroup_cache PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(netgroup_cache gsh_trace_header_generate) endif (USE_LTTNG) ########### next target ############### SET(support_STAT_SRCS nfs4_acls.c nfs_creds.c nfs_filehandle_mgmt.c nfs_read_conf.c nfs_convert.c nfs_ip_name.c ds.c exports.c fridgethr.c delayed_exec.c bsd-base64.c server_stats.c export_mgr.c nfs4_fs_locations.c xprt_handler.c ) if(ERROR_INJECTION) set(support_STAT_SRCS ${support_STAT_SRCS} err_inject.c ) endif(ERROR_INJECTION) add_library(support OBJECT ${support_STAT_SRCS}) add_sanitizers(support) set_target_properties(support PROPERTIES COMPILE_FLAGS "-fPIC") if (USE_LTTNG) add_dependencies(support gsh_trace_header_generate) endif (USE_LTTNG) ########### install files ############### �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/bsd-base64.c������������������������������������������������������������0000664�0000000�0000000�00000023735�14737566223�0020110�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: 0BSD /* $OpenBSD: base64.c,v 1.3 1997/11/08 20:46:55 deraadt Exp $ */ /* * Copyright (c) 1996 by Internet Software Consortium. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS * SOFTWARE. */ /* * Portions Copyright (c) 1995 by International Business Machines, Inc. * * International Business Machines, Inc. (hereinafter called IBM) grants * permission under its copyrights to use, copy, modify, and distribute this * Software with or without fee, provided that the above copyright notice and * all paragraphs of this notice appear in all copies, and that the name of IBM * not be used in connection with the marketing of any product incorporating * the Software or modifications thereof, without specific, written prior * permission. * * To the extent it has a right to do so, IBM grants an immunity from suit * under its patents, if any, for the use, sale or manufacture of products to * the extent that such products are used for performing Domain Name System * dynamic updates in TCP/IP networks by means of the Software. No immunity is * granted for any product per se or for any other function of any product. * * THE SOFTWARE IS PROVIDED "AS IS", AND IBM DISCLAIMS ALL WARRANTIES, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE. IN NO EVENT SHALL IBM BE LIABLE FOR ANY SPECIAL, * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE, EVEN * IF IBM IS APPRISED OF THE POSSIBILITY OF SUCH DAMAGES. */ #include "config.h" #if !defined(HAVE_B64_NTOP) && !defined(HAVE___B64_NTOP) #include <sys/types.h> #include <ctype.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "bsd-base64.h" #define Assert(Cond) \ do { \ if (!(Cond)) \ abort(); \ } while (0) static const char Base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static const char Pad64 = '='; /* * URL or file name safe encoding, see * http://tools.ietf.org/html/rfc4648 for mapping details */ static const char Base64url[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; /* (From RFC1521 and draft-ietf-dnssec-secext-03.txt) The following encoding technique is taken from RFC 1521 by Borenstein and Freed. It is reproduced here in a slightly edited form for convenience. A 65-character subset of US-ASCII is used, enabling 6 bits to be represented per printable character. (The extra 65th character, "=", is used to signify a special processing function.) The encoding process represents 24-bit groups of input bits as output strings of 4 encoded characters. Proceeding from left to right, a 24-bit input group is formed by concatenating 3 8-bit input groups. These 24 bits are then treated as 4 concatenated 6-bit groups, each of which is translated into a single digit in the base64 alphabet. Each 6-bit group is used as an index into an array of 64 printable characters. The character referenced by the index is placed in the output string. Table 1: The Base64 Alphabet Value Encoding Value Encoding Value Encoding Value Encoding 0 A 17 R 34 i 51 z 1 B 18 S 35 j 52 0 2 C 19 T 36 k 53 1 3 D 20 U 37 l 54 2 4 E 21 V 38 m 55 3 5 F 22 W 39 n 56 4 6 G 23 X 40 o 57 5 7 H 24 Y 41 p 58 6 8 I 25 Z 42 q 59 7 9 J 26 a 43 r 60 8 10 K 27 b 44 s 61 9 11 L 28 c 45 t 62 + 12 M 29 d 46 u 63 / 13 N 30 e 47 v 14 O 31 f 48 w (pad) = 15 P 32 g 49 x 16 Q 33 h 50 y Special processing is performed if fewer than 24 bits are available at the end of the data being encoded. A full encoding quantum is always completed at the end of a quantity. When fewer than 24 input bits are available in an input group, zero bits are added (on the right) to form an integral number of 6-bit groups. Padding at the end of the data is performed using the '=' character. Since all base64 input is an integral number of octets, only the ------------------------------------------------- following cases can arise: (1) the final quantum of encoding input is an integral multiple of 24 bits; here, the final unit of encoded output will be an integral multiple of 4 characters with no "=" padding, (2) the final quantum of encoding input is exactly 8 bits; here, the final unit of encoded output will be two characters followed by two "=" padding characters, or (3) the final quantum of encoding input is exactly 16 bits; here, the final unit of encoded output will be three characters followed by one "=" padding character. */ int b64_enc(u_char const *src, size_t srclength, char *target, size_t targsize, const char *map) { size_t datalength = 0; u_char input[3]; u_char output[4]; int i; while (srclength > 2) { input[0] = *src++; input[1] = *src++; input[2] = *src++; srclength -= 3; output[0] = input[0] >> 2; output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); output[3] = input[2] & 0x3f; Assert(output[0] < 64); Assert(output[1] < 64); Assert(output[2] < 64); Assert(output[3] < 64); if (datalength + 4 > targsize) return -1; target[datalength++] = map[output[0]]; target[datalength++] = map[output[1]]; target[datalength++] = map[output[2]]; target[datalength++] = map[output[3]]; } /* Now we worry about padding. */ if (srclength != 0) { /* Get what's left. */ input[0] = input[1] = input[2] = '\0'; for (i = 0; i < srclength; i++) input[i] = *src++; output[0] = input[0] >> 2; output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4); output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6); Assert(output[0] < 64); Assert(output[1] < 64); Assert(output[2] < 64); if (datalength + 4 > targsize) return -1; target[datalength++] = map[output[0]]; target[datalength++] = map[output[1]]; if (srclength == 1) target[datalength++] = Pad64; else target[datalength++] = map[output[2]]; target[datalength++] = Pad64; } if (datalength >= targsize) return -1; target[datalength] = '\0'; /* Returned value doesn't count \0. */ return datalength; } int b64_ntop(u_char const *src, size_t srclength, char *target, size_t targsize) { return b64_enc(src, srclength, target, targsize, Base64); } int base64url_encode(u_char const *src, size_t srclength, char *target, size_t targsize) { return b64_enc(src, srclength, target, targsize, Base64url); } /* skips all whitespace anywhere. converts characters, four at a time, starting at (or after) src from base - 64 numbers into three 8 bit bytes in the target area. it returns the number of data bytes stored at the target, or -1 on error. */ int b64_pton(char const *src, u_char *target, size_t targsize) { int tarindex, state, ch; char *pos; state = 0; tarindex = 0; while ((ch = *src++) != '\0') { if (isspace(ch)) /* Skip whitespace anywhere. */ continue; if (ch == Pad64) break; pos = strchr(Base64, ch); if (pos == 0) /* A non-base64 character. */ return -1; switch (state) { case 0: if (target) { if (tarindex >= targsize) return -1; target[tarindex] = (pos - Base64) << 2; } state = 1; break; case 1: if (target) { if (tarindex + 1 >= targsize) return -1; target[tarindex] |= (pos - Base64) >> 4; target[tarindex + 1] = ((pos - Base64) & 0x0f) << 4; } tarindex++; state = 2; break; case 2: if (target) { if (tarindex + 1 >= targsize) return -1; target[tarindex] |= (pos - Base64) >> 2; target[tarindex + 1] = ((pos - Base64) & 0x03) << 6; } tarindex++; state = 3; break; case 3: if (target) { if (tarindex >= targsize) return -1; target[tarindex] |= (pos - Base64); } tarindex++; state = 0; break; } } /* * We are done decoding Base-64 chars. Let's see if we ended * on a byte boundary, and/or with erroneous trailing characters. */ if (ch == Pad64) { /* We got a pad char. */ ch = *src++; /* Skip it, get next. */ switch (state) { case 0: /* Invalid = in first position */ case 1: /* Invalid = in second position */ return -1; case 2: /* Valid, means one byte of info */ /* Skip any number of spaces. */ for (; ch != '\0'; ch = *src++) if (!isspace(ch)) break; /* Make sure there is another trailing = sign. */ if (ch != Pad64) return -1; ch = *src++; /* Skip the = */ /* Fall through to "single trailing =" case. */ /* FALLTHROUGH */ case 3: /* Valid, means two bytes of info */ /* * We know this char is an =. Is there anything but * whitespace after it? */ for (; ch != '\0'; ch = *src++) if (!isspace(ch)) return -1; /* * Now make sure for cases 2 and 3 that the "extra" * bits that slopped past the last full byte were * zeros. If we don't check them, they become a * subliminal channel. */ if (target && target[tarindex] != 0) return -1; } } else { /* * We ended by seeing the end of the string. Make sure we * have no partial bytes lying around. */ if (state != 0) return -1; } return tarindex; } #endif /* !defined(HAVE_B64_NTOP) && !defined(HAVE___B64_NTOP) */ �����������������������������������nfs-ganesha-6.5/src/support/city-test.c�������������������������������������������������������������0000664�0000000�0000000�00000316660�14737566223�0020205�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: MIT /* city-test.c - cityhash-c * CityHash on C * Copyright (c) 2011-2012, Alexander Nusov * * - original copyright notice - * Copyright (c) 2011 Google, Inc. * * 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. */ #include <string.h> #include <stdio.h> #include "city.h" #ifdef __SSE4_2__ #include "citycrc.h" #endif static const uint64 k0 = 0xc3a5c85c97cb3127ULL; static const uint64 kSeed0 = 1234567; static const uint64 kSeed1 = 0xc3a5c85c97cb3127ULL; static const uint128 kSeed128 = { 1234567, 0xc3a5c85c97cb3127ULL }; static const int kDataSize = 1 << 20; static const int kTestSize = 300; static char data[1 << 20]; static int errors; /* global error count */ /* Initialize data to pseudorandom values. */ void setup(void) { uint64 a = 9; uint64 b = 777; int i; for (i = 0; i < kDataSize; i++) { a = (a ^ (a >> 41)) * k0 + b; b = (b ^ (b >> 41)) * k0 + i; uint8 u = b >> 37; memcpy(data + i, &u, 1); /* uint8 -> char */ } } #define C(x) (0x##x##ULL) static const uint64 testdata[300][15] = { { C(9 ae16a3b2f90404f), C(75106 db890237a4a), C(3f eac5f636039766), C(3df 09df c64c09a2b), C(3 cb540c392e51e29), C(6 b56343feac0663), C(5 b7bc50fd8e8ad92), C(3df 09df c64c09a2b), C(3 cb540c392e51e29), C(6 b56343feac0663), C(5 b7bc50fd8e8ad92), C(889f 555 a0f5b2dc0), C(7767800902 c8a8ce), C(bcd2a808f4cb4a44), C(e9024dba8f94f2f3) }, { C(75e9 dee28ded761d), C(931992 c1b14334c5), C(245ee b25ba2c172e), C(1290f 0e8 a5caa74d), C(ca4c6bf7583f5cda), C(e1d60d51632c536d), C(cbc54a1db641910a), C(1290f 0e8 a5caa74d), C(ca4c6bf7583f5cda), C(e1d60d51632c536d), C(cbc54a1db641910a), C(9866 d68d17c2c08e), C(8 d84ba63eb4d020a), C(df0ad99c78cbce44), C(7 c98593ef62573ed) }, { C(75 de892fdc5ba914), C(f89832e71f764c86), C(39 a82df1f278a297), C(b4af8ae673acb930), C(992 b7acb203d8885), C(57 b533f3f8b94d50), C(bbb69298a5dcf1a1), C(b4af8ae673acb930), C(992 b7acb203d8885), C(57 b533f3f8b94d50), C(bbb69298a5dcf1a1), C(433495196 af9ac4f), C(53445 c0896ae1fe6), C(f7b939315f6fb56f), C(ac1b05e5a2e0335e) }, { C(69 cfe9fca1cc683a), C(e65f2a81e19b8067), C(20575ea6370 a9d14), C(8f 52532f c6f005b7), C(4eb e60df371ec129), C(c6ef8a7f8deb8116), C(83df 17e3 c9bb9a67), C(8f 52532f c6f005b7), C(4eb e60df371ec129), C(c6ef8a7f8deb8116), C(83df 17e3 c9bb9a67), C(6 a0aaf51016e19cd), C(fb0d1e89f39dbf6a), C(c73095102872943a), C(405ea97456 c28a75) }, { C(675 b04c582a34966), C(53624 b5ef8cd4f45), C(c412e0931ac8c9b1), C(798637e677 c65a3), C(83e3 b06adc4cd3ff), C(f3e76e8a7135852f), C(111e66 cfbb05366d), C(798637e677 c65a3), C(83e3 b06adc4cd3ff), C(f3e76e8a7135852f), C(111e66 cfbb05366d), C(29 c4f84aa48e8682), C(b77a8685750c94d0), C(7 cab65571969123f), C(fb1dbd79f68a8519) }, { C(46f a817397ea8b68), C(cc960c1c15ce2d20), C(e5f9f947bafb9e79), C(b342cdf0d7ac4b2a), C(66914 d44b373b232), C(261194e76 cb43966), C(45 a0010190365048), C(b342cdf0d7ac4b2a), C(66914 d44b373b232), C(261194e76 cb43966), C(45 a0010190365048), C(e2586947ca8eac83), C(6650 daf2d9677cdc), C(2f 9533 d8f4951a9), C(a5bdc0f3edc4bd7b) }, { C(406e959 cdffadec7), C(e80dc125dca28ed1), C(e5beb146d4b79a21), C(e66d5c1bb441541a), C(d14961bc1fd265a2), C(e4cc669d4fc0577f), C(abf4a51e36da2702), C(e66d5c1bb441541a), C(d14961bc1fd265a2), C(e4cc669d4fc0577f), C(abf4a51e36da2702), C(21236 d12df338f75), C(54 b8c4a5ad2ae4a4), C(202 d50ef9c2d4465), C(5ec c6a128e51a797) }, { C(46663908 b4169b95), C(4e7 e90b5c426bf1d), C(dc660b58daaf8b2c), C(b298265ebd1bd55f), C(4 a5f6838b55c0b08), C(fc003c97aa05d397), C(2f b5adad3380c3bc), C(b298265ebd1bd55f), C(4 a5f6838b55c0b08), C(fc003c97aa05d397), C(2f b5adad3380c3bc), C(c46fd01d253b4a0b), C(4 c799235c2a33188), C(7e21 bc57487a11bf), C(e1392bb1994bd4f2) }, { C(f214b86cffeab596), C(5f ccb0b132da564f), C(86e7 aa8b4154b883), C(763529 c8d4189ea8), C(860 d77e7fef74ca3), C(3 b1ba41191219b6b), C(722 b25dfa6d0a04b), C(763529 c8d4189ea8), C(860 d77e7fef74ca3), C(3 b1ba41191219b6b), C(722 b25dfa6d0a04b), C(5f 7 b463094e22a91), C(75 d6f57376b31bd7), C(d253c7f89efec8e6), C(efe56ac880a2b8a3) }, { C(eba670441d1a4f7d), C(eb6b272502d975fa), C(69f 8 d424d50c083e), C(313 d49cb51b8cd2c), C(6e982 d8b4658654a), C(dd59629a17e5492d), C(81 cb23bdab95e30e), C(313 d49cb51b8cd2c), C(6e982 d8b4658654a), C(dd59629a17e5492d), C(81 cb23bdab95e30e), C(1e6 c3e6c454c774f), C(177655172666 d5ea), C(9 cc67e0d38d80886), C(36 a2d64d7bc58d22) }, { C(172 c17ff21dbf88d), C(1f 5104e320f 0 c815), C(1e34 e9f1fa63bcef), C(3506 ae8fae368d2a), C(59f a2b2de5306203), C(67 d1119dcfa6007e), C(1f 7190 c648ad9aef), C(3506 ae8fae368d2a), C(59f a2b2de5306203), C(67 d1119dcfa6007e), C(1f 7190 c648ad9aef), C(7e8 b1e689137b637), C(cbe373368a31db3c), C(dbc79d82cd49c671), C(641399520 c452c99) }, { C(5 a0838df8a019b8c), C(73f c859b4952923), C(45e39 daf153491bd), C(a9b91459a5fada46), C(de0fbf8800a2da3), C(21800e4 b5af9dedb), C(517 c3726ae0dbae7), C(a9b91459a5fada46), C(de0fbf8800a2da3), C(21800e4 b5af9dedb), C(517 c3726ae0dbae7), C(1 ccffbd74acf9d66), C(cbb08cf95e7eda99), C(61444f 09e2 a29587), C(35 c0d15745f96455) }, { C(8f 42 b1fbb2fc0302), C(5 ae31626076ab6ca), C(b87f0cb67cb75d28), C(2498586 ac2e1fab2), C(e683f9cbea22809a), C(a9728d0b2bbe377c), C(46 baf5cae53dc39a), C(2498586 ac2e1fab2), C(e683f9cbea22809a), C(a9728d0b2bbe377c), C(46 baf5cae53dc39a), C(806f 4352 c99229e), C(d4643728fc71754a), C(998 c1647976bc893), C(d8094fdc2d6bb032) }, { C(72085e82 d70dcea9), C(32f 502 c43349ba16), C(5eb c98c3645a018f), C(c7fa762238fd90ac), C(8 d03b5652d615677), C(a3f5226e51d42217), C(46 d5010a7cae8c1e), C(c7fa762238fd90ac), C(8 d03b5652d615677), C(a3f5226e51d42217), C(46 d5010a7cae8c1e), C(4293122580 db7f5f), C(3df 6042f 39 c6d487), C(439124809 cf5c90e), C(90 b704e4f71d0ccf) }, { C(32 b75fc2223b5032), C(246f ff80eb230868), C(a6fdbc82c9aeecc0), C(c089498074167021), C(ab094a9f9ab81c23), C(4f acf3d9466bcb03), C(57 aa9c67938cf3eb), C(c089498074167021), C(ab094a9f9ab81c23), C(4f acf3d9466bcb03), C(57 aa9c67938cf3eb), C(79 a769ca1c762117), C(9 c8dee60337f87a8), C(dabf1b96535a3abb), C(f87e9fbb590ba446) }, { C(e1dd010487d2d647), C(12352858295 d2167), C(acc5e9b6f6b02dbb), C(1 c66ceea473413df), C(dc3f70a124b25a40), C(66 a6dfe54c441cd8), C(b436dabdaaa37121), C(1 c66ceea473413df), C(dc3f70a124b25a40), C(66 a6dfe54c441cd8), C(b436dabdaaa37121), C(6 d95aa6890f51674), C(42 c6c0fc7ab3c107), C(83 b9dfe082e76140), C(939 cdbd3614d6416) }, { C(2994f 9245194 a7e2), C(b7cd7249d6db6c0c), C(2170 a7d119c5c6c3), C(8505 c996b70ee9fc), C(b92bba6b5d778eb7), C(4 db4c57f3a7a4aee), C(3 cfd441cb222d06f), C(8505 c996b70ee9fc), C(b92bba6b5d778eb7), C(4 db4c57f3a7a4aee), C(3 cfd441cb222d06f), C(4 d940313c96ac6bd), C(43762837 c9ffac4b), C(480f cf58920722e3), C(4 bbd1e1a1d06752f) }, { C(32e2 ed6fa03e5b22), C(58 baf09d7c71c62b), C(a9c599f3f8f50b5b), C(1660 a2c4972d0fa1), C(1 a1538d6b50a57c), C(8 a5362485bbc9363), C(e8eec3c84fd9f2f8), C(1660 a2c4972d0fa1), C(1 a1538d6b50a57c), C(8 a5362485bbc9363), C(e8eec3c84fd9f2f8), C(2562514461 d373da), C(33857675f ed52b4), C(e58d2a17057f1943), C(fe7d3f30820e4925) }, { C(37 a72b6e89410c9f), C(139f ec53b78cee23), C(4f ccd8f0da7575c3), C(3 a5f04166518ac75), C(f49afe05a44fc090), C(cb01b4713cfda4bd), C(9027 bd37ffc0a5de), C(3 a5f04166518ac75), C(f49afe05a44fc090), C(cb01b4713cfda4bd), C(9027 bd37ffc0a5de), C(e15144d3ad46ec1b), C(736f d99679a5ae78), C(b3d7ed9ed0ddfe57), C(cef60639457867d7) }, { C(10836563 cb8ff3a1), C(d36f67e2dfc085f7), C(edc1bb6a3dcba8df), C(bd4f3a0566df3bed), C(81f c8230c163dcbe), C(4168 bc8417a8281b), C(7100 c9459827c6a6), C(bd4f3a0566df3bed), C(81f c8230c163dcbe), C(4168 bc8417a8281b), C(7100 c9459827c6a6), C(21 cad59eaf79e72f), C(61 c8af6fb71469f3), C(b0dfc42ce4f578b), C(33ea34 ccea305d4e) }, { C(4 dabcb5c1d382e5c), C(9 a868c608088b7a4), C(7 b2b6c389b943be5), C(c914b925ab69fda0), C(6 bafe864647c94d7), C(7 a48682dd4afa22), C(40f e01210176ba10), C(c914b925ab69fda0), C(6 bafe864647c94d7), C(7 a48682dd4afa22), C(40f e01210176ba10), C(88dd 28f 33ec31388), C(c6db60abf1d45381), C(7 b94c447298824d5), C(6 b2a5e05ad0b9fc0) }, { C(296 afb509046d945), C(c38fe9eb796bd4be), C(d7b17535df110279), C(dd2482b87d1ade07), C(662785 d2e3e78ddf), C(eae39994375181bb), C(9994500 c077ee1db), C(dd2482b87d1ade07), C(662785 d2e3e78ddf), C(eae39994375181bb), C(9994500 c077ee1db), C(a275489f8c6bb289), C(30695ea31df 1 a369), C(1 aeeb31802d701b5), C(7799 d5a6d5632838) }, { C(f7c0257efde772ea), C(af6af9977ecf7bff), C(1 cdff4bd07e8d973), C(fab1f4acd2cd4ab4), C(b4e19ba52b566bd), C(7f 1 db45725fe2881), C(70276f f8763f8396), C(fab1f4acd2cd4ab4), C(b4e19ba52b566bd), C(7f 1 db45725fe2881), C(70276f f8763f8396), C(1 b0f2b546dddd16b), C(aa066984b5fd5144), C(7 c3f9386c596a5a8), C(e5befdb24b665d5f) }, { C(61e021 c8da344ba1), C(cf9c720676244755), C(354f fa8e9d3601f6), C(44e40 a03093fbd92), C(bda9481cc5b93cae), C(986 b589cbc0cf617), C(210f 59f 074044831), C(44e40 a03093fbd92), C(bda9481cc5b93cae), C(986 b589cbc0cf617), C(210f 59f 074044831), C(ac32cbbb6f50245a), C(afa6f712efb22075), C(47289f 7 af581719f), C(31 b6e75d3aa0e54b) }, { C(c0a86ed83908560b), C(440 c8b6f97bd1749), C(a99bf2891726ea93), C(ac0c0b84df66df9d), C(3ee2337 b437eb264), C(8 a341daed9a25f98), C(cc665499aa38c78c), C(ac0c0b84df66df9d), C(3ee2337 b437eb264), C(8 a341daed9a25f98), C(cc665499aa38c78c), C(af7275299d79a727), C(874f a8434b45d0e), C(ca7b67388950dd33), C(2 db5cd3675ec58f7) }, { C(35 c9cf87e4accbf3), C(2267eb4 d2191b2a3), C(80217695666 b2c9), C(cd43a24abbaae6d), C(a88abf0ea1b2a8ff), C(e297ff01427e2a9d), C(935 d545695b2b41d), C(cd43a24abbaae6d), C(a88abf0ea1b2a8ff), C(e297ff01427e2a9d), C(935 d545695b2b41d), C(6 accd4dbcb52e849), C(261295 acb662fd49), C(f9d91f1ac269a8a2), C(5e45f 39df 355e395) }, { C(e74c366b3091e275), C(522e657 c5da94b06), C(ca9afa806f1a54ac), C(b545042f67929471), C(90 d10e75ed0e75d8), C(3ea60f 8f 158df 77e), C(8863eff 3 c2d670b7), C(b545042f67929471), C(90 d10e75ed0e75d8), C(3ea60f 8f 158df 77e), C(8863eff 3 c2d670b7), C(5799296e97f 144 a7), C(1 d6e517c12a88271), C(da579e9e1add90ef), C(942f b4cdbc3a4da) }, { C(a3f2ca45089ad1a6), C(13f 6270f e56fbce4), C(1f 93 a534bf03e705), C(aaea14288ae2d90c), C(1 be3cd51ef0f15e8), C(e8b47c84d5a4aac1), C(297 d27d55b766782), C(aaea14288ae2d90c), C(1 be3cd51ef0f15e8), C(e8b47c84d5a4aac1), C(297 d27d55b766782), C(e922d1d8bb2afd0), C(b4481c4fa2e7d8d5), C(691e21538 af794d5), C(9 bd4fb0a53962a72) }, { C(e5181466d8e60e26), C(cf31f3a2d582c4f3), C(d9cee87cb71f75b2), C(4750 ca6050a2d726), C(d6e6dd8940256849), C(f3b3749fdab75b0), C(c55d8a0f85ba0ccf), C(4750 ca6050a2d726), C(d6e6dd8940256849), C(f3b3749fdab75b0), C(c55d8a0f85ba0ccf), C(47f 134f 9544 c6da6), C(e1cdd9cb74ad764), C(3 ce2d096d844941e), C(321f e62f608d2d4e) }, { C(fb528a8dd1e48ad7), C(98 c4fd149c8a63dd), C(4 abd8fc3377ae1f), C(d7a9304abbb47cc5), C(7f 2 b9a27aa57f99), C(353 ab332d4ef9f18), C(47 d56b8d6c8cf578), C(d7a9304abbb47cc5), C(7f 2 b9a27aa57f99), C(353 ab332d4ef9f18), C(47 d56b8d6c8cf578), C(df55f58ae09f311f), C(dba9511784fa86e3), C(c43ce0288858a47e), C(62971e94270 b78e1) }, { C(da6d2b7ea9d5f9b6), C(57 b11153ee3b4cc8), C(7 d3bd1256037142f), C(90 b16ff331b719b5), C(fc294e7ad39e01e6), C(d2145386bab41623), C(7045 a63d44d76011), C(90 b16ff331b719b5), C(fc294e7ad39e01e6), C(d2145386bab41623), C(7045 a63d44d76011), C(a232222ed0fe2fa4), C(e6c17dff6c323a8a), C(bbcb079be123ac6c), C(4121f e2c5de7d850) }, { C(61 d95225bc2293e), C(f6c52cb6be9889a8), C(91 a0667a7ed6a113), C(441133 d221486a3d), C(fb9c5a40e19515b), C(6 c967b6c69367c2d), C(145 bd9ef258c4099), C(441133 d221486a3d), C(fb9c5a40e19515b), C(6 c967b6c69367c2d), C(145 bd9ef258c4099), C(a0197657160c686e), C(91 ada0871c23f379), C(2f d74fceccb5c80c), C(bf04f24e2dc17913) }, { C(81247 c01ab6a9cc1), C(fbccea953e810636), C(ae18965000c31be0), C(15 bb46383daec2a5), C(716294063 b4ba089), C(f3bd691ce02c3014), C(14 ccaad685a20764), C(15 bb46383daec2a5), C(716294063 b4ba089), C(f3bd691ce02c3014), C(14 ccaad685a20764), C(5 db25914279d6f24), C(25 c451fce3b2ed06), C(e6bacb43ba1ddb9a), C(6 d77493a2e6fd76) }, { C(c17f3ebd3257cb8b), C(e9e68c939c118c8d), C(72 a5572be35bfc1b), C(f6916c341cb31f2a), C(591 da1353ee5f31c), C(f1313c98a836b407), C(e0b8473eada48cd1), C(f6916c341cb31f2a), C(591 da1353ee5f31c), C(f1313c98a836b407), C(e0b8473eada48cd1), C(ac5c2fb40b09ba46), C(3 a3e8a9344eb6548), C(3 bf9349a9d8483a6), C(c30dd0d9b15e92d0) }, { C(9802438969 c3043b), C(6 cd07575c948dd82), C(83e26 b6830ea8640), C(d52f1fa190576961), C(11 d182e4f0d419cc), C(5 d9ccf1b56617424), C(c8a16debb585e452), C(d52f1fa190576961), C(11 d182e4f0d419cc), C(5 d9ccf1b56617424), C(c8a16debb585e452), C(2158 a752d2686d40), C(b93c1fdf54789e8c), C(3 a9a435627b2a30b), C(de6e5e551e7e5ad5) }, { C(3dd 8ed248 a03d754), C(d8c1fcf001cb62e0), C(87 a822141ed64927), C(4 bfaf6fd26271f47), C(aefeae8222ad3c77), C(cfb7b24351a60585), C(8678904e9 e890b8f), C(4 bfaf6fd26271f47), C(aefeae8222ad3c77), C(cfb7b24351a60585), C(8678904e9 e890b8f), C(968dd 1 aa4d7dcf31), C(7 ac643b015007a39), C(d1e1bac3d94406ec), C(babfa52474a404fa) }, { C(c5bf48d7d3e9a5a3), C(8f 0249 b5c5996341), C(c6d2c8a606f45125), C(fd1779db740e2c48), C(1950ef50f efab3f8), C(e4536426a6196809), C(699556 c502a01a6a), C(fd1779db740e2c48), C(1950ef50f efab3f8), C(e4536426a6196809), C(699556 c502a01a6a), C(2f 49 d268bb57b946), C(b205baa6c66ebfa5), C(ab91ebe4f48b0da1), C(c7e0718ccc360328) }, { C(bc4a21d00cf52288), C(28df 3eb5 a533fa87), C(6081 bbc2a18dd0d), C(8ee d355d219e58b9), C(2 d7b9f1a3d645165), C(5758 d1aa8d85f7b2), C(9 c90c65920041dff), C(8ee d355d219e58b9), C(2 d7b9f1a3d645165), C(5758 d1aa8d85f7b2), C(9 c90c65920041dff), C(3 c5c4ea46645c7f1), C(346879ec c0e2eb90), C(8434f ec461bb5a0f), C(783 ccede50ef5ce9) }, { C(172 c8674913ff413), C(1815 a22400e832bf), C(7e011f 9467 a06650), C(161 be43353a31dd0), C(79 a8afddb0642ac3), C(df43af54e3e16709), C(6e12553 a75b43f07), C(161 be43353a31dd0), C(79 a8afddb0642ac3), C(df43af54e3e16709), C(6e12553 a75b43f07), C(3 ac1b1121e87d023), C(2 d47d33df7b9b027), C(e2d3f71f4e817ff5), C(70 b3a11ca85f8a39) }, { C(17 a361dbdaaa7294), C(c67d368223a3b83c), C(f49cf8d51ab583d2), C(666eb21 e2eaa596), C(778f 3e1 b6650d56), C(3f 6 be451a668fe2d), C(5452892 b0b101388), C(666eb21 e2eaa596), C(778f 3e1 b6650d56), C(3f 6 be451a668fe2d), C(5452892 b0b101388), C(cc867fceaeabdb95), C(f238913c18aaa101), C(f5236b44f324cea1), C(c507cc892ff83dd1) }, { C(5 cc268bac4bd55f), C(232717 a35d5b2f1), C(38 da1393365c961d), C(2 d187f89c16f7b62), C(4eb504204f a1be8), C(222 bd53d2efe5fa), C(a4dcd6d721ddb187), C(2 d187f89c16f7b62), C(4eb504204f a1be8), C(222 bd53d2efe5fa), C(a4dcd6d721ddb187), C(d86bbe67666eca70), C(c8bbae99d8e6429f), C(41 dac4ceb2cb6b10), C(2f 90 c331755f6c48) }, { C(db04969cc06547f1), C(fcacc8a75332f120), C(967 ccec4ed0c977e), C(ac5d1087e454b6cd), C(c1f8b2e284d28f6c), C(cc3994f4a9312cfa), C(8 d61606dbc4e060d), C(ac5d1087e454b6cd), C(c1f8b2e284d28f6c), C(cc3994f4a9312cfa), C(8 d61606dbc4e060d), C(17315 af3202a1307), C(850775145e01163 a), C(96f 10e7357f 930 d2), C(abf27049cf07129) }, { C(25 bd8d3ca1b375b2), C(4 ad34c2c865816f9), C(9 be30ad32f8f28aa), C(7755ea02 dbccad6a), C(cb8aaf8886247a4a), C(8f 6966 ce7ea1b6e6), C(3f 2863090f a45a70), C(7755ea02 dbccad6a), C(cb8aaf8886247a4a), C(8f 6966 ce7ea1b6e6), C(3f 2863090f a45a70), C(1e46 d73019c9fb06), C(af37f39351616f2c), C(657efdf ff20ea2ed), C(93 bdf4c58ada3ecb) }, { C(166 c11fbcbc89fd8), C(cce1af56c48a48aa), C(78908959 b8ede084), C(19032925 ba2c951a), C(a53ed6e81b67943a), C(edc871a9e8ef4bdf), C(ae66cf46a8371aba), C(19032925 ba2c951a), C(a53ed6e81b67943a), C(edc871a9e8ef4bdf), C(ae66cf46a8371aba), C(a37b97790fe75861), C(eda28c8622708b98), C(3f 0 a23509d3d5c9d), C(5787 b0e7976c97cf) }, { C(3565 bcc4ca4ce807), C(ec35bfbe575819d5), C(6 a1f690d886e0270), C(1 ab8c584625f6a04), C(ccfcdafb81b572c4), C(53 b04ba39fef5af9), C(64 ce81828eefeed4), C(1 ab8c584625f6a04), C(ccfcdafb81b572c4), C(53 b04ba39fef5af9), C(64 ce81828eefeed4), C(131 af99997fc662c), C(8 d9081192fae833c), C(2828064791 cb2eb), C(80554 d2e8294065c) }, { C(b7897fd2f274307d), C(6 d43a9e5dd95616d), C(31 a2218e64d8fce0), C(664e581f c1cf769b), C(415110942f c97022), C(7 a5d38fee0bfa763), C(dc87ddb4d7495b6c), C(664e581f c1cf769b), C(415110942f c97022), C(7 a5d38fee0bfa763), C(dc87ddb4d7495b6c), C(7 c3b66372e82e64b), C(1 c89c0ceeeb2dd1), C(dad76d2266214dbd), C(744783486e43 cc61) }, { C(aba98113ab0e4a16), C(287f 883 aede0274d), C(3ec d2a607193ba3b), C(e131f6cc9e885c28), C(b399f98d827e4958), C(6eb90 c8ed6c9090c), C(ec89b378612a2b86), C(e131f6cc9e885c28), C(b399f98d827e4958), C(6eb90 c8ed6c9090c), C(ec89b378612a2b86), C(cfc0e126e2f860c0), C(a9a8ab5dec95b1c), C(d06747f372f7c733), C(fbd643f943a026d3) }, { C(17f 7796e0 d4b636c), C(ddba5551d716137b), C(65f 9735375df 1 ada), C(a39e946d02e14ec2), C(1 c88cc1d3822a193), C(663f 8074 a5172bb4), C(8 ad2934942e4cb9c), C(a39e946d02e14ec2), C(1 c88cc1d3822a193), C(663f 8074 a5172bb4), C(8 ad2934942e4cb9c), C(3 da03b033a95f16c), C(54 a52f1932a1749d), C(779ee b734199bc25), C(359 ce8c8faccc57b) }, { C(33 c0128e62122440), C(b23a588c8c37ec2b), C(f2608199ca14c26a), C(acab0139dc4f36df), C(9502 b1605ca1345a), C(32174ef1 e06a5e9c), C(d824b7869258192b), C(acab0139dc4f36df), C(9502 b1605ca1345a), C(32174ef1 e06a5e9c), C(d824b7869258192b), C(681 d021b52064762), C(30 b6c735f80ac371), C(6 a12d8d7f78896b3), C(157111657 a972144) }, { C(988 bc5d290b97aef), C(6754 bb647eb47666), C(44 b5cf8b5b8106a8), C(a1c5ba961937f723), C(32 d6bc7214dfcb9b), C(6863397e0f 4 c6758), C(e644bcb87e3eef70), C(a1c5ba961937f723), C(32 d6bc7214dfcb9b), C(6863397e0f 4 c6758), C(e644bcb87e3eef70), C(bf25ae22e7aa7c97), C(f5f3177da5756312), C(56 a469cb0dbb58cd), C(5233184 bb6130470) }, { C(23 c8c25c2ab72381), C(d6bc672da4175fba), C(6 aef5e6eb4a4eb10), C(3df 880 c945e68aed), C(5e08 a75e956d456f), C(f984f088d1a322d7), C(7 d44a1b597b7a05e), C(3df 880 c945e68aed), C(5e08 a75e956d456f), C(f984f088d1a322d7), C(7 d44a1b597b7a05e), C(cbd7d157b7fcb020), C(2e2945 e90749c2aa), C(a86a13c934d8b1bb), C(fbe3284bb4eab95f) }, { C(450f e4acc4ad3749), C(3111 b29565e4f852), C(db570fc2abaf13a9), C(35107 d593ba38b22), C(fd8212a125073d88), C(72805 d6e015bfacf), C(6 b22ae1a29c4b853), C(35107 d593ba38b22), C(fd8212a125073d88), C(72805 d6e015bfacf), C(6 b22ae1a29c4b853), C(df2401f5c3c1b633), C(c72307e054c81c8f), C(3ef bfe65bd2922c0), C(b4f632e240b3190c) }, { C(48e1 eff032d90c50), C(dee0fe333d962b62), C(c845776990c96775), C(8ea71758346 b71c9), C(d84258cab79431fd), C(af566b4975cce10a), C(5 c5c7e70a91221d2), C(8ea71758346 b71c9), C(d84258cab79431fd), C(af566b4975cce10a), C(5 c5c7e70a91221d2), C(c33202c7be49ea6f), C(e8ade53b6cbf4caf), C(102ea04f c82ce320), C(c1f7226614715e5e) }, { C(c048604ba8b6c753), C(21ea6 d24b417fdb6), C(4e40 a127ad2d6834), C(5234231 bf173c51), C(62319525583eaf 29), C(87632ef a9144cc04), C(1749 de70c8189067), C(5234231 bf173c51), C(62319525583eaf 29), C(87632ef a9144cc04), C(1749 de70c8189067), C(29672240923e8207), C(11dd 247 a815f6d0d), C(8 d64e16922487ed0), C(9f a6f45d50d83627) }, { C(67f f1cbe469ebf84), C(3 a828ac9e5040eb0), C(85 bf1ad6b363a14b), C(2f c6c0783390d035), C(ef78307f5be5524e), C(a46925b7a1a77905), C(fea37470f9a51514), C(2f c6c0783390d035), C(ef78307f5be5524e), C(a46925b7a1a77905), C(fea37470f9a51514), C(9 d6504cf6d3947ce), C(174 cc006b8e96e7), C(d653a06d8a009836), C(7 d22b5399326a76c) }, { C(b45c7536bd7a5416), C(e2d17c16c4300d3c), C(b70b641138765ff5), C(a5a859ab7d0ddcfc), C(8730164 a0b671151), C(af93810c10348dd0), C(7256010 c74f5d573), C(a5a859ab7d0ddcfc), C(8730164 a0b671151), C(af93810c10348dd0), C(7256010 c74f5d573), C(e22a335be6cd49f3), C(3 bc9c8b40c9c397a), C(18 da5c08e28d3fb5), C(f58ea5a00404a5c9) }, { C(215 c2eaacdb48f6f), C(33 b09acf1bfa2880), C(78 c4e94ba9f28bf), C(981 b7219224443d1), C(1f 476f c4344d7bba), C(abad36e07283d3a5), C(831 bf61190eaaead), C(981 b7219224443d1), C(1f 476f c4344d7bba), C(abad36e07283d3a5), C(831 bf61190eaaead), C(4 c90729f62432254), C(2f fadc94c89f47b3), C(677e790 b43d20e9a), C(bb0a1686e7c3ae5f) }, { C(241 baf16d80e0fe8), C(b6b3c5b53a3ce1d), C(6 ae6b36209eecd70), C(a560b6a4aa3743a4), C(b3e04f202b7a99b), C(3 b3b1573f4c97d9f), C(ccad8715a65af186), C(a560b6a4aa3743a4), C(b3e04f202b7a99b), C(3 b3b1573f4c97d9f), C(ccad8715a65af186), C(d0c93a838b0c37e7), C(7150 aa1be7eb1aad), C(755 b1e60b84d8d), C(51916e77 b1b05ba9) }, { C(d10a9743b5b1c4d1), C(f16e0e147ff9ccd6), C(fbd20a91b6085ed3), C(43 d309eb00b771d5), C(a6d1f26105c0f61b), C(d37ad62406e5c37e), C(75 d9b28c717c8cf7), C(43 d309eb00b771d5), C(a6d1f26105c0f61b), C(d37ad62406e5c37e), C(75 d9b28c717c8cf7), C(8f 5f 118 b425b57cd), C(5 d806318613275f3), C(8150848 bcf89d009), C(d5531710d53e1462) }, { C(919ef9 e209f2edd1), C(684 c33fb726a720a), C(540353f 94e8033), C(26 da1a143e7d4ec4), C(55095ea e445aacf4), C(31ef ad866d075938), C(f9b580cff4445f94), C(26 da1a143e7d4ec4), C(55095ea e445aacf4), C(31ef ad866d075938), C(f9b580cff4445f94), C(b1bea6b8716d9c48), C(9ed2 a3df4a15dc53), C(11f 1 be58843eb8e9), C(d9899ecaaef3c77c) }, { C(b5f9519b6c9280b), C(7823 a2fe2e103803), C(d379a205a3bd4660), C(466ec55 ee4b4302a), C(714f 1 b9985deeaf0), C(728595f 26e633 cf7), C(25ec d0738e1bee2b), C(466ec55 ee4b4302a), C(714f 1 b9985deeaf0), C(728595f 26e633 cf7), C(25ec d0738e1bee2b), C(db51771ad4778278), C(763e5742 ac55639e), C(df040e92d38aa785), C(5df 997 d298499bf1) }, { C(77 a75e89679e6757), C(25 d31fee616b5dd0), C(d81f2dfd08890060), C(7598df 8911dd 40 a4), C(3 b6dda517509b41b), C(7 dae29d248dfffae), C(6697 c427733135f), C(7598df 8911dd 40 a4), C(3 b6dda517509b41b), C(7 dae29d248dfffae), C(6697 c427733135f), C(834 d6c0444c90899), C(c790675b3cd53818), C(28 bb4c996ecadf18), C(92 c648513e6e6064) }, { C(9 d709e1b086aabe2), C(4 d6d6a6c543e3fec), C(df73b01acd416e84), C(d54f613658e35418), C(fcc88fd0567afe77), C(d18f2380980db355), C(ec3896137dfbfa8b), C(d54f613658e35418), C(fcc88fd0567afe77), C(d18f2380980db355), C(ec3896137dfbfa8b), C(eb48dbd9a1881600), C(ca7bd7415ab43ca9), C(e6c5a362919e2351), C(2f 4e4 bd2d5267c21) }, { C(91 c89971b3c20a8a), C(87 b82b1d55780b5), C(bc47bb80dfdaefcd), C(87e11 c0f44454863), C(2df 1 aedb5871cc4b), C(ba72fd91536382c8), C(52 cebef9e6ea865d), C(87e11 c0f44454863), C(2df 1 aedb5871cc4b), C(ba72fd91536382c8), C(52 cebef9e6ea865d), C(5 befc3fc66bc7fc5), C(b128bbd735a89061), C(f8f500816fa012b3), C(f828626c9612f04) }, { C(16468 c55a1b3f2b4), C(40 b1e8d6c63c9ff4), C(143 adc6fee592576), C(4 caf4deeda66a6ee), C(264720f 6f 35f 7840), C(71 c3aef9e59e4452), C(97886 ca1cb073c55), C(4 caf4deeda66a6ee), C(264720f 6f 35f 7840), C(71 c3aef9e59e4452), C(97886 ca1cb073c55), C(16155f ef16fc08e8), C(9 d0c1d1d5254139a), C(246513 bf2ac95ee2), C(22 c8440f59925034) }, { C(1 a2bd6641870b0e4), C(e4126e928f4a7314), C(1e9227 d52aab00b2), C(d82489179f16d4e8), C(a3c59f65e2913cc5), C(36 cbaecdc3532b3b), C(f1b454616cfeca41), C(d82489179f16d4e8), C(a3c59f65e2913cc5), C(36 cbaecdc3532b3b), C(f1b454616cfeca41), C(99393e31 e3eefc16), C(3 ca886eac5754cdf), C(c11776fc3e4756dd), C(ca118f7059198ba) }, { C(1 d2f92f23d3e811a), C(e0812edbcd475412), C(92 d2d6ad29c05767), C(fd7feb3d2956875e), C(d7192a886b8b01b6), C(16e71 dba55f5b85a), C(93 dabd3ff22ff144), C(fd7feb3d2956875e), C(d7192a886b8b01b6), C(16e71 dba55f5b85a), C(93 dabd3ff22ff144), C(14f f0a5444c272c9), C(fb024d3bb8d915c2), C(1 bc3229a94cab5fe), C(6f 6f 1f b3c0dccf09) }, { C(a47c08255da30ca8), C(cf6962b7353f4e68), C(2808051ea18946 b1), C(b5b472960ece11ec), C(13935 c99b9abbf53), C(3e80 d95687f0432c), C(3516 ab536053be5), C(b5b472960ece11ec), C(13935 c99b9abbf53), C(3e80 d95687f0432c), C(3516 ab536053be5), C(748 ce6a935755e20), C(2961 b51d61b0448c), C(864624113 aae88d2), C(a143805366f91338) }, { C(efb3b0262c9cd0c), C(1273901e9 e7699b3), C(58633f 4 ad0dcd5bb), C(62e33 ba258712d51), C(fa085c15d779c0e), C(2 c15d9142308c5ad), C(feb517011f27be9e), C(62e33 ba258712d51), C(fa085c15d779c0e), C(2 c15d9142308c5ad), C(feb517011f27be9e), C(1 b2b049793b9eedb), C(d26be505fabc5a8f), C(adc483e42a5c36c5), C(c81ff37d56d3b00b) }, { C(5029700 a7773c3a4), C(d01231e97e300d0f), C(397 cdc80f1f0ec58), C(e4041579de57c879), C(bbf513cb7bab5553), C(66 ad0373099d5fa0), C(44 bb6b21b87f3407), C(e4041579de57c879), C(bbf513cb7bab5553), C(66 ad0373099d5fa0), C(44 bb6b21b87f3407), C(a8108c43b4daba33), C(c0b5308c311e865e), C(cdd265ada48f6fcf), C(efbc1dae0a95ac0a) }, { C(71 c8287225d96c9a), C(eb836740524735c4), C(4777522 d0e09846b), C(16f de90d02a1343b), C(ad14e0ed6e165185), C(8df 6e0 b2f24085dd), C(caa8a47292d50263), C(16f de90d02a1343b), C(ad14e0ed6e165185), C(8df 6e0 b2f24085dd), C(caa8a47292d50263), C(a020413ba660359d), C(9 de401413f7c8a0c), C(20 bfb965927a7c85), C(b52573e5f817ae27) }, { C(4e8 b9ad9347d7277), C(c0f195eeee7641cf), C(dbd810bee1ad5e50), C(8459801016414808), C(6f bf75735353c2d1), C(6e69 aaf2d93ed647), C(85 bb5b90167cce5e), C(8459801016414808), C(6f bf75735353c2d1), C(6e69 aaf2d93ed647), C(85 bb5b90167cce5e), C(39 d79ee490d890cc), C(ac9f31f7ec97deb0), C(3 bdc1cae4ed46504), C(eb5c63cfaee05622) }, { C(1 d5218d6ee2e52ab), C(cb25025c4daeff3b), C(aaf107566f31bf8c), C(aad20d70e231582b), C(eab92d70d9a22e54), C(cc5ab266375580c0), C(85091463e3630 dce), C(aad20d70e231582b), C(eab92d70d9a22e54), C(cc5ab266375580c0), C(85091463e3630 dce), C(b830b617a4690089), C(9 dacf13cd76f13cf), C(d47cc5224265c68f), C(f04690880202b002) }, { C(162360 be6c293c8b), C(ff672b4a831953c8), C(dda57487ab6f78b5), C(38 a42e0db55a4275), C(585971 da56bb56d6), C(cd957009adc1482e), C(d6a96021e427567d), C(38 a42e0db55a4275), C(585971 da56bb56d6), C(cd957009adc1482e), C(d6a96021e427567d), C(8e2 b1a5a63cd96fe), C(426ef8 ce033d722d), C(c4d1c3d8acdda5f), C(4e694 c9be38769b2) }, { C(31459914f 13 c8867), C(ef96f4342d3bef53), C(a4e944ee7a1762fc), C(3526 d9b950a1d910), C(a58ba01135bca7c0), C(cbad32e86d60a87c), C(adde1962aad3d730), C(3526 d9b950a1d910), C(a58ba01135bca7c0), C(cbad32e86d60a87c), C(adde1962aad3d730), C(55f aade148929704), C(bfc06376c72a2968), C(97762698 b87f84be), C(117483 d4828cbaf7) }, { C(6 b4e8fca9b3aecff), C(3ea0 a33def0a296c), C(901f cb5fe05516f5), C(7 c909e8cd5261727), C(c5acb3d5fbdc832e), C(54eff 5 c782ad3cdd), C(9 d54397f3caf5bfa), C(7 c909e8cd5261727), C(c5acb3d5fbdc832e), C(54eff 5 c782ad3cdd), C(9 d54397f3caf5bfa), C(6 b53ce24c4fc3092), C(2789 abfdd4c9a14d), C(94 d6a2261637276c), C(648 aa4a2a1781f25) }, { C(dd3271a46c7aec5d), C(fb1dcb0683d711c3), C(240332e9 ebe5da44), C(479f 936 b6d496dca), C(dc2dc93d63739d4a), C(27e4151 c3870498c), C(3 a3a22ba512d13ba), C(479f 936 b6d496dca), C(dc2dc93d63739d4a), C(27e4151 c3870498c), C(3 a3a22ba512d13ba), C(5 da92832f96d3cde), C(439 b9ad48c4e7644), C(d2939279030accd9), C(6829f 920e2950 dbe) }, { C(109 b226238347d6e), C(e27214c32c43b7e7), C(eb71b0afaf0163ef), C(464f 1 adf4c68577), C(acf3961e1c9d897f), C(985 b01ab89b41fe1), C(6972 d6237390aac0), C(464f 1 adf4c68577), C(acf3961e1c9d897f), C(985 b01ab89b41fe1), C(6972 d6237390aac0), C(122 d89898e256a0e), C(ac830561bd8be599), C(5744312574f bf0ad), C(7 bff7f480a924ce9) }, { C(cc920608aa94cce4), C(d67efe9e097bce4f), C(5687727 c2c9036a9), C(8 af42343888843c), C(191433f fcbab7800), C(7eb45f c94f88a71), C(31 bc5418ffb88fa8), C(8 af42343888843c), C(191433f fcbab7800), C(7eb45f c94f88a71), C(31 bc5418ffb88fa8), C(4 b53a37d8f446cb7), C(a6a7dfc757a60d28), C(a074be7bacbc013a), C(cc6db5f270de7adc) }, { C(901f f46f22283dbe), C(9dd 59794 d049a066), C(3 c7d9c3b0e77d2c6), C(dc46069eec17bfdf), C(cacb63fe65d9e3e), C(362f b57287d530c6), C(5854 a4fbe1762d9), C(dc46069eec17bfdf), C(cacb63fe65d9e3e), C(362f b57287d530c6), C(5854 a4fbe1762d9), C(3197427495021ef c), C(5f abf34386aa4205), C(ca662891de36212), C(21f 603e4 d39bca84) }, { C(11 b3bdda68b0725d), C(2366 bf0aa97a00bd), C(55 dc4a4f6bf47e2b), C(69437142 dae5a255), C(f2980cc4816965ac), C(dbbe76ba1d9adfcf), C(49 c18025c0a8b0b5), C(69437142 dae5a255), C(f2980cc4816965ac), C(dbbe76ba1d9adfcf), C(49 c18025c0a8b0b5), C(fe25c147c9001731), C(38 b99cad0ca30c81), C(c7ff06ac47eb950), C(a10f92885a6b3c02) }, { C(9f 5f 03e84 a40d232), C(1151 a9ff99da844), C(d6f2e7c559ac4657), C(5e351 e20f30377bf), C(91 b3805daf12972c), C(94417f a6452a265e), C(bfa301a26765a7c), C(5e351 e20f30377bf), C(91 b3805daf12972c), C(94417f a6452a265e), C(bfa301a26765a7c), C(6924e2 a053297d13), C(ed4a7904ed30d77e), C(d734abaad66d6eab), C(ce373e6c09e6e8a1) }, { C(39eef f4f60f439be), C(1f 7559 c118517c70), C(6139 d2492237a36b), C(fd39b7642cecf78f), C(104f 1 af4e9201df5), C(ab1a3cc7eaeab609), C(cee3363f210a3d8b), C(fd39b7642cecf78f), C(104f 1 af4e9201df5), C(ab1a3cc7eaeab609), C(cee3363f210a3d8b), C(51490f 65f e56c884), C(6 a8c8322cda993c), C(1f 90609 a017de1f0), C(9f 3 acea480a41edf) }, { C(9 b9e0126fe4b8b04), C(6 a6190d520886c41), C(69640 b27c16b3ed8), C(18865f f87619fd8f), C(dec5293e665663d8), C(ea07c345872d3201), C(6f ce64da038a17ab), C(18865f f87619fd8f), C(dec5293e665663d8), C(ea07c345872d3201), C(6f ce64da038a17ab), C(ad48f3c826c6a83e), C(70 a1ff080a4da737), C(ecdac686c7d7719), C(700338424 b657470) }, { C(3ec4 b8462b36df47), C(ff8de4a1cbdb7e37), C(4ed e0449884716ac), C(b5f630ac75a8ce03), C(7 cf71ae74fa8566a), C(e068f2b4618df5d), C(369df 952 ad3fd0b8), C(b5f630ac75a8ce03), C(7 cf71ae74fa8566a), C(e068f2b4618df5d), C(369df 952 ad3fd0b8), C(5e1 ba38fea018eb6), C(5ea5 edce48e3da30), C(9 b3490c941069dcb), C(e17854a44cc2fff) }, { C(5e3f d9298fe7009f), C(d2058a44222d5a1d), C(cc25df39bfeb005c), C(1 b0118c5c60a99c7), C(6 ae919ef932301b8), C(cde25defa089c2fc), C(c2a3776e3a7716c4), C(1 b0118c5c60a99c7), C(6 ae919ef932301b8), C(cde25defa089c2fc), C(c2a3776e3a7716c4), C(2557 bf65fb26269e), C(b2edabba58f2ae4f), C(264144e9f 0e632 cb), C(ad6481273c979566) }, { C(7504ec b4727b274e), C(f698cfed6bc11829), C(71 b62c425ecd348e), C(2 a5e555fd35627db), C(55 d5da439c42f3b8), C(a758e451732a1c6f), C(18 caa6b46664b484), C(2 a5e555fd35627db), C(55 d5da439c42f3b8), C(a758e451732a1c6f), C(18 caa6b46664b484), C(6ec1 c7d1524bbad7), C(1 cc3531dc422529d), C(61 a6eeb29c0e5110), C(9 cc8652016784a6a) }, { C(4 bdedc104d5eaed5), C(531 c4bb4fd721e5d), C(1 d860834e94a219f), C(1944ec723253392 b), C(7ea6 aa6a2f278ea5), C(5f f786af8113b3d5), C(194832eb9 b0b8d0f), C(1944ec723253392 b), C(7ea6 aa6a2f278ea5), C(5f f786af8113b3d5), C(194832eb9 b0b8d0f), C(56 ab0396ed73fd38), C(2 c88725b3dfbf89d), C(7f f57adf6275c816), C(b32f7630bcdb218) }, { C(da0b4a6fb26a4748), C(8 a3165320ae1af74), C(4803664ee3 d61d09), C(81 d90ddff0d00fdb), C(2 c8c7ce1173b5c77), C(18 c6b6c8d3f91dfb), C(415 d5cbbf7d9f717), C(81 d90ddff0d00fdb), C(2 c8c7ce1173b5c77), C(18 c6b6c8d3f91dfb), C(415 d5cbbf7d9f717), C(b683e956f1eb3235), C(43166dd e2b64d11f), C(f9689c90f5aad771), C(ca0ebc253c2eec38) }, { C(bad6dd64d1b18672), C(6 d4c4b91c68bd23f), C(d8f1507176822db7), C(381068e0f 65f 708 b), C(b4f3762e451b12a6), C(6 d61ed2f6d4e741), C(8 b3b9df537b91a2c), C(381068e0f 65f 708 b), C(b4f3762e451b12a6), C(6 d61ed2f6d4e741), C(8 b3b9df537b91a2c), C(b0759e599a91575c), C(9e7 adbcc77212239), C(cf0eba98436555fe), C(b1fcc9c42c4cd1e6) }, { C(98 da3fe388d5860e), C(14 a9fda8b3adb103), C(d85f5f798637994b), C(6e8 e8ff107799274), C(24 a2ef180891b531), C(c0eaf33a074bcb9d), C(1f a399a82974e17e), C(6e8 e8ff107799274), C(24 a2ef180891b531), C(c0eaf33a074bcb9d), C(1f a399a82974e17e), C(e7c116bef933725d), C(859908 c7d17b93de), C(f6cfa27113af4a72), C(edf41c5d83c721a8) }, { C(ef243a576431d7ac), C(92 a32619ecfae0a5), C(fb34d2c062dc803a), C(f5f8b21ec30bd3a0), C(80 a442fd5c6482a8), C(4f de11e5ccde5169), C(55671451f 661 a885), C(f5f8b21ec30bd3a0), C(80 a442fd5c6482a8), C(4f de11e5ccde5169), C(55671451f 661 a885), C(94f 27 bc2d5d8d63e), C(2156968 b87f084dc), C(b591bcae146f6fea), C(f57f4c01e41ac7fe) }, { C(97854 de6f22c97b6), C(1292 ac07b0f426bb), C(9 a099a28b22d3a38), C(caac64f5865d87f3), C(771 b9fdbd3aa4bd2), C(88446393 c3606c2d), C(bc3d3dcd5b7d6d7f), C(caac64f5865d87f3), C(771 b9fdbd3aa4bd2), C(88446393 c3606c2d), C(bc3d3dcd5b7d6d7f), C(56e22512 b832d3ee), C(bbc677fe5ce0b665), C(f1914b0f070e5c32), C(c10d40362472dcd1) }, { C(d26ce17bfc1851d), C(db30fb632c7da294), C(26 cb7b1a465400a5), C(401 a0581221957e2), C(fc04e99ae3a283ce), C(fe895303ab2d1e3e), C(35 ab7c498403975b), C(401 a0581221957e2), C(fc04e99ae3a283ce), C(fe895303ab2d1e3e), C(35 ab7c498403975b), C(c6e4c8dc6f52fb11), C(63f 0 b484c2c7502f), C(93693 da3439bdbe9), C(1264 dbaaaaf6b7f1) }, { C(97477 bac0ba4c7f1), C(788ef8729 dca29ac), C(63 d88e226d36132c), C(330 b7e93663affbd), C(3 c59913fcf0d603f), C(e207e6572672fd0a), C(8 a5dc17019c8a667), C(330 b7e93663affbd), C(3 c59913fcf0d603f), C(e207e6572672fd0a), C(8 a5dc17019c8a667), C(5 c8f47ade659d40), C(6e0838 e5a808e9a2), C(8 a2d9a0afcd48b19), C(d1c9d5af7b48418d) }, { C(f6bbcba92b11f5c8), C(72 cf221cad20f191), C(a04726593764122d), C(77f bb70409d316e2), C(c864432c5208e583), C(d3f593922668c184), C(23307562648 bdb54), C(77f bb70409d316e2), C(c864432c5208e583), C(d3f593922668c184), C(23307562648 bdb54), C(b03e0b274f848a74), C(c6121e3af71f4281), C(2e48dd 2 a16ca63ec), C(f4cd44c69ae024df) }, { C(1 ac8b67c1c82132), C(7536 db9591be9471), C(42f 18f be7141e565), C(20085827 a39ff749), C(42e6 c504df174606), C(839 da16331fea7ac), C(7f d768552b10ffc6), C(20085827 a39ff749), C(42e6 c504df174606), C(839 da16331fea7ac), C(7f d768552b10ffc6), C(d1c53c90fde72640), C(c61ae7cf4e266556), C(127561e440 e4c156), C(f329cae8c26af3e1) }, { C(9 cd716ca0eee52fa), C(67 c1076e1ef11f93), C(927342024f 36f 5 d7), C(d0884af223fd056b), C(bb33aafc7b80b3e4), C(36 b722fea81a4c88), C(6e72 e3022c0ed97), C(d0884af223fd056b), C(bb33aafc7b80b3e4), C(36 b722fea81a4c88), C(6e72 e3022c0ed97), C(5 db446a3ba66e0ba), C(2e138f b81b28ad9), C(16e8 e82995237c85), C(9730 dbfb072fbf03) }, { C(1909f 39123 d9ad44), C(c0bdd71c5641fdb7), C(112e5 d19abda9b14), C(984 cf3f611546e28), C(d7d9c9c4e7efb5d7), C(b3152c389532b329), C(1 c168b512ec5f659), C(984 cf3f611546e28), C(d7d9c9c4e7efb5d7), C(b3152c389532b329), C(1 c168b512ec5f659), C(eca67cc49e26069a), C(73 cb0b224d36d541), C(df8379190ae6c5fe), C(e0f6bde7c4726211) }, { C(1 d206f99f535efeb), C(882e15548 afc3422), C(c94f203775c8c634), C(24940 a3adac420b8), C(5 adf73051c52bce0), C(1 aa5030247ed3d32), C(e1ae74ab6804c08b), C(24940 a3adac420b8), C(5 adf73051c52bce0), C(1 aa5030247ed3d32), C(e1ae74ab6804c08b), C(95217 bf71b0da84c), C(ca9bb91c0126a36e), C(741 b9a99ea470974), C(2 adc4e34b8670f41) }, { C(b38c3a83042eb802), C(ea134be7c6e0c326), C(81 d396c683df4f35), C(2 a55645640911e27), C(4f ac2eefbd36e26f), C(79 ad798fb4c5835c), C(359 aa2faec050131), C(2 a55645640911e27), C(4f ac2eefbd36e26f), C(79 ad798fb4c5835c), C(359 aa2faec050131), C(5 b802dcec21a7157), C(6ec de915b75ede0a), C(f2e653587e89058b), C(a661be80528d3385) }, { C(488 d6b45d927161b), C(f5cac66d869a8aaf), C(c326d56c643a214e), C(10 a7228693eb083e), C(1054f b19cbacf01c), C(a8f389d24587ebd8), C(afcb783a39926dba), C(10 a7228693eb083e), C(1054f b19cbacf01c), C(a8f389d24587ebd8), C(afcb783a39926dba), C(fe83e658532edf8f), C(6f dcf97f147dc4db), C(dc5e487845abef4b), C(137693f 4ea b77e27) }, { C(3 d6aaa43af5d4f86), C(44 c7d370910418d8), C(d099515f7c5c4eca), C(39756960441f be2f), C(fb68e5fedbe3d874), C(3f f380fbdd27b8e), C(f48832fdda648998), C(39756960441f be2f), C(fb68e5fedbe3d874), C(3f f380fbdd27b8e), C(f48832fdda648998), C(270dd bf2327058c9), C(9ee ad83a8319d0c4), C(b4c3356e162b086d), C(88f 013588f 411 b7) }, { C(e5c40a6381e43845), C(312 a18e66bbceaa3), C(31365186 c2059563), C(cba4c10e65410ba0), C(3 c250c8b2d72c1b6), C(177e82f 415595117), C(8 c8dcfb9e73d3f6), C(cba4c10e65410ba0), C(3 c250c8b2d72c1b6), C(177e82f 415595117), C(8 c8dcfb9e73d3f6), C(c017a797e49c0f7), C(ea2b233b2e7d5aea), C(878 d204c55a56cb1), C(7 b1b62cc0dfdc523) }, { C(86f b323e5a4b710b), C(710 c1092c23a79e0), C(bd2c6d3fc949402e), C(951f 2078 aa4b8099), C(e68b7fefa1cfd190), C(41525 a4990ba6d4a), C(c373552ef4b51712), C(951f 2078 aa4b8099), C(e68b7fefa1cfd190), C(41525 a4990ba6d4a), C(c373552ef4b51712), C(73eb44 c6122bdf5a), C(58047289 a314b013), C(e31d30432521705b), C(6 cf856774873faa4) }, { C(7930 c09adaf6e62e), C(f230d3311593662c), C(a795b9bf6c37d211), C(b57ec44bc7101b96), C(6 cb710e77767a25a), C(2f 446152 d5e3a6d0), C(cd69172f94543ce3), C(b57ec44bc7101b96), C(6 cb710e77767a25a), C(2f 446152 d5e3a6d0), C(cd69172f94543ce3), C(e6c2483cf425f072), C(2060 d5d4379d6d5a), C(86 a3c04c2110d893), C(561 d3b8a509313c6) }, { C(e505e86f0eff4ecd), C(cf31e1ccb273b9e6), C(d8efb8e9d0fe575), C(ed094f47671e359d), C(d9ebdb047d57611a), C(1 c620e4d301037a3), C(df6f401c172f68e8), C(ed094f47671e359d), C(d9ebdb047d57611a), C(1 c620e4d301037a3), C(df6f401c172f68e8), C(af0a2c7f72388ec7), C(6 d4c4a087fa4564a), C(411 b30def69700a), C(67e5 c84557a47e01) }, { C(dedccb12011e857), C(d831f899174feda8), C(ee4bcdb5804c582a), C(5 d765af4e88f3277), C(d2abe1c63ad4d103), C(342 a8ce0bc7af6e4), C(31 bfda956f3e5058), C(5 d765af4e88f3277), C(d2abe1c63ad4d103), C(342 a8ce0bc7af6e4), C(31 bfda956f3e5058), C(4 c7a1fec9af54bbb), C(84 a88f0655899bf4), C(66f b60d0582ac601), C(be0dd1ffe967bd4a) }, { C(4 d679bda26f5555f), C(7 deb387eb7823c1c), C(a65ef3b4fecd6888), C(a6814d3dc578b9df), C(3372111 a3292b691), C(e97589c81d92b513), C(74ed d943d1b9b5bf), C(a6814d3dc578b9df), C(3372111 a3292b691), C(e97589c81d92b513), C(74ed d943d1b9b5bf), C(889e38 b0af80bb7a), C(a416349af3c5818b), C(f5f5bb25576221c1), C(3 be023fa6912c32e) }, { C(e47cd22995a75a51), C(3686350 c2569a162), C(861 afcb185b8efd9), C(63672 de7951e1853), C(3 ca0c763273b99db), C(29e04f a994cccb98), C(b02587d792be5ee8), C(63672 de7951e1853), C(3 ca0c763273b99db), C(29e04f a994cccb98), C(b02587d792be5ee8), C(c85ada4858f7e4fc), C(3f 280 ab7d5864460), C(4109822f 92f 68326), C(2 d73f61314a2f630) }, { C(92 ba8e12e0204f05), C(4e29321580273802), C(aa83b675ed74a851), C(a16cd2e8b445a3fd), C(f0d4f9fb613c38ef), C(eee7755d444d8f2f), C(b530591eb67ae30d), C(a16cd2e8b445a3fd), C(f0d4f9fb613c38ef), C(eee7755d444d8f2f), C(b530591eb67ae30d), C(6f b3031a6edf8fec), C(65118 d08aecf56d8), C(9 a2117bbef1faa8), C(97055 c5fd310aa93) }, { C(bb3a8427c64f8939), C(b5902af2ec095a04), C(89f 1 b440667b2a28), C(5386ef0 b438d0330), C(d39e03c686f8a2da), C(9555249 bb9073d78), C(8 c0b3623fdf0b156), C(5386ef0 b438d0330), C(d39e03c686f8a2da), C(9555249 bb9073d78), C(8 c0b3623fdf0b156), C(354f c5d3a5504e5e), C(b2fd7391719aa614), C(13 cd4ce3dfe27b3d), C(a2d63a85dc3cae4b) }, { C(998988f 7 d6dacc43), C(5f 2 b853d841152db), C(d76321badc5cb978), C(e381f24ee1d9a97d), C(7 c5d95b2a3af2e08), C(ca714acc461cdc93), C(1 a8ee94bc847aa3e), C(e381f24ee1d9a97d), C(7 c5d95b2a3af2e08), C(ca714acc461cdc93), C(1 a8ee94bc847aa3e), C(ee59ee4c21a36f47), C(d476e8bba5bf5143), C(22 a03cb5900f6ec8), C(19 d954e14f35d7a8) }, { C(3f 1049221dd 72 b98), C(8 d9200d7a0664c37), C(3925704 c83a5f406), C(4 cbef49086e62678), C(d77dfecc2819ef19), C(c327e4deaf4c7e72), C(b4d58c73a262a32d), C(4 cbef49086e62678), C(d77dfecc2819ef19), C(c327e4deaf4c7e72), C(b4d58c73a262a32d), C(78 cd002324861653), C(7 c3f3977576efb88), C(d1c9975fd4a4cc26), C(3e3 cbc90a9baa442) }, { C(419e4f f78c3e06f3), C(aa8ff514c8a141d7), C(5 bb176e21f89f10d), C(becb065dc12d8b4e), C(ebee135492a2018), C(d3f07e65bcd9e13a), C(85 c933e85382e9f9), C(becb065dc12d8b4e), C(ebee135492a2018), C(d3f07e65bcd9e13a), C(85 c933e85382e9f9), C(2 c19ab7c419ebaca), C(982375 b2999bdb46), C(652 ca1c6325d9296), C(e9c790fa8561940a) }, { C(9 ba090af14171317), C(b0445c5232d7be53), C(72 cc929d1577ddb8), C(bc944c1b5ba2184d), C(ab3d57e5e60e9714), C(5 d8d27e7dd0a365a), C(4dd 809e11740 af1a), C(bc944c1b5ba2184d), C(ab3d57e5e60e9714), C(5 d8d27e7dd0a365a), C(4dd 809e11740 af1a), C(6f 42 d856faad44df), C(5118 dc58d7eaf56e), C(829 bbc076a43004), C(1747f bbfaca6da98) }, { C(6 ad739e4ada9a340), C(2 c6c4fb3a2e9b614), C(ab58620e94ca8a77), C(aaa144fbe3e6fda2), C(52 a9291d1e212bc5), C(2 b4c68291f26b570), C(45351 ab332855267), C(aaa144fbe3e6fda2), C(52 a9291d1e212bc5), C(2 b4c68291f26b570), C(45351 ab332855267), C(1149f 55400 bc9799), C(8 c6ec1a0c617771f), C(e9966cc03f3bec05), C(3e6889140 ccd2646) }, { C(8ecf f07fd67e4abd), C(f1b8029b17006ece), C(21 d96d5859229a61), C(b8c18d66154ac51), C(5807350371 ad7388), C(81f 783f 4f 5 ab2b8), C(fa4e659f90744de7), C(b8c18d66154ac51), C(5807350371 ad7388), C(81f 783f 4f 5 ab2b8), C(fa4e659f90744de7), C(809 da4baa51cad2c), C(88 d5c11ff5598342), C(7 c7125b0681d67d0), C(1 b5ba6124bfed8e8) }, { C(497 ca8dbfee8b3a7), C(58 c708155d70e20e), C(90428 a7e349d6949), C(b744f5056e74ca86), C(88 aa27b96f3d84a5), C(b4b1ee0470ac3826), C(aeb46264f4e15d4f), C(b744f5056e74ca86), C(88 aa27b96f3d84a5), C(b4b1ee0470ac3826), C(aeb46264f4e15d4f), C(14921 b1ee856bc55), C(a341d74aaba00a02), C(4f 50 aa8e3d08a919), C(75 a148668ff3869e) }, { C(a929cd66daa65b0a), C(7 c0150a2d9ca564d), C(46dd ec37e2ec0a6d), C(4323852 cc57e4af3), C(1f 5f 638 bbf9d2e5b), C(578f b6ac89a31d9), C(7792536 d9ac4bf12), C(4323852 cc57e4af3), C(1f 5f 638 bbf9d2e5b), C(578f b6ac89a31d9), C(7792536 d9ac4bf12), C(60 be62e795ef5798), C(c276cc5b44febefe), C(519 ba0b9f6d1be95), C(1f dce3561ed35bb8) }, { C(4107 c4156bc8d4bc), C(1 cda0c6f3f0f48af), C(cf11a23299cf7181), C(766 b71bff7d6f461), C(b004f2c910a6659e), C(4 c0eb3848e1a7c8), C(3f 90439 d05c3563b), C(766 b71bff7d6f461), C(b004f2c910a6659e), C(4 c0eb3848e1a7c8), C(3f 90439 d05c3563b), C(4 a2a013f4bc2c1d7), C(888779 ab0c272548), C(ae0f8462d89a4241), C(c5c85b7c44679abd) }, { C(15 b38dc0e40459d1), C(344f edcfc00fff43), C(b9215c5a0fcf17df), C(d178444a236c1f2d), C(5576 deee27f3f103), C(943611 bb5b1b0736), C(a0fde17cb5c2316d), C(d178444a236c1f2d), C(5576 deee27f3f103), C(943611 bb5b1b0736), C(a0fde17cb5c2316d), C(feaa1a047f4375f3), C(5435f 313e84767 e), C(522e4333 cd0330c1), C(7e6 b609b0ea9e91f) }, { C(e5e5370ed3186f6c), C(4592e75 db47ea35d), C(355 d452b82250e83), C(7 a265e37da616168), C(6 a1f06c34bafa27), C(fbae175e7ed22a9c), C(b144e84f6f33c098), C(7 a265e37da616168), C(6 a1f06c34bafa27), C(fbae175e7ed22a9c), C(b144e84f6f33c098), C(bd444561b0db41fc), C(2072 c85731e7b0b0), C(ce1b1fac436b51f3), C(4f 5 d44f31a3dcdb9) }, { C(ea2785c8f873e28f), C(3e257272f 4464f 5f), C(9267e7 e0cc9c7fb5), C(9f d4d9362494cbbc), C(e562bc615befb1b9), C(8096808 d8646cfde), C(c4084a587b9776ec), C(9f d4d9362494cbbc), C(e562bc615befb1b9), C(8096808 d8646cfde), C(c4084a587b9776ec), C(a9135db8a850d8e4), C(fffc4f8b1a11f5af), C(c50e9173c2c6fe64), C(a32630581df4ceda) }, { C(e7bf98235fc8a4a8), C(4042ef2 aae400e64), C(6538 ba9ffe72dd70), C(c84bb7b3881ab070), C(36f e6c51023fbda0), C(d62838514bb87ea4), C(9ee b5e7934373d86), C(c84bb7b3881ab070), C(36f e6c51023fbda0), C(d62838514bb87ea4), C(9ee b5e7934373d86), C(5f 8480 d0a2750a96), C(40 afa38506456ad9), C(e4012b7ef2e0ddea), C(659 da200a011836b) }, { C(b94e261a90888396), C(1f 468 d07e853294c), C(cb2c9b863a5317b9), C(4473 c8e2a3458ee0), C(258053945 ab4a39a), C(f8d745ca41962817), C(7 afb6d40df9b8f71), C(4473 c8e2a3458ee0), C(258053945 ab4a39a), C(f8d745ca41962817), C(7 afb6d40df9b8f71), C(9030 c2349604f677), C(f544dcd593087faf), C(77 a3b0efe6345d12), C(fff4e398c05817cc) }, { C(4 b0226e5f5cdc9c), C(a836ae7303dc4301), C(8505e1 b628bac101), C(b5f52041a698da7), C(29864874 b5f1936d), C(49 b3a0c6d78f98da), C(93 a1a8c7d90de296), C(b5f52041a698da7), C(29864874 b5f1936d), C(49 b3a0c6d78f98da), C(93 a1a8c7d90de296), C(ed62288423c17b7f), C(685 afa2cfba09660), C(6 d9b6f59585452c6), C(e505535c4010efb9) }, { C(e07edbe7325c718c), C(9 db1eda964f06827), C(2f 245 ad774e4cb1b), C(664ec3f ad8521859), C(406f 082 beb9ca29a), C(b6b0fb3a7981c7c8), C(3eb d280b598a9721), C(664ec3f ad8521859), C(406f 082 beb9ca29a), C(b6b0fb3a7981c7c8), C(3eb d280b598a9721), C(d9a6ceb072eab22a), C(d5bc5df5eb2ff6f1), C(488 db3cab48daa0b), C(9916f 14f a5672f37) }, { C(f4b56421eae4c4e7), C(5 da0070cf40937a0), C(aca4a5e01295984a), C(5414e385f 5677 a6d), C(41ef105f 8 a682a28), C(4 cd2e95ea7f5e7b0), C(775 bb1e0d57053b2), C(5414e385f 5677 a6d), C(41ef105f 8 a682a28), C(4 cd2e95ea7f5e7b0), C(775 bb1e0d57053b2), C(8919017805e84 b3f), C(15402f 44e0 e2b259), C(483 b1309e1403c87), C(85 c7b4232d45b0d9) }, { C(c07fcb8ae7b4e480), C(4eb cad82e0b53976), C(8643 c63d6c78a6ce), C(d4bd358fed3e6aa5), C(8 a1ba396356197d9), C(7 afc2a54733922cc), C(b813bdac4c7c02ef), C(d4bd358fed3e6aa5), C(8 a1ba396356197d9), C(7 afc2a54733922cc), C(b813bdac4c7c02ef), C(f6c610cf7e7c955), C(dab6a53e1c0780f8), C(837 c9ffec33e5d48), C(8 cb8c20032af152d) }, { C(3ed ad9568a9aaab), C(23891 bbaeb3a17bc), C(4eb7238738 b0c51a), C(db0c32f76f5b7fc1), C(5e41 b711f0abd1a0), C(bcb758f01ded0a11), C(7 d15f7d87955e28b), C(db0c32f76f5b7fc1), C(5e41 b711f0abd1a0), C(bcb758f01ded0a11), C(7 d15f7d87955e28b), C(cd2dc1f0b05939b), C(9f d6d680462e4c47), C(95 d5846e993bc8ff), C(f0b3cafc2697b8a8) }, { C(fcabde8700de91e8), C(63784 d19c60bf366), C(8f 3 af9a056b1a1c8), C(32 d3a29cf49e2dc9), C(3079 c0b0c2269bd0), C(ed76ba44f04e7b82), C(6ee e76a90b83035f), C(32 d3a29cf49e2dc9), C(3079 c0b0c2269bd0), C(ed76ba44f04e7b82), C(6ee e76a90b83035f), C(4 a9286f545bbc09), C(bd36525be4dd1b51), C(5f 7 a9117228fdee5), C(543 c96a08f03151c) }, { C(362f c5ba93e8eb31), C(7549 ae99fa609d61), C(47e4 cf524e37178f), C(a54eaa5d7f3a7227), C(9 d26922965d54727), C(27 d22acb31a194d4), C(e9b8e68771db0da6), C(a54eaa5d7f3a7227), C(9 d26922965d54727), C(27 d22acb31a194d4), C(e9b8e68771db0da6), C(16f d0e006209abe8), C(81 d3f72987a6a81a), C(74e96 e4044817bc7), C(924 ca5f08572fef9) }, { C(e323b1c5b55a4dfb), C(719993 d7d1ad77fb), C(555 ca6c6166e989c), C(ea37f61c0c2f6d53), C(9 b0c2174f14a01f5), C(7 bbe6921e26293f3), C(2 ab6c72235b6c98a), C(ea37f61c0c2f6d53), C(9 b0c2174f14a01f5), C(7 bbe6921e26293f3), C(2 ab6c72235b6c98a), C(2 c6e7668f37f6d23), C(3e8 edb057a57c2dd), C(2595f c79768c8b34), C(ffc541f5efed9c43) }, { C(9461913 a153530ef), C(83f c6d9ed7d1285a), C(73df 90 bdc50807cf), C(a32c192f6e3c3f66), C(8f 10077 b8a902d00), C(61 a227f2faac29b4), C(1 a71466fc005a61d), C(a32c192f6e3c3f66), C(8f 10077 b8a902d00), C(61 a227f2faac29b4), C(1 a71466fc005a61d), C(12545812f 3 d01a92), C(aece72f823ade07d), C(52634 cdd5f9e5260), C(cb48f56805c08e98) }, { C(ec2332acc6df0c41), C(59f 5ee17 e20a8263), C(1087 d756afcd8e7b), C(a82a7bb790678fc9), C(d197682c421e4373), C(dd78d25c7f0f935a), C(9850 cb6fbfee520f), C(a82a7bb790678fc9), C(d197682c421e4373), C(dd78d25c7f0f935a), C(9850 cb6fbfee520f), C(2590847398688 a46), C(ad266f08713ca5fe), C(25 b978be91e830b5), C(2996 c8f2b4c8f231) }, { C(aae00b3a289bc82), C(4f 6 d69f5a5a5b659), C(3f f5abc145614e3), C(33322363 b5f45216), C(7e83f 1f e4189e843), C(df384b2adfc35b03), C(396 ce7790a5ada53), C(33322363 b5f45216), C(7e83f 1f e4189e843), C(df384b2adfc35b03), C(396 ce7790a5ada53), C(c3286e44108b8d36), C(6 db8716c498d703f), C(d1db09466f37f4e7), C(56 c98e7f68a41388) }, { C(4 c842e732fcd25f), C(e7dd7b953cf9c2b2), C(911ee248 a76ae3), C(33 c6690937582317), C(fe6d61a77985d7bb), C(97 b153d04a115535), C(d3fde02e42cfe6df), C(33 c6690937582317), C(fe6d61a77985d7bb), C(97 b153d04a115535), C(d3fde02e42cfe6df), C(d1c7d1efa52a016), C(1 d6ed137f4634c), C(1 a260ec505097081), C(8 d1e70861a1c7db6) }, { C(40e23 ca5817a91f3), C(353e2935809 b7ad1), C(f7820021b86391bb), C(f3d41b3d4717eb83), C(2670 d457dde68842), C(19707 a6732c49278), C(5 d0f05a83569ba26), C(f3d41b3d4717eb83), C(2670 d457dde68842), C(19707 a6732c49278), C(5 d0f05a83569ba26), C(6f e5bc84e528816a), C(94df 3 dca91a29ace), C(420196ed097 e8b6f), C(7 c52da0e1f043ad6) }, { C(2564527f ad710b8d), C(2 bdcca8d57f890f), C(81f 7 bfcd9ea5a532), C(dd70e407984cfa80), C(66996 d6066db6e1a), C(36 a812bc418b97c9), C(18ea2 c63da57f36e), C(dd70e407984cfa80), C(66996 d6066db6e1a), C(36 a812bc418b97c9), C(18ea2 c63da57f36e), C(937f d7ad09be1a8f), C(163 b12cab35d5d15), C(3606 c3e441335cce), C(949f 6ea5 bb241ae8) }, { C(6 bf70df9d15a2bf6), C(81 cad17764b8e0dd), C(58 b349a9ba22a7ef), C(9432536dd 9f 65229), C(192 dc54522da3e3d), C(274 c6019e0227ca9), C(160 abc932a4e4f35), C(9432536dd 9f 65229), C(192 dc54522da3e3d), C(274 c6019e0227ca9), C(160 abc932a4e4f35), C(1204f 2f b5aa79dc6), C(2536ed af890f0760), C(6f 2 b561f44ff46b4), C(8 c7b3e95baa8d984) }, { C(45e6f 446eb6 bbcf5), C(98 ab0ef06f1a7d84), C(85 ae96bacca50de6), C(b9aa5bead3352801), C(8 a6d9e02a19a4229), C(c352f5b6d5ee1d9d), C(ce562bdb0cfa84fb), C(b9aa5bead3352801), C(8 a6d9e02a19a4229), C(c352f5b6d5ee1d9d), C(ce562bdb0cfa84fb), C(d47b768a85283981), C(1f e72557be57a11b), C(95 d8afe4af087d51), C(2f 59 c4e383f30045) }, { C(620 d3fe4b8849c9e), C(975 a15812a429ec2), C(437 c453593dcaf13), C(8 d8e7c63385df78e), C(16 d55add72a5e25e), C(aa6321421dd87eb5), C(6f 27f 62e785f 0203), C(8 d8e7c63385df78e), C(16 d55add72a5e25e), C(aa6321421dd87eb5), C(6f 27f 62e785f 0203), C(829030 a61078206e), C(ae1f30fcfa445cc8), C(f61f21c9df4ef68d), C(1e5 b1945f858dc4c) }, { C(535 aa7340b3c168f), C(bed5d3c3cd87d48a), C(266 d40ae10f0cbc1), C(ce218d5b44f7825a), C(2 ae0c64765800d3a), C(f22dc1ae0728fc01), C(48 a171bc666d227f), C(ce218d5b44f7825a), C(2 ae0c64765800d3a), C(f22dc1ae0728fc01), C(48 a171bc666d227f), C(e7367aff24203c97), C(da39d2be1db3a58d), C(85 ce86523003933a), C(dfd4ef2ae83f138a) }, { C(dd3e761d4eada300), C(893 d7e4c3bea5bb6), C(cc6d6783bf43eea), C(eb8eed7c391f0044), C(b58961c3abf80753), C(3 d75ea687191521), C(389 be7bbd8e478f3), C(eb8eed7c391f0044), C(b58961c3abf80753), C(3 d75ea687191521), C(389 be7bbd8e478f3), C(917070 a07441ee47), C(d78efa8cd65b313), C(a8a16f4c1c08c8a1), C(b69cb8ee549eb113) }, { C(4 ac1902ccde06545), C(2 c44aeb0983a7a07), C(b566035215b309f9), C(64 c136fe9404a7b3), C(99f 3 d8c98a399d5e), C(6319 c7cb14180185), C(fbacdbd277d33f4c), C(64 c136fe9404a7b3), C(99f 3 d8c98a399d5e), C(6319 c7cb14180185), C(fbacdbd277d33f4c), C(a96a5626c2adda86), C(39ea72f d2ad133ed), C(b5583f2f736df73e), C(ef2c63619782b7ba) }, { C(aee339a23bb00a5e), C(cbb402255318f919), C(9922948e99 aa0781), C(df367034233fedc4), C(dcbe14db816586e5), C(f4b1cb814adf21d3), C(f4690695102fa00a), C(df367034233fedc4), C(dcbe14db816586e5), C(f4b1cb814adf21d3), C(f4690695102fa00a), C(6 b4f01dd6b76dafc), C(b79388676b50da5d), C(cb64f8bde5ed3393), C(9 b422781f13219d3) }, { C(627599e91148df 4f), C(3e2 d01e8baab062b), C(2 daab20edb245251), C(9 a958bc3a895a223), C(331058dd 6 c5d2064), C(46 c4d962072094fa), C(e6207c19160e58eb), C(9 a958bc3a895a223), C(331058dd 6 c5d2064), C(46 c4d962072094fa), C(e6207c19160e58eb), C(5655e4 dbf7272728), C(67 b217b1f56c747d), C(3 ac0be79691b9a0d), C(9 d0954dd0b57073) }, { C(cfb04cf00cfed6b3), C(5f e75fc559af22fa), C(c440a935d72cdc40), C(3 ab0d0691b251b8b), C(47181 a443504a819), C(9 bcaf1253f99f499), C(8ee002 b89c1b6b3f), C(3 ab0d0691b251b8b), C(47181 a443504a819), C(9 bcaf1253f99f499), C(8ee002 b89c1b6b3f), C(55df e8eedcd1ec5e), C(1 bf50f0bbad796a5), C(9044369 a042d7fd6), C(d423df3e3738ba8f) }, { C(942631 c47a26889), C(427962 c82d8a6e00), C(224071 a6592537ff), C(d3e96f4fb479401), C(68 b3f2ec11de9368), C(cb51b01083acad4f), C(500 cec4564d62aeb), C(d3e96f4fb479401), C(68 b3f2ec11de9368), C(cb51b01083acad4f), C(500 cec4564d62aeb), C(4 ce547491e732887), C(9423883 a9a11df4c), C(1 a0fc7a14214360), C(9e837914505 da6ed) }, { C(4 c9eb4e09726b47e), C(fd927483a2b38cf3), C(6 d7e56407d1ba870), C(9f 5 dc7db69fa1e29), C(f42fff56934533d5), C(92 d768c230a53918), C(f3360ff11642136c), C(9f 5 dc7db69fa1e29), C(f42fff56934533d5), C(92 d768c230a53918), C(f3360ff11642136c), C(9e989932 eb86d1b5), C(449 a77f69a8a9d65), C(efabaf8a7789ed9a), C(2798eb4 c50c826fd) }, { C(cf7f208ef20e887a), C(f4ce4edeadcaf1a1), C(7ee15226 eaf4a74d), C(17 ab41ab2ae0705d), C(9dd 56694 aa2dcd4e), C(dd4fa2add9baced2), C(7 ad99099c9e199a3), C(17 ab41ab2ae0705d), C(9dd 56694 aa2dcd4e), C(dd4fa2add9baced2), C(7 ad99099c9e199a3), C(a59112144accef0e), C(5838df 47e38 d251d), C(8750f e45760331e5), C(4 b2ce14732e0312a) }, { C(a8dc4687bcf27f4), C(c4aadd7802553f15), C(5401eb9912 be5269), C(5 c2a2b5b0657a928), C(1e1968 ebb38fcb99), C(a082d0e067c4a59c), C(18 b616495ad9bf5d), C(5 c2a2b5b0657a928), C(1e1968 ebb38fcb99), C(a082d0e067c4a59c), C(18 b616495ad9bf5d), C(18 c5dc6c78a7f9ed), C(b3cc94fe34b68aa1), C(3 b77e91292be38cc), C(61 d1786ec5097971) }, { C(daed638536ed19df), C(1 a762ea5d7ac6f7e), C(48 a1cc07a798b84f), C(7f 15 bdaf50d550f9), C(4 c1d48aa621a037e), C(2 b1d7a389d497ee0), C(81 c6775d46f4b517), C(7f 15 bdaf50d550f9), C(4 c1d48aa621a037e), C(2 b1d7a389d497ee0), C(81 c6775d46f4b517), C(35296005 cbba3ebe), C(db1642f825b53532), C(3e07588 a9fd829a4), C(60f 13 b5446bc7638) }, { C(90 a04b11ee1e4af3), C(ab09a35f8f2dff95), C(d7cbe82231ae1e83), C(3262e9017 bb788c4), C(1612017731 c997bc), C(e789d66134aff5e1), C(275642f d17048af1), C(3262e9017 bb788c4), C(1612017731 c997bc), C(e789d66134aff5e1), C(275642f d17048af1), C(99255 b68d0b46b51), C(74 a6f1ad4b2bb296), C(4164222761 af840e), C(54 d59bf6211a8fe6) }, { C(511f 29e1 b732617d), C(551 cb47a9a83d769), C(df6f56fbda20e7a), C(f27583a930221d44), C(d7d2c46de69b2ed8), C(add24ddd2be4a850), C(5 cf2f688dbb93585), C(f27583a930221d44), C(d7d2c46de69b2ed8), C(add24ddd2be4a850), C(5 cf2f688dbb93585), C(a7f8e42d5dd4aa00), C(72 dc11fd76b4dea9), C(8886f 194e6f 8e3f f), C(7e8 ead04a0e0b1ef) }, { C(95567f 03939e651f), C(62 a426f09d81d884), C(15 cb96e36a8e712c), C(1 a2f43bdeaea9c28), C(bca2fd840831291f), C(83446 d4a1f7dcc1a), C(449 a211df83b6187), C(1 a2f43bdeaea9c28), C(bca2fd840831291f), C(83446 d4a1f7dcc1a), C(449 a211df83b6187), C(553 ce97832b2f695), C(3110 a2ba303db75), C(b91d6d399a02f453), C(3 cb148561e0ef2bb) }, { C(248 a32ad10e76bc3), C(dac39c8b036985e9), C(79 d38c4af2958b56), C(cc954b4e56275f54), C(700 cd864e04e8aaa), C(d6ba03cbff7cc34b), C(da297d7891c9c046), C(cc954b4e56275f54), C(700 cd864e04e8aaa), C(d6ba03cbff7cc34b), C(da297d7891c9c046), C(c05d2be8f8ee8114), C(7f 4541 cbe2ec0025), C(8f 0 a7a70af6ea926), C(3837dd ce693781b5) }, { C(f9f05a2a892242eb), C(de00b6b2e0998460), C(f1f4bd817041497a), C(3 deac49eb42a1e26), C(642f 77f 7 c57e84b7), C(2f 2 c231222651e8b), C(380202ec06 bdc29e), C(3 deac49eb42a1e26), C(642f 77f 7 c57e84b7), C(2f 2 c231222651e8b), C(380202ec06 bdc29e), C(59 abc4ff54765e66), C(8561ea1dd dd1f742), C(9 ca1f94b0d3f3875), C(b7fa93c3a9fa4ec4) }, { C(3 a015cea8c3f5bdf), C(5583521 b852fc3ac), C(53 d5cd66029a1014), C(ac2eeca7bb04412a), C(daba45cb16ccff2b), C(ddd90b51209e414), C(d90e74ee28cb6271), C(ac2eeca7bb04412a), C(daba45cb16ccff2b), C(ddd90b51209e414), C(d90e74ee28cb6271), C(117027648 ca9db68), C(29 c1dba39bbcf072), C(787f 6 bb010a34cd9), C(e099f487e09b847) }, { C(670e43506 aa1f71b), C(1 cd7929573e54c05), C(cbb00a0aaba5f20a), C(f779909e3d5688d1), C(88211 b9117678271), C(59f 44f 73759 a8bc6), C(ef14f73c405123b4), C(f779909e3d5688d1), C(88211 b9117678271), C(59f 44f 73759 a8bc6), C(ef14f73c405123b4), C(78775601f 11186f), C(fc4641d676fbeed9), C(669 ca96b5a2ae5b), C(67 b5f0d072025f8d) }, { C(977 bb79b58bbd984), C(26 d45cfcfb0e9756), C(df8885db518d5f6a), C(6 a1d2876488bed06), C(ae35d83c3afb5769), C(33667427 d99f9f4e), C(d84c31c17495e3ba), C(6 a1d2876488bed06), C(ae35d83c3afb5769), C(33667427 d99f9f4e), C(d84c31c17495e3ba), C(31357 cded7495ffc), C(295e2 eefcd383a2e), C(25063ef4 a24c29ae), C(88 c694170fcbf0b7) }, { C(e6264fbccd93a530), C(c92f420494e99a7d), C(c14001a298cf976), C(5 c8685fee2e4ce55), C(228 c49268d6a4345), C(3 b04ee2861baec6d), C(7334878 a00e96e72), C(5 c8685fee2e4ce55), C(228 c49268d6a4345), C(3 b04ee2861baec6d), C(7334878 a00e96e72), C(7317164 b2ce711bb), C(e645447e363e8ca1), C(d326d129ad7b4e7f), C(58 b9b76d5c2eb272) }, { C(54e4 d0cab7ec5c27), C(31 ca61d2262a9acc), C(30 bd3a50d8082ff6), C(46 b3b963bf7e2847), C(b319d04e16ad10b0), C(76 c8dd82e6f5a0eb), C(2070363 cefb488bc), C(46 b3b963bf7e2847), C(b319d04e16ad10b0), C(76 c8dd82e6f5a0eb), C(2070363 cefb488bc), C(6f 9 dbacb2bdc556d), C(88 a5fb0b293c1e22), C(cb131d9b9abd84b7), C(21 db6f0e147a0040) }, { C(882 a598e98cf5416), C(36 c8dca4a80d9788), C(c386480f07591cfe), C(5 b517bcf2005fd9c), C(b9b8f8e5f90e7025), C(2 a833e6199e21708), C(bcb7549de5fda812), C(5 b517bcf2005fd9c), C(b9b8f8e5f90e7025), C(2 a833e6199e21708), C(bcb7549de5fda812), C(44f c96a3cafa1c34), C(fb7724d4899ec7c7), C(4662e3 b87df93e13), C(bcf22545acbcfd4e) }, { C(7 c37a5376c056d55), C(e0cce8936a06b6f6), C(d32f933fdbec4c7d), C(7 ac50423e2be4703), C(546 d4b42340d6dc7), C(624f 56ee027f 12 bf), C(5f 7f 65 d1e90c30f9), C(7 ac50423e2be4703), C(546 d4b42340d6dc7), C(624f 56ee027f 12 bf), C(5f 7f 65 d1e90c30f9), C(d6f15c19625d2621), C(c7afd12394f24b50), C(2 c6adde5d249bcd0), C(6 c857e6aa07b9fd2) }, { C(21 c5e9616f24be97), C(ba3536c86e4b6fe9), C(6 d3a65cfe3a9ae06), C(2113903eb d760a31), C(e561f76a5eac8beb), C(86 b5b3e76392e166), C(68 c8004ccc53e049), C(2113903eb d760a31), C(e561f76a5eac8beb), C(86 b5b3e76392e166), C(68 c8004ccc53e049), C(b51a28fe4251dd79), C(fd9c2d4d2a84c3c7), C(5 bf2ec8a470d2553), C(135 a52cdc76241c9) }, { C(a6eaefe74fa7d62b), C(cb34669c751b10eb), C(80 da952ad8abd5f3), C(3368262 b0e172d82), C(1 d51f6c982476285), C(4497675 ac57228a9), C(2 a71766a71d0b83f), C(3368262 b0e172d82), C(1 d51f6c982476285), C(4497675 ac57228a9), C(2 a71766a71d0b83f), C(79 ad94d1e9c1dedd), C(cbf1a1c9f23bfa40), C(3ebf 24e068 cd638b), C(be8e63472edfb462) }, { C(764 af88ed4b0b828), C(36946775f 20457 ce), C(d4bc88ac8281c22e), C(3 b2104d68dd9ac02), C(2ec a14fcdc0892d0), C(7913 b0c09329cd47), C(9373f 458938688 c8), C(3 b2104d68dd9ac02), C(2ec a14fcdc0892d0), C(7913 b0c09329cd47), C(9373f 458938688 c8), C(b4448f52a5bf9425), C(9f 8 c8b90b61ed532), C(78f 6774f 48e72961), C(e47c00bf9c1206f4) }, { C(5f 55 a694fb173ea3), C(7 db02b80ef5a918b), C(d87ff079f476ca3a), C(1 d11117374e0da3), C(744 bfbde42106439), C(93 a99fab10bb1789), C(246 ba292a85d8d7c), C(1 d11117374e0da3), C(744 bfbde42106439), C(93 a99fab10bb1789), C(246 ba292a85d8d7c), C(e5bd7838e9edd53a), C(d9c0b104c79d9019), C(ee3dcc7a8e565de5), C(619 c9e0a9cf3596d) }, { C(86 d086738b0a7701), C(d2402313a4280dda), C(b327aa1a25278366), C(49efdd e5d1f98163), C(cbcffcee90f22824), C(951 aec1daeb79bab), C(7055e2 c70d2eeb4c), C(49efdd e5d1f98163), C(cbcffcee90f22824), C(951 aec1daeb79bab), C(7055e2 c70d2eeb4c), C(1f c0de9399bacb96), C(dab7bbe67901959e), C(375805ec cf683ef0), C(bbb6f465c4bae04e) }, { C(acfc8be97115847b), C(c8f0d887bf8d9d1), C(e698fbc6d39bf837), C(61f d1d6b13c1ea77), C(527ed97f f4ae24f0), C(af51a9ebb322c0), C(14f 7 c25058864825), C(61f d1d6b13c1ea77), C(527ed97f f4ae24f0), C(af51a9ebb322c0), C(14f 7 c25058864825), C(f40b2bbeaf9f021d), C(80 d827160dfdc2d2), C(77 baea2e3650486e), C(5 de2d256740a1a97) }, { C(dc5ad3c016024d4), C(a0235e954da1a152), C(6 daa8a4ed194cc43), C(185e650 afc8d39f8), C(adba03a4d40de998), C(9975 c776b499b26f), C(9770 c59368a43a2), C(185e650 afc8d39f8), C(adba03a4d40de998), C(9975 c776b499b26f), C(9770 c59368a43a2), C(d2776f0cf0e4f66c), C(38ea aabfb743f7f6), C(c066f03d959b3f07), C(9 d91c2d52240d546) }, { C(a0e91182f03277f7), C(15 c6ebef7376556), C(516f 887657 ab5a), C(f95050524c7f4b84), C(460 dcebbaaa09ae3), C(a9f7a9f0b1b2a961), C(5f 8 dc5e198e34539), C(f95050524c7f4b84), C(460 dcebbaaa09ae3), C(a9f7a9f0b1b2a961), C(5f 8 dc5e198e34539), C(9 c49227ffcff07cb), C(a29388e9fcb794c8), C(475867910 d110cba), C(8 c9a5cee480b5bac) }, { C(767f 1 dbd1dba673b), C(1e466 a3848a5b01e), C(483ea def1347cd6e), C(a67645c72f54fe24), C(c7a5562c69bd796b), C(e14201a35b55e4a6), C(b3a6d89f19d8f774), C(a67645c72f54fe24), C(c7a5562c69bd796b), C(e14201a35b55e4a6), C(b3a6d89f19d8f774), C(bb4d607ac22bebe5), C(792030ed eaa924e0), C(138730 dcb60f7e32), C(699 d9dcc326c72dc) }, { C(a5e30221500dcd53), C(3 a1058d71c9fad93), C(510520710 c6444e8), C(a6a5e60c2c1d0108), C(45 c8ea4e14bf8c6b), C(213 a7235416b86df), C(c186072f80d56ad3), C(a6a5e60c2c1d0108), C(45 c8ea4e14bf8c6b), C(213 a7235416b86df), C(c186072f80d56ad3), C(2e7 be098db59d832), C(d5fa382f3717a0ee), C(b168b26921d243d), C(61601 a60c2addfbb) }, { C(ebaed82e48e18ce4), C(cfe6836b65ebe7c7), C(504 d9d388684d449), C(bd9c744ee9e3308e), C(faefbb8d296b65d4), C(eba051fe2404c25f), C(250 c8510b8931f87), C(bd9c744ee9e3308e), C(faefbb8d296b65d4), C(eba051fe2404c25f), C(250 c8510b8931f87), C(3 c4a49150dc5676f), C(6 c28793c565345c4), C(9df 6dd 8829 a6d8fb), C(760 d3a023fab72e7) }, { C(ffa50913362b118d), C(626 d52251a8ec3e0), C(76 ce4b9dde2e8c5e), C(fc57418d92e52355), C(6 b46c559e67a063), C(3f 5 c269e10690c5c), C(6870 de8d49e65349), C(fc57418d92e52355), C(6 b46c559e67a063), C(3f 5 c269e10690c5c), C(6870 de8d49e65349), C(88737e5 c672de296), C(ca71fca5f4c4f1ce), C(42f ca3fa7f60e031), C(4 a70246d0d4c2bd8) }, { C(256186 bcda057f54), C(fb059b012049fd8e), C(304e07418 b5f739b), C(3e166f 9f ac2eec0b), C(82 bc11707ec4a7a4), C(e29acd3851ce36b6), C(9765 ca9323d30046), C(3e166f 9f ac2eec0b), C(82 bc11707ec4a7a4), C(e29acd3851ce36b6), C(9765 ca9323d30046), C(dab63e7790017f7c), C(b9559988bff0f170), C(48 d9ef8aea13eee8), C(e31e47857c511ec2) }, { C(382 b15315e84f28b), C(f9a2578b79590b72), C(708936 af6d4450e8), C(76 a9d4843df75c1c), C(2 c33447da3f2c70a), C(5e4 dcf2eaeace0d6), C(2 ae1727aa7220634), C(76 a9d4843df75c1c), C(2 c33447da3f2c70a), C(5e4 dcf2eaeace0d6), C(2 ae1727aa7220634), C(a122f6b52e1130ba), C(a17ae9a21f345e91), C(ff67313f1d0906a9), C(bb16dc0acd6ebecc) }, { C(9983 a9cc5576d967), C(29e37689 a173109f), C(c526073a91f2808c), C(fe9a9d4a799cf817), C(7 ca841999012c0d1), C(8 b3abfa4bd2aa28e), C(4ed49274526602 eb), C(fe9a9d4a799cf817), C(7 ca841999012c0d1), C(8 b3abfa4bd2aa28e), C(4ed49274526602 eb), C(40995df 99063f e23), C(7f 51 b7ceded05144), C(743 c89732b265bf2), C(10 c8e1fd835713fd) }, { C(c2c58a843f733bdb), C(516 c47c97b4ba886), C(abc3cae0339517db), C(be29af0dad5c9d27), C(70f 802599 d97fe08), C(23 af3f67d941e52b), C(a031edd8b3a008fb), C(be29af0dad5c9d27), C(70f 802599 d97fe08), C(23 af3f67d941e52b), C(a031edd8b3a008fb), C(43431336 b198f8fd), C(7 c4b60284e1c2245), C(51ee580dd abae1b3), C(ca99bd13845d8f7f) }, { C(648f f27fabf93521), C(d7fba33cbc153035), C(3 dbcdcf87ad06c9e), C(52dd bdc9dfd26990), C(d46784cd2aeabb28), C(bd3a15e5e4eb7177), C(b5d7632e19a2cd), C(52dd bdc9dfd26990), C(d46784cd2aeabb28), C(bd3a15e5e4eb7177), C(b5d7632e19a2cd), C(8007450f a355dc04), C(41 ca59f64588bb5c), C(66f 2 ca6b7487499d), C(8098716530 db9bea) }, { C(99 be55475dcb3461), C(d94ffa462f6ba8dc), C(dbab2b456bdf13bb), C(f28f496e15914b2d), C(1171 ce20f49cc87d), C(1 b5f514bc1b377a9), C(8 a02cb12ec4d6397), C(f28f496e15914b2d), C(1171 ce20f49cc87d), C(1 b5f514bc1b377a9), C(8 a02cb12ec4d6397), C(1 c6540740c128d79), C(d085b67114969f41), C(af8c1988085306f3), C(4681f 415 d9ce8038) }, { C(e16fbb9303dd6d92), C(4 d92b99dd164db74), C(3f 98f 2 c9da4f5ce3), C(c65b38c5a47eeed0), C(5 c5301c8ee3923a6), C(51 bf9f9eddec630e), C(b1cbf1a68be455c2), C(c65b38c5a47eeed0), C(5 c5301c8ee3923a6), C(51 bf9f9eddec630e), C(b1cbf1a68be455c2), C(c356f5f98499bdb8), C(d897df1ad63fc1d4), C(9 bf2a3a69982e93a), C(a2380d43e271bcc8) }, { C(4 a57a4899834e4c0), C(836 c4df2aac32257), C(cdb66b29e3e12147), C(c734232cbda1eb4c), C(30 a3cffff6b9dda0), C(d199313e17cca1ed), C(594 d99e4c1360d82), C(c734232cbda1eb4c), C(30 a3cffff6b9dda0), C(d199313e17cca1ed), C(594 d99e4c1360d82), C(ccc37662829a65b7), C(cae30ff4d2343ce9), C(54 da907f7aade4fa), C(5 d6e4a0272958922) }, { C(f658958cdf49f149), C(de8e4a622b7a16b), C(a227ebf448c80415), C(3 de9e38b3a369785), C(84 d160d688c573a9), C(8f 562593 add0ad54), C(4446 b762cc34e6bf), C(3 de9e38b3a369785), C(84 d160d688c573a9), C(8f 562593 add0ad54), C(4446 b762cc34e6bf), C(2f 795f 1594 c7d598), C(29e05 bd1e0dceaff), C(a9a88f2962b49589), C(4 b9c86c141ac120b) }, { C(ae1befc65d3ea04d), C(cfd9bc0388c8fd00), C(522f 2e1f 6 cdb31af), C(585447eb e078801a), C(14 a31676ec4a2cbd), C(b274e7e6af86a5e1), C(2 d487019570bedce), C(585447eb e078801a), C(14 a31676ec4a2cbd), C(b274e7e6af86a5e1), C(2 d487019570bedce), C(ea1dc9ef3c7b2fcc), C(bde99d4af2f4ee8c), C(64e4 c43cd7c43442), C(9 b5262ee2eed2f99) }, { C(2f c8f9fc5946296d), C(6 a2b94c6765ebfa2), C(f4108b8c79662fd8), C(3 a48de4a1e994623), C(6318e6 e1ff7bc092), C(84 aee2ea26a048fb), C(cf3c393fdad7b184), C(3 a48de4a1e994623), C(6318e6 e1ff7bc092), C(84 aee2ea26a048fb), C(cf3c393fdad7b184), C(28 b265bd8985a71e), C(bd3d97dbd76d89a5), C(b04ba1623c0937d), C(b6de821229693515) }, { C(efdb4dc26e84dce4), C(9 ce45b6172dffee8), C(c15ad8c8bcaced19), C(f10cc2bcf0475411), C(1126f 457 c160d8f5), C(34 c67f6ea249d5cc), C(3 ab7633f4557083), C(f10cc2bcf0475411), C(1126f 457 c160d8f5), C(34 c67f6ea249d5cc), C(3 ab7633f4557083), C(3 b2e4d8611a03bd7), C(3103 d6e63d71c3c9), C(43 a56a0b93bb9d53), C(50 aa3ae25803c403) }, { C(e84a123b3e1b0c91), C(735 cc1d493c5e524), C(287030 af8f4ac951), C(fb46abaf4713dda0), C(e8835b9a08cf8cb2), C(3 b85a40e6bee4cce), C(eea02a3930757200), C(fb46abaf4713dda0), C(e8835b9a08cf8cb2), C(3 b85a40e6bee4cce), C(eea02a3930757200), C(fe7057d5fb18ee87), C(723 d258b36eada2a), C(67641393692 a716c), C(c8539a48dae2e539) }, { C(686 c22d2863c48a6), C(1ee6804 e3ddde627), C(8 d66184dd34ddac8), C(35 ac1bc76c11976), C(fed58f898503280d), C(ab6fcb01c630071e), C(edabf3ec7663c3c9), C(35 ac1bc76c11976), C(fed58f898503280d), C(ab6fcb01c630071e), C(edabf3ec7663c3c9), C(591ec5025592 b76e), C(918 a77179b072163), C(25421 d9db4c81e1a), C(96f 1 b3be51f0b548) }, { C(2 c5c1c9fa0ecfde0), C(266 a71b430afaec3), C(53 ab2d731bd8184a), C(5722f 16 b15e7f206), C(35 bb5922c0946610), C(b8d72c08f927f2aa), C(65f 2 c378cb9e8c51), C(5722f 16 b15e7f206), C(35 bb5922c0946610), C(b8d72c08f927f2aa), C(65f 2 c378cb9e8c51), C(cd42fec772c2d221), C(10 ccd5d7bacffdd9), C(a75ecb52192f60e2), C(a648f5fe45e5c164) }, { C(7 a0ac8dd441c9a9d), C(4 a4315964b7377f0), C(24092991 c8f27459), C(9 c6868d561691eb6), C(78 b7016996f98828), C(651e072f 06 c9e7b7), C(fed953d1251ae90), C(9 c6868d561691eb6), C(78 b7016996f98828), C(651e072f 06 c9e7b7), C(fed953d1251ae90), C(7 a4d19fdd89e368c), C(d8224d83b6b9a753), C(3 a93520a455ee9c9), C(159942 bea42b999c) }, { C(c6f9a31dfc91537c), C(b3a250ae029272f8), C(d065fc76d79ec222), C(d2baa99749c71d52), C(5f 90 a2cfc2a3f637), C(79e4 aca7c8bb0998), C(981633149 c85c0ba), C(d2baa99749c71d52), C(5f 90 a2cfc2a3f637), C(79e4 aca7c8bb0998), C(981633149 c85c0ba), C(5 ded415df904b2ee), C(d37d1fc032ebca94), C(ed5b024594967bf7), C(ed7ae636d467e961) }, { C(2 d12010eaf7d8d3d), C(eaec74ccd9b76590), C(541338571 d45608b), C(e97454e4191065f3), C(afb357655f2a5d1c), C(521 ac1614653c130), C(c8a8cac96aa7f32c), C(e97454e4191065f3), C(afb357655f2a5d1c), C(521 ac1614653c130), C(c8a8cac96aa7f32c), C(196 d7f3f386dfd29), C(1 dcd2da5227325cc), C(10e3 b9fa712d3405), C(fdf7864ede0856c0) }, { C(f46de22b2d79a5bd), C(e3e198ba766c0a29), C(828 d8c137216b797), C(bafdb732c8a29420), C(2ed0 b9f4548a9ac3), C(f1ed2d5417d8d1f7), C(451462f 90354 d097), C(bafdb732c8a29420), C(2ed0 b9f4548a9ac3), C(f1ed2d5417d8d1f7), C(451462f 90354 d097), C(bdd091094408851a), C(c4c1731c1ea46c2c), C(615 a2348d60409a8), C(fbc2f058d5539bcc) }, { C(2 ce2f3e89fa141fe), C(ac588fe6ab2b719), C(59 b848c80739487d), C(423722957 b566d10), C(ae4be02664998dc6), C(64017 aacfa69ef80), C(28076dd dbf65a40a), C(423722957 b566d10), C(ae4be02664998dc6), C(64017 aacfa69ef80), C(28076dd dbf65a40a), C(873 bc41acb810f94), C(ac0edafb574b7c0d), C(937 d5d5fd95330bf), C(4ea91171 e208bd7e) }, { C(8 aa75419d95555dd), C(bdb046419d0bf1b0), C(aadf49f217b153da), C(c3cbbe7eb0f5e126), C(fd1809c329311bf6), C(9 c26cc255714d79d), C(67093 aeb89f5d8c8), C(c3cbbe7eb0f5e126), C(fd1809c329311bf6), C(9 c26cc255714d79d), C(67093 aeb89f5d8c8), C(265954 c61009eaf7), C(a5703e8073eaf83f), C(855382 b1aed9c128), C(a6652d5a53d4a008) }, { C(1f bf19dd9207e6aa), C(722834f 3 c5e43cb7), C(e3c13578c5a69744), C(db9120bc83472135), C(f3d9f715e669cfd5), C(63f acc852f487dda), C(9f 08f d85a3a78111), C(db9120bc83472135), C(f3d9f715e669cfd5), C(63f acc852f487dda), C(9f 08f d85a3a78111), C(6 c1e5c694b51b7ca), C(bbceb2e47d44f6a1), C(2eb472 efe06f8330), C(1844408e2 bb87ee) }, { C(6f 11f 9 c1131f1182), C(6f 90740 debc7bad2), C(8 d6e4e2d46ee614b), C(403e3793f 0805 ac3), C(6278 da3d8667a055), C(98ec eadb4f237978), C(4 daa96284c847b0), C(403e3793f 0805 ac3), C(6278 da3d8667a055), C(98ec eadb4f237978), C(4 daa96284c847b0), C(ab119ac9f803d770), C(ab893fe847208376), C(f9d9968ae4472ac3), C(b149ff3b35874201) }, { C(92e896 d8bfdebdb5), C(2 d5c691a0acaeba7), C(377 d7f86b7cb2f8b), C(b8a0738135dde772), C(57f b6c9033fc5f35), C(20e628f 266e63 e1), C(1 ad6647eaaa153a3), C(b8a0738135dde772), C(57f b6c9033fc5f35), C(20e628f 266e63 e1), C(1 ad6647eaaa153a3), C(10005 c85a89e601a), C(cc9088ed03a78e4a), C(c8d3049b8c0d26a1), C(26e8 c0e936cf8cce) }, { C(369 ba54df3c534d1), C(972 c7d2be5f62834), C(112 c8d0cfcc8b1e), C(bcddd22a14192678), C(446 cf170a4f05e72), C(c9e992c7a79ce219), C(fa4762e60a93cf84), C(bcddd22a14192678), C(446 cf170a4f05e72), C(c9e992c7a79ce219), C(fa4762e60a93cf84), C(b2e11a375a352f), C(a70467d0fd624cf1), C(776 b638246febf88), C(e7d1033f7faa39b5) }, { C(bcc4229e083e940e), C(7 a42ebe9e8f526b5), C(bb8d1f389b0769ee), C(ae6790e9fe24c57a), C(659 a16feab53eb5), C(6f d4cfade750bf16), C(31 b1acd328815c81), C(ae6790e9fe24c57a), C(659 a16feab53eb5), C(6f d4cfade750bf16), C(31 b1acd328815c81), C(8 a711090a6ccfd44), C(363240 c31681b80e), C(ad791f19de0b07e9), C(d512217d21c7c370) }, { C(17 c648f416fb15ca), C(fe4d070d14d71a1d), C(ff22eac66f7eb0d3), C(fa4c10f92facc6c7), C(94 cad9e4daecfd58), C(6f fcf829a275d7ef), C(2 a35d2436894d549), C(fa4c10f92facc6c7), C(94 cad9e4daecfd58), C(6f fcf829a275d7ef), C(2 a35d2436894d549), C(c9ea25549513f5a), C(93f 7 cf06df2d0206), C(ef0da319d38fe57c), C(f715dc84df4f4a75) }, { C(8 b752dfa2f9fa592), C(ca95e87b662fe94d), C(34 da3aadfa49936d), C(bf1696df6e61f235), C(9724f ac2c03e3859), C(d9fd1463b07a8b61), C(f8e397251053d8ca), C(bf1696df6e61f235), C(9724f ac2c03e3859), C(d9fd1463b07a8b61), C(f8e397251053d8ca), C(c6d26d868c9e858e), C(2f 4 a1cb842ed6105), C(6 cc48927bd59d1c9), C(469e836 d0b7901e1) }, { C(3ed da5262a7869bf), C(a15eab8c522050c9), C(ba0853c48707207b), C(4 d751c1a836dcda3), C(9747 a6e96f1dd82c), C(3 c986fc5c9dc9755), C(a9d04f3a92844ecd), C(4 d751c1a836dcda3), C(9747 a6e96f1dd82c), C(3 c986fc5c9dc9755), C(a9d04f3a92844ecd), C(2 da9c6cede185e36), C(fae575ef03f987d6), C(b4a6a620b2bee11a), C(8 acba91c5813c424) }, { C(b5776f9ceaf0dba2), C(546ee e4cee927b0a), C(ce70d774c7b1cf77), C(7f 707785 c2d807d7), C(1ea8247 d40cdfae9), C(4945806ea c060028), C(1 a14948790321c37), C(7f 707785 c2d807d7), C(1ea8247 d40cdfae9), C(4945806ea c060028), C(1 a14948790321c37), C(ba3327bf0a6ab79e), C(54e2939592862 de8), C(b7d4651234fa11c7), C(d122970552454def) }, { C(313161f 3 ce61ec83), C(c6c5acb78303987d), C(f00761c6c6e44cee), C(ea660b39d2528951), C(e84537f81a44826a), C(b850bbb69593c26d), C(22499793145e1209), C(ea660b39d2528951), C(e84537f81a44826a), C(b850bbb69593c26d), C(22499793145e1209), C(4 c61b993560bbd58), C(636 d296abe771743), C(f1861b17b8bc3146), C(cd5fca4649d30f8a) }, { C(6e23080 c57f4bcb), C(5f 4 dad6078644535), C(f1591bc445804407), C(46 ca76959d0d4824), C(200 b16bb4031e6a5), C(3 d0e4718ed5363d2), C(4 c8cfcc96382106f), C(46 ca76959d0d4824), C(200 b16bb4031e6a5), C(3 d0e4718ed5363d2), C(4 c8cfcc96382106f), C(8 d6258d795b8097b), C(23 ae7cd1cab4b141), C(cbe74e8fd420afa), C(d553da4575629c63) }, { C(a194c120f440fd48), C(ac0d985eef446947), C(5df 9f a7d97244438), C(fce2269035535eba), C(2 d9b4b2010a90960), C(2 b0952b893dd72f0), C(9 a51e8462c1111de), C(fce2269035535eba), C(2 d9b4b2010a90960), C(2 b0952b893dd72f0), C(9 a51e8462c1111de), C(8682 b5e0624432a4), C(de8500edda7c67a9), C(4821 b171f562c5a2), C(ecb17dea1002e2df) }, { C(3 c78f67ee87b62fe), C(274 c83c73f20f662), C(25 a94c36d3763332), C(7e053f 1 b873bed61), C(d1c343547cd9c816), C(4 deee69b90a52394), C(14038f 0f 3128 ca46), C(7e053f 1 b873bed61), C(d1c343547cd9c816), C(4 deee69b90a52394), C(14038f 0f 3128 ca46), C(ebbf836e38c70747), C(c3c1077b9a7598d0), C(e73c720a27b07ba7), C(ec57f8a9a75af4d9) }, { C(b7d2aee81871e3ac), C(872 ac6546cc94ff2), C(a1b0d2f507ad2d8f), C(bdd983653b339252), C(c02783d47ab815f8), C(36 c5dc27d64d776c), C(5193988ee a7df808), C(bdd983653b339252), C(c02783d47ab815f8), C(36 c5dc27d64d776c), C(5193988ee a7df808), C(8 d8cca9c605cdb4a), C(334904f d32a1f934), C(dbfc15742057a47f), C(f3f92db42ec0cba1) }, { C(41ec0382933 e8f72), C(bd5e52d651bf3a41), C(cbf51a6873d4b29e), C(1 c8c650bfed2c546), C(9 c9085c070350c27), C(e82305be3bded854), C(cf56326bab3d685d), C(1 c8c650bfed2c546), C(9 c9085c070350c27), C(e82305be3bded854), C(cf56326bab3d685d), C(f94db129adc6cecc), C(1f 80871ec4 b35deb), C(c0dc1a4c74d63d0), C(d3cac509f998c174) }, { C(7f e4e777602797f0), C(626e62f 39f 7 c575d), C(d15d6185215fee2f), C(f82ef80641514b70), C(e2702de53389d34e), C(9950592 b7f2da8d8), C(d6b960bf3503f893), C(f82ef80641514b70), C(e2702de53389d34e), C(9950592 b7f2da8d8), C(d6b960bf3503f893), C(95 de69e4f131a9b), C(ee6f56eeff9cdefa), C(28f 4f 86 c2b856b72), C(b73d2decaac56b5b) }, { C(aa71127fd91bd68a), C(960f 6304500f 8069), C(5 cfa9758933beba8), C(dcbbdeb1f56b0ac5), C(45164 c603d084ce4), C(85693f 4ef7 e34314), C(e3a3e3a5ec1f6252), C(dcbbdeb1f56b0ac5), C(45164 c603d084ce4), C(85693f 4ef7 e34314), C(e3a3e3a5ec1f6252), C(91f 4711 c59532bab), C(5e5 a61d26f97200b), C(ffa65a1a41da5883), C(5f 0e712235371 eef) }, { C(677 b53782a8af152), C(90 d76ef694361f72), C(fa2cb9714617a9e0), C(72 c8667cc1e45aa9), C(3 a0aa035bbcd1ef6), C(588e89 b034fde91b), C(f62e4e1d81c1687), C(72 c8667cc1e45aa9), C(3 a0aa035bbcd1ef6), C(588e89 b034fde91b), C(f62e4e1d81c1687), C(1ea81508 efa11e09), C(1 cf493a4dcd49aad), C(8217 d0fbe8226130), C(607 b979c0eb297dd) }, { C(8f 97 bb03473c860f), C(e23e420f9a32e4a2), C(3432 c97895fea7cf), C(69 cc85dac0991c6c), C(4 a6c529f94e9c36a), C(e5865f8da8c887df), C(27e8 c77da38582e0), C(69 cc85dac0991c6c), C(4 a6c529f94e9c36a), C(e5865f8da8c887df), C(27e8 c77da38582e0), C(8e60596 b4e327dbc), C(955 cf21baa1ddb18), C(c24a8eb9360370aa), C(70 d75fd116c2cab1) }, { C(fe50ea9f58e4de6f), C(f0a085b814230ce7), C(89407f 0548f 90e9 d), C(6 c595ea139648eba), C(efe867c726ab2974), C(26f 48ec c1c3821cf), C(55 c63c1b3d0f1549), C(6 c595ea139648eba), C(efe867c726ab2974), C(26f 48ec c1c3821cf), C(55 c63c1b3d0f1549), C(552e5f 78e1 d87a69), C(c9bfe2747a4eedf0), C(d5230acb6ef95a1), C(1e812f 3 c0d9962bd) }, { C(56eb0f cb9852bd27), C(c817b9a578c7b12), C(45427842795 bfa84), C(8 dccc5f52a65030c), C(f89ffa1f4fab979), C(7 d94da4a61305982), C(1 ba6839d59f1a07a), C(8 dccc5f52a65030c), C(f89ffa1f4fab979), C(7 d94da4a61305982), C(1 ba6839d59f1a07a), C(e0162ec1f40d583e), C(6 abf0b85552c7c33), C(f14bb021a875867d), C(c12a569c8bfe3ba7) }, { C(6 be2903d8f07af90), C(26 aaf7b795987ae8), C(44 a19337cb53fdeb), C(f0e14afc59e29a3a), C(a4d0084172a98c0d), C(275998 a345d04f0f), C(db73704d81680e8d), C(f0e14afc59e29a3a), C(a4d0084172a98c0d), C(275998 a345d04f0f), C(db73704d81680e8d), C(351388 cf7529b1b1), C(a3155d0237571da5), C(355231 b516da2890), C(263 c5a3d498c1cc) }, { C(58668066 da6bfc4), C(a4ea2eb7212df3dd), C(481f 64f 7 ca220524), C(11 b3b649b1cea339), C(57f 4 ad5b54d71118), C(feeb30bec803ab49), C(6ed9 bcc1973d9bf9), C(11 b3b649b1cea339), C(57f 4 ad5b54d71118), C(feeb30bec803ab49), C(6ed9 bcc1973d9bf9), C(bf2859d9964a70c8), C(d31ab162ca25f24e), C(70349336f f55d5d5), C(9 a2fa97115ef4409) }, { C(2 d04d1fbab341106), C(efe0c5b2878b444c), C(882 a2a889b5e8e71), C(18 cc96be09e5455), C(1 ad58fd26919e409), C(76593521 c4a0006b), C(f1361f348fa7cbfb), C(18 cc96be09e5455), C(1 ad58fd26919e409), C(76593521 c4a0006b), C(f1361f348fa7cbfb), C(205 bc68e660b0560), C(74360e11f 9f c367e), C(a88b7b0fa86caf), C(a982d749b30d4e4c) }, { C(d366b37bcd83805b), C(a6d16fea50466886), C(cb76dfa8eaf74d70), C(389 c44e423749aa), C(a30d802bec4e5430), C(9 ac1279f92bea800), C(686ef471 c2624025), C(389 c44e423749aa), C(a30d802bec4e5430), C(9 ac1279f92bea800), C(686ef471 c2624025), C(2 c21a72f8e3a3423), C(df5ab83f0918646a), C(cd876e0cb4df80fa), C(5 abbb92679b3ea36) }, { C(bbb9bc819ab65946), C(25e0 c756c95803e2), C(82 a73a1e1cc9bf6a), C(671 b931b702519a3), C(61609e7 dc0dd9488), C(9 cb329b8cab5420), C(3 c64f8ea340096ca), C(671 b931b702519a3), C(61609e7 dc0dd9488), C(9 cb329b8cab5420), C(3 c64f8ea340096ca), C(1690 afe3befd3afb), C(4 d3c18a846602740), C(a6783133a31dd64d), C(ecf4665e6bc76729) }, { C(8e994 eac99bbc61), C(84 de870b6f3c114e), C(150ef c95ce7b0cd2), C(4 c5d48abf41185e3), C(86049 a83c7cdcc70), C(ad828ff609277b93), C(f60fe028d582ccc7), C(4 c5d48abf41185e3), C(86049 a83c7cdcc70), C(ad828ff609277b93), C(f60fe028d582ccc7), C(464e0 b174da0cbd4), C(eadf1df69041b06e), C(48 cb9c96a9df1cdc), C(b7e5ee62809223a1) }, { C(364 cabf6585e2f7d), C(3 be1cc452509807e), C(1236 ce85788680d4), C(4 cea77c54fc3583a), C(9 a2a64766fd77614), C(63e6 c9254b5dc4db), C(26 af12ba3bf5988e), C(4 cea77c54fc3583a), C(9 a2a64766fd77614), C(63e6 c9254b5dc4db), C(26 af12ba3bf5988e), C(4 a821aca3ffa26a1), C(99 aa9aacbb3d08e3), C(619 ac77b52e8a823), C(68 c745a1ce4b7adb) }, { C(e878e2200893d775), C(76 b1e0a25867a803), C(9 c14d6d91f5ae2c5), C(ac0ffd8d64e242ed), C(e1673ee2dd997587), C(8 cdf3e9369d61003), C(c37c9a5258b98eba), C(ac0ffd8d64e242ed), C(e1673ee2dd997587), C(8 cdf3e9369d61003), C(c37c9a5258b98eba), C(f252b2e7b67dd012), C(47f c1eb088858f28), C(59 c42e4af1353223), C(e05b6c61c19eb26e) }, { C(6f 6 a014b9a861926), C(269e13 a120277867), C(37f c8a181e78711b), C(33dd 054 c41f3aef2), C(4f c8ab1a2ef3da7b), C(597178 c3756a06dc), C(748f 8 aadc540116f), C(33dd 054 c41f3aef2), C(4f c8ab1a2ef3da7b), C(597178 c3756a06dc), C(748f 8 aadc540116f), C(78e3 be34de99461e), C(28 b7b60d90dddab4), C(e47475fa9327a619), C(88 b17629e6265924) }, { C(da52b64212e8149b), C(121e713 c1692086f), C(f3d63cfa03850a02), C(f0d82bafec3c564c), C(37 dece35b549a1ce), C(5f b28f6078c4a2bd), C(b69990b7d9405710), C(f0d82bafec3c564c), C(37 dece35b549a1ce), C(5f b28f6078c4a2bd), C(b69990b7d9405710), C(3 af5223132071100), C(56 d5bb35f3bb5d2a), C(fcad4a4d5d3a1bc7), C(f17bf3d8853724d0) }, { C(1100f 797 ce53a629), C(f528c6614a1a30c2), C(30e49f b56bec67fa), C(f991664844003cf5), C(d54f5f6c8c7cf835), C(ca9cc4437c591ef3), C(d5871c77cf8fb424), C(f991664844003cf5), C(d54f5f6c8c7cf835), C(ca9cc4437c591ef3), C(d5871c77cf8fb424), C(5 cf90f1e617b750c), C(1648f 825 ab986232), C(936 cf225126a60), C(90f a5311d6f2445c) }, { C(4f 00655 b76e9cfda), C(9 dc5c707772ed283), C(b0f885f1e01927ec), C(6e4 d6843289dfb47), C(357 b41c6e5fd561f), C(491e386 bacb6df3c), C(86 be1b64ecd9945c), C(6e4 d6843289dfb47), C(357 b41c6e5fd561f), C(491e386 bacb6df3c), C(86 be1b64ecd9945c), C(be9547e3cfd85fae), C(f9e26ac346b430a8), C(38508 b84b0e68cff), C(a28d49dbd5562703) }, { C(d970198b6ca854db), C(92e3 d1786ae556a0), C(99 a165d7f0d85cf1), C(6548910 c5f668397), C(a5c8d20873e7de65), C(5 b7c4ecfb8e38e81), C(6 aa50a5531dad63e), C(6548910 c5f668397), C(a5c8d20873e7de65), C(5 b7c4ecfb8e38e81), C(6 aa50a5531dad63e), C(ab903d724449e003), C(ea3cc836c28fef88), C(4 b250d6c7200949d), C(13 a110654fa916c0) }, { C(76 c850754f28803), C(a4bffed2982cb821), C(6710e352247 caf63), C(d9cbf5b9c31d964e), C(25 c8f890178b97ae), C(e7c46064676cde9f), C(d8bb5eeb49c06336), C(d9cbf5b9c31d964e), C(25 c8f890178b97ae), C(e7c46064676cde9f), C(d8bb5eeb49c06336), C(962 b35ae89d5f4c1), C(c49083801ac2c21), C(2 db46ddec36ff33b), C(da48992ab8da284) }, { C(9 c98da9763f0d691), C(f5437139a3d40401), C(6f 493 c26c42f91e2), C(e857e4ab2d124d5), C(6417 bb2f363f36da), C(adc36c9c92193bb1), C(d35bd456172df3df), C(e857e4ab2d124d5), C(6417 bb2f363f36da), C(adc36c9c92193bb1), C(d35bd456172df3df), C(577 da94064d3a3d6), C(23f 13 d7532ea496a), C(6e09392 d80b8e85b), C(2e05f f6f23663892) }, { C(22f 8f 6869 a5f325), C(a0e7a96180772c26), C(cb71ea6825fa3b77), C(39 d3dec4e718e903), C(900 c9fbdf1ae2428), C(305301 da2584818), C(c6831f674e1fdb1f), C(39 d3dec4e718e903), C(900 c9fbdf1ae2428), C(305301 da2584818), C(c6831f674e1fdb1f), C(8 ad0e38ffe71babf), C(554 ac85a8a837e64), C(9900 c582cf401356), C(169f 646 b01ed7762) }, { C(9 ae7575fc14256bb), C(ab9c5a397fabc1b3), C(1 d3f582aaa724b2e), C(94412f 598ef156), C(15 bf1a588f25b327), C(5756646 bd68ce022), C(f062a7d29be259a5), C(94412f 598ef156), C(15 bf1a588f25b327), C(5756646 bd68ce022), C(f062a7d29be259a5), C(aa99c683cfb60b26), C(9e3 b7d4b17f91273), C(301 d3f5422dd34cf), C(53 d3769127253551) }, { C(540040e79752 b619), C(670327e237 c88cb3), C(50962f 261 bcc31d9), C(9 a8ea2b68b2847ec), C(bc24ab7d4cbbda31), C(df5aff1cd42a9b57), C(db47d368295f4628), C(9 a8ea2b68b2847ec), C(bc24ab7d4cbbda31), C(df5aff1cd42a9b57), C(db47d368295f4628), C(9 a66c221d1bf3f3), C(7 ae74ee1281de8ee), C(a4e173e2c787621f), C(5 b51062d10ae472) }, { C(34 cbf85722d897b1), C(6208 cb2a0fff4eba), C(e926cbc7e86f544e), C(883706 c4321efee0), C(8f d5d3d84c7827e4), C(a5c80e455a7ccaaa), C(3515f 41164654591), C(883706 c4321efee0), C(8f d5d3d84c7827e4), C(a5c80e455a7ccaaa), C(3515f 41164654591), C(2 c08bfc75dbfd261), C(6e9 eadf14f8c965e), C(18783f 5770 cd19a3), C(a6c7f2f1aa7b59ea) }, { C(46 afa66366bf5989), C(aa0d424ac649008b), C(97 a9108b3cd9c5c9), C(6 ca08e09227a9630), C(8 b11f73a8e5b80eb), C(2391 bb535dc7ce02), C(e43e2529cf36f4b9), C(6 ca08e09227a9630), C(8 b11f73a8e5b80eb), C(2391 bb535dc7ce02), C(e43e2529cf36f4b9), C(c9bd6d82b7a73d9d), C(b2ed9bae888447ac), C(bd22bb13af0cd06d), C(62781441785 b355b) }, { C(e15074b077c6e560), C(7 c8f2173fcc34afa), C(8 aad55bc3bd38370), C(d407ecdbfb7cb138), C(642442eff 44578 af), C(d3e9fdaf71a5b79e), C(c87c53eda46aa860), C(d407ecdbfb7cb138), C(642442eff 44578 af), C(d3e9fdaf71a5b79e), C(c87c53eda46aa860), C(8462310 a2c76ff51), C(1 bc17a2e0976665e), C(6ec446 b13b4d79cf), C(388 c7a904b4264c1) }, { C(9740 b2b2d6d06c6), C(e738265f9de8dafc), C(fdc947c1fca8be9e), C(d6936b41687c1e3d), C(a1a2deb673345994), C(91501e58 b17168bd), C(b8edee2b0b708dfc), C(d6936b41687c1e3d), C(a1a2deb673345994), C(91501e58 b17168bd), C(b8edee2b0b708dfc), C(ddf4b43dafd17445), C(44015 d050a04ce5c), C(1019f d9ab82c4655), C(c803aea0957bcdd1) }, { C(f1431889f2db1bff), C(85257 aa1dc6bd0d0), C(1 abbdea0edda5be4), C(775 aa89d278f26c3), C(a542d20265e3ef09), C(933 bdcac58a33090), C(c43614862666ca42), C(775 aa89d278f26c3), C(a542d20265e3ef09), C(933 bdcac58a33090), C(c43614862666ca42), C(4 c5e54d481a9748d), C(65 ce3cd0db838b26), C(9 ccbb4005c7f09d2), C(e6dda9555dde899a) }, { C(e2dd273a8d28c52d), C(8 cd95915fdcfd96b), C(67 c0f5b1025f0699), C(cbc94668d48df4d9), C(7e3 d656e49d632d1), C(8329e30 cac7a61d4), C(38e6 cd1e2034e668), C(cbc94668d48df4d9), C(7e3 d656e49d632d1), C(8329e30 cac7a61d4), C(38e6 cd1e2034e668), C(41e0 bce03ed9394b), C(7 be48d0158b9834a), C(9ea8 d5d1a976b18b), C(606 c424c33617e7a) }, { C(e0f79029834cc6ac), C(f2b1dcb87cc5e94c), C(4210 bc221fe5e70a), C(fd4a4301d4e2ac67), C(8f 84358 d25b2999b), C(6 c4b7d8a5a22ccbb), C(25df 606 bb23c9d40), C(fd4a4301d4e2ac67), C(8f 84358 d25b2999b), C(6 c4b7d8a5a22ccbb), C(25df 606 bb23c9d40), C(915298 b0eaadf85b), C(5ec23 cc4c6a74e62), C(d640a4ff99763439), C(1603753f b34ad427) }, { C(9 dc0a29830bcbec1), C(ec4a01dbd52d96a0), C(cd49c657eff87b05), C(ea487fe948c399e1), C(f5de9b2e59192609), C(4604 d9b3248b3a5), C(1929878 a22c86a1d), C(ea487fe948c399e1), C(f5de9b2e59192609), C(4604 d9b3248b3a5), C(1929878 a22c86a1d), C(3 cf6cd7c19dfa1ef), C(46e404 ee4af2d726), C(613 ab0588a5527b5), C(73e39385 ced7e684) }, { C(d10b70dde60270a6), C(be0f3b256e23422a), C(6 c601297a3739826), C(e327ffc477cd2467), C(ebebba63911f32b2), C(2 c2c5c24cf4970a2), C(a3cd2c192c1b8bf), C(e327ffc477cd2467), C(ebebba63911f32b2), C(2 c2c5c24cf4970a2), C(a3cd2c192c1b8bf), C(94 cb02c94aaf250b), C(30 ca38d5e3dac579), C(d68598a91dc597b5), C(162 b050e8de2d92) }, { C(58 d2459f094d075c), C(b4df247528d23251), C(355283f 2128 a9e71), C(d046198e4df506c2), C(c61bb9705786ae53), C(b360200380d10da8), C(59942 bf009ee7bc), C(d046198e4df506c2), C(c61bb9705786ae53), C(b360200380d10da8), C(59942 bf009ee7bc), C(95806 d027f8d245e), C(32df 87487ed9 d0f4), C(e2c5bc224ce97a98), C(9 a47c1e33cfb1cc5) }, { C(68 c600cdd42d9f65), C(bdf0c331f039ff25), C(1354 ac1d98944023), C(b5cdfc0b06fd1bd9), C(71f 0 ce33b183efab), C(d8ae4f9d4b949755), C(877 da19d6424f6b3), C(b5cdfc0b06fd1bd9), C(71f 0 ce33b183efab), C(d8ae4f9d4b949755), C(877 da19d6424f6b3), C(f7cc5cbf76bc6006), C(c93078f44b98efdb), C(3 d482142c727e8bc), C(8e23f 92e0616 d711) }, { C(9f c0bd876cb975da), C(80f 41015045 d1ade), C(5 cbf601fc55c809a), C(7 d9c567075001705), C(a2fafeed0df46d5d), C(a70b82990031da8f), C(8611 c76abf697e56), C(7 d9c567075001705), C(a2fafeed0df46d5d), C(a70b82990031da8f), C(8611 c76abf697e56), C(806911617e1 ee53), C(1 ce82ae909fba503), C(52df 85f ea9e404bd), C(dbd184e5d9a11a3e) }, { C(7 b3e8c267146c361), C(c6ad095af345b726), C(af702ddc731948bd), C(7 ca4c883bded44b5), C(c90beb31ee9b699a), C(2 cdb4aba3d59b8a3), C(df0d4fa685e938f0), C(7 ca4c883bded44b5), C(c90beb31ee9b699a), C(2 cdb4aba3d59b8a3), C(df0d4fa685e938f0), C(cc0e568e91aaa382), C(70 ca583a464dbea), C(b7a5859b44710e1a), C(ad141467fdf9a83a) }, { C(6 c49c6b3c9dd340f), C(897 c41d89af37bd1), C(52df 69e0 e2c68a8d), C(eec4be1f65531a50), C(bf23d928f20f1b50), C(c642009b9c593940), C(c5e59e6ca9e96f85), C(eec4be1f65531a50), C(bf23d928f20f1b50), C(c642009b9c593940), C(c5e59e6ca9e96f85), C(7f bd53343e7da499), C(dd87e7b88afbd251), C(92696e7683 b9f322), C(60f f51ef02c24652) }, { C(47324327 a4cf1732), C(6044753 d211e1dd5), C(1ec ae46d75192d3b), C(b6d6315a902807e3), C(ccc8312c1b488e5d), C(b933a7b48a338ec), C(9 d6753cd83422074), C(b6d6315a902807e3), C(ccc8312c1b488e5d), C(b933a7b48a338ec), C(9 d6753cd83422074), C(5714 bd5c0efdc7a8), C(221585e2 c88068ca), C(303342 b25678904), C(8 c174a03e69a76e) }, { C(1e984 ef53c5f6aae), C(99ea10 dac804298b), C(a3f8c241100fb14d), C(259eb3 c63a9c9be6), C(f8991532947c7037), C(a16d20b3fc29cfee), C(493 c2e91a775af8c), C(259eb3 c63a9c9be6), C(f8991532947c7037), C(a16d20b3fc29cfee), C(493 c2e91a775af8c), C(275f ccf4acb08abc), C(d13fb6ea3eeaf070), C(505283e5 b702b9ea), C(64 c092f9f8df1901) }, { C(b88f5c9b8b854cc6), C(54f c5d39825b446), C(a12fc1546eac665d), C(ab90eb7fa58b280c), C(dda26598356aa599), C(64191 d63f2586e52), C(cada0075c34e8b02), C(ab90eb7fa58b280c), C(dda26598356aa599), C(64191 d63f2586e52), C(cada0075c34e8b02), C(e7de6532b691d87c), C(a28fec86e368624), C(796 c280eebd0241a), C(acfcecb641fdbeee) }, { C(9f cb3fdb09e7a63a), C(7 a115c9ded150112), C(e9ba629108852f37), C(9 b03c7c218c192a), C(93 c1dd563f46308e), C(f9553625917ea800), C(e0a52f8a5024c59), C(9 b03c7c218c192a), C(93 c1dd563f46308e), C(f9553625917ea800), C(e0a52f8a5024c59), C(2 bb3a9e8b053e490), C(8 b97936723cd8ff6), C(bf3f835246d02722), C(c8e033da88ecd724) }, { C(d58438d62089243), C(d8c19375b228e9d3), C(13042546ed96 e790), C(4 a42ef343514138c), C(549e62449 e225cf1), C(dd8260e2808f68e8), C(69580f c81fcf281b), C(4 a42ef343514138c), C(549e62449 e225cf1), C(dd8260e2808f68e8), C(69580f c81fcf281b), C(fc0e30d682e87289), C(f44b784248d6107b), C(df25119527fdf209), C(cc265612588171a8) }, { C(7ea73 b6b74c8cd0b), C(e07188dd9b5bf3ca), C(6ef62f f2dd008ed4), C(acd94b3038342152), C(1 b0ed99c9b7ba297), C(b794a93f4c895939), C(97 a60cd93021206d), C(acd94b3038342152), C(1 b0ed99c9b7ba297), C(b794a93f4c895939), C(97 a60cd93021206d), C(9e0 c0e6da5001b07), C(5f 5 b817de5d2a391), C(35 b8a8702acdd533), C(3 bbcfef344f455) }, { C(e42ffdf6278bb21), C(59df 3e5 ca582ff9d), C(f3108785599dbde9), C(f78e8a2d4aba6a1d), C(700473f b0d8380fc), C(d0a0d68061ac74b2), C(11650612f a426e5a), C(f78e8a2d4aba6a1d), C(700473f b0d8380fc), C(d0a0d68061ac74b2), C(11650612f a426e5a), C(e39ceb5b2955710c), C(f559ff201f8cebaa), C(1f bc182809e829a0), C(295 c7fc82fa6fb5b) }, { C(9 ad37fcd49fe4aa0), C(76 d40da71930f708), C(bea08b630f731623), C(797292108901 a81f), C(3 b94127b18fae49c), C(688247179f 144f 1 b), C(48 a507a1625d13d7), C(797292108901 a81f), C(3 b94127b18fae49c), C(688247179f 144f 1 b), C(48 a507a1625d13d7), C(452322 aaad817005), C(51 d730d973e13d44), C(c883eb30176652ea), C(8 d338fd678b2404d) }, { C(27 b7ff391136696e), C(60 db94a18593438c), C(b5e46d79c4dafbad), C(ad56fd25a6f15289), C(68 a0ec7c0179df80), C(a0aacfc36620957), C(87 a0762a09e2e1c1), C(ad56fd25a6f15289), C(68 a0ec7c0179df80), C(a0aacfc36620957), C(87 a0762a09e2e1c1), C(d50ace99460f0be3), C(7f 1f e5653ae0d999), C(3870899 d9d6c22c), C(df5f952dd90d5a09) }, { C(76 bd077e42692ddf), C(c14b60958c2c7a85), C(fd9f3b0b3b1e2738), C(273 d2c51a8e65e71), C(ac531423f670bf34), C(7f 40 c6bfb8c5758a), C(5f de65b433a10b02), C(273 d2c51a8e65e71), C(ac531423f670bf34), C(7f 40 c6bfb8c5758a), C(5f de65b433a10b02), C(dbda6c4252b0a75c), C(5 d4cfd8f937b23d9), C(3895f 478e1 c29c9d), C(e3e7c1fd1199aec6) }, { C(81 c672225442e053), C(927 c3f6c8964050e), C(cb59f8f2bb36fac5), C(298f 3583326f d942), C(b85602a9a2e2f97c), C(65 c849bfa3191459), C(bf21329dfb496c0d), C(298f 3583326f d942), C(b85602a9a2e2f97c), C(65 c849bfa3191459), C(bf21329dfb496c0d), C(ea7b7b44c596aa18), C(c18bfb6e9a36d59c), C(1 b55f03e8a38cc0a), C(b6a94cd47bbf847f) }, { C(37 b9e308747448ca), C(513f 39f 5545 b1bd), C(145 b32114ca00f9c), C(cce24b9910eb0489), C(af4ac64668ac57d9), C(ea0e44c13a9a5d5e), C(b224fb0c680455f4), C(cce24b9910eb0489), C(af4ac64668ac57d9), C(ea0e44c13a9a5d5e), C(b224fb0c680455f4), C(a7714bbba8699be7), C(fecad6e0e0092204), C(c1ce8bd5ac247eb4), C(3993 aef5c07cdca2) }, { C(dab71695950a51d4), C(9e98 e4dfa07566fe), C(fab3587513b84ec0), C(2409f 60f 0854f 305), C(b17f6e6c8ff1894c), C(62f a048551dc7ad6), C(d99f4fe2799bad72), C(2409f 60f 0854f 305), C(b17f6e6c8ff1894c), C(62f a048551dc7ad6), C(d99f4fe2799bad72), C(4 a38e7f2f4a669d3), C(53173510 ca91f0e3), C(cc9096c0df860b0), C(52ed637026 a4a0d5) }, { C(28630288285 c747b), C(a165a5bf51aaec95), C(927 d211f27370016), C(727 c782893d30c22), C(742706852989 c247), C(c546494c3bb5e7e2), C(1f b2a5d1570f5dc0), C(727 c782893d30c22), C(742706852989 c247), C(c546494c3bb5e7e2), C(1f b2a5d1570f5dc0), C(71e498804df 91 b76), C(4 a6a5aa6f7e5621), C(871 a63730d13a544), C(63f 77 c8f371cc2f8) }, { C(4 b591ad5160b6c1b), C(e8f85ddd5a1143f7), C(377e18171476 d64), C(829481773 cce2cb1), C(c9d9fb4e25e4d243), C(c1fff894f0cf713b), C(69ed d73ec20984b0), C(829481773 cce2cb1), C(c9d9fb4e25e4d243), C(c1fff894f0cf713b), C(69ed d73ec20984b0), C(7f b1132262925f4a), C(a292e214fe56794f), C(915 bfee68e16f46f), C(98 bcc857bb6d31e7) }, { C(7e02f 7 a5a97dd3df), C(9724 a88ac8c30809), C(d8dee12589eeaf36), C(c61f8fa31ad1885b), C(3e3744 e04485ff9a), C(939335 b37f34c7a2), C(faa5de308dbbbc39), C(c61f8fa31ad1885b), C(3e3744 e04485ff9a), C(939335 b37f34c7a2), C(faa5de308dbbbc39), C(f5996b1be7837a75), C(4f cb12d267f5af4f), C(39 be67b8cd132169), C(5 c39e3819198b8a1) }, { C(ff66660873521fb2), C(d82841f7e714ce03), C(c830d273f005e378), C(66990 c8c54782228), C(4f 28 bea83dda97c), C(6 a24c64698688de0), C(69721141111 da99b), C(66990 c8c54782228), C(4f 28 bea83dda97c), C(6 a24c64698688de0), C(69721141111 da99b), C(d5c771fade83931b), C(8094ed75 e6feb396), C(7 a79d4de8efd1a2c), C(5f 9e50167693 e363) }, { C(ef3c4dd60fa37412), C(e8d2898c86d11327), C(8 c883d860aafacfe), C(a4ace72ba19d6de5), C(4 cae26627dfc5511), C(38e496 de9f677b05), C(558770996e1906 d6), C(a4ace72ba19d6de5), C(4 cae26627dfc5511), C(38e496 de9f677b05), C(558770996e1906 d6), C(40df 30e332 ceca69), C(8f 106 cbd94166c42), C(332 b6ab4f4c1014e), C(7 c0bc3092ad850e5) }, { C(a7b07bcb1a1333ba), C(9 d007956720914c3), C(4751f 60ef2 b15545), C(77 ac4dcee10c9023), C(e90235108fa20e56), C(1 d3ea38535215800), C(5ed1 ccfff26bc64), C(77 ac4dcee10c9023), C(e90235108fa20e56), C(1 d3ea38535215800), C(5ed1 ccfff26bc64), C(789 a1c352bf5c61e), C(860 a119056da8252), C(a6c268a238699086), C(4 d70f5cccf4ef2eb) }, { C(89858f c94ee25469), C(f72193b78aeaa896), C(7 dba382760727c27), C(846 b72f372f1685a), C(f708db2fead5433c), C(c04e121770ee5dc), C(4619793 b67d0daa4), C(846 b72f372f1685a), C(f708db2fead5433c), C(c04e121770ee5dc), C(4619793 b67d0daa4), C(79f 80506f 152285f), C(5300074926f ccd56), C(7f bbff6cc418fce6), C(b908f77c676b32e4) }, { C(e6344d83aafdca2e), C(6e147816 e6ebf87), C(8508 c38680732caf), C(f4ce36d3a375c981), C(9 d67e5572f8d7bf4), C(900 d63d9ec79e477), C(5251 c85ab52839a3), C(f4ce36d3a375c981), C(9 d67e5572f8d7bf4), C(900 d63d9ec79e477), C(5251 c85ab52839a3), C(92ec4 b3952e38027), C(40 b2dc421a518cbf), C(661ea97 b2331a070), C(8 d428a4a9485179b) }, { C(3dd bb400198d3d4d), C(fe73de3ada21af5c), C(cd7df833dacd8da3), C(162 be779eea87bf8), C(7 d62d36edf759e6d), C(dc20f528362e37b2), C(1 a902edfe4a5824e), C(162 be779eea87bf8), C(7 d62d36edf759e6d), C(dc20f528362e37b2), C(1 a902edfe4a5824e), C(e6a258d30fa817ba), C(c5d73adf6fb196fd), C(475 b7a6286a207fb), C(d35f96363e8eba95) }, { C(79 d4c20cf83a7732), C(651ea0 a6ab059bcd), C(94631144f 363 cdef), C(894 a0ee0c1f87a22), C(4e682573f 8 b38f25), C(89803f c082816289), C(71613963 a02d90e1), C(894 a0ee0c1f87a22), C(4e682573f 8 b38f25), C(89803f c082816289), C(71613963 a02d90e1), C(4 c6cc0e5a737c910), C(a3765b5da16bccd9), C(8 bf483c4d735ec96), C(7f d7c8ba1934afec) }, { C(5 aaf0d7b669173b5), C(19661 ca108694547), C(5 d03d681639d71fe), C(7 c422f4a12fd1a66), C(aa561203e7413665), C(e99d8d202a04d573), C(6090357ec6f 1f 1), C(7 c422f4a12fd1a66), C(aa561203e7413665), C(e99d8d202a04d573), C(6090357ec6f 1f 1), C(dbfe89f01f0162e), C(49 aa89da4f1e389b), C(7119 a6f4514efb22), C(56593f 6 b4e7318d9) }, { C(35 d6cc883840170c), C(444694 c4f8928732), C(98500f 14 b8741c6), C(5021 ac9480077dd), C(44 c2ebc11cfb9837), C(e5d310c4b5c1d9fd), C(a577102c33ac773c), C(5021 ac9480077dd), C(44 c2ebc11cfb9837), C(e5d310c4b5c1d9fd), C(a577102c33ac773c), C(a00d2efd2effa3cf), C(c2c33ffcda749df6), C(d172099d3b6f2986), C(f308fe33fcd23338) }, { C(b07eead7a57ff2fe), C(c1ffe295ca7dbf47), C(ef137b125cfa8851), C(8f 8ee c5cde7a490a), C(79916 d20a405760b), C(3 c30188c6d38c43c), C(b17e3c3ff7685e8d), C(8f 8ee c5cde7a490a), C(79916 d20a405760b), C(3 c30188c6d38c43c), C(b17e3c3ff7685e8d), C(ac8aa3cd0790c4c9), C(78 ca60d8bf10f670), C(26f 522 be4fbc1184), C(55 bc7688083326d4) }, { C(20f ba36c76380b18), C(95 c39353c2a3477d), C(4f 362902 cf9117ad), C(89816ec851 e3f405), C(65258396f 932858 d), C(b7dcaf3cc57a0017), C(b368f482afc90506), C(89816ec851 e3f405), C(65258396f 932858 d), C(b7dcaf3cc57a0017), C(b368f482afc90506), C(88f 08 c74465015f1), C(94eb af209d59099d), C(c1b7ff7304b0a87), C(56 bf8235257d4435) }, { C(c7e9e0c45afeab41), C(999 d95f41d9ee841), C(55ef15 ac11ea010), C(cc951b8eab5885d), C(956 c702c88ac056b), C(de355f324a37e3c0), C(ed09057eb60bd463), C(cc951b8eab5885d), C(956 c702c88ac056b), C(de355f324a37e3c0), C(ed09057eb60bd463), C(1f 44 b6d04a43d088), C(53631822 a26ba96d), C(90305f c2d21f8d28), C(60693 a9a6093351a) }, { C(69 a8e59c1577145d), C(cb04a6e309ebc626), C(9 b3326a5b250e9b1), C(d805f665265fd867), C(82 b2b019652c19c6), C(f0df7738353c82a6), C(6 a9acf124383ca5f), C(d805f665265fd867), C(82 b2b019652c19c6), C(f0df7738353c82a6), C(6 a9acf124383ca5f), C(6838374508 a7a99f), C(7 b6719db8d3e40af), C(1 a22666cf0dcb7cf), C(989 a9cf7f46b434d) }, { C(6638191337226509), C(42 b55e08e4894870), C(a7696f5fbd51878e), C(433 bbdd27481d85d), C(ee32136b5a47bbec), C(769 a77f346d82f4e), C(38 b91b1cb7e34be), C(433 bbdd27481d85d), C(ee32136b5a47bbec), C(769 a77f346d82f4e), C(38 b91b1cb7e34be), C(cb10fd95c0e43875), C(ce9744efd6f11427), C(946 b32bddada6a13), C(35 d544690b99e3b6) }, { C(c44e8c33ff6c5e13), C(1f 128 a22aab3007f), C(6 a8b41bf04cd593), C(1 b9b0deaf126522a), C(cc51d382baedc2eb), C(8df 8831 bb2e75daa), C(de4e7a4b5de99588), C(1 b9b0deaf126522a), C(cc51d382baedc2eb), C(8df 8831 bb2e75daa), C(de4e7a4b5de99588), C(55 a2707103a9f968), C(e0063f4e1649702d), C(7e82f 5 b440e74043), C(649 b44a27f00219d) }, { C(68125009158 bded7), C(563 a9a62753fc088), C(b97a9873a352cf6a), C(237 d1de15ae56127), C(b96445f758ba57d), C(b842628a9f9938eb), C(70313 d232dc2cd0d), C(237 d1de15ae56127), C(b96445f758ba57d), C(b842628a9f9938eb), C(70313 d232dc2cd0d), C(8 bfe1f78cb40ad5b), C(a5bde811d49f56e1), C(1 acd0cf913ded507), C(820 b3049fa5e6786) }, { C(e0dd644db35a62d6), C(292889772752 ab42), C(b80433749dbb8793), C(7032f e67035f95db), C(d8076d1fda17eb8d), C(115 ca1775560f946), C(92 da1e16f396bf61), C(7032f e67035f95db), C(d8076d1fda17eb8d), C(115 ca1775560f946), C(92 da1e16f396bf61), C(17 c8bc7f6d23a639), C(fb28a2afa4d562a9), C(6 c59c95fa2450d5f), C(fe0d41d5ebfbce2a) }, { C(21 ce9eab220aaf87), C(27 d20caec922d708), C(610 c51f976cb1d30), C(6052f 97 a1e02d2ba), C(836ee a7ce63dea17), C(e1f8efb81b443b45), C(ddbdbbe717570246), C(6052f 97 a1e02d2ba), C(836ee a7ce63dea17), C(e1f8efb81b443b45), C(ddbdbbe717570246), C(69551045 b0e56f60), C(625 a435960ba7466), C(9 cdb004e8b11405c), C(d6284db99a3b16af) }, { C(83 b54046fdca7c1e), C(e3709e9153c01626), C(f306b5edc2682490), C(88f 14 b0b554fba02), C(a0ec13fac0a24d0), C(f468ebbc03b05f47), C(a9cc417c8dad17f0), C(88f 14 b0b554fba02), C(a0ec13fac0a24d0), C(f468ebbc03b05f47), C(a9cc417c8dad17f0), C(4 c1ababa96d42275), C(c112895a2b751f17), C(5dd 7 d9fa55927aa9), C(ca09db548d91cd46) }, { C(dd3b2ce7dabb22fb), C(64888 c62a5cb46ee), C(f004e8b4b2a97362), C(31831 cf3efc20c84), C(901 ba53808e677ae), C(4 b36895c097d0683), C(7 d93ad993f9179aa), C(31831 cf3efc20c84), C(901 ba53808e677ae), C(4 b36895c097d0683), C(7 d93ad993f9179aa), C(a4c5ea29ae78ba6b), C(9 cf637af6d607193), C(5731 bd261d5b3adc), C(d59a9e4f796984f3) }, { C(9ee08f c7a86b0ea6), C(5 c8d17dff5768e66), C(18859672 bafd1661), C(d3815c5f595e513e), C(44 b3bdbdc0fe061f), C(f5f43b2a73ad2df5), C(7 c0e6434c8d7553c), C(d3815c5f595e513e), C(44 b3bdbdc0fe061f), C(f5f43b2a73ad2df5), C(7 c0e6434c8d7553c), C(8 c05859060821002), C(73629 a0d275008ce), C(860 c012879e6f00f), C(d48735a120d2c37c) }, { C(4e2 a10f1c409dfa5), C(6e684591f 5 da86bd), C(ff8c9305d447cadb), C(c43ae49df25b1c86), C(d4f42115cee1ac8), C(a0e6a714471b975c), C(a40089dec5fe07b0), C(c43ae49df25b1c86), C(d4f42115cee1ac8), C(a0e6a714471b975c), C(a40089dec5fe07b0), C(18 c3d8f967915e10), C(739 c747dbe05adfb), C(4 b0397b596e16230), C(3 c57d7e1de9e58d1) }, { C(bdf3383d7216ee3c), C(eed3a37e4784d324), C(247 cff656d081ba0), C(76059e4 cb25d4700), C(e0af815fe1fa70ed), C(5 a6ccb4f36c5b3df), C(391 a274cd5f5182d), C(76059e4 cb25d4700), C(e0af815fe1fa70ed), C(5 a6ccb4f36c5b3df), C(391 a274cd5f5182d), C(ff1579baa6a2b511), C(c385fc5062e8a728), C(e940749739a37c78), C(a093523a5b5edee5) }, { C(a22e8f6681f0267d), C(61e79 bc120729914), C(86ec13 c84c1600d3), C(1614811 d59dcab44), C(d1ddcca9a2675c33), C(f3c551d5fa617763), C(5 c78d4181402e98c), C(1614811 d59dcab44), C(d1ddcca9a2675c33), C(f3c551d5fa617763), C(5 c78d4181402e98c), C(b43b4a9caa6f5d4c), C(f112829bca2df8f3), C(87e5 c85db80d06c3), C(8eb4 bac85453409) }, { C(6997121 cae0ce362), C(ba3594cbcc299a07), C(7e4 b71c7de25a5e4), C(16 ad89e66db557ba), C(a43c401140ffc77d), C(3780 a8b3fd91e68), C(48190678248 a06b5), C(16 ad89e66db557ba), C(a43c401140ffc77d), C(3780 a8b3fd91e68), C(48190678248 a06b5), C(d10deb97b651ad42), C(3 a69f3f29046a24f), C(f7179735f2c6dab4), C(ac82965ad3b67a02) }, { C(9 bfc2c3e050a3c27), C(dc434110e1059ff3), C(5426055 da178decd), C(cb44d00207e16f99), C(9 d9e99afedc8107f), C(56907 c4fb7b3bc01), C(bcff1472bb01f85a), C(cb44d00207e16f99), C(9 d9e99afedc8107f), C(56907 c4fb7b3bc01), C(bcff1472bb01f85a), C(516f 800f 74 ad0985), C(f93193ade9614da4), C(9f 4 a7845355b75b7), C(423 c17045824dea5) }, { C(a3f37e415aedf14), C(8 d21c92bfa0dc545), C(a2715ebb07deaf80), C(98 ce1ff2b3f99f0f), C(162 acfd3b47c20bf), C(62 b9a25fd39dc6c0), C(c165c3c95c878dfe), C(98 ce1ff2b3f99f0f), C(162 acfd3b47c20bf), C(62 b9a25fd39dc6c0), C(c165c3c95c878dfe), C(2 b9a7e1f055bd27c), C(e91c8099cafaa75d), C(37e38 d64ef0263b), C(a46e89f47a1a70d5) }, { C(cef3c748045e7618), C(41dd 44f aef4ca301), C(6 add718a88f383c6), C(1197ec a317e70a93), C(61f 9497e6 cc4a33), C(22e7178 d1e57af73), C(5df 95 da0ff1c6435), C(1197ec a317e70a93), C(61f 9497e6 cc4a33), C(22e7178 d1e57af73), C(5df 95 da0ff1c6435), C(934327643705e56 c), C(11eb0 eec553137c9), C(1e6 b9b57ac5283ec), C(6934785 db184b2e4) }, { C(fe2b841766a4d212), C(42 cf817e58fe998c), C(29f 7f 493 ba9cbe6c), C(2 a9231d98b441827), C(fca55e769df78f6c), C(da87ea680eb14df4), C(e0b77394b0fd2bcc), C(2 a9231d98b441827), C(fca55e769df78f6c), C(da87ea680eb14df4), C(e0b77394b0fd2bcc), C(f36a2a3c73ab371a), C(d52659d36d93b71), C(3 d3b7d2d2fafbb14), C(b4b7b317d9266711) }, { C(d6131e688593a181), C(5 b658b282688ccd3), C(b9f7c066beed1204), C(e9dd79bad89f6b19), C(b420092bae6aaf41), C(515f 9 bbd06069d77), C(80664957 a02cbc29), C(e9dd79bad89f6b19), C(b420092bae6aaf41), C(515f 9 bbd06069d77), C(80664957 a02cbc29), C(f9dc7a744a56d9b3), C(7eb2 bdcd6667f383), C(c5914296fbdaf9d1), C(af0d5a8fec374fc4) }, { C(91288884ebf cf145), C(3df fd892d36403af), C(7 c4789db82755080), C(634 acbe037edec27), C(878 a97fab822d804), C(fcb042af908f0577), C(4 cbafc318bb90a2e), C(634 acbe037edec27), C(878 a97fab822d804), C(fcb042af908f0577), C(4 cbafc318bb90a2e), C(68 a96d589d5e5654), C(a752cb250bca1bc0), C(8f 228f 406024 aa7e), C(fc5408cf22a080b5) }, { C(754 c7e98ae3495ea), C(2030124 a22512c19), C(ec241579c626c39d), C(e682b5c87fa8e41b), C(6 cfa4baff26337ac), C(4 d66358112f09b2a), C(58889 d3f50ffa99c), C(33f c6ffd1ffb8676), C(36 db7617b765f6e2), C(8df 41 c03c514a9dc), C(6707 cc39a809bb74), C(3f 27 d7bb79e31984), C(a39dc6ac6cb0b0a8), C(33f c6ffd1ffb8676), C(36 db7617b765f6e2) }, }; void Check(uint64 expected, uint64 actual) { if (expected != actual) { fprintf(stderr, "ERROR: expected %llx, but got %llx", expected, actual); ++errors; } } void Test(const uint64 *expected, int offset, int len) { const uint128 u = CityHash128(data + offset, len); const uint128 v = CityHash128WithSeed(data + offset, len, kSeed128); Check(expected[0], CityHash64(data + offset, len)); Check(expected[1], CityHash64WithSeed(data + offset, len, kSeed0)); Check(expected[2], CityHash64WithSeeds(data + offset, len, kSeed0, kSeed1)); Check(expected[3], Uint128Low64(u)); Check(expected[4], Uint128High64(u)); Check(expected[5], Uint128Low64(v)); Check(expected[6], Uint128High64(v)); #ifdef __SSE4_2__ const uint128 y = CityHashCrc128(data + offset, len); const uint128 z = CityHashCrc128WithSeed(data + offset, len, kSeed128); uint64 crc256_results[4]; CityHashCrc256(data + offset, len, crc256_results); Check(expected[7], Uint128Low64(y)); Check(expected[8], Uint128High64(y)); Check(expected[9], Uint128Low64(z)); Check(expected[10], Uint128High64(z)); int i; for (i = 0; i < 4; i++) Check(expected[11 + i], crc256_results[i]); #endif } int main(int argc, char **argv) { setup(); int i; for (i = 0; i < kTestSize - 1; i++) Test(testdata[i], i * i, i); Test(testdata[i], 0, kDataSize); return errors > 0; } ��������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/city.c������������������������������������������������������������������0000664�0000000�0000000�00000035564�14737566223�0017231�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: MIT /* city.c - cityhash-c * CityHash on C * Copyright (c) 2011-2012, Alexander Nusov * * - original copyright notice - * Copyright (c) 2011 Google, Inc. * * 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. * * CityHash, by Geoff Pike and Jyrki Alakuijala * * This file provides CityHash64() and related functions. * * It's probably possible to create even faster hash functions by * writing a program that systematically explores some of the space of * possible hash functions, by using SIMD instructions, or by * compromising on hash quality. */ #include <string.h> #include "city.h" #include <misc/portable.h> static uint64 UNALIGNED_LOAD64(const char *p) { uint64 result; memcpy(&result, p, sizeof(result)); return result; } static uint32 UNALIGNED_LOAD32(const char *p) { uint32 result; memcpy(&result, p, sizeof(result)); return result; } #if !defined(WORDS_BIGENDIAN) #define uint32_in_expected_order(x) (x) #define uint64_in_expected_order(x) (x) #else #define uint32_in_expected_order(x) (bswap_32(x)) #define uint64_in_expected_order(x) (bswap_64(x)) #endif /* WORDS_BIGENDIAN */ #if !defined(LIKELY) #if HAVE_BUILTIN_EXPECT #define LIKELY(x) (__builtin_expect(!!(x), 1)) #else #define LIKELY(x) (x) #endif #endif static uint64 Fetch64(const char *p) { return uint64_in_expected_order(UNALIGNED_LOAD64(p)); } static uint32 Fetch32(const char *p) { return uint32_in_expected_order(UNALIGNED_LOAD32(p)); } /* Some primes between 2^63 and 2^64 for various uses. */ static const uint64 k0 = 0xc3a5c85c97cb3127ULL; static const uint64 k1 = 0xb492b66fbe98f273ULL; static const uint64 k2 = 0x9ae16a3b2f90404fULL; static const uint64 k3 = 0xc949d7c7509e6557ULL; /* Hash 128 input bits down to 64 bits of output. * This is intended to be a reasonably good hash function. */ static inline uint64 Hash128to64(const uint128 x) { /* Murmur-inspired hashing. */ const uint64 kMul = 0x9ddfea08eb382d69ULL; uint64 a = (Uint128Low64(x) ^ Uint128High64(x)) * kMul; a ^= (a >> 47); uint64 b = (Uint128High64(x) ^ a) * kMul; b ^= (b >> 47); b *= kMul; return b; } /* Bitwise right rotate. Normally this will compile to a single * instruction, especially if the shift is a manifest constant. */ static uint64 Rotate(uint64 val, int shift) { /* Avoid shifting by 64: doing so yields an undefined result. */ return shift == 0 ? val : ((val >> shift) | (val << (64 - shift))); } /* Equivalent to Rotate(), but requires the second arg to be non-zero. * On x86-64, and probably others, it's possible for this to compile * to a single instruction if both args are already in registers. */ static uint64 RotateByAtLeast1(uint64 val, int shift) { return (val >> shift) | (val << (64 - shift)); } static uint64 ShiftMix(uint64 val) { return val ^ (val >> 47); } static uint64 HashLen16(uint64 u, uint64 v) { uint128 result; result.first = u; result.second = v; return Hash128to64(result); } static uint64 HashLen0to16(const char *s, size_t len) { if (len > 8) { uint64 a = Fetch64(s); uint64 b = Fetch64(s + len - 8); return HashLen16(a, RotateByAtLeast1(b + len, len)) ^ b; } if (len >= 4) { uint64 a = Fetch32(s); return HashLen16(len + (a << 3), Fetch32(s + len - 4)); } if (len > 0) { uint8 a = s[0]; uint8 b = s[len >> 1]; uint8 c = s[len - 1]; uint32 y = (uint32)(a) + ((uint32)(b) << 8); uint32 z = len + ((uint32)(c) << 2); return ShiftMix(y * k2 ^ z * k3) * k2; } return k2; } /* This probably works well for 16-byte strings as well, but it may be overkill * in that case. */ static uint64 HashLen17to32(const char *s, size_t len) { uint64 a = Fetch64(s) * k1; uint64 b = Fetch64(s + 8); uint64 c = Fetch64(s + len - 8) * k2; uint64 d = Fetch64(s + len - 16) * k0; return HashLen16(Rotate(a - b, 43) + Rotate(c, 30) + d, a + Rotate(b ^ k3, 20) - c + len); } /* Return a 16-byte hash for 48 bytes. Quick and dirty. * Callers do best to use "random-looking" values for a and b. * static pair<uint64, uint64> WeakHashLen32WithSeeds( */ uint128 WeakHashLen32WithSeeds6(uint64 w, uint64 x, uint64 y, uint64 z, uint64 a, uint64 b) { a += w; b = Rotate(b + a + z, 21); uint64 c = a; a += x; a += y; b += Rotate(a, 44); uint128 result; result.first = (uint64)(a + z); result.second = (uint64)(b + c); return result; } /* Return a 16-byte hash for s[0] ... s[31], a, and b. Quick and dirty. * static pair<uint64, uint64> WeakHashLen32WithSeeds( */ uint128 WeakHashLen32WithSeeds(const char *s, uint64 a, uint64 b) { return WeakHashLen32WithSeeds6(Fetch64(s), Fetch64(s + 8), Fetch64(s + 16), Fetch64(s + 24), a, b); } /* Return an 8-byte hash for 33 to 64 bytes. */ static uint64 HashLen33to64(const char *s, size_t len) { uint64 z = Fetch64(s + 24); uint64 a = Fetch64(s) + (len + Fetch64(s + len - 16)) * k0; uint64 b = Rotate(a + z, 52); uint64 c = Rotate(a, 37); a += Fetch64(s + 8); c += Rotate(a, 7); a += Fetch64(s + 16); uint64 vf = a + z; uint64 vs = b + Rotate(a, 31) + c; a = Fetch64(s + 16) + Fetch64(s + len - 32); z = Fetch64(s + len - 8); b = Rotate(a + z, 52); c = Rotate(a, 37); a += Fetch64(s + len - 24); c += Rotate(a, 7); a += Fetch64(s + len - 16); uint64 wf = a + z; uint64 ws = b + Rotate(a, 31) + c; uint64 r = ShiftMix((vf + ws) * k2 + (wf + vs) * k0); return ShiftMix(r * k0 + vs) * k2; } uint64 CityHash64(const char *s, size_t len) { if (len <= 32) { if (len <= 16) return HashLen0to16(s, len); else return HashLen17to32(s, len); } else if (len <= 64) return HashLen33to64(s, len); /* For strings over 64 bytes we hash the end first, and then as we * loop we keep 56 bytes of state: v, w, x, y, and z. */ uint64 x = Fetch64(s + len - 40); uint64 y = Fetch64(s + len - 16) + Fetch64(s + len - 56); uint64 z = HashLen16(Fetch64(s + len - 48) + len, Fetch64(s + len - 24)); uint64 temp; uint128 v = WeakHashLen32WithSeeds(s + len - 64, len, z); uint128 w = WeakHashLen32WithSeeds(s + len - 32, y + k1, x); x = x * k1 + Fetch64(s); /* Decrease len to the nearest multiple of 64, * and operate on 64-byte chunks. */ len = (len - 1) & ~(size_t)(63); do { x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; x ^= w.second; y += v.first + Fetch64(s + 40); z = Rotate(z + w.first, 33) * k1; v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); temp = z; z = x; x = temp; s += 64; len -= 64; } while (len != 0); return HashLen16(HashLen16(v.first, w.first) + ShiftMix(y) * k1 + z, HashLen16(v.second, w.second) + x); } uint64 CityHash64WithSeed(const char *s, size_t len, uint64 seed) { return CityHash64WithSeeds(s, len, k2, seed); } uint64 CityHash64WithSeeds(const char *s, size_t len, uint64 seed0, uint64 seed1) { return HashLen16(CityHash64(s, len) - seed0, seed1); } /* A subroutine for CityHash128(). Returns a decent 128-bit hash for strings * of any length representable in signed long. Based on City and Murmur. */ static uint128 CityMurmur(const char *s, size_t len, uint128 seed) { uint64 a = Uint128Low64(seed); uint64 b = Uint128High64(seed); uint64 c = 0; uint64 d = 0; signed long l = len - 16; if (l <= 0) { /* len <= 16 */ a = ShiftMix(a * k1) * k1; c = b * k1 + HashLen0to16(s, len); d = ShiftMix(a + (len >= 8 ? Fetch64(s) : c)); } else { /* len > 16 */ c = HashLen16(Fetch64(s + len - 8) + k1, a); d = HashLen16(b + len, c + Fetch64(s + len - 16)); a += d; do { a ^= ShiftMix(Fetch64(s) * k1) * k1; a *= k1; b ^= a; c ^= ShiftMix(Fetch64(s + 8) * k1) * k1; c *= k1; d ^= c; s += 16; l -= 16; } while (l > 0); } a = HashLen16(a, c); b = HashLen16(d, b); uint128 result; result.first = (uint64)(a ^ b); result.second = (uint64)(HashLen16(b, a)); return result; } uint128 CityHash128WithSeed(const char *s, size_t len, uint128 seed) { if (len < 128) return CityMurmur(s, len, seed); /* We expect len >= 128 to be the common case. Keep 56 bytes of state: * v, w, x, y, and z. */ uint128 v, w; uint64 x = Uint128Low64(seed); uint64 y = Uint128High64(seed); uint64 z = len * k1; uint64 temp; v.first = Rotate(y ^ k1, 49) * k1 + Fetch64(s); v.second = Rotate(v.first, 42) * k1 + Fetch64(s + 8); w.first = Rotate(y + z, 35) * k1 + x; w.second = Rotate(x + Fetch64(s + 88), 53) * k1; /* This is the same inner loop as CityHash64(), manually unrolled. */ do { x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; x ^= w.second; y += v.first + Fetch64(s + 40); z = Rotate(z + w.first, 33) * k1; v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); temp = z; z = x; x = temp; s += 64; x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; x ^= w.second; y += v.first + Fetch64(s + 40); z = Rotate(z + w.first, 33) * k1; v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); temp = z; z = x; x = temp; s += 64; len -= 128; } while (LIKELY(len >= 128)); x += Rotate(v.first + z, 49) * k0; z += Rotate(w.first, 37) * k0; /* If 0 < len < 128, hash up to 4 chunks * of 32 bytes each from the end of s. */ size_t tail_done; for (tail_done = 0; tail_done < len;) { tail_done += 32; y = Rotate(x + y, 42) * k0 + v.second; w.first += Fetch64(s + len - tail_done + 16); x = x * k0 + w.first; z += w.second + Fetch64(s + len - tail_done); w.second += v.first; v = WeakHashLen32WithSeeds(s + len - tail_done, v.first + z, v.second); } /* At this point our 56 bytes of state should contain more than * enough information for a strong 128-bit hash. We use two * different 56-byte-to-8-byte hashes to get a 16-byte final result. */ x = HashLen16(x, v.first); y = HashLen16(y + z, w.first); uint128 result; result.first = (uint64)(HashLen16(x + v.second, w.second) + y); result.second = (uint64)HashLen16(x + w.second, y + v.second); return result; } uint128 CityHash128(const char *s, size_t len) { uint128 r; if (len >= 16) { r.first = (uint64)(Fetch64(s) ^ k3); r.second = (uint64)(Fetch64(s + 8)); return CityHash128WithSeed(s + 16, len - 16, r); } else if (len >= 8) { r.first = (uint64)(Fetch64(s) ^ (len * k0)); r.second = (uint64)(Fetch64(s + len - 8) ^ k1); return CityHash128WithSeed(NULL, 0, r); } else { r.first = (uint64)k0; r.second = (uint64)k1; return CityHash128WithSeed(s, len, r); } } #ifdef __SSE4_2__ #include "citycrc.h" #include <nmmintrin.h> /* Requires len >= 240. */ static void CityHashCrc256Long(const char *s, size_t len, uint32 seed, uint64 *result) { uint64 a = Fetch64(s + 56) + k0; uint64 b = Fetch64(s + 96) + k0; uint64 c = result[0] = HashLen16(b, len); uint64 d = result[1] = Fetch64(s + 120) * k0 + len; uint64 e = Fetch64(s + 184) + seed; uint64 f = seed; uint64 g = 0; uint64 h = 0; uint64 i = 0; uint64 j = 0; uint64 t = c + d; /* 240 bytes of input per iter. */ size_t iters = len / 240; len -= iters * 240; do { #define CHUNK(multiplier, z) \ do { \ uint64 old_a = a; \ \ a = Rotate(b, 41 ^ z) * multiplier + Fetch64(s); \ b = Rotate(c, 27 ^ z) * multiplier + Fetch64(s + 8); \ c = Rotate(d, 41 ^ z) * multiplier + Fetch64(s + 16); \ d = Rotate(e, 33 ^ z) * multiplier + Fetch64(s + 24); \ e = Rotate(t, 25 ^ z) * multiplier + Fetch64(s + 32); \ t = old_a; \ f = _mm_crc32_u64(f, a); \ g = _mm_crc32_u64(g, b); \ h = _mm_crc32_u64(h, c); \ i = _mm_crc32_u64(i, d); \ j = _mm_crc32_u64(j, e); \ s += 40; \ } while (0) CHUNK(1, 1); CHUNK(k0, 0); CHUNK(1, 1); CHUNK(k0, 0); CHUNK(1, 1); CHUNK(k0, 0); } while (--iters > 0); while (len >= 40) { CHUNK(k0, 0); len -= 40; } if (len > 0) { s = s + len - 40; CHUNK(k0, 0); } j += i << 32; a = HashLen16(a, j); h += g << 32; b += h; c = HashLen16(c, f) + i; d = HashLen16(d, e + result[0]); j += e; i += HashLen16(h, t); e = HashLen16(a, d) + j; f = HashLen16(b, c) + a; g = HashLen16(j, i) + c; result[0] = e + f + g + h; a = ShiftMix((a + g) * k0) * k0 + b; result[1] += a + result[0]; a = ShiftMix(a * k0) * k0 + c; result[2] = a + result[1]; a = ShiftMix((a + e) * k0) * k0; result[3] = a + result[2]; } /* Requires len < 240. */ static void CityHashCrc256Short(const char *s, size_t len, uint64 *result) { char buf[240]; memcpy(buf, s, len); memset(buf + len, 0, 240 - len); CityHashCrc256Long(buf, 240, ~(uint32)(len), result); } void CityHashCrc256(const char *s, size_t len, uint64 *result) { if (LIKELY(len >= 240)) CityHashCrc256Long(s, len, 0, result); else CityHashCrc256Short(s, len, result); } uint128 CityHashCrc128WithSeed(const char *s, size_t len, uint128 seed) { if (len <= 900) { return CityHash128WithSeed(s, len, seed); } else { uint64 result[4]; CityHashCrc256(s, len, result); uint64 u = Uint128High64(seed) + result[0]; uint64 v = Uint128Low64(seed) + result[1]; uint128 crc; crc.first = (uint64)(HashLen16(u, v + result[2])); crc.second = (uint64)(HashLen16(Rotate(v, 32), u * k0 + result[3])); return crc; } } uint128 CityHashCrc128(const char *s, size_t len) { if (len <= 900) { return CityHash128(s, len); } else { uint64 result[4]; CityHashCrc256(s, len, result); uint128 crc; crc.first = (uint64)result[2]; crc.second = (uint64)result[3]; return crc; } } #endif ��������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/client_mgr.c������������������������������������������������������������0000664�0000000�0000000�00000126330�14737566223�0020374�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2013 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @defgroup clntmmt Client management * @{ */ /** * @file client_mgr.c * @author Jim Lieb <jlieb@panasas.com> * @brief Protocol client manager */ #include "config.h" #include <time.h> #include <unistd.h> #include <sys/socket.h> #ifdef RPC_VSOCK #include <linux/vm_sockets.h> #endif /* VSOCK */ #include <sys/types.h> #include <sys/param.h> #include <pthread.h> #include <assert.h> #include <arpa/inet.h> #include <fnmatch.h> #include "gsh_list.h" #include "fsal.h" #include "nfs_core.h" #include "log.h" #include "avltree.h" #include "gsh_types.h" #ifdef USE_DBUS #include "gsh_dbus.h" #endif #include "client_mgr.h" #include "export_mgr.h" #include "server_stats_private.h" #include "abstract_atomic.h" #include "gsh_intrinsic.h" #include "server_stats.h" #include "sal_functions.h" #include "nfs_ip_stats.h" #include "netgroup_cache.h" /* Clients are stored in an AVL tree */ struct client_by_ip { struct avltree t; pthread_rwlock_t cip_lock; struct avltree_node **cache; uint32_t cache_sz; }; static struct client_by_ip client_by_ip; /** * @brief Compute cache slot for an entry * * This function computes a hash slot, taking an address modulo the * number of cache slots (which should be prime). * * @param wt [in] The table * @param ptr [in] Entry address * * @return The computed offset. */ static inline int eip_cache_offsetof(struct client_by_ip *eid, uint64_t k) { return k % eid->cache_sz; } /** * @brief IP address comparator for AVL tree walk * * We tell the difference between IPv4 and IPv6 addresses * by size (4 vs. 16). IPv4 addresses are "lower", left, sorted * first. */ static int client_ip_cmpf(const struct avltree_node *lhs, const struct avltree_node *rhs) { struct gsh_client *lk, *rk; lk = avltree_container_of(lhs, struct gsh_client, node_k); rk = avltree_container_of(rhs, struct gsh_client, node_k); return sockaddr_cmpf(&lk->cl_addrbuf, &rk->cl_addrbuf, true); } /** * @brief Lookup the client manager struct for this client IP * * Lookup the client manager struct by client host IP address. * IPv4 and IPv6 addresses both handled. Sets a reference on the * block. * * @param[in] client_ipaddr The sockaddr struct with the v4/v6 address * @param[in] lookup_only If true, only look up, don't create * * @return pointer to ref locked stats block */ struct gsh_client *get_gsh_client(sockaddr_t *client_ipaddr, bool lookup_only) { struct avltree_node *node = NULL; struct gsh_client *cl; struct server_stats *server_st; struct gsh_client v; void **cache_slot; uint64_t hash = hash_sockaddr(client_ipaddr, true); memcpy(&v.cl_addrbuf, client_ipaddr, sizeof(v.cl_addrbuf)); PTHREAD_RWLOCK_rdlock(&client_by_ip.cip_lock); /* check cache */ cache_slot = (void **)&( client_by_ip.cache[eip_cache_offsetof(&client_by_ip, hash)]); node = (struct avltree_node *)atomic_fetch_voidptr(cache_slot); if (node) { if (client_ip_cmpf(&v.node_k, node) == 0) { /* got it in 1 */ LogDebug(COMPONENT_HASHTABLE_CACHE, "client_mgr cache hit slot %d", eip_cache_offsetof(&client_by_ip, hash)); cl = avltree_container_of(node, struct gsh_client, node_k); goto out; } } /* fall back to AVL */ node = avltree_lookup(&v.node_k, &client_by_ip.t); if (node) { cl = avltree_container_of(node, struct gsh_client, node_k); /* update cache */ atomic_store_voidptr(cache_slot, node); goto out; } else if (lookup_only) { PTHREAD_RWLOCK_unlock(&client_by_ip.cip_lock); return NULL; } PTHREAD_RWLOCK_unlock(&client_by_ip.cip_lock); server_st = gsh_calloc(1, sizeof(*server_st)); cl = &server_st->client; cl->cl_addrbuf = *client_ipaddr; cl->refcnt = 0; if (!sprint_sockip(client_ipaddr, cl->hostaddr_str, sizeof(cl->hostaddr_str))) { (void)strlcpy(cl->hostaddr_str, "<unknown>", sizeof(cl->hostaddr_str)); } LogDebug(COMPONENT_HASHTABLE, "Inserting new gsh_client with IP(%s) hash(%lu)", cl->hostaddr_str, hash); PTHREAD_RWLOCK_wrlock(&client_by_ip.cip_lock); node = avltree_insert(&cl->node_k, &client_by_ip.t); if (node) { gsh_free(server_st); /* somebody beat us to it */ cl = avltree_container_of(node, struct gsh_client, node_k); } else { PTHREAD_RWLOCK_init(&cl->client_lock, NULL); connection_manager__client_init(&cl->connection_manager); /* update cache */ atomic_store_voidptr(cache_slot, &cl->node_k); } out: /* we will hold a ref starting out... */ inc_gsh_client_refcount(cl); PTHREAD_RWLOCK_unlock(&client_by_ip.cip_lock); return cl; } /** * @brief Release the client management struct * * We are done with it, let it go. */ void put_gsh_client(struct gsh_client *client) { int64_t __attribute__((unused)) new_refcnt = atomic_dec_int64_t(&client->refcnt); assert(new_refcnt >= 0); } /** * @brief Remove a client from the AVL and free its resources * * @param client_ipaddr [IN] sockaddr (key) to remove * * @retval 0 if removed * @retval ENOENT if not found * @retval EBUSY if in use */ int remove_gsh_client(sockaddr_t *client_ipaddr) { struct avltree_node *node = NULL; struct avltree_node *cnode = NULL; struct gsh_client *cl = NULL; struct server_stats *server_st; struct gsh_client v; int removed = 0; void **cache_slot; uint64_t hash = hash_sockaddr(client_ipaddr, true); /* Copy the search address into the key */ memcpy(&v.cl_addrbuf, client_ipaddr, sizeof(v.cl_addrbuf)); PTHREAD_RWLOCK_wrlock(&client_by_ip.cip_lock); node = avltree_lookup(&v.node_k, &client_by_ip.t); if (node) { cl = avltree_container_of(node, struct gsh_client, node_k); if (atomic_fetch_int64_t(&cl->refcnt) > 0) { removed = EBUSY; goto out; } cache_slot = (void **)&(client_by_ip.cache[eip_cache_offsetof( &client_by_ip, hash)]); cnode = (struct avltree_node *)atomic_fetch_voidptr(cache_slot); if (node == cnode) atomic_store_voidptr(cache_slot, NULL); avltree_remove(node, &client_by_ip.t); } else { removed = ENOENT; } out: PTHREAD_RWLOCK_unlock(&client_by_ip.cip_lock); if (removed == 0) { server_st = container_of(cl, struct server_stats, client); server_stats_free(&server_st->st); server_stats_allops_free(&server_st->c_all); connection_manager__client_fini(&cl->connection_manager); PTHREAD_RWLOCK_destroy(&cl->client_lock); gsh_free(server_st); } return removed; } /** * @ Walk the tree and do the callback on each node * * @param cb [IN] Callback function * @param state [IN] param block to pass */ int foreach_gsh_client(bool (*cb)(struct gsh_client *cl, void *state), void *state) { struct avltree_node *client_node; struct gsh_client *cl; int cnt = 0; PTHREAD_RWLOCK_rdlock(&client_by_ip.cip_lock); for (client_node = avltree_first(&client_by_ip.t); client_node != NULL; client_node = avltree_next(client_node)) { cl = avltree_container_of(client_node, struct gsh_client, node_k); if (!cb(cl, state)) break; cnt++; } PTHREAD_RWLOCK_unlock(&client_by_ip.cip_lock); return cnt; } #ifdef USE_DBUS /* DBUS helpers */ /* parse the ipaddr string in args */ static bool arg_ipaddr(DBusMessageIter *args, sockaddr_t *sp, char **errormsg) { char *client_addr; unsigned char cl_addrbuf[sizeof(struct in6_addr)]; bool success = true; /* XXX AF_VSOCK addresses are not self-describing--and one might * question whether inet addresses really are, either...so? */ if (args == NULL) { success = false; *errormsg = "message has no arguments"; } else if (dbus_message_iter_get_arg_type(args) != DBUS_TYPE_STRING) { success = false; *errormsg = "arg not a string"; } else { dbus_message_iter_get_basic(args, &client_addr); if (inet_pton(AF_INET, client_addr, cl_addrbuf) == 1) { sp->ss_family = AF_INET; memcpy(&((struct sockaddr_in *)sp)->sin_addr, cl_addrbuf, sizeof(struct in_addr)); } else if (inet_pton(AF_INET6, client_addr, cl_addrbuf) == 1) { sp->ss_family = AF_INET6; memcpy(&((struct sockaddr_in6 *)sp)->sin6_addr, cl_addrbuf, sizeof(struct in6_addr)); } else { success = false; *errormsg = "can't decode client address"; } } return success; } /** @brief lookup gsh_client from input ip-address */ static struct gsh_client *lookup_client(DBusMessageIter *args, char **errormsg) { sockaddr_t sockaddr; struct gsh_client *client = NULL; bool success = true; success = arg_ipaddr(args, &sockaddr, errormsg); if (success) { client = get_gsh_client(&sockaddr, true); if (client == NULL) *errormsg = "Client IP address not found"; } return client; } /* DBUS interface(s) */ /* org.ganesha.nfsd.clienttmgr interface */ /** * @brief Add a client into the client manager via DBUS * * DBUS interface method call * * @param args [IN] dbus argument stream from the message * @param reply [OUT] dbus reply stream for method to fill */ static bool gsh_client_addclient(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_client *client; sockaddr_t sockaddr; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); success = arg_ipaddr(args, &sockaddr, &errormsg); if (success) { client = get_gsh_client(&sockaddr, false); if (client != NULL) { put_gsh_client(client); } else { success = false; errormsg = "No memory to insert client"; } } gsh_dbus_status_reply(&iter, success, errormsg); return true; } static struct gsh_dbus_method cltmgr_add_client = { .name = "AddClient", .method = gsh_client_addclient, .args = { IPADDR_ARG, STATUS_REPLY, END_ARG_LIST } }; static bool gsh_client_removeclient(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { sockaddr_t sockaddr; bool success = false; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (arg_ipaddr(args, &sockaddr, &errormsg)) { switch (remove_gsh_client(&sockaddr)) { case 0: errormsg = "OK"; success = true; break; case ENOENT: errormsg = "Client with that address not found"; break; case EBUSY: errormsg = "Client with that address is in use (busy)"; break; default: errormsg = "Unexpected error"; } } gsh_dbus_status_reply(&iter, success, errormsg); return true; } static struct gsh_dbus_method cltmgr_remove_client = { .name = "RemoveClient", .method = gsh_client_removeclient, .args = { IPADDR_ARG, STATUS_REPLY, END_ARG_LIST } }; struct showclients_state { DBusMessageIter client_iter; }; void client_state_stats(DBusMessageIter *iter, struct gsh_client *cl_node) { DBusMessageIter ss_iter; char *state_type; dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &ss_iter); state_type = "Open"; dbus_message_iter_append_basic(&ss_iter, DBUS_TYPE_STRING, &state_type); dbus_message_iter_append_basic(&ss_iter, DBUS_TYPE_UINT64, &cl_node->state_stats[STATE_TYPE_SHARE]); state_type = "Lock"; dbus_message_iter_append_basic(&ss_iter, DBUS_TYPE_STRING, &state_type); dbus_message_iter_append_basic(&ss_iter, DBUS_TYPE_UINT64, &cl_node->state_stats[STATE_TYPE_LOCK]); state_type = "Delegation"; dbus_message_iter_append_basic(&ss_iter, DBUS_TYPE_STRING, &state_type); dbus_message_iter_append_basic(&ss_iter, DBUS_TYPE_UINT64, &cl_node->state_stats[STATE_TYPE_DELEG]); dbus_message_iter_close_container(iter, &ss_iter); } static bool client_to_dbus(struct gsh_client *cl_node, void *state) { struct showclients_state *iter_state = (struct showclients_state *)state; struct server_stats *cl; char *ipaddr = alloca(SOCK_NAME_MAX); DBusMessageIter struct_iter; cl = container_of(cl_node, struct server_stats, client); if (!sprint_sockip(&cl_node->cl_addrbuf, ipaddr, SOCK_NAME_MAX)) (void)strlcpy(ipaddr, "<unknown>", SOCK_NAME_MAX); dbus_message_iter_open_container(&iter_state->client_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &ipaddr); server_stats_summary(&struct_iter, &cl->st); client_state_stats(&struct_iter, cl_node); gsh_dbus_append_timestamp(&struct_iter, &cl_node->last_update); dbus_message_iter_close_container(&iter_state->client_iter, &struct_iter); return true; } static bool gsh_client_showclients(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { DBusMessageIter iter; struct showclients_state iter_state; struct timespec timestamp; now(×tamp); /* create a reply from the message */ dbus_message_iter_init_append(reply, &iter); gsh_dbus_append_timestamp(&iter, ×tamp); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, CLIENT_CONTAINER, &iter_state.client_iter); (void)foreach_gsh_client(client_to_dbus, (void *)&iter_state); dbus_message_iter_close_container(&iter, &iter_state.client_iter); return true; } static struct gsh_dbus_method cltmgr_show_clients = { .name = "ShowClients", .method = gsh_client_showclients, .args = { TIMESTAMP_REPLY, CLIENTS_REPLY, END_ARG_LIST } }; static bool disconnect_nfsv41_client(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { sockaddr_t sockaddr; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; int connections_destroyed = 0; dbus_message_iter_init_append(reply, &iter); success = arg_ipaddr(args, &sockaddr, &errormsg); struct gsh_client *const client = lookup_client(args, &errormsg); if (client == NULL) { success = false; } else { LogInfo(COMPONENT_NFS_V4, "Found gsh-client for input ip-address. Now disconnecting it"); connections_destroyed = destroy_all_client_connections(client); } gsh_dbus_status_reply(&iter, success, errormsg); dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &connections_destroyed); return true; } static struct gsh_dbus_method cltmgr_disconnect_nfsv41_client = { .name = "DisconnectNfsv41Client", .method = disconnect_nfsv41_client, .args = { IPADDR_ARG, STATUS_REPLY, TOTAL_DESTROYED_CONNECTIONS_REPLY, END_ARG_LIST } }; /* Reset Client specific stats counters */ void reset_client_stats(void) { struct avltree_node *client_node; struct gsh_client *cl; struct server_stats *clnt; PTHREAD_RWLOCK_rdlock(&client_by_ip.cip_lock); for (client_node = avltree_first(&client_by_ip.t); client_node != NULL; client_node = avltree_next(client_node)) { cl = avltree_container_of(client_node, struct gsh_client, node_k); clnt = container_of(cl, struct server_stats, client); reset_gsh_stats(&clnt->st); /* reset stats counter for allops structs */ reset_gsh_allops_stats(&clnt->c_all); } PTHREAD_RWLOCK_unlock(&client_by_ip.cip_lock); } /* Reset Client specific stats counters for allops */ void reset_clnt_allops_stats(void) { struct avltree_node *client_node; struct gsh_client *cl; struct server_stats *clnt; PTHREAD_RWLOCK_rdlock(&client_by_ip.cip_lock); for (client_node = avltree_first(&client_by_ip.t); client_node != NULL; client_node = avltree_next(client_node)) { cl = avltree_container_of(client_node, struct gsh_client, node_k); clnt = container_of(cl, struct server_stats, client); reset_gsh_allops_stats(&clnt->c_all); } PTHREAD_RWLOCK_unlock(&client_by_ip.cip_lock); } static struct gsh_dbus_method *cltmgr_client_methods[] = { &cltmgr_add_client, &cltmgr_remove_client, &cltmgr_show_clients, &cltmgr_disconnect_nfsv41_client, NULL }; static struct gsh_dbus_interface cltmgr_client_table = { .name = "org.ganesha.nfsd.clientmgr", .props = NULL, .methods = cltmgr_client_methods, .signals = NULL }; /* org.ganesha.nfsd.clientstats interface */ /** * DBUS method to get client IO ops statistics */ static bool gsh_client_io_ops(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "OK"; struct gsh_client *client = NULL; bool success = true; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); client = lookup_client(args, &errormsg); if (client == NULL) { success = false; errormsg = "Client IP address not found"; } gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_client_io_ops(&iter, client); if (client != NULL) put_gsh_client(client); return true; } static struct gsh_dbus_method cltmgr_client_io_ops = { .name = "GetClientIOops", .method = gsh_client_io_ops, .args = { IPADDR_ARG, STATUS_REPLY, TIMESTAMP_REPLY, CE_STATS_REPLY, END_ARG_LIST } }; /** * DBUS method to get all ops statistics for a client */ static bool gsh_client_all_ops(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "OK"; struct gsh_client *client = NULL; bool success = true; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (!nfs_param.core_param.enable_CLNTALLSTATS) { errormsg = "Stat counting for all ops for a client is disabled"; success = false; } else { client = lookup_client(args, &errormsg); if (client == NULL) { success = false; errormsg = "Client IP address not found"; } } gsh_dbus_status_reply(&iter, success, errormsg); if (success) { server_dbus_client_all_ops(&iter, client); put_gsh_client(client); } return true; } static struct gsh_dbus_method cltmgr_client_all_ops = { .name = "GetClientAllops", .method = gsh_client_all_ops, .args = { IPADDR_ARG, STATUS_REPLY, TIMESTAMP_REPLY, #ifdef _USE_NFS3 { .name = "clnt_v3", .type = "b", .direction = "out" }, CLNT_V3NLM_OPS_REPLY, #endif #ifdef _USE_NLM { .name = "clnt_nlm", .type = "b", .direction = "out" }, CLNT_V3NLM_OPS_REPLY, #endif { .name = "clnt_v4", .type = "b", .direction = "out" }, CLNT_V4_OPS_REPLY, { .name = "clnt_cmp", .type = "b", .direction = "out" }, CLNT_CMP_OPS_REPLY, END_ARG_LIST } }; #ifdef _USE_NFS3 /** * DBUS method to report NFSv3 I/O statistics * */ static bool get_nfsv3_stats_io(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_client *client = NULL; struct server_stats *server_st = NULL; bool success = true; char *errormsg = NULL; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (!nfs_param.core_param.enable_NFSSTATS) errormsg = "NFS stat counting disabled"; client = lookup_client(args, &errormsg); if (client == NULL) { success = false; if (errormsg == NULL) errormsg = "Client IP address not found"; } else { server_st = container_of(client, struct server_stats, client); if (server_st->st.nfsv3 == NULL) { success = false; errormsg = "Client does not have any NFSv3 activity"; } } gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_v3_iostats(server_st->st.nfsv3, &iter); if (client != NULL) put_gsh_client(client); return true; } static struct gsh_dbus_method cltmgr_show_v3_io = { .name = "GetNFSv3IO", .method = get_nfsv3_stats_io, .args = { IPADDR_ARG, STATUS_REPLY, TIMESTAMP_REPLY, IOSTATS_REPLY, END_ARG_LIST } }; #endif /** * DBUS method to report NFSv40 I/O statistics * */ static bool get_nfsv40_stats_io(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_client *client = NULL; struct server_stats *server_st = NULL; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (!nfs_param.core_param.enable_NFSSTATS) errormsg = "NFS stat counting disabled"; client = lookup_client(args, &errormsg); if (client == NULL) { success = false; if (errormsg == NULL) errormsg = "Client IP address not found"; } else { server_st = container_of(client, struct server_stats, client); if (server_st->st.nfsv40 == NULL) { success = false; errormsg = "Client does not have any NFSv4.0 activity"; } } gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_v40_iostats(server_st->st.nfsv40, &iter); if (client != NULL) put_gsh_client(client); return true; } static struct gsh_dbus_method cltmgr_show_v40_io = { .name = "GetNFSv40IO", .method = get_nfsv40_stats_io, .args = { IPADDR_ARG, STATUS_REPLY, TIMESTAMP_REPLY, IOSTATS_REPLY, END_ARG_LIST } }; /** * DBUS method to report NFSv41 I/O statistics * */ static bool get_nfsv41_stats_io(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_client *client = NULL; struct server_stats *server_st = NULL; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (!nfs_param.core_param.enable_NFSSTATS) errormsg = "NFS stat counting disabled"; client = lookup_client(args, &errormsg); if (client == NULL) { success = false; if (errormsg == NULL) errormsg = "Client IP address not found"; } else { server_st = container_of(client, struct server_stats, client); if (server_st->st.nfsv41 == NULL) { success = false; errormsg = "Client does not have any NFSv4.1 activity"; } } gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_v41_iostats(server_st->st.nfsv41, &iter); if (client != NULL) put_gsh_client(client); return true; } static struct gsh_dbus_method cltmgr_show_v41_io = { .name = "GetNFSv41IO", .method = get_nfsv41_stats_io, .args = { IPADDR_ARG, STATUS_REPLY, TIMESTAMP_REPLY, IOSTATS_REPLY, END_ARG_LIST } }; /** * DBUS method to report NFSv41 layout statistics * */ static bool get_nfsv41_stats_layouts(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_client *client = NULL; struct server_stats *server_st = NULL; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (!nfs_param.core_param.enable_NFSSTATS) errormsg = "NFS stat counting disabled"; client = lookup_client(args, &errormsg); if (client == NULL) { success = false; if (errormsg == NULL) errormsg = "Client IP address not found"; } else { server_st = container_of(client, struct server_stats, client); if (server_st->st.nfsv41 == NULL) { success = false; errormsg = "Client does not have any NFSv4.1 activity"; } } gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_v41_layouts(server_st->st.nfsv41, &iter); if (client != NULL) put_gsh_client(client); return true; } static struct gsh_dbus_method cltmgr_show_v41_layouts = { .name = "GetNFSv41Layouts", .method = get_nfsv41_stats_layouts, .args = { IPADDR_ARG, STATUS_REPLY, TIMESTAMP_REPLY, LAYOUTS_REPLY, END_ARG_LIST } }; /** * DBUS method to report NFSv42 I/O statistics * */ static bool get_nfsv42_stats_io(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_client *client = NULL; struct server_stats *server_st = NULL; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (!nfs_param.core_param.enable_NFSSTATS) errormsg = "NFS stat counting disabled"; client = lookup_client(args, &errormsg); if (client == NULL) { success = false; if (errormsg == NULL) errormsg = "Client IP address not found"; } else { server_st = container_of(client, struct server_stats, client); if (server_st->st.nfsv42 == NULL) { success = false; errormsg = "Client does not have any NFSv4.2 activity"; } } gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_v42_iostats(server_st->st.nfsv42, &iter); if (client != NULL) put_gsh_client(client); return true; } static struct gsh_dbus_method cltmgr_show_v42_io = { .name = "GetNFSv42IO", .method = get_nfsv42_stats_io, .args = { IPADDR_ARG, STATUS_REPLY, TIMESTAMP_REPLY, IOSTATS_REPLY, END_ARG_LIST } }; /** * DBUS method to report NFSv42 layout statistics * */ static bool get_nfsv42_stats_layouts(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_client *client = NULL; struct server_stats *server_st = NULL; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (!nfs_param.core_param.enable_NFSSTATS) errormsg = "NFS stat counting disabled"; client = lookup_client(args, &errormsg); if (client == NULL) { success = false; if (errormsg == NULL) errormsg = "Client IP address not found"; } else { server_st = container_of(client, struct server_stats, client); if (server_st->st.nfsv42 == NULL) { success = false; errormsg = "Client does not have any NFSv4.2 activity"; } } gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_v42_layouts(server_st->st.nfsv42, &iter); if (client != NULL) put_gsh_client(client); return true; } static struct gsh_dbus_method cltmgr_show_v42_layouts = { .name = "GetNFSv42Layouts", .method = get_nfsv42_stats_layouts, .args = { IPADDR_ARG, STATUS_REPLY, TIMESTAMP_REPLY, LAYOUTS_REPLY, END_ARG_LIST } }; /** * DBUS method to report NFSv4 delegation statistics */ static bool get_stats_delegations(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "OK"; struct gsh_client *client = NULL; struct server_stats *server_st = NULL; bool success = true; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); client = lookup_client(args, &errormsg); if (client == NULL) { success = false; errormsg = "Client IP address not found"; } else { server_st = container_of(client, struct server_stats, client); if (server_st->st.deleg == NULL) { success = false; errormsg = "Client does not have any Delegation activity"; } } gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_delegations(server_st->st.deleg, &iter); if (client != NULL) put_gsh_client(client); return true; } static struct gsh_dbus_method cltmgr_show_delegations = { .name = "GetDelegations", .method = get_stats_delegations, .args = { IPADDR_ARG, STATUS_REPLY, TIMESTAMP_REPLY, DELEG_REPLY, END_ARG_LIST } }; #ifdef _USE_9P /** * DBUS method to report 9p I/O statistics * */ static bool get_9p_stats_io(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_client *client = NULL; struct server_stats *server_st = NULL; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); client = lookup_client(args, &errormsg); if (client == NULL) { success = false; if (errormsg == NULL) errormsg = "Client IP address not found"; } else { server_st = container_of(client, struct server_stats, client); if (server_st->st._9p == NULL) { success = false; errormsg = "Client does not have any 9p activity"; } } gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_9p_iostats(server_st->st._9p, &iter); if (client != NULL) put_gsh_client(client); return true; } static struct gsh_dbus_method cltmgr_show_9p_io = { .name = "Get9pIO", .method = get_9p_stats_io, .args = { IPADDR_ARG, STATUS_REPLY, TIMESTAMP_REPLY, IOSTATS_REPLY, END_ARG_LIST } }; /** * DBUS method to report 9p transport statistics * */ static bool get_9p_stats_trans(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_client *client = NULL; struct server_stats *server_st = NULL; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); client = lookup_client(args, &errormsg); if (client == NULL) { success = false; if (errormsg == NULL) errormsg = "Client IP address not found"; } else { server_st = container_of(client, struct server_stats, client); if (server_st->st._9p == NULL) { success = false; errormsg = "Client does not have any 9p activity"; } } gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_9p_transstats(server_st->st._9p, &iter); if (client != NULL) put_gsh_client(client); return true; } static struct gsh_dbus_method cltmgr_show_9p_trans = { .name = "Get9pTrans", .method = get_9p_stats_trans, .args = { IPADDR_ARG, STATUS_REPLY, TIMESTAMP_REPLY, TRANSPORT_REPLY, END_ARG_LIST } }; /** * DBUS method to report 9p protocol operation statistics * */ static bool get_9p_client_op_stats(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_client *client = NULL; struct server_stats *server_st = NULL; u8 opcode; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); client = lookup_client(args, &errormsg); if (client == NULL) { success = false; } else { server_st = container_of(client, struct server_stats, client); if (server_st->st._9p == NULL) { success = false; errormsg = "Client does not have any 9p activity"; } } dbus_message_iter_next(args); if (success) success = arg_9p_op(args, &opcode, &errormsg); gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_9p_opstats(server_st->st._9p, opcode, &iter); if (client != NULL) put_gsh_client(client); return true; } static struct gsh_dbus_method cltmgr_show_9p_op_stats = { .name = "Get9pOpStats", .method = get_9p_client_op_stats, .args = { IPADDR_ARG, _9P_OP_ARG, STATUS_REPLY, TIMESTAMP_REPLY, OP_STATS_REPLY, END_ARG_LIST } }; #endif static struct gsh_dbus_method *cltmgr_stats_methods[] = { #ifdef _USE_NFS3 &cltmgr_show_v3_io, #endif &cltmgr_show_v40_io, &cltmgr_show_v41_io, &cltmgr_show_v41_layouts, &cltmgr_show_v42_io, &cltmgr_show_v42_layouts, &cltmgr_show_delegations, &cltmgr_client_io_ops, &cltmgr_client_all_ops, #ifdef _USE_9P &cltmgr_show_9p_io, &cltmgr_show_9p_trans, &cltmgr_show_9p_op_stats, #endif NULL }; static struct gsh_dbus_interface cltmgr_stats_table = { .name = "org.ganesha.nfsd.clientstats", .props = NULL, .methods = cltmgr_stats_methods, .signals = NULL }; /* DBUS list of interfaces on /org/ganesha/nfsd/ClientMgr */ static struct gsh_dbus_interface *cltmgr_interfaces[] = { &cltmgr_client_table, &cltmgr_stats_table, NULL }; void dbus_client_init(void) { gsh_dbus_register_path("ClientMgr", cltmgr_interfaces); } #endif /* USE_DBUS */ /* Cleanup on shutdown */ void client_mgr_cleanup(void) { PTHREAD_RWLOCK_destroy(&client_by_ip.cip_lock); } struct cleanup_list_element client_mgr_cleanup_element = { .clean = client_mgr_cleanup, }; /** * @brief Initialize client manager */ void client_pkginit(void) { PTHREAD_RWLOCK_init(&client_by_ip.cip_lock, NULL); avltree_init(&client_by_ip.t, client_ip_cmpf, 0); client_by_ip.cache_sz = 32767; client_by_ip.cache = gsh_calloc(client_by_ip.cache_sz, sizeof(struct avltree_node *)); RegisterCleanup(&client_mgr_cleanup_element); } static char *client_types[] = { [PROTO_CLIENT] = "PROTO_CLIENT", [NETWORK_CLIENT] = "NETWORK_CLIENT", [NETGROUP_CLIENT] = "NETGROUP_CLIENT", [WILDCARDHOST_CLIENT] = "WILDCARDHOST_CLIENT", [GSSPRINCIPAL_CLIENT] = "GSSPRINCIPAL_CLIENT", [MATCH_ANY_CLIENT] = "MATCH_ANY_CLIENT", [BAD_CLIENT] = "BAD_CLIENT" }; int StrClient(struct display_buffer *dspbuf, struct base_client_entry *client) { char *paddr = NULL; char *free_paddr = NULL; int b_left = display_start(dspbuf); switch (client->type) { case NETWORK_CLIENT: free_paddr = cidr_to_str(client->client.network.cidr, CIDR_NOFLAGS); paddr = free_paddr; break; case NETGROUP_CLIENT: paddr = client->client.netgroup.netgroupname; break; case WILDCARDHOST_CLIENT: paddr = client->client.wildcard.wildcard; break; case GSSPRINCIPAL_CLIENT: paddr = client->client.gssprinc.princname; break; case MATCH_ANY_CLIENT: paddr = "*"; break; case PROTO_CLIENT: case BAD_CLIENT: paddr = "<unknown>"; break; default: break; } if (client->type > BAD_CLIENT) { b_left = display_printf(dspbuf, "UNKNOWN_CLIENT_TYPE: 0x%08x", client->type); } else { b_left = display_printf(dspbuf, "%s: %s", client_types[client->type], paddr); } gsh_free(free_paddr); return b_left; } void LogClientListEntry(enum log_components component, log_levels_t level, int line, const char *func, const char *tag, struct base_client_entry *entry) { char buf[1024] = "\0"; struct display_buffer dspbuf = { sizeof(buf), buf, buf }; int b_left = display_start(&dspbuf); if (!isLevel(component, level)) return; if (b_left > 0 && tag != NULL) b_left = display_cat(&dspbuf, tag); if (b_left > 0 && level >= NIV_DEBUG) b_left = display_printf(&dspbuf, "%p ", entry); if (b_left > 0) b_left = StrClient(&dspbuf, entry); DisplayLogComponentLevel(component, (char *)__FILE__, line, func, level, "%s", buf); } void FreeClientList(struct glist_head *clients, client_free_func free_func) { struct glist_head *glist; struct glist_head *glistn; glist_for_each_safe(glist, glistn, clients) { struct base_client_entry *client; client = glist_entry(glist, struct base_client_entry, cle_list); glist_del(&client->cle_list); switch (client->type) { case NETWORK_CLIENT: if (client->client.network.cidr != NULL) cidr_free(client->client.network.cidr); break; case NETGROUP_CLIENT: gsh_free(client->client.netgroup.netgroupname); break; case WILDCARDHOST_CLIENT: gsh_free(client->client.wildcard.wildcard); break; case GSSPRINCIPAL_CLIENT: gsh_free(client->client.gssprinc.princname); break; case PROTO_CLIENT: case MATCH_ANY_CLIENT: case BAD_CLIENT: /* Do nothing for these client types */ break; } free_func(client); } } void *base_client_allocator(void) { return gsh_calloc(1, sizeof(struct base_client_entry)); } /** * @brief Expand the client name token into one or more client entries * * @param component [IN] component for logging * @param client_list [IN] the client list this gets linked to (in tail order) * @param client_tok [IN] the name string. We modify it. * @param type_hint [IN] type hint from parser for client_tok * @param cnode [IN] opaque pointer needed for config_proc_error() * @param err_type [OUT] error handling ref * @param cle_allocator [IN] function to allocate a list entry * @param cle_filler [IN] function to fill in a list entry * @param private_data [IN] data to be passed to cle_filler function * * @returns 0 on success, error count on failure */ int add_client(enum log_components component, struct glist_head *client_list, const char *client_tok, enum term_type type_hint, void *cnode, struct config_error_type *err_type, client_list_entry_allocator_t cle_allocator, client_list_entry_filler_t cle_filler, void *private_data) { int errcnt = 0; struct addrinfo *info; CIDR *cidr; int rc; struct base_client_entry *cli; if (cle_allocator == NULL) cle_allocator = base_client_allocator; cli = cle_allocator(); cli->client.network.cidr = NULL; glist_init(&cli->cle_list); switch (type_hint) { case TERM_V4_ANY: cli->type = MATCH_ANY_CLIENT; break; case TERM_NETGROUP: if (strlen(client_tok) > MAXHOSTNAMELEN) { config_proc_error(cnode, err_type, "netgroup (%s) name too long", client_tok); err_type->invalid = true; errcnt++; goto out; } cli->client.netgroup.netgroupname = gsh_strdup(client_tok + 1); cli->type = NETGROUP_CLIENT; break; case TERM_V4CIDR: case TERM_V6CIDR: case TERM_V4ADDR: case TERM_V6ADDR: cidr = cidr_from_str(client_tok); if (cidr == NULL) { switch (type_hint) { case TERM_V4CIDR: config_proc_error( cnode, err_type, "Expected a IPv4 CIDR address, got (%s)", client_tok); break; case TERM_V6CIDR: config_proc_error( cnode, err_type, "Expected a IPv6 CIDR address, got (%s)", client_tok); break; case TERM_V4ADDR: config_proc_error( cnode, err_type, "IPv4 addr (%s) not in presentation format", client_tok); break; case TERM_V6ADDR: config_proc_error( cnode, err_type, "IPv6 addr (%s) not in presentation format", client_tok); break; default: break; } err_type->invalid = true; errcnt++; goto out; } cli->client.network.cidr = cidr; cli->type = NETWORK_CLIENT; break; case TERM_REGEX: if (strlen(client_tok) > MAXHOSTNAMELEN) { config_proc_error(cnode, err_type, "Wildcard client (%s) name too long", client_tok); err_type->invalid = true; errcnt++; goto out; } cli->client.wildcard.wildcard = gsh_strdup(client_tok); cli->type = WILDCARDHOST_CLIENT; break; case TERM_TOKEN: /* only dns names now. */ rc = gsh_getaddrinfo(client_tok, NULL, NULL, &info, nfs_param.core_param.enable_AUTHSTATS); if (rc == 0) { struct addrinfo *ap, *ap_last = NULL; struct in_addr in_addr_last; struct in6_addr in6_addr_last; for (ap = info; ap != NULL; ap = ap->ai_next) { LogFullDebug( COMPONENT_EXPORT, "flags=%d family=%d socktype=%d protocol=%d addrlen=%d name=%s", ap->ai_flags, ap->ai_family, ap->ai_socktype, ap->ai_protocol, (int)ap->ai_addrlen, ap->ai_canonname); if (cli == NULL) { cli = cle_allocator(); glist_init(&cli->cle_list); } if (ap->ai_family == AF_INET && (ap->ai_socktype == SOCK_STREAM || ap->ai_socktype == SOCK_DGRAM)) { struct in_addr infoaddr = ((struct sockaddr_in *) ap->ai_addr) ->sin_addr; if (ap_last != NULL && ap_last->ai_family == ap->ai_family && memcmp(&infoaddr, &in_addr_last, sizeof(struct in_addr)) == 0) continue; cli->client.network.cidr = cidr_from_inaddr(&infoaddr); cli->type = NETWORK_CLIENT; ap_last = ap; in_addr_last = infoaddr; } else if (ap->ai_family == AF_INET6 && (ap->ai_socktype == SOCK_STREAM || ap->ai_socktype == SOCK_DGRAM)) { struct in6_addr infoaddr = ((struct sockaddr_in6 *) ap->ai_addr) ->sin6_addr; if (ap_last != NULL && ap_last->ai_family == ap->ai_family && !memcmp(&infoaddr, &in6_addr_last, sizeof(struct in6_addr))) continue; /* IPv6 address */ cli->client.network.cidr = cidr_from_in6addr(&infoaddr); cli->type = NETWORK_CLIENT; ap_last = ap; in6_addr_last = infoaddr; } else { continue; } if (cle_filler != NULL) cle_filler(cli, private_data); else LogMidDebug_ClientListEntry(component, "", cli); glist_add_tail(client_list, &cli->cle_list); cli = NULL; /* let go of it */ } freeaddrinfo(info); goto out; } else { config_proc_error(cnode, err_type, "Client (%s) not found because %s", client_tok, gai_strerror(rc)); err_type->bogus = true; errcnt++; } break; default: config_proc_error(cnode, err_type, "Expected a client, got a %s for (%s)", config_term_desc(type_hint), client_tok); err_type->bogus = true; errcnt++; goto out; } if (cle_filler != NULL) cle_filler(cli, private_data); else LogMidDebug_ClientListEntry(component, "", cli); glist_add_tail(client_list, &cli->cle_list); cli = NULL; out: gsh_free(cli); return errcnt; } /** * @brief Match a specific client in a client list * * @param[in] hostaddr Host to search for * @param[in] clients Client list to search * * @return the client entry or NULL if failure. */ struct base_client_entry *client_match(enum log_components component, const char *str, sockaddr_t *clientaddr, struct glist_head *clients) { struct glist_head *glist; int rc; int ipvalid = -1; /* -1 need to print, 0 - invalid, 1 - ok */ char hostname[NI_MAXHOST]; char ipstring[SOCK_NAME_MAX]; CIDR *host_prefix = NULL; struct base_client_entry *client; sockaddr_t alt_hostaddr; sockaddr_t *hostaddr = NULL; hostaddr = convert_ipv6_to_ipv4(clientaddr, &alt_hostaddr); if (isMidDebug(component)) { char ipstring[SOCK_NAME_MAX]; struct display_buffer dspbuf = { sizeof(ipstring), ipstring, ipstring }; display_sockip(&dspbuf, hostaddr); LogMidDebug(component, "Check for address %s%s", ipstring, str ? str : ""); } glist_for_each(glist, clients) { client = glist_entry(glist, struct base_client_entry, cle_list); LogMidDebug_ClientListEntry(component, "Match V4: ", client); switch (client->type) { case NETWORK_CLIENT: if (host_prefix == NULL) { if (hostaddr->ss_family == AF_INET6) { host_prefix = cidr_from_in6addr( &((struct sockaddr_in6 *) hostaddr) ->sin6_addr); } else { host_prefix = cidr_from_inaddr( &((struct sockaddr_in *)hostaddr) ->sin_addr); } } if (cidr_contains(client->client.network.cidr, host_prefix) == 0) { goto out; } break; case NETGROUP_CLIENT: /* Try to get the entry from th IP/name cache */ rc = nfs_ip_name_get(hostaddr, hostname, sizeof(hostname)); if (rc == IP_NAME_NOT_FOUND) { /* IPaddr was not cached, add it to the cache */ rc = nfs_ip_name_add(hostaddr, hostname, sizeof(hostname)); } if (rc != IP_NAME_SUCCESS) break; /* Fatal failure */ /* At this point 'hostname' should contain the * name that was found */ if (ng_innetgr(client->client.netgroup.netgroupname, hostname)) { goto out; } break; case WILDCARDHOST_CLIENT: /* Now checking for IP wildcards */ if (ipvalid < 0) ipvalid = sprint_sockip(hostaddr, ipstring, sizeof(ipstring)); if (ipvalid && (fnmatch(client->client.wildcard.wildcard, ipstring, FNM_PATHNAME) == 0)) { goto out; } /* Try to get the entry from th IP/name cache */ rc = nfs_ip_name_get(hostaddr, hostname, sizeof(hostname)); if (rc == IP_NAME_NOT_FOUND) { /* IPaddr was not cached, add it to the cache */ /** @todo this change from 1.5 is not IPv6 * useful. come back to this and use the * string from client mgr inside op_context... */ rc = nfs_ip_name_add(hostaddr, hostname, sizeof(hostname)); } if (rc != IP_NAME_SUCCESS) break; /* At this point 'hostname' should contain the * name that was found */ if (fnmatch(client->client.wildcard.wildcard, hostname, FNM_PATHNAME) == 0) { goto out; } break; case GSSPRINCIPAL_CLIENT: /** @todo BUGAZOMEU a completer lors de l'integration de RPCSEC_GSS */ LogCrit(COMPONENT_EXPORT, "Unsupported type GSS_PRINCIPAL_CLIENT"); break; case MATCH_ANY_CLIENT: goto out; case BAD_CLIENT: default: continue; } } client = NULL; out: if (host_prefix != NULL) cidr_free(host_prefix); return client; } bool haproxy_match(SVCXPRT *xprt) { struct base_client_entry *host = NULL; if (glist_empty(&nfs_param.core_param.haproxy_hosts)) return false; /* Does the host match anyone on the host list? */ host = client_match(COMPONENT_DISPATCH, " for HAProxy", &xprt->xp_proxy.ss, &nfs_param.core_param.haproxy_hosts); return host != NULL; } /** @} */ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/delayed_exec.c����������������������������������������������������������0000664�0000000�0000000�00000022236�14737566223�0020664�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright (c) 2013, The Linux Box Corporation * * Some portions copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @addtogroup delayed * @{ */ /** * @file delayed_exec.c * @author Adam C. Emerson <aemerson@linuxbox.com> * @brief Implementation of the delayed execution system */ #include "config.h" #include <pthread.h> #ifdef LINUX #include <sys/signal.h> #elif FREEBSD #include <signal.h> #endif #include <urcu-bp.h> #include "abstract_mem.h" #include "delayed_exec.h" #include "log.h" #include "avltree.h" #include "misc/queue.h" #include "gsh_intrinsic.h" #include "common_utils.h" /** * @brief A list of tasks */ LIST_HEAD(delayed_tasklist, delayed_task); /** * @brief All tasks executing at a given time. */ struct delayed_multi { struct timespec realtime; /*< A wallclock time at which to perform the given task. */ struct delayed_tasklist list; /*< The linked list of tasks to perform. */ struct avltree_node node; /*< Our node in the tree */ }; /** * @brief An individual delayed task */ struct delayed_task { /** Function for delayed task */ void (*func)(void *); /** Argument for delayed task */ void *arg; /** Link in the task list. */ LIST_ENTRY(delayed_task) link; }; /** * @brief A list of threads */ LIST_HEAD(delayed_threadlist, delayed_thread); /** * @brief An individual executor thread */ struct delayed_thread { pthread_t id; /*< Thread id */ LIST_ENTRY(delayed_thread) link; /*< Link in the thread list. */ }; /** * @{ * Delayed execution state. */ /** list of all threads */ static struct delayed_threadlist thread_list; /** Mutex for delayed execution */ static pthread_mutex_t dle_mtx; /** Condition variable for delayed execution */ static pthread_cond_t dle_cv; /** The timer tree */ static struct avltree tree; /** * @brief Possible states for the delayed executor */ enum delayed_state { delayed_running, /*< Executor is running */ delayed_stopping /*< Executor is stopping */ }; /** State for the executor */ static enum delayed_state delayed_state; enum delayed_employment { delayed_employed, /*< Work is available, do it. */ delayed_on_break, /*< Work is available, but wait for it */ delayed_unemployed /*< No work is available, wait indefinitely */ }; /** @} */ static int comparator(const struct avltree_node *a, const struct avltree_node *b) { struct delayed_multi *p = avltree_container_of(a, struct delayed_multi, node); struct delayed_multi *q = avltree_container_of(b, struct delayed_multi, node); return gsh_time_cmp(&p->realtime, &q->realtime); } /** * @brief Get a task to perform * * This function must be called with the mutex held. * * @param[out] when The time for which to wait, if there is one * @param[out] func The function to execute, if there is one * @param[out] arg The argument to supply, if there is one * * @retval delayed_employed if there is a task to perform now. Its * argument and function are stored in @c arg and @c func. * @retval delayed_on_break if there is work but it is not to be * performed yet. The time at which it is to be performed is * set in *when. * @retval delayed_unemployed if there is no work at all. The caller * should wait indefinitely to be signalled. */ static enum delayed_employment delayed_get_work(struct timespec *when, void (**func)(void *), void **arg) { struct avltree_node *first = avltree_first(&tree); struct delayed_multi *mul; struct timespec current; struct delayed_task *task; if (first == NULL) return delayed_unemployed; now(¤t); mul = avltree_container_of(first, struct delayed_multi, node); if (gsh_time_cmp(&mul->realtime, ¤t) > 0) { *when = mul->realtime; return delayed_on_break; } task = LIST_FIRST(&mul->list); *func = task->func; *arg = task->arg; LIST_REMOVE(task, link); gsh_free(task); if (LIST_EMPTY(&mul->list)) { avltree_remove(first, &tree); gsh_free(mul); } return delayed_employed; } /** * @brief Thread function to execute delayed tasks * * @param[in] arg The thread entry (cast to void) * * @return NULL, always and forever. */ void *delayed_thread(void *arg) { struct delayed_thread *thr = arg; int old_type = 0; int old_state = 0; sigset_t old_sigmask; SetNameFunction("Async"); rcu_register_thread(); /* Explicitly and definitely enable cancellation */ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_state); /** * @see fridgethr_start_routine on asynchronous cancellation */ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old_type); pthread_sigmask(SIG_SETMASK, NULL, &old_sigmask); PTHREAD_MUTEX_lock(&dle_mtx); while (delayed_state == delayed_running) { struct timespec then; void (*func)(void *); void *farg; switch (delayed_get_work(&then, &func, &farg)) { case delayed_unemployed: pthread_cond_wait(&dle_cv, &dle_mtx); break; case delayed_on_break: pthread_cond_timedwait(&dle_cv, &dle_mtx, &then); break; case delayed_employed: PTHREAD_MUTEX_unlock(&dle_mtx); func(farg); PTHREAD_MUTEX_lock(&dle_mtx); break; } } LIST_REMOVE(thr, link); if (LIST_EMPTY(&thread_list)) pthread_cond_broadcast(&dle_cv); PTHREAD_MUTEX_unlock(&dle_mtx); gsh_free(thr); rcu_unregister_thread(); return NULL; } /** * @brief Initialize and start the delayed execution system */ void delayed_start(void) { /* Make this a parameter later */ const size_t threads_to_start = 1; /* Thread attributes */ pthread_attr_t attr; /* Thread index */ int i; PTHREAD_MUTEX_init(&dle_mtx, NULL); PTHREAD_COND_init(&dle_cv, NULL); LIST_INIT(&thread_list); avltree_init(&tree, comparator, 0); if (threads_to_start == 0) { LogFatal(COMPONENT_THREAD, "You can't execute tasks with zero threads."); } PTHREAD_ATTR_init(&attr); PTHREAD_ATTR_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); PTHREAD_MUTEX_lock(&dle_mtx); delayed_state = delayed_running; for (i = 0; i < threads_to_start; ++i) { struct delayed_thread *thread = gsh_malloc(sizeof(struct delayed_thread)); int rc = 0; rc = PTHREAD_create(&thread->id, &attr, delayed_thread, thread); if (rc != 0) { LogFatal(COMPONENT_THREAD, "Unable to start delayed executor: %d", rc); } LIST_INSERT_HEAD(&thread_list, thread, link); } PTHREAD_MUTEX_unlock(&dle_mtx); PTHREAD_ATTR_destroy(&attr); } /** * @brief Shut down the delayed executor */ void delayed_shutdown(void) { int rc = -1; struct timespec then; now(&then); then.tv_sec += 120; PTHREAD_MUTEX_lock(&dle_mtx); delayed_state = delayed_stopping; pthread_cond_broadcast(&dle_cv); while ((rc != ETIMEDOUT) && !LIST_EMPTY(&thread_list)) rc = pthread_cond_timedwait(&dle_cv, &dle_mtx, &then); if (!LIST_EMPTY(&thread_list)) { struct delayed_thread *thr; LogMajor( COMPONENT_THREAD, "Delayed executor threads not shutting down cleanly, taking harsher measures."); while ((thr = LIST_FIRST(&thread_list)) != NULL) { LIST_REMOVE(thr, link); pthread_cancel(thr->id); gsh_free(thr); } } PTHREAD_MUTEX_unlock(&dle_mtx); PTHREAD_MUTEX_destroy(&dle_mtx); PTHREAD_COND_destroy(&dle_cv); } /** * @brief Submit a new task * * @param[in] func The function to run * @param[in] arg The argument to run it with * @param[in] delay The delay in nanoseconds * * @retval 0 on success. * @retval ENOMEM on inability to allocate memory causing other than success. */ int delayed_submit(void (*func)(void *), void *arg, nsecs_elapsed_t delay) { struct delayed_multi *mul = NULL; struct delayed_task *task = NULL; struct avltree_node *collision = NULL; struct avltree_node *first = NULL; mul = gsh_malloc(sizeof(struct delayed_multi)); task = gsh_malloc(sizeof(struct delayed_task)); now(&mul->realtime); timespec_add_nsecs(delay, &mul->realtime); PTHREAD_MUTEX_lock(&dle_mtx); first = avltree_first(&tree); collision = avltree_insert(&mul->node, &tree); if (unlikely(collision)) { gsh_free(mul); /* There is already a node for this exact time in the tree. Add our task to its list. */ mul = avltree_container_of(collision, struct delayed_multi, node); } else { LIST_INIT(&mul->list); } task->func = func; task->arg = arg; LIST_INSERT_HEAD(&mul->list, task, link); if (!first || comparator(&mul->node, first) < 0) pthread_cond_broadcast(&dle_cv); PTHREAD_MUTEX_unlock(&dle_mtx); return 0; } /** @} */ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/ds.c��������������������������������������������������������������������0000664�0000000�0000000�00000033006�14737566223�0016654�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright (C) CohortFS (2014) * contributor : William Allen Simpson <bill@CohortFS.com> * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file ds.c * @brief Data Server parsing and management */ #include "config.h" #include "config_parsing.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "FSAL/fsal_commonlib.h" #include "pnfs_utils.h" /** * @brief Servers are stored in an AVL tree with front-end cache. * * @note number of cache slots should be prime. */ #define SERVER_BY_ID_CACHE_SIZE 193 struct server_by_id { pthread_rwlock_t sid_lock; struct avltree t; struct avltree_node *cache[SERVER_BY_ID_CACHE_SIZE]; }; static struct server_by_id server_by_id; /** List of all active data servers, * protected by server_by_id.lock */ static struct glist_head dslist; /** * @brief Compute cache slot for an entry * * This function computes a hash slot, taking an address modulo the * number of cache slots (which should be prime). * * @param k [in] Entry index value * * @return The computed offset. */ static inline uint16_t id_cache_offsetof(uint16_t k) { return k % SERVER_BY_ID_CACHE_SIZE; } /** * @brief Server id comparator for AVL tree walk * */ static int server_id_cmpf(const struct avltree_node *lhs, const struct avltree_node *rhs) { struct fsal_pnfs_ds *lk, *rk; lk = avltree_container_of(lhs, struct fsal_pnfs_ds, ds_node); rk = avltree_container_of(rhs, struct fsal_pnfs_ds, ds_node); if (lk->id_servers != rk->id_servers) return (lk->id_servers < rk->id_servers) ? -1 : 1; else return 0; } /** * @brief Allocate the pDS entry. * * @return pointer to fsal_pnfs_ds. * NULL on allocation errors. */ struct fsal_pnfs_ds *pnfs_ds_alloc(void) { return gsh_calloc(1, sizeof(struct fsal_pnfs_ds)); } /** * @brief Free the pDS entry. */ void pnfs_ds_free(struct fsal_pnfs_ds *pds) { if (!pds->ds_refcount) return; gsh_free(pds); } /** * @brief Insert the pDS entry into the AVL tree. * * @param exp [IN] the server entry * * @return false on failure. */ bool pnfs_ds_insert(struct fsal_pnfs_ds *pds) { struct avltree_node *node; void **cache_slot = (void **)&( server_by_id.cache[id_cache_offsetof(pds->id_servers)]); /* we will hold a ref starting out... */ assert(pds->ds_refcount == 1); PTHREAD_RWLOCK_wrlock(&server_by_id.sid_lock); node = avltree_insert(&pds->ds_node, &server_by_id.t); if (node) { /* somebody beat us to it */ PTHREAD_RWLOCK_unlock(&server_by_id.sid_lock); return false; } /* update cache */ atomic_store_voidptr(cache_slot, &pds->ds_node); glist_add_tail(&dslist, &pds->ds_list); pnfs_ds_get_ref(pds); /* == 2 */ if (pds->mds_export != NULL) { /* also bump related export for duration */ get_gsh_export_ref(pds->mds_export); pds->mds_export->has_pnfs_ds = true; } PTHREAD_RWLOCK_unlock(&server_by_id.sid_lock); return true; } /** * @brief Lookup the fsal_pnfs_ds struct for this server id * * Lookup the fsal_pnfs_ds struct by id_servers. * Server ids are assigned by the config file and carried about * by file handles. * * NOTE: DOES NOT take a reference on mds_export * * @param id_servers [IN] the server id extracted from the handle * * @return pointer to ref locked server */ struct fsal_pnfs_ds *pnfs_ds_get(uint16_t id_servers) { struct fsal_pnfs_ds v; struct avltree_node *node; struct fsal_pnfs_ds *pds; void **cache_slot = (void **)&(server_by_id.cache[id_cache_offsetof(id_servers)]); v.id_servers = id_servers; PTHREAD_RWLOCK_rdlock(&server_by_id.sid_lock); /* check cache */ node = (struct avltree_node *)atomic_fetch_voidptr(cache_slot); if (node) { pds = avltree_container_of(node, struct fsal_pnfs_ds, ds_node); if (pds->id_servers == id_servers) { /* got it in 1 */ LogDebug(COMPONENT_HASHTABLE_CACHE, "server_by_id cache hit slot %d", id_cache_offsetof(id_servers)); goto out; } } /* fall back to AVL */ node = avltree_lookup(&v.ds_node, &server_by_id.t); if (node) { pds = avltree_container_of(node, struct fsal_pnfs_ds, ds_node); /* update cache */ atomic_store_voidptr(cache_slot, node); } else { PTHREAD_RWLOCK_unlock(&server_by_id.sid_lock); return NULL; } out: pnfs_ds_get_ref(pds); PTHREAD_RWLOCK_unlock(&server_by_id.sid_lock); return pds; } /** * @brief Release the fsal_pnfs_ds struct * * @param exp [IN] the server entry */ void pnfs_ds_put(struct fsal_pnfs_ds *pds) { int32_t refcount = atomic_dec_int32_t(&pds->ds_refcount); if (refcount != 0) { assert(refcount > 0); return; } /* free resources */ fsal_pnfs_ds_fini(pds); pnfs_ds_free(pds); } /** * @brief Remove the pDS entry from the AVL tree and from the FSAL. * * @param id_servers [IN] the server id extracted from the handle */ void pnfs_ds_remove(uint16_t id_servers) { struct fsal_pnfs_ds v; struct avltree_node *node; struct fsal_pnfs_ds *pds = NULL; void **cache_slot = (void **)&(server_by_id.cache[id_cache_offsetof(id_servers)]); v.id_servers = id_servers; PTHREAD_RWLOCK_wrlock(&server_by_id.sid_lock); node = avltree_lookup(&v.ds_node, &server_by_id.t); if (node) { struct avltree_node *cnode = (struct avltree_node *)atomic_fetch_voidptr(cache_slot); /* Remove from the AVL cache and tree */ if (node == cnode) atomic_store_voidptr(cache_slot, NULL); avltree_remove(node, &server_by_id.t); pds = avltree_container_of(node, struct fsal_pnfs_ds, ds_node); /* Remove the DS from the DS list */ glist_del(&pds->ds_list); } PTHREAD_RWLOCK_unlock(&server_by_id.sid_lock); /* removal has a once-only semantic */ if (pds != NULL) { if (pds->mds_export != NULL) { struct req_op_context op_context; /* special case: avoid lookup of related export. * get_gsh_export_ref() was bumped in pnfs_ds_insert() * * once-only, so no need for lock here. * do not pre-clear related export (mds_export). * always check pnfs_ds_status instead. * * We need an op_context to release an export ref, since * we are using this op context to release a reference * via release_op_context, we don't need to take a * reference here. */ init_op_context_simple(&op_context, pds->mds_export, pds->mds_export->fsal_export); release_op_context(); } /* Release table reference to the server. * Release of resources will occur on last reference. * Which may or may not be from this call. * * Releases the reference taken in pnfs_ds_insert */ pnfs_ds_put(pds); /* Also drop from FSAL. Instead of pDS thread, * relying on export cleanup thread. * * Releases the reference taken in fsal_pnfs_ds_init */ pnfs_ds_put(pds); } } /** * @brief Remove all DSs left in the system * * Make sure all DSs are freed on shutdown. This will catch all DSs not * associated with an export. * */ void remove_all_dss(void) { struct glist_head tmplist, *glist, *glistn; struct fsal_pnfs_ds *pds; glist_init(&tmplist); /* pnfs_ds_remove() take the lock, so move the entire list to a tmp head * under the lock, then process it outside the lock. */ PTHREAD_RWLOCK_wrlock(&server_by_id.sid_lock); glist_splice_tail(&tmplist, &dslist); PTHREAD_RWLOCK_unlock(&server_by_id.sid_lock); /* Now we can safely process the list without the lock */ glist_for_each_safe(glist, glistn, &tmplist) { pds = glist_entry(glist, struct fsal_pnfs_ds, ds_list); /* Remove and destroy the fsal_pnfs_ds */ pnfs_ds_remove(pds->id_servers); } } /** * @brief Commit a FSAL sub-block * * Use the Name parameter passed in via the self_struct to lookup the * fsal. If the fsal is not loaded (yet), load it and call its init. * * Create the pDS and pass the FSAL sub-block to it so that the * fsal method can process the rest of the parameters in the block */ static int fsal_cfg_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { struct fsal_args *fp = self_struct; struct fsal_module **pds_fsal = link_mem; struct fsal_pnfs_ds *pds = container_of(pds_fsal, struct fsal_pnfs_ds, fsal); struct fsal_module *fsal; struct req_op_context op_context; fsal_status_t status; int errcnt; /* Initialize op_context */ init_op_context_simple(&op_context, NULL, NULL); /* The following returns a reference to the FSAL, if ds creation * succeeds the reference will be passed off to the ds, otherwise it * will be put below. */ errcnt = fsal_load_init(node, fp->name, &fsal, err_type); if (errcnt > 0) goto err; status = fsal->m_ops.create_fsal_pnfs_ds(fsal, node, &pds); /* If the create succeeded, an additional FSAL reference was taken, * since otherwise we are done with the FSAL, put the reference given * by fsal_load_init. */ fsal_put(fsal); if (status.major != ERR_FSAL_NO_ERROR) { LogCrit(COMPONENT_CONFIG, "Could not create pNFS DS"); LogFullDebug(COMPONENT_FSAL, "FSAL %s fsal_refcount %" PRIu32, fsal->name, atomic_fetch_int32_t(&fsal->refcount)); err_type->init = true; errcnt++; goto err; } LogEvent(COMPONENT_CONFIG, "DS %d fsal config commit at FSAL (%s) with path (%s)", pds->id_servers, pds->fsal->name, pds->fsal->path); err: release_op_context(); return errcnt; } /** * @brief pNFS DS block handlers */ /** * @brief Initialize the DS block */ static void *pds_init(void *link_mem, void *self_struct) { static struct fsal_pnfs_ds special_ds; if (link_mem == (void *)~0UL) { /* This is the special case of no config. We cannot malloc * this, as it's never committed, so it's leaked. */ memset(&special_ds, 0, sizeof(special_ds)); return &special_ds; } else if (self_struct == NULL) { return pnfs_ds_alloc(); } else { /* free resources case */ pnfs_ds_free(self_struct); return NULL; } } /** * @brief Commit the DS block * * Validate the DS level parameters? fsal and client * parameters are already done. */ static int pds_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { struct fsal_pnfs_ds *pds = self_struct; struct fsal_pnfs_ds *probe = pnfs_ds_get(pds->id_servers); /* redundant probe before insert??? */ if (probe != NULL) { LogDebug(COMPONENT_CONFIG, "Server %d already exists!", pds->id_servers); pnfs_ds_put(probe); err_type->exists = true; return 1; } if (!pnfs_ds_insert(pds)) { LogCrit(COMPONENT_CONFIG, "Server id %d already in use.", pds->id_servers); err_type->exists = true; return 1; } LogEvent(COMPONENT_CONFIG, "DS %d created at FSAL (%s) with path (%s)", pds->id_servers, pds->fsal->name, pds->fsal->path); return 0; } /** * @brief Display the DS block */ static void pds_display(const char *step, void *node, void *link_mem, void *self_struct) { struct fsal_pnfs_ds *pds = self_struct; struct fsal_module *fsal = pds->fsal; LogMidDebug(COMPONENT_CONFIG, "%s %p DS %d FSAL (%s) with path (%s)", step, pds, pds->id_servers, fsal->name, fsal->path); } /** * @brief Table of FSAL sub-block parameters * * NOTE: this points to a struct that is private to * fsal_cfg_commit. */ static struct config_item fsal_params[] = { CONF_ITEM_STR("Name", 1, 10, NULL, fsal_args, name), /* cheater union */ CONFIG_EOL }; /** * @brief Table of DS block parameters * * NOTE: the Client and FSAL sub-blocks must be the *last* * two entries in the list. This is so all other * parameters have been processed before these sub-blocks * are processed. */ static struct config_item pds_items[] = { CONF_ITEM_UI16("Number", 0, UINT16_MAX, 0, fsal_pnfs_ds, id_servers), CONF_RELAX_BLOCK("FSAL", fsal_params, fsal_init, fsal_cfg_commit, fsal_pnfs_ds, fsal), CONFIG_EOL }; /** * @brief Top level definition for each DS block */ static struct config_block pds_block = { .dbus_interface_name = "org.ganesha.nfsd.config.ds.%d", .blk_desc.name = "DS", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = pds_init, .blk_desc.u.blk.params = pds_items, .blk_desc.u.blk.commit = pds_commit, .blk_desc.u.blk.display = pds_display }; /** * @brief Read the DS blocks from the parsed configuration file. * * @param[in] in_config The file that contains the DS list * * @return A negative value on error; * otherwise, the number of DS blocks. */ int ReadDataServers(config_file_t in_config, struct config_error_type *err_type) { int rc; rc = load_config_from_parse(in_config, &pds_block, NULL, false, err_type); if (!config_error_is_harmless(err_type)) return -1; return rc; } /* Cleanup on shutdown */ void ds_cleanup(void) { PTHREAD_RWLOCK_destroy(&server_by_id.sid_lock); } struct cleanup_list_element ds_cleanup_element = { .clean = ds_cleanup, }; /** * @brief Initialize server tree */ void server_pkginit(void) { PTHREAD_RWLOCK_init(&server_by_id.sid_lock, NULL); avltree_init(&server_by_id.t, server_id_cmpf, 0); glist_init(&dslist); memset(&server_by_id.cache, 0, sizeof(server_by_id.cache)); RegisterCleanup(&ds_cleanup_element); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/err_inject.c������������������������������������������������������������0000664�0000000�0000000�00000004717�14737566223�0020401�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2011 * Contributor: Frank Filz <ffilzlnx@us.ibm.com> * * -------------------------- * * This program 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 3 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 * 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 * * */ #include "config.h" #include <stdio.h> #include <string.h> #include <pthread.h> #include <sys/stat.h> #include <time.h> #include "nfs_core.h" #include "nfs_exports.h" #include "external_tools.h" #include "common_utils.h" #include "log.h" int worker_delay_time; int next_worker_delay_time; /** * @TODO convert these to admin dbus methods */ #if 0 int getErrInjectInteger(snmp_adm_type_union *param, void *opt) { long option = (long)opt; switch (option) { case 0: param->integer = worker_delay_time; break; case 1: param->integer = next_worker_delay_time; break; default: return 1; } return 0; } int setErrInjectInteger(const snmp_adm_type_union *param, void *opt) { long option = (long)opt; switch (option) { case 0: worker_delay_time = param->integer; break; case 1: next_worker_delay_time = param->integer; break; default: return 1; } return 0; } static register_get_set snmp_error_injection[] = { {"worker_delay", "Delay for each request processed by worker threads", SNMP_ADM_INTEGER, SNMP_ADM_ACCESS_RW, getErrInjectInteger, setErrInjectInteger, (void *)0}, {"next_worker_delay", "Delay for next request processed by worker threads", SNMP_ADM_INTEGER, SNMP_ADM_ACCESS_RW, getErrInjectInteger, setErrInjectInteger, (void *)1}, }; #define SNMPADM_ERROR_INJECTION_COUNT 2 int init_error_injector(void) { if (snmp_adm_register_get_set_function (INJECT_OID, snmp_error_injection, SNMPADM_ERROR_INJECTION_COUNT)) { LogCrit(COMPONENT_INIT, "Error registering error injection to SNMP"); return 2; } return 0; } #endif �������������������������������������������������nfs-ganesha-6.5/src/support/export_mgr.c������������������������������������������������������������0000664�0000000�0000000�00000231751�14737566223�0020443�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2013 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @defgroup Filesystem export management * @{ */ /** * @file export_mgr.c * @author Jim Lieb <jlieb@panasas.com> * @brief export manager */ #include "config.h" #include <time.h> #include <unistd.h> #include <stdint.h> #include <sys/types.h> #include <sys/param.h> #include <pthread.h> #include <assert.h> #include <arpa/inet.h> #include "fsal.h" #include "nfs_core.h" #include "log.h" #include "avltree.h" #include "gsh_types.h" #ifdef USE_DBUS #include "gsh_dbus.h" #endif #include "export_mgr.h" #include "client_mgr.h" #include "server_stats_private.h" #include "server_stats.h" #include "abstract_atomic.h" #include "gsh_intrinsic.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "pnfs_utils.h" #include "idmapper.h" /** Mutex to serialize export admin operations. */ pthread_mutex_t export_admin_mutex; uint64_t export_admin_counter; struct timespec nfs_stats_time; struct timespec fsal_stats_time; struct timespec v3_full_stats_time; struct timespec v4_full_stats_time; struct timespec auth_stats_time; struct timespec clnt_allops_stats_time; /** * @brief Exports are stored in an AVL tree with front-end cache. * * @note number of cache slots should be prime. */ #define EXPORT_BY_ID_CACHE_SIZE 769 struct export_by_id { pthread_rwlock_t eid_lock; struct avltree t; struct avltree_node *cache[EXPORT_BY_ID_CACHE_SIZE]; }; static struct export_by_id export_by_id; /** List of all active exports, * protected by export_admin_mutex */ static struct glist_head exportlist = GLIST_HEAD_INIT(exportlist); /** List of exports to be mounted in PseudoFS, * protected by export_admin_mutex */ static struct glist_head mount_work = GLIST_HEAD_INIT(mount_work); /** List of exports to be cleaned up on unexport, * protected by export_admin_mutex */ static struct glist_head unexport_work = GLIST_HEAD_INIT(unexport_work); void export_add_to_mount_work(struct gsh_export *export) { glist_add_tail(&mount_work, &export->exp_work); } void export_add_to_unexport_work(struct gsh_export *export) { glist_add_tail(&unexport_work, &export->exp_work); } struct gsh_export *export_take_mount_work(void) { struct gsh_export *export; export = glist_first_entry(&mount_work, struct gsh_export, exp_work); if (export != NULL) glist_del(&export->exp_work); return export; } /** * @brief Compute cache slot for an entry * * This function computes a hash slot, taking an address modulo the * number of cache slots (which should be prime). * * @param k [in] Entry index value * * @return The computed offset. */ static inline uint16_t eid_cache_offsetof(uint16_t k) { return k % EXPORT_BY_ID_CACHE_SIZE; } /** * @brief Revert export_commit() * * @param export [in] the export just inserted/committed */ void export_revert(struct gsh_export *export) { struct avltree_node *cnode; void **cache_slot = (void **)&( export_by_id.cache[eid_cache_offsetof(export->export_id)]); struct req_op_context op_context; PTHREAD_RWLOCK_wrlock(&export_by_id.eid_lock); cnode = (struct avltree_node *)atomic_fetch_voidptr(cache_slot); if (&export->node_k == cnode) atomic_store_voidptr(cache_slot, NULL); avltree_remove(&export->node_k, &export_by_id.t); glist_del(&export->exp_list); glist_del(&export->exp_work); PTHREAD_RWLOCK_unlock(&export_by_id.eid_lock); init_op_context_simple(&op_context, export, export->fsal_export); if (export->has_pnfs_ds) { /* once-only, so no need for lock here */ export->has_pnfs_ds = false; /* Remove and destroy the fsal_pnfs_ds */ pnfs_ds_remove(export->export_id); } /* Release the sentinel refcount */ release_op_context(); } /** * @brief Export id comparator for AVL tree walk * */ static int export_id_cmpf(const struct avltree_node *lhs, const struct avltree_node *rhs) { struct gsh_export *lk, *rk; lk = avltree_container_of(lhs, struct gsh_export, node_k); rk = avltree_container_of(rhs, struct gsh_export, node_k); if (lk->export_id != rk->export_id) return (lk->export_id < rk->export_id) ? -1 : 1; else return 0; } /** * @brief Allocate a gsh_export entry. * * This is the ONLY function that should allocate gsh_exports * * @return pointer to gsh_export. * */ struct gsh_export *alloc_export(void) { struct export_stats *export_st; struct gsh_export *export; export_st = gsh_calloc(1, sizeof(struct export_stats)); export = &export_st->export; LogFullDebug(COMPONENT_EXPORT, "Allocated export %p", export); glist_init(&export->exp_state_list); glist_init(&export->exp_lock_list); glist_init(&export->exp_nlm_share_list); glist_init(&export->mounted_exports_list); glist_init(&export->clients); /* Take an initial refcount */ export->refcnt = 1; PTHREAD_RWLOCK_init(&export->exp_lock, NULL); return export; } /** * @brief Insert an export list entry into the export manager * * WARNING! This takes a pointer to the container of the exportlist. * The struct exports_stats is the container lots of stuff besides * the exportlist so only pass an object you got from alloc_exportlist. * * @param exp [IN] the exportlist entry to insert * * @retval false on error */ bool insert_gsh_export(struct gsh_export *export) { struct avltree_node *node; void **cache_slot = (void **)&( export_by_id.cache[eid_cache_offsetof(export->export_id)]); PTHREAD_RWLOCK_wrlock(&export_by_id.eid_lock); node = avltree_insert(&export->node_k, &export_by_id.t); if (node) { /* somebody beat us to it */ PTHREAD_RWLOCK_unlock(&export_by_id.eid_lock); return false; } /* take an additional ref for the sentinel reference... */ get_gsh_export_ref(export); /* update cache */ atomic_store_voidptr(cache_slot, &export->node_k); glist_add_tail(&exportlist, &export->exp_list); PTHREAD_RWLOCK_unlock(&export_by_id.eid_lock); return true; } /** * @brief Lookup the export manager struct for this export id * * Lookup the export manager struct by export id. * Export ids are assigned by the config file and carried about * by file handles. * * @param export_id [IN] the export id extracted from the handle * * @return pointer to ref locked export */ struct gsh_export *get_gsh_export(uint16_t export_id) { struct gsh_export v; struct avltree_node *node; struct gsh_export *exp; void **cache_slot = (void **)&(export_by_id.cache[eid_cache_offsetof(export_id)]); v.export_id = export_id; PTHREAD_RWLOCK_rdlock(&export_by_id.eid_lock); /* check cache */ node = (struct avltree_node *)atomic_fetch_voidptr(cache_slot); if (node) { exp = avltree_container_of(node, struct gsh_export, node_k); if (exp->export_id == export_id) { /* got it in 1 */ LogDebug(COMPONENT_HASHTABLE_CACHE, "export_mgr cache hit slot %d", eid_cache_offsetof(export_id)); goto out; } } /* fall back to AVL */ node = avltree_lookup(&v.node_k, &export_by_id.t); if (node) { exp = avltree_container_of(node, struct gsh_export, node_k); /* update cache */ atomic_store_voidptr(cache_slot, node); } else { PTHREAD_RWLOCK_unlock(&export_by_id.eid_lock); LOG_EXPORT(NIV_DEBUG, "Found", NULL, false); return NULL; } out: get_gsh_export_ref(exp); PTHREAD_RWLOCK_unlock(&export_by_id.eid_lock); LOG_EXPORT(NIV_DEBUG, "Found", exp, false); return exp; } /** * @brief Lookup the export manager struct by export path * * Gets an export entry from its path using a substring match and * linear search of the export list, assumes being called with * export manager lock held (such as from within foreach_gsh_export. * If path has a trailing '/', ignore it. * * @param path [IN] the path for the entry to be found. * @param exact_match [IN] the path must match exactly * * @return pointer to ref counted export */ struct gsh_export *get_gsh_export_by_path_locked(char *path, bool exact_match) { struct gsh_export *export; struct glist_head *glist; int len_path = strlen(path); int len_export; struct gsh_export *ret_exp = NULL; int len_ret = 0; if (len_path > 1 && path[len_path - 1] == '/') len_path--; LogFullDebug(COMPONENT_EXPORT, "Searching for export matching path %s", path); glist_for_each(glist, &exportlist) { struct gsh_refstr *ref_fullpath; export = glist_entry(glist, struct gsh_export, exp_list); rcu_read_lock(); ref_fullpath = gsh_refstr_get(rcu_dereference(export->fullpath)); rcu_read_unlock(); if (ref_fullpath == NULL) { LogFatal(COMPONENT_EXPORT, "Export %d has no fullpath", export->export_id); } len_export = strlen(ref_fullpath->gr_val); if (len_path == 0 && len_export == 1) { /* Special case for root match */ ret_exp = export; gsh_refstr_put(ref_fullpath); break; } /* A path shorter than the full path cannot match. * Also skip if this export has a shorter path than * the previous match. */ if (len_path < len_export || len_export < len_ret) { gsh_refstr_put(ref_fullpath); continue; } /* If partial match is not allowed, lengths must be the same */ if (exact_match && len_path != len_export) { gsh_refstr_put(ref_fullpath); continue; } /* if the char in fullpath just after the end of path is not '/' * it is a name token longer, i.e. /mnt/foo != /mnt/foob/ */ if (len_export > 1 && path[len_export] != '/' && path[len_export] != '\0') { gsh_refstr_put(ref_fullpath); continue; } /* we agree on size, now compare the leading substring */ if (strncmp(ref_fullpath->gr_val, path, len_export) == 0) { ret_exp = export; len_ret = len_export; /* If we have found an exact match, exit loop. */ if (len_export == len_path) { gsh_refstr_put(ref_fullpath); break; } } } if (ret_exp != NULL) get_gsh_export_ref(ret_exp); LOG_EXPORT(NIV_DEBUG, "Found", ret_exp, false); return ret_exp; } /** * @brief Lookup the export manager struct by export path * * Gets an export entry from its path using a substring match and * linear search of the export list. * If path has a trailing '/', ignore it. * * @param path [IN] the path for the entry to be found. * @param exact_match [IN] the path must match exactly * * @return pointer to ref counted export */ struct gsh_export *get_gsh_export_by_path(char *path, bool exact_match) { struct gsh_export *exp; PTHREAD_RWLOCK_rdlock(&export_by_id.eid_lock); exp = get_gsh_export_by_path_locked(path, exact_match); PTHREAD_RWLOCK_unlock(&export_by_id.eid_lock); return exp; } /** * @brief Lookup the export manager struct by export pseudo path * * Gets an export entry from its pseudo (if it exists), assumes * being called with export manager lock held (such as from within * foreach_gsh_export. * * @param path [IN] the path for the entry to be found. * @param exact_match [IN] the path must match exactly * * @return pointer to ref counted export */ struct gsh_export *get_gsh_export_by_pseudo_locked(char *path, bool exact_match) { struct gsh_export *export; struct glist_head *glist; int len_path = strlen(path); int len_export; struct gsh_export *ret_exp = NULL; int len_ret = 0; /* Ignore trailing slash in path */ if (len_path > 1 && path[len_path - 1] == '/') len_path--; LogFullDebug(COMPONENT_EXPORT, "Searching for export matching pseudo path %s", path); glist_for_each(glist, &exportlist) { struct gsh_refstr *ref_pseudopath; export = glist_entry(glist, struct gsh_export, exp_list); if (export->pseudopath == NULL) continue; rcu_read_lock(); ref_pseudopath = gsh_refstr_get(rcu_dereference(export->pseudopath)); rcu_read_unlock(); if (ref_pseudopath == NULL) { LogFatal(COMPONENT_EXPORT, "Export %d has no pseudopath", export->export_id); } len_export = strlen(ref_pseudopath->gr_val); LogFullDebug(COMPONENT_EXPORT, "Comparing %s %d to %s %d", path, len_path, ref_pseudopath->gr_val, len_export); if (len_path == 0 && len_export == 1) { /* Special case for Pseudo root match */ ret_exp = export; gsh_refstr_put(ref_pseudopath); break; } /* A path shorter than the full path cannot match. * Also skip if this export has a shorter path than * the previous match. */ if (len_path < len_export || len_export < len_ret) { gsh_refstr_put(ref_pseudopath); continue; } /* If partial match is not allowed, lengths must be the same */ if (exact_match && len_path != len_export) { gsh_refstr_put(ref_pseudopath); continue; } /* if the char in pseudopath just after the end of path is not * '/' it is a name token longer, i.e. /mnt/foo != /mnt/foob/ */ if (len_export > 1 && path[len_export] != '/' && path[len_export] != '\0') { gsh_refstr_put(ref_pseudopath); continue; } /* we agree on size, now compare the leading substring */ if (strncmp(ref_pseudopath->gr_val, path, len_export) == 0) { ret_exp = export; len_ret = len_export; /* If we have found an exact match, exit loop. */ if (len_export == len_path) { gsh_refstr_put(ref_pseudopath); break; } } gsh_refstr_put(ref_pseudopath); } if (ret_exp != NULL) get_gsh_export_ref(ret_exp); LOG_EXPORT(NIV_DEBUG, "Found", ret_exp, false); return ret_exp; } /** * @brief Lookup the export manager struct by export pseudo path * * Gets an export entry from its pseudo (if it exists) * * @param path [IN] the path for the entry to be found. * @param exact_match [IN] the path must match exactly * * @return pointer to ref counted export */ struct gsh_export *get_gsh_export_by_pseudo(char *path, bool exact_match) { struct gsh_export *exp; PTHREAD_RWLOCK_rdlock(&export_by_id.eid_lock); exp = get_gsh_export_by_pseudo_locked(path, exact_match); PTHREAD_RWLOCK_unlock(&export_by_id.eid_lock); return exp; } /** * @brief Lookup the export manager struct by export tag * * Gets an export entry from its pseudo (if it exists) * * @param path [IN] the path for the entry to be found. * * @return pointer to ref locked export */ struct gsh_export *get_gsh_export_by_tag(char *tag) { struct gsh_export *export; struct glist_head *glist; PTHREAD_RWLOCK_rdlock(&export_by_id.eid_lock); glist_for_each(glist, &exportlist) { export = glist_entry(glist, struct gsh_export, exp_list); if (export->FS_tag != NULL && !strcmp(export->FS_tag, tag)) goto out; } PTHREAD_RWLOCK_unlock(&export_by_id.eid_lock); LOG_EXPORT(NIV_DEBUG, "Found", NULL, false); return NULL; out: get_gsh_export_ref(export); PTHREAD_RWLOCK_unlock(&export_by_id.eid_lock); LOG_EXPORT(NIV_DEBUG, "Found", export, false); return export; } /** * @brief mount the export in pseudo FS * */ bool mount_gsh_export(struct gsh_export *exp) { struct req_op_context op_context; bool rc = true; /* Initialize op_context */ init_op_context(&op_context, NULL, NULL, NULL, NULL, NFS_V4, 0, NFS_RELATED); if (!pseudo_mount_export(exp)) rc = false; release_op_context(); return rc; } /** * @brief unmount the export in pseudo FS * */ void unmount_gsh_export(struct gsh_export *exp) { struct req_op_context op_context; /* Initialize op_context */ init_op_context(&op_context, NULL, NULL, NULL, NULL, NFS_V4, 0, NFS_RELATED); pseudo_unmount_export_tree(exp); release_op_context(); } /** * @brief Take a reference to an export. */ void _get_gsh_export_ref(struct gsh_export *a_export, char *file, int line, char *function) { int64_t refcount = atomic_inc_int64_t(&a_export->refcnt); if (isFullDebug(COMPONENT_EXPORT)) { struct tmp_export_paths tmp_path = { NULL, NULL }; tmp_get_exp_paths(&tmp_path, a_export); DisplayLogComponentLevel(COMPONENT_EXPORT, file, line, function, NIV_FULL_DEBUG, "get export ref for id %" PRIu16 " %s, exp_refcount = %" PRIi64, a_export->export_id, tmp_export_path(&tmp_path), refcount); tmp_put_exp_paths(&tmp_path); } } /** * @brief Release the export management struct * * We are done with it, let it go. */ void _put_gsh_export(struct gsh_export *export, bool config, char *file, int line, char *function) { int64_t refcount = atomic_dec_int64_t(&export->refcnt); struct export_stats *export_st; assert(refcount >= 0); if (isFullDebug(COMPONENT_EXPORT)) { struct tmp_export_paths tmp_path = { NULL, NULL }; tmp_get_exp_paths(&tmp_path, export); DisplayLogComponentLevel(COMPONENT_EXPORT, file, line, function, NIV_FULL_DEBUG, "put export ref for id %" PRIu16 " %s, exp_refcount = %" PRIi64, export->export_id, tmp_export_path(&tmp_path), refcount); tmp_put_exp_paths(&tmp_path); } if (refcount != 0) return; /* Released last reference, free resources */ free_export_resources(export, config); export_st = container_of(export, struct export_stats, export); server_stats_free(&export_st->st); PTHREAD_RWLOCK_destroy(&export->exp_lock); gsh_free(export_st); } /** * @brief Remove the export management struct * * Remove it from the AVL tree. */ void remove_gsh_export(uint16_t export_id) { struct gsh_export v; struct avltree_node *node; struct gsh_export *export = NULL; void **cache_slot = (void **)&(export_by_id.cache[eid_cache_offsetof(export_id)]); v.export_id = export_id; PTHREAD_RWLOCK_wrlock(&export_by_id.eid_lock); node = avltree_lookup(&v.node_k, &export_by_id.t); if (node) { struct avltree_node *cnode = (struct avltree_node *)atomic_fetch_voidptr(cache_slot); /* Remove from the AVL cache and tree */ if (node == cnode) atomic_store_voidptr(cache_slot, NULL); avltree_remove(node, &export_by_id.t); export = avltree_container_of(node, struct gsh_export, node_k); /* Remove the export from the export list */ glist_del(&export->exp_list); /* No new references will be granted. Idempotent. */ export->export_status = EXPORT_STALE; } PTHREAD_RWLOCK_unlock(&export_by_id.eid_lock); /* removal has a once-only semantic */ if (export != NULL) { if (export->has_pnfs_ds) { /* once-only, so no need for lock here */ export->has_pnfs_ds = false; /* Remove and destroy the fsal_pnfs_ds */ pnfs_ds_remove(export->export_id); } /* Release sentinel reference to the export. * Release of resources will occur on last reference. * Which may or may not be from this call. */ put_gsh_export(export); } } /** * @ Walk the tree and do the callback on each node * * @param cb [IN] Callback function * @param state [IN] param block to pass */ bool foreach_gsh_export(bool (*cb)(struct gsh_export *exp, void *state), bool wrlock, void *state) { struct glist_head *glist, *glistn; struct gsh_export *export; bool rc = true; if (wrlock) PTHREAD_RWLOCK_wrlock(&export_by_id.eid_lock); else PTHREAD_RWLOCK_rdlock(&export_by_id.eid_lock); glist_for_each_safe(glist, glistn, &exportlist) { export = glist_entry(glist, struct gsh_export, exp_list); rc = cb(export, state); if (!rc) break; } PTHREAD_RWLOCK_unlock(&export_by_id.eid_lock); return rc; } bool remove_one_export(struct gsh_export *export, void *state) { export_add_to_unexport_work(export); return true; } static void process_unexports(void) { struct gsh_export *export; /* Now process all the unexports */ while (true) { export = glist_first_entry(&unexport_work, struct gsh_export, exp_work); if (export == NULL) break; glist_del(&export->exp_work); /* Get reference to export and add it to op_ctx */ get_gsh_export_ref(export); set_op_context_export(export); /* Do the actual unexport work */ release_export(export, false); clear_op_context_export(); } } /** * @brief Bring down all exports in an orderly fashion. */ void remove_all_exports(void) { struct gsh_export *export; struct req_op_context op_context; EXPORT_ADMIN_LOCK(); /* Get a reference to the PseudoFS Root Export and initialize the * op_context. */ export = get_gsh_export_by_pseudo("/", true); init_op_context(&op_context, export, export->fsal_export, NULL, NULL, NFS_V4, 0, NFS_RELATED); /* Clean up the whole PseudoFS */ pseudo_unmount_export_tree(export); clear_op_context_export(); /* Put all exports on the unexport work list. * Ignore return since remove_one_export can't fail. */ (void)foreach_gsh_export(remove_one_export, true, NULL); process_unexports(); release_op_context(); EXPORT_ADMIN_UNLOCK(); } static bool prune_defunct_export(struct gsh_export *exp, void *state) { uint64_t generation = *((uint64_t *)state); if (exp->config_gen < generation) { if (isDebug(COMPONENT_EXPORT)) { struct tmp_export_paths tmp; tmp_get_exp_paths(&tmp, exp); LogDebug(COMPONENT_EXPORT, "Pruning export %d path %s pseudo %s", exp->export_id, TMP_FULLPATH(&tmp), TMP_PSEUDOPATH(&tmp)); tmp_put_exp_paths(&tmp); } export_add_to_unexport_work(exp); } return true; } void prune_defunct_exports(uint64_t generation) { struct req_op_context op_context; /* * Initialize op_context, we use NFSv4 types here to make paths show * up sanely in the logs. */ init_op_context(&op_context, NULL, NULL, NULL, NULL, NFS_V4, 0, NFS_RELATED); (void)foreach_gsh_export(prune_defunct_export, true, &generation); /* now run the work */ process_unexports(); release_op_context(); } /** * @brief Initialize all stats at startup time. * * Note: This function needs to be in all builds, not just USE_DBUS enabled * ones. * */ void nfs_init_stats_time(void) { now(&nfs_stats_time); fsal_stats_time = v3_full_stats_time = v4_full_stats_time = auth_stats_time = clnt_allops_stats_time = nfs_stats_time; } #ifdef USE_DBUS /* DBUS interfaces */ /** * @brief Return all IO stats of an export * DBUS_TYPE_ARRAY, "qs(tttttt)(tttttt)" */ static bool get_all_export_io(struct gsh_export *export_node, void *array_iter) { struct export_stats *export_statistics; if (isFullDebug(COMPONENT_DBUS)) { struct gsh_refstr *ref_fullpath; rcu_read_lock(); ref_fullpath = gsh_refstr_get(rcu_dereference(export_node->fullpath)); rcu_read_unlock(); LogFullDebug(COMPONENT_DBUS, "export id: %i, path: %s", export_node->export_id, ref_fullpath->gr_val); gsh_refstr_put(ref_fullpath); } export_statistics = container_of(export_node, struct export_stats, export); server_dbus_all_iostats(export_statistics, (DBusMessageIter *)array_iter); return true; } /* parse the export_id in args */ static bool arg_export_id(DBusMessageIter *args, uint16_t *export_id, char **errormsg) { bool success = true; if (args == NULL) { success = false; *errormsg = "message has no arguments"; } else if (dbus_message_iter_get_arg_type(args) != DBUS_TYPE_UINT16) { success = false; *errormsg = "arg not a 16 bit integer"; } else { dbus_message_iter_get_basic(args, export_id); } return success; } /* DBUS export manager stats helpers */ static struct gsh_export *lookup_export(DBusMessageIter *args, char **errormsg) { uint16_t export_id; struct gsh_export *export = NULL; bool success = true; success = arg_export_id(args, &export_id, errormsg); if (success) { export = get_gsh_export(export_id); if (export == NULL) *errormsg = "Export id not found"; } return export; } /* Private state for config_errs_to_dbus to convert * parsing error stream in to a string of '\n' terminated * message lines. */ struct error_detail { char *buf; size_t bufsize; FILE *fp; }; /** * @brief Report processing errors to the DBUS client. * * For now, they just go to the log (as before). */ static void config_errs_to_dbus(char *err, void *dest, struct config_error_type *err_type) { struct error_detail *err_dest = dest; if (err_dest->fp == NULL) { err_dest->fp = open_memstream(&err_dest->buf, &err_dest->bufsize); if (err_dest->fp == NULL) { LogCrit(COMPONENT_EXPORT, "Unable to allocate space for parse errors"); return; } } fprintf(err_dest->fp, "%s\n", err); } struct showexports_state { DBusMessageIter export_iter; }; /** * @brief Add an export either before or after the ID'd export * * This method passes a pathname in the server's local filesystem * that should be parsed and processed by the config_parsing module. * The resulting export entry is then added before or after the ID'd * export entry. Params are in the args iter * * @param "path" [IN] A local path to a file with only an EXPORT {...} * * @return true for success, false with error filled out for failure */ static bool gsh_export_addexport(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { int rc, exp_cnt = 0, err; bool status = true; char *file_path = NULL; char *export_expr = NULL; config_file_t config_struct = NULL; struct config_node_list *config_list, *lp, *lp_next; struct config_error_type err_type; DBusMessageIter iter; char *err_detail = NULL; struct error_detail conf_errs = { NULL, 0, NULL }; struct stat st; /* Get path */ if (dbus_message_iter_get_arg_type(args) == DBUS_TYPE_STRING) dbus_message_iter_get_basic(args, &file_path); else { dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, "Pathname is not a string. It is a (%c)", dbus_message_iter_get_arg_type(args)); status = false; goto out; } if (dbus_message_iter_next(args) && dbus_message_iter_get_arg_type(args) == DBUS_TYPE_STRING) dbus_message_iter_get_basic(args, &export_expr); else { dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, "expression is not a string. It is a (%c)", dbus_message_iter_get_arg_type(args)); status = false; goto out; } if (EXPORT_ADMIN_TRYLOCK() != 0) { dbus_set_error( error, DBUS_ERROR_INVALID_ARGS, "another export admin operation is in progress, try again later"); status = false; goto out; } LogInfo(COMPONENT_EXPORT, "Adding export from file: %s with %s", file_path, export_expr); /* Create a memstream for parser+processing error messages */ if (!init_error_type(&err_type)) goto out_unlock; /* The parser fatal errors if file_path is a directory, so check it * before calling parser. */ rc = stat(file_path, &st); if (rc < 0) { err = errno; dbus_set_error( error, DBUS_ERROR_INVALID_ARGS, "error %d (%s) when attempting to stat config file %s", err, strerror(err), file_path); status = false; goto out_unlock; } if ((st.st_mode & S_IFMT) != S_IFREG) { dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, "config file path %s is not a regular file", file_path); status = false; goto out_unlock; } config_struct = config_ParseFile(file_path, &err_type); if (!cur_exp_config_error_is_harmless(&err_type)) { err_detail = err_type_str(&err_type); LogCrit(COMPONENT_EXPORT, "Error while parsing %s", file_path); (void)report_config_errors(&err_type, &conf_errs, config_errs_to_dbus); if (conf_errs.fp != NULL) fclose(conf_errs.fp); dbus_set_error( error, DBUS_ERROR_INVALID_FILE_CONTENT, "Error while parsing %s because of %s errors. Details:\n%s", file_path, err_detail != NULL ? err_detail : "unknown", conf_errs.buf); status = false; goto out_unlock; } rc = find_config_nodes(config_struct, export_expr, &config_list, &err_type); if (rc != 0) { LogCrit(COMPONENT_EXPORT, "Error finding exports: %s because %s", export_expr, strerror(rc)); (void)report_config_errors(&err_type, &conf_errs, config_errs_to_dbus); if (conf_errs.fp != NULL) fclose(conf_errs.fp); dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, "Error finding exports: %s because %s", export_expr, strerror(rc)); status = false; goto out_unlock; } /* Load export entries from list */ for (lp = config_list; lp != NULL; lp = lp_next) { lp_next = lp->next; if (status) { rc = load_config_from_node(lp->tree_node, &add_export_param, NULL, false, &err_type); if (rc == 0 || cur_exp_config_error_is_harmless(&err_type)) exp_cnt++; else if (!err_type.exists) status = false; } gsh_free(lp); } (void)report_config_errors(&err_type, &conf_errs, config_errs_to_dbus); if (conf_errs.fp != NULL) fclose(conf_errs.fp); if (status) { if (exp_cnt > 0) { size_t msg_size = sizeof("%d exports added") + 10; char *message; if (conf_errs.buf != NULL && strlen(conf_errs.buf) > 0) { msg_size += (strlen(conf_errs.buf) + strlen(". Errors found:\n")); message = gsh_calloc(1, msg_size); (void)snprintf( message, msg_size, "%d exports added. Errors found:\n%s", exp_cnt, conf_errs.buf); } else { message = gsh_calloc(1, msg_size); (void)snprintf(message, msg_size, "%d exports added", exp_cnt); } dbus_message_iter_init_append(reply, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &message); gsh_free(message); } else if (err_type.exists) { LogWarn(COMPONENT_EXPORT, "Selected entries in %s already active!!!", file_path); dbus_set_error( error, DBUS_ERROR_INVALID_FILE_CONTENT, "Selected entries in %s already active!!!", file_path); status = false; } else { LogWarn(COMPONENT_EXPORT, "No usable export entry found in %s!!!", file_path); dbus_set_error(error, DBUS_ERROR_INVALID_FILE_CONTENT, "No new export entries found in %s", file_path); status = false; } goto out_unlock; } else { err_detail = err_type_str(&err_type); LogCrit(COMPONENT_EXPORT, "%d export entries in %s added because %s errors", exp_cnt, file_path, err_detail != NULL ? err_detail : "unknown"); dbus_set_error( error, DBUS_ERROR_INVALID_FILE_CONTENT, "%d export entries in %s added because %s errors. Details:\n%s", exp_cnt, file_path, err_detail != NULL ? err_detail : "unknown", conf_errs.buf); } out_unlock: EXPORT_ADMIN_UNLOCK(); out: if (conf_errs.buf) gsh_free(conf_errs.buf); if (err_detail != NULL) gsh_free(err_detail); config_Free(config_struct); return status; } static struct gsh_dbus_method export_add_export = { .name = "AddExport", .method = gsh_export_addexport, .args = { PATH_ARG, EXPR_ARG, MESSAGE_REPLY, END_ARG_LIST } }; /** * @brief Remove an export * * @param "id" [IN] the id of the export to remove * * @return As above, use DBusError to return errors. */ static bool gsh_export_removeexport(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_export *export = NULL; char *errormsg; bool rc = false; struct req_op_context op_context; export = lookup_export(args, &errormsg); if (export == NULL) { LogDebug(COMPONENT_EXPORT, "lookup_export failed with %s", errormsg); dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, "lookup_export failed with %s", errormsg); goto out; } if (export->export_id == 0) { LogDebug(COMPONENT_EXPORT, "Cannot remove export with id 0"); put_gsh_export(export); dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, "Cannot remove export with id 0"); goto out; } if (EXPORT_ADMIN_TRYLOCK() != 0) { dbus_set_error( error, DBUS_ERROR_INVALID_ARGS, "another export admin operation is in progress, try again later"); rc = false; goto out; } PTHREAD_RWLOCK_rdlock(&export->exp_lock); rc = glist_empty(&export->mounted_exports_list); PTHREAD_RWLOCK_unlock(&export->exp_lock); if (!rc) { LogDebug(COMPONENT_EXPORT, "Cannot remove export with submounts"); put_gsh_export(export); dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, "Cannot remove export with submounts"); goto out_unlock; } /* Lots of obj_ops may be called during cleanup; make sure that an * op_ctx exists */ init_op_context_simple(&op_context, export, export->fsal_export); release_export(export, false); LogInfo(COMPONENT_EXPORT, "Removed export with id %d", export->export_id); release_op_context(); out_unlock: EXPORT_ADMIN_UNLOCK(); out: return rc; } static struct gsh_dbus_method export_remove_export = { .name = "RemoveExport", .method = gsh_export_removeexport, .args = { ID_ARG, END_ARG_LIST } }; #define DISP_EXP_REPLY \ { .name = "id", .type = "q", .direction = "out" }, \ { .name = "fullpath", .type = "s", .direction = "out" }, \ { .name = "pseudopath", .type = "s", .direction = "out" }, \ { .name = "tag", .type = "s", .direction = "out" }, \ { \ .name = "clients", .type = "a(siyyiuuuuu)", \ .direction = "out", \ } static void client_of_export(struct exportlist_client_entry *expclient, void *state) { struct showexports_state *client_array_iter = (struct showexports_state *)state; DBusMessageIter client_struct_iter; const char *grp_name; struct base_client_entry *client = &expclient->client_entry; switch (client->type) { case NETWORK_CLIENT: grp_name = cidr_to_str(client->client.network.cidr, CIDR_NOFLAGS); if (grp_name == NULL) { grp_name = "Invalid Network Address"; } break; case NETGROUP_CLIENT: grp_name = client->client.netgroup.netgroupname; break; case GSSPRINCIPAL_CLIENT: grp_name = client->client.gssprinc.princname; break; case MATCH_ANY_CLIENT: grp_name = "*"; break; case WILDCARDHOST_CLIENT: grp_name = client->client.wildcard.wildcard; break; default: grp_name = "<unknown>"; } dbus_message_iter_open_container(&client_array_iter->export_iter, DBUS_TYPE_STRUCT, NULL, &client_struct_iter); // Client type dbus_message_iter_append_basic(&client_struct_iter, DBUS_TYPE_STRING, &grp_name); // Client Cidr block if (client->type == NETWORK_CLIENT) { dbus_message_iter_append_basic( &client_struct_iter, DBUS_TYPE_INT32, &client->client.network.cidr->version); dbus_message_iter_append_basic( &client_struct_iter, DBUS_TYPE_BYTE, &client->client.network.cidr->addr); dbus_message_iter_append_basic( &client_struct_iter, DBUS_TYPE_BYTE, &client->client.network.cidr->mask); dbus_message_iter_append_basic( &client_struct_iter, DBUS_TYPE_INT32, &client->client.network.cidr->proto); } else { int dummy_val1 = 0; uint8_t dummy_val2 = 0; dbus_message_iter_append_basic(&client_struct_iter, DBUS_TYPE_INT32, &dummy_val1); dbus_message_iter_append_basic(&client_struct_iter, DBUS_TYPE_BYTE, &dummy_val2); dbus_message_iter_append_basic(&client_struct_iter, DBUS_TYPE_BYTE, &dummy_val2); dbus_message_iter_append_basic(&client_struct_iter, DBUS_TYPE_INT32, &dummy_val1); } // Client Export Permissions dbus_message_iter_append_basic(&client_struct_iter, DBUS_TYPE_UINT32, &expclient->client_perms.anonymous_uid); dbus_message_iter_append_basic(&client_struct_iter, DBUS_TYPE_UINT32, &expclient->client_perms.anonymous_gid); dbus_message_iter_append_basic( &client_struct_iter, DBUS_TYPE_UINT32, &expclient->client_perms.expire_time_attr); dbus_message_iter_append_basic(&client_struct_iter, DBUS_TYPE_UINT32, &expclient->client_perms.options); dbus_message_iter_append_basic(&client_struct_iter, DBUS_TYPE_UINT32, &expclient->client_perms.set); dbus_message_iter_close_container(&client_array_iter->export_iter, &client_struct_iter); } /** * @brief Display the contents of an export * * NOTE: this is probably better done as properties. * the interfaces are set up for it. This is here for now * but should not be considered a permanent method */ static bool gsh_export_displayexport(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { DBusMessageIter iter; struct gsh_export *export = NULL; char *errormsg; bool rc = true; char *path; struct showexports_state client_array_iter; struct glist_head *glist; struct tmp_export_paths tmp = { NULL, NULL }; export = lookup_export(args, &errormsg); if (export == NULL) { LogDebug(COMPONENT_EXPORT, "lookup_export failed with %s", errormsg); dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, "lookup_export failed with %s", errormsg); rc = false; goto out; } tmp_get_exp_paths(&tmp, export); /* create a reply from the message */ dbus_message_iter_init_append(reply, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT16, &export->export_id); path = TMP_FULLPATH(&tmp); if (path == NULL) path = ""; dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &path); path = tmp_export_path(&tmp); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &path); path = (export->FS_tag != NULL) ? export->FS_tag : ""; dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &path); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(siyyiuuuuu)", &client_array_iter.export_iter); PTHREAD_RWLOCK_rdlock(&export->exp_lock); glist_for_each(glist, &export->clients) { struct base_client_entry *client; struct exportlist_client_entry *expclient; client = glist_entry(glist, struct base_client_entry, cle_list); expclient = container_of(client, struct exportlist_client_entry, client_entry); client_of_export(expclient, (void *)&client_array_iter); } PTHREAD_RWLOCK_unlock(&export->exp_lock); dbus_message_iter_close_container(&iter, &client_array_iter.export_iter); tmp_put_exp_paths(&tmp); put_gsh_export(export); out: return rc; } static struct gsh_dbus_method export_display_export = { .name = "DisplayExport", .method = gsh_export_displayexport, .args = { ID_ARG, DISP_EXP_REPLY, END_ARG_LIST } }; static bool export_to_dbus(struct gsh_export *exp_node, void *state) { struct showexports_state *iter_state = (struct showexports_state *)state; struct export_stats *exp; DBusMessageIter struct_iter; const char *path; struct tmp_export_paths tmp = { NULL, NULL }; tmp_get_exp_paths(&tmp, exp_node); path = tmp_export_path(&tmp); tmp_put_exp_paths(&tmp); exp = container_of(exp_node, struct export_stats, export); dbus_message_iter_open_container(&iter_state->export_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT16, &exp_node->export_id); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &path); server_stats_summary(&struct_iter, &exp->st); gsh_dbus_append_timestamp(&struct_iter, &exp_node->last_update); dbus_message_iter_close_container(&iter_state->export_iter, &struct_iter); return true; } static bool gsh_export_showexports(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { DBusMessageIter iter; struct showexports_state iter_state; /* create a reply from the message */ dbus_message_iter_init_append(reply, &iter); gsh_dbus_append_timestamp(&iter, &nfs_stats_time); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, EXPORT_CONTAINER, &iter_state.export_iter); (void)foreach_gsh_export(export_to_dbus, false, (void *)&iter_state); dbus_message_iter_close_container(&iter, &iter_state.export_iter); return true; } static struct gsh_dbus_method export_show_exports = { .name = "ShowExports", .method = gsh_export_showexports, .args = { TIMESTAMP_REPLY, EXPORTS_REPLY, END_ARG_LIST } }; /** * DBUS method to detailed client statistics */ static bool gsh_export_details(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "OK"; bool success = true; DBusMessageIter iter; struct gsh_export *export = NULL; dbus_message_iter_init_append(reply, &iter); export = lookup_export(args, &errormsg); if (export == NULL) { success = false; errormsg = "Export ID not found"; } gsh_dbus_status_reply(&iter, success, errormsg); if (success) { server_dbus_export_details(&iter, export); put_gsh_export(export); } return true; } static struct gsh_dbus_method export_details = { .name = "GetExportDetails", .method = gsh_export_details, .args = { ID_ARG, STATUS_REPLY, TIMESTAMP_REPLY, CE_STATS_REPLY, END_ARG_LIST } }; /** * @brief Reset stat counters for all exports */ void reset_export_stats(void) { struct glist_head *glist; struct gsh_export *export; struct export_stats *exp; PTHREAD_RWLOCK_rdlock(&export_by_id.eid_lock); glist_for_each(glist, &exportlist) { export = glist_entry(glist, struct gsh_export, exp_list); exp = container_of(export, struct export_stats, export); reset_gsh_stats(&exp->st); } PTHREAD_RWLOCK_unlock(&export_by_id.eid_lock); } /** * @brief Update an export * * This method passes a pathname in the server's local filesystem * that should be parsed and processed by the config_parsing module. * The resulting export entry is then updated. Params are in the args iter * * @param "path" [IN] A local path to a file with only an EXPORT {...} * * @return true for success, false with error filled out for failure */ static bool gsh_export_update_export(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { int rc, exp_cnt = 0; bool status = true; char *file_path = NULL; char *export_expr = NULL; config_file_t config_struct = NULL; struct config_node_list *config_list, *lp, *lp_next; struct config_error_type err_type; DBusMessageIter iter; char *err_detail = NULL; struct error_detail conf_errs = { NULL, 0, NULL }; /* Get path */ if (dbus_message_iter_get_arg_type(args) == DBUS_TYPE_STRING) dbus_message_iter_get_basic(args, &file_path); else { dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, "Pathname is not a string. It is a (%c)", dbus_message_iter_get_arg_type(args)); status = false; goto out; } if (dbus_message_iter_next(args) && dbus_message_iter_get_arg_type(args) == DBUS_TYPE_STRING) dbus_message_iter_get_basic(args, &export_expr); else { dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, "expression is not a string. It is a (%c)", dbus_message_iter_get_arg_type(args)); status = false; goto out; } LogInfo(COMPONENT_EXPORT, "Updating export from file: %s with %s", file_path, export_expr); /* Create a memstream for parser+processing error messages */ if (!init_error_type(&err_type)) goto out; config_struct = config_ParseFile(file_path, &err_type); if (!cur_exp_config_error_is_harmless(&err_type)) { err_detail = err_type_str(&err_type); LogCrit(COMPONENT_EXPORT, "Error while parsing %s", file_path); (void)report_config_errors(&err_type, &conf_errs, config_errs_to_dbus); if (conf_errs.fp != NULL) fclose(conf_errs.fp); dbus_set_error( error, DBUS_ERROR_INVALID_FILE_CONTENT, "Error while parsing %s because of %s errors. Details:\n%s", file_path, err_detail != NULL ? err_detail : "unknown", conf_errs.buf); status = false; goto out; } rc = find_config_nodes(config_struct, export_expr, &config_list, &err_type); if (rc != 0) { LogCrit(COMPONENT_EXPORT, "Error finding exports: %s because %s", export_expr, strerror(rc)); (void)report_config_errors(&err_type, &conf_errs, config_errs_to_dbus); if (conf_errs.fp != NULL) fclose(conf_errs.fp); dbus_set_error(error, DBUS_ERROR_INVALID_ARGS, "Error finding exports: %s because %s", export_expr, strerror(rc)); status = false; goto out; } /* Update export entries from list */ for (lp = config_list; lp != NULL; lp = lp_next) { lp_next = lp->next; if (status) { rc = load_config_from_node(lp->tree_node, &update_export_param, NULL, false, &err_type); if (rc == 0 || cur_exp_config_error_is_harmless(&err_type)) exp_cnt++; else if (!err_type.exists) status = false; } gsh_free(lp); } (void)report_config_errors(&err_type, &conf_errs, config_errs_to_dbus); if (conf_errs.fp != NULL) fclose(conf_errs.fp); if (status) { if (exp_cnt > 0) { size_t msg_size = sizeof("%d exports updated") + 10; char *message; if (conf_errs.buf != NULL && strlen(conf_errs.buf) > 0) { msg_size += (strlen(conf_errs.buf) + strlen(". Errors found:\n")); message = gsh_calloc(1, msg_size); (void)snprintf( message, msg_size, "%d exports updated. Errors found:\n%s", exp_cnt, conf_errs.buf); } else { message = gsh_calloc(1, msg_size); (void)snprintf(message, msg_size, "%d exports updated", exp_cnt); } dbus_message_iter_init_append(reply, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &message); gsh_free(message); } else if (err_type.exists) { LogWarn(COMPONENT_EXPORT, "Selected entries in %s already active!!!", file_path); dbus_set_error( error, DBUS_ERROR_INVALID_FILE_CONTENT, "Selected entries in %s already active!!!", file_path); status = false; } else { LogWarn(COMPONENT_EXPORT, "No usable export entry found in %s!!!", file_path); dbus_set_error(error, DBUS_ERROR_INVALID_FILE_CONTENT, "No new export entries found in %s", file_path); status = false; } goto out; } else { err_detail = err_type_str(&err_type); LogCrit(COMPONENT_EXPORT, "%d export entries in %s updated because %s errors", exp_cnt, file_path, err_detail != NULL ? err_detail : "unknown"); dbus_set_error( error, DBUS_ERROR_INVALID_FILE_CONTENT, "%d export entries in %s updated because %s errors. Details:\n%s", exp_cnt, file_path, err_detail != NULL ? err_detail : "unknown", conf_errs.buf); } out: if (conf_errs.buf) gsh_free(conf_errs.buf); if (err_detail != NULL) gsh_free(err_detail); config_Free(config_struct); return status; } static struct gsh_dbus_method export_update_export = { .name = "UpdateExport", .method = gsh_export_update_export, .args = { PATH_ARG, EXPR_ARG, MESSAGE_REPLY, END_ARG_LIST } }; static struct gsh_dbus_method *export_mgr_methods[] = { &export_add_export, &export_remove_export, &export_display_export, &export_show_exports, &export_update_export, NULL }; /* org.ganesha.nfsd.exportmgr interface */ static struct gsh_dbus_interface export_mgr_table = { .name = "org.ganesha.nfsd.exportmgr", .props = NULL, .methods = export_mgr_methods, .signals = NULL }; /* org.ganesha.nfsd.exportstats interface */ #ifdef _USE_NFS3 /** * DBUS method to report NFSv3 I/O statistics * */ static bool get_nfsv3_export_io(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_export *export = NULL; struct export_stats *export_st = NULL; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (!nfs_param.core_param.enable_NFSSTATS) errormsg = "NFS stat counting disabled"; export = lookup_export(args, &errormsg); if (export == NULL) { success = false; errormsg = "No export available"; } else { export_st = container_of(export, struct export_stats, export); if (export_st->st.nfsv3 == NULL) { success = false; errormsg = "Export does not have any NFSv3 activity"; } } gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_v3_iostats(export_st->st.nfsv3, &iter); if (export != NULL) put_gsh_export(export); return true; } static struct gsh_dbus_method export_show_v3_io = { .name = "GetNFSv3IO", .method = get_nfsv3_export_io, .args = { EXPORT_ID_ARG, STATUS_REPLY, TIMESTAMP_REPLY, IOSTATS_REPLY, END_ARG_LIST } }; #endif /** * DBUS method to report NFSv40 I/O statistics * */ static bool get_nfsv40_export_io(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_export *export = NULL; struct export_stats *export_st = NULL; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); export = lookup_export(args, &errormsg); if (!nfs_param.core_param.enable_NFSSTATS) errormsg = "NFS stat counting disabled"; if (export == NULL) { success = false; } else { export_st = container_of(export, struct export_stats, export); if (export_st->st.nfsv40 == NULL) { success = false; errormsg = "Export does not have any NFSv4.0 activity"; } } gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_v40_iostats(export_st->st.nfsv40, &iter); if (export != NULL) put_gsh_export(export); return true; } static struct gsh_dbus_method export_show_v40_io = { .name = "GetNFSv40IO", .method = get_nfsv40_export_io, .args = { EXPORT_ID_ARG, STATUS_REPLY, TIMESTAMP_REPLY, IOSTATS_REPLY, END_ARG_LIST } }; /** * DBUS method to report NFSv41 I/O statistics * */ static bool get_nfsv41_export_io(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_export *export = NULL; struct export_stats *export_st = NULL; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); export = lookup_export(args, &errormsg); if (!nfs_param.core_param.enable_NFSSTATS) errormsg = "NFS stat counting disabled"; if (export == NULL) { success = false; } else { export_st = container_of(export, struct export_stats, export); if (export_st->st.nfsv41 == NULL) { success = false; errormsg = "Export does not have any NFSv4.1 activity"; } } gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_v41_iostats(export_st->st.nfsv41, &iter); if (export != NULL) put_gsh_export(export); return true; } static struct gsh_dbus_method export_show_v41_io = { .name = "GetNFSv41IO", .method = get_nfsv41_export_io, .args = { EXPORT_ID_ARG, STATUS_REPLY, TIMESTAMP_REPLY, IOSTATS_REPLY, END_ARG_LIST } }; /** * DBUS method to report NFSv41 layout statistics * */ static bool get_nfsv41_export_layouts(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_export *export = NULL; struct export_stats *export_st = NULL; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); export = lookup_export(args, &errormsg); if (!nfs_param.core_param.enable_NFSSTATS) errormsg = "NFS stat counting disabled"; if (export == NULL) { success = false; } else { export_st = container_of(export, struct export_stats, export); if (export_st->st.nfsv41 == NULL) { success = false; errormsg = "Export does not have any NFSv4.1 activity"; } } gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_v41_layouts(export_st->st.nfsv41, &iter); if (export != NULL) put_gsh_export(export); return true; } /** * DBUS method to report NFSv42 I/O statistics * */ static bool get_nfsv42_export_io(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_export *export = NULL; struct export_stats *export_st = NULL; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); export = lookup_export(args, &errormsg); if (!nfs_param.core_param.enable_NFSSTATS) errormsg = "NFS stat counting disabled"; if (export == NULL) { success = false; } else { export_st = container_of(export, struct export_stats, export); if (export_st->st.nfsv42 == NULL) { success = false; errormsg = "Export does not have any NFSv4.2 activity"; } } gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_v42_iostats(export_st->st.nfsv42, &iter); if (export != NULL) put_gsh_export(export); return true; } static struct gsh_dbus_method export_show_v42_io = { .name = "GetNFSv42IO", .method = get_nfsv42_export_io, .args = { EXPORT_ID_ARG, STATUS_REPLY, TIMESTAMP_REPLY, IOSTATS_REPLY, END_ARG_LIST } }; /** * DBUS method to report NFSv42 layout statistics * */ static bool get_nfsv42_export_layouts(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_export *export = NULL; struct export_stats *export_st = NULL; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); export = lookup_export(args, &errormsg); if (!nfs_param.core_param.enable_NFSSTATS) errormsg = "NFS stat counting disabled"; if (export == NULL) { success = false; } else { export_st = container_of(export, struct export_stats, export); if (export_st->st.nfsv42 == NULL) { success = false; errormsg = "Export does not have any NFSv4.2 activity"; } } gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_v42_layouts(export_st->st.nfsv42, &iter); if (export != NULL) put_gsh_export(export); return true; } /** * DBUS method to report NFS I/O statistics * */ static bool get_nfsmon_export_io(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_export *export = NULL; struct export_stats *export_st = NULL; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); export = lookup_export(args, &errormsg); if (!nfs_param.core_param.enable_NFSSTATS) errormsg = "NFS stat counting disabled"; if (export == NULL) success = false; else export_st = container_of(export, struct export_stats, export); gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_nfsmon_iostats(export_st, &iter); if (export != NULL) put_gsh_export(export); return true; } static struct gsh_dbus_method export_show_nfsmon_io = { .name = "GetNFSIOMon", .method = get_nfsmon_export_io, .args = { EXPORT_ID_ARG, STATUS_REPLY, TIMESTAMP_REPLY, IOSTATS_REPLY, END_ARG_LIST } }; /** * DBUS method to report total ops statistics * */ static bool get_nfsv_export_total_ops(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_export *export = NULL; struct export_stats *export_st = NULL; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (!nfs_param.core_param.enable_NFSSTATS) errormsg = "NFS stat counting disabled"; export = lookup_export(args, &errormsg); if (export != NULL) { export_st = container_of(export, struct export_stats, export); gsh_dbus_status_reply(&iter, success, errormsg); server_dbus_total_ops(export_st, &iter); put_gsh_export(export); } else { success = false; errormsg = "Export does not have any activity"; gsh_dbus_status_reply(&iter, success, errormsg); } return true; } static bool get_nfsv_global_total_ops(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (!nfs_param.core_param.enable_NFSSTATS) errormsg = "NFS stat counting disabled"; gsh_dbus_status_reply(&iter, success, errormsg); if (success) global_dbus_total_ops(&iter); return true; } static bool get_nfsv_global_fast_ops(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (!nfs_param.core_param.enable_NFSSTATS) errormsg = "NFS stat counting disabled"; gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_fast_ops(&iter); return true; } static bool show_mdcache_stats(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { bool success = true; char *errormsg = "OK"; DBusMessageIter iter; struct timespec timestamp; now(×tamp); dbus_message_iter_init_append(reply, &iter); gsh_dbus_status_reply(&iter, success, errormsg); gsh_dbus_append_timestamp(&iter, ×tamp); mdcache_dbus_show(&iter); mdcache_utilization(&iter); return true; } static bool show_fd_usage_summary(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { bool success = true; char *errormsg = "OK"; DBusMessageIter iter; struct timespec timestamp; now(×tamp); dbus_message_iter_init_append(reply, &iter); gsh_dbus_status_reply(&iter, success, errormsg); gsh_dbus_append_timestamp(&iter, ×tamp); fd_usage_summarize_dbus(&iter); return true; } static struct gsh_dbus_method export_show_v41_layouts = { .name = "GetNFSv41Layouts", .method = get_nfsv41_export_layouts, .args = { EXPORT_ID_ARG, STATUS_REPLY, TIMESTAMP_REPLY, LAYOUTS_REPLY, END_ARG_LIST } }; static struct gsh_dbus_method export_show_v42_layouts = { .name = "GetNFSv42Layouts", .method = get_nfsv42_export_layouts, .args = { EXPORT_ID_ARG, STATUS_REPLY, TIMESTAMP_REPLY, LAYOUTS_REPLY, END_ARG_LIST } }; /* Reset FSAL stats */ static void reset_fsal_stats(void) { /* Module iterator */ struct glist_head *mi = NULL; /* Next module */ struct glist_head *mn = NULL; glist_for_each_safe(mi, mn, &fsal_list) { /* The module to reset stats */ struct fsal_module *m = glist_entry(mi, struct fsal_module, fsals); if (m->stats != NULL) m->m_ops.fsal_reset_stats(m); } } /** * DBUS method to reset all ops statistics * */ static bool stats_reset(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { bool success = true; char *errormsg = "OK"; DBusMessageIter iter; struct timespec timestamp; dbus_message_iter_init_append(reply, &iter); gsh_dbus_status_reply(&iter, success, errormsg); now(×tamp); gsh_dbus_append_timestamp(&iter, ×tamp); reset_fsal_stats(); reset_server_stats(); reset_auth_stats(); /* update the stats counting time */ nfs_init_stats_time(); return true; } static struct gsh_dbus_method reset_statistics = { .name = "ResetStats", .method = stats_reset, .args = { STATUS_REPLY, TIMESTAMP_REPLY, END_ARG_LIST } }; #ifdef _USE_NFS3 /** * DBUS method to get NFSv3 Detailed stats */ static bool stats_v3_full(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (!nfs_param.core_param.enable_FULLV3STATS) { success = false; errormsg = "v3_full stats disabled"; gsh_dbus_status_reply(&iter, success, errormsg); return true; } gsh_dbus_status_reply(&iter, success, errormsg); server_dbus_v3_full_stats(&iter); return true; } static struct gsh_dbus_method v3_full_statistics = { .name = "GetFULLV3Stats", .method = stats_v3_full, .args = { STATUS_REPLY, TIMESTAMP_REPLY, V3_FULL_REPLY, MESSAGE_REPLY, END_ARG_LIST } }; #endif /** * DBUS method to get NFSv4 Detailed stats */ static bool stats_v4_full(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); if (!nfs_param.core_param.enable_FULLV4STATS) { success = false; errormsg = "v4_full stats disabled"; gsh_dbus_status_reply(&iter, success, errormsg); return true; } gsh_dbus_status_reply(&iter, success, errormsg); server_dbus_v4_full_stats(&iter); return true; } static struct gsh_dbus_method v4_full_statistics = { .name = "GetFULLV4Stats", .method = stats_v4_full, .args = { STATUS_REPLY, TIMESTAMP_REPLY, V4_FULL_REPLY, MESSAGE_REPLY, END_ARG_LIST } }; /** * DBUS method to know current status of stats counting */ static bool stats_status(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { bool success = true; char *errormsg = "OK"; DBusMessageIter iter, nfsstatus, fsalstatus, clnt_allops_status; #ifdef _USE_NFS3 DBusMessageIter v3_full_status; #endif DBusMessageIter v4_full_status, authstatus; dbus_bool_t value; dbus_message_iter_init_append(reply, &iter); gsh_dbus_status_reply(&iter, success, errormsg); /* Send info about NFS server stats */ dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, NULL, &nfsstatus); value = nfs_param.core_param.enable_NFSSTATS; dbus_message_iter_append_basic(&nfsstatus, DBUS_TYPE_BOOLEAN, &value); gsh_dbus_append_timestamp(&nfsstatus, &nfs_stats_time); dbus_message_iter_close_container(&iter, &nfsstatus); /* Send info about FSAL stats */ dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, NULL, &fsalstatus); value = nfs_param.core_param.enable_FSALSTATS; dbus_message_iter_append_basic(&fsalstatus, DBUS_TYPE_BOOLEAN, &value); gsh_dbus_append_timestamp(&fsalstatus, &fsal_stats_time); dbus_message_iter_close_container(&iter, &fsalstatus); #ifdef _USE_NFS3 /* Send info about NFSv3 Detailed stats */ dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, NULL, &v3_full_status); value = nfs_param.core_param.enable_FULLV3STATS; dbus_message_iter_append_basic(&v3_full_status, DBUS_TYPE_BOOLEAN, &value); gsh_dbus_append_timestamp(&v3_full_status, &v3_full_stats_time); dbus_message_iter_close_container(&iter, &v3_full_status); #endif /* Send info about NFSv4 Detailed stats */ dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, NULL, &v4_full_status); value = nfs_param.core_param.enable_FULLV4STATS; dbus_message_iter_append_basic(&v4_full_status, DBUS_TYPE_BOOLEAN, &value); gsh_dbus_append_timestamp(&v4_full_status, &v4_full_stats_time); dbus_message_iter_close_container(&iter, &v4_full_status); /* Send info about auth stats */ dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, NULL, &authstatus); value = nfs_param.core_param.enable_AUTHSTATS; dbus_message_iter_append_basic(&authstatus, DBUS_TYPE_BOOLEAN, &value); gsh_dbus_append_timestamp(&authstatus, &auth_stats_time); dbus_message_iter_close_container(&iter, &authstatus); /* Send info about client allops stats */ dbus_message_iter_open_container(&iter, DBUS_TYPE_STRUCT, NULL, &clnt_allops_status); value = nfs_param.core_param.enable_CLNTALLSTATS; dbus_message_iter_append_basic(&clnt_allops_status, DBUS_TYPE_BOOLEAN, &value); gsh_dbus_append_timestamp(&clnt_allops_status, &clnt_allops_stats_time); dbus_message_iter_close_container(&iter, &clnt_allops_status); return true; } static struct gsh_dbus_method status_stats = { .name = "StatusStats", .method = stats_status, .args = { STATUS_REPLY, STATS_STATUS_REPLY, END_ARG_LIST } }; /** * DBUS method to disable statistics counting * */ static bool stats_disable(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "OK"; char *stat_type = NULL; DBusMessageIter iter; struct timespec timestamp; dbus_message_iter_init_append(reply, &iter); if (args == NULL) { errormsg = "message has no arguments"; goto error; } else if (dbus_message_iter_get_arg_type(args) != DBUS_TYPE_STRING) { errormsg = "arg not string"; goto error; } else { dbus_message_iter_get_basic(args, &stat_type); } if (strcmp(stat_type, "all") == 0) { nfs_param.core_param.enable_NFSSTATS = false; nfs_param.core_param.enable_FSALSTATS = false; #ifdef _USE_NFS3 nfs_param.core_param.enable_FULLV3STATS = false; #endif nfs_param.core_param.enable_FULLV4STATS = false; nfs_param.core_param.enable_AUTHSTATS = false; nfs_param.core_param.enable_CLNTALLSTATS = false; LogEvent(COMPONENT_CONFIG, "Disabling NFS server statistics counting"); LogEvent(COMPONENT_CONFIG, "Disabling FSAL statistics counting"); /* reset all stats counters */ reset_fsal_stats(); /* resetting server stats includes v3_full & v4_full stats */ reset_server_stats(); LogEvent(COMPONENT_CONFIG, "Disabling auth statistics counting"); /* reset auth counters */ reset_auth_stats(); } if (strcmp(stat_type, "nfs") == 0) { nfs_param.core_param.enable_NFSSTATS = false; #ifdef _USE_NFS3 nfs_param.core_param.enable_FULLV3STATS = false; #endif nfs_param.core_param.enable_FULLV4STATS = false; nfs_param.core_param.enable_CLNTALLSTATS = false; LogEvent(COMPONENT_CONFIG, "Disabling NFS server statistics counting"); /* reset server stats counters */ reset_server_stats(); } if (strcmp(stat_type, "fsal") == 0) { nfs_param.core_param.enable_FSALSTATS = false; LogEvent(COMPONENT_CONFIG, "Disabling FSAL statistics counting"); /* reset fsal stats counters */ reset_fsal_stats(); } #ifdef _USE_NFS3 if (strcmp(stat_type, "v3_full") == 0) { nfs_param.core_param.enable_FULLV3STATS = false; LogEvent(COMPONENT_CONFIG, "Disabling NFSv3 Detailed statistics counting"); /* reset v3_full stats counters */ reset_v3_full_stats(); } #endif if (strcmp(stat_type, "v4_full") == 0) { nfs_param.core_param.enable_FULLV4STATS = false; LogEvent(COMPONENT_CONFIG, "Disabling NFSv4 Detailed statistics counting"); /* reset v4_full stats counters */ reset_v4_full_stats(); } if (strcmp(stat_type, "auth") == 0) { nfs_param.core_param.enable_AUTHSTATS = false; LogEvent(COMPONENT_CONFIG, "Disabling auth statistics counting"); /* reset auth counters */ reset_auth_stats(); } if (strcmp(stat_type, "client_all_ops") == 0) { nfs_param.core_param.enable_CLNTALLSTATS = false; LogEvent(COMPONENT_CONFIG, "Disabling client all ops statistics counting"); /* reset client all ops counters */ reset_clnt_allops_stats(); } gsh_dbus_status_reply(&iter, true, errormsg); now(×tamp); gsh_dbus_append_timestamp(&iter, ×tamp); return true; error: gsh_dbus_status_reply(&iter, false, errormsg); return true; } static struct gsh_dbus_method disable_statistics = { .name = "DisableStats", .method = stats_disable, .args = { STAT_TYPE_ARG, STATUS_REPLY, TIMESTAMP_REPLY, END_ARG_LIST } }; /** * DBUS method to enable statistics counting * */ static bool stats_enable(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "OK"; char *stat_type = NULL; DBusMessageIter iter; struct timespec timestamp; dbus_message_iter_init_append(reply, &iter); if (args == NULL) { errormsg = "message has no arguments"; goto error; } else if (dbus_message_iter_get_arg_type(args) != DBUS_TYPE_STRING) { errormsg = "arg not string"; goto error; } else { dbus_message_iter_get_basic(args, &stat_type); } if (strcmp(stat_type, "all") == 0) { if (!nfs_param.core_param.enable_NFSSTATS) { nfs_param.core_param.enable_NFSSTATS = true; LogEvent(COMPONENT_CONFIG, "Enabling NFS server statistics counting"); now(&nfs_stats_time); } if (!nfs_param.core_param.enable_FSALSTATS) { nfs_param.core_param.enable_FSALSTATS = true; LogEvent(COMPONENT_CONFIG, "Enabling FSAL statistics counting"); now(&fsal_stats_time); } #ifdef _USE_NFS3 if (!nfs_param.core_param.enable_FULLV3STATS) { nfs_param.core_param.enable_FULLV3STATS = true; LogEvent(COMPONENT_CONFIG, "Enabling NFSv3 Detailed statistics counting"); now(&v3_full_stats_time); } #endif if (!nfs_param.core_param.enable_FULLV4STATS) { nfs_param.core_param.enable_FULLV4STATS = true; LogEvent(COMPONENT_CONFIG, "Enabling NFSv4 Detailed statistics counting"); now(&v4_full_stats_time); } if (!nfs_param.core_param.enable_AUTHSTATS) { nfs_param.core_param.enable_AUTHSTATS = true; LogEvent(COMPONENT_CONFIG, "Enabling auth statistics counting"); now(&auth_stats_time); } if (!nfs_param.core_param.enable_CLNTALLSTATS) { nfs_param.core_param.enable_CLNTALLSTATS = true; LogEvent(COMPONENT_CONFIG, "Enabling client all ops statistics counting"); now(&clnt_allops_stats_time); } } if (strcmp(stat_type, "nfs") == 0 && !nfs_param.core_param.enable_NFSSTATS) { nfs_param.core_param.enable_NFSSTATS = true; LogEvent(COMPONENT_CONFIG, "Enabling NFS server statistics counting"); now(&nfs_stats_time); } if (strcmp(stat_type, "fsal") == 0 && !nfs_param.core_param.enable_FSALSTATS) { nfs_param.core_param.enable_FSALSTATS = true; LogEvent(COMPONENT_CONFIG, "Enabling FSAL statistics counting"); now(&fsal_stats_time); } #ifdef _USE_NFS3 if (strcmp(stat_type, "v3_full") == 0 && !nfs_param.core_param.enable_FULLV3STATS) { if (!nfs_param.core_param.enable_NFSSTATS) { errormsg = "First enable NFS stats counting"; goto error; } else { nfs_param.core_param.enable_FULLV3STATS = true; LogEvent(COMPONENT_CONFIG, "Enabling NFSv3 Detailed statistics counting"); now(&v3_full_stats_time); } } #endif if (strcmp(stat_type, "v4_full") == 0 && !nfs_param.core_param.enable_FULLV4STATS) { if (!nfs_param.core_param.enable_NFSSTATS) { errormsg = "First enable NFS stats counting"; goto error; } else { nfs_param.core_param.enable_FULLV4STATS = true; LogEvent(COMPONENT_CONFIG, "Enabling NFSv4 Detailed statistics counting"); now(&v4_full_stats_time); } } if (strcmp(stat_type, "client_all_ops") == 0 && !nfs_param.core_param.enable_CLNTALLSTATS) { if (!nfs_param.core_param.enable_NFSSTATS) { errormsg = "First enable NFS stats counting"; goto error; } else { nfs_param.core_param.enable_CLNTALLSTATS = true; LogEvent(COMPONENT_CONFIG, "Enabling client all ops statistics counting"); now(&clnt_allops_stats_time); } } if (strcmp(stat_type, "auth") == 0 && !nfs_param.core_param.enable_AUTHSTATS) { nfs_param.core_param.enable_AUTHSTATS = true; LogEvent(COMPONENT_CONFIG, "Enabling auth statistics counting"); now(&auth_stats_time); } gsh_dbus_status_reply(&iter, true, errormsg); now(×tamp); gsh_dbus_append_timestamp(&iter, ×tamp); return true; error: gsh_dbus_status_reply(&iter, false, errormsg); return true; } static struct gsh_dbus_method enable_statistics = { .name = "EnableStats", .method = stats_enable, .args = { STAT_TYPE_ARG, STATUS_REPLY, TIMESTAMP_REPLY, END_ARG_LIST } }; /** * DBUS method to gather FSAL statistics * */ static bool stats_fsal(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { char *errormsg = "OK"; char *fsal_name; DBusMessageIter iter; struct fsal_module *fsal_hdl; struct req_op_context op_context; dbus_message_iter_init_append(reply, &iter); if (args == NULL) { errormsg = "message has no arguments"; goto error; } else if (dbus_message_iter_get_arg_type(args) != DBUS_TYPE_STRING) { errormsg = "arg not string"; goto error; } else { dbus_message_iter_get_basic(args, &fsal_name); } if (!nfs_param.core_param.enable_FSALSTATS) { errormsg = "FSAL stat counting disabled"; goto error; } init_op_context_simple(&op_context, NULL, NULL); fsal_hdl = lookup_fsal(fsal_name); release_op_context(); if (fsal_hdl == NULL) { errormsg = "Incorrect FSAL name"; goto error; } if (fsal_hdl->stats == NULL) { errormsg = "FSAL do not support stats counting"; goto error; } if (nfs_param.core_param.enable_FSALSTATS != true) { errormsg = "FSAL stats disabled"; goto error; } gsh_dbus_status_reply(&iter, true, errormsg); gsh_dbus_append_timestamp(&iter, &fsal_stats_time); fsal_hdl->m_ops.fsal_extract_stats(fsal_hdl, &iter); return true; error: gsh_dbus_status_reply(&iter, false, errormsg); return true; } /* Note that just after enabling FSAL stats, we may not have any * stats to return, hence added another message to deal with such * situations. */ static struct gsh_dbus_method fsal_statistics = { .name = "GetFSALStats", .method = stats_fsal, .args = { FSAL_ARG, STATUS_REPLY, TIMESTAMP_REPLY, FSAL_OPS_REPLY, MESSAGE_REPLY, END_ARG_LIST } }; #ifdef _USE_9P /** * DBUS method to report 9p I/O statistics * */ static bool get_9p_export_io(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_export *export = NULL; struct export_stats *export_st = NULL; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); export = lookup_export(args, &errormsg); if (export == NULL) { success = false; } else { export_st = container_of(export, struct export_stats, export); if (export_st->st._9p == NULL) { success = false; errormsg = "Export does not have any 9p activity"; } } gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_9p_iostats(export_st->st._9p, &iter); if (export != NULL) put_gsh_export(export); return true; } static struct gsh_dbus_method export_show_9p_io = { .name = "Get9pIO", .method = get_9p_export_io, .args = { EXPORT_ID_ARG, STATUS_REPLY, TIMESTAMP_REPLY, IOSTATS_REPLY, END_ARG_LIST } }; /** * DBUS method to report 9p protocol operation statistics * */ static bool get_9p_export_op_stats(DBusMessageIter *args, DBusMessage *reply, DBusError *error) { struct gsh_export *export = NULL; struct export_stats *export_st = NULL; u8 opcode; bool success = true; char *errormsg = "OK"; DBusMessageIter iter; dbus_message_iter_init_append(reply, &iter); export = lookup_export(args, &errormsg); if (export == NULL) { success = false; } else { export_st = container_of(export, struct export_stats, export); if (export_st->st._9p == NULL) { success = false; errormsg = "Export does not have any 9p activity"; } } dbus_message_iter_next(args); if (success) success = arg_9p_op(args, &opcode, &errormsg); gsh_dbus_status_reply(&iter, success, errormsg); if (success) server_dbus_9p_opstats(export_st->st._9p, opcode, &iter); if (export != NULL) put_gsh_export(export); return true; } static struct gsh_dbus_method export_show_9p_op_stats = { .name = "Get9pOpStats", .method = get_9p_export_op_stats, .args = { EXPORT_ID_ARG, _9P_OP_ARG, STATUS_REPLY, TIMESTAMP_REPLY, OP_STATS_REPLY, END_ARG_LIST } }; #endif static struct gsh_dbus_method export_show_total_ops = { .name = "GetTotalOPS", .method = get_nfsv_export_total_ops, .args = { EXPORT_ID_ARG, STATUS_REPLY, TIMESTAMP_REPLY, TOTAL_OPS_REPLY, END_ARG_LIST } }; static struct gsh_dbus_method global_show_total_ops = { .name = "GetGlobalOPS", .method = get_nfsv_global_total_ops, .args = { STATUS_REPLY, TIMESTAMP_REPLY, TOTAL_OPS_REPLY, END_ARG_LIST } }; static struct gsh_dbus_method global_show_fast_ops = { .name = "GetFastOPS", .method = get_nfsv_global_fast_ops, .args = { STATUS_REPLY, TIMESTAMP_REPLY, TOTAL_OPS_REPLY, END_ARG_LIST } }; static struct gsh_dbus_method mdcache_show = { .name = "ShowMDCache", .method = show_mdcache_stats, .args = { STATUS_REPLY, TIMESTAMP_REPLY, TOTAL_OPS_REPLY, LRU_UTILIZATION_REPLY, END_ARG_LIST } }; static struct gsh_dbus_method fd_usage_summary = { .name = "ShowFDUsage", .method = show_fd_usage_summary, .args = { STATUS_REPLY, TIMESTAMP_REPLY, FD_USAGE_SUMM_REPLY, END_ARG_LIST } }; /** * @brief Report all IO stats of all exports in one call * * @return * status * error message * time * array of ( * export id * string containing the protocol version * read statistics structure * (requested, transferred, total, errors, latency, * queue wait) * write statistics structure * (requested, transferred, total, errors, latency, * queue wait) * ) */ static bool get_nfs_io(DBusMessageIter *args, DBusMessage *message, DBusError *error) { bool success = true; char *errormsg = "OK"; DBusMessageIter reply_iter, array_iter; /* create a reply iterator from the message */ dbus_message_iter_init_append(message, &reply_iter); if (!nfs_param.core_param.enable_NFSSTATS) errormsg = "NFS stat counting disabled"; /* status and timestamp reply */ gsh_dbus_status_reply(&reply_iter, success, errormsg); gsh_dbus_append_timestamp(&reply_iter, &nfs_stats_time); /* create an array container iterator and loop over all exports */ dbus_message_iter_open_container(&reply_iter, DBUS_TYPE_ARRAY, NFS_ALL_IO_REPLY_ARRAY_TYPE, &array_iter); (void)foreach_gsh_export(&get_all_export_io, false, (void *)&array_iter); dbus_message_iter_close_container(&reply_iter, &array_iter); return true; } static struct gsh_dbus_method export_show_all_io = { .name = "GetNFSIO", .method = get_nfs_io, .args = { STATUS_REPLY, TIMESTAMP_REPLY, NFS_ALL_IO_REPLY, END_ARG_LIST } }; static struct gsh_dbus_method *export_stats_methods[] = { #ifdef _USE_NFS3 &export_show_v3_io, #endif &export_show_v40_io, &export_show_v41_io, &export_show_v42_io, &export_show_nfsmon_io, &export_show_v41_layouts, &export_show_v42_layouts, &export_show_total_ops, #ifdef _USE_9P &export_show_9p_io, &export_show_9p_op_stats, #endif &global_show_total_ops, &global_show_fast_ops, &mdcache_show, &export_show_all_io, &reset_statistics, &fsal_statistics, &enable_statistics, &disable_statistics, &status_stats, #ifdef _USE_NFS3 &v3_full_statistics, #endif &v4_full_statistics, #ifdef _HAVE_GSSAPI &auth_statistics, #endif /* _HAVE_GSSAPI */ &export_details, &fd_usage_summary, NULL }; static struct gsh_dbus_interface export_stats_table = { .name = "org.ganesha.nfsd.exportstats", .methods = export_stats_methods }; /* DBUS list of interfaces on /org/ganesha/nfsd/ExportMgr */ static struct gsh_dbus_interface *export_interfaces[] = { &export_mgr_table, &export_stats_table, NULL }; void dbus_export_init(void) { gsh_dbus_register_path("ExportMgr", export_interfaces); } #endif /* USE_DBUS */ /* Cleanup on shutdown */ void export_mgr_cleanup(void) { PTHREAD_RWLOCK_destroy(&export_by_id.eid_lock); PTHREAD_MUTEX_destroy(&export_admin_mutex); } struct cleanup_list_element export_mgr_cleanup_element = { .clean = export_mgr_cleanup, }; /** * @brief Initialize export manager */ void export_pkginit(void) { PTHREAD_MUTEX_init(&export_admin_mutex, NULL); PTHREAD_RWLOCK_init(&export_by_id.eid_lock, NULL); avltree_init(&export_by_id.t, export_id_cmpf, 0); memset(&export_by_id.cache, 0, sizeof(export_by_id.cache)); RegisterCleanup(&export_mgr_cleanup_element); } /** @} */ �����������������������nfs-ganesha-6.5/src/support/exports.c���������������������������������������������������������������0000664�0000000�0000000�00000306377�14737566223�0017770�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file exports.c * @brief Export parsing and management */ #include "config.h" #include "cidr.h" #include "log.h" #include "fsal.h" #include "nfs_core.h" #include "nfs_file_handle.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_dupreq.h" #include "config_parsing.h" #include "common_utils.h" #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <strings.h> #include <ctype.h> #include "export_mgr.h" #include "fsal_up.h" #include "sal_functions.h" #include "pnfs_utils.h" #include "mdcache.h" /** * @brief Protect EXPORT_DEFAULTS structure for dynamic update. * * If an export->exp_lock is also held by the code, this lock MUST be * taken AFTER the export->exp_lock to avoid ABBA deadlock. * */ pthread_rwlock_t export_opt_lock; #define GLOBAL_EXPORT_PERMS_INITIALIZER(self) \ .def.anonymous_uid = ANON_UID, .def.anonymous_gid = ANON_GID, \ .def.expire_time_attr = \ EXPORT_DEFAULT_CACHE_EXPIRY, /* Note: Access_Type defaults to None on purpose */ /* And no PROTO is included - that is filled */ /* from nfs_param.core_param.core_options. */ \ .def.options = EXPORT_OPTION_ROOT_SQUASH | \ EXPORT_OPTION_NO_ACCESS | \ EXPORT_OPTION_AUTH_DEFAULTS | \ EXPORT_OPTION_XPORT_DEFAULTS | \ EXPORT_OPTION_NO_DELEGATIONS, \ .def.set = UINT32_MAX, .clients = { &self.clients, &self.clients }, struct global_export_perms export_opt = { GLOBAL_EXPORT_PERMS_INITIALIZER( export_opt) }; /* A second copy used in configuration, so we can atomically update the * primary set. */ struct global_export_perms export_opt_cfg = { GLOBAL_EXPORT_PERMS_INITIALIZER( export_opt_cfg) }; static int StrExportOptions(struct display_buffer *dspbuf, struct export_perms *p_perms) { int b_left = display_start(dspbuf); if (b_left <= 0) return b_left; b_left = display_printf(dspbuf, "options=%08" PRIx32 "/%08" PRIx32 " ", p_perms->options, p_perms->set); if (b_left <= 0) return b_left; if ((p_perms->set & EXPORT_OPTION_SQUASH_TYPES) != 0) { if ((p_perms->options & EXPORT_OPTION_ROOT_SQUASH) != 0) b_left = display_cat(dspbuf, "root_squash "); if (b_left <= 0) return b_left; if ((p_perms->options & EXPORT_OPTION_ROOT_ID_SQUASH) != 0) b_left = display_cat(dspbuf, "root_id_squash"); if (b_left <= 0) return b_left; if ((p_perms->options & EXPORT_OPTION_ALL_ANONYMOUS) != 0) b_left = display_cat(dspbuf, "all_squash "); if (b_left <= 0) return b_left; if ((p_perms->options & EXPORT_OPTION_SQUASH_TYPES) == 0) b_left = display_cat(dspbuf, "no_root_squash"); } else b_left = display_cat(dspbuf, " "); if (b_left <= 0) return b_left; if ((p_perms->set & EXPORT_OPTION_ACCESS_MASK) != 0) { if ((p_perms->options & EXPORT_OPTION_READ_ACCESS) != 0) b_left = display_cat(dspbuf, ", R"); else b_left = display_cat(dspbuf, ", -"); if (b_left <= 0) return b_left; if ((p_perms->options & EXPORT_OPTION_WRITE_ACCESS) != 0) b_left = display_cat(dspbuf, "W"); else b_left = display_cat(dspbuf, "-"); if (b_left <= 0) return b_left; if ((p_perms->options & EXPORT_OPTION_MD_READ_ACCESS) != 0) b_left = display_cat(dspbuf, "r"); else b_left = display_cat(dspbuf, "-"); if (b_left <= 0) return b_left; if ((p_perms->options & EXPORT_OPTION_MD_WRITE_ACCESS) != 0) b_left = display_cat(dspbuf, "w"); else b_left = display_cat(dspbuf, "-"); } else b_left = display_cat(dspbuf, ", "); if (b_left <= 0) return b_left; if ((p_perms->set & EXPORT_OPTION_PROTOCOLS) != 0) { if ((p_perms->options & EXPORT_OPTION_NFSV3) != 0) b_left = display_cat(dspbuf, ", 3"); else b_left = display_cat(dspbuf, ", -"); if (b_left <= 0) return b_left; if ((p_perms->options & EXPORT_OPTION_NFSV4) != 0) b_left = display_cat(dspbuf, "4"); else b_left = display_cat(dspbuf, "-"); if (b_left <= 0) return b_left; if ((p_perms->options & EXPORT_OPTION_9P) != 0) b_left = display_cat(dspbuf, "9"); else b_left = display_cat(dspbuf, "-"); } else b_left = display_cat(dspbuf, ", "); if (b_left <= 0) return b_left; if ((p_perms->set & EXPORT_OPTION_TRANSPORTS) != 0) { if ((p_perms->options & EXPORT_OPTION_UDP) != 0) b_left = display_cat(dspbuf, ", UDP"); else b_left = display_cat(dspbuf, ", ---"); if (b_left <= 0) return b_left; if ((p_perms->options & EXPORT_OPTION_TCP) != 0) b_left = display_cat(dspbuf, ", TCP"); else b_left = display_cat(dspbuf, ", ---"); if (b_left <= 0) return b_left; if ((p_perms->options & EXPORT_OPTION_RDMA) != 0) b_left = display_cat(dspbuf, ", RDMA"); else b_left = display_cat(dspbuf, ", ----"); } else b_left = display_cat(dspbuf, ", "); if (b_left <= 0) return b_left; if ((p_perms->set & EXPORT_OPTION_MANAGE_GIDS) == 0) b_left = display_cat(dspbuf, ", "); else if ((p_perms->options & EXPORT_OPTION_MANAGE_GIDS) != 0) b_left = display_cat(dspbuf, ", Manage_Gids "); else b_left = display_cat(dspbuf, ", No Manage_Gids"); if (b_left <= 0) return b_left; if ((p_perms->set & EXPORT_OPTION_DELEGATIONS) != 0) { if ((p_perms->options & EXPORT_OPTION_READ_DELEG) != 0) b_left = display_cat(dspbuf, ", R"); else b_left = display_cat(dspbuf, ", -"); if (b_left <= 0) return b_left; if ((p_perms->options & EXPORT_OPTION_WRITE_DELEG) != 0) b_left = display_cat(dspbuf, "W Deleg"); else b_left = display_cat(dspbuf, "- Deleg"); } else b_left = display_cat(dspbuf, ", "); if (b_left <= 0) return b_left; if ((p_perms->set & EXPORT_OPTION_ANON_UID_SET) != 0) b_left = display_printf(dspbuf, ", anon_uid=%6d", (int)p_perms->anonymous_uid); else b_left = display_cat(dspbuf, ", "); if (b_left <= 0) return b_left; if ((p_perms->set & EXPORT_OPTION_ANON_GID_SET) != 0) b_left = display_printf(dspbuf, ", anon_gid=%6d", (int)p_perms->anonymous_gid); else b_left = display_cat(dspbuf, ", "); if (b_left <= 0) return b_left; if ((p_perms->set & EXPORT_OPTION_EXPIRE_SET) != 0) b_left = display_printf(dspbuf, ", expire=%8" PRIi32, (int)p_perms->expire_time_attr); else b_left = display_cat(dspbuf, ", "); if (b_left <= 0) return b_left; if ((p_perms->set & EXPORT_OPTION_AUTH_TYPES) != 0) { if ((p_perms->options & EXPORT_OPTION_AUTH_NONE) != 0) b_left = display_cat(dspbuf, ", none"); if (b_left <= 0) return b_left; if ((p_perms->options & EXPORT_OPTION_AUTH_UNIX) != 0) b_left = display_cat(dspbuf, ", sys"); if (b_left <= 0) return b_left; if ((p_perms->options & EXPORT_OPTION_RPCSEC_GSS_NONE) != 0) b_left = display_cat(dspbuf, ", krb5"); if (b_left <= 0) return b_left; if ((p_perms->options & EXPORT_OPTION_RPCSEC_GSS_INTG) != 0) b_left = display_cat(dspbuf, ", krb5i"); if (b_left <= 0) return b_left; if ((p_perms->options & EXPORT_OPTION_RPCSEC_GSS_PRIV) != 0) b_left = display_cat(dspbuf, ", krb5p"); } return b_left; } void LogExportClientListEntry(log_levels_t level, int line, const char *func, const char *tag, struct exportlist_client_entry *entry) { char buf[1024] = "\0"; struct display_buffer dspbuf = { sizeof(buf), buf, buf }; int b_left = display_start(&dspbuf); if (!isLevel(COMPONENT_EXPORT, level)) return; if (b_left > 0 && tag != NULL) b_left = display_cat(&dspbuf, tag); if (b_left > 0 && level >= NIV_DEBUG) b_left = display_printf(&dspbuf, "%p ", entry); if (b_left > 0) b_left = StrClient(&dspbuf, &entry->client_entry); if (b_left > 0) b_left = display_cat(&dspbuf, " ("); if (b_left > 0) b_left = StrExportOptions(&dspbuf, &entry->client_perms); if (b_left > 0) b_left = display_cat(&dspbuf, ")"); DisplayLogComponentLevel(COMPONENT_EXPORT, (char *)__FILE__, line, func, level, "%s", buf); } #define LogMidDebug_ExportClientListEntry(tag, cli) \ LogExportClientListEntry(NIV_MID_DEBUG, __LINE__, (char *)__func__, \ tag, cli) static void LogExportClients(log_levels_t level, int line, const char *func, const char *tag, struct gsh_export *export) { struct glist_head *glist; PTHREAD_RWLOCK_rdlock(&export->exp_lock); glist_for_each(glist, &export->clients) { struct base_client_entry *client; client = glist_entry(glist, struct base_client_entry, cle_list); LogExportClientListEntry( level, line, func, tag, container_of(client, struct exportlist_client_entry, client_entry)); } PTHREAD_RWLOCK_unlock(&export->exp_lock); } #define LogMidDebug_ExportClients(export) \ LogExportClients(NIV_MID_DEBUG, __LINE__, __func__, NULL, export) void FreeExportClient(struct base_client_entry *client) { struct exportlist_client_entry *expclient = NULL; expclient = container_of(client, struct exportlist_client_entry, client_entry); gsh_free(expclient); } /** * @brief Commit and FSAL sub-block init/commit helpers */ /** * @brief Init for CLIENT sub-block of an export. * * Allocate one exportlist_client structure for parameter * processing. The client_commit will allocate additional * exportlist_client__ storage for each of its enumerated * clients and free the initial block. We only free that * resource here on errors. */ static void *client_init(void *link_mem, void *self_struct) { struct exportlist_client_entry *expcli; struct base_client_entry *cli; assert(link_mem != NULL || self_struct != NULL); if (link_mem == NULL) { return self_struct; } else if (self_struct == NULL) { expcli = gsh_calloc(1, sizeof(struct exportlist_client_entry)); cli = &expcli->client_entry; glist_init(&cli->cle_list); cli->type = PROTO_CLIENT; return expcli; } else { /* free resources case */ expcli = self_struct; cli = &expcli->client_entry; if (!glist_empty(&cli->cle_list)) FreeClientList(&cli->cle_list, FreeExportClient); assert(glist_empty(&cli->cle_list)); gsh_free(expcli); return NULL; } } /** * @brief Init for CLIENT sub-block of an export. * * Allocate one exportlist_client structure for parameter * processing. The client_commit will allocate additional * exportlist_client__ storage for each of its enumerated * clients and free the initial block. We only free that * resource here on errors. */ static void *pseudofs_client_init(void *link_mem, void *self_struct) { struct exportlist_client_entry *expcli; assert(link_mem != NULL || self_struct != NULL); expcli = client_init(link_mem, self_struct); if (self_struct != NULL) return expcli; expcli->client_perms.options = EXPORT_OPTION_ROOT | EXPORT_OPTION_NFSV4; expcli->client_perms.set = EXPORT_OPTION_SQUASH_TYPES | EXPORT_OPTION_PROTOCOLS; return expcli; } /** * @brief Commit this client block * * Validate "clients" token(s) and perms. We enter with a client entry * allocated by proc_block. Since we expand the clients token both * here and in add_client, we allocate new client entries and free * what was passed to us rather than try and link it in. * * @param node [IN] the config_node **not used** * @param link_mem [IN] the exportlist entry. add_client adds to its glist. * @param self_struct [IN] the filled out client entry with a PROTO_CLIENT * * @return 0 on success, error count for failure. */ static int client_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { struct exportlist_client_entry *expcli; struct base_client_entry *cli; struct gsh_export *export; int errcnt = 0; export = container_of(link_mem, struct gsh_export, clients); expcli = self_struct; cli = &expcli->client_entry; assert(cli->type == PROTO_CLIENT); if (glist_empty(&cli->cle_list)) { LogCrit(COMPONENT_CONFIG, "No clients specified"); err_type->invalid = true; errcnt++; } else { uint32_t cl_perm_opt, def_opt; cl_perm_opt = expcli->client_perms.options; def_opt = export_opt.def.options; if ((cl_perm_opt & def_opt & EXPORT_OPTION_PROTOCOLS) != (cl_perm_opt & EXPORT_OPTION_PROTOCOLS)) { /* There is a protocol bit set in the options that was * not set by the core param Protocols. */ LogWarn(COMPONENT_CONFIG, "A protocol is specified for a CLIENT block that is not enabled in NFS_CORE_PARAM, fixing up"); expcli->client_perms.options = (cl_perm_opt & ~EXPORT_OPTION_PROTOCOLS) | (cl_perm_opt & def_opt & EXPORT_OPTION_PROTOCOLS); } glist_splice_tail(&export->clients, &cli->cle_list); } if (errcnt == 0) client_init(link_mem, self_struct); return errcnt; } /** * @brief Clean up EXPORT path strings */ void clean_export_paths(struct gsh_export *export) { LogFullDebug(COMPONENT_EXPORT, "Cleaning paths for %d fullpath %s pseudopath %s", export->export_id, export->cfg_fullpath, export->cfg_pseudopath); /* Some admins stuff a '/' at the end for some reason. * chomp it so we have a /dir/path/basename to work * with. But only if it's a non-root path starting * with /. */ if (export->cfg_fullpath && export->cfg_fullpath[0] == '/') { int pathlen; pathlen = strlen(export->cfg_fullpath); while ((export->cfg_fullpath[pathlen - 1] == '/') && (pathlen > 1)) pathlen--; export->cfg_fullpath[pathlen] = '\0'; } /* Remove trailing slash */ if (export->cfg_pseudopath && export->cfg_pseudopath[0] == '/') { int pathlen; pathlen = strlen(export->cfg_pseudopath); while ((export->cfg_pseudopath[pathlen - 1] == '/') && (pathlen > 1)) pathlen--; export->cfg_pseudopath[pathlen] = '\0'; } LogFullDebug(COMPONENT_EXPORT, "Final paths for %d fullpath %s pseudopath %s", export->export_id, export->cfg_fullpath, export->cfg_pseudopath); } /** * @brief Commit a FSAL sub-block * * Use the Name parameter passed in via the link_mem to lookup the * fsal. If the fsal is not loaded (yet), load it and call its init. * * Create an export and pass the FSAL sub-block to it so that the * fsal method can process the rest of the parameters in the block */ static int fsal_cfg_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { struct fsal_export **exp_hdl = link_mem; struct gsh_export *export = container_of(exp_hdl, struct gsh_export, fsal_export); struct fsal_args *fp = self_struct; struct fsal_module *fsal; struct req_op_context op_context; uint64_t MaxRead, MaxWrite; fsal_status_t status; int errcnt; /* Get a ref to the export and initialize op_context */ get_gsh_export_ref(export); init_op_context_simple(&op_context, export, NULL); errcnt = fsal_load_init(node, fp->name, &fsal, err_type); if (errcnt > 0) goto err; clean_export_paths(export); /* Since as yet, we don't have gsh_refstr for op_ctx, we need to * create temporary ones here. */ op_ctx->ctx_fullpath = gsh_refstr_dup(export->cfg_fullpath); if (export->cfg_pseudopath != NULL) { op_ctx->ctx_pseudopath = gsh_refstr_dup(export->cfg_pseudopath); } else { /* An export that does not export NFSv4 may not have a * Pseudo Path. */ op_ctx->ctx_pseudopath = gsh_refstr_get(no_export); } /* The handle cache (currently MDCACHE) must be at the top of the stack * of FSALs. To achieve this, call directly into MDCACHE, passing the * sub-FSAL's fsal_module. MDCACHE will stack itself on top of that * FSAL, continuing down the chain. */ status = mdcache_fsal_create_export(fsal, node, err_type, &fsal_up_top); if (FSAL_IS_ERROR(status)) { fsal_put(fsal); LogCrit(COMPONENT_CONFIG, "Could not create export for (%s) to (%s)", export->cfg_pseudopath, export->cfg_fullpath); LogFullDebug(COMPONENT_FSAL, "FSAL %s fsal_refcount %" PRIu32, fsal->name, atomic_fetch_int32_t(&fsal->refcount)); err_type->cur_exp_create_err = true; errcnt++; goto err; } assert(op_ctx->fsal_export != NULL); export->fsal_export = op_ctx->fsal_export; /* We are connected up to the fsal side. Now * validate maxread/write etc with fsal params */ MaxRead = export->fsal_export->exp_ops.fs_maxread(export->fsal_export); MaxWrite = export->fsal_export->exp_ops.fs_maxwrite(export->fsal_export); if (export->MaxRead > MaxRead && MaxRead != 0) { LogInfo(COMPONENT_CONFIG, "Readjusting MaxRead to FSAL, %" PRIu64 " -> %" PRIu64, export->MaxRead, MaxRead); export->MaxRead = MaxRead; } if (export->MaxWrite > MaxWrite && MaxWrite != 0) { LogInfo(COMPONENT_CONFIG, "Readjusting MaxWrite to FSAL, %" PRIu64 " -> %" PRIu64, export->MaxWrite, MaxWrite); export->MaxWrite = MaxWrite; } err: release_op_context(); /* Don't leak the FSAL block */ err_type->dispose = true; return errcnt; } /** * @brief Commit a FSAL sub-block for export update * * Use the Name parameter passed in via the link_mem to lookup the * fsal. If the fsal is not loaded (yet), load it and call its init. * * Create an export and pass the FSAL sub-block to it so that the * fsal method can process the rest of the parameters in the block */ static int fsal_update_cfg_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { struct fsal_export **exp_hdl = link_mem; struct gsh_export *probe_exp; struct gsh_export *export = container_of(exp_hdl, struct gsh_export, fsal_export); struct fsal_args *fp = self_struct; struct req_op_context op_context; uint64_t MaxRead, MaxWrite; struct fsal_module *fsal; fsal_status_t status; int errcnt; /* Determine if this is actually an update */ probe_exp = get_gsh_export(export->export_id); if (probe_exp == NULL) { /* Export not found by ID, assume it's a new export. */ return fsal_cfg_commit(node, link_mem, self_struct, err_type); } /* Initialize op_context from the probe_exp */ init_op_context_simple(&op_context, probe_exp, probe_exp->fsal_export); errcnt = fsal_load_init(node, fp->name, &fsal, err_type); if (errcnt > 0) goto err; /* We have to clean the export paths so we can properly compare them * later. */ clean_export_paths(export); /* The handle cache (currently MDCACHE) must be at the top of the stack * of FSALs. To achieve this, call directly into MDCACHE, passing the * sub-FSAL's fsal_module. MDCACHE will stack itself on top of that * FSAL, continuing down the chain. */ status = mdcache_fsal_update_export(fsal, node, err_type, probe_exp->fsal_export); if (FSAL_IS_ERROR(status)) { fsal_put(fsal); LogCrit(COMPONENT_CONFIG, "Could not update export for (%s) to (%s)", export->cfg_pseudopath, export->cfg_fullpath); LogFullDebug(COMPONENT_FSAL, "FSAL %s fsal_refcount %" PRIu32, fsal->name, atomic_fetch_int32_t(&fsal->refcount)); err_type->cur_exp_create_err = true; errcnt++; goto err; } /* We don't assign export->fsal_export because we don't have a new * fsal_export to later release... */ /* Now validate maxread/write etc with fsal params based on the * original export, which will then allow us to validate the * possibly changed values in the new export config. */ MaxRead = probe_exp->fsal_export->exp_ops.fs_maxread( probe_exp->fsal_export); MaxWrite = probe_exp->fsal_export->exp_ops.fs_maxwrite( probe_exp->fsal_export); if (export->MaxRead > MaxRead && MaxRead != 0) { LogInfo(COMPONENT_CONFIG, "Readjusting MaxRead to FSAL, %" PRIu64 " -> %" PRIu64, export->MaxRead, MaxRead); export->MaxRead = MaxRead; } if (export->MaxWrite > MaxWrite && MaxWrite != 0) { LogInfo(COMPONENT_CONFIG, "Readjusting MaxWrite to FSAL, %" PRIu64 " -> %" PRIu64, export->MaxWrite, MaxWrite); export->MaxWrite = MaxWrite; } LogDebug(COMPONENT_EXPORT, "Export %d FSAL config update processed", export->export_id); err: release_op_context(); /* Don't leak the FSAL block */ err_type->dispose = true; return errcnt; } /** * @brief EXPORT block handlers */ /** * @brief Initialize an export block * * There is no link_mem init required because we are allocating * here and doing an insert_gsh_export at the end of export_commit * to attach it to the export manager. * * Use free_exportlist here because in this case, we have not * gotten far enough to hand it over to the export manager. */ static void *export_init(void *link_mem, void *self_struct) { struct gsh_export *export; if (self_struct == NULL) { export = alloc_export(); LogFullDebug(COMPONENT_EXPORT, "Allocated export %p", export); return export; } else { /* free resources case */ export = self_struct; /* As part of create_export(), FSAL shall take * reference to the export if it supports pNFS. */ if (export->has_pnfs_ds) { assert(export->refcnt == 1); /* export is not yet added to the export * manager. Hence there shall not be any * other thread racing here. So no need * to take lock. */ export->has_pnfs_ds = false; /* Remove and destroy the fsal_pnfs_ds */ pnfs_ds_remove(export->export_id); } else { /* Release the export allocated above */ LogFullDebug(COMPONENT_EXPORT, "Releasing export %p", export); put_gsh_export_config(export); } return NULL; } } static inline int strcmp_null(const char *s1, const char *s2) { if (s1 == s2) { /* Both strings are NULL or both are same pointer */ return 0; } if (s1 == NULL) { /* First string is NULL, consider that LESS than */ return -1; } if (s2 == NULL) { /* Second string is NULL, consider that GREATER than */ return 1; } return strcmp(s1, s2); } static inline void update_atomic_fields(struct gsh_export *export, struct gsh_export *src) { atomic_store_uint64_t(&export->MaxRead, src->MaxRead); atomic_store_uint64_t(&export->MaxWrite, src->MaxWrite); atomic_store_uint64_t(&export->PrefRead, src->PrefRead); atomic_store_uint64_t(&export->PrefWrite, src->PrefWrite); atomic_store_uint64_t(&export->PrefReaddir, src->PrefReaddir); atomic_store_uint64_t(&export->MaxOffsetWrite, src->MaxOffsetWrite); atomic_store_uint64_t(&export->MaxOffsetRead, src->MaxOffsetRead); atomic_store_uint32_t(&export->options, src->options); atomic_store_uint32_t(&export->options_set, src->options_set); } static inline void copy_gsh_export(struct gsh_export *dest, struct gsh_export *src) { struct gsh_refstr *old_fullpath = NULL, *old_pseudopath = NULL; /* Update atomic fields */ update_atomic_fields(dest, src); /* Now take lock and swap out client list and export_perms... */ PTHREAD_RWLOCK_wrlock(&dest->exp_lock); /* Put references to old refstr */ if (dest->fullpath != NULL) old_fullpath = rcu_dereference(dest->fullpath); if (dest->pseudopath != NULL) old_pseudopath = rcu_dereference(dest->pseudopath); /* Free old cfg_fullpath and cfg_pseudopath */ gsh_free(dest->cfg_fullpath); gsh_free(dest->cfg_pseudopath); /* Copy config fullpath and create new refstr */ if (src->cfg_fullpath != NULL) { dest->cfg_fullpath = gsh_strdup(src->cfg_fullpath); rcu_set_pointer(&(dest->fullpath), gsh_refstr_dup(dest->cfg_fullpath)); } else { dest->cfg_fullpath = NULL; rcu_set_pointer(&(dest->fullpath), NULL); } /* Copy config pseudopath and create new refstr */ if (src->cfg_pseudopath != NULL) { dest->cfg_pseudopath = gsh_strdup(src->cfg_pseudopath); rcu_set_pointer(&(dest->pseudopath), gsh_refstr_dup(dest->cfg_pseudopath)); } else { dest->cfg_pseudopath = NULL; rcu_set_pointer(&(dest->pseudopath), NULL); } synchronize_rcu(); if (old_fullpath) gsh_refstr_put(old_fullpath); if (old_pseudopath) gsh_refstr_put(old_pseudopath); /* Copy the export perms into the existing export. */ dest->export_perms = src->export_perms; /* Swap the client list from the src export and the dest * export. When we then dispose of the new export, the * old client list will also be disposed of. */ LogFullDebug(COMPONENT_EXPORT, "Original clients = (%p,%p) New clients = (%p,%p)", dest->clients.next, dest->clients.prev, src->clients.next, src->clients.prev); glist_swap_lists(&dest->clients, &src->clients); PTHREAD_RWLOCK_unlock(&dest->exp_lock); } uint32_t export_check_options(struct gsh_export *exp) { struct export_perms perms; memset(&perms, 0, sizeof(perms)); /* Take lock */ PTHREAD_RWLOCK_rdlock(&exp->exp_lock); /* Start with options set for the export */ perms.options = exp->export_perms.options & exp->export_perms.set; perms.set = exp->export_perms.set; PTHREAD_RWLOCK_rdlock(&export_opt_lock); /* Any options not set by the export, take from the EXPORT_DEFAULTS * block. */ perms.options |= export_opt.conf.options & export_opt.conf.set & ~perms.set; perms.set |= export_opt.conf.set; /* And finally take any options not yet set from global defaults */ perms.options |= export_opt.def.options & ~perms.set; perms.set |= export_opt.def.set; if (isMidDebug(COMPONENT_EXPORT)) { char str[1024] = "\0"; struct display_buffer dspbuf = { sizeof(str), str, str }; (void)StrExportOptions(&dspbuf, &exp->export_perms); LogMidDebug(COMPONENT_EXPORT, "EXPORT (%s)", str); display_reset_buffer(&dspbuf); (void)StrExportOptions(&dspbuf, &export_opt.conf); LogMidDebug(COMPONENT_EXPORT, "EXPORT_DEFAULTS (%s)", str); display_reset_buffer(&dspbuf); (void)StrExportOptions(&dspbuf, &export_opt.def); LogMidDebug(COMPONENT_EXPORT, "default options (%s)", str); display_reset_buffer(&dspbuf); (void)StrExportOptions(&dspbuf, &perms); LogMidDebug(COMPONENT_EXPORT, "Final options (%s)", str); } PTHREAD_RWLOCK_unlock(&export_opt_lock); /* Release lock */ PTHREAD_RWLOCK_unlock(&exp->exp_lock); return perms.options; } /** * @brief Commit an export block * * Validate the export level parameters. fsal and client * parameters are already done. */ enum export_commit_type { initial_export, add_export, update_export, }; static int export_commit_common(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type, enum export_commit_type commit_type) { struct gsh_export *export = self_struct, *probe_exp; int errcnt = 0; char perms[1024] = "\0"; struct display_buffer dspbuf = { sizeof(perms), perms, perms }; uint32_t options = export_check_options(export); LogFullDebug(COMPONENT_EXPORT, "Processing %p", export); /* Validate the pseudo path if present is an absolute path. */ if (export->cfg_pseudopath != NULL && export->cfg_pseudopath[0] != '/') { LogCrit(COMPONENT_CONFIG, "A Pseudo path must be an absolute path"); err_type->invalid = true; errcnt++; return errcnt; } /* Validate, if export_id 0 is explicitly configured, the pseudopath * MUST be "/". */ if (export->export_id == 0 && export->cfg_pseudopath != NULL && export->cfg_pseudopath[1] != '\0') { LogCrit(COMPONENT_CONFIG, "Export id 0 can only export \"/\" not (%s)", export->cfg_pseudopath); err_type->invalid = true; errcnt++; return errcnt; } /* validate the export now */ if ((export->export_perms.options & EXPORT_OPTION_NFSV4) != 0 && (export->export_perms.set & EXPORT_OPTION_NFSV4) != 0 && export->cfg_pseudopath == NULL) { /* This is only an error if the export is explicitly * exported NFSv4. */ LogCrit(COMPONENT_CONFIG, "Export %d would be exported NFSv4 explicitly but no Pseudo path defined", export->export_id); err_type->invalid = true; errcnt++; return errcnt; } else if ((options & EXPORT_OPTION_NFSV4) != 0 && export->cfg_pseudopath == NULL) { /* This is an export without a pseudopath when the default * options indicate all exports are to be exported NFSv4. */ LogWarn(COMPONENT_CONFIG, "Export %d would be exported NFSv4 by default but no Pseudo path defined", export->export_id); export->export_perms.options = (export->export_perms.options & ~EXPORT_OPTION_PROTOCOLS) | (options & EXPORT_OPTION_PROTOCOLS & ~EXPORT_OPTION_NFSV4); export->export_perms.set |= EXPORT_OPTION_PROTOCOLS; } /* Validate if export_id 0 is explicitly configured that it WILL be * exported NFSv4, even if due to defaults. */ if (export->export_id == 0 && (options & EXPORT_OPTION_NFSV4) == 0) { LogCrit(COMPONENT_CONFIG, "Export id 0 MUST be exported at least NFSv4"); err_type->invalid = true; errcnt++; return errcnt; } if ((export->export_perms.options & export_opt.def.options & export->export_perms.set & EXPORT_OPTION_PROTOCOLS) != (export->export_perms.options & export->export_perms.set & EXPORT_OPTION_PROTOCOLS)) { /* There is a protocol bit set in the options that was not * set by the core param Protocols. */ LogWarn(COMPONENT_CONFIG, "A protocol is specified for export %d that is not enabled in NFS_CORE_PARAM, fixing up", export->export_id); export->export_perms.options = (export->export_perms.options & ~EXPORT_OPTION_PROTOCOLS) | (export->export_perms.options & export_opt.def.options & EXPORT_OPTION_PROTOCOLS); } /* If we are using mount_path_pseudo = true we MUST have a Pseudo Path. */ if (nfs_param.core_param.mount_path_pseudo && export->cfg_pseudopath == NULL) { LogCrit(COMPONENT_CONFIG, "NFS_CORE_PARAM mount_path_pseudo is TRUE but no Pseudo path defined"); err_type->invalid = true; errcnt++; return errcnt; } if (export->export_id == 0) { if (export->cfg_pseudopath == NULL) { LogCrit(COMPONENT_CONFIG, "Pseudo path must be \"/\" for export id 0"); err_type->invalid = true; errcnt++; } else if (export->cfg_pseudopath[1] != '\0') { LogCrit(COMPONENT_CONFIG, "Pseudo path must be \"/\" for export id 0"); err_type->invalid = true; errcnt++; } if ((export->export_perms.options & export->export_perms.set & EXPORT_OPTION_NFSV4) != EXPORT_OPTION_NFSV4) { LogCrit(COMPONENT_CONFIG, "Export id 0 must include 4 in Protocols"); err_type->invalid = true; errcnt++; } } if (errcnt) { LogCrit(COMPONENT_CONFIG, "Error count %d exiting", errcnt); return errcnt; /* have basic errors. don't even try more... */ } /* Note: need to check export->fsal_export AFTER we have checked for * duplicate export_id. That is because an update export WILL NOT * have fsal_export attached. */ probe_exp = get_gsh_export(export->export_id); if (commit_type == update_export && probe_exp != NULL) { bool mount_export = false; bool mount_status_changed = false; /* We have an actual update case, probe_exp is the target * to update. Check all the options that MUST match. * Note that Path/fullpath will not be NULL, but we compare * the same way as the other string options for code * consistency. * * It's ok in here to directly access the gsh_refstr because we * can't be racing with another thread for this export... */ LogFullDebug(COMPONENT_EXPORT, "Updating %p", probe_exp); LogMidDebug(COMPONENT_EXPORT, "Old Client List"); LogMidDebug_ExportClients(probe_exp); LogMidDebug(COMPONENT_EXPORT, "New Client List"); LogMidDebug_ExportClients(export); if (strcmp_null(export->FS_tag, probe_exp->FS_tag) != 0) { /* Tag does not match, currently not a candidate for * update. */ LogCrit(COMPONENT_CONFIG, "Tag for export update %d %s doesn't match %s", export->export_id, export->FS_tag, probe_exp->FS_tag); err_type->invalid = true; errcnt++; } if (strcmp_null(export->cfg_pseudopath, probe_exp->cfg_pseudopath) != 0) { /* Pseudo does not match, mark to unmount old and * mount at new location. */ LogInfo(COMPONENT_EXPORT, "Pseudo for export %d changing to %s from to %s", export->export_id, export->cfg_pseudopath, probe_exp->cfg_pseudopath); mount_status_changed |= true; mount_export |= export_can_be_mounted(export); } if (strcmp_null(export->cfg_fullpath, probe_exp->cfg_fullpath) != 0) { /* Path does not match, currently not a candidate for * update. */ LogCrit(COMPONENT_CONFIG, "Path for export update %d %s doesn't match %s", export->export_id, export->cfg_fullpath, probe_exp->cfg_fullpath); err_type->invalid = true; errcnt++; } /* At present Filesystem_Id is not updateable, check that * it did not change. */ if (probe_exp->filesystem_id.major != export->filesystem_id.major || probe_exp->filesystem_id.minor != export->filesystem_id.minor) { LogCrit(COMPONENT_CONFIG, "Filesystem_Id for export update %d %" PRIu64 ".%" PRIu64 " doesn't match%" PRIu64 ".%" PRIu64, export->export_id, export->filesystem_id.major, export->filesystem_id.minor, probe_exp->filesystem_id.major, probe_exp->filesystem_id.minor); err_type->invalid = true; errcnt++; } /* We can't compare the FSAL names because we don't actually * have an fsal_export for "export". */ if (errcnt > 0) { put_gsh_export(probe_exp); LogCrit(COMPONENT_CONFIG, "Error count %d exiting", errcnt); return errcnt; } if (((options & EXPORT_OPTION_NFSV4) == EXPORT_OPTION_NFSV4) != probe_exp->is_mounted) { /* The new options for NFSv4 probably don't match the * old options. */ bool mountable = export_can_be_mounted(export); LogDebug(COMPONENT_EXPORT, "Export %d NFSv4 changing from %s to %s", probe_exp->export_id, probe_exp->is_mounted ? "mounted" : "not mounted", mountable ? "can be mounted" : "can not be mounted"); mount_status_changed |= true; mount_export |= mountable; } if (mount_export) { /* This export has changed in a way that it needs to be * remounted. */ probe_exp->update_remount = true; } if (mount_status_changed && probe_exp->is_mounted) { /* Mark this export to be unmounted during the prune * phase, it will also be added to the remount work if * appropriate. */ probe_exp->update_prune_unmount = true; } /* Grab config_generation for this config */ probe_exp->config_gen = get_parse_root_generation(node); copy_gsh_export(probe_exp, export); /* We will need to dispose of the config export since we * updated the existing export. */ err_type->dispose = true; /* Release the reference to the updated export. */ put_gsh_export(probe_exp); goto success; } if (commit_type == update_export) { /* We found a new export during export update, consider it * an add_export for the rest of configuration. */ commit_type = add_export; } if (probe_exp != NULL) { LogDebug(COMPONENT_EXPORT, "Export %d already exists", export->export_id); put_gsh_export(probe_exp); err_type->exists = true; errcnt++; } /* export->fsal_export is valid iff fsal_cfg_commit succeeds. * Config code calls export_commit even if fsal_cfg_commit fails at * the moment, so error out here if fsal_cfg_commit failed. */ if (export->fsal_export == NULL) { LogCrit(COMPONENT_CONFIG, "fsal_export is NULL"); err_type->validate = true; errcnt++; return errcnt; } if (export->FS_tag != NULL) { probe_exp = get_gsh_export_by_tag(export->FS_tag); if (probe_exp != NULL) { put_gsh_export(probe_exp); LogCrit(COMPONENT_CONFIG, "Tag (%s) is a duplicate", export->FS_tag); if (!err_type->exists) err_type->invalid = true; errcnt++; } } if (export->cfg_pseudopath != NULL) { probe_exp = get_gsh_export_by_pseudo(export->cfg_pseudopath, true); if (probe_exp != NULL) { LogCrit(COMPONENT_CONFIG, "Pseudo path (%s) is a duplicate", export->cfg_pseudopath); if (!err_type->exists) err_type->invalid = true; errcnt++; put_gsh_export(probe_exp); } } probe_exp = get_gsh_export_by_path(export->cfg_fullpath, true); if (probe_exp != NULL) { if (export->cfg_pseudopath == NULL && export->FS_tag == NULL) { LogCrit(COMPONENT_CONFIG, "Duplicate path (%s) without unique tag or Pseudo path", export->cfg_fullpath); err_type->invalid = true; errcnt++; } /* If unique Tag and/or Pseudo, there is no error, but we still * need to release the export reference. */ put_gsh_export(probe_exp); } if (errcnt) { if (err_type->exists && !err_type->invalid) LogDebug(COMPONENT_EXPORT, "Duplicate export id = %d", export->export_id); else LogCrit(COMPONENT_CONFIG, "Duplicate export id = %d", export->export_id); return errcnt; /* have errors. don't init or load a fsal */ } /* Convert fullpath and pseudopath into gsh_refstr. Do this now so that * init_export_root() has them available when it creates root context. */ export->fullpath = gsh_refstr_dup(export->cfg_fullpath); if (export->cfg_pseudopath != NULL) { export->pseudopath = gsh_refstr_dup(export->cfg_pseudopath); } else { /* An export that does not export NFSv4 may not have a * Pseudo Path. */ export->pseudopath = NULL; } if (commit_type != initial_export) { /* add_export or update_export with new export_id. */ int rc = init_export_root(export); if (rc) { switch (rc) { case EINVAL: err_type->invalid = true; break; case EFAULT: err_type->internal = true; break; default: err_type->resource = true; } LogCrit(COMPONENT_CONFIG, "init_export_root failed"); errcnt++; return errcnt; } if (!mount_gsh_export(export)) { LogCrit(COMPONENT_CONFIG, "mount_gsh_export failed"); err_type->internal = true; errcnt++; return errcnt; } } if (!insert_gsh_export(export)) { LogCrit(COMPONENT_CONFIG, "Export id %d already in use.", export->export_id); err_type->exists = true; errcnt++; return errcnt; } /* add_export_commit shouldn't add this export to mount work as * add_export_commit deals with creating pseudo mount directly. * So add this export to mount work only if is not a dynamically * added export. We add all such exports because only once all the * export config load is complete can we be sure of all the options. */ if (commit_type == initial_export) export_add_to_mount_work(export); LogMidDebug_ExportClients(export); /* Copy the generation */ export->config_gen = get_parse_root_generation(node); success: (void)StrExportOptions(&dspbuf, &export->export_perms); /* It's ok below to directly access the gsh_refstr without an additional * reference because we can't be racing with another thread on this * export... */ LogInfo(COMPONENT_CONFIG, "Export %d %s at pseudo (%s) with path (%s) and tag (%s) perms (%s)", export->export_id, commit_type == update_export ? "updated" : "created", export->cfg_pseudopath, export->cfg_fullpath, export->FS_tag, perms); LogInfo(COMPONENT_CONFIG, "Export %d has %zd defined clients", export->export_id, glist_length(&export->clients)); if (commit_type != update_export) { /* For initial or add export, the alloc_export in export_init * gave a reference to the export for use during the * configuration. Above insert_gsh_export added a sentinel * reference. Now, since this export commit is final, we can * drop the reference from the alloc_export. * * In the case of update_export, we already dropped the * reference to the updated export, and this export has * no references and will be freed by the config code. */ put_gsh_export(export); } if (errcnt > 0) { LogCrit(COMPONENT_CONFIG, "Error count %d exiting", errcnt); } return errcnt; } static int export_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { LogDebug(COMPONENT_EXPORT, "EXPORT commit"); return export_commit_common(node, link_mem, self_struct, err_type, initial_export); } int pseudofs_fsal_commit(void *self_struct, struct config_error_type *err_type) { struct gsh_export *export = self_struct; struct fsal_module *fsal_hdl = NULL; struct req_op_context op_context; int errcnt = 0; /* Take an export reference and initialize req_ctx with the export * reasonably constructed */ get_gsh_export_ref(export); init_op_context_simple(&op_context, export, NULL); /* Assign FSAL_PSEUDO */ fsal_hdl = lookup_fsal("PSEUDO"); if (fsal_hdl == NULL) { LogCrit(COMPONENT_CONFIG, "FSAL PSEUDO is not loaded!"); err_type->invalid = true; errcnt = 1; goto err_out; } else { fsal_status_t rc; rc = mdcache_fsal_create_export(fsal_hdl, NULL, err_type, &fsal_up_top); if (FSAL_IS_ERROR(rc)) { fsal_put(fsal_hdl); LogCrit(COMPONENT_CONFIG, "Could not create FSAL export for %s", export->cfg_fullpath); LogFullDebug(COMPONENT_FSAL, "FSAL %s fsal_refcount %" PRIu32, fsal_hdl->name, atomic_fetch_int32_t(&fsal_hdl->refcount)); err_type->invalid = true; errcnt = 1; goto err_out; } } assert(op_ctx->fsal_export != NULL); export->fsal_export = op_ctx->fsal_export; err_out: /* Release the export reference from above. */ release_op_context(); return errcnt; } static int pseudofs_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { int rc = pseudofs_fsal_commit(self_struct, err_type); if (rc != 0) return rc; LogDebug(COMPONENT_EXPORT, "PSEUDOFS commit"); return export_commit_common(node, link_mem, self_struct, err_type, initial_export); } /** * @brief Display an export block * * Validate the export level parameters. fsal and client * parameters are already done. */ static void export_display(const char *step, void *node, void *link_mem, void *self_struct) { struct gsh_export *export = self_struct; char perms[1024] = "\0"; struct display_buffer dspbuf = { sizeof(perms), perms, perms }; (void)StrExportOptions(&dspbuf, &export->export_perms); LogMidDebug( COMPONENT_EXPORT, "%s %p Export %d pseudo (%s) with path (%s) and tag (%s) perms (%s)", step, export, export->export_id, export->cfg_pseudopath, export->cfg_fullpath, export->FS_tag, perms); } /** * @brief Commit an add export * commit the export * init export root and mount it in pseudo fs */ static int add_export_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { LogDebug(COMPONENT_EXPORT, "ADD EXPORT commit"); return export_commit_common(node, link_mem, self_struct, err_type, add_export); } /** * @brief Check if the ExportId already exists and active * Duplicate ExportID cannot be exported more than once */ static bool check_export_duplicate(void *self_struct, struct config_error_type *err_type) { bool duplicate = false; struct gsh_export *export = self_struct, *probe_exp; probe_exp = get_gsh_export(export->export_id); if (probe_exp != NULL) { LogDebug(COMPONENT_EXPORT, "Export %d already exists", export->export_id); put_gsh_export(probe_exp); err_type->exists = true; duplicate = true; } return duplicate; } /** * @brief Commit an update export * commit the export * init export root and mount it in pseudo fs */ static int update_export_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { LogDebug(COMPONENT_EXPORT, "UPDATE EXPORT commit"); return export_commit_common(node, link_mem, self_struct, err_type, update_export); } /** * @brief Commit an update export * commit the export * init export root and mount it in pseudo fs */ static int update_pseudofs_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { int rc; LogDebug(COMPONENT_EXPORT, "UPDATE PSEUDOFS commit"); rc = pseudofs_fsal_commit(self_struct, err_type); if (rc != 0) return rc; return export_commit_common(node, link_mem, self_struct, err_type, update_export); } /** * @brief Initialize an EXPORT_DEFAULTS block * */ static void *export_defaults_init(void *link_mem, void *self_struct) { if (link_mem == NULL) { return self_struct; } else if (self_struct == NULL) { return &export_opt_cfg; } else { /* free resources case */ FreeClientList(&export_opt_cfg.clients, FreeExportClient); return NULL; } } /** * @brief Commit an EXPORT_DEFAULTS block * * Validate the export level parameters. fsal and client * parameters are already done. */ static int export_defaults_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { char perms[1024] = "\0"; struct display_buffer dspbuf = { sizeof(perms), perms, perms }; (void)StrExportOptions(&dspbuf, &export_opt_cfg.conf); LogInfo(COMPONENT_CONFIG, "Export Defaults now (%s)", perms); /* Update under lock. */ PTHREAD_RWLOCK_wrlock(&export_opt_lock); export_opt.conf = export_opt_cfg.conf; /* Swap the client list from export_opt_cfg export and export_opt. */ LogFullDebug(COMPONENT_EXPORT, "Original clients = (%p,%p) New clients = (%p,%p)", export_opt.clients.next, export_opt.clients.prev, export_opt_cfg.clients.next, export_opt_cfg.clients.prev); glist_swap_lists(&export_opt.clients, &export_opt_cfg.clients); PTHREAD_RWLOCK_unlock(&export_opt_lock); return 0; } /** * @brief Display an EXPORT_DEFAULTS block * * Validate the export level parameters. fsal and client * parameters are already done. */ static void export_defaults_display(const char *step, void *node, void *link_mem, void *self_struct) { struct export_perms *defaults = self_struct; char perms[1024] = "\0"; struct display_buffer dspbuf = { sizeof(perms), perms, perms }; (void)StrExportOptions(&dspbuf, defaults); LogMidDebug(COMPONENT_EXPORT, "%s Export Defaults (%s)", step, perms); } /** * @brief Configuration processing tables for EXPORT blocks */ /** * @brief Access types list for the Access_type parameter */ static struct config_item_list access_types[] = { CONFIG_LIST_TOK("NONE", 0), CONFIG_LIST_TOK("RW", (EXPORT_OPTION_RW_ACCESS | EXPORT_OPTION_MD_ACCESS)), CONFIG_LIST_TOK("RO", (EXPORT_OPTION_READ_ACCESS | EXPORT_OPTION_MD_READ_ACCESS)), CONFIG_LIST_TOK("MDONLY", EXPORT_OPTION_MD_ACCESS), CONFIG_LIST_TOK("MDONLY_RO", EXPORT_OPTION_MD_READ_ACCESS), CONFIG_LIST_EOL }; /** * @brief Protocols options list for NFS_Protocols parameter */ static struct config_item_list nfs_protocols[] = { CONFIG_LIST_TOK("3", EXPORT_OPTION_NFSV3), CONFIG_LIST_TOK("4", EXPORT_OPTION_NFSV4), CONFIG_LIST_TOK("NFS3", EXPORT_OPTION_NFSV3), CONFIG_LIST_TOK("NFS4", EXPORT_OPTION_NFSV4), CONFIG_LIST_TOK("V3", EXPORT_OPTION_NFSV3), CONFIG_LIST_TOK("V4", EXPORT_OPTION_NFSV4), CONFIG_LIST_TOK("NFSV3", EXPORT_OPTION_NFSV3), CONFIG_LIST_TOK("NFSV4", EXPORT_OPTION_NFSV4), CONFIG_LIST_TOK("9P", EXPORT_OPTION_9P), CONFIG_LIST_EOL }; /** * @brief Transport type options list for Transport_Protocols parameter */ static struct config_item_list transports[] = { CONFIG_LIST_TOK("UDP", EXPORT_OPTION_UDP), CONFIG_LIST_TOK("TCP", EXPORT_OPTION_TCP), CONFIG_LIST_EOL }; /** * @brief Security options list for SecType parameter */ static struct config_item_list sec_types[] = { CONFIG_LIST_TOK("none", EXPORT_OPTION_AUTH_NONE), CONFIG_LIST_TOK("sys", EXPORT_OPTION_AUTH_UNIX), CONFIG_LIST_TOK("krb5", EXPORT_OPTION_RPCSEC_GSS_NONE), CONFIG_LIST_TOK("krb5i", EXPORT_OPTION_RPCSEC_GSS_INTG), CONFIG_LIST_TOK("krb5p", EXPORT_OPTION_RPCSEC_GSS_PRIV), CONFIG_LIST_EOL }; /** * @brief Client UID squash item list for Squash parameter */ static struct config_item_list squash_types[] = { CONFIG_LIST_TOK("Root", EXPORT_OPTION_ROOT_SQUASH), CONFIG_LIST_TOK("Root_Squash", EXPORT_OPTION_ROOT_SQUASH), CONFIG_LIST_TOK("RootSquash", EXPORT_OPTION_ROOT_SQUASH), CONFIG_LIST_TOK("All", EXPORT_OPTION_ALL_ANONYMOUS), CONFIG_LIST_TOK("All_Squash", EXPORT_OPTION_ALL_ANONYMOUS), CONFIG_LIST_TOK("AllSquash", EXPORT_OPTION_ALL_ANONYMOUS), CONFIG_LIST_TOK("All_Anonymous", EXPORT_OPTION_ALL_ANONYMOUS), CONFIG_LIST_TOK("AllAnonymous", EXPORT_OPTION_ALL_ANONYMOUS), CONFIG_LIST_TOK("No_Root_Squash", EXPORT_OPTION_ROOT), CONFIG_LIST_TOK("None", EXPORT_OPTION_ROOT), CONFIG_LIST_TOK("NoIdSquash", EXPORT_OPTION_ROOT), CONFIG_LIST_TOK("RootId", EXPORT_OPTION_ROOT_ID_SQUASH), CONFIG_LIST_TOK("Root_Id_Squash", EXPORT_OPTION_ROOT_ID_SQUASH), CONFIG_LIST_TOK("RootIdSquash", EXPORT_OPTION_ROOT_ID_SQUASH), CONFIG_LIST_EOL }; /** * @brief Delegations types list for the Delegations parameter */ static struct config_item_list delegations[] = { CONFIG_LIST_TOK("NONE", EXPORT_OPTION_NO_DELEGATIONS), CONFIG_LIST_TOK("Read", EXPORT_OPTION_READ_DELEG), CONFIG_LIST_TOK("Write", EXPORT_OPTION_WRITE_DELEG), CONFIG_LIST_TOK("Readwrite", EXPORT_OPTION_DELEGATIONS), CONFIG_LIST_TOK("R", EXPORT_OPTION_READ_DELEG), CONFIG_LIST_TOK("W", EXPORT_OPTION_WRITE_DELEG), CONFIG_LIST_TOK("RW", EXPORT_OPTION_DELEGATIONS), CONFIG_LIST_EOL }; struct config_item_list deleg_types[] = { CONFIG_LIST_TOK("NONE", FSAL_OPTION_NO_DELEGATIONS), CONFIG_LIST_TOK("Read", FSAL_OPTION_FILE_READ_DELEG), CONFIG_LIST_TOK("Write", FSAL_OPTION_FILE_WRITE_DELEG), CONFIG_LIST_TOK("Readwrite", FSAL_OPTION_FILE_DELEGATIONS), CONFIG_LIST_TOK("R", FSAL_OPTION_FILE_READ_DELEG), CONFIG_LIST_TOK("W", FSAL_OPTION_FILE_WRITE_DELEG), CONFIG_LIST_TOK("RW", FSAL_OPTION_FILE_DELEGATIONS), CONFIG_LIST_EOL }; /** * @brief read permission mode options */ static struct config_item_list read_access_check_policy_type[] = { CONFIG_LIST_TOK("pre", READ_ACCESS_CHECK_POLICY_PRE), CONFIG_LIST_TOK("post", READ_ACCESS_CHECK_POLICY_POST), CONFIG_LIST_TOK("all", READ_ACCESS_CHECK_POLICY_ALL), CONFIG_LIST_EOL }; #define CONF_EXPORT_PERMS(_struct_, _perms_) \ /* Note: Access_Type defaults to None on purpose */ \ CONF_ITEM_ENUM_BITS_SET( \ "Access_Type", EXPORT_OPTION_NO_ACCESS, \ EXPORT_OPTION_ACCESS_MASK, access_types, _struct_, \ _perms_.options, \ _perms_.set), /* Note: Protocols will now pick up from NFS Core Param */ \ CONF_ITEM_LIST_BITS_SET("Protocols", \ EXPORT_OPTION_PROTO_DEFAULTS, \ EXPORT_OPTION_PROTOCOLS, \ nfs_protocols, _struct_, \ _perms_.options, _perms_.set), \ CONF_ITEM_LIST_BITS_SET("Transports", \ EXPORT_OPTION_XPORT_DEFAULTS, \ EXPORT_OPTION_TRANSPORTS, transports, \ _struct_, _perms_.options, \ _perms_.set), \ CONF_ITEM_ANON_ID_SET("Anonymous_uid", ANON_UID, _struct_, \ _perms_.anonymous_uid, \ EXPORT_OPTION_ANON_UID_SET, \ _perms_.set), \ CONF_ITEM_ANON_ID_SET("Anonymous_gid", ANON_GID, _struct_, \ _perms_.anonymous_gid, \ EXPORT_OPTION_ANON_GID_SET, \ _perms_.set), \ CONF_ITEM_LIST_BITS_SET("SecType", \ EXPORT_OPTION_AUTH_DEFAULTS, \ EXPORT_OPTION_AUTH_TYPES, sec_types, \ _struct_, _perms_.options, \ _perms_.set), \ CONF_ITEM_BOOLBIT_SET("PrivilegedPort", false, \ EXPORT_OPTION_PRIVILEGED_PORT, _struct_, \ _perms_.options, _perms_.set), \ CONF_ITEM_BOOLBIT_SET("Manage_Gids", false, \ EXPORT_OPTION_MANAGE_GIDS, _struct_, \ _perms_.options, _perms_.set), \ CONF_ITEM_LIST_BITS_SET("Squash", EXPORT_OPTION_ROOT_SQUASH, \ EXPORT_OPTION_SQUASH_TYPES, \ squash_types, _struct_, \ _perms_.options, _perms_.set), \ CONF_ITEM_BOOLBIT_SET("NFS_Commit", false, \ EXPORT_OPTION_COMMIT, _struct_, \ _perms_.options, _perms_.set), \ CONF_ITEM_ENUM_BITS_SET("Delegations", \ EXPORT_OPTION_NO_DELEGATIONS, \ EXPORT_OPTION_DELEGATIONS, \ delegations, _struct_, \ _perms_.options, _perms_.set) #define CONF_PSEUDOFS_PERMS(_struct_, _perms_) \ /* Note: Access_Type defaults to MD READ on purpose */ \ /* MD READ or NONE are the only access that makes sense. */ \ CONF_ITEM_ENUM_BITS_SET("Access_Type", EXPORT_OPTION_MD_READ_ACCESS, \ EXPORT_OPTION_ACCESS_MASK, access_types, \ _struct_, _perms_.options, _perms_.set), \ CONF_ITEM_LIST_BITS_SET("Transports", EXPORT_OPTION_TCP, \ EXPORT_OPTION_TRANSPORTS, transports, \ _struct_, _perms_.options, \ _perms_.set), \ CONF_ITEM_LIST_BITS_SET("SecType", EXPORT_OPTION_AUTH_TYPES, \ EXPORT_OPTION_AUTH_TYPES, sec_types, \ _struct_, _perms_.options, \ _perms_.set), \ CONF_ITEM_BOOLBIT_SET("PrivilegedPort", false, \ EXPORT_OPTION_PRIVILEGED_PORT, _struct_, \ _perms_.options, _perms_.set) void *export_client_allocator(void) { struct exportlist_client_entry *expcli; expcli = gsh_calloc(1, sizeof(struct exportlist_client_entry)); return &expcli->client_entry; } void export_client_filler(struct base_client_entry *client, void *private_data) { struct exportlist_client_entry *expcli; struct export_perms *perms = private_data; expcli = container_of(client, struct exportlist_client_entry, client_entry); expcli->client_perms = *perms; LogMidDebug_ExportClientListEntry("", expcli); } /** * @brief Process a list of clients for a client block * * CONFIG_PROC handler that gets called for each token in the term list. * Create a exportlist_client_entry for each token and link it into * the proto client's cle_list list head. We will pass that head to the * export in commit. * * NOTES: this is the place to expand a node list with perhaps moving the * call to add_client into the expander rather than build a list there * to be then walked here... * * @param token [IN] pointer to token string from parse tree * @param type_hint [IN] a type hint from what the parser recognized * @param item [IN] pointer to the config item table entry * @param param_addr [IN] pointer to prototype client entry * @param err_type [OUT] error handling * @return error count */ static int client_adder(const char *token, enum term_type type_hint, struct config_item *item, void *param_addr, void *cnode, struct config_error_type *err_type) { struct base_client_entry *client; struct exportlist_client_entry *proto_cli; int rc; client = container_of(param_addr, struct base_client_entry, cle_list); proto_cli = container_of(client, struct exportlist_client_entry, client_entry); LogMidDebug(COMPONENT_EXPORT, "Adding client %s", token); rc = add_client(COMPONENT_EXPORT, &client->cle_list, token, type_hint, cnode, err_type, export_client_allocator, export_client_filler, &proto_cli->client_perms); return rc; } /** * @brief Table of client sub-block parameters * * NOTE: node discovery is ordered by this table! * "Clients" is last because we must have all other params processed * before we walk the list of accessing clients! */ static struct config_item client_params[] = { CONF_EXPORT_PERMS(exportlist_client_entry, client_perms), CONF_ITEM_PROC_MULT("Clients", noop_conf_init, client_adder, base_client_entry, cle_list), CONFIG_EOL }; /** * @brief Table of pseudofs client sub-block parameters * * NOTE: node discovery is ordered by this table! * "Clients" is last because we must have all other params processed * before we walk the list of accessing clients! */ static struct config_item pseudo_fs_client_params[] = { CONF_PSEUDOFS_PERMS(exportlist_client_entry, client_perms), CONF_ITEM_PROC_MULT("Clients", noop_conf_init, client_adder, base_client_entry, cle_list), CONFIG_EOL }; /** * @brief Table of DEXPORT_DEFAULTS block parameters * * NOTE: node discovery is ordered by this table! */ static struct config_item export_defaults_params[] = { CONF_EXPORT_PERMS(global_export_perms, conf), CONF_ITEM_I32_SET("Attr_Expiration_Time", -1, INT32_MAX, EXPORT_DEFAULT_CACHE_EXPIRY, global_export_perms, conf.expire_time_attr, EXPORT_OPTION_EXPIRE_SET, conf.set), CONF_ITEM_BLOCK_MULT("Client", client_params, client_init, client_commit, global_export_perms, clients), CONFIG_EOL }; /** * @brief Table of FSAL sub-block parameters * * NOTE: this points to a struct that is private to * fsal_cfg_commit. */ static struct config_item fsal_params[] = { CONF_ITEM_STR("Name", 1, 10, NULL, fsal_args, name), /* cheater union */ CONFIG_EOL }; /** * @brief Common EXPORT block parameters */ #define CONF_EXPORT_PARAMS(_struct_) \ CONF_MAND_UI16("Export_id", 0, UINT16_MAX, 1, _struct_, export_id), \ CONF_MAND_PATH("Path", 1, MAXPATHLEN, NULL, _struct_, \ cfg_fullpath), /* must chomp '/' */ \ CONF_ITEM_PATH("Pseudo", 1, MAXPATHLEN, NULL, _struct_, \ cfg_pseudopath), \ CONF_ITEM_UI64_SET("MaxRead", 512, FSAL_MAXIOSIZE, \ FSAL_MAXIOSIZE, _struct_, MaxRead, \ EXPORT_OPTION_MAXREAD_SET, options_set), \ CONF_ITEM_UI64_SET("MaxWrite", 512, FSAL_MAXIOSIZE, \ FSAL_MAXIOSIZE, _struct_, MaxWrite, \ EXPORT_OPTION_MAXWRITE_SET, options_set), \ CONF_ITEM_UI64_SET("PrefRead", 512, FSAL_MAXIOSIZE, \ FSAL_MAXIOSIZE, _struct_, PrefRead, \ EXPORT_OPTION_PREFREAD_SET, options_set), \ CONF_ITEM_UI64_SET("PrefWrite", 512, FSAL_MAXIOSIZE, \ FSAL_MAXIOSIZE, _struct_, PrefWrite, \ EXPORT_OPTION_PREFWRITE_SET, options_set), \ CONF_ITEM_UI64("PrefReaddir", 512, FSAL_MAXIOSIZE, 16384, \ _struct_, PrefReaddir), \ CONF_ITEM_FSID_SET("Filesystem_id", 666, 666, _struct_, \ filesystem_id, /* major.minor */ \ EXPORT_OPTION_FSID_SET, options_set), \ CONF_ITEM_LIST("Read_Access_Check_Policy", \ READ_ACCESS_CHECK_POLICY_PRE, \ read_access_check_policy_type, _struct_, \ read_access_check_policy), \ CONF_ITEM_STR("Tag", 1, MAXPATHLEN, NULL, _struct_, FS_tag), \ CONF_ITEM_UI64("MaxOffsetWrite", 512, UINT64_MAX, INT64_MAX, \ _struct_, MaxOffsetWrite), \ CONF_ITEM_UI64("MaxOffsetRead", 512, UINT64_MAX, INT64_MAX, \ _struct_, MaxOffsetRead), \ CONF_ITEM_BOOLBIT_SET("UseCookieVerifier", false, \ EXPORT_OPTION_USE_COOKIE_VERIFIER, \ _struct_, options, options_set), \ CONF_ITEM_BOOLBIT_SET("DisableReaddirPlus", false, \ EXPORT_OPTION_NO_READDIR_PLUS, _struct_, \ options, options_set), \ CONF_ITEM_BOOLBIT_SET( \ "Trust_Readdir_Negative_Cache", false, \ EXPORT_OPTION_TRUST_READIR_NEGATIVE_CACHE, _struct_, \ options, options_set), \ CONF_ITEM_BOOLBIT_SET("Disable_ACL", false, \ EXPORT_OPTION_DISABLE_ACL, _struct_, \ options, options_set), \ CONF_ITEM_BOOLBIT_SET("Security_Label", false, \ EXPORT_OPTION_SECLABEL_SET, _struct_, \ options, options_set) /** * @brief Table of EXPORT block parameters */ static struct config_item export_params[] = { CONF_EXPORT_PARAMS(gsh_export), CONF_EXPORT_PERMS(gsh_export, export_perms), CONF_ITEM_I32_SET("Attr_Expiration_Time", -1, INT32_MAX, EXPORT_DEFAULT_CACHE_EXPIRY, gsh_export, export_perms.expire_time_attr, EXPORT_OPTION_EXPIRE_SET, export_perms.set), /* NOTE: the Client and FSAL sub-blocks must be the *last* * two entries in the list. This is so all other * parameters have been processed before these sub-blocks * are processed. */ CONF_ITEM_BLOCK_MULT("Client", client_params, client_init, client_commit, gsh_export, clients), CONF_RELAX_BLOCK("FSAL", fsal_params, fsal_init, fsal_cfg_commit, gsh_export, fsal_export), CONFIG_EOL }; /** * @brief Table of EXPORT update block parameters */ static struct config_item export_update_params[] = { CONF_EXPORT_PARAMS(gsh_export), CONF_EXPORT_PERMS(gsh_export, export_perms), CONF_ITEM_I32_SET("Attr_Expiration_Time", -1, INT32_MAX, EXPORT_DEFAULT_CACHE_EXPIRY, gsh_export, export_perms.expire_time_attr, EXPORT_OPTION_EXPIRE_SET, export_perms.set), /* NOTE: the Client and FSAL sub-blocks must be the *last* * two entries in the list. This is so all other * parameters have been processed before these sub-blocks * are processed. */ CONF_ITEM_BLOCK_MULT("Client", client_params, client_init, client_commit, gsh_export, clients), CONF_RELAX_BLOCK("FSAL", fsal_params, fsal_init, fsal_update_cfg_commit, gsh_export, fsal_export), CONFIG_EOL }; /** * @brief Initialize an export block * * There is no link_mem init required because we are allocating * here and doing an insert_gsh_export at the end of export_commit * to attach it to the export manager. * * Use free_exportlist here because in this case, we have not * gotten far enough to hand it over to the export manager. */ static void *pseudofs_init(void *link_mem, void *self_struct) { struct gsh_export *export = export_init(link_mem, self_struct); if (self_struct != NULL) { return export; } /* The initialization case */ export->filesystem_id.major = 152; export->filesystem_id.minor = 152; export->MaxWrite = FSAL_MAXIOSIZE; export->MaxRead = FSAL_MAXIOSIZE; export->PrefWrite = FSAL_MAXIOSIZE; export->PrefRead = FSAL_MAXIOSIZE; export->PrefReaddir = 16384; export->config_gen = UINT64_MAX; /*Don't set anonymous uid and gid, they will actually be ignored */ /* Support only NFS v4 and TCP. * Root is allowed * MD Read Access * Allow use of default auth types * * Allow non-privileged client ports to access pseudo export. */ export->export_perms.options = EXPORT_OPTION_ROOT | EXPORT_OPTION_MD_READ_ACCESS | EXPORT_OPTION_NFSV4 | EXPORT_OPTION_AUTH_TYPES | EXPORT_OPTION_TCP; export->export_perms.set = EXPORT_OPTION_SQUASH_TYPES | EXPORT_OPTION_ACCESS_MASK | EXPORT_OPTION_PROTOCOLS | EXPORT_OPTION_TRANSPORTS | EXPORT_OPTION_AUTH_TYPES | EXPORT_OPTION_PRIVILEGED_PORT; export->options = EXPORT_OPTION_USE_COOKIE_VERIFIER; export->options_set = EXPORT_OPTION_FSID_SET | EXPORT_OPTION_USE_COOKIE_VERIFIER | EXPORT_OPTION_MAXREAD_SET | EXPORT_OPTION_MAXWRITE_SET | EXPORT_OPTION_PREFREAD_SET | EXPORT_OPTION_PREFWRITE_SET; /* Set the fullpath to "/" */ export->cfg_fullpath = gsh_strdup("/"); /* Set Pseudo Path to "/" */ export->cfg_pseudopath = gsh_strdup("/"); export->pseudopath = gsh_refstr_dup("/"); export->fullpath = gsh_refstr_dup("/"); LOG_EXPORT(NIV_FULL_DEBUG, "pseudofs_init", export, true); return export; } /** * @brief Common PSEUDOFS block parameters */ #define CONF_PSEUDOFS_PARAMS(_struct_) \ CONF_ITEM_UI16("Export_id", 0, UINT16_MAX, 0, _struct_, export_id), \ CONF_ITEM_UI64("PrefReaddir", 512, FSAL_MAXIOSIZE, 16384, \ _struct_, PrefReaddir), \ CONF_ITEM_FSID_SET("Filesystem_id", 152, 152, _struct_, \ filesystem_id, /* major.minor */ \ EXPORT_OPTION_FSID_SET, options_set), \ CONF_ITEM_BOOLBIT_SET("UseCookieVerifier", false, \ EXPORT_OPTION_USE_COOKIE_VERIFIER, \ _struct_, options, options_set), \ CONF_ITEM_BOOLBIT_SET("DisableReaddirPlus", false, \ EXPORT_OPTION_NO_READDIR_PLUS, _struct_, \ options, options_set), \ CONF_ITEM_BOOLBIT_SET( \ "Trust_Readdir_Negative_Cache", false, \ EXPORT_OPTION_TRUST_READIR_NEGATIVE_CACHE, _struct_, \ options, options_set) /** * @brief Table of PSEUDOFS block parameters */ static struct config_item pseudofs_params[] = { CONF_PSEUDOFS_PARAMS(gsh_export), CONF_PSEUDOFS_PERMS(gsh_export, export_perms), /* NOTE: the Client sub-block must be the *last* * entry in the list. This is so all other * parameters have been processed before this sub-block * is processed. */ CONF_ITEM_BLOCK_MULT("Client", pseudo_fs_client_params, pseudofs_client_init, client_commit, gsh_export, clients), CONFIG_EOL }; /** * @brief Table of PSEUDOFS update block parameters */ static struct config_item pseudofs_update_params[] = { CONF_PSEUDOFS_PARAMS(gsh_export), CONF_PSEUDOFS_PERMS(gsh_export, export_perms), /* NOTE: the Client sub-block must be the *last* * entry in the list. This is so all other * parameters have been processed before this sub-block * is processed. */ CONF_ITEM_BLOCK_MULT("Client", pseudo_fs_client_params, pseudofs_client_init, client_commit, gsh_export, clients), CONFIG_EOL }; /** * @brief Top level definition for an EXPORT block */ static struct config_block export_param = { .dbus_interface_name = "org.ganesha.nfsd.config.%d", .blk_desc.name = "EXPORT", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = export_init, .blk_desc.u.blk.params = export_params, .blk_desc.u.blk.commit = export_commit, .blk_desc.u.blk.display = export_display }; /** * @brief Top level definition for an ADD EXPORT block */ struct config_block add_export_param = { .dbus_interface_name = "org.ganesha.nfsd.config.%d", .blk_desc.name = "EXPORT", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = export_init, .blk_desc.u.blk.params = export_params, .blk_desc.u.blk.commit = add_export_commit, .blk_desc.u.blk.display = export_display, .blk_desc.u.blk.check = check_export_duplicate }; /** * @brief Top level definition for an UPDATE EXPORT block */ struct config_block update_export_param = { .dbus_interface_name = "org.ganesha.nfsd.config.%d", .blk_desc.name = "EXPORT", .blk_desc.type = CONFIG_BLOCK, .blk_desc.u.blk.init = export_init, .blk_desc.u.blk.params = export_update_params, .blk_desc.u.blk.commit = update_export_commit, .blk_desc.u.blk.display = export_display }; /** * @brief Top level definition for an PSEUDOFS block */ static struct config_block pseudofs_param = { .dbus_interface_name = "org.ganesha.nfsd.config.%d", .blk_desc.name = "PSEUDOFS", .blk_desc.type = CONFIG_BLOCK, /* too risky to have more, and don't allocate if block not present */ .blk_desc.flags = CONFIG_UNIQUE | CONFIG_NO_DEFAULT, .blk_desc.u.blk.init = pseudofs_init, .blk_desc.u.blk.params = pseudofs_params, .blk_desc.u.blk.commit = pseudofs_commit, .blk_desc.u.blk.display = export_display }; /** * @brief Top level definition for an UPDATE PSEUDOFS block */ struct config_block update_pseudofs_param = { .dbus_interface_name = "org.ganesha.nfsd.config.%d", .blk_desc.name = "PSEUDOFS", .blk_desc.type = CONFIG_BLOCK, /* too risky to have more, and don't allocate if block not present */ .blk_desc.flags = CONFIG_UNIQUE | CONFIG_NO_DEFAULT, .blk_desc.u.blk.init = pseudofs_init, .blk_desc.u.blk.params = pseudofs_update_params, .blk_desc.u.blk.commit = update_pseudofs_commit, .blk_desc.u.blk.display = export_display }; /** * @brief Top level definition for an EXPORT_DEFAULTS block */ struct config_block export_defaults_param = { .dbus_interface_name = "org.ganesha.nfsd.config.defaults", .blk_desc.name = "EXPORT_DEFAULTS", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = export_defaults_init, .blk_desc.u.blk.params = export_defaults_params, .blk_desc.u.blk.commit = export_defaults_commit, .blk_desc.u.blk.display = export_defaults_display }; /** * @brief builds an export entry for '/' with default parameters * * If export_id = 0 has not been specified, and not other export * for Pseudo "/" has been specified, build an FSAL_PSEUDO export * for the root of the Pseudo FS. * * @return -1 on error, 0 if we already have one, 1 if created one */ static int build_default_root(struct config_error_type *err_type) { struct gsh_export *export; struct fsal_module *fsal_hdl = NULL; struct req_op_context op_context; /* See if export_id = 0 has already been specified */ export = get_gsh_export(0); if (export != NULL) { /* export_id = 0 has already been specified */ LogDebug(COMPONENT_EXPORT, "Export 0 already exists"); put_gsh_export(export); return 0; } /* See if another export with Pseudo = "/" has already been specified. */ export = get_gsh_export_by_pseudo("/", true); if (export != NULL) { /* Pseudo = / has already been specified */ LogDebug(COMPONENT_EXPORT, "Pseudo root already exists"); put_gsh_export(export); return 0; } /* allocate and initialize the exportlist part with the id */ LogDebug(COMPONENT_EXPORT, "Allocating Pseudo root export"); /* We can call the same function that config uses to allocate and * initialize a gsh_export structure by passing both parameters as * NULL. */ export = pseudofs_init(NULL, NULL); /* Initialize req_ctx with the export reasonably constructed using the * reference provided above by alloc_export(). */ init_op_context_simple(&op_context, export, NULL); /* Assign FSAL_PSEUDO */ fsal_hdl = lookup_fsal("PSEUDO"); if (fsal_hdl == NULL) { LogCrit(COMPONENT_CONFIG, "FSAL PSEUDO is not loaded!"); goto err_out; } else { fsal_status_t rc; rc = mdcache_fsal_create_export(fsal_hdl, NULL, err_type, &fsal_up_top); if (FSAL_IS_ERROR(rc)) { fsal_put(fsal_hdl); LogCrit(COMPONENT_CONFIG, "Could not create FSAL export for %s", export->cfg_fullpath); LogFullDebug(COMPONENT_FSAL, "FSAL %s fsal_refcount %" PRIu32, fsal_hdl->name, atomic_fetch_int32_t(&fsal_hdl->refcount)); goto err_out; } } assert(op_ctx->fsal_export != NULL); export->fsal_export = op_ctx->fsal_export; if (!insert_gsh_export(export)) { export->fsal_export->exp_ops.release(export->fsal_export); fsal_put(fsal_hdl); LogCrit(COMPONENT_CONFIG, "Failed to insert pseudo root In use??"); LogFullDebug(COMPONENT_FSAL, "FSAL %s fsal_refcount %" PRIu32, fsal_hdl->name, atomic_fetch_int32_t(&fsal_hdl->refcount)); goto err_out; } /* This export must be mounted to the PseudoFS */ export_add_to_mount_work(export); LogInfo(COMPONENT_CONFIG, "Export 0 (/) successfully created"); /* Release the reference from alloc_export() above, since the * insert worked, a sentinel reference has been taken, so this * reference release won't result in freeing the export. */ release_op_context(); return 1; err_out: /* Release the export reference from alloc_export() above which will * result in cleaning up and freeing the export. */ release_op_context(); return -1; } const char *readable_read_access_check_policy(uint32_t read_access_check_policy) { static const char *READABLE_READ_ACCESS_CHECK_POLICY_ALL = "all"; static const char *READABLE_READ_ACCESS_CHECK_POLICY_PRE = "pre"; static const char *READABLE_READ_ACCESS_CHECK_POLICY_POST = "post"; static const char *READABLE_READ_ACCESS_CHECK_POLICY_INVALID = "none/invalid"; if (read_access_check_policy == READ_ACCESS_CHECK_POLICY_ALL) return READABLE_READ_ACCESS_CHECK_POLICY_ALL; if (read_access_check_policy & READ_ACCESS_CHECK_POLICY_PRE) return READABLE_READ_ACCESS_CHECK_POLICY_PRE; if (read_access_check_policy & READ_ACCESS_CHECK_POLICY_POST) return READABLE_READ_ACCESS_CHECK_POLICY_POST; return READABLE_READ_ACCESS_CHECK_POLICY_INVALID; } bool log_an_export(struct gsh_export *exp, void *state) { struct log_exports_parms *lep = state; char perms[1024] = "\0"; struct display_buffer dspbuf = { sizeof(perms), perms, perms }; if (exp == NULL) { if (isLevel(COMPONENT_EXPORT, lep->level)) { DisplayLogComponentLevel( COMPONENT_EXPORT, (char *)lep->file, lep->line, lep->func, lep->level, "%s%sNO EXPORT", lep->tag ? lep->tag : "", lep->tag ? " " : ""); } return false; } (void)StrExportOptions(&dspbuf, &exp->export_perms); if (isLevel(COMPONENT_EXPORT, lep->level)) { DisplayLogComponentLevel( COMPONENT_EXPORT, (char *)lep->file, lep->line, lep->func, lep->level, "%s%sExport %p %5d pseudo (%s) with path (%s) and tag (%s) perms (%s) Read_Access_Check_Policy (%s)", lep->tag ? lep->tag : "", lep->tag ? " " : "", exp, exp->export_id, exp->cfg_pseudopath, exp->cfg_fullpath, exp->FS_tag, perms, readable_read_access_check_policy( exp->read_access_check_policy)); } if (lep->clients) LogExportClients(lep->level, lep->line, lep->func, " ", exp); return true; } void log_all_exports(log_levels_t level, int line, const char *func) { struct log_exports_parms lep = { level, __FILE__, line, func, NULL, true }; foreach_gsh_export(log_an_export, false, &lep); } /** * @brief Read the export entries from the parsed configuration file. * * @param[in] in_config The file that contains the export list * * @return A negative value on error, * the number of export entries else. */ #define NFS_options nfs_param.core_param.core_options int ReadExports(config_file_t in_config, struct config_error_type *err_type) { int rc, num_exp; LogMidDebug( COMPONENT_EXPORT, "CORE_OPTION_NFSV3 %d CORE_OPTION_NFSV4 %d CORE_OPTION_9P %d", (NFS_options & CORE_OPTION_NFSV3) != 0, (NFS_options & CORE_OPTION_NFSV4) != 0, (NFS_options & CORE_OPTION_9P) != 0); /* Set Protocols in export_opt.def.options from nfs_core_param. */ if (NFS_options & CORE_OPTION_NFSV3) export_opt.def.options |= EXPORT_OPTION_NFSV3; if (NFS_options & CORE_OPTION_NFSV4) export_opt.def.options |= EXPORT_OPTION_NFSV4; if (NFS_options & CORE_OPTION_9P) export_opt.def.options |= EXPORT_OPTION_9P; rc = load_config_from_parse(in_config, &export_defaults_param, &export_opt_cfg, false, err_type); if (rc < 0) { LogCrit(COMPONENT_CONFIG, "Export defaults block error"); return -1; } if (isMidDebug(COMPONENT_EXPORT)) { char perms[1024] = "\0"; struct display_buffer dspbuf = { sizeof(perms), perms, perms }; (void)StrExportOptions(&dspbuf, &export_opt.conf); LogMidDebug(COMPONENT_EXPORT, "EXPORT_DEFAULTS (%s)", perms); display_reset_buffer(&dspbuf); (void)StrExportOptions(&dspbuf, &export_opt.def); LogMidDebug(COMPONENT_EXPORT, "default options (%s)", perms); display_reset_buffer(&dspbuf); } rc = load_config_from_parse(in_config, &pseudofs_param, NULL, false, err_type); if (rc < 0) { LogCrit(COMPONENT_CONFIG, "Pseudofs block error"); return -1; } num_exp = load_config_from_parse(in_config, &export_param, NULL, false, err_type); if (num_exp < 0) { LogCrit(COMPONENT_CONFIG, "Export block error"); return -1; } rc = build_default_root(err_type); if (rc < 0) { LogCrit(COMPONENT_CONFIG, "No pseudo root!"); return -1; } log_all_exports(NIV_INFO, __LINE__, __func__); return num_exp; } /** * @brief Reread the export entries from the parsed configuration file. * * @param[in] in_config The file that contains the export list * * @return A negative value on error, * the number of export entries else. */ int reread_exports(config_file_t in_config, struct config_error_type *err_type) { int rc, num_exp; uint64_t generation; EXPORT_ADMIN_LOCK(); LogInfo(COMPONENT_CONFIG, "Reread exports starting"); LogDebug(COMPONENT_EXPORT, "Exports before update"); log_all_exports(NIV_DEBUG, __LINE__, __func__); rc = load_config_from_parse(in_config, &export_defaults_param, &export_opt_cfg, false, err_type); if (rc < 0) { LogCrit(COMPONENT_CONFIG, "Export defaults block error"); num_exp = -1; goto out; } LogDebug(COMPONENT_EXPORT, "About to update pseudofs block"); rc = load_config_from_parse(in_config, &update_pseudofs_param, NULL, false, err_type); if (rc < 0) { LogCrit(COMPONENT_CONFIG, "Pseudofs block error"); num_exp = -1; goto out; } num_exp = load_config_from_parse(in_config, &update_export_param, NULL, false, err_type); if (num_exp < 0) { LogCrit(COMPONENT_CONFIG, "Export block error"); num_exp = -1; goto out; } generation = get_config_generation(in_config); /* Prune the pseudofs of all exports that will be unexported (defunct) * as well as any descendant exports. Then unexport all defunct exports. * Finally remount all the exports that were unmounted. If that fails, * create_pseudofs() will LogFatal and abort. */ prune_pseudofs_subtree(NULL, generation, false); prune_defunct_exports(generation); create_pseudofs(); LogEvent(COMPONENT_CONFIG, "Reread exports complete"); LogInfo(COMPONENT_EXPORT, "Exports after update"); log_all_exports(NIV_INFO, __LINE__, __func__); out: EXPORT_ADMIN_UNLOCK(); return num_exp; } /** * @brief Free resources attached to an export * * @param export [IN] pointer to export * * @return true if all went well */ void free_export_resources(struct gsh_export *export, bool config) { struct req_op_context op_context; bool restore_op_ctx = false; LogDebug(COMPONENT_EXPORT, "Free resources for export %p id %d path %s", export, export->export_id, export->cfg_fullpath); if (op_ctx == NULL || op_ctx->ctx_export != export) { /* We need to complete export cleanup with this export. * Otherwise we SHOULD be being called in the final throes * of releasing an op context, or at least the export so * attached. We don't need a reference to the export because we * are already inside freeing it. */ init_op_context_simple(&op_context, export, export->fsal_export); restore_op_ctx = true; } LogDebug(COMPONENT_EXPORT, "Export root %p", export->exp_root_obj); release_export(export, config); LogDebug(COMPONENT_EXPORT, "release_export complete"); FreeClientList(&export->clients, FreeExportClient); if (export->fsal_export != NULL) { struct fsal_module *fsal = export->fsal_export->fsal; export->fsal_export->exp_ops.release(export->fsal_export); fsal_put(fsal); LogFullDebug(COMPONENT_FSAL, "FSAL %s fsal_refcount %" PRIu32, fsal->name, atomic_fetch_int32_t(&fsal->refcount)); } export->fsal_export = NULL; /* free strings here */ gsh_free(export->cfg_fullpath); gsh_free(export->cfg_pseudopath); gsh_free(export->FS_tag); /* Release the refstr if they have been created. Note that we * normally expect a refstr to be created, but we could be freeing an * export for which config failed before we were able to create the * refstr for it. */ if (export->fullpath != NULL) gsh_refstr_put(export->fullpath); if (export->pseudopath != NULL) gsh_refstr_put(export->pseudopath); /* At this point the export is no longer usable so poison the op * context (whether the one set up above or a pre-existing one. * However, we leave the refstr in the op context alone, they will be * cleaned up by the eventual clear_op_context_export() on the current * op context (either the original one or the temporary one created * above). */ op_ctx->ctx_export = NULL; op_ctx->fsal_export = NULL; LogDebug(COMPONENT_EXPORT, "Goodbye export %p path %s pseudo %s", export, CTX_FULLPATH(op_ctx), CTX_PSEUDOPATH(op_ctx)); if (restore_op_ctx) { /* And restore to the original op context */ release_op_context(); } } /** * @brief pkginit callback to initialize exports from nfs_init * * Assumes being called with the export_by_id.lock held. * true on success */ static bool init_export_cb(struct gsh_export *exp, void *state) { struct glist_head *errlist = state; if (init_export_root(exp)) { glist_del(&exp->exp_list); glist_add(errlist, &exp->exp_list); } return true; } /** * @brief Initialize exports over a live fsal layer */ void exports_pkginit(void) { struct glist_head errlist; struct glist_head *glist, *glistn; struct gsh_export *export; glist_init(&errlist); foreach_gsh_export(init_export_cb, true, &errlist); glist_for_each_safe(glist, glistn, &errlist) { export = glist_entry(glist, struct gsh_export, exp_list); export_revert(export); } } /** * @brief Return a reference to the root object of the export * * Must be called with the caller holding a reference to the export. * * Returns with an additional reference to the obj held for use by the * caller. * * @param export [IN] the aforementioned export * @param entry [IN/OUT] call by ref pointer to store obj * * @return FSAL status */ fsal_status_t nfs_export_get_root_entry(struct gsh_export *export, struct fsal_obj_handle **obj) { PTHREAD_RWLOCK_rdlock(&export->exp_lock); if (export->exp_root_obj) export->exp_root_obj->obj_ops->get_ref(export->exp_root_obj); *obj = export->exp_root_obj; PTHREAD_RWLOCK_unlock(&export->exp_lock); if (!(*obj)) return fsalstat(ERR_FSAL_NOENT, 0); if ((*obj)->type != DIRECTORY) { (*obj)->obj_ops->put_ref(*obj); *obj = NULL; return fsalstat(ERR_FSAL_NOTDIR, 0); } return fsalstat(ERR_FSAL_NO_ERROR, 0); } /** * @brief Set file systems max read write sizes in the export * * @param export [IN] the export * @param maxread [IN] maxread size * @param maxwrite [IN] maxwrite size */ static void set_fs_max_rdwr_size(struct gsh_export *export, uint64_t maxread, uint64_t maxwrite) { if (maxread != 0) { if (!op_ctx_export_has_option_set(EXPORT_OPTION_MAXREAD_SET)) { LogInfo(COMPONENT_EXPORT, "Readjusting MaxRead to %" PRIu64, maxread); export->MaxRead = maxread; } } if (maxwrite != 0) { if (!op_ctx_export_has_option_set(EXPORT_OPTION_MAXWRITE_SET)) { LogInfo(COMPONENT_EXPORT, "Readjusting MaxWrite to %" PRIu64, maxwrite); export->MaxWrite = maxwrite; } } if (export->PrefRead > export->MaxRead) { LogInfo(COMPONENT_EXPORT, "Readjusting PrefRead to %" PRIu64, export->MaxRead); export->PrefRead = export->MaxRead; } if (export->PrefWrite > export->MaxWrite) { LogInfo(COMPONENT_EXPORT, "Readjusting PrefWrite to %" PRIu64, export->MaxWrite); export->PrefWrite = export->MaxWrite; } } /** * @brief Initialize the root object for an export. * * Assumes being called with the export_by_id.lock held. * * @param exp [IN] the export * * @return 0 if successful otherwise err. */ int init_export_root(struct gsh_export *export) { fsal_status_t fsal_status; struct fsal_obj_handle *obj; struct req_op_context op_context; int my_status; /* Get a ref to the export and initialize op_context */ get_gsh_export_ref(export); init_op_context_simple(&op_context, export, export->fsal_export); /* set expire_time_attr if appropriate */ if ((op_ctx->export_perms.set & EXPORT_OPTION_EXPIRE_SET) == 0 && (op_ctx->ctx_export->export_perms.set & EXPORT_OPTION_EXPIRE_SET) != 0) { op_ctx->export_perms.expire_time_attr = op_ctx->ctx_export->export_perms.expire_time_attr; op_ctx->export_perms.set |= EXPORT_OPTION_EXPIRE_SET; } if ((op_ctx->export_perms.set & EXPORT_OPTION_EXPIRE_SET) == 0 && (export_opt.conf.set & EXPORT_OPTION_EXPIRE_SET) != 0) { op_ctx->export_perms.expire_time_attr = export_opt.conf.expire_time_attr; op_ctx->export_perms.set |= EXPORT_OPTION_EXPIRE_SET; } if ((op_ctx->export_perms.set & EXPORT_OPTION_EXPIRE_SET) == 0) op_ctx->export_perms.expire_time_attr = export_opt.def.expire_time_attr; /* set the EXPORT_OPTION_EXPIRE_SET bit from the export * into the op_ctx */ op_ctx->export_perms.options |= EXPORT_OPTION_EXPIRE_SET; /* Lookup for the FSAL Path */ LogDebug(COMPONENT_EXPORT, "About to lookup_path for ExportId=%u Path=%s", export->export_id, CTX_FULLPATH(op_ctx)); /* This takes a reference, which will keep the root object around for * the lifetime of the export. */ fsal_status = export->fsal_export->exp_ops.lookup_path( export->fsal_export, CTX_FULLPATH(op_ctx), &obj, NULL); if (FSAL_IS_ERROR(fsal_status)) { my_status = EINVAL; LogCrit(COMPONENT_EXPORT, "Lookup failed on path, ExportId=%u Path=%s FSAL_ERROR=(%s,%u)", export->export_id, CTX_FULLPATH(op_ctx), msg_fsal_err(fsal_status.major), fsal_status.minor); goto out; } if (!op_ctx_export_has_option_set(EXPORT_OPTION_MAXREAD_SET) || !op_ctx_export_has_option_set(EXPORT_OPTION_MAXWRITE_SET) || !op_ctx_export_has_option_set(EXPORT_OPTION_PREFREAD_SET) || !op_ctx_export_has_option_set(EXPORT_OPTION_PREFWRITE_SET)) { fsal_dynamicfsinfo_t dynamicinfo; dynamicinfo.maxread = 0; dynamicinfo.maxwrite = 0; fsal_status = export->fsal_export->exp_ops.get_fs_dynamic_info( export->fsal_export, obj, &dynamicinfo); if (!FSAL_IS_ERROR(fsal_status)) { set_fs_max_rdwr_size(export, dynamicinfo.maxread, dynamicinfo.maxwrite); } } PTHREAD_RWLOCK_wrlock(&export->exp_lock); PTHREAD_RWLOCK_wrlock(&obj->state_hdl->jct_lock); /* Get a reference on the object */ obj->obj_ops->get_ref(obj); /* Pass ref off to export */ export_root_object_get(obj); export->exp_root_obj = obj; glist_add_tail(&obj->state_hdl->dir.export_roots, &export->exp_root_list); /* Protect this entry from removal (unlink) */ (void)atomic_inc_int32_t(&obj->state_hdl->dir.exp_root_refcount); PTHREAD_RWLOCK_unlock(&obj->state_hdl->jct_lock); PTHREAD_RWLOCK_unlock(&export->exp_lock); LogDebug(COMPONENT_EXPORT, "Added root obj %p FSAL %s for path %s on export_id=%d", obj, obj->fsal->name, CTX_FULLPATH(op_ctx), export->export_id); my_status = 0; out: release_op_context(); return my_status; } /** * @brief Release all the export state, including the root object * * @param exp [IN] the export * @param config [IN] this export is only a config object */ void release_export(struct gsh_export *export, bool config) { struct fsal_obj_handle *obj = NULL; fsal_status_t fsal_status; if (!config) { LogDebug(COMPONENT_EXPORT, "Unexport %s, Pseudo %s", CTX_FULLPATH(op_ctx), CTX_PSEUDOPATH(op_ctx)); } /* Get a reference to the root entry */ fsal_status = nfs_export_get_root_entry(export, &obj); if (FSAL_IS_ERROR(fsal_status)) { /* No more root entry, bail out, this export is * probably about to be destroyed. */ LogInfo(COMPONENT_MDCACHE, "Export root for export id %d status %s", export->export_id, msg_fsal_err(fsal_status.major)); return; } /* Make the export unreachable as a root object */ PTHREAD_RWLOCK_wrlock(&export->exp_lock); PTHREAD_RWLOCK_wrlock(&obj->state_hdl->jct_lock); glist_del(&export->exp_root_list); export_root_object_put(export->exp_root_obj); export->exp_root_obj->obj_ops->put_ref(export->exp_root_obj); export->exp_root_obj = NULL; (void)atomic_dec_int32_t(&obj->state_hdl->dir.exp_root_refcount); PTHREAD_RWLOCK_unlock(&obj->state_hdl->jct_lock); PTHREAD_RWLOCK_unlock(&export->exp_lock); LogDebug(COMPONENT_EXPORT, "Released root obj %p for path %s, pseudo %s on export_id=%d", obj, CTX_FULLPATH(op_ctx), CTX_PSEUDOPATH(op_ctx), export->export_id); if (!config) { /* Make export unreachable via pseudo fs. * We keep the export in the export hash table through the * following so that the underlying FSALs have access to the * export while performing the various cleanup operations. */ pseudo_unmount_export_tree(export); } export->fsal_export->exp_ops.prepare_unexport(export->fsal_export); if (!config) { /* Release state belonging to this export */ state_release_export(export); } /* Flush FSAL-specific state */ LogFullDebug( COMPONENT_EXPORT, "About to unexport from FSAL root obj %p for path %s, pseudo %s on export_id=%d", obj, CTX_FULLPATH(op_ctx), CTX_PSEUDOPATH(op_ctx), export->export_id); export->fsal_export->exp_ops.unexport(export->fsal_export, obj); if (!config) { /* Remove the mapping to the export now that cleanup is * complete. */ remove_gsh_export(export->export_id); } /* Release the reference */ obj->obj_ops->put_ref(obj); /* Release ref taken above */ LogFullDebug( COMPONENT_EXPORT, "About to put_ref root obj %p for path %s, pseudo %s on export_id=%d", obj, CTX_FULLPATH(op_ctx), CTX_PSEUDOPATH(op_ctx), export->export_id); obj->obj_ops->put_ref(obj); } /** * @brief Checks if request security flavor is suffcient for the requested * export * * @param[in] req Related RPC request. * * @return true if the request flavor exists in the matching export * false otherwise */ bool export_check_security(struct svc_req *req) { switch (req->rq_msg.cb_cred.oa_flavor) { case AUTH_NONE: if ((op_ctx->export_perms.options & EXPORT_OPTION_AUTH_NONE) == 0) { LogInfo(COMPONENT_EXPORT, "Export %s does not support AUTH_NONE", op_ctx_export_path(op_ctx)); return false; } break; case AUTH_UNIX: if ((op_ctx->export_perms.options & EXPORT_OPTION_AUTH_UNIX) == 0) { LogInfo(COMPONENT_EXPORT, "Export %s does not support AUTH_UNIX", op_ctx_export_path(op_ctx)); return false; } break; #ifdef _HAVE_GSSAPI case RPCSEC_GSS: if ((op_ctx->export_perms.options & (EXPORT_OPTION_RPCSEC_GSS_NONE | EXPORT_OPTION_RPCSEC_GSS_INTG | EXPORT_OPTION_RPCSEC_GSS_PRIV)) == 0) { LogInfo(COMPONENT_EXPORT, "Export %s does not support RPCSEC_GSS", op_ctx_export_path(op_ctx)); return false; } else { struct rpc_gss_cred *gc = (struct rpc_gss_cred *)req->rq_msg.rq_cred_body; rpc_gss_svc_t svc = gc->gc_svc; LogFullDebug(COMPONENT_EXPORT, "Testing svc %d", (int)svc); switch (svc) { case RPCSEC_GSS_SVC_NONE: if ((op_ctx->export_perms.options & EXPORT_OPTION_RPCSEC_GSS_NONE) == 0) { LogInfo(COMPONENT_EXPORT, "Export %s does not support RPCSEC_GSS_SVC_NONE", op_ctx_export_path(op_ctx)); return false; } break; case RPCSEC_GSS_SVC_INTEGRITY: if ((op_ctx->export_perms.options & EXPORT_OPTION_RPCSEC_GSS_INTG) == 0) { LogInfo(COMPONENT_EXPORT, "Export %s does not support RPCSEC_GSS_SVC_INTEGRITY", op_ctx_export_path(op_ctx)); return false; } break; case RPCSEC_GSS_SVC_PRIVACY: if ((op_ctx->export_perms.options & EXPORT_OPTION_RPCSEC_GSS_PRIV) == 0) { LogInfo(COMPONENT_EXPORT, "Export %s does not support RPCSEC_GSS_SVC_PRIVACY", op_ctx_export_path(op_ctx)); return false; } break; default: LogInfo(COMPONENT_EXPORT, "Export %s does not support unknown RPCSEC_GSS_SVC %d", op_ctx_export_path(op_ctx), (int)svc); return false; } } break; #endif default: LogInfo(COMPONENT_EXPORT, "Export %s does not support unknown oa_flavor %d", op_ctx_export_path(op_ctx), (int)req->rq_msg.cb_cred.oa_flavor); return false; } return true; } /** * @brief Get the best anonymous uid available. * * This is safe if there is no op_ctx or there is one but there is no * export_perms attached. * */ uid_t get_anonymous_uid(void) { uid_t anon_uid; if (op_ctx != NULL && (op_ctx->export_perms.set & EXPORT_OPTION_ANON_UID_SET) != 0) { /* We have export_perms, use it. */ return op_ctx->export_perms.anonymous_uid; } PTHREAD_RWLOCK_rdlock(&export_opt_lock); if ((export_opt.conf.set & EXPORT_OPTION_ANON_UID_SET) != 0) { /* Option was set in EXPORT_DEFAULTS */ anon_uid = export_opt.conf.anonymous_uid; } else { /* Default to code default. */ anon_uid = export_opt.def.anonymous_uid; } PTHREAD_RWLOCK_unlock(&export_opt_lock); return anon_uid; } /** * @brief Get the best anonymous gid available. * * This is safe if there is no op_ctx or there is one but there is no * export_perms attached. * */ gid_t get_anonymous_gid(void) { /* Default to code default. */ gid_t anon_gid = export_opt.def.anonymous_gid; if (op_ctx != NULL && (op_ctx->export_perms.set & EXPORT_OPTION_ANON_GID_SET) != 0) { /* We have export_perms, use it. */ return op_ctx->export_perms.anonymous_gid; } PTHREAD_RWLOCK_rdlock(&export_opt_lock); if ((export_opt.conf.set & EXPORT_OPTION_ANON_GID_SET) != 0) { /* Option was set in EXPORT_DEFAULTS */ anon_gid = export_opt.conf.anonymous_gid; } else { /* Default to code default. */ anon_gid = export_opt.def.anonymous_gid; } PTHREAD_RWLOCK_unlock(&export_opt_lock); return anon_gid; } /** * @brief Checks if a machine is authorized to access an export entry * * Permissions in the op context get updated based on export and client. * * Takes the export->exp_lock in read mode to protect the client list and * export permissions while performing this work. */ void export_check_access(void) { struct base_client_entry *client = NULL; struct exportlist_client_entry *expclient = NULL; char exp_str[PATH_MAX + 64]; struct display_buffer dspbuf = { sizeof(exp_str), exp_str, exp_str }; /* Initialize permissions to allow nothing, anonymous_uid and * anonymous_gid will get set farther down. */ memset(&op_ctx->export_perms, 0, sizeof(op_ctx->export_perms)); if (op_ctx->ctx_export != NULL) { /* Take lock */ PTHREAD_RWLOCK_rdlock(&op_ctx->ctx_export->exp_lock); PTHREAD_RWLOCK_rdlock(&export_opt_lock); } else { /* Shortcut if no export */ PTHREAD_RWLOCK_rdlock(&export_opt_lock); goto no_export; } if (isMidDebug(COMPONENT_EXPORT)) { display_printf(&dspbuf, " for export id %u path %s", op_ctx->ctx_export->export_id, op_ctx_export_path(op_ctx)); } else { exp_str[0] = '\0'; } if (glist_empty(&op_ctx->ctx_export->clients)) { /* No client list so use the export defaults client list to * see if there's a match. */ client = client_match(COMPONENT_EXPORT, exp_str, op_ctx->caller_addr, &export_opt.clients); } else { /* Does the client match anyone on the client list? */ client = client_match(COMPONENT_EXPORT, exp_str, op_ctx->caller_addr, &op_ctx->ctx_export->clients); } if (client != NULL) { /* Take client options */ expclient = container_of(client, struct exportlist_client_entry, client_entry); op_ctx->export_perms.options = expclient->client_perms.options & expclient->client_perms.set; if (expclient->client_perms.set & EXPORT_OPTION_ANON_UID_SET) op_ctx->export_perms.anonymous_uid = expclient->client_perms.anonymous_uid; if (expclient->client_perms.set & EXPORT_OPTION_ANON_GID_SET) op_ctx->export_perms.anonymous_gid = expclient->client_perms.anonymous_gid; op_ctx->export_perms.set = expclient->client_perms.set; } /* Any options not set by the client, take from the export */ op_ctx->export_perms.options |= op_ctx->ctx_export->export_perms.options & op_ctx->ctx_export->export_perms.set & ~op_ctx->export_perms.set; if ((op_ctx->export_perms.set & EXPORT_OPTION_ANON_UID_SET) == 0 && (op_ctx->ctx_export->export_perms.set & EXPORT_OPTION_ANON_UID_SET) != 0) op_ctx->export_perms.anonymous_uid = op_ctx->ctx_export->export_perms.anonymous_uid; if ((op_ctx->export_perms.set & EXPORT_OPTION_ANON_GID_SET) == 0 && (op_ctx->ctx_export->export_perms.set & EXPORT_OPTION_ANON_GID_SET) != 0) op_ctx->export_perms.anonymous_gid = op_ctx->ctx_export->export_perms.anonymous_gid; if ((op_ctx->export_perms.set & EXPORT_OPTION_EXPIRE_SET) == 0 && (op_ctx->ctx_export->export_perms.set & EXPORT_OPTION_EXPIRE_SET) != 0) op_ctx->export_perms.expire_time_attr = op_ctx->ctx_export->export_perms.expire_time_attr; op_ctx->export_perms.set |= op_ctx->ctx_export->export_perms.set; no_export: /* Any options not set by the client or export, take from the * EXPORT_DEFAULTS block. */ op_ctx->export_perms.options |= export_opt.conf.options & export_opt.conf.set & ~op_ctx->export_perms.set; if ((op_ctx->export_perms.set & EXPORT_OPTION_ANON_UID_SET) == 0 && (export_opt.conf.set & EXPORT_OPTION_ANON_UID_SET) != 0) op_ctx->export_perms.anonymous_uid = export_opt.conf.anonymous_uid; if ((op_ctx->export_perms.set & EXPORT_OPTION_ANON_GID_SET) == 0 && (export_opt.conf.set & EXPORT_OPTION_ANON_GID_SET) != 0) op_ctx->export_perms.anonymous_gid = export_opt.conf.anonymous_gid; if ((op_ctx->export_perms.set & EXPORT_OPTION_EXPIRE_SET) == 0 && (export_opt.conf.set & EXPORT_OPTION_EXPIRE_SET) != 0) op_ctx->export_perms.expire_time_attr = export_opt.conf.expire_time_attr; op_ctx->export_perms.set |= export_opt.conf.set; /* And finally take any options not yet set from global defaults */ op_ctx->export_perms.options |= export_opt.def.options & ~op_ctx->export_perms.set; if ((op_ctx->export_perms.set & EXPORT_OPTION_ANON_UID_SET) == 0) op_ctx->export_perms.anonymous_uid = export_opt.def.anonymous_uid; if ((op_ctx->export_perms.set & EXPORT_OPTION_ANON_GID_SET) == 0) op_ctx->export_perms.anonymous_gid = export_opt.def.anonymous_gid; if ((op_ctx->export_perms.set & EXPORT_OPTION_EXPIRE_SET) == 0) op_ctx->export_perms.expire_time_attr = export_opt.def.expire_time_attr; op_ctx->export_perms.set |= export_opt.def.set; if (isMidDebug(COMPONENT_EXPORT)) { char perms[1024] = "\0"; struct display_buffer dspbuf = { sizeof(perms), perms, perms }; if (expclient != NULL) { (void)StrExportOptions(&dspbuf, &expclient->client_perms); LogMidDebug(COMPONENT_EXPORT, "CLIENT (%s)", perms); display_reset_buffer(&dspbuf); } if (op_ctx->ctx_export != NULL) { (void)StrExportOptions( &dspbuf, &op_ctx->ctx_export->export_perms); LogMidDebug(COMPONENT_EXPORT, "EXPORT (%s)", perms); display_reset_buffer(&dspbuf); } (void)StrExportOptions(&dspbuf, &export_opt.conf); LogMidDebug(COMPONENT_EXPORT, "EXPORT_DEFAULTS (%s)", perms); display_reset_buffer(&dspbuf); (void)StrExportOptions(&dspbuf, &export_opt.def); LogMidDebug(COMPONENT_EXPORT, "default options (%s)", perms); display_reset_buffer(&dspbuf); (void)StrExportOptions(&dspbuf, &op_ctx->export_perms); LogMidDebug(COMPONENT_EXPORT, "Final options (%s)", perms); } PTHREAD_RWLOCK_unlock(&export_opt_lock); if (op_ctx->ctx_export != NULL) { /* Release lock */ PTHREAD_RWLOCK_unlock(&op_ctx->ctx_export->exp_lock); } } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/fridgethr.c�������������������������������������������������������������0000664�0000000�0000000�00000105722�14737566223�0020231�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @addtogroup fridgethr * @{ */ /** * @file fridgethr.c * @brief Implementation of the thread fridge * */ #include "config.h" #include "log.h" #include <stdlib.h> #include <string.h> #include <pthread.h> #ifdef LINUX #include <sys/signal.h> #elif FREEBSD #include <signal.h> #endif #include <urcu-bp.h> #include "abstract_mem.h" #include "common_utils.h" #include "fridgethr.h" #include "nfs_core.h" /** * @brief Initialize a thread fridge * * @note It is more robust to initialize the parameters to 0 than set * specifically what is desired, otherwise uninitialized memory could * provoke unexpected behaviour when new parameters are added. * * @param[out] frout The fridge to initialize * @param[in] s The name of the fridge * @param[in] p Fridge parameters * * @return 0 on success, POSIX errors on failure. */ int fridgethr_init(struct fridgethr **frout, const char *s, const struct fridgethr_params *p) { /* The fridge under construction */ struct fridgethr *frobj; if ((p->thr_min > p->thr_max) && p->thr_max != 0) { LogMajor( COMPONENT_THREAD, "Minimum of %d is greater than maximum of %d in fridge %s", p->thr_min, p->thr_max, s); return EINVAL; } if ((p->wake_threads != NULL) && (p->flavor != fridgethr_flavor_looper)) { LogMajor(COMPONENT_THREAD, "Wake function only allowed on loopers: %s", s); return EINVAL; } frobj = gsh_malloc(sizeof(struct fridgethr)); *frout = NULL; frobj->p = *p; frobj->s = NULL; frobj->nthreads = 0; frobj->nidle = 0; frobj->flags = fridgethr_flag_none; PTHREAD_ATTR_init(&frobj->attr); PTHREAD_ATTR_setscope(&frobj->attr, PTHREAD_SCOPE_SYSTEM); PTHREAD_ATTR_setdetachstate(&frobj->attr, PTHREAD_CREATE_DETACHED); PTHREAD_MUTEX_init(&frobj->frt_mtx, NULL); frobj->s = gsh_strdup(s); frobj->command = fridgethr_comm_run; frobj->transitioning = false; /* Thread list */ glist_init(&frobj->thread_list); /* Idle threads queue */ glist_init(&frobj->idle_q); /* Flavor */ if (frobj->p.flavor == fridgethr_flavor_worker) { /* Deferment */ switch (frobj->p.deferment) { case fridgethr_defer_queue: glist_init(&frobj->work_q); break; case fridgethr_defer_fail: /* Failing is easy. */ break; default: LogMajor(COMPONENT_THREAD, "Invalid value fridgethr_defer_t of %d in %s", frobj->p.deferment, s); goto inval; } } else if (frobj->p.flavor == fridgethr_flavor_looper) { if (frobj->p.deferment != fridgethr_defer_fail) { LogMajor( COMPONENT_THREAD, "Deferment is not allowed in looper fridges: In fridge %s, requested deferment of %d.", s, frobj->p.deferment); goto inval; } } else { LogMajor(COMPONENT_THREAD, "Thread flavor of %d is disallowed in fridge: %s", frobj->p.flavor, s); goto inval; } *frout = frobj; return 0; inval: PTHREAD_MUTEX_destroy(&frobj->frt_mtx); PTHREAD_ATTR_destroy(&frobj->attr); gsh_free(frobj->s); gsh_free(frobj); return EINVAL; } /** * @brief Destroy a thread fridge * * @param[in] fr The fridge to destroy */ void fridgethr_destroy(struct fridgethr *fr) { /* make sure that fridgethr_freeze has released this mutex */ PTHREAD_MUTEX_lock(&fr->frt_mtx); PTHREAD_MUTEX_unlock(&fr->frt_mtx); PTHREAD_MUTEX_destroy(&fr->frt_mtx); PTHREAD_ATTR_destroy(&fr->attr); gsh_free(fr->s); gsh_free(fr); } /** * @brief Finish a transition * * Notify whoever cares that we're done and mark the transition as * complete. The fridge lock must be held when calling this function. * * @param[in,out] fr The fridge * @param[in] locked The completion mutex is already locked. * Neither acquire nor release it. */ static void fridgethr_finish_transition(struct fridgethr *fr, bool locked) { if (!fr->transitioning) return; if (fr->cb_mtx && !locked) PTHREAD_MUTEX_lock(fr->cb_mtx); if (fr->cb_func != NULL) fr->cb_func(fr->cb_arg); if (fr->cb_cv) pthread_cond_broadcast(fr->cb_cv); if (fr->cb_mtx && !locked) PTHREAD_MUTEX_unlock(fr->cb_mtx); if (!locked) { fr->cb_mtx = NULL; fr->cb_cv = NULL; } fr->cb_func = NULL; fr->cb_arg = NULL; fr->transitioning = false; } /** * @brief Test whether the fridge has deferred work waiting * * @note This function must be called with the fridge mutex held. * * @return true if deferred work is waiting. */ static bool fridgethr_deferredwork(struct fridgethr *fr) { bool res = false; switch (fr->p.deferment) { case fridgethr_defer_queue: res = !glist_empty(&fr->work_q); break; case fridgethr_defer_fail: res = false; break; } return res; } /** * @brief Get deferred work * * This function only does something in the case of a queueing * fridge. If work is available, it loads it into the thread context * and returns true. If work is not available (or the fridge is not a * queueing fridge) it returns false and leaves the context untouched. * * @param[in,out] fr Fridge * @param[in,out] fe Fridge entry * * @note This function must be called with the fridge mutex held. * * @return true if deferred work has been dequeued. */ static bool fridgethr_getwork(struct fridgethr *fr, struct fridgethr_entry *fe) { if ((fr->p.deferment == fridgethr_defer_fail) || glist_empty(&fr->work_q)) { return false; } else { struct fridgethr_work *q = glist_first_entry( &fr->work_q, struct fridgethr_work, link); glist_del(&q->link); fe->ctx.func = q->func; fe->ctx.arg = q->arg; gsh_free(q); return true; } } /** * @brief Wait for more work * * This function, called by a worker thread, will cause it to wait for * more work (or exit). * * @note To dispatch a task to a sleeping thread, that is, to load a * function and argument into its context and have them executed, * fridgethr_flag_dispatched must be set. If the thread awakes and * firdgethr_flag_dispatched is not set, it will decide what to do on * its own based on the current command and queue. * * @retval true if we have more work to do. * @retval false if we need to go away. */ static bool fridgethr_freeze(struct fridgethr *fr, struct fridgethr_context *thr_ctx) { /* Entry for this thread */ struct fridgethr_entry *fe = container_of(thr_ctx, struct fridgethr_entry, ctx); /* Return code from system calls */ int rc = 0; PTHREAD_MUTEX_lock(&fr->frt_mtx); restart: /* If we are not paused and there is work left to do in the queue, do it. */ if (!(fr->command == fridgethr_comm_pause) && fridgethr_getwork(fr, fe)) { PTHREAD_MUTEX_unlock(&fr->frt_mtx); return true; } /* rc would have been set in the while loop below */ if (((rc == ETIMEDOUT) && (fr->nthreads > fr->p.thr_min)) || (fr->command == fridgethr_comm_stop)) { /* We do this here since we already have the fridge lock. */ --(fr->nthreads); glist_del(&fe->thread_link); if ((fr->nthreads == 0) && (fr->command == fridgethr_comm_stop) && (fr->transitioning) && !fridgethr_deferredwork(fr)) { /* We're the last thread to exit, signal the transition to pause complete. */ fridgethr_finish_transition(fr, false); } PTHREAD_MUTEX_lock(&fe->ctx.fre_mtx); PTHREAD_MUTEX_unlock(&fe->ctx.fre_mtx); PTHREAD_MUTEX_unlock(&fr->frt_mtx); return false; } assert(fr->command != fridgethr_comm_stop); glist_add_tail(&fr->idle_q, &fe->idle_link); ++(fr->nidle); if ((fr->nidle == fr->nthreads) && (fr->command == fridgethr_comm_pause) && (fr->transitioning)) { /* We're the last thread to suspend, signal the transition to pause complete. */ fridgethr_finish_transition(fr, false); } PTHREAD_MUTEX_lock(&fe->ctx.fre_mtx); fe->frozen = true; fe->flags |= fridgethr_flag_available; /* It is a state machine, keep going until we have a transition that gets us out. */ while (true) { bool fre_mtx_locked = true; if ((fr->p.wake_threads == NULL) || (fr->command != fridgethr_comm_run)) { if (fr->p.thread_delay > 0) { clock_gettime(CLOCK_REALTIME, &fe->timeout); fe->timeout.tv_sec += fr->p.thread_delay; PTHREAD_MUTEX_unlock(&fr->frt_mtx); rc = pthread_cond_timedwait(&fe->ctx.fre_cv, &fe->ctx.fre_mtx, &fe->timeout); } else { PTHREAD_MUTEX_unlock(&fr->frt_mtx); rc = pthread_cond_wait(&fe->ctx.fre_cv, &fe->ctx.fre_mtx); } fre_mtx_locked = false; } if (rc == ETIMEDOUT) fe->ctx.woke = false; else fe->ctx.woke = true; /* Clear this while we have the lock, we can set it again before continuing */ fe->frozen = false; /* It's repetition, but it saves us from having to drop and then reacquire the lock later. */ if (fe->flags & fridgethr_flag_dispatched) { fe->flags &= ~(fridgethr_flag_available | fridgethr_flag_dispatched); PTHREAD_MUTEX_unlock(&fe->ctx.fre_mtx); if (fre_mtx_locked) PTHREAD_MUTEX_unlock(&fr->frt_mtx); break; } /* Clear available so we won't be dispatched while we're acquiring the fridge lock. */ fe->flags &= ~fridgethr_flag_available; PTHREAD_MUTEX_unlock(&fe->ctx.fre_mtx); if (!fre_mtx_locked) PTHREAD_MUTEX_lock(&fr->frt_mtx); /* Nothing to do, loop around. */ if (fr->command != fridgethr_comm_stop && ((fr->command == fridgethr_comm_pause) || fridgethr_deferredwork(fr)) && (fr->p.flavor == fridgethr_flavor_worker)) { PTHREAD_MUTEX_lock(&fe->ctx.fre_mtx); fe->frozen = true; fe->flags |= fridgethr_flag_available; continue; } --(fr->nidle); glist_del(&fe->idle_link); if (fr->p.flavor == fridgethr_flavor_worker) goto restart; else { PTHREAD_MUTEX_unlock(&fr->frt_mtx); break; } } /* We were already unfrozen and taken off the idle queue, so there's nothing more to do than: */ return true; } /** * @brief Operation context. * * This carries everything relevant to a protocol operation * Since it is a thread local, it is exclusively in the thread context * and cannot be shared with another thread. * * This will always point to a valid structure. When its contents go out * of scope this is set to NULL but since dereferencing with this expectation, * a SEGV will result. This will point to one of three structures: * * 1. The op_context declared in nfs_request_t(). This is the state for any NFS * op. * * 2. The op_context declared/referenced in _9p_execute for 9P operations. * * 3. A req_op_context which is used for upcalls, exports bashing, and async * events that call functions that expect a context set up. */ __thread struct req_op_context *op_ctx; /** * @brief Initialization of a new thread in the fridge * * This routine calls the procedure that implements the actual * functionality wanted by a thread in a loop, handling rescheduling. * * @param[in] arg The fridge entry for this thread * * @return NULL. */ static void *fridgethr_start_routine(void *arg) { struct fridgethr_entry *fe = arg; struct fridgethr *fr = fe->fr; bool reschedule; int __attribute__((unused)) rc = 0; int old_type = 0; int old_state = 0; rcu_register_thread(); SetNameFunction(fr->s); /* Explicitly and definitely enable cancellation */ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_state); /* The only time a thread would be cancelled is if it were to fail to honor a more civil timeout request that times out. In these cases we assume the thread has gone into an infinite loop or deadlocked or otherwise experienced some unfortunate state. Since deferred cancellation is effective on condition waits, may be effective on read-write locks and won't be effective on mutexes, asynchronous seems the way to go. We would only do this on the way to taking down the system in any case. */ pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old_type); rc = pthread_sigmask(SIG_SETMASK, NULL, &fe->ctx.sigmask); /* The only allowable errors are EFAULT and EINVAL, both of which would indicate bugs in the code. */ assert(rc == 0); if (fr->p.thread_initialize) fr->p.thread_initialize(&fe->ctx); do { fe->ctx.func(&fe->ctx); if (fr->p.task_cleanup) fr->p.task_cleanup(&fe->ctx); reschedule = fridgethr_freeze(fr, &fe->ctx); } while (reschedule); if (fr->p.thread_finalize) fr->p.thread_finalize(&fe->ctx); PTHREAD_MUTEX_destroy(&fe->ctx.fre_mtx); PTHREAD_COND_destroy(&fe->ctx.fre_cv); gsh_free(fe); fe = NULL; /* At this point the fridge entry no longer exists and must not be accessed. */ rcu_unregister_thread(); return NULL; } /** * @brief Do the actual work of spawning a thread * * @note This function must be called with the fridge mutex held and * it releases the fridge mutex. * * @param[in] fr The fridge in which to spawn the thread * @param[in] func The thing to do * @param[in] arg The thing to do it to * * @return 0 on success or POSIX error codes. */ static int fridgethr_spawn(struct fridgethr *fr, void (*func)(struct fridgethr_context *), void *arg) { /* Return code */ int rc = 0; /* Newly created thread entry */ struct fridgethr_entry *fe = NULL; fe = gsh_calloc(1, sizeof(struct fridgethr_entry)); glist_init(&fe->thread_link); fe->fr = fr; PTHREAD_MUTEX_init(&fe->ctx.fre_mtx, NULL); PTHREAD_COND_init(&fe->ctx.fre_cv, NULL); fe->ctx.func = func; fe->ctx.arg = arg; fe->frozen = false; rc = PTHREAD_create(&fe->ctx.id, &fr->attr, fridgethr_start_routine, fe); if (rc != 0) { LogMajor(COMPONENT_THREAD, "Unable to create new thread in fridge %s: %d", fr->s, rc); goto create_err; } #ifdef LINUX /* pthread_t is a 'pointer to struct' on FreeBSD vs 'unsigned long' on Linux */ LogFullDebug(COMPONENT_THREAD, "fr %p created thread %u (nthreads %u nidle %u)", fr, (unsigned int)fe->ctx.id, fr->nthreads, fr->nidle); #endif /* Make a new thread */ ++(fr->nthreads); glist_add_tail(&fr->thread_list, &fe->thread_link); PTHREAD_MUTEX_unlock(&fr->frt_mtx); return rc; create_err: PTHREAD_COND_destroy(&fe->ctx.fre_cv); PTHREAD_MUTEX_destroy(&fe->ctx.fre_mtx); gsh_free(fe); PTHREAD_MUTEX_unlock(&fr->frt_mtx); return rc; } /** * @brief Queue a request * * Put a request on the queue and return immediately. * * @note This function must be called with the fridge lock held. * * @param[in] fr The fridge in which to find a thread * @param[in] func The thing to do * @param[in] arg The thing to do it to * * @return 0 or POSIX errors. */ static int fridgethr_queue(struct fridgethr *fr, void (*func)(struct fridgethr_context *), void *arg) { /* Queue */ struct fridgethr_work *q; assert(fr->p.deferment == fridgethr_defer_queue); q = gsh_malloc(sizeof(struct fridgethr_work)); glist_init(&q->link); q->func = func; q->arg = arg; glist_add_tail(&fr->work_q, &q->link); return 0; } /** * @brief Dispatch a job to an idle queue * * @note The fridge lock must be held when calling this routine. * * @param[in] fr The fridge in which to find a thread * @param[in] func The thing to do * @param[in] arg The thing to do it to * * @return true if the job was successfully dispatched. */ static bool fridgethr_dispatch(struct fridgethr *fr, void (*func)(struct fridgethr_context *), void *arg) { /* The entry for the found thread */ struct fridgethr_entry *fe; /* Iterator over the list */ struct glist_head *g = NULL; /* Saved pointer so we don't trash iteration */ struct glist_head *n = NULL; /* If we successfully dispatched */ bool dispatched = false; /* Try to grab a thread */ glist_for_each_safe(g, n, &fr->idle_q) { fe = container_of(g, struct fridgethr_entry, idle_link); PTHREAD_MUTEX_lock(&fe->ctx.fre_mtx); /* Get rid of a potential race condition where the thread wakes up and exits or otherwise redirects itself */ if (fe->flags & fridgethr_flag_available) { glist_del(&fe->idle_link); --(fr->nidle); fe->ctx.func = func; fe->ctx.arg = arg; fe->frozen = false; fe->flags |= fridgethr_flag_dispatched; pthread_cond_signal(&fe->ctx.fre_cv); PTHREAD_MUTEX_unlock(&fe->ctx.fre_mtx); dispatched = true; break; } PTHREAD_MUTEX_unlock(&fe->ctx.fre_mtx); } return dispatched; } /** * @brief Schedule a thread to perform a function * * This function finds an idle thread to perform func, creating one if * no thread is idle and we have not reached maxthreads. If we have * reached maxthreads, defer the request in accord with the fridge's * deferment policy. * * @param[in] fr The fridge in which to find a thread * @param[in] func The thing to do * @param[in] arg The thing to do it to * * @retval 0 on success. * @retval EPIPE if the fridge is stopped. * @retval EWOULDBLOCK if no threads are available. * @retval Other POSIX return codes. */ int fridgethr_submit(struct fridgethr *fr, void (*func)(struct fridgethr_context *), void *arg) { /* Return code */ int rc = 0; if (fr == NULL) { LogMajor(COMPONENT_THREAD, "Attempt to schedule job with no fridge thread"); return EPIPE; } PTHREAD_MUTEX_lock(&fr->frt_mtx); if (fr->command == fridgethr_comm_stop) { LogMajor(COMPONENT_THREAD, "Attempt to schedule job in stopped fridge %s.", fr->s); PTHREAD_MUTEX_unlock(&fr->frt_mtx); return EPIPE; } if (fr->command == fridgethr_comm_pause) { LogFullDebug( COMPONENT_THREAD, "Attempt to schedule job in paused fridge %s, pausing.", fr->s); goto defer; } if (fr->nidle > 0) { if (fridgethr_dispatch(fr, func, arg)) { PTHREAD_MUTEX_unlock(&fr->frt_mtx); return 0; } } if ((fr->p.thr_max == 0) || (fr->nthreads < fr->p.thr_max)) { rc = fridgethr_spawn(fr, func, arg); } else { defer: switch (fr->p.deferment) { case fridgethr_defer_queue: rc = fridgethr_queue(fr, func, arg); break; case fridgethr_defer_fail: rc = EWOULDBLOCK; break; }; PTHREAD_MUTEX_unlock(&fr->frt_mtx); } return rc; } /** * @brief Wake idle threads * * This function is intended for use in fridgethr_flavor_looper * fridges, but nothing bad happens if you call it for * fridgethr_flavor_worker fridges. It wakes all idle threads and * exits. * * @note If there are no idle threads we successfully do nothing. * * @param[in] fr The fridge in which to find a thread * * @retval 0 all idle threads woke. * @retval EPIPE fridge is stopped or paused. */ int fridgethr_wake(struct fridgethr *fr) { /* Iterator over the list */ struct glist_head *g = NULL; PTHREAD_MUTEX_lock(&fr->frt_mtx); if (fr->command != fridgethr_comm_run) { LogMajor(COMPONENT_THREAD, "Attempt to wake stopped/paused fridge %s.", fr->s); PTHREAD_MUTEX_unlock(&fr->frt_mtx); return EPIPE; } /* Wake the threads */ glist_for_each(g, &fr->idle_q) { /* The entry for the found thread */ struct fridgethr_entry *fe = container_of(g, struct fridgethr_entry, idle_link); PTHREAD_MUTEX_lock(&fe->ctx.fre_mtx); pthread_cond_signal(&fe->ctx.fre_cv); PTHREAD_MUTEX_unlock(&fe->ctx.fre_mtx); } PTHREAD_MUTEX_unlock(&fr->frt_mtx); return 0; } /** * @brief Suspend execution in the fridge * * Simply change the state to pause. If everything is already paused, * call the callback. * * @note Both @c mtx and @c cv may be NULL if you want to manage * synchrony without any help from the fridge. * * @param[in,out] fr The fridge to pause * @param[in] mtx Mutex (must be held when this function is called) * @param[in] cv Condition variable to be signalled on completion. * @param[in] cb Function to call once all threads are paused * @param[in] arg Argument to supply * * @retval 0 on success. * @retval EBUSY if a state transition is in progress. * @retval EALREADY if the fridge is already paused. * @retval EINVAL if an invalid transition (from stopped to paused) * was requested or one of @c mtx and @c cv was NULL but not * both. */ int fridgethr_pause(struct fridgethr *fr, pthread_mutex_t *pmtx, pthread_cond_t *pcv, void (*cb)(void *), void *arg) { PTHREAD_MUTEX_lock(&fr->frt_mtx); if (fr->transitioning) { PTHREAD_MUTEX_unlock(&fr->frt_mtx); LogMajor(COMPONENT_THREAD, "Transition requested during transition in fridge %s", fr->s); return EBUSY; } if ((pmtx && !pcv) || (pcv && !pmtx)) { PTHREAD_MUTEX_unlock(&fr->frt_mtx); LogMajor(COMPONENT_THREAD, "Iff, if you please: %s", fr->s); return EINVAL; } if (fr->command == fridgethr_comm_pause) { PTHREAD_MUTEX_unlock(&fr->frt_mtx); LogMajor(COMPONENT_THREAD, "Do not pause that which is already paused: %s", fr->s); return EALREADY; } if (fr->command == fridgethr_comm_stop) { PTHREAD_MUTEX_unlock(&fr->frt_mtx); LogMajor(COMPONENT_THREAD, "Invalid transition, stop to pause: %s", fr->s); return EINVAL; } fr->command = fridgethr_comm_pause; fr->transitioning = true; fr->cb_mtx = pmtx; fr->cb_cv = pcv; fr->cb_func = cb; fr->cb_arg = arg; if (fr->nthreads == fr->nidle) fridgethr_finish_transition(fr, true); if (fr->p.wake_threads != NULL) fr->p.wake_threads(fr->p.wake_threads_arg); PTHREAD_MUTEX_unlock(&fr->frt_mtx); return 0; } /** * @brief Slightly stupid workaround for an unlikely case * * @param[in] dummy Ignored */ static void fridgethr_noop(struct fridgethr_context *dummy) { /* return */ } /** * @brief Stop execution in the fridge * * Change state to stopped. Wake up all the idlers so they stop, * too. If there are no threads and the idle queue is empty, start * one up to finish any pending jobs. (This can happen if we go * straight from paused to stopped.) * * @note Both @c mtx and @c cv may be NULL if you want to manage * synchrony without any help from the fridge. * * @param[in,out] fr The fridge to pause * @param[in] mtx Mutex (must be held when this function is called) * @param[in] cv Condition variable to be signalled on completion. * @param[in] cb Function to call once all threads are paused * @param[in] arg Argument to supply * * @retval 0 on success. * @retval EBUSY if a state transition is in progress. * @retval EALREADY if the fridge is already paused. * @retval EINVAL if one of @c mtx and @c cv was NULL but not both. */ int fridgethr_stop(struct fridgethr *fr, pthread_mutex_t *pmtx, pthread_cond_t *pcv, void (*cb)(void *), void *arg) { int rc = 0; PTHREAD_MUTEX_lock(&fr->frt_mtx); if (fr->transitioning) { PTHREAD_MUTEX_unlock(&fr->frt_mtx); LogMajor(COMPONENT_THREAD, "Transition requested during transition in fridge %s", fr->s); return EBUSY; } if (fr->command == fridgethr_comm_stop) { PTHREAD_MUTEX_unlock(&fr->frt_mtx); LogMajor(COMPONENT_THREAD, "Do not stop that which is already stopped: %s", fr->s); return EALREADY; } if ((pmtx && !pcv) || (pcv && !pmtx)) { PTHREAD_MUTEX_unlock(&fr->frt_mtx); LogMajor(COMPONENT_THREAD, "Iff, if you please: %s", fr->s); return EINVAL; } fr->command = fridgethr_comm_stop; fr->transitioning = true; fr->cb_mtx = pmtx; fr->cb_cv = pcv; fr->cb_func = cb; fr->cb_arg = arg; if ((fr->nthreads == 0) && !fridgethr_deferredwork(fr)) { fridgethr_finish_transition(fr, true); PTHREAD_MUTEX_unlock(&fr->frt_mtx); return 0; } if (fr->nthreads > 0) { /* Wake the idle! */ /* Iterator over the list */ struct glist_head *g = NULL; glist_for_each(g, &fr->idle_q) { struct fridgethr_entry *fe; fe = container_of(g, struct fridgethr_entry, idle_link); PTHREAD_MUTEX_lock(&fe->ctx.fre_mtx); /* We don't dispatch or anything, just wake them all up and let them grab work off the queue or terminate. */ pthread_cond_signal(&fe->ctx.fre_cv); PTHREAD_MUTEX_unlock(&fe->ctx.fre_mtx); if (fr->p.wake_threads != NULL) fr->p.wake_threads(fr->p.wake_threads_arg); } PTHREAD_MUTEX_unlock(&fr->frt_mtx); } else { /* Well, this is embarrassing. */ assert(fr->p.deferment != fridgethr_defer_fail); if (fr->p.deferment == fridgethr_defer_queue) { struct fridgethr_work *q = glist_first_entry( &fr->work_q, struct fridgethr_work, link); glist_del(&q->link); rc = fridgethr_spawn(fr, q->func, q->arg); gsh_free(q); } else { /* Spawn a dummy to clean out the queue */ rc = fridgethr_spawn(fr, fridgethr_noop, NULL); } PTHREAD_MUTEX_unlock(&fr->frt_mtx); } return rc; } /** * @brief Start execution in the fridge * * Change state to running. Wake up all the idlers. If there's work * queued and we're below maxthreads, start some more threads. * * @note Both @c mtx and @c cv may be NULL if you want to manage * synchrony without any help from the fridge. * * @param[in,out] fr The fridge to pause * @param[in] mtx Mutex (must be held when this function is called) * @param[in] cv Condition variable to be signalled on completion. * @param[in] cb Function to call once all threads are paused * @param[in] arg Argument to supply * * @retval 0 on success. * @retval EBUSY if a state transition is in progress. * @retval EALREADY if the fridge is already paused. */ int fridgethr_start(struct fridgethr *fr, pthread_mutex_t *pmtx, pthread_cond_t *cv, void (*cb)(void *), void *arg) { /* Return code */ int rc = 0; /* Cap on the number of threads to spawn, just so we know we can terminate. */ int maybe_spawn = 50; PTHREAD_MUTEX_lock(&fr->frt_mtx); if (fr->transitioning) { PTHREAD_MUTEX_unlock(&fr->frt_mtx); LogMajor(COMPONENT_THREAD, "Transition requested during transition in fridge %s", fr->s); return EBUSY; } if (fr->command == fridgethr_comm_run) { PTHREAD_MUTEX_unlock(&fr->frt_mtx); LogMajor(COMPONENT_THREAD, "Do not start that which is already started: %s", fr->s); return EALREADY; } fr->command = fridgethr_comm_run; fr->transitioning = true; fr->cb_mtx = pmtx; fr->cb_cv = cv; fr->cb_func = cb; fr->cb_arg = arg; if ((fr->nthreads == 0) && !fridgethr_deferredwork(fr)) { /* No work scheduled and no threads running, but ready to accept requests once more. */ fridgethr_finish_transition(fr, true); PTHREAD_MUTEX_unlock(&fr->frt_mtx); return 0; } if (fr->nidle > 0) { /* Iterator over the list */ struct glist_head *g = NULL; glist_for_each(g, &fr->idle_q) { struct fridgethr_entry *fe; fe = container_of(g, struct fridgethr_entry, idle_link); PTHREAD_MUTEX_lock(&fe->ctx.fre_mtx); /* We don't dispatch or anything, just wake them all up and let them grab work off the queue or terminate. */ pthread_cond_signal(&fe->ctx.fre_cv); PTHREAD_MUTEX_unlock(&fe->ctx.fre_mtx); } } while (fridgethr_deferredwork(fr) && (maybe_spawn-- > 0) && ((fr->nthreads < fr->p.thr_max) || (fr->p.thr_max == 0))) { /* Start some threads to finish the work */ if (fr->p.deferment == fridgethr_defer_queue) { struct fridgethr_work *q = glist_first_entry( &fr->work_q, struct fridgethr_work, link); glist_del(&q->link); rc = fridgethr_spawn(fr, q->func, q->arg); gsh_free(q); PTHREAD_MUTEX_lock(&fr->frt_mtx); if (rc != 0) break; } else { rc = fridgethr_spawn(fr, fridgethr_noop, NULL); PTHREAD_MUTEX_lock(&fr->frt_mtx); if (rc != 0) break; } } if (fr->p.wake_threads != NULL) fr->p.wake_threads(fr->p.wake_threads_arg); PTHREAD_MUTEX_unlock(&fr->frt_mtx); return rc; } /** * @brief Set a flag to true, to prevent racing condition variable * * @param[in,out] flag The flag to set */ static void fridgethr_trivial_syncer(void *flag) { *(bool *)flag = true; } /** * @brief Synchronously change the state of the fridge * * A convenience function that issues a state change and waits for it * to complete. * * @param[in,out] fr The fridge to change * @param[in] command The command to issue * @param[in] timeout Number of seconds to wait for change or 0 * to wait forever. * * @retval 0 Success. * @retval EINVAL invalid state change requested. * @retval EALREADY fridge already in requested state. * @retval EBUSY fridge currently in transition. * @retval ETIMEDOUT timed out on wait. */ int fridgethr_sync_command(struct fridgethr *fr, fridgethr_comm_t command, time_t timeout) { pthread_mutex_t fsc_mtx; pthread_cond_t fsc_cv; bool done = false; int rc = 0; struct timespec ts; PTHREAD_MUTEX_init(&fsc_mtx, NULL); PTHREAD_COND_init(&fsc_cv, NULL); PTHREAD_MUTEX_lock(&fsc_mtx); switch (command) { case fridgethr_comm_run: rc = fridgethr_start(fr, &fsc_mtx, &fsc_cv, fridgethr_trivial_syncer, &done); break; case fridgethr_comm_pause: rc = fridgethr_pause(fr, &fsc_mtx, &fsc_cv, fridgethr_trivial_syncer, &done); break; case fridgethr_comm_stop: rc = fridgethr_stop(fr, &fsc_mtx, &fsc_cv, fridgethr_trivial_syncer, &done); break; default: rc = EINVAL; } if (rc != 0) { PTHREAD_MUTEX_unlock(&fsc_mtx); return rc; } if (timeout != 0) { clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += timeout; } while (!done) { if (timeout == 0) { rc = pthread_cond_wait(&fsc_cv, &fsc_mtx); assert(rc == 0); } else { rc = pthread_cond_timedwait(&fsc_cv, &fsc_mtx, &ts); if (rc == ETIMEDOUT) { LogMajor(COMPONENT_THREAD, "Sync command seems to be stalled"); /* we timed out and the callback * was not triggered, therefore, * we must exit the loop manually. */ break; } else assert(rc == 0); } } PTHREAD_MUTEX_unlock(&fsc_mtx); PTHREAD_MUTEX_destroy(&fsc_mtx); PTHREAD_COND_destroy(&fsc_cv); return rc; } /** * @brief Return true if a looper function should return * * For the moment, this checks if we're in the middle of a state * transition. * * @param[in] ctx The thread context * * @retval true if you should break. * @retval false if you don't have to. You still can if you want to. */ bool fridgethr_you_should_break(struct fridgethr_context *ctx) { /* Entry for this thread */ struct fridgethr_entry *fe = container_of(ctx, struct fridgethr_entry, ctx); struct fridgethr *fr = fe->fr; /* No locking is needed as it is only read */ return fr->transitioning; } /** * @brief Populate a fridge with threads all running the same thing * * @param[in,out] fr Fridge to populate * @param[in] func Function each thread should run * @param[in] arg Argument supplied for that function * * @retval 0 on success. * @retval EINVAL if there is no well-defined thread count. * @retval Other codes from thread creation. */ int fridgethr_populate(struct fridgethr *fr, void (*func)(struct fridgethr_context *), void *arg) { int threads_to_run; int i; PTHREAD_MUTEX_lock(&fr->frt_mtx); if (fr->p.thr_min != 0) { threads_to_run = fr->p.thr_min; } else if (fr->p.thr_max != 0) { threads_to_run = fr->p.thr_max; } else { PTHREAD_MUTEX_unlock(&fr->frt_mtx); LogMajor( COMPONENT_THREAD, "Cannot populate fridge with undefined number of threads: %s", fr->s); return EINVAL; } for (i = 0; i < threads_to_run; ++i) { struct fridgethr_entry *fe = NULL; int rc = 0; fe = gsh_calloc(1, sizeof(struct fridgethr_entry)); /* Make a new thread */ ++(fr->nthreads); glist_add_tail(&fr->thread_list, &fe->thread_link); fe->fr = fr; PTHREAD_MUTEX_init(&fe->ctx.fre_mtx, NULL); PTHREAD_COND_init(&fe->ctx.fre_cv, NULL); fe->ctx.func = func; fe->ctx.arg = arg; fe->frozen = false; rc = PTHREAD_create(&fe->ctx.id, &fr->attr, fridgethr_start_routine, fe); if (rc != 0) { LogMajor(COMPONENT_THREAD, "Unable to create new thread in fridge %s: %d", fr->s, rc); PTHREAD_MUTEX_unlock(&fr->frt_mtx); PTHREAD_MUTEX_destroy(&fe->ctx.fre_mtx); PTHREAD_COND_destroy(&fe->ctx.fre_cv); return rc; } } PTHREAD_MUTEX_unlock(&fr->frt_mtx); return 0; } /** * @brief Set the wait time of a running fridge * * @param[in] ctx Thread context * @param[in] thread_delay New time delay */ void fridgethr_setwait(struct fridgethr_context *ctx, time_t thread_delay) { struct fridgethr_entry *fe = container_of(ctx, struct fridgethr_entry, ctx); struct fridgethr *fr = fe->fr; PTHREAD_MUTEX_lock(&fr->frt_mtx); fr->p.thread_delay = thread_delay; PTHREAD_MUTEX_unlock(&fr->frt_mtx); } /** * @brief Get the wait time of a running fridge * * @param[in] ctx Thread context */ time_t fridgethr_getwait(struct fridgethr_context *ctx) { struct fridgethr_entry *fe = container_of(ctx, struct fridgethr_entry, ctx); struct fridgethr *fr = fe->fr; time_t thread_delay = 0; PTHREAD_MUTEX_lock(&fr->frt_mtx); thread_delay = fr->p.thread_delay; PTHREAD_MUTEX_unlock(&fr->frt_mtx); return thread_delay; } /** * @brief Cancel all of the threads in the fridge * * This function is done only on Ganesha shutdown and only if a * shutdown request has been ignored. We make no attempt to free the * fridge entries, since the threads are set detached and we're on the * way out anyway. * * @param[in,out] fr Fridge to cancel */ void fridgethr_cancel(struct fridgethr *fr) { /* Thread iterator */ struct glist_head *ti = NULL; /* Next thread link */ struct glist_head *tn = NULL; PTHREAD_MUTEX_lock(&fr->frt_mtx); LogEvent(COMPONENT_THREAD, "Cancelling %d threads from fridge %s.", fr->nthreads, fr->s); glist_for_each_safe(ti, tn, &fr->thread_list) { struct fridgethr_entry *t = glist_entry(ti, struct fridgethr_entry, thread_link); /* The only error we can get is no such thread. Which means the thread isn't running. Which is good enough for me. */ pthread_cancel(t->ctx.id); pthread_join(t->ctx.id, NULL); glist_del(&t->thread_link); gsh_free(t); --(fr->nthreads); } PTHREAD_MUTEX_unlock(&fr->frt_mtx); LogEvent(COMPONENT_THREAD, "All threads in %s cancelled.", fr->s); } struct fridgethr *general_fridge; int general_fridge_init(void) { struct fridgethr_params frp; int rc = 0; memset(&frp, 0, sizeof(struct fridgethr_params)); frp.thr_max = 4; frp.thr_min = 0; frp.flavor = fridgethr_flavor_worker; frp.deferment = fridgethr_defer_queue; rc = fridgethr_init(&general_fridge, "Gen_Fridge", &frp); if (rc != 0) { LogMajor(COMPONENT_THREAD, "Unable to initialize general fridge, error code %d.", rc); return rc; } return 0; } int general_fridge_shutdown(void) { int rc = fridgethr_sync_command(general_fridge, fridgethr_comm_stop, 120); if (rc == ETIMEDOUT) { LogMajor(COMPONENT_THREAD, "Shutdown timed out, cancelling threads."); fridgethr_cancel(general_fridge); } else if (rc != 0) { LogMajor(COMPONENT_THREAD, "Failed shutting down general fridge: %d", rc); } return rc; } /** @} */ ����������������������������������������������nfs-ganesha-6.5/src/support/murmur3.c���������������������������������������������������������������0000664�0000000�0000000�00000016200�14737566223�0017655�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: unknown license... /*----------------------------------------------------------------------------- * MurmurHash3 was written by Austin Appleby, and is placed in the public * domain. The author hereby disclaims copyright to this source code. * * Note - The x86 and x64 versions do _not_ produce the same results, as the * algorithms are optimized for their respective platforms. You can still * compile and run any of them on any platform, but your performance with the * non-native version will be less than optimal. */ #include "config.h" #include "murmur3.h" /*------------------------------------------------------------------------------ * Platform-specific functions and macros */ static inline uint32_t rotl32(uint32_t x, int8_t r) { return (x << r) | (x >> (32 - r)); } static inline uint64_t rotl64(uint64_t x, int8_t r) { return (x << r) | (x >> (64 - r)); } #define ROTL32(x, y) rotl32(x, y) #define ROTL64(x, y) rotl64(x, y) #define BIG_CONSTANT(x) (x##LLU) /*------------------------------------------------------------------------------ * Block read - if your platform needs to do endian-swapping or can only * handle aligned reads, do the conversion here */ #define getblock(p, i) (p[i]) /*------------------------------------------------------------------------------ * Finalization mix - force all bits of a hash block to avalanche */ static inline __attribute__((always_inline)) uint32_t fmix32(uint32_t h) { h ^= h >> 16; h *= 0x85ebca6b; h ^= h >> 13; h *= 0xc2b2ae35; h ^= h >> 16; return h; } static inline __attribute__((always_inline)) uint64_t fmix64(uint64_t k) { k ^= k >> 33; k *= BIG_CONSTANT(0xff51afd7ed558ccd); k ^= k >> 33; k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53); k ^= k >> 33; return k; } /*----------------------------------------------------------------------------*/ void MurmurHash3_x86_32(const void *key, int len, uint32_t seed, void *out) { const uint8_t *data = (const uint8_t *)key; const int nblocks = len / 4; int i; uint32_t h1 = seed; uint32_t c1 = 0xcc9e2d51; uint32_t c2 = 0x1b873593; /* body */ const uint32_t *blocks = (const uint32_t *)(data + nblocks * 4); for (i = -nblocks; i; i++) { uint32_t k1 = getblock(blocks, i); k1 *= c1; k1 = ROTL32(k1, 15); k1 *= c2; h1 ^= k1; h1 = ROTL32(h1, 13); h1 = h1 * 5 + 0xe6546b64; } /* tail */ const uint8_t *tail = (const uint8_t *)(data + nblocks * 4); uint32_t k1 = 0; switch (len & 3) { case 3: k1 ^= tail[2] << 16; case 2: k1 ^= tail[1] << 8; case 1: k1 ^= tail[0]; k1 *= c1; k1 = ROTL32(k1, 15); k1 *= c2; h1 ^= k1; }; /* finalization */ h1 ^= len; h1 = fmix32(h1); *(uint32_t *)out = h1; } /*----------------------------------------------------------------------------*/ void MurmurHash3_x86_128(const void *key, const int len, uint32_t seed, void *out) { const uint8_t *data = (const uint8_t *)key; const int nblocks = len / 16; int i; uint32_t h1 = seed; uint32_t h2 = seed; uint32_t h3 = seed; uint32_t h4 = seed; uint32_t c1 = 0x239b961b; uint32_t c2 = 0xab0e9789; uint32_t c3 = 0x38b34ae5; uint32_t c4 = 0xa1e38b93; /* body */ const uint32_t *blocks = (const uint32_t *)(data + nblocks * 16); for (i = -nblocks; i; i++) { uint32_t k1 = getblock(blocks, i * 4 + 0); uint32_t k2 = getblock(blocks, i * 4 + 1); uint32_t k3 = getblock(blocks, i * 4 + 2); uint32_t k4 = getblock(blocks, i * 4 + 3); k1 *= c1; k1 = ROTL32(k1, 15); k1 *= c2; h1 ^= k1; h1 = ROTL32(h1, 19); h1 += h2; h1 = h1 * 5 + 0x561ccd1b; k2 *= c2; k2 = ROTL32(k2, 16); k2 *= c3; h2 ^= k2; h2 = ROTL32(h2, 17); h2 += h3; h2 = h2 * 5 + 0x0bcaa747; k3 *= c3; k3 = ROTL32(k3, 17); k3 *= c4; h3 ^= k3; h3 = ROTL32(h3, 15); h3 += h4; h3 = h3 * 5 + 0x96cd1c35; k4 *= c4; k4 = ROTL32(k4, 18); k4 *= c1; h4 ^= k4; h4 = ROTL32(h4, 13); h4 += h1; h4 = h4 * 5 + 0x32ac3b17; } /* tail */ const uint8_t *tail = (const uint8_t *)(data + nblocks * 16); uint32_t k1 = 0; uint32_t k2 = 0; uint32_t k3 = 0; uint32_t k4 = 0; switch (len & 15) { case 15: k4 ^= tail[14] << 16; case 14: k4 ^= tail[13] << 8; case 13: k4 ^= tail[12] << 0; k4 *= c4; k4 = ROTL32(k4, 18); k4 *= c1; h4 ^= k4; case 12: k3 ^= tail[11] << 24; case 11: k3 ^= tail[10] << 16; case 10: k3 ^= tail[9] << 8; case 9: k3 ^= tail[8] << 0; k3 *= c3; k3 = ROTL32(k3, 17); k3 *= c4; h3 ^= k3; case 8: k2 ^= tail[7] << 24; case 7: k2 ^= tail[6] << 16; case 6: k2 ^= tail[5] << 8; case 5: k2 ^= tail[4] << 0; k2 *= c2; k2 = ROTL32(k2, 16); k2 *= c3; h2 ^= k2; case 4: k1 ^= tail[3] << 24; case 3: k1 ^= tail[2] << 16; case 2: k1 ^= tail[1] << 8; case 1: k1 ^= tail[0] << 0; k1 *= c1; k1 = ROTL32(k1, 15); k1 *= c2; h1 ^= k1; }; /* finalization */ h1 ^= len; h2 ^= len; h3 ^= len; h4 ^= len; h1 += h2; h1 += h3; h1 += h4; h2 += h1; h3 += h1; h4 += h1; h1 = fmix64(h1); h2 = fmix64(h2); h3 = fmix64(h3); h4 = fmix64(h4); h1 += h2; h1 += h3; h1 += h4; h2 += h1; h3 += h1; h4 += h1; ((uint32_t *)out)[0] = h1; ((uint32_t *)out)[1] = h2; ((uint32_t *)out)[2] = h3; ((uint32_t *)out)[3] = h4; } /*----------------------------------------------------------------------------*/ void MurmurHash3_x64_128(const void *key, const int len, const uint32_t seed, void *out) { const uint8_t *data = (const uint8_t *)key; const int nblocks = len / 16; int i; uint64_t h1 = seed; uint64_t h2 = seed; uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5); uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f); /* body */ const uint64_t *blocks = (const uint64_t *)(data); for (i = 0; i < nblocks; i++) { uint64_t k1 = getblock(blocks, i * 2 + 0); uint64_t k2 = getblock(blocks, i * 2 + 1); k1 *= c1; k1 = ROTL64(k1, 31); k1 *= c2; h1 ^= k1; h1 = ROTL64(h1, 27); h1 += h2; h1 = h1 * 5 + 0x52dce729; k2 *= c2; k2 = ROTL64(k2, 33); k2 *= c1; h2 ^= k2; h2 = ROTL64(h2, 31); h2 += h1; h2 = h2 * 5 + 0x38495ab5; } /* tail */ const uint8_t *tail = (const uint8_t *)(data + nblocks * 16); uint64_t k1 = 0; uint64_t k2 = 0; switch (len & 15) { case 15: k2 ^= (uint64_t)(tail[14]) << 48; case 14: k2 ^= (uint64_t)(tail[13]) << 40; case 13: k2 ^= (uint64_t)(tail[12]) << 32; case 12: k2 ^= (uint64_t)(tail[11]) << 24; case 11: k2 ^= (uint64_t)(tail[10]) << 16; case 10: k2 ^= (uint64_t)(tail[9]) << 8; case 9: k2 ^= (uint64_t)(tail[8]) << 0; k2 *= c2; k2 = ROTL64(k2, 33); k2 *= c1; h2 ^= k2; case 8: k1 ^= (uint64_t)(tail[7]) << 56; case 7: k1 ^= (uint64_t)(tail[6]) << 48; case 6: k1 ^= (uint64_t)(tail[5]) << 40; case 5: k1 ^= (uint64_t)(tail[4]) << 32; case 4: k1 ^= (uint64_t)(tail[3]) << 24; case 3: k1 ^= (uint64_t)(tail[2]) << 16; case 2: k1 ^= (uint64_t)(tail[1]) << 8; case 1: k1 ^= (uint64_t)(tail[0]) << 0; k1 *= c1; k1 = ROTL64(k1, 31); k1 *= c2; h1 ^= k1; }; /* finalization */ h1 ^= len; h2 ^= len; h1 += h2; h2 += h1; h1 = fmix64(h1); h2 = fmix64(h2); h1 += h2; h2 += h1; ((uint64_t *)out)[0] = h1; ((uint64_t *)out)[1] = h2; } /*----------------------------------------------------------------------------*/ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/netgroup_cache.c��������������������������������������������������������0000664�0000000�0000000�00000021254�14737566223�0021236�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * This program 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 3 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 * 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 * * --------------------------------------- */ #include "config.h" #include "log.h" #include "config_parsing.h" #include <string.h> #include <unistd.h> #include "gsh_intrinsic.h" #include "gsh_types.h" #include "common_utils.h" #include "avltree.h" #include "abstract_atomic.h" #include "netdb.h" #include "abstract_mem.h" #include "netgroup_cache.h" /* Netgroup cache information */ struct ng_cache_info { struct avltree_node ng_node; struct gsh_buffdesc ng_group; struct gsh_buffdesc ng_host; time_t ng_epoch; }; #define NG_CACHE_SIZE 1009 /* Uses FNV hash */ #define FNV_PRIME32 16777619 #define FNV_OFFSET32 2166136261U static int ng_hash_key(struct ng_cache_info *info) { uint32_t hash = FNV_OFFSET32; char *bp, *end; bp = info->ng_host.addr; end = bp + info->ng_host.len; while (bp < end) { hash ^= *bp++; hash *= FNV_PRIME32; } bp = info->ng_group.addr; end = bp + info->ng_group.len; while (bp < end) { hash ^= *bp++; hash *= FNV_PRIME32; } return hash % NG_CACHE_SIZE; } static struct avltree_node *ng_cache[NG_CACHE_SIZE]; pthread_rwlock_t ng_lock; /* Positive and negative cache trees */ static struct avltree pos_ng_tree; static struct avltree neg_ng_tree; static inline int buffdesc_comparator(const struct gsh_buffdesc *buff1, const struct gsh_buffdesc *buff2) { if (buff1->len < buff2->len) return -1; if (buff1->len > buff2->len) return 1; return memcmp(buff1->addr, buff2->addr, buff1->len); } static int ng_comparator(const struct avltree_node *node1, const struct avltree_node *node2) { int rc; struct ng_cache_info *info1; struct ng_cache_info *info2; info1 = avltree_container_of(node1, struct ng_cache_info, ng_node); info2 = avltree_container_of(node2, struct ng_cache_info, ng_node); /* compare host followed by group if needed */ rc = buffdesc_comparator(&info1->ng_host, &info2->ng_host); if (rc == 0) rc = buffdesc_comparator(&info1->ng_group, &info2->ng_group); return rc; } static bool ng_expired(struct avltree_node *node) { struct ng_cache_info *info; info = avltree_container_of(node, struct ng_cache_info, ng_node); /* Hardcoded to 30 minutes for now */ if (time(NULL) - info->ng_epoch > 30 * 60) return true; return false; } /* Cleanup on shutdown */ void ng_cache_cleanup(void) { PTHREAD_RWLOCK_destroy(&ng_lock); } struct cleanup_list_element ng_cache_cleanup_element = { .clean = ng_cache_cleanup, }; /** * @brief Initialize the netgroups cache */ void ng_cache_init(void) { PTHREAD_RWLOCK_init(&ng_lock, NULL); avltree_init(&pos_ng_tree, ng_comparator, 0); avltree_init(&neg_ng_tree, ng_comparator, 0); memset(ng_cache, 0, NG_CACHE_SIZE * sizeof(struct avltree_node *)); RegisterCleanup(&ng_cache_cleanup_element); } static void ng_free(struct ng_cache_info *info) { gsh_free(info->ng_group.addr); gsh_free(info->ng_host.addr); gsh_free(info); } /* The caller must hold ng_lock for write */ static void ng_remove(struct ng_cache_info *info, bool negative) { if (negative) { avltree_remove(&info->ng_node, &neg_ng_tree); } else { ng_cache[ng_hash_key(info)] = NULL; avltree_remove(&info->ng_node, &pos_ng_tree); } } /* The caller must hold ng_lock for write */ static void ng_add(const char *group, const char *host, bool negative) { struct ng_cache_info *info; struct avltree_node *found_node; struct ng_cache_info *found_info; info = gsh_malloc(sizeof(struct ng_cache_info)); if (!info) LogFatal(COMPONENT_IDMAPPER, "memory alloc failed"); info->ng_group.addr = gsh_strdup(group); info->ng_group.len = strlen(group) + 1; info->ng_host.addr = gsh_strdup(host); info->ng_host.len = strlen(host) + 1; info->ng_epoch = time(NULL); if (negative) { /* @todo check positive cache first? */ found_node = avltree_insert(&info->ng_node, &neg_ng_tree); /* If an already existing entry is found, keep the old * entry, and free the current entry */ if (found_node) { found_info = avltree_container_of( found_node, struct ng_cache_info, ng_node); found_info->ng_epoch = info->ng_epoch; ng_free(info); } } else { /* @todo delete from negative cache if there? */ found_node = avltree_insert(&info->ng_node, &pos_ng_tree); /* If an already existing entry is found, keep the old * entry, and free the current entry */ if (found_node) { found_info = avltree_container_of( found_node, struct ng_cache_info, ng_node); ng_cache[ng_hash_key(found_info)] = found_node; found_info->ng_epoch = info->ng_epoch; ng_free(info); } else { ng_cache[ng_hash_key(info)] = &info->ng_node; } } } /* The caller must hold ng_lock for read */ static bool ng_lookup(const char *group, const char *host, bool negative) { struct ng_cache_info prototype = { .ng_group.addr = (char *)group, .ng_group.len = strlen(group) + 1, .ng_host.addr = (char *)host, .ng_host.len = strlen(host) + 1 }; struct avltree_node *node; struct ng_cache_info *info; void **cache_slot; if (negative) { node = avltree_lookup(&prototype.ng_node, &neg_ng_tree); if (!node) return false; if (!ng_expired(node)) return true; goto expired; } /* Positive lookups are stored in the cache */ cache_slot = (void **)&ng_cache[ng_hash_key(&prototype)]; node = atomic_fetch_voidptr(cache_slot); if (node && ng_comparator(node, &prototype.ng_node) == 0) { if (!ng_expired(node)) return true; goto expired; } /* cache miss, search AVL tree */ node = avltree_lookup(&prototype.ng_node, &pos_ng_tree); if (!node) return false; if (ng_expired(node)) goto expired; atomic_store_voidptr(cache_slot, node); return true; expired: /* entry expired, acquire write mode lock for removal */ PTHREAD_RWLOCK_unlock(&ng_lock); PTHREAD_RWLOCK_wrlock(&ng_lock); /* Since we dropped the read mode lock and acquired write mode * lock, make sure that the entry is still in the tree. */ if (negative) node = avltree_lookup(&prototype.ng_node, &neg_ng_tree); else node = avltree_lookup(&prototype.ng_node, &pos_ng_tree); if (node) { info = avltree_container_of(node, struct ng_cache_info, ng_node); ng_remove(info, negative); ng_free(info); } PTHREAD_RWLOCK_unlock(&ng_lock); PTHREAD_RWLOCK_rdlock(&ng_lock); return false; } /** * @brief Verify if the given host is in the given netgroup or not */ bool ng_innetgr(const char *group, const char *host) { int rc; /* Check positive lookup and then negative lookup. If absent in * both, then do a real innetgr call and cache the results. */ PTHREAD_RWLOCK_rdlock(&ng_lock); if (ng_lookup(group, host, false)) { /* positive lookup */ PTHREAD_RWLOCK_unlock(&ng_lock); return true; } if (ng_lookup(group, host, true)) { /* negative lookup */ PTHREAD_RWLOCK_unlock(&ng_lock); return false; } PTHREAD_RWLOCK_unlock(&ng_lock); /* Call innetgr() under a lock. It is supposed to be thread safe * but sssd doesn't handle multiple threads calling innetgr() at * the same time resulting in erratic returns. sssd team will * fix this behavior in a future release but we can make it * single threaded as a workaround. Shouldn't be a performance * issue as this shouldn't happen often. */ PTHREAD_RWLOCK_wrlock(&ng_lock); rc = innetgr(group, host, NULL, NULL); if (rc) ng_add(group, host, false); /* positive lookup */ else ng_add(group, host, true); /* negative lookup */ PTHREAD_RWLOCK_unlock(&ng_lock); return rc; } /** * @brief Wipe out the netgroup cache */ void ng_clear_cache(void) { struct avltree_node *node; struct ng_cache_info *info; PTHREAD_RWLOCK_wrlock(&ng_lock); while ((node = avltree_first(&pos_ng_tree))) { info = avltree_container_of(node, struct ng_cache_info, ng_node); ng_remove(info, false); ng_free(info); } while ((node = avltree_first(&neg_ng_tree))) { info = avltree_container_of(node, struct ng_cache_info, ng_node); ng_remove(info, true); ng_free(info); } assert(avltree_first(&pos_ng_tree) == NULL); assert(avltree_first(&neg_ng_tree) == NULL); PTHREAD_RWLOCK_unlock(&ng_lock); } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/nfs4_acls.c�������������������������������������������������������������0000664�0000000�0000000�00000015333�14737566223�0020125�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2012 * Contributor: Frank Filz <ffilz@us.ibm.com> * * -------------------------- * * This program 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 3 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 * 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 * * */ #include "config.h" #include "abstract_mem.h" #include "fsal_types.h" #include "hashtable.h" #include "log.h" #include "nfs4_acls.h" #include "city.h" #include "common_utils.h" pool_t *fsal_acl_pool; static int fsal_acl_hash_both(hash_parameter_t *, struct gsh_buffdesc *, uint32_t *, uint64_t *); static int compare_fsal_acl(struct gsh_buffdesc *, struct gsh_buffdesc *); /* DEFAULT PARAMETERS for hash table */ static hash_parameter_t fsal_acl_hash_config = { .index_size = 67, .hash_func_key = NULL, .hash_func_rbt = NULL, .hash_func_both = fsal_acl_hash_both, .compare_key = compare_fsal_acl, .ht_name = "ACL Table", .flags = HT_FLAG_CACHE, .ht_log_component = COMPONENT_NFS_V4_ACL }; static hash_table_t *fsal_acl_hash; /* hash table functions */ static int fsal_acl_hash_both(hash_parameter_t *hparam, struct gsh_buffdesc *key, uint32_t *index, uint64_t *rbthash) { *rbthash = CityHash64(key->addr, key->len); *index = *rbthash % hparam->index_size; return 1; } static int compare_fsal_acl(struct gsh_buffdesc *key1, struct gsh_buffdesc *keya) { if (key1->len != keya->len) return -1; return memcmp(key1->addr, keya->addr, key1->len); } fsal_ace_t *nfs4_ace_alloc(int nace) { fsal_ace_t *ace = NULL; ace = gsh_calloc(nace, sizeof(fsal_ace_t)); return ace; } fsal_acl_t *nfs4_acl_alloc(void) { fsal_acl_t *acl = pool_alloc(fsal_acl_pool); PTHREAD_RWLOCK_init(&acl->acl_lock, NULL); return acl; } void nfs4_ace_free(fsal_ace_t *ace) { if (!ace) return; LogDebug(COMPONENT_NFS_V4_ACL, "free ace %p", ace); gsh_free(ace); } void nfs4_acl_free(fsal_acl_t *acl) { if (!acl) return; if (acl->aces) nfs4_ace_free(acl->aces); PTHREAD_RWLOCK_destroy(&acl->acl_lock); pool_free(fsal_acl_pool, acl); } void nfs4_acl_entry_inc_ref(fsal_acl_t *acl) { /* Increase ref counter */ PTHREAD_RWLOCK_wrlock(&acl->acl_lock); acl->ref++; LogDebug(COMPONENT_NFS_V4_ACL, "(acl, ref) = (%p, %u)", acl, acl->ref); PTHREAD_RWLOCK_unlock(&acl->acl_lock); } /* Should be called with lock held. */ static void nfs4_acl_entry_dec_ref(fsal_acl_t *acl) { /* Decrease ref counter */ acl->ref--; LogDebug(COMPONENT_NFS_V4_ACL, "(acl, ref) = (%p, %u)", acl, acl->ref); } fsal_acl_t *nfs4_acl_new_entry(fsal_acl_data_t *acldata, fsal_acl_status_t *status) { fsal_acl_t *acl = NULL; struct gsh_buffdesc key; struct gsh_buffdesc value; int rc; struct hash_latch latch; /* Set the return default to NFS_V4_ACL_SUCCESS */ *status = NFS_V4_ACL_SUCCESS; key.addr = acldata->aces; key.len = acldata->naces * sizeof(fsal_ace_t); /* Check if the entry already exists */ rc = hashtable_getlatch(fsal_acl_hash, &key, &value, true, &latch); if (rc == HASHTABLE_SUCCESS) { /* Entry is already in the cache, do not add it */ acl = value.addr; *status = NFS_V4_ACL_EXISTS; nfs4_ace_free(acldata->aces); nfs4_acl_entry_inc_ref(acl); hashtable_releaselatched(fsal_acl_hash, &latch); return acl; } /* Any other result other than no such key is an error */ if (rc != HASHTABLE_ERROR_NO_SUCH_KEY) { *status = NFS_V4_ACL_INIT_ENTRY_FAILED; nfs4_ace_free(acldata->aces); return NULL; } /* Adding the entry in the cache */ acl = nfs4_acl_alloc(); acl->naces = acldata->naces; acl->aces = acldata->aces; acl->ref = 1; /* We give out one reference */ /* Build the value */ value.addr = acl; value.len = sizeof(fsal_acl_t); rc = hashtable_setlatched(fsal_acl_hash, &key, &value, &latch, false, NULL, NULL); if (rc != HASHTABLE_SUCCESS) { /* Put the entry back in its pool */ nfs4_acl_free(acl); LogWarn(COMPONENT_NFS_V4_ACL, "New ACL entry could not be added to hash, rc=%s", hash_table_err_to_str(rc)); *status = NFS_V4_ACL_HASH_SET_ERROR; return NULL; } return acl; } void nfs4_acl_release_entry(fsal_acl_t *acl) { struct gsh_buffdesc key, old_key; struct gsh_buffdesc old_value; int rc; struct hash_latch latch; if (!acl) return; PTHREAD_RWLOCK_wrlock(&acl->acl_lock); if (acl->ref > 1) { nfs4_acl_entry_dec_ref(acl); PTHREAD_RWLOCK_unlock(&acl->acl_lock); return; } else LogDebug(COMPONENT_NFS_V4_ACL, "Free ACL %p", acl); key.addr = acl->aces; key.len = acl->naces * sizeof(fsal_ace_t); PTHREAD_RWLOCK_unlock(&acl->acl_lock); /* Get the hash table entry and hold latch */ rc = hashtable_getlatch(fsal_acl_hash, &key, &old_value, true, &latch); switch (rc) { case HASHTABLE_ERROR_NO_SUCH_KEY: hashtable_releaselatched(fsal_acl_hash, &latch); break; case HASHTABLE_SUCCESS: PTHREAD_RWLOCK_wrlock(&acl->acl_lock); nfs4_acl_entry_dec_ref(acl); if (acl->ref != 0) { /* Did not actually release last reference */ hashtable_releaselatched(fsal_acl_hash, &latch); PTHREAD_RWLOCK_unlock(&acl->acl_lock); return; } /* use the key to delete the entry */ hashtable_deletelatched(fsal_acl_hash, &key, &latch, &old_key, &old_value); /* Release the latch */ hashtable_releaselatched(fsal_acl_hash, &latch); /* Sanity check: old_value.addr is expected to be equal to acl, * and is released later in this function */ assert(old_value.addr == acl); PTHREAD_RWLOCK_unlock(&acl->acl_lock); break; default: LogCrit(COMPONENT_NFS_V4_ACL, "ACL entry could not be deleted, status=%s", hash_table_err_to_str(rc)); break; } /* Release acl */ nfs4_acl_free(acl); } int nfs4_acls_init(void) { LogDebug(COMPONENT_NFS_V4_ACL, "Initialize NFSv4 ACLs"); LogDebug(COMPONENT_NFS_V4_ACL, "sizeof(fsal_ace_t)=%zu, sizeof(fsal_acl_t)=%zu", sizeof(fsal_ace_t), sizeof(fsal_acl_t)); /* Initialize memory pool of ACLs. */ fsal_acl_pool = pool_basic_init("acl_pool", sizeof(fsal_acl_t)); /* Create hash table. */ fsal_acl_hash = hashtable_init(&fsal_acl_hash_config); if (!fsal_acl_hash) { LogCrit(COMPONENT_NFS_V4_ACL, "ERROR creating hash table for NFSv4 ACLs"); return NFS_V4_ACL_INTERNAL_ERROR; } return NFS_V4_ACL_SUCCESS; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/nfs4_fs_locations.c�����������������������������������������������������0000664�0000000�0000000�00000006341�14737566223�0021665�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright 2018 VMware, Inc. * Contributor: Sriram Patil <sriramp@vmware.com> * * -------------------------- * * This program 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 3 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 * 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 * * */ #include <errno.h> #include <string.h> #include "nfs4_fs_locations.h" #include "fsal_types.h" #include "common_utils.h" static fsal_fs_locations_t *nfs4_fs_locations_alloc(const unsigned int count) { fsal_fs_locations_t *fs_locations; fs_locations = gsh_calloc(1, sizeof(fsal_fs_locations_t)); if (count) fs_locations->server = gsh_calloc(count, sizeof(utf8string)); PTHREAD_RWLOCK_init(&(fs_locations->fsloc_lock), NULL); return fs_locations; } void nfs4_fs_locations_free(fsal_fs_locations_t *fs_locations) { unsigned int i; if (!fs_locations) return; gsh_free(fs_locations->fs_root); gsh_free(fs_locations->rootpath); for (i = 0; i < fs_locations->nservers; ++i) gsh_free(fs_locations->server[i].utf8string_val); PTHREAD_RWLOCK_destroy(&(fs_locations->fsloc_lock)); gsh_free(fs_locations->server); gsh_free(fs_locations); } void nfs4_fs_locations_get_ref(fsal_fs_locations_t *fs_locations) { PTHREAD_RWLOCK_wrlock(&fs_locations->fsloc_lock); fs_locations->ref++; LogFullDebug(COMPONENT_NFS_V4, "(fs_locations, ref) = (%p, %u)", fs_locations, fs_locations->ref); PTHREAD_RWLOCK_unlock(&fs_locations->fsloc_lock); } /* Must be called with lock held */ static void nfs4_fs_locations_put_ref(fsal_fs_locations_t *fs_locations) { fs_locations->ref--; LogFullDebug(COMPONENT_NFS_V4, "(fs_locations, ref) = (%p, %u)", fs_locations, fs_locations->ref); } void nfs4_fs_locations_release(fsal_fs_locations_t *fs_locations) { if (fs_locations == NULL) return; PTHREAD_RWLOCK_wrlock(&fs_locations->fsloc_lock); if (fs_locations->ref > 1) { nfs4_fs_locations_put_ref(fs_locations); PTHREAD_RWLOCK_unlock(&fs_locations->fsloc_lock); return; } else { LogFullDebug(COMPONENT_NFS_V4, "Free fs_locations: %p", fs_locations); } PTHREAD_RWLOCK_unlock(&fs_locations->fsloc_lock); // Releasing fs_locations nfs4_fs_locations_free(fs_locations); } fsal_fs_locations_t *nfs4_fs_locations_new(const char *fs_root, const char *rootpath, const unsigned int count) { fsal_fs_locations_t *fs_locations; fs_locations = nfs4_fs_locations_alloc(count); if (fs_locations == NULL) { LogCrit(COMPONENT_NFS_V4, "Could not allocate fs_locations"); return NULL; } fs_locations->fs_root = gsh_strdup(fs_root); fs_locations->rootpath = gsh_strdup(rootpath); fs_locations->ref = 1; return fs_locations; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/nfs_convert.c�����������������������������������������������������������0000664�0000000�0000000�00000060000�14737566223�0020566�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_convert.c * @brief NFS conversion tools. */ #include <string.h> #include "nfs_convert.h" #include "nfs23.h" #include "nfs4.h" #include "mount.h" struct op_name { char *name; }; #ifdef _USE_NFS3 /* NFSv3 op_names *must* start with NFSPROC3_ */ static const struct op_name op_names_v3[] = { [NFSPROC3_NULL] = {.name = "NFSPROC3_NULL", }, [NFSPROC3_GETATTR] = {.name = "NFSPROC3_GETATTR", }, [NFSPROC3_SETATTR] = {.name = "NFSPROC3_SETATTR", }, [NFSPROC3_LOOKUP] = {.name = "NFSPROC3_LOOKUP", }, [NFSPROC3_ACCESS] = {.name = "NFSPROC3_ACCESS", }, [NFSPROC3_READLINK] = {.name = "NFSPROC3_READLINK", }, [NFSPROC3_READ] = {.name = "NFSPROC3_READ", }, [NFSPROC3_WRITE] = {.name = "NFSPROC3_WRITE", }, [NFSPROC3_CREATE] = {.name = "NFSPROC3_CREATE", }, [NFSPROC3_MKDIR] = {.name = "NFSPROC3_MKDIR", }, [NFSPROC3_SYMLINK] = {.name = "NFSPROC3_SYMLINK", }, [NFSPROC3_MKNOD] = {.name = "NFSPROC3_MKNOD", }, [NFSPROC3_REMOVE] = {.name = "NFSPROC3_REMOVE", }, [NFSPROC3_RMDIR] = {.name = "NFSPROC3_RMDIR", }, [NFSPROC3_RENAME] = {.name = "NFSPROC3_RENAME", }, [NFSPROC3_LINK] = {.name = "NFSPROC3_LINK", }, [NFSPROC3_READDIR] = {.name = "NFSPROC3_READDIR", }, [NFSPROC3_READDIRPLUS] = {.name = "NFSPROC3_READDIRPLUS", }, [NFSPROC3_FSSTAT] = {.name = "NFSPROC3_FSSTAT", }, [NFSPROC3_FSINFO] = {.name = "NFSPROC3_FSINFO", }, [NFSPROC3_PATHCONF] = {.name = "NFSPROC3_PATHCONF", }, [NFSPROC3_COMMIT] = {.name = "NFSPROC3_COMMIT", }, }; static const int MAX_NFS3_PROC = NFSPROC3_COMMIT; const char *nfsproc3_to_str(int nfsproc3) { /* Add 9 to remove "NFSPROC3_" prefix. */ if (nfsproc3 < 0 || nfsproc3 > MAX_NFS3_PROC) return op_names_v3[0].name + 9; return op_names_v3[nfsproc3].name + 9; } #endif /* NFSv4 op_names *must* start with OP_ */ static const struct op_name op_names_v4[] = { [0] = {.name = "OP_ILLEGAL", }, [1] = {.name = "OP_ILLEGAL",}, [2] = {.name = "OP_ILLEGAL",}, [NFS4_OP_ACCESS] = {.name = "OP_ACCESS",}, [NFS4_OP_CLOSE] = {.name = "OP_CLOSE",}, [NFS4_OP_COMMIT] = {.name = "OP_COMMIT",}, [NFS4_OP_CREATE] = {.name = "OP_CREATE",}, [NFS4_OP_DELEGPURGE] = {.name = "OP_DELEGPURGE",}, [NFS4_OP_DELEGRETURN] = {.name = "OP_DELEGRETURN",}, [NFS4_OP_GETATTR] = {.name = "OP_GETATTR",}, [NFS4_OP_GETFH] = {.name = "OP_GETFH",}, [NFS4_OP_LINK] = {.name = "OP_LINK",}, [NFS4_OP_LOCK] = {.name = "OP_LOCK",}, [NFS4_OP_LOCKT] = {.name = "OP_LOCKT",}, [NFS4_OP_LOCKU] = {.name = "OP_LOCKU",}, [NFS4_OP_LOOKUP] = {.name = "OP_LOOKUP",}, [NFS4_OP_LOOKUPP] = {.name = "OP_LOOKUPP",}, [NFS4_OP_NVERIFY] = {.name = "OP_NVERIFY",}, [NFS4_OP_OPEN] = {.name = "OP_OPEN",}, [NFS4_OP_OPENATTR] = {.name = "OP_OPENATTR",}, [NFS4_OP_OPEN_CONFIRM] = {.name = "OP_OPEN_CONFIRM",}, [NFS4_OP_OPEN_DOWNGRADE] = {.name = "OP_OPEN_DOWNGRADE",}, [NFS4_OP_PUTFH] = {.name = "OP_PUTFH",}, [NFS4_OP_PUTPUBFH] = {.name = "OP_PUTPUBFH",}, [NFS4_OP_PUTROOTFH] = {.name = "OP_PUTROOTFH",}, [NFS4_OP_READ] = {.name = "OP_READ",}, [NFS4_OP_READDIR] = {.name = "OP_READDIR",}, [NFS4_OP_READLINK] = {.name = "OP_READLINK",}, [NFS4_OP_REMOVE] = {.name = "OP_REMOVE",}, [NFS4_OP_RENAME] = {.name = "OP_RENAME",}, [NFS4_OP_RENEW] = {.name = "OP_RENEW",}, [NFS4_OP_RESTOREFH] = {.name = "OP_RESTOREFH",}, [NFS4_OP_SAVEFH] = {.name = "OP_SAVEFH",}, [NFS4_OP_SECINFO] = {.name = "OP_SECINFO",}, [NFS4_OP_SETATTR] = {.name = "OP_SETATTR",}, [NFS4_OP_SETCLIENTID] = {.name = "OP_SETCLIENTID",}, [NFS4_OP_SETCLIENTID_CONFIRM] = {.name = "OP_SETCLIENTID_CONFIRM",}, [NFS4_OP_VERIFY] = {.name = "OP_VERIFY",}, [NFS4_OP_WRITE] = {.name = "OP_WRITE",}, [NFS4_OP_RELEASE_LOCKOWNER] = {.name = "OP_RELEASE_LOCKOWNER",}, [NFS4_OP_BACKCHANNEL_CTL] = {.name = "OP_BACKCHANNEL_CTL",}, [NFS4_OP_BIND_CONN_TO_SESSION] = {.name = "OP_BIND_CONN_TO_SESSION",}, [NFS4_OP_EXCHANGE_ID] = {.name = "OP_EXCHANGE_ID",}, [NFS4_OP_CREATE_SESSION] = {.name = "OP_CREATE_SESSION",}, [NFS4_OP_DESTROY_SESSION] = {.name = "OP_DESTROY_SESSION",}, [NFS4_OP_FREE_STATEID] = {.name = "OP_FREE_STATEID",}, [NFS4_OP_GET_DIR_DELEGATION] = {.name = "OP_GET_DIR_DELEGATION",}, [NFS4_OP_GETDEVICEINFO] = {.name = "OP_GETDEVICEINFO",}, [NFS4_OP_GETDEVICELIST] = {.name = "OP_GETDEVICELIST",}, [NFS4_OP_LAYOUTCOMMIT] = {.name = "OP_LAYOUTCOMMIT",}, [NFS4_OP_LAYOUTGET] = {.name = "OP_LAYOUTGET",}, [NFS4_OP_LAYOUTRETURN] = {.name = "OP_LAYOUTRETURN",}, [NFS4_OP_SECINFO_NO_NAME] = {.name = "OP_SECINFO_NO_NAME",}, [NFS4_OP_SEQUENCE] = {.name = "OP_SEQUENCE",}, [NFS4_OP_SET_SSV] = {.name = "OP_SET_SSV",}, [NFS4_OP_TEST_STATEID] = {.name = "OP_TEST_STATEID",}, [NFS4_OP_WANT_DELEGATION] = {.name = "OP_WANT_DELEGATION",}, [NFS4_OP_DESTROY_CLIENTID] = {.name = "OP_DESTROY_CLIENTID",}, [NFS4_OP_RECLAIM_COMPLETE] = {.name = "OP_RECLAIM_COMPLETE",}, /* NFSv4.2 */ [NFS4_OP_ALLOCATE] = {.name = "OP_ALLOCATE",}, [NFS4_OP_COPY] = {.name = "OP_COPY",}, [NFS4_OP_COPY_NOTIFY] = {.name = "OP_COPY_NOTIFY",}, [NFS4_OP_DEALLOCATE] = {.name = "OP_DEALLOCATE",}, [NFS4_OP_IO_ADVISE] = {.name = "OP_IO_ADVISE",}, [NFS4_OP_LAYOUTERROR] = {.name = "OP_LAYOUTERROR",}, [NFS4_OP_LAYOUTSTATS] = {.name = "OP_LAYOUTSTATS",}, [NFS4_OP_OFFLOAD_CANCEL] = {.name = "OP_OFFLOAD_CANCEL",}, [NFS4_OP_OFFLOAD_STATUS] = {.name = "OP_OFFLOAD_STATUS",}, [NFS4_OP_READ_PLUS] = {.name = "OP_READ_PLUS",}, [NFS4_OP_SEEK] = {.name = "OP_SEEK",}, [NFS4_OP_WRITE_SAME] = {.name = "OP_WRITE_SAME",}, [NFS4_OP_CLONE] = {.name = "OP_CLONE",}, /* NFSv4.3 */ [NFS4_OP_GETXATTR] = {.name = "OP_GETXATTR",}, [NFS4_OP_SETXATTR] = {.name = "OP_SETXATTR",}, [NFS4_OP_LISTXATTR] = {.name = "OP_LISTXATTR",}, [NFS4_OP_REMOVEXATTR] = {.name = "OP_REMOVEXATTR",}, }; const char *nfsop4_to_str(int nfsop4) { /* Add 3 to remove "OP_" prefix. */ if (nfsop4 < 0 || nfsop4 >= NFS4_OP_LAST_ONE) return op_names_v4[0].name + 3; return op_names_v4[nfsop4].name + 3; } char *nfsstat3_to_str(nfsstat3 code) { switch (code) { /* no need for break statements, * because we "return". */ case NFS3_OK: return "NFS3_OK"; case NFS3ERR_PERM: return "NFS3ERR_PERM"; case NFS3ERR_NOENT: return "NFS3ERR_NOENT"; case NFS3ERR_IO: return "NFS3ERR_IO"; case NFS3ERR_NXIO: return "NFS3ERR_NXIO"; case NFS3ERR_ACCES: return "NFS3ERR_ACCES"; case NFS3ERR_EXIST: return "NFS3ERR_EXIST"; case NFS3ERR_XDEV: return "NFS3ERR_XDEV"; case NFS3ERR_NODEV: return "NFS3ERR_NODEV"; case NFS3ERR_NOTDIR: return "NFS3ERR_NOTDIR"; case NFS3ERR_ISDIR: return "NFS3ERR_ISDIR"; case NFS3ERR_INVAL: return "NFS3ERR_INVAL"; case NFS3ERR_FBIG: return "NFS3ERR_FBIG"; case NFS3ERR_NOSPC: return "NFS3ERR_NOSPC"; case NFS3ERR_ROFS: return "NFS3ERR_ROFS"; case NFS3ERR_MLINK: return "NFS3ERR_MLINK"; case NFS3ERR_NAMETOOLONG: return "NFS3ERR_NAMETOOLONG"; case NFS3ERR_NOTEMPTY: return "NFS3ERR_NOTEMPTY"; case NFS3ERR_DQUOT: return "NFS3ERR_DQUOT"; case NFS3ERR_STALE: return "NFS3ERR_STALE"; case NFS3ERR_REMOTE: return "NFS3ERR_REMOTE"; case NFS3ERR_BADHANDLE: return "NFS3ERR_BADHANDLE"; case NFS3ERR_NOT_SYNC: return "NFS3ERR_NOT_SYNC"; case NFS3ERR_BAD_COOKIE: return "NFS3ERR_BAD_COOKIE"; case NFS3ERR_NOTSUPP: return "NFS3ERR_NOTSUPP"; case NFS3ERR_TOOSMALL: return "NFS3ERR_TOOSMALL"; case NFS3ERR_SERVERFAULT: return "NFS3ERR_SERVERFAULT"; case NFS3ERR_BADTYPE: return "NFS3ERR_BADTYPE"; case NFS3ERR_JUKEBOX: return "NFS3ERR_JUKEBOX"; } return "UNKNOWN NFSv3 ERROR CODE"; } char *nfsstat4_to_str(nfsstat4 code) { switch (code) { case NFS4_OK: return "NFS4_OK"; case NFS4ERR_PERM: return "NFS4ERR_PERM"; case NFS4ERR_NOENT: return "NFS4ERR_NOENT"; case NFS4ERR_IO: return "NFS4ERR_IO"; case NFS4ERR_NXIO: return "NFS4ERR_NXIO"; case NFS4ERR_ACCESS: return "NFS4ERR_ACCESS"; case NFS4ERR_EXIST: return "NFS4ERR_EXIST"; case NFS4ERR_XDEV: return "NFS4ERR_XDEV"; case NFS4ERR_NOTDIR: return "NFS4ERR_NOTDIR"; case NFS4ERR_ISDIR: return "NFS4ERR_ISDIR"; case NFS4ERR_INVAL: return "NFS4ERR_INVAL"; case NFS4ERR_FBIG: return "NFS4ERR_FBIG"; case NFS4ERR_NOSPC: return "NFS4ERR_NOSPC"; case NFS4ERR_ROFS: return "NFS4ERR_ROFS"; case NFS4ERR_MLINK: return "NFS4ERR_MLINK"; case NFS4ERR_NAMETOOLONG: return "NFS4ERR_NAMETOOLONG"; case NFS4ERR_NOTEMPTY: return "NFS4ERR_NOTEMPTY"; case NFS4ERR_DQUOT: return "NFS4ERR_DQUOT"; case NFS4ERR_STALE: return "NFS4ERR_STALE"; case NFS4ERR_BADHANDLE: return "NFS4ERR_BADHANDLE"; case NFS4ERR_BAD_COOKIE: return "NFS4ERR_BAD_COOKIE"; case NFS4ERR_NOTSUPP: return "NFS4ERR_NOTSUPP"; case NFS4ERR_TOOSMALL: return "NFS4ERR_TOOSMALL"; case NFS4ERR_SERVERFAULT: return "NFS4ERR_SERVERFAULT"; case NFS4ERR_BADTYPE: return "NFS4ERR_BADTYPE"; case NFS4ERR_DELAY: return "NFS4ERR_DELAY"; case NFS4ERR_SAME: return "NFS4ERR_SAME"; case NFS4ERR_DENIED: return "NFS4ERR_DENIED"; case NFS4ERR_EXPIRED: return "NFS4ERR_EXPIRED"; case NFS4ERR_LOCKED: return "NFS4ERR_LOCKED"; case NFS4ERR_GRACE: return "NFS4ERR_GRACE"; case NFS4ERR_FHEXPIRED: return "NFS4ERR_FHEXPIRED"; case NFS4ERR_SHARE_DENIED: return "NFS4ERR_SHARE_DENIED"; case NFS4ERR_WRONGSEC: return "NFS4ERR_WRONGSEC"; case NFS4ERR_CLID_INUSE: return "NFS4ERR_CLID_INUSE"; case NFS4ERR_RESOURCE: return "NFS4ERR_RESOURCE"; case NFS4ERR_MOVED: return "NFS4ERR_MOVED"; case NFS4ERR_NOFILEHANDLE: return "NFS4ERR_NOFILEHANDLE"; case NFS4ERR_MINOR_VERS_MISMATCH: return "NFS4ERR_MINOR_VERS_MISMATCH"; case NFS4ERR_STALE_CLIENTID: return "NFS4ERR_STALE_CLIENTID"; case NFS4ERR_STALE_STATEID: return "NFS4ERR_STALE_STATEID"; case NFS4ERR_OLD_STATEID: return "NFS4ERR_OLD_STATEID"; case NFS4ERR_BAD_STATEID: return "NFS4ERR_BAD_STATEID"; case NFS4ERR_BAD_SEQID: return "NFS4ERR_BAD_SEQID"; case NFS4ERR_NOT_SAME: return "NFS4ERR_NOT_SAME"; case NFS4ERR_LOCK_RANGE: return "NFS4ERR_LOCK_RANGE"; case NFS4ERR_SYMLINK: return "NFS4ERR_SYMLINK"; case NFS4ERR_RESTOREFH: return "NFS4ERR_RESTOREFH"; case NFS4ERR_LEASE_MOVED: return "NFS4ERR_LEASE_MOVED"; case NFS4ERR_ATTRNOTSUPP: return "NFS4ERR_ATTRNOTSUPP"; case NFS4ERR_NO_GRACE: return "NFS4ERR_NO_GRACE"; case NFS4ERR_RECLAIM_BAD: return "NFS4ERR_RECLAIM_BAD"; case NFS4ERR_RECLAIM_CONFLICT: return "NFS4ERR_RECLAIM_CONFLICT"; case NFS4ERR_BADXDR: return "NFS4ERR_BADXDR"; case NFS4ERR_LOCKS_HELD: return "NFS4ERR_LOCKS_HELD"; case NFS4ERR_OPENMODE: return "NFS4ERR_OPENMODE"; case NFS4ERR_BADOWNER: return "NFS4ERR_BADOWNER"; case NFS4ERR_BADCHAR: return "NFS4ERR_BADCHAR"; case NFS4ERR_BADNAME: return "NFS4ERR_BADNAME"; case NFS4ERR_BAD_RANGE: return "NFS4ERR_BAD_RANGE"; case NFS4ERR_LOCK_NOTSUPP: return "NFS4ERR_LOCK_NOTSUPP"; case NFS4ERR_OP_ILLEGAL: return "NFS4ERR_OP_ILLEGAL"; case NFS4ERR_DEADLOCK: return "NFS4ERR_DEADLOCK"; case NFS4ERR_FILE_OPEN: return "NFS4ERR_FILE_OPEN"; case NFS4ERR_ADMIN_REVOKED: return "NFS4ERR_ADMIN_REVOKED"; case NFS4ERR_CB_PATH_DOWN: return "NFS4ERR_CB_PATH_DOWN"; case NFS4ERR_BADIOMODE: return "NFS4ERR_BADIOMODE"; case NFS4ERR_BADLAYOUT: return "NFS4ERR_BADLAYOUT"; case NFS4ERR_BAD_SESSION_DIGEST: return "NFS4ERR_BAD_SESSION_DIGEST"; case NFS4ERR_BADSESSION: return "NFS4ERR_BADSESSION"; case NFS4ERR_BADSLOT: return "NFS4ERR_BADSLOT"; case NFS4ERR_COMPLETE_ALREADY: return "NFS4ERR_COMPLETE_ALREADY"; case NFS4ERR_CONN_NOT_BOUND_TO_SESSION: return "NFS4ERR_CONN_NOT_BOUND_TO_SESSION"; case NFS4ERR_DELEG_ALREADY_WANTED: return "NFS4ERR_DELEG_ALREADY_WANTED"; case NFS4ERR_BACK_CHAN_BUSY: return "NFS4ERR_BACK_CHAN_BUSY"; case NFS4ERR_LAYOUTTRYLATER: return "NFS4ERR_LAYOUTTRYLATER"; case NFS4ERR_LAYOUTUNAVAILABLE: return "NFS4ERR_LAYOUTUNAVAILABLE"; case NFS4ERR_NOMATCHING_LAYOUT: return "NFS4ERR_NOMATCHING_LAYOUT"; case NFS4ERR_RECALLCONFLICT: return "NFS4ERR_RECALLCONFLICT"; case NFS4ERR_UNKNOWN_LAYOUTTYPE: return "NFS4ERR_UNKNOWN_LAYOUTTYPE"; case NFS4ERR_SEQ_MISORDERED: return "NFS4ERR_SEQ_MISORDERED"; case NFS4ERR_SEQUENCE_POS: return "NFS4ERR_SEQUENCE_POS"; case NFS4ERR_REQ_TOO_BIG: return "NFS4ERR_REQ_TOO_BIG"; case NFS4ERR_REP_TOO_BIG: return "NFS4ERR_REP_TOO_BIG"; case NFS4ERR_REP_TOO_BIG_TO_CACHE: return "NFS4ERR_REP_TOO_BIG_TO_CACHE"; case NFS4ERR_RETRY_UNCACHED_REP: return "NFS4ERR_RETRY_UNCACHED_REP"; case NFS4ERR_UNSAFE_COMPOUND: return "NFS4ERR_UNSAFE_COMPOUND"; case NFS4ERR_TOO_MANY_OPS: return "NFS4ERR_TOO_MANY_OPS"; case NFS4ERR_OP_NOT_IN_SESSION: return "NFS4ERR_OP_NOT_IN_SESSION"; case NFS4ERR_HASH_ALG_UNSUPP: return "NFS4ERR_HASH_ALG_UNSUPP"; case NFS4ERR_CLIENTID_BUSY: return "NFS4ERR_CLIENTID_BUSY"; case NFS4ERR_PNFS_IO_HOLE: return "NFS4ERR_PNFS_IO_HOLE"; case NFS4ERR_SEQ_FALSE_RETRY: return "NFS4ERR_SEQ_FALSE_RETRY"; case NFS4ERR_BAD_HIGH_SLOT: return "NFS4ERR_BAD_HIGH_SLOT"; case NFS4ERR_DEADSESSION: return "NFS4ERR_DEADSESSION"; case NFS4ERR_ENCR_ALG_UNSUPP: return "NFS4ERR_ENCR_ALG_UNSUPP"; case NFS4ERR_PNFS_NO_LAYOUT: return "NFS4ERR_PNFS_NO_LAYOUT"; case NFS4ERR_NOT_ONLY_OP: return "NFS4ERR_NOT_ONLY_OP"; case NFS4ERR_WRONG_CRED: return "NFS4ERR_WRONG_CRED"; case NFS4ERR_WRONG_TYPE: return "NFS4ERR_WRONG_TYPE"; case NFS4ERR_DIRDELEG_UNAVAIL: return "NFS4ERR_DIRDELEG_UNAVAIL"; case NFS4ERR_REJECT_DELEG: return "NFS4ERR_REJECT_DELEG"; case NFS4ERR_RETURNCONFLICT: return "NFS4ERR_RETURNCONFLICT"; case NFS4ERR_DELEG_REVOKED: return "NFS4ERR_DELEG_REVOKED"; /* NFSv4.2 */ case NFS4ERR_PARTNER_NOTSUPP: return "NFS4ERR_PARTNER_NOTSUPP"; case NFS4ERR_PARTNER_NO_AUTH: return "NFS4ERR_PARTNER_NO_AUTH"; case NFS4ERR_OFFLOAD_DENIED: return "NFS4ERR_OFFLOAD_DENIED"; case NFS4ERR_WRONG_LFS: return "NFS4ERR_WRONG_LFS"; case NFS4ERR_BADLABEL: return "NFS4ERR_BADLABEL"; case NFS4ERR_OFFLOAD_NO_REQS: return "NFS4ERR_OFFLOAD_NO_REQS"; case NFS4ERR_UNION_NOTSUPP: return "NFS4ERR_UNION_NOTSUPP"; case NFS4ERR_REPLAY: return "NFS4ERR_REPLAY"; /* NFSv4.3 */ case NFS4ERR_NOXATTR: return "NFS4ERR_NOXATTR"; case NFS4ERR_XATTR2BIG: return "NFS4ERR_XATTR2BIG"; } return "UNKNOWN NFSv4 ERROR CODE"; } char *nfstype3_to_str(ftype3 code) { switch (code) { /* no need for break statements, * because we "return". */ case NF3REG: return "NF3REG"; case NF3DIR: return "NF3DIR"; case NF3BLK: return "NF3BLK"; case NF3CHR: return "NF3CHR"; case NF3LNK: return "NF3LNK"; case NF3SOCK: return "NF3SOCK"; case NF3FIFO: return "NF3FIFO"; } return "UNKNOWN NFSv3 TYPE"; } /** * @brief Same as htonl, but on 64 bits. * * @param[in] arg64 Value in host byte order * * @return Value in network byte order */ uint64_t nfs_htonl64(uint64_t arg64) { uint64_t res64; #ifdef LITTLEEND uint32_t low = (uint32_t)(arg64 & 0x00000000FFFFFFFFLL); uint32_t high = (uint32_t)((arg64 & 0xFFFFFFFF00000000LL) >> 32); low = htonl(low); high = htonl(high); res64 = (uint64_t)high + (((uint64_t)low) << 32); #else res64 = arg64; #endif return res64; } /** * @brief Same as ntohl, but on 64 bits. * * @param[in] arg64 Value in network byte order * * @return value in host byte order. */ uint64_t nfs_ntohl64(uint64_t arg64) { uint64_t res64; #ifdef LITTLEEND uint32_t low = (uint32_t)(arg64 & 0x00000000FFFFFFFFLL); uint32_t high = (uint32_t)((arg64 & 0xFFFFFFFF00000000LL) >> 32); low = ntohl(low); high = ntohl(high); res64 = (uint64_t)high + (((uint64_t)low) << 32); #else res64 = arg64; #endif return res64; } /** * @brief Converts an auth_stat enum to a string * * @param[in] why The stat to convert * * @return String describing the status */ const char *auth_stat2str(enum auth_stat why) { switch (why) { case AUTH_OK: return "AUTH_OK"; case AUTH_BADCRED: return "AUTH_BADCRED"; case AUTH_REJECTEDCRED: return "AUTH_REJECTEDCRED"; case AUTH_BADVERF: return "AUTH_BADVERF"; case AUTH_REJECTEDVERF: return "AUTH_REJECTEDVERF"; case AUTH_TOOWEAK: return "AUTH_TOOWEAK"; case AUTH_INVALIDRESP: return "AUTH_INVALIDRESP"; case AUTH_FAILED: return "AUTH_FAILED"; case RPCSEC_GSS_CREDPROBLEM: return "RPCSEC_GSS_CREDPROBLEM"; case RPCSEC_GSS_CTXPROBLEM: return "RPCSEC_GSS_CTXPROBLEM"; } return "UNKNOWN AUTH"; } /* Error conversion routines */ /** * @brief Convert a FSAL status.major error to a nfsv4 status. * * @param[in] status The FSAL status * @param[in] where String identifying the caller * * @return the converted NFSv4 status. * */ nfsstat4 nfs4_Errno_verbose(fsal_status_t status, const char *where) { nfsstat4 nfserror = NFS4ERR_INVAL; switch (status.major) { case ERR_FSAL_NO_ERROR: nfserror = NFS4_OK; break; case ERR_FSAL_NOMEM: nfserror = NFS4ERR_SERVERFAULT; break; case ERR_FSAL_SYMLINK: nfserror = NFS4ERR_SYMLINK; break; case ERR_FSAL_BADTYPE: case ERR_FSAL_INVAL: case ERR_FSAL_OVERFLOW: nfserror = NFS4ERR_INVAL; break; case ERR_FSAL_NOTDIR: nfserror = NFS4ERR_NOTDIR; break; case ERR_FSAL_EXIST: nfserror = NFS4ERR_EXIST; break; case ERR_FSAL_NOTEMPTY: nfserror = NFS4ERR_NOTEMPTY; break; case ERR_FSAL_NOENT: nfserror = NFS4ERR_NOENT; break; case ERR_FSAL_NOT_OPENED: case ERR_FSAL_BLOCKED: case ERR_FSAL_INTERRUPT: case ERR_FSAL_NOT_INIT: case ERR_FSAL_ALREADY_INIT: case ERR_FSAL_BAD_INIT: case ERR_FSAL_TIMEOUT: case ERR_FSAL_IO: if (status.major == ERR_FSAL_IO && status.minor != 0) LogCrit(COMPONENT_NFS_V4, "Error %s with error code %d in %s converted " "to NFS4ERR_IO but was set non-retryable", msg_fsal_err(status.major), status.minor, where); else LogCrit(COMPONENT_NFS_V4, "Error %s in %s converted to NFS4ERR_IO but " "was set non-retryable", msg_fsal_err(status.major), where); nfserror = NFS4ERR_IO; break; case ERR_FSAL_NXIO: nfserror = NFS4ERR_NXIO; break; case ERR_FSAL_ACCESS: nfserror = NFS4ERR_ACCESS; break; case ERR_FSAL_PERM: case ERR_FSAL_SEC: nfserror = NFS4ERR_PERM; break; case ERR_FSAL_NOSPC: nfserror = NFS4ERR_NOSPC; break; case ERR_FSAL_ISDIR: nfserror = NFS4ERR_ISDIR; break; case ERR_FSAL_ROFS: nfserror = NFS4ERR_ROFS; break; case ERR_FSAL_NAMETOOLONG: nfserror = NFS4ERR_NAMETOOLONG; break; case ERR_FSAL_STALE: case ERR_FSAL_FHEXPIRED: nfserror = NFS4ERR_STALE; break; case ERR_FSAL_DQUOT: case ERR_FSAL_NO_QUOTA: nfserror = NFS4ERR_DQUOT; break; case ERR_FSAL_NOTSUPP: nfserror = NFS4ERR_NOTSUPP; break; case ERR_FSAL_ATTRNOTSUPP: nfserror = NFS4ERR_ATTRNOTSUPP; break; case ERR_FSAL_UNION_NOTSUPP: nfserror = NFS4ERR_UNION_NOTSUPP; break; case ERR_FSAL_DELAY: nfserror = NFS4ERR_DELAY; break; case ERR_FSAL_FBIG: nfserror = NFS4ERR_FBIG; break; case ERR_FSAL_FILE_OPEN: nfserror = NFS4ERR_FILE_OPEN; break; case ERR_FSAL_BADCOOKIE: nfserror = NFS4ERR_BAD_COOKIE; break; case ERR_FSAL_TOOSMALL: nfserror = NFS4ERR_TOOSMALL; break; case ERR_FSAL_NO_DATA: case ERR_FSAL_FAULT: case ERR_FSAL_SERVERFAULT: nfserror = NFS4ERR_SERVERFAULT; break; case ERR_FSAL_DEADLOCK: nfserror = NFS4ERR_DEADLOCK; break; case ERR_FSAL_XDEV: nfserror = NFS4ERR_XDEV; break; case ERR_FSAL_BADHANDLE: nfserror = NFS4ERR_BADHANDLE; break; case ERR_FSAL_MLINK: nfserror = NFS4ERR_MLINK; break; case ERR_FSAL_SHARE_DENIED: nfserror = NFS4ERR_SHARE_DENIED; break; case ERR_FSAL_LOCKED: nfserror = NFS4ERR_LOCKED; break; case ERR_FSAL_IN_GRACE: nfserror = NFS4ERR_GRACE; break; case ERR_FSAL_BAD_RANGE: nfserror = NFS4ERR_BAD_RANGE; break; case ERR_FSAL_BADNAME: nfserror = NFS4ERR_BADNAME; break; case ERR_FSAL_NOXATTR: nfserror = NFS4ERR_NOXATTR; break; case ERR_FSAL_XATTR2BIG: nfserror = NFS4ERR_XATTR2BIG; break; case ERR_FSAL_CROSS_JUNCTION: case ERR_FSAL_NO_ACE: case ERR_FSAL_STILL_IN_USE: /* Should not occur */ LogDebug( COMPONENT_NFS_V4, "Line %u should never be reached in nfs4_Errno from %s for cache_status=%u", __LINE__, where, status.major); nfserror = NFS4ERR_INVAL; break; } return nfserror; } /** * * @brief Convert a FSAL status.major error to a nfsv3 status. * * @param[in] status Input FSAL status * @param[in] where String identifying caller * * @return the converted NFSv3 status. * */ #ifdef _USE_NFS3 nfsstat3 nfs3_Errno_verbose(fsal_status_t status, const char *where) { nfsstat3 nfserror = NFS3ERR_INVAL; switch (status.major) { case ERR_FSAL_NO_ERROR: nfserror = NFS3_OK; break; case ERR_FSAL_NOMEM: case ERR_FSAL_FILE_OPEN: case ERR_FSAL_NOT_OPENED: case ERR_FSAL_IO: if (status.major == ERR_FSAL_IO && status.minor != 0) LogCrit(COMPONENT_NFSPROTO, "Error %s with error code %d in %s converted " "to NFS3ERR_IO but was set non-retryable", msg_fsal_err(status.major), status.minor, where); else LogCrit(COMPONENT_NFSPROTO, "Error %s in %s converted to NFS3ERR_IO but " "was set non-retryable", msg_fsal_err(status.major), where); nfserror = NFS3ERR_IO; break; case ERR_FSAL_NXIO: nfserror = NFS3ERR_NXIO; break; case ERR_FSAL_INVAL: case ERR_FSAL_OVERFLOW: nfserror = NFS3ERR_INVAL; break; case ERR_FSAL_NOTDIR: nfserror = NFS3ERR_NOTDIR; break; case ERR_FSAL_EXIST: nfserror = NFS3ERR_EXIST; break; case ERR_FSAL_NOTEMPTY: nfserror = NFS3ERR_NOTEMPTY; break; case ERR_FSAL_NOENT: nfserror = NFS3ERR_NOENT; break; case ERR_FSAL_ACCESS: nfserror = NFS3ERR_ACCES; break; case ERR_FSAL_PERM: case ERR_FSAL_SEC: nfserror = NFS3ERR_PERM; break; case ERR_FSAL_NOSPC: nfserror = NFS3ERR_NOSPC; break; case ERR_FSAL_ISDIR: nfserror = NFS3ERR_ISDIR; break; case ERR_FSAL_ROFS: nfserror = NFS3ERR_ROFS; break; case ERR_FSAL_STALE: case ERR_FSAL_FHEXPIRED: nfserror = NFS3ERR_STALE; break; case ERR_FSAL_DQUOT: case ERR_FSAL_NO_QUOTA: nfserror = NFS3ERR_DQUOT; break; case ERR_FSAL_SYMLINK: case ERR_FSAL_BADTYPE: nfserror = NFS3ERR_BADTYPE; break; case ERR_FSAL_NOTSUPP: case ERR_FSAL_ATTRNOTSUPP: case ERR_FSAL_UNION_NOTSUPP: nfserror = NFS3ERR_NOTSUPP; break; case ERR_FSAL_DELAY: case ERR_FSAL_SHARE_DENIED: case ERR_FSAL_LOCKED: nfserror = NFS3ERR_JUKEBOX; break; case ERR_FSAL_NAMETOOLONG: nfserror = NFS3ERR_NAMETOOLONG; break; case ERR_FSAL_FBIG: nfserror = NFS3ERR_FBIG; break; case ERR_FSAL_BADCOOKIE: nfserror = NFS3ERR_BAD_COOKIE; break; case ERR_FSAL_TOOSMALL: nfserror = NFS3ERR_TOOSMALL; break; case ERR_FSAL_NO_DATA: case ERR_FSAL_FAULT: case ERR_FSAL_SERVERFAULT: case ERR_FSAL_DEADLOCK: nfserror = NFS3ERR_SERVERFAULT; break; case ERR_FSAL_XDEV: nfserror = NFS3ERR_XDEV; break; case ERR_FSAL_BADNAME: nfserror = NFS3ERR_INVAL; break; case ERR_FSAL_BADHANDLE: nfserror = NFS3ERR_BADHANDLE; break; case ERR_FSAL_MLINK: nfserror = NFS3ERR_MLINK; break; case ERR_FSAL_IN_GRACE: nfserror = NFS3ERR_JUKEBOX; break; case ERR_FSAL_CROSS_JUNCTION: case ERR_FSAL_BLOCKED: case ERR_FSAL_INTERRUPT: case ERR_FSAL_NOT_INIT: case ERR_FSAL_ALREADY_INIT: case ERR_FSAL_BAD_INIT: case ERR_FSAL_TIMEOUT: case ERR_FSAL_NO_ACE: case ERR_FSAL_BAD_RANGE: case ERR_FSAL_STILL_IN_USE: case ERR_FSAL_NOXATTR: case ERR_FSAL_XATTR2BIG: /* Should not occur */ LogDebug( COMPONENT_NFSPROTO, "Line %u should never be reached in nfs3_Errno from %s for FSAL error=%s", __LINE__, where, msg_fsal_err(status.major)); nfserror = NFS3ERR_INVAL; break; } return nfserror; } /* nfs3_Errno */ #endif nfs-ganesha-6.5/src/support/nfs_creds.c�������������������������������������������������������������0000664�0000000�0000000�00000066160�14737566223�0020223�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_export_list.c * @brief Routines for managing the export list. */ #include "config.h" #include <stdio.h> #include <sys/types.h> #include <ctype.h> /* for having isalnum */ #include <stdlib.h> /* for having atoi */ #include <dirent.h> /* for having MAXNAMLEN */ #include <netdb.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <pthread.h> #include <fcntl.h> #include <sys/file.h> /* for having FNDELAY */ #include <pwd.h> #include <grp.h> #include "log.h" #include "gsh_rpc.h" #include "nfs_core.h" #include "nfs23.h" #include "nfs4.h" #include "fsal.h" #include "nfs_exports.h" #include "nfs_creds.h" #include "nfs_file_handle.h" #include "idmapper.h" #include "export_mgr.h" #include "uid2grp.h" #include "client_mgr.h" #include "idmapper_monitoring.h" /* Export permissions for root op context */ uint32_t root_op_export_options = EXPORT_OPTION_ROOT | EXPORT_OPTION_ACCESS_MASK | EXPORT_OPTION_AUTH_TYPES | EXPORT_OPTION_PROTOCOLS | EXPORT_OPTION_TRANSPORTS; uint32_t root_op_export_set = EXPORT_OPTION_SQUASH_TYPES | EXPORT_OPTION_ANON_UID_SET | EXPORT_OPTION_ANON_GID_SET | EXPORT_OPTION_ACCESS_MASK | EXPORT_OPTION_AUTH_TYPES | EXPORT_OPTION_PROTOCOLS | EXPORT_OPTION_TRANSPORTS; void squash_setattr(struct fsal_attrlist *attr) { if (attr->valid_mask & ATTR_OWNER && op_ctx->export_perms.anonymous_uid != 0) { if (op_ctx->export_perms.options & EXPORT_OPTION_ALL_ANONYMOUS) attr->owner = op_ctx->export_perms.anonymous_uid; else if (((op_ctx->export_perms.options & EXPORT_OPTION_ROOT_SQUASH) || (op_ctx->export_perms.options & EXPORT_OPTION_ROOT_ID_SQUASH)) && (attr->owner == 0) && ((op_ctx->cred_flags & UID_SQUASHED) != 0)) attr->owner = op_ctx->export_perms.anonymous_uid; } if (attr->valid_mask & ATTR_GROUP && op_ctx->export_perms.anonymous_gid != 0) { /* If all squashed, then always squash the owner_group. * * If root squashed, then squash owner_group if * caller_gid has been squashed or one of the caller's * alternate groups has been squashed. */ if (op_ctx->export_perms.options & EXPORT_OPTION_ALL_ANONYMOUS) attr->group = op_ctx->export_perms.anonymous_gid; else if (((op_ctx->export_perms.options & EXPORT_OPTION_ROOT_SQUASH) || (op_ctx->export_perms.options & EXPORT_OPTION_ROOT_ID_SQUASH)) && (attr->group == 0) && ((op_ctx->cred_flags & (GID_SQUASHED | GARRAY_SQUASHED)) != 0)) attr->group = op_ctx->export_perms.anonymous_gid; } } /** * @brief Compares two RPC creds * * @param[in] cred1 First RPC cred * @param[in] cred2 Second RPC cred * * @return true if same, false otherwise */ bool nfs_compare_clientcred(nfs_client_cred_t *cred1, nfs_client_cred_t *cred2) { #ifdef _HAVE_GSSAPI gss_name_t cred1_cred_name; gss_name_t cred2_cred_name; OM_uint32 maj_stat, min_stat; int status; #endif if (cred1 == NULL) return false; if (cred2 == NULL) return false; if (cred1->flavor != cred2->flavor) return false; switch (cred1->flavor) { case AUTH_UNIX: if (cred1->auth_union.auth_unix.aup_uid != cred2->auth_union.auth_unix.aup_uid) return false; if (cred1->auth_union.auth_unix.aup_gid != cred2->auth_union.auth_unix.aup_gid) return false; break; #ifdef _HAVE_GSSAPI case RPCSEC_GSS: maj_stat = gss_inquire_context( &min_stat, cred1->auth_union.auth_gss.gd->ctx, &cred1_cred_name, NULL, NULL, NULL, NULL, NULL, NULL); if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTEXT_EXPIRED) return false; maj_stat = gss_inquire_context( &min_stat, cred2->auth_union.auth_gss.gd->ctx, &cred2_cred_name, NULL, NULL, NULL, NULL, NULL, NULL); if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTEXT_EXPIRED) { gss_release_name(&min_stat, &cred1_cred_name); return false; } maj_stat = gss_compare_name(&min_stat, cred1_cred_name, cred2_cred_name, &status); /* release the names */ gss_release_name(&min_stat, &cred1_cred_name); gss_release_name(&min_stat, &cred2_cred_name); if (maj_stat != GSS_S_COMPLETE) return false; if (status == 0) return false; break; #endif default: if (memcmp(&cred1->auth_union, &cred2->auth_union, cred1->length)) return false; break; } /* If this point is reached, structures are the same */ return true; } int nfs_rpc_req2client_cred(struct svc_req *req, nfs_client_cred_t *pcred) { /* Structure for managing basic AUTH_UNIX authentication */ struct authunix_parms *aup = NULL; /* Stuff needed for managing RPCSEC_GSS */ #ifdef _HAVE_GSSAPI struct svc_rpc_gss_data *gd = NULL; #endif pcred->length = req->rq_msg.cb_cred.oa_length; pcred->flavor = req->rq_msg.cb_cred.oa_flavor; switch (req->rq_msg.cb_cred.oa_flavor) { case AUTH_NONE: /* Do nothing... */ break; case AUTH_UNIX: aup = (struct authunix_parms *)req->rq_msg.rq_cred_body; pcred->auth_union.auth_unix.aup_uid = aup->aup_uid; pcred->auth_union.auth_unix.aup_gid = aup->aup_gid; pcred->auth_union.auth_unix.aup_time = aup->aup_time; break; #ifdef _HAVE_GSSAPI case RPCSEC_GSS: /* Extract the information from the RPCSEC_GSS * opaque structure */ gd = SVCAUTH_PRIVATE(req->rq_auth); pcred->auth_union.auth_gss.svc = (unsigned int)(gd->sec.svc); pcred->auth_union.auth_gss.qop = (unsigned int)(gd->sec.qop); pcred->auth_union.auth_gss.gd = gd; break; #endif default: /* Unsupported authentication flavour */ return -1; } return 1; } static void set_extended_groups(void) { /****************************************************************/ /* Check if we have manage_gids. */ /****************************************************************/ if ((op_ctx->cred_flags & MANAGED_GIDS) != 0) { /* Fetch the group data if required */ if (op_ctx->caller_gdata == NULL && !uid2grp(op_ctx->original_creds.caller_uid, &op_ctx->caller_gdata)) { LogInfo(COMPONENT_DISPATCH, "Attempt to fetch managed_gids failed"); idmapper_monitoring__failure(IDMAPPING_UID_TO_GROUPLIST, IDMAPPING_PWUTILS); /** @todo: do we really want to bail here? */ if (nfs_param.core_param.enable_rpc_cred_fallback) { LogInfo(COMPONENT_DISPATCH, "Attempt to fetch managed_gids failed for uid=%u, using cred info from rpc request", op_ctx->original_creds.caller_uid); /* Use the original_creds group list */ op_ctx->creds.caller_glen = op_ctx->original_creds.caller_glen; op_ctx->creds.caller_garray = op_ctx->original_creds.caller_garray; } else { LogInfo(COMPONENT_DISPATCH, "Attempt to fetch managed_gids failed for uid=%u", op_ctx->original_creds.caller_uid); op_ctx->creds.caller_glen = 0; } } else { op_ctx->creds.caller_glen = op_ctx->caller_gdata->nbgroups; op_ctx->creds.caller_garray = op_ctx->caller_gdata->groups; } } else { /* Use the original_creds group list */ op_ctx->creds.caller_glen = op_ctx->original_creds.caller_glen; op_ctx->creds.caller_garray = op_ctx->original_creds.caller_garray; } } #ifdef _HAVE_GSSAPI static void rpcsec_gss_fetch_managed_groups(char *principal) { /* Fetch the group data only if required */ if (op_ctx->caller_gdata != NULL) return; if (nfs_param.nfsv4_param.use_getpwnam) { if (!uid2grp(op_ctx->original_creds.caller_uid, &op_ctx->caller_gdata)) { LogInfo(COMPONENT_DISPATCH, "Attempt to fetch managed groups failed"); idmapper_monitoring__failure(IDMAPPING_UID_TO_GROUPLIST, IDMAPPING_PWUTILS); op_ctx->creds.caller_glen = 0; } } else { #ifdef USE_NFSIDMAP if (!principal2grp(principal, &op_ctx->caller_gdata, op_ctx->original_creds.caller_uid, op_ctx->original_creds.caller_gid)) { LogInfo(COMPONENT_DISPATCH, "Attempt to fetch managed groups failed"); idmapper_monitoring__failure( IDMAPPING_PRINCIPAL_TO_GROUPLIST, IDMAPPING_NFSIDMAP); op_ctx->creds.caller_glen = 0; } #else LogWarn(COMPONENT_DISPATCH, "Unsupported code path for principal %s", principal); op_ctx->creds.caller_glen = 0; #endif } } #endif static void squash_creds(const char *auth_label) { unsigned int i; gid_t **garray_copy = &op_ctx->caller_garray_copy; /****************************************************************/ /* Now check for anon creds or id squashing */ /****************************************************************/ if ((op_ctx->cred_flags & CREDS_ANON) != 0 || ((op_ctx->export_perms.options & EXPORT_OPTION_ALL_ANONYMOUS) != 0) || ((op_ctx->export_perms.options & EXPORT_OPTION_ROOT_SQUASH) != 0 && op_ctx->fsal_export->exp_ops.is_superuser( op_ctx->fsal_export, &op_ctx->original_creds))) { /* Squash uid, gid, and discard groups */ op_ctx->creds.caller_uid = op_ctx->export_perms.anonymous_uid; op_ctx->creds.caller_gid = op_ctx->export_perms.anonymous_gid; op_ctx->creds.caller_glen = 0; LogMidDebugAlt(COMPONENT_DISPATCH, COMPONENT_EXPORT, "%s creds squashed to uid=%u, gid=%u", auth_label, op_ctx->creds.caller_uid, op_ctx->creds.caller_gid); op_ctx->cred_flags |= UID_SQUASHED | GID_SQUASHED; return; } else if ((op_ctx->export_perms.options & EXPORT_OPTION_ROOT_ID_SQUASH) != 0 && op_ctx->fsal_export->exp_ops.is_superuser( op_ctx->fsal_export, &op_ctx->original_creds)) { /* Only squash root id, leave gid and groups alone for now */ op_ctx->creds.caller_uid = op_ctx->export_perms.anonymous_uid; op_ctx->cred_flags |= UID_SQUASHED; } else { /* Use original_creds uid */ op_ctx->creds.caller_uid = op_ctx->original_creds.caller_uid; } /****************************************************************/ /* Now sqush group or use original_creds gid */ /****************************************************************/ if (((op_ctx->export_perms.options & EXPORT_OPTION_ROOT_SQUASH) != 0 || (op_ctx->export_perms.options & EXPORT_OPTION_ROOT_ID_SQUASH) != 0) && op_ctx->original_creds.caller_gid == 0) { /* Squash gid */ op_ctx->creds.caller_gid = op_ctx->export_perms.anonymous_gid; op_ctx->cred_flags |= GID_SQUASHED; } else { /* Use original_creds gid */ op_ctx->creds.caller_gid = op_ctx->original_creds.caller_gid; } /****************************************************************/ /* Check the garray for gid 0 to squash */ /****************************************************************/ /* If no root squashing in caller_garray, return now */ if ((op_ctx->export_perms.options & EXPORT_OPTION_SQUASH_TYPES) == 0 || op_ctx->creds.caller_glen == 0) return; if (op_ctx->cred_flags & MANAGED_GIDS) garray_copy = &op_ctx->managed_garray_copy; for (i = 0; i < op_ctx->creds.caller_glen; i++) { if (op_ctx->creds.caller_garray[i] == 0) { /* Meed to make a copy, or use the old copy */ if ((*garray_copy) == NULL) { /* Make a copy of the active garray */ (*garray_copy) = gsh_malloc(op_ctx->creds.caller_glen * sizeof(gid_t)); memcpy((*garray_copy), op_ctx->creds.caller_garray, op_ctx->creds.caller_glen * sizeof(gid_t)); } /* Now squash the root id. Since the original copy is * always the same, any root ids in it were still in * the same place, so even if using a copy that had a * different anonymous_gid, we're fine. */ (*garray_copy)[i] = op_ctx->export_perms.anonymous_gid; /* Indicate we squashed the caller_garray */ op_ctx->cred_flags |= GARRAY_SQUASHED; } } /* If we squashed the caller_garray, use the squashed copy */ if ((op_ctx->cred_flags & GARRAY_SQUASHED) != 0) op_ctx->creds.caller_garray = *garray_copy; } /** * @brief Get numeric credentials from request * * @todo This MUST be refactored to not use TI-RPC private structures. * Instead, export appropriate functions from lib(n)tirpc. * * fills out creds in op_ctx * * @param[in] req Incoming request. * * @return NFS4_OK if successful, NFS4ERR_ACCESS otherwise. * */ nfsstat4 nfs_req_creds(struct svc_req *req) { const char *auth_label = "UNKNOWN"; #ifdef _HAVE_GSSAPI struct svc_rpc_gss_data *gd = NULL; char principal[MAXNAMLEN + 1]; #endif /* Make sure we clear out all the cred_flags except CREDS_LOADED and * CREDS_ANON. */ op_ctx->cred_flags &= CREDS_LOADED | CREDS_ANON; switch (req->rq_msg.cb_cred.oa_flavor) { case AUTH_NONE: /* Nothing to be done here... */ op_ctx->cred_flags |= CREDS_LOADED | CREDS_ANON; auth_label = "AUTH_NONE"; break; case AUTH_SYS: if ((op_ctx->cred_flags & CREDS_LOADED) == 0) { struct authunix_parms *creds = (struct authunix_parms *) req->rq_msg.rq_cred_body; /* We map the rq_cred to Authunix_parms */ op_ctx->original_creds.caller_uid = creds->aup_uid; op_ctx->original_creds.caller_gid = creds->aup_gid; op_ctx->original_creds.caller_glen = creds->aup_len; op_ctx->original_creds.caller_garray = creds->aup_gids; op_ctx->cred_flags |= CREDS_LOADED; } /* Copy original_creds creds */ op_ctx->creds = op_ctx->original_creds; /* Do we trust AUTH_SYS creds for groups or not ? */ if ((op_ctx->export_perms.options & EXPORT_OPTION_MANAGE_GIDS) != 0) { op_ctx->cred_flags |= MANAGED_GIDS; } auth_label = "AUTH_SYS"; break; #ifdef _HAVE_GSSAPI case RPCSEC_GSS: /* Get the gss data to process them */ gd = SVCAUTH_PRIVATE(req->rq_auth); memcpy(principal, gd->cname.value, gd->cname.length); principal[gd->cname.length] = 0; if (op_ctx->cred_flags & CREDS_LOADED) { /* Creds are already loaded, get auth_label using * existing anon flag */ auth_label = (op_ctx->cred_flags & CREDS_ANON) ? "RPCSEC_GSS (no mapping)" : "RPCSEC_GSS"; break; } LogMidDebug(COMPONENT_DISPATCH, "Mapping RPCSEC_GSS principal %s to uid/gid", principal); /* Convert to uid */ #if _MSPAC_SUPPORT if (!principal2uid(principal, &op_ctx->original_creds.caller_uid, &op_ctx->original_creds.caller_gid, gd)) { idmapper_monitoring__failure( IDMAPPING_PRINCIPAL_TO_UIDGID, IDMAPPING_WINBIND); #else if (!principal2uid(principal, &op_ctx->original_creds.caller_uid, &op_ctx->original_creds.caller_gid)) { #endif LogInfo(COMPONENT_IDMAPPER, "Could not map principal %s to uid", principal); /* For compatibility with Linux knfsd, we set * the uid/gid to anonymous when a name->uid * mapping can't be found. */ op_ctx->cred_flags |= CREDS_ANON | CREDS_LOADED; auth_label = "RPCSEC_GSS (no mapping)"; break; } op_ctx->cred_flags |= CREDS_LOADED | MANAGED_GIDS; auth_label = "RPCSEC_GSS"; /* Fetch extended groups */ rpcsec_gss_fetch_managed_groups(principal); break; #endif /* _HAVE_GSSAPI */ default: LogMidDebug(COMPONENT_DISPATCH, "FAILURE: Request xid=%" PRIu32 ", has unsupported authentication %" PRIu32, req->rq_msg.rm_xid, req->rq_msg.cb_cred.oa_flavor); /* Reject the request for weak authentication and * return to worker */ return NFS4ERR_ACCESS; break; } set_extended_groups(); squash_creds(auth_label); LogMidDebugAlt( COMPONENT_DISPATCH, COMPONENT_EXPORT, "%s creds mapped to uid=%u%s, gid=%u%s, glen=%d%s", auth_label, op_ctx->creds.caller_uid, (op_ctx->cred_flags & UID_SQUASHED) != 0 ? " (squashed)" : "", op_ctx->creds.caller_gid, (op_ctx->cred_flags & GID_SQUASHED) != 0 ? " (squashed)" : "", op_ctx->creds.caller_glen, (op_ctx->cred_flags & MANAGED_GIDS) != 0 ? ((op_ctx->cred_flags & GARRAY_SQUASHED) != 0 ? " (managed and squashed)" : " (managed)") : ((op_ctx->cred_flags & GARRAY_SQUASHED) != 0 ? " (squashed)" : "")); return NFS4_OK; } /** * @brief Initialize request context and credentials. * */ void init_credentials(void) { memset(&op_ctx->creds, 0, sizeof(op_ctx->creds)); memset(&op_ctx->original_creds, 0, sizeof(op_ctx->original_creds)); op_ctx->creds.caller_uid = op_ctx->export_perms.anonymous_uid; op_ctx->creds.caller_gid = op_ctx->export_perms.anonymous_gid; op_ctx->caller_gdata = NULL; op_ctx->caller_garray_copy = NULL; op_ctx->managed_garray_copy = NULL; op_ctx->cred_flags = 0; } /** * @brief Release temporary credential resources. * */ void clean_credentials(void) { /* If Manage_gids is used, unref the group list. */ if (op_ctx->caller_gdata != NULL) uid2grp_unref(op_ctx->caller_gdata); /* Have we made a local copy of the managed_gids garray? */ if (op_ctx->managed_garray_copy != NULL) gsh_free(op_ctx->managed_garray_copy); /* Have we made a local copy of the AUTH_SYS garray? */ if (op_ctx->caller_garray_copy != NULL) gsh_free(op_ctx->caller_garray_copy); /* Prepare the request context and creds for re-use */ init_credentials(); } /** * @brief Validate export permissions * * @param[in] req Incoming request. * * @return NFS4_OK if successful, NFS4ERR_ACCESS or NFS4ERR_WRONGSEC otherwise. * */ nfsstat4 nfs4_export_check_access(struct svc_req *req) { xprt_type_t xprt_type = svc_get_xprt_type(req->rq_xprt); int port = get_port(op_ctx->caller_addr); LogMidDebugAlt(COMPONENT_NFS_V4, COMPONENT_EXPORT, "about to call export_check_access"); export_check_access(); /* Check if any access at all */ if ((op_ctx->export_perms.options & EXPORT_OPTION_ACCESS_MASK) == 0) { LogInfoAlt( COMPONENT_NFS_V4, COMPONENT_EXPORT, "Access not allowed on Export_Id %d %s for client %s", op_ctx->ctx_export->export_id, CTX_PSEUDOPATH(op_ctx), op_ctx->client ? op_ctx->client->hostaddr_str : "unknown client"); return NFS4ERR_ACCESS; } /* Check protocol version */ if ((op_ctx->export_perms.options & EXPORT_OPTION_NFSV4) == 0) { LogInfoAlt(COMPONENT_NFS_V4, COMPONENT_EXPORT, "NFS4 not allowed on Export_Id %d %s for client %s", op_ctx->ctx_export->export_id, CTX_PSEUDOPATH(op_ctx), op_ctx->client ? op_ctx->client->hostaddr_str : "unknown client"); return NFS4ERR_ACCESS; } /* Check transport type */ if (((xprt_type == XPRT_UDP) && ((op_ctx->export_perms.options & EXPORT_OPTION_UDP) == 0)) || ((xprt_type == XPRT_TCP) && ((op_ctx->export_perms.options & EXPORT_OPTION_TCP) == 0))) { LogInfoAlt( COMPONENT_NFS_V4, COMPONENT_EXPORT, "NFS4 over %s not allowed on Export_Id %d %s for client %s", xprt_type_to_str(xprt_type), op_ctx->ctx_export->export_id, CTX_PSEUDOPATH(op_ctx), op_ctx->client ? op_ctx->client->hostaddr_str : "unknown client"); return NFS4ERR_ACCESS; } /* Check if client is using a privileged port. */ if (((op_ctx->export_perms.options & EXPORT_OPTION_PRIVILEGED_PORT) != 0) && (port >= IPPORT_RESERVED)) { LogInfoAlt( COMPONENT_NFS_V4, COMPONENT_EXPORT, "Non-reserved Port %d is not allowed on Export_Id %d %s for client %s", port, op_ctx->ctx_export->export_id, CTX_PSEUDOPATH(op_ctx), op_ctx->client ? op_ctx->client->hostaddr_str : "unknown client"); return NFS4ERR_ACCESS; } /* Test if export allows the authentication provided */ if (export_check_security(req) == false) { LogInfoAlt( COMPONENT_NFS_V4, COMPONENT_EXPORT, "NFS4 auth not allowed on Export_Id %d %s for client %s", op_ctx->ctx_export->export_id, CTX_PSEUDOPATH(op_ctx), op_ctx->client ? op_ctx->client->hostaddr_str : "unknown client"); return NFS4ERR_WRONGSEC; } /* Get creds */ return nfs_req_creds(req); } /** * @brief Perform version independent ACCESS operation. * * This function wraps a call to fsal_access, determining the appropriate * access_mask to use to check all the requested access bits. It requests the * allowed and denied access so that it can respond for each requested access * with a single access call. * * @param[in] obj Object handle to check access for * @param[in] requested_access The ACCESS3 or ACCESS4 bits requested * @param[out] granted_access The bits granted * @param[out] supported_access The bits supported for this inode * * @return FSAL status which may have in 'status.major': * - ERR_FSAL_NO_ERROR all access was granted * - ERR_FSAL_ACCESS one or more access bits were denied * - other values indicate a FSAL failure * */ fsal_status_t nfs_access_op(struct fsal_obj_handle *obj, uint32_t requested_access, uint32_t *granted_access, uint32_t *supported_access) { fsal_status_t fsal_status; fsal_accessflags_t access_mask; fsal_accessflags_t access_allowed; fsal_accessflags_t access_denied; uint32_t granted_mask = requested_access; access_mask = 0; *granted_access = 0; LogDebugAlt( COMPONENT_NFSPROTO, COMPONENT_NFS_V4_ACL, "Requested ACCESS=%s,%s,%s,%s,%s,%s", FSAL_TEST_MASK(requested_access, ACCESS3_READ) ? "READ" : "-", FSAL_TEST_MASK(requested_access, ACCESS3_LOOKUP) ? "LOOKUP" : "-", FSAL_TEST_MASK(requested_access, ACCESS3_MODIFY) ? "MODIFY" : "-", FSAL_TEST_MASK(requested_access, ACCESS3_EXTEND) ? "EXTEND" : "-", FSAL_TEST_MASK(requested_access, ACCESS3_DELETE) ? "DELETE" : "-", FSAL_TEST_MASK(requested_access, ACCESS3_EXECUTE) ? "EXECUTE" : "-"); /* Set mode for read. * NOTE: FSAL_ACE_PERM_LIST_DIR and FSAL_ACE_PERM_READ_DATA have * the same bit value so we don't bother looking at file type. */ if (requested_access & (ACCESS3_READ | ACCESS4_XAREAD | ACCESS4_XALIST)) access_mask |= FSAL_R_OK | FSAL_ACE_PERM_READ_DATA; if (requested_access & ACCESS3_LOOKUP) { if (obj->type == DIRECTORY) access_mask |= FSAL_X_OK | FSAL_ACE_PERM_EXECUTE; else granted_mask &= ~ACCESS3_LOOKUP; } if (requested_access & (ACCESS3_MODIFY | ACCESS4_XAWRITE)) { if (obj->type == DIRECTORY) access_mask |= FSAL_W_OK | FSAL_ACE_PERM_DELETE_CHILD; else access_mask |= FSAL_W_OK | FSAL_ACE_PERM_WRITE_DATA; } if (requested_access & ACCESS3_EXTEND) { if (obj->type == DIRECTORY) access_mask |= FSAL_W_OK | FSAL_ACE_PERM_ADD_FILE | FSAL_ACE_PERM_ADD_SUBDIRECTORY; else access_mask |= FSAL_W_OK | FSAL_ACE_PERM_APPEND_DATA; } if (requested_access & ACCESS3_DELETE) { if (obj->type == DIRECTORY) access_mask |= FSAL_W_OK | FSAL_ACE_PERM_DELETE_CHILD; else granted_mask &= ~ACCESS3_DELETE; } if (requested_access & ACCESS3_EXECUTE) { if (obj->type != DIRECTORY) access_mask |= FSAL_X_OK | FSAL_ACE_PERM_EXECUTE; else granted_mask &= ~ACCESS3_EXECUTE; } if (access_mask != 0) access_mask |= FSAL_MODE_MASK_FLAG | FSAL_ACE4_MASK_FLAG | FSAL_ACE4_PERM_CONTINUE; LogDebugAlt( COMPONENT_NFSPROTO, COMPONENT_NFS_V4_ACL, "access_mask = mode(%c%c%c) ACL(%s,%s,%s,%s,%s)", FSAL_TEST_MASK(access_mask, FSAL_R_OK) ? 'r' : '-', FSAL_TEST_MASK(access_mask, FSAL_W_OK) ? 'w' : '-', FSAL_TEST_MASK(access_mask, FSAL_X_OK) ? 'x' : '-', FSAL_TEST_MASK(access_mask, FSAL_ACE_PERM_READ_DATA) ? obj->type == DIRECTORY ? "list_dir" : "read_data" : "-", FSAL_TEST_MASK(access_mask, FSAL_ACE_PERM_WRITE_DATA) ? obj->type == DIRECTORY ? "add_file" : "write_data" : "-", FSAL_TEST_MASK(access_mask, FSAL_ACE_PERM_EXECUTE) ? "execute" : "-", FSAL_TEST_MASK(access_mask, FSAL_ACE_PERM_ADD_SUBDIRECTORY) ? "add_subdirectory" : "-", FSAL_TEST_MASK(access_mask, FSAL_ACE_PERM_DELETE_CHILD) ? "delete_child" : "-"); fsal_status = obj->obj_ops->test_access( obj, access_mask, &access_allowed, &access_denied, false); if (fsal_status.major == ERR_FSAL_NO_ERROR || fsal_status.major == ERR_FSAL_ACCESS) { /* Define granted access based on granted mode bits. */ if (access_allowed & FSAL_R_OK) { *granted_access |= ACCESS3_READ | ACCESS4_XAREAD | ACCESS4_XALIST; } if (access_allowed & FSAL_W_OK) { *granted_access |= ACCESS3_MODIFY | ACCESS3_EXTEND | ACCESS3_DELETE | ACCESS4_XAWRITE; } if (access_allowed & FSAL_X_OK) *granted_access |= ACCESS3_LOOKUP | ACCESS3_EXECUTE; /* Define granted access based on granted ACL bits. */ if (access_allowed & FSAL_ACE_PERM_READ_DATA) *granted_access |= ACCESS3_READ | ACCESS4_XAREAD | ACCESS4_XALIST; if (obj->type == DIRECTORY) { if (access_allowed & FSAL_ACE_PERM_DELETE_CHILD) *granted_access |= ACCESS3_MODIFY | ACCESS3_DELETE | ACCESS4_XAWRITE; if (access_allowed & FSAL_ACE_PERM_ADD_FILE) *granted_access |= ACCESS3_EXTEND | ACCESS4_XAWRITE; if (access_allowed & FSAL_ACE_PERM_ADD_SUBDIRECTORY) *granted_access |= ACCESS3_EXTEND | ACCESS4_XAWRITE; } else { if (access_allowed & FSAL_ACE_PERM_WRITE_DATA) *granted_access |= ACCESS3_MODIFY | ACCESS4_XAWRITE; if (access_allowed & FSAL_ACE_PERM_APPEND_DATA) *granted_access |= ACCESS3_EXTEND | ACCESS4_XAWRITE; } if (access_allowed & FSAL_ACE_PERM_EXECUTE) *granted_access |= ACCESS3_LOOKUP | ACCESS3_EXECUTE; /* Allow only read if client has read only access * on this share. */ if (!(op_ctx->export_perms.options & EXPORT_OPTION_WRITE_ACCESS)) *granted_access &= ~(ACCESS3_EXTEND | ACCESS3_MODIFY | ACCESS3_DELETE | ACCESS4_XAWRITE); /* Don't allow any bits that weren't set on request or * allowed by the file type. */ *granted_access &= granted_mask; if (supported_access != NULL) *supported_access = granted_mask; LogDebugAlt(COMPONENT_NFSPROTO, COMPONENT_NFS_V4_ACL, "Supported ACCESS=%s,%s,%s,%s,%s,%s", FSAL_TEST_MASK(granted_mask, ACCESS3_READ) ? "READ" : "-", FSAL_TEST_MASK(granted_mask, ACCESS3_LOOKUP) ? "LOOKUP" : "-", FSAL_TEST_MASK(granted_mask, ACCESS3_MODIFY) ? "MODIFY" : "-", FSAL_TEST_MASK(granted_mask, ACCESS3_EXTEND) ? "EXTEND" : "-", FSAL_TEST_MASK(granted_mask, ACCESS3_DELETE) ? "DELETE" : "-", FSAL_TEST_MASK(granted_mask, ACCESS3_EXECUTE) ? "EXECUTE" : "-"); LogDebugAlt(COMPONENT_NFSPROTO, COMPONENT_NFS_V4_ACL, "Granted ACCESS=%s,%s,%s,%s,%s,%s", FSAL_TEST_MASK(*granted_access, ACCESS3_READ) ? "READ" : "-", FSAL_TEST_MASK(*granted_access, ACCESS3_LOOKUP) ? "LOOKUP" : "-", FSAL_TEST_MASK(*granted_access, ACCESS3_MODIFY) ? "MODIFY" : "-", FSAL_TEST_MASK(*granted_access, ACCESS3_EXTEND) ? "EXTEND" : "-", FSAL_TEST_MASK(*granted_access, ACCESS3_DELETE) ? "DELETE" : "-", FSAL_TEST_MASK(*granted_access, ACCESS3_EXECUTE) ? "EXECUTE" : "-"); } return fsal_status; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/nfs_filehandle_mgmt.c���������������������������������������������������0000664�0000000�0000000�00000036177�14737566223�0022247�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_filehandle_mgmt.c * @brief Some tools for managing the file handles * */ #include "config.h" #include "log.h" #include "nfs_core.h" #include "nfs23.h" #include "nfs4.h" #include "fsal.h" #include "nfs_exports.h" #include "nfs_file_handle.h" #include "nfs_proto_functions.h" #include "nfs_proto_tools.h" #include "nfs_convert.h" #include "export_mgr.h" #include "fsal_convert.h" #ifdef _USE_NFS3 /** * * nfs3_FhandleToCache: gets the FSAL obj from the NFSv3 file handle. * * Validates and Converts a V3 file handle and then gets the FSAL obj. * * @param fh3 [IN] pointer to the file handle to be converted * @param status [OUT] protocol status * @param rc [OUT] operation status * * @return FSAL obj or NULL on failure * */ struct fsal_obj_handle *nfs3_FhandleToCache(nfs_fh3 *fh3, nfsstat3 *status, int *rc) { fsal_status_t fsal_status; file_handle_v3_t *v3_handle; struct fsal_export *export; struct fsal_obj_handle *obj = NULL; struct gsh_buffdesc fh_desc; char fhbuf[NFS3_FHSIZE]; /* Default behaviour */ *rc = NFS_REQ_OK; /* validate the filehandle */ *status = nfs3_Is_Fh_Invalid(fh3); if (*status != NFS3_OK) goto badhdl; /* Cast the fh as a non opaque structure */ v3_handle = (file_handle_v3_t *)(fh3->data.data_val); assert(ntohs(v3_handle->exportid) == op_ctx->ctx_export->export_id); export = op_ctx->fsal_export; /* * FIXME: the wire handle can obviously be no larger than NFS4_FHSIZE, * but there is no such limit on a host handle. Here, we assume that as * the size limit. Eventually it might be nice to call into the FSAL to * ask how large a buffer it needs for a host handle. */ memcpy(fhbuf, &v3_handle->fsopaque, v3_handle->fs_len); fh_desc.len = v3_handle->fs_len; fh_desc.addr = fhbuf; /* adjust the wire handle opaque into a host-handle */ fsal_status = export->exp_ops.wire_to_host( export, FSAL_DIGEST_NFSV3, &fh_desc, v3_handle->fhflags1); if (!FSAL_IS_ERROR(fsal_status)) fsal_status = export->exp_ops.create_handle(export, &fh_desc, &obj, NULL); if (FSAL_IS_ERROR(fsal_status)) { *status = nfs3_Errno_status(fsal_status); if (nfs_RetryableError(fsal_status.major)) *rc = NFS_REQ_DROP; } badhdl: return obj; } #endif /* _USE_NFS3 */ /** * @brief Converts an FSAL object to an NFSv4 file handle * * @param[out] fh4 The extracted file handle * @param[in] fsalhandle The FSAL handle to be converted * * @return true if successful, false otherwise */ bool nfs4_FSALToFhandle(bool allocate, nfs_fh4 *fh4, const struct fsal_obj_handle *fsalhandle, struct gsh_export *exp) { file_handle_v4_t *file_handle; struct gsh_buffdesc fh_desc; if (allocate) { /* Allocating the filehandle in memory */ nfs4_AllocateFH(fh4); } else { /* reset the buffer to be used as handle */ fh4->nfs_fh4_len = NFS4_FHSIZE; memset(fh4->nfs_fh4_val, 0, NFS4_FHSIZE); } file_handle = (file_handle_v4_t *)fh4->nfs_fh4_val; /* Fill in the fs opaque part */ fh_desc.addr = &file_handle->fsopaque; fh_desc.len = fh4->nfs_fh4_len - offsetof(file_handle_v4_t, fsopaque); if (FSAL_IS_ERROR(fsalhandle->obj_ops->handle_to_wire( fsalhandle, FSAL_DIGEST_NFSV4, &fh_desc))) { LogDebug(COMPONENT_FILEHANDLE, "handle_to_wire FSAL_DIGEST_NFSV4 failed"); if (allocate) nfs4_freeFH(fh4); return false; } file_handle->fhversion = GANESHA_FH_VERSION; #if (BYTE_ORDER == BIG_ENDIAN) file_handle->fhflags1 = FH_FSAL_BIG_ENDIAN; #endif file_handle->fs_len = fh_desc.len; /* set the actual size */ /* keep track of the export id network byte order for nfs_fh4*/ file_handle->id.exports = htons(exp->export_id); /* Set the len */ fh4->nfs_fh4_len = nfs4_sizeof_handle(file_handle); LogFullDebugOpaque(COMPONENT_FILEHANDLE, "NFS4 Handle %s", LEN_FH_STR, fh4->nfs_fh4_val, fh4->nfs_fh4_len); return true; } /** * @brief Converts an FSAL object to an NFSv3 file handle * * @param[out] fh3 The extracted file handle * @param[in] fsalhandle The FSAL handle to be converted * @param[in] exp The gsh_export that this handle belongs to * * @return true if successful, false otherwise * * @todo Do we have to worry about buffer alignment and memcpy to * compensate?? */ bool nfs3_FSALToFhandle(bool allocate, nfs_fh3 *fh3, const struct fsal_obj_handle *fsalhandle, struct gsh_export *exp) { file_handle_v3_t *file_handle; struct gsh_buffdesc fh_desc; if (allocate) { /* Allocating the filehandle in memory */ nfs3_AllocateFH(fh3); } else { /* reset the buffer to be used as handle */ fh3->data.data_len = NFS3_FHSIZE; memset(fh3->data.data_val, 0, NFS3_FHSIZE); } file_handle = (file_handle_v3_t *)fh3->data.data_val; /* Fill in the fs opaque part */ fh_desc.addr = &file_handle->fsopaque; fh_desc.len = NFS3_FHSIZE - offsetof(file_handle_v3_t, fsopaque); if (FSAL_IS_ERROR(fsalhandle->obj_ops->handle_to_wire( fsalhandle, FSAL_DIGEST_NFSV3, &fh_desc))) { LogDebug(COMPONENT_FILEHANDLE, "handle_to_wire FSAL_DIGEST_NFSV3 failed"); if (allocate) nfs3_freeFH(fh3); return false; } file_handle->fhversion = GANESHA_FH_VERSION; #if (BYTE_ORDER == BIG_ENDIAN) file_handle->fhflags1 = FH_FSAL_BIG_ENDIAN; #endif file_handle->fs_len = fh_desc.len; /* set the actual size */ /* keep track of the export id in network byte order*/ file_handle->exportid = htons(exp->export_id); /* Set the len */ /* re-adjust to as built */ fh3->data.data_len = nfs3_sizeof_handle(file_handle); LogFullDebugOpaque(COMPONENT_FILEHANDLE, "NFS3 Handle %s", LEN_FH_STR, fh3->data.data_val, fh3->data.data_len); /* Check VMware NFSv3 client's 56 byte file handle size restriction */ if (nfs_param.core_param.short_file_handle && fh3->data.data_len > 56) LogWarnOnce( COMPONENT_FILEHANDLE, "Short file handle option is enabled but file handle size computed is: %d", fh3->data.data_len); return true; } /** * * nfs4_Is_Fh_DSHandle * * This routine is used to test if a fh is a DS fh * * @param fh [IN] file handle to test. * * @return true if DS fh, false otherwise * */ int nfs4_Is_Fh_DSHandle(nfs_fh4 *fh) { file_handle_v4_t *fhandle4; if (fh == NULL) return 0; fhandle4 = (file_handle_v4_t *)(fh->nfs_fh4_val); return (fhandle4->fhflags1 & FILE_HANDLE_V4_FLAG_DS) != 0; } /** * @brief Test if a filehandle is invalid * * @param[in] fh File handle to test. * * @return NFS4_OK if successful. */ int nfs4_Is_Fh_Invalid(nfs_fh4 *fh) { file_handle_v4_t *pfile_handle; if (fh == NULL) { LogMajor(COMPONENT_FILEHANDLE, "INVALID HANDLE: fh==NULL"); return NFS4ERR_BADHANDLE; } LogFullDebugOpaque(COMPONENT_FILEHANDLE, "NFS4 Handle %s", LEN_FH_STR, fh->nfs_fh4_val, fh->nfs_fh4_len); /* Cast the fh as a non opaque structure */ pfile_handle = (file_handle_v4_t *)(fh->nfs_fh4_val); /* validate the filehandle */ if (pfile_handle == NULL || fh->nfs_fh4_len == 0 || pfile_handle->fhversion != GANESHA_FH_VERSION || fh->nfs_fh4_len < offsetof(struct file_handle_v4, fsopaque) || fh->nfs_fh4_len > NFS4_FHSIZE || !valid_Fh4_Len(fh, pfile_handle)) { if (isInfo(COMPONENT_FILEHANDLE)) { if (pfile_handle == NULL) { LogInfo(COMPONENT_FILEHANDLE, "INVALID HANDLE: nfs_fh4_val=NULL"); } else if (fh->nfs_fh4_len == 0) { LogInfo(COMPONENT_FILEHANDLE, "INVALID HANDLE: zero length handle"); } else if (pfile_handle->fhversion != GANESHA_FH_VERSION) { LogInfo(COMPONENT_FILEHANDLE, "INVALID HANDLE: not a Ganesha handle, fhversion=%d", pfile_handle->fhversion); } else if (fh->nfs_fh4_len < offsetof(struct file_handle_v4, fsopaque)) { LogInfo(COMPONENT_FILEHANDLE, "INVALID HANDLE: data.data_len=%d is less than %d", fh->nfs_fh4_len, (int)offsetof(struct file_handle_v4, fsopaque)); } else if (fh->nfs_fh4_len > NFS4_FHSIZE) { LogInfo(COMPONENT_FILEHANDLE, "INVALID HANDLE: data.data_len=%d is greater than %d", fh->nfs_fh4_len, (int)NFS4_FHSIZE); } else if (fh->nfs_fh4_len != nfs4_sizeof_handle(pfile_handle)) { LogInfo(COMPONENT_FILEHANDLE, "INVALID HANDLE: nfs_fh4_len=%d, should be %d", fh->nfs_fh4_len, (int)nfs4_sizeof_handle(pfile_handle)); } else { LogInfo(COMPONENT_FILEHANDLE, "INVALID HANDLE: is_pseudofs=%d", ntohs(pfile_handle->id.exports) == 0); } } return NFS4ERR_BADHANDLE; /* Bad FH */ } return NFS4_OK; } /* nfs4_Is_Fh_Invalid */ /** * @brief Test if a filehandle is invalid. * * @param[in] fh3 File handle to test. * * @return NFS4_OK if successful. * */ int nfs3_Is_Fh_Invalid(nfs_fh3 *fh3) { file_handle_v3_t *pfile_handle; if (fh3 == NULL) { LogMajor(COMPONENT_FILEHANDLE, "INVALID HANDLE: fh3==NULL"); return NFS3ERR_BADHANDLE; } LogFullDebugOpaque(COMPONENT_FILEHANDLE, "NFS3 Handle %s", LEN_FH_STR, fh3->data.data_val, fh3->data.data_len); /* Cast the fh as a non opaque structure */ pfile_handle = (file_handle_v3_t *)(fh3->data.data_val); /* validate the filehandle */ if (pfile_handle == NULL || fh3->data.data_len == 0 || pfile_handle->fhversion != GANESHA_FH_VERSION || fh3->data.data_len < offsetof(file_handle_v3_t, fsopaque) || fh3->data.data_len > NFS3_FHSIZE || fh3->data.data_len != nfs3_sizeof_handle(pfile_handle)) { if (isInfo(COMPONENT_FILEHANDLE)) { if (pfile_handle == NULL) { LogInfo(COMPONENT_FILEHANDLE, "INVALID HANDLE: data.data_val=NULL"); } else if (fh3->data.data_len == 0) { LogInfo(COMPONENT_FILEHANDLE, "INVALID HANDLE: zero length handle"); } else if (pfile_handle->fhversion != GANESHA_FH_VERSION) { LogInfo(COMPONENT_FILEHANDLE, "INVALID HANDLE: not a Ganesha handle, fhversion=%d", pfile_handle->fhversion); } else if (fh3->data.data_len < sizeof(file_handle_v3_t)) { LogInfo(COMPONENT_FILEHANDLE, "INVALID HANDLE: data.data_len=%d is less than %d", fh3->data.data_len, (int)offsetof(file_handle_v3_t, fsopaque)); } else if (fh3->data.data_len > NFS3_FHSIZE) { LogInfo(COMPONENT_FILEHANDLE, "INVALID HANDLE: data.data_len=%d is greater than %d", fh3->data.data_len, (int)NFS3_FHSIZE); } else if (fh3->data.data_len != nfs3_sizeof_handle(pfile_handle)) { LogInfo(COMPONENT_FILEHANDLE, "INVALID HANDLE: data.data_len=%d, should be %d", fh3->data.data_len, (int)nfs3_sizeof_handle(pfile_handle)); } } return NFS3ERR_BADHANDLE; /* Bad FH */ } return NFS3_OK; } /* nfs3_Is_Fh_Invalid */ /** * @brief Do basic checks on the CurrentFH * * This function performs basic checks to make sure the supplied * filehandle is sane for a given operation. * * @param data [IN] Compound_data_t for the operation to check * @param required_type [IN] The file type this operation requires. * Set to 0 to allow any type. * @param ds_allowed [IN] true if DS handles are allowed. * * @return NFSv4.1 status codes */ nfsstat4 nfs4_sanity_check_FH(compound_data_t *data, object_file_type_t required_type, bool ds_allowed) { int fh_status; /* If there is no FH */ fh_status = nfs4_Is_Fh_Empty(&data->currentFH); if (fh_status != NFS4_OK) return fh_status; assert(data->current_obj != NULL && data->current_filetype != NO_FILE_TYPE); /* If the filehandle is invalid */ fh_status = nfs4_Is_Fh_Invalid(&data->currentFH); if (fh_status != NFS4_OK) return fh_status; /* Check for the correct file type */ if (required_type != NO_FILE_TYPE && data->current_filetype != required_type) { LogDebug(COMPONENT_NFS_V4, "Wrong file type expected %s actual %s", object_file_type_to_str(required_type), object_file_type_to_str(data->current_filetype)); if (required_type == DIRECTORY) { if (data->current_filetype == SYMBOLIC_LINK) return NFS4ERR_SYMLINK; else return NFS4ERR_NOTDIR; } else if (required_type == SYMBOLIC_LINK) return NFS4ERR_INVAL; switch (data->current_filetype) { case DIRECTORY: return NFS4ERR_ISDIR; default: return NFS4ERR_INVAL; } } if (nfs4_Is_Fh_DSHandle(&data->currentFH) && !ds_allowed) { LogDebug(COMPONENT_NFS_V4, "DS Handle"); return NFS4ERR_INVAL; } return NFS4_OK; } /* nfs4_sanity_check_FH */ /** * @brief Do basic checks on the SavedFH * * This function performs basic checks to make sure the supplied * filehandle is sane for a given operation. * * @param data [IN] Compound_data_t for the operation to check * @param required_type [IN] The file type this operation requires. * Set to 0 to allow any type. A negative value * indicates any type BUT that type is allowed. * @param ds_allowed [IN] true if DS handles are allowed. * * @return NFSv4.1 status codes */ nfsstat4 nfs4_sanity_check_saved_FH(compound_data_t *data, int required_type, bool ds_allowed) { int fh_status; /* If there is no FH */ fh_status = nfs4_Is_Fh_Empty(&data->savedFH); if (fh_status != NFS4_OK) return fh_status; /* If the filehandle is invalid */ fh_status = nfs4_Is_Fh_Invalid(&data->savedFH); if (fh_status != NFS4_OK) return fh_status; if (nfs4_Is_Fh_DSHandle(&data->savedFH) && !ds_allowed) { LogDebug(COMPONENT_NFS_V4, "DS Handle"); return NFS4ERR_INVAL; } /* Check for the correct file type */ if (required_type < 0) { if (-required_type == data->saved_filetype) { LogDebug(COMPONENT_NFS_V4, "Wrong file type expected not to be %s was %s", object_file_type_to_str( (object_file_type_t)-required_type), object_file_type_to_str( data->current_filetype)); if (-required_type == DIRECTORY) { return NFS4ERR_ISDIR; } } } else if (required_type != NO_FILE_TYPE && data->saved_filetype != required_type) { LogDebug(COMPONENT_NFS_V4, "Wrong file type expected %s was %s", object_file_type_to_str( (object_file_type_t)required_type), object_file_type_to_str(data->current_filetype)); if (required_type == DIRECTORY) { if (data->current_filetype == SYMBOLIC_LINK) return NFS4ERR_SYMLINK; else return NFS4ERR_NOTDIR; } else if (required_type == SYMBOLIC_LINK) return NFS4ERR_INVAL; switch (data->saved_filetype) { case DIRECTORY: return NFS4ERR_ISDIR; default: return NFS4ERR_INVAL; } } return NFS4_OK; } /* nfs4_sanity_check_saved_FH */ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/nfs_ip_name.c�����������������������������������������������������������0000664�0000000�0000000�00000030125�14737566223�0020523�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_ip_name.c * @brief The management of the IP/name cache. */ #include "config.h" #include "hashtable.h" #include "log.h" #include "nfs_core.h" #include "nfs_exports.h" #include "nfs_ip_stats.h" #include "config_parsing.h" #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> /* Hashtable used to cache the hostname, accessed by their IP address */ hash_table_t *ht_ip_name; unsigned int expiration_time; /** * @name Compute the hash value for the entry in IP/name cache * * @param[in] hparam Hash table parameter. * @param[in] buffcleff The hash key buffer * * @return the computed hash value. * * @see hashtable_init * */ uint32_t ip_name_value_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *buffclef) { return hash_sockaddr(buffclef->addr, true) % hparam->index_size; } /** * @brief Compute the rbt value for the entry in IP/name cache * * @param[in] hparam Hash table parameter * @param[in] buffclef Hash key buffer * * @return the computed rbt value. * * @see hashtable_init * */ uint64_t ip_name_rbt_hash_func(hash_parameter_t *hparam, struct gsh_buffdesc *buffclef) { return hash_sockaddr(buffclef->addr, true); } /** * * compare_ip_name: compares the ip address stored in the key buffers. * * compare the ip address stored in the key buffers. This function is to be used * as 'compare_key' field in the hashtable storing the nfs duplicated requests. * * @param buff1 [IN] first key * @param buff2 [IN] second key * * @return 0 if keys are identifical, 1 if they are different. * */ int compare_ip_name(struct gsh_buffdesc *buff1, struct gsh_buffdesc *buff2) { return (cmp_sockaddr(buff1->addr, buff2->addr, true) != 0) ? 0 : 1; } /** * @brief Display the ip_name stored in the buffer * * @param[in] dspbuf display buffer to display into * @param[in] buff Buffer to display */ int display_ip_name_key(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { return display_sockip(dspbuf, buff->addr); } /** * @brief Display the ip_name stored in the buffer * * @param[in] dspbuf display buffer to display into * @param[in] buff Buffer to display */ int display_ip_name_val(struct display_buffer *dspbuf, struct gsh_buffdesc *buff) { nfs_ip_name_t *nfs_ip_name = (buff->addr); return display_cat(dspbuf, nfs_ip_name->hostname); } /** * * nfs_ip_name_add: adds an entry into IP/name cache. * * Adds an entry in the duplicate requests cache. * * @param ipaddr[IN] the ipaddr to be used as key * @param hostname[OUT] the hostname added (found by using getnameinfo) * * @return IP_NAME_SUCCESS if successful * @return IP_NAME_INSERT_MALLOC_ERROR if an error occurred during the insertion * process * */ int nfs_ip_name_add(sockaddr_t *ipaddr, char *hostname, size_t maxsize) { struct gsh_buffdesc buffkey; struct gsh_buffdesc buffdata; nfs_ip_name_t *nfs_ip_name = NULL; struct timeval tv0, tv1, dur; int rc, len, size; char ipstring[SOCK_NAME_MAX]; char *hn = hostname; hash_error_t hash_rc; gettimeofday(&tv0, NULL); /* Speculatively get the hostname into our caller's buffer... */ rc = gsh_getnameinfo((struct sockaddr *)ipaddr, sizeof(sockaddr_t), hostname, maxsize, NULL, 0, 0, nfs_param.core_param.enable_AUTHSTATS); gettimeofday(&tv1, NULL); timersub(&tv1, &tv0, &dur); if (!sprint_sockip(ipaddr, ipstring, sizeof(ipstring))) { /* Error in converting socket address into string. */ return IP_NAME_INSERT_MALLOC_ERROR; } /* display warning if DNS resolution took more that 1.0s */ if (dur.tv_sec >= 1) { LogEvent(COMPONENT_DISPATCH, "Warning: long DNS query for %s: %u.%06u sec", ipstring, (unsigned int)dur.tv_sec, (unsigned int)dur.tv_usec); } /* Ask for the name to be cached */ if (rc != 0) { hn = ipstring; LogEvent( COMPONENT_DISPATCH, "Cannot resolve address %s, error %s, using address as hostname", ipstring, gai_strerror(rc)); if (maxsize < SOCK_NAME_MAX) { LogMajor( COMPONENT_DISPATCH, "Could not return ip address because caller's buffer was too small"); return IP_NAME_INSERT_MALLOC_ERROR; } /* And copy the ipstring out to the caller's buffer. */ strcpy(hostname, ipstring); } /* At this point, no matter what, the caller's buffer has been filled * with the hostname we would cache. */ /* I have to keep an integer as key, I will use the pointer * buffkey->addr for this, this also means that buffkey->len will be 0 */ buffkey.len = sizeof(sockaddr_t); buffkey.addr = gsh_memdup(ipaddr, buffkey.len); /* Now setup the cached hostname */ len = strlen(hn); size = sizeof(nfs_ip_name_t) + len + 1; nfs_ip_name = gsh_malloc(size); nfs_ip_name->timestamp = time(NULL); memcpy(nfs_ip_name->hostname, hn, len + 1); LogDebug(COMPONENT_DISPATCH, "Inserting %s->%s to addr cache", ipstring, hn); buffdata.addr = nfs_ip_name; buffdata.len = size; /* Multiple threads may try to add the same IP/name to cache. Need to * return success if we get HASHTABLE_ERROR_KEY_ALREADY_EXISTS * as return status */ hash_rc = HashTable_Set(ht_ip_name, &buffkey, &buffdata); /* No matter if we were able to cache or not, we either have a hostname * or it didn't work, so we will return the hostname from above which is * already in the caller's buffer. */ if (hash_rc != HASHTABLE_SUCCESS) { if (hash_rc != HASHTABLE_ERROR_KEY_ALREADY_EXISTS) { /* This should not happen */ LogEvent(COMPONENT_DISPATCH, "Error %s while adding host %s to cache", hash_table_err_to_str(hash_rc), hn); } /* Release not required allocations */ gsh_free(nfs_ip_name); gsh_free(buffkey.addr); } return IP_NAME_SUCCESS; } /* nfs_ip_name_add */ /** * * nfs_ip_name_get: Tries to get an entry for ip_name cache. * * Tries to get an entry for ip_name cache. * * @param ipaddr [IN] the ip address requested * @param hostname [OUT] the hostname * * @return the result previously set if *pstatus == IP_NAME_SUCCESS * */ int nfs_ip_name_get(sockaddr_t *ipaddr, char *hostname, size_t size) { struct gsh_buffdesc buffkey; struct gsh_buffdesc buffval; nfs_ip_name_t *nfs_ip_name; char ipstring[SOCK_NAME_MAX]; if (!sprint_sockip(ipaddr, ipstring, sizeof(ipstring))) { /* Error in converting socket address into string. */ return IP_NAME_NOT_FOUND; } buffkey.addr = ipaddr; buffkey.len = sizeof(sockaddr_t); if (HashTable_Get(ht_ip_name, &buffkey, &buffval) == HASHTABLE_SUCCESS) { nfs_ip_name = buffval.addr; if ((time(NULL) - nfs_ip_name->timestamp) > expiration_time) { LogFullDebug(COMPONENT_DISPATCH, "Found an expired host %s entry, removing", nfs_ip_name->hostname); if (HashTable_Del(ht_ip_name, &buffkey, NULL, &buffval) == HASHTABLE_SUCCESS) { nfs_ip_name = (nfs_ip_name_t *)buffval.addr; LogFullDebug(COMPONENT_DISPATCH, "Removing cache entry %s->%s", ipstring, nfs_ip_name->hostname); gsh_free(nfs_ip_name); } return IP_NAME_NOT_FOUND; } if (strlcpy(hostname, nfs_ip_name->hostname, size) >= size) { LogWarn(COMPONENT_DISPATCH, "Could not return host %s to caller, too big", nfs_ip_name->hostname); return IP_NAME_INSERT_MALLOC_ERROR; } LogFullDebug(COMPONENT_DISPATCH, "Cache get hit for %s->%s", ipstring, nfs_ip_name->hostname); return IP_NAME_SUCCESS; } LogFullDebug(COMPONENT_DISPATCH, "Cache get miss for %s", ipstring); return IP_NAME_NOT_FOUND; } /* nfs_ip_name_get */ /** * * nfs_ip_name_remove: Tries to remove an entry for ip_name cache * * Tries to remove an entry for ip_name cache. * * @param ipaddr [IN] the ip address to be uncached. * * @return the result previously set if *pstatus == IP_NAME_SUCCESS * */ int nfs_ip_name_remove(sockaddr_t *ipaddr) { struct gsh_buffdesc buffkey, old_value; nfs_ip_name_t *nfs_ip_name = NULL; char ipstring[SOCK_NAME_MAX]; if (!sprint_sockip(ipaddr, ipstring, sizeof(ipstring))) { /* Error in converting socket address into string. */ return IP_NAME_NOT_FOUND; } buffkey.addr = ipaddr; buffkey.len = sizeof(sockaddr_t); if (HashTable_Del(ht_ip_name, &buffkey, NULL, &old_value) == HASHTABLE_SUCCESS) { nfs_ip_name = (nfs_ip_name_t *)old_value.addr; LogFullDebug(COMPONENT_DISPATCH, "Cache remove hit for %s->%s", ipstring, nfs_ip_name->hostname); gsh_free(nfs_ip_name); return IP_NAME_SUCCESS; } LogFullDebug(COMPONENT_DISPATCH, "Cache remove miss for %s", ipstring); return IP_NAME_NOT_FOUND; } /* nfs_ip_name_remove */ /** * @defgroup config_ipnamemap Structure and defaults for NFS_IP_Name * * @{ */ /** * @brief Default index size for IP-Name hash */ #define PRIME_IP_NAME 17 /** * @brief Default value for ip_name_param.expiration-time */ #define IP_NAME_EXPIRATION 3600 /** @} */ /** * @brief NFS_IP_Name configuration stanza */ struct ip_name_cache { /** Configuration for hash table for NFS Name/IP map. Default index size is PRIME_IP_NAME, settable with Index_Size. */ hash_parameter_t hash_param; /** Expiration time for ip-name mappings. Defaults to IP_NAME_Expiration, and settable with Expiration_Time. */ uint32_t expiration_time; }; static struct ip_name_cache ip_name_cache = { .hash_param.hash_func_key = ip_name_value_hash_func, .hash_param.hash_func_rbt = ip_name_rbt_hash_func, .hash_param.compare_key = compare_ip_name, .hash_param.display_key = display_ip_name_key, .hash_param.display_val = display_ip_name_val, .hash_param.flags = HT_FLAG_NONE, }; /** * @brief IP name cache parameters */ static struct config_item ip_name_params[] = { CONF_ITEM_UI32("Index_Size", 1, 51, PRIME_IP_NAME, ip_name_cache, hash_param.index_size), CONF_ITEM_UI32("Expiration_Time", 1, 60 * 60 * 24, IP_NAME_EXPIRATION, ip_name_cache, expiration_time), CONFIG_EOL }; static void *ip_name_init(void *link_mem, void *self_struct) { if (self_struct == NULL) return &ip_name_cache; else return NULL; } static int ip_name_commit(void *node, void *link_mem, void *self_struct, struct config_error_type *err_type) { struct ip_name_cache *params = self_struct; if (!is_prime(params->hash_param.index_size)) { LogCrit(COMPONENT_CONFIG, "IP name cache index size must be a prime."); return 1; } return 0; } struct config_block nfs_ip_name = { .dbus_interface_name = "org.ganesha.nfsd.config.ip_name", .blk_desc.name = "NFS_IP_Name", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = ip_name_init, .blk_desc.u.blk.params = ip_name_params, .blk_desc.u.blk.commit = ip_name_commit }; /** * * nfs_Init_ip_name: Init the hashtable for IP/name cache. * * Perform all the required initialization for hashtable IP/name cache * * @return 0 if successful, -1 otherwise * */ int nfs_Init_ip_name(void) { ht_ip_name = hashtable_init(&ip_name_cache.hash_param); if (ht_ip_name == NULL) { LogCrit(COMPONENT_INIT, "NFS IP_NAME: Cannot init IP/name cache"); return -1; } /* Set the expiration time */ expiration_time = ip_name_cache.expiration_time; return IP_NAME_SUCCESS; } /* nfs_Init_ip_name */ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/nfs_read_conf.c���������������������������������������������������������0000664�0000000�0000000�00000053664�14737566223�0021050�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file nfs_read_conf.c * @brief This file tables required for parsing the NFS specific parameters. */ #include "config.h" #include <stdio.h> #include <string.h> #include <pthread.h> #include <fcntl.h> #include <sys/file.h> /* for having FNDELAY */ #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <ctype.h> #include "log.h" #include "gsh_rpc.h" #include "fsal.h" #include "nfs23.h" #include "nfs4.h" #include "mount.h" #include "nfs_core.h" #include "nfs_file_handle.h" #include "nfs_exports.h" #include "nfs_proto_functions.h" #include "nfs_dupreq.h" #include "config_parsing.h" /** * @brief Core configuration parameters */ static struct config_item_list udp_listener_type[] = { CONFIG_LIST_TOK("false", UDP_LISTENER_NONE), CONFIG_LIST_TOK("no", UDP_LISTENER_NONE), CONFIG_LIST_TOK("off", UDP_LISTENER_NONE), CONFIG_LIST_TOK("true", UDP_LISTENER_ALL), CONFIG_LIST_TOK("yes", UDP_LISTENER_ALL), CONFIG_LIST_TOK("on", UDP_LISTENER_ALL), CONFIG_LIST_TOK("mount", UDP_LISTENER_MOUNT), CONFIG_LIST_EOL }; /** * @brief host options for assigning root privileges */ static struct config_item_list root_kerberos_principal_options[] = { CONFIG_LIST_TOK("none", ROOT_KERBEROS_PRINCIPAL_NONE), CONFIG_LIST_TOK("nfs", ROOT_KERBEROS_PRINCIPAL_NFS), CONFIG_LIST_TOK("root", ROOT_KERBEROS_PRINCIPAL_ROOT), CONFIG_LIST_TOK("host", ROOT_KERBEROS_PRINCIPAL_HOST), CONFIG_LIST_TOK("all", ROOT_KERBEROS_PRINCIPAL_ALL), CONFIG_LIST_EOL }; static struct config_item_list protocols[] = { CONFIG_LIST_TOK("none", CORE_OPTION_NONE), #ifdef _USE_NFS3 CONFIG_LIST_TOK("3", CORE_OPTION_NFSV3), CONFIG_LIST_TOK("v3", CORE_OPTION_NFSV3), CONFIG_LIST_TOK("nfs3", CORE_OPTION_NFSV3), CONFIG_LIST_TOK("nfsv3", CORE_OPTION_NFSV3), #endif CONFIG_LIST_TOK("4", CORE_OPTION_NFSV4), CONFIG_LIST_TOK("v4", CORE_OPTION_NFSV4), CONFIG_LIST_TOK("nfs4", CORE_OPTION_NFSV4), CONFIG_LIST_TOK("nfsv4", CORE_OPTION_NFSV4), #ifdef RPC_VSOCK CONFIG_LIST_TOK("nfsvsock", CORE_OPTION_NFS_VSOCK), #endif #ifdef _USE_NFS_RDMA CONFIG_LIST_TOK("nfsrdma", CORE_OPTION_NFS_RDMA), CONFIG_LIST_TOK("rpcrdma", CORE_OPTION_NFS_RDMA), #endif #ifdef _USE_9P CONFIG_LIST_TOK("9p", CORE_OPTION_9P), #endif CONFIG_LIST_EOL }; /** * @brief Support all protocols */ #ifdef _USE_NFS3 #define DEFAULT_INCLUDES_NFSV3 CORE_OPTION_NFSV3 #else #define DEFAULT_INCLUDES_NFSV3 CORE_OPTION_NONE #endif #define DEFAULT_INCLUDES_NFSV4 CORE_OPTION_NFSV4 #define DEFAULT_PROTOCOLS (DEFAULT_INCLUDES_NFSV3 | DEFAULT_INCLUDES_NFSV4) #ifdef _USE_NFS_RDMA static struct config_item_list nfs_rdma_protocol_versions[] = { CONFIG_LIST_TOK("NONE", NFS_RDMA_ENABLE_FOR_NONE), CONFIG_LIST_TOK("3", NFS_RDMA_ENABLE_FOR_NFSV3), CONFIG_LIST_TOK("v3", NFS_RDMA_ENABLE_FOR_NFSV3), CONFIG_LIST_TOK("NFS3", NFS_RDMA_ENABLE_FOR_NFSV3), CONFIG_LIST_TOK("NFSv3", NFS_RDMA_ENABLE_FOR_NFSV3), CONFIG_LIST_TOK("4.0", NFS_RDMA_ENABLE_FOR_NFSV40), CONFIG_LIST_TOK("v4.0", NFS_RDMA_ENABLE_FOR_NFSV40), CONFIG_LIST_TOK("NFS4.0", NFS_RDMA_ENABLE_FOR_NFSV40), CONFIG_LIST_TOK("NFSv4.0", NFS_RDMA_ENABLE_FOR_NFSV40), #ifdef _USE_NFS_RDMA_UNUSED /* Enable 4.1, v4.1, NFS4.1, NFSv4.1, 4.2, v4.2, NFS4.2, NFSv4.2 * once Ganesha starts supporting these versions */ CONFIG_LIST_TOK("4.1", NFS_RDMA_ENABLE_FOR_NFSV41), CONFIG_LIST_TOK("v4.1", NFS_RDMA_ENABLE_FOR_NFSV41), CONFIG_LIST_TOK("NFS4.1", NFS_RDMA_ENABLE_FOR_NFSV41), CONFIG_LIST_TOK("NFSv4.1", NFS_RDMA_ENABLE_FOR_NFSV41), CONFIG_LIST_TOK("4.2", NFS_RDMA_ENABLE_FOR_NFSV42), CONFIG_LIST_TOK("v4.2", NFS_RDMA_ENABLE_FOR_NFSV42), CONFIG_LIST_TOK("NFS4.2", NFS_RDMA_ENABLE_FOR_NFSV42), CONFIG_LIST_TOK("NFSv4.2", NFS_RDMA_ENABLE_FOR_NFSV42), #endif CONFIG_LIST_TOK("ALL", NFS_RDMA_ENABLE_FOR_ALL), CONFIG_LIST_EOL }; #endif /** * @brief Process a list of hosts for haproxy_hosts * * CONFIG_PROC handler that gets called for each token in the term list. * Create a exportlist_client_entry for each token and link it into * the proto host's cle_list list head. We will pass that head to the * export in commit. * * NOTES: this is the place to expand a node list with perhaps moving the * call to add_client into the expander rather than build a list there * to be then walked here... * * @param token [IN] pointer to token string from parse tree * @param type_hint [IN] a type hint from what the parser recognized * @param item [IN] pointer to the config item table entry * @param param_addr [IN] pointer to prototype host entry * @param err_type [OUT] error handling * @return error count */ static int haproxy_host_adder(const char *token, enum term_type type_hint, struct config_item *item, void *param_addr, void *cnode, struct config_error_type *err_type) { struct base_client_entry *host; int rc; host = container_of(param_addr, struct base_client_entry, cle_list); LogMidDebug(COMPONENT_CONFIG, "Adding host %s", token); rc = add_client(COMPONENT_CONFIG, &host->cle_list, token, type_hint, cnode, err_type, NULL, NULL, NULL); return rc; } static struct config_item core_params[] = { CONF_ITEM_PROC_MULT("HAProxy_Hosts", noop_conf_init, haproxy_host_adder, base_client_entry, cle_list), CONF_ITEM_UI16("NFS_Port", 0, UINT16_MAX, NFS_PORT, nfs_core_param, port[P_NFS]), #ifdef _USE_NFS3 CONF_ITEM_UI16("MNT_Port", 0, UINT16_MAX, 0, nfs_core_param, port[P_MNT]), #endif #ifdef _USE_NLM CONF_ITEM_UI16("NLM_Port", 0, UINT16_MAX, 0, nfs_core_param, port[P_NLM]), #endif #ifdef _USE_RQUOTA CONF_ITEM_UI16("Rquota_Port", 0, UINT16_MAX, RQUOTA_PORT, nfs_core_param, port[P_RQUOTA]), #endif #ifdef _USE_NFS_RDMA CONF_ITEM_UI16("NFS_RDMA_Port", 0, UINT16_MAX, NFS_RDMA_PORT, nfs_core_param, port[P_NFS_RDMA]), CONF_ITEM_LIST("NFS_RDMA_Protocol_Versions", NFS_RDMA_ENABLE_BY_DEFAULT, nfs_rdma_protocol_versions, nfs_core_param, nfs_rdma_supported_protocol_versions), #endif CONF_ITEM_IP_ADDR("Bind_Addr", "0.0.0.0", nfs_core_param, bind_addr), CONF_ITEM_UI32("NFS_Program", 1, INT32_MAX, NFS_PROGRAM, nfs_core_param, program[P_NFS]), #ifdef _USE_NFS3 CONF_ITEM_UI32("MNT_Program", 1, INT32_MAX, MOUNTPROG, nfs_core_param, program[P_MNT]), #endif #ifdef _USE_NLM CONF_ITEM_UI32("NLM_Program", 1, INT32_MAX, NLMPROG, nfs_core_param, program[P_NLM]), #endif #ifdef _USE_RQUOTA CONF_ITEM_UI32("Rquota_Program", 1, INT32_MAX, RQUOTAPROG, nfs_core_param, program[P_RQUOTA]), #endif #ifdef USE_NFSACL3 CONF_ITEM_UI32("NFSACL_Program", 1, INT32_MAX, NFSACLPROG, nfs_core_param, program[P_NFSACL]), #endif CONF_ITEM_DEPRECATED( "Nb_Worker", "This parameter has been replaced with _9P { Nb_Worker}"), CONF_ITEM_BOOL("Drop_IO_Errors", false, nfs_core_param, drop_io_errors), CONF_ITEM_BOOL("Drop_Inval_Errors", false, nfs_core_param, drop_inval_errors), CONF_ITEM_BOOL("Drop_Delay_Errors", false, nfs_core_param, drop_delay_errors), CONF_ITEM_BOOL("DRC_Disabled", false, nfs_core_param, drc.disabled), CONF_ITEM_UI32("DRC_Recycle_Hiwat", 1, 1000000, DRC_RECYCLE_HIWAT, nfs_core_param, drc.recycle_hiwat), CONF_ITEM_UI32("DRC_TCP_Npart", 1, 20, DRC_TCP_NPART, nfs_core_param, drc.tcp.npart), CONF_ITEM_UI32("DRC_TCP_Size", 1, 32767, DRC_TCP_SIZE, nfs_core_param, drc.tcp.size), CONF_ITEM_UI32("DRC_TCP_Cachesz", 1, 255, DRC_TCP_CACHESZ, nfs_core_param, drc.tcp.cachesz), CONF_ITEM_UI32("DRC_TCP_Hiwat", 1, 256, DRC_TCP_HIWAT, nfs_core_param, drc.tcp.hiwat), CONF_ITEM_UI32("DRC_TCP_Recycle_Npart", 1, 20, DRC_TCP_RECYCLE_NPART, nfs_core_param, drc.tcp.recycle_npart), CONF_ITEM_UI32("DRC_TCP_Recycle_Expire_S", 0, 60 * 60, 600, nfs_core_param, drc.tcp.recycle_expire_s), CONF_ITEM_BOOL("DRC_TCP_Checksum", DRC_TCP_CHECKSUM, nfs_core_param, drc.tcp.checksum), CONF_ITEM_UI32("DRC_UDP_Npart", 1, 100, DRC_UDP_NPART, nfs_core_param, drc.udp.npart), CONF_ITEM_UI32("DRC_UDP_Size", 512, 32768, DRC_UDP_SIZE, nfs_core_param, drc.udp.size), CONF_ITEM_UI32("DRC_UDP_Cachesz", 1, 2047, DRC_UDP_CACHESZ, nfs_core_param, drc.udp.cachesz), CONF_ITEM_UI32("DRC_UDP_Hiwat", 1, 32768, DRC_UDP_HIWAT, nfs_core_param, drc.udp.hiwat), CONF_ITEM_BOOL("DRC_UDP_Checksum", DRC_UDP_CHECKSUM, nfs_core_param, drc.udp.checksum), CONF_ITEM_UI32("RPC_Max_Connections", 1, 1000000, 1024, nfs_core_param, rpc.max_connections), CONF_ITEM_UI32("RPC_Idle_Timeout_S", 0, 60 * 60, 300, nfs_core_param, rpc.idle_timeout_s), CONF_ITEM_UI32("MaxRPCSendBufferSize", 1, 1048576 * 9, NFS_DEFAULT_SEND_BUFFER_SIZE, nfs_core_param, rpc.max_send_buffer_size), CONF_ITEM_UI32("MaxRPCRecvBufferSize", 1, 1048576 * 9, NFS_DEFAULT_RECV_BUFFER_SIZE, nfs_core_param, rpc.max_recv_buffer_size), #ifdef _USE_NFS_RDMA CONF_ITEM_UI32("RPC_Max_RDMA_Connections", 1, 1024, 64, nfs_core_param, rpc.max_rdma_connections), CONF_ITEM_UI32("MaxRPCRdmaCredits", 1, 4096, 64, nfs_core_param, rpc.rdma_credits), #endif CONF_ITEM_UI32("rpc_ioq_thrdmin", 2, 1024 * 128, 2, nfs_core_param, rpc.ioq_thrd_min), CONF_ITEM_UI32("RPC_Ioq_ThrdMax", 2, 1024 * 128, 200, nfs_core_param, rpc.ioq_thrd_max), CONF_ITEM_UI32("RPC_GSS_Npart", 1, 1021, 13, nfs_core_param, rpc.gss.ctx_hash_partitions), CONF_ITEM_UI32("RPC_GSS_Max_Ctx", 1, 1024 * 1024, 16384, nfs_core_param, rpc.gss.max_ctx), CONF_ITEM_UI32("RPC_GSS_Max_GC", 1, 1024 * 1024, 200, nfs_core_param, rpc.gss.max_gc), CONF_ITEM_I64("Blocked_Lock_Poller_Interval", 0, 180, 10, nfs_core_param, blocked_lock_poller_interval), CONF_ITEM_LIST("NFS_Protocols", DEFAULT_PROTOCOLS, protocols, nfs_core_param, core_options), CONF_ITEM_LIST("Protocols", DEFAULT_PROTOCOLS, protocols, nfs_core_param, core_options), CONF_ITEM_BOOL("Clustered", true, nfs_core_param, clustered), #ifdef _USE_NLM CONF_ITEM_BOOL("Enable_NLM", true, nfs_core_param, enable_NLM), CONF_ITEM_BOOL("Disable_NLM_SHARE", false, nfs_core_param, disable_NLM_SHARE), CONF_ITEM_BOOL("NSM_Use_Caller_Name", false, nfs_core_param, nsm_use_caller_name), #endif #ifdef _USE_RQUOTA CONF_ITEM_BOOL("Enable_RQUOTA", true, nfs_core_param, enable_RQUOTA), #endif #ifdef USE_NFSACL3 CONF_ITEM_BOOL("Enable_NFSACL", false, nfs_core_param, enable_NFSACL), #endif CONF_ITEM_BOOL("Enable_TCP_keepalive", true, nfs_core_param, enable_tcp_keepalive), CONF_ITEM_UI32("TCP_KEEPCNT", 0, 255, 0, nfs_core_param, tcp_keepcnt), CONF_ITEM_UI32("TCP_KEEPIDLE", 0, 65535, 0, nfs_core_param, tcp_keepidle), CONF_ITEM_UI32("TCP_KEEPINTVL", 0, 65535, 0, nfs_core_param, tcp_keepintvl), CONF_ITEM_BOOL("Enable_NFS_Stats", true, nfs_core_param, enable_NFSSTATS), CONF_ITEM_BOOL("Enable_Fast_Stats", false, nfs_core_param, enable_FASTSTATS), CONF_ITEM_BOOL("Enable_FSAL_Stats", false, nfs_core_param, enable_FSALSTATS), #ifdef _USE_NFS3 CONF_ITEM_BOOL("Enable_FULLV3_Stats", false, nfs_core_param, enable_FULLV3STATS), #endif CONF_ITEM_BOOL("Enable_FULLV4_Stats", false, nfs_core_param, enable_FULLV4STATS), CONF_ITEM_BOOL("Enable_AUTH_Stats", false, nfs_core_param, enable_AUTHSTATS), CONF_ITEM_BOOL("Enable_CLNT_AllOps_Stats", false, nfs_core_param, enable_CLNTALLSTATS), CONF_ITEM_BOOL("Short_File_Handle", false, nfs_core_param, short_file_handle), CONF_ITEM_I64("Manage_Gids_Expiration", 0, 7 * 24 * 60 * 60, 30 * 60, nfs_core_param, manage_gids_expiration), CONF_ITEM_PATH("Plugins_Dir", 1, MAXPATHLEN, FSAL_MODULE_LOC, nfs_core_param, ganesha_modules_loc), CONF_ITEM_UI32("heartbeat_freq", 0, 5000, 1000, nfs_core_param, heartbeat_freq), CONF_ITEM_BOOL("fsid_device", false, nfs_core_param, fsid_device), CONF_ITEM_UI32("resolve_fs_retries", 1, 1000, 10, nfs_core_param, resolve_fs_retries), CONF_ITEM_UI32("resolve_fs_delay", 1, 1000, 100, nfs_core_param, resolve_fs_delay), CONF_ITEM_BOOL("mount_path_pseudo", false, nfs_core_param, mount_path_pseudo), CONF_ITEM_ENUM_BITS("Enable_UDP", UDP_LISTENER_ALL, UDP_LISTENER_MASK, udp_listener_type, nfs_core_param, enable_UDP), CONF_ITEM_STR("Dbus_Name_Prefix", 1, 255, NULL, nfs_core_param, dbus_name_prefix), CONF_ITEM_UI32("Max_Uid_To_Group_Reqs", 0, INT32_MAX, 0, nfs_core_param, max_uid_to_grp_reqs), CONF_ITEM_BOOL("Enable_V3fh_Validation_For_V4", false, nfs_core_param, enable_v3_fh_for_v4), CONF_ITEM_UI32("Readdir_Res_Size", 4096, FSAL_MAXIOSIZE, 32 * 1024, nfs_core_param, readdir_res_size), CONF_ITEM_UI32("Readdir_Max_Count", 32, 1024 * 1024, 1024 * 1024, nfs_core_param, readdir_max_count), CONF_ITEM_BOOL("Getattrs_In_Complete_Read", true, nfs_core_param, getattrs_in_complete_read), CONF_ITEM_BOOL("Enable_malloc_trim", false, nfs_core_param, malloc_trim), CONF_ITEM_UI32("Malloc_trim_MinThreshold", 1, INT32_MAX, 15 * 1024, nfs_core_param, malloc_trim_minthreshold), #ifdef USE_MONITORING CONF_ITEM_UI16("Monitoring_Port", 0, UINT16_MAX, MONITORING_PORT, nfs_core_param, monitoring_port), CONF_ITEM_BOOL("Enable_Dynamic_Metrics", true, nfs_core_param, enable_dynamic_metrics), #endif CONF_ITEM_BOOL("enable_rpc_cred_fallback", false, nfs_core_param, enable_rpc_cred_fallback), CONF_ITEM_UI32("Unique_Server_Id", 0, UINT32_MAX, 0, nfs_core_param, unique_server_id), CONF_ITEM_BOOL("Enable_Connection_Manager", false, nfs_core_param, enable_connection_manager), CONF_ITEM_UI32("Connection_Manager_Timeout_sec", 0, UINT32_MAX, 2 * 60, nfs_core_param, connection_manager_timeout_sec), CONF_ITEM_BOOL("Allow_Set_Io_Flusher_Fail", false, nfs_core_param, allow_set_io_flusher_fail), CONFIG_EOL }; struct config_block nfs_core = { .dbus_interface_name = "org.ganesha.nfsd.config.core", .blk_desc.name = "NFS_Core_Param", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = core_params, .blk_desc.u.blk.commit = noop_conf_commit }; /** * @brief Kerberos/GSSAPI parameters */ #ifdef _HAVE_GSSAPI static struct config_item krb5_params[] = { CONF_ITEM_STR("PrincipalName", 1, MAXPATHLEN, DEFAULT_NFS_PRINCIPAL, nfs_krb5_param, svc.principal), CONF_ITEM_PATH("KeytabPath", 1, MAXPATHLEN, DEFAULT_NFS_KEYTAB, nfs_krb5_param, keytab), CONF_ITEM_PATH("CCacheDir", 1, MAXPATHLEN, DEFAULT_NFS_CCACHE_DIR, nfs_krb5_param, ccache_dir), CONF_ITEM_BOOL("Active_krb5", true, nfs_krb5_param, active_krb5), CONFIG_EOL }; struct config_block krb5_param = { .dbus_interface_name = "org.ganesha.nfsd.config.krb5", .blk_desc.name = "NFS_KRB5", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = krb5_params, .blk_desc.u.blk.commit = noop_conf_commit }; #endif static struct config_item directory_services_params[] = { CONF_ITEM_STR("DomainName", 1, MAXPATHLEN, NULL, directory_services_param, domainname), CONF_ITEM_BOOL("Idmapping_Active", true, directory_services_param, idmapping_active), CONF_ITEM_I64("Idmapped_User_Time_Validity", -1, INT64_MAX, -1, directory_services_param, idmapped_user_time_validity), CONF_ITEM_I64("Idmapped_Group_Time_Validity", -1, INT64_MAX, -1, directory_services_param, idmapped_group_time_validity), CONF_ITEM_LIST("Root_Kerberos_Principal", ROOT_KERBEROS_PRINCIPAL_DEFAULT, root_kerberos_principal_options, directory_services_param, root_kerberos_principal), CONF_ITEM_UI32("Cache_Users_Max_Count", 0, INT32_MAX, INT32_MAX, directory_services_param, cache_users_max_count), CONF_ITEM_UI32("Cache_Groups_Max_Count", 0, INT32_MAX, INT32_MAX, directory_services_param, cache_groups_max_count), CONF_ITEM_UI32("Cache_User_Groups_Max_Count", 0, INT32_MAX, INT32_MAX, directory_services_param, cache_user_groups_max_count), CONF_ITEM_I64("Negative_Cache_Time_Validity", 0, INT64_MAX, 5 * 60, directory_services_param, negative_cache_time_validity), CONF_ITEM_UI32("Negative_Cache_Users_Max_Count", 0, INT32_MAX, 50000, directory_services_param, negative_cache_users_max_count), CONF_ITEM_UI32("Negative_Cache_Groups_Max_Count", 0, INT32_MAX, 50000, directory_services_param, negative_cache_groups_max_count), CONF_ITEM_I64("Cache_Reaping_Interval", 0, 3650 * 86400, 0, directory_services_param, cache_reaping_interval), CONF_ITEM_BOOL("Pwutils_Use_Fully_Qualified_Names", false, directory_services_param, pwutils_use_fully_qualified_names), CONFIG_EOL }; struct config_block directory_services_param = { .dbus_interface_name = "org.ganesha.nfsd.config.directory_services", .blk_desc.name = "DIRECTORY_SERVICES", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = directory_services_params, .blk_desc.u.blk.commit = noop_conf_commit }; #ifdef USE_NFSIDMAP #define GETPWNAMDEF false #else #define GETPWNAMDEF true #endif /** * @brief NFSv4 specific parameters */ static struct config_item_list minor_versions[] = { CONFIG_LIST_TOK("0", NFSV4_MINOR_VERSION_ZERO), CONFIG_LIST_TOK("1", NFSV4_MINOR_VERSION_ONE), CONFIG_LIST_TOK("2", NFSV4_MINOR_VERSION_TWO), CONFIG_LIST_EOL }; static struct config_item_list recovery_backend_types[] = { CONFIG_LIST_TOK("fs", RECOVERY_BACKEND_FS), CONFIG_LIST_TOK("fs_ng", RECOVERY_BACKEND_FS_NG), CONFIG_LIST_TOK("rados_kv", RECOVERY_BACKEND_RADOS_KV), CONFIG_LIST_TOK("rados_ng", RECOVERY_BACKEND_RADOS_NG), CONFIG_LIST_TOK("rados_cluster", RECOVERY_BACKEND_RADOS_CLUSTER), CONFIG_LIST_EOL }; static struct config_item version4_params[] = { CONF_ITEM_BOOL("Sticky_Grace", false, nfs_version4_parameter, sticky_grace), CONF_ITEM_BOOL("Graceless", false, nfs_version4_parameter, graceless), CONF_ITEM_UI32("Lease_Lifetime", 1, 180, LEASE_LIFETIME_DEFAULT, nfs_version4_parameter, lease_lifetime), CONF_ITEM_UI32("Grace_Period", 0, 270, GRACE_PERIOD_DEFAULT, nfs_version4_parameter, grace_period), CONF_ITEM_STR("Server_Scope", 1, MAXNAMLEN, NULL, nfs_version4_parameter, server_scope), CONF_ITEM_STR("Server_Owner", 1, MAXNAMLEN, NULL, nfs_version4_parameter, server_owner), CONF_ITEM_STR("DomainName", 1, MAXPATHLEN, DOMAINNAME_DEFAULT, nfs_version4_parameter, domainname), CONF_ITEM_PATH("IdmapConf", 1, MAXPATHLEN, IDMAPCONF_DEFAULT, nfs_version4_parameter, idmapconf), CONF_ITEM_BOOL("UseGetpwnam", GETPWNAMDEF, nfs_version4_parameter, use_getpwnam), CONF_ITEM_BOOL("Allow_Numeric_Owners", true, nfs_version4_parameter, allow_numeric_owners), CONF_ITEM_BOOL("Only_Numeric_Owners", false, nfs_version4_parameter, only_numeric_owners), CONF_ITEM_BOOL("Delegations", false, nfs_version4_parameter, allow_delegations), CONF_ITEM_UI32("Deleg_Recall_Retry_Delay", 0, 10, DELEG_RECALL_RETRY_DELAY_DEFAULT, nfs_version4_parameter, deleg_recall_retry_delay), CONF_ITEM_BOOL("PNFS_MDS", false, nfs_version4_parameter, pnfs_mds), CONF_ITEM_BOOL("PNFS_DS", false, nfs_version4_parameter, pnfs_ds), CONF_ITEM_TOKEN("RecoveryBackend", RECOVERY_BACKEND_DEFAULT, recovery_backend_types, nfs_version4_parameter, recovery_backend), CONF_ITEM_PATH("RecoveryRoot", 1, MAXPATHLEN, NFS_V4_RECOV_ROOT, nfs_version4_parameter, recov_root), CONF_ITEM_PATH("RecoveryDir", 1, MAXNAMLEN, NFS_V4_RECOV_DIR, nfs_version4_parameter, recov_dir), CONF_ITEM_PATH("RecoveryOldDir", 1, MAXNAMLEN, NFS_V4_OLD_DIR, nfs_version4_parameter, recov_old_dir), CONF_ITEM_LIST("minor_versions", NFSV4_MINOR_VERSION_ALL, minor_versions, nfs_version4_parameter, minor_versions), CONF_ITEM_UI32("slot_table_size", 1, 1024, NFS41_NB_SLOTS_DEF, nfs_version4_parameter, nb_slots), CONF_ITEM_BOOL("Enforce_UTF8_Validation", false, nfs_version4_parameter, enforce_utf8_vld), CONF_ITEM_UI32("Max_Client_Ids", 0, UINT32_MAX, 0, nfs_version4_parameter, max_client_ids), CONF_ITEM_UI32("Max_Open_States_Per_Client", 0, UINT32_MAX, 0, nfs_version4_parameter, max_open_states_per_client), CONF_ITEM_UI32("Expired_Client_Threshold", 0, 256, 16, nfs_version4_parameter, expired_client_threshold), CONF_ITEM_UI32("Max_Open_Files_For_Expired_Client", 0, UINT32_MAX, 4000, nfs_version4_parameter, max_open_files_for_expired_client), CONF_ITEM_UI64("Max_Alive_Time_For_Expired_Client", 0, UINT64_MAX, 86400, nfs_version4_parameter, max_alive_time_for_expired_client), CONFIG_EOL }; struct config_block version4_param = { .dbus_interface_name = "org.ganesha.nfsd.config.nfsv4", .blk_desc.name = "NFSv4", .blk_desc.type = CONFIG_BLOCK, .blk_desc.flags = CONFIG_UNIQUE, /* too risky to have more */ .blk_desc.u.blk.init = noop_conf_init, .blk_desc.u.blk.params = version4_params, .blk_desc.u.blk.commit = noop_conf_commit }; ����������������������������������������������������������������������������nfs-ganesha-6.5/src/support/rados_grace.c�����������������������������������������������������������0000664�0000000�0000000�00000044444�14737566223�0020527�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2018 Red Hat, Inc. and/or its affiliates. * Author: Jeff Layton <jlayton@redhat.com> * * This program 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 3 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 * 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 */ #include "config.h" #include <stdio.h> #include <stdint.h> #include <endian.h> #include <rados/librados.h> #include <errno.h> #include <getopt.h> #include <stdlib.h> #include <limits.h> #include <stdbool.h> /* Each cluster node needs a slot here */ #define MAX_ITEMS 1024 /* Flags for the omap value flags field */ /* Does this node currently require a grace period? */ #define RADOS_GRACE_NEED_GRACE 0x1 /* Is this node currently enforcing its grace period locally? */ #define RADOS_GRACE_ENFORCING 0x2 static void rados_grace_notify(rados_ioctx_t io_ctx, const char *oid) { static char *buf; static size_t len; /* FIXME: we don't really want or need this to be synchronous */ rados_notify2(io_ctx, oid, "", 0, 3000, &buf, &len); rados_buffer_free(buf); } int rados_grace_create(rados_ioctx_t io_ctx, const char *oid) { int ret; rados_write_op_t op = NULL; uint64_t cur = htole64(1); // starting epoch uint64_t rec = htole64(0); // no recovery yet char buf[sizeof(uint64_t) * 2]; /* * 2 uint64_t's * * The first denotes the current epoch serial number, the epoch serial * number under which new recovery records should be created. * * The second number denotes the epoch from which clients are allowed * to reclaim. * * An epoch of zero is never allowed, so if rec=0, then the grace * period is no longer in effect, and can't be joined. */ memcpy(buf, (char *)&cur, sizeof(cur)); memcpy(buf + sizeof(cur), (char *)&rec, sizeof(rec)); op = rados_create_write_op(); /* Create the object */ rados_write_op_create(op, LIBRADOS_CREATE_EXCLUSIVE, NULL); /* Set serial numbers if we created the object */ rados_write_op_write_full(op, buf, sizeof(buf)); ret = rados_write_op_operate(op, io_ctx, oid, NULL, 0); rados_release_write_op(op); return ret; } int rados_grace_dump(rados_ioctx_t io_ctx, const char *oid, FILE *stream) { int ret; rados_omap_iter_t iter; rados_read_op_t op; char *key_out = NULL; char *val_out = NULL; unsigned char more = '\0'; size_t len_out = 0; char buf[sizeof(uint64_t) * 2]; uint64_t cur, rec; op = rados_create_read_op(); rados_read_op_read(op, 0, sizeof(buf), buf, &len_out, NULL); rados_read_op_omap_get_vals2(op, "", "", MAX_ITEMS, &iter, &more, NULL); ret = rados_read_op_operate(op, io_ctx, oid, 0); if (ret < 0) goto out; if (len_out != sizeof(buf)) { ret = -ENOTRECOVERABLE; goto out; } if (more) { ret = -ENOTRECOVERABLE; goto out; } cur = le64toh(*(uint64_t *)buf); rec = le64toh(*(uint64_t *)(buf + sizeof(uint64_t))); fprintf(stream, "cur=%lu rec=%lu\n", cur, rec); fprintf(stream, "======================================================\n"); for (;;) { char need = ' ', enforcing = ' '; rados_omap_get_next(iter, &key_out, &val_out, &len_out); if (key_out == NULL || val_out == NULL) break; if (*val_out & RADOS_GRACE_NEED_GRACE) need = 'N'; if (*val_out & RADOS_GRACE_ENFORCING) enforcing = 'E'; fprintf(stream, "%s\t%c%c\n", key_out, need, enforcing); } rados_omap_get_end(iter); out: rados_release_read_op(op); return ret; } int rados_grace_epochs(rados_ioctx_t io_ctx, const char *oid, uint64_t *cur, uint64_t *rec) { int ret; rados_read_op_t op; size_t len_out = 0; char buf[sizeof(uint64_t) * 2]; op = rados_create_read_op(); rados_read_op_read(op, 0, sizeof(buf), buf, &len_out, NULL); ret = rados_read_op_operate(op, io_ctx, oid, 0); if (ret < 0) goto out; ret = -ENOTRECOVERABLE; if (len_out != sizeof(buf)) goto out; *cur = le64toh(*(uint64_t *)buf); *rec = le64toh(*(uint64_t *)(buf + sizeof(uint64_t))); ret = 0; out: rados_release_read_op(op); return ret; } int rados_grace_enforcing_toggle(rados_ioctx_t io_ctx, const char *oid, int nodes, const char *const *nodeids, uint64_t *pcur, uint64_t *prec, bool enable) { int i, ret; char *flags = NULL; char **vals = NULL; size_t *lens = NULL; bool *match = NULL; uint64_t cur, rec, ver; /* allocate an array of flag bytes */ flags = calloc(nodes, 1); if (!flags) { ret = -ENOMEM; goto out; } /* pointers to each val byte */ vals = calloc(nodes, sizeof(char *)); if (!vals) { ret = -ENOMEM; goto out; } /* lengths */ lens = calloc(nodes, sizeof(size_t)); if (!lens) { ret = -ENOMEM; goto out; } match = calloc(nodes, sizeof(bool)); if (!match) { ret = -ENOMEM; goto out; } /* each val is one flag byte */ for (i = 0; i < nodes; ++i) { vals[i] = &flags[i]; lens[i] = 1; } do { rados_write_op_t wop; rados_read_op_t rop; rados_omap_iter_t iter; size_t len = 0; unsigned char more = 0; char buf[sizeof(uint64_t) * 2]; /* read epoch blob */ rop = rados_create_read_op(); rados_read_op_read(rop, 0, sizeof(buf), buf, &len, NULL); rados_read_op_omap_get_vals2(rop, "", "", MAX_ITEMS, &iter, &more, NULL); ret = rados_read_op_operate(rop, io_ctx, oid, 0); if (ret < 0) { rados_release_read_op(rop); break; } if (more || (len != sizeof(buf))) { ret = -ENOTRECOVERABLE; rados_release_read_op(rop); break; } ver = rados_get_last_version(io_ctx); /* * Walk the returned kv pairs and flip on any existing flags * in the matching nodeid (if there is one) */ for (;;) { char *key, *val; rados_omap_get_next(iter, &key, &val, &len); if (!key) break; for (i = 0; i < nodes; ++i) { if (strcmp(key, nodeids[i])) continue; flags[i] = *val; match[i] = true; if (enable) flags[i] |= RADOS_GRACE_ENFORCING; else flags[i] &= ~RADOS_GRACE_ENFORCING; break; } } rados_omap_get_end(iter); rados_release_read_op(rop); /* Ensure that all given nodes have a key in the omap */ for (i = 0; i < nodes; ++i) { if (!match[i]) { ret = -ENOKEY; goto out; } } /* Get old epoch numbers and version */ cur = le64toh(*(uint64_t *)buf); rec = le64toh(*(uint64_t *)(buf + sizeof(uint64_t))); /* Attempt to update object */ wop = rados_create_write_op(); /* Ensure that nothing has changed */ rados_write_op_assert_version(wop, ver); /* Set omap values to given ones */ rados_write_op_omap_set(wop, nodeids, (const char *const *)vals, lens, nodes); ret = rados_write_op_operate(wop, io_ctx, oid, NULL, 0); rados_release_write_op(wop); if (ret >= 0) rados_grace_notify(io_ctx, oid); } while (ret == -ERANGE); if (!ret) { *pcur = cur; *prec = rec; } out: free(match); free(lens); free(flags); free(vals); return ret; } int rados_grace_enforcing_check(rados_ioctx_t io_ctx, const char *oid, const char *nodeid) { int ret; rados_read_op_t rop; rados_omap_iter_t iter; unsigned char more = 0; rop = rados_create_read_op(); rados_read_op_omap_get_vals2(rop, "", "", MAX_ITEMS, &iter, &more, NULL); ret = rados_read_op_operate(rop, io_ctx, oid, 0); if (ret < 0) { rados_release_read_op(rop); goto out; } if (more) { ret = -ENOTRECOVERABLE; rados_release_read_op(rop); goto out; } ret = -ENOKEY; for (;;) { char *key, *val; size_t len = 0; rados_omap_get_next(iter, &key, &val, &len); if (!key) break; /* If anyone isn't enforcing, then return an err */ if (!(*val & RADOS_GRACE_ENFORCING)) { ret = -EL2NSYNC; break; } /* Only return 0 if this node is in the omap */ if (!strcmp(nodeid, key)) ret = 0; } rados_omap_get_end(iter); rados_release_read_op(rop); out: return ret; } int rados_grace_join_bulk(rados_ioctx_t io_ctx, const char *oid, int nodes, const char *const *nodeids, uint64_t *pcur, uint64_t *prec, bool start) { int i, ret; char *flags = NULL; char **vals = NULL; size_t *lens = NULL; bool *match = NULL; uint64_t cur, rec, ver; /* flag bytes */ flags = malloc(nodes); if (!flags) { ret = -ENOMEM; goto out; } /* pointers to each val byte */ vals = calloc(nodes, sizeof(char *)); if (!vals) { ret = -ENOMEM; goto out; } /* lengths */ lens = calloc(nodes, sizeof(size_t)); if (!lens) { ret = -ENOMEM; goto out; } match = calloc(nodes, sizeof(bool)); if (!match) { ret = -ENOMEM; goto out; } /* each val is one flag byte */ for (i = 0; i < nodes; ++i) { vals[i] = &flags[i]; lens[i] = 1; } do { rados_write_op_t wop; rados_read_op_t rop; rados_omap_iter_t iter; size_t len = 0; unsigned char more = 0; char buf[sizeof(uint64_t) * 2]; /* read epoch blob */ rop = rados_create_read_op(); rados_read_op_read(rop, 0, sizeof(buf), buf, &len, NULL); rados_read_op_omap_get_vals2(rop, "", "", MAX_ITEMS, &iter, &more, NULL); ret = rados_read_op_operate(rop, io_ctx, oid, 0); if (ret < 0) { rados_release_read_op(rop); break; } if (more || (len != sizeof(buf))) { ret = -ENOTRECOVERABLE; rados_release_read_op(rop); break; } ver = rados_get_last_version(io_ctx); /* * Walk the returned kv pairs and flip on any existing flags * in the matching nodeid (if there is one) */ memset(flags, RADOS_GRACE_NEED_GRACE | RADOS_GRACE_ENFORCING, nodes); for (;;) { char *key, *val; rados_omap_get_next(iter, &key, &val, &len); if (!key) break; for (i = 0; i < nodes; ++i) { if (!strcmp(key, nodeids[i])) { flags[i] |= *val; match[i] = true; break; } } } rados_omap_get_end(iter); rados_release_read_op(rop); /* Ensure that all given nodes have a key in the omap */ for (i = 0; i < nodes; ++i) { if (!match[i]) { ret = -ENOKEY; goto out; } } /* Get old epoch numbers and version */ cur = le64toh(*(uint64_t *)buf); rec = le64toh(*(uint64_t *)(buf + sizeof(uint64_t))); /* Only start a new grace period if start bool is set */ /* FIXME: do we need this with real membership? */ if (rec == 0 && !start) break; /* Attempt to update object */ wop = rados_create_write_op(); /* Ensure that nothing has changed */ rados_write_op_assert_version(wop, ver); /* Update the object data iff rec == 0 */ if (rec == 0) { uint64_t tc, tr; rec = cur; ++cur; tc = htole64(cur); tr = htole64(rec); memcpy(buf, (char *)&tc, sizeof(tc)); memcpy(buf + sizeof(tc), (char *)&tr, sizeof(tr)); rados_write_op_write_full(wop, buf, sizeof(buf)); } /* Set omap values to given ones */ rados_write_op_omap_set(wop, nodeids, (const char *const *)vals, lens, nodes); ret = rados_write_op_operate(wop, io_ctx, oid, NULL, 0); rados_release_write_op(wop); if (ret >= 0) rados_grace_notify(io_ctx, oid); } while (ret == -ERANGE); if (!ret) { *pcur = cur; *prec = rec; } out: free(match); free(lens); free(flags); free(vals); return ret; } int rados_grace_lift_bulk(rados_ioctx_t io_ctx, const char *oid, int nodes, const char *const *nodeids, uint64_t *pcur, uint64_t *prec, bool remove) { int ret; char *flags = NULL; char **vals = NULL; size_t *lens = NULL; const char **keys = NULL; bool *match = NULL; uint64_t cur, rec; keys = calloc(nodes, sizeof(char *)); if (!keys) { ret = -ENOMEM; goto out; } match = calloc(nodes, sizeof(bool)); if (!match) { ret = -ENOMEM; goto out; } /* We don't need these arrays if we're just removing the keys */ if (!remove) { flags = calloc(nodes, 1); if (!flags) { ret = -ENOMEM; goto out; } /* pointers to each val byte */ vals = calloc(nodes, sizeof(char *)); if (!vals) { ret = -ENOMEM; goto out; } lens = calloc(nodes, sizeof(size_t)); if (!lens) { ret = -ENOMEM; goto out; } } do { int i, k, need; rados_write_op_t wop; rados_read_op_t rop; rados_omap_iter_t iter; char *key, *val; size_t len; char buf[sizeof(uint64_t) * 2]; unsigned char more = '\0'; uint64_t ver; bool enforcing; /* read epoch blob and omap keys */ rop = rados_create_read_op(); rados_read_op_read(rop, 0, sizeof(buf), buf, &len, NULL); rados_read_op_omap_get_vals2(rop, "", "", MAX_ITEMS, &iter, &more, NULL); ret = rados_read_op_operate(rop, io_ctx, oid, 0); if (ret < 0) { rados_release_read_op(rop); break; } if (more) { ret = -ENOTRECOVERABLE; rados_release_read_op(rop); break; } if (len != sizeof(buf)) { ret = -ENOTRECOVERABLE; rados_release_read_op(rop); break; } /* Get old epoch numbers and version */ ver = rados_get_last_version(io_ctx); cur = le64toh(*(uint64_t *)buf); rec = le64toh(*(uint64_t *)(buf + sizeof(uint64_t))); /* * Walk omap keys, see if it's in nodeids array. Add any that * are and have NEED_GRACE set to the "keys" array along with * the flags field with the NEED_GRACE flag cleared. * * If the remove flag is set, then just remove them from the * omap and don't bother with changing the flags. */ need = 0; k = 0; enforcing = true; for (;;) { ret = rados_omap_get_next(iter, &key, &val, &len); if (!key) break; /* Make note if anyone is not enforcing */ if (!(*val & RADOS_GRACE_ENFORCING)) enforcing = false; if (*val & RADOS_GRACE_NEED_GRACE) ++need; for (i = 0; i < nodes; ++i) { if (strcmp(key, nodeids[i])) continue; match[i] = true; if (!remove && !(*val & RADOS_GRACE_NEED_GRACE)) break; keys[k] = nodeids[i]; /* * For the removal case, we just need the * keys. */ if (!remove) { flags[k] = *val & ~RADOS_GRACE_NEED_GRACE; vals[k] = &flags[k]; lens[k] = 1; } ++k; break; } }; rados_omap_get_end(iter); rados_release_read_op(rop); /* * We can't lift if we're in a grace period and there are * cluster members that haven't started enforcement yet. Wait * until they catch up. */ if (rec && !enforcing) goto out; /* Ensure that all given nodes have a key in the omap */ for (i = 0; i < nodes; ++i) { if (!match[i]) { ret = -ENOKEY; goto out; } } /* No matching keys? Nothing to do. */ if (k == 0) break; /* Attempt to update object */ wop = rados_create_write_op(); /* Ensure that nothing has changed */ rados_write_op_assert_version(wop, ver); /* Set or remove any keys we matched earlier */ if (remove) rados_write_op_omap_rm_keys(wop, keys, k); else rados_write_op_omap_set( wop, keys, (const char *const *)vals, lens, k); /* * If number of omap records we're setting or removing is the * same as the number of hosts that have NEED_GRACE set, then * fully lift the grace period. */ if (need == k) { uint64_t tc, tr; rec = 0; tr = htole64(rec); tc = htole64(cur); memcpy(buf, (char *)&tc, sizeof(tc)); memcpy(buf + sizeof(tc), (char *)&tr, sizeof(tr)); rados_write_op_write_full(wop, buf, sizeof(buf)); } ret = rados_write_op_operate(wop, io_ctx, oid, NULL, 0); rados_release_write_op(wop); if (ret >= 0) rados_grace_notify(io_ctx, oid); } while (ret == -ERANGE); out: if (!ret) { *pcur = cur; *prec = rec; } free(match); free(vals); free(lens); free(flags); free(keys); return ret; } int rados_grace_add(rados_ioctx_t io_ctx, const char *oid, int nodes, const char *const *nodeids) { int i, ret; char *flags = NULL; char **vals = NULL; size_t *lens = NULL; uint64_t ver; /* allocate an array of flag bytes */ flags = calloc(nodes, 1); if (!flags) { ret = -ENOMEM; goto out; } /* pointers to each val byte */ vals = calloc(nodes, sizeof(char *)); if (!vals) { ret = -ENOMEM; goto out; } /* lengths */ lens = calloc(nodes, sizeof(size_t)); if (!lens) { ret = -ENOMEM; goto out; } /* each val is one flag byte */ for (i = 0; i < nodes; ++i) { flags[i] = RADOS_GRACE_ENFORCING; vals[i] = &flags[i]; lens[i] = 1; } do { rados_write_op_t wop; rados_read_op_t rop; rados_omap_iter_t iter; unsigned char more = 0; /* read epoch blob */ rop = rados_create_read_op(); rados_read_op_omap_get_vals2(rop, "", "", MAX_ITEMS, &iter, &more, NULL); ret = rados_read_op_operate(rop, io_ctx, oid, 0); if (ret < 0) { rados_release_read_op(rop); break; } if (more) { ret = -ENOTRECOVERABLE; rados_release_read_op(rop); break; } ver = rados_get_last_version(io_ctx); /* Ensure no nodes in the list already exist */ for (;;) { char *key, *val; size_t len; rados_omap_get_next(iter, &key, &val, &len); if (!key) break; for (i = 0; i < nodes; ++i) { if (!strcmp(key, nodeids[i])) { ret = 0; rados_omap_get_end(iter); rados_release_read_op(rop); goto out; } } } rados_omap_get_end(iter); rados_release_read_op(rop); /* Attempt to update object */ wop = rados_create_write_op(); /* Ensure that nothing has changed */ rados_write_op_assert_version(wop, ver); /* Set omap values to given ones */ rados_write_op_omap_set(wop, nodeids, (const char *const *)vals, lens, nodes); ret = rados_write_op_operate(wop, io_ctx, oid, NULL, 0); rados_release_write_op(wop); if (ret >= 0) rados_grace_notify(io_ctx, oid); } while (ret == -ERANGE); out: free(lens); free(flags); free(vals); return ret; } int rados_grace_member_bulk(rados_ioctx_t io_ctx, const char *oid, int nodes, const char *const *nodeids) { int ret, rval, cnt; rados_read_op_t rop; rados_omap_iter_t iter; /* read epoch blob */ rop = rados_create_read_op(); rados_read_op_omap_get_vals_by_keys(rop, nodeids, nodes, &iter, &rval); ret = rados_read_op_operate(rop, io_ctx, oid, 0); if (ret < 0) { rados_release_read_op(rop); return ret; } /* Count the returned keys */ cnt = 0; for (;;) { char *key, *val; size_t len; rados_omap_get_next(iter, &key, &val, &len); if (!key) break; ++cnt; } rados_omap_get_end(iter); rados_release_read_op(rop); return (cnt == nodes) ? 0 : -ENOENT; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/refstr.c����������������������������������������������������������������0000664�0000000�0000000�00000002506�14737566223�0017554�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /** * Copyright (c) 2018 Jeff Layton <jlayton@redhat.com> * * This program 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 3 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 * 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 */ #include "config.h" #include <stddef.h> #include <urcu/ref.h> #include "gsh_refstr.h" #include "abstract_mem.h" #include "gsh_list.h" struct gsh_refstr *gsh_refstr_alloc(size_t len) { struct gsh_refstr *gr; gr = gsh_malloc(sizeof(*gr) + len); urcu_ref_init(&gr->gr_ref); return gr; } void gsh_refstr_release(struct urcu_ref *ref) { struct gsh_refstr *gr = container_of(ref, struct gsh_refstr, gr_ref); LogFullDebug(COMPONENT_EXPORT, "Releasing refstr %s", gr->gr_val); gsh_free(gr); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/server_stats.c����������������������������������������������������������0000664�0000000�0000000�00000255054�14737566223�0021003�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Panasas Inc., 2013 * Author: Jim Lieb jlieb@panasas.com * * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ /** * @defgroup Server statistics management * @{ */ /** * @file server_stats.c * @author Jim Lieb <jlieb@panasas.com> * @brief FSAL module manager */ #include "config.h" #include <time.h> #include <unistd.h> #include <sys/types.h> #include <stdint.h> #include <sys/param.h> #include <pthread.h> #include <assert.h> #include <arpa/inet.h> #include "fsal.h" #include "nfs_core.h" #include "log.h" #include "avltree.h" #include "gsh_types.h" #ifdef USE_DBUS #include "gsh_dbus.h" #endif #include "client_mgr.h" #include "export_mgr.h" #include "server_stats.h" #include <abstract_atomic.h> #include "nfs_proto_functions.h" #include "nfs_convert.h" #include "nfs_metrics.h" #define NFS_pcp nfs_param.core_param #define NFS_program NFS_pcp.program #ifdef USE_DBUS struct op_name { char *name; }; #ifdef _USE_RQUOTA static const struct op_name optqta[] = { [RQUOTAPROC_GETQUOTA] = {.name = "GETQUOTA", }, [RQUOTAPROC_GETACTIVEQUOTA] = {.name = "GETACTIVEQUOTA", }, [RQUOTAPROC_SETQUOTA] = {.name = "SETQUOTA", }, [RQUOTAPROC_SETACTIVEQUOTA] = {.name = "SETACTIVEQUOTA", }, }; #endif #ifdef _USE_NFS3 static const struct op_name optmnt[] = { [MOUNTPROC3_NULL] = {.name = "NULL", }, [MOUNTPROC3_MNT] = {.name = "MNT", }, [MOUNTPROC3_DUMP] = {.name = "DUMP", }, [MOUNTPROC3_UMNT] = {.name = "UMNT", }, [MOUNTPROC3_UMNTALL] = {.name = "UMNTALL", }, [MOUNTPROC3_EXPORT] = {.name = "EXPORT", }, }; #endif #ifdef _USE_NLM static const struct op_name optnlm[] = { [NLMPROC4_NULL] = {.name = "NULL", }, [NLMPROC4_TEST] = {.name = "TEST", }, [NLMPROC4_LOCK] = {.name = "LOCK", }, [NLMPROC4_CANCEL] = {.name = "CANCEL", }, [NLMPROC4_UNLOCK] = {.name = "UNLOCK", }, [NLMPROC4_GRANTED] = {.name = "GRANTED", }, [NLMPROC4_TEST_MSG] = {.name = "TEST_MSG", }, [NLMPROC4_LOCK_MSG] = {.name = "LOCK_MSG", }, [NLMPROC4_CANCEL_MSG] = {.name = "CANCEL_MSG", }, [NLMPROC4_UNLOCK_MSG] = {.name = "UNLOCK_MSG", }, [NLMPROC4_GRANTED_MSG] = {.name = "GRANTED_MSG", }, [NLMPROC4_TEST_RES] = {.name = "TEST_RES ", }, [NLMPROC4_LOCK_RES] = {.name = "LOCK_RES", }, [NLMPROC4_CANCEL_RES] = {.name = "CANCEL_RES", }, [NLMPROC4_UNLOCK_RES] = {.name = "UNLOCK_RES", }, [NLMPROC4_GRANTED_RES] = {.name = "GRANTED_RES", }, [NLMPROC4_SM_NOTIFY] = {.name = "SM_NOTIFY", }, [NLMPROC4_SHARE] = {.name = "SHARE", }, [NLMPROC4_UNSHARE] = {.name = "UNSHARE", }, [NLMPROC4_NM_LOCK] = {.name = "NM_LOCK", }, [NLMPROC4_FREE_ALL] = {.name = "FREE_ALL", }, }; #endif #endif /* Classify protocol ops for stats purposes */ enum proto_op_type { GENERAL_OP = 0, /* default for array init */ READ_OP, WRITE_OP, LAYOUT_OP }; #ifdef _USE_NFS3 static const uint32_t nfsv3_optype[NFS_V3_NB_COMMAND] = { [NFSPROC3_READ] = READ_OP, [NFSPROC3_WRITE] = WRITE_OP, }; #endif static const uint32_t nfsv40_optype[NFS_V40_NB_OPERATION] = { [NFS4_OP_READ] = READ_OP, [NFS4_OP_WRITE] = WRITE_OP, }; static const uint32_t nfsv41_optype[NFS_V41_NB_OPERATION] = { [NFS4_OP_READ] = READ_OP, [NFS4_OP_WRITE] = WRITE_OP, [NFS4_OP_GETDEVICEINFO] = LAYOUT_OP, [NFS4_OP_GETDEVICELIST] = LAYOUT_OP, [NFS4_OP_LAYOUTCOMMIT] = LAYOUT_OP, [NFS4_OP_LAYOUTGET] = LAYOUT_OP, [NFS4_OP_LAYOUTRETURN] = LAYOUT_OP, }; static const uint32_t nfsv42_optype[NFS_V42_NB_OPERATION] = { [NFS4_OP_READ] = READ_OP, [NFS4_OP_WRITE] = WRITE_OP, [NFS4_OP_GETDEVICEINFO] = LAYOUT_OP, [NFS4_OP_GETDEVICELIST] = LAYOUT_OP, [NFS4_OP_LAYOUTCOMMIT] = LAYOUT_OP, [NFS4_OP_LAYOUTGET] = LAYOUT_OP, [NFS4_OP_LAYOUTRETURN] = LAYOUT_OP, [NFS4_OP_WRITE_SAME] = WRITE_OP, [NFS4_OP_READ_PLUS] = READ_OP, }; /* latency stats */ struct op_latency { uint64_t latency; uint64_t min; uint64_t max; }; #ifdef _USE_NFS3 /* v3 ops */ struct nfsv3_ops { uint64_t op[NFS_V3_NB_COMMAND]; }; #endif #ifdef _USE_RQUOTA /* quota ops */ struct qta_ops { uint64_t op[RQUOTA_NB_COMMAND]; }; #endif #ifdef _USE_NLM /* nlm ops */ struct nlm_ops { uint64_t op[NLM_V4_NB_OPERATION]; }; #endif #ifdef _USE_NFS3 /* mount ops */ struct mnt_ops { uint64_t op[MNT_V3_NB_COMMAND]; }; #endif /* v4 ops */ struct nfsv4_ops { uint64_t op[NFS4_OP_LAST_ONE]; }; /* basic op counter */ struct op_count { uint64_t total; /* total of any kind */ uint64_t errors; /* ! NFS_OK */ uint64_t dups; /* detected dup requests */ }; struct proto_op { uint64_t total; /* total of any kind */ uint64_t errors; /* ! NFS_OK */ uint64_t dups; /* detected dup requests */ struct op_latency latency; /* either executed ops latency */ struct op_latency dup_latency; /* or latency (runtime) to replay */ }; /* basic I/O transfer counter */ struct xfer_op { struct proto_op cmd; uint64_t requested; uint64_t transferred; }; /* pNFS Layout counters */ struct layout_op { uint64_t total; /* total ops */ uint64_t errors; /* ! NFS4_OK && !NFS4ERR_DELAY */ uint64_t delays; /* NFS4ERR_DELAY */ }; #ifdef _USE_NFS3 /* NFSv3 statistics counters */ struct nfsv3_stats { struct proto_op cmds; /* non-I/O ops = cmds - (read+write) */ struct xfer_op read; struct xfer_op write; }; struct clnt_allops_v3_stats { struct op_count cmds[NFS_V3_NB_COMMAND]; /* all NFSv3 ops */ }; /* Mount statistics counters */ struct mnt_stats { struct proto_op v1_ops; struct proto_op v3_ops; }; #endif #ifdef _USE_NLM /* lock manager counters */ struct nlmv4_stats { struct proto_op ops; }; struct clnt_allops_nlm_stats { struct op_count cmds[NLM_V4_NB_OPERATION]; /* all NLMv4 ops */ }; #endif #ifdef _USE_RQUOTA /* Quota counters */ struct rquota_stats { struct proto_op ops; struct proto_op ext_ops; }; #endif /* NFSv4 statistics counters */ struct nfsv40_stats { struct proto_op compounds; uint64_t ops_per_compound; /* avg = total / ops_per */ struct xfer_op read; struct xfer_op write; }; struct nfsv41_stats { struct proto_op compounds; uint64_t ops_per_compound; /* for size averaging */ struct xfer_op read; struct xfer_op write; struct layout_op getdevinfo; struct layout_op layout_get; struct layout_op layout_commit; struct layout_op layout_return; struct layout_op recall; }; struct clnt_allops_v4_stats { struct op_count cmds[NFS4_OP_LAST_ONE]; /* all ops for NFSv4.x */ }; struct transport_stats { uint64_t rx_bytes; uint64_t rx_pkt; uint64_t rx_err; uint64_t tx_bytes; uint64_t tx_pkt; uint64_t tx_err; }; #ifdef _USE_9P struct _9p_stats { struct proto_op cmds; /* non-I/O ops */ struct xfer_op read; struct xfer_op write; struct transport_stats trans; struct proto_op *opcodes[_9P_RWSTAT + 1]; }; #endif struct global_stats { #ifdef _USE_NFS3 struct nfsv3_stats nfsv3; struct mnt_stats mnt; #endif #ifdef _USE_NLM struct nlmv4_stats nlm4; #endif #ifdef _USE_RQUOTA struct rquota_stats rquota; #endif struct nfsv40_stats nfsv40; struct nfsv41_stats nfsv41; struct nfsv41_stats nfsv42; /* Uses v41 stats */ #ifdef _USE_NFS3 struct nfsv3_ops v3; #endif struct nfsv4_ops v4; #ifdef _USE_NLM struct nlm_ops lm; #endif #ifdef _USE_NFS3 struct mnt_ops mn; #endif #ifdef _USE_RQUOTA struct qta_ops qt; #endif }; struct deleg_stats { uint32_t curr_deleg_grants; /* current num of delegations owned by this client */ uint32_t tot_recalls; /* total num of times client was asked to recall */ uint32_t failed_recalls; /* times client failed to process recall */ uint32_t num_revokes; /* Num revokes for the client */ }; static struct global_stats global_st; /* include the top level server_stats struct definition */ #include "server_stats_private.h" #ifdef _USE_NFS3 /* NFSv3 Detailed stats holder */ struct proto_op v3_full_stats[NFS_V3_NB_COMMAND]; #endif /* NFSv4 Detailed stats holder */ struct proto_op v4_full_stats[NFS4_OP_LAST_ONE]; /** * @brief Get stats struct helpers * * These functions dereference the protocol specific struct * silently calloc the struct on first use. * * @param stats [IN] the stats structure to dereference in * @param lock [IN] the lock in the stats owning struct * * @return pointer to proto struct * * @TODO make them inlines for release */ #ifdef _USE_NFS3 static struct nfsv3_stats *get_v3(struct gsh_stats *stats, pthread_rwlock_t *lock) { if (unlikely(stats->nfsv3 == NULL)) { PTHREAD_RWLOCK_wrlock(lock); if (stats->nfsv3 == NULL) stats->nfsv3 = gsh_calloc(1, sizeof(struct nfsv3_stats)); PTHREAD_RWLOCK_unlock(lock); } return stats->nfsv3; } static struct clnt_allops_v3_stats *get_v3_all(struct gsh_clnt_allops_stats *st, pthread_rwlock_t *lock) { if (unlikely(st->nfsv3 == NULL)) { PTHREAD_RWLOCK_wrlock(lock); if (st->nfsv3 == NULL) st->nfsv3 = gsh_calloc( 1, sizeof(struct clnt_allops_v3_stats)); PTHREAD_RWLOCK_unlock(lock); } return st->nfsv3; } static struct mnt_stats *get_mnt(struct gsh_stats *stats, pthread_rwlock_t *lock) { if (unlikely(stats->mnt == NULL)) { PTHREAD_RWLOCK_wrlock(lock); if (stats->mnt == NULL) stats->mnt = gsh_calloc(1, sizeof(struct mnt_stats)); PTHREAD_RWLOCK_unlock(lock); } return stats->mnt; } #endif #ifdef _USE_NLM static struct nlmv4_stats *get_nlm4(struct gsh_stats *stats, pthread_rwlock_t *lock) { if (unlikely(stats->nlm4 == NULL)) { PTHREAD_RWLOCK_wrlock(lock); if (stats->nlm4 == NULL) stats->nlm4 = gsh_calloc(1, sizeof(struct nlmv4_stats)); PTHREAD_RWLOCK_unlock(lock); } return stats->nlm4; } static struct clnt_allops_nlm_stats * get_nlm4_all(struct gsh_clnt_allops_stats *stats, pthread_rwlock_t *lock) { if (unlikely(stats->nlm4 == NULL)) { PTHREAD_RWLOCK_wrlock(lock); if (stats->nlm4 == NULL) stats->nlm4 = gsh_calloc( 1, sizeof(struct clnt_allops_nlm_stats)); PTHREAD_RWLOCK_unlock(lock); } return stats->nlm4; } #endif #ifdef _USE_RQUOTA static struct rquota_stats *get_rquota(struct gsh_stats *stats, pthread_rwlock_t *lock) { if (unlikely(stats->rquota == NULL)) { PTHREAD_RWLOCK_wrlock(lock); if (stats->rquota == NULL) stats->rquota = gsh_calloc(1, sizeof(struct rquota_stats)); PTHREAD_RWLOCK_unlock(lock); } return stats->rquota; } #endif static struct nfsv40_stats *get_v40(struct gsh_stats *stats, pthread_rwlock_t *lock) { if (unlikely(stats->nfsv40 == NULL)) { PTHREAD_RWLOCK_wrlock(lock); if (stats->nfsv40 == NULL) stats->nfsv40 = gsh_calloc(1, sizeof(struct nfsv40_stats)); PTHREAD_RWLOCK_unlock(lock); } return stats->nfsv40; } static struct nfsv41_stats *get_v41(struct gsh_stats *stats, pthread_rwlock_t *lock) { if (unlikely(stats->nfsv41 == NULL)) { PTHREAD_RWLOCK_wrlock(lock); if (stats->nfsv41 == NULL) stats->nfsv41 = gsh_calloc(1, sizeof(struct nfsv41_stats)); PTHREAD_RWLOCK_unlock(lock); } return stats->nfsv41; } static struct nfsv41_stats *get_v42(struct gsh_stats *stats, pthread_rwlock_t *lock) { if (unlikely(stats->nfsv42 == NULL)) { PTHREAD_RWLOCK_wrlock(lock); if (stats->nfsv42 == NULL) stats->nfsv42 = gsh_calloc(1, sizeof(struct nfsv41_stats)); PTHREAD_RWLOCK_unlock(lock); } return stats->nfsv42; } static struct clnt_allops_v4_stats * get_v4_all(struct gsh_clnt_allops_stats *stats, pthread_rwlock_t *lock) { if (unlikely(stats->nfsv4 == NULL)) { PTHREAD_RWLOCK_wrlock(lock); if (stats->nfsv4 == NULL) stats->nfsv4 = gsh_calloc( 1, sizeof(struct clnt_allops_v4_stats)); PTHREAD_RWLOCK_unlock(lock); } return stats->nfsv4; } #ifdef _USE_9P static struct _9p_stats *get_9p(struct gsh_stats *stats, pthread_rwlock_t *lock) { if (unlikely(stats->_9p == NULL)) { PTHREAD_RWLOCK_wrlock(lock); if (stats->_9p == NULL) stats->_9p = gsh_calloc(1, sizeof(struct _9p_stats)); PTHREAD_RWLOCK_unlock(lock); } return stats->_9p; } #endif /* Functions for recording statistics */ /** * @brief Record latency stats * * @param op [IN] protocol op stats struct * @param request_time [IN] time consumed by request * @param dup [IN] detected this was a dup request */ void record_latency(struct proto_op *op, nsecs_elapsed_t request_time, bool dup) { /* dup latency is counted separately */ if (likely(!dup)) { (void)atomic_add_uint64_t(&op->latency.latency, request_time); if (op->latency.min == 0L || op->latency.min > request_time) (void)atomic_store_uint64_t(&op->latency.min, request_time); if (op->latency.max == 0L || op->latency.max < request_time) (void)atomic_store_uint64_t(&op->latency.max, request_time); } else { (void)atomic_add_uint64_t(&op->dup_latency.latency, request_time); if (op->dup_latency.min == 0L || op->dup_latency.min > request_time) (void)atomic_store_uint64_t(&op->dup_latency.min, request_time); if (op->dup_latency.max == 0L || op->dup_latency.max < request_time) (void)atomic_store_uint64_t(&op->dup_latency.max, request_time); } } /** * @brief count the i/o stats * * We do the transfer counts here. Latency is done later at * operation/compound completion. * * @param iop [IN] transfer stats struct * @param requested [IN] bytes requested * @param transferred [IN] bytes actually transferred * @param success [IN] the op returned OK (or error) */ static void record_io(struct xfer_op *iop, size_t requested, size_t transferred, bool success) { (void)atomic_inc_uint64_t(&iop->cmd.total); if (success) { (void)atomic_add_uint64_t(&iop->requested, requested); (void)atomic_add_uint64_t(&iop->transferred, transferred); } else { (void)atomic_inc_uint64_t(&iop->cmd.errors); } /* somehow we must record latency */ } /** * @brief record i/o stats by protocol */ static void record_io_stats(struct gsh_stats *gsh_st, pthread_rwlock_t *lock, size_t requested, size_t transferred, bool success, bool is_write) { struct xfer_op *iop = NULL; if (op_ctx->req_type == NFS_REQUEST) { if (op_ctx->nfs_vers == NFS_V4) { if (op_ctx->nfs_minorvers == 0) { struct nfsv40_stats *sp = get_v40(gsh_st, lock); iop = is_write ? &sp->write : &sp->read; } else if (op_ctx->nfs_minorvers == 1) { struct nfsv41_stats *sp = get_v41(gsh_st, lock); iop = is_write ? &sp->write : &sp->read; } else if (op_ctx->nfs_minorvers == 2) { struct nfsv41_stats *sp = get_v42(gsh_st, lock); iop = is_write ? &sp->write : &sp->read; } /* the frightening thought is someday minor == 3 */ #ifdef _USE_NFS3 } else if (op_ctx->nfs_vers == NFS_V3) { struct nfsv3_stats *sp = get_v3(gsh_st, lock); iop = is_write ? &sp->write : &sp->read; #endif } else { return; } #ifdef _USE_9P } else if (op_ctx->req_type == _9P_REQUEST) { struct _9p_stats *sp = get_9p(gsh_st, lock); iop = is_write ? &sp->write : &sp->read; #endif } else { return; } record_io(iop, requested, transferred, success); } /** * @brief count the protocol operation * * Use atomic ops to avoid locks. We don't lock for the max * and min because if there is a collision, over the long haul, * the error is near zero... * * @param op [IN] pointer to specific protocol struct * @param request_time [IN] wallclock time (nsecs) for this op * @param success [IN] protocol error code == OK * @param dup [IN] true if op was detected duplicate */ static void record_op(struct proto_op *op, nsecs_elapsed_t request_time, bool success, bool dup) { /* count the op */ (void)atomic_inc_uint64_t(&op->total); /* also count it as an error if protocol not happy */ if (!success) (void)atomic_inc_uint64_t(&op->errors); if (unlikely(dup)) (void)atomic_inc_uint64_t(&op->dups); record_latency(op, request_time, dup); } /** * @brief count the protocol operation only * * Use atomic ops to avoid locks. We don't lock for the max * and min because if there is a collision, over the long haul, * the error is near zero... * * @param op [IN] pointer to specific protocol struct * @param success [IN] protocol error code == OK * @param dup [IN] true if op was detected duplicate */ static void record_op_only(struct proto_op *op, bool success, bool dup) { /* count the op */ (void)atomic_inc_uint64_t(&op->total); /* also count it as an error if protocol not happy */ if (!success) (void)atomic_inc_uint64_t(&op->errors); if (unlikely(dup)) (void)atomic_inc_uint64_t(&op->dups); } static void record_clnt_ops(struct op_count *op, bool success, bool dup) { /* count the op */ (void)atomic_inc_uint64_t(&op->total); /* also count it as an error if protocol not happy */ if (!success) (void)atomic_inc_uint64_t(&op->errors); if (unlikely(dup)) (void)atomic_inc_uint64_t(&op->dups); } #ifdef USE_DBUS /** * @brief reset the counts for protocol operation * Use atomic ops to avoid locks. * @param op [IN] pointer to specific protocol struct */ static void reset_op(struct proto_op *op) { (void)atomic_store_uint64_t(&op->total, 0); (void)atomic_store_uint64_t(&op->errors, 0); (void)atomic_store_uint64_t(&op->dups, 0); /* reset latency related counters */ (void)atomic_store_uint64_t(&op->latency.latency, 0); (void)atomic_store_uint64_t(&op->latency.min, 0); (void)atomic_store_uint64_t(&op->latency.max, 0); (void)atomic_store_uint64_t(&op->dup_latency.latency, 0); (void)atomic_store_uint64_t(&op->dup_latency.min, 0); (void)atomic_store_uint64_t(&op->dup_latency.max, 0); } /** * @brief reset the counts for op_count struct * Use atomic ops to avoid locks. * @param op [IN] pointer to specific op_count struct */ static void reset_op_count(struct op_count *op) { (void)atomic_store_uint64_t(&op->total, 0); (void)atomic_store_uint64_t(&op->errors, 0); (void)atomic_store_uint64_t(&op->dups, 0); } /** * @brief reset the counts for xfer protocol operation * Use atomic ops to avoid locks. * @param xfer [IN] pointer to specific xfer protocol struct */ static void reset_xfer_op(struct xfer_op *xfer) { reset_op(&xfer->cmd); (void)atomic_store_uint64_t(&xfer->requested, 0); (void)atomic_store_uint64_t(&xfer->transferred, 0); } /** * @brief reset the counts related to layout * Use atomic ops to avoid locks. * @param lo [IN] pointer to specific layout struct */ static void reset_layout_op(struct layout_op *lo) { (void)atomic_store_uint64_t(&lo->total, 0); (void)atomic_store_uint64_t(&lo->errors, 0); (void)atomic_store_uint64_t(&lo->delays, 0); } #ifdef _USE_NFS3 /** * @brief reset the counts nfsv3_stats * Use atomic ops to avoid locks. * @param nfsv3 [IN] pointer to nfsv3_stats struct */ static void reset_nfsv3_stats(struct nfsv3_stats *nfsv3) { /* Reset stats counter for nfsv3 protocol */ reset_op(&nfsv3->cmds); reset_xfer_op(&nfsv3->read); reset_xfer_op(&nfsv3->write); } #endif /** * @brief reset the counts nfsv40_stats * Use atomic ops to avoid locks. * @param nfsv40 [IN] pointer to nfsv40_stats struct */ static void reset_nfsv40_stats(struct nfsv40_stats *nfsv40) { /* Reset stats counter for nfsv4 protocol */ reset_op(&nfsv40->compounds); (void)atomic_store_uint64_t(&nfsv40->ops_per_compound, 0); reset_xfer_op(&nfsv40->read); reset_xfer_op(&nfsv40->write); } /** * @brief reset the counts nfsv41_stats * Use atomic ops to avoid locks. * @param nfsv41 [IN] pointer to nfsv41_stats struct */ static void reset_nfsv41_stats(struct nfsv41_stats *nfsv41) { /* Reset stats counter for nfsv41 protocol */ reset_op(&nfsv41->compounds); (void)atomic_store_uint64_t(&nfsv41->ops_per_compound, 0); reset_xfer_op(&nfsv41->read); reset_xfer_op(&nfsv41->write); reset_layout_op(&nfsv41->getdevinfo); reset_layout_op(&nfsv41->layout_get); reset_layout_op(&nfsv41->layout_commit); reset_layout_op(&nfsv41->layout_return); reset_layout_op(&nfsv41->recall); } #ifdef _USE_NFS3 /** * @brief reset the counts mnt_stats * Use atomic ops to avoid locks. * @param mnt [IN] pointer to mnt_stats struct */ static void reset_mnt_stats(struct mnt_stats *mnt) { /* Reset stats counter for mount protocol */ reset_op(&mnt->v1_ops); reset_op(&mnt->v3_ops); } #endif #ifdef _USE_RQUOTA /** * @brief reset the counts rquota_stats * Use atomic ops to avoid locks. * @param rquota [IN] pointer to rquota_stats struct */ static void reset_rquota_stats(struct rquota_stats *rquota) { /* Reset stats counter for quota */ reset_op(&rquota->ops); reset_op(&rquota->ext_ops); } #endif #ifdef _USE_NLM /** * @brief reset the counts nlmv4_stats * Use atomic ops to avoid locks. * @param nlmv4 [IN] pointer to nlmv4_stats struct */ static void reset_nlmv4_stats(struct nlmv4_stats *nlmv4) { /* Reset stats counter for nlmv4 */ reset_op(&nlmv4->ops); } #endif /** * @brief reset the counts nlmv4_stats * Use atomic ops to avoid locks. * @param nlmv4 [IN] pointer to nlmv4_stats struct */ static void reset_deleg_stats(struct deleg_stats *deleg) { /* Reset stats counter for deleg */ (void)atomic_store_uint32_t(&deleg->curr_deleg_grants, 0); (void)atomic_store_uint32_t(&deleg->tot_recalls, 0); (void)atomic_store_uint32_t(&deleg->failed_recalls, 0); (void)atomic_store_uint32_t(&deleg->num_revokes, 0); } #ifdef _USE_9P static void reset__9P_stats(struct _9p_stats *_9p) { u8 opc; reset_op(&_9p->cmds); reset_xfer_op(&_9p->read); reset_xfer_op(&_9p->write); (void)atomic_store_uint64_t(&_9p->trans.rx_bytes, 0); (void)atomic_store_uint64_t(&_9p->trans.rx_pkt, 0); (void)atomic_store_uint64_t(&_9p->trans.rx_err, 0); (void)atomic_store_uint64_t(&_9p->trans.tx_bytes, 0); (void)atomic_store_uint64_t(&_9p->trans.tx_pkt, 0); (void)atomic_store_uint64_t(&_9p->trans.tx_err, 0); for (opc = 0; opc <= _9P_RWSTAT; opc++) { if (_9p->opcodes[opc] != NULL) reset_op(_9p->opcodes[opc]); } } #endif #endif /* USE_DBUS */ /** * @brief record V4.1 layout op stats * * @param sp [IN] stats block to update * @param proto_op [IN] protocol op * @param status [IN] operation status */ static void record_layout(struct nfsv41_stats *sp, int proto_op, int status) { struct layout_op *lp; if (proto_op == NFS4_OP_GETDEVICEINFO) lp = &sp->getdevinfo; else if (proto_op == NFS4_OP_GETDEVICELIST) lp = &sp->getdevinfo; else if (proto_op == NFS4_OP_LAYOUTGET) lp = &sp->layout_get; else if (proto_op == NFS4_OP_LAYOUTCOMMIT) lp = &sp->layout_commit; else if (proto_op == NFS4_OP_LAYOUTRETURN) lp = &sp->layout_return; else return; (void)atomic_inc_uint64_t(&lp->total); if (status == NFS4ERR_DELAY) (void)atomic_inc_uint64_t(&lp->delays); else if (status != NFS4_OK) (void)atomic_inc_uint64_t(&lp->errors); } /** * @brief Record NFS V4 compound stats */ static void record_nfsv4_op(struct gsh_stats *gsh_st, pthread_rwlock_t *lock, int proto_op, int minorversion, nsecs_elapsed_t request_time, int status, bool is_export) { if (minorversion == 0) { struct nfsv40_stats *sp = get_v40(gsh_st, lock); /* record stuff */ switch (nfsv40_optype[proto_op]) { case READ_OP: if (is_export) record_latency(&sp->read.cmd, request_time, false); break; case WRITE_OP: if (is_export) record_latency(&sp->write.cmd, request_time, false); break; default: if (is_export) record_op(&sp->compounds, request_time, status == NFS4_OK, false); else record_op_only(&sp->compounds, status == NFS4_OK, false); } } else if (minorversion == 1) { struct nfsv41_stats *sp = get_v41(gsh_st, lock); /* record stuff */ switch (nfsv41_optype[proto_op]) { case READ_OP: if (is_export) record_latency(&sp->read.cmd, request_time, false); break; case WRITE_OP: if (is_export) record_latency(&sp->write.cmd, request_time, false); break; case LAYOUT_OP: record_layout(sp, proto_op, status); break; default: if (is_export) record_op(&sp->compounds, request_time, status == NFS4_OK, false); else record_op_only(&sp->compounds, status == NFS4_OK, false); } } else if (minorversion == 2) { struct nfsv41_stats *sp = get_v42(gsh_st, lock); /* record stuff */ switch (nfsv42_optype[proto_op]) { case READ_OP: if (is_export) record_latency(&sp->read.cmd, request_time, false); break; case WRITE_OP: if (is_export) record_latency(&sp->write.cmd, request_time, false); break; case LAYOUT_OP: record_layout(sp, proto_op, status); break; default: if (is_export) record_op(&sp->compounds, request_time, status == NFS4_OK, false); else record_op_only(&sp->compounds, status == NFS4_OK, false); } } } /** * @brief Record NFS V4 compound stats */ static void record_compound(struct gsh_stats *gsh_st, pthread_rwlock_t *lock, int minorversion, uint64_t num_ops, nsecs_elapsed_t request_time, bool success) { if (minorversion == 0) { struct nfsv40_stats *sp = get_v40(gsh_st, lock); /* record stuff */ record_op(&sp->compounds, request_time, success, false); (void)atomic_add_uint64_t(&sp->ops_per_compound, num_ops); } else if (minorversion == 1) { struct nfsv41_stats *sp = get_v41(gsh_st, lock); /* record stuff */ record_op(&sp->compounds, request_time, success, false); (void)atomic_add_uint64_t(&sp->ops_per_compound, num_ops); } else if (minorversion == 2) { struct nfsv41_stats *sp = get_v42(gsh_st, lock); /* record stuff */ record_op(&sp->compounds, request_time, success, false); (void)atomic_add_uint64_t(&sp->ops_per_compound, num_ops); } } /** * @brief Record request statistics (V3 era protos only) * * Decode the protocol and find the proto specific stats struct. * Once we found the stats block, do the update(s). * * @param gsh_st [IN] stats struct from client or export * @param lock [IN] lock on client|export for malloc * @param reqdata [IN] info about the proto request * @param success [IN] the op returned OK (or error) * @param dup [IN] detected this was a dup request */ static void record_clnt_stats(struct gsh_stats *gsh_st, pthread_rwlock_t *lock, nfs_request_t *reqdata, bool success, bool dup) { struct svc_req *req = &reqdata->svc; uint32_t proto_op = req->rq_msg.cb_proc; uint32_t program_op = req->rq_msg.cb_prog; if (program_op == NFS_program[P_NFS]) { if (proto_op == 0) return; /* we don't count NULL ops */ #ifdef _USE_NFS3 if (req->rq_msg.cb_vers == NFS_V3) { struct nfsv3_stats *sp = get_v3(gsh_st, lock); /* record stuff */ switch (nfsv3_optype[proto_op]) { /* op count for READ & WRITE is already done */ case READ_OP: case WRITE_OP: break; default: record_op_only(&sp->cmds, success, dup); } } /* We don't do V4 here and V2 is toast */ #endif #ifdef _USE_NFS3 } else if (program_op == NFS_program[P_MNT]) { struct mnt_stats *sp = get_mnt(gsh_st, lock); if (req->rq_msg.cb_vers == MOUNT_V1) record_op_only(&sp->v1_ops, success, dup); else record_op_only(&sp->v3_ops, success, dup); #endif #ifdef _USE_NLM } else if (program_op == NFS_program[P_NLM]) { struct nlmv4_stats *sp = get_nlm4(gsh_st, lock); record_op_only(&sp->ops, success, dup); #endif #ifdef _USE_RQUOTA } else if (program_op == NFS_program[P_RQUOTA]) { struct rquota_stats *sp = get_rquota(gsh_st, lock); if (req->rq_msg.cb_vers == RQUOTAVERS) record_op_only(&sp->ops, success, dup); else record_op_only(&sp->ext_ops, success, dup); #endif } } static void record_clnt_all_stats(struct gsh_clnt_allops_stats *gsh_st, pthread_rwlock_t *lock, uint32_t prog_op, uint32_t proto_op, uint32_t vers, bool success, bool dup) { if (prog_op == NFS_program[P_NFS]) { if (proto_op == 0) return; /* we don't count NULL ops */ if (vers == NFS_V4) { struct clnt_allops_v4_stats *sp = get_v4_all(gsh_st, lock); /* record stuff */ record_clnt_ops(&(sp->cmds[proto_op]), success, dup); #ifdef _USE_NFS3 } else if (vers == NFS_V3) { struct clnt_allops_v3_stats *sp = get_v3_all(gsh_st, lock); /* record stuff */ record_clnt_ops(&(sp->cmds[proto_op]), success, dup); #endif } #ifdef _USE_NLM } else if (prog_op == NFS_program[P_NLM]) { struct clnt_allops_nlm_stats *sp = get_nlm4_all(gsh_st, lock); record_clnt_ops(&(sp->cmds[proto_op]), success, dup); #endif } } /** * @brief Record request statistics (V3 era protos only) * * Decode the protocol and find the proto specific stats struct. * Once we found the stats block, do the update(s). * * @param gsh_st [IN] stats struct from client or export * @param lock [IN] lock on client|export for malloc * @param reqdata [IN] info about the proto request * @param success [IN] the op returned OK (or error) * @param request_time [IN] time consumed by request * @param dup [IN] detected this was a dup request */ static void record_stats(struct gsh_stats *gsh_st, pthread_rwlock_t *lock, nfs_request_t *reqdata, nsecs_elapsed_t request_time, bool success, bool dup, bool global) { struct svc_req *req = &reqdata->svc; uint32_t proto_op = req->rq_msg.cb_proc; uint32_t program_op = req->rq_msg.cb_prog; if (program_op == NFS_program[P_NFS]) { if (proto_op == 0) return; /* we don't count NULL ops */ #ifdef _USE_NFS3 if (req->rq_msg.cb_vers == NFS_V3) { struct nfsv3_stats *sp = get_v3(gsh_st, lock); /* record stuff */ if (global) record_op(&global_st.nfsv3.cmds, request_time, success, dup); switch (nfsv3_optype[proto_op]) { case READ_OP: record_latency(&sp->read.cmd, request_time, dup); break; case WRITE_OP: record_latency(&sp->write.cmd, request_time, dup); break; default: record_op(&sp->cmds, request_time, success, dup); } } #endif /* We don't do V4 here and V2 is toast */ #ifdef _USE_NFS3 } else if (program_op == NFS_program[P_MNT]) { struct mnt_stats *sp = get_mnt(gsh_st, lock); if (global && req->rq_msg.cb_vers == MOUNT_V1) record_op(&global_st.mnt.v1_ops, request_time, success, dup); else if (global) record_op(&global_st.mnt.v3_ops, request_time, success, dup); /* record stuff */ if (req->rq_msg.cb_vers == MOUNT_V1) record_op(&sp->v1_ops, request_time, success, dup); else record_op(&sp->v3_ops, request_time, success, dup); #endif #ifdef _USE_NLM } else if (program_op == NFS_program[P_NLM]) { struct nlmv4_stats *sp = get_nlm4(gsh_st, lock); if (global) record_op(&global_st.nlm4.ops, request_time, success, dup); /* record stuff */ record_op(&sp->ops, request_time, success, dup); #endif #ifdef _USE_RQUOTA } else if (program_op == NFS_program[P_RQUOTA]) { struct rquota_stats *sp = get_rquota(gsh_st, lock); if (global) record_op(&global_st.rquota.ops, request_time, success, dup); /* record stuff */ if (req->rq_msg.cb_vers == RQUOTAVERS) record_op(&sp->ops, request_time, success, dup); else record_op(&sp->ext_ops, request_time, success, dup); #endif } } #ifdef _USE_9P /** * @brief Record transport stats * */ static void record_transport_stats(struct transport_stats *t_st, uint64_t rx_bytes, uint64_t rx_pkt, uint64_t rx_err, uint64_t tx_bytes, uint64_t tx_pkt, uint64_t tx_err) { if (rx_bytes) atomic_add_uint64_t(&t_st->rx_bytes, rx_bytes); if (rx_pkt) atomic_add_uint64_t(&t_st->rx_pkt, rx_pkt); if (rx_err) atomic_add_uint64_t(&t_st->rx_err, rx_err); if (tx_bytes) atomic_add_uint64_t(&t_st->tx_bytes, tx_bytes); if (tx_pkt) atomic_add_uint64_t(&t_st->tx_pkt, tx_pkt); if (tx_err) atomic_add_uint64_t(&t_st->tx_err, tx_err); } /** * @brief record 9P tcp transport stats * * Called from 9P functions doing send/recv */ void server_stats_transport_done(struct gsh_client *client, uint64_t rx_bytes, uint64_t rx_pkt, uint64_t rx_err, uint64_t tx_bytes, uint64_t tx_pkt, uint64_t tx_err) { struct server_stats *server_st = container_of(client, struct server_stats, client); struct _9p_stats *sp = get_9p(&server_st->st, &client->client_lock); if (sp != NULL) record_transport_stats(&sp->trans, rx_bytes, rx_pkt, rx_err, tx_bytes, tx_pkt, tx_err); } /** * @bried record 9p operation stats * * Called from 9P interpreter at operation completion */ void server_stats_9p_done(u8 opc, struct _9p_request_data *req9p) { struct gsh_client *client; struct gsh_export *export; struct _9p_stats *sp; client = req9p->pconn->client; if (client) { struct server_stats *server_st; server_st = container_of(client, struct server_stats, client); sp = get_9p(&server_st->st, &client->client_lock); if (sp->opcodes[opc] == NULL) sp->opcodes[opc] = gsh_calloc(1, sizeof(struct proto_op)); record_op(sp->opcodes[opc], 0, true, false); } if (op_ctx->ctx_export) { struct export_stats *exp_st; export = op_ctx->ctx_export; exp_st = container_of(export, struct export_stats, export); sp = get_9p(&exp_st->st, &export->exp_lock); if (sp->opcodes[opc] == NULL) sp->opcodes[opc] = gsh_calloc(1, sizeof(struct proto_op)); record_op(sp->opcodes[opc], 0, true, false); } } #endif #ifdef _USE_NFS3 static void record_v3_full_stats(struct svc_req *req, nsecs_elapsed_t request_time, int status, bool dup); #endif static void record_v4_full_stats(uint32_t proc, nsecs_elapsed_t request_time, nfsstat4 status); /** * @brief record NFS op finished * * Called from nfs_rpc_process_request at operation/command completion */ void server_stats_nfs_done(nfs_request_t *reqdata, int rc, bool dup) { struct gsh_client *client = op_ctx->client; struct timespec current_time; nsecs_elapsed_t time_diff; struct svc_req *req = &reqdata->svc; uint32_t proto_op = req->rq_msg.cb_proc; uint32_t program_op = req->rq_msg.cb_prog; if (!nfs_param.core_param.enable_NFSSTATS) return; #ifdef _USE_NFS3 if (program_op == NFS_PROGRAM && op_ctx->nfs_vers == NFS_V3) global_st.v3.op[proto_op]++; #endif #ifdef _USE_NLM else if (program_op == NFS_program[P_NLM]) global_st.lm.op[proto_op]++; #endif #ifdef _USE_NFS3 else if (program_op == NFS_program[P_MNT]) global_st.mn.op[proto_op]++; #endif #ifdef _USE_RQUOTA else if (program_op == NFS_program[P_RQUOTA]) global_st.qt.op[proto_op]++; #endif if (nfs_param.core_param.enable_FASTSTATS) return; now(¤t_time); time_diff = timespec_diff(&op_ctx->start_time, ¤t_time); #ifdef _USE_NFS3 if (nfs_param.core_param.enable_FULLV3STATS) record_v3_full_stats(req, time_diff, rc, dup); #endif if (client != NULL) { struct server_stats *server_st; server_st = container_of(client, struct server_stats, client); record_clnt_stats(&server_st->st, &client->client_lock, reqdata, rc == NFS_REQ_OK, dup); if (nfs_param.core_param.enable_CLNTALLSTATS) record_clnt_all_stats(&server_st->c_all, &client->client_lock, program_op, proto_op, NFS_V3, rc == NFS_REQ_OK, dup); timespec_update(&client->last_update, ¤t_time); } if (!dup && op_ctx->ctx_export != NULL) { struct export_stats *exp_st; exp_st = container_of(op_ctx->ctx_export, struct export_stats, export); record_stats(&exp_st->st, &op_ctx->ctx_export->exp_lock, reqdata, time_diff, rc == NFS_REQ_OK, dup, true); timespec_update(&op_ctx->ctx_export->last_update, ¤t_time); } } /** * @brief record NFS V4 compound finished * * Called from nfs4_compound at compound loop completion */ void server_stats_nfsv4_op_done(int proto_op, struct timespec *start_time, int status) { struct gsh_client *client = op_ctx->client; struct timespec current_time; nsecs_elapsed_t time_diff; if (!nfs_param.core_param.enable_NFSSTATS) return; if (op_ctx->nfs_vers == NFS_V4) global_st.v4.op[proto_op]++; if (nfs_param.core_param.enable_FASTSTATS) return; now(¤t_time); time_diff = timespec_diff(start_time, ¤t_time); nfs_metrics__nfs4_op_completed(proto_op, status, time_diff); if (nfs_param.core_param.enable_FULLV4STATS) record_v4_full_stats(proto_op, time_diff, status); if (client != NULL) { struct server_stats *server_st; server_st = container_of(client, struct server_stats, client); record_nfsv4_op(&server_st->st, &client->client_lock, proto_op, op_ctx->nfs_minorvers, time_diff, status, false); if (nfs_param.core_param.enable_CLNTALLSTATS) record_clnt_all_stats(&server_st->c_all, &client->client_lock, NFS_program[P_NFS], proto_op, NFS_V4, status == NFS_REQ_OK, false); timespec_update(&client->last_update, ¤t_time); } if (op_ctx->nfs_minorvers == 0) record_op(&global_st.nfsv40.compounds, time_diff, status == NFS4_OK, false); else if (op_ctx->nfs_minorvers == 1) record_op(&global_st.nfsv41.compounds, time_diff, status == NFS4_OK, false); else if (op_ctx->nfs_minorvers == 2) record_op(&global_st.nfsv42.compounds, time_diff, status == NFS4_OK, false); if (op_ctx->ctx_export != NULL) { struct export_stats *exp_st; exp_st = container_of(op_ctx->ctx_export, struct export_stats, export); record_nfsv4_op(&exp_st->st, &op_ctx->ctx_export->exp_lock, proto_op, op_ctx->nfs_minorvers, time_diff, status, true); timespec_update(&op_ctx->ctx_export->last_update, ¤t_time); } } /** * @brief record NFS V4 compound finished * * Called from nfs4_compound at compound loop completion */ void server_stats_compound_done(int num_ops, int status) { struct gsh_client *client = op_ctx->client; struct timespec current_time; nsecs_elapsed_t time_diff; if (!nfs_param.core_param.enable_NFSSTATS) return; now(¤t_time); time_diff = timespec_diff(&op_ctx->start_time, ¤t_time); nfs_metrics__nfs4_compound_completed(status, time_diff, num_ops); if (client != NULL) { struct server_stats *server_st; server_st = container_of(client, struct server_stats, client); record_compound(&server_st->st, &client->client_lock, op_ctx->nfs_minorvers, num_ops, time_diff, status == NFS4_OK); timespec_update(&client->last_update, ¤t_time); } if (op_ctx->ctx_export != NULL) { struct export_stats *exp_st; exp_st = container_of(op_ctx->ctx_export, struct export_stats, export); record_compound(&exp_st->st, &op_ctx->ctx_export->exp_lock, op_ctx->nfs_minorvers, num_ops, time_diff, status == NFS4_OK); timespec_update(&op_ctx->ctx_export->last_update, ¤t_time); } } /** * @brief Record I/O stats for protocol read/write * * Called from protocol operation/command handlers to record * transfers */ void server_stats_io_done(size_t requested, size_t transferred, bool success, bool is_write) { if (!nfs_param.core_param.enable_NFSSTATS) return; if (op_ctx->client != NULL) { struct server_stats *server_st; server_st = container_of(op_ctx->client, struct server_stats, client); record_io_stats(&server_st->st, &op_ctx->client->client_lock, requested, transferred, success, is_write); } if (op_ctx->ctx_export != NULL) { struct export_stats *exp_st; exp_st = container_of(op_ctx->ctx_export, struct export_stats, export); record_io_stats(&exp_st->st, &op_ctx->ctx_export->exp_lock, requested, transferred, success, is_write); } #ifdef USE_MONITORING if (op_ctx->req_type == NFS_REQUEST) { uint16_t export_id = 0; struct fsal_export *export = op_ctx->fsal_export; struct gsh_client *client = op_ctx->client; const char *client_ip = client == NULL ? "" : client->hostaddr_str; if (export != NULL) export_id = export->export_id; monitoring__dynamic_observe_nfs_io(requested, transferred, success, is_write, export_id, client_ip); } #endif } /** * @brief record Delegation stats * * Called from a bunch of places. */ void check_deleg_struct(struct gsh_stats *stats, pthread_rwlock_t *lock) { if (unlikely(stats->deleg == NULL)) { PTHREAD_RWLOCK_wrlock(lock); if (stats->deleg == NULL) stats->deleg = gsh_calloc(1, sizeof(struct deleg_stats)); PTHREAD_RWLOCK_unlock(lock); } } void inc_grants(struct gsh_client *client) { if (client != NULL) { struct server_stats *server_st; server_st = container_of(client, struct server_stats, client); check_deleg_struct(&server_st->st, &client->client_lock); server_st->st.deleg->curr_deleg_grants++; } } void dec_grants(struct gsh_client *client) { if (client != NULL) { struct server_stats *server_st; server_st = container_of(client, struct server_stats, client); check_deleg_struct(&server_st->st, &client->client_lock); server_st->st.deleg->curr_deleg_grants++; } } void inc_revokes(struct gsh_client *client) { if (client != NULL) { struct server_stats *server_st; server_st = container_of(client, struct server_stats, client); check_deleg_struct(&server_st->st, &client->client_lock); server_st->st.deleg->num_revokes++; } } void inc_recalls(struct gsh_client *client) { if (client != NULL) { struct server_stats *server_st; server_st = container_of(client, struct server_stats, client); check_deleg_struct(&server_st->st, &client->client_lock); server_st->st.deleg->tot_recalls++; } } void inc_failed_recalls(struct gsh_client *client) { if (client != NULL) { struct server_stats *server_st; server_st = container_of(client, struct server_stats, client); check_deleg_struct(&server_st->st, &client->client_lock); server_st->st.deleg->failed_recalls++; } } #ifdef USE_DBUS void dbus_message_iter_append_protocol_info(DBusMessageIter *niter, char **protocol, dbus_bool_t *enabled) { DBusMessageIter p_iter; dbus_message_iter_open_container(niter, DBUS_TYPE_STRUCT, NULL, &p_iter); dbus_message_iter_append_basic(&p_iter, DBUS_TYPE_STRING, protocol); dbus_message_iter_append_basic(&p_iter, DBUS_TYPE_BOOLEAN, enabled); dbus_message_iter_close_container(niter, &p_iter); } /* Functions for marshalling statistics to DBUS */ /** * @brief Report Stats availability as members of a struct * * For every protocol variant, report whether any stats are available by * setting the boolean to true. This is used to determine whether to * report the protocol in the stats struct. This boolean parameter is set based * on whether the corresponding structure was initialised and if there were * any IO operations for the protocol. * * struct available_stats { * ... * bool nfsv3; * bool mnt; * bool nlm4; * bool rquota; * bool nfsv40; * bool nfsv41; * bool nfsv42; * bool _9p; * ... * } * * @param name [IN] name of export or IP address as string * @param stats [IN] pointer to server stats struct * @param last_update [IN] elapsed timestamp of last activity * @param iter [IN] iterator to stuff struct into */ void server_stats_summary(DBusMessageIter *iter, struct gsh_stats *st) { dbus_bool_t stats_available; DBusMessageIter st_iter; char *protocol; dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &st_iter); #ifdef _USE_NFS3 stats_available = (st->nfsv3 != 0 && (st->nfsv3->cmds.total != 0 || st->nfsv3->read.cmd.total != 0 || st->nfsv3->write.cmd.total != 0)); protocol = "NFSv3"; dbus_message_iter_append_protocol_info(&st_iter, &protocol, &stats_available); stats_available = (st->mnt != 0 && (st->mnt->v1_ops.total != 0 || st->mnt->v3_ops.total != 0)); protocol = "MNT"; dbus_message_iter_append_protocol_info(&st_iter, &protocol, &stats_available); #endif #ifdef _USE_NLM stats_available = (st->nlm4 != 0 && st->nlm4->ops.total != 0); protocol = "NLMv4"; dbus_message_iter_append_protocol_info(&st_iter, &protocol, &stats_available); #endif #ifdef _USE_RQUOTA stats_available = (st->rquota != 0 && (st->rquota->ops.total != 0 || st->rquota->ext_ops.total != 0)); protocol = "RQUOTA"; dbus_message_iter_append_protocol_info(&st_iter, &protocol, &stats_available); #endif stats_available = (st->nfsv40 != 0 && (st->nfsv40->compounds.total != 0 || st->nfsv40->read.cmd.total != 0 || st->nfsv40->write.cmd.total != 0)); protocol = "NFSv40"; dbus_message_iter_append_protocol_info(&st_iter, &protocol, &stats_available); stats_available = (st->nfsv41 != 0 && (st->nfsv41->compounds.total != 0 || st->nfsv41->read.cmd.total != 0 || st->nfsv41->write.cmd.total != 0)); protocol = "NFSv41"; dbus_message_iter_append_protocol_info(&st_iter, &protocol, &stats_available); stats_available = (st->nfsv42 != 0 && (st->nfsv42->compounds.total != 0 || st->nfsv42->read.cmd.total != 0 || st->nfsv42->write.cmd.total != 0)); protocol = "NFSv42"; dbus_message_iter_append_protocol_info(&st_iter, &protocol, &stats_available); #ifdef _USE_9P stats_available = (st->_9p != 0 && (st->_9p->cmds.total != 0 || st->_9p->read.cmd.total != 0 || st->_9p->write.cmd.total != 0)); protocol = "9P"; dbus_message_iter_append_protocol_info(&st_iter, &protocol, &stats_available); #endif dbus_message_iter_close_container(iter, &st_iter); } #ifdef _USE_9P /** @brief Report protocol operation statistics * * struct proto_op { * uint64_t total; * uint64_t errors; * ... * } * * @param op [IN] pointer to proto op sub-structure of interest * @param iter [IN] iterator in reply stream to fill */ static void server_dbus_op_stats(struct proto_op *op, DBusMessageIter *iter) { DBusMessageIter struct_iter; uint64_t zero = 0; dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, op == NULL ? &zero : &op->total); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, op == NULL ? &zero : &op->errors); dbus_message_iter_close_container(iter, &struct_iter); } #endif /** * @brief Report I/O statistics as a struct * * struct iostats { * uint64_t bytes_requested; * uint64_t bytes_transferred; * uint64_t total_ops; * uint64_t errors; * uint64_t latency; * uint64_t queue_wait; * } * * @param iop [IN] pointer to xfer op sub-structure of interest * @param iter [IN] iterator in reply stream to fill */ static void server_dbus_iostats(struct xfer_op *iop, DBusMessageIter *iter) { DBusMessageIter struct_iter; uint64_t zero = 0; dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &iop->requested); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &iop->transferred); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &iop->cmd.total); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &iop->cmd.errors); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &iop->cmd.latency.latency); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &zero); dbus_message_iter_close_container(iter, &struct_iter); } /** * @brief Report Client/Export statistics for xfer_op * * struct iostats { * uint64_t total_ops; * uint64_t errors; * double latency; * uint64_t bytes_transferred; * } * * @param iop [IN] pointer to xfer op sub-structure of interest * @param iter [IN] iterator in reply stream to fill * @param for_export [IN] boolean indicating whether it is for an export **/ static void server_dbus_cexop_stats(struct xfer_op *iop, DBusMessageIter *iter, bool for_export) { DBusMessageIter struct_iter; double res = 0.0; dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &iop->cmd.total); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &iop->cmd.errors); if (for_export) { if (iop->cmd.total) res = (double)(iop->cmd.latency.latency * 0.000001) / (iop->cmd.total); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_DOUBLE, &res); } dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &iop->transferred); dbus_message_iter_close_container(iter, &struct_iter); } /** * @brief Report Client/Export statistics for proto_op * * struct iostats { * uint64_t total_ops; * uint64_t errors; * double latency; * } * * @param iop [IN] pointer to proto_op sub-structure of interest * @param iter [IN] iterator in reply stream to fill * @param for_export [IN] boolean indicating whether it is for an export **/ static void server_dbus_ceop_stats(struct proto_op *op, DBusMessageIter *iter, bool for_export) { DBusMessageIter struct_iter; double res = 0.0; dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &op->total); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &op->errors); if (for_export) { if (op->total) res = (double)(op->latency.latency * 0.000001) / op->total; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_DOUBLE, &res); } dbus_message_iter_close_container(iter, &struct_iter); } /** * @brief Report Client/Export statistics for layout operations * * struct layout { * uint64_t total; * uint64_t errors; * uint64_t delays; * } * * @param sp [IN] pointer to nfsv41_stats sub-structure of interest * @param iter [IN] iterator in reply stream to fill * @param for_export [IN] boolean indicating whether it is for an export **/ static void server_dbus_celo_stats(struct nfsv41_stats *sp, DBusMessageIter *iter, bool for_export) { DBusMessageIter struct_iter; uint64_t total, errors, delays; total = sp->getdevinfo.total + sp->layout_get.total + sp->layout_commit.total + sp->layout_return.total; errors = sp->getdevinfo.errors + sp->layout_get.errors + sp->layout_commit.errors + sp->layout_return.errors; delays = sp->getdevinfo.delays + sp->layout_get.delays + sp->layout_commit.delays + sp->layout_return.delays; dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &total); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &errors); if (for_export) dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &delays); dbus_message_iter_close_container(iter, &struct_iter); } #ifdef _USE_9P static void server_dbus_transportstats(struct transport_stats *tstats, DBusMessageIter *iter) { DBusMessageIter struct_iter; dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &tstats->rx_bytes); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &tstats->rx_pkt); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &tstats->rx_err); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &tstats->tx_bytes); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &tstats->tx_pkt); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &tstats->tx_err); dbus_message_iter_close_container(iter, &struct_iter); } #endif void server_dbus_client_io_ops(DBusMessageIter *iter, struct gsh_client *client) { struct server_stats *svr = NULL; struct gsh_stats *st; dbus_bool_t stats_available; svr = container_of(client, struct server_stats, client); st = &svr->st; gsh_dbus_append_timestamp(iter, &client->last_update); #ifdef _USE_NFS3 stats_available = st->nfsv3 != 0; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &stats_available); if (st->nfsv3) { server_dbus_cexop_stats(&st->nfsv3->read, iter, false); server_dbus_cexop_stats(&st->nfsv3->write, iter, false); server_dbus_ceop_stats(&st->nfsv3->cmds, iter, false); } #endif stats_available = st->nfsv40 != 0; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &stats_available); if (st->nfsv40) { server_dbus_cexop_stats(&st->nfsv40->read, iter, false); server_dbus_cexop_stats(&st->nfsv40->write, iter, false); server_dbus_ceop_stats(&st->nfsv40->compounds, iter, false); } stats_available = st->nfsv41 != 0; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &stats_available); if (st->nfsv41) { server_dbus_cexop_stats(&st->nfsv41->read, iter, false); server_dbus_cexop_stats(&st->nfsv41->write, iter, false); server_dbus_ceop_stats(&st->nfsv41->compounds, iter, false); server_dbus_celo_stats(st->nfsv41, iter, false); } stats_available = st->nfsv42 != 0; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &stats_available); if (st->nfsv42) { server_dbus_cexop_stats(&st->nfsv42->read, iter, false); server_dbus_cexop_stats(&st->nfsv42->write, iter, false); server_dbus_ceop_stats(&st->nfsv42->compounds, iter, false); server_dbus_celo_stats(st->nfsv42, iter, false); } } void server_dbus_client_all_ops(DBusMessageIter *iter, struct gsh_client *client) { struct server_stats *svr = NULL; struct gsh_clnt_allops_stats *c_all; dbus_bool_t stats_available; int i; DBusMessageIter array_iter; struct gsh_stats *st; uint64_t tot_cmp = 0, err_cmp = 0, ops_in_cmp = 0; char *op_name; svr = container_of(client, struct server_stats, client); c_all = &svr->c_all; st = &svr->st; gsh_dbus_append_timestamp(iter, &client->last_update); #ifdef _USE_NFS3 /* Stats of NFSv3 ops */ stats_available = c_all->nfsv3 != 0; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &stats_available); if (c_all->nfsv3) { dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &array_iter); for (i = 0; i < NFS_V3_NB_COMMAND; i++) { if (c_all->nfsv3->cmds[i].total) { op_name = (char *)nfsproc3_to_str(i); dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, &op_name); dbus_message_iter_append_basic( &array_iter, DBUS_TYPE_UINT64, &c_all->nfsv3->cmds[i].total); dbus_message_iter_append_basic( &array_iter, DBUS_TYPE_UINT64, &c_all->nfsv3->cmds[i].errors); dbus_message_iter_append_basic( &array_iter, DBUS_TYPE_UINT64, &c_all->nfsv3->cmds[i].dups); } } dbus_message_iter_close_container(iter, &array_iter); } #endif #ifdef _USE_NLM /* Stats of NLMv4 ops */ stats_available = c_all->nlm4 != 0; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &stats_available); if (c_all->nlm4) { dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &array_iter); for (i = 0; i < NLM_V4_NB_OPERATION; i++) { if (c_all->nlm4->cmds[i].total) { dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, &optnlm[i].name); dbus_message_iter_append_basic( &array_iter, DBUS_TYPE_UINT64, &c_all->nlm4->cmds[i].total); dbus_message_iter_append_basic( &array_iter, DBUS_TYPE_UINT64, &c_all->nlm4->cmds[i].errors); dbus_message_iter_append_basic( &array_iter, DBUS_TYPE_UINT64, &c_all->nlm4->cmds[i].dups); } } dbus_message_iter_close_container(iter, &array_iter); } #endif /* Stats of NFSv4 ops */ stats_available = c_all->nfsv4 != 0; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &stats_available); if (c_all->nfsv4) { dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &array_iter); for (i = 0; i < NFS4_OP_LAST_ONE; i++) { if (c_all->nfsv4->cmds[i].total) { op_name = (char *)nfsop4_to_str(i); dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, &op_name); dbus_message_iter_append_basic( &array_iter, DBUS_TYPE_UINT64, &c_all->nfsv4->cmds[i].total); dbus_message_iter_append_basic( &array_iter, DBUS_TYPE_UINT64, &c_all->nfsv4->cmds[i].errors); } } dbus_message_iter_close_container(iter, &array_iter); } /* Gather info abt compound ops */ if (st->nfsv40) { tot_cmp += st->nfsv40->compounds.total; err_cmp += st->nfsv40->compounds.errors; ops_in_cmp += st->nfsv40->ops_per_compound; } if (st->nfsv41) { tot_cmp += st->nfsv41->compounds.total; err_cmp += st->nfsv41->compounds.errors; ops_in_cmp += st->nfsv41->ops_per_compound; } if (st->nfsv42) { tot_cmp += st->nfsv42->compounds.total; err_cmp += st->nfsv42->compounds.errors; ops_in_cmp += st->nfsv42->ops_per_compound; } stats_available = tot_cmp != 0; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &stats_available); if (stats_available) { dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &array_iter); dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_UINT64, &tot_cmp); dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_UINT64, &err_cmp); dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_UINT64, &ops_in_cmp); dbus_message_iter_close_container(iter, &array_iter); } } void server_dbus_export_details(DBusMessageIter *iter, struct gsh_export *g_export) { struct export_stats *exp_st = NULL; struct gsh_stats *st; dbus_bool_t stats_available; exp_st = container_of(g_export, struct export_stats, export); st = &exp_st->st; gsh_dbus_append_timestamp(iter, &g_export->last_update); #ifdef _USE_NFS3 stats_available = st->nfsv3 != 0; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &stats_available); if (st->nfsv3) { server_dbus_cexop_stats(&st->nfsv3->read, iter, true); server_dbus_cexop_stats(&st->nfsv3->write, iter, true); server_dbus_ceop_stats(&st->nfsv3->cmds, iter, true); } #endif stats_available = st->nfsv40 != 0; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &stats_available); if (st->nfsv40) { server_dbus_cexop_stats(&st->nfsv40->read, iter, true); server_dbus_cexop_stats(&st->nfsv40->write, iter, true); server_dbus_ceop_stats(&st->nfsv40->compounds, iter, true); } stats_available = st->nfsv41 != 0; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &stats_available); if (st->nfsv41) { server_dbus_cexop_stats(&st->nfsv41->read, iter, true); server_dbus_cexop_stats(&st->nfsv41->write, iter, true); server_dbus_ceop_stats(&st->nfsv41->compounds, iter, true); server_dbus_celo_stats(st->nfsv41, iter, true); } stats_available = st->nfsv42 != 0; dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &stats_available); if (st->nfsv42) { server_dbus_cexop_stats(&st->nfsv42->read, iter, true); server_dbus_cexop_stats(&st->nfsv42->write, iter, true); server_dbus_ceop_stats(&st->nfsv42->compounds, iter, true); server_dbus_celo_stats(st->nfsv42, iter, true); } } void server_dbus_total(struct export_stats *export_st, DBusMessageIter *iter) { DBusMessageIter struct_iter; uint64_t total = 0; char *version; dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); #ifdef _USE_NFS3 version = "NFSv3"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &version); if (export_st->st.nfsv3 == NULL) dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &total); else dbus_message_iter_append_basic( &struct_iter, DBUS_TYPE_UINT64, &export_st->st.nfsv3->cmds.total); #endif version = "NFSv40"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &version); if (export_st->st.nfsv40 == NULL) dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &total); else dbus_message_iter_append_basic( &struct_iter, DBUS_TYPE_UINT64, &export_st->st.nfsv40->compounds.total); version = "NFSv41"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &version); if (export_st->st.nfsv41 == NULL) dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &total); else dbus_message_iter_append_basic( &struct_iter, DBUS_TYPE_UINT64, &export_st->st.nfsv41->compounds.total); version = "NFSv42"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &version); if (export_st->st.nfsv42 == NULL) dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &total); else dbus_message_iter_append_basic( &struct_iter, DBUS_TYPE_UINT64, &export_st->st.nfsv42->compounds.total); dbus_message_iter_close_container(iter, &struct_iter); } void global_dbus_total(DBusMessageIter *iter) { DBusMessageIter struct_iter; char *version; dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); #ifdef _USE_NFS3 version = "NFSv3"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &version); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &global_st.nfsv3.cmds.total); #endif version = "NFSv40"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &version); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &global_st.nfsv40.compounds.total); version = "NFSv41"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &version); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &global_st.nfsv41.compounds.total); version = "NFSv42"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &version); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &global_st.nfsv42.compounds.total); #ifdef _USE_NLM version = "NLM4"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &version); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &global_st.nlm4.ops.total); #endif #ifdef _USE_NFS3 version = "MNTv1"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &version); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &global_st.mnt.v1_ops.total); version = "MNTv3"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &version); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &global_st.mnt.v3_ops.total); #endif #ifdef _USE_RQUOTA version = "RQUOTA"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &version); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &global_st.rquota.ops.total); #endif dbus_message_iter_close_container(iter, &struct_iter); } void global_dbus_fast(DBusMessageIter *iter) { DBusMessageIter struct_iter; char *version; char *op; int i; dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); #ifdef _USE_NFS3 version = "NFSv3:"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &version); for (i = 0; i < NFS_V3_NB_COMMAND; i++) { if (global_st.v3.op[i] > 0) { op = (char *)nfsproc3_to_str(i); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &op); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &global_st.v3.op[i]); } } #endif version = "\nNFSv4:"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &version); for (i = 0; i < NFS4_OP_LAST_ONE; i++) { if (global_st.v4.op[i] > 0) { op = (char *)nfsop4_to_str(i); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &op); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &global_st.v4.op[i]); } } #ifdef _USE_NLM version = "\nNLM:"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &version); for (i = 0; i < NLM_V4_NB_OPERATION; i++) { if (global_st.lm.op[i] > 0) { op = optnlm[i].name; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &op); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &global_st.lm.op[i]); } } #endif #ifdef _USE_NFS3 version = "\nMNT:"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &version); for (i = 0; i < MNT_V3_NB_COMMAND; i++) { if (global_st.mn.op[i] > 0) { op = optmnt[i].name; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &op); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &global_st.mn.op[i]); } } #endif #ifdef _USE_RQUOTA version = "\nQUOTA:"; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &version); for (i = 0; i < RQUOTA_NB_COMMAND; i++) { if (global_st.qt.op[i] > 0) { op = optqta[i].name; dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &op); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &global_st.qt.op[i]); } } #endif dbus_message_iter_close_container(iter, &struct_iter); } #ifdef _USE_NFS3 void server_dbus_v3_iostats(struct nfsv3_stats *v3p, DBusMessageIter *iter) { gsh_dbus_append_timestamp(iter, &nfs_stats_time); server_dbus_iostats(&v3p->read, iter); server_dbus_iostats(&v3p->write, iter); } #endif void server_dbus_v40_iostats(struct nfsv40_stats *v40p, DBusMessageIter *iter) { gsh_dbus_append_timestamp(iter, &nfs_stats_time); server_dbus_iostats(&v40p->read, iter); server_dbus_iostats(&v40p->write, iter); } void server_dbus_v41_iostats(struct nfsv41_stats *v41p, DBusMessageIter *iter) { gsh_dbus_append_timestamp(iter, &nfs_stats_time); server_dbus_iostats(&v41p->read, iter); server_dbus_iostats(&v41p->write, iter); } void server_dbus_v42_iostats(struct nfsv41_stats *v42p, DBusMessageIter *iter) { gsh_dbus_append_timestamp(iter, &nfs_stats_time); server_dbus_iostats(&v42p->read, iter); server_dbus_iostats(&v42p->write, iter); } void server_nfsmon_export_iostats(struct export_stats *export_st, struct xfer_op *opread, struct xfer_op *opwrite) { struct gsh_stats gsh_st = export_st->st; #ifdef _USE_NFS3 if (gsh_st.nfsv3 != NULL) { (void)atomic_add_uint64_t(&opread->cmd.total, gsh_st.nfsv3->read.cmd.total); (void)atomic_add_uint64_t(&opread->requested, gsh_st.nfsv3->read.requested); (void)atomic_add_uint64_t(&opread->transferred, gsh_st.nfsv3->read.transferred); (void)atomic_add_uint64_t(&opwrite->cmd.total, gsh_st.nfsv3->write.cmd.total); (void)atomic_add_uint64_t(&opwrite->requested, gsh_st.nfsv3->write.requested); (void)atomic_add_uint64_t(&opwrite->transferred, gsh_st.nfsv3->write.transferred); } #endif if (gsh_st.nfsv40 != NULL) { (void)atomic_add_uint64_t(&opread->cmd.total, gsh_st.nfsv40->read.cmd.total); (void)atomic_add_uint64_t(&opread->requested, gsh_st.nfsv40->read.requested); (void)atomic_add_uint64_t(&opread->transferred, gsh_st.nfsv40->read.transferred); (void)atomic_add_uint64_t(&opwrite->cmd.total, gsh_st.nfsv40->write.cmd.total); (void)atomic_add_uint64_t(&opwrite->requested, gsh_st.nfsv40->write.requested); (void)atomic_add_uint64_t(&opwrite->transferred, gsh_st.nfsv40->write.transferred); } if (gsh_st.nfsv41 != NULL) { (void)atomic_add_uint64_t(&opread->cmd.total, gsh_st.nfsv41->read.cmd.total); (void)atomic_add_uint64_t(&opread->requested, gsh_st.nfsv41->read.requested); (void)atomic_add_uint64_t(&opread->transferred, gsh_st.nfsv41->read.transferred); (void)atomic_add_uint64_t(&opwrite->cmd.total, gsh_st.nfsv41->write.cmd.total); (void)atomic_add_uint64_t(&opwrite->requested, gsh_st.nfsv41->write.requested); (void)atomic_add_uint64_t(&opwrite->transferred, gsh_st.nfsv41->write.transferred); } if (gsh_st.nfsv42 != NULL) { (void)atomic_add_uint64_t(&opread->cmd.total, gsh_st.nfsv42->read.cmd.total); (void)atomic_add_uint64_t(&opread->requested, gsh_st.nfsv42->read.requested); (void)atomic_add_uint64_t(&opread->transferred, gsh_st.nfsv42->read.transferred); (void)atomic_add_uint64_t(&opwrite->cmd.total, gsh_st.nfsv42->write.cmd.total); (void)atomic_add_uint64_t(&opwrite->requested, gsh_st.nfsv42->write.requested); (void)atomic_add_uint64_t(&opwrite->transferred, gsh_st.nfsv42->write.transferred); } } void server_ret_nfsmon_iostats(struct xfer_op *op_read, struct xfer_op *op_write, struct xfer_op *op_preread, struct xfer_op *op_prewrite) { (void)atomic_sub_uint64_t(&op_read->cmd.total, op_preread->cmd.total); (void)atomic_sub_uint64_t(&op_read->requested, op_preread->requested); (void)atomic_sub_uint64_t(&op_read->transferred, op_preread->transferred); (void)atomic_sub_uint64_t(&op_write->cmd.total, op_prewrite->cmd.total); (void)atomic_sub_uint64_t(&op_write->requested, op_prewrite->requested); (void)atomic_sub_uint64_t(&op_write->transferred, op_prewrite->transferred); } void server_dbus_nfsmon_iostats(struct export_stats *export_st, DBusMessageIter *iter) { struct xfer_op *op_preread = NULL; struct xfer_op *op_prewrite = NULL; struct xfer_op *op_read = NULL; struct xfer_op *op_write = NULL; op_preread = (struct xfer_op *)gsh_calloc(1, sizeof(struct xfer_op)); op_prewrite = (struct xfer_op *)gsh_calloc(1, sizeof(struct xfer_op)); op_read = (struct xfer_op *)gsh_calloc(1, sizeof(struct xfer_op)); op_write = (struct xfer_op *)gsh_calloc(1, sizeof(struct xfer_op)); server_nfsmon_export_iostats(export_st, op_preread, op_prewrite); sleep(1); server_nfsmon_export_iostats(export_st, op_read, op_write); server_ret_nfsmon_iostats(op_read, op_write, op_preread, op_prewrite); gsh_dbus_append_timestamp(iter, &nfs_stats_time); server_dbus_iostats(op_read, iter); server_dbus_iostats(op_write, iter); gsh_free(op_preread); gsh_free(op_prewrite); gsh_free(op_read); gsh_free(op_write); } void server_dbus_fill_io(DBusMessageIter *array_iter, uint16_t *export_id, const char *protocolversion, struct xfer_op *read, struct xfer_op *write) { DBusMessageIter struct_iter; LogFullDebug(COMPONENT_DBUS, " Found %s I/O stats for export ID %d", protocolversion, *export_id); /* create a structure container iterator for the export statistics */ dbus_message_iter_open_container(array_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); /* append export statistics */ dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT16, export_id); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &(protocolversion)); server_dbus_iostats(read, &struct_iter); server_dbus_iostats(write, &struct_iter); /* close the structure container */ dbus_message_iter_close_container(array_iter, &struct_iter); } /** * @brief Return all IO stats of an export * * @reply DBUS_TYPE_ARRAY, "qs(tttttt)(tttttt)" * export id * string containing the protocol version * read statistics structure * (requested, transferred, total, errors, latency, queue wait) * write statistics structure * (requested, transferred, total, errors, latency, queue wait) */ void server_dbus_all_iostats(struct export_stats *export_statistics, DBusMessageIter *array_iter) { #ifdef _USE_NFS3 if (export_statistics->st.nfsv3 != NULL) { server_dbus_fill_io(array_iter, &(export_statistics->export.export_id), "NFSv3", &(export_statistics->st.nfsv3->read), &(export_statistics->st.nfsv3->write)); } #endif if (export_statistics->st.nfsv40 != NULL) { server_dbus_fill_io(array_iter, &(export_statistics->export.export_id), "NFSv40", &(export_statistics->st.nfsv40->read), &(export_statistics->st.nfsv40->write)); } if (export_statistics->st.nfsv41 != NULL) { server_dbus_fill_io(array_iter, &(export_statistics->export.export_id), "NFSv41", &(export_statistics->st.nfsv41->read), &(export_statistics->st.nfsv41->write)); } if (export_statistics->st.nfsv42 != NULL) { server_dbus_fill_io(array_iter, &(export_statistics->export.export_id), "NFSv42", &(export_statistics->st.nfsv42->read), &(export_statistics->st.nfsv42->write)); } } void reset_gsh_stats(struct gsh_stats *st) { #ifdef _USE_NFS3 if (st->nfsv3) reset_nfsv3_stats(st->nfsv3); #endif if (st->nfsv40) reset_nfsv40_stats(st->nfsv40); if (st->nfsv41) reset_nfsv41_stats(st->nfsv41); if (st->nfsv42) reset_nfsv41_stats(st->nfsv42); /* Uses v41 stats */ #ifdef _USE_NFS3 if (st->mnt) reset_mnt_stats(st->mnt); #endif #ifdef _USE_RQUOTA if (st->rquota) reset_rquota_stats(st->rquota); #endif #ifdef _USE_NLM if (st->nlm4) reset_nlmv4_stats(st->nlm4); #endif if (st->deleg) reset_deleg_stats(st->deleg); #ifdef _USE_9P if (st->_9p) reset__9P_stats(st->_9p); #endif } void reset_gsh_allops_stats(struct gsh_clnt_allops_stats *st) { int i; #ifdef _USE_NFS3 if (st->nfsv3) { for (i = 0; i < NFS_V3_NB_COMMAND; i++) reset_op_count(&(st->nfsv3->cmds[i])); } #endif if (st->nfsv4) { for (i = 0; i < NFS4_OP_LAST_ONE; i++) reset_op_count(&(st->nfsv4->cmds[i])); } #ifdef _USE_NLM if (st->nlm4) { for (i = 0; i < NLM_V4_NB_OPERATION; i++) reset_op_count(&(st->nlm4->cmds[i])); } #endif } void reset_global_stats(void) { int i; #ifdef _USE_NFS3 /* Reset all ops counters of nfsv3 */ for (i = 0; i < NFS_V3_NB_COMMAND; i++) (void)atomic_store_uint64_t(&global_st.v3.op[i], 0); #endif /* Reset all ops counters of nfsv4 */ for (i = 0; i < NFS4_OP_LAST_ONE; i++) (void)atomic_store_uint64_t(&global_st.v4.op[i], 0); #ifdef _USE_NLM /* Reset all ops counters of lock manager */ for (i = 0; i < NLM_V4_NB_OPERATION; i++) (void)atomic_store_uint64_t(&global_st.lm.op[i], 0); #endif #ifdef _USE_NFS3 /* Reset all ops counters of mountd */ for (i = 0; i < MNT_V3_NB_COMMAND; i++) (void)atomic_store_uint64_t(&global_st.mn.op[i], 0); #endif #ifdef _USE_RQUOTA /* Reset all ops counters of rquotad */ for (i = 0; i < RQUOTA_NB_COMMAND; i++) (void)atomic_store_uint64_t(&global_st.qt.op[i], 0); #endif #ifdef _USE_NFS3 reset_nfsv3_stats(&global_st.nfsv3); #endif reset_nfsv40_stats(&global_st.nfsv40); reset_nfsv41_stats(&global_st.nfsv41); reset_nfsv41_stats(&global_st.nfsv42); /* Uses v41 stats */ #ifdef _USE_NFS3 reset_mnt_stats(&global_st.mnt); #endif #ifdef _USE_RQUOTA reset_rquota_stats(&global_st.rquota); #endif #ifdef _USE_NLM reset_nlmv4_stats(&global_st.nlm4); #endif } void server_dbus_total_ops(struct export_stats *export_st, DBusMessageIter *iter) { gsh_dbus_append_timestamp(iter, &nfs_stats_time); server_dbus_total(export_st, iter); } void server_dbus_fast_ops(DBusMessageIter *iter) { gsh_dbus_append_timestamp(iter, &nfs_stats_time); global_dbus_fast(iter); } void global_dbus_total_ops(DBusMessageIter *iter) { gsh_dbus_append_timestamp(iter, &nfs_stats_time); global_dbus_total(iter); } void reset_server_stats(void) { reset_global_stats(); reset_export_stats(); reset_client_stats(); #ifdef _USE_NFS3 reset_v3_full_stats(); #endif reset_v4_full_stats(); } #ifdef _USE_9P void server_dbus_9p_iostats(struct _9p_stats *_9pp, DBusMessageIter *iter) { struct timespec timestamp; now(×tamp); gsh_dbus_append_timestamp(iter, ×tamp); server_dbus_iostats(&_9pp->read, iter); server_dbus_iostats(&_9pp->write, iter); } void server_dbus_9p_transstats(struct _9p_stats *_9pp, DBusMessageIter *iter) { struct timespec timestamp; now(×tamp); gsh_dbus_append_timestamp(iter, ×tamp); server_dbus_transportstats(&_9pp->trans, iter); } void server_dbus_9p_opstats(struct _9p_stats *_9pp, u8 opcode, DBusMessageIter *iter) { struct timespec timestamp; now(×tamp); gsh_dbus_append_timestamp(iter, ×tamp); server_dbus_op_stats(_9pp->opcodes[opcode], iter); } #endif /** * @brief Report layout statistics as a struct * * struct layout { * uint64_t total_layouts; * uint64_t errors; * uint64_t delays; * } * * @param iop [IN] pointer to xfer op sub-structure of interest * @param iter [IN] iterator in reply stream to fill */ static void server_dbus_layouts(struct layout_op *lop, DBusMessageIter *iter) { DBusMessageIter struct_iter; dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &lop->total); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &lop->errors); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT64, &lop->delays); dbus_message_iter_close_container(iter, &struct_iter); } void server_dbus_v41_layouts(struct nfsv41_stats *v41p, DBusMessageIter *iter) { gsh_dbus_append_timestamp(iter, &nfs_stats_time); server_dbus_layouts(&v41p->getdevinfo, iter); server_dbus_layouts(&v41p->layout_get, iter); server_dbus_layouts(&v41p->layout_commit, iter); server_dbus_layouts(&v41p->layout_return, iter); server_dbus_layouts(&v41p->recall, iter); } void server_dbus_v42_layouts(struct nfsv41_stats *v42p, DBusMessageIter *iter) { gsh_dbus_append_timestamp(iter, &nfs_stats_time); server_dbus_layouts(&v42p->getdevinfo, iter); server_dbus_layouts(&v42p->layout_get, iter); server_dbus_layouts(&v42p->layout_commit, iter); server_dbus_layouts(&v42p->layout_return, iter); server_dbus_layouts(&v42p->recall, iter); } /** * @brief Report delegation statistics as a struct * * @param iop [IN] pointer to xfer op sub-structure of interest * @param iter [IN] iterator in reply stream to fill */ void server_dbus_delegations(struct deleg_stats *ds, DBusMessageIter *iter) { DBusMessageIter struct_iter; gsh_dbus_append_timestamp(iter, &nfs_stats_time); dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, NULL, &struct_iter); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &ds->curr_deleg_grants); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &ds->tot_recalls); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &ds->failed_recalls); dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &ds->num_revokes); dbus_message_iter_close_container(iter, &struct_iter); } #ifdef _USE_NFS3 /** * @brief NFSv3 Detailed stats reporting */ void server_dbus_v3_full_stats(DBusMessageIter *iter) { DBusMessageIter array_iter, op_iter; int op; double res = 0.0; uint64_t op_counter = 0; char *message, *op_name; gsh_dbus_append_timestamp(iter, &v3_full_stats_time); dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "(stttddd)", &array_iter); for (op = 1; op < NFS_V3_NB_COMMAND; op++) { if (v3_full_stats[op].total) { op_name = (char *)nfsproc3_to_str(op); dbus_message_iter_open_container( &array_iter, DBUS_TYPE_STRUCT, NULL, &op_iter); dbus_message_iter_append_basic( &op_iter, DBUS_TYPE_STRING, &op_name); dbus_message_iter_append_basic( &op_iter, DBUS_TYPE_UINT64, &v3_full_stats[op].total); dbus_message_iter_append_basic( &op_iter, DBUS_TYPE_UINT64, &v3_full_stats[op].errors); dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_UINT64, &v3_full_stats[op].dups); res = (double)v3_full_stats[op].latency.latency * 0.000001 / v3_full_stats[op].total; dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_DOUBLE, &res); res = (double)v3_full_stats[op].latency.min * 0.000001; dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_DOUBLE, &res); res = (double)v3_full_stats[op].latency.max * 0.000001; dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_DOUBLE, &res); dbus_message_iter_close_container(&array_iter, &op_iter); op_counter += v3_full_stats[op].total; } } if (op_counter == 0) { message = "None"; /* insert dummy stats to avoid dbus crash */ dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, NULL, &op_iter); dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_STRING, &message); dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_UINT64, &op_counter); dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_UINT64, &op_counter); dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_UINT64, &op_counter); dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_DOUBLE, &res); dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_DOUBLE, &res); dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_DOUBLE, &res); dbus_message_iter_close_container(&array_iter, &op_iter); } else { message = "OK"; } dbus_message_iter_close_container(iter, &array_iter); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &message); } #endif /** * @brief NFSv4 Detailed stats reporting */ void server_dbus_v4_full_stats(DBusMessageIter *iter) { DBusMessageIter array_iter, op_iter; int op; double res = 0.0; uint64_t op_counter = 0; char *message, *op_name; gsh_dbus_append_timestamp(iter, &v4_full_stats_time); dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "(sttddd)", &array_iter); for (op = 1; op < NFS_V42_NB_OPERATION; op++) { if (v4_full_stats[op].total) { op_name = (char *)nfsop4_to_str(op); dbus_message_iter_open_container( &array_iter, DBUS_TYPE_STRUCT, NULL, &op_iter); dbus_message_iter_append_basic( &op_iter, DBUS_TYPE_STRING, &op_name); dbus_message_iter_append_basic( &op_iter, DBUS_TYPE_UINT64, &v4_full_stats[op].total); dbus_message_iter_append_basic( &op_iter, DBUS_TYPE_UINT64, &v4_full_stats[op].errors); res = (double)v4_full_stats[op].latency.latency * 0.000001 / v4_full_stats[op].total; dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_DOUBLE, &res); res = (double)v4_full_stats[op].latency.min * 0.000001; dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_DOUBLE, &res); res = (double)v4_full_stats[op].latency.max * 0.000001; dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_DOUBLE, &res); dbus_message_iter_close_container(&array_iter, &op_iter); op_counter += v4_full_stats[op].total; } } if (op_counter == 0) { message = "None"; dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, NULL, &op_iter); /* insert dummy stats to avoid dbus crash */ dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_STRING, &message); dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_UINT64, &op_counter); dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_UINT64, &op_counter); dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_DOUBLE, &res); dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_DOUBLE, &res); dbus_message_iter_append_basic(&op_iter, DBUS_TYPE_DOUBLE, &res); dbus_message_iter_close_container(&array_iter, &op_iter); } else { message = "OK"; } dbus_message_iter_close_container(iter, &array_iter); dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &message); } #endif /* USE_DBUS */ /** * @brief Free statistics storage * * The struct itself is not freed because it is a member * of either the client manager struct or the export struct. * * @param statsp [IN] pointer to stats to be cleaned */ void server_stats_free(struct gsh_stats *statsp) { #ifdef _USE_NFS3 if (statsp->nfsv3 != NULL) { gsh_free(statsp->nfsv3); statsp->nfsv3 = NULL; } if (statsp->mnt != NULL) { gsh_free(statsp->mnt); statsp->mnt = NULL; } #endif #ifdef _USE_NLM if (statsp->nlm4 != NULL) { gsh_free(statsp->nlm4); statsp->nlm4 = NULL; } #endif #ifdef _USE_RQUOTA if (statsp->rquota != NULL) { gsh_free(statsp->rquota); statsp->rquota = NULL; } #endif if (statsp->nfsv40 != NULL) { gsh_free(statsp->nfsv40); statsp->nfsv40 = NULL; } if (statsp->nfsv41 != NULL) { gsh_free(statsp->nfsv41); statsp->nfsv41 = NULL; } if (statsp->nfsv42 != NULL) { gsh_free(statsp->nfsv42); statsp->nfsv42 = NULL; } #ifdef _USE_9P if (statsp->_9p != NULL) { u8 opc; for (opc = 0; opc <= _9P_RWSTAT; opc++) { if (statsp->_9p->opcodes[opc] != NULL) gsh_free(statsp->_9p->opcodes[opc]); } gsh_free(statsp->_9p); statsp->_9p = NULL; } #endif } /** * @brief Free statistics storage used for all ops * * The struct itself is not freed because it is a member * of either the client manager struct. * * @param statsp [IN] pointer to stats to be cleaned */ void server_stats_allops_free(struct gsh_clnt_allops_stats *statsp) { #ifdef _USE_NFS3 if (statsp->nfsv3 != NULL) { gsh_free(statsp->nfsv3); statsp->nfsv3 = NULL; } #endif if (statsp->nfsv4 != NULL) { gsh_free(statsp->nfsv4); statsp->nfsv4 = NULL; } #ifdef _USE_NLM if (statsp->nlm4 != NULL) { gsh_free(statsp->nlm4); statsp->nlm4 = NULL; } #endif } #ifdef _USE_NFS3 static void record_v3_full_stats(struct svc_req *req, nsecs_elapsed_t request_time, int status, bool dup) { uint32_t prog = req->rq_msg.cb_prog; uint32_t vers = req->rq_msg.cb_vers; uint32_t proc = req->rq_msg.cb_proc; #ifdef USE_MONITORING if (prog == NFS_PROGRAM) { uint16_t export_id = 0; struct fsal_export *export = op_ctx->fsal_export; struct gsh_client *client = op_ctx->client; const char *client_ip = client == NULL ? "" : client->hostaddr_str; if (export != NULL) export_id = export->export_id; nfs_metrics__nfs3_request(proc, request_time, status, export_id, client_ip); } #endif if (prog == NFS_program[P_NFS] && vers == NFS_V3) { if (proc >= NFS_V3_NB_COMMAND) { LogCrit(COMPONENT_DBUS, "req->rq_proc is more than NFS_V3_NB_COMMAND: %d\n", proc); return; } record_op(&v3_full_stats[proc], request_time, status == NFS_REQ_OK, dup); } } void reset_v3_full_stats(void) { int op; for (op = 1; op < NFS_V3_NB_COMMAND; op++) { v3_full_stats[op].total = 0; v3_full_stats[op].errors = 0; v3_full_stats[op].dups = 0; v3_full_stats[op].latency.latency = 0; v3_full_stats[op].latency.min = 0; v3_full_stats[op].latency.max = 0; } } #endif static void record_v4_full_stats(uint32_t proc, nsecs_elapsed_t request_time, nfsstat4 status) { #ifdef USE_MONITORING uint16_t export_id = 0; struct fsal_export *export = op_ctx->fsal_export; struct gsh_client *client = op_ctx->client; const char *client_ip = client == NULL ? "" : client->hostaddr_str; if (export != NULL) export_id = export->export_id; nfs_metrics__nfs4_request(proc, request_time, status, export_id, client_ip); #endif if (proc >= NFS4_OP_LAST_ONE) { LogCrit(COMPONENT_DBUS, "proc is more than NFS4_OP_LAST_ONE: %d\n", proc); return; } record_op(&v4_full_stats[proc], request_time, status == NFS4_OK, false); } void reset_v4_full_stats(void) { int op; for (op = 1; op < NFS4_OP_LAST_ONE; op++) { v4_full_stats[op].total = 0; v4_full_stats[op].errors = 0; v4_full_stats[op].dups = 0; v4_full_stats[op].latency.latency = 0; v4_full_stats[op].latency.min = 0; v4_full_stats[op].latency.max = 0; } } /** @} */ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/strlcpy.c���������������������������������������������������������������0000664�0000000�0000000�00000003153�14737566223�0017746�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: 0BSD /* * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef HAVE_STRLCPY #include <sys/types.h> /* * Copy src to string dst of size siz. At most siz-1 characters * will be copied. Always NUL terminates (unless siz == 0). * Returns strlen(src); if retval >= siz, truncation occurred. */ size_t strlcpy(char *dst, const char *src, size_t siz) { register char *d = dst; register const char *s = src; register size_t n = siz; /* Copy as many bytes as will fit */ if (n != 0 && --n != 0) { do { *d++ = *s; if (*s++ == 0) break; } while (--n != 0); } /* Not enough room in dst, add NUL and traverse rest of src */ if (n == 0) { if (siz != 0) { /* NUL-terminate dst */ *d = '\0'; } /* Search for NUL in string */ while (*s++) { /* do nothing */ } } /* count does not include NUL */ return s - src - 1; } #endif ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/strnlen.c���������������������������������������������������������������0000664�0000000�0000000�00000002170�14737566223�0017731�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright (C) Paul Sheer, 2012 * Author: Paul Sheer paulsheer@gmail.com * * This program 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 3 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 * 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 * */ #ifndef HAVE_STRNLEN #include <sys/types.h> #include <stdlib.h> size_t gsh_strnlen(const char *s, size_t max) { register const char *p; for (p = s; *p && max--; ++p) { /* do nothing */ } return p - s; } #endif ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/uid2grp.c���������������������������������������������������������������0000664�0000000�0000000�00000045404�14737566223�0017627�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @addtogroup uid2grp * @{ */ /** * @file uid2grp.c * @brief Uid to group list conversion */ #include "config.h" #include "gsh_rpc.h" #include "nfs_core.h" #include <unistd.h> /* for using gethostname */ #include <stdlib.h> /* for using exit */ #include <strings.h> #include <string.h> #include <sys/types.h> #include <pwd.h> #include <grp.h> #include <stdint.h> #include <stdbool.h> #include "common_utils.h" #include "uid2grp.h" #include "idmapper.h" #ifdef USE_NFSIDMAP #include <nfsidmap.h> #endif #include "idmapper_monitoring.h" /* Switch to enable or disable idmapping */ extern bool idmapping_enabled; sem_t uid2grp_sem; /* group_data has a reference counter. If it goes to zero, it implies * that it is out of the cache (AVL trees) and should be freed. The * reference count is 1 when we put it into AVL trees. We decrement when * we take it out of AVL trees. Also incremented when we pass this to * out siders (uid2grp and friends) and decremented when they are done * (in uid2grp_unref()). * * When a group_data needs to be removed or expired after a certain * timeout, we take it out of the cache (AVL trees). When everyone using * the group_data are done, the refcount will go to zero at which point * we free group_data as well as the buffer holding supplementary * groups. */ void uid2grp_hold_group_data(struct group_data *gdata) { PTHREAD_MUTEX_lock(&gdata->gd_lock); gdata->refcount++; PTHREAD_MUTEX_unlock(&gdata->gd_lock); } void uid2grp_release_group_data(struct group_data *gdata) { unsigned int refcount; PTHREAD_MUTEX_lock(&gdata->gd_lock); refcount = --gdata->refcount; PTHREAD_MUTEX_unlock(&gdata->gd_lock); if (refcount == 0) { PTHREAD_MUTEX_destroy(&gdata->gd_lock); gsh_free(gdata->groups); gsh_free(gdata); } else if (refcount == (unsigned int)-1) { LogAlways(COMPONENT_IDMAPPER, "negative refcount on gdata: %p", gdata); } } /* Allocate supplementary groups buffer */ static bool my_getgrouplist_alloc(char *user, gid_t gid, struct group_data *gdata) { int ngroups = 1000; gid_t *groups = NULL; struct timespec s_time, e_time; bool stats = nfs_param.core_param.enable_AUTHSTATS; int ret; /* We call getgrouplist() with ngroups set to 1000 first. This should * reduce the number of getgrouplist() calls made to 1, for most cases. * However, getgrouplist() return -1 if the actual number of groups the * user is in, is more than 1000 (very rare) and ngroups will be set to * the actual number of groups the user is in. We can then make a second * query to fetch all the groups when ngroups is greater than 1000. * * The manpage doesn't say anything about errno value, it was usually * zero but was set to 34 (ERANGE) under some environments. ngroups was * set correctly no matter what the errno value is! * We assume that ngroups is correctly set, no matter what the * errno value is. The man page says, "The ngroups argument * is a value-result argument: on return it always contains * the number of groups found for user." */ groups = gsh_malloc(ngroups * sizeof(gid_t)); now(&s_time); ret = getgrouplist(user, gid, groups, &ngroups); now(&e_time); idmapper_monitoring__external_request(IDMAPPING_USERNAME_TO_GROUPLIST, IDMAPPING_PWUTILS, ret != -1, &s_time, &e_time); if (ret == -1) { LogEvent(COMPONENT_IDMAPPER, "getgrouplist for user: %s failed retrying", user); gsh_free(groups); /* Try with the actual ngroups if user is part of more than 1000 * groups. */ groups = gsh_malloc(ngroups * sizeof(gid_t)); now(&s_time); ret = getgrouplist(user, gid, groups, &ngroups); now(&e_time); idmapper_monitoring__external_request( IDMAPPING_USERNAME_TO_GROUPLIST, IDMAPPING_PWUTILS, ret != -1, &s_time, &e_time); if (ret == -1) { LogWarn(COMPONENT_IDMAPPER, "getgrouplist for user:%s failed, ngroups: %d", user, ngroups); gsh_free(groups); return false; } if (stats) { gc_stats_update(&s_time, &e_time); stats = false; } } idmapper_monitoring__user_groups(ngroups); if (ngroups != 0) { /* Resize the buffer, if it fails, gsh_realloc will * abort. */ groups = gsh_realloc(groups, ngroups * sizeof(gid_t)); } else { /* We need to free groups because later code may not. */ gsh_free(groups); groups = NULL; } if (stats) gc_stats_update(&s_time, &e_time); gdata->groups = groups; gdata->nbgroups = ngroups; return true; } /* Allocate and fill in group_data structure */ static struct group_data * uid2grp_allocate_by_name(const struct gsh_buffdesc *name) { struct passwd p; struct passwd *pp; char *namebuff = alloca(name->len + 1); struct group_data *gdata = NULL; char *buff; size_t buff_size; int retval; struct timespec s_time, e_time; memcpy(namebuff, name->addr, name->len); *(namebuff + name->len) = '\0'; buff_size = sysconf(_SC_GETPW_R_SIZE_MAX); if (buff_size == -1) { LogMajor(COMPONENT_IDMAPPER, "sysconf failure: %d", errno); buff_size = PWENT_BEST_GUESS_LEN; } while (buff_size <= PWENT_MAX_SIZE) { buff = gsh_malloc(buff_size); now_mono(&s_time); retval = getpwnam_r(namebuff, &p, buff, buff_size, &pp); now_mono(&e_time); idmapper_monitoring__external_request( IDMAPPING_USERNAME_TO_UIDGID, IDMAPPING_PWUTILS, retval == 0, &s_time, &e_time); if (retval != ERANGE) break; gsh_free(buff); buff_size *= 16; } if (retval == ERANGE) { LogWarn(COMPONENT_IDMAPPER, "Received ERANGE when fetching pw-entry from name: %s", namebuff); return NULL; } if (retval != 0) { LogEvent(COMPONENT_IDMAPPER, "getpwnam_r for %s failed, error %d", namebuff, retval); goto out; } if (pp == NULL) { LogEvent(COMPONENT_IDMAPPER, "No matching password record found for name %s", namebuff); goto out; } gdata = gsh_malloc(sizeof(struct group_data) + strlen(p.pw_name)); gdata->uname.len = strlen(p.pw_name); gdata->uname.addr = (char *)gdata + sizeof(struct group_data); memcpy(gdata->uname.addr, p.pw_name, gdata->uname.len); gdata->uid = p.pw_uid; gdata->gid = p.pw_gid; /* Throttle getgrouplist queries to Directory Server if required. */ if (nfs_param.core_param.max_uid_to_grp_reqs) sem_wait(&uid2grp_sem); if (!my_getgrouplist_alloc(p.pw_name, p.pw_gid, gdata)) { gsh_free(gdata); gdata = NULL; if (nfs_param.core_param.max_uid_to_grp_reqs) sem_post(&uid2grp_sem); goto out; } if (nfs_param.core_param.max_uid_to_grp_reqs) sem_post(&uid2grp_sem); PTHREAD_MUTEX_init(&gdata->gd_lock, NULL); gdata->epoch = time(NULL); gdata->refcount = 0; out: gsh_free(buff); return gdata; } /* Allocate and fill in group_data structure */ static struct group_data *uid2grp_allocate_by_uid(uid_t uid) { struct passwd p; struct passwd *pp; struct group_data *gdata = NULL; char *buff; size_t buff_size; int retval; struct timespec s_time, e_time; buff_size = sysconf(_SC_GETPW_R_SIZE_MAX); if (buff_size == -1) { LogMajor(COMPONENT_IDMAPPER, "sysconf failure: %d", errno); buff_size = PWENT_BEST_GUESS_LEN; } while (buff_size <= PWENT_MAX_SIZE) { buff = gsh_malloc(buff_size); now_mono(&s_time); retval = getpwuid_r(uid, &p, buff, buff_size, &pp); now_mono(&e_time); idmapper_monitoring__external_request(IDMAPPING_UID_TO_UIDGID, IDMAPPING_PWUTILS, retval == 0, &s_time, &e_time); if (retval != ERANGE) break; gsh_free(buff); buff_size *= 16; } if (retval == ERANGE) { LogWarn(COMPONENT_IDMAPPER, "Received ERANGE when fetching pw-entry from uid: %u", uid); return NULL; } if (retval != 0) { LogEvent(COMPONENT_IDMAPPER, "getpwuid_r for uid %u failed, error %d", uid, retval); goto out; } if (pp == NULL) { LogInfo(COMPONENT_IDMAPPER, "No matching password record found for uid %u", uid); goto out; } gdata = gsh_malloc(sizeof(struct group_data) + strlen(p.pw_name)); gdata->uname.len = strlen(p.pw_name); gdata->uname.addr = (char *)gdata + sizeof(struct group_data); memcpy(gdata->uname.addr, p.pw_name, gdata->uname.len); gdata->uid = p.pw_uid; gdata->gid = p.pw_gid; /* Throttle getgrouplist queries to Directory Server if required. */ if (nfs_param.core_param.max_uid_to_grp_reqs) sem_wait(&uid2grp_sem); if (!my_getgrouplist_alloc(p.pw_name, p.pw_gid, gdata)) { gsh_free(gdata); gdata = NULL; if (nfs_param.core_param.max_uid_to_grp_reqs) sem_post(&uid2grp_sem); goto out; } if (nfs_param.core_param.max_uid_to_grp_reqs) sem_post(&uid2grp_sem); PTHREAD_MUTEX_init(&gdata->gd_lock, NULL); gdata->epoch = time(NULL); gdata->refcount = 0; out: gsh_free(buff); return gdata; } /** * @brief Allocate supplementary groups using principal * * @note This function uses libnfsidmap internally * * @param[in] principal The principal name * @param[in] uid The uid of user represented by the principal * @param[in] gid The gid of user represented by the principal * * @return group_data with fetched groups. It can be NULL on lookup or * allocation failures. */ static struct group_data *uid2grp_allocate_by_principal(char *principal, uid_t uid, gid_t gid) { #ifdef USE_NFSIDMAP struct group_data *grpdata = NULL; const int default_ngroups = 1000; int ngroups = default_ngroups; gid_t *groups = NULL; int ret; struct timespec s_time, e_time; #ifdef _MSPAC_SUPPORT /* TODO */ LogWarn(COMPONENT_IDMAPPER, "Unsupported code path for principal %s", principal); return NULL; #endif /* We call nfs4_gss_princ_to_grouplist() with ngroups set to 1000 first. * This should reduce number of nfs4_gss_princ_to_grouplist() calls made * to 1, for most cases. However, nfs4_gss_princ_to_grouplist() returns * -ERANGE if the actual number of groups the user is in, is more * than 1000 (very rare) and ngroups will be set to the actual number of * groups the user is in. We can then make a second query to fetch all * the groups when ngroups is greater than 1000. */ groups = gsh_malloc(ngroups * sizeof(gid_t)); now_mono(&s_time); ret = nfs4_gss_princ_to_grouplist("krb5", principal, groups, &ngroups); now_mono(&e_time); idmapper_monitoring__external_request(IDMAPPING_PRINCIPAL_TO_GROUPLIST, IDMAPPING_NFSIDMAP, ret == 0, &s_time, &e_time); if (ret == -ERANGE) { /* Try with the actual ngroups since user is part of more than * 1000 groups */ gsh_free(groups); groups = gsh_malloc(ngroups * sizeof(gid_t)); now_mono(&s_time); ret = nfs4_gss_princ_to_grouplist("krb5", principal, groups, &ngroups); now_mono(&e_time); idmapper_monitoring__external_request( IDMAPPING_PRINCIPAL_TO_GROUPLIST, IDMAPPING_NFSIDMAP, ret == 0, &s_time, &e_time); if (ret) { LogWarn(COMPONENT_IDMAPPER, "Could not re-resolve principal %s to groups using nfsidmap, err: %d", principal, ret); gsh_free(groups); return NULL; } } else if (ret) { LogWarn(COMPONENT_IDMAPPER, "Could not resolve principal %s to groups using nfsidmap, err: %d", principal, ret); gsh_free(groups); return NULL; } LogDebug(COMPONENT_IDMAPPER, "Resolved principal %s to %d groups using nfsidmap", principal, ngroups); idmapper_monitoring__user_groups(ngroups); /* Resize or free the buffer as appropriate */ if (ngroups == 0) { gsh_free(groups); groups = NULL; } else if (ngroups < default_ngroups) { groups = gsh_realloc(groups, ngroups * sizeof(gid_t)); } grpdata = gsh_malloc(sizeof(struct group_data) + strlen(principal) + 1); /* We populate principal as the uname here */ grpdata->uname.len = strlen(principal); grpdata->uname.addr = (char *)grpdata + sizeof(struct group_data); memcpy(grpdata->uname.addr, principal, grpdata->uname.len); /* Null-terminate the uname string */ ((char *)grpdata->uname.addr)[grpdata->uname.len] = 0; grpdata->uid = uid; grpdata->gid = gid; grpdata->groups = groups; grpdata->nbgroups = ngroups; PTHREAD_MUTEX_init(&grpdata->gd_lock, NULL); grpdata->epoch = time(NULL); grpdata->refcount = 0; return grpdata; #else LogWarn(COMPONENT_IDMAPPER, "Invalid code path"); return NULL; #endif } /** * @brief Add a user entry to the cache * * @param[in] group_data user entry with allocated supplementary groups */ static void add_user_groups_to_cache(struct group_data **gdata) { /* Do not add to cache if idmapping is disabled */ if (!idmapping_enabled) { LogWarn(COMPONENT_IDMAPPER, "Idmapping is disabled, add-to-cache skipped"); return; } PTHREAD_RWLOCK_wrlock(&uid2grp_user_lock); /* Recheck if idmapping is enabled because it is possible that after * cache cleanup completes (resulting from the disabling of idmapping), * there are waiting requests with older idmapping data that might end * up writing their data to the cache. We need to stop them. */ if (!idmapping_enabled) { PTHREAD_RWLOCK_unlock(&uid2grp_user_lock); LogWarn(COMPONENT_IDMAPPER, "Idmapping is disabled, add-to-cache skipped"); return; } uid2grp_add_user(*gdata); uid2grp_hold_group_data(*gdata); PTHREAD_RWLOCK_unlock(&uid2grp_user_lock); } /** * @brief Get supplementary groups given uname * * @param[in] name The name of the user * @param[out] group_data The group data of the user * * @return true if successful, false otherwise */ bool name2grp(const struct gsh_buffdesc *name, struct group_data **gdata) { bool success = false; uid_t uid = -1; if (!idmapping_enabled) { LogWarn(COMPONENT_IDMAPPER, "Idmapping is disabled, name-to-group skipped"); return false; } PTHREAD_RWLOCK_rdlock(&uid2grp_user_lock); success = uid2grp_lookup_by_uname(name, &uid, gdata); /* Return success if we find non-expired group-data in cache */ if (success && !uid2grp_is_group_data_expired(*gdata)) { uid2grp_hold_group_data(*gdata); PTHREAD_RWLOCK_unlock(&uid2grp_user_lock); return success; } PTHREAD_RWLOCK_unlock(&uid2grp_user_lock); /* We could not find non-expired group-data in cache, fetch it afresh */ *gdata = uid2grp_allocate_by_name(name); if (*gdata) { /* This will also remove existing expired cache entry */ add_user_groups_to_cache(gdata); return true; } /* * At this point, we could not find non-expired group-data in cache, * and we also weren't able to fetch fresh group-data. * If the group-data in cache is expired, we stll want to remove it. */ if (success) { /* Remove expired cache entry */ PTHREAD_RWLOCK_wrlock(&uid2grp_user_lock); uid2grp_remove_expired_by_uname(name); PTHREAD_RWLOCK_unlock(&uid2grp_user_lock); } return false; } /** * @brief Get supplementary groups given uid * * @param[in] uid The uid of the user * @param[out] group_data The group data of the user * * @return true if successful, false otherwise */ bool uid2grp(uid_t uid, struct group_data **gdata) { bool success = false; if (!idmapping_enabled) { LogWarn(COMPONENT_IDMAPPER, "Idmapping is disabled, uid-to-group skipped"); return false; } PTHREAD_RWLOCK_rdlock(&uid2grp_user_lock); success = uid2grp_lookup_by_uid(uid, gdata); /* Return success if we find non-expired group-data in cache */ if (success && !uid2grp_is_group_data_expired(*gdata)) { uid2grp_hold_group_data(*gdata); PTHREAD_RWLOCK_unlock(&uid2grp_user_lock); return success; } PTHREAD_RWLOCK_unlock(&uid2grp_user_lock); /* We could not find non-expired group-data in cache, fetch it afresh */ *gdata = uid2grp_allocate_by_uid(uid); if (*gdata) { /* This will also remove existing expired cache entry */ add_user_groups_to_cache(gdata); return true; } /* * At this point, we could not find non-expired group-data in cache, * and we also weren't able to fetch fresh group-data. * If the group-data in cache is expired, we still want to remove it. */ if (success) { /* Remove expired cache entry */ PTHREAD_RWLOCK_wrlock(&uid2grp_user_lock); uid2grp_remove_expired_by_uid(uid); PTHREAD_RWLOCK_unlock(&uid2grp_user_lock); } return false; } /** * @brief Get supplementary groups given principal * * @note This function internally uses libnfsidmap functions * * @param[in] principal The principal name * @param[in] uid The uid of user represented by the principal * @param[in] gid The gid of user represented by the principal * @param[out] gdata Filled group_data structure containing user groups * * @return true if successful, false otherwise */ bool principal2grp(char *principal, struct group_data **gdata, const uid_t uid, const gid_t gid) { bool success = false; uid_t unused_cached_uid = -1; struct gsh_buffdesc princbuff = { .addr = principal, .len = strlen(principal) }; LogDebug(COMPONENT_IDMAPPER, "Resolve principal %s to groups", principal); if (!idmapping_enabled) { LogWarn(COMPONENT_IDMAPPER, "Idmapping is disabled, principal-to-group skipped"); return false; } PTHREAD_RWLOCK_rdlock(&uid2grp_user_lock); success = uid2grp_lookup_by_uname(&princbuff, &unused_cached_uid, gdata); /* Return success if we find non-expired group-data in cache */ if (success && !uid2grp_is_group_data_expired(*gdata)) { uid2grp_hold_group_data(*gdata); PTHREAD_RWLOCK_unlock(&uid2grp_user_lock); return true; } PTHREAD_RWLOCK_unlock(&uid2grp_user_lock); /* We could not find non-expired group-data in cache, fetch it afresh */ *gdata = uid2grp_allocate_by_principal(principal, uid, gid); if (*gdata) { /* This will also remove existing expired cache entry */ add_user_groups_to_cache(gdata); return true; } /* * At this point, we could not find non-expired group-data in cache, * and we also weren't able to fetch fresh group-data. * If the group-data in cache is expired, we still want to remove it. */ if (success) { /* Remove expired cache entry */ PTHREAD_RWLOCK_wrlock(&uid2grp_user_lock); uid2grp_remove_expired_by_uname(&princbuff); PTHREAD_RWLOCK_unlock(&uid2grp_user_lock); } return false; } /* * All callers of uid2grp(), uname2grp() and principal2grp() must call * this when they are done accessing supplementary groups */ void uid2grp_unref(struct group_data *gdata) { uid2grp_release_group_data(gdata); } /** @} */ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/uid2grp_cache.c���������������������������������������������������������0000664�0000000�0000000�00000033300�14737566223�0020742�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @addtogroup idmapper * @{ */ /** * @file uid_grplist_cache.c * @brief Uid->Group List mapping cache functions */ #include "config.h" #include "log.h" #include "config_parsing.h" #include <string.h> #include <pwd.h> #include <grp.h> #include <unistd.h> #include "gsh_intrinsic.h" #include "gsh_types.h" #include "common_utils.h" #include "avltree.h" #include "uid2grp.h" #include "abstract_atomic.h" #include "nfs_core.h" #include <misc/queue.h> #include "idmapper_monitoring.h" /** * @brief User entry in the uid2grp cache */ struct cache_info { uid_t uid; /*< Corresponding UID */ struct gsh_buffdesc uname; struct group_data *gdata; struct avltree_node uname_node; /*< Node in the name tree */ struct avltree_node uid_node; /*< Node in the UID tree */ TAILQ_ENTRY(cache_info) queue_entry; /* Node in groups-fifo-queue */ }; /** * @brief Number of entries in the UID cache, should be prime. */ #define id_cache_size 1009 /** * @brief A user-groups fifo queue ordered by insertion timestamp * * This fifo queue also mimics the order of expiration time of the cache * entries, since the expiration time is a linear function of the insertion * time. * * Expiration_time = Insertion_time + Cache_time_validity (constant) * * The head of the queue contains the entry with least time-validity. * The tail of the queue contains the entry with most time-validity. * The eviction happens from the head, and insertion happens at the tail. */ static TAILQ_HEAD(, cache_info) groups_fifo_queue; /** * @brief UID cache, may only be accessed with uid2grp_user_lock * held. If uid2grp_user_lock is held for read, it must be accessed * atomically. (For a write, normal fetch/store is sufficient since * others are kept out.) */ static struct avltree_node *uid_grplist_cache[id_cache_size]; /** * @brief Lock that protects the idmapper user cache */ pthread_rwlock_t uid2grp_user_lock; /** * @brief Tree of users, by name */ static struct avltree uname_tree; /** * @brief Tree of users, by ID */ static struct avltree uid_tree; /** * @brief Comparison for user names * * @param[in] node1 A node * @param[in] nodea Another node * * @retval -1 if node1 is less than nodea * @retval 0 if node1 and nodea are equal * @retval 1 if node1 is greater than nodea */ static int uname_comparator(const struct avltree_node *node1, const struct avltree_node *nodea) { struct cache_info *user1 = avltree_container_of(node1, struct cache_info, uname_node); struct cache_info *usera = avltree_container_of(nodea, struct cache_info, uname_node); return gsh_buffdesc_comparator(&user1->uname, &usera->uname); } /** * @brief Comparison for UIDs * * @param[in] node1 A node * @param[in] nodea Another node * * @retval -1 if node1 is less than nodea * @retval 0 if node1 and nodea are equal * @retval 1 if node1 is greater than nodea */ static int uid_comparator(const struct avltree_node *node1, const struct avltree_node *nodea) { struct cache_info *user1 = avltree_container_of(node1, struct cache_info, uid_node); struct cache_info *usera = avltree_container_of(nodea, struct cache_info, uid_node); if (user1->uid < usera->uid) return -1; else if (user1->uid > usera->uid) return 1; else return 0; } /* Cleanup on shutdown */ void uid2grp_cache_cleanup(void) { uid2grp_clear_cache(); PTHREAD_RWLOCK_destroy(&uid2grp_user_lock); } struct cleanup_list_element uid2grp_cache_cleanup_element = { .clean = uid2grp_cache_cleanup, }; /* Remove given user/cache_info from the AVL trees * * @note The caller must hold uid2grp_user_lock for write. */ static void uid2grp_remove_user(struct cache_info *info) { uid_grplist_cache[info->uid % id_cache_size] = NULL; avltree_remove(&info->uid_node, &uid_tree); avltree_remove(&info->uname_node, &uname_tree); TAILQ_REMOVE(&groups_fifo_queue, info, queue_entry); /* We decrement hold on group data when it is * removed from cache trees. */ uid2grp_release_group_data(info->gdata); gsh_free(info); } /** * @brief Reaps the uid2grp cache entries * * Since the fifo queue stores entries in increasing order of time validity, * the reaper reaps from the queue head in the same order. It stops when it * first encounters a non-expired entry. */ void uid2grp_cache_reap(void) { struct cache_info *groups; LogFullDebug(COMPONENT_IDMAPPER, "uid2grp cache reaper run started"); PTHREAD_RWLOCK_wrlock(&uid2grp_user_lock); for (groups = TAILQ_FIRST(&groups_fifo_queue); groups != NULL;) { if (!uid2grp_is_group_data_expired(groups->gdata)) break; uid2grp_remove_user(groups); groups = TAILQ_FIRST(&groups_fifo_queue); } PTHREAD_RWLOCK_unlock(&uid2grp_user_lock); LogFullDebug(COMPONENT_IDMAPPER, "uid2grp cache reaper run ended"); } /** * @brief Initialize the user-groups cache */ void uid2grp_cache_init(void) { PTHREAD_RWLOCK_init(&uid2grp_user_lock, NULL); if (nfs_param.core_param.max_uid_to_grp_reqs) sem_init(&uid2grp_sem, 0, nfs_param.core_param.max_uid_to_grp_reqs); avltree_init(&uname_tree, uname_comparator, 0); avltree_init(&uid_tree, uid_comparator, 0); memset(uid_grplist_cache, 0, id_cache_size * sizeof(struct avltree_node *)); TAILQ_INIT(&groups_fifo_queue); RegisterCleanup(&uid2grp_cache_cleanup_element); } /** * @brief Add a user entry to the cache * * @note The caller must hold uid2grp_user_lock for write. * * @param[in] group_data that has supplementary groups allocated */ void uid2grp_add_user(struct group_data *gdata) { struct avltree_node *name_node; struct avltree_node *id_node; struct avltree_node *name_node2 = NULL; struct avltree_node *id_node2 = NULL; struct cache_info *info; struct cache_info *tmp; struct cache_info *groups_fifo_queue_head_node; directory_services_param_t *ds_param = &nfs_param.directory_services_param; info = gsh_malloc(sizeof(struct cache_info)); info->uid = gdata->uid; info->uname.addr = gdata->uname.addr; info->uname.len = gdata->uname.len; info->gdata = gdata; /* The refcount on group_data should be 1 when we put it in * AVL trees. */ uid2grp_hold_group_data(gdata); /* We may have lost the race to insert. We remove existing * entry and insert this new entry if so! */ name_node = avltree_insert(&info->uname_node, &uname_tree); if (unlikely(name_node)) { tmp = avltree_container_of(name_node, struct cache_info, uname_node); uid2grp_remove_user(tmp); name_node2 = avltree_insert(&info->uname_node, &uname_tree); assert(name_node2 == NULL); } id_node = avltree_insert(&info->uid_node, &uid_tree); if (unlikely(id_node)) { /* We should not come here unless someone changed uid of * a user. Remove old entry and re-insert the new * entry. */ tmp = avltree_container_of(id_node, struct cache_info, uid_node); uid2grp_remove_user(tmp); id_node2 = avltree_insert(&info->uid_node, &uid_tree); assert(id_node2 == NULL); } uid_grplist_cache[info->uid % id_cache_size] = &info->uid_node; TAILQ_INSERT_TAIL(&groups_fifo_queue, info, queue_entry); /* If we breach max-cache capacity, remove the queue's head node */ if (avltree_size(&uname_tree) > ds_param->cache_user_groups_max_count) { LogInfo(COMPONENT_IDMAPPER, "Cache size limit violated, removing entry with least time validity"); groups_fifo_queue_head_node = TAILQ_FIRST(&groups_fifo_queue); const time_t cached_duration = time(NULL) - groups_fifo_queue_head_node->gdata->epoch; uid2grp_remove_user(groups_fifo_queue_head_node); idmapper_monitoring__evicted_cache_entity( IDMAPPING_CACHE_ENTITY_USER_GROUPS, cached_duration); } if (name_node && id_node) LogWarn(COMPONENT_IDMAPPER, "shouldn't happen, internal error"); if ((name_node && name_node2) || (id_node && id_node2)) LogWarn(COMPONENT_IDMAPPER, "shouldn't happen, internal error"); } static bool lookup_by_uname(const struct gsh_buffdesc *name, struct cache_info **info) { struct cache_info prototype = { .uname = *name }; struct avltree_node *found_node = avltree_lookup(&prototype.uname_node, &uname_tree); struct cache_info *found_info; void **cache_slot; if (unlikely(!found_node)) return false; found_info = avltree_container_of(found_node, struct cache_info, uname_node); /* I assume that if someone likes this user enough to look it up by name, they'll like it enough to look it up by ID later. */ cache_slot = (void **)&uid_grplist_cache[found_info->uid % id_cache_size]; atomic_store_voidptr(cache_slot, &found_info->uid_node); *info = found_info; return true; } static bool lookup_by_uid(const uid_t uid, struct cache_info **info) { struct cache_info prototype = { .uid = uid }; void **cache_slot = (void **)&uid_grplist_cache[prototype.uid % id_cache_size]; struct avltree_node *found_node = atomic_fetch_voidptr(cache_slot); struct cache_info *found_info; bool found = false; /* Verify that the node found in the cache array is in fact what we * want. */ if (likely(found_node)) { found_info = avltree_container_of(found_node, struct cache_info, uid_node); if (found_info->uid == uid) found = true; } if (unlikely(!found)) { found_node = avltree_lookup(&prototype.uid_node, &uid_tree); if (unlikely(!found_node)) return false; atomic_store_voidptr(cache_slot, found_node); found_info = avltree_container_of(found_node, struct cache_info, uid_node); } *info = found_info; return true; } /** * @brief Look up a user by name (can return expired cache entry) * * @note The caller must hold uid2grp_user_lock for read. * * @param[in] name The user name to look up. * @param[out] uid The user ID found. May be NULL if the caller * isn't interested in the UID. (This seems * unlikely.) * @param[out] gdata group_data containing supplementary groups. * * @retval true on success. * @retval false if we need to try, try again. */ bool uid2grp_lookup_by_uname(const struct gsh_buffdesc *name, uid_t *uid, struct group_data **gdata) { struct cache_info *info; bool success; success = lookup_by_uname(name, &info); if (success) { *gdata = info->gdata; *uid = info->gdata->uid; } return success; } /** * @brief Look up a user by ID (can return expired cache entry) * * @note The caller must hold uid2grp_user_lock for read. * * @param[in] uid The user ID to look up. * @gdata[out] group_data containing supplementary groups. * * @retval true on success. * @retval false if we weren't so successful. */ bool uid2grp_lookup_by_uid(const uid_t uid, struct group_data **gdata) { struct cache_info *info; bool success; success = lookup_by_uid(uid, &info); if (success) *gdata = info->gdata; return success; } bool uid2grp_is_group_data_expired(struct group_data *gdata) { time_t gdata_age = time(NULL) - gdata->epoch; return gdata_age > nfs_param.core_param.manage_gids_expiration; } /** * @brief Remove a user by ID * * @note The caller must hold uid2grp_user_lock for write. * * @param[in] uid The user ID to remove. */ void uid2grp_remove_by_uid(const uid_t uid) { struct cache_info *info; bool success; success = lookup_by_uid(uid, &info); if (success) uid2grp_remove_user(info); } /** * @brief Remove an expired user by ID * * @note The caller must hold uid2grp_user_lock for write. * * @param[in] uid The user ID to remove. */ void uid2grp_remove_expired_by_uid(const uid_t uid) { struct cache_info *info; bool success; success = lookup_by_uid(uid, &info); if (success && uid2grp_is_group_data_expired(info->gdata)) uid2grp_remove_user(info); } /** * @brief Remove a user by name * * @note The caller must hold uid2grp_user_lock for write. * * @param[in] name The user name to remove. */ void uid2grp_remove_by_uname(const struct gsh_buffdesc *name) { struct cache_info *info; bool success; success = lookup_by_uname(name, &info); if (success) uid2grp_remove_user(info); } /** * @brief Remove an expired user by name * * @note The caller must hold uid2grp_user_lock for write. * * @param[in] name The user name to remove. */ void uid2grp_remove_expired_by_uname(const struct gsh_buffdesc *name) { struct cache_info *info; bool success; success = lookup_by_uname(name, &info); if (success && uid2grp_is_group_data_expired(info->gdata)) uid2grp_remove_user(info); } /** * @brief Wipe out the uid2grp cache */ void uid2grp_clear_cache(void) { struct avltree_node *node; int removed_group_data_entries = 0; PTHREAD_RWLOCK_wrlock(&uid2grp_user_lock); while ((node = avltree_first(&uname_tree))) { struct cache_info *info = avltree_container_of( node, struct cache_info, uname_node); uid2grp_remove_user(info); removed_group_data_entries++; } assert(avltree_first(&uid_tree) == NULL); PTHREAD_RWLOCK_unlock(&uid2grp_user_lock); LogDebug(COMPONENT_IDMAPPER, "Total group-data cache entries removed: %d", removed_group_data_entries); } /** @} */ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/support/xprt_handler.c����������������������������������������������������������0000664�0000000�0000000�00000026302�14737566223�0020741�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2023 Google LLC * Contributor : Dipit Grover dipit@google.com * * This program 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 3 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 * 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 * * --------------------------------------- */ /** * @file xprt_handler.c * @brief This file handles functionality related to service transport. */ #include "xprt_handler.h" #include "nfs_core.h" #include "sal_functions.h" #include "sal_metrics.h" #include "gsh_xprt_tracepoint.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/xprt_handler.h" #endif /** * @brief Inits the xprt's user-data represented by the `xprt_custom_data_t` * struct. * * For each xprt, it must be called only once, during the xprt initialisation. */ void init_custom_data_for_xprt(SVCXPRT *xprt) { xprt_custom_data_t *xprt_data; char sockaddr_str[SOCK_NAME_MAX] = "\0"; struct display_buffer db = { sizeof(sockaddr_str), sockaddr_str, sockaddr_str }; assert(xprt->xp_u1 == NULL); xprt_data = gsh_malloc(sizeof(xprt_custom_data_t)); glist_init(&xprt_data->nfs41_sessions_holder.sessions); PTHREAD_RWLOCK_init(&xprt_data->nfs41_sessions_holder.sessions_lock, NULL); xprt_data->nfs41_sessions_holder.num_sessions = 0; xprt->xp_u1 = (void *)xprt_data; xprt_data->status = ASSOCIATED_TO_XPRT; sal_metrics__xprt_custom_data_status(ASSOCIATED_TO_XPRT); display_xprt_sockaddr(&db, xprt); LogDebug(COMPONENT_XPRT, "xp_u1 initialised for xprt with FD: %d and socket-addr: %s", xprt->xp_fd, sockaddr_str); GSH_XPRT_AUTO_TRACEPOINT(xprt_handler, init_custom_data, TRACE_INFO, xprt, "Initialised custom-data, socket-addr: {}", TP_STR(sockaddr_str)); } /** * @brief Adds the nfs41_session to the xprt session-list * * @note The caller must call this only after verifying that the xprt * is not already associated with the session */ bool add_nfs41_session_to_xprt(SVCXPRT *xprt, nfs41_session_t *session) { xprt_custom_data_t *xprt_data; nfs41_session_list_entry_t *new_entry; assert(xprt->xp_u1 != NULL); xprt_data = (xprt_custom_data_t *)xprt->xp_u1; new_entry = gsh_malloc(sizeof(nfs41_session_list_entry_t)); new_entry->session = session; inc_session_ref(session); PTHREAD_RWLOCK_wrlock(&xprt_data->nfs41_sessions_holder.sessions_lock); /* It is possible that the xprt_data is already being dissociated from * xprt. If so, we do not want to associate such xprt to the session. */ if (xprt_data->status == DISSOCIATED_FROM_XPRT) { PTHREAD_RWLOCK_unlock( &xprt_data->nfs41_sessions_holder.sessions_lock); LogWarn(COMPONENT_SESSIONS, "Do not associate xprt-data under dissociation with xprt FD: %d to the session", xprt->xp_fd); GSH_XPRT_AUTO_TRACEPOINT( xprt_handler, do_not_add_nfs41_session, TRACE_WARNING, xprt, "Do not associate xprt to the session: {}", TP_SESSION(session->session_id)); dec_session_ref(session); gsh_free(new_entry); sal_metrics__xprt_association_denied(); return false; } glist_add_tail(&xprt_data->nfs41_sessions_holder.sessions, &new_entry->node); const uint8_t num_sessions = ++xprt_data->nfs41_sessions_holder.num_sessions; PTHREAD_RWLOCK_unlock(&xprt_data->nfs41_sessions_holder.sessions_lock); sal_metrics__xprt_sessions(num_sessions); GSH_XPRT_AUTO_TRACEPOINT(xprt_handler, add_nfs41_session, TRACE_INFO, xprt, "Added nfs41 session: {}", TP_SESSION(session->session_id)); return true; } /** * @brief Removes the nfs41_session from the xprt session-list */ void remove_nfs41_session_from_xprt(SVCXPRT *xprt, nfs41_session_t *session) { struct glist_head *curr_node, *next_node; xprt_custom_data_t *xprt_data; nfs41_sessions_holder_t *sessions_holder; uint8_t found_session_count = 0; assert(xprt->xp_u1 != NULL); xprt_data = (xprt_custom_data_t *)xprt->xp_u1; sessions_holder = &xprt_data->nfs41_sessions_holder; PTHREAD_RWLOCK_wrlock(&sessions_holder->sessions_lock); glist_for_each_safe(curr_node, next_node, &sessions_holder->sessions) { nfs41_session_list_entry_t *const curr_entry = glist_entry( curr_node, nfs41_session_list_entry_t, node); if (curr_entry->session == session) { dec_session_ref(curr_entry->session); glist_del(curr_node); gsh_free(curr_entry); GSH_XPRT_AUTO_TRACEPOINT( xprt_handler, remove_nfs41_session, TRACE_INFO, xprt, "Removed session {} from xprt", TP_SESSION(session->session_id)); found_session_count++; } } sessions_holder->num_sessions -= found_session_count; const uint8_t num_sessions = sessions_holder->num_sessions; PTHREAD_RWLOCK_unlock(&sessions_holder->sessions_lock); if (found_session_count > 0) sal_metrics__xprt_sessions(num_sessions); } /** * @brief Removes xprt references, both of the xprt from the custom-data * components, and of the custom-data components from the xprt. * * This function should be called when destroying a xprt, in order to release * the above mentioned references. */ void dissociate_custom_data_from_xprt(SVCXPRT *xprt) { struct glist_head *curr_node, *next_node; struct glist_head duplicate_sessions; char xprt_addr_str[SOCK_NAME_MAX] = "\0"; struct display_buffer db = { sizeof(xprt_addr_str), xprt_addr_str, xprt_addr_str }; xprt_custom_data_t *xprt_data; nfs41_sessions_holder_t *sessions_holder; display_xprt_sockaddr(&db, xprt); if (xprt->xp_u1 == NULL) { LogInfo(COMPONENT_XPRT, "The xprt FD: %d, socket-addr: %s is not associated with any custom-data, done un-referencing.", xprt->xp_fd, xprt_addr_str); return; } LogDebug( COMPONENT_XPRT, "About to un-reference custom-data from xprt with FD: %d, socket-addr: %s", xprt->xp_fd, xprt_addr_str); GSH_XPRT_AUTO_TRACEPOINT( xprt_handler, dissociate_custom_data, TRACE_INFO, xprt, "Dissociate custom-data from xprt. socket-addr: {}", TP_STR(xprt_addr_str)); xprt_data = (xprt_custom_data_t *)xprt->xp_u1; assert(xprt_data->status == ASSOCIATED_TO_XPRT); sessions_holder = &xprt_data->nfs41_sessions_holder; /* Copy the xprt sessions to a new list to avoid deadlock that can * happen if we take xprt's session-list lock followed by session's * connections lock (this lock order is reverse of the order used * during xprt connection association and dis-association with a * session) * * With the below change, we do not acquire the nested session's * connections lock, while holding the xprt's session-list lock. We * first release the xprt's session-list lock after copying the xprt's * sessions to a duplicate session-list. We then acquire the session's * connection lock to process each session in the duplicate list. That * is, both the mentioned operations are not done atomically. * * This can result in possible situations where the xprt's session-list * has been cleared after the first operation, but those cleared * sessions still have the xprt's reference, until the second operation. * During this interval between the two operation, it is possible that * another thread (in a different operation) sees a missing session on * this xprt's session-list and tries to add it to that xprt, even * though that session already had a reference to this xprt. If this * situation happens, such a session added to this xprt's session-list * will be at risk of never getting un-referenced. Also, such a xprt's * reference would get re-added to the session, and then the xprt would * be at risk of never getting destroyed. However, since this other * thread additionally MUST also check if the xprt under consideration * is being destroyed (that is, xprt's custom-data is dissociated from * xprt) before adding the session to it, we are able to avoid this * situation. * * The same situation can also happen after the xprt is un-referenced * through this function, but another in-flight request is still * operating on this same xprt (under destruction). The above mentioned * check will also prevent this from happening. */ glist_init(&duplicate_sessions); PTHREAD_RWLOCK_wrlock(&sessions_holder->sessions_lock); /* Move all xprt-data sessions to the duplicate-sessions list */ glist_splice_tail(&duplicate_sessions, &sessions_holder->sessions); sessions_holder->num_sessions = 0; xprt_data->status = DISSOCIATED_FROM_XPRT; PTHREAD_RWLOCK_unlock(&sessions_holder->sessions_lock); /* Now process the duplicate list: for each session referenced by the * xprt, destroy the backchannel and release the connection_xprt held * by the session. */ glist_for_each_safe(curr_node, next_node, &duplicate_sessions) { nfs41_session_list_entry_t *curr_entry = glist_entry( curr_node, nfs41_session_list_entry_t, node); nfs41_Session_Destroy_Backchannel_For_Xprt(curr_entry->session, xprt); nfs41_Session_Remove_Connection(curr_entry->session, xprt); /* Release session reference */ dec_session_ref(curr_entry->session); /* Free the session-node allocated for the xprt */ glist_del(curr_node); gsh_free(curr_entry); } sal_metrics__xprt_custom_data_status(DISSOCIATED_FROM_XPRT); LogDebug( COMPONENT_XPRT, "Done dissociating custom-data for xprt with FD: %d, socket-addr: %s", xprt->xp_fd, xprt_addr_str); GSH_XPRT_AUTO_TRACEPOINT( xprt_handler, done_dissociate_custom_data, TRACE_INFO, xprt, "Done dissociating custom-data from xprt. socket-addr: {}", TP_STR(xprt_addr_str)); } /** * @brief handles cleanup of the custom data associated with the xprt * (if any), after the xprt is destroyed * * It is supposed to be invoked only once after the xprt's connection is * closed. */ void destroy_custom_data_for_destroyed_xprt(SVCXPRT *xprt) { char sockaddr_str[SOCK_NAME_MAX] = "\0"; struct display_buffer db = { sizeof(sockaddr_str), sockaddr_str, sockaddr_str }; xprt_custom_data_t *xprt_data; if (xprt->xp_u1 == NULL) { LogDebug(COMPONENT_XPRT, "No custom data to destroy for the destroyed xprt"); return; } display_xprt_sockaddr(&db, xprt); LogDebug( COMPONENT_XPRT, "Processing custom data for destroyed xprt: %p with FD: %d, socket-addr: %s", xprt, xprt->xp_fd, sockaddr_str); assert(xprt->xp_flags & SVC_XPRT_FLAG_DESTROYED); xprt_data = (xprt_custom_data_t *)xprt->xp_u1; assert(glist_empty(&xprt_data->nfs41_sessions_holder.sessions)); assert(xprt_data->status == DISSOCIATED_FROM_XPRT); PTHREAD_RWLOCK_destroy(&xprt_data->nfs41_sessions_holder.sessions_lock); xprt_data->status = DESTROYED; gsh_free(xprt_data); xprt->xp_u1 = NULL; GSH_XPRT_AUTO_TRACEPOINT( xprt_handler, destroy_custom_data, TRACE_INFO, xprt, "Destroy custom-data from xprt. socket-addr: {}", TP_STR(sockaddr_str)); sal_metrics__xprt_custom_data_status(DESTROYED); } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/test/���������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0015343�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/test/.gitignore�����������������������������������������������������������������0000664�0000000�0000000�00000000035�14737566223�0017331�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������test_glist test_mesure_temps ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/test/CMakeLists.txt�������������������������������������������������������������0000664�0000000�0000000�00000003412�14737566223�0020103�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb <jlieb@panasas.com> # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- if(USE_CUNIT) SET(test_avl_SRCS test_avl.c ) add_executable(test_avl EXCLUDE_FROM_ALL ${test_avl_SRCS}) target_link_libraries(test_avl ganesha_nfsd ${CMAKE_THREAD_LIBS_INIT}) SET(test_mh_avl_SRCS test_mh_avl.c ../support/murmur3.c ) add_executable(test_mh_avl EXCLUDE_FROM_ALL ${test_mh_avl_SRCS}) target_link_libraries(test_mh_avl ganesha_nfsd ${CMAKE_THREAD_LIBS_INIT}) endif(USE_CUNIT) SET(test_glist_SRCS test_glist.c ) add_executable(test_glist EXCLUDE_FROM_ALL ${test_glist_SRCS}) target_link_libraries(test_glist ganesha_nfsd ${CMAKE_THREAD_LIBS_INIT}) SET(test_url_regex_SRCS test_url_regex.c ) add_executable(test_url_regex EXCLUDE_FROM_ALL ${test_url_regex_SRCS}) target_link_libraries(test_url_regex ganesha_nfsd ${CMAKE_THREAD_LIBS_INIT}) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/test/run_test_mode.sh�����������������������������������������������������������0000775�0000000�0000000�00000002563�14737566223�0020557�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Script to run Ganesha for presubmit testing (e.g. pynfs, cthon) # # This script is the default way to run Ganesha for testing before submitting # new code. # # Usage: # $ run_test_mode.sh <build_path> # # Example: # 1. Build Ganesha # $ cd $(mktemp --directory) # $ cmake ~/git/nfs-ganesha/src # $ make # 2. Run Ganesha: # $ ~/git/nfs-ganesha/src/run_test_mode.sh . # 3. Run testing: # $ cd /opt/pynfs # $ ./nfs4.1/testserver.py -v --outfile pynfs-results.log \ # --maketree 127.0.0.1:/export/test1/pynfs --showomit \ # --rundeps all ganesha set -euo pipefail if [ $# -ne 1 ]; then echo "Usage: $0 <build_path>" exit 1 fi BUILD_PATH="$1" GANESHA_EXE="$BUILD_PATH/ganesha.nfsd" if [ ! -f "$GANESHA_EXE" ]; then echo "Error: Ganesha executable is missing: $GANESHA_EXE" exit 1 fi TEMP_DIR=$(mktemp --directory) CONFIG_FILE="$TEMP_DIR/conf" LOG_FILE="$TEMP_DIR/log" PID_FILE="$TEMP_DIR/pid" EXPORT_PATH="$TEMP_DIR/export/" mkdir --parents $EXPORT_PATH cat << EOF > "$CONFIG_FILE" NFS_CORE_PARAM { Plugins_Dir = "$BUILD_PATH/FSAL/FSAL_VFS/vfs/"; Protocols = 3, 4; } EXPORT_DEFAULTS { Access_Type = RW; } EXPORT { Export_Id = 1; Path = $EXPORT_PATH; Pseudo = /export; Protocols = 3, 4; Access_Type = RW; Sectype = sys; FSAL { Name = VFS; } } LOG { Default_Log_Level = INFO; } EOF set -x sudo /tmp/ganesha/ganesha.nfsd -F -x -f $CONFIG_FILE -L $LOG_FILE -p $PID_FILE ���������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/test/test_avl.c�����������������������������������������������������������������0000664�0000000�0000000�00000047102�14737566223�0017334�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * This program 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 3 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 * 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 * */ #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <stddef.h> #include <string.h> #include "CUnit/Basic.h" #include "abstract_mem.h" #include "avltree.h" #define DEBUG 1 /* STATICS we use across multiple tests */ struct avltree avl_tree_1; struct avltree avl_tree_2; struct avltree avl_tree_100; struct avltree avl_tree_10000; typedef struct avl_unit_val { int refs; struct avltree_node node_k; unsigned long key; unsigned long val; } avl_unit_val_t; int avl_unit_cmpf(const struct avltree_node *lhs, const struct avltree_node *rhs) { avl_unit_val_t *lk, *rk; lk = avltree_container_of(lhs, avl_unit_val_t, node_k); rk = avltree_container_of(rhs, avl_unit_val_t, node_k); if (lk->key < rk->key) return -1; if (lk->key == rk->key) return 0; return 1; } avl_unit_val_t *avl_unit_new_val(unsigned long intval) { avl_unit_val_t *v = gsh_malloc(sizeof(avl_unit_val_t)); memset(v, 0, sizeof(avl_unit_val_t)); v->val = (intval + 1); return v; } void avl_unit_free_val(avl_unit_val_t *v) { gsh_free(v); } void avl_unit_clear_tree(struct avltree *t) { avl_unit_val_t *v; struct avltree_node *node, *next_node; if (avltree_size(t) < 1) return; node = avltree_first(t); while (node) { next_node = avltree_next(node); v = avltree_container_of(node, avl_unit_val_t, node_k); avltree_remove(&v->node_k, &avl_tree_1); avl_unit_free_val(v); node = next_node; } } /* dne */ void avltree_destroy(struct avltree *t) { /* return */ } void avl_unit_clear_and_destroy_tree(struct avltree *t) { avl_unit_clear_tree(t); avltree_destroy(t); } /* * BEGIN SUITE INITIALIZATION and CLEANUP FUNCTIONS */ void avl_setup(void) { /* nothing */ } void avl_teardown(void) { /* nothing */ } void avl_unit_PkgInit(void) { /* nothing */ } /* * The suite initialization function. * Initializes resources to be shared across tests. * Returns zero on success, non-zero otherwise. * */ int init_suite1(void) { avltree_init(&avl_tree_1, avl_unit_cmpf, 0 /* flags */); return 0; } /* The suite cleanup function. * Closes the temporary resources used by the tests. * Returns zero on success, non-zero otherwise. */ int clean_suite1(void) { if (avltree_size(&avl_tree_1) > 0) avl_unit_clear_tree(&avl_tree_1); avltree_destroy(&avl_tree_1); return 0; } /* * The suite initialization function. * Initializes resources to be shared across tests. * Returns zero on success, non-zero otherwise. * */ int init_suite2(void) { avltree_init(&avl_tree_2, avl_unit_cmpf, 0 /* flags */); return 0; } /* The suite cleanup function. * Closes the temporary resources used by the tests. * Returns zero on success, non-zero otherwise. */ int clean_suite2(void) { avltree_destroy(&avl_tree_2); return 0; } /* * The suite initialization function. * Initializes resources to be shared across tests. * Returns zero on success, non-zero otherwise. * */ int init_suite100(void) { avltree_init(&avl_tree_100, avl_unit_cmpf, 0 /* flags */); return 0; } /* The suite cleanup function. * Closes the temporary resources used by the tests. * Returns zero on success, non-zero otherwise. */ int clean_suite100(void) { avltree_destroy(&avl_tree_100); return 0; } /* * The suite initialization function. * Initializes resources to be shared across tests. * Returns zero on success, non-zero otherwise. * */ int init_suite10000(void) { avltree_init(&avl_tree_10000, avl_unit_cmpf, 0 /* flags */); return 0; } /* The suite cleanup function. * Closes the temporary resources used by the tests. * Returns zero on success, non-zero otherwise. */ int clean_suite10000(void) { avltree_destroy(&avl_tree_10000); return 0; } /* * The suite initialization function. * Initializes resources to be shared across tests. * Returns zero on success, non-zero otherwise. * */ int init_supremum(void) { avltree_init(&avl_tree_2, avl_unit_cmpf, 0 /* flags */); return 0; } /* The suite cleanup function. * Closes the temporary resources used by the tests. * Returns zero on success, non-zero otherwise. */ int clean_supremum(void) { avltree_destroy(&avl_tree_2); return 0; } /* * END SUITE INITIALIZATION and CLEANUP FUNCTIONS */ /* * BEGIN BASIC TESTS */ void inserts_tree_1(void) { avl_unit_val_t *v; int ix; for (ix = 1; ix < 2; ++ix) { /* new k, v */ v = avl_unit_new_val(ix); /* if actual key cannot be marshalled as a pointer */ v->key = ix; /* insert mapping */ avltree_insert(&v->node_k, &avl_tree_1); } } void check_tree_1(void) { int code = 0; CU_ASSERT_EQUAL(code, 0); } void lookups_tree_1(void) { struct avltree_node *node; avl_unit_val_t *v2, *v = avl_unit_new_val(0); int ix; for (ix = 1; ix < 2; ++ix) { /* reuse v */ v->key = ix; /* lookup mapping */ node = avltree_lookup(&v->node_k, &avl_tree_1); v2 = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT((unsigned long)v2->val == (ix + 1)); } /* free v */ avl_unit_free_val(v); } void deletes_tree_1(void) { struct avltree_node *node; avl_unit_val_t *v2, *v = avl_unit_new_val(0); int ix; for (ix = 1; ix < 2; ++ix) { /* reuse key */ v->key = ix; /* find mapping */ node = avltree_lookup(&v->node_k, &avl_tree_1); v2 = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT(v2->val == (ix + 1)); /* and remove it */ avltree_remove(&v2->node_k, &avl_tree_1); avl_unit_free_val(v2); } /* free search k */ avl_unit_free_val(v); } void inserts_tree_2(void) { avl_unit_val_t *v; int ix; for (ix = 1; ix < 4; ++ix) { /* new k, v */ v = avl_unit_new_val(ix); /* if actual key cannot be marshalled as a pointer */ v->key = ix; /* insert mapping */ avltree_insert(&v->node_k, &avl_tree_2); } } void inserts_tree_2r(void) { avl_unit_val_t *v; int ix; for (ix = 3; ix > 0; --ix) { /* new k, v */ v = avl_unit_new_val(ix); /* if actual key cannot be marshalled as a pointer */ v->key = ix; /* insert mapping */ avltree_insert(&v->node_k, &avl_tree_2); } } void check_tree_2(void) { int code = 0; CU_ASSERT_EQUAL(code, 0); } void lookups_tree_2(void) { struct avltree_node *node; avl_unit_val_t *v2, *v = avl_unit_new_val(0); int ix; for (ix = 1; ix < 4; ++ix) { /* reuse v */ v->key = ix; /* lookup mapping */ node = avltree_lookup(&v->node_k, &avl_tree_2); v2 = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT((unsigned long)v2->val == (ix + 1)); } /* free v */ avl_unit_free_val(v); } void deletes_tree_2(void) { struct avltree_node *node; avl_unit_val_t *v2, *v = avl_unit_new_val(0); int ix; for (ix = 1; ix < 4; ++ix) { /* reuse key */ v->key = ix; /* find mapping */ node = avltree_lookup(&v->node_k, &avl_tree_2); v2 = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT(v2->val == (ix + 1)); /* and remove it */ avltree_remove(&v2->node_k, &avl_tree_2); avl_unit_free_val(v2); } /* free search k */ avl_unit_free_val(v); } void inserts_supremum(void) { avl_unit_val_t *v; int ix; for (ix = 100; ix < 1000; ix += 100) { /* new k, v */ v = avl_unit_new_val(ix); /* if actual key cannot be marshalled as a pointer */ v->key = ix; /* insert mapping */ avltree_insert(&v->node_k, &avl_tree_2); } } void checks_supremum(void) { struct avltree_node *node; avl_unit_val_t *v2, *v = avl_unit_new_val(0); int ix; for (ix = 100; ix < 1000; ix += 100) { /* reuse v */ v->key = (ix - 2); /* a value -just less than ix- */ /* lookup mapping */ node = avltree_sup(&v->node_k, &avl_tree_2); CU_ASSERT(node != NULL); if (node) { v2 = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT((unsigned long)v2->val == (ix + 1)); } /* ok, now find the -infimum- */ v->key = ix + 2; /* a value just above ix */ /* lookup mapping */ node = avltree_inf(&v->node_k, &avl_tree_2); CU_ASSERT(node != NULL); if (node) { v2 = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT((unsigned long)v2->val == (ix + 1)); } } /* now check the boundary case for supremum */ v->key = 500; node = avltree_sup(&v->node_k, &avl_tree_2); CU_ASSERT(node != NULL); if (node) { v2 = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT((unsigned long)v2->val == (v->key + 1)); } /* and infimum */ node = avltree_inf(&v->node_k, &avl_tree_2); CU_ASSERT(node != NULL); if (node) { v2 = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT((unsigned long)v2->val == (v->key + 1)); } /* free v */ avl_unit_free_val(v); } void deletes_supremum(void) { struct avltree_node *node; avl_unit_val_t *v2, *v = avl_unit_new_val(0); int ix; for (ix = 100; ix < 1000; ix += 100) { /* reuse key */ v->key = ix; /* find mapping */ node = avltree_lookup(&v->node_k, &avl_tree_2); v2 = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT(v2->val == (ix + 1)); /* and remove it */ avltree_remove(&v2->node_k, &avl_tree_2); avl_unit_free_val(v2); } /* free search k */ avl_unit_free_val(v); } void inserts_tree_100(void) { avl_unit_val_t *v; int ix; for (ix = 1; ix < 101; ++ix) { /* new k, v */ v = avl_unit_new_val(ix); /* if actual key cannot be marshalled as a pointer */ v->key = ix; /* insert mapping */ avltree_insert(&v->node_k, &avl_tree_100); } } void check_tree_100(void) { int code = 0; CU_ASSERT_EQUAL(code, 0); } void lookups_tree_100(void) { struct avltree_node *node; avl_unit_val_t *v2, *v = avl_unit_new_val(0); int ix; for (ix = 1; ix < 2; ++ix) { /* reuse v */ v->key = ix; /* lookup mapping */ node = avltree_lookup(&v->node_k, &avl_tree_100); v2 = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT((unsigned long)v2->val == (ix + 1)); } /* free v */ avl_unit_free_val(v); } void trav_tree_100(void) { int ntrav = 0; struct avltree_node *node; avl_unit_val_t *v; node = avltree_first(&avl_tree_100); while (node) { ntrav++; v = avltree_container_of(node, avl_unit_val_t, node_k); if ((ntrav % 10) == 0) printf("Node at %p key: %lu val: %lu (%d)\n", v, v->key, v->val, ntrav); node = avltree_next(node); } CU_ASSERT_EQUAL(ntrav, 100); } void deletes_tree_100(void) { struct avltree_node *node; avl_unit_val_t *v2, *v = avl_unit_new_val(0); int ix; for (ix = 1; ix < 101; ++ix) { /* reuse key */ v->key = ix; /* find mapping */ node = avltree_lookup(&v->node_k, &avl_tree_100); v2 = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT(v2->val == (ix + 1)); /* and remove it */ avltree_remove(&v2->node_k, &avl_tree_100); avl_unit_free_val(v2); } /* free search k */ avl_unit_free_val(v); } void inserts_tree_10000(void) { avl_unit_val_t *v; int ix; for (ix = 1; ix < 10001; ++ix) { /* new k, v */ v = avl_unit_new_val(ix); /* if actual key cannot be marshalled as a pointer */ v->key = ix; /* insert mapping */ avltree_insert(&v->node_k, &avl_tree_10000); } } void check_tree_10000(void) { int code = 0; CU_ASSERT_EQUAL(code, 0); } void lookups_tree_10000(void) { struct avltree_node *node; avl_unit_val_t *v2, *v = avl_unit_new_val(0); int ix; for (ix = 1; ix < 2; ++ix) { /* reuse v */ v->key = ix; /* lookup mapping */ node = avltree_lookup(&v->node_k, &avl_tree_10000); v2 = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT((unsigned long)v2->val == (ix + 1)); } /* free v */ avl_unit_free_val(v); } void trav_tree_10000(void) { int ntrav = 0; struct avltree_node *node; avl_unit_val_t *v; node = avltree_first(&avl_tree_10000); while (node) { ntrav++; v = avltree_container_of(node, avl_unit_val_t, node_k); if ((ntrav % 1000) == 0) printf("Node at %p key: %lu val: %lu (%d)\n", v, v->key, v->val, ntrav); node = avltree_next(node); } CU_ASSERT_EQUAL(ntrav, 10000); } void deletes_tree_10000(void) { struct avltree_node *node; avl_unit_val_t *v2, *v = avl_unit_new_val(0); int ix; for (ix = 1; ix < 10001; ++ix) { /* reuse key */ v->key = ix; /* find mapping */ node = avltree_lookup(&v->node_k, &avl_tree_10000); v2 = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT(v2->val == (ix + 1)); /* and remove it */ avltree_remove(&v2->node_k, &avl_tree_10000); avl_unit_free_val(v2); } /* free search k */ avl_unit_free_val(v); } void insert_long_val(struct avltree *t, unsigned long l) { avl_unit_val_t *v; /* new k, v */ v = avl_unit_new_val(l); /* if actual key cannot be marshalled as a pointer */ v->key = l; /* insert mapping */ avltree_insert(&v->node_k, t); } void insert_long_val_safe(struct avltree *t, unsigned long l) { struct avltree_node *node; avl_unit_val_t *v; /* new k, v */ v = avl_unit_new_val(l); v->key = l; node = avltree_lookup(&v->node_k, t); if (node == NULL) avltree_insert(&v->node_k, t); else avl_unit_free_val(v); } void delete_long_val(struct avltree *t, unsigned long l) { struct avltree_node *node; avl_unit_val_t *v, *v2; /* new key, v */ v = avl_unit_new_val(l); v->key = l; /* find mapping */ node = avltree_lookup(&v->node_k, t); v2 = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT(v2->key == l); /* delete mapping */ avltree_remove(&v2->node_k, t); /* free original v */ avl_unit_free_val(v2); /* free search k, v */ avl_unit_free_val(v); } void check_delete_1(void) { struct avltree_node *node; avl_unit_val_t *v, *v2; avl_unit_clear_and_destroy_tree(&avl_tree_1); avltree_init(&avl_tree_1, avl_unit_cmpf, 0 /* flags */); insert_long_val(&avl_tree_1, 4); insert_long_val(&avl_tree_1, 1); insert_long_val(&avl_tree_1, 10010); insert_long_val(&avl_tree_1, 267); insert_long_val(&avl_tree_1, 3382); insert_long_val(&avl_tree_1, 22); insert_long_val(&avl_tree_1, 82); insert_long_val(&avl_tree_1, 3); node = avltree_first(&avl_tree_1); v2 = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT(v2->val == (1 + 1)); delete_long_val(&avl_tree_1, 1); /* new key */ v = avl_unit_new_val(4); v->key = 4; node = avltree_lookup(&v->node_k, &avl_tree_1); v2 = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT(v2->val == (4 + 1)); delete_long_val(&avl_tree_1, 267); v->key = 3382; node = avltree_lookup(&v->node_k, &avl_tree_1); v2 = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT(v2->val == (3382 + 1)); avl_unit_free_val(v); } void check_min_1(void) { avl_unit_val_t *v; struct avltree_node *node; avl_unit_clear_and_destroy_tree(&avl_tree_1); avltree_init(&avl_tree_1, avl_unit_cmpf, 0 /* flags */); insert_long_val(&avl_tree_1, 4); insert_long_val(&avl_tree_1, 10); insert_long_val(&avl_tree_1, 10010); insert_long_val(&avl_tree_1, 267); insert_long_val(&avl_tree_1, 3382); insert_long_val(&avl_tree_1, 22); insert_long_val(&avl_tree_1, 82); node = avltree_first(&avl_tree_1); v = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT(v->val == (4 + 1)); /* insert new min */ insert_long_val(&avl_tree_1, 3); node = avltree_first(&avl_tree_1); v = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT(v->val == (3 + 1)); /* delete current min */ delete_long_val(&avl_tree_1, 3); node = avltree_first(&avl_tree_1); v = avltree_container_of(node, avl_unit_val_t, node_k); CU_ASSERT(v->val == (4 + 1)); } void check_min_2(void) { avl_unit_val_t *v; unsigned long mval, rv; struct avltree_node *node; int ix; srand(time(0)); avl_unit_clear_and_destroy_tree(&avl_tree_1); avltree_init(&avl_tree_1, avl_unit_cmpf, 0 /* flags */); mval = ULONG_MAX; for (ix = 0; ix < 100000; ix++) { rv = rand(); /* in solaris avl, inserting an value that compares equal * to an already inserted value is illegal */ insert_long_val_safe(&avl_tree_1, rv); if ((mval < 0) || (rv < mval)) mval = rv; } node = avltree_first(&avl_tree_1); v = avltree_container_of(node, avl_unit_val_t, node_k); printf("rv: %lu mval: %lu val: %lu\n", rv, mval, v->val - 1); CU_ASSERT(v->val == (mval + 1)); } /* The main() function for setting up and running the tests. * Returns a CUE_SUCCESS on successful running, another * CUnit error code on failure. */ int main(int argc, char *argv[]) { /* initialize the CUnit test registry... get this party started */ if (CU_initialize_registry() != CUE_SUCCESS) return CU_get_error(); /* General avl_tree test. */ CU_TestInfo avl_tree_unit_1_arr[] = { { "Tree insertions 1.", inserts_tree_1 }, { "Tree check 1.", check_tree_1 }, { "Tree lookups 1.", lookups_tree_1 }, { "Tree deletes 1.", deletes_tree_1 }, CU_TEST_INFO_NULL, }; CU_TestInfo avl_tree_unit_2_arr[] = { { "Tree insertions 2.", inserts_tree_2 }, { "Tree check 2.", check_tree_2 }, { "Tree lookups 2.", lookups_tree_2 }, { "Tree deletes 2.", deletes_tree_2 }, CU_TEST_INFO_NULL, }; CU_TestInfo avl_tree_unit_2r_arr[] = { { "Tree insertions 2.", inserts_tree_2r }, { "Tree check 2.", check_tree_2 }, { "Tree lookups 2.", lookups_tree_2 }, { "Tree deletes 2.", deletes_tree_2 }, CU_TEST_INFO_NULL, }; CU_TestInfo avl_tree_unit_100_arr[] = { { "Tree insertions 100.", inserts_tree_100 }, { "Tree check 100.", check_tree_100 }, { "Tree lookups 100.", lookups_tree_100 }, { "Tree traverse 100.", trav_tree_100 }, { "Tree deletes 100.", deletes_tree_100 }, CU_TEST_INFO_NULL, }; CU_TestInfo avl_tree_unit_10000_arr[] = { { "Tree insertions 10000.", inserts_tree_10000 }, { "Tree lookups 10000.", lookups_tree_10000 }, { "Tree check 10000.", check_tree_10000 }, { "Tree traverse 10000.", trav_tree_10000 }, { "Tree deletes 10000.", deletes_tree_10000 }, CU_TEST_INFO_NULL, }; CU_TestInfo avl_tree_unit_min_1_arr[] = { { "Check min after inserts, deletes.", check_min_1 }, { "Check lookup after delete.", check_delete_1 }, #if 1 /* skews perf */ { "Random min check.", check_min_2 }, #endif CU_TEST_INFO_NULL, }; CU_TestInfo avl_tree_unit_supremum[] = { { "Inserts supremum.", inserts_supremum }, { "Checks supremum (and infimum).", checks_supremum }, { "Deletes supremum.", deletes_supremum }, CU_TEST_INFO_NULL, }; CU_SuiteInfo suites[] = { { "Avl operations 1", init_suite1, clean_suite1, avl_setup, avl_teardown, avl_tree_unit_1_arr }, { "Avl operations 2", init_suite2, clean_suite2, avl_setup, avl_teardown, avl_tree_unit_2_arr }, { "Avl operations 2 R", init_suite2, clean_suite2, avl_setup, avl_teardown, avl_tree_unit_2r_arr }, { "Avl operations 100", init_suite100, clean_suite100, avl_setup, avl_teardown, avl_tree_unit_100_arr }, { "Avl operations 10000", init_suite10000, clean_suite10000, avl_setup, avl_teardown, avl_tree_unit_10000_arr }, { "Check min 1", init_suite1, clean_suite1, avl_setup, avl_teardown, avl_tree_unit_min_1_arr }, { "Check supremum", init_supremum, clean_supremum, avl_setup, avl_teardown, avl_tree_unit_supremum }, CU_SUITE_INFO_NULL, }; CU_register_suites(suites); /* Initialize the avl_tree package */ avl_unit_PkgInit(); /* Run all tests using the CUnit Basic interface */ CU_basic_set_mode(CU_BRM_VERBOSE); CU_basic_run_tests(); CU_cleanup_registry(); return CU_get_error(); } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/test/test_glist.c���������������������������������������������������������������0000664�0000000�0000000�00000006443�14737566223�0017677�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2010 * Contributor: Aneesh Kumar K.v <aneesh.kumar@linux.vnet.ibm.com> * * * This program 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 3 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 * 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 * * --------------------------------------- */ #include <stdio.h> #include "gsh_list.h" struct myteststruct { int value; struct glist_head glist; }; struct glist_head mytestglist; struct glist_head mytestglist_new; static void print_glist(struct glist_head *head) { struct myteststruct *entry; struct glist_head *glist; glist_for_each(glist, head) { entry = glist_entry(glist, struct myteststruct, glist); printf("The value is %d\n", entry->value); } } void basic_test(void) { struct myteststruct node1; struct myteststruct node2; struct myteststruct node3; struct myteststruct node4; struct myteststruct node1_new; struct myteststruct node2_new; glist_init(&mytestglist); glist_init(&mytestglist_new); node1.value = 10; node2.value = 11; node3.value = 12; glist_add(&mytestglist, &node1.glist); glist_add(&mytestglist, &node2.glist); glist_add(&mytestglist, &node3.glist); print_glist(&mytestglist); printf("Now test tail add\n"); node4.value = 13; glist_add_tail(&mytestglist, &node4.glist); print_glist(&mytestglist); printf("Delete test\n"); glist_del(&node2.glist); print_glist(&mytestglist); node1_new.value = 15; node2_new.value = 16; glist_add(&mytestglist_new, &node1_new.glist); glist_add(&mytestglist_new, &node2_new.glist); printf("Add the below two list\n"); printf("list1\n"); print_glist(&mytestglist); printf("list2\n"); print_glist(&mytestglist_new); glist_add_list_tail(&mytestglist, &mytestglist_new); printf("combined list\n"); print_glist(&mytestglist); } void splice_tail_test(void) { struct myteststruct nodes[10]; int ix; glist_init(&mytestglist); glist_init(&mytestglist_new); for (ix = 0; ix < 10; ++ix) { struct myteststruct *node = &nodes[ix]; node->value = ix + 1; /* add nodes 1-5 to mytestglist */ if (ix < 5) { glist_add_tail(&mytestglist, &node->glist); } else { /* and 6-10 to mytestglist_new */ glist_add_tail(&mytestglist_new, &node->glist); } } printf("List mytestglist should have nodes 1..5\n"); print_glist(&mytestglist); printf("List mytestglist_new should have nodes 6..10\n"); print_glist(&mytestglist_new); printf("Now after glist_splice_tail mytestglist should have all 10 nodes:\n"); glist_splice_tail(&mytestglist, &mytestglist_new); print_glist(&mytestglist); printf("And mytestglist_new no nodes:\n"); print_glist(&mytestglist_new); } int main(int argc, char *argv[]) { basic_test(); splice_tail_test(); return 0; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/test/test_mh_avl.c��������������������������������������������������������������0000664�0000000�0000000�00000024257�14737566223�0020026�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:expandtab:shiftwidth=8:tabstop=8: * * Copyright (C) 2010, The Linux Box Corporation * Contributor : Matt Benjamin <matt@linuxbox.com> * * Some portions Copyright CEA/DAM/DIF (2008) * contributeur : Philippe DENIEL philippe.deniel@cea.fr * Thomas LEIBOVICI thomas.leibovici@cea.fr * * * This program 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 3 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 * 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 * * ------------- */ #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <stddef.h> #include <string.h> #include <assert.h> #include "CUnit/Basic.h" #include "abstract_mem.h" #include "avltree.h" #include "murmur3.h" #define DEBUG 1 /* STATICS we use across multiple tests */ struct avltree avl_tree_1; /* dirent-like structure */ typedef struct avl_unit_val { struct avltree_node node_n; struct avltree_node node_hk; struct { uint64_t k; uint32_t p; /* nprobes , eff. metric */ } hk; char *name; uint64_t fsal_cookie; } avl_unit_val_t; static inline int avl_unit_hk_cmpf(const struct avltree_node *lhs, const struct avltree_node *rhs) { avl_unit_val_t *lk, *rk; lk = avltree_container_of(lhs, avl_unit_val_t, node_hk); rk = avltree_container_of(rhs, avl_unit_val_t, node_hk); if (lk->hk.k < rk->hk.k) return -1; if (lk->hk.k == rk->hk.k) return 0; return 1; } avl_unit_val_t *avl_unit_new_val(const char *name) { avl_unit_val_t *v = gsh_malloc(sizeof(avl_unit_val_t)); memset(v, 0, sizeof(avl_unit_val_t)); v->name = (char *)name; return v; } static int qp_avl_insert(struct avltree *t, avl_unit_val_t *v) { /* * Insert with quadratic, linear probing. A unique k is assured for * any k whenever size(t) < max(uint64_t). * * First try quadratic probing, with coeff. 2 (since m = 2^n.) * A unique k is not assured, since the codomain is not prime. * If this fails, fall back to linear probing from hk.k+1. * * On return, the stored key is in v->hk.k, the iteration * count in v->hk.p. **/ struct avltree_node *tmpnode; uint32_t j, j2; uint32_t hk[4]; assert(avltree_size(t) < UINT64_MAX); MurmurHash3_x64_128(v->name, strlen(v->name), 67, hk); memcpy(&v->hk.k, hk, 8); for (j = 0; j < UINT64_MAX; j++) { v->hk.k = (v->hk.k + (j * 2)); tmpnode = avltree_insert(&v->node_hk, t); if (!tmpnode) { /* success, note iterations and return */ v->hk.p = j; return 0; } } /* warn debug */ memcpy(&v->hk.k, hk, 8); for (j2 = 1 /* tried j=0 */; j2 < UINT64_MAX; j2++) { v->hk.k = v->hk.k + j2; tmpnode = avltree_insert(&v->node_hk, t); if (!tmpnode) { /* success, note iterations and return */ v->hk.p = j + j2; return 0; } j2++; } /* warn crit */ return -1; } static avl_unit_val_t *qp_avl_lookup_s(struct avltree *t, avl_unit_val_t *v, int maxj) { struct avltree_node *node; avl_unit_val_t *v2; uint32_t j; uint32_t hk[4]; assert(avltree_size(t) < UINT64_MAX); MurmurHash3_x64_128(v->name, strlen(v->name), 67, hk); memcpy(&v->hk.k, hk, 8); for (j = 0; j < maxj; j++) { v->hk.k = (v->hk.k + (j * 2)); node = avltree_lookup(&v->node_hk, t); if (node) { /* it's almost but not entirely certain that node is * related to v. in the general case, j is also not * constrained to be v->hk.p */ v2 = avltree_container_of(node, avl_unit_val_t, node_hk); if (!strcmp(v->name, v2->name)) return v2; } } /* warn crit */ return NULL; } static struct dir_data { char *name; } dir_data[] = { { ".gitignore" }, { "Makefile" }, { "Makefile.gate" }, { "acpi-ext.c" }, { "acpi-processor.c" }, { "acpi.c" }, { "asm-offsets.c" }, { "audit.c" }, { "brl_emu.c" }, { "cpufreq" }, { "crash.c" }, { "crash_dump.c" }, { "cyclone.c" }, { "dma-mapping.c" }, { "efi.c" }, { "efi_stub.S" }, { "entry.S" }, { "entry.h" }, { "err_inject.c" }, { "esi.c" }, { "esi_stub.S" }, { "fsys.S" }, { "fsyscall_gtod_data.h" }, { "ftrace.c" }, { "gate-data.S" }, { "gate.S" }, { "gate.lds.S" }, { "head.S" }, { "ia64_ksyms.c" }, { "init_task.c" }, { "iosapic.c" }, { "irq.c" }, { "irq_ia64.c" }, { "irq_lsapic.c" }, { "ivt.S" }, { "jprobes.S" }, { "kprobes.c" }, { "machine_kexec.c" }, { "machvec.c" }, { "mca.c" }, { "mca_asm.S" }, { "mca_drv.c" }, { "mca_drv.h" }, { "mca_drv_asm.S" }, { "minstate.h" }, { "module.c" }, { "msi_ia64.c" }, { "nr-irqs.c" }, { "numa.c" }, { "pal.S" }, { "palinfo.c" }, { "paravirt.c" }, { "paravirt_inst.h" }, { "paravirt_patch.c" }, { "paravirt_patchlist.c" }, { "paravirt_patchlist.h" }, { "paravirtentry.S" }, { "patch.c" }, { "pci-dma.c" }, { "pci-swiotlb.c" }, { "perfmon.c" }, { "perfmon_default_smpl.c" }, { "perfmon_generic.h" }, { "perfmon_itanium.h" }, { "perfmon_mckinley.h" }, { "perfmon_montecito.h" }, { "process.c" }, { "ptrace.c" }, { "relocate_kernel.S" }, { "sal.c" }, { "salinfo.c" }, { "setup.c" }, { "sigframe.h" }, { "signal.c" }, { "smp.c" }, { "smpboot.c" }, { "sys_ia64.c" }, { "time.c" }, { "topology.c" }, { "traps.c" }, { "unaligned.c" }, { "uncached.c" }, { "unwind.c" }, { "unwind_decoder.c" }, { "unwind_i.h" }, { "vmlinux.lds.S" }, { 0 } }; void avl_unit_free_val(avl_unit_val_t *v) { gsh_free(v); } void avl_unit_clear_tree(struct avltree *t) { avl_unit_val_t *v; struct avltree_node *node, *next_node; if (avltree_size(t) < 1) return; node = avltree_first(t); while (node) { next_node = avltree_next(node); v = avltree_container_of(node, avl_unit_val_t, node_hk); avltree_remove(&v->node_hk, &avl_tree_1); gsh_free(v->name); avl_unit_free_val(v); node = next_node; } } /* dne */ void avltree_destroy(struct avltree *t) { /* return */ } void avl_unit_clear_and_destroy_tree(struct avltree *t) { avl_unit_clear_tree(t); avltree_destroy(t); } /* * BEGIN SUITE INITIALIZATION and CLEANUP FUNCTIONS */ void avl_setup(void) { /* nothing */ } void avl_teardown(void) { /* nothing */ } void avl_unit_PkgInit(void) { /* nothing */ } /* * The suite initialization function. * Initializes resources to be shared across tests. * Returns zero on success, non-zero otherwise. * */ int init_suite1(void) { avltree_init(&avl_tree_1, avl_unit_hk_cmpf, 0 /* flags */); return 0; } /* The suite cleanup function. * Closes the temporary resources used by the tests. * Returns zero on success, non-zero otherwise. */ int clean_suite1(void) { if (avltree_size(&avl_tree_1) > 0) avl_unit_clear_tree(&avl_tree_1); avltree_destroy(&avl_tree_1); return 0; } /* * END SUITE INITIALIZATION and CLEANUP FUNCTIONS */ /* * BEGIN BASIC TESTS */ void inserts_tree_1(void) { avl_unit_val_t *v; char *s; int ix, code; ix = 0; while ((s = dir_data[ix].name) != NULL) { v = avl_unit_new_val(gsh_strdup(s)); code = qp_avl_insert(&avl_tree_1, v); if (code == -1) abort(); if (v->hk.p > 0) printf("%d positive p %d %s\n", ix, v->hk.p, s); ++ix; } } void check_tree_1(void) { int code = 0; CU_ASSERT_EQUAL(code, 0); } void lookups_tree_1(void) { avl_unit_val_t *v2, *v; char *s; int ix; ix = 0; while ((s = dir_data[ix].name) != NULL) { v = avl_unit_new_val(s); /* lookup mapping */ v2 = qp_avl_lookup_s(&avl_tree_1, v, 1); if (!v2) { abort(); } else { /* printf("%d %d %s\n", ix, v2->hk.p, v2->name); */ } ++ix; } /* free v */ avl_unit_free_val(v); } void deletes_tree_1(void) { avl_unit_clear_tree(&avl_tree_1); avltree_init(&avl_tree_1, avl_unit_hk_cmpf, 0 /* flags */); } void inserts_tree_2(void) { avl_unit_val_t *v; char s[256]; int ix, code; for (ix = 0; ix < 100000; ++ix) { sprintf(s, "file%d", ix); v = avl_unit_new_val(gsh_strdup(s)); code = qp_avl_insert(&avl_tree_1, v); if (code == -1) abort(); if (v->hk.p > 0) printf("%d positive p %d %s\n", ix, v->hk.p, s); } } void check_tree_2(void) { int code = 0; CU_ASSERT_EQUAL(code, 0); } void lookups_tree_2(void) { avl_unit_val_t *v2, *v; char s[256]; int ix; /* attempts 100K mits, 100K misses */ for (ix = 0; ix < 200000; ++ix) { sprintf(s, "file%d", ix); v = avl_unit_new_val(s); v2 = qp_avl_lookup_s(&avl_tree_1, v, 1); if (!v2) { if (ix < 100000) { abort(); } else { /* printf("%d %d %s\n", ix, v2->hk.p, v2->name); */ } } } /* free v */ avl_unit_free_val(v); } void deletes_tree_2(void) { avl_unit_clear_tree(&avl_tree_1); } /* The main() function for setting up and running the tests. * Returns a CUE_SUCCESS on successful running, another * CUnit error code on failure. */ int main(int argc, char *argv[]) { /* initialize the CUnit test registry... get this party started */ if (CU_initialize_registry() != CUE_SUCCESS) return CU_get_error(); /* General avl_tree test. */ CU_TestInfo avl_tree_unit_1_arr[] = { { "Tree insertions 1.", inserts_tree_1 }, { "Tree check 1.", check_tree_1 }, { "Tree lookups 1.", lookups_tree_1 }, { "Tree deletes 1.", deletes_tree_1 }, { "Tree insertions 2.", inserts_tree_2 }, { "Tree check 2.", check_tree_2 }, { "Tree lookups 2.", lookups_tree_2 }, { "Tree deletes 2.", deletes_tree_2 }, CU_TEST_INFO_NULL, }; CU_SuiteInfo suites[] = { { "Rb tree operations 1", init_suite1, clean_suite1, avl_setup, avl_teardown, avl_tree_unit_1_arr }, CU_SUITE_INFO_NULL, }; CU_register_suites(suites); /* Initialize the avl_tree package */ avl_unit_PkgInit(); /* Run all tests using the CUnit Basic interface */ CU_basic_set_mode(CU_BRM_VERBOSE); CU_basic_run_tests(); CU_cleanup_registry(); return CU_get_error(); } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/test/test_url_regex.c�����������������������������������������������������������0000664�0000000�0000000�00000010116�14737566223�0020541�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* ---------------------------------------------------------------------------- * Copyright (C) 2017, Red Hat, Inc. * contributeur : Matt Benjamin mbenjamin@redhat.com * * This program 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 3 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 * 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 * --------------------------------------- */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/param.h> #include <regex.h> /* decompose RADOS URL into (<pool>/)object */ #define RADOS_URL_REGEX "([-a-zA-Z0-9_&=.]+)/?([-a-zA-Z0-9_&=/.]+)?" #define URL1 "my_rados_object" #define URL2 "mypool_baby/myobject_baby" #define URL3 "mypool-baby/myobject-baby" #define URL4 "mypool.baby/myobject.conf" /* match general URL with optional enclosing quotes */ #define CONFIG_URL_REGEX "^\"?(rados)://([^\"]+)\"?" #define CONF_URL1 "rados://mypool-baby/myobject-baby" #define CONF_URL2 "\"rados://mypool-baby/myobject-baby\"" #define CONF_URL3 "\"rados://mypool/myobject.conf\"" #define gsh_malloc malloc static regex_t url_regex; static regex_t conf_url_regex; static inline char *match_dup(regmatch_t *m, char *in) { char *s = NULL; if (m->rm_so >= 0) { int size; size = m->rm_eo - m->rm_so + 1; s = (char *)gsh_malloc(size); snprintf(s, size, "%s", in + m->rm_so); } return s; } void split_pool(char *url) { regmatch_t match[3]; char *x0, *x1, *x2; int code; printf("%s url: %s\n", __func__, url); code = regexec(&url_regex, url, 3, match, 0); if (!code) { /* matched */ regmatch_t *m = &(match[0]); /* matched url pattern is NUL-terminated */ x0 = match_dup(m, url); printf("match0: %s\n", x0); m = &(match[1]); x1 = match_dup(m, url); printf("match1: %s\n", x1); m = &(match[2]); x2 = match_dup(m, url); printf("match2: %s\n", x2); free(x0); free(x1); free(x2); } else if (code == REG_NOMATCH) { printf("%s: Failed to match %s as a config URL\n", __func__, url); } else { char ebuf[100]; regerror(code, &url_regex, ebuf, sizeof(ebuf)); printf("%s: Error in regexec: %s\n", __func__, ebuf); } } void split_url(char *url) { regmatch_t match[3]; char *x0, *x1, *x2; int code; printf("%s url: %s\n", __func__, url); code = regexec(&conf_url_regex, url, 3, match, 0); if (!code) { /* matched */ regmatch_t *m = &(match[0]); /* matched url pattern is NUL-terminated */ x0 = match_dup(m, url); printf("match0: %s\n", x0); m = &(match[1]); x1 = match_dup(m, url); printf("match1: %s\n", x1); m = &(match[2]); x2 = match_dup(m, url); printf("match2: %s\n", x2); free(x0); free(x1); free(x2); } else if (code == REG_NOMATCH) { printf("%s: Failed to match %s as a config URL\n", __func__, url); } else { char ebuf[100]; regerror(code, &url_regex, ebuf, sizeof(ebuf)); printf("%s: Error in regexec: %s\n", __func__, ebuf); } } int main(int argc, char **argv) { printf("hi\n"); int r; r = regcomp(&url_regex, RADOS_URL_REGEX, REG_EXTENDED); if (!!r) { char ebuf[100]; regerror(r, &url_regex, ebuf, sizeof(ebuf)); printf("Error initializing rados url regex %s", ebuf); exit(1); } split_pool(URL1); split_pool(URL2); split_pool(URL3); split_pool(URL4); r = regcomp(&conf_url_regex, CONFIG_URL_REGEX, REG_EXTENDED); if (!!r) { char ebuf[100]; regerror(r, &url_regex, ebuf, sizeof(ebuf)); printf("Error initializing rados url regex %s", ebuf); exit(1); } split_url(CONF_URL1); split_url(CONF_URL2); split_url(CONF_URL3); return 0; } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/��������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0015524�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/CMakeLists.txt������������������������������������������������������������0000664�0000000�0000000�00000002154�14737566223�0020266�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb <jlieb@panasas.com> # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- if(USE_TOOL_MULTILOCK) add_subdirectory(multilock) endif(USE_TOOL_MULTILOCK) ########### install files ############### ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/findlog.sh����������������������������������������������������������������0000775�0000000�0000000�00000012175�14737566223�0017513�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright IBM Corporation, 2011 # Contributor: Frank Filz <ffilz@us.ibm.com> # # # This software is a server that implements the NFS protocol. # # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- # # This script is primarily intended to extract all the LogXXX functions calls # from the source code, however it can also be used to find other function calls # given certain limitations. # # Each function call found will have all of it's parameters pulled onto a single # output line, with some white space trimming, and the output line will be # tagged with the file name and line number. # # Limitation 1: # # There must be a blank or tab before the function name. # # Limitation 2: # # Code like th following will stump this script somewhat: # # #define DEFINED_TWICE_WARNING( _str_ ) \ # LogWarn(COMPONENT_CONFIG, \ # "NFS READ_EXPORT: WARNING: %s defined twice !!! (ignored)", _str_ ) # # The result will be to pull in extra code # # Note: # # The file tools/test_findlog.c can be used to see the types of things this # script is expected to deal with. If you find a new case the script doesn't # deal with, add it to this file, and possibly fix the script. #------------------------------------------------------------------------------- # NOTES: FUNC="Log[a-zA-Z][a-zA-Z0-9]*" PRINTF="[vsnf]*printf" FILES="" TODO="find_func_in_file" CSCOPE=0 FINAL="final_massage" LINESONLY=0 DIR="." find_funcs() { sed -ne " :again /[^a-zA-Z_]$FUNC[ \t\]*(.*\*\//{ = p s/\*/\*/ t end } /[^a-zA-Z_]$FUNC[ \t]*(.*)[ \t]*;[ \t\\]*$/{ = p s/;/;/ t end } /[^a-zA-Z_]$FUNC[ \t]*(.*)[ \t]*{[ \t\\]*$/{ = p s/{/{/ t end } /[^a-zA-Z_]$FUNC[ \t]*(.*$/{ = N s/\n[ \t]*/ / s/[ \t\\]*;[ \t\\]*$/;/ t again } /[^a-zA-Z_]$FUNC[ \t]*[\[\=]/d /[^a-zA-Z_]$FUNC[^)]*)/d /[^a-zA-Z_]$FUNC[^;()]*;/d /[^a-zA-Z_]$FUNC[ \t\\]*$/{ = N s/[ \t\\]*;[ \t\\]*$/;/ s/\n[ \t]*(/(/ t again } :end" } debug_mode() { cat $1 | find_funcs } no_xref() { cat $1 | find_funcs | grep -v '^[0-9][0-9]*$' | sed -e "s@.*\($FUNC.*\)@\1@" } final_massage() { sed -e "s@\([0-9][0-9]*\):[0-9:]*[ \t]*\(.*$FUNC.*\)@$1:\1: \2@" } final_lines_only() { sed -e "s@\([0-9][0-9]*\):[0-9:]*[ \t]*\(.*$FUNC.*\)@$1:\1@" } find_func_in_file() { cat $1 | find_funcs | sed -ne ' :done /[0-9][0-9]*:.*\*\//{ p d } /[0-9][0-9]*:.*[{;][ \t\\]*$/{ p d } /[0-9][0-9]*/{ N s/\n/:/ s/;/;/ t done } ' \ | $FINAL $1 } find_files() { while read line; do $TODO $line done } while getopts ":f:l:cp\?k:dxsnD:" OPT; do case $OPT in f) FUNC="$OPTARG" ;; l) FILES="$OPTARG" ;; c) FILES="cscope.files" ;; p) FUNC="$PRINTF" ;; d) TODO="debug_mode" ;; D) DIR="$OPTARG" ;; s) CSCOPE=1 ;; x) TODO="no_xref" ;; n) LINESONLY=1 FINAL="final_lines_only" ;; \?) echo "Usage: findlog.sh [-f function] [-l file] [-p] [-c] [-s] [-x] [-n] [list of files]" echo echo "Extract all examples of specified function calls from the files, combining all" echo "lines of the function call onto one line" echo echo " -f function grep pattern defining function, default is \"$FUNC\"" echo " -l file file containing list of files to search" echo " -D dir search in directory" echo " -c equivalent of -l cscope.files" echo " -p search for printf, equivalent of -f \"$PRINTF\"" echo " -d debug mode, don't massage the output" echo " -s call cscope instead of using script" echo " -x don't output file names and line numbers" echo " -n output file names and line numbers only" echo echo "if no files are specified, will do a find . -name '*.[ch]'" exit ;; esac done if [ $CSCOPE -eq 1 ] then if [ $LINESONLY -eq 0 ] then cscope -d -L -0 $FUNC | sed -e "s@\([^ ][^ ]*\) [^ ][^ ]* \([0-9][0-9]*\) \(Log.*\)@\1:\2:\3@" else cscope -d -L -0 $FUNC | sed -e "s@\([^ ][^ ]*\) [^ ][^ ]* \([0-9][0-9]*\) \(Log.*\)@\1:\2@" fi exit fi shift $(($OPTIND - 1)) if [ -n "$FILES" ] then cat $FILES | find_files if [ -z "$1" ] then exit fi fi if [ -z "$1" ] then find $DIR -name '*.[ch]' | find_files fi while [ -n "$1" ] do $TODO $1 shift done ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/ganesha-rados-grace.c�����������������������������������������������������0000664�0000000�0000000�00000012171�14737566223�0021465�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * vim:noexpandtab:shiftwidth=8:tabstop=8: * * Copyright 2017 Red Hat, Inc. and/or its affiliates. * Author: Jeff Layton <jlayton@redhat.com> * * This program 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 3 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 * 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 * * rados-grace: tool for managing coordinated grace period database * * This tool allows an administrator to make direct changes to the rados_grace * database. See the rados_grace support library sources for more info about * the internals. */ #include "config.h" #include <stdio.h> #include <stdint.h> #include <endian.h> #include <rados/librados.h> #include <errno.h> #include <getopt.h> #include <stdlib.h> #include <limits.h> #include <stdbool.h> #include <getopt.h> #include <rados_grace.h> static int cluster_connect(rados_ioctx_t *io_ctx, const char *id, const char *cephconf, const char *pool, const char *ns, bool create) { int ret; rados_t clnt; ret = rados_create(&clnt, id); if (ret < 0) { fprintf(stderr, "rados_create: %d\n", ret); return ret; } ret = rados_conf_read_file(clnt, cephconf); if (ret < 0) { fprintf(stderr, "rados_conf_read_file: %d\n", ret); return ret; } ret = rados_connect(clnt); if (ret < 0) { fprintf(stderr, "rados_connect: %d\n", ret); return ret; } if (create) { ret = rados_pool_create(clnt, pool); if (ret < 0 && ret != -EEXIST) { fprintf(stderr, "rados_pool_create: %d\n", ret); return ret; } } ret = rados_ioctx_create(clnt, pool, io_ctx); if (ret < 0) { fprintf(stderr, "rados_ioctx_create: %d\n", ret); return ret; } rados_ioctx_set_namespace(*io_ctx, ns); return 0; } static const struct option long_options[] = { { "cephconf", 1, NULL, 'c' }, { "ns", 1, NULL, 'n' }, { "oid", 1, NULL, 'o' }, { "pool", 1, NULL, 'p' }, { "userid", 1, NULL, 'u' }, { NULL, 0, NULL, 0 } }; static void usage(char *const *argv) { fprintf(stderr, "Usage:\n%s [ --userid ceph_user ] [ --cephconf /path/to/ceph.conf ] [ --ns namespace ] [ --oid obj_id ] [ --pool pool_id ] dump|add|start|join|lift|remove|enforce|noenforce|member [ nodeid ... ]\n", argv[0]); } int main(int argc, char *const *argv) { int ret, nodes = 0; rados_ioctx_t io_ctx; const char *cmd = "dump"; uint64_t cur, rec; char *userid = NULL; char *cephconf = NULL; char *pool = DEFAULT_RADOS_GRACE_POOL; char *oid = DEFAULT_RADOS_GRACE_OID; char *ns = NULL; int c; const char *const *nodeids = NULL; bool do_add; while ((c = getopt_long(argc, argv, "c:n:o:p:u:", long_options, NULL)) != -1) { switch (c) { case 'c': cephconf = optarg; break; case 'n': ns = optarg; break; case 'o': oid = optarg; break; case 'p': pool = optarg; break; case 'u': userid = optarg; break; default: usage(argv); return 1; } } if (argc > optind) { cmd = argv[optind]; ++optind; nodes = argc - optind; nodeids = (const char *const *)&argv[optind]; } do_add = !strcmp(cmd, "add"); ret = cluster_connect(&io_ctx, userid, cephconf, pool, ns, do_add); if (ret) { fprintf(stderr, "Can't connect to cluster: %d\n", ret); return 1; } if (!strcmp(cmd, "dump")) { ret = rados_grace_dump(io_ctx, oid, stdout); goto out; } if (!nodes) { fprintf(stderr, "Need at least one nodeid.\n"); ret = -EINVAL; goto out; } if (do_add) { ret = rados_grace_create(io_ctx, oid); if (ret < 0 && ret != -EEXIST) { fprintf(stderr, "Can't create grace db: %d\n", ret); return 1; } ret = rados_grace_add(io_ctx, oid, nodes, nodeids); } else if (!strcmp(cmd, "start")) { ret = rados_grace_join_bulk(io_ctx, oid, nodes, nodeids, &cur, &rec, true); } else if (!strcmp(cmd, "join")) { uint64_t cur, rec; ret = rados_grace_join_bulk(io_ctx, oid, nodes, nodeids, &cur, &rec, false); } else if (!strcmp(cmd, "lift")) { ret = rados_grace_lift_bulk(io_ctx, oid, nodes, nodeids, &cur, &rec, false); } else if (!strcmp(cmd, "remove")) { ret = rados_grace_lift_bulk(io_ctx, oid, nodes, nodeids, &cur, &rec, true); } else if (!strcmp(cmd, "enforce")) { ret = rados_grace_enforcing_toggle(io_ctx, oid, nodes, nodeids, &cur, &rec, true); } else if (!strcmp(cmd, "noenforce")) { ret = rados_grace_enforcing_toggle(io_ctx, oid, nodes, nodeids, &cur, &rec, false); } else if (!strcmp(cmd, "member")) { ret = rados_grace_member_bulk(io_ctx, oid, nodes, nodeids); } else { usage(argv); ret = -EINVAL; } out: if (ret) { fprintf(stderr, "Failure: %d\n", ret); return 1; } return 0; } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/helgrind-suppressions�����������������������������������������������������0000664�0000000�0000000�00000013537�14737566223�0022027�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# run with valgrind --suppressions=tools/helgrind-suppressions # to auto-generate more run with --gen-suppressions=all ############################################################################################### # # Ignore known ntirpc issues # ############################################################################################### { <tirpc_control> Helgrind:Race fun:tirpc_control fun:nfs_Init_svc fun:nfs_Init fun:nfs_start fun:main } { <svc_rqst_thrd_run_epoll> Helgrind:Race fun:svc_rqst_thrd_run_epoll fun:svc_rqst_thrd_run fun:rpc_dispatcher_thread fun:mythread_wrapper fun:start_thread fun:clone } { <work_pool_submit> Helgrind:Race fun:work_pool_submit fun:svc_ioq_append fun:svc_vc_reply fun:svc_sendreply fun:nfs_rpc_execute fun:worker_run fun:fridgethr_start_routine fun:mythread_wrapper fun:start_thread fun:clone } { <work_pool_thread> Helgrind:Race fun:work_pool_thread fun:mythread_wrapper fun:start_thread fun:clone } { <svc_rqst_handle_event> Helgrind:Race fun:svc_rqst_handle_event fun:svc_rqst_thrd_run_epoll fun:svc_rqst_thrd_run fun:rpc_dispatcher_thread fun:mythread_wrapper fun:start_thread fun:clone } { <nfs_rpc_getreq_ng> Helgrind:Race fun:nfs_rpc_getreq_ng fun:svc_rqst_handle_event fun:svc_rqst_thrd_run_epoll fun:svc_rqst_thrd_run fun:rpc_dispatcher_thread fun:mythread_wrapper fun:start_thread fun:clone } { <gsh_xprt_decoder_guard> Helgrind:Race fun:gsh_xprt_decoder_guard fun:nfs_rpc_getreq_ng fun:svc_rqst_handle_event fun:svc_rqst_thrd_run_epoll fun:svc_rqst_thrd_run fun:rpc_dispatcher_thread fun:mythread_wrapper fun:start_thread fun:clone } { <nfs_rpc_cond_stall_xprt> Helgrind:Race fun:nfs_rpc_cond_stall_xprt fun:nfs_rpc_getreq_ng fun:svc_rqst_handle_event fun:svc_rqst_thrd_run_epoll fun:svc_rqst_thrd_run fun:rpc_dispatcher_thread fun:mythread_wrapper fun:start_thread fun:clone } { <svc_rqst_rearm_events> Helgrind:Race fun:svc_rqst_rearm_events fun:thr_decode_rpc_requests fun:fridgethr_start_routine fun:mythread_wrapper fun:start_thread fun:clone } { <svc_vc_override_ops> Helgrind:Race fun:svc_vc_override_ops fun:rendezvous_request fun:thr_decode_rpc_request fun:thr_decode_rpc_requests fun:fridgethr_start_routine fun:mythread_wrapper fun:start_thread fun:clone } # This one should be fixed { <thread-still-holds-lock-should-be-fixed> Helgrind:Misc obj:/usr/lib64/libpthread-2.17.so fun:svc_read_vc fun:generic_read_vc fun:fill_input_buf fun:get_input_bytes fun:set_input_fragment fun:xdr_inrec_getbytes fun:xdr_inrec_getlong fun:xdr_getuint32 fun:xdr_dplx_decode fun:svc_vc_recv fun:thr_decode_rpc_request } # This one should be fixed { <evchan_unreg_impl-needs-fixing> Helgrind:LockOrder fun:mutex_lock_WRK fun:pthread_mutex_lock fun:evchan_unreg_impl fun:xprt_unregister fun:svc_vc_destroy fun:svc_release_it fun:gsh_xprt_unref fun:thr_decode_rpc_requests fun:fridgethr_start_routine fun:mythread_wrapper fun:start_thread fun:clone } ############################################################################################### # # Ignore some issues in stats collection # # These are probably harmless but may need a bit more investigation # ############################################################################################### { <record_latency> Helgrind:Race fun:record_latency ... } { <server_stats_nfsv4_op_done> Helgrind:Race fun:server_stats_nfsv4_op_done fun:nfs4_Compound fun:nfs_rpc_execute fun:worker_run fun:fridgethr_start_routine fun:mythread_wrapper fun:start_thread fun:clone } { <Copy_nfs4_state_req-this-one-needs-owner-serialization> Helgrind:Race fun:Copy_nfs4_state_req fun:nfs4_op_close fun:nfs4_Compound fun:nfs_rpc_execute fun:worker_run fun:fridgethr_start_routine fun:mythread_wrapper fun:start_thread fun:clone } ############################################################################################### # # The following are issues where a variable is used in a possibly racy way in a log # message. It is not worth adding a lock just to protect these. # ############################################################################################### { <reserve_lease> Helgrind:Race fun:reserve_lease ... } { <update_lease> Helgrind:Race fun:update_lease ... } { <display_client_id_rec> Helgrind:Race ... fun:display_client_id_rec ... } { <display_nfs4_owner> Helgrind:Race ... fun:display_nfs4_owner ... } ############################################################################################### # # Atomic variables should not contribute to data races. This MIGHT obscure a situation # where an atomic variable is used without an atomic primitive. However, there are many # places where sometimes the atomic is accessed under a lock, and sometimes not, and helgrind # doesn't properly identify that as safe. # ############################################################################################### { <atomic_fetch_uint16_t> Helgrind:Race fun:atomic_fetch_uint16_t ... } { <atomic_fetch_uint32_t> Helgrind:Race fun:atomic_fetch_uint32_t ... } { <atomic_store_uint32_t> Helgrind:Race fun:atomic_store_uint32_t ... } { <atomic_store_uint64_t> Helgrind:Race fun:atomic_store_uint64_t ... } { <atomic_fetch_voidptr> Helgrind:Race fun:atomic_fetch_voidptr ... } { <atomic_store_voidptr> Helgrind:Race fun:atomic_store_voidptr ... } { <atomic_fetch_time_t> Helgrind:Race fun:atomic_fetch_time_t ... } { <atomic_store_time_t> Helgrind:Race fun:atomic_store_time_t ... } �����������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/mount.9P������������������������������������������������������������������0000775�0000000�0000000�00000005230�14737566223�0017103�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/bash # # Usage: # /sbin/mount.9P spec dir [-fnrsvw] [-o options] restricted=1 if [ $UID -eq 0 ] && [ $UID -eq $EUID ]; then restricted=0 fi # mount(8) in restricted mode (for non-root users) does not allow to use any # mount options, types or so on command line. We have to call mount(8) with # mountpoint only. All necessary options have to be defined in /etc/fstab. # if [ $restricted -eq 1 ]; then exec /bin/mount -i "$@" fi # "Custom" default values (different from kernel default) access="user" uname="root" msize="65560" # Command line building variables opts="" switch="" IPADDR="" MNT="" # Options can sneak in anywhere, e.g. mount -o foo device -o bar dir -o meh # This allows for various flags and takes the first two non-options as # device and dir accordingly while [[ $# -ge $OPTIND ]]; do if getopts ":o:fnrsvwh" opt; then case "$opt" in o) IN_OPTIONS="${OPTARG//,/ }" for OPT in $IN_OPTIONS; do case "${OPT%%=*}" in # pass known options through... version|ro|rw|remount|posixacl|debug|trans|rq|sq|timeout| \ port|cache|loose|mmap|cachetag|noextend|privport|context) opts+="${OPT}," ;; access) access="${OPT#*=}" ;; uname) uname="${OPT#*=}" ;; msize) msize="${OPT#*=}" ;; *) echo "Invalid 9p option: $OPT" exit 1 ;; esac done ;; f|n|r|s|w) switch+="-$opt " ;; v) switch+="-$opt " VERBOSE=1 ;; h) echo "Usage: $0 remotehost:remotedir dir [-fnrsvw] [-o options]" exit 1 ;; \?) echo "Invalid mount option: -$OPTARG" exit 1 ;; esac elif [[ -z "$IPADDR" ]]; then shift $((OPTIND-1)) OPTIND=1 REMOTE="$1" shift if [[ "$REMOTE" != *:* ]]; then echo "Mount destination $REMOTE should be server:path" exit 1 fi SERVERID="${REMOTE%%:*}" aname="${REMOTE#*:}" if ! IPADDR=$(getent ahostsv4 "$SERVERID" 2>&1 | awk '/STREAM/ { print $1; exit }'); then echo "Hostname could not be resolved" exit 1 fi elif [[ -z "$MNT" ]]; then shift $((OPTIND-1)) OPTIND=1 MNT="$1" shift else echo "Extra argument: ${!OPTIND}" exit 1 fi done # the "-i" is mandatory to avoid looping on this helper EXEC_STRING="/bin/mount -i $switch -t 9p $IPADDR $MNT -o ${opts}access=${access},uname=${uname},msize=${msize},aname=${aname}" if [[ -n "$VERBOSE" ]]; then echo "$EXEC_STRING" fi exec $EXEC_STRING ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/����������������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0017527�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/CMakeLists.txt��������������������������������������������������0000664�0000000�0000000�00000003726�14737566223�0022277�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb <jlieb@panasas.com> # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- if(CEPH_FS_CEPH_STATX) add_definitions( -D_FILE_OFFSET_BITS=64 ) include_directories(${CEPHFS_INCLUDE_DIR}) endif(CEPH_FS_CEPH_STATX) SET(multilock_SRCS ml_functions.c multilock.h ) add_executable(ml_console ml_console.c ${multilock_SRCS} ) target_link_libraries(ml_console m ${SYSTEM_LIBRARIES}) add_executable(ml_posix_client ml_posix_client.c ${multilock_SRCS} ) target_link_libraries(ml_posix_client m pthread ${SYSTEM_LIBRARIES}) if(CEPH_FS_CEPH_STATX) add_executable(ml_cephfs_client ml_cephfs_client.c ${multilock_SRCS} ) target_link_libraries(ml_cephfs_client m pthread ${CEPHFS_LIBRARIES} ${SYSTEM_LIBRARIES}) endif(CEPH_FS_CEPH_STATX) if(USE_FSAL_GLUSTER AND USE_LKOWNER) include_directories(${GFAPI_INCLUDE_DIRS}) add_executable(ml_glusterfs_client ml_glusterfs_client.c ${multilock_SRCS} ) target_link_libraries(ml_glusterfs_client m pthread ${GFAPI_LIBRARIES} ${SYSTEM_LIBRARIES}) endif(USE_FSAL_GLUSTER AND USE_LKOWNER) ������������������������������������������nfs-ganesha-6.5/src/tools/multilock/README����������������������������������������������������������0000664�0000000�0000000�00000052764�14737566223�0020425�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later multilock test tool Copyright IBM Corporation, 2012 Contributor: Frank Filz <ffilz@us.ibm.com> This software is a server that implements the NFS protocol. This program 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 3 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 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 OVERVIEW -------- Multilock is a test tool for testing functionality of POSIX locks. It is directed at testing NFS server and client implementations. The architecture of the tool is a console program (ml_console) and client programs (currently ml_posix_chold). The console directs any number of clients in a sequence of lock operations. The communication between the console and clients is a TCP connection, so the clients may be run on multiple hosts (for example, multiple clients of the same NFS server, or even a local process on the NFS server itself). Other remote file system protocols may also be tested (any remote file system that can be mounted on Linux and provides byte range locks via POSIX fcntl may be exercised using ml_posix_client). The communication protocol with the clients is documented below so additional clients may be implemented to implement other OS flavors (such as Windows), or even directly drive protocols (such as NFS v3/NLM, NFS v4, or CIFS/Samba). The client programs are reasonably capable of being run using ssh which allows them to be used by an automated test case. ml_posix_client is also capable of being run standalone (the protocol used over TCP is plain text so it is trivial to read input from stdin instead of a TCP socket). EXECUTING THE PROGRAMS ---------------------- ml_console --------- ml_console has two modes, scripted and interractive. Several command line options allow some control over output and how errors are handled. Usage: ml_console [-p port] [-s] [-f] [-q] [-x script] [-d] -p port - specify the port to listen to clients on -s - specify strict mode (clients are not polled without EXPECT) -f - specify errors are fatal mode -q - speficy quiet mode -d - speficy dup errors mode (errors are sent to stdout and stderr) -x script - specify script to run -k - syntax check only -e - non-fatal errors, full accounting of errors to stderr, everything to stdout Since there is no method of automatic port discovery, the -p option is reccomended so that the clients can be told which port to contact the server on. The -k option when combined with -x to specify a script will result in the script just being syntax checked. Whenever a script is specified, it is completely syntax checked before the console starts up and runs the script. The -e option results in stderr showing the command leading to the failure, the expected output, and the actual response. This is useful for reporting problems. The -e option also duplicates stderr to stdout just as per -d. ml_posix_client -------------- ml_posix_client has three modes, interractive (standalone), scripted (standalone), and console. There is almost no difference between scripted mode and interracive mode where a stdin is redirected from a file. Usage: ml_posix_client -s server -p port -n name [-q] [-d] [-c path] ml_posix_client -x script [-q] [-d] [-c path] ml_posix_client [-q] [-d] [-c path] ml_posix_client may be run in three modes - In the first mode, the client will be driven by a console. - In the second mode, the client is driven by a script. - In the third mode, the client interractive. -s server - specify the console's hostname or IP address -p port - specify the console's port number -n name - specify the client's name -x script - specify the name of a script to execute -q - specify quiet mode -d - specify dup errors mode (errors are sent to stdout and stderr) -c path - chdir In console mode, the server's address and port must be specified. Also the client must be given a name (which the console will use to identify which client commands are intended for and from which responses are expected). The -c option allows the client to execute in a specific directory. This would allow a test client machine to have several different mounts to the same server using different protocols and have the script executed from the console easily direct testing to any of those mounts without the script needing to be modified (for example, the script can just refer to files by file name without any path). THE COMMAND PROTOCOL -------------------- The following documents the protocol the console uses to send commands to the client. Since this is a plain text protocol, it is also the command language of the clients in interractive mode. Note that the console doesn't necessarily utilize all the flexibility of this protocol (for example, the console will always tag commands, and will always quote strings). Note that the protocol is almost exclusively case insensitive (except for strings). Strings that are at the end of a command need not be quoted. Any leading blanks will not be part of the string. Strings must be quoted if they might be mistaken for an optional parameter. Strings currently can not contain new-line characters. The general format of a command is: [tag] command parameters Commands are terminated with a new-line. The tag is a numeric label for the command. The purpose of the tag is to allow the console to correlate responses with specific commands for verification. The commands and specific parameters are: [tag] OPEN file_pos rw|ro|wo|O_RDWR|O_RDONLY|O_WRONLY [create|creat|O_CREAT] [truncate|trunc|O_TRUNC] [exclusive|excl|O_EXCL] [mode modeval] [POSIX|OFD] "file-name" [tag] CLOSE file_pos [tag] READ file_pos length [tag] READ file_pos "string" [tag] WRITE file_pos "data" [tag] SEEK file_pos offset [tag] LOCK file_pos read|write|shared|exclusive|F_RDLCK|F_WRLCK start length [tag] LOCKW file_pos read|write|shared|exclusive|F_RDLCK|F_WRLCK start length [tag] UNLOCK file_pos start length [tag] TEST file_pos read|write|shared|exclusive|F_RDLCK|F_WRLCK start length [tag] LIST file_pos start length [tag] HOP file_pos read|write|shared|exclusive|F_RDLCK|F_WRLCK start length [tag] UNHOP file_pos start length [tag] COMMENT "string" [tag] HELLO "name" [tag] FORK "name" [tag] ALARM seconds [tag] QUIT OPEN ---- The client maintains an array of file references, file_pos specifies which entry is to be used to reference the file for other commands. It operates much like a file descriptor. Files may be opened read-write, read-only, or write-only. Files may also be created on open (other open flags may be supported in the future). The OFD option indicates that Open File Description locks (or equivalent) will be used on this file descriptor. file-name is allowed to be as long as PATH_MAX - 1. CLOSE ----- Closes a file and frees the file_pos for re-use. READ ---- Read data from the current position in the file (use SEEK to set the position). This is fundamentally a record read, though the record contents are expected to be ascii strings. If the second form of the command is given, the length of the string is used as the read length (this form is to be used with console scripting command OK). At most 1024 characters may be read. Note that the file data is expected to be characters and should not have 0 bytes within. WRITE ----- Write the string to the current position in the file (use SEEK to set the position). This is fundamentally a record write, though the record contents are expected to be ascii strings. At most 1024 characters may be written. Note that the file data is expected to be characters and should not have 0 bytes within. SEEK ---- Seek will set the current file position. Use this to select the record to read or write. LOCK ---- Lock requests a non-blocking lock. If the lock can not be granted, the command will return immediately with DENIED status. Locks may be read or write locks (aka shared or exclusive). LOCKW ----- LockW requests a blocking lock. If the lock can not be immediately granted, the command will block. Locks may be read or write locks (aka shared or exclusive). Implementation Note: ml_posix_client sets a default alarm of 5 seconds before this command if an alarm isn't already set. This will prevent script hangs if a lock will never be granted. If a script expects a longer delay, it should set an explicit alarm. TEST ---- Test will test for lock conflict, and if a lock conflict is found, details of a conflicting lock will be returned. Locks may be read or write locks (aka shared or exclusive). LIST ---- List will return a list of locks overlapping the range specified. The expectation is that effectively a series of TEST operations for write locks will will be used to discover the list of locks. COMMENT ------- Comment is a do nothing command. It allows the console to send some commentary to the clients. A comment is at most 1024 bytes. HELLO ----- This is another do nothing command. It's real purpose is a response place holder for the console to be able to receive initial "HELLO" communications from the clients. The name is expected to be no more than 1024 characters. FORK ----- Causes the client to fork, open a new socket back to the console and send a HELLO response on that socket with the forked name. The name may not be longer than 1024 characters. ALARM ----- Alarm is used to set an alarm, it may interrupt a blocked lock (which in fact is it's primary purpose). If an alarm is already running, the existing alarm will be cancelled and a new alarm set. A seconds value of 0 will cancel any existing alarm and not set a new one. QUIT ---- Quit instructs a client to release all locks, close all files, and exit. THE RESPONSE PROTOCOL --------------------- The following documents the protocol the clients uses to send responses to the console. Note that the clients don't utilize all the flexibility of this protocol (for example, ml_posix_client will always quote strings). Note that the protocol is almost exclusively case insensitive (except for strings). Strings that are at the end of a response need not be quoted. Any leading blanks will not be part of the string. Strings must be quoted if they might be mistaken for an optional parameter. Strings currently can not contain new-line characters. The general format is: tag COMMAND STATUS results The specific responses are: tag OPEN OK file_pos file_number tag CLOSE OK file_pos tag READ OK file_pos length "data" tag WRITE OK file_pos length tag SEEK OK file_pos tag LOCK GRANTED file_pos read|write|shared|exclusive|F_RDLCK|F_WRLCK start length tag LOCK DENIED file_pos read|write|shared|exclusive|F_RDLCK|F_WRLCK start length tag LOCKW GRANTED file_pos read|write|shared|exclusive|F_RDLCK|F_WRLCK start length tag LOCKW CANCELED file_pos read|write|shared|exclusive|F_RDLCK|F_WRLCK start length tag LOCKW DEADLOCK file_pos read|write|shared|exclusive|F_RDLCK|F_WRLCK start length tag UNLOCK GRANTED file_pos unlock|F_UNLCK start length tag TEST AVAILABLE file_pos read|write|shared|exclusive|F_RDLCK|F_WRLCK start length tag TEST CONFLICT file_pos pid read|write|shared|exclusive|F_RDLCK|F_WRLCK start length tag LIST AVAILABLE file_pos start length tag LIST DENIED file_pos start length tag LIST CONFLICT file_pos pid read|write|shared|exclusive|F_RDLCK|F_WRLCK start length tag HOP GRANTED file_pos read|write|shared|exclusive|F_RDLCK|F_WRLCK start length tag HOP DENIED file_pos read|write|shared|exclusive|F_RDLCK|F_WRLCK start length tag UNHOP GRANTED file_pos unlock|F_UNLCK start length tag COMMENT OK "string" tag HELLO OK "name" tag ALARM OK seconds tag ALARM CANCELED remain tag ALARM COMPLETED tag QUIT OK tag cmd ERRNO value "string" If a command results in an error, an errno and description of the error will be returned in the response. Currently ml_console doesn't really do anything to analyze these errors. They will cause failure of scripts of course. OPEN ---- Returns OK or ERRNO. A successful OPEN will respond with the file number that results from opening the file. This should not be relied on to be any specific value and is merely provided for edification. CLOSE ----- Returns OK or ERRNO. READ ---- Returns OK or ERRNO. If successful, the length of data read and the actual data will be returned. WRITE ----- Returns OK or ERRNO. If successful, the actual length of data written wil be returned. SEEK ---- Returns OK or ERRNO. LOCK ---- Returns GRANTED, DENIED, or ERRNO. LOCKW ----- Returns GRANTED, CANCELED, DEADLOCK, or ERRNO. If a blocked lock is canceled (for example by an alarm triggering), the CANCELED status will be returned. If a deadlock is detected, DEADLOCK will be returned. UNLOCK ------ Returns GRANTED or ERRNO. TEST ---- Returns AVAILABLE, CONLFICT or ERRNO. If there is a conflicting lock, one conflicting lock will be returned. LIST ---- Returns AVAILABLE, DENIED or ERRNO. Prior to returning DENIED, a list will return one or more CONFLICT responses corresponding to locks held that overlap the range. An AVAILABLE response indicates an empty list while a DENIED response indicates no more CONFLICT responses are forthcoming. It should be noted that LIST will only be able to return one instance of a read lock if there are multiple read locks on the same range. LIST may or may not return two read locks if there is one read lock and a second larger read lock that completely overlaps the first one. LIST should return two read locks if they partially overlap. For example: owner1 read 1-3, owner2 read 1-3, owner3 read 1-3 can only return one of those. onwer1 read 1-3, owner2 read 1-6 may return just owner2 read 1-6 or both. owner1 read 1-6, owner2 read 3-9 should return both. This is due to how fcntl with F_GETLK is implemented (or NFS v3 TEST or NFS v4 LOCKT). HOP --- Returns GRANTED, DENIED, or ERRNO. HOP is similar to LOCK, except the lock is acquired one byte at a time in an alternating pattern (for example, HOP read 0 5 is acquired in order 0, 2, 1, 4, 3). UNHOP ----- Returns GRANTED or ERRNO. UNHOP is similar to UNLOCK, except the lock is released one byte at a time in an alternating pattern (for example, UNHOP 0 5 is released in order 0, 2, 1, 4, 3). COMMENT ------- Returns OK and reflects back the original comment. HELLO ----- Returns OK and reflects back the original comment. Clients are expected to send a HELLO response to identify themselves when first connecting to the console. ALARM ----- Returns OK and CANCELED or COMPLETE. If the alarm is canceled before completion, a CANCELED response will be sent. If the alarm triggers, a COMPLETE response will be sent. QUIT ---- Returns OK. A client should response to QUIT before closing the connection with the console. CONSOLE SCRIPTING ----------------- -------------------------------------------------------------------------------- The console implements a simple script language which allows commands to be directed to multiple clients. The scripting language also allows for verification of client responses. The console script commands are: # QUIT STRICT on|off FATAL on|off SLEEP seconds name tag command parameters EXPECT name tag command results OK name command parameters GRANTED name command parameters AVAILABLE name command parameters DENIED name command parameters DEADLOCK name command parameters CLIENTS name name... FORK name1 name2 { } There are some special tag values that may be used. $ references the line number of a command. When used in an EXPECT, it uses the line number of the last command. $A to $Z increment the variable when used in a command, and store the tag into a slot. When used in an EXPECT, $A to $Z use the value out of the slot. This simplifies script writing while still allowing responses to be correlated with earlier commands (for example a LOCK, LOCKW (blocks), UNLOCK, GRANTED sequence). # - This allows comments to be placed in scripts. QUIT ---- This ends a script. QUIT will be sent to each connected client. Any outstanding responses from the clients will be indicated as unexpected and may result in script failure. STRICT ------ This turns strict mode on and off. It is only effective in interractive mode and will make interractive mode function like scripted mode where client responses are only processed after EXPECT, }, OK, or GRANTED commands. FATAL ----- This turns fatal mode on and off. When fatal mode is on, any error will cause ml_console to exit (after issuing a QUIT command) and indicate script failure. SLEEP ----- This inserts a delay into a script where no responses are expected. In fatal mode, any response received during this time will cause failure. name ---- The "default" command is to send a command to the named client. Note that the console does not block waiting for a response to one of these commands. EXPECT ------ This command waits for a client response and compares it to the results expected. An "*" may be used to indicate don't care for any parameters (for numeric parameters, -1 has the same effect). If any of the parameters in the response don't match, an error will be reported (and failure in fatal mode). OK, AVAILABLE, GRANTED, DENIED, and DEADLOCK -------------------------------------------- These commands issue a command to the named client and expect the appropriate response. These commands will block awaiting a response. OK is only valid for the following commands: OPEN, CLOSE, SEEK, READ, WRITE, COMMENT, ALARM, HELLO, and QUIT. READ must specify the expected string, and the length of that string is sent to the client as the expected length. OPEN accepts any file_number. GRANTED is only valid with LOCK, and UNLOCK. AVAILABLE is only valid for TEST and LIST. For LIST, an empty list is expected. DENIED is only valid with LOCK. DEADLOCK is only valus with LOCKW. CLIENTS ------- This command creates a list of EXPECT {name} * HELLO OK {name} for each client named on the command. FORK ---- This command sents a FORK message to client name1 with name2 as the parameter. It then creates a list: EXPECT {name1} * FORK OK {name2} EXPECT {name2} * HELLO OK {name2} { and } ------- These paired commands are used to group a set of EXPECT commands where the responses might arrive in any order. All responses are required for completion of this command. There is no restriction to what commands may be included inside a pair of braces, however, note that all OK and GRANTED commands will immediately block waiting for a response. IMPLEMENTATION NOTES FOR ml_console ---------------------------------- ml_console uses select to wait for input from all the clients (and the console in interractive mode). ml_console will respond to SIGINT (ctrl-c) and SIGTERM by cleanly exiting. These should be able to interrupt most hung scripts for a clean exit and failure. IMPLEMENTATION NOTES FOR ml_posix_client --------------------------------------- ml_posix_client is single threaded and if a command (primarily LOCKW) blocks, the client will not receive new commands or respond to the console. ml_posix_client uses ALARM and SIGALRM to interrupt a blocked lock. THOUGHTS ON POSSIBLE OTHER CLIENT IMPLEMENTATIONS ------------------------------------------------- Note that all the parsing and response formatting functions are implemented in functions.c and separated from the implementation of commands in ml_posix_client.c. This would make it easy to implement a client that could, for example, send NFS v3 and NLM requests corresponding to the commands. Such a client should have a command line option to specify the server's address and path for initial MOUNT. An OPEN command would do a LOOKUP (if the asumption was scripts that utilized files in a single directory, the LOOKUP implementation would not have to deal with path walking. Such a client of course need not actually block for a LOCKW command, allowing the same lock owner to have multiple blocking locks outstanding. A FEW NOTES ON THE FUNCTIONS IN ml_functions.c ---------------------------------------------- char * parse_response(char * line, response_t * resp); This function is used by the console to parse out a response. Then use int compare_responses(response_t * expected, response_t * received); to verify that response against an expected response (using parse_response to parse the EXPECT command also). void add_response(response_t * resp, response_t ** list); response_t * check_expected_responses(response_t *expected_responses, response_t * client_resp); These functions implement checking against a list of responses (used by {...}) char * parse_request(char * line, response_t * req, int no_tag); This would be the center of an alternate client implementation since it will parse the command stream. ml_console also uses this to syntax check commands and the built request is also used as the basis of the expected response for OK and GRANTED. void respond(response_t * resp); This function is used by a client to send a response to the output stream (console in interractive mode and console socket in console mode). void send_cmd(response_t * req); This function is used by the console to send commands to clients. ������������nfs-ganesha-6.5/src/tools/multilock/ml_cephfs_client.c����������������������������������������������0000664�0000000�0000000�00000074236�14737566223�0023205�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2012 * Contributor: Frank Filz <ffilzlnx@mindspring.com> * * * This software is a server that implements the NFS protocol. * * * This program 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 3 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 * 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 * * */ #include <fcntl.h> #include <cephfs/libcephfs.h> #include <pthread.h> #include <stdlib.h> #include <assert.h> #include <libgen.h> #include "multilock.h" #include "../../include/gsh_list.h" /* command line syntax */ char options[] = "c:qdx:s:n:p:h?C:"; char usage[] = "Usage: ml_cephfs_client -C ceph-config -s server -p port -n name [-q] [-d] [-c path]\n" " ml_cephfs_client -C ceph-config -x script [-q] [-d] [-c path]\n" " ml_cephfs_client -C ceph-config [-q] [-d] [-c path]\n" "\n" " ml_cephfs_client may be run in three modes\n" " - In the first mode, the client will be driven by a console.\n" " - In the second mode, the client is driven by a script.\n" " - In the third mode, the client interractive.\n" "\n" " -s server - specify the console's hostname or IP address\n" " -p port - specify the console's port number\n" " -n name - specify the client's name\n" " -x script - specify the name of a script to execute\n" " -q - specify quiet mode\n" " -d - specify dup errors mode (errors are sent to stdout and stderr)\n" " -c path - chdir\n" " -C ceph-config - path to the ceph config file\n"; #define NUM_WORKER 4 #define POLL_DELAY 10 enum thread_type { THREAD_NONE, THREAD_MAIN, THREAD_WORKER, THREAD_POLL, THREAD_CANCEL }; struct work_item { struct glist_head queue; struct glist_head fno_work; struct response resp; enum thread_type work_owner; pthread_t work_thread; time_t next_poll; }; char server[MAXSTR]; char name[MAXSTR]; char portstr[MAXSTR]; int port; char line[MAXXFER]; long alarmtag; Inode *inodes[MAXFPOS + 1]; Fh *filehandles[MAXFPOS + 1]; enum lock_mode lock_mode[MAXFPOS + 1]; char ceph_config[PATH_MAX]; char ceph_path[PATH_MAX] = "/"; /* Global variables to manage work list */ struct glist_head fno_work[MAXFPOS + 1]; struct glist_head work_queue = GLIST_HEAD_INIT(work_queue); struct glist_head poll_queue = GLIST_HEAD_INIT(poll_queue); pthread_t threads[NUM_WORKER + 1]; pthread_mutex_t work_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t work_cond = PTHREAD_COND_INITIALIZER; enum thread_type a_worker = THREAD_WORKER; enum thread_type a_poller = THREAD_POLL; /* The CephFS mount */ struct ceph_mount_info *cmount; /* Default mount UserPerm */ UserPerm *cephperms; void openserver(void) { struct addrinfo *addr; int rc; struct addrinfo hint; int sock; struct response resp; if (!quiet) fprintf(stdout, "server=%s port=%d name=%s\n", server, port, name); memset(&hint, 0, sizeof(hint)); hint.ai_family = AF_INET; hint.ai_socktype = SOCK_STREAM; rc = getaddrinfo(server, portstr, &hint, &addr); if (rc != 0) fatal("getaddrinfo error %d \"%s\"\n", rc, gai_strerror(rc)); rc = socket(addr[0].ai_family, SOCK_STREAM, 0); if (rc == -1) fatal("socket failed with ERRNO %d \"%s\"\n", errno, strerror(errno)); sock = rc; rc = connect(sock, addr[0].ai_addr, addr[0].ai_addrlen); if (rc == -1) fatal("connect failed with ERRNO %d \"%s\"\n", errno, strerror(errno)); input = fdopen(sock, "r"); if (input == NULL) fatal("Could not create input stream from socket ERROr %d \"%s\"\n", errno, strerror(errno)); output = fdopen(sock, "w"); if (output == NULL) fatal("Could not create output stream from socket ERROr %d \"%s\"\n", errno, strerror(errno)); if (!quiet) fprintf(stdout, "connected to server %s:%d\n", server, port); resp.r_cmd = CMD_HELLO; resp.r_status = STATUS_OK; resp.r_tag = 0; array_strcpy(resp.r_data, name); respond(&resp); } bool do_fork(struct response *resp, bool use_server) { pid_t forked; if (!use_server) { fprintf_stderr("FORK may only be used in server mode\n"); return false; } forked = fork(); if (forked < 0) { /* Error */ resp->r_status = STATUS_OK; resp->r_errno = errno; if (!quiet) fprintf(stdout, "fork failed %d (%s)\n", (int)resp->r_errno, strerror(resp->r_errno)); return true; } else if (forked == 0) { /* Parent sends a FORK response */ array_strcpy(resp->r_data, name); resp->r_status = STATUS_OK; if (!quiet) fprintf(stdout, "fork succeeded\n"); return true; } if (!quiet) fprintf(stdout, "forked\n"); /* This is the forked process */ /* First close the old output. */ fclose(output); /* Setup the new client name. */ array_strcpy(name, resp->r_data); /* Then open a new connection to the server. */ openserver(); /* Response already sent. */ return false; } void command(void) { } void sighandler(int sig) { struct response resp; switch (sig) { case SIGALRM: resp.r_cmd = CMD_ALARM; resp.r_tag = alarmtag; resp.r_status = STATUS_COMPLETED; alarmtag = 0; respond(&resp); break; case SIGPIPE: case SIGIO: break; } } void do_alarm(struct response *resp) { unsigned int remain; remain = alarm(resp->r_secs); if (remain != 0) { struct response resp2; resp2.r_cmd = CMD_ALARM; resp2.r_tag = alarmtag; resp2.r_secs = remain; resp2.r_status = STATUS_CANCELED; respond(&resp2); } if (resp->r_secs != 0) alarmtag = resp->r_tag; else alarmtag = 0; resp->r_status = STATUS_OK; } /* Hack for backwards compatibility */ #ifndef AT_STATX_DONT_SYNC #define AT_STATX_DONT_SYNC AT_NO_ATTR_SYNC #endif void do_open(struct response *resp) { int rc; Fh *filehandle = NULL; Inode *inode = NULL, *parent = NULL; struct ceph_statx stx; if (filehandles[resp->r_fpos] != NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EINVAL; array_strcpy(errdetail, "fpos in use"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } if ((resp->r_flags & O_CREAT) != 0) { char fullpath[PATH_MAX]; char *name; char *path; array_strcpy(fullpath, resp->r_data); name = basename(fullpath); path = dirname(fullpath); fprintf_stderr("path = '%s' name = '%s'\n", path, name); rc = ceph_ll_walk(cmount, path, &parent, &stx, 0, AT_STATX_DONT_SYNC, cephperms); if (rc >= 0) { rc = ceph_ll_create(cmount, parent, name, resp->r_mode, resp->r_flags, &inode, &filehandle, &stx, 0, AT_STATX_DONT_SYNC, cephperms); /* Release the parent directory. */ ceph_ll_put(cmount, parent); if (rc < 0) array_strcpy(errdetail, "ceph_ll_create"); } else { array_sprintf(errdetail, "ceph_ll_walk %s", path); } } else { rc = ceph_ll_walk(cmount, resp->r_data, &inode, &stx, 0, AT_STATX_DONT_SYNC, cephperms); if (rc >= 0) { rc = ceph_ll_open(cmount, inode, resp->r_flags, &filehandle, cephperms); if (rc < 0) array_strcpy(errdetail, "ceph_ll_open"); } else { array_strcpy(errdetail, "ceph_ll_walk"); } } if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(badtoken, resp->r_data); return; } inodes[resp->r_fpos] = inode; filehandles[resp->r_fpos] = filehandle; lock_mode[resp->r_fpos] = (enum lock_mode)resp->r_lock_type; resp->r_fno = resp->r_fpos; resp->r_status = STATUS_OK; } void do_write(struct response *resp) { long long rc; if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } rc = ceph_ll_write(cmount, filehandles[resp->r_fpos], -1, resp->r_length, resp->r_data); if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Write failed"); array_sprintf(badtoken, "%lld", resp->r_length); return; } if (rc != resp->r_length) { resp->r_status = STATUS_ERRNO; resp->r_errno = EIO; array_strcpy(errdetail, "Short write"); array_sprintf(badtoken, "%lld", rc); return; } resp->r_status = STATUS_OK; } void do_read(struct response *resp) { long long rc; if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } if (resp->r_length > MAXSTR) resp->r_length = MAXSTR; rc = ceph_ll_read(cmount, filehandles[resp->r_fpos], -1, resp->r_length, resp->r_data); if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Read failed"); array_sprintf(badtoken, "%lld", resp->r_length); return; } resp->r_data[rc] = '\0'; resp->r_length = strlen(resp->r_data); resp->r_status = STATUS_OK; } void do_seek(struct response *resp) { int rc; if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } rc = ceph_ll_lseek(cmount, filehandles[resp->r_fpos], resp->r_start, SEEK_SET); if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Seek failed"); array_sprintf(badtoken, "%lld", resp->r_start); return; } resp->r_status = STATUS_OK; } void free_work(struct work_item *work) { /* Make sure the item isn't on any queues */ glist_del(&work->fno_work); glist_del(&work->queue); free(work); } void cancel_work_item(struct work_item *work) { switch (work->work_owner) { case THREAD_NONE: case THREAD_MAIN: case THREAD_CANCEL: /* nothing special to do */ break; case THREAD_WORKER: case THREAD_POLL: /* Mark the work item to be canceled */ work->work_owner = THREAD_CANCEL; pthread_kill(work->work_thread, SIGIO); /* Wait for thread to be done or cancel the work */ while (work->work_owner != THREAD_NONE) pthread_cond_wait(&work_cond, &work_mutex); } /* Done with the work item, free the memory. */ free_work(work); } static inline long long lock_end(struct response *req) { if (req->r_length == 0) return LLONG_MAX; return req->r_start + req->r_length; } void cancel_work(struct response *req) { struct glist_head cancel_work = GLIST_HEAD_INIT(cancel_work); struct glist_head *glist; struct work_item *work; bool start_over = true; pthread_mutex_lock(&work_mutex); while (start_over) { start_over = false; glist_for_each(glist, fno_work + req->r_fpos) { work = glist_entry(glist, struct work_item, fno_work); if (work->resp.r_start >= req->r_start && lock_end(&work->resp) <= lock_end(req)) { /* Do something */ cancel_work_item(work); /* List may be messed up */ start_over = true; break; } } } pthread_mutex_unlock(&work_mutex); } /* Must only be called from main thread...*/ int schedule_work(struct response *resp) { struct work_item *work = calloc(1, sizeof(*work)); if (work == NULL) { errno = ENOMEM; return -1; } pthread_mutex_lock(&work_mutex); work->resp = *resp; work->work_owner = THREAD_NONE; glist_add_tail(&work_queue, &work->queue); glist_add_tail(fno_work + resp->r_fpos, &work->fno_work); /* Signal to the worker and polling threads there is new work */ pthread_cond_broadcast(&work_cond); pthread_mutex_unlock(&work_mutex); return 0; } bool do_lock(struct response *resp, enum thread_type thread_type) { int rc; struct flock lock; bool retry = false; uint64_t owner; bool sleep = false; if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return true; } switch (lock_mode[resp->r_fpos]) { case LOCK_MODE_POSIX: if (resp->r_cmd == CMD_LOCKW) { if (thread_type != THREAD_WORKER) { retry = true; sleep = false; } else { sleep = true; } } else { sleep = false; } owner = getpid(); break; case LOCK_MODE_OFD: if (resp->r_cmd == CMD_LOCKW) { if (thread_type != THREAD_WORKER) { retry = true; } else { sleep = true; } } else { sleep = false; } owner = resp->r_fpos; break; } lock.l_whence = SEEK_SET; lock.l_type = resp->r_lock_type; lock.l_start = resp->r_start; lock.l_len = resp->r_length; lock.l_pid = 0; rc = ceph_ll_setlk(cmount, filehandles[resp->r_fpos], &lock, owner, sleep); if (rc == -EAGAIN && retry && thread_type == THREAD_MAIN) { /* We need to schedule OFD blocked lock */ rc = schedule_work(resp); /* Check for scheduling success */ if (rc >= 0) return false; } if (rc < 0) { if (rc == -EAGAIN) { if (retry) { /* Let caller know we didn't complete */ return false; } resp->r_status = STATUS_DENIED; } else if (rc == -EINTR) { resp->r_status = STATUS_CANCELED; } else if (rc == -EDEADLK) { resp->r_status = STATUS_DEADLOCK; } else { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Lock failed"); array_sprintf(badtoken, "%s %lld %lld", str_lock_type(lock.l_type), resp->r_start, resp->r_length); } } else resp->r_status = STATUS_GRANTED; return true; } void do_hop(struct response *resp) { int rc; int pos; struct flock lock; uint64_t owner; if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } switch (lock_mode[resp->r_fpos]) { case LOCK_MODE_POSIX: owner = getpid(); break; case LOCK_MODE_OFD: owner = resp->r_fpos; break; } for (pos = resp->r_start; pos < resp->r_start + resp->r_length; pos++) { if ((pos == 0) || (pos == (resp->r_start + resp->r_length - 1))) lock.l_start = pos; else if ((pos & 1) == 0) lock.l_start = pos - 1; else lock.l_start = pos + 1; lock.l_whence = SEEK_SET; lock.l_type = resp->r_lock_type; lock.l_len = 1; lock.l_pid = 0; rc = ceph_ll_setlk(cmount, filehandles[resp->r_fpos], &lock, owner, false); if (rc < 0) { if (rc == -EAGAIN) { resp->r_start = lock.l_start; resp->r_length = 1; resp->r_status = STATUS_DENIED; break; } else { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Hop failed"); array_sprintf(badtoken, "%s %ld", str_lock_type(resp->r_lock_type), lock.l_start); break; } } else resp->r_status = STATUS_GRANTED; } if (resp->r_status != STATUS_GRANTED) { lock.l_whence = SEEK_SET; lock.l_type = F_UNLCK; lock.l_start = resp->r_start; lock.l_len = resp->r_length; lock.l_pid = 0; rc = ceph_ll_setlk(cmount, filehandles[resp->r_fpos], &lock, owner, false); if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Hop Unlock failed"); array_sprintf(badtoken, "%lld %lld", resp->r_start, resp->r_length); } } } void do_unhop(struct response *resp) { int rc; int pos; struct flock lock; uint64_t owner; if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } switch (lock_mode[resp->r_fpos]) { case LOCK_MODE_POSIX: owner = getpid(); break; case LOCK_MODE_OFD: owner = resp->r_fpos; break; } for (pos = resp->r_start; pos < resp->r_start + resp->r_length; pos++) { if ((pos == 0) || (pos == (resp->r_start + resp->r_length - 1))) lock.l_start = pos; else if ((pos & 1) == 0) lock.l_start = pos - 1; else lock.l_start = pos + 1; lock.l_whence = SEEK_SET; lock.l_type = resp->r_lock_type; lock.l_len = 1; lock.l_pid = 0; rc = ceph_ll_setlk(cmount, filehandles[resp->r_fpos], &lock, owner, false); if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Unhop failed"); array_sprintf(badtoken, "%s %ld", str_lock_type(resp->r_lock_type), lock.l_start); break; } else resp->r_status = STATUS_GRANTED; } if (resp->r_status != STATUS_GRANTED) { lock.l_whence = SEEK_SET; lock.l_type = F_UNLCK; lock.l_start = resp->r_start; lock.l_len = resp->r_length; lock.l_pid = 0; rc = ceph_ll_setlk(cmount, filehandles[resp->r_fpos], &lock, owner, false); if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Unhop Unlock failed"); array_sprintf(badtoken, "%lld %lld", resp->r_start, resp->r_length); } } } void do_unlock(struct response *resp) { int rc; struct flock lock; uint64_t owner; if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } /* If this fpos has a blocking lock, cancel it. */ cancel_work(resp); switch (lock_mode[resp->r_fpos]) { case LOCK_MODE_POSIX: owner = getpid(); break; case LOCK_MODE_OFD: owner = resp->r_fpos; break; } lock.l_whence = SEEK_SET; lock.l_type = resp->r_lock_type; lock.l_start = resp->r_start; lock.l_len = resp->r_length; lock.l_pid = 0; rc = ceph_ll_setlk(cmount, filehandles[resp->r_fpos], &lock, owner, false); if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Unlock failed"); array_sprintf(badtoken, "%lld %lld", resp->r_start, resp->r_length); return; } resp->r_status = STATUS_GRANTED; } void do_test(struct response *resp) { int rc; struct flock lock; uint64_t owner; if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } switch (lock_mode[resp->r_fpos]) { case LOCK_MODE_POSIX: owner = getpid(); break; case LOCK_MODE_OFD: owner = resp->r_fpos; break; } lock.l_whence = SEEK_SET; lock.l_type = resp->r_lock_type; lock.l_start = resp->r_start; lock.l_len = resp->r_length; lock.l_pid = 0; fprintf(stdout, "TEST lock type %s\n", str_lock_type(lock.l_type)); rc = ceph_ll_getlk(cmount, filehandles[resp->r_fpos], &lock, owner); if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Test failed"); array_sprintf(badtoken, "%s %lld %lld", str_lock_type(lock.l_type), resp->r_start, resp->r_length); return; } if (lock.l_type == F_UNLCK) { fprintf(stdout, "GRANTED TEST lock type %s\n", str_lock_type(lock.l_type)); resp->r_status = STATUS_GRANTED; } else { resp->r_lock_type = lock.l_type; resp->r_pid = lock.l_pid; resp->r_start = lock.l_start; resp->r_length = lock.l_len; resp->r_status = STATUS_CONFLICT; } } void do_close(struct response *resp) { int rc; if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } rc = ceph_ll_close(cmount, filehandles[resp->r_fpos]); ceph_ll_put(cmount, inodes[resp->r_fpos]); filehandles[resp->r_fpos] = NULL; inodes[resp->r_fpos] = NULL; if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Close failed"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } resp->r_status = STATUS_OK; } struct test_list { struct test_list *tl_next; long long tl_start; long long tl_end; }; struct test_list *tl_head; struct test_list *tl_tail; void remove_test_list_head(void) { struct test_list *item = tl_head; if (item == NULL) fatal("Corruption in test list\n"); tl_head = item->tl_next; if (tl_tail == item) tl_tail = NULL; free(item); } void make_test_item(long long start, long long end) { struct test_list *item = malloc(sizeof(*item)); if (item == NULL) fatal("Could not allocate test list item\n"); item->tl_next = NULL; item->tl_start = start; item->tl_end = end; if (tl_head == NULL) tl_head = item; if (tl_tail != NULL) tl_tail->tl_next = item; tl_tail = item; } int list_locks(long long start, long long end, struct response *resp) { long long conf_end; struct flock lock; int rc; lock.l_whence = SEEK_SET; lock.l_type = F_WRLCK; lock.l_start = start; lock.l_pid = 0; if (end == INT64_MAX) lock.l_len = 0; else lock.l_len = end - start; rc = ceph_ll_getlk(cmount, filehandles[resp->r_fpos], &lock, 0); if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Test failed"); array_sprintf(badtoken, "%s %lld %lld", str_lock_type(lock.l_type), resp->r_start, resp->r_length); respond(resp); return false; } /* Our test succeeded */ if (lock.l_type == F_UNLCK) return false; resp->r_status = STATUS_CONFLICT; resp->r_lock_type = lock.l_type; resp->r_pid = lock.l_pid; resp->r_start = lock.l_start; resp->r_length = lock.l_len; respond(resp); conf_end = lock_end(resp); if (lock.l_start > start) make_test_item(start, lock.l_start); if (conf_end < end) make_test_item(conf_end, end); return true; } void do_list(struct response *resp) { long long start = resp->r_start; long long length = resp->r_length; int conflict = false; if (resp->r_fpos != 0 && filehandles[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } resp->r_lock_type = F_WRLCK; make_test_item(start, lock_end(resp)); while (tl_head != NULL) { conflict |= list_locks(tl_head->tl_start, tl_head->tl_end, resp); remove_test_list_head(); } if (conflict) resp->r_status = STATUS_DENIED; else resp->r_status = STATUS_AVAILABLE; resp->r_lock_type = F_WRLCK; resp->r_start = start; resp->r_length = length; } struct work_item *get_work(enum thread_type thread_type) { struct work_item *work, *poll; while (true) { /* Check poll list first */ poll = glist_first_entry(&poll_queue, struct work_item, queue); work = poll; /* If we didn't find work on the poll list or this is a polling * thread and the work on the poll list isn't due for polling * yet, then look for work on the work list. */ if (work == NULL || (thread_type == THREAD_POLL && work->next_poll > time(NULL))) { /* Check work list now */ work = glist_first_entry(&work_queue, struct work_item, queue); } /* Assign the work to ourself and remove from the queue and * return to the caller. */ if (work != NULL) { work->work_owner = thread_type; work->work_thread = pthread_self(); glist_del(&work->queue); return work; } /* No work, decide what kind of wait to do. */ if (thread_type == THREAD_POLL && poll != NULL) { /* Since there is polling work to do, determine next * time to poll and wait that long for signal. */ struct timespec twait = { poll->next_poll - time(NULL), 0 }; pthread_cond_timedwait(&work_cond, &work_mutex, &twait); } else { /* Wait for signal */ pthread_cond_wait(&work_cond, &work_mutex); } } } void *worker(void *t_type) { struct work_item *work = NULL; bool complete, cancelled = false; enum thread_type thread_type = *((enum thread_type *)t_type); pthread_mutex_lock(&work_mutex); while (true) { /* Look for work */ work = get_work(thread_type); pthread_mutex_unlock(&work_mutex); assert(work != NULL); /* Do the work */ switch (work->resp.r_cmd) { case CMD_LOCKW: complete = do_lock(&work->resp, thread_type); break; default: work->resp.r_status = STATUS_ERRNO; work->resp.r_errno = EINVAL; complete = true; break; } if (complete) respond(&work->resp); pthread_mutex_lock(&work_mutex); if (complete) { /* Remember if the main thread was trying to cancel * the work. */ cancelled = work->work_owner == THREAD_CANCEL; /* Indicate this work is complete */ work->work_owner = THREAD_NONE; work->work_thread = 0; if (cancelled) { /* Signal that work has been canceled or * completed. */ pthread_cond_broadcast(&work_cond); } else { /* The work is done, and may be freed, except * that if the work was being cancelled by * cancel_work, then the main thread is waiting * for this thread to be done with the work * item, and thus we can not free it, * cancel_work_item will be responsible to free * the work item. This assures that the request * that caused the cancellation is blocked until * the cancellation has completed. */ free_work(work); } } else { /* This can ONLY happen for a polling thread. * Put this work back in the queue, with a new * next polling time. */ work->work_owner = THREAD_NONE; work->work_thread = 0; work->next_poll = time(NULL) + POLL_DELAY; glist_add_tail(&poll_queue, &work->queue); /* And let worker threads know there may be * more work to do. */ pthread_cond_broadcast(&work_cond); } } } int main(int argc, char **argv) { int opt; int len, rc, i; struct sigaction sigact; char *rest; int oflags = 0; int no_tag; /* Init the lists of work for each fno */ for (i = 0; i <= MAXFPOS; i++) glist_init(fno_work + i); /* Start the worker and polling threads */ for (i = 0; i <= NUM_WORKER; i++) { rc = pthread_create(&threads[i], NULL, worker, i == 0 ? &a_poller : &a_worker); if (rc == -1) { fprintf(stderr, "pthread_create failed %s\n", strerror(errno)); exit(1); } } /* Initialize the signal handling */ memset(&sigact, 0, sizeof(sigact)); sigact.sa_handler = sighandler; if (sigaction(SIGALRM, &sigact, NULL) == -1) return 1; if (sigaction(SIGPIPE, &sigact, NULL) == -1) return 1; if (sigaction(SIGIO, &sigact, NULL) == -1) return 1; input = stdin; output = stdout; /* now parsing options with getopt */ while ((opt = getopt(argc, argv, options)) != EOF) { switch (opt) { case 'c': array_strcpy(ceph_path, optarg); break; case 'q': quiet = true; break; case 'd': duperrors = true; break; case 's': if (oflags > 7) show_usage(1, "Can not combine -x and -s\n"); oflags |= 1; script = true; array_strcpy(server, optarg); break; case 'x': if ((oflags & 7) != 0) show_usage(1, "Can not combine -x and -s/-p/-n\n"); oflags |= 8; script = true; input = fopen(optarg, "r"); if (input == NULL) fatal("Could not open %s\n", optarg); break; case 'n': if (oflags > 7) show_usage(1, "Can not combine -x and -n\n"); oflags |= 2; array_strcpy(name, optarg); break; case 'p': if (oflags > 7) show_usage(1, "Can not combine -x and -p\n"); oflags |= 4; array_strcpy(portstr, optarg); port = atoi(optarg); break; case 'C': array_strcpy(ceph_config, optarg); break; case '?': case 'h': default: /* display the help */ show_usage(0, "Help\n"); } } if (ceph_config[0] == '\0') show_usage(1, "Must specifiy -C ceph-config\n"); if (oflags > 0 && oflags < 7) show_usage(1, "Must specify -s, -p, and -n together\n"); if (oflags == 7) openserver(); rc = ceph_create(&cmount, NULL); if (rc != 0) fatal("Unable to create ceph mount context\n"); rc = ceph_conf_read_file(cmount, ceph_config); if (rc != 0) fatal("Unable to read ceph config\n"); rc = ceph_mount(cmount, NULL); if (rc != 0) { fprintf_stderr("Unable to mount Ceph cluster %s(%d)\n", strerror(-rc), rc); fatal("Unable to mount Ceph cluster"); } cephperms = ceph_mount_perms(cmount); while (1) { len = readln(input, line, sizeof(line)); if (len < 0) { if (script) fatal("End of file on input\n"); else break; } else { struct response resp; bool complete = true; lno++; memset(&resp, 0, sizeof(resp)); rest = SkipWhite(line, REQUIRES_MORE, "Invalid line"); /* Skip totally blank line */ if (rest == NULL) continue; if (script && !quiet) fprintf(stdout, "%s\n", rest); /* If line doesn't start with a tag, that's ok */ no_tag = (!isdigit(*rest) && (*rest != '$') && (*rest != '-')); /* Parse request into response structure */ rest = parse_request(rest, &resp, no_tag); if (rest == NULL) { resp.r_status = STATUS_ERRNO; resp.r_errno = errno; } else { /* Make sure default status is ok */ resp.r_status = STATUS_OK; if (*rest != '\0' && *rest != '#') fprintf_stderr( "Command line not consumed, rest=\"%s\"\n", rest); switch (resp.r_cmd) { case CMD_OPEN: do_open(&resp); break; case CMD_CLOSE: do_close(&resp); break; case CMD_LOCKW: complete = do_lock(&resp, THREAD_MAIN); break; case CMD_LOCK: complete = do_lock(&resp, THREAD_MAIN); break; case CMD_UNLOCK: do_unlock(&resp); break; case CMD_TEST: do_test(&resp); break; case CMD_LIST: do_list(&resp); break; case CMD_HOP: do_hop(&resp); break; case CMD_UNHOP: do_unhop(&resp); break; case CMD_SEEK: do_seek(&resp); break; case CMD_READ: do_read(&resp); break; case CMD_WRITE: do_write(&resp); break; case CMD_ALARM: do_alarm(&resp); break; case CMD_FORK: complete = do_fork(&resp, oflags == 7); break; case CMD_HELLO: case CMD_COMMENT: case CMD_QUIT: resp.r_status = STATUS_OK; break; case NUM_COMMANDS: fprintf_stderr("Invalid command %s\n", line); continue; } } if (complete) respond(&resp); if (resp.r_cmd == CMD_QUIT) exit(0); } } return 0; } ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/ml_console.c����������������������������������������������������0000664�0000000�0000000�00000066662�14737566223�0022045�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2012 * Contributor: Frank Filz <ffilzlnx@mindspring.com> * * * This software is a server that implements the NFS protocol. * * * This program 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 3 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 * 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 * * */ #include "multilock.h" #include "assert.h" /* command line syntax */ char options[] = "ekdqfsp:h?x:"; char usage[] = "Usage: ml_console [-p port] [-s] [-f] [-q] [-x script] [-d]\n" "\n" " -p port - specify the port to listen to clients on\n" " -s - specify strict mode (clients are not polled without EXPECT)\n" " -f - specify errors are fatal mode\n" " -q - speficy quiet mode\n" " -d - speficy dup errors mode (errors are sent to stdout and stderr)\n" " -x script - specify script to run\n" " -k - syntax check only\n" " -e - non-fatal errors, full accounting of errors to stderr, everything\n" " to stdout\n"; int port, listensock; struct sockaddr_in addr; int maxfd; struct response *expected_responses; fd_set sockets; int num_errors; bool terminate; bool err_accounting; sigset_t full_signal_set; sigset_t original_signal_set; void open_socket(void) { const int reuse = 1; int rc; rc = socket(AF_INET, SOCK_STREAM, 0); if (rc == -1) fatal("socket failed with ERRNO %d \"%s\"\n", errno, strerror(errno)); listensock = rc; rc = setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); if (rc != 0) { fatal("failed to set SO_REUSEADDR with ERRNO %d \"%s\"\n", errno, strerror(errno)); } addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = INADDR_ANY; rc = bind(listensock, (struct sockaddr *)&addr, sizeof(addr)); if (rc == -1) fatal("bind failed with ERRNO %d \"%s\"\n", errno, strerror(errno)); FD_ZERO(&sockets); FD_SET(listensock, &sockets); maxfd = listensock; rc = listen(listensock, 10); if (rc == -1) fatal("listen failed with ERRNO %d \"%s\"\n", errno, strerror(errno)); } void do_accept(void) { struct client *client = malloc(sizeof(*client)); socklen_t len; int rc; if (client == NULL) fatal("Accept malloc failed\n"); memset(client, 0, sizeof(*client)); len = sizeof(client->c_addr); client->c_socket = accept(listensock, &client->c_addr, &len); if (client->c_socket == -1) fatal("Accept failed ERRNO %d \"%s\"\n", errno, strerror(errno)); FD_SET(client->c_socket, &sockets); array_sprintf(client->c_name, "<UNKNOWN_%d>", client->c_socket); if (client->c_socket > maxfd) maxfd = client->c_socket; if (!quiet) fprintf(output, "Accept for socket %d\n", client->c_socket); client->c_input = fdopen(client->c_socket, "r"); if (client->c_input == NULL) fatal("Accept fdopen for input failed ERRNO %d \"%s\"\n", errno, strerror(errno)); rc = setvbuf(client->c_input, NULL, _IONBF, 0); if (rc != 0) fatal("Accept setvbuf for input failed ERRNO %d \"%s\"\n", errno, strerror(errno)); client->c_output = fdopen(client->c_socket, "w"); if (client->c_output == NULL) fatal("Accept fdopen for output failed ERRNO %d \"%s\"\n", errno, strerror(errno)); rc = setvbuf(client->c_output, NULL, _IONBF, 0); if (rc != 0) fatal("Accept setvbuf for output failed ERRNO %d \"%s\"\n", errno, strerror(errno)); client->c_refcount++; client->c_next = client_list; if (client_list != NULL) client_list->c_prev = client; client_list = client; } void close_client(struct client *client) { close(client->c_socket); if (!quiet) fprintf(output, "Closed client socket %d\n", client->c_socket); FD_CLR(client->c_socket, &sockets); client->c_socket = 0; client->c_refcount--; } struct client *find_client_by_fd(int socket) { struct client *client = client_list; while (client != NULL && client->c_socket != socket) client = client->c_next; return client; } struct client *find_client_by_name(const char *name) { struct client *client = client_list; while (client != NULL && strcasecmp(client->c_name, name) != 0) client = client->c_next; return client; } int receive(bool watchin, long timeout_secs) { fd_set readfds, exceptfds; struct timespec timeout; int rc, i; int timeend = 0; if (timeout_secs > 0) timeend = time(NULL) + timeout_secs; while (1) { if (timeout_secs > 0) { timeout.tv_nsec = 0; timeout.tv_sec = timeend - time(NULL); if (timeout.tv_sec <= 0) return -2; } else if (timeout_secs == 0) { timeout.tv_nsec = 0; timeout.tv_sec = 0; } readfds = sockets; if (watchin) FD_SET(0, &readfds); exceptfds = sockets; if (watchin) FD_SET(0, &exceptfds); if (watchin && !script) { fprintf(output, "> "); fflush(output); } if (!watchin && !quiet) { fprintf(output, "Waiting for clients\n"); fflush(output); } if (timeout_secs >= 0) { fprintf(output, "About to sleep for %d secs\n", (int)timeout.tv_sec); rc = pselect(maxfd + 1, &readfds, NULL, &exceptfds, &timeout, &original_signal_set); } else rc = pselect(maxfd + 1, &readfds, NULL, &exceptfds, NULL, &original_signal_set); if (rc == -1) { if (watchin && !script) { fprintf(output, "\n"); fflush(output); } if (errno == EINTR && !terminate) { if (timeout_secs >= 0) return -2; fprintf_stderr("select timed out\n"); return -1; } else if (errno == EINTR) { fprintf_stderr("select terminated by signal\n"); return -3; } else { fprintf_stderr( "select failed with ERRNO %d \"%s\"\n", errno, strerror(errno)); return -1; } } for (i = 0; i <= maxfd; i++) { if (FD_ISSET(i, &readfds)) { if (watchin && !quiet && i != 0) { fprintf(output, "\n"); fflush(output); } if (i == listensock) do_accept(); else return i; } if (FD_ISSET(i, &exceptfds)) { fprintf_stderr( "select received exception for socket %d\n", i); } } } } void error(void) { int len = strlen(errdetail); num_errors++; if (errdetail[len - 1] == '\n') errdetail[len - 1] = '\0'; if (errno == 0) fprintf_stderr("%s\n", errdetail); else fprintf_stderr("ERRNO %d \"%s\" \"%s\" bad token \"%s\"\n", errno, strerror(errno), errdetail, badtoken); } struct response *alloc_resp(struct client *client) { struct response *resp = malloc(sizeof(*resp)); if (resp == NULL) fatal("Could not allocate response\n"); memset(resp, 0, sizeof(*resp)); resp->r_client = client; if (client != NULL) client->c_refcount++; return resp; } struct response *process_client_response(struct client *client) { int len; struct response *client_resp; char *rest; char line[MAXXFER]; client_resp = alloc_resp(client); len = readln(client->c_input, line, sizeof(line)); if (len >= 0) { array_sprintf(client_resp->r_original, "%s %s", client->c_name, line); fprintf(output, "%s\n", client_resp->r_original); rest = parse_response(line, client_resp); if (rest == NULL) return client_resp; if (client_resp->r_cmd == CMD_HELLO) { assert(client_resp->r_length < sizeof(client->c_name)); array_strcpy(client->c_name, client_resp->r_data); } } else { fprintf(output, "%s -2 QUIT OK # socket closed\n", client->c_name); close_client(client); client_resp->r_cmd = CMD_QUIT; client_resp->r_tag = -2; client_resp->r_status = STATUS_OK; } return client_resp; } static void console_command(void); struct response *receive_response(bool watchin, long timeout_secs) { int fd; struct client *client; fd = receive(watchin, timeout_secs); if (fd == -2 && timeout_secs >= 0) { /* Expected timeout */ return NULL; } else if (fd < 0) { struct response *resp = alloc_resp(NULL); if (fd == -3) { /* signal interrupted select */ fprintf_stderr("Receive interrupted - exiting...\n"); resp->r_tag = -1; resp->r_cmd = CMD_QUIT; resp->r_status = STATUS_CANCELED; array_strcpy(resp->r_original, "-1 QUIT CANCELED"); errno = 0; array_strcpy(errdetail, "Receive interrupted - exiting..."); } else { /* some other error occurred */ fprintf_stderr("Receive failed ERRNO %d \"%s\"\n", errno, strerror(errno)); resp->r_cmd = CMD_QUIT; resp->r_errno = errno; resp->r_tag = -1; array_strcpy(resp->r_data, "Receive failed"); array_sprintf( resp->r_original, "-1 QUIT ERRNO %d \"%s\" \"Receive failed\"", errno, strerror(errno)); array_strcpy(errdetail, "Receive failed"); array_strcpy(badtoken, ""); } return resp; } else if (watchin && fd == 0) { return NULL; } else { client = find_client_by_fd(fd); if (client == NULL) fatal("Could not find client for socket %d\n", fd); return process_client_response(client); } } enum console_cmd { CCMD_QUIT, CCMD_STRICT, CCMD_CLIENT_CMD, CCMD_EXPECT, CCMD_FATAL, CCMD_SLEEP, CCMD_OPEN_BRACE, CCMD_CLOSE_BRACE, CCMD_SIMPLE_OK, CCMD_SIMPLE_AVAILABLE, CCMD_SIMPLE_GRANTED, CCMD_SIMPLE_DENIED, CCMD_SIMPLE_DEADLOCK, CCMD_CLIENTS, CCMD_FORK, }; struct token console_commands[] = { { "QUIT", 4, CCMD_QUIT }, { "STRICT", 6, CCMD_STRICT }, { "EXPECT", 6, CCMD_EXPECT }, { "FATAL", 5, CCMD_FATAL }, { "SLEEP", 5, CCMD_SLEEP }, { "{", 1, CCMD_OPEN_BRACE }, { "}", 1, CCMD_CLOSE_BRACE }, { "OK", 2, CCMD_SIMPLE_OK }, { "AVAILABLE", 9, CCMD_SIMPLE_AVAILABLE }, { "GRANTED", 7, CCMD_SIMPLE_GRANTED }, { "DENIED", 6, CCMD_SIMPLE_DENIED }, { "DEADLOCK", 8, CCMD_SIMPLE_DEADLOCK }, { "CLIENTS", 7, CCMD_CLIENTS }, { "FORK", 4, CCMD_FORK }, { "", 0, CCMD_CLIENT_CMD } }; static void handle_quit(void); /* * wait_for_expected_responses * * Wait for a list of expected responses (in expected_responses). If any * unexpected response and this is not being called from handle_quit() force * fatal error. */ void wait_for_expected_responses(const char *label, int count, const char *last, bool could_quit) { struct response *expect_resp; struct response *client_resp; bool fatal = false; fprintf(output, "Waiting for %d %s...\n", count, label); while (expected_responses != NULL && (client_list != NULL || could_quit)) { client_resp = receive_response(false, -1); if (terminate && could_quit) { free_response(client_resp, NULL); break; } expect_resp = check_expected_responses(expected_responses, client_resp); if (expect_resp != NULL) { fprintf(output, "Matched %s\n", expect_resp->r_original); free_response(expect_resp, &expected_responses); free_response(client_resp, NULL); } else if (client_resp->r_cmd != CMD_QUIT) { errno = 0; if (err_accounting) fprintf(stderr, "%s\nResp: %s\n", last, client_resp->r_original); free_response(client_resp, NULL); array_strcpy(errdetail, "Unexpected response"); error(); /* If not called from handle_quit() dump list of * expected responses and quit if in error_is_fatal or * in a script. */ if (could_quit) { /* Error must be fatal if script since script * can't recover */ if (error_is_fatal || script) fatal = true; break; } } } /* Abandon any remaining responses */ while (expected_responses != NULL) { fprintf_stderr("Abandoning %s\n", expected_responses->r_original); free_response(expected_responses, &expected_responses); } if (fatal || terminate) handle_quit(); } void handle_quit(void) { struct response *expect_resp; struct client *client; int count = 0; char out[MAXSTR]; if (client_list != NULL) { for (client = client_list; client != NULL; client = client->c_next) { if (client->c_socket == 0) continue; array_sprintf(out, "%ld QUIT\n", ++global_tag); fputs(out, client->c_output); fflush(client->c_output); /* Build an EXPECT for -1 QUIT for this client */ expect_resp = alloc_resp(client); expect_resp->r_cmd = CMD_QUIT; expect_resp->r_status = STATUS_OK; expect_resp->r_tag = global_tag; array_sprintf(expect_resp->r_original, "EXPECT %s * QUIT OK", client->c_name); add_response(expect_resp, &expected_responses); count++; /* Build an EXPECT for -2 QUIT for this client */ expect_resp = alloc_resp(client); expect_resp->r_cmd = CMD_QUIT; expect_resp->r_status = STATUS_OK; expect_resp->r_tag = -2; array_sprintf(expect_resp->r_original, "EXPECT %s -2 QUIT OK", client->c_name); add_response(expect_resp, &expected_responses); count++; } wait_for_expected_responses("client_list", count, "QUIT", false); fprintf(output, "All clients exited\n"); } if (num_errors > 0) { fprintf_stderr("%d errors\n", num_errors); fprintf_stderr("FAIL\n"); } else { fprintf_stderr("SUCCESS\n"); } exit(num_errors > 0); } bool expect_one_response(struct response *expect_resp, const char *last) { struct response *client_resp; bool result; client_resp = receive_response(false, -1); if (terminate) result = true; else result = !compare_responses(expect_resp, client_resp); if (result) { if (err_accounting) fprintf(stderr, "%s\n%s\nResp: %s\n", last, expect_resp->r_original, client_resp->r_original); } else fprintf(output, "Matched\n"); free_response(expect_resp, NULL); free_response(client_resp, NULL); return result; } struct console_state { char *rest; char line[MAXXFER]; char out[MAXXFER]; char last[MAXXFER]; /* last command sent */ struct client *client; int len; int cmd; struct response *expect_resp; struct response *client_cmd; bool inbrace; int count; }; void ccmd_client_cmd(struct console_state *cs) { cs->rest = get_client(cs->line, &cs->client, syntax, REQUIRES_MORE); if (cs->rest == NULL) return; if (script) array_sprintf(cs->last, "Line %4ld: %s", lno, cs->line); else array_strcpy(cs->last, cs->line); cs->client_cmd = alloc_resp(cs->client); cs->rest = parse_request(cs->rest, cs->client_cmd, false); if (cs->rest != NULL && !syntax) send_cmd(cs->client_cmd); free_response(cs->client_cmd, NULL); } void ccmd_sleep(struct console_state *cs) { long secs; int t_end, t_now; cs->rest = get_long(cs->rest, &secs, true, "Invalid sleep time"); if (cs->rest == NULL) return; if (syntax) return; t_now = time(NULL); t_end = t_now + secs; while (t_now <= t_end && !terminate) { struct response *client_resp; client_resp = receive_response(false, t_end - t_now); t_now = time(NULL); if (client_resp != NULL) { errno = 0; if (err_accounting) fprintf(stderr, "%s\n%s\n", cs->last, client_resp->r_original); array_strcpy(errdetail, "Unexpected response"); cs->rest = NULL; free_response(client_resp, NULL); } /* If sleep 0 or we have run out, just want single iteration */ if (t_now == t_end) break; } } void ccmd_open_brace(struct console_state *cs) { if (cs->inbrace) { errno = 0; array_strcpy(errdetail, "Illegal nested brace"); cs->rest = NULL; } cs->count = 0; cs->inbrace = true; } void ccmd_close_brace(struct console_state *cs) { if (!cs->inbrace) { errno = 0; array_strcpy(errdetail, "Unmatched close brace"); cs->rest = NULL; } else if (!syntax) { cs->inbrace = false; wait_for_expected_responses("responses", cs->count, cs->last, true); fprintf(output, "All responses received OK\n"); cs->count = 0; } else { cs->inbrace = false; } } void ccmd_clients(struct console_state *cs) { if (cs->inbrace) { errno = 0; array_strcpy(errdetail, "CLIENTS command not allowed inside brace"); cs->rest = NULL; return; } while (cs->rest != NULL && cs->rest[0] != '\0' && cs->rest[0] != '#') { /* Get the next client to expect */ cs->rest = get_client(cs->rest, &cs->client, true, REQUIRES_EITHER); if (cs->rest == NULL) return; /* Build an EXPECT client * HELLO OK "client" */ cs->expect_resp = alloc_resp(cs->client); cs->expect_resp->r_cmd = CMD_HELLO; cs->expect_resp->r_tag = -1; cs->expect_resp->r_status = STATUS_OK; array_strcpy(cs->expect_resp->r_data, cs->client->c_name); array_sprintf(cs->expect_resp->r_original, "EXPECT %s * HELLO OK \"%s\"", cs->client->c_name, cs->client->c_name); cs->count++; if (syntax) { free_response(cs->expect_resp, NULL); } else { /* Add response to list of expected responses */ add_response(cs->expect_resp, &expected_responses); } } if (cs->count == 0) { errno = 0; array_strcpy(errdetail, "Expected at least one client"); cs->rest = NULL; return; } if (!syntax) { wait_for_expected_responses("clients", cs->count, cs->last, true); fprintf(output, "All clients said HELLO OK\n"); } cs->count = 0; } void ccmd_fork(struct console_state *cs) { if (cs->inbrace) { errno = 0; array_strcpy(errdetail, "FORK command not allowed inside brace"); cs->rest = NULL; return; } /* Get the client to send FORK to */ cs->rest = get_client(cs->rest, &cs->client, syntax, REQUIRES_MORE); if (cs->rest == NULL) return; /* Build an EXPECT client * FORK OK "client" */ cs->client_cmd = alloc_resp(cs->client); cs->client_cmd->r_cmd = CMD_FORK; cs->client_cmd->r_tag = -1; cs->client_cmd->r_status = STATUS_OK; cs->count++; /* Get the client that will be created */ cs->rest = get_client(cs->rest, &cs->client, true, REQUIRES_NO_MORE); if (cs->rest == NULL) return; /* Build an EXPECT client * HELLO OK "client" */ cs->expect_resp = alloc_resp(cs->client); cs->expect_resp->r_cmd = CMD_HELLO; cs->expect_resp->r_tag = -1; cs->expect_resp->r_status = STATUS_OK; /* Use the created client's name as the FORK data */ array_strcpy(cs->client_cmd->r_data, cs->expect_resp->r_client->c_name); array_strcpy(cs->expect_resp->r_data, cs->expect_resp->r_client->c_name); array_sprintf(cs->client_cmd->r_original, "EXPECT %s * FORK OK \"%s\"", cs->client->c_name, cs->client->c_name); array_sprintf(cs->expect_resp->r_original, "EXPECT %s * HELLO OK \"%s\"", cs->client->c_name, cs->client->c_name); cs->count++; if (syntax) { free_response(cs->client_cmd, NULL); free_response(cs->expect_resp, NULL); } else { /* Send the command */ send_cmd(cs->client_cmd); /* Now fixup to expect client name as FORK OK data */ array_strcpy(cs->client_cmd->r_data, cs->client_cmd->r_client->c_name); /* Add responses to list of expected responses */ add_response(cs->client_cmd, &expected_responses); add_response(cs->expect_resp, &expected_responses); } if (!syntax) { wait_for_expected_responses("clients", cs->count, cs->last, true); fprintf(output, "All clients responded OK\n"); } cs->count = 0; } void ccmd_expect(struct console_state *cs) { cs->rest = get_client(cs->rest, &cs->client, true, REQUIRES_MORE); if (cs->rest == NULL) return; cs->expect_resp = alloc_resp(cs->client); if (script) { array_sprintf(cs->expect_resp->r_original, "Line %4ld: EXPECT %s %s", lno, cs->client->c_name, cs->rest); } else { array_sprintf(cs->expect_resp->r_original, "EXPECT %s %s", cs->client->c_name, cs->rest); } cs->rest = parse_response(cs->rest, cs->expect_resp); if (cs->rest == NULL || syntax) { free_response(cs->expect_resp, NULL); } else if (cs->inbrace) { add_response(cs->expect_resp, &expected_responses); cs->count++; } else if (expect_one_response(cs->expect_resp, cs->last)) cs->rest = NULL; } void ccmd_simple(struct console_state *cs) { array_strcpy(cs->last, cs->line); cs->rest = get_client(cs->rest, &cs->client, syntax, REQUIRES_MORE); if (cs->rest == NULL) return; cs->client_cmd = alloc_resp(cs->client); if (cs->cmd == CCMD_SIMPLE_OK) cs->client_cmd->r_status = STATUS_OK; else if (cs->cmd == CCMD_SIMPLE_AVAILABLE) cs->client_cmd->r_status = STATUS_AVAILABLE; else if (cs->cmd == CCMD_SIMPLE_GRANTED) cs->client_cmd->r_status = STATUS_GRANTED; else if (cs->cmd == CCMD_SIMPLE_DEADLOCK) cs->client_cmd->r_status = STATUS_DEADLOCK; else cs->client_cmd->r_status = STATUS_DENIED; cs->rest = parse_request(cs->rest, cs->client_cmd, true); if (cs->rest == NULL) { free_response(cs->client_cmd, NULL); return; } switch (cs->client_cmd->r_cmd) { case CMD_OPEN: case CMD_CLOSE: case CMD_SEEK: case CMD_WRITE: case CMD_COMMENT: case CMD_ALARM: case CMD_HELLO: case CMD_QUIT: if (cs->cmd != CCMD_SIMPLE_OK) { array_sprintf(errdetail, "Simple %s command expects OK", commands[cs->client_cmd->r_cmd].cmd_name); errno = 0; cs->rest = NULL; } break; case CMD_READ: if (cs->cmd != CCMD_SIMPLE_OK) { array_sprintf(errdetail, "Simple %s command expects OK", commands[cs->client_cmd->r_cmd].cmd_name); errno = 0; cs->rest = NULL; } else if (cs->client_cmd->r_length == 0 || cs->client_cmd->r_data[0] == '\0') { array_strcpy(errdetail, "Simple READ must have compare data"); errno = 0; cs->rest = NULL; } break; case CMD_LOCKW: if (cs->cmd != CCMD_SIMPLE_DEADLOCK) { array_sprintf(errdetail, "%s command can not be a simple command", commands[cs->client_cmd->r_cmd].cmd_name); errno = 0; cs->rest = NULL; } break; case CMD_LOCK: case CMD_HOP: if (cs->cmd != CCMD_SIMPLE_DENIED && cs->cmd != CCMD_SIMPLE_GRANTED) { array_sprintf( errdetail, "Simple %s command requires GRANTED or DENIED status", commands[cs->client_cmd->r_cmd].cmd_name); errno = 0; cs->rest = NULL; } break; case CMD_TEST: case CMD_LIST: if (cs->cmd != CCMD_SIMPLE_AVAILABLE) { array_sprintf( errdetail, "Simple %s command requires AVAILABLE status", commands[cs->client_cmd->r_cmd].cmd_name); errno = 0; cs->rest = NULL; } break; case CMD_UNLOCK: case CMD_UNHOP: if (cs->cmd != CCMD_SIMPLE_GRANTED) { array_sprintf( errdetail, "Simple %s command requires GRANTED status", commands[cs->client_cmd->r_cmd].cmd_name); errno = 0; cs->rest = NULL; } break; case CMD_FORK: array_strcpy(errdetail, "FORK not compatible with a simple command"); break; case NUM_COMMANDS: array_strcpy(errdetail, "Invalid command"); errno = 0; cs->rest = NULL; break; } if (cs->rest == NULL || syntax) { free_response(cs->client_cmd, NULL); return; } send_cmd(cs->client_cmd); /* We can't know what file descriptor will be returned */ cs->client_cmd->r_fno = -1; sprintf_resp(cs->out, sizeof(cs->out), "EXPECT", cs->client_cmd); fprintf(output, "%s", cs->out); if (expect_one_response(cs->client_cmd, cs->last)) cs->rest = NULL; } void console_command(void) { struct console_state cs = { .inbrace = false, .count = 0, }; cs.last[0] = '\0'; while (1) { cs.len = readln(input, cs.line, sizeof(cs.line)); lno++; if (cs.len < 0) { array_sprintf(cs.line, "QUIT"); cs.len = strlen(cs.line); if (!syntax) fprintf(output, "QUIT\n"); } cs.rest = SkipWhite(cs.line, REQUIRES_MORE, "Invalid line"); /* Skip totally blank line and comments */ if (cs.rest == NULL || cs.rest[0] == '#') continue; if (script && !syntax) fprintf(output, "Line %4ld: %s\n", lno, cs.line); cs.rest = get_token_value(cs.rest, &cs.cmd, console_commands, true, REQUIRES_EITHER, "Invalid console command"); if (cs.rest != NULL) switch ((enum console_cmd)cs.cmd) { case CCMD_QUIT: if (syntax) return; else handle_quit(); break; case CCMD_STRICT: cs.rest = get_on_off(cs.rest, &strict); break; case CCMD_FATAL: cs.rest = get_on_off(cs.rest, &error_is_fatal); break; case CCMD_CLIENT_CMD: ccmd_client_cmd(&cs); break; case CCMD_SLEEP: ccmd_sleep(&cs); break; case CCMD_OPEN_BRACE: ccmd_open_brace(&cs); break; case CCMD_CLOSE_BRACE: ccmd_close_brace(&cs); break; case CCMD_CLIENTS: ccmd_clients(&cs); break; case CCMD_EXPECT: ccmd_expect(&cs); break; case CCMD_FORK: ccmd_fork(&cs); break; case CCMD_SIMPLE_OK: case CCMD_SIMPLE_AVAILABLE: case CCMD_SIMPLE_GRANTED: case CCMD_SIMPLE_DENIED: case CCMD_SIMPLE_DEADLOCK: ccmd_simple(&cs); break; } if (cs.rest == NULL) { error(); if (syntax) fprintf(output, "Line %4ld: %s\n", lno, cs.line); if ((error_is_fatal && !syntax) || terminate) handle_quit(); } if (!strict && !cs.inbrace && !script) break; if (!script) { fprintf(output, "> "); fflush(output); } } } void sighandler(int sig) { switch (sig) { case SIGINT: case SIGTERM: case SIGUSR1: terminate = true; break; case SIGPIPE: terminate = true; break; } } int main(int argc, char **argv) { int opt; struct response *resp; struct sigaction sigact; bool syntax_only = false; int rc; input = stdin; output = stdout; memset(&sigact, 0, sizeof(sigact)); sigact.sa_handler = sighandler; rc = sigaction(SIGINT, &sigact, NULL); if (rc == -1) fatal("sigaction(SIGINT, &sigact, NULL) returned -1 errno %d \"%s\"\n", errno, strerror(errno)); rc = sigaction(SIGTERM, &sigact, NULL); if (rc == -1) fatal("sigaction(SIGTERM, &sigact, NULL) returned -1 errno %d \"%s\"\n", errno, strerror(errno)); rc = sigaction(SIGUSR1, &sigact, NULL); if (rc == -1) fatal("sigaction(SIGUSR1, &sigact, NULL) returned -1 errno %d \"%s\"\n", errno, strerror(errno)); rc = sigaction(SIGPIPE, &sigact, NULL); if (rc == -1) fatal("sigaction(SIGPIPE, &sigact, NULL) returned -1 errno %d \"%s\"\n", errno, strerror(errno)); rc = sigfillset(&full_signal_set); if (rc == -1) fatal("sigfillset(&full_signal_set) returned -1 errno %d \"%s\"\n", errno, strerror(errno)); sigprocmask(SIG_SETMASK, &full_signal_set, &original_signal_set); /* now parsing options with getopt */ while ((opt = getopt(argc, argv, options)) != EOF) { switch (opt) { case 'e': duperrors = true; err_accounting = true; break; case 'd': duperrors = true; break; case 'p': port = atoi(optarg); break; case 'q': quiet = true; break; case 's': strict = true; break; case 'x': input = fopen(optarg, "r"); if (input == NULL) fatal("Could not open %s\n", optarg); script = true; break; case 'k': syntax_only = true; break; case 'f': error_is_fatal = true; break; case '?': case 'h': default: /* display the help */ fprintf(stderr, "%s", usage); fflush(stderr); exit(0); break; } } open_socket(); if (script) { syntax = true; console_command(); if (num_errors != 0) { fprintf(stdout, "Syntax checks fail\n"); return 1; } if (syntax_only) { fprintf(stdout, "Syntax checks ok\n"); return 0; } syntax = false; global_tag = lno; lno = 0; rewind(input); console_command(); /* Never returns */ } while (1) { resp = receive_response(true, -1); if (strict && resp != NULL) { errno = 0; array_strcpy(errdetail, "Unexpected response"); free_response(resp, NULL); error(); if (error_is_fatal) handle_quit(); } free_response(resp, NULL); console_command(); } /* Never get here */ return 0; } ������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/ml_functions.c��������������������������������������������������0000664�0000000�0000000�00000107277�14737566223�0022411�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2012 * Contributor: Frank Filz <ffilzlnx@mindspring.com> * * * This software is a server that implements the NFS protocol. * * * This program 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 3 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 * 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 * * */ #include "multilock.h" #include "assert.h" struct command_def commands[NUM_COMMANDS + 1] = { { "OPEN", 4 }, { "CLOSE", 5 }, { "LOCKW", 5 }, { "LOCK", 4 }, { "UNLOCK", 6 }, { "TEST", 4 }, { "LIST", 4 }, { "HOP", 3 }, { "UNHOP", 5 }, { "SEEK", 4 }, { "READ", 4 }, { "WRITE", 5 }, { "COMMENT", 7 }, { "ALARM", 5 }, { "HELLO", 5 }, { "FORK", 4 }, { "QUIT", 4 }, { "UNKNOWN", 0 }, }; char errdetail[MAXSTR * 2 + 1]; char badtoken[MAXSTR + 1]; struct client *client_list; FILE *input; FILE *output; bool script; bool quiet; bool duperrors; bool strict; bool error_is_fatal; long global_tag; long saved_tags[26]; bool syntax; long lno; long get_global_tag(bool increment) { if (script && increment) global_tag = lno; else if (increment) global_tag++; return global_tag; } struct token on_off[] = { { "on", 2, true }, { "off", 3, false }, { "", 0, true } }; struct token lock_types[] = { { "read", 4, F_RDLCK }, { "write", 5, F_WRLCK }, { "shared", 6, F_RDLCK }, { "exclusive", 9, F_WRLCK }, { "F_RDLCK", 7, F_RDLCK }, { "F_WRLCK", 7, F_WRLCK }, { "unlock", 6, F_UNLCK }, { "F_UNLCK", 7, F_UNLCK }, { "*", 1, -1 }, { "", 0, 0 } }; struct token read_write_flags[] = { { "rw", 2, O_RDWR }, { "ro", 2, O_RDONLY }, { "wo", 2, O_WRONLY }, { "O_RDWR", 6, O_RDWR }, { "O_RDONLY", 8, O_RDONLY }, { "O_WRONLY", 8, O_WRONLY }, { "", 0, 0 } }; const char *str_read_write_flags(int flags) { switch (flags & O_ACCMODE) { case O_RDWR: return "rw"; case O_RDONLY: return "ro"; case O_WRONLY: return "wo"; } return "unknown"; } struct token open_flags[] = { { "create", 6, O_CREAT }, { "creat", 5, O_CREAT }, { "O_CREAT", 7, O_CREAT }, { "exclusive", 9, O_EXCL }, { "excl", 4, O_EXCL }, { "O_EXCL", 6, O_EXCL }, { "truncate", 8, O_TRUNC }, { "trunc", 5, O_TRUNC }, { "O_TRUNC", 7, O_TRUNC }, { "", 0, 0 } }; void sprintf_open_flags(char **rest, int *left, int flags) { int i; int ex_flags = 0; for (i = 0; open_flags[i].t_len != 0; i++) { if ((ex_flags & open_flags[i].t_value) == 0 && (flags & open_flags[i].t_value) == open_flags[i].t_value) { /* Append open flag to line */ sprint_left(*rest, *left, " %s", open_flags[i].t_name); } ex_flags |= open_flags[i].t_value; } } struct token lock_modes[] = { { "POSIX", 5, LOCK_MODE_POSIX }, { "OFD", 3, LOCK_MODE_OFD }, { "", 0, 0 } }; void sprintf_lock_modes(char **rest, int *left, int mode) { int i; for (i = 0; lock_modes[i].t_len != 0; i++) { if (mode == lock_modes[i].t_value) { /* Append lock mode to line */ sprint_left(*rest, *left, " %s", lock_modes[i].t_name); break; } } } const char *str_lock_mode(int lock_mode) { switch ((enum lock_mode)lock_mode) { case LOCK_MODE_POSIX: return "POSIX"; case LOCK_MODE_OFD: return "OFD"; } return "unknown"; } int readln(FILE *in, char *buf, int buflen) { int len; if (fgets(buf, buflen, in) != NULL) { len = strlen(buf); if (buf[len - 1] == '\n') buf[--len] = '\0'; return len; } else { return -1; } } char *SkipWhite(char *line, enum requires_more requires_more, const char *who) { char *c = line; /* Skip white space */ while (*c == ' ' || *c == '\t') c++; switch (requires_more) { case REQUIRES_MORE: if (*c == '\0' || *c == '#') { array_sprintf(errdetail, "Expected more characters on command %s", who); if (*c == '\0') array_strcpy(badtoken, "<NULL>"); else array_strcpy(badtoken, c); errno = EINVAL; return NULL; } break; case REQUIRES_NO_MORE: if (*c != '\0' && *c != '#') { array_sprintf(errdetail, "Extra characters on command %s", who); array_strcpy(badtoken, c); errno = EINVAL; return NULL; } break; case REQUIRES_EITHER: break; } /* if(*c == '#') c += strlen(c); */ return c; } char *get_token(char *line, char **token, int *len, bool optional, enum requires_more requires_more, const char *invalid) { char *c = line; if (optional) c = SkipWhite(c, REQUIRES_EITHER, invalid); else c = SkipWhite(c, REQUIRES_MORE, invalid); if (c == NULL) return c; if (optional && (*c == '\0' || *c == '#')) { *token = NULL; return c; } /* Skip token */ *token = c; while (*c != ' ' && *c != '\0' && *c != '#') c++; *len = c - *token; return c; } char *get_token_value(char *line, int *value, struct token *tokens, bool optional, enum requires_more requires_more, const char *invalid) { char *t; int len; char *c = get_token(line, &t, &len, optional, requires_more, invalid); struct token *tok; if (c == NULL) return c; if (optional && t == NULL) { tok = tokens; while (tok->t_len != 0) tok++; *value = tok->t_value; return c; } for (tok = tokens; tok->t_len != 0; tok++) { if (tok->t_len != len) continue; if (strncasecmp(t, tok->t_name, len) == 0) { *value = tok->t_value; return SkipWhite(c, requires_more, invalid); } } if (optional) { /* optional token not found, rewind to before token and * use the default value */ *value = tok->t_value; return t; } array_strcpy(errdetail, invalid); array_strncpy(badtoken, t, len); errno = EINVAL; return NULL; } char *get_optional_token(char *line, bool *found, const char *token, enum requires_more requires_more, const char *invalid) { char *t; int len; char *c = get_token(line, &t, &len, true, requires_more, invalid); *found = false; if (c == NULL || t == NULL) return c; if (strncasecmp(t, token, strlen(token)) == 0) { *found = true; return SkipWhite(c, requires_more, invalid); } /* optional token not found, rewind to before token */ return t; } char *get_client(char *line, struct client **pclient, bool create, enum requires_more requires_more) { char *c = line; char *t; struct client *client; int len; c = get_token(line, &t, &len, false, requires_more, "Invalid client"); if (c == NULL) return c; for (client = client_list; client != NULL; client = client->c_next) { if (strlen(client->c_name) != len) continue; if (strncmp(t, client->c_name, len) == 0) break; } *pclient = client; if (client == NULL) { if (create) { client = calloc(1, sizeof(*client)); if (client == NULL) { array_strcpy(errdetail, "Could not create client"); errno = ENOMEM; array_strncpy(badtoken, t, len); return NULL; } array_strncpy(client->c_name, t, len); *pclient = client; c = SkipWhite(c, requires_more, "get_client"); if (c == NULL) free(client); else if (!quiet && !syntax) fprintf(output, "Created temp client %s\n", client->c_name); return c; } else { array_strcpy(errdetail, "Could not find client"); errno = ENOENT; array_strncpy(badtoken, t, len); return NULL; } } return SkipWhite(c, requires_more, "get_client"); } char *get_command(char *line, enum commands *cmd) { enum commands i; char *t; char *c; int len; *cmd = NUM_COMMANDS; c = get_token(line, &t, &len, false, REQUIRES_EITHER, "Invalid command 1"); if (c == NULL) return c; for (i = CMD_OPEN; i < NUM_COMMANDS; i++) { if (len == commands[i].cmd_len && strncasecmp(t, commands[i].cmd_name, commands[i].cmd_len) == 0) { *cmd = i; if (i == CMD_QUIT) return SkipWhite(c, REQUIRES_EITHER, ""); else return SkipWhite(c, REQUIRES_MORE, "Invalid command 2"); } } array_strcpy(errdetail, "Invalid command 3"); array_strcpy(badtoken, line); return NULL; } char *get_long(char *line, long *value, enum requires_more requires_more, const char *invalid) { char *c; char *t; char *e; int len; c = get_token(line, &t, &len, false, requires_more, invalid); if (c == NULL) return c; /* Extract value */ if (*t == '*' && len == 1) *value = -1; else { *value = strtol(t, &e, 0); if (e != NULL && e != c) { array_strcpy(errdetail, invalid); array_strncpy(badtoken, t, len); errno = EINVAL; return NULL; } } return SkipWhite(c, requires_more, invalid); } char *get_unsignedlonglong(char *line, long long *value, enum requires_more requires_more, const char *invalid) { char *c; char *t; char *e; int len; c = get_token(line, &t, &len, false, requires_more, invalid); if (c == NULL) return c; /* Extract value */ if (*t == '*' && len == 1) *value = -1; else { *value = strtoull(t, &e, 0); if (e != NULL && e != c) { array_strcpy(errdetail, invalid); array_strncpy(badtoken, t, len); errno = EINVAL; return NULL; } } return SkipWhite(c, requires_more, invalid); } char *get_lock_type(char *line, int *type) { return get_token_value(line, type, lock_types, false, REQUIRES_MORE, "Invalid lock type"); } char *get_on_off(char *line, bool *value) { int tvalue; char *rest; rest = get_token_value(line, &tvalue, on_off, true, REQUIRES_NO_MORE, "Invalid on/off"); *value = (bool)tvalue; return rest; } char *get_fpos(char *line, long *fpos, enum requires_more requires_more) { char *c = line; /* Get fpos */ c = get_long(c, fpos, requires_more, "Invalid fpos"); if (c == NULL) return c; if (*fpos < 0 || *fpos > MAXFPOS) { array_strcpy(errdetail, "Invalid fpos"); array_sprintf(badtoken, "%ld", *fpos); errno = EINVAL; return NULL; } return c; } char *get_rdata(char *line, struct response *resp, int max, enum requires_more requires_more) { char *c = line; char *t; int quoted; int len; assert(max < sizeof(resp->r_data)); c = SkipWhite(c, REQUIRES_MORE, "get rdata 1"); if (c == NULL) return c; if (*c != '"') { if (requires_more != REQUIRES_NO_MORE) { errno = EINVAL; array_strcpy(errdetail, "Expected string"); array_strcpy(badtoken, c); return NULL; } quoted = false; } else { c++; quoted = true; } t = c; if (quoted) while (*c != '"' && *c != '\0') c++; else while (*c != '\0') c++; if (quoted && *c == '\0') { errno = EINVAL; array_strcpy(errdetail, "Unterminated string"); array_strcpy(badtoken, t - 1); return NULL; } len = c - t; if (len > max) { errno = EINVAL; array_sprintf(errdetail, "String length %d longer than %d", len, max); array_strcpy(badtoken, t - 1); return NULL; } resp->r_length = len; array_strncpy(resp->r_data, t, len); c++; return SkipWhite(c, requires_more, "get rdata 2"); } char *get_open_opts(char *line, long *fpos, int *flags, int *mode, int *lock_mode) { char *c; int flag2 = -1; bool has_mode = false; /* Set default mode */ *mode = S_IRUSR | S_IWUSR; /* Get fpos */ c = get_fpos(line, fpos, REQUIRES_MORE); if (c == NULL) return c; c = get_token_value(c, flags, read_write_flags, false, REQUIRES_MORE, "Invalid open flags"); if (c == NULL) return c; *flags |= O_SYNC; /* Check optional open flags */ while (flag2 != 0) { c = get_token_value(c, &flag2, open_flags, true, REQUIRES_MORE, "Invalid optional open flag"); if (c == NULL) return c; *flags |= flag2; } c = get_optional_token(c, &has_mode, "mode", REQUIRES_MORE, "Invalid optional open flag"); if (c == NULL) return c; if (has_mode) { /* We got a mode, now get the mode value */ long modev; c = get_long(c, &modev, REQUIRES_MORE, "Invalid mode"); if (c == NULL) return c; *mode = modev; } /* Check optional lock mode, default to POSIX */ *lock_mode = LOCK_MODE_POSIX; c = get_token_value(c, lock_mode, lock_modes, true, REQUIRES_MORE, "Invalid optional lock mode"); if (c == NULL) return c; return SkipWhite(c, REQUIRES_MORE, "get_open_opts"); } const char *str_status(enum status status) { switch (status) { case STATUS_OK: return "OK"; case STATUS_AVAILABLE: return "AVAILABLE"; case STATUS_GRANTED: return "GRANTED"; case STATUS_DENIED: return "DENIED"; case STATUS_DEADLOCK: return "DEADLOCK"; case STATUS_CONFLICT: return "CONFLICT"; case STATUS_CANCELED: return "CANCELED"; case STATUS_COMPLETED: return "COMPLETED"; case STATUS_ERRNO: return "ERRNO"; case STATUS_PARSE_ERROR: return "PARSE_ERROR"; case STATUS_ERROR: return "ERROR"; } return "unknown"; } char *get_status(char *line, struct response *resp) { enum status stat; char *t; int len; char *c = get_token(line, &t, &len, false, REQUIRES_EITHER, "Invalid status"); if (c == NULL) return c; for (stat = STATUS_OK; stat <= STATUS_ERROR; stat++) { const char *cmp_status = str_status(stat); if (strlen(cmp_status) != len) continue; if (strncasecmp(cmp_status, t, len) == 0) { enum requires_more requires_more = REQUIRES_MORE; resp->r_status = stat; if (stat == STATUS_COMPLETED || (resp->r_cmd == CMD_QUIT && stat == STATUS_OK)) requires_more = REQUIRES_NO_MORE; return SkipWhite(c, requires_more, "get_status"); } } array_strcpy(errdetail, "Invalid status"); array_strncpy(badtoken, t, len); errno = EINVAL; return NULL; } void free_client(struct client *client) { if (client == NULL) return; if (client->c_prev != NULL) client->c_prev->c_next = client->c_next; if (client->c_next != NULL) client->c_next->c_prev = client->c_prev; if (client_list == client) client_list = client->c_next; free(client); } void free_response(struct response *resp, struct response **list) { if (resp == NULL) return; if (list != NULL && *list == resp) *list = resp->r_next; if (resp->r_prev != NULL) resp->r_prev->r_next = resp->r_next; if (resp->r_next != NULL) resp->r_next->r_prev = resp->r_prev; if (resp->r_client != NULL && --resp->r_client->c_refcount == 0) { free_client(resp->r_client); resp->r_client = NULL; } free(resp); } const char *str_lock_type(int type) { switch (type) { case F_RDLCK: return "read"; case F_WRLCK: return "write"; case F_UNLCK: return "unlock"; } return "unknown"; } char *get_tag(char *line, struct response *resp, int required, enum requires_more requires_more) { if (*line == '$') { char *c = line + 1; if (tolower(*c) >= 'a' || tolower(*c) <= 'z') resp->r_tag = saved_tags[tolower(*c++) - 'a']; else resp->r_tag = get_global_tag(false); return SkipWhite(c, requires_more, "get_tag"); } if (required || (*line != '\0' && *line != '#')) return get_long(line, &resp->r_tag, requires_more, "Invalid tag"); resp->r_tag = -1; return line; } char *get_rq_tag(char *line, struct response *req, int required, enum requires_more requires_more) { if (*line == '$') { char *c = line + 1; req->r_tag = get_global_tag(true); if (tolower(*c) >= 'a' || tolower(*c) <= 'z') saved_tags[tolower(*c++) - 'a'] = get_global_tag(false); return SkipWhite(c, requires_more, "get_rq_tag"); } if (required || (*line != '\0' && *line != '#')) return get_long(line, &req->r_tag, requires_more, "Invalid tag"); req->r_tag = -1; return line; } void sprintf_resp(char *line, int size, const char *lead, struct response *resp) { char *rest = line; int left = size - 1; /* Leave room for terminating NUL */ if (lead != NULL) { const char *name = "<NULL>"; if (resp->r_client != NULL) name = resp->r_client->c_name; sprint_left(rest, left, "%s %s ", lead, name); } sprint_left(rest, left, "%ld %s %s", resp->r_tag, commands[resp->r_cmd].cmd_name, str_status(resp->r_status)); switch (resp->r_status) { case STATUS_OK: switch (resp->r_cmd) { case CMD_COMMENT: case CMD_HELLO: case CMD_FORK: sprint_left(rest, left, " \"%s\"\n", resp->r_data); break; case CMD_LOCKW: case CMD_LOCK: case CMD_UNLOCK: case CMD_TEST: case CMD_LIST: case CMD_HOP: case CMD_UNHOP: case NUM_COMMANDS: sprint_left(rest, left, " Unexpected Status\n"); break; case CMD_ALARM: sprint_left(rest, left, " %ld\n", resp->r_secs); break; case CMD_QUIT: sprint_left(rest, left, "\n"); break; case CMD_OPEN: sprint_left(rest, left, " %ld %ld\n", resp->r_fpos, resp->r_fno); break; case CMD_CLOSE: case CMD_SEEK: sprint_left(rest, left, " %ld\n", resp->r_fpos); break; case CMD_WRITE: sprint_left(rest, left, " %ld %llu\n", resp->r_fpos, resp->r_length); break; case CMD_READ: sprint_left(rest, left, " %ld %llu \"%s\"\n", resp->r_fpos, resp->r_length, resp->r_data); break; } break; case STATUS_AVAILABLE: case STATUS_GRANTED: case STATUS_DENIED: case STATUS_DEADLOCK: if (resp->r_cmd == CMD_LIST) { sprint_left(rest, left, " %ld %llu %llu\n", resp->r_fpos, resp->r_start, resp->r_length); } else { sprint_left(rest, left, " %ld %s %llu %llu\n", resp->r_fpos, str_lock_type(resp->r_lock_type), resp->r_start, resp->r_length); } break; case STATUS_CONFLICT: sprint_left(rest, left, " %ld %ld %s %llu %llu\n", resp->r_fpos, resp->r_pid, str_lock_type(resp->r_lock_type), resp->r_start, resp->r_length); break; case STATUS_CANCELED: if (resp->r_cmd == CMD_LOCKW) { sprint_left(rest, left, " %ld %s %llu %llu\n", resp->r_fpos, str_lock_type(resp->r_lock_type), resp->r_start, resp->r_length); } else if (resp->r_cmd == CMD_ALARM) { sprint_left(rest, left, " %ld\n", resp->r_secs); } break; case STATUS_COMPLETED: sprint_left(rest, left, "\n"); break; case STATUS_ERRNO: if (errno == 0) { sprint_left(rest, left, " %ld \"%s\"\n", resp->r_errno, errdetail); } else { sprint_left(rest, left, " %ld \"%s\" \"%s\" bad token \"%s\"\n", resp->r_errno, strerror(resp->r_errno), errdetail, badtoken); } break; case STATUS_PARSE_ERROR: break; case STATUS_ERROR: break; } /* Make sure we are NUL terminated even if we used the last byte. */ *rest = '\0'; } void respond(struct response *resp) { char line[MAXXFER]; sprintf_resp(line, sizeof(line), NULL, resp); if (output != stdout) { fputs(line, output); fflush(output); } if (resp->r_status >= STATUS_ERRNO) fprintf_stderr("%s", line); else if (!quiet) fprintf(stdout, "%s", line); } char *parse_response(char *line, struct response *resp) { char *rest; long long verify_len; if (resp->r_original[0] == '\0') array_strcpy(resp->r_original, line); resp->r_cmd = NUM_COMMANDS; resp->r_tag = -1; rest = get_tag(line, resp, true, REQUIRES_MORE); if (rest == NULL) goto fail; rest = get_command(rest, &resp->r_cmd); if (rest == NULL) goto fail; rest = get_status(rest, resp); if (rest == NULL) goto fail; switch (resp->r_status) { case STATUS_OK: switch (resp->r_cmd) { case CMD_COMMENT: case CMD_HELLO: case CMD_FORK: rest = get_rdata(rest, resp, MAXSTR, REQUIRES_NO_MORE); break; case CMD_LOCKW: case CMD_LOCK: case CMD_UNLOCK: case CMD_TEST: case CMD_LIST: case CMD_HOP: case CMD_UNHOP: case NUM_COMMANDS: array_strcpy(errdetail, "Unexpected Status"); errno = EINVAL; array_sprintf(badtoken, "%s", str_status(resp->r_status)); goto fail; case CMD_ALARM: return get_long(rest, &resp->r_secs, REQUIRES_NO_MORE, "Invalid alarm time"); case CMD_QUIT: return rest; case CMD_OPEN: rest = get_fpos(rest, &resp->r_fpos, REQUIRES_MORE); if (rest == NULL) goto fail; rest = get_long(rest, &resp->r_fno, REQUIRES_NO_MORE, "Invalid file number"); break; case CMD_CLOSE: case CMD_SEEK: rest = get_fpos(rest, &resp->r_fpos, REQUIRES_NO_MORE); break; case CMD_WRITE: rest = get_fpos(rest, &resp->r_fpos, REQUIRES_MORE); if (rest == NULL) goto fail; rest = get_unsignedlonglong(rest, &resp->r_length, REQUIRES_NO_MORE, "Invalid length"); break; case CMD_READ: rest = get_fpos(rest, &resp->r_fpos, REQUIRES_MORE); if (rest == NULL) goto fail; rest = get_unsignedlonglong(rest, &resp->r_length, REQUIRES_MORE, "Invalid length"); if (rest == NULL) goto fail; verify_len = resp->r_length; rest = get_rdata(rest, resp, MAXSTR, REQUIRES_NO_MORE); if (verify_len != resp->r_length) { array_strcpy(errdetail, "Read length doesn't match"); errno = EINVAL; array_sprintf(badtoken, "%llu != %llu", verify_len, resp->r_length); goto fail; } break; } break; case STATUS_AVAILABLE: case STATUS_GRANTED: case STATUS_DENIED: case STATUS_DEADLOCK: rest = get_fpos(rest, &resp->r_fpos, REQUIRES_MORE); if (rest == NULL) goto fail; if (resp->r_cmd != CMD_LIST) { rest = get_lock_type(rest, &resp->r_lock_type); if (rest == NULL) goto fail; } rest = get_unsignedlonglong(rest, &resp->r_start, REQUIRES_MORE, "Invalid lock start"); if (rest == NULL) goto fail; rest = get_unsignedlonglong(rest, &resp->r_length, REQUIRES_NO_MORE, "Invalid lock length"); break; case STATUS_CONFLICT: rest = get_fpos(rest, &resp->r_fpos, REQUIRES_MORE); if (rest == NULL) goto fail; rest = get_long(rest, &resp->r_pid, REQUIRES_MORE, "Invalid conflict pid"); if (rest == NULL) goto fail; rest = get_lock_type(rest, &resp->r_lock_type); if (rest == NULL) goto fail; rest = get_unsignedlonglong(rest, &resp->r_start, REQUIRES_MORE, "Invalid lock start"); if (rest == NULL) goto fail; rest = get_unsignedlonglong(rest, &resp->r_length, REQUIRES_NO_MORE, "Invalid lock length"); break; case STATUS_CANCELED: if (resp->r_cmd == CMD_LOCKW) { rest = get_fpos(rest, &resp->r_fpos, REQUIRES_MORE); if (rest == NULL) goto fail; rest = get_lock_type(rest, &resp->r_lock_type); if (rest == NULL) goto fail; rest = get_unsignedlonglong(rest, &resp->r_start, REQUIRES_MORE, "Invalid lock start"); if (rest == NULL) goto fail; rest = get_unsignedlonglong(rest, &resp->r_length, REQUIRES_NO_MORE, "Invalid lock length"); } else if (resp->r_cmd == CMD_ALARM) { rest = get_long(rest, &resp->r_secs, REQUIRES_NO_MORE, "Invalid alarm time"); } break; case STATUS_COMPLETED: break; case STATUS_ERRNO: rest = get_long(rest, &resp->r_errno, REQUIRES_MORE, "Invalid errno"); if (rest == NULL) goto fail; array_strcpy(resp->r_data, rest); rest += strlen(rest); break; case STATUS_PARSE_ERROR: break; case STATUS_ERROR: break; } if (rest != NULL) return rest; fail: resp->r_status = STATUS_PARSE_ERROR; array_sprintf(resp->r_data, "%s %ld ERRNO %d \"%s\" \"%s\" bad token \"%s\"", commands[resp->r_cmd].cmd_name, resp->r_tag, errno, strerror(errno), errdetail, badtoken); resp->r_cmd = NUM_COMMANDS; return NULL; } #define return_if_ne_lock_type(expected, received) \ do { \ if (expected != -1 && expected != received) { \ array_sprintf(errdetail, "Unexpected lock type %s", \ lock_types[received].t_name); \ return false; \ } \ } while (0) #define return_if_ne_long(expected, received, fmt) \ do { \ if (expected != -1 && expected != received) { \ array_sprintf(errdetail, fmt " %ld", received); \ return false; \ } \ } while (0) #define return_if_ne_unsignedlonglong(expected, received, fmt) \ do { \ if (expected != -1 && expected != received) { \ array_sprintf(errdetail, fmt " %llu", received); \ return false; \ } \ } while (0) #define return_if_ne_string(expected, received, fmt) \ do { \ if (strcmp(expected, "*") != 0 && \ strcmp(expected, received) != 0) { \ array_sprintf(errdetail, fmt " %s", received); \ return false; \ } \ } while (0) int compare_responses(struct response *expected, struct response *received) { errno = 0; if (received == NULL) { array_strcpy(errdetail, "Unexpected NULL response"); return false; } if (expected->r_client != received->r_client && strcmp(expected->r_client->c_name, received->r_client->c_name) != 0) { array_sprintf(errdetail, "Unexpected response from %s", received->r_client->c_name); return false; } if (expected->r_cmd != received->r_cmd) { array_sprintf(errdetail, "Unexpected command %s", commands[received->r_cmd].cmd_name); return false; } return_if_ne_long(expected->r_tag, received->r_tag, "Unexpected tag"); if (expected->r_status != received->r_status) { array_sprintf(errdetail, "Unexpected status %s", str_status(received->r_status)); return false; } switch (expected->r_status) { case STATUS_OK: switch (expected->r_cmd) { case CMD_COMMENT: case CMD_HELLO: case CMD_FORK: /* could check string, but not worth it - HELLO has * already set client name and that has been checked */ break; case CMD_LOCKW: case CMD_LOCK: case CMD_UNLOCK: case CMD_TEST: case CMD_LIST: case CMD_HOP: case CMD_UNHOP: case NUM_COMMANDS: array_sprintf(errdetail, "Unexpected Status %s for %s", str_status(received->r_status), commands[received->r_cmd].cmd_name); return false; case CMD_ALARM: return_if_ne_long(expected->r_secs, received->r_secs, "Unexpected secs"); break; case CMD_QUIT: break; case CMD_OPEN: return_if_ne_long(expected->r_fpos, received->r_fpos, "Unexpected fpos"); return_if_ne_long(expected->r_fno, received->r_fno, "Unexpected file number"); break; case CMD_CLOSE: case CMD_SEEK: return_if_ne_long(expected->r_fpos, received->r_fpos, "Unexpected fpos"); break; case CMD_WRITE: return_if_ne_long(expected->r_fpos, received->r_fpos, "Unexpected fpos"); return_if_ne_unsignedlonglong(expected->r_length, received->r_length, "Unexpected length"); break; case CMD_READ: return_if_ne_long(expected->r_fpos, received->r_fpos, "Unexpected fpos"); return_if_ne_unsignedlonglong(expected->r_length, received->r_length, "Unexpected length"); return_if_ne_string(expected->r_data, received->r_data, "Unexpected data"); break; } break; case STATUS_AVAILABLE: case STATUS_GRANTED: case STATUS_DENIED: case STATUS_DEADLOCK: return_if_ne_long(expected->r_fpos, received->r_fpos, "Unexpected fpos"); if (expected->r_cmd != CMD_LIST) return_if_ne_lock_type(expected->r_lock_type, received->r_lock_type); return_if_ne_unsignedlonglong(expected->r_start, received->r_start, "Unexpected start"); return_if_ne_unsignedlonglong(expected->r_length, received->r_length, "Unexpected length"); break; case STATUS_CONFLICT: return_if_ne_long(expected->r_fpos, received->r_fpos, "Unexpected fpos"); return_if_ne_long(expected->r_pid, received->r_pid, "Unexpected pid"); return_if_ne_lock_type(expected->r_lock_type, received->r_lock_type); return_if_ne_unsignedlonglong(expected->r_start, received->r_start, "Unexpected start"); return_if_ne_unsignedlonglong(expected->r_length, received->r_length, "Unexpected length"); break; case STATUS_CANCELED: if (expected->r_cmd == CMD_LOCKW) { return_if_ne_long(expected->r_fpos, received->r_fpos, "Unexpected fpos"); return_if_ne_lock_type(expected->r_lock_type, received->r_lock_type); return_if_ne_unsignedlonglong(expected->r_start, received->r_start, "Unexpected start"); return_if_ne_unsignedlonglong(expected->r_length, received->r_length, "Unexpected length"); } else if (expected->r_cmd == CMD_ALARM) { return_if_ne_long(expected->r_secs, received->r_secs, "Unexpected secs"); } break; case STATUS_COMPLETED: break; case STATUS_ERRNO: break; case STATUS_PARSE_ERROR: break; case STATUS_ERROR: break; } return true; } void add_response(struct response *resp, struct response **list) { resp->r_next = *list; if (*list != NULL) (*list)->r_prev = resp; *list = resp; } struct response *check_expected_responses(struct response *expected_responses, struct response *client_resp) { struct response *expected_resp = expected_responses; while (expected_resp != NULL && !compare_responses(expected_resp, client_resp)) expected_resp = expected_resp->r_next; return expected_resp; } char *parse_alarm(char *line, struct response *req) { return get_long(line, &req->r_secs, REQUIRES_NO_MORE, "Invalid secs"); } char *parse_open(char *line, struct response *req) { char *more = get_open_opts(line, &req->r_fpos, &req->r_flags, &req->r_mode, &req->r_lock_type); if (more == NULL) return more; return get_rdata(more, req, PATH_MAX - 1, REQUIRES_NO_MORE); } char *parse_write(char *line, struct response *req) { char *more; more = get_fpos(line, &req->r_fpos, REQUIRES_MORE); if (more == NULL) return more; return get_rdata(more, req, MAXSTR, REQUIRES_NO_MORE); } char *parse_read(char *line, struct response *req) { char *more; req->r_data[0] = '\0'; more = get_fpos(line, &req->r_fpos, REQUIRES_MORE); if (more == NULL) return more; if (*more == '"') return get_rdata(more, req, MAXSTR, REQUIRES_NO_MORE); else return get_unsignedlonglong(more, &req->r_length, REQUIRES_NO_MORE, "Invalid len"); } char *parse_seek(char *line, struct response *req) { char *more; more = get_fpos(line, &req->r_fpos, REQUIRES_MORE); if (more == NULL) return more; return get_unsignedlonglong(more, &req->r_start, REQUIRES_NO_MORE, "Invalid pos"); } char *parse_lock(char *line, struct response *req) { char *more; more = get_fpos(line, &req->r_fpos, REQUIRES_MORE); if (more == NULL) return more; more = get_lock_type(more, &req->r_lock_type); if (more == NULL) return more; if (req->r_lock_type != F_RDLCK && req->r_lock_type != F_WRLCK) { errno = EINVAL; array_strcpy(errdetail, "Invalid lock type"); array_sprintf(badtoken, "%s", str_lock_type(req->r_lock_type)); } more = get_unsignedlonglong(more, &req->r_start, REQUIRES_MORE, "Invalid lock start"); if (more == NULL) return more; return get_unsignedlonglong(more, &req->r_length, REQUIRES_NO_MORE, "Invalid lock len"); } char *parse_unlock(char *line, struct response *req) { char *more; req->r_lock_type = F_UNLCK; more = get_fpos(line, &req->r_fpos, REQUIRES_MORE); if (more == NULL) return more; more = get_unsignedlonglong(more, &req->r_start, REQUIRES_MORE, "Invalid lock start"); if (more == NULL) return more; return get_unsignedlonglong(more, &req->r_length, REQUIRES_NO_MORE, "Invalid lock len"); } char *parse_close(char *line, struct response *req) { return get_fpos(line, &req->r_fpos, REQUIRES_NO_MORE); } char *parse_list(char *line, struct response *req) { char *more; req->r_lock_type = F_WRLCK; more = get_fpos(line, &req->r_fpos, REQUIRES_MORE); if (more == NULL) return more; more = get_unsignedlonglong(more, &req->r_start, REQUIRES_MORE, "Invalid lock start"); if (more == NULL) return more; return get_unsignedlonglong(more, &req->r_length, REQUIRES_NO_MORE, "Invalid lock len"); } char *parse_string(char *line, struct response *req) { return get_rdata(line, req, MAXSTR, REQUIRES_NO_MORE); } char *parse_empty(char *line, struct response *req) { return line; } typedef char *(*parse_function_t)(char *line, struct response *req); parse_function_t parse_functions[NUM_COMMANDS] = { parse_open, parse_close, parse_lock, /* lock */ parse_lock, /* lockw */ parse_unlock, parse_lock, /* test */ parse_list, parse_lock, /* hop */ parse_unlock, /* unhop */ parse_seek, parse_read, parse_write, parse_string, /* comment */ parse_alarm, parse_string, /* hello */ parse_string, /* fork */ parse_empty, /* quit */ }; char *parse_request(char *line, struct response *req, int no_tag) { char *rest = line; req->r_cmd = NUM_COMMANDS; req->r_tag = -1; if (no_tag) req->r_tag = get_global_tag(true); else rest = get_rq_tag(rest, req, true, REQUIRES_MORE); if (rest == NULL) return rest; rest = get_command(rest, &req->r_cmd); if (rest == NULL) return rest; switch (req->r_cmd) { case CMD_OPEN: case CMD_CLOSE: case CMD_LOCKW: case CMD_LOCK: case CMD_UNLOCK: case CMD_TEST: case CMD_LIST: case CMD_HOP: case CMD_UNHOP: case CMD_SEEK: case CMD_READ: case CMD_WRITE: case CMD_HELLO: case CMD_FORK: case CMD_COMMENT: case CMD_ALARM: case CMD_QUIT: rest = parse_functions[req->r_cmd](rest, req); break; case NUM_COMMANDS: break; } return rest; } void send_cmd(struct response *req) { char line[MAXXFER]; sprintf_req(line, sizeof(line), NULL, req); fputs(line, req->r_client->c_output); fflush(req->r_client->c_output); } void sprintf_req(char *line, int size, const char *lead, struct response *req) { char *rest = line; int left = size - 1; /* Leave room for terminating NUL */ if (lead != NULL) { const char *name = "<NULL>"; if (req->r_client != NULL) name = req->r_client->c_name; sprint_left(rest, left, "%s %s ", lead, name); } sprint_left(rest, left, "%ld %s", req->r_tag, commands[req->r_cmd].cmd_name); switch (req->r_cmd) { case CMD_COMMENT: case CMD_HELLO: case CMD_FORK: sprint_left(rest, left, " \"%s\"\n", req->r_data); break; case CMD_LOCKW: case CMD_LOCK: case CMD_TEST: case CMD_HOP: sprint_left(rest, left, " %ld %s %llu %llu\n", req->r_fpos, str_lock_type(req->r_lock_type), req->r_start, req->r_length); break; case CMD_UNLOCK: case CMD_LIST: case CMD_UNHOP: sprint_left(rest, left, " %ld %llu %llu\n", req->r_fpos, req->r_start, req->r_length); break; case NUM_COMMANDS: sprint_left(rest, left, " Unexpected Command\n"); break; case CMD_ALARM: sprint_left(rest, left, " %ld\n", req->r_secs); break; case CMD_QUIT: sprint_left(rest, left, "\n"); break; case CMD_OPEN: sprint_left(rest, left, " %ld %s", req->r_fpos, str_read_write_flags(req->r_flags)); sprintf_open_flags(&rest, &left, req->r_flags); sprintf_lock_modes(&rest, &left, req->r_lock_type); sprint_left(rest, left, " \"%s\"\n", req->r_data); break; case CMD_CLOSE: sprint_left(rest, left, " %ld\n", req->r_fpos); break; case CMD_SEEK: sprint_left(rest, left, " %ld %llu\n", req->r_fpos, req->r_start); break; case CMD_WRITE: sprint_left(rest, left, " %ld %s\n", req->r_fpos, req->r_data); break; case CMD_READ: sprint_left(rest, left, " %ld %llu\n", req->r_fpos, req->r_length); break; } /* Make sure we are NUL terminated even if we used the last byte. */ *rest = '\0'; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/ml_glusterfs_client.c�������������������������������������������0000664�0000000�0000000�00000100253�14737566223�0023740�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright (C) Red Hat Inc., 2017 * Contributor: Frank Filz <ffilzlnx@mindspring.com> * Contributor: Soumya koduri <skoduri@redhat.com> * * This program 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 3 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 * 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 * * */ #include <fcntl.h> #include <pthread.h> #include <stdlib.h> #include <assert.h> #include <libgen.h> #include "multilock.h" #include "../../include/gsh_list.h" #include <glusterfs/api/glfs.h> #include <glusterfs/api/glfs-handles.h> /* command line syntax */ char options[] = "c:qdx:s:n:p:v:g:h?"; char usage[] = "Usage: ml_glusterfs_client -s server -v volname -p port -n name -g glusterserver [-q] [-d] [-c path]\n" " ml_glusterfs_client -v volname -g glusterserver -x script [-q] [-d] [-c path]\n" " ml_glusterfs_client -v volname -g glusterserver [-q] [-d] [-c path]\n" " ml_glusterfs_client may be run in three modes\n" " - In the first mode, the client will be driven by a console.\n" " - In the second mode, the client is driven by a script.\n" " - In the third mode, the client interractive.\n" " -s server - specify the console's hostname or IP address\n" " -p port - specify the console's port number\n" " -n name - specify the client's name\n" " -x script - specify the name of a script to execute\n" " -q - specify quiet mode\n" " -d - specify dup errors mode (errors are sent to stdout and stderr)\n" " -c path - chdir\n" " -g glusterserver -specify the hostname or IP address of glusterfs server\n" " -v volname - glusterfs volume name\n"; #define NUM_WORKER 4 #define POLL_DELAY 10 enum thread_type { THREAD_NONE, THREAD_MAIN, THREAD_WORKER, THREAD_POLL, THREAD_CANCEL }; struct work_item { struct glist_head queue; struct glist_head fno_work; struct response resp; enum thread_type work_owner; pthread_t work_thread; time_t next_poll; }; char server[MAXSTR]; char name[MAXSTR]; char portstr[MAXSTR]; char volname[MAXSTR]; char glusterserver[MAXSTR]; int port; char line[MAXXFER]; long alarmtag; struct glfs_object *handles[MAXFPOS + 1]; struct glfs_fd *fds[MAXFPOS + 1]; enum lock_mode lock_mode[MAXFPOS + 1]; /* Global variables to manage work list */ struct glist_head fno_work[MAXFPOS + 1]; struct glist_head work_queue = GLIST_HEAD_INIT(work_queue); struct glist_head poll_queue = GLIST_HEAD_INIT(poll_queue); pthread_t threads[NUM_WORKER + 1]; pthread_mutex_t work_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t work_cond = PTHREAD_COND_INITIALIZER; enum thread_type a_worker = THREAD_WORKER; enum thread_type a_poller = THREAD_POLL; /* The CephFS mount */ struct ceph_mount_info *cmount; glfs_t *fs; struct glusterfs_fs *gl_fs; void openserver(void) { struct addrinfo *addr; int rc; struct addrinfo hint; int sock; struct response resp; if (!quiet) fprintf(stdout, "server=%s port=%d name=%s\n", server, port, name); memset(&hint, 0, sizeof(hint)); hint.ai_family = AF_INET; hint.ai_socktype = SOCK_STREAM; rc = getaddrinfo(server, portstr, &hint, &addr); if (rc != 0) fatal("getaddrinfo error %d \"%s\"\n", rc, gai_strerror(rc)); rc = socket(addr[0].ai_family, SOCK_STREAM, 0); if (rc == -1) fatal("socket failed with ERRNO %d \"%s\"\n", errno, strerror(errno)); sock = rc; rc = connect(sock, addr[0].ai_addr, addr[0].ai_addrlen); if (rc == -1) fatal("connect failed with ERRNO %d \"%s\"\n", errno, strerror(errno)); input = fdopen(sock, "r"); if (input == NULL) fatal("Could not create input stream from socket ERROr %d \"%s\"\n", errno, strerror(errno)); output = fdopen(sock, "w"); if (output == NULL) fatal("Could not create output stream from socket ERROr %d \"%s\"\n", errno, strerror(errno)); if (!quiet) fprintf(stdout, "connected to server %s:%d\n", server, port); resp.r_cmd = CMD_HELLO; resp.r_status = STATUS_OK; resp.r_tag = 0; array_strcpy(resp.r_data, name); respond(&resp); } bool do_fork(struct response *resp, bool use_server) { pid_t forked; if (!use_server) { fprintf_stderr("FORK may only be used in server mode\n"); return false; } forked = fork(); if (forked < 0) { /* Error */ resp->r_status = STATUS_OK; resp->r_errno = errno; if (!quiet) fprintf(stdout, "fork failed %d (%s)\n", (int)resp->r_errno, strerror(resp->r_errno)); return true; } else if (forked == 0) { /* Parent sends a FORK response */ array_strcpy(resp->r_data, name); resp->r_status = STATUS_OK; if (!quiet) fprintf(stdout, "fork succeeded\n"); return true; } if (!quiet) fprintf(stdout, "forked\n"); /* This is the forked process */ /* First close the old output. */ fclose(output); /* Setup the new client name. */ array_strcpy(name, resp->r_data); /* Then open a new connection to the server. */ openserver(); /* Response already sent. */ return false; } void command(void) { } void sighandler(int sig) { struct response resp; switch (sig) { case SIGALRM: resp.r_cmd = CMD_ALARM; resp.r_tag = alarmtag; resp.r_status = STATUS_COMPLETED; alarmtag = 0; respond(&resp); break; case SIGPIPE: case SIGIO: break; } } void do_alarm(struct response *resp) { unsigned int remain; remain = alarm(resp->r_secs); if (remain != 0) { struct response resp2; resp2.r_cmd = CMD_ALARM; resp2.r_tag = alarmtag; resp2.r_secs = remain; resp2.r_status = STATUS_CANCELED; respond(&resp2); } if (resp->r_secs != 0) alarmtag = resp->r_tag; else alarmtag = 0; resp->r_status = STATUS_OK; } void do_open(struct response *resp) { struct glfs_object *glhandle = NULL, *parent = NULL; struct stat sb; struct glfs_fd *glfd = NULL; if (fds[resp->r_fpos] != NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EINVAL; array_strcpy(errdetail, "fpos in use"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } if ((resp->r_flags & O_CREAT) != 0) { char fullpath[PATH_MAX]; char *name; char *path; array_strcpy(fullpath, resp->r_data); name = basename(fullpath); path = dirname(fullpath); fprintf_stderr("path = '%s' name = '%s'\n", path, name); parent = glfs_h_lookupat(fs, NULL, path, &sb, 0); if (parent != NULL) { glhandle = glfs_h_creat(fs, parent, name, resp->r_flags, resp->r_mode, &sb); /* Release the parent directory. */ glfs_h_close(parent); if (!glhandle) { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_strcpy(errdetail, "glfs_h_creat"); return; } } else { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_sprintf(errdetail, "glfs_h_lookupat %s", path); return; } } else { glhandle = glfs_h_lookupat(fs, NULL, resp->r_data, &sb, 0); } if (glhandle != NULL) { glfd = glfs_h_open(fs, glhandle, resp->r_flags); if (!glfd) { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_strcpy(errdetail, "glfs_h_open"); return; } } else { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_strcpy(errdetail, "glfs_h_lookupat"); return; } if (!glfd) { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_strcpy(badtoken, resp->r_data); return; } fds[resp->r_fpos] = glfd; handles[resp->r_fpos] = glhandle; lock_mode[resp->r_fpos] = (enum lock_mode)resp->r_lock_type; resp->r_fno = resp->r_fpos; resp->r_status = STATUS_OK; } void do_write(struct response *resp) { long long rc; if (resp->r_fpos != 0 && fds[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } rc = glfs_write(fds[resp->r_fpos], resp->r_data, resp->r_length, 0); if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Write failed"); array_sprintf(badtoken, "%lld", resp->r_length); return; } if (rc != resp->r_length) { resp->r_status = STATUS_ERRNO; resp->r_errno = EIO; array_strcpy(errdetail, "Short write"); array_sprintf(badtoken, "%lld", rc); return; } resp->r_status = STATUS_OK; } void do_read(struct response *resp) { long long rc; if (resp->r_fpos != 0 && fds[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } if (resp->r_length > MAXSTR) resp->r_length = MAXSTR; #ifdef USE_GLUSTER_STAT_FETCH_API rc = glfs_read(fds[resp->r_fpos], resp->r_data, resp->r_length, 0, NULL); #else rc = glfs_read(fds[resp->r_fpos], resp->r_data, resp->r_length, 0); #endif if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Read failed"); array_sprintf(badtoken, "%lld", resp->r_length); return; } resp->r_data[rc] = '\0'; resp->r_length = strlen(resp->r_data); resp->r_status = STATUS_OK; } void do_seek(struct response *resp) { int rc; if (resp->r_fpos != 0 && fds[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } rc = glfs_lseek(fds[resp->r_fpos], resp->r_start, SEEK_SET); if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Seek failed"); array_sprintf(badtoken, "%lld", resp->r_start); return; } resp->r_status = STATUS_OK; } void free_work(struct work_item *work) { /* Make sure the item isn't on any queues */ glist_del(&work->fno_work); glist_del(&work->queue); free(work); } void cancel_work_item(struct work_item *work) { switch (work->work_owner) { case THREAD_NONE: case THREAD_MAIN: case THREAD_CANCEL: /* nothing special to do */ break; case THREAD_WORKER: case THREAD_POLL: /* Mark the work item to be canceled */ work->work_owner = THREAD_CANCEL; pthread_kill(work->work_thread, SIGIO); /* Wait for thread to be done or cancel the work */ while (work->work_owner != THREAD_NONE) pthread_cond_wait(&work_cond, &work_mutex); } /* Done with the work item, free the memory. */ free_work(work); } static inline long long lock_end(struct response *req) { if (req->r_length == 0) return LLONG_MAX; return req->r_start + req->r_length; } void cancel_work(struct response *req) { struct glist_head cancel_work = GLIST_HEAD_INIT(cancel_work); struct glist_head *glist; struct work_item *work; bool start_over = true; pthread_mutex_lock(&work_mutex); while (start_over) { start_over = false; glist_for_each(glist, fno_work + req->r_fpos) { work = glist_entry(glist, struct work_item, fno_work); if (work->resp.r_start >= req->r_start && lock_end(&work->resp) <= lock_end(req)) { /* Do something */ cancel_work_item(work); /* List may be messed up */ start_over = true; break; } } } pthread_mutex_unlock(&work_mutex); } /* Must only be called from main thread...*/ int schedule_work(struct response *resp) { struct work_item *work = calloc(1, sizeof(*work)); if (work == NULL) { errno = ENOMEM; return -1; } pthread_mutex_lock(&work_mutex); work->resp = *resp; work->work_owner = THREAD_NONE; glist_add_tail(&work_queue, &work->queue); glist_add_tail(fno_work + resp->r_fpos, &work->fno_work); /* Signal to the worker and polling threads there is new work */ pthread_cond_broadcast(&work_cond); pthread_mutex_unlock(&work_mutex); return 0; } bool do_lock(struct response *resp, enum thread_type thread_type) { int rc; struct flock lock; bool retry = false; uint64_t owner; if (resp->r_fpos != 0 && fds[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return true; } switch (lock_mode[resp->r_fpos]) { case LOCK_MODE_POSIX: if (resp->r_cmd == CMD_LOCKW) { retry = true; } owner = getpid(); break; case LOCK_MODE_OFD: if (resp->r_cmd == CMD_LOCKW) { retry = true; } owner = resp->r_fpos; break; } lock.l_whence = SEEK_SET; lock.l_type = resp->r_lock_type; lock.l_start = resp->r_start; lock.l_len = resp->r_length; lock.l_pid = 0; rc = glfs_fd_set_lkowner(fds[resp->r_fpos], (void *)&owner, sizeof(owner)); if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "setting lkowner failed"); array_sprintf(badtoken, "%lld %lld", resp->r_start, resp->r_length); return false; } rc = glfs_posix_lock(fds[resp->r_fpos], F_SETLK, &lock); if (rc < 0) rc = -errno; if (rc == -EAGAIN && retry && thread_type == THREAD_MAIN) { /* We need to schedule OFD blocked lock */ rc = schedule_work(resp); /* Check for scheduling success */ if (rc >= 0) return false; } if (rc < 0) { if (rc == -EAGAIN) { if (retry) { /* Let caller know we didn't complete */ return false; } resp->r_status = STATUS_DENIED; } else if (rc == -EINTR) { resp->r_status = STATUS_CANCELED; } else if (rc == -EDEADLK) { resp->r_status = STATUS_DEADLOCK; } else { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Lock failed"); array_sprintf(badtoken, "%s %lld %lld", str_lock_type(lock.l_type), resp->r_start, resp->r_length); } } else resp->r_status = STATUS_GRANTED; return true; } void do_hop(struct response *resp) { int rc; int pos; struct flock lock; uint64_t owner; if (resp->r_fpos != 0 && fds[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } switch (lock_mode[resp->r_fpos]) { case LOCK_MODE_POSIX: owner = getpid(); break; case LOCK_MODE_OFD: owner = resp->r_fpos; break; } for (pos = resp->r_start; pos < resp->r_start + resp->r_length; pos++) { if ((pos == 0) || (pos == (resp->r_start + resp->r_length - 1))) lock.l_start = pos; else if ((pos & 1) == 0) lock.l_start = pos - 1; else lock.l_start = pos + 1; lock.l_whence = SEEK_SET; lock.l_type = resp->r_lock_type; lock.l_len = 1; lock.l_pid = 0; rc = glfs_fd_set_lkowner(fds[resp->r_fpos], (void *)&owner, sizeof(owner)); if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "setting lkowner failed"); array_sprintf(badtoken, "%lld %lld", resp->r_start, resp->r_length); return; } rc = glfs_posix_lock(fds[resp->r_fpos], F_SETLK, &lock); if (rc < 0) rc = -errno; if (rc < 0) { if (rc == -EAGAIN) { resp->r_start = lock.l_start; resp->r_length = 1; resp->r_status = STATUS_DENIED; break; } else { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Hop failed"); array_sprintf(badtoken, "%s %ld", str_lock_type(resp->r_lock_type), lock.l_start); break; } } else resp->r_status = STATUS_GRANTED; } if (resp->r_status != STATUS_GRANTED) { lock.l_whence = SEEK_SET; lock.l_type = F_UNLCK; lock.l_start = resp->r_start; lock.l_len = resp->r_length; lock.l_pid = 0; rc = glfs_fd_set_lkowner(fds[resp->r_fpos], (void *)&owner, sizeof(owner)); if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "setting lkowner failed"); array_sprintf(badtoken, "%lld %lld", resp->r_start, resp->r_length); return; } rc = glfs_posix_lock(fds[resp->r_fpos], F_SETLK, &lock); if (rc < 0) rc = -errno; if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Hop Unlock failed"); array_sprintf(badtoken, "%lld %lld", resp->r_start, resp->r_length); } } } void do_unhop(struct response *resp) { int rc; int pos; struct flock lock; uint64_t owner; if (resp->r_fpos != 0 && fds[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } switch (lock_mode[resp->r_fpos]) { case LOCK_MODE_POSIX: owner = getpid(); break; case LOCK_MODE_OFD: owner = resp->r_fpos; break; } for (pos = resp->r_start; pos < resp->r_start + resp->r_length; pos++) { if ((pos == 0) || (pos == (resp->r_start + resp->r_length - 1))) lock.l_start = pos; else if ((pos & 1) == 0) lock.l_start = pos - 1; else lock.l_start = pos + 1; lock.l_whence = SEEK_SET; lock.l_type = resp->r_lock_type; lock.l_len = 1; lock.l_pid = 0; rc = glfs_posix_lock(fds[resp->r_fpos], F_SETLK, &lock); if (rc < 0) rc = -errno; if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Unhop failed"); array_sprintf(badtoken, "%s %ld", str_lock_type(resp->r_lock_type), lock.l_start); break; } else resp->r_status = STATUS_GRANTED; } if (resp->r_status != STATUS_GRANTED) { lock.l_whence = SEEK_SET; lock.l_type = F_UNLCK; lock.l_start = resp->r_start; lock.l_len = resp->r_length; lock.l_pid = 0; rc = glfs_fd_set_lkowner(fds[resp->r_fpos], (void *)&owner, sizeof(owner)); if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "setting lkowner failed"); array_sprintf(badtoken, "%lld %lld", resp->r_start, resp->r_length); return; } rc = glfs_posix_lock(fds[resp->r_fpos], F_SETLK, &lock); if (rc < 0) rc = -errno; if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Unhop Unlock failed"); array_sprintf(badtoken, "%lld %lld", resp->r_start, resp->r_length); } } } void do_unlock(struct response *resp) { int rc; struct flock lock; uint64_t owner; if (resp->r_fpos != 0 && fds[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } /* If this fpos has a blocking lock, cancel it. */ cancel_work(resp); switch (lock_mode[resp->r_fpos]) { case LOCK_MODE_POSIX: owner = getpid(); break; case LOCK_MODE_OFD: owner = resp->r_fpos; break; } lock.l_whence = SEEK_SET; lock.l_type = resp->r_lock_type; lock.l_start = resp->r_start; lock.l_len = resp->r_length; lock.l_pid = 0; rc = glfs_fd_set_lkowner(fds[resp->r_fpos], (void *)&owner, sizeof(owner)); if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "setting lkowner failed"); array_sprintf(badtoken, "%lld %lld", resp->r_start, resp->r_length); return; } rc = glfs_posix_lock(fds[resp->r_fpos], F_SETLK, &lock); if (rc < 0) rc = -errno; if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Unlock failed"); array_sprintf(badtoken, "%lld %lld", resp->r_start, resp->r_length); return; } resp->r_status = STATUS_GRANTED; } void do_test(struct response *resp) { int rc; struct flock lock; uint64_t owner; if (resp->r_fpos != 0 && fds[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } switch (lock_mode[resp->r_fpos]) { case LOCK_MODE_POSIX: owner = getpid(); break; case LOCK_MODE_OFD: owner = resp->r_fpos; break; } lock.l_whence = SEEK_SET; lock.l_type = resp->r_lock_type; lock.l_start = resp->r_start; lock.l_len = resp->r_length; lock.l_pid = 0; fprintf(stdout, "TEST lock type %s\n", str_lock_type(lock.l_type)); rc = glfs_fd_set_lkowner(fds[resp->r_fpos], (void *)&owner, sizeof(owner)); if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "setting lkowner failed"); array_sprintf(badtoken, "%lld %lld", resp->r_start, resp->r_length); return; } rc = glfs_posix_lock(fds[resp->r_fpos], F_GETLK, &lock); if (rc < 0) rc = -errno; if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Test failed"); array_sprintf(badtoken, "%s %lld %lld", str_lock_type(lock.l_type), resp->r_start, resp->r_length); return; } if (lock.l_type == F_UNLCK) { fprintf(stdout, "GRANTED TEST lock type %s\n", str_lock_type(lock.l_type)); resp->r_status = STATUS_GRANTED; } else { resp->r_lock_type = lock.l_type; resp->r_pid = lock.l_pid; resp->r_start = lock.l_start; resp->r_length = lock.l_len; resp->r_status = STATUS_CONFLICT; } } void do_close(struct response *resp) { int rc; if (resp->r_fpos != 0 && fds[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } rc = glfs_close(fds[resp->r_fpos]); glfs_h_close(handles[resp->r_fpos]); fds[resp->r_fpos] = NULL; handles[resp->r_fpos] = NULL; if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Close failed"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } resp->r_status = STATUS_OK; } struct test_list { struct test_list *tl_next; long long tl_start; long long tl_end; }; struct test_list *tl_head; struct test_list *tl_tail; void remove_test_list_head(void) { struct test_list *item = tl_head; if (item == NULL) fatal("Corruption in test list\n"); tl_head = item->tl_next; if (tl_tail == item) tl_tail = NULL; free(item); } void make_test_item(long long start, long long end) { struct test_list *item = malloc(sizeof(*item)); if (item == NULL) fatal("Could not allocate test list item\n"); item->tl_next = NULL; item->tl_start = start; item->tl_end = end; if (tl_head == NULL) tl_head = item; if (tl_tail != NULL) tl_tail->tl_next = item; tl_tail = item; } int list_locks(long long start, long long end, struct response *resp) { long long conf_end; struct flock lock; int rc; lock.l_whence = SEEK_SET; lock.l_type = F_WRLCK; lock.l_start = start; lock.l_pid = 0; if (end == INT64_MAX) lock.l_len = 0; else lock.l_len = end - start; rc = glfs_posix_lock(fds[resp->r_fpos], F_GETLK, &lock); if (rc < 0) rc = -errno; if (rc < 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = -rc; array_strcpy(errdetail, "Test failed"); array_sprintf(badtoken, "%s %lld %lld", str_lock_type(lock.l_type), resp->r_start, resp->r_length); respond(resp); return false; } /* Our test succeeded */ if (lock.l_type == F_UNLCK) return false; resp->r_status = STATUS_CONFLICT; resp->r_lock_type = lock.l_type; resp->r_pid = lock.l_pid; resp->r_start = lock.l_start; resp->r_length = lock.l_len; respond(resp); conf_end = lock_end(resp); if (lock.l_start > start) make_test_item(start, lock.l_start); if (conf_end < end) make_test_item(conf_end, end); return true; } void do_list(struct response *resp) { long long start = resp->r_start; long long length = resp->r_length; int conflict = false; if (resp->r_fpos != 0 && fds[resp->r_fpos] == NULL) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } resp->r_lock_type = F_WRLCK; make_test_item(start, lock_end(resp)); while (tl_head != NULL) { conflict |= list_locks(tl_head->tl_start, tl_head->tl_end, resp); remove_test_list_head(); } if (conflict) resp->r_status = STATUS_DENIED; else resp->r_status = STATUS_AVAILABLE; resp->r_lock_type = F_WRLCK; resp->r_start = start; resp->r_length = length; } struct work_item *get_work(enum thread_type thread_type) { struct work_item *work, *poll; while (true) { /* Check poll list first */ poll = glist_first_entry(&poll_queue, struct work_item, queue); work = poll; /* If we didn't find work on the poll list or this is a polling * thread and the work on the poll list isn't due for polling * yet, then look for work on the work list. */ if (work == NULL || (thread_type == THREAD_POLL && work->next_poll > time(NULL))) { /* Check work list now */ work = glist_first_entry(&work_queue, struct work_item, queue); } /* Assign the work to ourself and remove from the queue and * return to the caller. */ if (work != NULL) { work->work_owner = thread_type; work->work_thread = pthread_self(); glist_del(&work->queue); return work; } /* No work, decide what kind of wait to do. */ if (thread_type == THREAD_POLL && poll != NULL) { /* Since there is polling work to do, determine next * time to poll and wait that long for signal. */ struct timespec twait = { poll->next_poll - time(NULL), 0 }; pthread_cond_timedwait(&work_cond, &work_mutex, &twait); } else { /* Wait for signal */ pthread_cond_wait(&work_cond, &work_mutex); } } } void *worker(void *t_type) { struct work_item *work = NULL; bool complete, cancelled = false; enum thread_type thread_type = *((enum thread_type *)t_type); pthread_mutex_lock(&work_mutex); while (true) { /* Look for work */ work = get_work(thread_type); pthread_mutex_unlock(&work_mutex); assert(work != NULL); /* Do the work */ switch (work->resp.r_cmd) { case CMD_LOCKW: complete = do_lock(&work->resp, thread_type); break; default: work->resp.r_status = STATUS_ERRNO; work->resp.r_errno = EINVAL; complete = true; break; } if (complete) respond(&work->resp); pthread_mutex_lock(&work_mutex); if (complete) { /* Remember if the main thread was trying to cancel * the work. */ cancelled = work->work_owner == THREAD_CANCEL; /* Indicate this work is complete */ work->work_owner = THREAD_NONE; work->work_thread = 0; if (cancelled) { /* Signal that work has been canceled or * completed. */ pthread_cond_broadcast(&work_cond); } else { /* The work is done, and may be freed, except * that if the work was being cancelled by * cancel_work, then the main thread is waiting * for this thread to be done with the work * item, and thus we can not free it, * cancel_work_item will be responsible to free * the work item. This assures that the request * that caused the cancellation is blocked until * the cancellation has completed. */ free_work(work); } } else { /* This can ONLY happen for a polling thread. * Put this work back in the queue, with a new * next polling time. */ work->work_owner = THREAD_NONE; work->work_thread = 0; work->next_poll = time(NULL) + POLL_DELAY; glist_add_tail(&poll_queue, &work->queue); /* And let worker threads know there may be * more work to do. */ pthread_cond_broadcast(&work_cond); } } } int main(int argc, char **argv) { int opt; int len, rc, i; struct sigaction sigact; char *rest; int oflags = 0; int no_tag; /* Init the lists of work for each fno */ for (i = 0; i <= MAXFPOS; i++) glist_init(fno_work + i); /* Start the worker and polling threads */ for (i = 0; i <= NUM_WORKER; i++) { rc = pthread_create(&threads[i], NULL, worker, i == 0 ? &a_poller : &a_worker); if (rc == -1) { fprintf(stderr, "pthread_create failed %s\n", strerror(errno)); exit(1); } } /* Initialize the signal handling */ memset(&sigact, 0, sizeof(sigact)); sigact.sa_handler = sighandler; if (sigaction(SIGALRM, &sigact, NULL) == -1) return 1; if (sigaction(SIGPIPE, &sigact, NULL) == -1) return 1; if (sigaction(SIGIO, &sigact, NULL) == -1) return 1; input = stdin; output = stdout; /* now parsing options with getopt */ while ((opt = getopt(argc, argv, options)) != EOF) { switch (opt) { case 'c': rc = chdir(optarg); if (rc == -1) { fprintf(stderr, "Can not change dir to %s errno = %d \"%s\"\n", optarg, errno, strerror(errno)); exit(1); } break; case 'q': quiet = true; break; case 'd': duperrors = true; break; case 's': if (oflags > 31) show_usage(1, "Can not combine -x and -s\n"); oflags |= 1; script = true; array_strcpy(server, optarg); break; case 'x': if ((oflags & 31) != 0) show_usage( 1, "Can not combine -x and -s/-p/-n/-g/-v\n"); oflags |= 32; script = true; input = fopen(optarg, "r"); if (input == NULL) fatal("Could not open %s\n", optarg); break; case 'n': if (oflags > 31) show_usage(1, "Can not combine -x and -n\n"); oflags |= 2; array_strcpy(name, optarg); break; case 'p': if (oflags > 31) show_usage(1, "Can not combine -x and -p\n"); oflags |= 4; array_strcpy(portstr, optarg); port = atoi(optarg); break; case 'g': if (oflags > 31) show_usage(1, "Can not combine -x and -g\n"); oflags |= 8; array_strcpy(glusterserver, optarg); break; case 'v': if (oflags > 31) show_usage(1, "Can not combine -x and -v\n"); oflags |= 16; array_strcpy(volname, optarg); break; case '?': case 'h': default: /* display the help */ show_usage(0, "Help\n"); } } if (oflags > 0 && oflags < 31) show_usage(1, "Must specify -s, -p, -n, -g and -v together\n"); if (oflags == 31) openserver(); fs = glfs_new(volname); if (!fs) { fatal("Unable to create new glfs. Volume: %s", volname); } rc = glfs_set_volfile_server(fs, "tcp", glusterserver, 24007); if (rc != 0) { fatal("Unable to set volume file. Volume: %s", volname); } rc = glfs_set_logging(fs, "stdout", 7); if (rc != 0) { fatal("Unable to set logging. Volume: %s", volname); } rc = glfs_init(fs); if (rc != 0) { fatal("Unable to initialize volume. Volume: %s", volname); } while (1) { len = readln(input, line, sizeof(line)); if (len < 0) { if (script) fatal("End of file on input\n"); else break; } else { struct response resp; bool complete = true; lno++; memset(&resp, 0, sizeof(resp)); rest = SkipWhite(line, REQUIRES_MORE, "Invalid line"); /* Skip totally blank line */ if (rest == NULL) continue; if (script && !quiet) fprintf(stdout, "%s\n", rest); /* If line doesn't start with a tag, that's ok */ no_tag = (!isdigit(*rest) && (*rest != '$') && (*rest != '-')); /* Parse request into response structure */ rest = parse_request(rest, &resp, no_tag); if (rest == NULL) { resp.r_status = STATUS_ERRNO; resp.r_errno = errno; } else { /* Make sure default status is ok */ resp.r_status = STATUS_OK; if (*rest != '\0' && *rest != '#') fprintf_stderr( "Command line not consumed, rest=\"%s\"\n", rest); switch (resp.r_cmd) { case CMD_OPEN: do_open(&resp); break; case CMD_CLOSE: do_close(&resp); break; case CMD_LOCKW: complete = do_lock(&resp, THREAD_MAIN); break; case CMD_LOCK: complete = do_lock(&resp, THREAD_MAIN); break; case CMD_UNLOCK: do_unlock(&resp); break; case CMD_TEST: do_test(&resp); break; case CMD_LIST: do_list(&resp); break; case CMD_HOP: do_hop(&resp); break; case CMD_UNHOP: do_unhop(&resp); break; case CMD_SEEK: do_seek(&resp); break; case CMD_READ: do_read(&resp); break; case CMD_WRITE: do_write(&resp); break; case CMD_ALARM: do_alarm(&resp); break; case CMD_FORK: complete = do_fork(&resp, oflags == 7); break; case CMD_HELLO: case CMD_COMMENT: case CMD_QUIT: resp.r_status = STATUS_OK; break; case NUM_COMMANDS: fprintf_stderr("Invalid command %s\n", line); continue; } } if (complete) respond(&resp); if (resp.r_cmd == CMD_QUIT) exit(0); } } return 0; } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/ml_posix_client.c�����������������������������������������������0000664�0000000�0000000�00000070621�14737566223�0023071�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * Copyright IBM Corporation, 2012 * Contributor: Frank Filz <ffilzlnx@mindspring.com> * * * This software is a server that implements the NFS protocol. * * * This program 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 3 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 * 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 * * */ #include <pthread.h> #include <assert.h> #include <stdint.h> #include "multilock.h" #include "../../include/gsh_list.h" /* command line syntax */ char options[] = "c:qdx:s:n:p:h?"; char usage[] = "Usage: ml_posix_client -s server -p port -n name [-q] [-d] [-c path]\n" " ml_posix_client -x script [-q] [-d] [-c path]\n" " ml_posix_client [-q] [-d] [-c path]\n" "\n" " ml_posix_client may be run in three modes\n" " - In the first mode, the client will be driven by a console.\n" " - In the second mode, the client is driven by a script.\n" " - In the third mode, the client interractive.\n" "\n" " -s server - specify the console's hostname or IP address\n" " -p port - specify the console's port number\n" " -n name - specify the client's name\n" " -x script - specify the name of a script to execute\n" " -q - specify quiet mode\n" " -d - specify dup errors mode (errors are sent to stdout and stderr)\n" " -c path - chdir\n"; #define NUM_WORKER 4 #define POLL_DELAY 10 enum thread_type { THREAD_NONE, THREAD_MAIN, THREAD_WORKER, THREAD_POLL, THREAD_CANCEL }; struct work_item { struct glist_head queue; struct glist_head fno_work; struct response resp; enum thread_type work_owner; pthread_t work_thread; time_t next_poll; }; char server[MAXSTR]; char name[MAXSTR]; char portstr[MAXSTR]; int port; char line[MAXXFER]; long alarmtag; int fno[MAXFPOS + 1]; enum lock_mode lock_mode[MAXFPOS + 1]; /* Global variables to manage work list */ struct glist_head fno_work[MAXFPOS + 1]; struct glist_head work_queue = GLIST_HEAD_INIT(work_queue); struct glist_head poll_queue = GLIST_HEAD_INIT(poll_queue); pthread_t threads[NUM_WORKER + 1]; pthread_mutex_t work_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t work_cond = PTHREAD_COND_INITIALIZER; enum thread_type a_worker = THREAD_WORKER; enum thread_type a_poller = THREAD_POLL; void openserver(void) { struct addrinfo *addr; int rc; struct addrinfo hint; int sock; struct response resp; if (!quiet) fprintf(stdout, "server=%s port=%d name=%s\n", server, port, name); memset(&hint, 0, sizeof(hint)); hint.ai_family = AF_INET; hint.ai_socktype = SOCK_STREAM; rc = getaddrinfo(server, portstr, &hint, &addr); if (rc != 0) fatal("getaddrinfo error %d \"%s\"\n", rc, gai_strerror(rc)); rc = socket(addr[0].ai_family, SOCK_STREAM, 0); if (rc == -1) fatal("socket failed with ERRNO %d \"%s\"\n", errno, strerror(errno)); sock = rc; rc = connect(sock, addr[0].ai_addr, addr[0].ai_addrlen); if (rc == -1) fatal("connect failed with ERRNO %d \"%s\"\n", errno, strerror(errno)); input = fdopen(sock, "r"); if (input == NULL) fatal("Could not create input stream from socket ERROr %d \"%s\"\n", errno, strerror(errno)); output = fdopen(sock, "w"); if (output == NULL) fatal("Could not create output stream from socket ERROr %d \"%s\"\n", errno, strerror(errno)); if (!quiet) fprintf(stdout, "connected to server %s:%d\n", server, port); resp.r_cmd = CMD_HELLO; resp.r_status = STATUS_OK; resp.r_tag = 0; array_strcpy(resp.r_data, name); respond(&resp); } bool do_fork(struct response *resp, bool use_server) { pid_t forked; if (!use_server) { fprintf_stderr("FORK may only be used in server mode\n"); return false; } forked = fork(); if (forked < 0) { /* Error */ resp->r_status = STATUS_OK; resp->r_errno = errno; if (!quiet) fprintf(stdout, "fork failed %d (%s)\n", (int)resp->r_errno, strerror(resp->r_errno)); return true; } else if (forked == 0) { /* Parent sends a FORK response */ array_strcpy(resp->r_data, name); resp->r_status = STATUS_OK; if (!quiet) fprintf(stdout, "fork succeeded\n"); return true; } if (!quiet) fprintf(stdout, "forked\n"); /* This is the forked process */ /* First close the old output. */ fclose(output); /* Setup the new client name. */ array_strcpy(name, resp->r_data); /* Then open a new connection to the server. */ openserver(); /* Response already sent. */ return false; } void command(void) { } void sighandler(int sig) { struct response resp; switch (sig) { case SIGALRM: resp.r_cmd = CMD_ALARM; resp.r_tag = alarmtag; resp.r_status = STATUS_COMPLETED; alarmtag = 0; respond(&resp); break; case SIGPIPE: case SIGIO: break; } } void do_alarm(struct response *resp) { unsigned int remain; remain = alarm(resp->r_secs); if (remain != 0) { struct response resp2; resp2.r_cmd = CMD_ALARM; resp2.r_tag = alarmtag; resp2.r_secs = remain; resp2.r_status = STATUS_CANCELED; respond(&resp2); } if (resp->r_secs != 0) alarmtag = resp->r_tag; else alarmtag = 0; resp->r_status = STATUS_OK; } void do_open(struct response *resp) { int fd, rc; struct flock lock; if (fno[resp->r_fpos] != 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = EINVAL; array_strcpy(errdetail, "fpos in use"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } if ((resp->r_flags & O_CREAT) == 0) fd = open(resp->r_data, resp->r_flags); else fd = open(resp->r_data, resp->r_flags, resp->r_mode); if (fd == -1) { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_strcpy(badtoken, resp->r_data); return; } /* Test lock mode */ switch ((enum lock_mode)resp->r_lock_type) { case LOCK_MODE_POSIX: break; case LOCK_MODE_OFD: lock.l_whence = SEEK_SET; lock.l_type = F_RDLCK; lock.l_start = 0; lock.l_len = 0; lock.l_pid = 0; rc = fcntl(fd, F_OFD_GETLK, &lock); if (rc == -1) { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_strcpy(errdetail, "Open verify OFD locks failed"); array_strcpy(badtoken, resp->r_data); close(fd); return; } break; } fno[resp->r_fpos] = fd; lock_mode[resp->r_fpos] = (enum lock_mode)resp->r_lock_type; resp->r_fno = fd; resp->r_status = STATUS_OK; } void do_write(struct response *resp) { long long rc; if (resp->r_fpos != 0 && fno[resp->r_fpos] == 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } rc = write(fno[resp->r_fpos], resp->r_data, resp->r_length); if (rc == -1) { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_strcpy(errdetail, "Write failed"); array_sprintf(badtoken, "%llu", resp->r_length); return; } if (rc != resp->r_length) { resp->r_status = STATUS_ERRNO; resp->r_errno = EIO; array_strcpy(errdetail, "Short write"); array_sprintf(badtoken, "%lld", rc); return; } resp->r_status = STATUS_OK; } void do_read(struct response *resp) { long long rc; if (resp->r_fpos != 0 && fno[resp->r_fpos] == 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } if (resp->r_length > MAXSTR) resp->r_length = MAXSTR; rc = read(fno[resp->r_fpos], resp->r_data, resp->r_length); if (rc == -1) { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_strcpy(errdetail, "Read failed"); array_sprintf(badtoken, "%llu", resp->r_length); return; } resp->r_data[rc] = '\0'; resp->r_length = strlen(resp->r_data); resp->r_status = STATUS_OK; } void do_seek(struct response *resp) { int rc; if (resp->r_fpos != 0 && fno[resp->r_fpos] == 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } rc = lseek(fno[resp->r_fpos], resp->r_start, SEEK_SET); if (rc == -1) { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_strcpy(errdetail, "Seek failed"); array_sprintf(badtoken, "%llu", resp->r_start); return; } resp->r_status = STATUS_OK; } void free_work(struct work_item *work) { /* Make sure the item isn't on any queues */ glist_del(&work->fno_work); glist_del(&work->queue); free(work); } void cancel_work_item(struct work_item *work) { switch (work->work_owner) { case THREAD_NONE: case THREAD_MAIN: case THREAD_CANCEL: /* nothing special to do */ break; case THREAD_WORKER: case THREAD_POLL: /* Mark the work item to be canceled */ work->work_owner = THREAD_CANCEL; pthread_kill(work->work_thread, SIGIO); /* Wait for thread to be done or cancel the work */ while (work->work_owner != THREAD_NONE) pthread_cond_wait(&work_cond, &work_mutex); } /* Done with the work item, free the memory. */ free_work(work); } static inline long long lock_end(struct response *req) { if (req->r_length == 0) return LLONG_MAX; return req->r_start + req->r_length; } void cancel_work(struct response *req) { struct glist_head cancel_work = GLIST_HEAD_INIT(cancel_work); struct glist_head *glist; struct work_item *work; bool start_over = true; pthread_mutex_lock(&work_mutex); while (start_over) { start_over = false; glist_for_each(glist, fno_work + req->r_fpos) { work = glist_entry(glist, struct work_item, fno_work); if (work->resp.r_start >= req->r_start && lock_end(&work->resp) <= lock_end(req)) { /* Do something */ cancel_work_item(work); /* List may be messed up */ start_over = true; break; } } } pthread_mutex_unlock(&work_mutex); } /* Must only be called from main thread...*/ int schedule_work(struct response *resp) { struct work_item *work = calloc(1, sizeof(*work)); if (work == NULL) { errno = ENOMEM; return -1; } pthread_mutex_lock(&work_mutex); work->resp = *resp; work->work_owner = THREAD_NONE; glist_add_tail(&work_queue, &work->queue); glist_add_tail(fno_work + resp->r_fpos, &work->fno_work); /* Signal to the worker and polling threads there is new work */ pthread_cond_broadcast(&work_cond); pthread_mutex_unlock(&work_mutex); return 0; } bool do_lock(struct response *resp, enum thread_type thread_type) { int rc; struct flock lock; int cmd = -1; bool retry = false; if (resp->r_fpos != 0 && fno[resp->r_fpos] == 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return true; } switch (lock_mode[resp->r_fpos]) { case LOCK_MODE_POSIX: if (resp->r_cmd == CMD_LOCKW) { if (thread_type != THREAD_WORKER) { cmd = F_SETLK; retry = true; } else { cmd = F_SETLKW; } } else { cmd = F_SETLK; } break; case LOCK_MODE_OFD: if (resp->r_cmd == CMD_LOCKW) { if (thread_type != THREAD_WORKER) { cmd = F_OFD_SETLK; retry = true; } else { cmd = F_OFD_SETLKW; } } else { cmd = F_OFD_SETLK; } break; } lock.l_whence = SEEK_SET; lock.l_type = resp->r_lock_type; lock.l_start = resp->r_start; lock.l_len = resp->r_length; lock.l_pid = 0; rc = fcntl(fno[resp->r_fpos], cmd, &lock); if (rc == -1 && errno == EAGAIN && retry && thread_type == THREAD_MAIN) { /* We need to schedule OFD blocked lock */ rc = schedule_work(resp); /* Check for scheduling success */ if (rc == 0) return false; } if (rc == -1) { /* If a conflicting lock is held, F_SETLK returns -1 and sets errno to EACCES or EAGAIN */ if ((errno == EAGAIN) || ((errno == EACCES) && (cmd == F_SETLK))) { if (retry) { /* Let caller know we didn't complete */ return false; } resp->r_status = STATUS_DENIED; } else if (errno == EINTR) { resp->r_status = STATUS_CANCELED; } else if (errno == EDEADLK) { resp->r_status = STATUS_DEADLOCK; } else { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_strcpy(errdetail, "Lock failed"); array_sprintf(badtoken, "%s %llu %llu", str_lock_type(lock.l_type), resp->r_start, resp->r_length); } } else resp->r_status = STATUS_GRANTED; return true; } void do_hop(struct response *resp) { int rc; int pos; struct flock lock; int cmd = -1; if (resp->r_fpos != 0 && fno[resp->r_fpos] == 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } switch (lock_mode[resp->r_fpos]) { case LOCK_MODE_POSIX: cmd = F_SETLK; break; case LOCK_MODE_OFD: cmd = F_OFD_SETLK; break; } for (pos = resp->r_start; pos < resp->r_start + resp->r_length; pos++) { if ((pos == 0) || (pos == (resp->r_start + resp->r_length - 1))) lock.l_start = pos; else if ((pos & 1) == 0) lock.l_start = pos - 1; else lock.l_start = pos + 1; lock.l_whence = SEEK_SET; lock.l_type = resp->r_lock_type; lock.l_len = 1; lock.l_pid = 0; rc = fcntl(fno[resp->r_fpos], cmd, &lock); if (rc == -1) { /* If a conflicting lock is held, F_SETLK returns -1 and sets errno to EACCES or EAGAIN */ if ((errno == EAGAIN) || ((errno == EACCES) && (cmd == F_SETLK))) { resp->r_start = lock.l_start; resp->r_length = 1; resp->r_status = STATUS_DENIED; break; } else { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_strcpy(errdetail, "Hop failed"); array_sprintf(badtoken, "%s %ld", str_lock_type(resp->r_lock_type), lock.l_start); break; } } else resp->r_status = STATUS_GRANTED; } if (resp->r_status != STATUS_GRANTED) { lock.l_whence = SEEK_SET; lock.l_type = F_UNLCK; lock.l_start = resp->r_start; lock.l_len = resp->r_length; lock.l_pid = 0; rc = fcntl(fno[resp->r_fpos], cmd, &lock); if (rc == -1) { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_strcpy(errdetail, "Hop Unlock failed"); array_sprintf(badtoken, "%llu %llu", resp->r_start, resp->r_length); } } } void do_unhop(struct response *resp) { int rc; int pos; struct flock lock; int cmd = -1; if (resp->r_fpos != 0 && fno[resp->r_fpos] == 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } switch (lock_mode[resp->r_fpos]) { case LOCK_MODE_POSIX: cmd = F_SETLK; break; case LOCK_MODE_OFD: cmd = F_OFD_SETLK; break; } for (pos = resp->r_start; pos < resp->r_start + resp->r_length; pos++) { if ((pos == 0) || (pos == (resp->r_start + resp->r_length - 1))) lock.l_start = pos; else if ((pos & 1) == 0) lock.l_start = pos - 1; else lock.l_start = pos + 1; lock.l_whence = SEEK_SET; lock.l_type = resp->r_lock_type; lock.l_len = 1; lock.l_pid = 0; rc = fcntl(fno[resp->r_fpos], cmd, &lock); if (rc == -1) { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_strcpy(errdetail, "Unhop failed"); array_sprintf(badtoken, "%s %ld", str_lock_type(resp->r_lock_type), lock.l_start); break; } else resp->r_status = STATUS_GRANTED; } if (resp->r_status != STATUS_GRANTED) { lock.l_whence = SEEK_SET; lock.l_type = F_UNLCK; lock.l_start = resp->r_start; lock.l_len = resp->r_length; lock.l_pid = 0; rc = fcntl(fno[resp->r_fpos], cmd, &lock); if (rc == -1) { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_strcpy(errdetail, "Unhop Unlock failed"); array_sprintf(badtoken, "%llu %llu", resp->r_start, resp->r_length); } } } void do_unlock(struct response *resp) { int rc; struct flock lock; int cmd = -1; if (resp->r_fpos != 0 && fno[resp->r_fpos] == 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } /* If this fpos has a blocking lock, cancel it. */ cancel_work(resp); switch (lock_mode[resp->r_fpos]) { case LOCK_MODE_POSIX: cmd = F_SETLK; break; case LOCK_MODE_OFD: cmd = F_OFD_SETLK; break; } lock.l_whence = SEEK_SET; lock.l_type = resp->r_lock_type; lock.l_start = resp->r_start; lock.l_len = resp->r_length; lock.l_pid = 0; rc = fcntl(fno[resp->r_fpos], cmd, &lock); if (rc == -1) { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_strcpy(errdetail, "Unlock failed"); array_sprintf(badtoken, "%llu %llu", resp->r_start, resp->r_length); return; } resp->r_status = STATUS_GRANTED; } void do_test(struct response *resp) { int rc; struct flock lock; int cmd = -1; if (resp->r_fpos != 0 && fno[resp->r_fpos] == 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } switch (lock_mode[resp->r_fpos]) { case LOCK_MODE_POSIX: cmd = F_GETLK; break; case LOCK_MODE_OFD: cmd = F_OFD_GETLK; break; } lock.l_whence = SEEK_SET; lock.l_type = resp->r_lock_type; lock.l_start = resp->r_start; lock.l_len = resp->r_length; lock.l_pid = 0; fprintf(stdout, "TEST lock type %s\n", str_lock_type(lock.l_type)); rc = fcntl(fno[resp->r_fpos], cmd, &lock); if (rc == -1) { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_strcpy(errdetail, "Test failed"); array_sprintf(badtoken, "%s %llu %llu", str_lock_type(lock.l_type), resp->r_start, resp->r_length); return; } if (lock.l_type == F_UNLCK) { fprintf(stdout, "GRANTED TEST lock type %s\n", str_lock_type(lock.l_type)); resp->r_status = STATUS_GRANTED; } else { resp->r_lock_type = lock.l_type; resp->r_pid = lock.l_pid; resp->r_start = lock.l_start; resp->r_length = lock.l_len; resp->r_status = STATUS_CONFLICT; } } void do_close(struct response *resp) { int rc; if (resp->r_fpos != 0 && fno[resp->r_fpos] == 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } rc = close(fno[resp->r_fpos]); fno[resp->r_fpos] = 0; if (rc == -1) { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_strcpy(errdetail, "Close failed"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } resp->r_status = STATUS_OK; } struct test_list { struct test_list *tl_next; long long tl_start; long long tl_end; }; struct test_list *tl_head; struct test_list *tl_tail; void remove_test_list_head(void) { struct test_list *item = tl_head; if (item == NULL) fatal("Corruption in test list\n"); tl_head = item->tl_next; if (tl_tail == item) tl_tail = NULL; free(item); } void make_test_item(long long start, long long end) { struct test_list *item = malloc(sizeof(*item)); if (item == NULL) fatal("Could not allocate test list item\n"); item->tl_next = NULL; item->tl_start = start; item->tl_end = end; if (tl_head == NULL) tl_head = item; if (tl_tail != NULL) tl_tail->tl_next = item; tl_tail = item; } int list_locks(long long start, long long end, struct response *resp) { long long conf_end; struct flock lock; int rc; lock.l_whence = SEEK_SET; lock.l_type = F_WRLCK; lock.l_start = start; lock.l_pid = 0; if (end == INT64_MAX) lock.l_len = 0; else lock.l_len = end - start; rc = fcntl(fno[resp->r_fpos], F_GETLK, &lock); if (rc == -1) { resp->r_status = STATUS_ERRNO; resp->r_errno = errno; array_strcpy(errdetail, "Test failed"); array_sprintf(badtoken, "%s %llu %llu", str_lock_type(lock.l_type), resp->r_start, resp->r_length); respond(resp); return false; } /* Our test succeeded */ if (lock.l_type == F_UNLCK) return false; resp->r_status = STATUS_CONFLICT; resp->r_lock_type = lock.l_type; resp->r_pid = lock.l_pid; resp->r_start = lock.l_start; resp->r_length = lock.l_len; respond(resp); conf_end = lock_end(resp); if (lock.l_start > start) make_test_item(start, lock.l_start); if (conf_end < end) make_test_item(conf_end, end); return true; } void do_list(struct response *resp) { long long start = resp->r_start; long long length = resp->r_length; int conflict = false; if (resp->r_fpos != 0 && fno[resp->r_fpos] == 0) { resp->r_status = STATUS_ERRNO; resp->r_errno = EBADF; array_strcpy(errdetail, "Invalid file number"); array_sprintf(badtoken, "%ld", resp->r_fpos); return; } resp->r_lock_type = F_WRLCK; make_test_item(start, lock_end(resp)); while (tl_head != NULL) { conflict |= list_locks(tl_head->tl_start, tl_head->tl_end, resp); remove_test_list_head(); } if (conflict) resp->r_status = STATUS_DENIED; else resp->r_status = STATUS_AVAILABLE; resp->r_lock_type = F_WRLCK; resp->r_start = start; resp->r_length = length; } struct work_item *get_work(enum thread_type thread_type) { struct work_item *work, *poll; while (true) { /* Check poll list first */ poll = glist_first_entry(&poll_queue, struct work_item, queue); work = poll; /* If we didn't find work on the poll list or this is a polling * thread and the work on the poll list isn't due for polling * yet, then look for work on the work list. */ if (work == NULL || (thread_type == THREAD_POLL && work->next_poll > time(NULL))) { /* Check work list now */ work = glist_first_entry(&work_queue, struct work_item, queue); } /* Assign the work to ourself and remove from the queue and * return to the caller. */ if (work != NULL) { work->work_owner = thread_type; work->work_thread = pthread_self(); glist_del(&work->queue); return work; } /* No work, decide what kind of wait to do. */ if (thread_type == THREAD_POLL && poll != NULL) { /* Since there is polling work to do, determine next * time to poll and wait that long for signal. */ struct timespec twait = { poll->next_poll - time(NULL), 0 }; pthread_cond_timedwait(&work_cond, &work_mutex, &twait); } else { /* Wait for signal */ pthread_cond_wait(&work_cond, &work_mutex); } } } void *worker(void *t_type) { struct work_item *work = NULL; bool complete, cancelled = false; enum thread_type thread_type = *((enum thread_type *)t_type); pthread_mutex_lock(&work_mutex); while (true) { /* Look for work */ work = get_work(thread_type); pthread_mutex_unlock(&work_mutex); assert(work != NULL); /* Do the work */ switch (work->resp.r_cmd) { case CMD_LOCKW: complete = do_lock(&work->resp, thread_type); break; default: work->resp.r_status = STATUS_ERRNO; work->resp.r_errno = EINVAL; complete = true; break; } if (complete) respond(&work->resp); pthread_mutex_lock(&work_mutex); if (complete) { /* Remember if the main thread was trying to cancel * the work. */ cancelled = work->work_owner == THREAD_CANCEL; /* Indicate this work is complete */ work->work_owner = THREAD_NONE; work->work_thread = 0; if (cancelled) { /* Signal that work has been canceled or * completed. */ pthread_cond_broadcast(&work_cond); } else { /* The work is done, and may be freed, except * that if the work was beeing cancelled by * cancel_work, then the main thread is waiting * for this thread to be done with the work * item, and thus we can not free it, * cancel_work_item will be responsible to free * the work item. This assures that the request * that caused the cancellation is blocked until * the cancellation has completed. */ free_work(work); } } else { /* This can ONLY happen for a polling thread. * Put this work back in the queue, with a new * next polling time. */ work->work_owner = THREAD_NONE; work->work_thread = 0; work->next_poll = time(NULL) + POLL_DELAY; glist_add_tail(&poll_queue, &work->queue); /* And let worker threads know there may be * more work to do. */ pthread_cond_broadcast(&work_cond); } } } int main(int argc, char **argv) { int opt; int len, rc, i; struct sigaction sigact; char *rest; int oflags = 0; int no_tag; /* Init the lists of work for each fno */ for (i = 0; i <= MAXFPOS; i++) glist_init(fno_work + i); /* Start the worker and polling threads */ for (i = 0; i <= NUM_WORKER; i++) { rc = pthread_create(&threads[i], NULL, worker, i == 0 ? &a_poller : &a_worker); if (rc == -1) { fprintf(stderr, "pthread_create failed %s\n", strerror(errno)); exit(1); } } /* Initialize the signal handling */ memset(&sigact, 0, sizeof(sigact)); sigact.sa_handler = sighandler; if (sigaction(SIGALRM, &sigact, NULL) == -1) return 1; if (sigaction(SIGPIPE, &sigact, NULL) == -1) return 1; if (sigaction(SIGIO, &sigact, NULL) == -1) return 1; input = stdin; output = stdout; /* now parsing options with getopt */ while ((opt = getopt(argc, argv, options)) != EOF) { switch (opt) { case 'c': rc = chdir(optarg); if (rc == -1) { fprintf(stderr, "Can not change dir to %s errno = %d \"%s\"\n", optarg, errno, strerror(errno)); exit(1); } break; case 'q': quiet = true; break; case 'd': duperrors = true; break; case 's': if (oflags > 7) show_usage(1, "Can not combine -x and -s\n"); oflags |= 1; script = true; array_strcpy(server, optarg); break; case 'x': if ((oflags & 7) != 0) show_usage(1, "Can not combine -x and -s/-p/-n\n"); oflags |= 8; script = true; input = fopen(optarg, "r"); if (input == NULL) fatal("Could not open %s\n", optarg); break; case 'n': if (oflags > 7) show_usage(1, "Can not combine -x and -n\n"); oflags |= 2; array_strcpy(name, optarg); break; case 'p': if (oflags > 7) show_usage(1, "Can not combine -x and -p\n"); oflags |= 4; array_strcpy(portstr, optarg); port = atoi(optarg); break; case '?': case 'h': default: /* display the help */ show_usage(0, "Help\n"); } } if (oflags > 0 && oflags < 7) show_usage(1, "Must specify -s, -p, and -n together\n"); if (oflags == 7) openserver(); while (1) { len = readln(input, line, sizeof(line)); if (len < 0) { if (script) fatal("End of file on input\n"); else break; } else { struct response resp; bool complete = true; lno++; memset(&resp, 0, sizeof(resp)); rest = SkipWhite(line, REQUIRES_MORE, "Invalid line"); /* Skip totally blank line */ if (rest == NULL) continue; if (script && !quiet) fprintf(stdout, "%s\n", rest); /* If line doesn't start with a tag, that's ok */ no_tag = (!isdigit(*rest) && (*rest != '$') && (*rest != '-')); /* Parse request into response structure */ rest = parse_request(rest, &resp, no_tag); if (rest == NULL) { resp.r_status = STATUS_ERRNO; resp.r_errno = errno; } else { /* Make sure default status is ok */ resp.r_status = STATUS_OK; if (*rest != '\0' && *rest != '#') fprintf_stderr( "Command line not consumed, rest=\"%s\"\n", rest); switch (resp.r_cmd) { case CMD_OPEN: do_open(&resp); break; case CMD_CLOSE: do_close(&resp); break; case CMD_LOCKW: complete = do_lock(&resp, THREAD_MAIN); break; case CMD_LOCK: complete = do_lock(&resp, THREAD_MAIN); break; case CMD_UNLOCK: do_unlock(&resp); break; case CMD_TEST: do_test(&resp); break; case CMD_LIST: do_list(&resp); break; case CMD_HOP: do_hop(&resp); break; case CMD_UNHOP: do_unhop(&resp); break; case CMD_SEEK: do_seek(&resp); break; case CMD_READ: do_read(&resp); break; case CMD_WRITE: do_write(&resp); break; case CMD_ALARM: do_alarm(&resp); break; case CMD_FORK: complete = do_fork(&resp, oflags == 7); break; case CMD_HELLO: case CMD_COMMENT: case CMD_QUIT: resp.r_status = STATUS_OK; break; case NUM_COMMANDS: fprintf_stderr("Invalid command %s\n", line); continue; } } if (complete) respond(&resp); if (resp.r_cmd == CMD_QUIT) exit(0); } } return 0; } ���������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/multilock.h�����������������������������������������������������0000664�0000000�0000000�00000022750�14737566223�0021711�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* SPDX-License-Identifier: LGPL-3.0-or-later */ /* * Copyright IBM Corporation, 2012 * Contributor: Frank Filz <ffilzlnx@mindspring.com> * * * This software is a server that implements the NFS protocol. * * * This program 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 3 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 * 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 * * */ #ifndef _MULTILOCK_H #define _MULTILOCK_H #include <stdio.h> #include <stdlib.h> #include <limits.h> #include <unistd.h> #include <string.h> #include <strings.h> #include <errno.h> #include <ctype.h> #include <signal.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/socket.h> #include <sys/select.h> #include <netinet/in.h> #include <netdb.h> #include <time.h> #include <stdbool.h> #include <sys/param.h> #define MAXSTR 1024 #define MAXDATA MAX(PATH_MAX, MAXSTR + 1) /* Define the maximum request/response */ #define MAXXFER (MAXDATA + MAXSTR * 3) #define MAXFPOS 16 /* If not otherwise defined, define OFD locks */ #ifndef F_OFD_GETLK #define F_OFD_GETLK 36 #endif #ifndef F_OFD_SETLK #define F_OFD_SETLK 37 #endif #ifndef F_OFD_SETLKW #define F_OFD_SETLKW 38 #endif enum lock_mode { LOCK_MODE_POSIX = 0, LOCK_MODE_OFD = 1, }; int readln(FILE *in, char *buf, int buflen); struct response; struct command_def; struct client; struct token { const char *t_name; int t_len; int t_value; }; /* Commands to Client */ /* Responses use the same strings */ enum commands { CMD_OPEN, CMD_CLOSE, CMD_LOCKW, CMD_LOCK, CMD_UNLOCK, CMD_TEST, CMD_LIST, CMD_HOP, CMD_UNHOP, CMD_SEEK, CMD_READ, CMD_WRITE, CMD_COMMENT, CMD_ALARM, CMD_HELLO, CMD_FORK, CMD_QUIT, NUM_COMMANDS }; enum requires_more { REQUIRES_MORE, REQUIRES_NO_MORE, REQUIRES_EITHER, }; struct command_def { const char *cmd_name; int cmd_len; }; struct client { struct client *c_next; struct client *c_prev; int c_socket; struct sockaddr c_addr; char c_name[MAXSTR + 1]; FILE *c_input; FILE *c_output; int c_refcount; }; enum status { STATUS_OK, STATUS_AVAILABLE, STATUS_GRANTED, STATUS_DENIED, STATUS_DEADLOCK, STATUS_CONFLICT, STATUS_CANCELED, STATUS_COMPLETED, STATUS_ERRNO, STATUS_PARSE_ERROR, STATUS_ERROR /* must be last */ }; extern char errdetail[MAXSTR * 2 + 1]; extern char badtoken[MAXSTR + 1]; extern struct client *client_list; extern FILE *input; extern FILE *output; extern bool script; extern bool quiet; extern bool duperrors; extern bool strict; extern bool error_is_fatal; extern bool syntax; extern long lno; extern long global_tag; struct response; long get_global_tag(bool increment); #define array_strcpy(dest, src) \ do { \ strncpy(dest, src, sizeof(dest) - 1); \ dest[sizeof(dest) - 1] = '\0'; \ } while (0) #define array_strncpy(dest, src, len) \ do { \ if (len >= sizeof(dest)) \ len = sizeof(dest) - 1; \ \ memcpy(dest, src, len); \ dest[len] = '\0'; \ } while (0) #define array_sprintf(buf, fmt, args...) \ do { \ int left = sizeof(buf); \ int lx = snprintf(buf, left, fmt, ##args); \ \ left -= lx; \ } while (0) #define sprint_left(buf, left, fmt, args...) \ do { \ int lx = snprintf(buf, left, fmt, ##args); \ buf += lx; \ left -= lx; \ } while (0) #define fprintf_stderr(fmt, args...) \ do { \ if (duperrors && output != NULL) \ fprintf(output, fmt, ##args); \ fprintf(stderr, fmt, ##args); \ } while (0) #define fatal(str, args...) \ do { \ fprintf_stderr(str, ##args); \ fprintf_stderr("FAIL\n"); \ if (output != NULL) \ fflush(output); \ fflush(stderr); \ exit(1); \ } while (0) #define show_usage(ret, fmt, args...) \ do { \ fprintf_stderr(fmt, ##args); \ fprintf_stderr("%s", usage); \ fflush(stderr); \ fflush(stdout); \ exit(ret); \ } while (0) char *get_command(char *line, enum commands *cmd); char *get_tag(char *line, struct response *resp, int required, enum requires_more requires_more); char *get_rq_tag(char *line, struct response *req, int required, enum requires_more requires_more); char *get_long(char *line, long *value, enum requires_more requires_more, const char *invalid); char *get_unsignedlonglong(char *line, long long *value, enum requires_more requires_more, const char *invalid); char *get_fpos(char *line, long *fpos, enum requires_more requires_more); char *get_rdata(char *line, struct response *resp, int max, enum requires_more requires_more); char *get_lock_type(char *line, int *type); char *get_client(char *line, struct client **pclient, bool create, enum requires_more requires_more); char *get_token(char *line, char **token, int *len, bool optional, enum requires_more requires_more, const char *invalid); char *get_token_value(char *line, int *value, struct token *tokens, bool optional, enum requires_more requires_more, const char *invalid); char *get_status(char *line, struct response *resp); char *get_open_opts(char *line, long *fpos, int *flags, int *mode, int *lock_mode); char *parse_response(char *line, struct response *resp); char *parse_request(char *line, struct response *req, int no_tag); char *get_on_off(char *line, bool *value); char *SkipWhite(char *line, enum requires_more requires_more, const char *who); void respond(struct response *resp); const char *str_lock_type(int type); void sprintf_resp(char *line, int size, const char *lead, struct response *resp); void sprintf_req(char *line, int size, const char *lead, struct response *req); void send_cmd(struct response *req); const char *str_status(enum status status); const char *str_read_write_flags(int flags); enum status parse_status(char *str, int len); void free_response(struct response *resp, struct response **list); void free_client(struct client *client); int compare_responses(struct response *expected, struct response *received); void add_response(struct response *resp, struct response **list); struct response *check_expected_responses(struct response *expected_responses, struct response *client_resp); struct response { struct response *r_next; struct response *r_prev; struct client *r_client; enum commands r_cmd; enum status r_status; long r_tag; long r_fpos; long r_fno; long r_secs; unsigned long long r_start; unsigned long long r_length; long r_pid; int r_lock_type; int r_flags; int r_mode; long r_errno; /** * @brief complex data for a request/response * * OPEN - name of the file to open * READ - data read (response) * WRITE - date to write (request) * COMMENT - the string * HELLO - name of the client * FORK - name of the client */ char r_data[MAXDATA]; char r_original[MAXXFER]; }; extern struct command_def commands[NUM_COMMANDS + 1]; /* Command forms * * General format * tag cmd [options] - tag is numeric sequence * tag OPEN fpos {ro|wo|rw} [create] filename * tag CLOSE fpos * tag LOCK fpos type start length - type is READ or WRITE * tag LOCKW fpos type start length * tag UNLOCK fpos start length * tag TEST fpos type start length * tag LIST fpos start length * tag SEEK fpos start * tag READ fpos length * tag WRITE fpos "string" * tag COMMENT "string" * tag ALARM seconds * tag HELLO "name" (command ignored, really just a response to server) * tag QUIT (tag is optional, if not present, tag = -1) */ /* Response forms * * tag cmd ERRNO value "string" - for all commands, result was an error * tag OPEN OK fpos fd * tag CLOSE OK fpos * tag LOCK GRANTED fpos type start length * tag LOCK DENIED fpos type start length * tag LOCKW GRANTED fpos type start length * tag LOCKW CANCELED fpos type start length * tag UNLOCK GRANTED fpos type start length * tag TEST GRANTED fpos type start length * tag TEST CONFLICT fpos pid type start length * tag LIST GRANTED fpos start length (returned if no locks to list) * tag LIST DENIED fpos start length (returned if list had locks) * tag LIST CONFLICT fpos pid type start length (returned for each lock in * list) * tag SEEK OK fpos * tag READ OK fpos len "data" * tag WRITE OK fpos len * tag COMMENT OK "string" * tag ALARM OK seconds * tag ALARM CANCELED remain * tag ALARM COMPLETED * 0 HELLO OK "name" * tag QUIT OK */ #endif /* _MULTILOCK_H */ ������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/���������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0022232�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/blocking_lock_ofd����������������������������������0000664�0000000�0000000�00000002027�14737566223�0025606�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Test blocking locks grant notification for read # # Take a write lock from loader 1 # Try to take a blocking lock from loader 2 # Release the lock from loader 1 # Verify loader 2 received the lock # Release the lock from loader 2 # Test from loader 3 for the list of locks and expect not to see any lock CLIENTS c1 c2 c3 OK c1 OPEN 1 rw create OFD testFile OK c2 OPEN 1 rw OFD testFile OK c3 OPEN 1 rw OFD testFile # full range GRANTED c1 LOCK 1 write 0 0 OK c1 ALARM 10 c2 $G LOCKW 1 write 0 0 c1 $R UNLOCK 1 0 0 { EXPECT c1 $R UNLOCK GRANTED 1 unlock 0 0 EXPECT c2 $G LOCKW GRANTED 1 write 0 0 } c1 * ALARM 0 EXPECT c1 * ALARM CANCELED * EXPECT c1 * ALARM OK * GRANTED c2 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 # byte range GRANTED c1 LOCK 1 write 10 10 OK c1 ALARM 10 c2 $G LOCKW 1 write 10 20 c1 $R UNLOCK 1 0 0 { EXPECT c1 $R UNLOCK GRANTED 1 unlock 0 0 EXPECT c2 $G LOCKW GRANTED 1 write 10 20 } c1 * ALARM 0 EXPECT c1 * ALARM CANCELED * EXPECT c1 * ALARM OK * GRANTED c2 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 QUIT ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/blocking_lock_posix��������������������������������0000664�0000000�0000000�00000002035�14737566223�0026177�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Test blocking locks grant notification for read # # Take a write lock from loader 1 # Try to take a blocking lock from loader 2 # Release the lock from loader 1 # Verify loader 2 received the lock # Release the lock from loader 2 # Test from loader 3 for the list of locks and expect not to see any lock CLIENTS c1 c2 c3 OK c1 OPEN 1 rw create POSIX testFile OK c2 OPEN 1 rw POSIX testFile OK c3 OPEN 1 rw POSIX testFile # full range GRANTED c1 LOCK 1 write 0 0 OK c1 ALARM 10 c2 $G LOCKW 1 write 0 0 c1 $R UNLOCK 1 0 0 { EXPECT c1 $R UNLOCK GRANTED 1 unlock 0 0 EXPECT c2 $G LOCKW GRANTED 1 write 0 0 } c1 * ALARM 0 EXPECT c1 * ALARM CANCELED * EXPECT c1 * ALARM OK * GRANTED c2 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 # byte range GRANTED c1 LOCK 1 write 10 10 OK c1 ALARM 10 c2 $G LOCKW 1 write 10 20 c1 $R UNLOCK 1 0 0 { EXPECT c1 $R UNLOCK GRANTED 1 unlock 0 0 EXPECT c2 $G LOCKW GRANTED 1 write 10 20 } c1 * ALARM 0 EXPECT c1 * ALARM CANCELED * EXPECT c1 * ALARM OK * GRANTED c2 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 QUIT ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/deadlock�������������������������������������������0000664�0000000�0000000�00000006442�14737566223�0023731�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # Copyright IBM Corporation, 2012 # Contributor: Frank Filz <ffilz@us.ibm.com> # # # This software is a server that implements the NFS protocol. # # # This program 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 3 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 # 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 # need three clients for three way deadlock, but it doesn't work, so only # two clients #CLIENTS c1 c2 c3 CLIENTS c1 c2 OK c1 OPEN 1 rw create deadlock.file OK c2 OPEN 1 rw deadlock.file #OK c3 OPEN 1 rw deadlock.file #=============================================================================== # test 8 Test deadlock condition: owner-1 acquires read lock on 0-7, owner-2 # acquires read lock on 8-15, owner-1 requests write lock on 8-15 # (blocks), owner-2 requests write lock on 0-7 (deadlock (test case # should see EDEADLK return from lock call)) #=============================================================================== # The above description isn't quite right GRANTED c1 LOCK 1 read 0 100 GRANTED c2 LOCK 1 write 200 100 c2 $L LOCKW 1 write 0 100 DEADLOCK c1 LOCKW 1 read 200 100 DENIED c1 LOCK 1 read 200 100 c1 $U UNLOCK 1 0 100 { EXPECT c2 $L LOCKW GRANTED 1 write 0 100 EXPECT c1 $U UNLOCK GRANTED 1 unlock 0 100 } SLEEP 1 GRANTED c2 UNLOCK 1 0 100 #=============================================================================== # test 9 Complex Deadlock: owner-1 acquires read lock on 0-7, owner-2 acquires # read lock on 8-15, owner-3 acquires read lock on 16-23, owner-1 requests write # lock on 16-23 (blocks), owner-2 requests write-lock on 0-7 (blocks), owner-3 # requests write lock on 8-15 (deadlock). #=============================================================================== # Three way deadlock is not detected by kernel on totally local QUIT GRANTED c1 LOCK 1 read 0 8 GRANTED c2 LOCK 1 read 8 8 GRANTED c3 LOCK 1 read 16 8 c1 $L LOCKW 1 write 16 8 c2 $M LOCKW 1 write 0 8 SLEEP 1 DEADLOCK c3 LOCKW 1 write 8 8 c1 $U UNLOCK 1 0 8 { EXPECT c2 $M LOCKW GRANTED 1 write 0 8 EXPECT c1 $U UNLOCK GRANTED 1 unlock 0 8 } SLEEP 1 GRANTED c1 UNLOCK 1 0 8 c3 $U UNLOCK 1 16 8 { EXPECT c2 $L LOCKW GRANTED 1 write 16 8 EXPECT c1 $U UNLOCK GRANTED 1 unlock 16 8 } SLEEP 1 GRANTED c2 UNLOCK 1 16 8 GRANTED c2 UNLOCK 1 8 8 QUIT������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/design_tests���������������������������������������0000664�0000000�0000000�00000023536�14737566223�0024661�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # Copyright IBM Corporation, 2012 # Contributor: Frank Filz <ffilz@us.ibm.com> # # # This software is a server that implements the NFS protocol. # # # This program 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 3 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 # 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 # Tests from locking design CLIENTS c1 c2 OK c1 OPEN 1 rw create design.file OK c2 OPEN 1 rw design.file #=============================================================================== # test 1 owner-1 acquires a read lock, owner-2 acquires a read lock # (expect success) #=============================================================================== GRANTED c1 LOCK 1 read 100 100 GRANTED c2 LOCK 1 read 100 100 GRANTED c1 UNLOCK 1 100 100 GRANTED c2 UNLOCK 1 100 100 #=============================================================================== # test 2 owner-1 acquires a read lock, owner-2 requests a write lock (expect # 2nd lock to block) #=============================================================================== GRANTED c1 LOCK 1 read 200 100 DENIED c2 LOCK 1 write 200 100 c2 $L LOCKW 1 write 200 100 SLEEP 1 c1 $U UNLOCK 1 200 100 { EXPECT c2 $L LOCKW GRANTED 1 write 200 100 EXPECT c1 $U UNLOCK GRANTED 1 unlock 200 100 } SLEEP 1 GRANTED c2 UNLOCK 1 200 100 #=============================================================================== # test 3 owner-1 acquires a write lock, owner-2 requests a read lock (expect # 2nd lock to block) #=============================================================================== GRANTED c1 LOCK 1 write 300 100 DENIED c2 LOCK 1 read 300 100 c2 $L LOCKW 1 read 300 100 SLEEP 1 c1 $U UNLOCK 1 300 100 { EXPECT c2 $L LOCKW GRANTED 1 read 300 100 EXPECT c1 $U UNLOCK GRANTED 1 unlock 300 100 } SLEEP 1 GRANTED c2 UNLOCK 1 300 100 #=============================================================================== # test 4 owner-1 acquires a write lock, owner-2 requests a write lock (expect # 2nd lock to block) #=============================================================================== GRANTED c1 LOCK 1 write 300 100 DENIED c2 LOCK 1 write 300 100 c2 $L LOCKW 1 write 300 100 SLEEP 1 c1 $U UNLOCK 1 300 100 { EXPECT c2 $L LOCKW GRANTED 1 write 300 100 EXPECT c1 $U UNLOCK GRANTED 1 unlock 300 100 } SLEEP 1 GRANTED c2 UNLOCK 1 300 100 #=============================================================================== # test 5 Test lock splitting: acquire lock on range 0-15, release 0-7 (expect # to see 8-15 still locked), release 10-11 (expect to see 8-9 and 12-15 # still locked), release 14-15 (test to see 12-13 still locked) acquire # 16-31, acquire 32-63, release 30-33 (test to see 12-13, 16-29, 34-63 # still locked), release 0-EOF (represented as offset 0, length 0, # test to see all locks released) #=============================================================================== GRANTED c1 LOCK 1 read 0 16 DENIED c2 LOCK 1 write 0 16 GRANTED c1 UNLOCK 1 0 8 DENIED c2 LOCK 1 write 8 8 GRANTED c2 LOCK 1 write 0 8 GRANTED c2 UNLOCK 1 0 8 GRANTED c1 UNLOCK 1 10 2 DENIED c2 LOCK 1 write 8 2 DENIED c2 LOCK 1 write 12 4 GRANTED c2 LOCK 1 read 8 2 GRANTED c2 UNLOCK 1 8 2 GRANTED c1 UNLOCK 1 14 2 DENIED c2 LOCK 1 write 8 2 DENIED c2 LOCK 1 write 12 2 GRANTED c2 LOCK 1 write 14 0 GRANTED c2 UNLOCK 1 14 0 GRANTED c1 LOCK 1 read 16 16 DENIED c2 LOCK 1 write 16 16 GRANTED c2 LOCK 1 write 14 2 GRANTED c2 LOCK 1 write 32 0 GRANTED c2 UNLOCK 1 14 2 GRANTED c2 UNLOCK 1 32 0 GRANTED c1 LOCK 1 read 32 32 DENIED c2 LOCK 1 write 32 32 GRANTED c1 UNLOCK 1 30 4 DENIED c2 LOCK 1 write 12 2 DENIED c2 LOCK 1 write 16 14 DENIED c2 LOCK 1 write 34 30 GRANTED c2 LOCK 1 write 30 4 GRANTED c2 UNLOCK 1 30 4 GRANTED c1 UNLOCK 1 0 0 GRANTED c2 LOCK 1 write 0 0 GRANTED c2 UNLOCK 1 0 0 # test 5a try to combine different types of locks GRANTED c1 LOCK 1 write 0 4 GRANTED c1 LOCK 1 write 8 4 GRANTED c1 LOCK 1 read 4 4 DENIED c2 LOCK 1 write 0 4 DENIED c2 LOCK 1 write 4 4 DENIED c2 LOCK 1 write 8 4 DENIED c2 LOCK 1 write 0 12 GRANTED c2 LOCK 1 read 4 4 GRANTED c1 UNLOCK 1 0 0 GRANTED c2 UNLOCK 1 0 0 #=============================================================================== # test 6 acquire whole file lock one byte at a time in sequence 0-0, 2-2, 1-1, # 4-4, 3-3 (will verify no leaks in lock resources by combining locks - # there should never be more than two locks existing at once). Note that # this test will want to set some reasonable maximum since acquiring a # whole file lock on a 2 GB or larger file one byte at a time might take # forever. If this test passes without crashing Ganesha, Ganesha probably # isn't leaking locks... (we can also enable debug to verify - long term # we will need some objective ways for FVT to determine if Ganesha is # leaking memory) #=============================================================================== GRANTED c1 HOP 1 read 0 10000 DENIED c2 LOCK 1 write 0 10000 GRANTED c1 UNHOP 1 0 10000 GRANTED c1 HOP 1 write 0 10000 DENIED c2 LOCK 1 read 0 10000 GRANTED c1 UNHOP 1 0 10000 #=============================================================================== # test 7 Open and close several files, perform some lock TEST operations on # several files, acquire locks on several files, close files (some with # locks still active). #=============================================================================== # test currently not implemented because TEST operation is unreliable # also see split_lock test #=============================================================================== # test 8 Test deadlock condition: owner-1 acquires read lock on 0-7, owner-2 # acquires read lock on 8-15, owner-1 requests write lock on 8-15 # (blocks), owner-2 requests write lock on 0-7 (deadlock (test case # should see EDEADLK return from lock call)) #=============================================================================== # test kept separate as deadlock due to possibility test may not work for # all possible combinations of how c1 and c2 are run #=============================================================================== # test 9 Complex Deadlock: owner-1 acquires read lock on 0-7, owner-2 acquires # read lock on 8-15, owner-3 acquires read lock on 16-23, owner-1 requests write # lock on 16-23 (blocks), owner-2 requests write-lock on 0-7 (blocks), owner-3 # requests write lock on 8-15 (deadlock). #=============================================================================== # test kept separate as deadlock due to possibility test may not work for # all possible combinations of how c1 and c2 are run, also requires 3rd client. #=============================================================================== # test 10 Owner-1 acquires a read lock on 0-3, owner-2 acquires a write lock on # 4-7, owner-1 has two threads, owner-1 thread-1 requests a read lock on 4-7 # (which blocks), owner-1 thread-2 closes the file descriptor (test that all # owner-1 locks are released and the blocked lock is cleaned up) (this test is # only applicable to NFS v3) #=============================================================================== # Test not implemented, ml_posix_client is single threaded #=============================================================================== # test 11 owner-1 acquires a read lock on 0-3, owner-2 acquires a read lock on # 0-3, owner-1 does a test for read lock on 0-3 (should succeed), owner-1 does # a test for a write lock on 0-3 (should fail) #=============================================================================== # test currently not implemented because TEST operation is unreliable #=============================================================================== # test 12 owner-1 acquires a read lock on 0-3, owner-2 acquires a write lock on # 4-7, owner-1 does a test for a read lock on 0-3 (should succeed) #=============================================================================== # test currently not implemented because TEST operation is unreliable ������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/downgrade_blocking_lock_alarm_ofd������������������0000664�0000000�0000000�00000002127�14737566223�0031015�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Downgrade + notification # # Take a write lock from loader 1 # Try to take a non-blocking read lock from loader 2, expect to fail # Try to take a blocking read lock from loader 2 # Take a read lock from loader 1 (Lock downgrade) # Expect loader 2 to get the lock in a timely manner # Release the lock from loader 1 # Release the lock from loader 2 # Test from loader 3 for the list of locks and expect not to see any lock CLIENTS c1 c2 OK c1 OPEN 1 rw create OFD testFile OK c2 OPEN 1 rw OFD testFile # full range GRANTED c1 LOCK 1 write 0 0 DENIED c2 LOCK 1 read 0 0 OK c1 ALARM 10 c2 $G LOCKW 1 read 0 0 GRANTED c1 LOCK 1 read 0 0 { EXPECT c2 $G LOCKW GRANTED 1 read 0 0 } c1 * ALARM 0 EXPECT c1 * ALARM CANCELED * EXPECT c1 * ALARM OK * GRANTED c1 UNLOCK 1 0 0 GRANTED c2 UNLOCK 1 0 0 # byte range GRANTED c1 LOCK 1 write 10 10 DENIED c2 LOCK 1 read 10 10 OK c1 ALARM 10 c2 $G LOCKW 1 read 10 10 GRANTED c1 LOCK 1 read 10 10 { EXPECT c2 $G LOCKW GRANTED 1 read 10 10 } c1 * ALARM 0 EXPECT c1 * ALARM CANCELED * EXPECT c1 * ALARM OK * GRANTED c1 UNLOCK 1 0 0 GRANTED c2 UNLOCK 1 0 0 QUIT �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/downgrade_blocking_lock_alarm_posix����������������0000664�0000000�0000000�00000002133�14737566223�0031404�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Downgrade + notification # # Take a write lock from loader 1 # Try to take a non-blocking read lock from loader 2, expect to fail # Try to take a blocking read lock from loader 2 # Take a read lock from loader 1 (Lock downgrade) # Expect loader 2 to get the lock in a timely manner # Release the lock from loader 1 # Release the lock from loader 2 # Test from loader 3 for the list of locks and expect not to see any lock CLIENTS c1 c2 OK c1 OPEN 1 rw create POSIX testFile OK c2 OPEN 1 rw POSIX testFile # full range GRANTED c1 LOCK 1 write 0 0 DENIED c2 LOCK 1 read 0 0 OK c1 ALARM 10 c2 $G LOCKW 1 read 0 0 GRANTED c1 LOCK 1 read 0 0 { EXPECT c2 $G LOCKW GRANTED 1 read 0 0 } c1 * ALARM 0 EXPECT c1 * ALARM CANCELED * EXPECT c1 * ALARM OK * GRANTED c1 UNLOCK 1 0 0 GRANTED c2 UNLOCK 1 0 0 # byte range GRANTED c1 LOCK 1 write 10 10 DENIED c2 LOCK 1 read 10 10 OK c1 ALARM 10 c2 $G LOCKW 1 read 10 10 GRANTED c1 LOCK 1 read 10 10 { EXPECT c2 $G LOCKW GRANTED 1 read 10 10 } c1 * ALARM 0 EXPECT c1 * ALARM CANCELED * EXPECT c1 * ALARM OK * GRANTED c1 UNLOCK 1 0 0 GRANTED c2 UNLOCK 1 0 0 QUIT �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/downgrade_lock_ofd���������������������������������0000664�0000000�0000000�00000001553�14737566223�0025773�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Downgrade a lock # # Take a write lock from loader 1 # Try to take a read lock from loader 2, expect to fail # Take a read lock from loader 1 (Lock downgrade) # Try to take a read lock from loader 2, expect to succeed # Release the lock from loader 1 # Release the lock from loader 2 # Test from loader 3 for the list of locks and expect not to see any lock CLIENTS c1 c2 c3 OK c1 OPEN 1 rw create OFD testFile OK c2 OPEN 1 rw OFD testFile OK c3 OPEN 1 rw OFD testFile # full range GRANTED c1 LOCK 1 write 0 0 DENIED c2 LOCK 1 read 0 0 GRANTED c1 LOCK 1 read 0 0 GRANTED c2 LOCK 1 read 0 0 GRANTED c1 UNLOCK 1 0 0 GRANTED c2 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 # byte range GRANTED c1 LOCK 1 write 10 10 DENIED c2 LOCK 1 read 10 10 GRANTED c1 LOCK 1 read 10 10 GRANTED c2 LOCK 1 read 10 10 GRANTED c1 UNLOCK 1 0 0 GRANTED c2 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 QUIT �����������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/downgrade_lock_posix�������������������������������0000664�0000000�0000000�00000001561�14737566223�0026364�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Downgrade a lock # # Take a write lock from loader 1 # Try to take a read lock from loader 2, expect to fail # Take a read lock from loader 1 (Lock downgrade) # Try to take a read lock from loader 2, expect to succeed # Release the lock from loader 1 # Release the lock from loader 2 # Test from loader 3 for the list of locks and expect not to see any lock CLIENTS c1 c2 c3 OK c1 OPEN 1 rw create POSIX testFile OK c2 OPEN 1 rw POSIX testFile OK c3 OPEN 1 rw POSIX testFile # full range GRANTED c1 LOCK 1 write 0 0 DENIED c2 LOCK 1 read 0 0 GRANTED c1 LOCK 1 read 0 0 GRANTED c2 LOCK 1 read 0 0 GRANTED c1 UNLOCK 1 0 0 GRANTED c2 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 # byte range GRANTED c1 LOCK 1 write 10 10 DENIED c2 LOCK 1 read 10 10 GRANTED c1 LOCK 1 read 10 10 GRANTED c2 LOCK 1 read 10 10 GRANTED c1 UNLOCK 1 0 0 GRANTED c2 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 QUIT �����������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/fork�����������������������������������������������0000664�0000000�0000000�00000002664�14737566223�0023126�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # Copyright IBM Corporation, 2012 # Contributor: Frank Filz <ffilz@us.ibm.com> # # # This software is a server that implements the NFS protocol. # # # This program 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 3 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 # 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 # CLIENTS client1 FORK client1 client2 OK client1 OPEN 1 rw create lock1 OK client2 OPEN 1 rw lock1 GRANTED client1 LOCK 1 write 0 100 GRANTED client2 LOCK 1 write 100 100 # Now test the locks client1 $ TEST 1 read 0 100 EXPECT client1 $ TEST GRANTED 1 read 0 100 client1 $ TEST 1 read 100 100 EXPECT client1 $ TEST CONFLICT 1 * write 100 100 client2 $ TEST 1 read 0 100 EXPECT client2 $ TEST CONFLICT 1 * write 0 100 client2 $ TEST 1 read 100 100 EXPECT client2 $ TEST GRANTED 1 read 100 100 OK client2 QUIT OK client1 QUIT QUIT ����������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/hoptest��������������������������������������������0000664�0000000�0000000�00000002317�14737566223�0023646�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # Copyright IBM Corporation, 2012 # Contributor: Frank Filz <ffilz@us.ibm.com> # # # This software is a server that implements the NFS protocol. # # # This program 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 3 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 # 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 # This test case verifies the operation of the HOP and UNHOP commands CLIENTS c1 c2 OK c1 OPEN 1 rw create hoptest.file OK c2 OPEN 1 rw hoptest.file GRANTED c1 HOP 1 read 0 1000 c2 $ LIST 1 0 0 EXPECT c2 $ LIST CONFLICT 1 * read 0 1000 EXPECT c2 $ LIST DENIED 1 0 0 GRANTED c1 UNHOP 1 0 1000 QUIT �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/lock_splitting_ofd���������������������������������0000664�0000000�0000000�00000003105�14737566223�0026031�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Lock splitting # Take a write lock from loader 1 with range (example 0 - 30) # Try to take a read lock anywhere within that range from loader 2, expect to fail # Release a range within the range from loader 1 (example 10-19) # Try to take read locks within the ranges that are still locked from loader 2, expect to fail # Try to take a read lock within the range that was freed from loader 2, expect to succeed # Test from loader 3 for the list of locks and expect to see the whole range as locked # Release the lock from loader 1 # Release the lock from loader 2 # Test from loader 3 for the list of locks and expect not to see any lock CLIENTS c1 c2 c3 OK c1 OPEN 1 rw create OFD testFile OK c2 OPEN 1 rw OFD testFile OK c3 OPEN 1 rw OFD testFile # Splitting write lock GRANTED c1 LOCK 1 write 0 40 DENIED c2 LOCK 1 read 0 40 GRANTED c1 UNLOCK 1 10 20 DENIED c2 LOCK 1 read 0 10 GRANTED c2 LOCK 1 read 15 5 c3 $L LIST 1 0 0 { EXPECT c3 $L LIST CONFLICT 1 * write 0 10 EXPECT c3 $L LIST CONFLICT 1 * write 30 10 EXPECT c3 $L LIST CONFLICT 1 * read 15 5 } EXPECT c3 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 GRANTED c2 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 # Splitting read lock GRANTED c1 LOCK 1 read 0 40 DENIED c2 LOCK 1 write 0 40 GRANTED c1 UNLOCK 1 10 20 DENIED c2 LOCK 1 write 0 10 GRANTED c2 LOCK 1 write 15 5 c3 $L LIST 1 0 0 { EXPECT c3 $L LIST CONFLICT 1 * read 0 10 EXPECT c3 $L LIST CONFLICT 1 * read 30 10 EXPECT c3 $L LIST CONFLICT 1 * write 15 5 } EXPECT c3 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 GRANTED c2 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 QUIT �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/lock_splitting_posix�������������������������������0000664�0000000�0000000�00000003113�14737566223�0026422�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Lock splitting # Take a write lock from loader 1 with range (example 0 - 30) # Try to take a read lock anywhere within that range from loader 2, expect to fail # Release a range within the range from loader 1 (example 10-19) # Try to take read locks within the ranges that are still locked from loader 2, expect to fail # Try to take a read lock within the range that was freed from loader 2, expect to succeed # Test from loader 3 for the list of locks and expect to see the whole range as locked # Release the lock from loader 1 # Release the lock from loader 2 # Test from loader 3 for the list of locks and expect not to see any lock CLIENTS c1 c2 c3 OK c1 OPEN 1 rw create POSIX testFile OK c2 OPEN 1 rw POSIX testFile OK c3 OPEN 1 rw POSIX testFile # Splitting write lock GRANTED c1 LOCK 1 write 0 40 DENIED c2 LOCK 1 read 0 40 GRANTED c1 UNLOCK 1 10 20 DENIED c2 LOCK 1 read 0 10 GRANTED c2 LOCK 1 read 15 5 c3 $L LIST 1 0 0 { EXPECT c3 $L LIST CONFLICT 1 * write 0 10 EXPECT c3 $L LIST CONFLICT 1 * write 30 10 EXPECT c3 $L LIST CONFLICT 1 * read 15 5 } EXPECT c3 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 GRANTED c2 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 # Splitting read lock GRANTED c1 LOCK 1 read 0 40 DENIED c2 LOCK 1 write 0 40 GRANTED c1 UNLOCK 1 10 20 DENIED c2 LOCK 1 write 0 10 GRANTED c2 LOCK 1 write 15 5 c3 $L LIST 1 0 0 { EXPECT c3 $L LIST CONFLICT 1 * read 0 10 EXPECT c3 $L LIST CONFLICT 1 * read 30 10 EXPECT c3 $L LIST CONFLICT 1 * write 15 5 } EXPECT c3 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 GRANTED c2 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 QUIT �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/read_lock_two_clients_ofd��������������������������0000664�0000000�0000000�00000002445�14737566223�0027347�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Take locks from two loaders - read locks # # Take a read lock from loader 1 # Try to take an overlapping read lock from loader 2 and expect to succeed # Test from loader 3 for the list of locks expecting to see the ranges of both locks # Release the lock that was taken from loader 1 # Test from loader 3 for the list of locks expecting to see only the range of loader 2 # Release the lock that was taken from loader 2 # Test from loader 3 for the list of locks and expect not to see any locks CLIENTS c1 c2 c3 OK c1 OPEN 1 rw create OFD testFile OK c2 OPEN 1 rw OFD testFile OK c3 OPEN 1 rw OFD testFile # full range GRANTED c1 LOCK 1 read 0 0 GRANTED c2 LOCK 1 read 0 0 c3 $L LIST 1 0 0 EXPECT c3 $L LIST CONFLICT 1 * read 0 0 EXPECT c3 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 c3 $L LIST 1 0 0 EXPECT c3 $L LIST CONFLICT 1 * read 0 0 EXPECT c3 $L LIST DENIED 1 0 0 GRANTED c2 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 # byte range GRANTED c1 LOCK 1 read 0 100 GRANTED c2 LOCK 1 read 50 100 c3 $L LIST 1 0 0 { EXPECT c3 $L LIST CONFLICT 1 * read 0 100 EXPECT c3 $L LIST CONFLICT 1 * read 50 100 } EXPECT c3 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 100 c3 $L LIST 1 0 0 EXPECT c3 $L LIST CONFLICT 1 * read 50 100 EXPECT c3 $L LIST DENIED 1 0 0 GRANTED c2 UNLOCK 1 50 100 AVAILABLE c3 LIST 1 0 0 QUIT ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/read_lock_two_clients_posix������������������������0000664�0000000�0000000�00000002453�14737566223�0027740�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Take locks from two loaders - read locks # # Take a read lock from loader 1 # Try to take an overlapping read lock from loader 2 and expect to succeed # Test from loader 3 for the list of locks expecting to see the ranges of both locks # Release the lock that was taken from loader 1 # Test from loader 3 for the list of locks expecting to see only the range of loader 2 # Release the lock that was taken from loader 2 # Test from loader 3 for the list of locks and expect not to see any locks CLIENTS c1 c2 c3 OK c1 OPEN 1 rw create POSIX testFile OK c2 OPEN 1 rw POSIX testFile OK c3 OPEN 1 rw POSIX testFile # full range GRANTED c1 LOCK 1 read 0 0 GRANTED c2 LOCK 1 read 0 0 c3 $L LIST 1 0 0 EXPECT c3 $L LIST CONFLICT 1 * read 0 0 EXPECT c3 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 c3 $L LIST 1 0 0 EXPECT c3 $L LIST CONFLICT 1 * read 0 0 EXPECT c3 $L LIST DENIED 1 0 0 GRANTED c2 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 # byte range GRANTED c1 LOCK 1 read 0 100 GRANTED c2 LOCK 1 read 50 100 c3 $L LIST 1 0 0 { EXPECT c3 $L LIST CONFLICT 1 * read 0 100 EXPECT c3 $L LIST CONFLICT 1 * read 50 100 } EXPECT c3 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 100 c3 $L LIST 1 0 0 EXPECT c3 $L LIST CONFLICT 1 * read 50 100 EXPECT c3 $L LIST DENIED 1 0 0 GRANTED c2 UNLOCK 1 50 100 AVAILABLE c3 LIST 1 0 0 QUIT ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/remote_run_example���������������������������������0000775�0000000�0000000�00000001765�14737566223�0026063�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # Copyright IBM Corporation, 2012 # Contributor: Frank Filz <ffilz@us.ibm.com> # # # This software is a server that implements the NFS protocol. # # # This program 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 3 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 # 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 ssh 192.168.0.107 /usr/src/testing/ml_posix_client -s 192.168.0.103 -c /mnt -n c2 -p 2051 �����������nfs-ganesha-6.5/src/tools/multilock/sample_tests/script1��������������������������������������������0000664�0000000�0000000�00000002600�14737566223�0023540�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # Copyright IBM Corporation, 2012 # Contributor: Frank Filz <ffilz@us.ibm.com> # # # This software is a server that implements the NFS protocol. # # # This program 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 3 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 # 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 # simple script to test LIST command { # wait for 2 clients to start in any order EXPECT c1 * HELLO OK c1 EXPECT c2 * HELLO OK c2 } OK c1 OPEN 1 rw temp.out OK c2 OPEN 1 rw temp.out GRANTED c1 LOCK 1 write 10 20 GRANTED c1 LOCK 1 read 100 10 c2 $L LIST 1 0 0 { # Wait for two locks in any order EXPECT c2 $L LIST CONFLICT 1 * write 10 20 EXPECT c2 $L LIST CONFLICT 1 * read 100 10 } # List finished with a DENIED if any locks present EXPECT c2 $L LIST DENIED 1 0 0 QUIT ��������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/script2��������������������������������������������0000664�0000000�0000000�00000015505�14737566223�0023551�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # Copyright IBM Corporation, 2012 # Contributor: Frank Filz <ffilz@us.ibm.com> # # # This software is a server that implements the NFS protocol. # # # This program 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 3 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 # 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 # demonstrate issues on GPFS with Ganesha and async blocking locks # # script requires running ml_posix_client on two NFS clients and # two GPFS nodes. client1 should mount the GPFS file system via # node1 and client2 from node2. # CLIENTS client1 client2 node1 node2 # open file on all clients and nodes OK client1 OPEN 1 rw create script2.file OK client2 OPEN 1 rw script2.file OK node1 OPEN 1 rw script2.file OK node2 OPEN 1 rw script2.file # get some locks GRANTED client1 LOCK 1 read 100 10 GRANTED client1 LOCK 1 write 150 10 GRANTED client2 LOCK 1 read 200 10 GRANTED client2 LOCK 1 write 250 10 GRANTED node1 LOCK 1 read 300 10 GRANTED node1 LOCK 1 write 350 10 GRANTED node2 LOCK 1 read 400 10 GRANTED node2 LOCK 1 write 450 10 # now test the locks with TEST from client1 client1 $ TEST 1 write 200 10 # test lock from client2 EXPECT client1 $ TEST CONFLICT 1 * read 200 10 client1 $ TEST 1 write 250 10 # test lock from client2 EXPECT client1 $ TEST CONFLICT 1 * write 250 10 client1 $ TEST 1 write 300 10 # test lock from node1 EXPECT client1 $ TEST CONFLICT 1 * read 300 10 client1 $ TEST 1 write 350 10 # test lock from node1 EXPECT client1 $ TEST CONFLICT 1 * write 350 10 client1 $ TEST 1 write 400 10 # test lock from node2 EXPECT client1 $ TEST CONFLICT 1 * read 400 10 client1 $ TEST 1 write 450 10 # test lock from node2 EXPECT client1 $ TEST CONFLICT 1 * write 450 10 # now test the locks with TEST from client2 client2 $ TEST 1 write 100 10 # test lock from client1 EXPECT client2 $ TEST CONFLICT 1 * read 100 10 client2 $ TEST 1 write 150 10 # test lock from client1 EXPECT client2 $ TEST CONFLICT 1 * write 150 10 client2 $ TEST 1 write 300 10 # test lock from node1 EXPECT client2 $ TEST CONFLICT 1 * read 300 10 client2 $ TEST 1 write 350 10 # test lock from node1 EXPECT client2 $ TEST CONFLICT 1 * write 350 10 client2 $ TEST 1 write 400 10 # test lock from node2 EXPECT client2 $ TEST CONFLICT 1 * read 400 10 client2 $ TEST 1 write 450 10 # test lock from node2 EXPECT client2 $ TEST CONFLICT 1 * write 450 10 # now test the locks with TEST from node1 node1 $ TEST 1 write 100 10 # test lock from client1 EXPECT node1 $ TEST CONFLICT 1 * read 100 10 node1 $ TEST 1 write 150 10 # test lock from client1 EXPECT node1 $ TEST CONFLICT 1 * write 150 10 node1 $ TEST 1 write 200 10 # test lock from client2 EXPECT node1 $ TEST CONFLICT 1 * read 200 10 node1 $ TEST 1 write 250 10 # test lock from client2 EXPECT node1 $ TEST CONFLICT 1 * write 250 10 node1 $ TEST 1 write 400 10 # test lock from node2 EXPECT node1 $ TEST CONFLICT 1 * read 400 10 node1 $ TEST 1 write 450 10 # test lock from node2 EXPECT node1 $ TEST CONFLICT 1 * write 450 10 # now test the locks with TEST from node2 node2 $ TEST 1 write 100 10 # test lock from client1 EXPECT node2 $ TEST CONFLICT 1 * read 100 10 node2 $ TEST 1 write 150 10 # test lock from client1 EXPECT node2 $ TEST CONFLICT 1 * write 150 10 node2 $ TEST 1 write 200 10 # test lock from client2 EXPECT node2 $ TEST CONFLICT 1 * read 200 10 node2 $ TEST 1 write 250 10 # test lock from client2 EXPECT node2 $ TEST CONFLICT 1 * write 250 10 node2 $ TEST 1 write 300 10 # test lock from node1 EXPECT node2 $ TEST CONFLICT 1 * read 300 10 node2 $ TEST 1 write 350 10 # test lock from node1 EXPECT node2 $ TEST CONFLICT 1 * write 350 10 # now test the locks with TEST from client1 (test larger lock) client1 $ TEST 1 write 200 12 # test lock from client2 EXPECT client1 $ TEST CONFLICT 1 * read 200 10 client1 $ TEST 1 write 250 12 # test lock from client2 EXPECT client1 $ TEST CONFLICT 1 * write 250 10 client1 $ TEST 1 write 300 12 # test lock from node1 EXPECT client1 $ TEST CONFLICT 1 * read 300 10 client1 $ TEST 1 write 350 12 # test lock from node1 EXPECT client1 $ TEST CONFLICT 1 * write 350 10 client1 $ TEST 1 write 400 12 # test lock from node2 EXPECT client1 $ TEST CONFLICT 1 * read 400 10 client1 $ TEST 1 write 450 12 # test lock from node2 EXPECT client1 $ TEST CONFLICT 1 * write 450 10 # now test the locks with TEST from client2 (test larger lock) client2 $ TEST 1 write 100 12 # test lock from client1 EXPECT client2 $ TEST CONFLICT 1 * read 100 10 client2 $ TEST 1 write 150 12 # test lock from client1 EXPECT client2 $ TEST CONFLICT 1 * write 150 10 client2 $ TEST 1 write 300 12 # test lock from node1 EXPECT client2 $ TEST CONFLICT 1 * read 300 10 client2 $ TEST 1 write 350 12 # test lock from node1 EXPECT client2 $ TEST CONFLICT 1 * write 350 10 client2 $ TEST 1 write 400 12 # test lock from node2 EXPECT client2 $ TEST CONFLICT 1 * read 400 10 client2 $ TEST 1 write 450 12 # test lock from node2 EXPECT client2 $ TEST CONFLICT 1 * write 450 10 # now test the locks with TEST from node1 (test larger lock) node1 $ TEST 1 write 100 12 # test lock from client1 EXPECT node1 $ TEST CONFLICT 1 * read 100 10 node1 $ TEST 1 write 150 12 # test lock from client1 EXPECT node1 $ TEST CONFLICT 1 * write 150 10 node1 $ TEST 1 write 200 12 # test lock from client2 EXPECT node1 $ TEST CONFLICT 1 * read 200 10 node1 $ TEST 1 write 250 12 # test lock from client2 EXPECT node1 $ TEST CONFLICT 1 * write 250 10 node1 $ TEST 1 write 400 12 # test lock from node2 EXPECT node1 $ TEST CONFLICT 1 * read 400 10 node1 $ TEST 1 write 450 12 # test lock from node2 EXPECT node1 $ TEST CONFLICT 1 * write 450 10 # now test the locks with TEST from node2 (test larger lock) node2 $ TEST 1 write 100 12 # test lock from client1 EXPECT node2 $ TEST CONFLICT 1 * read 100 10 node2 $ TEST 1 write 150 12 # test lock from client1 EXPECT node2 $ TEST CONFLICT 1 * write 150 10 node2 $ TEST 1 write 200 12 # test lock from client2 EXPECT node2 $ TEST CONFLICT 1 * read 200 10 node2 $ TEST 1 write 250 12 # test lock from client2 EXPECT node2 $ TEST CONFLICT 1 * write 250 10 node2 $ TEST 1 write 300 12 # test lock from node1 EXPECT node2 $ TEST CONFLICT 1 * read 300 10 node2 $ TEST 1 write 350 12 # test lock from node1 EXPECT node2 $ TEST CONFLICT 1 * write 350 10 # quit and exit (all clients, closing all files, releasing all locks) QUIT�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/simple_block���������������������������������������0000664�0000000�0000000�00000002476�14737566223�0024631�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # Copyright IBM Corporation, 2012 # Contributor: Frank Filz <ffilz@us.ibm.com> # # # This software is a server that implements the NFS protocol. # # # This program 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 3 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 # 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 # This test case attempts a simple blocking lock situation CLIENTS c1 c2 OK c1 OPEN 1 rw create temp.out OK c2 OPEN 1 rw temp.out GRANTED c1 LOCK 1 write 0 10 # wait for conflicting lock c2 $G LOCKW 1 write 0 10 sleep 2 # now release c1's lock so c2 can get lock c1 $R UNLOCK 1 0 10 { EXPECT c1 $R UNLOCK GRANTED 1 unlock 0 10 EXPECT c2 $G LOCKW GRANTED 1 write 0 10 } sleep 1 GRANTED c2 UNLOCK 1 0 10 QUIT ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/split_lock�����������������������������������������0000664�0000000�0000000�00000015745�14737566223�0024334�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # Copyright IBM Corporation, 2012 # Contributor: Frank Filz <ffilz@us.ibm.com> # # # This software is a server that implements the NFS protocol. # # # This program 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 3 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 # 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 # This test case tests a variety of split lock situations CLIENTS c1 c2 # open a bunch of files and then close them # repeat several times OK c1 OPEN 1 rw create splitfile.1.file OK c1 OPEN 2 rw create splitfile.2.file OK c1 OPEN 3 rw create splitfile.3.file OK c1 OPEN 4 rw create splitfile.4.file OK c1 OPEN 5 rw create splitfile.5.file OK c1 OPEN 6 rw create splitfile.6.file OK c1 OPEN 7 rw create splitfile.7.file OK c1 OPEN 8 rw create splitfile.8.file OK c1 OPEN 9 rw create splitfile.9.file OK c1 OPEN 10 rw create splitfile.10.file OK c1 CLOSE 1 OK c1 CLOSE 2 OK c1 CLOSE 3 OK c1 CLOSE 4 OK c1 CLOSE 5 OK c1 CLOSE 6 OK c1 CLOSE 7 OK c1 CLOSE 8 OK c1 CLOSE 9 OK c1 CLOSE 10 OK c1 OPEN 1 rw splitfile.1.file OK c1 OPEN 2 rw splitfile.2.file OK c1 OPEN 3 rw splitfile.3.file OK c1 OPEN 4 rw splitfile.4.file OK c1 OPEN 5 rw splitfile.5.file OK c1 OPEN 6 rw splitfile.6.file OK c1 OPEN 7 rw splitfile.7.file OK c1 OPEN 8 rw splitfile.8.file OK c1 OPEN 9 rw splitfile.9.file OK c1 OPEN 10 rw splitfile.10.file OK c1 CLOSE 1 OK c1 CLOSE 2 OK c1 CLOSE 3 OK c1 CLOSE 4 OK c1 CLOSE 5 OK c1 CLOSE 6 OK c1 CLOSE 7 OK c1 CLOSE 8 OK c1 CLOSE 9 OK c1 CLOSE 10 OK c1 OPEN 1 rw splitfile.1.file OK c1 OPEN 2 rw splitfile.2.file OK c1 OPEN 3 rw splitfile.3.file OK c1 OPEN 4 rw splitfile.4.file OK c1 OPEN 5 rw splitfile.5.file OK c1 OPEN 6 rw splitfile.6.file OK c1 OPEN 7 rw splitfile.7.file OK c1 OPEN 8 rw splitfile.8.file OK c1 OPEN 9 rw splitfile.9.file OK c1 OPEN 10 rw splitfile.10.file OK c1 CLOSE 1 OK c1 CLOSE 2 OK c1 CLOSE 3 OK c1 CLOSE 4 OK c1 CLOSE 5 OK c1 CLOSE 6 OK c1 CLOSE 7 OK c1 CLOSE 8 OK c1 CLOSE 9 OK c1 CLOSE 10 OK c1 OPEN 1 rw splitfile.1.file OK c1 OPEN 2 rw splitfile.2.file OK c1 OPEN 3 rw splitfile.3.file OK c1 OPEN 4 rw splitfile.4.file OK c1 OPEN 5 rw splitfile.5.file OK c1 OPEN 6 rw splitfile.6.file OK c1 OPEN 7 rw splitfile.7.file OK c1 OPEN 8 rw splitfile.8.file OK c1 OPEN 9 rw splitfile.9.file OK c1 OPEN 10 rw splitfile.10.file OK c1 CLOSE 1 OK c1 CLOSE 2 OK c1 CLOSE 3 OK c1 CLOSE 4 OK c1 CLOSE 5 OK c1 CLOSE 6 OK c1 CLOSE 7 OK c1 CLOSE 8 OK c1 CLOSE 9 OK c1 CLOSE 10 # Now open a bunch of files and get locks on them OK c1 OPEN 1 rw splitfile.1.file OK c1 OPEN 2 rw splitfile.2.file OK c1 OPEN 3 rw splitfile.3.file OK c1 OPEN 4 rw splitfile.4.file OK c1 OPEN 5 rw splitfile.5.file OK c1 OPEN 6 rw splitfile.6.file OK c1 OPEN 7 rw splitfile.7.file OK c1 OPEN 8 rw splitfile.8.file OK c1 OPEN 9 rw splitfile.9.file OK c1 OPEN 10 rw splitfile.10.file OK c2 OPEN 1 rw splitfile.1.file OK c2 OPEN 2 rw splitfile.2.file OK c2 OPEN 3 rw splitfile.3.file OK c2 OPEN 4 rw splitfile.4.file OK c2 OPEN 5 rw splitfile.5.file OK c2 OPEN 6 rw splitfile.6.file OK c2 OPEN 7 rw splitfile.7.file OK c2 OPEN 8 rw splitfile.8.file OK c2 OPEN 9 rw splitfile.9.file OK c2 OPEN 10 rw splitfile.10.file GRANTED c1 LOCK 1 write 0 0 GRANTED c1 LOCK 2 write 0 0 GRANTED c1 LOCK 3 write 0 0 GRANTED c1 LOCK 4 write 0 0 GRANTED c1 LOCK 5 write 0 0 GRANTED c1 LOCK 6 write 0 0 GRANTED c1 LOCK 7 write 0 0 GRANTED c1 LOCK 8 write 0 0 GRANTED c1 LOCK 9 write 0 0 GRANTED c1 LOCK 10 write 0 0 # Now probe the locks DENIED c2 LOCK 1 write 0 0 DENIED c2 LOCK 2 write 0 0 DENIED c2 LOCK 3 write 0 0 DENIED c2 LOCK 4 write 0 0 DENIED c2 LOCK 5 write 0 0 DENIED c2 LOCK 6 write 0 0 DENIED c2 LOCK 7 write 0 0 DENIED c2 LOCK 8 write 0 0 DENIED c2 LOCK 9 write 0 0 DENIED c2 LOCK 10 write 0 0 #now release the locks GRANTED c1 UNLOCK 1 0 0 GRANTED c1 UNLOCK 2 0 0 GRANTED c1 UNLOCK 3 0 0 GRANTED c1 UNLOCK 4 0 0 GRANTED c1 UNLOCK 5 0 0 GRANTED c1 UNLOCK 6 0 0 GRANTED c1 UNLOCK 7 0 0 GRANTED c1 UNLOCK 8 0 0 GRANTED c1 UNLOCK 9 0 0 GRANTED c1 UNLOCK 10 0 0 # and close the files OK c1 CLOSE 1 OK c1 CLOSE 2 OK c1 CLOSE 3 OK c1 CLOSE 4 OK c1 CLOSE 5 OK c1 CLOSE 6 OK c1 CLOSE 7 OK c1 CLOSE 8 OK c1 CLOSE 9 OK c1 CLOSE 10 OK c2 CLOSE 1 OK c2 CLOSE 2 OK c2 CLOSE 3 OK c2 CLOSE 4 OK c2 CLOSE 5 OK c2 CLOSE 6 OK c2 CLOSE 7 OK c2 CLOSE 8 OK c2 CLOSE 9 OK c2 CLOSE 10 # Now do the split lock tests OK c1 OPEN 1 rw splitfile.1.file OK c2 OPEN 1 rw splitfile.1.file GRANTED c1 LOCK 1 read 0 4 GRANTED c1 LOCK 1 read 8 4 GRANTED c1 LOCK 1 read 16 4 GRANTED c1 LOCK 1 read 24 4 c2 $L LIST 1 0 0 { EXPECT c2 $L LIST CONFLICT 1 * read 0 4 EXPECT c2 $L LIST CONFLICT 1 * read 8 4 EXPECT c2 $L LIST CONFLICT 1 * read 16 4 EXPECT c2 $L LIST CONFLICT 1 * read 24 4 } EXPECT c2 $L LIST DENIED 1 0 0 DENIED c2 LOCK 1 write 0 4 DENIED c2 LOCK 1 write 8 4 DENIED c2 LOCK 1 write 16 4 DENIED c2 LOCK 1 write 24 4 GRANTED c2 LOCK 1 write 4 4 GRANTED c2 LOCK 1 write 12 4 GRANTED c2 LOCK 1 write 20 4 GRANTED c2 LOCK 1 write 28 0 GRANTED c2 LOCK 1 read 0 4 GRANTED c2 LOCK 1 read 8 4 GRANTED c2 LOCK 1 read 16 4 GRANTED c2 LOCK 1 read 24 4 GRANTED c1 UNLOCK 1 0 0 c1 $L LIST 1 0 0 { EXPECT c1 $L LIST CONFLICT 1 * read 0 4 EXPECT c1 $L LIST CONFLICT 1 * read 8 4 EXPECT c1 $L LIST CONFLICT 1 * read 16 4 EXPECT c1 $L LIST CONFLICT 1 * read 24 4 EXPECT c1 $L LIST CONFLICT 1 * write 4 4 EXPECT c1 $L LIST CONFLICT 1 * write 12 4 EXPECT c1 $L LIST CONFLICT 1 * write 20 4 EXPECT c1 $L LIST CONFLICT 1 * write 28 0 } EXPECT c1 $L LIST DENIED 1 0 0 GRANTED c2 UNLOCK 1 0 0 AVAILABLE c1 LIST 1 0 0 AVAILABLE c2 LIST 1 0 0 # now test splitting a lock GRANTED c1 LOCK 1 read 0 16 GRANTED c1 LOCK 1 write 32 16 DENIED c2 LOCK 1 write 0 16 DENIED c2 LOCK 1 write 32 16 c2 $L LIST 1 0 0 { EXPECT c2 $L LIST CONFLICT 1 * read 0 16 EXPECT c2 $L LIST CONFLICT 1 * write 32 16 } EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 4 4 GRANTED c1 UNLOCK 1 36 4 c2 $L LIST 1 0 0 { EXPECT c2 $L LIST CONFLICT 1 * read 0 4 EXPECT c2 $L LIST CONFLICT 1 * read 8 8 EXPECT c2 $L LIST CONFLICT 1 * write 32 4 EXPECT c2 $L LIST CONFLICT 1 * write 40 8 } EXPECT c2 $L LIST DENIED 1 0 0 # now combine locks GRANTED c1 LOCK 1 read 4 4 GRANTED c1 LOCK 1 write 36 4 c2 $L LIST 1 0 0 { EXPECT c2 $L LIST CONFLICT 1 * read 0 16 EXPECT c2 $L LIST CONFLICT 1 * write 32 16 } EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c1 LIST 1 0 0 AVAILABLE c2 LIST 1 0 0 QUIT ���������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/split_lock_3���������������������������������������0000664�0000000�0000000�00000016123�14737566223�0024545�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # Copyright IBM Corporation, 2012 # Contributor: Frank Filz <ffilz@us.ibm.com> # # # This software is a server that implements the NFS protocol. # # # This program 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 3 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 # 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 # This test is similar to split_lock, but it uses a 3rd client to perform all LIST operations # The 3rd client should be run the server since LIST does not work with Linux client. CLIENTS c1 c2 c3 # open a bunch of files and then close them # repeat several times OK c1 OPEN 1 rw create splitfile.1.file OK c1 OPEN 2 rw create splitfile.2.file OK c1 OPEN 3 rw create splitfile.3.file OK c1 OPEN 4 rw create splitfile.4.file OK c1 OPEN 5 rw create splitfile.5.file OK c1 OPEN 6 rw create splitfile.6.file OK c1 OPEN 7 rw create splitfile.7.file OK c1 OPEN 8 rw create splitfile.8.file OK c1 OPEN 9 rw create splitfile.9.file OK c1 OPEN 10 rw create splitfile.10.file OK c1 CLOSE 1 OK c1 CLOSE 2 OK c1 CLOSE 3 OK c1 CLOSE 4 OK c1 CLOSE 5 OK c1 CLOSE 6 OK c1 CLOSE 7 OK c1 CLOSE 8 OK c1 CLOSE 9 OK c1 CLOSE 10 OK c1 OPEN 1 rw splitfile.1.file OK c1 OPEN 2 rw splitfile.2.file OK c1 OPEN 3 rw splitfile.3.file OK c1 OPEN 4 rw splitfile.4.file OK c1 OPEN 5 rw splitfile.5.file OK c1 OPEN 6 rw splitfile.6.file OK c1 OPEN 7 rw splitfile.7.file OK c1 OPEN 8 rw splitfile.8.file OK c1 OPEN 9 rw splitfile.9.file OK c1 OPEN 10 rw splitfile.10.file OK c1 CLOSE 1 OK c1 CLOSE 2 OK c1 CLOSE 3 OK c1 CLOSE 4 OK c1 CLOSE 5 OK c1 CLOSE 6 OK c1 CLOSE 7 OK c1 CLOSE 8 OK c1 CLOSE 9 OK c1 CLOSE 10 OK c1 OPEN 1 rw splitfile.1.file OK c1 OPEN 2 rw splitfile.2.file OK c1 OPEN 3 rw splitfile.3.file OK c1 OPEN 4 rw splitfile.4.file OK c1 OPEN 5 rw splitfile.5.file OK c1 OPEN 6 rw splitfile.6.file OK c1 OPEN 7 rw splitfile.7.file OK c1 OPEN 8 rw splitfile.8.file OK c1 OPEN 9 rw splitfile.9.file OK c1 OPEN 10 rw splitfile.10.file OK c1 CLOSE 1 OK c1 CLOSE 2 OK c1 CLOSE 3 OK c1 CLOSE 4 OK c1 CLOSE 5 OK c1 CLOSE 6 OK c1 CLOSE 7 OK c1 CLOSE 8 OK c1 CLOSE 9 OK c1 CLOSE 10 OK c1 OPEN 1 rw splitfile.1.file OK c1 OPEN 2 rw splitfile.2.file OK c1 OPEN 3 rw splitfile.3.file OK c1 OPEN 4 rw splitfile.4.file OK c1 OPEN 5 rw splitfile.5.file OK c1 OPEN 6 rw splitfile.6.file OK c1 OPEN 7 rw splitfile.7.file OK c1 OPEN 8 rw splitfile.8.file OK c1 OPEN 9 rw splitfile.9.file OK c1 OPEN 10 rw splitfile.10.file OK c1 CLOSE 1 OK c1 CLOSE 2 OK c1 CLOSE 3 OK c1 CLOSE 4 OK c1 CLOSE 5 OK c1 CLOSE 6 OK c1 CLOSE 7 OK c1 CLOSE 8 OK c1 CLOSE 9 OK c1 CLOSE 10 # Now open a bunch of files and get locks on them OK c1 OPEN 1 rw splitfile.1.file OK c1 OPEN 2 rw splitfile.2.file OK c1 OPEN 3 rw splitfile.3.file OK c1 OPEN 4 rw splitfile.4.file OK c1 OPEN 5 rw splitfile.5.file OK c1 OPEN 6 rw splitfile.6.file OK c1 OPEN 7 rw splitfile.7.file OK c1 OPEN 8 rw splitfile.8.file OK c1 OPEN 9 rw splitfile.9.file OK c1 OPEN 10 rw splitfile.10.file OK c2 OPEN 1 rw splitfile.1.file OK c2 OPEN 2 rw splitfile.2.file OK c2 OPEN 3 rw splitfile.3.file OK c2 OPEN 4 rw splitfile.4.file OK c2 OPEN 5 rw splitfile.5.file OK c2 OPEN 6 rw splitfile.6.file OK c2 OPEN 7 rw splitfile.7.file OK c2 OPEN 8 rw splitfile.8.file OK c2 OPEN 9 rw splitfile.9.file OK c2 OPEN 10 rw splitfile.10.file GRANTED c1 LOCK 1 write 0 0 GRANTED c1 LOCK 2 write 0 0 GRANTED c1 LOCK 3 write 0 0 GRANTED c1 LOCK 4 write 0 0 GRANTED c1 LOCK 5 write 0 0 GRANTED c1 LOCK 6 write 0 0 GRANTED c1 LOCK 7 write 0 0 GRANTED c1 LOCK 8 write 0 0 GRANTED c1 LOCK 9 write 0 0 GRANTED c1 LOCK 10 write 0 0 # Now probe the locks DENIED c2 LOCK 1 write 0 0 DENIED c2 LOCK 2 write 0 0 DENIED c2 LOCK 3 write 0 0 DENIED c2 LOCK 4 write 0 0 DENIED c2 LOCK 5 write 0 0 DENIED c2 LOCK 6 write 0 0 DENIED c2 LOCK 7 write 0 0 DENIED c2 LOCK 8 write 0 0 DENIED c2 LOCK 9 write 0 0 DENIED c2 LOCK 10 write 0 0 #now release the locks GRANTED c1 UNLOCK 1 0 0 GRANTED c1 UNLOCK 2 0 0 GRANTED c1 UNLOCK 3 0 0 GRANTED c1 UNLOCK 4 0 0 GRANTED c1 UNLOCK 5 0 0 GRANTED c1 UNLOCK 6 0 0 GRANTED c1 UNLOCK 7 0 0 GRANTED c1 UNLOCK 8 0 0 GRANTED c1 UNLOCK 9 0 0 GRANTED c1 UNLOCK 10 0 0 # and close the files OK c1 CLOSE 1 OK c1 CLOSE 2 OK c1 CLOSE 3 OK c1 CLOSE 4 OK c1 CLOSE 5 OK c1 CLOSE 6 OK c1 CLOSE 7 OK c1 CLOSE 8 OK c1 CLOSE 9 OK c1 CLOSE 10 OK c2 CLOSE 1 OK c2 CLOSE 2 OK c2 CLOSE 3 OK c2 CLOSE 4 OK c2 CLOSE 5 OK c2 CLOSE 6 OK c2 CLOSE 7 OK c2 CLOSE 8 OK c2 CLOSE 9 OK c2 CLOSE 10 # Now do the split lock tests OK c1 OPEN 1 rw splitfile.1.file OK c2 OPEN 1 rw splitfile.1.file OK c3 OPEN 1 rw splitfile.1.file GRANTED c1 LOCK 1 read 0 4 GRANTED c1 LOCK 1 read 8 4 GRANTED c1 LOCK 1 read 16 4 GRANTED c1 LOCK 1 read 24 4 c3 $L LIST 1 0 0 { EXPECT c3 $L LIST CONFLICT 1 * read 0 4 EXPECT c3 $L LIST CONFLICT 1 * read 8 4 EXPECT c3 $L LIST CONFLICT 1 * read 16 4 EXPECT c3 $L LIST CONFLICT 1 * read 24 4 } EXPECT c3 $L LIST DENIED 1 0 0 DENIED c2 LOCK 1 write 0 4 DENIED c2 LOCK 1 write 8 4 DENIED c2 LOCK 1 write 16 4 DENIED c2 LOCK 1 write 24 4 GRANTED c2 LOCK 1 write 4 4 GRANTED c2 LOCK 1 write 12 4 GRANTED c2 LOCK 1 write 20 4 GRANTED c2 LOCK 1 write 28 0 GRANTED c2 LOCK 1 read 0 4 GRANTED c2 LOCK 1 read 8 4 GRANTED c2 LOCK 1 read 16 4 GRANTED c2 LOCK 1 read 24 4 GRANTED c1 UNLOCK 1 0 0 c3 $L LIST 1 0 0 { EXPECT c3 $L LIST CONFLICT 1 * read 0 4 EXPECT c3 $L LIST CONFLICT 1 * read 8 4 EXPECT c3 $L LIST CONFLICT 1 * read 16 4 EXPECT c3 $L LIST CONFLICT 1 * read 24 4 EXPECT c3 $L LIST CONFLICT 1 * write 4 4 EXPECT c3 $L LIST CONFLICT 1 * write 12 4 EXPECT c3 $L LIST CONFLICT 1 * write 20 4 EXPECT c3 $L LIST CONFLICT 1 * write 28 0 } EXPECT c3 $L LIST DENIED 1 0 0 GRANTED c2 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 # now test splitting a lock GRANTED c1 LOCK 1 read 0 16 GRANTED c1 LOCK 1 write 32 16 DENIED c2 LOCK 1 write 0 16 DENIED c2 LOCK 1 write 32 16 c3 $L LIST 1 0 0 { EXPECT c3 $L LIST CONFLICT 1 * read 0 16 EXPECT c3 $L LIST CONFLICT 1 * write 32 16 } EXPECT c3 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 4 4 GRANTED c1 UNLOCK 1 36 4 c3 $L LIST 1 0 0 { EXPECT c3 $L LIST CONFLICT 1 * read 0 4 EXPECT c3 $L LIST CONFLICT 1 * read 8 8 EXPECT c3 $L LIST CONFLICT 1 * write 32 4 EXPECT c3 $L LIST CONFLICT 1 * write 40 8 } EXPECT c3 $L LIST DENIED 1 0 0 # now combine locks GRANTED c1 LOCK 1 read 4 4 GRANTED c1 LOCK 1 write 36 4 c3 $L LIST 1 0 0 { EXPECT c3 $L LIST CONFLICT 1 * read 0 16 EXPECT c3 $L LIST CONFLICT 1 * write 32 16 } EXPECT c3 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 QUIT ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/split_lock_no_list���������������������������������0000664�0000000�0000000�00000014040�14737566223�0026046�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # Copyright IBM Corporation, 2012 # Contributor: Frank Filz <ffilz@us.ibm.com> # # # This software is a server that implements the NFS protocol. # # # This program 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 3 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 # 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 # This test is similar to split_lock, but doesn't use LIST at all CLIENTS c1 c2 # open a bunch of files and then close them # repeat several times OK c1 OPEN 1 rw create splitfile.1.file OK c1 OPEN 2 rw create splitfile.2.file OK c1 OPEN 3 rw create splitfile.3.file OK c1 OPEN 4 rw create splitfile.4.file OK c1 OPEN 5 rw create splitfile.5.file OK c1 OPEN 6 rw create splitfile.6.file OK c1 OPEN 7 rw create splitfile.7.file OK c1 OPEN 8 rw create splitfile.8.file OK c1 OPEN 9 rw create splitfile.9.file OK c1 OPEN 10 rw create splitfile.10.file OK c1 CLOSE 1 OK c1 CLOSE 2 OK c1 CLOSE 3 OK c1 CLOSE 4 OK c1 CLOSE 5 OK c1 CLOSE 6 OK c1 CLOSE 7 OK c1 CLOSE 8 OK c1 CLOSE 9 OK c1 CLOSE 10 OK c1 OPEN 1 rw splitfile.1.file OK c1 OPEN 2 rw splitfile.2.file OK c1 OPEN 3 rw splitfile.3.file OK c1 OPEN 4 rw splitfile.4.file OK c1 OPEN 5 rw splitfile.5.file OK c1 OPEN 6 rw splitfile.6.file OK c1 OPEN 7 rw splitfile.7.file OK c1 OPEN 8 rw splitfile.8.file OK c1 OPEN 9 rw splitfile.9.file OK c1 OPEN 10 rw splitfile.10.file OK c1 CLOSE 1 OK c1 CLOSE 2 OK c1 CLOSE 3 OK c1 CLOSE 4 OK c1 CLOSE 5 OK c1 CLOSE 6 OK c1 CLOSE 7 OK c1 CLOSE 8 OK c1 CLOSE 9 OK c1 CLOSE 10 OK c1 OPEN 1 rw splitfile.1.file OK c1 OPEN 2 rw splitfile.2.file OK c1 OPEN 3 rw splitfile.3.file OK c1 OPEN 4 rw splitfile.4.file OK c1 OPEN 5 rw splitfile.5.file OK c1 OPEN 6 rw splitfile.6.file OK c1 OPEN 7 rw splitfile.7.file OK c1 OPEN 8 rw splitfile.8.file OK c1 OPEN 9 rw splitfile.9.file OK c1 OPEN 10 rw splitfile.10.file OK c1 CLOSE 1 OK c1 CLOSE 2 OK c1 CLOSE 3 OK c1 CLOSE 4 OK c1 CLOSE 5 OK c1 CLOSE 6 OK c1 CLOSE 7 OK c1 CLOSE 8 OK c1 CLOSE 9 OK c1 CLOSE 10 OK c1 OPEN 1 rw splitfile.1.file OK c1 OPEN 2 rw splitfile.2.file OK c1 OPEN 3 rw splitfile.3.file OK c1 OPEN 4 rw splitfile.4.file OK c1 OPEN 5 rw splitfile.5.file OK c1 OPEN 6 rw splitfile.6.file OK c1 OPEN 7 rw splitfile.7.file OK c1 OPEN 8 rw splitfile.8.file OK c1 OPEN 9 rw splitfile.9.file OK c1 OPEN 10 rw splitfile.10.file OK c1 CLOSE 1 OK c1 CLOSE 2 OK c1 CLOSE 3 OK c1 CLOSE 4 OK c1 CLOSE 5 OK c1 CLOSE 6 OK c1 CLOSE 7 OK c1 CLOSE 8 OK c1 CLOSE 9 OK c1 CLOSE 10 # Now open a bunch of files and get locks on them OK c1 OPEN 1 rw splitfile.1.file OK c1 OPEN 2 rw splitfile.2.file OK c1 OPEN 3 rw splitfile.3.file OK c1 OPEN 4 rw splitfile.4.file OK c1 OPEN 5 rw splitfile.5.file OK c1 OPEN 6 rw splitfile.6.file OK c1 OPEN 7 rw splitfile.7.file OK c1 OPEN 8 rw splitfile.8.file OK c1 OPEN 9 rw splitfile.9.file OK c1 OPEN 10 rw splitfile.10.file OK c2 OPEN 1 rw splitfile.1.file OK c2 OPEN 2 rw splitfile.2.file OK c2 OPEN 3 rw splitfile.3.file OK c2 OPEN 4 rw splitfile.4.file OK c2 OPEN 5 rw splitfile.5.file OK c2 OPEN 6 rw splitfile.6.file OK c2 OPEN 7 rw splitfile.7.file OK c2 OPEN 8 rw splitfile.8.file OK c2 OPEN 9 rw splitfile.9.file OK c2 OPEN 10 rw splitfile.10.file GRANTED c1 LOCK 1 write 0 0 GRANTED c1 LOCK 2 write 0 0 GRANTED c1 LOCK 3 write 0 0 GRANTED c1 LOCK 4 write 0 0 GRANTED c1 LOCK 5 write 0 0 GRANTED c1 LOCK 6 write 0 0 GRANTED c1 LOCK 7 write 0 0 GRANTED c1 LOCK 8 write 0 0 GRANTED c1 LOCK 9 write 0 0 GRANTED c1 LOCK 10 write 0 0 # Now probe the locks DENIED c2 LOCK 1 write 0 0 DENIED c2 LOCK 2 write 0 0 DENIED c2 LOCK 3 write 0 0 DENIED c2 LOCK 4 write 0 0 DENIED c2 LOCK 5 write 0 0 DENIED c2 LOCK 6 write 0 0 DENIED c2 LOCK 7 write 0 0 DENIED c2 LOCK 8 write 0 0 DENIED c2 LOCK 9 write 0 0 DENIED c2 LOCK 10 write 0 0 #now release the locks GRANTED c1 UNLOCK 1 0 0 GRANTED c1 UNLOCK 2 0 0 GRANTED c1 UNLOCK 3 0 0 GRANTED c1 UNLOCK 4 0 0 GRANTED c1 UNLOCK 5 0 0 GRANTED c1 UNLOCK 6 0 0 GRANTED c1 UNLOCK 7 0 0 GRANTED c1 UNLOCK 8 0 0 GRANTED c1 UNLOCK 9 0 0 GRANTED c1 UNLOCK 10 0 0 # and close the files OK c1 CLOSE 1 OK c1 CLOSE 2 OK c1 CLOSE 3 OK c1 CLOSE 4 OK c1 CLOSE 5 OK c1 CLOSE 6 OK c1 CLOSE 7 OK c1 CLOSE 8 OK c1 CLOSE 9 OK c1 CLOSE 10 OK c2 CLOSE 1 OK c2 CLOSE 2 OK c2 CLOSE 3 OK c2 CLOSE 4 OK c2 CLOSE 5 OK c2 CLOSE 6 OK c2 CLOSE 7 OK c2 CLOSE 8 OK c2 CLOSE 9 OK c2 CLOSE 10 # Now do the split lock tests OK c1 OPEN 1 rw splitfile.1.file OK c2 OPEN 1 rw splitfile.1.file GRANTED c1 LOCK 1 read 0 4 GRANTED c1 LOCK 1 read 8 4 GRANTED c1 LOCK 1 read 16 4 GRANTED c1 LOCK 1 read 24 4 DENIED c2 LOCK 1 write 0 4 DENIED c2 LOCK 1 write 8 4 DENIED c2 LOCK 1 write 16 4 DENIED c2 LOCK 1 write 24 4 GRANTED c2 LOCK 1 write 4 4 GRANTED c2 LOCK 1 write 12 4 GRANTED c2 LOCK 1 write 20 4 GRANTED c2 LOCK 1 write 28 0 GRANTED c2 LOCK 1 read 0 4 GRANTED c2 LOCK 1 read 8 4 GRANTED c2 LOCK 1 read 16 4 GRANTED c2 LOCK 1 read 24 4 GRANTED c1 UNLOCK 1 0 0 GRANTED c2 UNLOCK 1 0 0 # now test splitting a lock GRANTED c1 LOCK 1 read 0 16 GRANTED c1 LOCK 1 write 32 16 DENIED c2 LOCK 1 write 0 16 DENIED c2 LOCK 1 write 32 16 DENIED c2 LOCK 1 write 4 4 DENIED c2 LOCK 1 write 36 4 GRANTED c1 UNLOCK 1 4 4 GRANTED c1 UNLOCK 1 36 4 GRANTED c2 LOCK 1 write 4 4 GRANTED c2 LOCK 1 write 36 4 GRANTED c2 UNLOCK 1 0 0 # now combine locks GRANTED c1 LOCK 1 read 4 4 GRANTED c1 LOCK 1 write 36 4 DENIED c2 LOCK 1 write 4 4 DENIED c2 LOCK 1 write 36 4 DENIED c2 LOCK 1 write 0 16 DENIED c2 LOCK 1 write 32 16 GRANTED c1 UNLOCK 1 0 0 QUIT ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/take_128_locks_medium_range_medium_gap_ofd���������0000664�0000000�0000000�00000063147�14737566223�0032434�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Implementation of same file concurrent locks with varied ranges # # Take 128 locks on a file with medium ranges and medium gaps # medium = 1 * size.MiB # Check to see that those locks exists # Release the locks # Check to see that those locks don’t exists CLIENTS c1 c2 OK c1 OPEN 1 rw create OFD testFile OK c2 OPEN 1 rw OFD testFile GRANTED c1 LOCK 1 read 0 1048576 GRANTED c1 LOCK 1 read 2097152 3145728 GRANTED c1 LOCK 1 read 4194304 5242880 GRANTED c1 LOCK 1 read 6291456 7340032 GRANTED c1 LOCK 1 read 8388608 9437184 GRANTED c1 LOCK 1 read 10485760 11534336 GRANTED c1 LOCK 1 read 12582912 13631488 GRANTED c1 LOCK 1 read 14680064 15728640 GRANTED c1 LOCK 1 read 16777216 17825792 GRANTED c1 LOCK 1 read 18874368 19922944 GRANTED c1 LOCK 1 read 20971520 22020096 GRANTED c1 LOCK 1 read 23068672 24117248 GRANTED c1 LOCK 1 read 25165824 26214400 GRANTED c1 LOCK 1 read 27262976 28311552 GRANTED c1 LOCK 1 read 29360128 30408704 GRANTED c1 LOCK 1 read 31457280 32505856 GRANTED c1 LOCK 1 read 33554432 34603008 GRANTED c1 LOCK 1 read 35651584 36700160 GRANTED c1 LOCK 1 read 37748736 38797312 GRANTED c1 LOCK 1 read 39845888 40894464 GRANTED c1 LOCK 1 read 41943040 42991616 GRANTED c1 LOCK 1 read 44040192 45088768 GRANTED c1 LOCK 1 read 46137344 47185920 GRANTED c1 LOCK 1 read 48234496 49283072 GRANTED c1 LOCK 1 read 50331648 51380224 GRANTED c1 LOCK 1 read 52428800 53477376 GRANTED c1 LOCK 1 read 54525952 55574528 GRANTED c1 LOCK 1 read 56623104 57671680 GRANTED c1 LOCK 1 read 58720256 59768832 GRANTED c1 LOCK 1 read 60817408 61865984 GRANTED c1 LOCK 1 read 62914560 63963136 GRANTED c1 LOCK 1 read 65011712 66060288 GRANTED c1 LOCK 1 read 67108864 68157440 GRANTED c1 LOCK 1 read 69206016 70254592 GRANTED c1 LOCK 1 read 71303168 72351744 GRANTED c1 LOCK 1 read 73400320 74448896 GRANTED c1 LOCK 1 read 75497472 76546048 GRANTED c1 LOCK 1 read 77594624 78643200 GRANTED c1 LOCK 1 read 79691776 80740352 GRANTED c1 LOCK 1 read 81788928 82837504 GRANTED c1 LOCK 1 read 83886080 84934656 GRANTED c1 LOCK 1 read 85983232 87031808 GRANTED c1 LOCK 1 read 88080384 89128960 GRANTED c1 LOCK 1 read 90177536 91226112 GRANTED c1 LOCK 1 read 92274688 93323264 GRANTED c1 LOCK 1 read 94371840 95420416 GRANTED c1 LOCK 1 read 96468992 97517568 GRANTED c1 LOCK 1 read 98566144 99614720 GRANTED c1 LOCK 1 read 100663296 101711872 GRANTED c1 LOCK 1 read 102760448 103809024 GRANTED c1 LOCK 1 read 104857600 105906176 GRANTED c1 LOCK 1 read 106954752 108003328 GRANTED c1 LOCK 1 read 109051904 110100480 GRANTED c1 LOCK 1 read 111149056 112197632 GRANTED c1 LOCK 1 read 113246208 114294784 GRANTED c1 LOCK 1 read 115343360 116391936 GRANTED c1 LOCK 1 read 117440512 118489088 GRANTED c1 LOCK 1 read 119537664 120586240 GRANTED c1 LOCK 1 read 121634816 122683392 GRANTED c1 LOCK 1 read 123731968 124780544 GRANTED c1 LOCK 1 read 125829120 126877696 GRANTED c1 LOCK 1 read 127926272 128974848 GRANTED c1 LOCK 1 read 130023424 131072000 GRANTED c1 LOCK 1 read 132120576 133169152 GRANTED c1 LOCK 1 read 134217728 135266304 GRANTED c1 LOCK 1 read 136314880 137363456 GRANTED c1 LOCK 1 read 138412032 139460608 GRANTED c1 LOCK 1 read 140509184 141557760 GRANTED c1 LOCK 1 read 142606336 143654912 GRANTED c1 LOCK 1 read 144703488 145752064 GRANTED c1 LOCK 1 read 146800640 147849216 GRANTED c1 LOCK 1 read 148897792 149946368 GRANTED c1 LOCK 1 read 150994944 152043520 GRANTED c1 LOCK 1 read 153092096 154140672 GRANTED c1 LOCK 1 read 155189248 156237824 GRANTED c1 LOCK 1 read 157286400 158334976 GRANTED c1 LOCK 1 read 159383552 160432128 GRANTED c1 LOCK 1 read 161480704 162529280 GRANTED c1 LOCK 1 read 163577856 164626432 GRANTED c1 LOCK 1 read 165675008 166723584 GRANTED c1 LOCK 1 read 167772160 168820736 GRANTED c1 LOCK 1 read 169869312 170917888 GRANTED c1 LOCK 1 read 171966464 173015040 GRANTED c1 LOCK 1 read 174063616 175112192 GRANTED c1 LOCK 1 read 176160768 177209344 GRANTED c1 LOCK 1 read 178257920 179306496 GRANTED c1 LOCK 1 read 180355072 181403648 GRANTED c1 LOCK 1 read 182452224 183500800 GRANTED c1 LOCK 1 read 184549376 185597952 GRANTED c1 LOCK 1 read 186646528 187695104 GRANTED c1 LOCK 1 read 188743680 189792256 GRANTED c1 LOCK 1 read 190840832 191889408 GRANTED c1 LOCK 1 read 192937984 193986560 GRANTED c1 LOCK 1 read 195035136 196083712 GRANTED c1 LOCK 1 read 197132288 198180864 GRANTED c1 LOCK 1 read 199229440 200278016 GRANTED c1 LOCK 1 read 201326592 202375168 GRANTED c1 LOCK 1 read 203423744 204472320 GRANTED c1 LOCK 1 read 205520896 206569472 GRANTED c1 LOCK 1 read 207618048 208666624 GRANTED c1 LOCK 1 read 209715200 210763776 GRANTED c1 LOCK 1 read 211812352 212860928 GRANTED c1 LOCK 1 read 213909504 214958080 GRANTED c1 LOCK 1 read 216006656 217055232 GRANTED c1 LOCK 1 read 218103808 219152384 GRANTED c1 LOCK 1 read 220200960 221249536 GRANTED c1 LOCK 1 read 222298112 223346688 GRANTED c1 LOCK 1 read 224395264 225443840 GRANTED c1 LOCK 1 read 226492416 227540992 GRANTED c1 LOCK 1 read 228589568 229638144 GRANTED c1 LOCK 1 read 230686720 231735296 GRANTED c1 LOCK 1 read 232783872 233832448 GRANTED c1 LOCK 1 read 234881024 235929600 GRANTED c1 LOCK 1 read 236978176 238026752 GRANTED c1 LOCK 1 read 239075328 240123904 GRANTED c1 LOCK 1 read 241172480 242221056 GRANTED c1 LOCK 1 read 243269632 244318208 GRANTED c1 LOCK 1 read 245366784 246415360 GRANTED c1 LOCK 1 read 247463936 248512512 GRANTED c1 LOCK 1 read 249561088 250609664 GRANTED c1 LOCK 1 read 251658240 252706816 GRANTED c1 LOCK 1 read 253755392 254803968 GRANTED c1 LOCK 1 read 255852544 256901120 GRANTED c1 LOCK 1 read 257949696 258998272 GRANTED c1 LOCK 1 read 260046848 261095424 GRANTED c1 LOCK 1 read 262144000 263192576 GRANTED c1 LOCK 1 read 264241152 265289728 GRANTED c1 LOCK 1 read 266338304 267386880 c2 $L LIST 1 0 0 { EXPECT c2 $L LIST CONFLICT 1 * read 0 1048576 EXPECT c2 $L LIST CONFLICT 1 * read 2097152 3145728 EXPECT c2 $L LIST CONFLICT 1 * read 4194304 5242880 EXPECT c2 $L LIST CONFLICT 1 * read 6291456 7340032 EXPECT c2 $L LIST CONFLICT 1 * read 8388608 9437184 EXPECT c2 $L LIST CONFLICT 1 * read 10485760 11534336 EXPECT c2 $L LIST CONFLICT 1 * read 12582912 13631488 EXPECT c2 $L LIST CONFLICT 1 * read 14680064 15728640 EXPECT c2 $L LIST CONFLICT 1 * read 16777216 17825792 EXPECT c2 $L LIST CONFLICT 1 * read 18874368 19922944 EXPECT c2 $L LIST CONFLICT 1 * read 20971520 22020096 EXPECT c2 $L LIST CONFLICT 1 * read 23068672 24117248 EXPECT c2 $L LIST CONFLICT 1 * read 25165824 26214400 EXPECT c2 $L LIST CONFLICT 1 * read 27262976 28311552 EXPECT c2 $L LIST CONFLICT 1 * read 29360128 30408704 EXPECT c2 $L LIST CONFLICT 1 * read 31457280 32505856 EXPECT c2 $L LIST CONFLICT 1 * read 33554432 34603008 EXPECT c2 $L LIST CONFLICT 1 * read 35651584 36700160 EXPECT c2 $L LIST CONFLICT 1 * read 37748736 38797312 EXPECT c2 $L LIST CONFLICT 1 * read 39845888 40894464 EXPECT c2 $L LIST CONFLICT 1 * read 41943040 42991616 EXPECT c2 $L LIST CONFLICT 1 * read 44040192 45088768 EXPECT c2 $L LIST CONFLICT 1 * read 46137344 47185920 EXPECT c2 $L LIST CONFLICT 1 * read 48234496 49283072 EXPECT c2 $L LIST CONFLICT 1 * read 50331648 51380224 EXPECT c2 $L LIST CONFLICT 1 * read 52428800 53477376 EXPECT c2 $L LIST CONFLICT 1 * read 54525952 55574528 EXPECT c2 $L LIST CONFLICT 1 * read 56623104 57671680 EXPECT c2 $L LIST CONFLICT 1 * read 58720256 59768832 EXPECT c2 $L LIST CONFLICT 1 * read 60817408 61865984 EXPECT c2 $L LIST CONFLICT 1 * read 62914560 63963136 EXPECT c2 $L LIST CONFLICT 1 * read 65011712 66060288 EXPECT c2 $L LIST CONFLICT 1 * read 67108864 68157440 EXPECT c2 $L LIST CONFLICT 1 * read 69206016 70254592 EXPECT c2 $L LIST CONFLICT 1 * read 71303168 72351744 EXPECT c2 $L LIST CONFLICT 1 * read 73400320 74448896 EXPECT c2 $L LIST CONFLICT 1 * read 75497472 76546048 EXPECT c2 $L LIST CONFLICT 1 * read 77594624 78643200 EXPECT c2 $L LIST CONFLICT 1 * read 79691776 80740352 EXPECT c2 $L LIST CONFLICT 1 * read 81788928 82837504 EXPECT c2 $L LIST CONFLICT 1 * read 83886080 84934656 EXPECT c2 $L LIST CONFLICT 1 * read 85983232 87031808 EXPECT c2 $L LIST CONFLICT 1 * read 88080384 89128960 EXPECT c2 $L LIST CONFLICT 1 * read 90177536 91226112 EXPECT c2 $L LIST CONFLICT 1 * read 92274688 93323264 EXPECT c2 $L LIST CONFLICT 1 * read 94371840 95420416 EXPECT c2 $L LIST CONFLICT 1 * read 96468992 97517568 EXPECT c2 $L LIST CONFLICT 1 * read 98566144 99614720 EXPECT c2 $L LIST CONFLICT 1 * read 100663296 101711872 EXPECT c2 $L LIST CONFLICT 1 * read 102760448 103809024 EXPECT c2 $L LIST CONFLICT 1 * read 104857600 105906176 EXPECT c2 $L LIST CONFLICT 1 * read 106954752 108003328 EXPECT c2 $L LIST CONFLICT 1 * read 109051904 110100480 EXPECT c2 $L LIST CONFLICT 1 * read 111149056 112197632 EXPECT c2 $L LIST CONFLICT 1 * read 113246208 114294784 EXPECT c2 $L LIST CONFLICT 1 * read 115343360 116391936 EXPECT c2 $L LIST CONFLICT 1 * read 117440512 118489088 EXPECT c2 $L LIST CONFLICT 1 * read 119537664 120586240 EXPECT c2 $L LIST CONFLICT 1 * read 121634816 122683392 EXPECT c2 $L LIST CONFLICT 1 * read 123731968 124780544 EXPECT c2 $L LIST CONFLICT 1 * read 125829120 126877696 EXPECT c2 $L LIST CONFLICT 1 * read 127926272 128974848 EXPECT c2 $L LIST CONFLICT 1 * read 130023424 131072000 EXPECT c2 $L LIST CONFLICT 1 * read 132120576 133169152 EXPECT c2 $L LIST CONFLICT 1 * read 134217728 135266304 EXPECT c2 $L LIST CONFLICT 1 * read 136314880 137363456 EXPECT c2 $L LIST CONFLICT 1 * read 138412032 139460608 EXPECT c2 $L LIST CONFLICT 1 * read 140509184 141557760 EXPECT c2 $L LIST CONFLICT 1 * read 142606336 143654912 EXPECT c2 $L LIST CONFLICT 1 * read 144703488 145752064 EXPECT c2 $L LIST CONFLICT 1 * read 146800640 147849216 EXPECT c2 $L LIST CONFLICT 1 * read 148897792 149946368 EXPECT c2 $L LIST CONFLICT 1 * read 150994944 152043520 EXPECT c2 $L LIST CONFLICT 1 * read 153092096 154140672 EXPECT c2 $L LIST CONFLICT 1 * read 155189248 156237824 EXPECT c2 $L LIST CONFLICT 1 * read 157286400 158334976 EXPECT c2 $L LIST CONFLICT 1 * read 159383552 160432128 EXPECT c2 $L LIST CONFLICT 1 * read 161480704 162529280 EXPECT c2 $L LIST CONFLICT 1 * read 163577856 164626432 EXPECT c2 $L LIST CONFLICT 1 * read 165675008 166723584 EXPECT c2 $L LIST CONFLICT 1 * read 167772160 168820736 EXPECT c2 $L LIST CONFLICT 1 * read 169869312 170917888 EXPECT c2 $L LIST CONFLICT 1 * read 171966464 173015040 EXPECT c2 $L LIST CONFLICT 1 * read 174063616 175112192 EXPECT c2 $L LIST CONFLICT 1 * read 176160768 177209344 EXPECT c2 $L LIST CONFLICT 1 * read 178257920 179306496 EXPECT c2 $L LIST CONFLICT 1 * read 180355072 181403648 EXPECT c2 $L LIST CONFLICT 1 * read 182452224 183500800 EXPECT c2 $L LIST CONFLICT 1 * read 184549376 185597952 EXPECT c2 $L LIST CONFLICT 1 * read 186646528 187695104 EXPECT c2 $L LIST CONFLICT 1 * read 188743680 189792256 EXPECT c2 $L LIST CONFLICT 1 * read 190840832 191889408 EXPECT c2 $L LIST CONFLICT 1 * read 192937984 193986560 EXPECT c2 $L LIST CONFLICT 1 * read 195035136 196083712 EXPECT c2 $L LIST CONFLICT 1 * read 197132288 198180864 EXPECT c2 $L LIST CONFLICT 1 * read 199229440 200278016 EXPECT c2 $L LIST CONFLICT 1 * read 201326592 202375168 EXPECT c2 $L LIST CONFLICT 1 * read 203423744 204472320 EXPECT c2 $L LIST CONFLICT 1 * read 205520896 206569472 EXPECT c2 $L LIST CONFLICT 1 * read 207618048 208666624 EXPECT c2 $L LIST CONFLICT 1 * read 209715200 210763776 EXPECT c2 $L LIST CONFLICT 1 * read 211812352 212860928 EXPECT c2 $L LIST CONFLICT 1 * read 213909504 214958080 EXPECT c2 $L LIST CONFLICT 1 * read 216006656 217055232 EXPECT c2 $L LIST CONFLICT 1 * read 218103808 219152384 EXPECT c2 $L LIST CONFLICT 1 * read 220200960 221249536 EXPECT c2 $L LIST CONFLICT 1 * read 222298112 223346688 EXPECT c2 $L LIST CONFLICT 1 * read 224395264 225443840 EXPECT c2 $L LIST CONFLICT 1 * read 226492416 227540992 EXPECT c2 $L LIST CONFLICT 1 * read 228589568 229638144 EXPECT c2 $L LIST CONFLICT 1 * read 230686720 231735296 EXPECT c2 $L LIST CONFLICT 1 * read 232783872 233832448 EXPECT c2 $L LIST CONFLICT 1 * read 234881024 235929600 EXPECT c2 $L LIST CONFLICT 1 * read 236978176 238026752 EXPECT c2 $L LIST CONFLICT 1 * read 239075328 240123904 EXPECT c2 $L LIST CONFLICT 1 * read 241172480 242221056 EXPECT c2 $L LIST CONFLICT 1 * read 243269632 244318208 EXPECT c2 $L LIST CONFLICT 1 * read 245366784 246415360 EXPECT c2 $L LIST CONFLICT 1 * read 247463936 248512512 EXPECT c2 $L LIST CONFLICT 1 * read 249561088 250609664 EXPECT c2 $L LIST CONFLICT 1 * read 251658240 252706816 EXPECT c2 $L LIST CONFLICT 1 * read 253755392 254803968 EXPECT c2 $L LIST CONFLICT 1 * read 255852544 256901120 EXPECT c2 $L LIST CONFLICT 1 * read 257949696 258998272 EXPECT c2 $L LIST CONFLICT 1 * read 260046848 261095424 EXPECT c2 $L LIST CONFLICT 1 * read 262144000 263192576 EXPECT c2 $L LIST CONFLICT 1 * read 264241152 265289728 EXPECT c2 $L LIST CONFLICT 1 * read 266338304 267386880 } EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c2 LIST 1 0 0 GRANTED c1 LOCK 1 write 0 1048576 GRANTED c1 LOCK 1 write 2097152 3145728 GRANTED c1 LOCK 1 write 4194304 5242880 GRANTED c1 LOCK 1 write 6291456 7340032 GRANTED c1 LOCK 1 write 8388608 9437184 GRANTED c1 LOCK 1 write 10485760 11534336 GRANTED c1 LOCK 1 write 12582912 13631488 GRANTED c1 LOCK 1 write 14680064 15728640 GRANTED c1 LOCK 1 write 16777216 17825792 GRANTED c1 LOCK 1 write 18874368 19922944 GRANTED c1 LOCK 1 write 20971520 22020096 GRANTED c1 LOCK 1 write 23068672 24117248 GRANTED c1 LOCK 1 write 25165824 26214400 GRANTED c1 LOCK 1 write 27262976 28311552 GRANTED c1 LOCK 1 write 29360128 30408704 GRANTED c1 LOCK 1 write 31457280 32505856 GRANTED c1 LOCK 1 write 33554432 34603008 GRANTED c1 LOCK 1 write 35651584 36700160 GRANTED c1 LOCK 1 write 37748736 38797312 GRANTED c1 LOCK 1 write 39845888 40894464 GRANTED c1 LOCK 1 write 41943040 42991616 GRANTED c1 LOCK 1 write 44040192 45088768 GRANTED c1 LOCK 1 write 46137344 47185920 GRANTED c1 LOCK 1 write 48234496 49283072 GRANTED c1 LOCK 1 write 50331648 51380224 GRANTED c1 LOCK 1 write 52428800 53477376 GRANTED c1 LOCK 1 write 54525952 55574528 GRANTED c1 LOCK 1 write 56623104 57671680 GRANTED c1 LOCK 1 write 58720256 59768832 GRANTED c1 LOCK 1 write 60817408 61865984 GRANTED c1 LOCK 1 write 62914560 63963136 GRANTED c1 LOCK 1 write 65011712 66060288 GRANTED c1 LOCK 1 write 67108864 68157440 GRANTED c1 LOCK 1 write 69206016 70254592 GRANTED c1 LOCK 1 write 71303168 72351744 GRANTED c1 LOCK 1 write 73400320 74448896 GRANTED c1 LOCK 1 write 75497472 76546048 GRANTED c1 LOCK 1 write 77594624 78643200 GRANTED c1 LOCK 1 write 79691776 80740352 GRANTED c1 LOCK 1 write 81788928 82837504 GRANTED c1 LOCK 1 write 83886080 84934656 GRANTED c1 LOCK 1 write 85983232 87031808 GRANTED c1 LOCK 1 write 88080384 89128960 GRANTED c1 LOCK 1 write 90177536 91226112 GRANTED c1 LOCK 1 write 92274688 93323264 GRANTED c1 LOCK 1 write 94371840 95420416 GRANTED c1 LOCK 1 write 96468992 97517568 GRANTED c1 LOCK 1 write 98566144 99614720 GRANTED c1 LOCK 1 write 100663296 101711872 GRANTED c1 LOCK 1 write 102760448 103809024 GRANTED c1 LOCK 1 write 104857600 105906176 GRANTED c1 LOCK 1 write 106954752 108003328 GRANTED c1 LOCK 1 write 109051904 110100480 GRANTED c1 LOCK 1 write 111149056 112197632 GRANTED c1 LOCK 1 write 113246208 114294784 GRANTED c1 LOCK 1 write 115343360 116391936 GRANTED c1 LOCK 1 write 117440512 118489088 GRANTED c1 LOCK 1 write 119537664 120586240 GRANTED c1 LOCK 1 write 121634816 122683392 GRANTED c1 LOCK 1 write 123731968 124780544 GRANTED c1 LOCK 1 write 125829120 126877696 GRANTED c1 LOCK 1 write 127926272 128974848 GRANTED c1 LOCK 1 write 130023424 131072000 GRANTED c1 LOCK 1 write 132120576 133169152 GRANTED c1 LOCK 1 write 134217728 135266304 GRANTED c1 LOCK 1 write 136314880 137363456 GRANTED c1 LOCK 1 write 138412032 139460608 GRANTED c1 LOCK 1 write 140509184 141557760 GRANTED c1 LOCK 1 write 142606336 143654912 GRANTED c1 LOCK 1 write 144703488 145752064 GRANTED c1 LOCK 1 write 146800640 147849216 GRANTED c1 LOCK 1 write 148897792 149946368 GRANTED c1 LOCK 1 write 150994944 152043520 GRANTED c1 LOCK 1 write 153092096 154140672 GRANTED c1 LOCK 1 write 155189248 156237824 GRANTED c1 LOCK 1 write 157286400 158334976 GRANTED c1 LOCK 1 write 159383552 160432128 GRANTED c1 LOCK 1 write 161480704 162529280 GRANTED c1 LOCK 1 write 163577856 164626432 GRANTED c1 LOCK 1 write 165675008 166723584 GRANTED c1 LOCK 1 write 167772160 168820736 GRANTED c1 LOCK 1 write 169869312 170917888 GRANTED c1 LOCK 1 write 171966464 173015040 GRANTED c1 LOCK 1 write 174063616 175112192 GRANTED c1 LOCK 1 write 176160768 177209344 GRANTED c1 LOCK 1 write 178257920 179306496 GRANTED c1 LOCK 1 write 180355072 181403648 GRANTED c1 LOCK 1 write 182452224 183500800 GRANTED c1 LOCK 1 write 184549376 185597952 GRANTED c1 LOCK 1 write 186646528 187695104 GRANTED c1 LOCK 1 write 188743680 189792256 GRANTED c1 LOCK 1 write 190840832 191889408 GRANTED c1 LOCK 1 write 192937984 193986560 GRANTED c1 LOCK 1 write 195035136 196083712 GRANTED c1 LOCK 1 write 197132288 198180864 GRANTED c1 LOCK 1 write 199229440 200278016 GRANTED c1 LOCK 1 write 201326592 202375168 GRANTED c1 LOCK 1 write 203423744 204472320 GRANTED c1 LOCK 1 write 205520896 206569472 GRANTED c1 LOCK 1 write 207618048 208666624 GRANTED c1 LOCK 1 write 209715200 210763776 GRANTED c1 LOCK 1 write 211812352 212860928 GRANTED c1 LOCK 1 write 213909504 214958080 GRANTED c1 LOCK 1 write 216006656 217055232 GRANTED c1 LOCK 1 write 218103808 219152384 GRANTED c1 LOCK 1 write 220200960 221249536 GRANTED c1 LOCK 1 write 222298112 223346688 GRANTED c1 LOCK 1 write 224395264 225443840 GRANTED c1 LOCK 1 write 226492416 227540992 GRANTED c1 LOCK 1 write 228589568 229638144 GRANTED c1 LOCK 1 write 230686720 231735296 GRANTED c1 LOCK 1 write 232783872 233832448 GRANTED c1 LOCK 1 write 234881024 235929600 GRANTED c1 LOCK 1 write 236978176 238026752 GRANTED c1 LOCK 1 write 239075328 240123904 GRANTED c1 LOCK 1 write 241172480 242221056 GRANTED c1 LOCK 1 write 243269632 244318208 GRANTED c1 LOCK 1 write 245366784 246415360 GRANTED c1 LOCK 1 write 247463936 248512512 GRANTED c1 LOCK 1 write 249561088 250609664 GRANTED c1 LOCK 1 write 251658240 252706816 GRANTED c1 LOCK 1 write 253755392 254803968 GRANTED c1 LOCK 1 write 255852544 256901120 GRANTED c1 LOCK 1 write 257949696 258998272 GRANTED c1 LOCK 1 write 260046848 261095424 GRANTED c1 LOCK 1 write 262144000 263192576 GRANTED c1 LOCK 1 write 264241152 265289728 GRANTED c1 LOCK 1 write 266338304 267386880 c2 $L LIST 1 0 0 { EXPECT c2 $L LIST CONFLICT 1 * write 0 1048576 EXPECT c2 $L LIST CONFLICT 1 * write 2097152 3145728 EXPECT c2 $L LIST CONFLICT 1 * write 4194304 5242880 EXPECT c2 $L LIST CONFLICT 1 * write 6291456 7340032 EXPECT c2 $L LIST CONFLICT 1 * write 8388608 9437184 EXPECT c2 $L LIST CONFLICT 1 * write 10485760 11534336 EXPECT c2 $L LIST CONFLICT 1 * write 12582912 13631488 EXPECT c2 $L LIST CONFLICT 1 * write 14680064 15728640 EXPECT c2 $L LIST CONFLICT 1 * write 16777216 17825792 EXPECT c2 $L LIST CONFLICT 1 * write 18874368 19922944 EXPECT c2 $L LIST CONFLICT 1 * write 20971520 22020096 EXPECT c2 $L LIST CONFLICT 1 * write 23068672 24117248 EXPECT c2 $L LIST CONFLICT 1 * write 25165824 26214400 EXPECT c2 $L LIST CONFLICT 1 * write 27262976 28311552 EXPECT c2 $L LIST CONFLICT 1 * write 29360128 30408704 EXPECT c2 $L LIST CONFLICT 1 * write 31457280 32505856 EXPECT c2 $L LIST CONFLICT 1 * write 33554432 34603008 EXPECT c2 $L LIST CONFLICT 1 * write 35651584 36700160 EXPECT c2 $L LIST CONFLICT 1 * write 37748736 38797312 EXPECT c2 $L LIST CONFLICT 1 * write 39845888 40894464 EXPECT c2 $L LIST CONFLICT 1 * write 41943040 42991616 EXPECT c2 $L LIST CONFLICT 1 * write 44040192 45088768 EXPECT c2 $L LIST CONFLICT 1 * write 46137344 47185920 EXPECT c2 $L LIST CONFLICT 1 * write 48234496 49283072 EXPECT c2 $L LIST CONFLICT 1 * write 50331648 51380224 EXPECT c2 $L LIST CONFLICT 1 * write 52428800 53477376 EXPECT c2 $L LIST CONFLICT 1 * write 54525952 55574528 EXPECT c2 $L LIST CONFLICT 1 * write 56623104 57671680 EXPECT c2 $L LIST CONFLICT 1 * write 58720256 59768832 EXPECT c2 $L LIST CONFLICT 1 * write 60817408 61865984 EXPECT c2 $L LIST CONFLICT 1 * write 62914560 63963136 EXPECT c2 $L LIST CONFLICT 1 * write 65011712 66060288 EXPECT c2 $L LIST CONFLICT 1 * write 67108864 68157440 EXPECT c2 $L LIST CONFLICT 1 * write 69206016 70254592 EXPECT c2 $L LIST CONFLICT 1 * write 71303168 72351744 EXPECT c2 $L LIST CONFLICT 1 * write 73400320 74448896 EXPECT c2 $L LIST CONFLICT 1 * write 75497472 76546048 EXPECT c2 $L LIST CONFLICT 1 * write 77594624 78643200 EXPECT c2 $L LIST CONFLICT 1 * write 79691776 80740352 EXPECT c2 $L LIST CONFLICT 1 * write 81788928 82837504 EXPECT c2 $L LIST CONFLICT 1 * write 83886080 84934656 EXPECT c2 $L LIST CONFLICT 1 * write 85983232 87031808 EXPECT c2 $L LIST CONFLICT 1 * write 88080384 89128960 EXPECT c2 $L LIST CONFLICT 1 * write 90177536 91226112 EXPECT c2 $L LIST CONFLICT 1 * write 92274688 93323264 EXPECT c2 $L LIST CONFLICT 1 * write 94371840 95420416 EXPECT c2 $L LIST CONFLICT 1 * write 96468992 97517568 EXPECT c2 $L LIST CONFLICT 1 * write 98566144 99614720 EXPECT c2 $L LIST CONFLICT 1 * write 100663296 101711872 EXPECT c2 $L LIST CONFLICT 1 * write 102760448 103809024 EXPECT c2 $L LIST CONFLICT 1 * write 104857600 105906176 EXPECT c2 $L LIST CONFLICT 1 * write 106954752 108003328 EXPECT c2 $L LIST CONFLICT 1 * write 109051904 110100480 EXPECT c2 $L LIST CONFLICT 1 * write 111149056 112197632 EXPECT c2 $L LIST CONFLICT 1 * write 113246208 114294784 EXPECT c2 $L LIST CONFLICT 1 * write 115343360 116391936 EXPECT c2 $L LIST CONFLICT 1 * write 117440512 118489088 EXPECT c2 $L LIST CONFLICT 1 * write 119537664 120586240 EXPECT c2 $L LIST CONFLICT 1 * write 121634816 122683392 EXPECT c2 $L LIST CONFLICT 1 * write 123731968 124780544 EXPECT c2 $L LIST CONFLICT 1 * write 125829120 126877696 EXPECT c2 $L LIST CONFLICT 1 * write 127926272 128974848 EXPECT c2 $L LIST CONFLICT 1 * write 130023424 131072000 EXPECT c2 $L LIST CONFLICT 1 * write 132120576 133169152 EXPECT c2 $L LIST CONFLICT 1 * write 134217728 135266304 EXPECT c2 $L LIST CONFLICT 1 * write 136314880 137363456 EXPECT c2 $L LIST CONFLICT 1 * write 138412032 139460608 EXPECT c2 $L LIST CONFLICT 1 * write 140509184 141557760 EXPECT c2 $L LIST CONFLICT 1 * write 142606336 143654912 EXPECT c2 $L LIST CONFLICT 1 * write 144703488 145752064 EXPECT c2 $L LIST CONFLICT 1 * write 146800640 147849216 EXPECT c2 $L LIST CONFLICT 1 * write 148897792 149946368 EXPECT c2 $L LIST CONFLICT 1 * write 150994944 152043520 EXPECT c2 $L LIST CONFLICT 1 * write 153092096 154140672 EXPECT c2 $L LIST CONFLICT 1 * write 155189248 156237824 EXPECT c2 $L LIST CONFLICT 1 * write 157286400 158334976 EXPECT c2 $L LIST CONFLICT 1 * write 159383552 160432128 EXPECT c2 $L LIST CONFLICT 1 * write 161480704 162529280 EXPECT c2 $L LIST CONFLICT 1 * write 163577856 164626432 EXPECT c2 $L LIST CONFLICT 1 * write 165675008 166723584 EXPECT c2 $L LIST CONFLICT 1 * write 167772160 168820736 EXPECT c2 $L LIST CONFLICT 1 * write 169869312 170917888 EXPECT c2 $L LIST CONFLICT 1 * write 171966464 173015040 EXPECT c2 $L LIST CONFLICT 1 * write 174063616 175112192 EXPECT c2 $L LIST CONFLICT 1 * write 176160768 177209344 EXPECT c2 $L LIST CONFLICT 1 * write 178257920 179306496 EXPECT c2 $L LIST CONFLICT 1 * write 180355072 181403648 EXPECT c2 $L LIST CONFLICT 1 * write 182452224 183500800 EXPECT c2 $L LIST CONFLICT 1 * write 184549376 185597952 EXPECT c2 $L LIST CONFLICT 1 * write 186646528 187695104 EXPECT c2 $L LIST CONFLICT 1 * write 188743680 189792256 EXPECT c2 $L LIST CONFLICT 1 * write 190840832 191889408 EXPECT c2 $L LIST CONFLICT 1 * write 192937984 193986560 EXPECT c2 $L LIST CONFLICT 1 * write 195035136 196083712 EXPECT c2 $L LIST CONFLICT 1 * write 197132288 198180864 EXPECT c2 $L LIST CONFLICT 1 * write 199229440 200278016 EXPECT c2 $L LIST CONFLICT 1 * write 201326592 202375168 EXPECT c2 $L LIST CONFLICT 1 * write 203423744 204472320 EXPECT c2 $L LIST CONFLICT 1 * write 205520896 206569472 EXPECT c2 $L LIST CONFLICT 1 * write 207618048 208666624 EXPECT c2 $L LIST CONFLICT 1 * write 209715200 210763776 EXPECT c2 $L LIST CONFLICT 1 * write 211812352 212860928 EXPECT c2 $L LIST CONFLICT 1 * write 213909504 214958080 EXPECT c2 $L LIST CONFLICT 1 * write 216006656 217055232 EXPECT c2 $L LIST CONFLICT 1 * write 218103808 219152384 EXPECT c2 $L LIST CONFLICT 1 * write 220200960 221249536 EXPECT c2 $L LIST CONFLICT 1 * write 222298112 223346688 EXPECT c2 $L LIST CONFLICT 1 * write 224395264 225443840 EXPECT c2 $L LIST CONFLICT 1 * write 226492416 227540992 EXPECT c2 $L LIST CONFLICT 1 * write 228589568 229638144 EXPECT c2 $L LIST CONFLICT 1 * write 230686720 231735296 EXPECT c2 $L LIST CONFLICT 1 * write 232783872 233832448 EXPECT c2 $L LIST CONFLICT 1 * write 234881024 235929600 EXPECT c2 $L LIST CONFLICT 1 * write 236978176 238026752 EXPECT c2 $L LIST CONFLICT 1 * write 239075328 240123904 EXPECT c2 $L LIST CONFLICT 1 * write 241172480 242221056 EXPECT c2 $L LIST CONFLICT 1 * write 243269632 244318208 EXPECT c2 $L LIST CONFLICT 1 * write 245366784 246415360 EXPECT c2 $L LIST CONFLICT 1 * write 247463936 248512512 EXPECT c2 $L LIST CONFLICT 1 * write 249561088 250609664 EXPECT c2 $L LIST CONFLICT 1 * write 251658240 252706816 EXPECT c2 $L LIST CONFLICT 1 * write 253755392 254803968 EXPECT c2 $L LIST CONFLICT 1 * write 255852544 256901120 EXPECT c2 $L LIST CONFLICT 1 * write 257949696 258998272 EXPECT c2 $L LIST CONFLICT 1 * write 260046848 261095424 EXPECT c2 $L LIST CONFLICT 1 * write 262144000 263192576 EXPECT c2 $L LIST CONFLICT 1 * write 264241152 265289728 EXPECT c2 $L LIST CONFLICT 1 * write 266338304 267386880 } EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c2 LIST 1 0 0 QUIT �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/take_128_locks_medium_range_medium_gap_posix�������0000664�0000000�0000000�00000063153�14737566223�0033023�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Implementation of same file concurrent locks with varied ranges # # Take 128 locks on a file with medium ranges and medium gaps # medium = 1 * size.MiB # Check to see that those locks exists # Release the locks # Check to see that those locks don’t exists CLIENTS c1 c2 OK c1 OPEN 1 rw create POSIX testFile OK c2 OPEN 1 rw POSIX testFile GRANTED c1 LOCK 1 read 0 1048576 GRANTED c1 LOCK 1 read 2097152 3145728 GRANTED c1 LOCK 1 read 4194304 5242880 GRANTED c1 LOCK 1 read 6291456 7340032 GRANTED c1 LOCK 1 read 8388608 9437184 GRANTED c1 LOCK 1 read 10485760 11534336 GRANTED c1 LOCK 1 read 12582912 13631488 GRANTED c1 LOCK 1 read 14680064 15728640 GRANTED c1 LOCK 1 read 16777216 17825792 GRANTED c1 LOCK 1 read 18874368 19922944 GRANTED c1 LOCK 1 read 20971520 22020096 GRANTED c1 LOCK 1 read 23068672 24117248 GRANTED c1 LOCK 1 read 25165824 26214400 GRANTED c1 LOCK 1 read 27262976 28311552 GRANTED c1 LOCK 1 read 29360128 30408704 GRANTED c1 LOCK 1 read 31457280 32505856 GRANTED c1 LOCK 1 read 33554432 34603008 GRANTED c1 LOCK 1 read 35651584 36700160 GRANTED c1 LOCK 1 read 37748736 38797312 GRANTED c1 LOCK 1 read 39845888 40894464 GRANTED c1 LOCK 1 read 41943040 42991616 GRANTED c1 LOCK 1 read 44040192 45088768 GRANTED c1 LOCK 1 read 46137344 47185920 GRANTED c1 LOCK 1 read 48234496 49283072 GRANTED c1 LOCK 1 read 50331648 51380224 GRANTED c1 LOCK 1 read 52428800 53477376 GRANTED c1 LOCK 1 read 54525952 55574528 GRANTED c1 LOCK 1 read 56623104 57671680 GRANTED c1 LOCK 1 read 58720256 59768832 GRANTED c1 LOCK 1 read 60817408 61865984 GRANTED c1 LOCK 1 read 62914560 63963136 GRANTED c1 LOCK 1 read 65011712 66060288 GRANTED c1 LOCK 1 read 67108864 68157440 GRANTED c1 LOCK 1 read 69206016 70254592 GRANTED c1 LOCK 1 read 71303168 72351744 GRANTED c1 LOCK 1 read 73400320 74448896 GRANTED c1 LOCK 1 read 75497472 76546048 GRANTED c1 LOCK 1 read 77594624 78643200 GRANTED c1 LOCK 1 read 79691776 80740352 GRANTED c1 LOCK 1 read 81788928 82837504 GRANTED c1 LOCK 1 read 83886080 84934656 GRANTED c1 LOCK 1 read 85983232 87031808 GRANTED c1 LOCK 1 read 88080384 89128960 GRANTED c1 LOCK 1 read 90177536 91226112 GRANTED c1 LOCK 1 read 92274688 93323264 GRANTED c1 LOCK 1 read 94371840 95420416 GRANTED c1 LOCK 1 read 96468992 97517568 GRANTED c1 LOCK 1 read 98566144 99614720 GRANTED c1 LOCK 1 read 100663296 101711872 GRANTED c1 LOCK 1 read 102760448 103809024 GRANTED c1 LOCK 1 read 104857600 105906176 GRANTED c1 LOCK 1 read 106954752 108003328 GRANTED c1 LOCK 1 read 109051904 110100480 GRANTED c1 LOCK 1 read 111149056 112197632 GRANTED c1 LOCK 1 read 113246208 114294784 GRANTED c1 LOCK 1 read 115343360 116391936 GRANTED c1 LOCK 1 read 117440512 118489088 GRANTED c1 LOCK 1 read 119537664 120586240 GRANTED c1 LOCK 1 read 121634816 122683392 GRANTED c1 LOCK 1 read 123731968 124780544 GRANTED c1 LOCK 1 read 125829120 126877696 GRANTED c1 LOCK 1 read 127926272 128974848 GRANTED c1 LOCK 1 read 130023424 131072000 GRANTED c1 LOCK 1 read 132120576 133169152 GRANTED c1 LOCK 1 read 134217728 135266304 GRANTED c1 LOCK 1 read 136314880 137363456 GRANTED c1 LOCK 1 read 138412032 139460608 GRANTED c1 LOCK 1 read 140509184 141557760 GRANTED c1 LOCK 1 read 142606336 143654912 GRANTED c1 LOCK 1 read 144703488 145752064 GRANTED c1 LOCK 1 read 146800640 147849216 GRANTED c1 LOCK 1 read 148897792 149946368 GRANTED c1 LOCK 1 read 150994944 152043520 GRANTED c1 LOCK 1 read 153092096 154140672 GRANTED c1 LOCK 1 read 155189248 156237824 GRANTED c1 LOCK 1 read 157286400 158334976 GRANTED c1 LOCK 1 read 159383552 160432128 GRANTED c1 LOCK 1 read 161480704 162529280 GRANTED c1 LOCK 1 read 163577856 164626432 GRANTED c1 LOCK 1 read 165675008 166723584 GRANTED c1 LOCK 1 read 167772160 168820736 GRANTED c1 LOCK 1 read 169869312 170917888 GRANTED c1 LOCK 1 read 171966464 173015040 GRANTED c1 LOCK 1 read 174063616 175112192 GRANTED c1 LOCK 1 read 176160768 177209344 GRANTED c1 LOCK 1 read 178257920 179306496 GRANTED c1 LOCK 1 read 180355072 181403648 GRANTED c1 LOCK 1 read 182452224 183500800 GRANTED c1 LOCK 1 read 184549376 185597952 GRANTED c1 LOCK 1 read 186646528 187695104 GRANTED c1 LOCK 1 read 188743680 189792256 GRANTED c1 LOCK 1 read 190840832 191889408 GRANTED c1 LOCK 1 read 192937984 193986560 GRANTED c1 LOCK 1 read 195035136 196083712 GRANTED c1 LOCK 1 read 197132288 198180864 GRANTED c1 LOCK 1 read 199229440 200278016 GRANTED c1 LOCK 1 read 201326592 202375168 GRANTED c1 LOCK 1 read 203423744 204472320 GRANTED c1 LOCK 1 read 205520896 206569472 GRANTED c1 LOCK 1 read 207618048 208666624 GRANTED c1 LOCK 1 read 209715200 210763776 GRANTED c1 LOCK 1 read 211812352 212860928 GRANTED c1 LOCK 1 read 213909504 214958080 GRANTED c1 LOCK 1 read 216006656 217055232 GRANTED c1 LOCK 1 read 218103808 219152384 GRANTED c1 LOCK 1 read 220200960 221249536 GRANTED c1 LOCK 1 read 222298112 223346688 GRANTED c1 LOCK 1 read 224395264 225443840 GRANTED c1 LOCK 1 read 226492416 227540992 GRANTED c1 LOCK 1 read 228589568 229638144 GRANTED c1 LOCK 1 read 230686720 231735296 GRANTED c1 LOCK 1 read 232783872 233832448 GRANTED c1 LOCK 1 read 234881024 235929600 GRANTED c1 LOCK 1 read 236978176 238026752 GRANTED c1 LOCK 1 read 239075328 240123904 GRANTED c1 LOCK 1 read 241172480 242221056 GRANTED c1 LOCK 1 read 243269632 244318208 GRANTED c1 LOCK 1 read 245366784 246415360 GRANTED c1 LOCK 1 read 247463936 248512512 GRANTED c1 LOCK 1 read 249561088 250609664 GRANTED c1 LOCK 1 read 251658240 252706816 GRANTED c1 LOCK 1 read 253755392 254803968 GRANTED c1 LOCK 1 read 255852544 256901120 GRANTED c1 LOCK 1 read 257949696 258998272 GRANTED c1 LOCK 1 read 260046848 261095424 GRANTED c1 LOCK 1 read 262144000 263192576 GRANTED c1 LOCK 1 read 264241152 265289728 GRANTED c1 LOCK 1 read 266338304 267386880 c2 $L LIST 1 0 0 { EXPECT c2 $L LIST CONFLICT 1 * read 0 1048576 EXPECT c2 $L LIST CONFLICT 1 * read 2097152 3145728 EXPECT c2 $L LIST CONFLICT 1 * read 4194304 5242880 EXPECT c2 $L LIST CONFLICT 1 * read 6291456 7340032 EXPECT c2 $L LIST CONFLICT 1 * read 8388608 9437184 EXPECT c2 $L LIST CONFLICT 1 * read 10485760 11534336 EXPECT c2 $L LIST CONFLICT 1 * read 12582912 13631488 EXPECT c2 $L LIST CONFLICT 1 * read 14680064 15728640 EXPECT c2 $L LIST CONFLICT 1 * read 16777216 17825792 EXPECT c2 $L LIST CONFLICT 1 * read 18874368 19922944 EXPECT c2 $L LIST CONFLICT 1 * read 20971520 22020096 EXPECT c2 $L LIST CONFLICT 1 * read 23068672 24117248 EXPECT c2 $L LIST CONFLICT 1 * read 25165824 26214400 EXPECT c2 $L LIST CONFLICT 1 * read 27262976 28311552 EXPECT c2 $L LIST CONFLICT 1 * read 29360128 30408704 EXPECT c2 $L LIST CONFLICT 1 * read 31457280 32505856 EXPECT c2 $L LIST CONFLICT 1 * read 33554432 34603008 EXPECT c2 $L LIST CONFLICT 1 * read 35651584 36700160 EXPECT c2 $L LIST CONFLICT 1 * read 37748736 38797312 EXPECT c2 $L LIST CONFLICT 1 * read 39845888 40894464 EXPECT c2 $L LIST CONFLICT 1 * read 41943040 42991616 EXPECT c2 $L LIST CONFLICT 1 * read 44040192 45088768 EXPECT c2 $L LIST CONFLICT 1 * read 46137344 47185920 EXPECT c2 $L LIST CONFLICT 1 * read 48234496 49283072 EXPECT c2 $L LIST CONFLICT 1 * read 50331648 51380224 EXPECT c2 $L LIST CONFLICT 1 * read 52428800 53477376 EXPECT c2 $L LIST CONFLICT 1 * read 54525952 55574528 EXPECT c2 $L LIST CONFLICT 1 * read 56623104 57671680 EXPECT c2 $L LIST CONFLICT 1 * read 58720256 59768832 EXPECT c2 $L LIST CONFLICT 1 * read 60817408 61865984 EXPECT c2 $L LIST CONFLICT 1 * read 62914560 63963136 EXPECT c2 $L LIST CONFLICT 1 * read 65011712 66060288 EXPECT c2 $L LIST CONFLICT 1 * read 67108864 68157440 EXPECT c2 $L LIST CONFLICT 1 * read 69206016 70254592 EXPECT c2 $L LIST CONFLICT 1 * read 71303168 72351744 EXPECT c2 $L LIST CONFLICT 1 * read 73400320 74448896 EXPECT c2 $L LIST CONFLICT 1 * read 75497472 76546048 EXPECT c2 $L LIST CONFLICT 1 * read 77594624 78643200 EXPECT c2 $L LIST CONFLICT 1 * read 79691776 80740352 EXPECT c2 $L LIST CONFLICT 1 * read 81788928 82837504 EXPECT c2 $L LIST CONFLICT 1 * read 83886080 84934656 EXPECT c2 $L LIST CONFLICT 1 * read 85983232 87031808 EXPECT c2 $L LIST CONFLICT 1 * read 88080384 89128960 EXPECT c2 $L LIST CONFLICT 1 * read 90177536 91226112 EXPECT c2 $L LIST CONFLICT 1 * read 92274688 93323264 EXPECT c2 $L LIST CONFLICT 1 * read 94371840 95420416 EXPECT c2 $L LIST CONFLICT 1 * read 96468992 97517568 EXPECT c2 $L LIST CONFLICT 1 * read 98566144 99614720 EXPECT c2 $L LIST CONFLICT 1 * read 100663296 101711872 EXPECT c2 $L LIST CONFLICT 1 * read 102760448 103809024 EXPECT c2 $L LIST CONFLICT 1 * read 104857600 105906176 EXPECT c2 $L LIST CONFLICT 1 * read 106954752 108003328 EXPECT c2 $L LIST CONFLICT 1 * read 109051904 110100480 EXPECT c2 $L LIST CONFLICT 1 * read 111149056 112197632 EXPECT c2 $L LIST CONFLICT 1 * read 113246208 114294784 EXPECT c2 $L LIST CONFLICT 1 * read 115343360 116391936 EXPECT c2 $L LIST CONFLICT 1 * read 117440512 118489088 EXPECT c2 $L LIST CONFLICT 1 * read 119537664 120586240 EXPECT c2 $L LIST CONFLICT 1 * read 121634816 122683392 EXPECT c2 $L LIST CONFLICT 1 * read 123731968 124780544 EXPECT c2 $L LIST CONFLICT 1 * read 125829120 126877696 EXPECT c2 $L LIST CONFLICT 1 * read 127926272 128974848 EXPECT c2 $L LIST CONFLICT 1 * read 130023424 131072000 EXPECT c2 $L LIST CONFLICT 1 * read 132120576 133169152 EXPECT c2 $L LIST CONFLICT 1 * read 134217728 135266304 EXPECT c2 $L LIST CONFLICT 1 * read 136314880 137363456 EXPECT c2 $L LIST CONFLICT 1 * read 138412032 139460608 EXPECT c2 $L LIST CONFLICT 1 * read 140509184 141557760 EXPECT c2 $L LIST CONFLICT 1 * read 142606336 143654912 EXPECT c2 $L LIST CONFLICT 1 * read 144703488 145752064 EXPECT c2 $L LIST CONFLICT 1 * read 146800640 147849216 EXPECT c2 $L LIST CONFLICT 1 * read 148897792 149946368 EXPECT c2 $L LIST CONFLICT 1 * read 150994944 152043520 EXPECT c2 $L LIST CONFLICT 1 * read 153092096 154140672 EXPECT c2 $L LIST CONFLICT 1 * read 155189248 156237824 EXPECT c2 $L LIST CONFLICT 1 * read 157286400 158334976 EXPECT c2 $L LIST CONFLICT 1 * read 159383552 160432128 EXPECT c2 $L LIST CONFLICT 1 * read 161480704 162529280 EXPECT c2 $L LIST CONFLICT 1 * read 163577856 164626432 EXPECT c2 $L LIST CONFLICT 1 * read 165675008 166723584 EXPECT c2 $L LIST CONFLICT 1 * read 167772160 168820736 EXPECT c2 $L LIST CONFLICT 1 * read 169869312 170917888 EXPECT c2 $L LIST CONFLICT 1 * read 171966464 173015040 EXPECT c2 $L LIST CONFLICT 1 * read 174063616 175112192 EXPECT c2 $L LIST CONFLICT 1 * read 176160768 177209344 EXPECT c2 $L LIST CONFLICT 1 * read 178257920 179306496 EXPECT c2 $L LIST CONFLICT 1 * read 180355072 181403648 EXPECT c2 $L LIST CONFLICT 1 * read 182452224 183500800 EXPECT c2 $L LIST CONFLICT 1 * read 184549376 185597952 EXPECT c2 $L LIST CONFLICT 1 * read 186646528 187695104 EXPECT c2 $L LIST CONFLICT 1 * read 188743680 189792256 EXPECT c2 $L LIST CONFLICT 1 * read 190840832 191889408 EXPECT c2 $L LIST CONFLICT 1 * read 192937984 193986560 EXPECT c2 $L LIST CONFLICT 1 * read 195035136 196083712 EXPECT c2 $L LIST CONFLICT 1 * read 197132288 198180864 EXPECT c2 $L LIST CONFLICT 1 * read 199229440 200278016 EXPECT c2 $L LIST CONFLICT 1 * read 201326592 202375168 EXPECT c2 $L LIST CONFLICT 1 * read 203423744 204472320 EXPECT c2 $L LIST CONFLICT 1 * read 205520896 206569472 EXPECT c2 $L LIST CONFLICT 1 * read 207618048 208666624 EXPECT c2 $L LIST CONFLICT 1 * read 209715200 210763776 EXPECT c2 $L LIST CONFLICT 1 * read 211812352 212860928 EXPECT c2 $L LIST CONFLICT 1 * read 213909504 214958080 EXPECT c2 $L LIST CONFLICT 1 * read 216006656 217055232 EXPECT c2 $L LIST CONFLICT 1 * read 218103808 219152384 EXPECT c2 $L LIST CONFLICT 1 * read 220200960 221249536 EXPECT c2 $L LIST CONFLICT 1 * read 222298112 223346688 EXPECT c2 $L LIST CONFLICT 1 * read 224395264 225443840 EXPECT c2 $L LIST CONFLICT 1 * read 226492416 227540992 EXPECT c2 $L LIST CONFLICT 1 * read 228589568 229638144 EXPECT c2 $L LIST CONFLICT 1 * read 230686720 231735296 EXPECT c2 $L LIST CONFLICT 1 * read 232783872 233832448 EXPECT c2 $L LIST CONFLICT 1 * read 234881024 235929600 EXPECT c2 $L LIST CONFLICT 1 * read 236978176 238026752 EXPECT c2 $L LIST CONFLICT 1 * read 239075328 240123904 EXPECT c2 $L LIST CONFLICT 1 * read 241172480 242221056 EXPECT c2 $L LIST CONFLICT 1 * read 243269632 244318208 EXPECT c2 $L LIST CONFLICT 1 * read 245366784 246415360 EXPECT c2 $L LIST CONFLICT 1 * read 247463936 248512512 EXPECT c2 $L LIST CONFLICT 1 * read 249561088 250609664 EXPECT c2 $L LIST CONFLICT 1 * read 251658240 252706816 EXPECT c2 $L LIST CONFLICT 1 * read 253755392 254803968 EXPECT c2 $L LIST CONFLICT 1 * read 255852544 256901120 EXPECT c2 $L LIST CONFLICT 1 * read 257949696 258998272 EXPECT c2 $L LIST CONFLICT 1 * read 260046848 261095424 EXPECT c2 $L LIST CONFLICT 1 * read 262144000 263192576 EXPECT c2 $L LIST CONFLICT 1 * read 264241152 265289728 EXPECT c2 $L LIST CONFLICT 1 * read 266338304 267386880 } EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c2 LIST 1 0 0 GRANTED c1 LOCK 1 write 0 1048576 GRANTED c1 LOCK 1 write 2097152 3145728 GRANTED c1 LOCK 1 write 4194304 5242880 GRANTED c1 LOCK 1 write 6291456 7340032 GRANTED c1 LOCK 1 write 8388608 9437184 GRANTED c1 LOCK 1 write 10485760 11534336 GRANTED c1 LOCK 1 write 12582912 13631488 GRANTED c1 LOCK 1 write 14680064 15728640 GRANTED c1 LOCK 1 write 16777216 17825792 GRANTED c1 LOCK 1 write 18874368 19922944 GRANTED c1 LOCK 1 write 20971520 22020096 GRANTED c1 LOCK 1 write 23068672 24117248 GRANTED c1 LOCK 1 write 25165824 26214400 GRANTED c1 LOCK 1 write 27262976 28311552 GRANTED c1 LOCK 1 write 29360128 30408704 GRANTED c1 LOCK 1 write 31457280 32505856 GRANTED c1 LOCK 1 write 33554432 34603008 GRANTED c1 LOCK 1 write 35651584 36700160 GRANTED c1 LOCK 1 write 37748736 38797312 GRANTED c1 LOCK 1 write 39845888 40894464 GRANTED c1 LOCK 1 write 41943040 42991616 GRANTED c1 LOCK 1 write 44040192 45088768 GRANTED c1 LOCK 1 write 46137344 47185920 GRANTED c1 LOCK 1 write 48234496 49283072 GRANTED c1 LOCK 1 write 50331648 51380224 GRANTED c1 LOCK 1 write 52428800 53477376 GRANTED c1 LOCK 1 write 54525952 55574528 GRANTED c1 LOCK 1 write 56623104 57671680 GRANTED c1 LOCK 1 write 58720256 59768832 GRANTED c1 LOCK 1 write 60817408 61865984 GRANTED c1 LOCK 1 write 62914560 63963136 GRANTED c1 LOCK 1 write 65011712 66060288 GRANTED c1 LOCK 1 write 67108864 68157440 GRANTED c1 LOCK 1 write 69206016 70254592 GRANTED c1 LOCK 1 write 71303168 72351744 GRANTED c1 LOCK 1 write 73400320 74448896 GRANTED c1 LOCK 1 write 75497472 76546048 GRANTED c1 LOCK 1 write 77594624 78643200 GRANTED c1 LOCK 1 write 79691776 80740352 GRANTED c1 LOCK 1 write 81788928 82837504 GRANTED c1 LOCK 1 write 83886080 84934656 GRANTED c1 LOCK 1 write 85983232 87031808 GRANTED c1 LOCK 1 write 88080384 89128960 GRANTED c1 LOCK 1 write 90177536 91226112 GRANTED c1 LOCK 1 write 92274688 93323264 GRANTED c1 LOCK 1 write 94371840 95420416 GRANTED c1 LOCK 1 write 96468992 97517568 GRANTED c1 LOCK 1 write 98566144 99614720 GRANTED c1 LOCK 1 write 100663296 101711872 GRANTED c1 LOCK 1 write 102760448 103809024 GRANTED c1 LOCK 1 write 104857600 105906176 GRANTED c1 LOCK 1 write 106954752 108003328 GRANTED c1 LOCK 1 write 109051904 110100480 GRANTED c1 LOCK 1 write 111149056 112197632 GRANTED c1 LOCK 1 write 113246208 114294784 GRANTED c1 LOCK 1 write 115343360 116391936 GRANTED c1 LOCK 1 write 117440512 118489088 GRANTED c1 LOCK 1 write 119537664 120586240 GRANTED c1 LOCK 1 write 121634816 122683392 GRANTED c1 LOCK 1 write 123731968 124780544 GRANTED c1 LOCK 1 write 125829120 126877696 GRANTED c1 LOCK 1 write 127926272 128974848 GRANTED c1 LOCK 1 write 130023424 131072000 GRANTED c1 LOCK 1 write 132120576 133169152 GRANTED c1 LOCK 1 write 134217728 135266304 GRANTED c1 LOCK 1 write 136314880 137363456 GRANTED c1 LOCK 1 write 138412032 139460608 GRANTED c1 LOCK 1 write 140509184 141557760 GRANTED c1 LOCK 1 write 142606336 143654912 GRANTED c1 LOCK 1 write 144703488 145752064 GRANTED c1 LOCK 1 write 146800640 147849216 GRANTED c1 LOCK 1 write 148897792 149946368 GRANTED c1 LOCK 1 write 150994944 152043520 GRANTED c1 LOCK 1 write 153092096 154140672 GRANTED c1 LOCK 1 write 155189248 156237824 GRANTED c1 LOCK 1 write 157286400 158334976 GRANTED c1 LOCK 1 write 159383552 160432128 GRANTED c1 LOCK 1 write 161480704 162529280 GRANTED c1 LOCK 1 write 163577856 164626432 GRANTED c1 LOCK 1 write 165675008 166723584 GRANTED c1 LOCK 1 write 167772160 168820736 GRANTED c1 LOCK 1 write 169869312 170917888 GRANTED c1 LOCK 1 write 171966464 173015040 GRANTED c1 LOCK 1 write 174063616 175112192 GRANTED c1 LOCK 1 write 176160768 177209344 GRANTED c1 LOCK 1 write 178257920 179306496 GRANTED c1 LOCK 1 write 180355072 181403648 GRANTED c1 LOCK 1 write 182452224 183500800 GRANTED c1 LOCK 1 write 184549376 185597952 GRANTED c1 LOCK 1 write 186646528 187695104 GRANTED c1 LOCK 1 write 188743680 189792256 GRANTED c1 LOCK 1 write 190840832 191889408 GRANTED c1 LOCK 1 write 192937984 193986560 GRANTED c1 LOCK 1 write 195035136 196083712 GRANTED c1 LOCK 1 write 197132288 198180864 GRANTED c1 LOCK 1 write 199229440 200278016 GRANTED c1 LOCK 1 write 201326592 202375168 GRANTED c1 LOCK 1 write 203423744 204472320 GRANTED c1 LOCK 1 write 205520896 206569472 GRANTED c1 LOCK 1 write 207618048 208666624 GRANTED c1 LOCK 1 write 209715200 210763776 GRANTED c1 LOCK 1 write 211812352 212860928 GRANTED c1 LOCK 1 write 213909504 214958080 GRANTED c1 LOCK 1 write 216006656 217055232 GRANTED c1 LOCK 1 write 218103808 219152384 GRANTED c1 LOCK 1 write 220200960 221249536 GRANTED c1 LOCK 1 write 222298112 223346688 GRANTED c1 LOCK 1 write 224395264 225443840 GRANTED c1 LOCK 1 write 226492416 227540992 GRANTED c1 LOCK 1 write 228589568 229638144 GRANTED c1 LOCK 1 write 230686720 231735296 GRANTED c1 LOCK 1 write 232783872 233832448 GRANTED c1 LOCK 1 write 234881024 235929600 GRANTED c1 LOCK 1 write 236978176 238026752 GRANTED c1 LOCK 1 write 239075328 240123904 GRANTED c1 LOCK 1 write 241172480 242221056 GRANTED c1 LOCK 1 write 243269632 244318208 GRANTED c1 LOCK 1 write 245366784 246415360 GRANTED c1 LOCK 1 write 247463936 248512512 GRANTED c1 LOCK 1 write 249561088 250609664 GRANTED c1 LOCK 1 write 251658240 252706816 GRANTED c1 LOCK 1 write 253755392 254803968 GRANTED c1 LOCK 1 write 255852544 256901120 GRANTED c1 LOCK 1 write 257949696 258998272 GRANTED c1 LOCK 1 write 260046848 261095424 GRANTED c1 LOCK 1 write 262144000 263192576 GRANTED c1 LOCK 1 write 264241152 265289728 GRANTED c1 LOCK 1 write 266338304 267386880 c2 $L LIST 1 0 0 { EXPECT c2 $L LIST CONFLICT 1 * write 0 1048576 EXPECT c2 $L LIST CONFLICT 1 * write 2097152 3145728 EXPECT c2 $L LIST CONFLICT 1 * write 4194304 5242880 EXPECT c2 $L LIST CONFLICT 1 * write 6291456 7340032 EXPECT c2 $L LIST CONFLICT 1 * write 8388608 9437184 EXPECT c2 $L LIST CONFLICT 1 * write 10485760 11534336 EXPECT c2 $L LIST CONFLICT 1 * write 12582912 13631488 EXPECT c2 $L LIST CONFLICT 1 * write 14680064 15728640 EXPECT c2 $L LIST CONFLICT 1 * write 16777216 17825792 EXPECT c2 $L LIST CONFLICT 1 * write 18874368 19922944 EXPECT c2 $L LIST CONFLICT 1 * write 20971520 22020096 EXPECT c2 $L LIST CONFLICT 1 * write 23068672 24117248 EXPECT c2 $L LIST CONFLICT 1 * write 25165824 26214400 EXPECT c2 $L LIST CONFLICT 1 * write 27262976 28311552 EXPECT c2 $L LIST CONFLICT 1 * write 29360128 30408704 EXPECT c2 $L LIST CONFLICT 1 * write 31457280 32505856 EXPECT c2 $L LIST CONFLICT 1 * write 33554432 34603008 EXPECT c2 $L LIST CONFLICT 1 * write 35651584 36700160 EXPECT c2 $L LIST CONFLICT 1 * write 37748736 38797312 EXPECT c2 $L LIST CONFLICT 1 * write 39845888 40894464 EXPECT c2 $L LIST CONFLICT 1 * write 41943040 42991616 EXPECT c2 $L LIST CONFLICT 1 * write 44040192 45088768 EXPECT c2 $L LIST CONFLICT 1 * write 46137344 47185920 EXPECT c2 $L LIST CONFLICT 1 * write 48234496 49283072 EXPECT c2 $L LIST CONFLICT 1 * write 50331648 51380224 EXPECT c2 $L LIST CONFLICT 1 * write 52428800 53477376 EXPECT c2 $L LIST CONFLICT 1 * write 54525952 55574528 EXPECT c2 $L LIST CONFLICT 1 * write 56623104 57671680 EXPECT c2 $L LIST CONFLICT 1 * write 58720256 59768832 EXPECT c2 $L LIST CONFLICT 1 * write 60817408 61865984 EXPECT c2 $L LIST CONFLICT 1 * write 62914560 63963136 EXPECT c2 $L LIST CONFLICT 1 * write 65011712 66060288 EXPECT c2 $L LIST CONFLICT 1 * write 67108864 68157440 EXPECT c2 $L LIST CONFLICT 1 * write 69206016 70254592 EXPECT c2 $L LIST CONFLICT 1 * write 71303168 72351744 EXPECT c2 $L LIST CONFLICT 1 * write 73400320 74448896 EXPECT c2 $L LIST CONFLICT 1 * write 75497472 76546048 EXPECT c2 $L LIST CONFLICT 1 * write 77594624 78643200 EXPECT c2 $L LIST CONFLICT 1 * write 79691776 80740352 EXPECT c2 $L LIST CONFLICT 1 * write 81788928 82837504 EXPECT c2 $L LIST CONFLICT 1 * write 83886080 84934656 EXPECT c2 $L LIST CONFLICT 1 * write 85983232 87031808 EXPECT c2 $L LIST CONFLICT 1 * write 88080384 89128960 EXPECT c2 $L LIST CONFLICT 1 * write 90177536 91226112 EXPECT c2 $L LIST CONFLICT 1 * write 92274688 93323264 EXPECT c2 $L LIST CONFLICT 1 * write 94371840 95420416 EXPECT c2 $L LIST CONFLICT 1 * write 96468992 97517568 EXPECT c2 $L LIST CONFLICT 1 * write 98566144 99614720 EXPECT c2 $L LIST CONFLICT 1 * write 100663296 101711872 EXPECT c2 $L LIST CONFLICT 1 * write 102760448 103809024 EXPECT c2 $L LIST CONFLICT 1 * write 104857600 105906176 EXPECT c2 $L LIST CONFLICT 1 * write 106954752 108003328 EXPECT c2 $L LIST CONFLICT 1 * write 109051904 110100480 EXPECT c2 $L LIST CONFLICT 1 * write 111149056 112197632 EXPECT c2 $L LIST CONFLICT 1 * write 113246208 114294784 EXPECT c2 $L LIST CONFLICT 1 * write 115343360 116391936 EXPECT c2 $L LIST CONFLICT 1 * write 117440512 118489088 EXPECT c2 $L LIST CONFLICT 1 * write 119537664 120586240 EXPECT c2 $L LIST CONFLICT 1 * write 121634816 122683392 EXPECT c2 $L LIST CONFLICT 1 * write 123731968 124780544 EXPECT c2 $L LIST CONFLICT 1 * write 125829120 126877696 EXPECT c2 $L LIST CONFLICT 1 * write 127926272 128974848 EXPECT c2 $L LIST CONFLICT 1 * write 130023424 131072000 EXPECT c2 $L LIST CONFLICT 1 * write 132120576 133169152 EXPECT c2 $L LIST CONFLICT 1 * write 134217728 135266304 EXPECT c2 $L LIST CONFLICT 1 * write 136314880 137363456 EXPECT c2 $L LIST CONFLICT 1 * write 138412032 139460608 EXPECT c2 $L LIST CONFLICT 1 * write 140509184 141557760 EXPECT c2 $L LIST CONFLICT 1 * write 142606336 143654912 EXPECT c2 $L LIST CONFLICT 1 * write 144703488 145752064 EXPECT c2 $L LIST CONFLICT 1 * write 146800640 147849216 EXPECT c2 $L LIST CONFLICT 1 * write 148897792 149946368 EXPECT c2 $L LIST CONFLICT 1 * write 150994944 152043520 EXPECT c2 $L LIST CONFLICT 1 * write 153092096 154140672 EXPECT c2 $L LIST CONFLICT 1 * write 155189248 156237824 EXPECT c2 $L LIST CONFLICT 1 * write 157286400 158334976 EXPECT c2 $L LIST CONFLICT 1 * write 159383552 160432128 EXPECT c2 $L LIST CONFLICT 1 * write 161480704 162529280 EXPECT c2 $L LIST CONFLICT 1 * write 163577856 164626432 EXPECT c2 $L LIST CONFLICT 1 * write 165675008 166723584 EXPECT c2 $L LIST CONFLICT 1 * write 167772160 168820736 EXPECT c2 $L LIST CONFLICT 1 * write 169869312 170917888 EXPECT c2 $L LIST CONFLICT 1 * write 171966464 173015040 EXPECT c2 $L LIST CONFLICT 1 * write 174063616 175112192 EXPECT c2 $L LIST CONFLICT 1 * write 176160768 177209344 EXPECT c2 $L LIST CONFLICT 1 * write 178257920 179306496 EXPECT c2 $L LIST CONFLICT 1 * write 180355072 181403648 EXPECT c2 $L LIST CONFLICT 1 * write 182452224 183500800 EXPECT c2 $L LIST CONFLICT 1 * write 184549376 185597952 EXPECT c2 $L LIST CONFLICT 1 * write 186646528 187695104 EXPECT c2 $L LIST CONFLICT 1 * write 188743680 189792256 EXPECT c2 $L LIST CONFLICT 1 * write 190840832 191889408 EXPECT c2 $L LIST CONFLICT 1 * write 192937984 193986560 EXPECT c2 $L LIST CONFLICT 1 * write 195035136 196083712 EXPECT c2 $L LIST CONFLICT 1 * write 197132288 198180864 EXPECT c2 $L LIST CONFLICT 1 * write 199229440 200278016 EXPECT c2 $L LIST CONFLICT 1 * write 201326592 202375168 EXPECT c2 $L LIST CONFLICT 1 * write 203423744 204472320 EXPECT c2 $L LIST CONFLICT 1 * write 205520896 206569472 EXPECT c2 $L LIST CONFLICT 1 * write 207618048 208666624 EXPECT c2 $L LIST CONFLICT 1 * write 209715200 210763776 EXPECT c2 $L LIST CONFLICT 1 * write 211812352 212860928 EXPECT c2 $L LIST CONFLICT 1 * write 213909504 214958080 EXPECT c2 $L LIST CONFLICT 1 * write 216006656 217055232 EXPECT c2 $L LIST CONFLICT 1 * write 218103808 219152384 EXPECT c2 $L LIST CONFLICT 1 * write 220200960 221249536 EXPECT c2 $L LIST CONFLICT 1 * write 222298112 223346688 EXPECT c2 $L LIST CONFLICT 1 * write 224395264 225443840 EXPECT c2 $L LIST CONFLICT 1 * write 226492416 227540992 EXPECT c2 $L LIST CONFLICT 1 * write 228589568 229638144 EXPECT c2 $L LIST CONFLICT 1 * write 230686720 231735296 EXPECT c2 $L LIST CONFLICT 1 * write 232783872 233832448 EXPECT c2 $L LIST CONFLICT 1 * write 234881024 235929600 EXPECT c2 $L LIST CONFLICT 1 * write 236978176 238026752 EXPECT c2 $L LIST CONFLICT 1 * write 239075328 240123904 EXPECT c2 $L LIST CONFLICT 1 * write 241172480 242221056 EXPECT c2 $L LIST CONFLICT 1 * write 243269632 244318208 EXPECT c2 $L LIST CONFLICT 1 * write 245366784 246415360 EXPECT c2 $L LIST CONFLICT 1 * write 247463936 248512512 EXPECT c2 $L LIST CONFLICT 1 * write 249561088 250609664 EXPECT c2 $L LIST CONFLICT 1 * write 251658240 252706816 EXPECT c2 $L LIST CONFLICT 1 * write 253755392 254803968 EXPECT c2 $L LIST CONFLICT 1 * write 255852544 256901120 EXPECT c2 $L LIST CONFLICT 1 * write 257949696 258998272 EXPECT c2 $L LIST CONFLICT 1 * write 260046848 261095424 EXPECT c2 $L LIST CONFLICT 1 * write 262144000 263192576 EXPECT c2 $L LIST CONFLICT 1 * write 264241152 265289728 EXPECT c2 $L LIST CONFLICT 1 * write 266338304 267386880 } EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c2 LIST 1 0 0 QUIT ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/take_128_locks_small_range_none_gap_ofd������������0000664�0000000�0000000�00000055677�14737566223�0031754�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Implementation of same file concurrent locks with varied ranges # # Take 128 locks on a file with small ranges and none gaps # small = 4 * size.KiB # Check to see that those locks exists # Release the locks # Check to see that those locks don’t exists CLIENTS c1 c2 OK c1 OPEN 1 rw create OFD testFile OK c2 OPEN 1 rw OFD testFile GRANTED c1 LOCK 1 read 0 4096 GRANTED c1 LOCK 1 read 4096 8192 GRANTED c1 LOCK 1 read 8192 12288 GRANTED c1 LOCK 1 read 12288 16384 GRANTED c1 LOCK 1 read 16384 20480 GRANTED c1 LOCK 1 read 20480 24576 GRANTED c1 LOCK 1 read 24576 28672 GRANTED c1 LOCK 1 read 28672 32768 GRANTED c1 LOCK 1 read 32768 36864 GRANTED c1 LOCK 1 read 36864 40960 GRANTED c1 LOCK 1 read 40960 45056 GRANTED c1 LOCK 1 read 45056 49152 GRANTED c1 LOCK 1 read 49152 53248 GRANTED c1 LOCK 1 read 53248 57344 GRANTED c1 LOCK 1 read 57344 61440 GRANTED c1 LOCK 1 read 61440 65536 GRANTED c1 LOCK 1 read 65536 69632 GRANTED c1 LOCK 1 read 69632 73728 GRANTED c1 LOCK 1 read 73728 77824 GRANTED c1 LOCK 1 read 77824 81920 GRANTED c1 LOCK 1 read 81920 86016 GRANTED c1 LOCK 1 read 86016 90112 GRANTED c1 LOCK 1 read 90112 94208 GRANTED c1 LOCK 1 read 94208 98304 GRANTED c1 LOCK 1 read 98304 102400 GRANTED c1 LOCK 1 read 102400 106496 GRANTED c1 LOCK 1 read 106496 110592 GRANTED c1 LOCK 1 read 110592 114688 GRANTED c1 LOCK 1 read 114688 118784 GRANTED c1 LOCK 1 read 118784 122880 GRANTED c1 LOCK 1 read 122880 126976 GRANTED c1 LOCK 1 read 126976 131072 GRANTED c1 LOCK 1 read 131072 135168 GRANTED c1 LOCK 1 read 135168 139264 GRANTED c1 LOCK 1 read 139264 143360 GRANTED c1 LOCK 1 read 143360 147456 GRANTED c1 LOCK 1 read 147456 151552 GRANTED c1 LOCK 1 read 151552 155648 GRANTED c1 LOCK 1 read 155648 159744 GRANTED c1 LOCK 1 read 159744 163840 GRANTED c1 LOCK 1 read 163840 167936 GRANTED c1 LOCK 1 read 167936 172032 GRANTED c1 LOCK 1 read 172032 176128 GRANTED c1 LOCK 1 read 176128 180224 GRANTED c1 LOCK 1 read 180224 184320 GRANTED c1 LOCK 1 read 184320 188416 GRANTED c1 LOCK 1 read 188416 192512 GRANTED c1 LOCK 1 read 192512 196608 GRANTED c1 LOCK 1 read 196608 200704 GRANTED c1 LOCK 1 read 200704 204800 GRANTED c1 LOCK 1 read 204800 208896 GRANTED c1 LOCK 1 read 208896 212992 GRANTED c1 LOCK 1 read 212992 217088 GRANTED c1 LOCK 1 read 217088 221184 GRANTED c1 LOCK 1 read 221184 225280 GRANTED c1 LOCK 1 read 225280 229376 GRANTED c1 LOCK 1 read 229376 233472 GRANTED c1 LOCK 1 read 233472 237568 GRANTED c1 LOCK 1 read 237568 241664 GRANTED c1 LOCK 1 read 241664 245760 GRANTED c1 LOCK 1 read 245760 249856 GRANTED c1 LOCK 1 read 249856 253952 GRANTED c1 LOCK 1 read 253952 258048 GRANTED c1 LOCK 1 read 258048 262144 GRANTED c1 LOCK 1 read 262144 266240 GRANTED c1 LOCK 1 read 266240 270336 GRANTED c1 LOCK 1 read 270336 274432 GRANTED c1 LOCK 1 read 274432 278528 GRANTED c1 LOCK 1 read 278528 282624 GRANTED c1 LOCK 1 read 282624 286720 GRANTED c1 LOCK 1 read 286720 290816 GRANTED c1 LOCK 1 read 290816 294912 GRANTED c1 LOCK 1 read 294912 299008 GRANTED c1 LOCK 1 read 299008 303104 GRANTED c1 LOCK 1 read 303104 307200 GRANTED c1 LOCK 1 read 307200 311296 GRANTED c1 LOCK 1 read 311296 315392 GRANTED c1 LOCK 1 read 315392 319488 GRANTED c1 LOCK 1 read 319488 323584 GRANTED c1 LOCK 1 read 323584 327680 GRANTED c1 LOCK 1 read 327680 331776 GRANTED c1 LOCK 1 read 331776 335872 GRANTED c1 LOCK 1 read 335872 339968 GRANTED c1 LOCK 1 read 339968 344064 GRANTED c1 LOCK 1 read 344064 348160 GRANTED c1 LOCK 1 read 348160 352256 GRANTED c1 LOCK 1 read 352256 356352 GRANTED c1 LOCK 1 read 356352 360448 GRANTED c1 LOCK 1 read 360448 364544 GRANTED c1 LOCK 1 read 364544 368640 GRANTED c1 LOCK 1 read 368640 372736 GRANTED c1 LOCK 1 read 372736 376832 GRANTED c1 LOCK 1 read 376832 380928 GRANTED c1 LOCK 1 read 380928 385024 GRANTED c1 LOCK 1 read 385024 389120 GRANTED c1 LOCK 1 read 389120 393216 GRANTED c1 LOCK 1 read 393216 397312 GRANTED c1 LOCK 1 read 397312 401408 GRANTED c1 LOCK 1 read 401408 405504 GRANTED c1 LOCK 1 read 405504 409600 GRANTED c1 LOCK 1 read 409600 413696 GRANTED c1 LOCK 1 read 413696 417792 GRANTED c1 LOCK 1 read 417792 421888 GRANTED c1 LOCK 1 read 421888 425984 GRANTED c1 LOCK 1 read 425984 430080 GRANTED c1 LOCK 1 read 430080 434176 GRANTED c1 LOCK 1 read 434176 438272 GRANTED c1 LOCK 1 read 438272 442368 GRANTED c1 LOCK 1 read 442368 446464 GRANTED c1 LOCK 1 read 446464 450560 GRANTED c1 LOCK 1 read 450560 454656 GRANTED c1 LOCK 1 read 454656 458752 GRANTED c1 LOCK 1 read 458752 462848 GRANTED c1 LOCK 1 read 462848 466944 GRANTED c1 LOCK 1 read 466944 471040 GRANTED c1 LOCK 1 read 471040 475136 GRANTED c1 LOCK 1 read 475136 479232 GRANTED c1 LOCK 1 read 479232 483328 GRANTED c1 LOCK 1 read 483328 487424 GRANTED c1 LOCK 1 read 487424 491520 GRANTED c1 LOCK 1 read 491520 495616 GRANTED c1 LOCK 1 read 495616 499712 GRANTED c1 LOCK 1 read 499712 503808 GRANTED c1 LOCK 1 read 503808 507904 GRANTED c1 LOCK 1 read 507904 512000 GRANTED c1 LOCK 1 read 512000 516096 GRANTED c1 LOCK 1 read 516096 520192 GRANTED c1 LOCK 1 read 520192 524288 c2 $L LIST 1 0 0 { EXPECT c2 $L LIST CONFLICT 1 * read 0 4096 EXPECT c2 $L LIST CONFLICT 1 * read 4096 8192 EXPECT c2 $L LIST CONFLICT 1 * read 8192 12288 EXPECT c2 $L LIST CONFLICT 1 * read 12288 16384 EXPECT c2 $L LIST CONFLICT 1 * read 16384 20480 EXPECT c2 $L LIST CONFLICT 1 * read 20480 24576 EXPECT c2 $L LIST CONFLICT 1 * read 24576 28672 EXPECT c2 $L LIST CONFLICT 1 * read 28672 32768 EXPECT c2 $L LIST CONFLICT 1 * read 32768 36864 EXPECT c2 $L LIST CONFLICT 1 * read 36864 40960 EXPECT c2 $L LIST CONFLICT 1 * read 40960 45056 EXPECT c2 $L LIST CONFLICT 1 * read 45056 49152 EXPECT c2 $L LIST CONFLICT 1 * read 49152 53248 EXPECT c2 $L LIST CONFLICT 1 * read 53248 57344 EXPECT c2 $L LIST CONFLICT 1 * read 57344 61440 EXPECT c2 $L LIST CONFLICT 1 * read 61440 65536 EXPECT c2 $L LIST CONFLICT 1 * read 65536 69632 EXPECT c2 $L LIST CONFLICT 1 * read 69632 73728 EXPECT c2 $L LIST CONFLICT 1 * read 73728 77824 EXPECT c2 $L LIST CONFLICT 1 * read 77824 81920 EXPECT c2 $L LIST CONFLICT 1 * read 81920 86016 EXPECT c2 $L LIST CONFLICT 1 * read 86016 90112 EXPECT c2 $L LIST CONFLICT 1 * read 90112 94208 EXPECT c2 $L LIST CONFLICT 1 * read 94208 98304 EXPECT c2 $L LIST CONFLICT 1 * read 98304 102400 EXPECT c2 $L LIST CONFLICT 1 * read 102400 106496 EXPECT c2 $L LIST CONFLICT 1 * read 106496 110592 EXPECT c2 $L LIST CONFLICT 1 * read 110592 114688 EXPECT c2 $L LIST CONFLICT 1 * read 114688 118784 EXPECT c2 $L LIST CONFLICT 1 * read 118784 122880 EXPECT c2 $L LIST CONFLICT 1 * read 122880 126976 EXPECT c2 $L LIST CONFLICT 1 * read 126976 131072 EXPECT c2 $L LIST CONFLICT 1 * read 131072 135168 EXPECT c2 $L LIST CONFLICT 1 * read 135168 139264 EXPECT c2 $L LIST CONFLICT 1 * read 139264 143360 EXPECT c2 $L LIST CONFLICT 1 * read 143360 147456 EXPECT c2 $L LIST CONFLICT 1 * read 147456 151552 EXPECT c2 $L LIST CONFLICT 1 * read 151552 155648 EXPECT c2 $L LIST CONFLICT 1 * read 155648 159744 EXPECT c2 $L LIST CONFLICT 1 * read 159744 163840 EXPECT c2 $L LIST CONFLICT 1 * read 163840 167936 EXPECT c2 $L LIST CONFLICT 1 * read 167936 172032 EXPECT c2 $L LIST CONFLICT 1 * read 172032 176128 EXPECT c2 $L LIST CONFLICT 1 * read 176128 180224 EXPECT c2 $L LIST CONFLICT 1 * read 180224 184320 EXPECT c2 $L LIST CONFLICT 1 * read 184320 188416 EXPECT c2 $L LIST CONFLICT 1 * read 188416 192512 EXPECT c2 $L LIST CONFLICT 1 * read 192512 196608 EXPECT c2 $L LIST CONFLICT 1 * read 196608 200704 EXPECT c2 $L LIST CONFLICT 1 * read 200704 204800 EXPECT c2 $L LIST CONFLICT 1 * read 204800 208896 EXPECT c2 $L LIST CONFLICT 1 * read 208896 212992 EXPECT c2 $L LIST CONFLICT 1 * read 212992 217088 EXPECT c2 $L LIST CONFLICT 1 * read 217088 221184 EXPECT c2 $L LIST CONFLICT 1 * read 221184 225280 EXPECT c2 $L LIST CONFLICT 1 * read 225280 229376 EXPECT c2 $L LIST CONFLICT 1 * read 229376 233472 EXPECT c2 $L LIST CONFLICT 1 * read 233472 237568 EXPECT c2 $L LIST CONFLICT 1 * read 237568 241664 EXPECT c2 $L LIST CONFLICT 1 * read 241664 245760 EXPECT c2 $L LIST CONFLICT 1 * read 245760 249856 EXPECT c2 $L LIST CONFLICT 1 * read 249856 253952 EXPECT c2 $L LIST CONFLICT 1 * read 253952 258048 EXPECT c2 $L LIST CONFLICT 1 * read 258048 262144 EXPECT c2 $L LIST CONFLICT 1 * read 262144 266240 EXPECT c2 $L LIST CONFLICT 1 * read 266240 270336 EXPECT c2 $L LIST CONFLICT 1 * read 270336 274432 EXPECT c2 $L LIST CONFLICT 1 * read 274432 278528 EXPECT c2 $L LIST CONFLICT 1 * read 278528 282624 EXPECT c2 $L LIST CONFLICT 1 * read 282624 286720 EXPECT c2 $L LIST CONFLICT 1 * read 286720 290816 EXPECT c2 $L LIST CONFLICT 1 * read 290816 294912 EXPECT c2 $L LIST CONFLICT 1 * read 294912 299008 EXPECT c2 $L LIST CONFLICT 1 * read 299008 303104 EXPECT c2 $L LIST CONFLICT 1 * read 303104 307200 EXPECT c2 $L LIST CONFLICT 1 * read 307200 311296 EXPECT c2 $L LIST CONFLICT 1 * read 311296 315392 EXPECT c2 $L LIST CONFLICT 1 * read 315392 319488 EXPECT c2 $L LIST CONFLICT 1 * read 319488 323584 EXPECT c2 $L LIST CONFLICT 1 * read 323584 327680 EXPECT c2 $L LIST CONFLICT 1 * read 327680 331776 EXPECT c2 $L LIST CONFLICT 1 * read 331776 335872 EXPECT c2 $L LIST CONFLICT 1 * read 335872 339968 EXPECT c2 $L LIST CONFLICT 1 * read 339968 344064 EXPECT c2 $L LIST CONFLICT 1 * read 344064 348160 EXPECT c2 $L LIST CONFLICT 1 * read 348160 352256 EXPECT c2 $L LIST CONFLICT 1 * read 352256 356352 EXPECT c2 $L LIST CONFLICT 1 * read 356352 360448 EXPECT c2 $L LIST CONFLICT 1 * read 360448 364544 EXPECT c2 $L LIST CONFLICT 1 * read 364544 368640 EXPECT c2 $L LIST CONFLICT 1 * read 368640 372736 EXPECT c2 $L LIST CONFLICT 1 * read 372736 376832 EXPECT c2 $L LIST CONFLICT 1 * read 376832 380928 EXPECT c2 $L LIST CONFLICT 1 * read 380928 385024 EXPECT c2 $L LIST CONFLICT 1 * read 385024 389120 EXPECT c2 $L LIST CONFLICT 1 * read 389120 393216 EXPECT c2 $L LIST CONFLICT 1 * read 393216 397312 EXPECT c2 $L LIST CONFLICT 1 * read 397312 401408 EXPECT c2 $L LIST CONFLICT 1 * read 401408 405504 EXPECT c2 $L LIST CONFLICT 1 * read 405504 409600 EXPECT c2 $L LIST CONFLICT 1 * read 409600 413696 EXPECT c2 $L LIST CONFLICT 1 * read 413696 417792 EXPECT c2 $L LIST CONFLICT 1 * read 417792 421888 EXPECT c2 $L LIST CONFLICT 1 * read 421888 425984 EXPECT c2 $L LIST CONFLICT 1 * read 425984 430080 EXPECT c2 $L LIST CONFLICT 1 * read 430080 434176 EXPECT c2 $L LIST CONFLICT 1 * read 434176 438272 EXPECT c2 $L LIST CONFLICT 1 * read 438272 442368 EXPECT c2 $L LIST CONFLICT 1 * read 442368 446464 EXPECT c2 $L LIST CONFLICT 1 * read 446464 450560 EXPECT c2 $L LIST CONFLICT 1 * read 450560 454656 EXPECT c2 $L LIST CONFLICT 1 * read 454656 458752 EXPECT c2 $L LIST CONFLICT 1 * read 458752 462848 EXPECT c2 $L LIST CONFLICT 1 * read 462848 466944 EXPECT c2 $L LIST CONFLICT 1 * read 466944 471040 EXPECT c2 $L LIST CONFLICT 1 * read 471040 475136 EXPECT c2 $L LIST CONFLICT 1 * read 475136 479232 EXPECT c2 $L LIST CONFLICT 1 * read 479232 483328 EXPECT c2 $L LIST CONFLICT 1 * read 483328 487424 EXPECT c2 $L LIST CONFLICT 1 * read 487424 491520 EXPECT c2 $L LIST CONFLICT 1 * read 491520 495616 EXPECT c2 $L LIST CONFLICT 1 * read 495616 499712 EXPECT c2 $L LIST CONFLICT 1 * read 499712 503808 EXPECT c2 $L LIST CONFLICT 1 * read 503808 507904 EXPECT c2 $L LIST CONFLICT 1 * read 507904 512000 EXPECT c2 $L LIST CONFLICT 1 * read 512000 516096 EXPECT c2 $L LIST CONFLICT 1 * read 516096 520192 EXPECT c2 $L LIST CONFLICT 1 * read 520192 524288 } EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c2 LIST 1 0 0 GRANTED c1 LOCK 1 write 0 4096 GRANTED c1 LOCK 1 write 4096 8192 GRANTED c1 LOCK 1 write 8192 12288 GRANTED c1 LOCK 1 write 12288 16384 GRANTED c1 LOCK 1 write 16384 20480 GRANTED c1 LOCK 1 write 20480 24576 GRANTED c1 LOCK 1 write 24576 28672 GRANTED c1 LOCK 1 write 28672 32768 GRANTED c1 LOCK 1 write 32768 36864 GRANTED c1 LOCK 1 write 36864 40960 GRANTED c1 LOCK 1 write 40960 45056 GRANTED c1 LOCK 1 write 45056 49152 GRANTED c1 LOCK 1 write 49152 53248 GRANTED c1 LOCK 1 write 53248 57344 GRANTED c1 LOCK 1 write 57344 61440 GRANTED c1 LOCK 1 write 61440 65536 GRANTED c1 LOCK 1 write 65536 69632 GRANTED c1 LOCK 1 write 69632 73728 GRANTED c1 LOCK 1 write 73728 77824 GRANTED c1 LOCK 1 write 77824 81920 GRANTED c1 LOCK 1 write 81920 86016 GRANTED c1 LOCK 1 write 86016 90112 GRANTED c1 LOCK 1 write 90112 94208 GRANTED c1 LOCK 1 write 94208 98304 GRANTED c1 LOCK 1 write 98304 102400 GRANTED c1 LOCK 1 write 102400 106496 GRANTED c1 LOCK 1 write 106496 110592 GRANTED c1 LOCK 1 write 110592 114688 GRANTED c1 LOCK 1 write 114688 118784 GRANTED c1 LOCK 1 write 118784 122880 GRANTED c1 LOCK 1 write 122880 126976 GRANTED c1 LOCK 1 write 126976 131072 GRANTED c1 LOCK 1 write 131072 135168 GRANTED c1 LOCK 1 write 135168 139264 GRANTED c1 LOCK 1 write 139264 143360 GRANTED c1 LOCK 1 write 143360 147456 GRANTED c1 LOCK 1 write 147456 151552 GRANTED c1 LOCK 1 write 151552 155648 GRANTED c1 LOCK 1 write 155648 159744 GRANTED c1 LOCK 1 write 159744 163840 GRANTED c1 LOCK 1 write 163840 167936 GRANTED c1 LOCK 1 write 167936 172032 GRANTED c1 LOCK 1 write 172032 176128 GRANTED c1 LOCK 1 write 176128 180224 GRANTED c1 LOCK 1 write 180224 184320 GRANTED c1 LOCK 1 write 184320 188416 GRANTED c1 LOCK 1 write 188416 192512 GRANTED c1 LOCK 1 write 192512 196608 GRANTED c1 LOCK 1 write 196608 200704 GRANTED c1 LOCK 1 write 200704 204800 GRANTED c1 LOCK 1 write 204800 208896 GRANTED c1 LOCK 1 write 208896 212992 GRANTED c1 LOCK 1 write 212992 217088 GRANTED c1 LOCK 1 write 217088 221184 GRANTED c1 LOCK 1 write 221184 225280 GRANTED c1 LOCK 1 write 225280 229376 GRANTED c1 LOCK 1 write 229376 233472 GRANTED c1 LOCK 1 write 233472 237568 GRANTED c1 LOCK 1 write 237568 241664 GRANTED c1 LOCK 1 write 241664 245760 GRANTED c1 LOCK 1 write 245760 249856 GRANTED c1 LOCK 1 write 249856 253952 GRANTED c1 LOCK 1 write 253952 258048 GRANTED c1 LOCK 1 write 258048 262144 GRANTED c1 LOCK 1 write 262144 266240 GRANTED c1 LOCK 1 write 266240 270336 GRANTED c1 LOCK 1 write 270336 274432 GRANTED c1 LOCK 1 write 274432 278528 GRANTED c1 LOCK 1 write 278528 282624 GRANTED c1 LOCK 1 write 282624 286720 GRANTED c1 LOCK 1 write 286720 290816 GRANTED c1 LOCK 1 write 290816 294912 GRANTED c1 LOCK 1 write 294912 299008 GRANTED c1 LOCK 1 write 299008 303104 GRANTED c1 LOCK 1 write 303104 307200 GRANTED c1 LOCK 1 write 307200 311296 GRANTED c1 LOCK 1 write 311296 315392 GRANTED c1 LOCK 1 write 315392 319488 GRANTED c1 LOCK 1 write 319488 323584 GRANTED c1 LOCK 1 write 323584 327680 GRANTED c1 LOCK 1 write 327680 331776 GRANTED c1 LOCK 1 write 331776 335872 GRANTED c1 LOCK 1 write 335872 339968 GRANTED c1 LOCK 1 write 339968 344064 GRANTED c1 LOCK 1 write 344064 348160 GRANTED c1 LOCK 1 write 348160 352256 GRANTED c1 LOCK 1 write 352256 356352 GRANTED c1 LOCK 1 write 356352 360448 GRANTED c1 LOCK 1 write 360448 364544 GRANTED c1 LOCK 1 write 364544 368640 GRANTED c1 LOCK 1 write 368640 372736 GRANTED c1 LOCK 1 write 372736 376832 GRANTED c1 LOCK 1 write 376832 380928 GRANTED c1 LOCK 1 write 380928 385024 GRANTED c1 LOCK 1 write 385024 389120 GRANTED c1 LOCK 1 write 389120 393216 GRANTED c1 LOCK 1 write 393216 397312 GRANTED c1 LOCK 1 write 397312 401408 GRANTED c1 LOCK 1 write 401408 405504 GRANTED c1 LOCK 1 write 405504 409600 GRANTED c1 LOCK 1 write 409600 413696 GRANTED c1 LOCK 1 write 413696 417792 GRANTED c1 LOCK 1 write 417792 421888 GRANTED c1 LOCK 1 write 421888 425984 GRANTED c1 LOCK 1 write 425984 430080 GRANTED c1 LOCK 1 write 430080 434176 GRANTED c1 LOCK 1 write 434176 438272 GRANTED c1 LOCK 1 write 438272 442368 GRANTED c1 LOCK 1 write 442368 446464 GRANTED c1 LOCK 1 write 446464 450560 GRANTED c1 LOCK 1 write 450560 454656 GRANTED c1 LOCK 1 write 454656 458752 GRANTED c1 LOCK 1 write 458752 462848 GRANTED c1 LOCK 1 write 462848 466944 GRANTED c1 LOCK 1 write 466944 471040 GRANTED c1 LOCK 1 write 471040 475136 GRANTED c1 LOCK 1 write 475136 479232 GRANTED c1 LOCK 1 write 479232 483328 GRANTED c1 LOCK 1 write 483328 487424 GRANTED c1 LOCK 1 write 487424 491520 GRANTED c1 LOCK 1 write 491520 495616 GRANTED c1 LOCK 1 write 495616 499712 GRANTED c1 LOCK 1 write 499712 503808 GRANTED c1 LOCK 1 write 503808 507904 GRANTED c1 LOCK 1 write 507904 512000 GRANTED c1 LOCK 1 write 512000 516096 GRANTED c1 LOCK 1 write 516096 520192 GRANTED c1 LOCK 1 write 520192 524288 c2 $L LIST 1 0 0 { EXPECT c2 $L LIST CONFLICT 1 * write 0 4096 EXPECT c2 $L LIST CONFLICT 1 * write 4096 8192 EXPECT c2 $L LIST CONFLICT 1 * write 8192 12288 EXPECT c2 $L LIST CONFLICT 1 * write 12288 16384 EXPECT c2 $L LIST CONFLICT 1 * write 16384 20480 EXPECT c2 $L LIST CONFLICT 1 * write 20480 24576 EXPECT c2 $L LIST CONFLICT 1 * write 24576 28672 EXPECT c2 $L LIST CONFLICT 1 * write 28672 32768 EXPECT c2 $L LIST CONFLICT 1 * write 32768 36864 EXPECT c2 $L LIST CONFLICT 1 * write 36864 40960 EXPECT c2 $L LIST CONFLICT 1 * write 40960 45056 EXPECT c2 $L LIST CONFLICT 1 * write 45056 49152 EXPECT c2 $L LIST CONFLICT 1 * write 49152 53248 EXPECT c2 $L LIST CONFLICT 1 * write 53248 57344 EXPECT c2 $L LIST CONFLICT 1 * write 57344 61440 EXPECT c2 $L LIST CONFLICT 1 * write 61440 65536 EXPECT c2 $L LIST CONFLICT 1 * write 65536 69632 EXPECT c2 $L LIST CONFLICT 1 * write 69632 73728 EXPECT c2 $L LIST CONFLICT 1 * write 73728 77824 EXPECT c2 $L LIST CONFLICT 1 * write 77824 81920 EXPECT c2 $L LIST CONFLICT 1 * write 81920 86016 EXPECT c2 $L LIST CONFLICT 1 * write 86016 90112 EXPECT c2 $L LIST CONFLICT 1 * write 90112 94208 EXPECT c2 $L LIST CONFLICT 1 * write 94208 98304 EXPECT c2 $L LIST CONFLICT 1 * write 98304 102400 EXPECT c2 $L LIST CONFLICT 1 * write 102400 106496 EXPECT c2 $L LIST CONFLICT 1 * write 106496 110592 EXPECT c2 $L LIST CONFLICT 1 * write 110592 114688 EXPECT c2 $L LIST CONFLICT 1 * write 114688 118784 EXPECT c2 $L LIST CONFLICT 1 * write 118784 122880 EXPECT c2 $L LIST CONFLICT 1 * write 122880 126976 EXPECT c2 $L LIST CONFLICT 1 * write 126976 131072 EXPECT c2 $L LIST CONFLICT 1 * write 131072 135168 EXPECT c2 $L LIST CONFLICT 1 * write 135168 139264 EXPECT c2 $L LIST CONFLICT 1 * write 139264 143360 EXPECT c2 $L LIST CONFLICT 1 * write 143360 147456 EXPECT c2 $L LIST CONFLICT 1 * write 147456 151552 EXPECT c2 $L LIST CONFLICT 1 * write 151552 155648 EXPECT c2 $L LIST CONFLICT 1 * write 155648 159744 EXPECT c2 $L LIST CONFLICT 1 * write 159744 163840 EXPECT c2 $L LIST CONFLICT 1 * write 163840 167936 EXPECT c2 $L LIST CONFLICT 1 * write 167936 172032 EXPECT c2 $L LIST CONFLICT 1 * write 172032 176128 EXPECT c2 $L LIST CONFLICT 1 * write 176128 180224 EXPECT c2 $L LIST CONFLICT 1 * write 180224 184320 EXPECT c2 $L LIST CONFLICT 1 * write 184320 188416 EXPECT c2 $L LIST CONFLICT 1 * write 188416 192512 EXPECT c2 $L LIST CONFLICT 1 * write 192512 196608 EXPECT c2 $L LIST CONFLICT 1 * write 196608 200704 EXPECT c2 $L LIST CONFLICT 1 * write 200704 204800 EXPECT c2 $L LIST CONFLICT 1 * write 204800 208896 EXPECT c2 $L LIST CONFLICT 1 * write 208896 212992 EXPECT c2 $L LIST CONFLICT 1 * write 212992 217088 EXPECT c2 $L LIST CONFLICT 1 * write 217088 221184 EXPECT c2 $L LIST CONFLICT 1 * write 221184 225280 EXPECT c2 $L LIST CONFLICT 1 * write 225280 229376 EXPECT c2 $L LIST CONFLICT 1 * write 229376 233472 EXPECT c2 $L LIST CONFLICT 1 * write 233472 237568 EXPECT c2 $L LIST CONFLICT 1 * write 237568 241664 EXPECT c2 $L LIST CONFLICT 1 * write 241664 245760 EXPECT c2 $L LIST CONFLICT 1 * write 245760 249856 EXPECT c2 $L LIST CONFLICT 1 * write 249856 253952 EXPECT c2 $L LIST CONFLICT 1 * write 253952 258048 EXPECT c2 $L LIST CONFLICT 1 * write 258048 262144 EXPECT c2 $L LIST CONFLICT 1 * write 262144 266240 EXPECT c2 $L LIST CONFLICT 1 * write 266240 270336 EXPECT c2 $L LIST CONFLICT 1 * write 270336 274432 EXPECT c2 $L LIST CONFLICT 1 * write 274432 278528 EXPECT c2 $L LIST CONFLICT 1 * write 278528 282624 EXPECT c2 $L LIST CONFLICT 1 * write 282624 286720 EXPECT c2 $L LIST CONFLICT 1 * write 286720 290816 EXPECT c2 $L LIST CONFLICT 1 * write 290816 294912 EXPECT c2 $L LIST CONFLICT 1 * write 294912 299008 EXPECT c2 $L LIST CONFLICT 1 * write 299008 303104 EXPECT c2 $L LIST CONFLICT 1 * write 303104 307200 EXPECT c2 $L LIST CONFLICT 1 * write 307200 311296 EXPECT c2 $L LIST CONFLICT 1 * write 311296 315392 EXPECT c2 $L LIST CONFLICT 1 * write 315392 319488 EXPECT c2 $L LIST CONFLICT 1 * write 319488 323584 EXPECT c2 $L LIST CONFLICT 1 * write 323584 327680 EXPECT c2 $L LIST CONFLICT 1 * write 327680 331776 EXPECT c2 $L LIST CONFLICT 1 * write 331776 335872 EXPECT c2 $L LIST CONFLICT 1 * write 335872 339968 EXPECT c2 $L LIST CONFLICT 1 * write 339968 344064 EXPECT c2 $L LIST CONFLICT 1 * write 344064 348160 EXPECT c2 $L LIST CONFLICT 1 * write 348160 352256 EXPECT c2 $L LIST CONFLICT 1 * write 352256 356352 EXPECT c2 $L LIST CONFLICT 1 * write 356352 360448 EXPECT c2 $L LIST CONFLICT 1 * write 360448 364544 EXPECT c2 $L LIST CONFLICT 1 * write 364544 368640 EXPECT c2 $L LIST CONFLICT 1 * write 368640 372736 EXPECT c2 $L LIST CONFLICT 1 * write 372736 376832 EXPECT c2 $L LIST CONFLICT 1 * write 376832 380928 EXPECT c2 $L LIST CONFLICT 1 * write 380928 385024 EXPECT c2 $L LIST CONFLICT 1 * write 385024 389120 EXPECT c2 $L LIST CONFLICT 1 * write 389120 393216 EXPECT c2 $L LIST CONFLICT 1 * write 393216 397312 EXPECT c2 $L LIST CONFLICT 1 * write 397312 401408 EXPECT c2 $L LIST CONFLICT 1 * write 401408 405504 EXPECT c2 $L LIST CONFLICT 1 * write 405504 409600 EXPECT c2 $L LIST CONFLICT 1 * write 409600 413696 EXPECT c2 $L LIST CONFLICT 1 * write 413696 417792 EXPECT c2 $L LIST CONFLICT 1 * write 417792 421888 EXPECT c2 $L LIST CONFLICT 1 * write 421888 425984 EXPECT c2 $L LIST CONFLICT 1 * write 425984 430080 EXPECT c2 $L LIST CONFLICT 1 * write 430080 434176 EXPECT c2 $L LIST CONFLICT 1 * write 434176 438272 EXPECT c2 $L LIST CONFLICT 1 * write 438272 442368 EXPECT c2 $L LIST CONFLICT 1 * write 442368 446464 EXPECT c2 $L LIST CONFLICT 1 * write 446464 450560 EXPECT c2 $L LIST CONFLICT 1 * write 450560 454656 EXPECT c2 $L LIST CONFLICT 1 * write 454656 458752 EXPECT c2 $L LIST CONFLICT 1 * write 458752 462848 EXPECT c2 $L LIST CONFLICT 1 * write 462848 466944 EXPECT c2 $L LIST CONFLICT 1 * write 466944 471040 EXPECT c2 $L LIST CONFLICT 1 * write 471040 475136 EXPECT c2 $L LIST CONFLICT 1 * write 475136 479232 EXPECT c2 $L LIST CONFLICT 1 * write 479232 483328 EXPECT c2 $L LIST CONFLICT 1 * write 483328 487424 EXPECT c2 $L LIST CONFLICT 1 * write 487424 491520 EXPECT c2 $L LIST CONFLICT 1 * write 491520 495616 EXPECT c2 $L LIST CONFLICT 1 * write 495616 499712 EXPECT c2 $L LIST CONFLICT 1 * write 499712 503808 EXPECT c2 $L LIST CONFLICT 1 * write 503808 507904 EXPECT c2 $L LIST CONFLICT 1 * write 507904 512000 EXPECT c2 $L LIST CONFLICT 1 * write 512000 516096 EXPECT c2 $L LIST CONFLICT 1 * write 516096 520192 EXPECT c2 $L LIST CONFLICT 1 * write 520192 524288 } EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c2 LIST 1 0 0 QUIT �����������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/take_128_locks_small_range_none_gap_posix����������0000664�0000000�0000000�00000055703�14737566223�0032334�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Implementation of same file concurrent locks with varied ranges # # Take 128 locks on a file with small ranges and none gaps # small = 4 * size.KiB # Check to see that those locks exists # Release the locks # Check to see that those locks don’t exists CLIENTS c1 c2 OK c1 OPEN 1 rw create POSIX testFile OK c2 OPEN 1 rw POSIX testFile GRANTED c1 LOCK 1 read 0 4096 GRANTED c1 LOCK 1 read 4096 8192 GRANTED c1 LOCK 1 read 8192 12288 GRANTED c1 LOCK 1 read 12288 16384 GRANTED c1 LOCK 1 read 16384 20480 GRANTED c1 LOCK 1 read 20480 24576 GRANTED c1 LOCK 1 read 24576 28672 GRANTED c1 LOCK 1 read 28672 32768 GRANTED c1 LOCK 1 read 32768 36864 GRANTED c1 LOCK 1 read 36864 40960 GRANTED c1 LOCK 1 read 40960 45056 GRANTED c1 LOCK 1 read 45056 49152 GRANTED c1 LOCK 1 read 49152 53248 GRANTED c1 LOCK 1 read 53248 57344 GRANTED c1 LOCK 1 read 57344 61440 GRANTED c1 LOCK 1 read 61440 65536 GRANTED c1 LOCK 1 read 65536 69632 GRANTED c1 LOCK 1 read 69632 73728 GRANTED c1 LOCK 1 read 73728 77824 GRANTED c1 LOCK 1 read 77824 81920 GRANTED c1 LOCK 1 read 81920 86016 GRANTED c1 LOCK 1 read 86016 90112 GRANTED c1 LOCK 1 read 90112 94208 GRANTED c1 LOCK 1 read 94208 98304 GRANTED c1 LOCK 1 read 98304 102400 GRANTED c1 LOCK 1 read 102400 106496 GRANTED c1 LOCK 1 read 106496 110592 GRANTED c1 LOCK 1 read 110592 114688 GRANTED c1 LOCK 1 read 114688 118784 GRANTED c1 LOCK 1 read 118784 122880 GRANTED c1 LOCK 1 read 122880 126976 GRANTED c1 LOCK 1 read 126976 131072 GRANTED c1 LOCK 1 read 131072 135168 GRANTED c1 LOCK 1 read 135168 139264 GRANTED c1 LOCK 1 read 139264 143360 GRANTED c1 LOCK 1 read 143360 147456 GRANTED c1 LOCK 1 read 147456 151552 GRANTED c1 LOCK 1 read 151552 155648 GRANTED c1 LOCK 1 read 155648 159744 GRANTED c1 LOCK 1 read 159744 163840 GRANTED c1 LOCK 1 read 163840 167936 GRANTED c1 LOCK 1 read 167936 172032 GRANTED c1 LOCK 1 read 172032 176128 GRANTED c1 LOCK 1 read 176128 180224 GRANTED c1 LOCK 1 read 180224 184320 GRANTED c1 LOCK 1 read 184320 188416 GRANTED c1 LOCK 1 read 188416 192512 GRANTED c1 LOCK 1 read 192512 196608 GRANTED c1 LOCK 1 read 196608 200704 GRANTED c1 LOCK 1 read 200704 204800 GRANTED c1 LOCK 1 read 204800 208896 GRANTED c1 LOCK 1 read 208896 212992 GRANTED c1 LOCK 1 read 212992 217088 GRANTED c1 LOCK 1 read 217088 221184 GRANTED c1 LOCK 1 read 221184 225280 GRANTED c1 LOCK 1 read 225280 229376 GRANTED c1 LOCK 1 read 229376 233472 GRANTED c1 LOCK 1 read 233472 237568 GRANTED c1 LOCK 1 read 237568 241664 GRANTED c1 LOCK 1 read 241664 245760 GRANTED c1 LOCK 1 read 245760 249856 GRANTED c1 LOCK 1 read 249856 253952 GRANTED c1 LOCK 1 read 253952 258048 GRANTED c1 LOCK 1 read 258048 262144 GRANTED c1 LOCK 1 read 262144 266240 GRANTED c1 LOCK 1 read 266240 270336 GRANTED c1 LOCK 1 read 270336 274432 GRANTED c1 LOCK 1 read 274432 278528 GRANTED c1 LOCK 1 read 278528 282624 GRANTED c1 LOCK 1 read 282624 286720 GRANTED c1 LOCK 1 read 286720 290816 GRANTED c1 LOCK 1 read 290816 294912 GRANTED c1 LOCK 1 read 294912 299008 GRANTED c1 LOCK 1 read 299008 303104 GRANTED c1 LOCK 1 read 303104 307200 GRANTED c1 LOCK 1 read 307200 311296 GRANTED c1 LOCK 1 read 311296 315392 GRANTED c1 LOCK 1 read 315392 319488 GRANTED c1 LOCK 1 read 319488 323584 GRANTED c1 LOCK 1 read 323584 327680 GRANTED c1 LOCK 1 read 327680 331776 GRANTED c1 LOCK 1 read 331776 335872 GRANTED c1 LOCK 1 read 335872 339968 GRANTED c1 LOCK 1 read 339968 344064 GRANTED c1 LOCK 1 read 344064 348160 GRANTED c1 LOCK 1 read 348160 352256 GRANTED c1 LOCK 1 read 352256 356352 GRANTED c1 LOCK 1 read 356352 360448 GRANTED c1 LOCK 1 read 360448 364544 GRANTED c1 LOCK 1 read 364544 368640 GRANTED c1 LOCK 1 read 368640 372736 GRANTED c1 LOCK 1 read 372736 376832 GRANTED c1 LOCK 1 read 376832 380928 GRANTED c1 LOCK 1 read 380928 385024 GRANTED c1 LOCK 1 read 385024 389120 GRANTED c1 LOCK 1 read 389120 393216 GRANTED c1 LOCK 1 read 393216 397312 GRANTED c1 LOCK 1 read 397312 401408 GRANTED c1 LOCK 1 read 401408 405504 GRANTED c1 LOCK 1 read 405504 409600 GRANTED c1 LOCK 1 read 409600 413696 GRANTED c1 LOCK 1 read 413696 417792 GRANTED c1 LOCK 1 read 417792 421888 GRANTED c1 LOCK 1 read 421888 425984 GRANTED c1 LOCK 1 read 425984 430080 GRANTED c1 LOCK 1 read 430080 434176 GRANTED c1 LOCK 1 read 434176 438272 GRANTED c1 LOCK 1 read 438272 442368 GRANTED c1 LOCK 1 read 442368 446464 GRANTED c1 LOCK 1 read 446464 450560 GRANTED c1 LOCK 1 read 450560 454656 GRANTED c1 LOCK 1 read 454656 458752 GRANTED c1 LOCK 1 read 458752 462848 GRANTED c1 LOCK 1 read 462848 466944 GRANTED c1 LOCK 1 read 466944 471040 GRANTED c1 LOCK 1 read 471040 475136 GRANTED c1 LOCK 1 read 475136 479232 GRANTED c1 LOCK 1 read 479232 483328 GRANTED c1 LOCK 1 read 483328 487424 GRANTED c1 LOCK 1 read 487424 491520 GRANTED c1 LOCK 1 read 491520 495616 GRANTED c1 LOCK 1 read 495616 499712 GRANTED c1 LOCK 1 read 499712 503808 GRANTED c1 LOCK 1 read 503808 507904 GRANTED c1 LOCK 1 read 507904 512000 GRANTED c1 LOCK 1 read 512000 516096 GRANTED c1 LOCK 1 read 516096 520192 GRANTED c1 LOCK 1 read 520192 524288 c2 $L LIST 1 0 0 { EXPECT c2 $L LIST CONFLICT 1 * read 0 4096 EXPECT c2 $L LIST CONFLICT 1 * read 4096 8192 EXPECT c2 $L LIST CONFLICT 1 * read 8192 12288 EXPECT c2 $L LIST CONFLICT 1 * read 12288 16384 EXPECT c2 $L LIST CONFLICT 1 * read 16384 20480 EXPECT c2 $L LIST CONFLICT 1 * read 20480 24576 EXPECT c2 $L LIST CONFLICT 1 * read 24576 28672 EXPECT c2 $L LIST CONFLICT 1 * read 28672 32768 EXPECT c2 $L LIST CONFLICT 1 * read 32768 36864 EXPECT c2 $L LIST CONFLICT 1 * read 36864 40960 EXPECT c2 $L LIST CONFLICT 1 * read 40960 45056 EXPECT c2 $L LIST CONFLICT 1 * read 45056 49152 EXPECT c2 $L LIST CONFLICT 1 * read 49152 53248 EXPECT c2 $L LIST CONFLICT 1 * read 53248 57344 EXPECT c2 $L LIST CONFLICT 1 * read 57344 61440 EXPECT c2 $L LIST CONFLICT 1 * read 61440 65536 EXPECT c2 $L LIST CONFLICT 1 * read 65536 69632 EXPECT c2 $L LIST CONFLICT 1 * read 69632 73728 EXPECT c2 $L LIST CONFLICT 1 * read 73728 77824 EXPECT c2 $L LIST CONFLICT 1 * read 77824 81920 EXPECT c2 $L LIST CONFLICT 1 * read 81920 86016 EXPECT c2 $L LIST CONFLICT 1 * read 86016 90112 EXPECT c2 $L LIST CONFLICT 1 * read 90112 94208 EXPECT c2 $L LIST CONFLICT 1 * read 94208 98304 EXPECT c2 $L LIST CONFLICT 1 * read 98304 102400 EXPECT c2 $L LIST CONFLICT 1 * read 102400 106496 EXPECT c2 $L LIST CONFLICT 1 * read 106496 110592 EXPECT c2 $L LIST CONFLICT 1 * read 110592 114688 EXPECT c2 $L LIST CONFLICT 1 * read 114688 118784 EXPECT c2 $L LIST CONFLICT 1 * read 118784 122880 EXPECT c2 $L LIST CONFLICT 1 * read 122880 126976 EXPECT c2 $L LIST CONFLICT 1 * read 126976 131072 EXPECT c2 $L LIST CONFLICT 1 * read 131072 135168 EXPECT c2 $L LIST CONFLICT 1 * read 135168 139264 EXPECT c2 $L LIST CONFLICT 1 * read 139264 143360 EXPECT c2 $L LIST CONFLICT 1 * read 143360 147456 EXPECT c2 $L LIST CONFLICT 1 * read 147456 151552 EXPECT c2 $L LIST CONFLICT 1 * read 151552 155648 EXPECT c2 $L LIST CONFLICT 1 * read 155648 159744 EXPECT c2 $L LIST CONFLICT 1 * read 159744 163840 EXPECT c2 $L LIST CONFLICT 1 * read 163840 167936 EXPECT c2 $L LIST CONFLICT 1 * read 167936 172032 EXPECT c2 $L LIST CONFLICT 1 * read 172032 176128 EXPECT c2 $L LIST CONFLICT 1 * read 176128 180224 EXPECT c2 $L LIST CONFLICT 1 * read 180224 184320 EXPECT c2 $L LIST CONFLICT 1 * read 184320 188416 EXPECT c2 $L LIST CONFLICT 1 * read 188416 192512 EXPECT c2 $L LIST CONFLICT 1 * read 192512 196608 EXPECT c2 $L LIST CONFLICT 1 * read 196608 200704 EXPECT c2 $L LIST CONFLICT 1 * read 200704 204800 EXPECT c2 $L LIST CONFLICT 1 * read 204800 208896 EXPECT c2 $L LIST CONFLICT 1 * read 208896 212992 EXPECT c2 $L LIST CONFLICT 1 * read 212992 217088 EXPECT c2 $L LIST CONFLICT 1 * read 217088 221184 EXPECT c2 $L LIST CONFLICT 1 * read 221184 225280 EXPECT c2 $L LIST CONFLICT 1 * read 225280 229376 EXPECT c2 $L LIST CONFLICT 1 * read 229376 233472 EXPECT c2 $L LIST CONFLICT 1 * read 233472 237568 EXPECT c2 $L LIST CONFLICT 1 * read 237568 241664 EXPECT c2 $L LIST CONFLICT 1 * read 241664 245760 EXPECT c2 $L LIST CONFLICT 1 * read 245760 249856 EXPECT c2 $L LIST CONFLICT 1 * read 249856 253952 EXPECT c2 $L LIST CONFLICT 1 * read 253952 258048 EXPECT c2 $L LIST CONFLICT 1 * read 258048 262144 EXPECT c2 $L LIST CONFLICT 1 * read 262144 266240 EXPECT c2 $L LIST CONFLICT 1 * read 266240 270336 EXPECT c2 $L LIST CONFLICT 1 * read 270336 274432 EXPECT c2 $L LIST CONFLICT 1 * read 274432 278528 EXPECT c2 $L LIST CONFLICT 1 * read 278528 282624 EXPECT c2 $L LIST CONFLICT 1 * read 282624 286720 EXPECT c2 $L LIST CONFLICT 1 * read 286720 290816 EXPECT c2 $L LIST CONFLICT 1 * read 290816 294912 EXPECT c2 $L LIST CONFLICT 1 * read 294912 299008 EXPECT c2 $L LIST CONFLICT 1 * read 299008 303104 EXPECT c2 $L LIST CONFLICT 1 * read 303104 307200 EXPECT c2 $L LIST CONFLICT 1 * read 307200 311296 EXPECT c2 $L LIST CONFLICT 1 * read 311296 315392 EXPECT c2 $L LIST CONFLICT 1 * read 315392 319488 EXPECT c2 $L LIST CONFLICT 1 * read 319488 323584 EXPECT c2 $L LIST CONFLICT 1 * read 323584 327680 EXPECT c2 $L LIST CONFLICT 1 * read 327680 331776 EXPECT c2 $L LIST CONFLICT 1 * read 331776 335872 EXPECT c2 $L LIST CONFLICT 1 * read 335872 339968 EXPECT c2 $L LIST CONFLICT 1 * read 339968 344064 EXPECT c2 $L LIST CONFLICT 1 * read 344064 348160 EXPECT c2 $L LIST CONFLICT 1 * read 348160 352256 EXPECT c2 $L LIST CONFLICT 1 * read 352256 356352 EXPECT c2 $L LIST CONFLICT 1 * read 356352 360448 EXPECT c2 $L LIST CONFLICT 1 * read 360448 364544 EXPECT c2 $L LIST CONFLICT 1 * read 364544 368640 EXPECT c2 $L LIST CONFLICT 1 * read 368640 372736 EXPECT c2 $L LIST CONFLICT 1 * read 372736 376832 EXPECT c2 $L LIST CONFLICT 1 * read 376832 380928 EXPECT c2 $L LIST CONFLICT 1 * read 380928 385024 EXPECT c2 $L LIST CONFLICT 1 * read 385024 389120 EXPECT c2 $L LIST CONFLICT 1 * read 389120 393216 EXPECT c2 $L LIST CONFLICT 1 * read 393216 397312 EXPECT c2 $L LIST CONFLICT 1 * read 397312 401408 EXPECT c2 $L LIST CONFLICT 1 * read 401408 405504 EXPECT c2 $L LIST CONFLICT 1 * read 405504 409600 EXPECT c2 $L LIST CONFLICT 1 * read 409600 413696 EXPECT c2 $L LIST CONFLICT 1 * read 413696 417792 EXPECT c2 $L LIST CONFLICT 1 * read 417792 421888 EXPECT c2 $L LIST CONFLICT 1 * read 421888 425984 EXPECT c2 $L LIST CONFLICT 1 * read 425984 430080 EXPECT c2 $L LIST CONFLICT 1 * read 430080 434176 EXPECT c2 $L LIST CONFLICT 1 * read 434176 438272 EXPECT c2 $L LIST CONFLICT 1 * read 438272 442368 EXPECT c2 $L LIST CONFLICT 1 * read 442368 446464 EXPECT c2 $L LIST CONFLICT 1 * read 446464 450560 EXPECT c2 $L LIST CONFLICT 1 * read 450560 454656 EXPECT c2 $L LIST CONFLICT 1 * read 454656 458752 EXPECT c2 $L LIST CONFLICT 1 * read 458752 462848 EXPECT c2 $L LIST CONFLICT 1 * read 462848 466944 EXPECT c2 $L LIST CONFLICT 1 * read 466944 471040 EXPECT c2 $L LIST CONFLICT 1 * read 471040 475136 EXPECT c2 $L LIST CONFLICT 1 * read 475136 479232 EXPECT c2 $L LIST CONFLICT 1 * read 479232 483328 EXPECT c2 $L LIST CONFLICT 1 * read 483328 487424 EXPECT c2 $L LIST CONFLICT 1 * read 487424 491520 EXPECT c2 $L LIST CONFLICT 1 * read 491520 495616 EXPECT c2 $L LIST CONFLICT 1 * read 495616 499712 EXPECT c2 $L LIST CONFLICT 1 * read 499712 503808 EXPECT c2 $L LIST CONFLICT 1 * read 503808 507904 EXPECT c2 $L LIST CONFLICT 1 * read 507904 512000 EXPECT c2 $L LIST CONFLICT 1 * read 512000 516096 EXPECT c2 $L LIST CONFLICT 1 * read 516096 520192 EXPECT c2 $L LIST CONFLICT 1 * read 520192 524288 } EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c2 LIST 1 0 0 GRANTED c1 LOCK 1 write 0 4096 GRANTED c1 LOCK 1 write 4096 8192 GRANTED c1 LOCK 1 write 8192 12288 GRANTED c1 LOCK 1 write 12288 16384 GRANTED c1 LOCK 1 write 16384 20480 GRANTED c1 LOCK 1 write 20480 24576 GRANTED c1 LOCK 1 write 24576 28672 GRANTED c1 LOCK 1 write 28672 32768 GRANTED c1 LOCK 1 write 32768 36864 GRANTED c1 LOCK 1 write 36864 40960 GRANTED c1 LOCK 1 write 40960 45056 GRANTED c1 LOCK 1 write 45056 49152 GRANTED c1 LOCK 1 write 49152 53248 GRANTED c1 LOCK 1 write 53248 57344 GRANTED c1 LOCK 1 write 57344 61440 GRANTED c1 LOCK 1 write 61440 65536 GRANTED c1 LOCK 1 write 65536 69632 GRANTED c1 LOCK 1 write 69632 73728 GRANTED c1 LOCK 1 write 73728 77824 GRANTED c1 LOCK 1 write 77824 81920 GRANTED c1 LOCK 1 write 81920 86016 GRANTED c1 LOCK 1 write 86016 90112 GRANTED c1 LOCK 1 write 90112 94208 GRANTED c1 LOCK 1 write 94208 98304 GRANTED c1 LOCK 1 write 98304 102400 GRANTED c1 LOCK 1 write 102400 106496 GRANTED c1 LOCK 1 write 106496 110592 GRANTED c1 LOCK 1 write 110592 114688 GRANTED c1 LOCK 1 write 114688 118784 GRANTED c1 LOCK 1 write 118784 122880 GRANTED c1 LOCK 1 write 122880 126976 GRANTED c1 LOCK 1 write 126976 131072 GRANTED c1 LOCK 1 write 131072 135168 GRANTED c1 LOCK 1 write 135168 139264 GRANTED c1 LOCK 1 write 139264 143360 GRANTED c1 LOCK 1 write 143360 147456 GRANTED c1 LOCK 1 write 147456 151552 GRANTED c1 LOCK 1 write 151552 155648 GRANTED c1 LOCK 1 write 155648 159744 GRANTED c1 LOCK 1 write 159744 163840 GRANTED c1 LOCK 1 write 163840 167936 GRANTED c1 LOCK 1 write 167936 172032 GRANTED c1 LOCK 1 write 172032 176128 GRANTED c1 LOCK 1 write 176128 180224 GRANTED c1 LOCK 1 write 180224 184320 GRANTED c1 LOCK 1 write 184320 188416 GRANTED c1 LOCK 1 write 188416 192512 GRANTED c1 LOCK 1 write 192512 196608 GRANTED c1 LOCK 1 write 196608 200704 GRANTED c1 LOCK 1 write 200704 204800 GRANTED c1 LOCK 1 write 204800 208896 GRANTED c1 LOCK 1 write 208896 212992 GRANTED c1 LOCK 1 write 212992 217088 GRANTED c1 LOCK 1 write 217088 221184 GRANTED c1 LOCK 1 write 221184 225280 GRANTED c1 LOCK 1 write 225280 229376 GRANTED c1 LOCK 1 write 229376 233472 GRANTED c1 LOCK 1 write 233472 237568 GRANTED c1 LOCK 1 write 237568 241664 GRANTED c1 LOCK 1 write 241664 245760 GRANTED c1 LOCK 1 write 245760 249856 GRANTED c1 LOCK 1 write 249856 253952 GRANTED c1 LOCK 1 write 253952 258048 GRANTED c1 LOCK 1 write 258048 262144 GRANTED c1 LOCK 1 write 262144 266240 GRANTED c1 LOCK 1 write 266240 270336 GRANTED c1 LOCK 1 write 270336 274432 GRANTED c1 LOCK 1 write 274432 278528 GRANTED c1 LOCK 1 write 278528 282624 GRANTED c1 LOCK 1 write 282624 286720 GRANTED c1 LOCK 1 write 286720 290816 GRANTED c1 LOCK 1 write 290816 294912 GRANTED c1 LOCK 1 write 294912 299008 GRANTED c1 LOCK 1 write 299008 303104 GRANTED c1 LOCK 1 write 303104 307200 GRANTED c1 LOCK 1 write 307200 311296 GRANTED c1 LOCK 1 write 311296 315392 GRANTED c1 LOCK 1 write 315392 319488 GRANTED c1 LOCK 1 write 319488 323584 GRANTED c1 LOCK 1 write 323584 327680 GRANTED c1 LOCK 1 write 327680 331776 GRANTED c1 LOCK 1 write 331776 335872 GRANTED c1 LOCK 1 write 335872 339968 GRANTED c1 LOCK 1 write 339968 344064 GRANTED c1 LOCK 1 write 344064 348160 GRANTED c1 LOCK 1 write 348160 352256 GRANTED c1 LOCK 1 write 352256 356352 GRANTED c1 LOCK 1 write 356352 360448 GRANTED c1 LOCK 1 write 360448 364544 GRANTED c1 LOCK 1 write 364544 368640 GRANTED c1 LOCK 1 write 368640 372736 GRANTED c1 LOCK 1 write 372736 376832 GRANTED c1 LOCK 1 write 376832 380928 GRANTED c1 LOCK 1 write 380928 385024 GRANTED c1 LOCK 1 write 385024 389120 GRANTED c1 LOCK 1 write 389120 393216 GRANTED c1 LOCK 1 write 393216 397312 GRANTED c1 LOCK 1 write 397312 401408 GRANTED c1 LOCK 1 write 401408 405504 GRANTED c1 LOCK 1 write 405504 409600 GRANTED c1 LOCK 1 write 409600 413696 GRANTED c1 LOCK 1 write 413696 417792 GRANTED c1 LOCK 1 write 417792 421888 GRANTED c1 LOCK 1 write 421888 425984 GRANTED c1 LOCK 1 write 425984 430080 GRANTED c1 LOCK 1 write 430080 434176 GRANTED c1 LOCK 1 write 434176 438272 GRANTED c1 LOCK 1 write 438272 442368 GRANTED c1 LOCK 1 write 442368 446464 GRANTED c1 LOCK 1 write 446464 450560 GRANTED c1 LOCK 1 write 450560 454656 GRANTED c1 LOCK 1 write 454656 458752 GRANTED c1 LOCK 1 write 458752 462848 GRANTED c1 LOCK 1 write 462848 466944 GRANTED c1 LOCK 1 write 466944 471040 GRANTED c1 LOCK 1 write 471040 475136 GRANTED c1 LOCK 1 write 475136 479232 GRANTED c1 LOCK 1 write 479232 483328 GRANTED c1 LOCK 1 write 483328 487424 GRANTED c1 LOCK 1 write 487424 491520 GRANTED c1 LOCK 1 write 491520 495616 GRANTED c1 LOCK 1 write 495616 499712 GRANTED c1 LOCK 1 write 499712 503808 GRANTED c1 LOCK 1 write 503808 507904 GRANTED c1 LOCK 1 write 507904 512000 GRANTED c1 LOCK 1 write 512000 516096 GRANTED c1 LOCK 1 write 516096 520192 GRANTED c1 LOCK 1 write 520192 524288 c2 $L LIST 1 0 0 { EXPECT c2 $L LIST CONFLICT 1 * write 0 4096 EXPECT c2 $L LIST CONFLICT 1 * write 4096 8192 EXPECT c2 $L LIST CONFLICT 1 * write 8192 12288 EXPECT c2 $L LIST CONFLICT 1 * write 12288 16384 EXPECT c2 $L LIST CONFLICT 1 * write 16384 20480 EXPECT c2 $L LIST CONFLICT 1 * write 20480 24576 EXPECT c2 $L LIST CONFLICT 1 * write 24576 28672 EXPECT c2 $L LIST CONFLICT 1 * write 28672 32768 EXPECT c2 $L LIST CONFLICT 1 * write 32768 36864 EXPECT c2 $L LIST CONFLICT 1 * write 36864 40960 EXPECT c2 $L LIST CONFLICT 1 * write 40960 45056 EXPECT c2 $L LIST CONFLICT 1 * write 45056 49152 EXPECT c2 $L LIST CONFLICT 1 * write 49152 53248 EXPECT c2 $L LIST CONFLICT 1 * write 53248 57344 EXPECT c2 $L LIST CONFLICT 1 * write 57344 61440 EXPECT c2 $L LIST CONFLICT 1 * write 61440 65536 EXPECT c2 $L LIST CONFLICT 1 * write 65536 69632 EXPECT c2 $L LIST CONFLICT 1 * write 69632 73728 EXPECT c2 $L LIST CONFLICT 1 * write 73728 77824 EXPECT c2 $L LIST CONFLICT 1 * write 77824 81920 EXPECT c2 $L LIST CONFLICT 1 * write 81920 86016 EXPECT c2 $L LIST CONFLICT 1 * write 86016 90112 EXPECT c2 $L LIST CONFLICT 1 * write 90112 94208 EXPECT c2 $L LIST CONFLICT 1 * write 94208 98304 EXPECT c2 $L LIST CONFLICT 1 * write 98304 102400 EXPECT c2 $L LIST CONFLICT 1 * write 102400 106496 EXPECT c2 $L LIST CONFLICT 1 * write 106496 110592 EXPECT c2 $L LIST CONFLICT 1 * write 110592 114688 EXPECT c2 $L LIST CONFLICT 1 * write 114688 118784 EXPECT c2 $L LIST CONFLICT 1 * write 118784 122880 EXPECT c2 $L LIST CONFLICT 1 * write 122880 126976 EXPECT c2 $L LIST CONFLICT 1 * write 126976 131072 EXPECT c2 $L LIST CONFLICT 1 * write 131072 135168 EXPECT c2 $L LIST CONFLICT 1 * write 135168 139264 EXPECT c2 $L LIST CONFLICT 1 * write 139264 143360 EXPECT c2 $L LIST CONFLICT 1 * write 143360 147456 EXPECT c2 $L LIST CONFLICT 1 * write 147456 151552 EXPECT c2 $L LIST CONFLICT 1 * write 151552 155648 EXPECT c2 $L LIST CONFLICT 1 * write 155648 159744 EXPECT c2 $L LIST CONFLICT 1 * write 159744 163840 EXPECT c2 $L LIST CONFLICT 1 * write 163840 167936 EXPECT c2 $L LIST CONFLICT 1 * write 167936 172032 EXPECT c2 $L LIST CONFLICT 1 * write 172032 176128 EXPECT c2 $L LIST CONFLICT 1 * write 176128 180224 EXPECT c2 $L LIST CONFLICT 1 * write 180224 184320 EXPECT c2 $L LIST CONFLICT 1 * write 184320 188416 EXPECT c2 $L LIST CONFLICT 1 * write 188416 192512 EXPECT c2 $L LIST CONFLICT 1 * write 192512 196608 EXPECT c2 $L LIST CONFLICT 1 * write 196608 200704 EXPECT c2 $L LIST CONFLICT 1 * write 200704 204800 EXPECT c2 $L LIST CONFLICT 1 * write 204800 208896 EXPECT c2 $L LIST CONFLICT 1 * write 208896 212992 EXPECT c2 $L LIST CONFLICT 1 * write 212992 217088 EXPECT c2 $L LIST CONFLICT 1 * write 217088 221184 EXPECT c2 $L LIST CONFLICT 1 * write 221184 225280 EXPECT c2 $L LIST CONFLICT 1 * write 225280 229376 EXPECT c2 $L LIST CONFLICT 1 * write 229376 233472 EXPECT c2 $L LIST CONFLICT 1 * write 233472 237568 EXPECT c2 $L LIST CONFLICT 1 * write 237568 241664 EXPECT c2 $L LIST CONFLICT 1 * write 241664 245760 EXPECT c2 $L LIST CONFLICT 1 * write 245760 249856 EXPECT c2 $L LIST CONFLICT 1 * write 249856 253952 EXPECT c2 $L LIST CONFLICT 1 * write 253952 258048 EXPECT c2 $L LIST CONFLICT 1 * write 258048 262144 EXPECT c2 $L LIST CONFLICT 1 * write 262144 266240 EXPECT c2 $L LIST CONFLICT 1 * write 266240 270336 EXPECT c2 $L LIST CONFLICT 1 * write 270336 274432 EXPECT c2 $L LIST CONFLICT 1 * write 274432 278528 EXPECT c2 $L LIST CONFLICT 1 * write 278528 282624 EXPECT c2 $L LIST CONFLICT 1 * write 282624 286720 EXPECT c2 $L LIST CONFLICT 1 * write 286720 290816 EXPECT c2 $L LIST CONFLICT 1 * write 290816 294912 EXPECT c2 $L LIST CONFLICT 1 * write 294912 299008 EXPECT c2 $L LIST CONFLICT 1 * write 299008 303104 EXPECT c2 $L LIST CONFLICT 1 * write 303104 307200 EXPECT c2 $L LIST CONFLICT 1 * write 307200 311296 EXPECT c2 $L LIST CONFLICT 1 * write 311296 315392 EXPECT c2 $L LIST CONFLICT 1 * write 315392 319488 EXPECT c2 $L LIST CONFLICT 1 * write 319488 323584 EXPECT c2 $L LIST CONFLICT 1 * write 323584 327680 EXPECT c2 $L LIST CONFLICT 1 * write 327680 331776 EXPECT c2 $L LIST CONFLICT 1 * write 331776 335872 EXPECT c2 $L LIST CONFLICT 1 * write 335872 339968 EXPECT c2 $L LIST CONFLICT 1 * write 339968 344064 EXPECT c2 $L LIST CONFLICT 1 * write 344064 348160 EXPECT c2 $L LIST CONFLICT 1 * write 348160 352256 EXPECT c2 $L LIST CONFLICT 1 * write 352256 356352 EXPECT c2 $L LIST CONFLICT 1 * write 356352 360448 EXPECT c2 $L LIST CONFLICT 1 * write 360448 364544 EXPECT c2 $L LIST CONFLICT 1 * write 364544 368640 EXPECT c2 $L LIST CONFLICT 1 * write 368640 372736 EXPECT c2 $L LIST CONFLICT 1 * write 372736 376832 EXPECT c2 $L LIST CONFLICT 1 * write 376832 380928 EXPECT c2 $L LIST CONFLICT 1 * write 380928 385024 EXPECT c2 $L LIST CONFLICT 1 * write 385024 389120 EXPECT c2 $L LIST CONFLICT 1 * write 389120 393216 EXPECT c2 $L LIST CONFLICT 1 * write 393216 397312 EXPECT c2 $L LIST CONFLICT 1 * write 397312 401408 EXPECT c2 $L LIST CONFLICT 1 * write 401408 405504 EXPECT c2 $L LIST CONFLICT 1 * write 405504 409600 EXPECT c2 $L LIST CONFLICT 1 * write 409600 413696 EXPECT c2 $L LIST CONFLICT 1 * write 413696 417792 EXPECT c2 $L LIST CONFLICT 1 * write 417792 421888 EXPECT c2 $L LIST CONFLICT 1 * write 421888 425984 EXPECT c2 $L LIST CONFLICT 1 * write 425984 430080 EXPECT c2 $L LIST CONFLICT 1 * write 430080 434176 EXPECT c2 $L LIST CONFLICT 1 * write 434176 438272 EXPECT c2 $L LIST CONFLICT 1 * write 438272 442368 EXPECT c2 $L LIST CONFLICT 1 * write 442368 446464 EXPECT c2 $L LIST CONFLICT 1 * write 446464 450560 EXPECT c2 $L LIST CONFLICT 1 * write 450560 454656 EXPECT c2 $L LIST CONFLICT 1 * write 454656 458752 EXPECT c2 $L LIST CONFLICT 1 * write 458752 462848 EXPECT c2 $L LIST CONFLICT 1 * write 462848 466944 EXPECT c2 $L LIST CONFLICT 1 * write 466944 471040 EXPECT c2 $L LIST CONFLICT 1 * write 471040 475136 EXPECT c2 $L LIST CONFLICT 1 * write 475136 479232 EXPECT c2 $L LIST CONFLICT 1 * write 479232 483328 EXPECT c2 $L LIST CONFLICT 1 * write 483328 487424 EXPECT c2 $L LIST CONFLICT 1 * write 487424 491520 EXPECT c2 $L LIST CONFLICT 1 * write 491520 495616 EXPECT c2 $L LIST CONFLICT 1 * write 495616 499712 EXPECT c2 $L LIST CONFLICT 1 * write 499712 503808 EXPECT c2 $L LIST CONFLICT 1 * write 503808 507904 EXPECT c2 $L LIST CONFLICT 1 * write 507904 512000 EXPECT c2 $L LIST CONFLICT 1 * write 512000 516096 EXPECT c2 $L LIST CONFLICT 1 * write 516096 520192 EXPECT c2 $L LIST CONFLICT 1 * write 520192 524288 } EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c2 LIST 1 0 0 QUIT �������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/take_release_locks_ofd�����������������������������0000664�0000000�0000000�00000004143�14737566223�0026626�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Take and release a lock from one loader (read / write, blocking / non-blocking, full / byte-range) # # Take a lock from loader 1 # Test from a loader 2 should reflect that this lock exist # Release the lock that was taken from loader 1 # Test from loader 2 should reflect that this lock doesn’t exist CLIENTS c1 c2 OK c1 OPEN 1 rw create OFD testFile OK c2 OPEN 1 rw OFD testFile # read - blocking - full c1 $G LOCKW 1 read 0 0 EXPECT c1 $G LOCKW GRANTED 1 read 0 0 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * read 0 0 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c2 LIST 1 0 0 # read - blocking - byte-range c1 $G LOCKW 1 read 100 10 EXPECT c1 $G LOCKW GRANTED 1 read 100 10 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * read 100 10 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 100 10 AVAILABLE c2 LIST 1 0 0 # read - non-blocking - full GRANTED c1 LOCK 1 read 0 0 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * read 0 0 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c2 LIST 1 0 0 # read - non-blocking - byte-range GRANTED c1 LOCK 1 read 100 10 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * read 100 10 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 100 10 AVAILABLE c2 LIST 1 0 0 # write - blocking - full c1 $G LOCKW 1 write 0 0 EXPECT c1 $G LOCKW GRANTED 1 write 0 0 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * write 0 0 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c2 LIST 1 0 0 # write - blocking - byte-range c1 $G LOCKW 1 write 100 10 EXPECT c1 $G LOCKW GRANTED 1 write 100 10 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * write 100 10 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 100 10 AVAILABLE c2 LIST 1 0 0 # write - non-blocking - full GRANTED c1 LOCK 1 write 0 0 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * write 0 0 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c2 LIST 1 0 0 # write - non-blocking - byte-range GRANTED c1 LOCK 1 write 100 10 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * write 100 10 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 100 10 AVAILABLE c2 LIST 1 0 0 QUIT �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/take_release_locks_posix���������������������������0000664�0000000�0000000�00000004147�14737566223�0027224�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Take and release a lock from one loader (read / write, blocking / non-blocking, full / byte-range) # # Take a lock from loader 1 # Test from a loader 2 should reflect that this lock exist # Release the lock that was taken from loader 1 # Test from loader 2 should reflect that this lock doesn’t exist CLIENTS c1 c2 OK c1 OPEN 1 rw create POSIX testFile OK c2 OPEN 1 rw POSIX testFile # read - blocking - full c1 $G LOCKW 1 read 0 0 EXPECT c1 $G LOCKW GRANTED 1 read 0 0 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * read 0 0 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c2 LIST 1 0 0 # read - blocking - byte-range c1 $G LOCKW 1 read 100 10 EXPECT c1 $G LOCKW GRANTED 1 read 100 10 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * read 100 10 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 100 10 AVAILABLE c2 LIST 1 0 0 # read - non-blocking - full GRANTED c1 LOCK 1 read 0 0 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * read 0 0 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c2 LIST 1 0 0 # read - non-blocking - byte-range GRANTED c1 LOCK 1 read 100 10 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * read 100 10 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 100 10 AVAILABLE c2 LIST 1 0 0 # write - blocking - full c1 $G LOCKW 1 write 0 0 EXPECT c1 $G LOCKW GRANTED 1 write 0 0 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * write 0 0 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c2 LIST 1 0 0 # write - blocking - byte-range c1 $G LOCKW 1 write 100 10 EXPECT c1 $G LOCKW GRANTED 1 write 100 10 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * write 100 10 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 100 10 AVAILABLE c2 LIST 1 0 0 # write - non-blocking - full GRANTED c1 LOCK 1 write 0 0 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * write 0 0 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c2 LIST 1 0 0 # write - non-blocking - byte-range GRANTED c1 LOCK 1 write 100 10 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * write 100 10 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 100 10 AVAILABLE c2 LIST 1 0 0 QUIT �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/try_test�������������������������������������������0000664�0000000�0000000�00000002367�14737566223�0024042�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later # Copyright IBM Corporation, 2012 # Contributor: Frank Filz <ffilz@us.ibm.com> # # # This software is a server that implements the NFS protocol. # # # This program 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 3 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 # 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 # This script requests a lock from one process and tries to test it from another process CLIENTS c1 c2 OK c1 OPEN 1 rw create try_test.file OK c2 OPEN 1 rw try_test.file GRANTED c1 LOCK 1 read 102030 123 c2 $ TEST 1 write 101020 1234 EXPECT c2 $ TEST CONFLICT 1 * read 102030 123 GRANTED c1 UNLOCK 1 102030 123 OK c1 CLOSE 1 OK c2 CLOSE 1 QUIT �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/upgrade_blocking_lock_ofd��������������������������0000664�0000000�0000000�00000002406�14737566223�0027316�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Upgrade + notification # # Take a read lock from loader 1 # Take a read lock from loader 2 # Try to take a non-blocking write lock from loader 1, expect to fail (Lock upgrade) # Try to take a blocking write lock from loader 1 (Lock upgrade attempt) # Release the lock from loader 2 # Expect the lock to be granted to loader 1 # Release the lock from loader 1 # Test from loader 3 for the list of locks and expect not to see any lock CLIENTS c1 c2 c3 OK c1 OPEN 1 rw create OFD testFile OK c2 OPEN 1 rw OFD testFile OK c3 OPEN 1 rw OFD testFile # full range GRANTED c1 LOCK 1 read 0 0 GRANTED c2 LOCK 1 read 0 0 DENIED c1 LOCK 1 write 0 0 OK c1 ALARM 10 c1 $G LOCKW 1 write 0 0 c2 $R UNLOCK 1 0 0 { EXPECT c2 $R UNLOCK GRANTED 1 unlock 0 0 EXPECT c1 $G LOCKW GRANTED 1 write 0 0 } c1 * ALARM 0 EXPECT c1 * ALARM CANCELED * EXPECT c1 * ALARM OK * GRANTED c1 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 # byte range GRANTED c1 LOCK 1 read 10 10 GRANTED c2 LOCK 1 read 10 10 DENIED c1 LOCK 1 write 10 10 OK c1 ALARM 10 c1 $G LOCKW 1 write 10 10 c2 $R UNLOCK 1 0 0 { EXPECT c2 $R UNLOCK GRANTED 1 unlock 0 0 EXPECT c1 $G LOCKW GRANTED 1 write 10 10 } c1 * ALARM 0 EXPECT c1 * ALARM CANCELED * EXPECT c1 * ALARM OK * GRANTED c1 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 QUIT ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/upgrade_blocking_lock_posix������������������������0000664�0000000�0000000�00000002414�14737566223�0027707�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Upgrade + notification # # Take a read lock from loader 1 # Take a read lock from loader 2 # Try to take a non-blocking write lock from loader 1, expect to fail (Lock upgrade) # Try to take a blocking write lock from loader 1 (Lock upgrade attempt) # Release the lock from loader 2 # Expect the lock to be granted to loader 1 # Release the lock from loader 1 # Test from loader 3 for the list of locks and expect not to see any lock CLIENTS c1 c2 c3 OK c1 OPEN 1 rw create POSIX testFile OK c2 OPEN 1 rw POSIX testFile OK c3 OPEN 1 rw POSIX testFile # full range GRANTED c1 LOCK 1 read 0 0 GRANTED c2 LOCK 1 read 0 0 DENIED c1 LOCK 1 write 0 0 OK c1 ALARM 10 c1 $G LOCKW 1 write 0 0 c2 $R UNLOCK 1 0 0 { EXPECT c2 $R UNLOCK GRANTED 1 unlock 0 0 EXPECT c1 $G LOCKW GRANTED 1 write 0 0 } c1 * ALARM 0 EXPECT c1 * ALARM CANCELED * EXPECT c1 * ALARM OK * GRANTED c1 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 # byte range GRANTED c1 LOCK 1 read 10 10 GRANTED c2 LOCK 1 read 10 10 DENIED c1 LOCK 1 write 10 10 OK c1 ALARM 10 c1 $G LOCKW 1 write 10 10 c2 $R UNLOCK 1 0 0 { EXPECT c2 $R UNLOCK GRANTED 1 unlock 0 0 EXPECT c1 $G LOCKW GRANTED 1 write 10 10 } c1 * ALARM 0 EXPECT c1 * ALARM CANCELED * EXPECT c1 * ALARM OK * GRANTED c1 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 QUIT ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/upgrade_read_lock_ofd������������������������������0000664�0000000�0000000�00000001570�14737566223�0026442�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Upgrade a read lock # # Take a read lock from loader 1 # Take a read lock from loader 2, expect to succeed # Release the lock from loader 2 # Upgrade the lock to a write lock from loader 1, expect to succeed # Try to take a read lock from loader 2, expect to fail # Release the lock from loader 1 # Test from loader 3 for the list of locks and expect not to see any lock CLIENTS c1 c2 c3 OK c1 OPEN 1 rw create OFD testFile OK c2 OPEN 1 rw OFD testFile OK c3 OPEN 1 rw OFD testFile # full range GRANTED c1 LOCK 1 read 0 0 GRANTED c2 LOCK 1 read 0 0 GRANTED c2 UNLOCK 1 0 0 GRANTED c1 LOCK 1 write 0 0 DENIED c2 LOCK 1 read 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 # byte range GRANTED c1 LOCK 1 read 10 10 GRANTED c2 LOCK 1 read 10 10 GRANTED c2 UNLOCK 1 0 0 GRANTED c1 LOCK 1 write 10 10 DENIED c2 LOCK 1 read 10 10 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 QUIT ����������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/upgrade_read_lock_posix����������������������������0000664�0000000�0000000�00000001576�14737566223�0027042�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Upgrade a read lock # # Take a read lock from loader 1 # Take a read lock from loader 2, expect to succeed # Release the lock from loader 2 # Upgrade the lock to a write lock from loader 1, expect to succeed # Try to take a read lock from loader 2, expect to fail # Release the lock from loader 1 # Test from loader 3 for the list of locks and expect not to see any lock CLIENTS c1 c2 c3 OK c1 OPEN 1 rw create POSIX testFile OK c2 OPEN 1 rw POSIX testFile OK c3 OPEN 1 rw POSIX testFile # full range GRANTED c1 LOCK 1 read 0 0 GRANTED c2 LOCK 1 read 0 0 GRANTED c2 UNLOCK 1 0 0 GRANTED c1 LOCK 1 write 0 0 DENIED c2 LOCK 1 read 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 # byte range GRANTED c1 LOCK 1 read 10 10 GRANTED c2 LOCK 1 read 10 10 GRANTED c2 UNLOCK 1 0 0 GRANTED c1 LOCK 1 write 10 10 DENIED c2 LOCK 1 read 10 10 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 QUIT ����������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/write_lock_two_clients_ofd�������������������������0000664�0000000�0000000�00000001731�14737566223�0027563�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Take locks from two loaders - write lock # # Take a write lock from loader 1 # Try to take an write lock from a loader 2 and expect to fail # Try to take a read lock from a loader 2 and expect to fail # Test from loader 2 for the list of locks expecting to see one lock # Release the lock that was taken from loader 1 # Test from loader 3 for the list of locks and expect not to see any lock CLIENTS c1 c2 c3 OK c1 OPEN 1 rw create OFD testFile OK c2 OPEN 1 rw OFD testFile OK c3 OPEN 1 rw OFD testFile # full range GRANTED c1 LOCK 1 write 0 0 DENIED c2 LOCK 1 write 0 0 DENIED c2 LOCK 1 read 0 0 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * write 0 0 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 # byte range GRANTED c1 LOCK 1 write 10 10 DENIED c2 LOCK 1 write 10 10 DENIED c2 LOCK 1 read 10 10 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * write 10 10 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 QUIT ���������������������������������������nfs-ganesha-6.5/src/tools/multilock/sample_tests/write_lock_two_clients_posix�����������������������0000664�0000000�0000000�00000001737�14737566223�0030163�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Take locks from two loaders - write lock # # Take a write lock from loader 1 # Try to take an write lock from a loader 2 and expect to fail # Try to take a read lock from a loader 2 and expect to fail # Test from loader 2 for the list of locks expecting to see one lock # Release the lock that was taken from loader 1 # Test from loader 3 for the list of locks and expect not to see any lock CLIENTS c1 c2 c3 OK c1 OPEN 1 rw create POSIX testFile OK c2 OPEN 1 rw POSIX testFile OK c3 OPEN 1 rw POSIX testFile # full range GRANTED c1 LOCK 1 write 0 0 DENIED c2 LOCK 1 write 0 0 DENIED c2 LOCK 1 read 0 0 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * write 0 0 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 # byte range GRANTED c1 LOCK 1 write 10 10 DENIED c2 LOCK 1 write 10 10 DENIED c2 LOCK 1 read 10 10 c2 $L LIST 1 0 0 EXPECT c2 $L LIST CONFLICT 1 * write 10 10 EXPECT c2 $L LIST DENIED 1 0 0 GRANTED c1 UNLOCK 1 0 0 AVAILABLE c3 LIST 1 0 0 QUIT ���������������������������������nfs-ganesha-6.5/src/tools/test_findlog.c������������������������������������������������������������0000664�0000000�0000000�00000005561�14737566223�0020360�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later // This is not a valid C file, it's just a bunch of Log function examples to test findlog.sh /* LogTest("/* comment"); */ /* * Copyright IBM Corporation, 2011 * Contributor: Frank Filz <ffilz@us.ibm.com> * * * This software is a server that implements the NFS protocol. * * * This program 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 3 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 * 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 */ /* LogTest("/* multiline comment"); */ LogTest("tab"); LogTest("space"); // LogTest("// comment"); // LogTest("2nd // comment"); LogTest("space before semi"); LogTest("space after semi"); LogTest("function with ; in quotes"); LogTest("parm 1", "parm 2"); LogTest("parm 1", "parm 2 with space after semi"); LogTest("parm 1 with ;", "parm 2 with space after semi"); LogTest("parm 1", "parm 2 with ;"); LogTest("parm 1", "parm 2"); LogTest("paren on new line"); LogVal = "dont find me" LogTest("But do find me"); #define MACRO LogWarn(COMPONENT_CONFIG, "MACRO") /* expected too much - need to figure out how to fix the script */ #define MACRO \ LogWarn(COMPONENT_CONFIG, "MACRO") \ { \ } /* expected too much - need to figure out how to fix the script */ LogComponents[dont find me]; LogAlways("but find me on line 53"); LogTest("and me", string, buff, compare); LogTest("and finally me"); #define MACRO1 \ LogTest("M1 A", parms); \ LogTest("M1 B", parms); \ } #define MACRO2 \ LogTest("M2 A", parms); \ LogTest("M2 B", parms); \ LogTest("M2 C", parms); \ } else if (!STRCMP(key_name, "LogFile not me 1")) LogFile = "not me 2"; else if (!STRCMP(key_name, "not me 3")) LogCrit(COMPONENT_CONFIG, "find me 1 on 76", key_name); else if (!STRCMP(key_name, "LogFile not me 5")) LogFile = "not me 6"; LogCrit(COMPONENT_CONFIG, "find me 2 on 82", key_name); (Logtest("in parens")); LogTest("foo"); if (LogTest("simple if")) foo; foo("oops shouldn't pick this up"); if (LogTest("if with braces on same line")) { foo; } foo("oops shouldn't pick this up"); if (LogTest("if with braces")) { foo; } foo("oops shouldn't pick this up"); if (LogTest("if with braces 2")) { foo(); } foo("oops shouldn't pick this up"); LogTest("fini"); �����������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tracing/������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14737566223�0016013�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tracing/CMakeLists.txt����������������������������������������������������������0000664�0000000�0000000�00000003403�14737566223�0020553�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SPDX-License-Identifier: LGPL-3.0-or-later #------------------------------------------------------------------------------- # # Copyright Panasas, 2012 # Contributor: Jim Lieb <jlieb@panasas.com> # # This program 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 3 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 # 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 # #------------------------------------------------------------------------------- include_directories( ${LTTNG_INCLUDE_DIR} ) set(ganesha_trace_LIB_SRCS lttng_probes.c ) add_library(ganesha_trace MODULE ${ganesha_trace_LIB_SRCS}) add_sanitizers(ganesha_trace) target_link_libraries(ganesha_trace ${LTTNG_LIBRARIES} ) install(TARGETS ganesha_trace COMPONENT tracing DESTINATION ${LIB_INSTALL_DIR} ) add_library(ganesha_trace_symbols INTERFACE) target_sources(ganesha_trace_symbols INTERFACE ${CMAKE_CURRENT_LIST_DIR}/lttng_defines.c) target_link_libraries(ganesha_trace_symbols INTERFACE ${LTTNG_LIBRARIES}) add_custom_target(gsh_trace_header_generate DEPENDS ntirpc_generate_lttng_trace_headers gsh_generate_lttng_trace_headers ) add_dependencies(ganesha_trace_symbols gsh_trace_header_generate) add_dependencies(ganesha_trace gsh_trace_header_generate)�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tracing/README.md���������������������������������������������������������������0000664�0000000�0000000�00000012237�14737566223�0017277�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Building and using LTTng an enabled server ========================================== LTTng is a big topic. Consult the online documentation for the details of running the tools. The development is being done using Fedora using their packages, version 2.3.0. Install the following RPMs on Fedora or, if on RHEL, use the EPEL repo. There are other lttng related packages but these are the relevant ones for this level. lttng-tools lttng-ust-devel lttng-ust babeltrace Build with `-DUSE_LTTNG=ON` to enable it. All of the tracepoints have an `#ifdef USE_LTTNG` around them so the default of 'OFF' creates no build or runtime dependencies on lttng. The build creates and installs an additional shared object. The `libganesha_trace.so` object which contains all the defined tracepoint code and the code in the application that uses it are constructed such that the tracepoint code does not require the shared object to be loaded. This means that the application can be built with LTTng code but it can be run on a system that has no LTTng support. Tracepoints are enabled once the module is loaded. See below for details. I use the following script to run: ``` #!/bin/sh DIR=/usr/local/lib64/ganesha lttng create lttng enable-event -u -a lttng start /usr/local/bin/ganesha.nfsd -G ${*} ``` The `-G` flag tells ganesha to load `libganesha_trace.so` and `libntirpc_tracepoints.so`, which turns on tracing before before the server starts. If you don't use `-G`, ganesha runs as if tracing is not there and the only overhead is the predicted missed branch to the tracer. There are never enough tracepoints. Like log messages, they get added from time to time as needed. There are two paths for adding tracepoints. - Sets of tracepoints are categorized in *components* (or providers in the LTTNG phrasing). These should conform to the same general categories as logging components. All the tracepoints in a component should be functionally related in some way. - Finer grained tracing adds new tracepoints to an existing category. LTTNG traces automation ----------------------- Ganesha uses LTTNG trace generator, which is an infrastructure to automatically generate all the boilerplate code for a tracepoint and adds a format string. With this, the user can just include the header file and add the tracepoint. For more information, see the trace generator README in: ``` src/libntirpc/src/lttng/generator/README.md ``` Adding a new tracepoint ---------------------- In order to add a tracepoint in a file, the file must include `gsh_lttng/gsh_lttng.h`, which provides the utility functions to add an auto-generated tracepoint. In addition, you should include the auto-generated header for the specific provider (or component you want to use). This file is auto-generated, so it will not exist usually, and it should be within an `#ifdef`, as follows: ``` #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/<provider>.h" #endif ``` Then, a tracepoint can be defined anywhere in the code in this way: ``` GSH_AUTO_TRACEPOINT(<provider>, <event_name>, <debug_level>, <format_string>, args...); ``` for example: ``` #include "gsh_lttng/gsh_lttng.h" #if defined(USE_LTTNG) && !defined(LTTNG_PARSING) #include "gsh_lttng/generated_traces/nfs.h" #endif int arg = 1; GSH_AUTO_TRACEPOINT(nfs, test, TRACE_DEBUG, "Test tracepoint with arg: {}", arg); ``` Note that the event name must be unique. If you want to use the same event name in more than one place, or in a macro that can be called from several places, use `GSH_UNIQUE_AUTO_TRACEPOINT`. This will add a unique suffix to the event name automatically. Note that all the relevant headers and tacepoint calls are wrapped with `#ifdef USE_LTTNG`, so adding traces in this way doesn't have any impact when building without LTTNG. Notes on Using Tracepoints ========================== All this trace point organization is for the "enable-event" command above. The `-a` turns them all on. See the LTTng docs for the details on how to filter or turn on specific components. Tracepoint logs are placed in your `$HOME/lttng-traces` directory. Note that if you are running as 'root' which is necessary for running nfs-ganesha, this directory is actually `/root/lttng-traces`, not your regular `$HOME`. The lttng commands will report the subdirectory where the traces are placed. You can also specify a directory name if you like. If the lttng command reports `auto-20140804-102010` as the trace directory, you can find those files in `$HOME/lttng-traces/auto-20140804-102010`. The `babeltrace` tool is used to process and display traces. There are others but this is the simplest and most general tool. To do a simple dump of the trace from above, use: ``` $ babeltrace $HOME/lttng-traces/auto-20140804-102010 ``` This will dump a trace in text form. See the man page for all the options. There are a number of other tools that can also munch traces. Traces are in a common format that many tools can read and process/display them. In addition, instead of using babeltrace, you can use `src/libntirpc/src/lttng/generator/trace_formatter.py` which will format the traces and show them in a pretty way with the format string. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tracing/lttng_defines.c���������������������������������������������������������0000664�0000000�0000000�00000002555�14737566223�0021013�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * This program 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 3 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 * 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 * */ /* This file creates LTTNG tracepoints weak symbols. These are empty weak * functions that are overloaded by libganesha_trace.so. This allows us * to compile with LTTNG, but only enable it when we load libganesha_trace.so. * * Build targets that call tracepoints need to link with this file, by linking * with ganesha_trace_symbols. This will allow them to compile. */ #ifdef USE_LTTNG #define TRACEPOINT_DEFINE #define TRACEPOINT_PROBE_DYNAMIC_LINKAGE #ifndef LTTNG_PARSING #include "gsh_lttng/generated_traces/generated_lttng.h" #endif /* LTTNG_PARSING */ #endif /* USE_LTTNG */ ���������������������������������������������������������������������������������������������������������������������������������������������������nfs-ganesha-6.5/src/tracing/lttng_probes.c����������������������������������������������������������0000664�0000000�0000000�00000002743�14737566223�0020667�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������// SPDX-License-Identifier: LGPL-3.0-or-later /* * This program 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 3 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 * 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 * */ /* This file creates LTTNG tracepoints probes. These are the actual trace * implementations that will be called when LTTNG is enabled and its shared * object is loaded. * * This file compiles into libganesha_trace.so, which can be dynamically loaded * when we want to enable LTTNG. It must include every trace header file * once (and only once). * When libganesha_trace.so is loaded, the functions generated here overload the * weak functions generated by lttng_defines, and allows for LTTNG traces to be * sent. */ #ifdef USE_LTTNG #define TRACEPOINT_CREATE_PROBES #ifndef LTTNG_PARSING #include "gsh_lttng/generated_traces/generated_lttng.h" #endif /* LTTNG_PARSING */ #endif /* USE_LTTNG */ �����������������������������nfs-ganesha-6.5/src/valgrind.sh���������������������������������������������������������������������0000775�0000000�0000000�00000000143�14737566223�0016527�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh valgrind --leak-check=full --max-stackframe=3280592 --log-file=/tmp/valgrind.log $* ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������